Skip to content

Allow JS library dependencies to be added in source code. #17854

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
Oct 7, 2022
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
6 changes: 6 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ See docs/process.md for more on how version tagging works.
-----------------------
- In Wasm exception mode (`-fwasm-exceptions`), when `ASSERTIONS` is enabled,
uncaught exceptions will display stack traces. (#17979)
- It is now possible to specify indirect dependencies on JS library functions
directly in C/C++ source code. For example, in the case of a EM_JS or EM_ASM
JavaScript function that depends on a JS library function. See the
`EM_JS_DEPS` macro in the `em_macros.h` header. Adding dependencies in this
way avoids the need to specify them on the command line with
`-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE`. (#17854)

3.1.23 - 09/23/22
-----------------
Expand Down
2 changes: 1 addition & 1 deletion emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1907,7 +1907,7 @@ def phase_linker_setup(options, state, newargs, user_settings):

if settings.USE_PTHREADS:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
'$registerTLSInit',
'$registerTLSInit',
]

if settings.RELOCATABLE:
Expand Down
3 changes: 3 additions & 0 deletions emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ def update_settings_glue(wasm_file, metadata):
# exported. In theory it should always be present since its defined in compiler-rt.
assert 'emscripten_stack_get_end' in metadata.exports

for deps in metadata.jsDeps:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.extend(deps.split(','))


def apply_static_code_hooks(forwarded_json, code):
code = shared.do_replace(code, '<<< ATINITS >>>', str(forwarded_json['ATINITS']))
Expand Down
1 change: 1 addition & 0 deletions src/settings_internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var SIDE_MODULE_IMPORTS = [];
// programs contains EM_JS or EM_ASM data section, in which case these symbols
// won't exist.
var EXPORT_IF_DEFINED = ['__start_em_asm', '__stop_em_asm',
'__start_em_lib_deps', '__stop_em_lib_deps',
'__start_em_js', '__stop_em_js'];

// Like EXPORTED_FUNCTIONS, but symbol is required to exist in native code.
Expand Down
27 changes: 27 additions & 0 deletions system/include/emscripten/em_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,30 @@
#else
#define EM_IMPORT(NAME)
#endif

/*
* EM_JS_DEPS: Use this macro to declare indirect dependencies on JS symbols.
* The first argument is just unique name for the set of dependencies. The
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* The first argument is just unique name for the set of dependencies. The
* The first argument is just a unique name for the set of dependencies. The

* second argument is a C string that lists JS library symbols in the same way
* they would be specified in the DEFAULT_LIBRARY_FUNCS_TO_INCLUDE command line
* setting.
*
* For example, if your code contains an EM_ASM or EM_JS block that make use of
* the allocate and stackSave JS library functions then you might write this in
* your library source code:
*
* EM_JS_DEPS(mylib_dep, "$allocate,$stackSave");
*
* The emscripten linker will then pick this up and make sure those symbols get
* included in the JS support library.
*
* Dependencies declared in this way will be included if-and-only-if the object
* file (translation unit) in which they exist is included by the linker, so
* it makes sense co-locate them with the EM_JS or EM_ASM code they correspond
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* it makes sense co-locate them with the EM_JS or EM_ASM code they correspond
* it makes sense to co-locate them with the EM_JS or EM_ASM code they correspond

* to.
*/
#define EM_JS_DEPS(tag, deps) \
EMSCRIPTEN_KEEPALIVE \
__attribute__((section("em_lib_deps"))) \
__attribute__((aligned(1))) \
char __em_lib_deps_##tag[] = deps;
2 changes: 2 additions & 0 deletions test/core/dyncall_specific.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ int waka(int w, long long xy, int z) {
return 42;
}

EM_JS_DEPS(main, "$dynCall");

int main() {
EM_ASM({
// Note that these would need to use BigInts if the file were built with
Expand Down
2 changes: 2 additions & 0 deletions test/core/test_asan_js_stack_op.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ EMSCRIPTEN_KEEPALIVE void c_func(char *str) {
printf("%s\n", str);
}

EM_JS_DEPS(js_func, "$allocateUTF8OnStack");

EM_JS(void, js_func, (void), {
_c_func(allocateUTF8OnStack('Hello, World!'));
});
Expand Down
2 changes: 2 additions & 0 deletions test/core/test_convertI32PairToI53Checked.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
// Uncomment to compute the expected results without testing:
//#define GENERATE_ANSWERS

EM_JS_DEPS(test, "$convertI32PairToI53Checked");

double test(int64_t val) {
int32_t lo = (uint32_t)val;
int32_t hi = (uint64_t)val >> 32;
Expand Down
2 changes: 2 additions & 0 deletions test/core/test_int53.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// Uncomment to compute the expected result:
//#define GENERATE_ANSWERS

EM_JS_DEPS(main, "$convertI32PairToI53,$convertU32PairToI53,$readI53FromU64,$readI53FromI64,$writeI53ToI64,$writeI53ToI64Clamped,$writeI53ToU64Clamped,$writeI53ToI64Signaling,$writeI53ToU64Signaling");

void writeI53ToI64_int64(int64_t *heapAddress, int64_t num) {
#ifdef GENERATE_ANSWERS
*heapAddress = num;
Expand Down
2 changes: 2 additions & 0 deletions test/interop/test_add_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ extern "C" int baz() {
return 3;
}

EM_JS_DEPS(main, "$addFunction,$removeFunction");

int main(int argc, char **argv) {
#if defined(GROWTH)
EM_ASM({
Expand Down
2 changes: 2 additions & 0 deletions test/other/test_offset_converter.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ void magic_test_function(void) {
puts("ok");
}

EM_JS_DEPS(test, "$ptrToString");

int main(void) {
magic_test_function();
return 0;
Expand Down
2 changes: 2 additions & 0 deletions test/other/test_runtime_keepalive.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <emscripten.h>
#include <stdio.h>

EM_JS_DEPS(main, "$runtimeKeepalivePush,$runtimeKeepalivePop,$callUserCallback");

int main() {
EM_ASM({
Module["onExit"] = () => { out("onExit"); };
Expand Down
13 changes: 8 additions & 5 deletions test/stack_overflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

#include <stdio.h>
#include <string.h>
#include <emscripten.h>

void __attribute__((noinline)) InteropString(char *staticBuffer)
{
#include <emscripten/em_asm.h>
#include <emscripten/em_macros.h>

EM_JS_DEPS(main, "$allocateUTF8OnStack");

void __attribute__((noinline)) InteropString(char *staticBuffer) {
char *string = (char*)EM_ASM_PTR({
var str = "hello, this is a string! ";
#if ONE_BIG_STRING
Expand All @@ -27,12 +30,12 @@ void __attribute__((noinline)) InteropString(char *staticBuffer)
});
}

int main()
{
int main() {
// Make C side consume a large portion of the stack, before bumping the rest with C++<->JS interop.
char staticBuffer[512288] = {};
InteropString(staticBuffer);
int stringLength = strlen(staticBuffer);
printf("Got string: %s\n", staticBuffer);
printf("Received a string of length %d.\n", stringLength);
return 0;
}
4 changes: 3 additions & 1 deletion test/test_c_preprocessor.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ EM_JS(void, test_remove_cpp_comments_in_shaders, (void), {
if (numFailed) throw numFailed + ' tests failed!';
});

EM_JS_DEPS(main, "$preprocess_c_code,$remove_cpp_comments_in_shaders");

EM_JS(void, test_c_preprocessor, (void), {
var numFailed = 0;
function test(input, expected) {
Expand Down Expand Up @@ -183,4 +185,4 @@ int main()
test_remove_cpp_comments_in_shaders();

test_c_preprocessor();
}
}
12 changes: 2 additions & 10 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,6 @@ def test_sintvars(self):
self.do_core_test('test_sintvars.c')

def test_int53(self):
self.emcc_args += ['-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[$convertI32PairToI53,$convertU32PairToI53,$readI53FromU64,$readI53FromI64,$writeI53ToI64,$writeI53ToI64Clamped,$writeI53ToU64Clamped,$writeI53ToI64Signaling,$writeI53ToU64Signaling]']

if common.EMTEST_REBASELINE:
self.run_process([EMCC, test_file('core/test_int53.c'), '-o', 'a.js', '-DGENERATE_ANSWERS'] + self.emcc_args)
ret = self.run_process(config.NODE_JS + ['a.js'], stdout=PIPE).stdout
Expand All @@ -508,7 +506,6 @@ def test_int53(self):
self.do_core_test('test_int53.c', interleaved_output=False)

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

@also_with_wasm_bigint
def test_unistd_io(self):
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ERRNO_CODES'])
orig_compiler_opts = self.emcc_args.copy()
for fs in ['MEMFS', 'NODEFS']:
self.clear()
Expand Down Expand Up @@ -7096,14 +7092,14 @@ def test_dyncall_specific(self, *args):
self.skipTest('not compatible with WASM_BIGINT')
cases = [
('DIRECT', []),
('DYNAMIC_SIG', ['-sDYNCALLS=1', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$dynCall']),
('DYNAMIC_SIG', ['-sDYNCALLS=1']),
]
if '-sMINIMAL_RUNTIME=1' in args:
self.emcc_args += ['--pre-js', test_file('minimal_runtime_exit_handling.js')]
else:
cases += [
('EXPORTED', []),
('EXPORTED_DYNAMIC_SIG', ['-sDYNCALLS=1', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$dynCall', '-sEXPORTED_RUNTIME_METHODS=dynCall']),
('EXPORTED_DYNAMIC_SIG', ['-sDYNCALLS=1', '-sEXPORTED_RUNTIME_METHODS=dynCall']),
('FROM_OUTSIDE', ['-sEXPORTED_RUNTIME_METHODS=dynCall_iiji'])
]

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

self.emcc_args += ['-DONE_BIG_STRING']
Expand Down Expand Up @@ -8983,7 +8976,6 @@ def test_asan_js_stack_op(self):
self.emcc_args.append('-fsanitize=address')
self.set_setting('ALLOW_MEMORY_GROWTH')
self.set_setting('INITIAL_MEMORY', '300mb')
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$allocateUTF8OnStack'])
self.do_runf(test_file('core/test_asan_js_stack_op.c'),
expected_output='Hello, World!')

Expand Down
35 changes: 29 additions & 6 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -9684,7 +9684,7 @@ def get_file_gzipped_size(f):

# Tests the library_c_preprocessor.js functionality.
def test_c_preprocessor(self):
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'])
self.run_process([EMXX, test_file('test_c_preprocessor.c'), '--js-library', path_from_root('src/library_c_preprocessor.js')])
normal = self.run_js('a.out.js')
print(str(normal))

Expand Down Expand Up @@ -10023,7 +10023,6 @@ def test_proxy_to_pthread_stack(self):
})
def test_offset_converter(self, *args):
self.set_setting('USE_OFFSET_CONVERTER')
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$ptrToString'])
self.emcc_args += ['--profiling-funcs']
self.do_runf(test_file('other/test_offset_converter.c'), 'ok', emcc_args=list(args))

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

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

def test_runtime_keepalive(self):
self.uses_es6 = True
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$runtimeKeepalivePush', '$runtimeKeepalivePop', '$callUserCallback'])
self.set_setting('EXIT_RUNTIME')
self.do_other_test('test_runtime_keepalive.cpp')

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

def test_warn_once(self):
self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$warnOnce'])
create_file('main.c', r'''\
#include <stdio.h>
#include <emscripten.h>

EM_JS_DEPS(main, "$warnOnce");

int main() {
EM_ASM({
warnOnce("foo");
Expand Down Expand Up @@ -12540,3 +12538,28 @@ def test_fs_icase(self):

def test_strict_js_closure(self):
self.do_runf(test_file('hello_world.c'), emcc_args=['-sSTRICT_JS', '-Werror=closure', '--closure=1', '-O3'])

def test_em_js_deps(self):
# Check that EM_JS_DEPS works. Specifically, multiple different instances in different
# object files.
create_file('f1.c', '''
#include <emscripten.h>

EM_JS_DEPS(other, "$allocateUTF8OnStack");
''')
create_file('f2.c', '''
#include <emscripten.h>

EM_JS_DEPS(main, "$getHeapMax");

int main() {
EM_ASM({
err(getHeapMax());
var x = stackSave();
allocateUTF8OnStack("hello");
stackRestore(x);
});
return 0;
}
''')
self.do_runf('f2.c', emcc_args=['f1.c'])
2 changes: 2 additions & 0 deletions test/unistd/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <sys/uio.h>
#include <emscripten.h>

EM_JS_DEPS(main, "$ERRNO_CODES");

int main() {
EM_ASM(
FS.mkdir('/working');
Expand Down
Loading