Skip to content

Commit 2f78716

Browse files
authored
[ASYNCIFY] Avoid dependency on the names of exports (#24382)
This change moves from using export names to function identity to track the functions in the resumption stack. The upside of this is that we can wrap non-exports, such as table entire, which we currently avoid by enabling `-sDYNCALLS` whenever `ASYNCIFY=1` is used. It also means we can potentially make `ASYNCIFY=1` work with the `wasmExports` global existing (i.e. in `WASM_ESM_INTEGRATION` mode.
1 parent 769fe3f commit 2f78716

File tree

3 files changed

+112
-73
lines changed

3 files changed

+112
-73
lines changed

src/lib/libasync.js

Lines changed: 110 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ addToLibrary({
3535
// Asyncify code that is shared between mode 1 (original) and mode 2 (JSPI).
3636
//
3737
#if ASYNCIFY == 1 && MEMORY64
38-
rewindArguments: {},
38+
rewindArguments: new Map(),
3939
#endif
4040
instrumentWasmImports(imports) {
4141
#if EMBIND_GEN_MODE
@@ -99,13 +99,54 @@ addToLibrary({
9999
}
100100
},
101101
#if ASYNCIFY == 1 && MEMORY64
102-
saveRewindArguments(funcName, passedArguments) {
103-
return Asyncify.rewindArguments[funcName] = Array.from(passedArguments)
102+
saveRewindArguments(func, passedArguments) {
103+
return Asyncify.rewindArguments.set(func, Array.from(passedArguments));
104104
},
105-
restoreRewindArguments(funcName) {
106-
return Asyncify.rewindArguments[funcName] || []
105+
restoreRewindArguments(func) {
106+
#if ASSERTIONS
107+
assert(Asyncify.rewindArguments.has(func));
108+
#endif
109+
return Asyncify.rewindArguments.get(func);
107110
},
108111
#endif
112+
113+
instrumentFunction(original) {
114+
var wrapper = (...args) => {
115+
#if ASYNCIFY_DEBUG >= 2
116+
dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} try ${original}`);
117+
#endif
118+
#if ASYNCIFY == 1
119+
Asyncify.exportCallStack.push(original);
120+
try {
121+
#endif
122+
#if ASYNCIFY == 1 && MEMORY64
123+
Asyncify.saveRewindArguments(original, args);
124+
#endif
125+
return original(...args);
126+
#if ASYNCIFY == 1
127+
} finally {
128+
if (!ABORT) {
129+
var top = Asyncify.exportCallStack.pop();
130+
#if ASSERTIONS
131+
assert(top === original);
132+
#endif
133+
#if ASYNCIFY_DEBUG >= 2
134+
dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} finally ${original}`);
135+
#endif
136+
Asyncify.maybeStopUnwind();
137+
}
138+
}
139+
#endif
140+
};
141+
#if ASYNCIFY == 1
142+
Asyncify.funcWrappers.set(original, wrapper);
143+
#endif
144+
#if MAIN_MODULE || ASYNCIFY_LAZY_LOAD_CODE
145+
wrapper.orig = original;
146+
#endif
147+
return wrapper;
148+
},
149+
109150
instrumentWasmExports(exports) {
110151
#if EMBIND_GEN_MODE
111152
// Instrumenting is not needed when generating code.
@@ -121,48 +162,27 @@ addToLibrary({
121162
var ret = {};
122163
for (let [x, original] of Object.entries(exports)) {
123164
if (typeof original == 'function') {
124-
#if ASYNCIFY == 2
165+
#if ASYNCIFY == 2
125166
// Wrap all exports with a promising WebAssembly function.
126167
let isAsyncifyExport = exportPattern.test(x);
127168
if (isAsyncifyExport) {
128169
Asyncify.asyncExports.add(original);
129170
original = Asyncify.makeAsyncFunction(original);
130171
}
131172
#endif
132-
ret[x] = (...args) => {
133-
#if ASYNCIFY_DEBUG >= 2
134-
dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} try ${x}`);
135-
#endif
136-
#if ASYNCIFY == 1
137-
Asyncify.exportCallStack.push(x);
138-
try {
139-
#endif
140-
#if ASYNCIFY == 1 && MEMORY64
141-
Asyncify.saveRewindArguments(x, args);
142-
#endif
143-
return original(...args);
144-
#if ASYNCIFY == 1
145-
} finally {
146-
if (!ABORT) {
147-
var y = Asyncify.exportCallStack.pop();
148-
#if ASSERTIONS
149-
assert(y === x);
173+
var wrapper = Asyncify.instrumentFunction(original);
174+
#if ASYNCIFY_LAZY_LOAD_CODE
175+
original.exportName = x;
150176
#endif
151-
#if ASYNCIFY_DEBUG >= 2
152-
dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} finally ${x}`);
153-
#endif
154-
Asyncify.maybeStopUnwind();
155-
}
156-
}
157-
#endif
158-
};
159-
#if MAIN_MODULE
160-
ret[x].orig = original;
161-
#endif
162-
} else {
177+
ret[x] = wrapper;
178+
179+
} else {
163180
ret[x] = original;
164181
}
165182
}
183+
#if ASYNCIFY_LAZY_LOAD_CODE
184+
Asyncify.updateFunctionMapping(ret);
185+
#endif
166186
return ret;
167187
},
168188

@@ -187,24 +207,54 @@ addToLibrary({
187207
// We must track which wasm exports are called into and
188208
// exited, so that we know where the call stack began,
189209
// which is where we must call to rewind it.
210+
// This list contains the original Wasm exports.
190211
exportCallStack: [],
191-
callStackNameToId: {},
192-
callStackIdToName: {},
212+
callstackFuncToId: new Map(),
213+
callStackIdToFunc: new Map(),
214+
// Maps wasm functions to their corresponding wrapper function.
215+
funcWrappers: new Map(),
193216
callStackId: 0,
194217
asyncPromiseHandlers: null, // { resolve, reject } pair for when *all* asynchronicity is done
195218
sleepCallbacks: [], // functions to call every time we sleep
196219

197-
getCallStackId(funcName) {
220+
#if ASYNCIFY_LAZY_LOAD_CODE
221+
updateFunctionMapping(newExports) {
222+
#if ASYNCIFY_DEBUG
223+
dbg('updateFunctionMapping', Asyncify.callStackIdToFunc);
224+
#endif
225+
#if ASSERTIONS
226+
assert(!Asyncify.exportCallStack.length);
227+
#endif
228+
Asyncify.callStackIdToFunc.forEach((func, id) => {
198229
#if ASSERTIONS
199-
assert(funcName);
230+
assert(func.exportName);
231+
assert(newExports[func.exportName]);
232+
assert(newExports[func.exportName].orig);
233+
#endif
234+
var newFunc = newExports[func.exportName].orig;
235+
Asyncify.callStackIdToFunc.set(id, newFunc)
236+
Asyncify.callstackFuncToId.set(newFunc, id);
237+
#if MEMORY64
238+
var args = Asyncify.rewindArguments.get(func);
239+
if (args) {
240+
Asyncify.rewindArguments.set(newFunc, args);
241+
Asyncify.rewindArguments.delete(func);
242+
}
200243
#endif
201-
var id = Asyncify.callStackNameToId[funcName];
202-
if (id === undefined) {
203-
id = Asyncify.callStackId++;
204-
Asyncify.callStackNameToId[funcName] = id;
205-
Asyncify.callStackIdToName[id] = funcName;
244+
});
245+
},
246+
#endif
247+
248+
getCallStackId(func) {
249+
#if ASSERTIONS
250+
assert(func);
251+
#endif
252+
if (!Asyncify.callstackFuncToId.has(func)) {
253+
var id = Asyncify.callStackId++;
254+
Asyncify.callstackFuncToId.set(func, id);
255+
Asyncify.callStackIdToFunc.set(id, func);
206256
}
207-
return id;
257+
return Asyncify.callstackFuncToId.get(func);
208258
},
209259

210260
maybeStopUnwind() {
@@ -246,7 +296,7 @@ addToLibrary({
246296
// An asyncify data structure has three fields:
247297
// 0 current stack pos
248298
// 4 max stack pos
249-
// 8 id of function at bottom of the call stack (callStackIdToName[id] == name of js function)
299+
// 8 id of function at bottom of the call stack (callStackIdToFunc[id] == wasm func)
250300
//
251301
// The Asyncify ABI only interprets the first two fields, the rest is for the runtime.
252302
// We also embed a stack in the same memory region here, right next to the structure.
@@ -274,38 +324,24 @@ addToLibrary({
274324
{{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'rewindId', 'i32') }}};
275325
},
276326

277-
getDataRewindFuncName(ptr) {
327+
getDataRewindFunc(ptr) {
278328
var id = {{{ makeGetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'i32') }}};
279-
var name = Asyncify.callStackIdToName[id];
280-
#if ASSERTIONS
281-
assert(name, `id ${id} not found in callStackIdToName`);
282-
#endif
283-
return name;
284-
},
285-
286-
#if RELOCATABLE
287-
getDataRewindFunc__deps: [ '$resolveGlobalSymbol' ],
288-
#endif
289-
getDataRewindFunc(name) {
290-
var func = wasmExports[name];
291-
#if RELOCATABLE
292-
// Exported functions in side modules are not listed in `wasmExports`,
293-
// So we should use `resolveGlobalSymbol` helper function, which is defined in `library_dylink.js`.
294-
if (!func) {
295-
func = resolveGlobalSymbol(name, false).sym;
296-
}
297-
#endif
329+
var func = Asyncify.callStackIdToFunc.get(id);
298330
#if ASSERTIONS
299-
assert(func, `export not found: ${name}`);
331+
assert(func, `id ${id} not found in callStackIdToFunc`);
300332
#endif
301333
return func;
302334
},
303335

304336
doRewind(ptr) {
305-
var name = Asyncify.getDataRewindFuncName(ptr);
306-
var func = Asyncify.getDataRewindFunc(name);
337+
var original = Asyncify.getDataRewindFunc(ptr);
307338
#if ASYNCIFY_DEBUG
308-
dbg('ASYNCIFY: doRewind:', name);
339+
dbg('ASYNCIFY: doRewind:', original);
340+
#endif
341+
var func = Asyncify.funcWrappers.get(original);
342+
#if ASSERTIONS
343+
assert(original);
344+
assert(func);
309345
#endif
310346
// Once we have rewound and the stack we no longer need to artificially
311347
// keep the runtime alive.
@@ -315,7 +351,7 @@ addToLibrary({
315351
// can just call the function with no args at all since and the engine will produce zeros
316352
// for all arguments. However, for i64 arguments we get `undefined cannot be converted to
317353
// BigInt`.
318-
return func(...Asyncify.restoreRewindArguments(name));
354+
return func(...Asyncify.restoreRewindArguments(original));
319355
#else
320356
return func();
321357
#endif
@@ -510,6 +546,7 @@ addToLibrary({
510546
});
511547
},
512548

549+
#if ASYNCIFY_LAZY_LOAD_CODE
513550
emscripten_lazy_load_code__async: true,
514551
emscripten_lazy_load_code: () => Asyncify.handleSleep((wakeUp) => {
515552
// Update the expected wasm binary file to be the lazy one.
@@ -519,6 +556,7 @@ addToLibrary({
519556
// Load the new wasm.
520557
createWasm();
521558
}),
559+
#endif
522560

523561
_load_secondary_module__sig: 'v',
524562
_load_secondary_module: async function() {

test/test_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9857,7 +9857,7 @@ def setUp(self):
98579857
settings={'INITIAL_MEMORY': '2200mb', 'GLOBAL_BASE': '2gb'})
98589858

98599859
# MEMORY64=1
9860-
wasm64 = make_run('wasm64', emcc_args=['-O1', '-Wno-experimental', '--profiling-funcs'],
9860+
wasm64 = make_run('wasm64', emcc_args=['-Wno-experimental', '--profiling-funcs'],
98619861
settings={'MEMORY64': 1}, require_wasm64=True, require_node=True)
98629862
wasm64_v8 = make_run('wasm64_v8', emcc_args=['-Wno-experimental', '--profiling-funcs'],
98639863
settings={'MEMORY64': 1}, require_wasm64=True, require_v8=True)

tools/maint/gen_sig_info.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ def main(args):
395395
'USE_SDL': 0,
396396
'MAX_WEBGL_VERSION': 0,
397397
'AUTO_JS_LIBRARIES': 0,
398+
'ASYNCIFY_LAZY_LOAD_CODE': 1,
398399
'ASYNCIFY': 1}, cxx=True, extra_cflags=['-std=c++20'])
399400
extract_sig_info(sig_info, {'LEGACY_GL_EMULATION': 1}, ['-DGLES'])
400401
extract_sig_info(sig_info, {'USE_GLFW': 2, 'FULL_ES3': 1, 'MAX_WEBGL_VERSION': 2})

0 commit comments

Comments
 (0)