Skip to content

Commit eee7240

Browse files
committed
[ASYNCIFY] Avoid dependency on the names of exports
This change moves from using export names to function identity to track the function 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 074e618 commit eee7240

File tree

1 file changed

+96
-73
lines changed

1 file changed

+96
-73
lines changed

src/lib/libasync.js

Lines changed: 96 additions & 73 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,45 +162,21 @@ 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
}
@@ -187,24 +204,43 @@ addToLibrary({
187204
// We must track which wasm exports are called into and
188205
// exited, so that we know where the call stack began,
189206
// which is where we must call to rewind it.
207+
// This list contains the original Wasm exports.
190208
exportCallStack: [],
191-
callStackNameToId: {},
192-
callStackIdToName: {},
209+
callstackFuncToId: new Map(),
210+
callStackIdToFunc: new Map(),
211+
// Maps wasm functions to their corresponding wrapper function.
212+
funcWrappers: new Map(),
193213
callStackId: 0,
194214
asyncPromiseHandlers: null, // { resolve, reject } pair for when *all* asynchronicity is done
195215
sleepCallbacks: [], // functions to call every time we sleep
196216

197-
getCallStackId(funcName) {
217+
#if ASYNCIFY_LAZY_LOAD_CODE
218+
updateFunctionMapping() {
219+
#if ASSERTIONS
220+
assert(!Asyncify.exportCallStack.length);
221+
#endif
222+
Asyncify.callStackIdToFunc.forEach((func, id) => {
223+
#if ASSERTIONS
224+
assert(func.exportName);
225+
assert(wasmExports[func.exportName]);
226+
#endif
227+
var newFunc = wasmExports[func.exportName].orig;
228+
Asyncify.callStackIdToFunc.set(id, newFunc)
229+
Asyncify.callstackFuncToId.set(newFunc, id);
230+
});
231+
},
232+
#endif
233+
234+
getCallStackId(func) {
198235
#if ASSERTIONS
199-
assert(funcName);
236+
assert(func);
200237
#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;
238+
if (!Asyncify.callstackFuncToId.has(func)) {
239+
var id = Asyncify.callStackId++;
240+
Asyncify.callstackFuncToId.set(func, id);
241+
Asyncify.callStackIdToFunc.set(id, func);
206242
}
207-
return id;
243+
return Asyncify.callstackFuncToId.get(func);
208244
},
209245

210246
maybeStopUnwind() {
@@ -246,7 +282,7 @@ addToLibrary({
246282
// An asyncify data structure has three fields:
247283
// 0 current stack pos
248284
// 4 max stack pos
249-
// 8 id of function at bottom of the call stack (callStackIdToName[id] == name of js function)
285+
// 8 id of function at bottom of the call stack (callStackIdToFunc[id] == wasm func)
250286
//
251287
// The Asyncify ABI only interprets the first two fields, the rest is for the runtime.
252288
// We also embed a stack in the same memory region here, right next to the structure.
@@ -274,39 +310,21 @@ addToLibrary({
274310
{{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'rewindId', 'i32') }}};
275311
},
276312

277-
getDataRewindFuncName(ptr) {
313+
getDataRewindFunc(ptr) {
278314
var id = {{{ makeGetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'i32') }}};
279-
var name = Asyncify.callStackIdToName[id];
315+
var func = Asyncify.callStackIdToFunc.get(id);
280316
#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
298-
#if ASSERTIONS
299-
assert(func, `export not found: ${name}`);
317+
assert(func, `id ${id} not found in callStackIdToFunc`);
300318
#endif
301319
return func;
302320
},
303321

304322
doRewind(ptr) {
305-
var name = Asyncify.getDataRewindFuncName(ptr);
306-
var func = Asyncify.getDataRewindFunc(name);
323+
var original = Asyncify.getDataRewindFunc(ptr);
307324
#if ASYNCIFY_DEBUG
308-
dbg('ASYNCIFY: doRewind:', name);
325+
dbg('ASYNCIFY: doRewind:', original);
309326
#endif
327+
var func = Asyncify.funcWrappers.get(original);
310328
// Once we have rewound and the stack we no longer need to artificially
311329
// keep the runtime alive.
312330
{{{ runtimeKeepalivePop(); }}}
@@ -315,7 +333,7 @@ addToLibrary({
315333
// can just call the function with no args at all since and the engine will produce zeros
316334
// for all arguments. However, for i64 arguments we get `undefined cannot be converted to
317335
// BigInt`.
318-
return func(...Asyncify.restoreRewindArguments(name));
336+
return func(...Asyncify.restoreRewindArguments(original));
319337
#else
320338
return func();
321339
#endif
@@ -510,15 +528,20 @@ addToLibrary({
510528
});
511529
},
512530

531+
#if ASYNCIFY_LAZY_LOAD_CODE
513532
emscripten_lazy_load_code__async: true,
514533
emscripten_lazy_load_code: () => Asyncify.handleSleep((wakeUp) => {
515534
// Update the expected wasm binary file to be the lazy one.
516535
wasmBinaryFile += '.lazy.wasm';
517536
// Add a callback for when all run dependencies are fulfilled, which happens when async wasm loading is done.
518-
dependenciesFulfilled = wakeUp;
537+
dependenciesFulfilled = () => {
538+
Asyncify.updateFunctionMapping();
539+
return wakeUp();
540+
}
519541
// Load the new wasm.
520542
createWasm();
521543
}),
544+
#endif
522545

523546
_load_secondary_module__sig: 'v',
524547
_load_secondary_module: async function() {

0 commit comments

Comments
 (0)