Skip to content

Fix external usage of EM_JS functions #18928

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

Merged
merged 1 commit into from
Mar 9, 2023
Merged
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
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 = (void*)&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 = (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__)
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