Skip to content

Commit 6221d6d

Browse files
committed
Allow JS library dependencies to be added in source code.
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 e98554f commit 6221d6d

14 files changed

+78
-24
lines changed

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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@
1414
#else
1515
#define EM_IMPORT(NAME)
1616
#endif
17+
18+
#define EM_JS_DEPS(deps) \
19+
__attribute__((section("em_lib_deps"))) \
20+
__attribute__((used)) \
21+
__attribute__((aligned(1))) \
22+
static char __em_lib_deps[] = deps;

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("$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("$convertI32PairToI53Checked");
14+
1315
double test(int64_t val) {
1416
int32_t lo = (uint32_t)val;
1517
int32_t hi = (uint64_t)val >> 32;

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("$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
@@ -33,3 +33,5 @@ int main(void) {
3333
magic_test_function();
3434
return 0;
3535
}
36+
37+
EM_JS_DEPS("$ptrToString");

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("$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("$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_core.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,6 @@ def test_int53(self):
482482
self.do_core_test('test_int53.c', interleaved_output=False)
483483

484484
def test_int53_convertI32PairToI53Checked(self):
485-
self.emcc_args += ['-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[$convertI32PairToI53Checked]']
486485
if common.EMTEST_REBASELINE:
487486
self.run_process([EMCC, test_file('core/test_convertI32PairToI53Checked.cpp'), '-o', 'a.js', '-DGENERATE_ANSWERS'] + self.emcc_args)
488487
ret = self.run_process(config.NODE_JS + ['a.js'], stdout=PIPE).stdout
@@ -6099,7 +6098,6 @@ def test_unistd_symlink_on_nodefs(self):
60996098

61006099
@also_with_wasm_bigint
61016100
def test_unistd_io(self):
6102-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ERRNO_CODES'])
61036101
orig_compiler_opts = self.emcc_args.copy()
61046102
for fs in ['MEMFS', 'NODEFS']:
61056103
self.clear()
@@ -7243,7 +7241,6 @@ def test_add_function(self):
72437241
self.set_setting('WASM_ASYNC_COMPILATION', 0)
72447242
self.set_setting('RESERVED_FUNCTION_POINTERS')
72457243
self.set_setting('EXPORTED_RUNTIME_METHODS', ['callMain'])
7246-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$addFunction', '$removeFunction'])
72477244
src = test_file('interop/test_add_function.cpp')
72487245
post_js = test_file('interop/test_add_function_post.js')
72497246
self.emcc_args += ['--post-js', post_js]
@@ -7641,7 +7638,6 @@ def test_webidl(self, mode, allow_memory_growth):
76417638
# TODO(): Remove once we make webidl output closure-warning free.
76427639
self.ldflags.remove('-sCLOSURE_WARNINGS=error')
76437640
self.set_setting('WASM_ASYNC_COMPILATION', 0)
7644-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$intArrayFromString'])
76457641
if self.maybe_closure():
76467642
# avoid closure minified names competing with our test code in the global name space
76477643
self.set_setting('MODULARIZE')
@@ -8529,7 +8525,6 @@ def test_fs_dict_none(self):
85298525
def test_stack_overflow_check(self):
85308526
self.set_setting('TOTAL_STACK', 1048576)
85318527
self.set_setting('STACK_OVERFLOW_CHECK', 2)
8532-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$allocateUTF8OnStack')
85338528
self.do_runf(test_file('stack_overflow.cpp'), 'Aborted(stack overflow', assert_returncode=NON_ZERO)
85348529

85358530
self.emcc_args += ['-DONE_BIG_STRING']
@@ -8946,7 +8941,6 @@ def test_asan_js_stack_op(self):
89468941
self.emcc_args.append('-fsanitize=address')
89478942
self.set_setting('ALLOW_MEMORY_GROWTH')
89488943
self.set_setting('INITIAL_MEMORY', '300mb')
8949-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$allocateUTF8OnStack'])
89508944
self.do_runf(test_file('core/test_asan_js_stack_op.c'),
89518945
expected_output='Hello, World!')
89528946

test/test_other.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9982,7 +9982,6 @@ def test_proxy_to_pthread_stack(self):
99829982
})
99839983
def test_offset_converter(self, *args):
99849984
self.set_setting('USE_OFFSET_CONVERTER')
9985-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ptrToString'])
99869985
self.emcc_args += ['--profiling-funcs']
99879986
self.do_runf(test_file('other/test_offset_converter.c'), 'ok', emcc_args=list(args))
99889987

@@ -9993,7 +9992,6 @@ def test_offset_converter(self, *args):
99939992
def test_offset_converter_source_map(self, *args):
99949993
self.set_setting('USE_OFFSET_CONVERTER')
99959994
self.set_setting('LOAD_SOURCE_MAP')
9996-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ptrToString'])
99979995
self.emcc_args += ['-gsource-map', '-DUSE_SOURCE_MAP']
99989996
self.do_runf(test_file('other/test_offset_converter.c'), 'ok', emcc_args=list(args))
99999997

@@ -11000,9 +10998,9 @@ def test_missing_malloc_export_indirect(self):
1100010998
# we used to include malloc by default. show a clear error in builds with
1100110999
# ASSERTIONS to help with any confusion when the user calls a JS API that
1100211000
# requires malloc
11003-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$allocateUTF8')
1100411001
create_file('unincluded_malloc.c', r'''
1100511002
#include <emscripten.h>
11003+
EM_JS_DEPS("$allocateUTF8");
1100611004
int main() {
1100711005
EM_ASM({
1100811006
try {
@@ -11437,7 +11435,6 @@ def test_shell_Oz(self):
1143711435

1143811436
def test_runtime_keepalive(self):
1143911437
self.uses_es6 = True
11440-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$runtimeKeepalivePush', '$runtimeKeepalivePop', '$callUserCallback'])
1144111438
self.set_setting('EXIT_RUNTIME')
1144211439
self.do_other_test('test_runtime_keepalive.cpp')
1144311440

@@ -12440,11 +12437,12 @@ def test_bigint64array_polyfill(self):
1244012437
self.assertEqual(v1, v2, msg=m)
1244112438

1244212439
def test_warn_once(self):
12443-
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$warnOnce'])
1244412440
create_file('main.c', r'''\
1244512441
#include <stdio.h>
1244612442
#include <emscripten.h>
1244712443
12444+
EM_JS_DEPS("$warnOnce");
12445+
1244812446
int main() {
1244912447
EM_ASM({
1245012448
warnOnce("foo");
@@ -12477,3 +12475,28 @@ def test_recursive_cache_lock(self):
1247712475
with shared.Cache.lock('testing'):
1247812476
err = self.expect_fail([EMBUILDER, 'build', 'libc', '--force'], expect_traceback=True)
1247912477
self.assertContained('AssertionError: attempt to lock the cache while a parent process is holding the lock', err)
12478+
12479+
def test_em_js_deps(self):
12480+
# Check that EM_JS_DEPS works. Specifically, multiple different instances in different
12481+
# object files.
12482+
create_file('f1.c', '''
12483+
#include <emscripten.h>
12484+
12485+
EM_JS_DEPS("$allocateUTF8OnStack");
12486+
''')
12487+
create_file('f2.c', '''
12488+
#include <emscripten.h>
12489+
12490+
EM_JS_DEPS("$getHeapMax");
12491+
12492+
int main() {
12493+
EM_ASM({
12494+
err(getHeapMax());
12495+
var x = stackSave();
12496+
allocateUTF8OnStack("hello");
12497+
stackRestore(x);
12498+
});
12499+
return 0;
12500+
}
12501+
''')
12502+
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_ASM_DEPS("$ERRNO_CODES");
17+
1618
int main() {
1719
EM_ASM(
1820
FS.mkdir('/working');

tools/extract_metadata.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
# University of Illinois/NCSA Open Source License. Both these licenses can be
44
# found in the LICENSE file.
55

6+
import logging
7+
68
from . import webassembly
79
from .webassembly import OpCode, AtomicOpCode, MemoryOpCode
810
from .shared import exit_with_error
911
from .settings import settings
1012

13+
logger = logging.getLogger('emcc')
14+
1115

1216
def skip_function_header(module):
1317
num_local_decls = module.read_uleb()
@@ -177,20 +181,23 @@ def data_to_string(data):
177181
return data
178182

179183

180-
def get_asm_strings(module, export_map):
181-
if '__start_em_asm' not in export_map or '__stop_em_asm' not in export_map:
184+
def get_section_strings(module, export_map, section_name):
185+
start_name = f'__start_{section_name}'
186+
stop_name = f'__stop_{section_name}'
187+
if start_name not in export_map or stop_name not in export_map:
188+
logger.debug(f'no start/stop symbols found for section: {section_name}')
182189
return {}
183190

184-
start = export_map['__start_em_asm']
185-
end = export_map['__stop_em_asm']
191+
start = export_map[start_name]
192+
end = export_map[stop_name]
186193
start_global = module.get_global(start.index)
187194
end_global = module.get_global(end.index)
188195
start_addr = get_global_value(start_global)
189196
end_addr = get_global_value(end_global)
190197

191198
seg = find_segment_with_address(module, start_addr)
192199
if not seg:
193-
exit_with_error('unable to find segment starting at __start_em_asm: %s' % start_addr)
200+
exit_with_error(f'unable to find segment starting at __start_{section_name}: {start_addr}')
194201
seg, seg_offset = seg
195202

196203
asm_strings = {}
@@ -225,9 +232,12 @@ def get_main_reads_params(module, export_map):
225232

226233
def get_named_globals(module, exports):
227234
named_globals = {}
235+
internal_start_stop_symbols = ('__start_em_asm', '__stop_em_asm',
236+
'__start_em_lib_deps', '__stop_em_lib_deps',
237+
'__em_lib_deps')
228238
for export in exports:
229239
if export.kind == webassembly.ExternType.GLOBAL:
230-
if export.name in ('__start_em_asm', '__stop_em_asm') or export.name.startswith('__em_js__'):
240+
if export.name in internal_start_stop_symbols or export.name.startswith('__em_js__'):
231241
continue
232242
g = module.get_global(export.index)
233243
named_globals[export.name] = str(get_global_value(g))
@@ -300,7 +310,8 @@ def extract_metadata(filename):
300310
# If main does not read its parameters, it will just be a stub that
301311
# calls __original_main (which has no parameters).
302312
metadata = {}
303-
metadata['asmConsts'] = get_asm_strings(module, export_map)
313+
metadata['asmConsts'] = get_section_strings(module, export_map, 'em_asm')
314+
metadata['jsDeps'] = get_section_strings(module, export_map, 'em_lib_deps').values()
304315
metadata['declares'] = declares
305316
metadata['emJsFuncs'] = em_js_funcs
306317
metadata['exports'] = export_names
@@ -309,5 +320,4 @@ def extract_metadata(filename):
309320
metadata['invokeFuncs'] = invoke_funcs
310321
metadata['mainReadsParams'] = get_main_reads_params(module, export_map)
311322
metadata['namedGlobals'] = get_named_globals(module, exports)
312-
# print("Metadata parsed: " + pprint.pformat(metadata))
313323
return metadata

tools/webidl_binder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ def getExtendedAttribute(self, name): # noqa: U100
8181

8282
pre_c += [r'''
8383
#include <emscripten.h>
84+
85+
EM_JS_DEPS("$intArrayFromString");
8486
''']
8587

8688
mid_c += [r'''

0 commit comments

Comments
 (0)