Skip to content

Commit 29f9c0d

Browse files
committed
Add option to use source phase imports for wasm module loading
Now that node support has been landed we can test this, at least against the latest node canary builds. See nodejs/node#56919 Fixes: emscripten-core#23047
1 parent b8b827a commit 29f9c0d

File tree

6 files changed

+68
-22
lines changed

6 files changed

+68
-22
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.5 (in development)
2222
----------------------
23+
- Added initial support for wasm source phase imports via
24+
`-sSOURCE_PHASE_IMPORTS`. This is currently experimental and not yet
25+
implemented in browsers. (#23175)
2326

2427
4.0.4 - 02/25/25
2528
----------------

site/source/docs/tools_reference/settings_reference.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3347,3 +3347,15 @@ Use _ for non-pointer arguments, p for pointer/i53 arguments, and P for optional
33473347
Example use -sSIGNATURE_CONVERSIONS=someFunction:_p,anotherFunction:p
33483348

33493349
Default value: []
3350+
3351+
.. _source_phase_imports:
3352+
3353+
SOURCE_PHASE_IMPORTS
3354+
====================
3355+
3356+
Experimental support for wasm source phase imports.
3357+
This is only currenty implement in the pre-release/nightly version of node,
3358+
and not yet supported by browsers.
3359+
Requires EXPORT_ES6
3360+
3361+
Default value: false

src/preamble.js

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -558,11 +558,32 @@ function instrumentWasmTableWithAbort() {
558558
}
559559
#endif
560560

561+
#if LOAD_SOURCE_MAP
562+
function receiveSourceMapJSON(sourceMap) {
563+
wasmSourceMap = new WasmSourceMap(sourceMap);
564+
{{{ runIfMainThread("removeRunDependency('source-map');") }}}
565+
}
566+
#endif
567+
568+
#if (PTHREADS || WASM_WORKERS) && (LOAD_SOURCE_MAP || USE_OFFSET_CONVERTER)
569+
// When using postMessage to send an object, it is processed by the structured
570+
// clone algorithm. The prototype, and hence methods, on that object is then
571+
// lost. This function adds back the lost prototype. This does not work with
572+
// nested objects that has prototypes, but it suffices for WasmSourceMap and
573+
// WasmOffsetConverter.
574+
function resetPrototype(constructor, attrs) {
575+
var object = Object.create(constructor.prototype);
576+
return Object.assign(object, attrs);
577+
}
578+
#endif
579+
580+
#if !SOURCE_PHASE_IMPORTS
561581
#if SINGLE_FILE
562582
// In SINGLE_FILE mode the wasm binary is encoded inline here as a data: URL.
563583
var wasmBinaryFile = '{{{ WASM_BINARY_FILE }}}';
564584
#else
565585
var wasmBinaryFile;
586+
566587
function findWasmBinary() {
567588
#if EXPORT_ES6 && !AUDIO_WORKLET
568589
if (Module['locateFile']) {
@@ -647,13 +668,6 @@ var splitModuleProxyHandler = {
647668
};
648669
#endif
649670

650-
#if LOAD_SOURCE_MAP
651-
function receiveSourceMapJSON(sourceMap) {
652-
wasmSourceMap = new WasmSourceMap(sourceMap);
653-
{{{ runIfMainThread("removeRunDependency('source-map');") }}}
654-
}
655-
#endif
656-
657671
#if SPLIT_MODULE || !WASM_ASYNC_COMPILATION
658672
function instantiateSync(file, info) {
659673
var module;
@@ -701,18 +715,6 @@ function instantiateSync(file, info) {
701715
}
702716
#endif
703717

704-
#if (PTHREADS || WASM_WORKERS) && (LOAD_SOURCE_MAP || USE_OFFSET_CONVERTER)
705-
// When using postMessage to send an object, it is processed by the structured
706-
// clone algorithm. The prototype, and hence methods, on that object is then
707-
// lost. This function adds back the lost prototype. This does not work with
708-
// nested objects that has prototypes, but it suffices for WasmSourceMap and
709-
// WasmOffsetConverter.
710-
function resetPrototype(constructor, attrs) {
711-
var object = Object.create(constructor.prototype);
712-
return Object.assign(object, attrs);
713-
}
714-
#endif
715-
716718
#if WASM_ASYNC_COMPILATION
717719
async function instantiateArrayBuffer(binaryFile, imports) {
718720
try {
@@ -815,6 +817,7 @@ async function instantiateAsync(binary, binaryFile, imports) {
815817
return instantiateArrayBuffer(binaryFile, imports);
816818
}
817819
#endif // WASM_ASYNC_COMPILATION
820+
#endif // SOURCE_PHASE_IMPORTS
818821

819822
function getWasmImports() {
820823
#if PTHREADS
@@ -1016,10 +1019,14 @@ function getWasmImports() {
10161019
}
10171020
#endif
10181021

1022+
#if SOURCE_PHASE_IMPORTS
1023+
var instance = await WebAssembly.instantiate(wasmModule, info);
1024+
var exports = receiveInstantiationResult({instance, 'module':wasmModule});
1025+
return exports;
1026+
#else
10191027
#if !SINGLE_FILE
10201028
wasmBinaryFile ??= findWasmBinary();
10211029
#endif
1022-
10231030
#if WASM_ASYNC_COMPILATION
10241031
#if RUNTIME_DEBUG
10251032
dbg('asynchronously preparing wasm');
@@ -1051,6 +1058,7 @@ function getWasmImports() {
10511058
return receiveInstance(result[0]);
10521059
#endif
10531060
#endif // WASM_ASYNC_COMPILATION
1061+
#endif // SOURCE_PHASE_IMPORTS
10541062
}
10551063

10561064
#if !WASM_BIGINT

src/settings.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,6 +2182,13 @@ var LEGACY_RUNTIME = false;
21822182
// [link]
21832183
var SIGNATURE_CONVERSIONS = [];
21842184

2185+
// Experimental support for wasm source phase imports.
2186+
// This is only currently implemented in the pre-release/nightly version of node,
2187+
// and not yet supported by browsers.
2188+
// Requires EXPORT_ES6
2189+
// [link]
2190+
var SOURCE_PHASE_IMPORTS = false;
2191+
21852192
// For renamed settings the format is:
21862193
// [OLD_NAME, NEW_NAME]
21872194
// For removed settings (which now effectively have a fixed value and can no

test/test_other.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,13 +358,23 @@ def test_emcc_generate_config(self, compiler):
358358
@parameterized({
359359
'': ([],),
360360
'node': (['-sENVIRONMENT=node'],),
361+
# load a worker before startup to check ES6 modules there as well
362+
'pthreads': (['-pthread', '-sPTHREAD_POOL_SIZE=1'],),
361363
})
362364
def test_esm(self, args):
363365
self.run_process([EMCC, '-o', 'hello_world.mjs',
364366
'--extern-post-js', test_file('modularize_post_js.js'),
365367
test_file('hello_world.c')] + args)
366-
src = read_file('hello_world.mjs')
367-
self.assertContained('export default Module;', src)
368+
self.assertContained('export default Module;', read_file('hello_world.mjs'))
369+
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
370+
371+
@requires_node_canary
372+
def test_esm_source_phase_imports(self):
373+
self.node_args += ['--experimental-wasm-modules']
374+
self.run_process([EMCC, '-o', 'hello_world.mjs', '-sSOURCE_PHASE_IMPORTS',
375+
'--extern-post-js', test_file('modularize_post_js.js'),
376+
test_file('hello_world.c')])
377+
self.assertContained('import source wasmModule from', read_file('hello_world.mjs'))
368378
self.assertContained('hello, world!', self.run_js('hello_world.mjs'))
369379

370380
@parameterized({

tools/link.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,9 @@ def get_full_import_name(name):
17571757
if settings.ASYNCIFY == 2:
17581758
diagnostics.warning('experimental', '-sASYNCIFY=2 (JSPI) is still experimental')
17591759

1760+
if settings.SOURCE_PHASE_IMPORTS:
1761+
diagnostics.warning('experimental', '-sSOURCE_PHASE_IMPORTS is still experimental and not yet supported in browsers')
1762+
17601763
if settings.WASM2JS:
17611764
if settings.GENERATE_SOURCE_MAP:
17621765
exit_with_error('wasm2js does not support source maps yet (debug in wasm for now)')
@@ -2478,6 +2481,9 @@ def modularize():
24782481
})();
24792482
''' % {'EXPORT_NAME': settings.EXPORT_NAME}
24802483

2484+
if settings.SOURCE_PHASE_IMPORTS:
2485+
src = f"import source wasmModule from './{settings.WASM_BINARY_FILE}';\n\n" + src
2486+
24812487
# Given the async nature of how the Module function and Module object
24822488
# come into existence in AudioWorkletGlobalScope, store the Module
24832489
# function under a different variable name so that AudioWorkletGlobalScope

0 commit comments

Comments
 (0)