Skip to content

Commit cd73024

Browse files
brendandahlkripken
andauthored
Support ASYNCIFY_IMPORTS and add ASYNCIFY_EXPORTS for JSPI. (#18165)
Pass asyncified imports and exports on to the JSPI wasm-opt pass so only the specified functions are wrapped. * Update src/library_async.js Co-authored-by: Alon Zakai <[email protected]>
1 parent 0806384 commit cd73024

File tree

3 files changed

+48
-27
lines changed

3 files changed

+48
-27
lines changed

emcc.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@
105105
'_dlopen_js', '__asyncjs__*'
106106
]
107107

108+
DEFAULT_ASYNCIFY_EXPORTS = [
109+
'main'
110+
]
111+
108112
# Target options
109113
final_js = None
110114

@@ -665,6 +669,8 @@ def check_human_readable_list(items):
665669
passes += ['--pass-arg=asyncify-onlylist@%s' % ','.join(settings.ASYNCIFY_ONLY)]
666670
elif settings.ASYNCIFY == 2:
667671
passes += ['--jspi']
672+
passes += ['--pass-arg=jspi-imports@%s' % ','.join(settings.ASYNCIFY_IMPORTS)]
673+
passes += ['--pass-arg=jspi-exports@%s' % ','.join(settings.ASYNCIFY_EXPORTS)]
668674
if settings.BINARYEN_IGNORE_IMPLICIT_TRAPS:
669675
passes += ['--ignore-implicit-traps']
670676
# normally we can assume the memory, if imported, has not been modified
@@ -2650,6 +2656,8 @@ def check_memory_setting(setting):
26502656
settings.ASYNCIFY_IMPORTS += ['invoke_*']
26512657
# add the default imports
26522658
settings.ASYNCIFY_IMPORTS += DEFAULT_ASYNCIFY_IMPORTS
2659+
# add the default exports (only used for ASYNCIFY == 2)
2660+
settings.ASYNCIFY_EXPORTS += DEFAULT_ASYNCIFY_EXPORTS
26532661

26542662
# return the full import name, including module. The name may
26552663
# already have a module prefix; if not, we assume it is "env".

src/library_async.js

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,24 @@ mergeInto(LibraryManager.library, {
4343
var isAsyncifyImport = ASYNCIFY_IMPORTS.indexOf(x) >= 0 ||
4444
x.startsWith('__asyncjs__');
4545
#if ASYNCIFY == 2
46-
// Wrap all imports with a suspending WebAssembly function.
47-
// TODO: wrap only async functions.
46+
// Wrap async imports with a suspending WebAssembly function.
47+
if (isAsyncifyImport) {
4848
#if ASSERTIONS
49-
assert(sig, 'Missing __sig for ' + x);
49+
assert(sig, 'Missing __sig for ' + x);
5050
#endif
51-
var type = sigToWasmTypes(sig);
51+
var type = sigToWasmTypes(sig);
5252
#if ASYNCIFY_DEBUG
53-
dbg('asyncify: suspendOnReturnedPromise for', x, original);
54-
#endif
55-
// Add space for the suspender promise that will be used in the
56-
// Wasm wrapper function.
57-
type.parameters.unshift('externref');
58-
imports[x] = original = new WebAssembly.Function(
59-
type,
60-
original,
61-
{ suspending: 'first' }
62-
);
53+
dbg('asyncify: suspendOnReturnedPromise for', x, original);
54+
#endif
55+
// Add space for the suspender promise that will be used in the
56+
// Wasm wrapper function.
57+
type.parameters.unshift('externref');
58+
imports[x] = original = new WebAssembly.Function(
59+
type,
60+
original,
61+
{ suspending: 'first' }
62+
);
63+
}
6364
#endif
6465
#if ASSERTIONS && ASYNCIFY != 2 // We cannot apply assertions with stack switching, as the imports must not be modified from suspender.suspendOnReturnedPromise TODO find a way
6566
imports[x] = function() {
@@ -102,6 +103,9 @@ mergeInto(LibraryManager.library, {
102103
instrumentWasmExports: function(exports) {
103104
#if ASYNCIFY_DEBUG
104105
dbg('asyncify instrumenting exports');
106+
#endif
107+
#if ASYNCIFY == 2
108+
var ASYNCIFY_EXPORTS = {{{ JSON.stringify(ASYNCIFY_EXPORTS) }}};
105109
#endif
106110
var ret = {};
107111
for (var x in exports) {
@@ -110,22 +114,25 @@ mergeInto(LibraryManager.library, {
110114
if (typeof original == 'function') {
111115
#if ASYNCIFY == 2
112116
// Wrap all exports with a promising WebAssembly function.
113-
// TODO: wrap only async functions.
117+
var isAsyncifyExport = ASYNCIFY_EXPORTS.indexOf(x) >= 0;
118+
if (isAsyncifyExport) {
114119
#if ASYNCIFY_DEBUG
115-
dbg('asyncify: returnPromiseOnSuspend for', x, original);
120+
dbg('asyncify: returnPromiseOnSuspend for', x, original);
116121
#endif
117-
var type = WebAssembly.Function.type(original);
118-
var parameters = type.parameters;
119-
var results = type.results;
122+
var type = WebAssembly.Function.type(original);
123+
var parameters = type.parameters;
124+
var results = type.results;
120125
#if ASSERTIONS
121-
assert(results.length !== 0, 'There must be a return result')
122-
#endif
123-
// Remove the extern ref;
124-
parameters.shift();
125-
original = new WebAssembly.Function(
126-
{ parameters , results: ['externref'] },
127-
original,
128-
{ promising : 'first' });
126+
assert(results.length !== 0, 'There must be a return result')
127+
assert(parameters[0] === 'externref', 'First param must be externref.');
128+
#endif
129+
// Remove the extern ref.
130+
parameters.shift();
131+
original = new WebAssembly.Function(
132+
{ parameters , results: ['externref'] },
133+
original,
134+
{ promising : 'first' });
135+
}
129136
#endif
130137
ret[x] = function() {
131138
#if ASYNCIFY_DEBUG >= 2

src/settings.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,12 @@ var ASYNCIFY_LAZY_LOAD_CODE = false;
864864
// [link]
865865
var ASYNCIFY_DEBUG = 0;
866866

867+
// Specify which of the exports will have JSPI applied to them and return a
868+
// promise.
869+
// Only supported for ASYNCIFY==2 mode.
870+
// [link]
871+
var ASYNCIFY_EXPORTS = [];
872+
867873
// Runtime elements that are exported on Module by default. We used to export
868874
// quite a lot here, but have removed them all. You should use
869875
// EXPORTED_RUNTIME_METHODS for things you want to export from the runtime.

0 commit comments

Comments
 (0)