blob: 784625c0712955447bc0f770f708a047e9f4a9a0 [file] [log] [blame] [edit]
// Copyright 2014 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.
//
// Async support
//
// Two experiments in async support: ASYNCIFY, and EMTERPRETIFY_ASYNC
mergeInto(LibraryManager.library, {
// error handling
$runAndAbortIfError: function(func) {
try {
return func();
} catch (e) {
abort(e);
}
},
#if !WASM_BACKEND && ASYNCIFY
/*
* The layout of normal and async stack frames
*
* --------------------- <-- saved sp for the current function
* <last normal stack frame>
* ---------------------
* pointer to the previous frame <-- __async_cur_frame
* saved sp
* callback function <-- ctx, returned by alloc/reallloc, used by the program
* saved local variable1
* saved local variable2
* ...
* --------------------- <-- STACKTOP
*
*/
__async: 0, // whether a truly async function has been called
__async_unwind: 1, // whether to unwind the async stack frame
__async_retval: '{{{ makeStaticAlloc(2) }}}', // store the return value for async functions
__async_cur_frame: 0, // address to the current frame, which stores previous frame, stack pointer and async context
// __async_retval is not actually required in emscripten_async_resume
// but we want it included when ASYNCIFY is enabled
emscripten_async_resume__deps: ['__async', '__async_unwind', '__async_retval', '__async_cur_frame'],
emscripten_async_resume__sig: 'v',
emscripten_async_resume__asm: true,
emscripten_async_resume: function() {
var callback = 0;
___async = 0;
___async_unwind = 1;
while (1) {
if (!___async_cur_frame) return;
callback = {{{ makeGetValueAsm('___async_cur_frame', 8, 'i32') }}};
// the signature of callback is always vi
// the only argument is ctx
{{{ makeDynCall('vi') }}}(callback | 0, (___async_cur_frame + 8)|0);
if (___async) return; // that was an async call
if (!___async_unwind) {
// keep the async stack
___async_unwind = 1;
continue;
}
// unwind normal stack frame
stackRestore({{{ makeGetValueAsm('___async_cur_frame', 4, 'i32') }}});
// pop the last async stack frame
___async_cur_frame = {{{ makeGetValueAsm('___async_cur_frame', 0, 'i32') }}};
}
},
emscripten_sleep__deps: ['emscripten_async_resume', '$Browser'],
emscripten_sleep: function(ms) {
Module['setAsync'](); // tell the scheduler that we have a callback on hold
Browser.safeSetTimeout(_emscripten_async_resume, ms);
},
emscripten_alloc_async_context__deps: ['__async_cur_frame'],
emscripten_alloc_async_context__sig: 'iii',
emscripten_alloc_async_context__asm: true,
emscripten_alloc_async_context: function(len, sp) {
len = len|0;
sp = sp|0;
// len is the size of ctx
// we also need to store prev_frame, stack pointer before ctx
var new_frame = 0; new_frame = stackAlloc((len + 8)|0)|0;
// save sp
{{{ makeSetValueAsm('new_frame', 4, 'sp', 'i32') }}};
// link the frame with previous one
{{{ makeSetValueAsm('new_frame', 0, '___async_cur_frame', 'i32') }}};
___async_cur_frame = new_frame;
return (___async_cur_frame + 8)|0;
},
emscripten_realloc_async_context__deps: ['__async_cur_frame'],
emscripten_realloc_async_context__sig: 'ii',
emscripten_realloc_async_context__asm: true,
emscripten_realloc_async_context: function(len) {
len = len|0;
// assuming that we have on the stacktop
stackRestore(___async_cur_frame | 0);
return ((stackAlloc((len + 8)|0)|0) + 8)|0;
},
emscripten_free_async_context__deps: ['__async_cur_frame'],
emscripten_free_async_context__sig: 'vi',
emscripten_free_async_context__asm: true,
emscripten_free_async_context: function(ctx) {
// this function is called when a possibly async function turned out to be sync
// just undo a recent emscripten_alloc_async_context
ctx = ctx|0;
#if ASSERTIONS
if ((((___async_cur_frame + 8)|0) != (ctx|0))|0) abort();
#endif
stackRestore(___async_cur_frame | 0);
___async_cur_frame = {{{ makeGetValueAsm('___async_cur_frame', 0, 'i32') }}};
},
emscripten_check_async: true,
emscripten_do_not_unwind: true,
emscripten_do_not_unwind_async: true,
emscripten_get_async_return_value_addr__deps: ['__async_retval'],
emscripten_get_async_return_value_addr: true,
/*
* Layout of an ASYNCIFY coroutine structure
*
* 0 callee's async ctx
* 4 callee's STACKTOP
* 8 callee's STACK_MAX
* 12 my async ctx
* 16 my STACKTOP
* 20 my stack size
* 24 coroutine function
* 28 coroutine arg
* 32 my stack:
* ...
*/
emscripten_coroutine_create__sig: 'iiii',
emscripten_coroutine_create__asm: true,
emscripten_coroutine_create__deps: ['malloc', 'emscripten_alloc_async_context'],
emscripten_coroutine_create: function(f, arg, stack_size) {
f = f|0;
arg = arg|0;
stack_size = stack_size|0;
var coroutine = 0;
if ((stack_size|0) <= 0) stack_size = 4096;
coroutine = _malloc(stack_size + 32 | 0) | 0;
{{{ makeSetValueAsm('coroutine', 12, 0, 'i32') }}};
{{{ makeSetValueAsm('coroutine', 16, '(coroutine+32)', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 20, 'stack_size', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 24, 'f', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 28, 'arg', 'i32') }}};
return coroutine|0;
},
emscripten_coroutine_next__sig: 'ii',
emscripten_coroutine_next__asm: true,
emscripten_coroutine_next__deps: ['__async_cur_frame', '__async', 'emscripten_async_resume', 'free'],
emscripten_coroutine_next: function(coroutine) {
coroutine = coroutine|0;
var coroutine_not_finished = 0, temp = 0;
// switch context
{{{ makeSetValueAsm('coroutine', 0, '___async_cur_frame', 'i32') }}};
temp = stackSave() | 0;
{{{ makeSetValueAsm('coroutine', 4, 'temp', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 8, 'STACK_MAX', 'i32') }}};
___async_cur_frame = {{{ makeGetValueAsm('coroutine', 12, 'i32') }}};
stackRestore({{{ makeGetValueAsm('coroutine', 16, 'i32') }}});
STACK_MAX = coroutine + 32 + {{{ makeGetValueAsm('coroutine', 20, 'i32') }}} | 0;
if (!___async_cur_frame) {
// first run
{{{ makeDynCall('vi') }}}(
{{{ makeGetValueAsm('coroutine', 24, 'i32') }}},
{{{ makeGetValueAsm('coroutine', 28, 'i32') }}}
);
} else {
_emscripten_async_resume();
}
// switch context
{{{ makeSetValueAsm('coroutine', 12, '___async_cur_frame', 'i32') }}};
temp = stackSave() | 0;
{{{ makeSetValueAsm('coroutine', 16, 'temp', 'i32') }}};
___async_cur_frame = {{{ makeGetValueAsm('coroutine', 0, 'i32') }}};
stackRestore({{{ makeGetValueAsm('coroutine', 4, 'i32') }}});
STACK_MAX = {{{ makeGetValueAsm('coroutine', 8, 'i32') }}};
coroutine_not_finished = ___async;
if (!coroutine_not_finished) {
// coroutine has finished
_free(coroutine);
}
// coroutine may be created during an async function
// we do not want to affect the original async ctx
// strictly we should backup and restore ___async, ___async_retval and ___async_unwind
// but ___async=0 seems enough
___async = 0;
return coroutine_not_finished|0;
},
emscripten_yield__sig: 'v',
emscripten_yield__asm: true,
emscripten_yield: function() {
___async = 1;
},
emscripten_wget__deps: ['emscripten_async_resume', '$PATH_FS', '$Browser'],
emscripten_wget: function(url, file) {
var _url = UTF8ToString(url);
var _file = UTF8ToString(file);
_file = PATH_FS.resolve(FS.cwd(), _file);
Module['setAsync']();
noExitRuntime = true;
var destinationDirectory = PATH.dirname(_file);
FS.createPreloadedFile(
destinationDirectory,
PATH.basename(_file),
_url, true, true,
_emscripten_async_resume,
_emscripten_async_resume,
undefined, // dontCreateFile
undefined, // canOwn
function() { // preFinish
// if the destination directory does not yet exist, create it
FS.mkdirTree(destinationDirectory);
}
);
},
emscripten_fiber_init: function() {
throw 'emscripten_fiber_init is not implemented for fastcomp ASYNCIFY';
},
emscripten_fiber_init_from_current_context: function() {
throw 'emscripten_fiber_init_from_current_context is not implemented for fastcomp ASYNCIFY';
},
emscripten_fiber_swap: function() {
throw 'emscripten_fiber_swap is not implemented for fastcomp ASYNCIFY';
},
#else // !WASM_BACKEND && ASYNCIFY
#if EMTERPRETIFY_ASYNC
// Emterpreter sync=>async support
//
// The idea here is that almost all interpreter frames are already on the stack (the
// emterpreter stack), so it's easy to save a callstack and reload it, we just need
// to also save the pc.
// Saving state keeps the pc right before the current call. That means when we reload,
// we are going to re-call in the exact same way as before - including the final call
// to the async function here! Therefore sleep etc. detect the state, so they know
// if they are the first call or the second. The second typically does nothing, but
// if there is a return value it could return it, etc.
$EmterpreterAsync__deps: ['$Browser'],
$EmterpreterAsync: {
initted: false,
state: 0, // 0 - Nothing/normal.
// 1 - Sleeping: This is set when we start to save the stack, and continues through the
// sleep, until we start to restore the stack.
// If we enter a function while in this state - that is, before we
// start to restore the stack, or in other words if we call a function while
// sleeping - then we switch to state 3, "definitely sleeping". That lets us know
// we are no longer saving the stack. How this works is that while we save the stack
// we don't hit any function entries, so they are valid places to switch to state 3,
// and then when we reach code later that checks if we need to save the stack, we
// know we don't need to.
// 2 - Restoring the stack: On the way to resume normal execution.
// 3 - Definitely sleeping, that is, sleeping and after saving the stack.
saveStack: '',
yieldCallbacks: [],
postAsync: null,
restartFunc: null, // During an async call started with ccall, this contains the function generated
// by the compiler that calls emterpret and returns the return value. If we call
// emterpret directly, we don't get the return value back (and we can't just read
// it from the stack because we don't know the correct type). Note that only
// a single one (and not a stack) is required because only one async ccall can be
// in flight at once.
asyncFinalizers: [], // functions to run when *all* asynchronicity is done
ensureInit: function() {
if (this.initted) return;
this.initted = true;
#if ASSERTIONS
abortDecorators.push(function(output, what) {
if (EmterpreterAsync.state === 1 || EmterpreterAsync.state === 2) {
return output + '\nThis error happened during an emterpreter-async operation. Was there non-emterpreted code on the stack during save (which is unallowed)? If so, you may want to adjust EMTERPRETIFY_BLACKLIST, EMTERPRETIFY_WHITELIST. For reference, this is what the stack looked like when we tried to save it: ' + [EmterpreterAsync.state, EmterpreterAsync.saveStack];
}
return output;
});
#endif
},
setState: function(s) {
this.ensureInit();
this.state = s;
Module['setAsyncState'](s);
},
handle: function(doAsyncOp, yieldDuring) {
noExitRuntime = true;
if (EmterpreterAsync.state === 0) {
// save the stack we want to resume. this lets other code run in between
// XXX this assumes that this stack top never ever leak! exceptions might violate that
var stack = new Int32Array(HEAP32.subarray(EMTSTACKTOP>>2, Module['emtStackSave']()>>2));
#if ASSERTIONS
var stacktop = Module['stackSave']();
#endif
var resumedCallbacksForYield = false;
function resumeCallbacksForYield() {
if (resumedCallbacksForYield) return;
resumedCallbacksForYield = true;
// allow async callbacks, and also make sure to call the specified yield callbacks. we must
// do this when nothing is on the stack, i.e. after it unwound
EmterpreterAsync.yieldCallbacks.forEach(function(func) {
func();
});
Browser.resumeAsyncCallbacks(); // if we were paused (e.g. we are after a sleep), then since we are now yielding, it is safe to call callbacks
}
var callingDoAsyncOp = 1; // if resume is called synchronously - during the doAsyncOp - we must make it truly async, for consistency
doAsyncOp(function resume(post) {
if (ABORT) {
return;
}
if (callingDoAsyncOp) {
assert(callingDoAsyncOp === 1); // avoid infinite recursion
callingDoAsyncOp++;
setTimeout(function() {
resume(post);
}, 0);
return;
}
assert(EmterpreterAsync.state === 1 || EmterpreterAsync.state === 3);
EmterpreterAsync.setState(3);
if (yieldDuring) {
resumeCallbacksForYield();
}
// copy the stack back in and resume
HEAP32.set(stack, EMTSTACKTOP>>2);
#if ASSERTIONS
assert(stacktop === Module['stackSave']()); // nothing should have modified the stack meanwhile
#endif
// we are now starting to restore the stack
EmterpreterAsync.setState(2);
// Resume the main loop
if (Browser.mainLoop.func) {
Browser.mainLoop.resume();
}
assert(!EmterpreterAsync.postAsync);
EmterpreterAsync.postAsync = post || null;
var asyncReturnValue;
if (!EmterpreterAsync.restartFunc) {
// pc of the first function, from which we can reconstruct the rest, is at position 0 on the stack
Module['emterpret'](stack[0]);
} else {
// the restartFunc knows how to emterpret the proper function, and also returns the return value
asyncReturnValue = EmterpreterAsync.restartFunc();
}
if (!yieldDuring && EmterpreterAsync.state === 0) {
// if we did *not* do another async operation, then we know that nothing is conceptually on the stack now, and we can re-allow async callbacks as well as run the queued ones right now
Browser.resumeAsyncCallbacks();
}
if (EmterpreterAsync.state === 0) {
// All async operations have concluded.
// In particular, if we were in an async ccall, we have
// consumed the restartFunc and can reset it to null.
EmterpreterAsync.restartFunc = null;
// The async finalizers can run now, after all async operations.
var asyncFinalizers = EmterpreterAsync.asyncFinalizers;
EmterpreterAsync.asyncFinalizers = [];
asyncFinalizers.forEach(function(func) {
func(asyncReturnValue);
});
}
});
callingDoAsyncOp = 0;
EmterpreterAsync.setState(1);
#if ASSERTIONS
EmterpreterAsync.saveStack = new Error().stack; // we can't call stackTrace() as it calls compiled code
#endif
// Pause the main loop, until we resume
if (Browser.mainLoop.func) {
Browser.mainLoop.pause();
}
if (yieldDuring) {
// do this when we are not on the stack, i.e., the stack unwound. we might be too late, in which case we do it in resume()
setTimeout(function() {
resumeCallbacksForYield();
}, 0);
} else {
Browser.pauseAsyncCallbacks();
}
} else {
// nothing to do here, the stack was just recreated. reset the state.
assert(EmterpreterAsync.state === 2);
EmterpreterAsync.setState(0);
if (EmterpreterAsync.postAsync) {
var ret = EmterpreterAsync.postAsync();
EmterpreterAsync.postAsync = null;
return ret;
}
}
}
},
emscripten_sleep__deps: ['$EmterpreterAsync'],
emscripten_sleep: function(ms) {
EmterpreterAsync.handle(function(resume) {
setTimeout(function() {
// do this manually; we can't call into Browser.safeSetTimeout, because that is paused/resumed!
resume();
}, ms);
});
},
emscripten_sleep_with_yield__deps: ['$EmterpreterAsync'],
emscripten_sleep_with_yield: function(ms) {
EmterpreterAsync.handle(function(resume) {
Browser.safeSetTimeout(resume, ms);
}, true);
},
emscripten_wget__deps: ['$EmterpreterAsync', '$PATH_FS', '$FS', '$Browser'],
emscripten_wget: function(url, file) {
EmterpreterAsync.handle(function(resume) {
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,
resume,
resume,
undefined, // dontCreateFile
undefined, // canOwn
function() { // preFinish
// if the destination directory does not yet exist, create it
FS.mkdirTree(destinationDirectory);
}
);
});
},
emscripten_wget_data__deps: ['$EmterpreterAsync', '$Browser'],
emscripten_wget_data: function(url, pbuffer, pnum, perror) {
EmterpreterAsync.handle(function(resume) {
Browser.asyncLoad(UTF8ToString(url), function(byteArray) {
resume(function() {
// can only allocate the buffer after the resume, not during an asyncing
var buffer = _malloc(byteArray.length); // must be freed by caller!
HEAPU8.set(byteArray, buffer);
{{{ makeSetValueAsm('pbuffer', 0, 'buffer', 'i32') }}};
{{{ makeSetValueAsm('pnum', 0, 'byteArray.length', 'i32') }}};
{{{ makeSetValueAsm('perror', 0, '0', 'i32') }}};
});
}, function() {
{{{ makeSetValueAsm('perror', 0, '1', 'i32') }}};
resume();
}, true /* no need for run dependency, this is async but will not do any prepare etc. step */ );
});
},
/*
* Layout of an EMTERPRETIFY_ASYNC coroutine structure:
*
* 0 callee's EMTSTACKTOP
* 4 callee's EMTSTACKTOP from the compiled code
* 8 callee's EMT_STACK_MAX
* 12 my EMTSTACKTOP
* 16 my EMTSTACKTOP from the compiled code
* 20 my EMT_STACK_MAX
* 24 coroutine function (0 if already started)
* 28 coroutine arg
* 32 my stack:
* ...
*/
emscripten_coroutine_create__sig: 'iiii',
emscripten_coroutine_create__asm: true,
emscripten_coroutine_create__deps: ['malloc'],
emscripten_coroutine_create: function(f, arg, stack_size) {
f = f|0;
arg = arg|0;
stack_size = stack_size|0;
var coroutine = 0;
if ((stack_size|0) <= 0) stack_size = 4096;
coroutine = _malloc(stack_size + 32 | 0) | 0;
{{{ makeSetValueAsm('coroutine', 12, '(coroutine+32)', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 16, '(coroutine+32)', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 20, '(coroutine+32+stack_size)', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 24, 'f', 'i32') }}};
{{{ makeSetValueAsm('coroutine', 28, 'arg', 'i32') }}};
return coroutine|0;
},
emscripten_coroutine_next__sig: 'ii',
emscripten_coroutine_next__deps: ['$EmterpreterAsync', 'free'],
emscripten_coroutine_next: function(coroutine) {
// this is a rewritten emscripten_coroutine_next function from ASYNCIFY
coroutine = coroutine|0;
var temp = 0, func = 0, funcArg = 0, coroutine_not_finished = 0;
// switch context
// TODO Save EMTSTACKTOP to EMTSTACK_BASE during startup and use it instead
{{{ makeSetValueAsm('coroutine', 0, 'EMTSTACKTOP', 'i32') }}};
temp = Module['emtStackSave']();
{{{ makeSetValueAsm('coroutine', 4, 'temp', 'i32') }}};
temp = Module['getEmtStackMax']();
{{{ makeSetValueAsm('coroutine', 8, 'temp', 'i32') }}};
EMTSTACKTOP = {{{ makeGetValueAsm('coroutine', 12, 'i32') }}};
Module['emtStackRestore']({{{ makeGetValueAsm('coroutine', 16, 'i32') }}});
Module['setEmtStackMax']({{{ makeGetValueAsm('coroutine', 20, 'i32') }}});
func = {{{ makeGetValueAsm('coroutine', 24, 'i32') }}};
if (func !== 0) {
// unset func
{{{ makeSetValueAsm('coroutine', 24, 0, 'i32') }}};
// first run
funcArg = {{{ makeGetValueAsm('coroutine', 28, 'i32') }}};
{{{ makeDynCall('vi') }}}(func, funcArg);
} else {
EmterpreterAsync.setState(2);
Module['emterpret']({{{ makeGetValue('EMTSTACKTOP', 0, 'i32')}}});
}
coroutine_not_finished = EmterpreterAsync.state !== 0;
EmterpreterAsync.setState(0);
// switch context
{{{ makeSetValueAsm('coroutine', 12, 'EMTSTACKTOP', 'i32') }}}; // cannot change?
temp = Module['emtStackSave']();
{{{ makeSetValueAsm('coroutine', 16, 'temp', 'i32') }}};
temp = Module['getEmtStackMax']();
{{{ makeSetValueAsm('coroutine', 20, 'temp', 'i32') }}}; // cannot change?
EMTSTACKTOP = {{{ makeGetValueAsm('coroutine', 0, 'i32') }}};
Module['emtStackRestore']({{{ makeGetValueAsm('coroutine', 4, 'i32') }}});
Module['setEmtStackMax']({{{ makeGetValueAsm('coroutine', 8, 'i32') }}});
if (!coroutine_not_finished) {
_free(coroutine);
}
return coroutine_not_finished|0;
},
emscripten_yield__sig: 'v',
emscripten_yield__deps: ['$EmterpreterAsync'],
emscripten_yield: function() {
if (EmterpreterAsync.state === 2) {
// re-entering after yield
EmterpreterAsync.setState(0);
} else {
EmterpreterAsync.setState(1);
}
},
emscripten_fiber_init: function() {
throw 'emscripten_fiber_init is not implemented for EMTERPRETIFY_ASYNC';
},
emscripten_fiber_init_from_current_context: function() {
throw 'emscripten_fiber_init_from_current_context is not implemented for EMTERPRETIFY_ASYNC';
},
emscripten_fiber_swap: function() {
throw 'emscripten_fiber_swap is not implemented for EMTERPRETIFY_ASYNC';
},
#else // EMTERPRETIFY_ASYNC
#if WASM_BACKEND && ASYNCIFY
$Asyncify__deps: ['$Browser', '$runAndAbortIfError'],
$Asyncify: {
State: {
Normal: 0,
Unwinding: 1,
Rewinding: 2
},
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: {},
callStackIdToFunc: {},
#if ASYNCIFY_LAZY_LOAD_CODE
callStackIdToName: {},
#endif
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;
#if ASYNCIFY_LAZY_LOAD_CODE
Asyncify.callStackIdToName[id] = funcName;
#else
Asyncify.callStackIdToFunc[id] = Module['asm'][funcName];
#endif
}
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() {
Asyncify.exportCallStack.push(x);
#if ASYNCIFY_DEBUG >= 2
err('ASYNCIFY: ' + ' '.repeat(Asyncify.exportCallStack.length) + ' try', x);
#endif
try {
return original.apply(null, arguments);
} finally {
if (ABORT) return;
var y = Asyncify.exportCallStack.pop(x);
assert(y === x);
#if ASYNCIFY_DEBUG >= 2
err('ASYNCIFY: ' + ' '.repeat(Asyncify.exportCallStack.length + 1) + ' finally', x);
#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
Asyncify.state = Asyncify.State.Normal;
runAndAbortIfError(Module['_asyncify_stop_unwind']);
if (typeof Fibers !== 'undefined') {
Fibers.trampoline();
}
if (Asyncify.afterUnwind) {
Asyncify.afterUnwind();
Asyncify.afterUnwind = null;
}
}
}
};
} else {
ret[x] = original;
}
})(x);
}
return ret;
},
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 (callStackIdToFunc[id] == 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 func = Asyncify.callStackIdToFunc[id];
#if ASYNCIFY_LAZY_LOAD_CODE
if (func === undefined) {
func = Module['asm'][Asyncify.callStackIdToName[id]];
Asyncify.callStackIdToFunc[id] = func;
}
#endif
return func;
},
handleSleep: function(startAsync) {
if (ABORT) return;
noExitRuntime = true;
#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'); // 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 (Browser.mainLoop.func) {
Browser.mainLoop.resume();
}
var start = Asyncify.getDataRewindFunc(Asyncify.currData);
#if ASYNCIFY_DEBUG
err('ASYNCIFY: start:', start);
#endif
var asyncWasmReturnValue = start();
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 (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) {
func();
});
} else {
abort('invalid state: ' + Asyncify.state);
}
return Asyncify.handleSleepReturnValue;
}
},
emscripten_sleep: function(ms) {
Asyncify.handleSleep(function(wakeUp) {
Browser.safeSetTimeout(wakeUp, ms);
});
},
emscripten_wget__deps: ['$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: function(url, pbuffer, pnum, perror) {
Asyncify.handleSleep(function(wakeUp) {
Browser.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);
{{{ makeSetValueAsm('pbuffer', 0, 'buffer', 'i32') }}};
{{{ makeSetValueAsm('pnum', 0, 'byteArray.length', 'i32') }}};
{{{ makeSetValueAsm('perror', 0, '0', 'i32') }}};
wakeUp();
}, function() {
{{{ makeSetValueAsm('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() {
{{{ makeDynCall('vii') }}}(func, Asyncify.currData + 8, HEAP32[Asyncify.currData >> 2]);
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) {
STACK_BASE = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_base, 'i32') }}};
STACK_MAX = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_limit, 'i32') }}};
#if WASM_BACKEND && STACK_OVERFLOW_CHECK >= 2
Module['___set_stack_limit'](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 + 20;
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);
var start = Asyncify.getDataRewindFunc(asyncifyData);
#if ASYNCIFY_DEBUG
err('ASYNCIFY/FIBER: start: ' + start);
#endif
start();
}
},
},
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, 'STACK_BASE', 'i32') }}};
{{{ makeSetValue('fiber', C_STRUCTS.emscripten_fiber_s.stack_limit, 'STACK_MAX', '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;
noExitRuntime = true;
#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;
}
},
emscripten_coroutine_create: function() {
throw 'emscripten_coroutine_create has been removed. Please use the Fibers API';
},
emscripten_coroutine_next: function() {
throw 'emscripten_coroutine_next has been removed. Please use the Fibers API';
},
emscripten_yield: function() {
throw 'emscripten_yield has been removed. Please use the Fibers API';
},
#else // ASYNCIFY
emscripten_sleep: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_sleep';
},
emscripten_coroutine_create: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_coroutine_create';
},
emscripten_coroutine_next: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_coroutine_next';
},
emscripten_yield: function() {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_yield';
},
emscripten_wget: function(url, file) {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_wget';
},
emscripten_wget_data: function(url, file) {
throw 'Please compile your program with async support in order to use asynchronous operations like emscripten_wget_data';
},
emscripten_scan_registers: function(url, file) {
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
#endif // EMTERPRETIFY_ASYNC
#endif // ASYNCIFY
});
if (EMTERPRETIFY_ASYNC && !EMTERPRETIFY) {
error('You must enable EMTERPRETIFY to use EMTERPRETIFY_ASYNC');
}
if (WASM_BACKEND && ASYNCIFY) {
DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('$Asyncify');
}