| // 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'); |
| } |