Skip to content

Commit

Permalink
Fix external useage of EM_JS function
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbc100 committed Mar 9, 2023
1 parent eda6632 commit f02d0cc
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 6 deletions.
20 changes: 14 additions & 6 deletions system/include/emscripten/em_js.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
//
// __attribute__((import_name("foo"))) int foo(int x, int y);
//
// __attribute__((used)) static void* __em_js_ref_foo = &foo;
//
// __attribute__((used, visibility("default")))
// char __em_js__foo[] = "(int x, int y)<::>{ return 2 * x + y; }";
//
Expand All @@ -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
Expand All @@ -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 = &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__)
Expand Down
23 changes: 23 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -11868,6 +11868,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 <emscripten/em_js.h>

EM_JS(void, js_func, (), {
out('js_func called');
});

// js_func is unused within this file
''')
create_file('main.c', '''
#include <stdio.h>

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
Expand Down

0 comments on commit f02d0cc

Please sign in to comment.