Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for user JS library code to be able to depend on C functions #15982

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from enum import Enum, unique, auto
from subprocess import PIPE
from urllib.parse import quote
from tools.deps_info import append_wasm_deps_info


import emscripten
Expand Down Expand Up @@ -3749,6 +3750,11 @@ def process_libraries(state, linker_inputs):
# Sort the input list from (order, lib_name) pairs to a flat array in the right order.
settings.JS_LIBRARIES.sort(key=lambda lib: lib[0])
settings.JS_LIBRARIES = [lib[1] for lib in settings.JS_LIBRARIES]

for f in settings.JS_LIBRARIES:
wasm_deps = shared.run_js_tool(shared.path_from_root('src/read_wasm_deps.js'), [shared.path_from_root('src', f)], stdout=PIPE)
append_wasm_deps_info(json.loads(wasm_deps))

state.link_flags = new_flags


Expand Down
76 changes: 76 additions & 0 deletions src/read_wasm_deps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
var fs = require('fs');

function readFile(filename) {
return fs.readFileSync(filename).toString();
}

function include(filename) {
eval.call(null, readFile(`${__dirname}/${filename}`));
}

include('utility.js');
include('settings.js');
include('settings_internal.js');
include('parseTools.js');

// Read all input JS library files into LibraryManager.
var LibraryManager = { library: {} };
for(let f of process.argv.slice(2)) {
eval(processMacros(preprocess(readFile(f), f)));
}

// Grab all JS -> JS deps and JS -> C deps from the input files
const DEPS_SUFFIX = '__deps';
const WASM_DEPS_SUFFIX = '__wasm_deps';

let jsToJsDeps = {};
let jsToWasmDeps = {};

for(let s in LibraryManager.library) {
if (s.endsWith(DEPS_SUFFIX)) jsToJsDeps[s.substr(0, s.length - DEPS_SUFFIX.length)] = LibraryManager.library[s];
else if (s.endsWith(WASM_DEPS_SUFFIX)) jsToWasmDeps[s.substr(0, s.length - WASM_DEPS_SUFFIX.length)] = LibraryManager.library[s];
}

// Key jsToJsDeps backwards: given a JS function as key, lists all functions that depend on this function.
let jsToJsBackDeps = {};

for(let depender in jsToJsDeps) {
for(let dependee of jsToJsDeps[depender]) {
if (!jsToJsBackDeps[dependee]) jsToJsBackDeps[dependee] = [];
if (!jsToJsBackDeps[dependee].includes(depender)) jsToJsBackDeps[dependee].push(depender);
}
}

// Appends those elements from array src to array dst that did not yet exist in dst.
// Operates in-place, returns true if any modifications to array dst were done.
function appendToArrayIfNotExists(dst, src) {
let modified = false;
for(let element of src) {
if (!dst.includes(element)) {
dst.push(element);
modified = true;
}
}
return modified;
}

// Transitively propagate all jsToWasm deps backwards, i.e. if jsFunc1 depends on jsFunc2,
// and jsFunc2 depends on wasmFunc1, also record jsFunc1 to depend on wasmFunc1.
// Perform the propagation to a new dictionary to not disturb iteration over jsToWasmDeps.
let transitiveJsToWasmDeps = {};
for(let dep in jsToWasmDeps) {
const wasmDeps = jsToWasmDeps[dep];
let stack = [dep];
while(stack.length > 0) {
let f = stack.pop();
if (!transitiveJsToWasmDeps[f]) transitiveJsToWasmDeps[f] = [];
if (appendToArrayIfNotExists(transitiveJsToWasmDeps[f], wasmDeps)) {
// Keep going if this append produced some modifications (this check makes sure we don't infinite loop on cycles)
if (jsToJsBackDeps[f]) stack = stack.concat(jsToJsBackDeps[f]);
}
}
}
jsToWasmDeps = transitiveJsToWasmDeps;

// Print final output
console.log(JSON.stringify(transitiveJsToWasmDeps));
2 changes: 1 addition & 1 deletion src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1947,7 +1947,7 @@ var SPLIT_MODULE = 0;
// llvm-nm on all input) and use the map in deps_info.py to determine
// the set of additional dependencies.
// 'all' : Include the full set of possible reverse dependencies.
// 'none': No reverse dependences will be added by emscriopten. Any reverse
// 'none': No reverse dependences will be added by emscripten. Any reverse
// dependencies will be assumed to be explicitly added to
// EXPORTED_FUNCTIONS and deps_info.py will be completely ignored.
// While 'auto' will produce a minimal set (so is good for code size), 'all'
Expand Down
26 changes: 26 additions & 0 deletions tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -11401,3 +11401,29 @@ def test_gmtime_noleak(self):
# Confirm that gmtime_r does not leak when called in isolation.
self.emcc_args.append('-fsanitize=leak')
self.do_other_test('test_gmtime_noleak.c')

# Tests the use of foo__wasm_deps: ['wasmFunc1', 'wasmFunc2'] from JS library files
def test_wasm_deps(self):
create_file('lib.js', r'''
mergeInto(LibraryManager.library, {
foo__wasm_deps: ['wasmFunction'],
foo: function() {
return _wasmFunction();
},

bar__deps: ['foo'],
bar: function() {
return _foo();
}
});
''')

create_file('main.c', r'''
#include <stdio.h>
int wasmFunction() { return 492; }
int bar(void);
int main() { printf("%d\n", bar()); }
''')

self.run_process([EMCC, 'main.c', '--js-library', 'lib.js'])
self.assertContained('492', self.run_js('a.out.js'))
5 changes: 5 additions & 0 deletions tools/deps_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@
}


def append_wasm_deps_info(wasm_deps_info):
for key, value in wasm_deps_info.items():
_deps_info[key] = value


def get_deps_info():
if not settings.EXCEPTION_HANDLING and settings.LINK_AS_CXX:
_deps_info['__cxa_begin_catch'] = ['__cxa_is_pointer_type']
Expand Down