blob: f379b81cfab8da6edac9e8f33a4986c4db14ed3e [file] [log] [blame]
// Async support
//
// Two experiments in async support: ASYNCIFY, and EMTERPRETIFY_ASYNC
mergeInto(LibraryManager.library, {
#if 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
assert((((___async_cur_frame + 8)|0) == (ctx|0))|0);
#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', '$Browser'],
emscripten_wget: function(url, file) {
var _url = Pointer_stringify(url);
var _file = Pointer_stringify(file);
_file = PATH.resolve(FS.cwd(), _file);
Module['setAsync']();
Module['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);
}
);
},
#else // 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 - saving the stack: functions should all be in emterpreter, and should all save&exit/return
// 2 - restoring the stack: functions should all be in emterpreter, and should all restore&continue
// 3 - during sleep. this is between 1 and 2. at this time it is ok to call yield funcs
saveStack: '',
yieldCallbacks: [],
postAsync: null,
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 !== 0) {
return output + '\nThis error happened during an emterpreter-async save or load of the stack. Was there non-emterpreted code on the stack during save (which is unallowed)? You may want to adjust EMTERPRETIFY_BLACKLIST, EMTERPRETIFY_WHITELIST.\nThis 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) {
Module['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));
var stacktop = Module['stackSave']();
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
EmterpreterAsync.setState(2);
// Resume the main loop
if (Browser.mainLoop.func) {
Browser.mainLoop.resume();
}
assert(!EmterpreterAsync.postAsync);
EmterpreterAsync.postAsync = post || null;
Module['emterpret'](stack[0]); // pc of the first function, from which we can reconstruct the rest, is at position 0 on the stack
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) {
EmterpreterAsync.asyncFinalizers.forEach(function(func) {
func();
});
EmterpreterAsync.asyncFinalizers.length = 0;
}
});
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', '$Browser'],
emscripten_wget: function(url, file) {
EmterpreterAsync.handle(function(resume) {
var _url = Pointer_stringify(url);
var _file = Pointer_stringify(file);
_file = PATH.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(Pointer_stringify(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;
{{{ 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);
}
},
#else // EMTERPRETIFY_ASYNC
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';
},
#endif // EMTERPRETIFY_ASYNC
#endif // ASYNCIFY
});
if (EMTERPRETIFY_ASYNC && !EMTERPRETIFY) {
error('You must enable EMTERPRETIFY to use EMTERPRETIFY_ASYNC');
}