diff --git a/emcc.py b/emcc.py index 202732113a62..3a4813b927a0 100755 --- a/emcc.py +++ b/emcc.py @@ -831,6 +831,10 @@ def process_dynamic_libs(dylibs, lib_dirs): for dylib in dylibs: exports = webassembly.get_exports(dylib) exports = set(e.name for e in exports) + # EM_JS function are exports with a special prefix. We need to strip + # this prefix to get the actaul symbol name. For the main module, this + # is handled by extract_metadata.py. + exports = [shared.maybe_strip_prefix(e, '__em_js__') for e in exports] settings.SIDE_MODULE_EXPORTS.extend(sorted(exports)) imports = webassembly.get_imports(dylib) diff --git a/emscripten.py b/emscripten.py index 43d9d51c68ec..6e22c5ce0964 100644 --- a/emscripten.py +++ b/emscripten.py @@ -344,8 +344,6 @@ def emscript(in_wasm, out_wasm, outfile_js, memfile, js_syms): diagnostics.warning('em-js-i64', 'using 64-bit arguments in EM_JS function without WASM_BIGINT is not yet fully supported: `%s` (%s, %s)', em_js_func, c_sig, signature.params) if settings.SIDE_MODULE: - if metadata.emJsFuncs: - exit_with_error('EM_JS is not supported in side modules') logger.debug('emscript: skipping remaining js glue generation') return diff --git a/src/library_dylink.js b/src/library_dylink.js index 043328302ad0..d5eb5b7adc84 100644 --- a/src/library_dylink.js +++ b/src/library_dylink.js @@ -178,7 +178,9 @@ var LibraryDylink = { '__wasm_call_ctors', '__start_em_asm', '__stop_em_asm', - ].includes(symName) + '__start_em_js', + '__stop_em_js', + ].includes(symName) || symName.startsWith('__em_js__') #if SPLIT_MODULE // Exports synthesized by wasm-split should be prefixed with '%' || symName[0] == '%' @@ -779,6 +781,39 @@ var LibraryDylink = { start = HEAPU8.indexOf(0, start) + 1; } } + + function addEmJs(name, cSig, body) { + // The signature here is a C signature (e.g. "(int foo, char* bar)"). + // See `create_em_js` in emcc.py` for the build-time version of this + // code. + var jsArgs = []; + cSig = cSig.slice(1, -1) + if (cSig != 'void') { + cSig = cSig.split(','); + for (var i in cSig) { + var jsArg = cSig[i].split(' ').pop(); + jsArgs.push(jsArg.replace('*', '')); + } + } + var func = `(${jsArgs}) => ${body};`; +#if DYLINK_DEBUG + dbg(`adding new EM_JS function: ${jsArgs} = ${func}`); +#endif + {{{ makeEval('moduleExports[name] = eval(func)') }}}; + } + + for (var name in moduleExports) { + if (name.startsWith('__em_js__')) { + var start = moduleExports[name] + {{{ from64('start') }}} + var jsString = UTF8ToString(start); + // EM_JS strings are stored in the data section in the form + // SIG<::>BODY. + var parts = jsString.split('<::>'); + addEmJs(name.replace('__em_js__', ''), parts[0], parts[1]); + delete moduleExports[name]; + } + } #endif // initialize the module diff --git a/test/other/metadce/test_metadce_hello_dylink.jssize b/test/other/metadce/test_metadce_hello_dylink.jssize index 497813ee81ff..ad3355357e31 100644 --- a/test/other/metadce/test_metadce_hello_dylink.jssize +++ b/test/other/metadce/test_metadce_hello_dylink.jssize @@ -1 +1 @@ -14888 +15270 diff --git a/test/other/test_em_js_main.c b/test/other/test_em_js_main.c new file mode 100644 index 000000000000..71048476e755 --- /dev/null +++ b/test/other/test_em_js_main.c @@ -0,0 +1,9 @@ +#include +#include + +int test_side(); + +int main() { + printf("in main\n"); + return test_side(); +} diff --git a/test/other/test_em_js_main.out b/test/other/test_em_js_main.out new file mode 100644 index 000000000000..eb0ab8066d2b --- /dev/null +++ b/test/other/test_em_js_main.out @@ -0,0 +1,4 @@ +in main +hello from side module 42 + hello +hello again +hello from void func diff --git a/test/other/test_em_js_side.c b/test/other/test_em_js_side.c new file mode 100644 index 000000000000..97ea8bef6724 --- /dev/null +++ b/test/other/test_em_js_side.c @@ -0,0 +1,21 @@ +#include +#include + +EM_JS(void*, js_side_func, (int num, char* ptr), { + out(`hello from side module ${num} + ${UTF8ToString(ptr)}`); + return 99; +}); + +EM_JS(void, js_side_func2, (char *ptr), { + out(UTF8ToString(ptr)); +}); + +EM_JS(void, js_side_func_void, (), { + out(`hello from void func`); +}); + +void test_side() { + js_side_func(42, "hello"); + js_side_func2("hello again"); + js_side_func_void(); +} diff --git a/test/test_other.py b/test/test_other.py index 0fe9cd7162fb..68435b60a972 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -11964,8 +11964,8 @@ def test_runtime_keepalive(self): @crossplatform def test_em_js_side_module(self): - err = self.expect_fail([EMXX, '-sSIDE_MODULE', test_file('core/test_em_js.cpp')]) - self.assertContained('EM_JS is not supported in side modules', err) + self.build(test_file('other/test_em_js_side.c'), js_outfile=False, emcc_args=['-sSIDE_MODULE'], output_basename='side') + self.do_other_test('test_em_js_main.c', emcc_args=['-sMAIN_MODULE=2', 'side.wasm']) def test_em_js_main_module(self): self.set_setting('MAIN_MODULE', 2) diff --git a/tools/shared.py b/tools/shared.py index 554a37151962..931123e3a118 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -673,6 +673,13 @@ def strip_prefix(string, prefix): return string[len(prefix):] +def maybe_strip_prefix(string, prefix): + if string.startswith(prefix): + return string[len(prefix):] + else: + return string + + def make_writable(filename): assert os.path.isfile(filename) old_mode = stat.S_IMODE(os.stat(filename).st_mode)