| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| addToLibrary({ |
| |
|
|
| $runAndAbortIfError: (func) => { |
| try { |
| return func(); |
| } catch (e) { |
| abort(e); |
| } |
| }, |
|
|
| #if ASYNCIFY |
| $Asyncify__deps: ['$runAndAbortIfError', '$callUserCallback', |
| #if ASSERTIONS |
| '$createNamedFunction', |
| #endif |
| #if !MINIMAL_RUNTIME |
| '$runtimeKeepalivePush', '$runtimeKeepalivePop', |
| #endif |
| #if ASYNCIFY == 1 |
| |
| 'malloc', 'free', |
| #endif |
| ], |
|
|
| $Asyncify: { |
| |
| |
| |
| #if ASYNCIFY == 1 && MEMORY64 |
| rewindArguments: new Map(), |
| #endif |
| instrumentWasmImports(imports) { |
| #if EMBIND_GEN_MODE |
| |
| return imports; |
| #endif |
| #if ASYNCIFY_DEBUG |
| dbg('asyncify instrumenting imports'); |
| #endif |
| #if ASSERTIONS && ASYNCIFY == 2 |
| assert('Suspending' in WebAssembly, 'JSPI not supported by current environment. Perhaps it needs to be enabled via flags?'); |
| #endif |
| var importPattern = {{{ new RegExp(`^(${ASYNCIFY_IMPORTS_EXCEPT_JS_LIBS.map(x => x.split('.')[1]).join('|').replace(/\*/g, '.*')})$`) }}}; |
|
|
| for (let [x, original] of Object.entries(imports)) { |
| if (typeof original == 'function') { |
| let isAsyncifyImport = original.isAsync || importPattern.test(x); |
| #if ASYNCIFY == 2 |
| |
| if (isAsyncifyImport) { |
| #if ASYNCIFY_DEBUG |
| dbg('asyncify: suspendOnReturnedPromise for', x, original); |
| #endif |
| imports[x] = original = new WebAssembly.Suspending(original); |
| } |
| #endif |
| #if ASSERTIONS && ASYNCIFY != 2 |
| imports[x] = (...args) => { |
| var originalAsyncifyState = Asyncify.state; |
| try { |
| return original(...args); |
| } finally { |
| |
| |
| |
| |
| |
| var changedToDisabled = |
| originalAsyncifyState === Asyncify.State.Normal && |
| Asyncify.state === Asyncify.State.Disabled; |
| |
| |
| var ignoredInvoke = x.startsWith('invoke_') && |
| {{{ !ASYNCIFY_IGNORE_INDIRECT }}}; |
| if (Asyncify.state !== originalAsyncifyState && |
| !isAsyncifyImport && |
| !changedToDisabled && |
| !ignoredInvoke) { |
| abort(`import ${x} was not in ASYNCIFY_IMPORTS, but changed the state`); |
| } |
| } |
| }; |
| #if MAIN_MODULE |
| |
| |
| |
| imports[x].sig = original.sig; |
| #endif |
| #endif |
| } |
| } |
| }, |
| #if ASYNCIFY == 1 && MEMORY64 |
| saveRewindArguments(func, passedArguments) { |
| return Asyncify.rewindArguments.set(func, Array.from(passedArguments)); |
| }, |
| restoreRewindArguments(func) { |
| #if ASSERTIONS |
| assert(Asyncify.rewindArguments.has(func)); |
| #endif |
| return Asyncify.rewindArguments.get(func); |
| }, |
| #endif |
|
|
| #if ASYNCIFY == 1 |
| instrumentFunction(original) { |
| var wrapper = (...args) => { |
| #if ASYNCIFY_DEBUG >= 2 |
| dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} try ${original}`); |
| #endif |
| Asyncify.exportCallStack.push(original); |
| try { |
| #if MEMORY64 |
| Asyncify.saveRewindArguments(original, args); |
| #endif |
| return original(...args); |
| } finally { |
| if (!ABORT) { |
| var top = Asyncify.exportCallStack.pop(); |
| #if ASSERTIONS |
| assert(top === original); |
| #endif |
| #if ASYNCIFY_DEBUG >= 2 |
| dbg(`ASYNCIFY: ${' '.repeat(Asyncify.exportCallStack.length)} finally ${original}`); |
| #endif |
| Asyncify.maybeStopUnwind(); |
| } |
| } |
| }; |
| Asyncify.funcWrappers.set(original, wrapper); |
| #if MAIN_MODULE |
| wrapper.orig = original; |
| #endif |
| #if ASSERTIONS |
| wrapper = createNamedFunction(`__asyncify_wrapper_${original.name}`, wrapper); |
| #endif |
| return wrapper; |
| }, |
| #endif |
|
|
| instrumentWasmExports(exports) { |
| #if EMBIND_GEN_MODE |
| |
| return exports; |
| #endif |
| #if ASYNCIFY_DEBUG |
| dbg('asyncify instrumenting exports'); |
| #endif |
| #if ASYNCIFY == 2 |
| var exportPattern = {{{ new RegExp(`^(${ASYNCIFY_EXPORTS.join('|').replace(/\*/g, '.*')})$`) }}}; |
| Asyncify.asyncExports = new Set(); |
| #endif |
| var ret = {}; |
| for (let [x, original] of Object.entries(exports)) { |
| if (typeof original == 'function') { |
| #if ASYNCIFY == 2 |
| |
| let isAsyncifyExport = exportPattern.test(x); |
| if (isAsyncifyExport) { |
| Asyncify.asyncExports.add(original); |
| original = Asyncify.makeAsyncFunction(original); |
| } |
| ret[x] = original; |
| #else |
| var wrapper = Asyncify.instrumentFunction(original); |
| ret[x] = wrapper; |
| #endif |
| } else { |
| ret[x] = original; |
| } |
| } |
| return ret; |
| }, |
|
|
| #if ASYNCIFY == 1 |
| |
| |
| |
| State: { |
| Normal: 0, |
| Unwinding: 1, |
| Rewinding: 2, |
| Disabled: 3, |
| }, |
| state: 0, |
| StackSize: {{{ ASYNCIFY_STACK_SIZE }}}, |
| currData: null, |
| |
| |
| |
| |
| handleSleepReturnValue: 0, |
| |
| |
| |
| |
| exportCallStack: [], |
| callstackFuncToId: new Map(), |
| callStackIdToFunc: new Map(), |
| |
| funcWrappers: new Map(), |
| callStackId: 0, |
| asyncPromiseHandlers: null, |
| sleepCallbacks: [], |
|
|
| getCallStackId(func) { |
| #if ASSERTIONS |
| assert(func); |
| #endif |
| if (!Asyncify.callstackFuncToId.has(func)) { |
| var id = Asyncify.callStackId++; |
| Asyncify.callstackFuncToId.set(func, id); |
| Asyncify.callStackIdToFunc.set(id, func); |
| } |
| return Asyncify.callstackFuncToId.get(func); |
| }, |
|
|
| maybeStopUnwind() { |
| #if ASYNCIFY_DEBUG |
| dbg('ASYNCIFY: maybe stop unwind', Asyncify.exportCallStack); |
| #endif |
| if (Asyncify.currData && |
| Asyncify.state === Asyncify.State.Unwinding && |
| Asyncify.exportCallStack.length === 0) { |
| |
| |
| |
| |
| |
| Asyncify.state = Asyncify.State.Normal; |
| #if ASYNCIFY_DEBUG |
| dbg('ASYNCIFY: stop unwind'); |
| #endif |
| {{{ runtimeKeepalivePush(); }}} |
| |
| runAndAbortIfError(_asyncify_stop_unwind); |
| if (typeof Fibers != 'undefined') { |
| Fibers.trampoline(); |
| } |
| } |
| }, |
|
|
| whenDone() { |
| #if ASSERTIONS |
| assert(Asyncify.currData, 'tried to wait for an async operation when none is in progress'); |
| assert(!Asyncify.asyncPromiseHandlers, 'cannot have multiple async operations in flight at once'); |
| #endif |
| return new Promise((resolve, reject) => { |
| Asyncify.asyncPromiseHandlers = { resolve, reject }; |
| }); |
| }, |
|
|
| allocateData() { |
| |
| |
| |
| |
| |
| |
| |
| |
| 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(ptr, stack, stackSize) { |
| {{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.stack_ptr, 'stack', '*') }}}; |
| {{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.stack_limit, 'stack + stackSize', '*') }}}; |
| }, |
|
|
| setDataRewindFunc(ptr) { |
| var bottomOfCallStack = Asyncify.exportCallStack[0]; |
| #if ASYNCIFY_DEBUG >= 2 |
| dbg(`ASYNCIFY: setDataRewindFunc(${ptr}), bottomOfCallStack is`, bottomOfCallStack, new Error().stack); |
| #endif |
| #if ASSERTIONS |
| assert(bottomOfCallStack, 'exportCallStack is empty'); |
| #endif |
| var rewindId = Asyncify.getCallStackId(bottomOfCallStack); |
| {{{ makeSetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'rewindId', 'i32') }}}; |
| }, |
|
|
| getDataRewindFunc(ptr) { |
| var id = {{{ makeGetValue('ptr', C_STRUCTS.asyncify_data_s.rewind_id, 'i32') }}}; |
| var func = Asyncify.callStackIdToFunc.get(id); |
| #if ASSERTIONS |
| assert(func, `id ${id} not found in callStackIdToFunc`); |
| #endif |
| return func; |
| }, |
|
|
| doRewind(ptr) { |
| var original = Asyncify.getDataRewindFunc(ptr); |
| #if ASYNCIFY_DEBUG |
| dbg('ASYNCIFY: doRewind:', original); |
| #endif |
| var func = Asyncify.funcWrappers.get(original); |
| #if ASSERTIONS |
| assert(original); |
| assert(func); |
| #endif |
| |
| |
| {{{ runtimeKeepalivePop(); }}} |
| #if MEMORY64 |
| |
| |
| |
| |
| func = func.bind(0, ...Asyncify.restoreRewindArguments(original)); |
| #endif |
| return callUserCallback(func); |
| }, |
|
|
| |
| |
| |
| handleSleep(startAsync) { |
| #if ASSERTIONS |
| assert(Asyncify.state !== Asyncify.State.Disabled, 'handleSleep called after Asyncify was shut down'); |
| #endif |
| if (ABORT) return; |
| #if ASYNCIFY_DEBUG |
| dbg(`ASYNCIFY: handleSleep ${Asyncify.state}`); |
| #endif |
| if (Asyncify.state === Asyncify.State.Normal) { |
| |
| |
| |
| |
| var reachedCallback = false; |
| var reachedAfterCallback = false; |
| startAsync((handleSleepReturnValue = 0) => { |
| #if ASSERTIONS |
| |
| assert(['undefined', 'number', 'boolean', 'bigint'].includes(typeof handleSleepReturnValue), `invalid type for handleSleepReturnValue: '${typeof handleSleepReturnValue}'`); |
| #endif |
| if (ABORT) return; |
| Asyncify.handleSleepReturnValue = handleSleepReturnValue; |
| reachedCallback = true; |
| if (!reachedAfterCallback) { |
| |
| return; |
| } |
| #if ASSERTIONS |
| |
| |
| |
| |
| |
| assert(!Asyncify.exportCallStack.length, 'waking up (starting to rewind) must be done from JS, without compiled code on the stack'); |
| #endif |
| #if ASYNCIFY_DEBUG |
| dbg(`ASYNCIFY: start rewind ${Asyncify.currData}`); |
| #endif |
| Asyncify.state = Asyncify.State.Rewinding; |
| runAndAbortIfError(() => _asyncify_start_rewind(Asyncify.currData)); |
| if (typeof MainLoop != 'undefined' && MainLoop.func) { |
| MainLoop.resume(); |
| } |
| var asyncWasmReturnValue, isError = false; |
| try { |
| asyncWasmReturnValue = Asyncify.doRewind(Asyncify.currData); |
| } catch (err) { |
| asyncWasmReturnValue = err; |
| isError = true; |
| } |
| |
| var handled = false; |
| if (!Asyncify.currData) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| var asyncPromiseHandlers = Asyncify.asyncPromiseHandlers; |
| if (asyncPromiseHandlers) { |
| Asyncify.asyncPromiseHandlers = null; |
| (isError ? asyncPromiseHandlers.reject : asyncPromiseHandlers.resolve)(asyncWasmReturnValue); |
| handled = true; |
| } |
| } |
| if (isError && !handled) { |
| |
| |
| |
| throw asyncWasmReturnValue; |
| } |
| }); |
| reachedAfterCallback = true; |
| if (!reachedCallback) { |
| |
| Asyncify.state = Asyncify.State.Unwinding; |
| |
| Asyncify.currData = Asyncify.allocateData(); |
| #if ASYNCIFY_DEBUG |
| dbg(`ASYNCIFY: start unwind ${Asyncify.currData}`); |
| #endif |
| if (typeof MainLoop != 'undefined' && MainLoop.func) { |
| MainLoop.pause(); |
| } |
| runAndAbortIfError(() => _asyncify_start_unwind(Asyncify.currData)); |
| } |
| } else if (Asyncify.state === Asyncify.State.Rewinding) { |
| |
| #if ASYNCIFY_DEBUG |
| dbg('ASYNCIFY: stop rewind'); |
| #endif |
| Asyncify.state = Asyncify.State.Normal; |
| runAndAbortIfError(_asyncify_stop_rewind); |
| _free(Asyncify.currData); |
| Asyncify.currData = null; |
| |
| Asyncify.sleepCallbacks.forEach(callUserCallback); |
| } else { |
| abort(`invalid state: ${Asyncify.state}`); |
| } |
| return Asyncify.handleSleepReturnValue; |
| }, |
|
|
| |
| |
| |
| |
| |
| handleAsync: (startAsync) => Asyncify.handleSleep(async (wakeUp) => { |
| |
| wakeUp(await startAsync()); |
| }), |
|
|
| #elif ASYNCIFY == 2 |
| |
| |
| |
|
|
| |
| |
| asyncExports: null, |
| isAsyncExport(func) { |
| return Asyncify.asyncExports?.has(func); |
| }, |
| handleAsync: async (startAsync) => { |
| {{{ runtimeKeepalivePush(); }}} |
| try { |
| return await startAsync(); |
| } finally { |
| {{{ runtimeKeepalivePop(); }}} |
| } |
| }, |
| handleSleep: (startAsync) => Asyncify.handleAsync(() => new Promise(startAsync)), |
| makeAsyncFunction(original) { |
| #if ASYNCIFY_DEBUG |
| dbg('asyncify: makeAsyncFunction for', original); |
| #endif |
| return WebAssembly.promising(original); |
| }, |
| #endif |
| }, |
|
|
| emscripten_sleep__async: 'auto', |
| emscripten_sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)), |
|
|
| emscripten_wget_data__deps: ['$asyncLoad', 'malloc'], |
| emscripten_wget_data__async: 'auto', |
| emscripten_wget_data: async (url, pbuffer, pnum, perror) => { |
| |
| try { |
| const byteArray = await asyncLoad(UTF8ToString(url)); |
| |
| var buffer = _malloc(byteArray.length); |
| HEAPU8.set(byteArray, buffer); |
| {{{ makeSetValue('pbuffer', 0, 'buffer', '*') }}}; |
| {{{ makeSetValue('pnum', 0, 'byteArray.length', 'i32') }}}; |
| {{{ makeSetValue('perror', 0, '0', 'i32') }}}; |
| } catch (err) { |
| {{{ makeSetValue('perror', 0, '1', 'i32') }}}; |
| } |
| }, |
|
|
| emscripten_scan_registers__deps: ['$safeSetTimeout'], |
| emscripten_scan_registers__async: true, |
| emscripten_scan_registers: (func) => { |
| return Asyncify.handleSleep((wakeUp) => { |
| |
| |
| |
| |
| safeSetTimeout(() => { |
| var stackBegin = Asyncify.currData + {{{ C_STRUCTS.asyncify_data_s.__size__ }}}; |
| var stackEnd = {{{ makeGetValue('Asyncify.currData', 0, '*') }}}; |
| {{{ makeDynCall('vpp', 'func') }}}(stackBegin, stackEnd); |
| wakeUp(); |
| }, 0); |
| }); |
| }, |
|
|
| $Fibers__deps: ['$Asyncify', 'emscripten_stack_set_limits', '$stackRestore'], |
| $Fibers: { |
| nextFiber: 0, |
| trampolineRunning: false, |
| trampoline() { |
| if (!Fibers.trampolineRunning && Fibers.nextFiber) { |
| Fibers.trampolineRunning = true; |
| do { |
| var fiber = Fibers.nextFiber; |
| Fibers.nextFiber = 0; |
| #if ASYNCIFY_DEBUG >= 2 |
| dbg("ASYNCIFY/FIBER: trampoline jump into fiber", fiber, new Error().stack); |
| #endif |
| Fibers.finishContextSwitch(fiber); |
| } while (Fibers.nextFiber); |
| Fibers.trampolineRunning = false; |
| } |
| }, |
| |
| |
| |
| finishContextSwitch(newFiber) { |
| var stack_base = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_base, '*') }}}; |
| var stack_max = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_limit, '*') }}}; |
| _emscripten_stack_set_limits(stack_base, stack_max); |
|
|
| #if STACK_OVERFLOW_CHECK >= 2 |
| ___set_stack_limits(stack_base, stack_max); |
| #endif |
|
|
| stackRestore({{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, '*') }}}); |
|
|
| var entryPoint = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.entry, '*') }}}; |
|
|
| if (entryPoint !== 0) { |
| #if STACK_OVERFLOW_CHECK |
| writeStackCookie(); |
| #endif |
| #if ASYNCIFY_DEBUG |
| dbg('ASYNCIFY/FIBER: entering fiber', newFiber, 'for the first time'); |
| #endif |
| Asyncify.currData = null; |
| {{{ makeSetValue('newFiber', C_STRUCTS.emscripten_fiber_s.entry, 0, '*') }}}; |
|
|
| var userData = {{{ makeGetValue('newFiber', C_STRUCTS.emscripten_fiber_s.user_data, '*') }}}; |
| {{{ makeDynCall('vp', 'entryPoint') }}}(userData); |
| } else { |
| var asyncifyData = newFiber + {{{ C_STRUCTS.emscripten_fiber_s.asyncify_data }}}; |
| Asyncify.currData = asyncifyData; |
|
|
| #if ASYNCIFY_DEBUG |
| dbg('ASYNCIFY/FIBER: start rewind', asyncifyData, '(resuming fiber', newFiber, ')'); |
| #endif |
| Asyncify.state = Asyncify.State.Rewinding; |
| _asyncify_start_rewind(asyncifyData); |
| Asyncify.doRewind(asyncifyData); |
| } |
| }, |
| }, |
|
|
| emscripten_fiber_swap__deps: ["$Asyncify", "$Fibers", '$stackSave'], |
| emscripten_fiber_swap__async: true, |
| emscripten_fiber_swap: (oldFiber, newFiber) => { |
| if (ABORT) return; |
| #if ASYNCIFY_DEBUG |
| dbg('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 |
| dbg('ASYNCIFY/FIBER: start unwind', asyncifyData); |
| #endif |
| _asyncify_start_unwind(asyncifyData); |
|
|
| var stackTop = stackSave(); |
| {{{ makeSetValue('oldFiber', C_STRUCTS.emscripten_fiber_s.stack_ptr, 'stackTop', '*') }}}; |
|
|
| Fibers.nextFiber = newFiber; |
| } else { |
| #if ASSERTIONS |
| assert(Asyncify.state === Asyncify.State.Rewinding); |
| #endif |
| #if ASYNCIFY_DEBUG |
| dbg('ASYNCIFY/FIBER: stop rewind'); |
| #endif |
| Asyncify.state = Asyncify.State.Normal; |
| _asyncify_stop_rewind(); |
| Asyncify.currData = null; |
| } |
| }, |
| #else |
| emscripten_sleep: () => { |
| abort('Please compile your program with async support in order to use asynchronous operations like emscripten_sleep'); |
| }, |
| emscripten_wget: (url, file) => { |
| abort('Please compile your program with async support in order to use asynchronous operations like emscripten_wget'); |
| }, |
| emscripten_wget_data: (url, pbuffer, pnum, perror) => { |
| abort('Please compile your program with async support in order to use asynchronous operations like emscripten_wget_data'); |
| }, |
| emscripten_scan_registers: (func) => { |
| abort('Please compile your program with async support in order to use asynchronous operations like emscripten_scan_registers'); |
| }, |
| emscripten_fiber_swap: (oldFiber, newFiber) => { |
| abort('Please compile your program with async support in order to use asynchronous operations like emscripten_fiber_swap'); |
| }, |
| #endif |
| }); |
|
|
| if (ASYNCIFY) { |
| extraLibraryFuncs.push('$Asyncify'); |
| } |
|
|