Buckets:
| /** | |
| * @license | |
| * Copyright 2015 The Emscripten Authors | |
| * SPDX-License-Identifier: MIT | |
| * | |
| * Because only modern JS engines support SAB we can use modern JS language | |
| * features within this file (ES2020). | |
| */ | |
| #if !PTHREADS | |
| #error "Internal error! PTHREADS should be enabled when including library_pthread.js." | |
| #endif | |
| #if !SHARED_MEMORY | |
| #error "Internal error! SHARED_MEMORY should be enabled when including library_pthread.js." | |
| #endif | |
| #if PTHREADS == 2 | |
| #error "PTHREADS=2 is no longer supported" | |
| #endif | |
| #if BUILD_AS_WORKER | |
| #error "pthreads + BUILD_AS_WORKER require separate modes that don't work together, see https://github.com/emscripten-core/emscripten/issues/8854" | |
| #endif | |
| #if EVAL_CTORS | |
| #error "EVAL_CTORS is not compatible with pthreads yet (passive segments)" | |
| #endif | |
| {{{ | |
| #if MEMORY64 | |
| const MAX_PTR = Number((2n ** 64n) - 1n); | |
| #else | |
| const MAX_PTR = (2 ** 32) - 1 | |
| #endif | |
| #if WASM_ESM_INTEGRATION | |
| const pthreadWorkerScript = TARGET_BASENAME + '.pthread.mjs'; | |
| #else | |
| const pthreadWorkerScript = TARGET_JS_NAME; | |
| #endif | |
| // Use a macro to avoid duplicating pthread worker options. | |
| // We cannot use a normal JS variable since the vite bundler requires that worker | |
| // options be inline. | |
| // See https://github.com/emscripten-core/emscripten/issues/22394 | |
| const pthreadWorkerOptions = `{ | |
| #if EXPORT_ES6 | |
| 'type': 'module', | |
| #endif | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| // This is the way that we signal to the node worker that it is hosting | |
| // a pthread. | |
| 'workerData': 'em-pthread', | |
| #if WASMFS | |
| // In WasmFS, close() is not proxied to the main thread. Suppress | |
| // warnings when a thread closes a file descriptor it didn't open. | |
| // See: https://github.com/emscripten-core/emscripten/issues/24731 | |
| 'trackUnmanagedFds': false, | |
| #endif | |
| #endif | |
| #if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER | |
| // This is the way that we signal to the Web Worker that it is hosting | |
| // a pthread. | |
| #if ASSERTIONS | |
| 'name': 'em-pthread-' + PThread.nextWorkerID, | |
| #else | |
| 'name': 'em-pthread', | |
| #endif | |
| #endif | |
| }`; | |
| }}} | |
| var LibraryPThread = { | |
| $PThread__postset: 'PThread.init();', | |
| $PThread__deps: ['_emscripten_thread_init', | |
| '$terminateWorker', | |
| '$cleanupThread', | |
| '$addOnPreRun', | |
| #if MAIN_MODULE | |
| '$markAsFinished', | |
| #endif | |
| #if !MINIMAL_RUNTIME && PTHREAD_POOL_SIZE && !PTHREAD_POOL_DELAY_LOAD | |
| '$addRunDependency', | |
| '$removeRunDependency', | |
| #endif | |
| '$spawnThread', | |
| '_emscripten_thread_free_data', | |
| 'exit', | |
| 'pthread_self', | |
| '__set_thread_state', | |
| '$waitAsyncPolyfilled', | |
| #if PTHREADS_DEBUG || ASSERTIONS | |
| '$ptrToString', | |
| #endif | |
| ], | |
| $PThread: { | |
| // Contains all Workers that are idle/unused and not currently hosting an | |
| // executing pthread. Unused Workers can either be pooled up before page | |
| // startup, but also when a pthread quits, its hosting Worker is not | |
| // terminated, but is returned to this pool as an optimization so that | |
| // starting the next thread is faster. | |
| unusedWorkers: [], | |
| // Contains all Workers that are currently hosting an active pthread. | |
| runningWorkers: [], | |
| tlsInitFunctions: [], | |
| // Maps pthread_t pointers to the workers on which they are running. For | |
| // the reverse mapping, each worker has a `pthread_ptr` when its running a | |
| // pthread. | |
| pthreads: {}, | |
| #if ASSERTIONS | |
| nextWorkerID: 1, | |
| #endif | |
| init() { | |
| if ({{{ ENVIRONMENT_IS_MAIN_THREAD() }}}) { | |
| PThread.initMainThread(); | |
| } | |
| }, | |
| initMainThread() { | |
| #if PTHREAD_POOL_SIZE | |
| var pthreadPoolSize = {{{ PTHREAD_POOL_SIZE }}}; | |
| // Start loading up the Worker pool, if requested. | |
| while (pthreadPoolSize--) { | |
| PThread.allocateUnusedWorker(); | |
| } | |
| #endif | |
| #if !MINIMAL_RUNTIME && PTHREAD_POOL_SIZE | |
| // MINIMAL_RUNTIME takes care of calling loadWasmModuleToAllWorkers | |
| // in postamble_minimal.js | |
| addOnPreRun(async () => { | |
| var pthreadPoolReady = PThread.loadWasmModuleToAllWorkers(); | |
| #if !PTHREAD_POOL_DELAY_LOAD | |
| addRunDependency('loading-workers'); | |
| await pthreadPoolReady; | |
| removeRunDependency('loading-workers'); | |
| #endif // PTHREAD_POOL_DELAY_LOAD | |
| }); | |
| #endif // !MINIMAL_RUNTIME && PTHREAD_POOL_SIZE | |
| #if MAIN_MODULE | |
| PThread.outstandingPromises = {}; | |
| // Finished threads are threads that have finished running but we are not yet | |
| // joined. | |
| PThread.finishedThreads = new Set(); | |
| #endif | |
| }, | |
| #if PTHREADS_PROFILING | |
| getThreadName(pthreadPtr) { | |
| var profilerBlock = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, '*') }}}; | |
| if (!profilerBlock) return ""; | |
| return UTF8ToString(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.name }}}); | |
| }, | |
| threadStatusToString(threadStatus) { | |
| switch (threadStatus) { | |
| case 0: return "not yet started"; | |
| case 1: return "running"; | |
| case 2: return "sleeping"; | |
| case 3: return "waiting for a futex"; | |
| case 4: return "waiting for a mutex"; | |
| case 5: return "waiting for a proxied operation"; | |
| case 6: return "finished execution"; | |
| default: return "unknown (corrupt?!)"; | |
| } | |
| }, | |
| threadStatusAsString(pthreadPtr) { | |
| var profilerBlock = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, '*') }}}; | |
| var status = (profilerBlock == 0) ? 0 : Atomics.load(HEAPU32, {{{ getHeapOffset('profilerBlock + ' + C_STRUCTS.thread_profiler_block.threadStatus, 'i32') }}}); | |
| return PThread.threadStatusToString(status); | |
| }, | |
| #endif | |
| terminateAllThreads: () => { | |
| #if ASSERTIONS | |
| assert(!ENVIRONMENT_IS_PTHREAD, 'terminateAllThreads() should only be called from the main thread'); | |
| #endif | |
| #if PTHREADS_DEBUG | |
| dbg('terminateAllThreads'); | |
| #endif | |
| // Attempt to kill all workers. Sadly (at least on the web) there is no | |
| // way to terminate a worker synchronously, or to be notified when a | |
| // worker is actually terminated. This means there is some risk that | |
| // pthreads will continue to be executing after `worker.terminate` has | |
| // returned. For this reason, we don't call `returnWorkerToPool` here or | |
| // free the underlying pthread data structures. | |
| for (var worker of PThread.runningWorkers) { | |
| terminateWorker(worker); | |
| } | |
| for (var worker of PThread.unusedWorkers) { | |
| terminateWorker(worker); | |
| } | |
| PThread.unusedWorkers = []; | |
| PThread.runningWorkers = []; | |
| PThread.pthreads = {}; | |
| }, | |
| terminateRuntime: () => { | |
| #if ASSERTIONS | |
| assert(!ENVIRONMENT_IS_PTHREAD, 'terminateRuntime() should only be called from the main thread'); | |
| #endif | |
| PThread.terminateAllThreads(); | |
| var pthread_ptr = _pthread_self(); | |
| ___set_thread_state(0, 0, 0, 1); | |
| if (!waitAsyncPolyfilled) { | |
| // Break the waitAsync loop. Note that checkMailbox will not | |
| // re-register since the `___set_thread_state` above causes _pthread_self | |
| // to return 0. | |
| Atomics.notify(HEAP32, {{{ getHeapOffset('pthread_ptr', 'i32') }}}); | |
| } | |
| }, | |
| returnWorkerToPool: (worker) => { | |
| // We don't want to run main thread queued calls here, since we are doing | |
| // some operations that leave the worker queue in an invalid state until | |
| // we are completely done (it would be bad if free() ends up calling a | |
| // queued pthread_create which looks at the global data structures we are | |
| // modifying). To achieve that, defer the free() until the very end, when | |
| // we are all done. | |
| var pthread_ptr = worker.pthread_ptr; | |
| delete PThread.pthreads[pthread_ptr]; | |
| // Note: worker is intentionally not terminated so the pool can | |
| // dynamically grow. | |
| PThread.unusedWorkers.push(worker); | |
| PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker), 1); | |
| // Not a running Worker anymore | |
| // Detach the worker from the pthread object, and return it to the | |
| // worker pool as an unused worker. | |
| worker.pthread_ptr = 0; | |
| #if ENVIRONMENT_MAY_BE_NODE && PROXY_TO_PTHREAD | |
| if (ENVIRONMENT_IS_NODE) { | |
| // Once the proxied main thread has finished, mark it as weakly | |
| // referenced so that its existence does not prevent Node.js from | |
| // exiting. This has no effect if the worker is already weakly | |
| // referenced. | |
| worker.unref(); | |
| } | |
| #endif | |
| // Finally, free the underlying (and now-unused) pthread structure in | |
| // linear memory. | |
| __emscripten_thread_free_data(pthread_ptr); | |
| }, | |
| #if OFFSCREENCANVAS_SUPPORT | |
| receiveOffscreenCanvases(data) { | |
| if (typeof GL != 'undefined') { | |
| Object.assign(GL.offscreenCanvases, data.offscreenCanvases); | |
| if (!Module['canvas'] && data.moduleCanvasId && GL.offscreenCanvases[data.moduleCanvasId]) { | |
| Module['canvas'] = GL.offscreenCanvases[data.moduleCanvasId].offscreenCanvas; | |
| Module['canvas'].id = data.moduleCanvasId; | |
| } | |
| } | |
| }, | |
| #endif | |
| // Called by worker.js each time a thread is started. | |
| threadInitTLS() { | |
| #if PTHREADS_DEBUG | |
| dbg('threadInitTLS'); | |
| #endif | |
| // Call thread init functions (these are the _emscripten_tls_init for each | |
| // module loaded. | |
| PThread.tlsInitFunctions.forEach((f) => f()); | |
| }, | |
| // Loads the WebAssembly module into the given Worker. | |
| // onFinishedLoading: A callback function that will be called once all of | |
| // the workers have been initialized and are | |
| // ready to host pthreads. | |
| loadWasmModuleToWorker: (worker) => new Promise((onFinishedLoading) => { | |
| worker.onmessage = (e) => { | |
| var d = e['data']; | |
| var cmd = d.cmd; | |
| #if PTHREADS_DEBUG | |
| dbg(`main thread: received message '${cmd}' from worker. ${d}`); | |
| #endif | |
| // If this message is intended to a recipient that is not the main | |
| // thread, forward it to the target thread. | |
| if (d.targetThread && d.targetThread != _pthread_self()) { | |
| var targetWorker = PThread.pthreads[d.targetThread]; | |
| if (targetWorker) { | |
| targetWorker.postMessage(d, d.transferList); | |
| } else { | |
| err(`worker sent message (${cmd}) to pthread (${d.targetThread}) that no longer exists`); | |
| } | |
| return; | |
| } | |
| if (cmd === 'checkMailbox') { | |
| checkMailbox(); | |
| } else if (cmd === 'spawnThread') { | |
| spawnThread(d); | |
| } else if (cmd === 'cleanupThread') { | |
| // cleanupThread needs to be run via callUserCallback since it calls | |
| // back into user code to free thread data. Without this it's possible | |
| // the unwind or ExitStatus exception could escape here. | |
| callUserCallback(() => cleanupThread(d.thread)); | |
| #if MAIN_MODULE | |
| } else if (cmd === 'markAsFinished') { | |
| markAsFinished(d.thread); | |
| #endif | |
| } else if (cmd === 'loaded') { | |
| worker.loaded = true; | |
| #if ENVIRONMENT_MAY_BE_NODE && PTHREAD_POOL_SIZE | |
| // Check that this worker doesn't have an associated pthread. | |
| if (ENVIRONMENT_IS_NODE && !worker.pthread_ptr) { | |
| // Once worker is loaded & idle, mark it as weakly referenced, | |
| // so that mere existence of a Worker in the pool does not prevent | |
| // Node.js from exiting the app. | |
| worker.unref(); | |
| } | |
| #endif | |
| onFinishedLoading(worker); | |
| } else if (d.target === 'setimmediate') { | |
| // Worker wants to postMessage() to itself to implement setImmediate() | |
| // emulation. | |
| worker.postMessage(d); | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| } else if (cmd === 'uncaughtException') { | |
| // Message handler for Node.js specific out-of-order behavior: | |
| // https://github.com/nodejs/node/issues/59617 | |
| // A pthread sent an uncaught exception event. Re-raise it on the main thread. | |
| worker.onerror(d.error); | |
| #endif | |
| } else if (cmd === 'callHandler') { | |
| Module[d.handler](...d.args); | |
| } else if (cmd) { | |
| // The received message looks like something that should be handled by this message | |
| // handler, (since there is a e.data.cmd field present), but is not one of the | |
| // recognized commands: | |
| err(`worker sent an unknown command ${cmd}`); | |
| } | |
| }; | |
| worker.onerror = (e) => { | |
| var message = 'worker sent an error!'; | |
| #if ASSERTIONS | |
| if (worker.pthread_ptr) { | |
| message = `Pthread ${ptrToString(worker.pthread_ptr)} sent an error!`; | |
| } | |
| #endif | |
| err(`${message} ${e.filename}:${e.lineno}: ${e.message}`); | |
| throw e; | |
| }; | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| if (ENVIRONMENT_IS_NODE) { | |
| worker.on('message', (data) => worker.onmessage({ data: data })); | |
| worker.on('error', (e) => worker.onerror(e)); | |
| #if PTHREADS_DEBUG | |
| worker.on('exit', (code) => { | |
| if (worker.pthread_ptr) dbg(`Worker hosting pthread ${ptrToString(worker.pthread_ptr)} has terminated with code ${code}.`); | |
| else dbg(`Worker has terminated with code ${code}.`); | |
| }); | |
| #endif | |
| } | |
| #endif | |
| #if ASSERTIONS | |
| assert(wasmMemory instanceof WebAssembly.Memory, 'wasmMemory should have been loaded by now'); | |
| #if !WASM_ESM_INTEGRATION | |
| assert(wasmModule instanceof WebAssembly.Module, 'wasmModule should have been loaded by now'); | |
| #endif | |
| #endif | |
| // When running on a pthread, none of the incoming parameters on the module | |
| // object are present. Proxy known handlers back to the main thread if specified. | |
| var handlers = []; | |
| var knownHandlers = [ | |
| #if expectToReceiveOnModule('onExit') | |
| 'onExit', | |
| #endif | |
| #if expectToReceiveOnModule('onAbort') | |
| 'onAbort', | |
| #endif | |
| #if expectToReceiveOnModule('print') | |
| 'print', | |
| #endif | |
| #if expectToReceiveOnModule('printErr') | |
| 'printErr', | |
| #endif | |
| #if expectToReceiveOnModule('onMalloc') | |
| 'onMalloc', | |
| #endif | |
| #if expectToReceiveOnModule('onRealloc') | |
| 'onRealloc', | |
| #endif | |
| #if expectToReceiveOnModule('onFree') | |
| 'onFree', | |
| #endif | |
| #if expectToReceiveOnModule('onSbrkGrow') | |
| 'onSbrkGrow', | |
| #endif | |
| ]; | |
| for (var handler of knownHandlers) { | |
| if (Module.propertyIsEnumerable(handler)) { | |
| handlers.push(handler); | |
| } | |
| } | |
| // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation. | |
| worker.postMessage({ | |
| cmd: 'load', | |
| handlers: handlers, | |
| #if WASM2JS | |
| // the polyfill WebAssembly.Memory instance has function properties, | |
| // which will fail in postMessage, so just send a custom object with the | |
| // property we need, the buffer | |
| wasmMemory: { 'buffer': wasmMemory.buffer }, | |
| #else // WASM2JS | |
| wasmMemory, | |
| #endif // WASM2JS | |
| #if !WASM_ESM_INTEGRATION | |
| wasmModule, | |
| #endif | |
| #if LOAD_SOURCE_MAP | |
| wasmSourceMap, | |
| #endif | |
| #if MAIN_MODULE | |
| dynamicLibraries, | |
| // Share all modules that have been loaded so far. New workers | |
| // won't start running threads until these are all loaded. | |
| sharedModules, | |
| #endif | |
| #if ASSERTIONS | |
| 'workerID': worker.workerID, | |
| #endif | |
| }); | |
| }), | |
| #if PTHREAD_POOL_SIZE | |
| async loadWasmModuleToAllWorkers() { | |
| // Instantiation is synchronous in pthreads. | |
| if ( | |
| ENVIRONMENT_IS_PTHREAD | |
| #if WASM_WORKERS | |
| || ENVIRONMENT_IS_WASM_WORKER | |
| #endif | |
| ) { | |
| return; | |
| } | |
| let pthreadPoolReady = Promise.all(PThread.unusedWorkers.map(PThread.loadWasmModuleToWorker)); | |
| #if PTHREAD_POOL_DELAY_LOAD | |
| // PTHREAD_POOL_DELAY_LOAD means we want to proceed synchronously without | |
| // waiting for the pthread pool during the startup phase. | |
| // If the user wants to wait on it elsewhere, they can do so via the | |
| // Module['pthreadPoolReady'] promise. | |
| Module['pthreadPoolReady'] = pthreadPoolReady; | |
| return; | |
| #else | |
| return pthreadPoolReady; | |
| #endif | |
| }, | |
| #endif // PTHREAD_POOL_SIZE | |
| // Creates a new web Worker and places it in the unused worker pool to wait for its use. | |
| allocateUnusedWorker() { | |
| var worker; | |
| #if EXPORT_ES6 | |
| // If we're using module output, use bundler-friendly pattern. | |
| #if PTHREADS_DEBUG | |
| dbg(`Allocating a new web worker from ${import.meta.url}`); | |
| #endif | |
| #if TRUSTED_TYPES | |
| // Use Trusted Types compatible wrappers. | |
| if (globalThis.trustedTypes?.createPolicy) { | |
| var p = trustedTypes.createPolicy('emscripten#workerPolicy1', { createScriptURL: (ignored) => new URL('{{{ pthreadWorkerScript }}}', import.meta.url) }); | |
| worker = new Worker(p.createScriptURL('ignored'), {{{ pthreadWorkerOptions }}}); | |
| } else | |
| #endif | |
| #if expectToReceiveOnModule('mainScriptUrlOrBlob') | |
| if (Module['mainScriptUrlOrBlob']) { | |
| var pthreadMainJs = Module['mainScriptUrlOrBlob']; | |
| if (typeof pthreadMainJs != 'string') { | |
| pthreadMainJs = URL.createObjectURL(pthreadMainJs); | |
| } | |
| worker = new Worker(pthreadMainJs, {{{ pthreadWorkerOptions }}}); | |
| } else | |
| #endif | |
| #if CROSS_ORIGIN && ENVIRONMENT_MAY_BE_WEB | |
| // Support cross-origin loading by creating a new Blob URL to actually | |
| // perform the `import`. Without this the `new Worker` would fail | |
| // due to CORS restrictions. | |
| // https://github.com/emscripten-core/emscripten/issues/21937 | |
| if (ENVIRONMENT_IS_WEB) { | |
| var url = URL.createObjectURL(new Blob([`import '${import.meta.url}'`], { type: 'application/javascript' })); | |
| worker = new Worker(url, {{{ pthreadWorkerOptions }}}); | |
| } else | |
| #endif | |
| // We need to generate the URL with import.meta.url as the base URL of the JS file | |
| // instead of just using new URL(import.meta.url) because bundlers only recognize | |
| // the first case in their bundling step. The latter ends up producing an invalid | |
| // URL to import from the server (e.g., for webpack the file:// path). | |
| // See https://github.com/webpack/webpack/issues/12638 | |
| worker = new Worker(new URL('{{{ pthreadWorkerScript }}}', import.meta.url), {{{ pthreadWorkerOptions }}}); | |
| #else // EXPORT_ES6 | |
| var pthreadMainJs = _scriptName; | |
| #if CROSS_ORIGIN && ENVIRONMENT_MAY_BE_WEB | |
| // In order to support cross origin loading of worker threads load the | |
| // worker via a tiny inline `importScripts` call. For some reason it's | |
| // fine to `importScripts` across origins, in cases where new Worker | |
| // itself does not allow this. | |
| // https://github.com/emscripten-core/emscripten/issues/21937 | |
| if (ENVIRONMENT_IS_WEB) { | |
| pthreadMainJs = URL.createObjectURL(new Blob([`importScripts('${_scriptName}')`], { type: 'application/javascript' })); | |
| } | |
| #endif | |
| #if expectToReceiveOnModule('mainScriptUrlOrBlob') | |
| // We can't use makeModuleReceiveWithVar here since we want to also | |
| // call URL.createObjectURL on the mainScriptUrlOrBlob. | |
| if (Module['mainScriptUrlOrBlob']) { | |
| pthreadMainJs = Module['mainScriptUrlOrBlob']; | |
| if (typeof pthreadMainJs != 'string') { | |
| pthreadMainJs = URL.createObjectURL(pthreadMainJs); | |
| } | |
| } | |
| #endif | |
| #if PTHREADS_DEBUG | |
| dbg(`Allocating a new web worker from ${pthreadMainJs}`); | |
| #endif | |
| #if TRUSTED_TYPES | |
| // Use Trusted Types compatible wrappers. | |
| if (globalThis.trustedTypes?.createPolicy) { | |
| var p = trustedTypes.createPolicy('emscripten#workerPolicy2', { createScriptURL: (ignored) => pthreadMainJs }); | |
| worker = new Worker(p.createScriptURL('ignored'), {{{ pthreadWorkerOptions }}}); | |
| } else | |
| #endif | |
| worker = new Worker(pthreadMainJs, {{{ pthreadWorkerOptions }}}); | |
| #endif // EXPORT_ES6 | |
| #if ASSERTIONS | |
| worker.workerID = PThread.nextWorkerID++; | |
| #endif | |
| PThread.unusedWorkers.push(worker); | |
| }, | |
| getNewWorker() { | |
| if (PThread.unusedWorkers.length == 0) { | |
| // PTHREAD_POOL_SIZE_STRICT should show a warning and, if set to level `2`, return from the function. | |
| #if (PTHREAD_POOL_SIZE_STRICT && ASSERTIONS) || PTHREAD_POOL_SIZE_STRICT == 2 | |
| // However, if we're in Node.js, then we can create new workers on the fly and PTHREAD_POOL_SIZE_STRICT | |
| // should be ignored altogether. | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| if (!ENVIRONMENT_IS_NODE) { | |
| #endif | |
| #if ASSERTIONS | |
| err('Tried to spawn a new thread, but the thread pool is exhausted.\n' + | |
| 'This might result in a deadlock unless some threads eventually exit or the code explicitly breaks out to the event loop.\n' + | |
| 'If you want to increase the pool size, use setting `-sPTHREAD_POOL_SIZE=...`.' | |
| #if PTHREAD_POOL_SIZE_STRICT == 1 | |
| + '\nIf you want to throw an explicit error instead of the risk of deadlocking in those cases, use setting `-sPTHREAD_POOL_SIZE_STRICT=2`.' | |
| #endif | |
| ); | |
| #endif // ASSERTIONS | |
| #if PTHREAD_POOL_SIZE_STRICT == 2 | |
| return; | |
| #endif | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| } | |
| #endif | |
| #endif // PTHREAD_POOL_SIZE_STRICT | |
| #if PTHREAD_POOL_SIZE_STRICT < 2 || ENVIRONMENT_MAY_BE_NODE | |
| PThread.allocateUnusedWorker(); | |
| PThread.loadWasmModuleToWorker(PThread.unusedWorkers[0]); | |
| #endif | |
| } | |
| return PThread.unusedWorkers.pop(); | |
| } | |
| }, | |
| $terminateWorker: (worker) => { | |
| #if PTHREADS_DEBUG | |
| dbg(`terminateWorker: ${worker.workerID}`); | |
| #endif | |
| worker.terminate(); | |
| // terminate() can be asynchronous, so in theory the worker can continue | |
| // to run for some amount of time after termination. However from our POV | |
| // the worker is now dead and we don't want to hear from it again, so we stub | |
| // out its message handler here. This avoids having to check in each of | |
| // the onmessage handlers if the message was coming from a valid worker. | |
| worker.onmessage = (e) => { | |
| #if ASSERTIONS | |
| var cmd = e['data'].cmd; | |
| err(`received "${cmd}" command from terminated worker: ${worker.workerID}`); | |
| #endif | |
| }; | |
| }, | |
| _emscripten_thread_cleanup: (thread) => { | |
| // Called when a thread needs to be cleaned up so it can be reused. | |
| // A thread is considered reusable when it either returns from its | |
| // entry point, calls pthread_exit, or acts upon a cancellation. | |
| // Detached threads are responsible for calling this themselves, | |
| // otherwise pthread_join is responsible for calling this. | |
| #if PTHREADS_DEBUG | |
| dbg(`_emscripten_thread_cleanup: ${ptrToString(thread)}`) | |
| #endif | |
| if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread); | |
| else postMessage({ cmd: 'cleanupThread', thread }); | |
| }, | |
| _emscripten_thread_set_strongref: (thread) => { | |
| // Called when a thread needs to be strongly referenced. | |
| // Currently only used for: | |
| // - keeping the "main" thread alive in PROXY_TO_PTHREAD mode; | |
| // - crashed threads that need to propagate the uncaught exception | |
| // back to the main thread. | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| if (ENVIRONMENT_IS_NODE) { | |
| PThread.pthreads[thread].ref(); | |
| } | |
| #endif | |
| }, | |
| $cleanupThread: (pthread_ptr) => { | |
| #if PTHREADS_DEBUG | |
| dbg(`cleanupThread: ${ptrToString(pthread_ptr)}`) | |
| #endif | |
| #if ASSERTIONS | |
| assert(!ENVIRONMENT_IS_PTHREAD, 'cleanupThread() should only be called from the main thread'); | |
| assert(pthread_ptr, 'null pthread_ptr passed to cleanupThread'); | |
| #endif | |
| var worker = PThread.pthreads[pthread_ptr]; | |
| #if MAIN_MODULE | |
| PThread.finishedThreads.delete(pthread_ptr); | |
| if (pthread_ptr in PThread.outstandingPromises) { | |
| PThread.outstandingPromises[pthread_ptr].resolve(); | |
| } | |
| #endif | |
| #if ASSERTIONS | |
| assert(worker); | |
| #endif | |
| PThread.returnWorkerToPool(worker); | |
| }, | |
| #if MAIN_MODULE | |
| $registerTLSInit: (tlsInitFunc, moduleExports, metadata) => { | |
| #if DYLINK_DEBUG | |
| dbg('registerTLSInit:', tlsInitFunc, metadata?.tlsExports); | |
| #endif | |
| // In relocatable builds, we use the result of calling tlsInitFunc | |
| // (`_emscripten_tls_init`) to relocate the TLS exports of the module | |
| // according to this new __tls_base. | |
| function tlsInitWrapper() { | |
| var __tls_base = tlsInitFunc(); | |
| #if DYLINK_DEBUG | |
| dbg(`tlsInit -> ${__tls_base}`); | |
| #endif | |
| if (!__tls_base) { | |
| #if ASSERTIONS | |
| // __tls_base should never be zero if there are tls exports | |
| assert(__tls_base || metadata.tlsExports.size == 0); | |
| #endif | |
| return; | |
| } | |
| var tlsExports = {}; | |
| metadata.tlsExports.forEach((s) => tlsExports[s] = moduleExports[s]); | |
| updateGOT(relocateExports(tlsExports, __tls_base), /*replace=*/true); | |
| } | |
| // Register this function so that its gets called for each thread on | |
| // startup. | |
| PThread.tlsInitFunctions.push(tlsInitWrapper); | |
| // If the main thread is already running we also need to call this function | |
| // now. If the main thread is not yet running this will happen when it | |
| // is initialized and processes `PThread.tlsInitFunctions`. | |
| if (runtimeInitialized) { | |
| tlsInitWrapper(); | |
| } | |
| }, | |
| #else | |
| $registerTLSInit: (tlsInitFunc) => PThread.tlsInitFunctions.push(tlsInitFunc), | |
| #endif | |
| $spawnThread: (threadParams) => { | |
| #if ASSERTIONS | |
| assert(!ENVIRONMENT_IS_PTHREAD, 'spawnThread() should only be called from the main thread'); | |
| assert(threadParams.pthread_ptr, 'spawnThread called with null pthread ptr'); | |
| #endif | |
| var worker = PThread.getNewWorker(); | |
| if (!worker) { | |
| // No available workers in the PThread pool. | |
| return {{{ cDefs.EAGAIN }}}; | |
| } | |
| #if ASSERTIONS | |
| assert(!worker.pthread_ptr); | |
| #endif | |
| PThread.runningWorkers.push(worker); | |
| // Add to pthreads map | |
| PThread.pthreads[threadParams.pthread_ptr] = worker; | |
| worker.pthread_ptr = threadParams.pthread_ptr; | |
| var msg = { | |
| cmd: 'run', | |
| start_routine: threadParams.startRoutine, | |
| arg: threadParams.arg, | |
| pthread_ptr: threadParams.pthread_ptr, | |
| }; | |
| #if OFFSCREENCANVAS_SUPPORT | |
| // Note that we do not need to quote these names because they are only used | |
| // in this file, and not from the external worker.js. | |
| msg.moduleCanvasId = threadParams.moduleCanvasId; | |
| msg.offscreenCanvases = threadParams.offscreenCanvases; | |
| #endif | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| if (ENVIRONMENT_IS_NODE) { | |
| // Mark worker as weakly referenced once we start executing a pthread, | |
| // so that its existence does not prevent Node.js from exiting. This | |
| // has no effect if the worker is already weakly referenced (e.g. if | |
| // this worker was previously idle/unused). | |
| worker.unref(); | |
| } | |
| #endif | |
| // Ask the worker to start executing its pthread entry point function. | |
| worker.postMessage(msg, threadParams.transferList); | |
| return 0; | |
| }, | |
| _emscripten_init_main_thread_js: (tb) => { | |
| // Pass the thread address to the native code where they are stored in wasm | |
| // globals which act as a form of TLS. Global constructors trying | |
| // to access this value will read the wrong value, but that is UB anyway. | |
| __emscripten_thread_init( | |
| tb, | |
| /*is_main=*/!ENVIRONMENT_IS_WORKER, | |
| /*is_runtime=*/1, | |
| /*can_block=*/!ENVIRONMENT_IS_WEB, | |
| /*default_stacksize=*/{{{ DEFAULT_PTHREAD_STACK_SIZE }}}, | |
| #if PTHREADS_PROFILING | |
| /*start_profiling=*/true, | |
| #else | |
| /*start_profiling=*/false, | |
| #endif | |
| ); | |
| PThread.threadInitTLS(); | |
| }, | |
| $pthreadCreateProxied__internal: true, | |
| $pthreadCreateProxied__proxy: 'sync', | |
| $pthreadCreateProxied__deps: ['__pthread_create_js'], | |
| $pthreadCreateProxied: (pthread_ptr, attr, startRoutine, arg) => ___pthread_create_js(pthread_ptr, attr, startRoutine, arg), | |
| #if OFFSCREENCANVAS_SUPPORT | |
| // ASan wraps the emscripten_builtin_pthread_create call in | |
| // __lsan::ScopedInterceptorDisabler. Unfortunately, that only disables it on | |
| // the thread that made the call. __pthread_create_js gets proxied to the | |
| // main thread, where LSan is not disabled. This makes it necessary for us to | |
| // disable LSan here (using __noleakcheck), so that it does not detect | |
| // pthread's internal allocations as leaks. If/when we remove all the | |
| // allocations from __pthread_create_js we could also remove this. | |
| __pthread_create_js__noleakcheck: true, | |
| #endif | |
| __pthread_create_js__deps: ['$spawnThread', '$pthreadCreateProxied', | |
| 'emscripten_has_threading_support', | |
| #if OFFSCREENCANVAS_SUPPORT | |
| 'malloc', | |
| #endif | |
| ], | |
| __pthread_create_js: (pthread_ptr, attr, startRoutine, arg) => { | |
| if (!_emscripten_has_threading_support()) { | |
| #if ASSERTIONS | |
| dbg('pthread_create: environment does not support SharedArrayBuffer, pthreads are not available'); | |
| #endif | |
| return {{{ cDefs.EAGAIN }}}; | |
| } | |
| #if PTHREADS_DEBUG | |
| dbg("createThread: " + ptrToString(pthread_ptr)); | |
| #endif | |
| // List of JS objects that will transfer ownership to the Worker hosting the thread | |
| var transferList = []; | |
| var error = 0; | |
| #if OFFSCREENCANVAS_SUPPORT | |
| // Deduce which WebGL canvases (HTMLCanvasElements or OffscreenCanvases) should be passed over to the | |
| // Worker that hosts the spawned pthread. | |
| // Comma-delimited list of CSS selectors that must identify canvases by IDs: "#canvas1, #canvas2, ..." | |
| var transferredCanvasNames = attr ? {{{ makeGetValue('attr', C_STRUCTS.pthread_attr_t._a_transferredcanvases, '*') }}} : 0; | |
| #if OFFSCREENCANVASES_TO_PTHREAD | |
| // Proxied canvases string pointer -1/MAX_PTR is used as a special token to | |
| // fetch whatever canvases were passed to build in | |
| // -sOFFSCREENCANVASES_TO_PTHREAD= command line. | |
| if (transferredCanvasNames == {{{ MAX_PTR }}}) { | |
| transferredCanvasNames = '{{{ OFFSCREENCANVASES_TO_PTHREAD }}}'; | |
| } else | |
| #endif | |
| { | |
| transferredCanvasNames = UTF8ToString(transferredCanvasNames).trim(); | |
| } | |
| transferredCanvasNames = transferredCanvasNames ? transferredCanvasNames.split(',') : []; | |
| #if GL_DEBUG | |
| dbg(`pthread_create: transferredCanvasNames="${transferredCanvasNames}"`); | |
| #endif | |
| var offscreenCanvases = {}; // Dictionary of OffscreenCanvas objects we'll transfer to the created thread to own | |
| var moduleCanvasId = Module['canvas']?.id || ''; | |
| // Note that transferredCanvasNames might be null (so we cannot do a for-of loop). | |
| for (var name of transferredCanvasNames) { | |
| name = name.trim(); | |
| var offscreenCanvasInfo; | |
| try { | |
| if (name == '#canvas') { | |
| if (!Module['canvas']) { | |
| err(`pthread_create: could not find canvas with ID "${name}" to transfer to thread!`); | |
| error = {{{ cDefs.EINVAL }}}; | |
| break; | |
| } | |
| name = Module['canvas'].id; | |
| } | |
| #if ASSERTIONS | |
| assert(typeof GL == 'object', 'OFFSCREENCANVAS_SUPPORT assumes GL is in use (you can force-include it with \'-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$GL\')'); | |
| #endif | |
| if (GL.offscreenCanvases[name]) { | |
| offscreenCanvasInfo = GL.offscreenCanvases[name]; | |
| GL.offscreenCanvases[name] = null; // This thread no longer owns this canvas. | |
| if (Module['canvas'] instanceof OffscreenCanvas && name === Module['canvas'].id) Module['canvas'] = null; | |
| } else if (!ENVIRONMENT_IS_PTHREAD) { | |
| var canvas = (Module['canvas'] && Module['canvas'].id === name) ? Module['canvas'] : document.querySelector(name); | |
| if (!canvas) { | |
| err(`pthread_create: could not find canvas with ID "${name}" to transfer to thread!`); | |
| error = {{{ cDefs.EINVAL }}}; | |
| break; | |
| } | |
| if (canvas.controlTransferredOffscreen) { | |
| err(`pthread_create: cannot transfer canvas with ID "${name}" to thread, since the current thread does not have control over it!`); | |
| error = {{{ cDefs.EPERM }}}; // Operation not permitted, some other thread is accessing the canvas. | |
| break; | |
| } | |
| if (canvas.transferControlToOffscreen) { | |
| #if GL_DEBUG | |
| dbg(`pthread_create: canvas.transferControlToOffscreen(), transferring canvas by name "${name}" (DOM id="${canvas.id}") from main thread to pthread`); | |
| #endif | |
| // Create a shared information block in heap so that we can control | |
| // the canvas size from any thread. | |
| if (!canvas.canvasSharedPtr) { | |
| canvas.canvasSharedPtr = _malloc({{{ 8 + POINTER_SIZE }}}); | |
| {{{ makeSetValue('canvas.canvasSharedPtr', 0, 'canvas.width', 'i32') }}}; | |
| {{{ makeSetValue('canvas.canvasSharedPtr', 4, 'canvas.height', 'i32') }}}; | |
| {{{ makeSetValue('canvas.canvasSharedPtr', 8, 0, '*') }}}; // pthread ptr to the thread that owns this canvas, filled in below. | |
| } | |
| offscreenCanvasInfo = { | |
| offscreenCanvas: canvas.transferControlToOffscreen(), | |
| canvasSharedPtr: canvas.canvasSharedPtr, | |
| id: canvas.id | |
| } | |
| // After calling canvas.transferControlToOffscreen(), it is no | |
| // longer possible to access certain operations on the canvas, such | |
| // as resizing it or obtaining GL contexts via it. | |
| // Use this field to remember that we have permanently converted | |
| // this Canvas to be controlled via an OffscreenCanvas (there is no | |
| // way to undo this in the spec) | |
| canvas.controlTransferredOffscreen = true; | |
| } else { | |
| err(`pthread_create: cannot transfer control of canvas "${name}" to pthread, because current browser does not support OffscreenCanvas!`); | |
| // If building with OFFSCREEN_FRAMEBUFFER=1 mode, we don't need to | |
| // be able to transfer control to offscreen, but WebGL can be | |
| // proxied from worker to main thread. | |
| #if !OFFSCREEN_FRAMEBUFFER | |
| err('pthread_create: Build with -sOFFSCREEN_FRAMEBUFFER to enable fallback proxying of GL commands from pthread to main thread.'); | |
| return {{{ cDefs.ENOSYS }}}; // Function not implemented, browser doesn't have support for this. | |
| #endif | |
| } | |
| } | |
| if (offscreenCanvasInfo) { | |
| transferList.push(offscreenCanvasInfo.offscreenCanvas); | |
| offscreenCanvases[offscreenCanvasInfo.id] = offscreenCanvasInfo; | |
| } | |
| } catch(e) { | |
| err(`pthread_create: failed to transfer control of canvas "${name}" to OffscreenCanvas! Error: ${e}`); | |
| return {{{ cDefs.EINVAL }}}; // Hitting this might indicate an implementation bug or some other internal error | |
| } | |
| } | |
| #endif // OFFSCREENCANVAS_SUPPORT | |
| // Synchronously proxy the thread creation to main thread if possible. If we | |
| // need to transfer ownership of objects, then proxy asynchronously via | |
| // postMessage. | |
| if (ENVIRONMENT_IS_PTHREAD && (transferList.length === 0 || error)) { | |
| return pthreadCreateProxied(pthread_ptr, attr, startRoutine, arg); | |
| } | |
| // If on the main thread, and accessing Canvas/OffscreenCanvas failed, abort | |
| // with the detected error. | |
| if (error) return error; | |
| #if OFFSCREENCANVAS_SUPPORT | |
| // Register for each of the transferred canvases that the new thread now | |
| // owns the OffscreenCanvas. | |
| for (var canvas of Object.values(offscreenCanvases)) { | |
| // pthread ptr to the thread that owns this canvas. | |
| {{{ makeSetValue('canvas.canvasSharedPtr', 8, 'pthread_ptr', '*') }}}; | |
| } | |
| #endif | |
| var threadParams = { | |
| startRoutine, | |
| pthread_ptr, | |
| arg, | |
| #if OFFSCREENCANVAS_SUPPORT | |
| moduleCanvasId, | |
| offscreenCanvases, | |
| #endif | |
| transferList, | |
| }; | |
| if (ENVIRONMENT_IS_PTHREAD) { | |
| // The prepopulated pool of web workers that can host pthreads is stored | |
| // in the main JS thread. Therefore if a pthread is attempting to spawn a | |
| // new thread, the thread creation must be deferred to the main JS thread. | |
| threadParams.cmd = 'spawnThread'; | |
| postMessage(threadParams, transferList); | |
| // When we defer thread creation this way, we have no way to detect thread | |
| // creation synchronously today, so we have to assume success and return 0. | |
| return 0; | |
| } | |
| // We are the main thread, so we have the pthread warmup pool in this | |
| // thread and can fire off JS thread creation directly ourselves. | |
| return spawnThread(threadParams); | |
| }, | |
| #if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME | |
| emscripten_check_blocking_allowed__deps: ['$warnOnce'], | |
| #endif | |
| emscripten_check_blocking_allowed: () => { | |
| #if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME | |
| #if ENVIRONMENT_MAY_BE_NODE | |
| if (ENVIRONMENT_IS_NODE) return; | |
| #endif | |
| if (ENVIRONMENT_IS_WORKER) return; // Blocking in a worker/pthread is fine. | |
| warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); | |
| #if !ALLOW_BLOCKING_ON_MAIN_THREAD | |
| abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread'); | |
| #endif | |
| #endif | |
| }, | |
| // This function is called by a pthread to signal that exit() was called and | |
| // that the entire process should exit. | |
| // This function is always called from a pthread, but is executed on the | |
| // main thread due to the __proxy attribute. | |
| $exitOnMainThread__deps: ['exit'], | |
| $exitOnMainThread__proxy: 'async', | |
| $exitOnMainThread: (returnCode) => { | |
| #if PTHREADS_DEBUG | |
| dbg('exitOnMainThread'); | |
| #endif | |
| #if PROXY_TO_PTHREAD | |
| {{{ runtimeKeepalivePop() }}}; | |
| #endif | |
| _exit(returnCode); | |
| }, | |
| #if MEMORY64 | |
| // Calls proxyToMainThread but returns a bigint rather than a number | |
| $proxyToMainThreadPtr__deps: ['$proxyToMainThread'], | |
| $proxyToMainThreadPtr: (...args) => BigInt(proxyToMainThread(...args)), | |
| #endif | |
| $proxyToMainThread__deps: ['$stackSave', '$stackRestore', '$stackAlloc', '_emscripten_run_js_on_main_thread'], | |
| $proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...number)} */', | |
| $proxyToMainThread: (funcIndex, emAsmAddr, proxyMode, ...callArgs) => { | |
| // EM_ASM proxying is done by passing a pointer to the address of the EM_ASM | |
| // content as `emAsmAddr`. JS library proxying is done by passing an index | |
| // into `proxiedJSCallArgs` as `funcIndex`. If `emAsmAddr` is non-zero then | |
| // `funcIndex` will be ignored. | |
| // Additional arguments are passed after the first three are the actual | |
| // function arguments. | |
| // The serialization buffer contains the number of call params, and then | |
| // all the args here. | |
| // | |
| // We also pass 'proxyMode' to C separately, since C needs to look at it. | |
| // | |
| // Allocate a buffer (on the stack), which will be copied if necessary by | |
| // the C code. | |
| // | |
| // First passed parameter specifies the number of arguments to the function. | |
| // When BigInt support is enabled, we must handle types in a more complex | |
| // way, detecting at runtime if a value is a BigInt or not (as we have no | |
| // type info here). To do that, add a "prefix" before each value that | |
| // indicates if it is a BigInt, which effectively doubles the number of | |
| // values we serialize for proxying. TODO: pack this? | |
| var bufSize = 8 * callArgs.length {{{ WASM_BIGINT ? "* 2" : "" }}}; | |
| var sp = stackSave(); | |
| var args = stackAlloc(bufSize); | |
| var b = {{{ getHeapOffset('args', 'i64') }}}; | |
| for (var arg of callArgs) { | |
| #if WASM_BIGINT | |
| if (typeof arg == 'bigint') { | |
| // The prefix is non-zero to indicate a bigint. | |
| HEAP64[b++] = 1n; | |
| HEAP64[b++] = arg; | |
| } else { | |
| // The prefix is zero to indicate a JS Number. | |
| HEAP64[b++] = 0n; | |
| HEAPF64[b++] = arg; | |
| } | |
| #else | |
| HEAPF64[b++] = arg; | |
| #endif | |
| } | |
| var rtn = __emscripten_run_js_on_main_thread(funcIndex, emAsmAddr, bufSize, args, proxyMode); | |
| stackRestore(sp); | |
| return rtn; | |
| }, | |
| // Reuse global JS array to avoid creating JS garbage for each proxied call | |
| $proxiedJSCallArgs: [], | |
| _emscripten_receive_on_main_thread_js__deps: [ | |
| '$proxyToMainThread', | |
| '_emscripten_run_js_on_main_thread_done', | |
| '$proxiedJSCallArgs'], | |
| _emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, bufSize, args, ctx, ctxArgs) => { | |
| // Sometimes we need to backproxy events to the calling thread (e.g. | |
| // HTML5 DOM events handlers such as | |
| // emscripten_set_mousemove_callback()), so keep track in a globally | |
| // accessible variable about the thread that initiated the proxying. | |
| proxiedJSCallArgs.length = 0; | |
| var b = {{{ getHeapOffset('args', 'i64') }}}; | |
| var end = {{{ getHeapOffset('args + bufSize', 'i64') }}}; | |
| while (b < end) { | |
| #if WASM_BIGINT | |
| var arg; | |
| if (HEAP64[b++]) { | |
| // It's a BigInt. | |
| arg = HEAP64[b++]; | |
| } else { | |
| // It's a Number. | |
| arg = HEAPF64[b++]; | |
| } | |
| #else | |
| var arg = HEAPF64[b++]; | |
| #endif | |
| proxiedJSCallArgs.push(arg); | |
| } | |
| // Proxied JS library funcs use funcIndex and EM_ASM functions use emAsmAddr | |
| #if HAVE_EM_ASM | |
| var func = emAsmAddr ? ASM_CONSTS[emAsmAddr] : proxiedFunctionTable[funcIndex]; | |
| #else | |
| #if ASSERTIONS | |
| assert(!emAsmAddr); | |
| #endif | |
| var func = proxiedFunctionTable[funcIndex]; | |
| #endif | |
| #if ASSERTIONS | |
| assert(!(funcIndex && emAsmAddr)); | |
| assert(func.length == proxiedJSCallArgs.length, 'Call args mismatch in _emscripten_receive_on_main_thread_js'); | |
| #endif | |
| PThread.currentProxiedOperationCallerThread = callingThread; | |
| var rtn = func(...proxiedJSCallArgs); | |
| PThread.currentProxiedOperationCallerThread = 0; | |
| if (ctx) { | |
| rtn.then((rtn) => __emscripten_run_js_on_main_thread_done(ctx, ctxArgs, rtn)); | |
| return; | |
| } | |
| #if MEMORY64 | |
| // In memory64 mode some proxied functions return bigint/pointer but | |
| // our return type is i53/double. | |
| if (typeof rtn == "bigint") { | |
| rtn = bigintToI53Checked(rtn); | |
| } | |
| #endif | |
| #if ASSERTIONS | |
| // Proxied functions can return any type except bigint. All other types | |
| // coerce to f64/double (the return type of this function in C) but not | |
| // bigint. | |
| assert(typeof rtn != "bigint"); | |
| #endif | |
| return rtn; | |
| }, | |
| $establishStackSpace__internal: true, | |
| $establishStackSpace__deps: ['$stackRestore', 'emscripten_stack_set_limits'], | |
| $establishStackSpace: function (pthread_ptr) { | |
| var stackHigh = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack, '*') }}}; | |
| var stackSize = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack_size, '*') }}}; | |
| var stackLow = stackHigh - stackSize; | |
| #if PTHREADS_DEBUG | |
| dbg(`establishStackSpace: ${ptrToString(stackHigh)} -> ${ptrToString(stackLow)}`); | |
| #endif | |
| #if ASSERTIONS | |
| assert(stackHigh != 0); | |
| assert(stackLow != 0); | |
| assert(stackHigh > stackLow, 'stackHigh must be higher then stackLow'); | |
| #endif | |
| // Set stack limits used by `emscripten/stack.h` function. These limits are | |
| // cached in wasm-side globals to make checks as fast as possible. | |
| _emscripten_stack_set_limits(stackHigh, stackLow); | |
| #if STACK_OVERFLOW_CHECK >= 2 | |
| setStackLimits(); | |
| #endif STACK_OVERFLOW_CHECK | |
| // Call inside wasm module to set up the stack frame for this pthread in wasm module scope | |
| stackRestore(stackHigh); | |
| #if STACK_OVERFLOW_CHECK | |
| // Write the stack cookie last, after we have set up the proper bounds and | |
| // current position of the stack. | |
| writeStackCookie(); | |
| #endif | |
| }, | |
| $invokeEntryPoint__deps: [ | |
| '_emscripten_thread_exit', | |
| #if !MINIMAL_RUNTIME | |
| '$keepRuntimeAlive', | |
| '$runtimeKeepaliveCounter', | |
| #endif | |
| ], | |
| $invokeEntryPoint: {{{ asyncIf(ASYNCIFY == 2) }}}(ptr, arg) => { | |
| #if PTHREADS_DEBUG | |
| dbg(`invokeEntryPoint: ${ptrToString(ptr)}`); | |
| #endif | |
| #if !MINIMAL_RUNTIME | |
| // An old thread on this worker may have been canceled without returning the | |
| // `runtimeKeepaliveCounter` to zero. Reset it now so the new thread won't | |
| // be affected. | |
| runtimeKeepaliveCounter = 0; | |
| #if isSymbolNeeded('$noExitRuntime') | |
| // Same for noExitRuntime. The default for pthreads should always be false | |
| // otherwise pthreads would never complete and attempts to pthread_join to | |
| // them would block forever. | |
| // pthreads can still choose to set `noExitRuntime` explicitly, or | |
| // call emscripten_unwind_to_js_event_loop to extend their lifetime beyond | |
| // their main function. See comment in src/runtime_pthread.js for more. | |
| noExitRuntime = 0; | |
| #endif | |
| #endif | |
| #if MAIN_MODULE | |
| // Before we call the thread entry point, make sure any shared libraries | |
| // have been loaded on this thread. Otherwise our table might be not be | |
| // in sync and might not contain the function pointer `ptr` at all. | |
| __emscripten_dlsync_self(); | |
| #endif | |
| // pthread entry points are always of signature 'void *ThreadMain(void *arg)' | |
| // Native codebases sometimes spawn threads with other thread entry point | |
| // signatures, such as void ThreadMain(void *arg), void *ThreadMain(), or | |
| // void ThreadMain(). That is not acceptable per C/C++ specification, but | |
| // x86 compiler ABI extensions enable that to work. If you find the | |
| // following line to crash, either change the signature to "proper" void | |
| // *ThreadMain(void *arg) form, or try linking with the Emscripten linker | |
| // flag -sEMULATE_FUNCTION_POINTER_CASTS to add in emulation for this x86 | |
| // ABI extension. | |
| var result = {{{ makeDynCall('pp', 'ptr', ASYNCIFY == 2) }}}(arg); | |
| #if STACK_OVERFLOW_CHECK | |
| checkStackCookie(); | |
| #endif | |
| function finish(result) { | |
| #if !MINIMAL_RUNTIME | |
| // In MINIMAL_RUNTIME the noExitRuntime concept does not apply to | |
| // pthreads. To exit a pthread with live runtime, use the function | |
| // emscripten_unwind_to_js_event_loop() in the pthread body. | |
| if (keepRuntimeAlive()) { | |
| EXITSTATUS = result; | |
| return; | |
| } | |
| #endif | |
| __emscripten_thread_exit(result); | |
| } | |
| #if ASYNCIFY == 2 | |
| result = await result; | |
| #endif | |
| finish(result); | |
| }, | |
| #if MAIN_MODULE | |
| _emscripten_thread_exit_joinable: (thread) => { | |
| // Called when a thread exits and is joinable. We mark these threads | |
| // as finished, which means they are in state where are no longer actually | |
| // running, but remain around waiting to be joined. In this state they | |
| // cannot run any more proxied work. | |
| if (!ENVIRONMENT_IS_PTHREAD) markAsFinished(thread); | |
| else postMessage({ cmd: 'markAsFinished', thread }); | |
| }, | |
| $markAsFinished: (pthread_ptr) => { | |
| #if PTHREADS_DEBUG | |
| dbg(`markAsFinished: ${ptrToString(pthread_ptr)}`); | |
| #endif | |
| PThread.finishedThreads.add(pthread_ptr); | |
| if (pthread_ptr in PThread.outstandingPromises) { | |
| PThread.outstandingPromises[pthread_ptr].resolve(); | |
| } | |
| }, | |
| // Asynchronous version _emscripten_dlsync_threads. | |
| // This is always called on the main thread. This work happens asynchronously. | |
| $dlsyncThreadsAsync__deps: ['_emscripten_proxy_dlsync_async', '$makePromise'], | |
| $dlsyncThreadsAsync: async () => { | |
| const caller = PThread.currentProxiedOperationCallerThread; | |
| #if PTHREADS_DEBUG | |
| dbg("dlsyncThreadsAsync caller=" + ptrToString(caller)); | |
| #endif | |
| #if ASSERTIONS | |
| assert(!ENVIRONMENT_IS_PTHREAD, 'dlsyncThreadsAsync() should only be called from the main thread'); | |
| assert(Object.keys(PThread.outstandingPromises).length === 0); | |
| #endif | |
| const promises = []; | |
| // This first promise resolves once the main thread has loaded all modules. | |
| var info = makePromise(); | |
| promises.push(info.promise); | |
| __emscripten_dlsync_self_async(info.id); | |
| // We then create a sequence of promises, one per thread, that resolve once | |
| // each thread has performed its sync using _emscripten_proxy_dlsync. | |
| // Any new threads that are created after this call will automatically be | |
| // in sync because we call `__emscripten_dlsync_self` in | |
| // invokeEntryPoint before the threads entry point is called. | |
| for (const ptr of Object.keys(PThread.pthreads)) { | |
| const pthread_ptr = Number(ptr); | |
| if (pthread_ptr !== caller && !PThread.finishedThreads.has(pthread_ptr)) { | |
| info = makePromise(); | |
| __emscripten_proxy_dlsync_async(pthread_ptr, info.id); | |
| PThread.outstandingPromises[pthread_ptr] = info; | |
| promises.push(info.promise); | |
| } | |
| } | |
| #if PTHREADS_DEBUG | |
| dbg(`dlsyncThreadsAsync: waiting on ${promises.length} promises`); | |
| #endif | |
| await Promise.all(promises); | |
| PThread.outstandingPromises = {}; | |
| #if PTHREADS_DEBUG | |
| dbg('dlsyncThreadsAsync done'); | |
| #endif | |
| }, | |
| // Synchronous version of dlsync_threads. This is only needed for the case when | |
| // the main thread call dlopen and in that case we have no choice but to | |
| // synchronously block the main thread until all other threads are in sync. | |
| // When `dlopen` is called from a worker, the worker itself is blocked but | |
| // the operation its waiting on (on the main thread) can be async. | |
| _emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync', '$dlsyncThreadsAsync'], | |
| _emscripten_dlsync_threads__async: 'auto', | |
| _emscripten_dlsync_threads__proxy: 'sync', | |
| _emscripten_dlsync_threads: () => { | |
| const callingThread = PThread.currentProxiedOperationCallerThread; | |
| if (callingThread) { | |
| return dlsyncThreadsAsync(); | |
| } | |
| for (const ptr of Object.keys(PThread.pthreads)) { | |
| const pthread_ptr = Number(ptr); | |
| if (!PThread.finishedThreads.has(pthread_ptr)) { | |
| __emscripten_proxy_dlsync(pthread_ptr); | |
| } | |
| } | |
| }, | |
| #endif // MAIN_MODULE | |
| $checkMailbox__deps: ['$callUserCallback', | |
| 'pthread_self', | |
| '_emscripten_check_mailbox', | |
| '_emscripten_thread_mailbox_await'], | |
| $checkMailbox: () => { | |
| // checkMailbox can be called after the pthread has shut down. See | |
| // Pthread.terminateRuntime(). | |
| // In this case we return silently without re-registering using waitAsync. | |
| // Perhaps there is a more universal way we can detect runtime has exited. | |
| // TODO(https://github.com/emscripten-core/emscripten/issues/25076) | |
| #if ABORT_ON_WASM_EXCEPTIONS | |
| if (ABORT) return; | |
| #endif | |
| var pthread_ptr = _pthread_self(); | |
| if (!pthread_ptr) return; | |
| callUserCallback(() => { | |
| // If we are using Atomics.waitAsync as our notification mechanism, wait | |
| // for a notification before processing the mailbox to avoid missing any | |
| // work that could otherwise arrive after we've finished processing the | |
| // mailbox and before we're ready for the next notification. | |
| __emscripten_thread_mailbox_await(pthread_ptr); | |
| __emscripten_check_mailbox(); | |
| }); | |
| }, | |
| _emscripten_thread_mailbox_await__deps: ['$checkMailbox', '$waitAsyncPolyfilled'], | |
| _emscripten_thread_mailbox_await: (pthread_ptr) => { | |
| if (!waitAsyncPolyfilled) { | |
| // Wait on the pthread's initial self-pointer field because it is easy and | |
| // safe to access from sending threads that need to notify the waiting | |
| // thread. | |
| // Note: Under wasm64 only the low 32-bit of the pthread_ptr are | |
| // read/compared here, but we don't actually care about the exact values | |
| // here as long as they match. | |
| var wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('pthread_ptr', 'i32') }}}, pthread_ptr); | |
| #if ASSERTIONS | |
| assert(wait.async); | |
| #endif | |
| wait.value.then(checkMailbox); | |
| var waitingAsync = pthread_ptr + {{{ C_STRUCTS.pthread.waiting_async }}}; | |
| Atomics.store(HEAP32, {{{ getHeapOffset('waitingAsync', 'i32') }}}, 1); | |
| } | |
| // If `Atomics.waitAsync` is not implemented, then we will always fall back | |
| // to postMessage and there is no need to do anything here. | |
| }, | |
| // PostMessage is used to notify threads instead of Atomics.notify whenever | |
| // the environment does not implement Atomics.waitAsync or when messaging a | |
| // new thread that has not had a chance to initialize itself and execute | |
| // Atomics.waitAsync to prepare for the notification. | |
| _emscripten_notify_mailbox_postmessage__deps: ['$checkMailbox'], | |
| _emscripten_notify_mailbox_postmessage: (targetThread, currThreadId) => { | |
| if (targetThread == currThreadId) { | |
| setTimeout(checkMailbox); | |
| } else if (ENVIRONMENT_IS_PTHREAD) { | |
| postMessage({targetThread, cmd: 'checkMailbox'}); | |
| } else { | |
| var worker = PThread.pthreads[targetThread]; | |
| if (!worker) { | |
| #if ASSERTIONS | |
| err(`Cannot send message to thread with ID ${targetThread}, unknown thread ID!`); | |
| #endif | |
| return; | |
| } | |
| worker.postMessage({cmd: 'checkMailbox'}); | |
| } | |
| } | |
| }; | |
| autoAddDeps(LibraryPThread, '$PThread'); | |
| addToLibrary(LibraryPThread); | |
Xet Storage Details
- Size:
- 52.5 kB
- Xet hash:
- 83840e04c18b3df1b330152cd21b4b2e02d89fe5d85d6183e21ede60d39e4522
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.