Skip to content

Commit 05cc9e0

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: #23047
1 parent 354fdd3 commit 05cc9e0

File tree

7 files changed

+86
-25
lines changed

7 files changed

+86
-25
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: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -558,11 +558,40 @@ function instrumentWasmTableWithAbort() {
558558
}
559559
#endif
560560

561+
#if LOAD_SOURCE_MAP
562+
var wasmSourceMap;
563+
#include "source_map_support.js"
564+
565+
function receiveSourceMapJSON(sourceMap) {
566+
wasmSourceMap = new WasmSourceMap(sourceMap);
567+
{{{ runIfMainThread("removeRunDependency('source-map');") }}}
568+
}
569+
#endif
570+
571+
#if (PTHREADS || WASM_WORKERS) && (LOAD_SOURCE_MAP || USE_OFFSET_CONVERTER)
572+
// When using postMessage to send an object, it is processed by the structured
573+
// clone algorithm. The prototype, and hence methods, on that object is then
574+
// lost. This function adds back the lost prototype. This does not work with
575+
// nested objects that has prototypes, but it suffices for WasmSourceMap and
576+
// WasmOffsetConverter.
577+
function resetPrototype(constructor, attrs) {
578+
var object = Object.create(constructor.prototype);
579+
return Object.assign(object, attrs);
580+
}
581+
#endif
582+
583+
#if USE_OFFSET_CONVERTER
584+
var wasmOffsetConverter;
585+
#include "wasm_offset_converter.js"
586+
#endif
587+
588+
#if !SOURCE_PHASE_IMPORTS
561589
#if SINGLE_FILE
562590
// In SINGLE_FILE mode the wasm binary is encoded inline here as a data: URL.
563591
var wasmBinaryFile = '{{{ WASM_BINARY_FILE }}}';
564592
#else
565593
var wasmBinaryFile;
594+
566595
function findWasmBinary() {
567596
#if EXPORT_ES6 && !AUDIO_WORKLET
568597
if (Module['locateFile']) {
@@ -647,13 +676,6 @@ var splitModuleProxyHandler = {
647676
};
648677
#endif
649678

650-
#if LOAD_SOURCE_MAP
651-
function receiveSourceMapJSON(sourceMap) {
652-
wasmSourceMap = new WasmSourceMap(sourceMap);
653-
{{{ runIfMainThread("removeRunDependency('source-map');") }}}
654-
}
655-
#endif
656-
657679
#if SPLIT_MODULE || !WASM_ASYNC_COMPILATION
658680
function instantiateSync(file, info) {
659681
var module;
@@ -701,18 +723,6 @@ function instantiateSync(file, info) {
701723
}
702724
#endif
703725

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-
716726
#if WASM_ASYNC_COMPILATION
717727
async function instantiateArrayBuffer(binaryFile, imports) {
718728
try {
@@ -815,6 +825,7 @@ async function instantiateAsync(binary, binaryFile, imports) {
815825
return instantiateArrayBuffer(binaryFile, imports);
816826
}
817827
#endif // WASM_ASYNC_COMPILATION
828+
#endif // SOURCE_PHASE_IMPORTS
818829

819830
function getWasmImports() {
820831
#if PTHREADS
@@ -1016,10 +1027,14 @@ function getWasmImports() {
10161027
}
10171028
#endif
10181029

1030+
#if SOURCE_PHASE_IMPORTS
1031+
var instance = await WebAssembly.instantiate(wasmModule, info);
1032+
var exports = receiveInstantiationResult({instance, 'module':wasmModule});
1033+
return exports;
1034+
#else
10191035
#if !SINGLE_FILE
10201036
wasmBinaryFile ??= findWasmBinary();
10211037
#endif
1022-
10231038
#if WASM_ASYNC_COMPILATION
10241039
#if RUNTIME_DEBUG
10251040
dbg('asynchronously preparing wasm');
@@ -1051,6 +1066,7 @@ function getWasmImports() {
10511066
return receiveInstance(result[0]);
10521067
#endif
10531068
#endif // WASM_ASYNC_COMPILATION
1069+
#endif // SOURCE_PHASE_IMPORTS
10541070
}
10551071

10561072
#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 currenty implement 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/building.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,14 @@ def version_split(v):
511511
@ToolchainProfiler.profile()
512512
def transpile(filename):
513513
config = {
514-
'sourceType': 'script',
515-
'targets': {}
514+
'sourceType': 'module',
515+
'targets': {},
516+
'plugins': [
517+
'@babel/plugin-proposal-import-wasm-source'
518+
],
516519
}
520+
if settings.EXPORT_ES6:
521+
config['sourceType'] = 'module'
517522
if settings.MIN_CHROME_VERSION != UNSUPPORTED:
518523
config['targets']['chrome'] = str(settings.MIN_CHROME_VERSION)
519524
if settings.MIN_FIREFOX_VERSION != UNSUPPORTED:
@@ -529,8 +534,10 @@ def transpile(filename):
529534
config_file = shared.get_temp_files().get('babel_config.json').name
530535
logger.debug(config_json)
531536
utils.write_file(config_file, config_json)
537+
env = os.environ.copy()
538+
env['NODE_PATH'] = path_from_root('node_modules')
532539
cmd = shared.get_npm_cmd('babel') + [filename, '-o', outfile, '--presets', '@babel/preset-env', '--config-file', config_file]
533-
check_call(cmd, cwd=path_from_root())
540+
check_call(cmd, cwd=path_from_root(), env=env)
534541
return outfile
535542

536543

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)