| /** |
| * @license |
| * Copyright 2015 The Emscripten Authors |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| // Pthread Web Worker handling code. |
| // This code runs only on pthread web workers and handles pthread setup |
| // and communication with the main thread via postMessage. |
| |
| #if ASSERTIONS |
| // Unique ID of the current pthread worker (zero on non-pthread-workers |
| // including the main thread). |
| var workerID = 0; |
| #endif |
| |
| #if MAIN_MODULE |
| // Map of modules to be shared with new threads. This gets populated by the |
| // main thread and shared with all new workers via the initial `load` message. |
| var sharedModules = {}; |
| #endif |
| |
| var startWorker; |
| |
| if (ENVIRONMENT_IS_PTHREAD) { |
| // Thread-local guard variable for one-time init of the JS state |
| var initializedJS = false; |
| |
| #if LOAD_SOURCE_MAP |
| // When using postMessage to send an object, it is processed by the structured |
| // clone algorithm. The prototype, and hence methods, on that object is then |
| // lost. This function adds back the lost prototype. This does not work with |
| // nested objects that has prototypes, but it suffices for WasmSourceMap and |
| // WasmOffsetConverter. |
| function resetPrototype(constructor, attrs) { |
| var object = Object.create(constructor.prototype); |
| return Object.assign(object, attrs); |
| } |
| #endif |
| |
| // Turn unhandled rejected promises into errors so that the main thread will be |
| // notified about them. |
| self.onunhandledrejection = (e) => { throw e.reason || e; }; |
| |
| {{{ asyncIf(ASYNCIFY == 2) }}}function handleMessage(e) { |
| try { |
| var msgData = e['data']; |
| //dbg('msgData: ' + Object.keys(msgData)); |
| var cmd = msgData.cmd; |
| if (cmd === 'load') { // Preload command that is called once per worker to parse and load the Emscripten code. |
| #if ASSERTIONS |
| workerID = msgData.workerID; |
| #endif |
| #if PTHREADS_DEBUG |
| dbg('worker: loading module') |
| #endif |
| |
| // Until we initialize the runtime, queue up any further incoming messages. |
| let messageQueue = []; |
| self.onmessage = (e) => messageQueue.push(e); |
| |
| // And add a callback for when the runtime is initialized. |
| startWorker = () => { |
| // Notify the main thread that this thread has loaded. |
| postMessage({ cmd: 'loaded' }); |
| // Process any messages that were queued before the thread was ready. |
| for (let msg of messageQueue) { |
| handleMessage(msg); |
| } |
| // Restore the real message handler. |
| self.onmessage = handleMessage; |
| }; |
| |
| #if MAIN_MODULE |
| dynamicLibraries = msgData.dynamicLibraries; |
| sharedModules = msgData.sharedModules; |
| #if RUNTIME_DEBUG |
| dbg(`worker: received ${Object.keys(msgData.sharedModules).length} shared modules: ${Object.keys(msgData.sharedModules)}`); |
| #endif |
| #endif |
| |
| // Use `const` here to ensure that the variable is scoped only to |
| // that iteration, allowing safe reference from a closure. |
| for (const handler of msgData.handlers) { |
| // The the main module has a handler for a certain even, but no |
| // handler exists on the pthread worker, then proxy that handler |
| // back to the main thread. |
| if (!Module[handler] || Module[handler].proxy) { |
| #if RUNTIME_DEBUG |
| dbg(`worker: installer proxying handler: ${handler}`); |
| #endif |
| Module[handler] = (...args) => { |
| #if RUNTIME_DEBUG |
| dbg(`worker: calling handler on main thread: ${handler}`); |
| #endif |
| postMessage({ cmd: 'callHandler', handler, args: args }); |
| } |
| // Rebind the out / err handlers if needed |
| if (handler == 'print') out = Module[handler]; |
| if (handler == 'printErr') err = Module[handler]; |
| } |
| #if RUNTIME_DEBUG |
| else dbg(`worker: using thread-local handler: ${handler}`); |
| #endif |
| } |
| |
| #if !WASM_ESM_INTEGRATION |
| wasmMemory = msgData.wasmMemory; |
| updateMemoryViews(); |
| #endif |
| |
| #if LOAD_SOURCE_MAP |
| wasmSourceMap = resetPrototype(WasmSourceMap, msgData.wasmSourceMap); |
| #endif |
| |
| #if !WASM_ESM_INTEGRATION |
| #if MINIMAL_RUNTIME |
| // Pass the shared Wasm module in the Module object for MINIMAL_RUNTIME. |
| Module['wasm'] = msgData.wasmModule; |
| loadModule(); |
| #else |
| wasmModule = msgData.wasmModule; |
| #if MODULARIZE == 'instance' |
| init(); |
| #else |
| createWasm(); |
| run(); |
| #endif |
| #endif // MINIMAL_RUNTIME |
| #endif |
| } else if (cmd === 'run') { |
| #if ASSERTIONS |
| assert(msgData.pthread_ptr); |
| #endif |
| // Call inside JS module to set up the stack frame for this pthread in JS module scope. |
| // This needs to be the first thing that we do, as we cannot call to any C/C++ functions |
| // until the thread stack is initialized. |
| establishStackSpace(msgData.pthread_ptr); |
| |
| // Pass the thread address to wasm to store it for fast access. |
| __emscripten_thread_init(msgData.pthread_ptr, /*is_main=*/0, /*is_runtime=*/0, /*can_block=*/1, 0, 0); |
| |
| #if OFFSCREENCANVAS_SUPPORT |
| PThread.receiveOffscreenCanvases(msgData); |
| #endif |
| PThread.threadInitTLS(); |
| |
| // Await mailbox notifications with `Atomics.waitAsync` so we can start |
| // using the fast `Atomics.notify` notification path. |
| __emscripten_thread_mailbox_await(msgData.pthread_ptr); |
| |
| if (!initializedJS) { |
| #if EMBIND |
| #if PTHREADS_DEBUG |
| dbg(`worker: Pthread 0x${_pthread_self().toString(16)} initializing embind.`); |
| #endif |
| // Embind must initialize itself on all threads, as it generates support JS. |
| // We only do this once per worker since they get reused |
| __embind_initialize_bindings(); |
| #endif // EMBIND |
| initializedJS = true; |
| } |
| |
| try { |
| {{{ awaitIf(ASYNCIFY == 2) }}}invokeEntryPoint(msgData.start_routine, msgData.arg); |
| } catch(ex) { |
| if (ex != 'unwind') { |
| // The pthread "crashed". Do not call `_emscripten_thread_exit` (which |
| // would make this thread joinable). Instead, re-throw the exception |
| // and let the top level handler propagate it back to the main thread. |
| throw ex; |
| } |
| #if RUNTIME_DEBUG |
| dbg(`worker: Pthread 0x${_pthread_self().toString(16)} completed its main entry point with an 'unwind', keeping the worker alive for asynchronous operation.`); |
| #endif |
| } |
| } else if (msgData.target === 'setimmediate') { |
| // no-op |
| } else if (cmd === 'checkMailbox') { |
| if (initializedJS) { |
| checkMailbox(); |
| } |
| } else if (cmd) { |
| // The received message looks like something that should be handled by this message |
| // handler, (since there is a cmd field present), but is not one of the |
| // recognized commands: |
| err(`worker: received unknown command ${cmd}`); |
| err(msgData); |
| } |
| } catch(ex) { |
| #if ASSERTIONS |
| err(`worker: onmessage() captured an uncaught exception: ${ex}`); |
| if (ex?.stack) err(ex.stack); |
| #endif |
| __emscripten_thread_crashed(); |
| throw ex; |
| } |
| }; |
| |
| self.onmessage = handleMessage; |
| |
| } // ENVIRONMENT_IS_PTHREAD |