Skip to content
Open
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
4 changes: 4 additions & 0 deletions src/jsifier.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,10 @@ var proxiedFunctionTable = [
'//FORWARDED_DATA:' +
JSON.stringify({
librarySymbols,
// The final EXPORTED_FUNCTIONS set, including any additions made by
// JS libraries at load time, so the caller can re-derive which
// library symbols were exported.
exportedFunctions: Array.from(EXPORTED_FUNCTIONS),
nativeAliases,
warnings: warningOccured(),
asyncFuncs,
Expand Down
31 changes: 30 additions & 1 deletion test/test_jslib.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from subprocess import PIPE

from common import RunnerCore, copy_asset, create_file, read_file, test_file
from decorators import also_with_wasm64, also_without_bigint, parameterized
from decorators import also_with_wasm64, also_without_bigint, parameterized, requires_node_25

from tools.shared import EMCC
from tools.utils import delete_file
Expand Down Expand Up @@ -164,6 +164,35 @@ def test_jslib_exported(self):
self.do_runf('src.c', 'c calling: 12\njs calling: 10.',
cflags=['--js-library', 'lib.js', '-sEXPORTED_FUNCTIONS=_main,_jslibfunc'])

@parameterized({
'': ([],),
'esm_integration': (['-sWASM_ESM_INTEGRATION'],),
})
@requires_node_25
def test_jslib_self_export(self, args):
# A JS library can add its own symbols to EXPORTED_FUNCTIONS at load time
# (e.g. a binding layer registering the public API it defines), making them
# ES module exports under MODULARIZE=instance without the user needing to
# list them on the command line.
self.node_args += ['--no-warnings']
create_file('lib.js', '''\
EXPORTED_FUNCTIONS.add('libExport');
addToLibrary({
$libExport: () => 42,
});
''')
create_file('main.c', 'int main() { return 0; }')
self.run_process([EMCC, 'main.c', '-sMODULARIZE=instance', '-Wno-experimental',
'--js-library', 'lib.js', '-o', 'mod.mjs'] + args + self.get_cflags())
create_file('runner.mjs', '''
import { strict as assert } from 'assert';
import init, { libExport } from './mod.mjs';
await init();
assert(libExport() == 42);
console.log('ok');
''')
self.assertContained('ok', self.run_js('runner.mjs'))

def test_jslib_using_asm_lib(self):
create_file('lib.js', r'''
addToLibrary({
Expand Down
4 changes: 4 additions & 0 deletions tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
_is_ar_cache: dict[str, bool] = {}
# the exports the user requested
user_requested_exports: set[str] = set()
# JS library symbols that ended up in EXPORTED_FUNCTIONS (including additions
# made by JS libraries themselves at load time), derived from the JS compiler's
# forwarded data; the WASM_ESM_INTEGRATION wrapper re-exports them.
exported_js_library_symbols: set[str] = set()
# A list of feature flags to pass to each binaryen invocation (like `wasm-opt`,
# etc.). This is received by the first call to binaryen (e.g. `wasm-emscripten-finalize`)
# which reads it using `--detect-features`.
Expand Down
8 changes: 8 additions & 0 deletions tools/emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,14 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadat

report_missing_exports(forwarded_json['librarySymbols'])

# A JS library symbol is exported (MODULARIZE=instance) when it is in
# EXPORTED_FUNCTIONS; derive that set rather than tracking it separately. The
# forwarded EXPORTED_FUNCTIONS includes additions made by JS libraries
# themselves at load time.
exported_functions = set(forwarded_json['exportedFunctions'])
building.exported_js_library_symbols.update(
s for s in forwarded_json['librarySymbols'] if s in exported_functions)

asm_const_pairs = ['%s: %s' % (key, value) for key, value in asm_consts]
if asm_const_pairs or settings.MAIN_MODULE:
pre += 'var ASM_CONSTS = {\n ' + ', \n '.join(asm_const_pairs) + '\n};\n'
Expand Down
4 changes: 4 additions & 0 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,10 @@ def node_detection_code():

def create_esm_wrapper(wrapper_file, support_target, wasm_target):
js_exports = building.user_requested_exports.union(settings.EXPORTED_RUNTIME_METHODS)
# JS library symbols the support module exports at declaration (including
# any the libraries themselves added to EXPORTED_FUNCTIONS at load time);
# the wrapper must forward these too.
js_exports |= building.exported_js_library_symbols
js_exports = ', '.join(sorted(js_exports))

wrapper = []
Expand Down
Loading