diff --git a/system/include/emscripten/em_js.h b/system/include/emscripten/em_js.h index e394881b2f091..e22b832d9be5c 100644 --- a/system/include/emscripten/em_js.h +++ b/system/include/emscripten/em_js.h @@ -39,6 +39,8 @@ // // __attribute__((import_name("foo"))) int foo(int x, int y); // +// __attribute__((used)) static void* __em_js_ref_foo = (void*)&foo; +// // __attribute__((used, visibility("default"))) // char __em_js__foo[] = "(int x, int y)<::>{ return 2 * x + y; }"; // @@ -50,6 +52,11 @@ // We use <::> to separate the arguments from the function body because it isn't // valid anywhere in a C function declaration. +// The __em_js_ref_foo pointer simply exists in order to force a reference to +// `foo` to exist in the object file, even if there are no other local uses. +// This means the linker will always use the import_name attribute for this +// function even if it is not locally used. + // Generated __em_js__-prefixed symbols are read by binaryen, and the string // data is extracted into the Emscripten metadata dictionary under the // "emJsFuncs" key. emJsFuncs itself is a dictionary where the keys are function @@ -59,12 +66,13 @@ // emJsFuncs metadata is read in emscripten.py's create_em_js, which creates an // array of JS function strings to be included in the JS output. -#define _EM_JS(ret, c_name, js_name, params, code) \ - _EM_JS_CPP_BEGIN \ - ret c_name params EM_IMPORT(js_name); \ - EMSCRIPTEN_KEEPALIVE \ - __attribute__((section("em_js"), aligned(1))) char __em_js__##js_name[] = \ - #params "<::>" code; \ +#define _EM_JS(ret, c_name, js_name, params, code) \ + _EM_JS_CPP_BEGIN \ + ret c_name params EM_IMPORT(js_name); \ + __attribute__((used)) static void* __em_js_ref_##c_name = (void*)&c_name; \ + EMSCRIPTEN_KEEPALIVE \ + __attribute__((section("em_js"), aligned(1))) char __em_js__##js_name[] = \ + #params "<::>" code; \ _EM_JS_CPP_END #define EM_JS(ret, name, params, ...) _EM_JS(ret, name, name, params, #__VA_ARGS__) diff --git a/test/test_other.py b/test/test_other.py index 2160ea8261168..b14d72db8566d 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -11914,6 +11914,29 @@ def test_em_js_main_module_address(self): expected = 'Aborted(Assertion failed: Missing signature argument to addFunction: function foo() { err("hello"); })' self.do_runf(test_file('other/test_em_js_main_module_address.c'), expected, assert_returncode=NON_ZERO) + def test_em_js_external_usage(self): + # Verify that EM_JS functions can be called from other source files, even in the case + # when they are not used within the defining file. + create_file('em_js.c', r''' + #include + + EM_JS(void, js_func, (), { + out('js_func called'); + }); + + // js_func is unused within this file + ''') + create_file('main.c', ''' + #include + + void js_func(); + int main() { + js_func(); + } + ''') + self.run_process([EMCC, 'em_js.c', '-c']) + self.do_runf('main.c', 'js_func called\n', emcc_args=['em_js.o']) + # On Windows maximum command line length is 32767 characters. Create such a long build line by linking together # several .o files to test that emcc internally uses response files properly when calling llvm-nm and wasm-ld. @is_slow_test