blob: e202c99a106c97042e2bf7f612d8f7d034b9ca91 [file] [log] [blame] [edit]
/**
* @license
* Copyright 2014 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
//
// Async support via ASYNCIFY
//
mergeInto(LibraryManager.library, {
// error handling
$runAndAbortIfError: function(func) {
try {
return func();
} catch (e) {
abort(e);
}
},
#if ASYNCIFY
$Asyncify__deps: ['$runAndAbortIfError', '$callUserCallback',
#if !MINIMAL_RUNTIME
'$runtimeKeepalivePush', '$runtimeKeepalivePop'
#endif
],
$Asyncify: {
State: {
Normal: 0,
Unwinding: 1,
Rewinding: 2,
Disabled: 3,
},
state: 0,
StackSize: {{{ ASYNCIFY_STACK_SIZE }}},
currData: null,
// The return value passed to wakeUp() in
// Asyncify.handleSleep(function(wakeUp){...}) is stored here,
// so we can return it later from the C function that called
// Asyncify.handleSleep() after rewinding finishes.
handleSleepReturnValue: 0,
// We must track which wasm exports are called into and
// exited, so that we know where the call stack began,
// which is where we must call to rewind it.
exportCallStack: [],
callStackNameToId: {},
callStackIdToName: {},
callStackId: 0,
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) {
id = Asyncify.callStackId++;
Asyncify.callStackNameToId[funcName] = id;
Asyncify.callStackIdToName[id] = funcName;
}
return id;
},
#if ASSERTIONS
instrumentWasmImports: function(imports) {
var ASYNCIFY_IMPORTS = {{{ JSON.stringify(ASYNCIFY_IMPORTS) }}}.map(function(x) {
return x.split('.')[1];
});
for (var x in imports) {
(function(x) {
var original = imports[x];
if (typeof original === 'function') {
imports[x] = function() {
var originalAsyncifyState = Asyncify.state;
try {
return original.apply(null, arguments);
} finally {
// Only functions in the list of known relevant imports are allowed to change the state.
// Note that invoke_* functions are allowed to change the state if we do not ignore
// indirect calls.
if (Asyncify.state !== originalAsyncifyState &&
ASYNCIFY_IMPORTS.indexOf(x) < 0 &&
!(x.startsWith('invoke_') && {{{ !ASYNCIFY_IGNORE_INDIRECT }}})) {
throw 'import ' + x + ' was not in ASYNCIFY_IMPORTS, but changed the state';
}
}
}
}
})(x);
}
},
#endif
instrumentWasmExports: function(exports) {
var ret = {};
for (var x in exports) {
(function(x) {
var original = exports[x];
if (typeof original === 'function') {
ret[x] = function() {
#if ASYNCIFY_DEBUG >= 2
err('ASYNCIFY: ' + ' '.repeat(Asyncify.exportCallStack.length) + ' try ' + x);
#endif
Asyncify.exportCallStack.push(x);
try {
return original.apply(null, arguments);
} finally {
if (!ABORT) {
var y = Asyncify.exportCallStack.pop();
assert(y === x);
#if ASYNCIFY_DEBUG >= 2
err('ASYNCIFY: ' + ' '.repeat(Asyncify.exportCallStack.length) + ' finally ' + x);
#endif
Asyncify.maybeStopUnwind();
}
}
};
} else {
ret[x] = original;
}
})(x);
}
return ret;
},
maybeStopUnwind: function() {
#if ASYNCIFY_DEBUG
err('ASYNCIFY: maybe stop unwind', Asyncify.exportCallStack);
#endif
if (Asyncify.currData &&
Asyncify.state === Asyncify.State.Unwinding &&
Asyncify.exportCallStack.length === 0) {
// We just finished unwinding.
#if ASYNCIFY_DEBUG
err('ASYNCIFY: stop unwind');
#endif
{{{ runtimeKeepalivePush(); }}}
Asyncify.state = Asyncify.State.Normal;
// Keep the runtime alive so that a re-wind can be done later.
runAndAbortIfError(Module['_asyncify_stop_unwind']);
if (typeof Fibers !== 'undefined') {
Fibers.trampoline();
}
if (Asyncify.afterUnwind) {
Asyncify.afterUnwind();
Asyncify.afterUnwind = null;
}
}
},
allocateData: function() {
// 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)
//
// 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.
// This struct is also defined as asyncify_data_t in emscripten/fiber.h
var ptr = _malloc({{{ C_STRUCTS.asyncify_data_s.__size__ }}} + Asyncify.StackSize);
Asyncify.setDataHeader(ptr, ptr + {{{ C_STRUCTS.asyncify_data_s.__size__ }}}, Asyncify.StackSize);
Asyncify.setDataRewindFunc(ptr);
return ptr;
},
setDataHeader: function(ptr, stack, stackSize) {
{{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.stack_ptr, 'stack', 'i32') }}};
{{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.stack_limit, 'stack + stackSize', 'i32') }}};
},
setDataRewindFunc: function(ptr) {
var bottomOfCallStack = Asyncify.exportCallStack[0];
#if ASYNCIFY_DEBUG >= 2
err('ASYNCIFY: setDataRewindFunc('+ptr+'), bottomOfCallStack is', bottomOfCallStack, new Error().stack);
#endif
var rewindId = Asyncify.getCallStackId(bottomOfCallStack);
{{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'rewindId', 'i32') }}};
},
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;
},
doRewind: function(ptr) {
var start = Asyncify.getDataRewindFunc(ptr);
#if ASYNCIFY_DEBUG
err('ASYNCIFY: start:', start);
#endif
// Once we have rewound and the stack we no longer need to artificially keep
// the runtime alive.
{{{ runtimeKeepalivePop(); }}}
return start();
},
handleSleep: function(startAsync) {
#if ASSERTIONS
assert(Asyncify.state !== Asyncify.State.Disabled, 'Asyncify cannot be done during or after the runtime exits');
#endif
if (ABORT) return;
#if ASYNCIFY_DEBUG
err('ASYNCIFY: handleSleep ' + Asyncify.state);
#endif
if (Asyncify.state === Asyncify.State.Normal) {
// Prepare to sleep. Call startAsync, and see what happens:
// if the code decided to call our callback synchronously,
// then no async operation was in fact begun, and we don't
// need to do anything.
var reachedCallback = false;
var reachedAfterCallback = false;
startAsync(function(handleSleepReturnValue) {
#if ASSERTIONS
assert(!handleSleepReturnValue || typeof handleSleepReturnValue === 'number' || typeof handleSleepReturnValue === 'boolean'); // old emterpretify API supported other stuff
#endif
if (ABORT) return;
Asyncify.handleSleepReturnValue = handleSleepReturnValue || 0;
reachedCallback = true;
if (!reachedAfterCallback) {
// We are happening synchronously, so no need for async.
return;
}
#if ASSERTIONS
// This async operation did not happen synchronously, so we did
// unwind. In that case there can be no compiled code on the stack,
// as it might break later operations (we can rewind ok now, but if
// we unwind again, we would unwind through the extra compiled code
// too).
assert(!Asyncify.exportCallStack.length, 'Waking up (starting to rewind) must be done from JS, without compiled code on the stack.');
#endif
#if ASYNCIFY_DEBUG
err('ASYNCIFY: start rewind ' + Asyncify.currData);
#endif
Asyncify.state = Asyncify.State.Rewinding;
runAndAbortIfError(function() { Module['_asyncify_start_rewind'](Asyncify.currData) });
if (typeof Browser !== 'undefined' && Browser.mainLoop.func) {
Browser.mainLoop.resume();
}
var asyncWasmReturnValue = Asyncify.doRewind(Asyncify.currData);
if (!Asyncify.currData) {
// All asynchronous execution has finished.
// `asyncWasmReturnValue` now contains the final
// return value of the exported async WASM function.
//
// Note: `asyncWasmReturnValue` is distinct from
// `Asyncify.handleSleepReturnValue`.
// `Asyncify.handleSleepReturnValue` contains the return
// value of the last C function to have executed
// `Asyncify.handleSleep()`, where as `asyncWasmReturnValue`
// contains the return value of the exported WASM function
// that may have called C functions that
// call `Asyncify.handleSleep()`.
var asyncFinalizers = Asyncify.asyncFinalizers;
Asyncify.asyncFinalizers = [];
asyncFinalizers.forEach(function(func) {
func(asyncWasmReturnValue);
});
}
});
reachedAfterCallback = true;
if (!reachedCallback) {
// A true async operation was begun; start a sleep.
Asyncify.state = Asyncify.State.Unwinding;
// TODO: reuse, don't alloc/free every sleep
Asyncify.currData = Asyncify.allocateData();
#if ASYNCIFY_DEBUG
err('ASYNCIFY: start unwind ' + Asyncify.currData);
#endif
runAndAbortIfError(function() { Module['_asyncify_start_unwind'](Asyncify.currData) });
if (typeof Browser !== 'undefined' && Browser.mainLoop.func) {
Browser.mainLoop.pause();
}
}
} else if (Asyncify.state === Asyncify.State.Rewinding) {
// Stop a resume.
#if ASYNCIFY_DEBUG
err('ASYNCIFY: stop rewind');
#endif
Asyncify.state = Asyncify.State.Normal;
runAndAbortIfError(Module['_asyncify_stop_rewind']);
_free(Asyncify.currData);
Asyncify.currData = null;
// Call all sleep callbacks now that the sleep-resume is all done.
Asyncify.sleepCallbacks.forEach(function(func) {
callUserCallback(func);
});
} else {
abort('invalid state: ' + Asyncify.state);
}
return Asyncify.handleSleepReturnValue;
},
// Unlike `handleSleep`, accepts a function returning a `Promise`
// and uses the fulfilled value instead of passing in a separate callback.
//
// This is particularly useful for native JS `async` functions where the
// returned value will "just work" and be passed back to C++.
handleAsync: function(startAsync) {
return Asyncify.handleSleep(function(wakeUp) {
// TODO: add error handling as a second param when handleSleep implements it.
startAsync().then(wakeUp);
});
},
},
emscripten_sleep__deps: ['$safeSetTimeout'],
emscripten_sleep: function(ms) {
Asyncify.handleSleep(function(wakeUp) {
safeSetTimeout(wakeUp, ms);
});
},
emscripten_wget__deps: ['$Browser', '$PATH_FS', '$FS'],
emscripten_wget: function(url, file) {
Asyncify.handleSleep(function(wakeUp) {
var _url = UTF8ToString(url);
var _file = UTF8ToString(file);
_file = PATH_FS.resolve(FS.cwd(), _file);
var destinationDirectory = PATH.dirname(_file);
FS.createPreloadedFile(
destinationDirectory,
PATH.basename(_file),
_url, true, true,
wakeUp,
wakeUp,
undefined, // dontCreateFile
undefined, // canOwn
function() { // preFinish
// if the destination directory does not yet exist, create it
FS.mkdirTree(destinationDirectory);
}
);
});
},
emscripten_wget_data__deps: ['$asyncLoad', 'malloc'],
emscripten_wget_data: function(url, pbuffer, pnum, perror) {
Asyncify.handleSleep(function(wakeUp) {
asyncLoad(UTF8ToString(url), function(byteArray) {
// can only allocate the buffer after the wakeUp, not during an asyncing
var buffer = _malloc(byteArray.length); // must be freed by caller!
HEAPU8.set(byteArray, buffer);
{{{ makeSetValue('pbuffer', 0, 'buffer', 'i32') }}};
{{{ makeSetValue('pnum', 0, 'byteArray.length', 'i32') }}};
{{{ makeSetValue('perror', 0, '0', 'i32') }}};
wakeUp();
}, function() {
{{{ makeSetValue('perror', 0, '1', 'i32') }}};
wakeUp();
}, true /* no need for run dependency, this is async but will not do any prepare etc. step */ );
});
},
emscripten_scan_registers: function(func) {
Asyncify.handleSleep(function(wakeUp) {
// We must first unwind, so things are spilled to the stack. We
// can resume right after unwinding, no need for a timeout.
Asyncify.afterUnwind = function() {
var stackBegin = Asyncify.currData + {{{ C_STRUCTS.asyncify_data_s.__size__ }}};
var stackEnd = HEAP32[Asyncify.currData >> 2];
{{{ makeDynCall('vii', 'func') }}}(stackBegin, stackEnd);
wakeUp();
};
});
},
emscripten_lazy_load_code: function() {
Asyncify.handleSleep(function(wakeUp) {
// Update the expected wasm binary file to be the lazy one.
wasmBinaryFile += '.lazy.wasm';
// Add a callback for when all run dependencies are fulfilled, which happens when async wasm loading is done.
dependenciesFulfilled = wakeUp;
// Load the new wasm.
asm = createWasm();
});
},
$Fibers__deps: ['$Asyncify'],
$Fibers: {
nextFiber: 0,
trampolineRunning: false,
trampoline: function() {
if (!Fibers.trampolineRunning && Fibers.nextFiber) {
Fibers.trampolineRunning = true;
do {
var fiber = Fibers.nextFiber;
Fibers.nextFiber = 0;
#if ASYNCIFY_DEBUG >= 2
err("ASYNCIFY/FIBER: trampoline jump into fiber", fiber, new Error().stack);
#endif
Fibers.finishContextSwitch(fiber);
} while (Fibers.nextFiber);
Fibers.trampolineRunning = false;
}
},
/*
* NOTE: This function is the asynchronous part of emscripten_fiber_swap.
*/
finishContextSwitch: function(newFiber) {
var stack_base = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_base, 'i32') }}};
var stack_max = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_limit, 'i32') }}};
_emscripten_stack_set_limits(stack_base, stack_max);
#if STACK_OVERFLOW_CHECK >= 2
Module['___set_stack_limits'](stack_base, stack_max);
#endif
stackRestore({{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, 'i32') }}});
var entryPoint = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.entry, 'i32') }}};
if (entryPoint !== 0) {
#if STACK_OVERFLOW_CHECK
writeStackCookie();
#endif
#if ASYNCIFY_DEBUG
err('ASYNCIFY/FIBER: entering fiber', newFiber, 'for the first time');
#endif
Asyncify.currData = null;
{{{ makeSetValue('newFiber', C_STRUCTS.emscripten_fiber_s.entry, 0, 'i32') }}};
var userData = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.user_data, 'i32') }}};
{{{ makeDynCall('vi', 'entryPoint') }}}(userData);
} else {
var asyncifyData = newFiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}};
Asyncify.currData = asyncifyData;
#if ASYNCIFY_DEBUG
err('ASYNCIFY/FIBER: start rewind', asyncifyData, '(resuming fiber', newFiber, ')');
#endif
Asyncify.state = Asyncify.State.Rewinding;
Module['_asyncify_start_rewind'](asyncifyData);
Asyncify.doRewind(asyncifyData);
}
},
},
emscripten_fiber_init__sig: 'viiiiiii',
emscripten_fiber_init__deps: ['$Asyncify'],
emscripten_fiber_init: function(fiber, entryPoint, userData, cStack, cStackSize, asyncStack, asyncStackSize) {
var cStackBase = cStack + cStackSize;
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_base, 'cStackBase', 'i32') }}};
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_limit, 'cStack', 'i32') }}};
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, 'cStackBase', 'i32') }}};
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.entry, 'entryPoint', 'i32') }}};
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.user_data, 'userData', 'i32') }}};
var asyncifyData = fiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}};
Asyncify.setDataHeader(asyncifyData, asyncStack, asyncStackSize);
},
emscripten_fiber_init_from_current_context__sig: 'vii',
emscripten_fiber_init_from_current_context__deps: ['$Asyncify'],
emscripten_fiber_init_from_current_context: function(fiber, asyncStack, asyncStackSize) {
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_base, '_emscripten_stack_get_base()', 'i32') }}};
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_limit, '_emscripten_stack_get_end()', 'i32') }}};
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.entry, 0, 'i32') }}};
var asyncifyData = fiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}};
Asyncify.setDataHeader(asyncifyData, asyncStack, asyncStackSize);
},
emscripten_fiber_swap__sig: 'vii',
emscripten_fiber_swap__deps: ["$Asyncify", "$Fibers"],
emscripten_fiber_swap: function(oldFiber, newFiber) {
if (ABORT) return;
#if ASYNCIFY_DEBUG
err('ASYNCIFY/FIBER: swap', oldFiber, '->', newFiber, 'state:', Asyncify.state);
#endif
if (Asyncify.state === Asyncify.State.Normal) {
Asyncify.state = Asyncify.State.Unwinding;
var asyncifyData = oldFiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}};
Asyncify.setDataRewindFunc(asyncifyData);
Asyncify.currData = asyncifyData;
#if ASYNCIFY_DEBUG
err('ASYNCIFY/FIBER: start unwind', asyncifyData);
#endif
Module['_asyncify_start_unwind'](asyncifyData);
var stackTop = stackSave();
{{{ makeSetValue('oldFiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, 'stackTop', 'i32') }}};
Fibers.nextFiber = newFiber;
} else {
#if ASSERTIONS
assert(Asyncify.state === Asyncify.State.Rewinding);
#endif
#if ASYNCIFY_DEBUG
err('ASYNCIFY/FIBER: stop rewind');
#endif
Asyncify.state = Asyncify.State.Normal;
Module['_asyncify_stop_rewind']();
Asyncify.currData = null;
}
},
#else // ASYNCIFY
emscripten_sleep: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_sleep';
},
emscripten_wget: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_wget';
},
emscripten_wget_data: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_wget_data';
},
emscripten_scan_registers: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_scan_registers';
},
emscripten_fiber_init: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_init';
},
emscripten_fiber_init_from_current_context: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_init_from_current_context';
},
emscripten_fiber_swap: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_swap';
},
#endif // ASYNCIFY
});
if (ASYNCIFY) {
DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('$Asyncify');
}