Skip to content

Commit 1a97955

Browse files
authored
Initial experimental support for ESM integration (#23985)
This change add a new experimental settings call `WASM_ESM_INTEGRATION`. In this mode we can elide all of the wasm module loading and instantiation code, and instead rely on the module loader to both load and instantiate the wasm file. Current limitations: - No support for INCOMING_JS_MODULE_API / moduleArg. - No support for closure - Many features such as threads and dynamic linking do not work
1 parent f2bf315 commit 1a97955

File tree

11 files changed

+108
-13
lines changed

11 files changed

+108
-13
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.7 (in development)
2222
----------------------
23+
- Added experimental support for Wasm ESM integration with
24+
`-sWASM_ESM_INTEGRATION`. This is currently only supported in node behind a
25+
flag and not in any browsers. (#23985)
2326

2427
4.0.6 - 03/26/25
2528
----------------

site/source/docs/tools_reference/settings_reference.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3345,3 +3345,13 @@ and not yet supported by browsers.
33453345
Requires EXPORT_ES6
33463346

33473347
Default value: false
3348+
3349+
.. _wasm_esm_integration:
3350+
3351+
WASM_ESM_INTEGRATION
3352+
====================
3353+
3354+
Experimental support for wasm ESM integration.
3355+
Requires EXPORT_ES6 and MODULARIZE=instance
3356+
3357+
Default value: false

src/jsifier.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ var proxiedFunctionTable = [
857857
includeFile(fileName);
858858
}
859859

860-
if (MODULARIZE) {
860+
if (MODULARIZE && !WASM_ESM_INTEGRATION) {
861861
includeSystemFile('postamble_modularize.js');
862862
}
863863

src/postamble.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,16 @@ consumedModuleProp('preInit');
305305
#endif
306306
#endif
307307

308+
309+
#if WASM_ESM_INTEGRATION
310+
export default function init(moduleArg = {}) {
311+
// TODO(sbc): moduleArg processing
312+
updateMemoryViews();
313+
run();
314+
}
315+
#else
308316
run();
317+
#endif
309318

310319
#if BUILD_AS_WORKER
311320

src/preamble.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ if (typeof WebAssembly != 'object') {
4646

4747
// Wasm globals
4848

49+
#if !WASM_ESM_INTEGRATION
4950
var wasmMemory;
51+
#endif
5052

5153
#if SHARED_MEMORY
5254
// For sending to workers.
@@ -220,7 +222,11 @@ function initRuntime() {
220222
<<< ATINITS >>>
221223

222224
#if hasExportedSymbol('__wasm_call_ctors')
225+
#if WASM_ESM_INTEGRATION
226+
___wasm_call_ctors();
227+
#else
223228
wasmExports['__wasm_call_ctors']();
229+
#endif
224230
#endif
225231

226232
<<< ATPOSTCTORS >>>
@@ -577,7 +583,7 @@ function resetPrototype(constructor, attrs) {
577583
}
578584
#endif
579585

580-
#if !SOURCE_PHASE_IMPORTS
586+
#if !SOURCE_PHASE_IMPORTS && !WASM_ESM_INTEGRATION
581587
var wasmBinaryFile;
582588

583589
function findWasmBinary() {
@@ -819,6 +825,7 @@ async function instantiateAsync(binary, binaryFile, imports) {
819825
#endif // WASM_ASYNC_COMPILATION
820826
#endif // SOURCE_PHASE_IMPORTS
821827

828+
#if !WASM_ESM_INTEGRATION
822829
function getWasmImports() {
823830
#if PTHREADS
824831
assignWasmImports();
@@ -1058,6 +1065,7 @@ function getWasmImports() {
10581065
#endif // WASM_ASYNC_COMPILATION
10591066
#endif // SOURCE_PHASE_IMPORTS
10601067
}
1068+
#endif
10611069

10621070
#if !WASM_BIGINT
10631071
// Globals used by JS i64 conversions (see makeSetValue)

src/runtime_shared.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ var wasmOffsetConverter;
5454
}
5555
}
5656
if (shouldExport) {
57-
if (MODULARIZE === 'instance') {
57+
if (MODULARIZE === 'instance' && !WASM_ESM_INTEGRATION) {
5858
return `__exp_${x} = `
5959
}
6060
return `Module['${x}'] = `;

src/settings.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,6 +2188,11 @@ var SIGNATURE_CONVERSIONS = [];
21882188
// [link]
21892189
var SOURCE_PHASE_IMPORTS = false;
21902190

2191+
// Experimental support for wasm ESM integration.
2192+
// Requires EXPORT_ES6 and MODULARIZE=instance
2193+
// [link]
2194+
var WASM_ESM_INTEGRATION = false;
2195+
21912196
// For renamed settings the format is:
21922197
// [OLD_NAME, NEW_NAME]
21932198
// For removed settings (which now effectively have a fixed value and can no

src/shell.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
// after the generated code, you will need to define var Module = {};
2121
// before the code. Then that object will be used in the code, and you
2222
// can continue to use Module afterwards as well.
23-
#if MODULARIZE
23+
#if WASM_ESM_INTEGRATION
24+
var Module = {};
25+
#elif MODULARIZE
2426
var Module = moduleArg;
2527
#elif USE_CLOSURE_COMPILER
2628
/** @type{Object} */

test/test_other.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,13 +360,23 @@ def test_esm(self, args):
360360

361361
@requires_node_canary
362362
def test_esm_source_phase_imports(self):
363-
self.node_args += ['--experimental-wasm-modules']
363+
self.node_args += ['--experimental-wasm-modules', '--no-warnings']
364364
self.run_process([EMCC, '-o', 'hello_world.mjs', '-sSOURCE_PHASE_IMPORTS',
365365
'--extern-post-js', test_file('modularize_post_js.js'),
366366
test_file('hello_world.c')])
367367
self.assertContained('import source wasmModule from', read_file('hello_world.mjs'))
368368
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
369369

370+
@requires_node_canary
371+
def test_esm_integration(self):
372+
self.node_args += ['--experimental-wasm-modules', '--no-warnings']
373+
self.run_process([EMCC, '-o', 'hello_world.mjs', '-sWASM_ESM_INTEGRATION', '-Wno-experimental', test_file('hello_world.c')])
374+
create_file('runner.mjs', '''
375+
import init from "./hello_world.mjs";
376+
await init();
377+
''')
378+
self.assertContained('hello, world!', self.run_js('runner.mjs'))
379+
370380
@parameterized({
371381
'': ([],),
372382
'node': (['-sENVIRONMENT=node'],),

tools/emscripten.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -435,17 +435,18 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadat
435435
pre = None
436436

437437
receiving = create_receiving(function_exports)
438-
439-
module = create_module(receiving, metadata, global_exports, forwarded_json['librarySymbols'])
440-
441-
metadata.library_definitions = forwarded_json['libraryDefinitions']
442-
438+
if settings.WASM_ESM_INTEGRATION:
439+
sending = create_sending(metadata, forwarded_json['librarySymbols'])
440+
module = [sending, receiving]
441+
else:
442+
module = create_module(receiving, metadata, global_exports, forwarded_json['librarySymbols'])
443443
write_output_file(out, module)
444444

445445
out.write(post)
446446
module = None
447447

448-
return metadata
448+
metadata.library_definitions = forwarded_json['libraryDefinitions']
449+
return metadata
449450

450451

451452
@ToolchainProfiler.profile()
@@ -851,6 +852,13 @@ def create_sending(metadata, library_symbols):
851852
send_items_map[demangled] = f
852853

853854
sorted_items = sorted(send_items_map.items())
855+
856+
if settings.WASM_ESM_INTEGRATION:
857+
elems = []
858+
for k, v in sorted_items:
859+
elems.append(f'{v} as {k}')
860+
return f"export {{ {', '.join(elems)} }};\n\n"
861+
854862
prefix = ''
855863
if settings.MAYBE_CLOSURE_COMPILER:
856864
# This prevents closure compiler from minifying the field names in this
@@ -924,6 +932,13 @@ def install_wrapper(sym):
924932

925933

926934
def create_receiving(function_exports):
935+
if settings.WASM_ESM_INTEGRATION:
936+
exports = [f'{f} as {asmjs_mangle(f)}' for f in function_exports]
937+
exports.append('memory as wasmMemory')
938+
exports.append('__indirect_function_table as wasmTable')
939+
exports = ',\n '.join(exports)
940+
return f"import {{\n {exports}\n}} from './{settings.WASM_BINARY_FILE}';\n\n"
941+
927942
# When not declaring asm exports this section is empty and we instead programmatically export
928943
# symbols on the global object by calling exportWasmSymbols after initialization
929944
if not settings.DECLARE_ASM_MODULE_EXPORTS:

0 commit comments

Comments
 (0)