Skip to content

Commit 769d26a

Browse files
committed
Add option to use source phase imports for wasm module loading
Fixes: #23047
1 parent 354fdd3 commit 769d26a

6 files changed

Lines changed: 70 additions & 25 deletions

File tree

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.
2326

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

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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,6 +2182,9 @@ var LEGACY_RUNTIME = false;
21822182
// [link]
21832183
var SIGNATURE_CONVERSIONS = [];
21842184

2185+
// Experimental support for wasm source phase imports. Requires EXPORT_ES6
2186+
var SOURCE_PHASE_IMPORTS = false;
2187+
21852188
// For renamed settings the format is:
21862189
// [OLD_NAME, NEW_NAME]
21872190
// 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)