Skip to content

Commit d3634c3

Browse files
committed
Fix external useage of EM_JS function
When no local usage of an EM_JS function is present the compiler was completely removing the import of the function (and its import_name). With this change we force at least one reference to the function by taking its address in an otherwise unused (and GC-able) pointer. This use case was broken by the switch to LLD_REPORT_UNDEFINED in #16003. Fixes: #18927
1 parent eda6632 commit d3634c3

File tree

2 files changed

+37
-6
lines changed

2 files changed

+37
-6
lines changed

system/include/emscripten/em_js.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
//
4040
// __attribute__((import_name("foo"))) int foo(int x, int y);
4141
//
42+
// __attribute__((used)) static void* __em_js_ref_foo = (void*)&foo;
43+
//
4244
// __attribute__((used, visibility("default")))
4345
// char __em_js__foo[] = "(int x, int y)<::>{ return 2 * x + y; }";
4446
//
@@ -50,6 +52,11 @@
5052
// We use <::> to separate the arguments from the function body because it isn't
5153
// valid anywhere in a C function declaration.
5254

55+
// The __em_js_ref_foo pointer simply exists in order to force a reference to
56+
// `foo` to exist in the object file, even if there are no other local uses.
57+
// This means the linker will always use the import_name attribute for this
58+
// function even if it is not locally used.
59+
5360
// Generated __em_js__-prefixed symbols are read by binaryen, and the string
5461
// data is extracted into the Emscripten metadata dictionary under the
5562
// "emJsFuncs" key. emJsFuncs itself is a dictionary where the keys are function
@@ -59,12 +66,13 @@
5966
// emJsFuncs metadata is read in emscripten.py's create_em_js, which creates an
6067
// array of JS function strings to be included in the JS output.
6168

62-
#define _EM_JS(ret, c_name, js_name, params, code) \
63-
_EM_JS_CPP_BEGIN \
64-
ret c_name params EM_IMPORT(js_name); \
65-
EMSCRIPTEN_KEEPALIVE \
66-
__attribute__((section("em_js"), aligned(1))) char __em_js__##js_name[] = \
67-
#params "<::>" code; \
69+
#define _EM_JS(ret, c_name, js_name, params, code) \
70+
_EM_JS_CPP_BEGIN \
71+
ret c_name params EM_IMPORT(js_name); \
72+
__attribute__((used)) static void* __em_js_ref_##c_name = (void*)&c_name; \
73+
EMSCRIPTEN_KEEPALIVE \
74+
__attribute__((section("em_js"), aligned(1))) char __em_js__##js_name[] = \
75+
#params "<::>" code; \
6876
_EM_JS_CPP_END
6977

7078
#define EM_JS(ret, name, params, ...) _EM_JS(ret, name, name, params, #__VA_ARGS__)

test/test_other.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11868,6 +11868,29 @@ def test_em_js_main_module_address(self):
1186811868
expected = 'Aborted(Assertion failed: Missing signature argument to addFunction: function foo() { err("hello"); })'
1186911869
self.do_runf(test_file('other/test_em_js_main_module_address.c'), expected, assert_returncode=NON_ZERO)
1187011870

11871+
def test_em_js_external_usage(self):
11872+
# Verify that EM_JS functions can be called from other source files, even in the case
11873+
# when they are not used within the defining file.
11874+
create_file('em_js.c', r'''
11875+
#include <emscripten/em_js.h>
11876+
11877+
EM_JS(void, js_func, (), {
11878+
out('js_func called');
11879+
});
11880+
11881+
// js_func is unused within this file
11882+
''')
11883+
create_file('main.c', '''
11884+
#include <stdio.h>
11885+
11886+
void js_func();
11887+
int main() {
11888+
js_func();
11889+
}
11890+
''')
11891+
self.run_process([EMCC, 'em_js.c', '-c'])
11892+
self.do_runf('main.c', 'js_func called\n', emcc_args=['em_js.o'])
11893+
1187111894
# On Windows maximum command line length is 32767 characters. Create such a long build line by linking together
1187211895
# several .o files to test that emcc internally uses response files properly when calling llvm-nm and wasm-ld.
1187311896
@is_slow_test

0 commit comments

Comments
 (0)