Skip to content

Handle entering the wasm via a table call in Asyncify #12088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,12 +1264,6 @@ def check(input_file):
if shared.Settings.RELOCATABLE:
shared.Settings.ALLOW_TABLE_GROWTH = 1

if shared.Settings.ASYNCIFY:
# See: https://github.com/emscripten-core/emscripten/issues/12065
# See: https://github.com/emscripten-core/emscripten/issues/12066
shared.Settings.USE_LEGACY_DYNCALLS = 1
shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getDynCaller']

# Reconfigure the cache now that settings have been applied. Some settings
# such as LTO and SIDE_MODULE/MAIN_MODULE effect which cache directory we use.
shared.reconfigure_cache()
Expand Down
55 changes: 45 additions & 10 deletions src/library_async.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,35 @@ mergeInto(LibraryManager.library, {
// which is where we must call to rewind it.
exportCallStack: [],
callStackNameToId: {},
callStackIdToName: {},
callStackIdToIdent: {},
callStackId: 0,
freeCallStackIds: [],
afterUnwind: null,
asyncFinalizers: [], // functions to run when *all* asynchronicity is done
sleepCallbacks: [], // functions to call every time we sleep

getCallStackId: function(funcName) {
var id = Asyncify.callStackNameToId[funcName];
if (id === undefined) {
getCallStackId: function(ident) {
var id;
// A string identifier is an export name
if (typeof ident === 'string') {
id = Asyncify.callStackNameToId[ident];
if (id === undefined) {
id = Asyncify.callStackId++;
Asyncify.callStackNameToId[ident] = id;
Asyncify.callStackIdToIdent[id] = ident;
}
return id;
}
// Otherwise, the identifier is an object with information for a table
// call. Create a new ID for each one, or use a free one if available.
if (Asyncify.freeCallStackIds.length > 0) {
id = Asyncify.freeCallStackIds.pop();
} else {
id = Asyncify.callStackId++;
Asyncify.callStackNameToId[funcName] = id;
Asyncify.callStackIdToName[id] = funcName;
}
// Note the ID so that we can free it later.
ident.id = id;
Asyncify.callStackIdToIdent[id] = ident;
return id;
},

Expand Down Expand Up @@ -144,7 +160,7 @@ mergeInto(LibraryManager.library, {
// An asyncify data structure has three fields:
// 0 current stack pos
// 4 max stack pos
// 8 id of function at bottom of the call stack (callStackIdToName[id] == name of js function)
// 8 id at the bottom of the call stack
//
// The Asyncify ABI only interprets the first two fields, the rest is for the runtime.
// We also embed a stack in the same memory region here, right next to the structure.
Expand All @@ -171,9 +187,28 @@ mergeInto(LibraryManager.library, {

getDataRewindFunc: function(ptr) {
var id = {{{ makeGetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'i32') }}};
var name = Asyncify.callStackIdToName[id];
var func = Module['asm'][name];
return func;
var ident = Asyncify.callStackIdToIdent[id];
// The typical case is an export, identified by a string name.
if (typeof ident === 'string') {
var func = Module['asm'][ident];
#if ASSERTIONS
assert(func);
#endif
return func;
}
// If we did a table call into the module, we have a special marker for
// that which tells us how to rewind. We can also free this ID now that
// we are about to rewind it.
Asyncify.freeCallStackIds.push(ident.id);
return function() {
// Note that we don't know the signature of the call here, but it
// does not matter in this code path: when using the legacy method
// of dynCalls in the wasm, we never do table calls into the wasm (we
// call the dynCall export). When we use the new direct table calls,
// we get to here but don't need to call getDynCaller which is the only
// part of makeDynCall that cares about the signature.
return ({{{ makeDynCall('?', 'ident.funcPtr') }}}).apply(null, ident.args);
};
},

handleSleep: function(startAsync) {
Expand Down
34 changes: 33 additions & 1 deletion src/parseTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -1458,9 +1458,41 @@ function makeDynCall(sig, funcPtr) {
assert(sig.indexOf('j') == -1);
if (USE_LEGACY_DYNCALLS) {
return `getDynCaller("${sig}", ${funcPtr})`;
} else {
}
if (!ASYNCIFY) {
return `wasmTable.get(${funcPtr})`;
}
// Asyncify needs to know how to call back into the wasm the way it was
// called, so that we can resume execution (resuming begins with calling
// back inside just as we were called before). Exports are handled by
// Asyncify.exportCallStack, which lets us track the export by which we
// entered the wasm, but calling the table requires some help. Track which
// function pointer and which parameters were used on that stack with
// special entries.
var debugPush = '', debugPop = '';
if (ASYNCIFY_DEBUG >= 2) {
debugPush = `err('ASYNCIFY: ' + ' '.repeat(Asyncify.exportCallStack.length) + ' try ' + entry);`;
debugPop = `err('ASYNCIFY: ' + ' '.repeat(Asyncify.exportCallStack.length) + ' finally ' + entry);`;
}
return `
(function() {
var args = Array.prototype.slice.call(arguments);
// Encode the information for a dynamic call: the function pointer, and the arguments,
// with room for the ID that will be filled in later.
var entry = { funcPtr: ${funcPtr}, args: args, id: -1 };
try {
${debugPush}
Asyncify.exportCallStack.push(entry);
return wasmTable.get(${funcPtr}).apply(null, args);
} finally {
if (ABORT) return;
var popped = Asyncify.exportCallStack.pop();
${debugPop}
assert(popped === entry);
Asyncify.maybeStopUnwind();
}
})
`;
}

function heapAndOffset(heap, ptr) { // given HEAP8, ptr , we return splitChunk, relptr
Expand Down