Skip to content

Commit c41417f

Browse files
committed
Allow exporting of symbols that are not valid JS identifiers
In this case we now generate a warning instead of an errors. Such symbols are not directly accessible in the module scope (since we cannot declare them there). They are only accessible via `wasmExports` or `Module` dictionary objects. See: llvm/llvm-project#169043 Fixes: #24825
1 parent d7fe11b commit c41417f

File tree

2 files changed

+56
-18
lines changed

2 files changed

+56
-18
lines changed

test/test_other.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14847,21 +14847,45 @@ def test_cxx20_modules_std_headers(self):
1484714847
self.do_runf('main.cpp', 'Hello Module!', cflags=['-std=c++20', '-fmodules'])
1484814848

1484914849
def test_invalid_export_name(self):
14850-
create_file('test.c', '__attribute__((export_name("my.func"))) void myfunc() {}')
14851-
expected = 'emcc: error: invalid export name: "_my.func"'
14852-
self.assert_fail([EMCC, 'test.c'], expected)
14850+
create_file('main.c', r'''
14851+
#include <emscripten.h>
14852+
#include <stdio.h>
14853+
14854+
__attribute__((export_name("my.func"))) int myfunc() { return 42; }
14855+
14856+
int main() {
14857+
int rtn = EM_ASM_INT(return Module['_my.func']());
14858+
printf("got: %d\n", rtn);
14859+
14860+
int rtn2 = EM_ASM_INT(return wasmExports['my.func']());
14861+
printf("got2: %d\n", rtn2);
14862+
return 0;
14863+
}
14864+
''')
14865+
expected = 'emcc: error: export name is not a valid JS symbol: "_my.func". Use `Module` or `wasmExports` to access this symbol'
14866+
self.assert_fail([EMCC, '-Werror', 'main.c'], expected)
14867+
14868+
# With warning suppressed the above program should work.
14869+
self.do_runf('main.c', 'got: 42\ngot2: 42\n', cflags=['-Wno-js-compiler'])
1485314870

1485414871
# When we are generating only wasm and not JS we don't need exports to
1485514872
# be valid JS symbols.
14856-
self.run_process([EMCC, 'test.c', '--no-entry', '-o', 'out.wasm'])
14857-
14858-
# GCC (and clang) and JavaScript also allow $ in symbol names
14859-
create_file('valid.c', '''
14860-
#include <emscripten.h>
14861-
EMSCRIPTEN_KEEPALIVE
14862-
void my$func() {}
14863-
''')
14864-
self.run_process([EMCC, 'valid.c'])
14873+
self.run_process([EMCC, '-Werror', 'main.c', '--no-entry', '-o', 'out.wasm'])
14874+
14875+
def test_export_with_dollarsign(self):
14876+
# GCC (and clang) and JavaScript both allow $ in symbol names
14877+
create_file('main.c', r'''
14878+
#include <emscripten.h>
14879+
#include <stdio.h>
14880+
14881+
EMSCRIPTEN_KEEPALIVE int my$func() { return 42; }
14882+
14883+
int main() {
14884+
int rtn = EM_ASM_INT(return _my$func());
14885+
printf("got: %d\n", rtn);
14886+
return 0;
14887+
}''')
14888+
self.do_runf('main.c', 'got: 42\n')
1486514889

1486614890
@also_with_modularize
1486714891
def test_instantiate_wasm(self):

tools/emscripten.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadat
339339

340340
for e in settings.EXPORTED_FUNCTIONS:
341341
if not js_manipulation.isidentifier(e):
342-
exit_with_error(f'invalid export name: "{e}"')
342+
diagnostics.warning('js-compiler', f'export name is not a valid JS symbol: "{e}". Use `Module` or `wasmExports` to access this symbol')
343343

344344
# memory and global initializers
345345

@@ -937,10 +937,16 @@ def create_receiving(function_exports, other_exports, library_symbols, aliases):
937937
# folks try to call/use a reference that was taken before the
938938
# wasm module is available.
939939
for sym in mangled:
940-
assignment = sym
941-
if (settings.MODULARIZE or not settings.MINIMAL_RUNTIME) and should_export(sym) and settings.MODULARIZE != 'instance':
942-
assignment += f" = Module['{sym}']"
943-
receiving.append(f"var {assignment} = makeInvalidEarlyAccess('{sym}');")
940+
module_export = (settings.MODULARIZE or not settings.MINIMAL_RUNTIME) and should_export(sym) and settings.MODULARIZE != 'instance'
941+
if not js_manipulation.isidentifier(sym) and not module_export:
942+
continue
943+
assignment = f'var {sym}'
944+
if module_export:
945+
if js_manipulation.isidentifier(sym):
946+
assignment += f" = Module['{sym}']"
947+
else:
948+
assignment = f"Module['{sym}']"
949+
receiving.append(f"{assignment} = makeInvalidEarlyAccess('{sym}');")
944950
else:
945951
# Declare all exports in a single var statement
946952
sep = ',\n '
@@ -975,7 +981,15 @@ def create_receiving(function_exports, other_exports, library_symbols, aliases):
975981
sig_str = sym.removeprefix('dynCall_')
976982
assignment += f" = dynCalls['{sig_str}']"
977983
if do_module_exports and should_export(mangled):
978-
assignment += f" = Module['{mangled}']"
984+
if js_manipulation.isidentifier(mangled):
985+
assignment += f" = Module['{mangled}']"
986+
else:
987+
assignment = f"Module['{mangled}']"
988+
elif not js_manipulation.isidentifier(mangled):
989+
# Symbol is not a valid JS identify and also not exported. In this case we
990+
# have nothing to do here.
991+
continue
992+
979993
if sym in alias_inverse_map:
980994
for target in alias_inverse_map[sym]:
981995
assignment += f" = {target}"

0 commit comments

Comments
 (0)