Skip to content

Commit 974c73e

Browse files
authored
Allow JS library dependencies to be added in source code. (#17854)
This change introduces a new `EM_JS_DEPS` macros that can be used to specific the JS library dependencies of user code in `EM_JS` and/or `EM_ASM` blocks. This is especially important for library authors who don't want to have their users maintain link-time list of required symbols. See #14729
1 parent c795453 commit 974c73e

20 files changed

+119
-30
lines changed

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ See docs/process.md for more on how version tagging works.
2222
-----------------------
2323
- In Wasm exception mode (`-fwasm-exceptions`), when `ASSERTIONS` is enabled,
2424
uncaught exceptions will display stack traces. (#17979)
25+
- It is now possible to specify indirect dependencies on JS library functions
26+
directly in C/C++ source code. For example, in the case of a EM_JS or EM_ASM
27+
JavaScript function that depends on a JS library function. See the
28+
`EM_JS_DEPS` macro in the `em_macros.h` header. Adding dependencies in this
29+
way avoids the need to specify them on the command line with
30+
`-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. (#17854)
2531

2632
3.1.23 - 09/23/22
2733
-----------------

emcc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1907,7 +1907,7 @@ def phase_linker_setup(options, state, newargs, user_settings):
19071907

19081908
if settings.USE_PTHREADS:
19091909
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
1910-
'$registerTLSInit',
1910+
'$registerTLSInit',
19111911
]
19121912

19131913
if settings.RELOCATABLE:

emscripten.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ def update_settings_glue(wasm_file, metadata):
165165
# exported. In theory it should always be present since its defined in compiler-rt.
166166
assert 'emscripten_stack_get_end' in metadata.exports
167167

168+
for deps in metadata.jsDeps:
169+
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(deps.split(','))
170+
168171

169172
def apply_static_code_hooks(forwarded_json, code):
170173
code = shared.do_replace(code, '<<< ATINITS >>>', str(forwarded_json['ATINITS']))

src/settings_internal.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var SIDE_MODULE_IMPORTS = [];
3838
// programs contains EM_JS or EM_ASM data section, in which case these symbols
3939
// won't exist.
4040
var EXPORT_IF_DEFINED = ['__start_em_asm', '__stop_em_asm',
41+
'__start_em_lib_deps', '__stop_em_lib_deps',
4142
'__start_em_js', '__stop_em_js'];
4243

4344
// Like EXPORTED_FUNCTIONS, but symbol is required to exist in native code.

system/include/emscripten/em_macros.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,30 @@
1414
#else
1515
#define EM_IMPORT(NAME)
1616
#endif
17+
18+
/*
19+
* EM_JS_DEPS: Use this macro to declare indirect dependencies on JS symbols.
20+
* The first argument is just unique name for the set of dependencies. The
21+
* second argument is a C string that lists JS library symbols in the same way
22+
* they would be specified in the DEFAULT_LIBRARY_FUNCS_TO_INCLUDE command line
23+
* setting.
24+
*
25+
* For example, if your code contains an EM_ASM or EM_JS block that make use of
26+
* the allocate and stackSave JS library functions then you might write this in
27+
* your library source code:
28+
*
29+
* EM_JS_DEPS(mylib_dep, "$allocate,$stackSave");
30+
*
31+
* The emscripten linker will then pick this up and make sure those symbols get
32+
* included in the JS support library.
33+
*
34+
* Dependencies declared in this way will be included if-and-only-if the object
35+
* file (translation unit) in which they exist is included by the linker, so
36+
* it makes sense co-locate them with the EM_JS or EM_ASM code they correspond
37+
* to.
38+
*/
39+
#define EM_JS_DEPS(tag, deps) \
40+
EMSCRIPTEN_KEEPALIVE \
41+
__attribute__((section("em_lib_deps"))) \
42+
__attribute__((aligned(1))) \
43+
char __em_lib_deps_##tag[] = deps;

test/core/dyncall_specific.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ int waka(int w, long long xy, int z) {
1919
return 42;
2020
}
2121

22+
EM_JS_DEPS(main, "$dynCall");
23+
2224
int main() {
2325
EM_ASM({
2426
// Note that these would need to use BigInts if the file were built with

test/core/test_asan_js_stack_op.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ EMSCRIPTEN_KEEPALIVE void c_func(char *str) {
55
printf("%s\n", str);
66
}
77

8+
EM_JS_DEPS(js_func, "$allocateUTF8OnStack");
9+
810
EM_JS(void, js_func, (void), {
911
_c_func(allocateUTF8OnStack('Hello, World!'));
1012
});

test/core/test_convertI32PairToI53Checked.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
// Uncomment to compute the expected results without testing:
1111
//#define GENERATE_ANSWERS
1212

13+
EM_JS_DEPS(test, "$convertI32PairToI53Checked");
14+
1315
double test(int64_t val) {
1416
int32_t lo = (uint32_t)val;
1517
int32_t hi = (uint64_t)val >> 32;

test/core/test_int53.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// Uncomment to compute the expected result:
1212
//#define GENERATE_ANSWERS
1313

14+
EM_JS_DEPS(main, "$convertI32PairToI53,$convertU32PairToI53,$readI53FromU64,$readI53FromI64,$writeI53ToI64,$writeI53ToI64Clamped,$writeI53ToU64Clamped,$writeI53ToI64Signaling,$writeI53ToU64Signaling");
15+
1416
void writeI53ToI64_int64(int64_t *heapAddress, int64_t num) {
1517
#ifdef GENERATE_ANSWERS
1618
*heapAddress = num;

test/interop/test_add_function.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ extern "C" int baz() {
2020
return 3;
2121
}
2222

23+
EM_JS_DEPS(main, "$addFunction,$removeFunction");
24+
2325
int main(int argc, char **argv) {
2426
#if defined(GROWTH)
2527
EM_ASM({

test/other/test_offset_converter.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ void magic_test_function(void) {
2929
puts("ok");
3030
}
3131

32+
EM_JS_DEPS(test, "$ptrToString");
33+
3234
int main(void) {
3335
magic_test_function();
3436
return 0;

test/other/test_runtime_keepalive.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include <emscripten.h>
22
#include <stdio.h>
33

4+
EM_JS_DEPS(main, "$runtimeKeepalivePush,$runtimeKeepalivePop,$callUserCallback");
5+
46
int main() {
57
EM_ASM({
68
Module["onExit"] = () => { out("onExit"); };

test/stack_overflow.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55

66
#include <stdio.h>
77
#include <string.h>
8-
#include <emscripten.h>
98

10-
void __attribute__((noinline)) InteropString(char *staticBuffer)
11-
{
9+
#include <emscripten/em_asm.h>
10+
#include <emscripten/em_macros.h>
11+
12+
EM_JS_DEPS(main, "$allocateUTF8OnStack");
13+
14+
void __attribute__((noinline)) InteropString(char *staticBuffer) {
1215
char *string = (char*)EM_ASM_PTR({
1316
var str = "hello, this is a string! ";
1417
#if ONE_BIG_STRING
@@ -27,12 +30,12 @@ void __attribute__((noinline)) InteropString(char *staticBuffer)
2730
});
2831
}
2932

30-
int main()
31-
{
33+
int main() {
3234
// Make C side consume a large portion of the stack, before bumping the rest with C++<->JS interop.
3335
char staticBuffer[512288] = {};
3436
InteropString(staticBuffer);
3537
int stringLength = strlen(staticBuffer);
3638
printf("Got string: %s\n", staticBuffer);
3739
printf("Received a string of length %d.\n", stringLength);
40+
return 0;
3841
}

test/test_c_preprocessor.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ EM_JS(void, test_remove_cpp_comments_in_shaders, (void), {
3333
if (numFailed) throw numFailed + ' tests failed!';
3434
});
3535

36+
EM_JS_DEPS(main, "$preprocess_c_code,$remove_cpp_comments_in_shaders");
37+
3638
EM_JS(void, test_c_preprocessor, (void), {
3739
var numFailed = 0;
3840
function test(input, expected) {
@@ -183,4 +185,4 @@ int main()
183185
test_remove_cpp_comments_in_shaders();
184186

185187
test_c_preprocessor();
186-
}
188+
}

test/test_core.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,6 @@ def test_sintvars(self):
498498
self.do_core_test('test_sintvars.c')
499499

500500
def test_int53(self):
501-
self.emcc_args += ['-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[$convertI32PairToI53,$convertU32PairToI53,$readI53FromU64,$readI53FromI64,$writeI53ToI64,$writeI53ToI64Clamped,$writeI53ToU64Clamped,$writeI53ToI64Signaling,$writeI53ToU64Signaling]']
502-
503501
if common.EMTEST_REBASELINE:
504502
self.run_process([EMCC, test_file('core/test_int53.c'), '-o', 'a.js', '-DGENERATE_ANSWERS'] + self.emcc_args)
505503
ret = self.run_process(config.NODE_JS + ['a.js'], stdout=PIPE).stdout
@@ -508,7 +506,6 @@ def test_int53(self):
508506
self.do_core_test('test_int53.c', interleaved_output=False)
509507

510508
def test_int53_convertI32PairToI53Checked(self):
511-
self.emcc_args += ['-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[$convertI32PairToI53Checked]']
512509
if common.EMTEST_REBASELINE:
513510
self.run_process([EMCC, test_file('core/test_convertI32PairToI53Checked.cpp'), '-o', 'a.js', '-DGENERATE_ANSWERS'] + self.emcc_args)
514511
ret = self.run_process(config.NODE_JS + ['a.js'], stdout=PIPE).stdout
@@ -6126,7 +6123,6 @@ def test_unistd_symlink_on_nodefs(self):
61266123

61276124
@also_with_wasm_bigint
61286125
def test_unistd_io(self):
6129-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ERRNO_CODES'])
61306126
orig_compiler_opts = self.emcc_args.copy()
61316127
for fs in ['MEMFS', 'NODEFS']:
61326128
self.clear()
@@ -7096,14 +7092,14 @@ def test_dyncall_specific(self, *args):
70967092
self.skipTest('not compatible with WASM_BIGINT')
70977093
cases = [
70987094
('DIRECT', []),
7099-
('DYNAMIC_SIG', ['-sDYNCALLS=1', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$dynCall']),
7095+
('DYNAMIC_SIG', ['-sDYNCALLS=1']),
71007096
]
71017097
if '-sMINIMAL_RUNTIME=1' in args:
71027098
self.emcc_args += ['--pre-js', test_file('minimal_runtime_exit_handling.js')]
71037099
else:
71047100
cases += [
71057101
('EXPORTED', []),
7106-
('EXPORTED_DYNAMIC_SIG', ['-sDYNCALLS=1', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$dynCall', '-sEXPORTED_RUNTIME_METHODS=dynCall']),
7102+
('EXPORTED_DYNAMIC_SIG', ['-sDYNCALLS=1', '-sEXPORTED_RUNTIME_METHODS=dynCall']),
71077103
('FROM_OUTSIDE', ['-sEXPORTED_RUNTIME_METHODS=dynCall_iiji'])
71087104
]
71097105

@@ -7269,7 +7265,6 @@ def test_add_function(self):
72697265
self.set_setting('WASM_ASYNC_COMPILATION', 0)
72707266
self.set_setting('RESERVED_FUNCTION_POINTERS')
72717267
self.set_setting('EXPORTED_RUNTIME_METHODS', ['callMain'])
7272-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$addFunction', '$removeFunction'])
72737268
src = test_file('interop/test_add_function.cpp')
72747269
post_js = test_file('interop/test_add_function_post.js')
72757270
self.emcc_args += ['--post-js', post_js]
@@ -7668,7 +7663,6 @@ def test_webidl(self, mode, allow_memory_growth):
76687663
# TODO(): Remove once we make webidl output closure-warning free.
76697664
self.ldflags.append('-Wno-error=closure')
76707665
self.set_setting('WASM_ASYNC_COMPILATION', 0)
7671-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$intArrayFromString'])
76727666
if self.maybe_closure():
76737667
# avoid closure minified names competing with our test code in the global name space
76747668
self.set_setting('MODULARIZE')
@@ -8575,7 +8569,6 @@ def test_fs_dict_none(self):
85758569
def test_stack_overflow_check(self):
85768570
self.set_setting('TOTAL_STACK', 1048576)
85778571
self.set_setting('STACK_OVERFLOW_CHECK', 2)
8578-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$allocateUTF8OnStack')
85798572
self.do_runf(test_file('stack_overflow.cpp'), 'Aborted(stack overflow', assert_returncode=NON_ZERO)
85808573

85818574
self.emcc_args += ['-DONE_BIG_STRING']
@@ -8983,7 +8976,6 @@ def test_asan_js_stack_op(self):
89838976
self.emcc_args.append('-fsanitize=address')
89848977
self.set_setting('ALLOW_MEMORY_GROWTH')
89858978
self.set_setting('INITIAL_MEMORY', '300mb')
8986-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$allocateUTF8OnStack'])
89878979
self.do_runf(test_file('core/test_asan_js_stack_op.c'),
89888980
expected_output='Hello, World!')
89898981

test/test_other.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9684,7 +9684,7 @@ def get_file_gzipped_size(f):
96849684

96859685
# Tests the library_c_preprocessor.js functionality.
96869686
def test_c_preprocessor(self):
9687-
self.run_process([EMXX, test_file('test_c_preprocessor.c'), '--js-library', path_from_root('src/library_c_preprocessor.js'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$remove_cpp_comments_in_shaders,$preprocess_c_code'])
9687+
self.run_process([EMXX, test_file('test_c_preprocessor.c'), '--js-library', path_from_root('src/library_c_preprocessor.js')])
96889688
normal = self.run_js('a.out.js')
96899689
print(str(normal))
96909690

@@ -10023,7 +10023,6 @@ def test_proxy_to_pthread_stack(self):
1002310023
})
1002410024
def test_offset_converter(self, *args):
1002510025
self.set_setting('USE_OFFSET_CONVERTER')
10026-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ptrToString'])
1002710026
self.emcc_args += ['--profiling-funcs']
1002810027
self.do_runf(test_file('other/test_offset_converter.c'), 'ok', emcc_args=list(args))
1002910028

@@ -10034,7 +10033,6 @@ def test_offset_converter(self, *args):
1003410033
def test_offset_converter_source_map(self, *args):
1003510034
self.set_setting('USE_OFFSET_CONVERTER')
1003610035
self.set_setting('LOAD_SOURCE_MAP')
10037-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ptrToString'])
1003810036
self.emcc_args += ['-gsource-map', '-DUSE_SOURCE_MAP']
1003910037
self.do_runf(test_file('other/test_offset_converter.c'), 'ok', emcc_args=list(args))
1004010038

@@ -11055,9 +11053,9 @@ def test_missing_malloc_export_indirect(self):
1105511053
# we used to include malloc by default. show a clear error in builds with
1105611054
# ASSERTIONS to help with any confusion when the user calls a JS API that
1105711055
# requires malloc
11058-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$allocateUTF8')
1105911056
create_file('unincluded_malloc.c', r'''
1106011057
#include <emscripten.h>
11058+
EM_JS_DEPS(main, "$allocateUTF8");
1106111059
int main() {
1106211060
EM_ASM({
1106311061
try {
@@ -11492,7 +11490,6 @@ def test_shell_Oz(self):
1149211490

1149311491
def test_runtime_keepalive(self):
1149411492
self.uses_es6 = True
11495-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$runtimeKeepalivePush', '$runtimeKeepalivePop', '$callUserCallback'])
1149611493
self.set_setting('EXIT_RUNTIME')
1149711494
self.do_other_test('test_runtime_keepalive.cpp')
1149811495

@@ -12495,11 +12492,12 @@ def test_bigint64array_polyfill(self):
1249512492
self.assertEqual(v1, v2, msg=m)
1249612493

1249712494
def test_warn_once(self):
12498-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$warnOnce'])
1249912495
create_file('main.c', r'''\
1250012496
#include <stdio.h>
1250112497
#include <emscripten.h>
1250212498
12499+
EM_JS_DEPS(main, "$warnOnce");
12500+
1250312501
int main() {
1250412502
EM_ASM({
1250512503
warnOnce("foo");
@@ -12540,3 +12538,28 @@ def test_fs_icase(self):
1254012538

1254112539
def test_strict_js_closure(self):
1254212540
self.do_runf(test_file('hello_world.c'), emcc_args=['-sSTRICT_JS', '-Werror=closure', '--closure=1', '-O3'])
12541+
12542+
def test_em_js_deps(self):
12543+
# Check that EM_JS_DEPS works. Specifically, multiple different instances in different
12544+
# object files.
12545+
create_file('f1.c', '''
12546+
#include <emscripten.h>
12547+
12548+
EM_JS_DEPS(other, "$allocateUTF8OnStack");
12549+
''')
12550+
create_file('f2.c', '''
12551+
#include <emscripten.h>
12552+
12553+
EM_JS_DEPS(main, "$getHeapMax");
12554+
12555+
int main() {
12556+
EM_ASM({
12557+
err(getHeapMax());
12558+
var x = stackSave();
12559+
allocateUTF8OnStack("hello");
12560+
stackRestore(x);
12561+
});
12562+
return 0;
12563+
}
12564+
''')
12565+
self.do_runf('f2.c', emcc_args=['f1.c'])

test/unistd/io.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <sys/uio.h>
1414
#include <emscripten.h>
1515

16+
EM_JS_DEPS(main, "$ERRNO_CODES");
17+
1618
int main() {
1719
EM_ASM(
1820
FS.mkdir('/working');

0 commit comments

Comments
 (0)