| // Copyright 2015 The Emscripten Authors. All rights reserved. |
| // Emscripten is available under two separate licenses, the MIT license and the |
| // University of Illinois/NCSA Open Source License. Both these licenses can be |
| // found in the LICENSE file. |
| |
| var LibraryPThread = { |
| $PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock();', |
| $PThread__deps: ['$PROCINFO', '_register_pthread_ptr', 'emscripten_main_thread_process_queued_calls'], |
| $PThread: { |
| MAIN_THREAD_ID: 1, // A special constant that identifies the main JS thread ID. |
| mainThreadInfo: { |
| schedPolicy: 0/*SCHED_OTHER*/, |
| schedPrio: 0 |
| }, |
| // Since creating a new Web Worker is so heavy (it must reload the whole compiled script page!), maintain a pool of such |
| // workers that have already parsed and loaded the scripts. |
| unusedWorkerPool: [], |
| // The currently executing pthreads. |
| runningWorkers: [], |
| // Points to a pthread_t structure in the Emscripten main heap, allocated on demand if/when first needed. |
| // mainThreadBlock: undefined, |
| initMainThreadBlock: function() { |
| if (ENVIRONMENT_IS_PTHREAD) return undefined; |
| PThread.mainThreadBlock = {{{ makeStaticAlloc(C_STRUCTS.pthread.__size__) }}}; |
| |
| for (var i = 0; i < {{{ C_STRUCTS.pthread.__size__ }}}/4; ++i) HEAPU32[PThread.mainThreadBlock/4+i] = 0; |
| |
| // The pthread struct has a field that points to itself - this is used as a magic ID to detect whether the pthread_t |
| // structure is 'alive'. |
| {{{ makeSetValue('PThread.mainThreadBlock', C_STRUCTS.pthread.self, 'PThread.mainThreadBlock', 'i32') }}}; |
| |
| // pthread struct robust_list head should point to itself. |
| var headPtr = PThread.mainThreadBlock + {{{ C_STRUCTS.pthread.robust_list }}}; |
| {{{ makeSetValue('headPtr', 0, 'headPtr', 'i32') }}}; |
| |
| // Allocate memory for thread-local storage. |
| var tlsMemory = {{{ makeStaticAlloc(cDefine('PTHREAD_KEYS_MAX') * 4) }}}; |
| for (var i = 0; i < {{{ cDefine('PTHREAD_KEYS_MAX') }}}; ++i) HEAPU32[tlsMemory/4+i] = 0; |
| Atomics.store(HEAPU32, (PThread.mainThreadBlock + {{{ C_STRUCTS.pthread.tsd }}} ) >> 2, tlsMemory); // Init thread-local-storage memory array. |
| Atomics.store(HEAPU32, (PThread.mainThreadBlock + {{{ C_STRUCTS.pthread.tid }}} ) >> 2, PThread.mainThreadBlock); // Main thread ID. |
| Atomics.store(HEAPU32, (PThread.mainThreadBlock + {{{ C_STRUCTS.pthread.pid }}} ) >> 2, PROCINFO.pid); // Process ID. |
| |
| #if PTHREADS_PROFILING |
| PThread.createProfilerBlock(PThread.mainThreadBlock); |
| PThread.setThreadName(PThread.mainThreadBlock, "Browser main thread"); |
| PThread.setThreadStatus(PThread.mainThreadBlock, {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}); |
| #endif |
| }, |
| // Maps pthread_t to pthread info objects |
| pthreads: {}, |
| pthreadIdCounter: 2, // 0: invalid thread, 1: main JS UI thread, 2+: IDs for pthreads |
| |
| exitHandlers: null, // An array of C functions to run when this thread exits. |
| |
| #if PTHREADS_PROFILING |
| createProfilerBlock: function(pthreadPtr) { |
| var profilerBlock = (pthreadPtr == PThread.mainThreadBlock) ? {{{ makeStaticAlloc(C_STRUCTS.thread_profiler_block.__size__) }}} : _malloc({{{ C_STRUCTS.thread_profiler_block.__size__ }}}); |
| Atomics.store(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, profilerBlock); |
| |
| // Zero fill contents at startup. |
| for (var i = 0; i < {{{ C_STRUCTS.thread_profiler_block.__size__ }}}; i += 4) Atomics.store(HEAPU32, (profilerBlock + i) >> 2, 0); |
| Atomics.store(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.thread_profiler_block.currentStatusStartTime }}} ) >> 2, performance.now()); |
| }, |
| |
| // Sets the current thread status, but only if it was in the given expected state before. This is used |
| // to allow high-level control flow "override" the thread status before low-level (futex wait) operations set it. |
| setThreadStatusConditional: function(pthreadPtr, expectedStatus, newStatus) { |
| var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2); |
| if (!profilerBlock) return; |
| |
| var prevStatus = Atomics.load(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.threadStatus }}} ) >> 2); |
| |
| if (prevStatus != newStatus && (prevStatus == expectedStatus || expectedStatus == -1)) { |
| var now = performance.now(); |
| var startState = HEAPF64[(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.currentStatusStartTime }}} ) >> 3]; |
| var duration = now - startState; |
| |
| HEAPF64[((profilerBlock + {{{ C_STRUCTS.thread_profiler_block.timeSpentInStatus }}} ) >> 3) + prevStatus] += duration; |
| Atomics.store(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.threadStatus }}} ) >> 2, newStatus); |
| HEAPF64[(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.currentStatusStartTime }}} ) >> 3] = now; |
| } |
| }, |
| |
| // Unconditionally sets the thread status. |
| setThreadStatus: function(pthreadPtr, newStatus) { |
| PThread.setThreadStatusConditional(pthreadPtr, -1, newStatus); |
| }, |
| |
| setThreadName: function(pthreadPtr, name) { |
| var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2); |
| if (!profilerBlock) return; |
| stringToUTF8(name, profilerBlock + {{{ C_STRUCTS.thread_profiler_block.name }}}, 32); |
| }, |
| |
| getThreadName: function(pthreadPtr) { |
| var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2); |
| if (!profilerBlock) return ""; |
| return UTF8ToString(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.name }}}); |
| }, |
| |
| threadStatusToString: function(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: function(pthreadPtr) { |
| var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2); |
| var status = (profilerBlock == 0) ? 0 : Atomics.load(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.threadStatus }}} ) >> 2); |
| return PThread.threadStatusToString(status); |
| }, |
| #else |
| setThreadStatus: function() {}, |
| #endif |
| |
| runExitHandlers: function() { |
| if (PThread.exitHandlers !== null) { |
| while (PThread.exitHandlers.length > 0) { |
| PThread.exitHandlers.pop()(); |
| } |
| PThread.exitHandlers = null; |
| } |
| |
| // Call into the musl function that runs destructors of all thread-specific data. |
| if (ENVIRONMENT_IS_PTHREAD && threadInfoStruct) ___pthread_tsd_run_dtors(); |
| }, |
| |
| // Called when we are performing a pthread_exit(), either explicitly called by programmer, |
| // or implicitly when leaving the thread main function. |
| threadExit: function(exitCode) { |
| var tb = _pthread_self(); |
| if (tb) { // If we haven't yet exited? |
| #if PTHREADS_PROFILING |
| var profilerBlock = Atomics.load(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2); |
| Atomics.store(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, 0); |
| _free(profilerBlock); |
| #endif |
| Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode); |
| // When we publish this, the main thread is free to deallocate the thread object and we are done. |
| // Therefore set threadInfoStruct = 0; above to 'release' the object in this worker thread. |
| Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); |
| |
| // Disable all cancellation so that executing the cleanup handlers won't trigger another JS |
| // canceled exception to be thrown. |
| Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2, 1/*PTHREAD_CANCEL_DISABLE*/); |
| Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.cancelasync }}} ) >> 2, 0/*PTHREAD_CANCEL_DEFERRED*/); |
| PThread.runExitHandlers(); |
| |
| _emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); |
| __register_pthread_ptr(0, 0, 0); // Unregister the thread block also inside the asm.js scope. |
| threadInfoStruct = 0; |
| if (ENVIRONMENT_IS_PTHREAD) { |
| // This worker no longer owns any WebGL OffscreenCanvases, so transfer them back to parent thread. |
| var transferList = []; |
| |
| #if OFFSCREENCANVAS_SUPPORT |
| var offscreenCanvases = {}; |
| if (typeof GL !== 'undefined') { |
| offscreenCanvases = GL.offscreenCanvases; |
| GL.offscreenCanvases = {}; |
| } |
| #if PTHREADS_DEBUG |
| console.error('[thread ' + _pthread_self() + ', ENVIRONMENT_IS_PTHREAD: ' + ENVIRONMENT_IS_PTHREAD + ']: returning ' + Object.keys(offscreenCanvases).length + ' OffscreenCanvases to parent thread ' + parentThreadId); |
| #endif |
| for (var i in offscreenCanvases) { |
| if (offscreenCanvases[i]) transferList.push(offscreenCanvases[i].offscreenCanvas); |
| } |
| if (transferList.length > 0) { |
| postMessage({ |
| targetThread: _emscripten_main_browser_thread_id(), |
| cmd: 'objectTransfer', |
| offscreenCanvases: offscreenCanvases, |
| moduleCanvasId: Module['canvas'].id, // moduleCanvasId specifies which canvas is denoted via the "#canvas" shorthand. |
| transferList: transferList |
| }, transferList); |
| } |
| // And clear the OffscreenCanvases from lingering around in this Worker as well. |
| delete Module['canvas']; |
| #endif |
| |
| postMessage({ cmd: 'exit' }); |
| } |
| } |
| }, |
| |
| threadCancel: function() { |
| PThread.runExitHandlers(); |
| Atomics.store(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, -1/*PTHREAD_CANCELED*/); |
| Atomics.store(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running. |
| _emscripten_futex_wake(threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads |
| threadInfoStruct = selfThreadId = 0; // Not hosting a pthread anymore in this worker, reset the info structures to null. |
| __register_pthread_ptr(0, 0, 0); // Unregister the thread block also inside the asm.js scope. |
| postMessage({ cmd: 'cancelDone' }); |
| }, |
| |
| terminateAllThreads: function() { |
| for (var t in PThread.pthreads) { |
| var pthread = PThread.pthreads[t]; |
| if (pthread) { |
| PThread.freeThreadData(pthread); |
| if (pthread.worker) pthread.worker.terminate(); |
| } |
| } |
| PThread.pthreads = {}; |
| for (var t in PThread.unusedWorkerPool) { |
| var pthread = PThread.unusedWorkerPool[t]; |
| if (pthread) { |
| PThread.freeThreadData(pthread); |
| if (pthread.worker) pthread.worker.terminate(); |
| } |
| } |
| PThread.unusedWorkerPool = []; |
| for (var t in PThread.runningWorkers) { |
| var pthread = PThread.runningWorkers[t]; |
| if (pthread) { |
| PThread.freeThreadData(pthread); |
| if (pthread.worker) pthread.worker.terminate(); |
| } |
| } |
| PThread.runningWorkers = []; |
| }, |
| freeThreadData: function(pthread) { |
| if (!pthread) return; |
| if (pthread.threadInfoStruct) { |
| var tlsMemory = {{{ makeGetValue('pthread.threadInfoStruct', C_STRUCTS.pthread.tsd, 'i32') }}}; |
| {{{ makeSetValue('pthread.threadInfoStruct', C_STRUCTS.pthread.tsd, 0, 'i32') }}}; |
| _free(pthread.tlsMemory); |
| _free(pthread.threadInfoStruct); |
| } |
| pthread.threadInfoStruct = 0; |
| if (pthread.allocatedOwnStack && pthread.stackBase) _free(pthread.stackBase); |
| pthread.stackBase = 0; |
| if (pthread.worker) pthread.worker.pthread = null; |
| }, |
| |
| receiveObjectTransfer: function(data) { |
| #if OFFSCREENCANVAS_SUPPORT |
| if (typeof GL !== 'undefined') { |
| for (var i in data.offscreenCanvases) { |
| GL.offscreenCanvases[i] = data.offscreenCanvases[i]; |
| } |
| if (!Module['canvas'] && data.moduleCanvasId && GL.offscreenCanvases[data.moduleCanvasId]) { |
| Module['canvas'] = GL.offscreenCanvases[data.moduleCanvasId].offscreenCanvas; |
| Module['canvas'].id = data.moduleCanvasId; |
| } |
| } |
| #endif |
| }, |
| |
| // Allocates the given amount of new web workers and stores them in the pool of unused workers. |
| // onFinishedLoading: A callback function that will be called once all of the workers have been initialized and are |
| // ready to host pthreads. Optional. This is used to mitigate bug https://bugzilla.mozilla.org/show_bug.cgi?id=1049079 |
| allocateUnusedWorkers: function(numWorkers, onFinishedLoading) { |
| if (typeof SharedArrayBuffer === 'undefined') return; // No multithreading support, no-op. |
| out('Preallocating ' + numWorkers + ' workers for a pthread spawn pool.'); |
| |
| var numWorkersLoaded = 0; |
| var pthreadMainJs = "{{{ PTHREAD_WORKER_FILE }}}"; |
| // Allow HTML module to configure the location where the 'worker.js' file will be loaded from, |
| // via Module.locateFile() function. If not specified, then the default URL 'worker.js' relative |
| // to the main html file is loaded. |
| pthreadMainJs = locateFile(pthreadMainJs); |
| |
| for (var i = 0; i < numWorkers; ++i) { |
| var worker = new Worker(pthreadMainJs); |
| |
| (function(worker) { |
| worker.onmessage = function(e) { |
| var d = e.data; |
| // 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. |
| if (worker.pthread) PThread.currentProxiedOperationCallerThread = worker.pthread.threadInfoStruct; |
| |
| // 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 thread = PThread.pthreads[d.targetThread]; |
| if (thread) { |
| thread.worker.postMessage(e.data, d.transferList); |
| } else { |
| console.error('Internal error! Worker sent a message "' + d.cmd + '" to target pthread ' + d.targetThread + ', but that thread no longer exists!'); |
| } |
| PThread.currentProxiedOperationCallerThread = undefined; |
| return; |
| } |
| |
| if (d.cmd === 'processQueuedMainThreadWork') { |
| // TODO: Must post message to main Emscripten thread in PROXY_TO_WORKER mode. |
| _emscripten_main_thread_process_queued_calls(); |
| } else if (d.cmd === 'spawnThread') { |
| __spawn_thread(e.data); |
| } else if (d.cmd === 'cleanupThread') { |
| __cleanup_thread(d.thread); |
| } else if (d.cmd === 'killThread') { |
| __kill_thread(d.thread); |
| } else if (d.cmd === 'cancelThread') { |
| __cancel_thread(d.thread); |
| } else if (d.cmd === 'loaded') { |
| worker.loaded = true; |
| // If this Worker is already pending to start running a thread, launch the thread now |
| if (worker.runPthread) { |
| worker.runPthread(); |
| delete worker.runPthread; |
| } |
| ++numWorkersLoaded; |
| if (numWorkersLoaded === numWorkers && onFinishedLoading) { |
| onFinishedLoading(); |
| } |
| } else if (d.cmd === 'print') { |
| out('Thread ' + d.threadId + ': ' + d.text); |
| } else if (d.cmd === 'printErr') { |
| err('Thread ' + d.threadId + ': ' + d.text); |
| } else if (d.cmd === 'alert') { |
| alert('Thread ' + d.threadId + ': ' + d.text); |
| } else if (d.cmd === 'exit') { |
| // Thread is exiting, no-op here |
| } else if (d.cmd === 'exitProcess') { |
| // A pthread has requested to exit the whole application process (runtime). |
| Module['noExitRuntime'] = false; |
| exit(d.returnCode); |
| } else if (d.cmd === 'cancelDone') { |
| PThread.freeThreadData(worker.pthread); |
| worker.pthread = undefined; // Detach the worker from the pthread object, and return it to the worker pool as an unused worker. |
| PThread.unusedWorkerPool.push(worker); |
| // TODO: Free if detached. |
| PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker.pthread), 1); // Not a running Worker anymore. |
| } else if (d.cmd === 'objectTransfer') { |
| PThread.receiveObjectTransfer(e.data); |
| } else if (e.data.target === 'setimmediate') { |
| worker.postMessage(e.data); // Worker wants to postMessage() to itself to implement setImmediate() emulation. |
| } else { |
| err("worker sent an unknown command " + d.cmd); |
| } |
| PThread.currentProxiedOperationCallerThread = undefined; |
| }; |
| |
| worker.onerror = function(e) { |
| err('pthread sent an error! ' + e.filename + ':' + e.lineno + ': ' + e.message); |
| }; |
| }(worker)); |
| |
| // Allocate tempDoublePtr for the worker. This is done here on the worker's behalf, since we may need to do this statically |
| // if the runtime has not been loaded yet, etc. - so we just use getMemory, which is main-thread only. |
| var tempDoublePtr = getMemory(8); // TODO: leaks. Cleanup after worker terminates. |
| |
| // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation. |
| worker.postMessage({ |
| cmd: 'load', |
| // If the application main .js file was loaded from a Blob, then it is not possible |
| // to access the URL of the current script that could be passed to a Web Worker so that |
| // it could load up the same file. In that case, developer must either deliver the Blob |
| // object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can |
| // independently load up the same main application file. |
| urlOrBlob: Module['mainScriptUrlOrBlob'] || _scriptDir, |
| #if WASM |
| wasmMemory: wasmMemory, |
| wasmModule: wasmModule, |
| #else |
| buffer: HEAPU8.buffer, |
| asmJsUrlOrBlob: Module["asmJsUrlOrBlob"], |
| #endif |
| tempDoublePtr: tempDoublePtr, |
| TOTAL_MEMORY: TOTAL_MEMORY, |
| DYNAMIC_BASE: DYNAMIC_BASE, |
| DYNAMICTOP_PTR: DYNAMICTOP_PTR, |
| PthreadWorkerInit: PthreadWorkerInit |
| }); |
| PThread.unusedWorkerPool.push(worker); |
| } |
| }, |
| |
| getNewWorker: function() { |
| if (PThread.unusedWorkerPool.length == 0) PThread.allocateUnusedWorkers(1); |
| if (PThread.unusedWorkerPool.length > 0) return PThread.unusedWorkerPool.pop(); |
| else return null; |
| }, |
| |
| busySpinWait: function(msecs) { |
| var t = performance.now() + msecs; |
| while(performance.now() < t) { |
| ; |
| } |
| } |
| }, |
| |
| _kill_thread: function(pthread_ptr) { |
| if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _kill_thread() can only ever be called from main application thread!'; |
| if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in _kill_thread!'; |
| {{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}}; |
| var pthread = PThread.pthreads[pthread_ptr]; |
| pthread.worker.terminate(); |
| PThread.freeThreadData(pthread); |
| // The worker was completely nuked (not just the pthread execution it was hosting), so remove it from running workers |
| // but don't put it back to the pool. |
| PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(pthread.worker.pthread), 1); // Not a running Worker anymore. |
| pthread.worker.pthread = undefined; |
| }, |
| |
| _cleanup_thread: function(pthread_ptr) { |
| if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _cleanup_thread() can only ever be called from main application thread!'; |
| if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in _cleanup_thread!'; |
| {{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}}; |
| var pthread = PThread.pthreads[pthread_ptr]; |
| var worker = pthread.worker; |
| PThread.freeThreadData(pthread); |
| worker.pthread = undefined; // Detach the worker from the pthread object, and return it to the worker pool as an unused worker. |
| PThread.unusedWorkerPool.push(worker); |
| PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker.pthread), 1); // Not a running Worker anymore. |
| }, |
| |
| _cancel_thread: function(pthread_ptr) { |
| if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _cancel_thread() can only ever be called from main application thread!'; |
| if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in _cancel_thread!'; |
| var pthread = PThread.pthreads[pthread_ptr]; |
| pthread.worker.postMessage({ cmd: 'cancel' }); |
| }, |
| |
| _spawn_thread: function(threadParams) { |
| if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _spawn_thread() can only ever be called from main application thread!'; |
| |
| var worker = PThread.getNewWorker(); |
| if (worker.pthread !== undefined) throw 'Internal error!'; |
| if (!threadParams.pthread_ptr) throw 'Internal error, no pthread ptr!'; |
| PThread.runningWorkers.push(worker); |
| |
| // Allocate memory for thread-local storage and initialize it to zero. |
| var tlsMemory = _malloc({{{ cDefine('PTHREAD_KEYS_MAX') }}} * 4); |
| for (var i = 0; i < {{{ cDefine('PTHREAD_KEYS_MAX') }}}; ++i) { |
| {{{ makeSetValue('tlsMemory', 'i*4', 0, 'i32') }}}; |
| } |
| |
| var pthread = PThread.pthreads[threadParams.pthread_ptr] = { // Create a pthread info object to represent this thread. |
| worker: worker, |
| stackBase: threadParams.stackBase, |
| stackSize: threadParams.stackSize, |
| allocatedOwnStack: threadParams.allocatedOwnStack, |
| thread: threadParams.pthread_ptr, |
| threadInfoStruct: threadParams.pthread_ptr // Info area for this thread in Emscripten HEAP (shared) |
| }; |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 0); // threadStatus <- 0, meaning not yet exited. |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, 0); // threadExitCode <- 0. |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, 0); // profilerBlock <- 0. |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.detached }}} ) >> 2, threadParams.detached); |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.tsd }}} ) >> 2, tlsMemory); // Init thread-local-storage memory array. |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.tsd_used }}} ) >> 2, 0); // Mark initial status to unused. |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.tid }}} ) >> 2, pthread.threadInfoStruct); // Main thread ID. |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.pid }}} ) >> 2, PROCINFO.pid); // Process ID. |
| |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}}) >> 2, threadParams.stackSize); |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.stack_size }}}) >> 2, threadParams.stackSize); |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.stack }}}) >> 2, threadParams.stackBase); |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 8) >> 2, threadParams.stackBase); |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 12) >> 2, threadParams.detached); |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 20) >> 2, threadParams.schedPolicy); |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 24) >> 2, threadParams.schedPrio); |
| |
| var global_libc = _emscripten_get_global_libc(); |
| var global_locale = global_libc + {{{ C_STRUCTS.libc.global_locale }}}; |
| Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.locale }}}) >> 2, global_locale); |
| |
| #if PTHREADS_PROFILING |
| PThread.createProfilerBlock(pthread.threadInfoStruct); |
| #endif |
| |
| worker.pthread = pthread; |
| var msg = { |
| cmd: 'run', |
| start_routine: threadParams.startRoutine, |
| arg: threadParams.arg, |
| threadInfoStruct: threadParams.pthread_ptr, |
| selfThreadId: threadParams.pthread_ptr, // TODO: Remove this since thread ID is now the same as the thread address. |
| parentThreadId: threadParams.parent_pthread_ptr, |
| stackBase: threadParams.stackBase, |
| stackSize: threadParams.stackSize, |
| #if OFFSCREENCANVAS_SUPPORT |
| moduleCanvasId: threadParams.moduleCanvasId, |
| offscreenCanvases: threadParams.offscreenCanvases, |
| #endif |
| }; |
| worker.runPthread = function() { |
| // Ask the worker to start executing its pthread entry point function. |
| msg.time = performance.now(); |
| worker.postMessage(msg, threadParams.transferList); |
| }; |
| if (worker.loaded) { |
| worker.runPthread(); |
| delete worker.runPthread; |
| } |
| }, |
| |
| _num_logical_cores__deps: ['emscripten_force_num_logical_cores'], |
| _num_logical_cores: '; if (ENVIRONMENT_IS_PTHREAD) __num_logical_cores = PthreadWorkerInit.__num_logical_cores; else { PthreadWorkerInit.__num_logical_cores = __num_logical_cores = {{{ makeStaticAlloc(4) }}}; HEAPU32[__num_logical_cores>>2] = navigator["hardwareConcurrency"] || ' + {{{ PTHREAD_HINT_NUM_CORES }}} + '; }', |
| |
| emscripten_has_threading_support: function() { |
| return typeof SharedArrayBuffer !== 'undefined'; |
| }, |
| |
| emscripten_num_logical_cores__deps: ['_num_logical_cores'], |
| emscripten_num_logical_cores: function() { |
| return {{{ makeGetValue('__num_logical_cores', 0, 'i32') }}}; |
| }, |
| |
| emscripten_force_num_logical_cores: function(cores) { |
| {{{ makeSetValue('__num_logical_cores', 0, 'cores', 'i32') }}}; |
| }, |
| |
| pthread_create__deps: ['_spawn_thread', 'pthread_getschedparam', 'pthread_self'], |
| pthread_create: function(pthread_ptr, attr, start_routine, arg) { |
| if (typeof SharedArrayBuffer === 'undefined') { |
| err('Current environment does not support SharedArrayBuffer, pthreads are not available!'); |
| return {{{ cDefine('EAGAIN') }}}; |
| } |
| if (!pthread_ptr) { |
| err('pthread_create called with a null thread pointer!'); |
| return {{{ cDefine('EINVAL') }}}; |
| } |
| |
| var transferList = []; // List of JS objects that will transfer ownership to the Worker hosting the thread |
| 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. |
| var transferredCanvasNames = attr ? {{{ makeGetValue('attr', 36, 'i32') }}} : 0; // Comma-delimited list of IDs "canvas1, canvas2, ..." |
| if (transferredCanvasNames) transferredCanvasNames = UTF8ToString(transferredCanvasNames).trim(); |
| if (transferredCanvasNames) transferredCanvasNames = transferredCanvasNames.split(','); |
| #if GL_DEBUG |
| console.log('pthread_create: transferredCanvasNames="' + transferredCanvasNames + '"'); |
| #endif |
| |
| var offscreenCanvases = {}; // Dictionary of OffscreenCanvas objects we'll transfer to the created thread to own |
| var moduleCanvasId = Module['canvas'] ? Module['canvas'].id : ''; |
| for (var i in transferredCanvasNames) { |
| var name = transferredCanvasNames[i].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 = {{{ cDefine('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 -s \'DEFAULT_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 { |
| var canvas = (Module['canvas'] && Module['canvas'].id === name) ? Module['canvas'] : document.getElementById(name); |
| if (!canvas) { |
| err('pthread_create: could not find canvas with ID "' + name + '" to transfer to thread!'); |
| error = {{{ cDefine('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 = {{{ cDefine('EPERM') }}}; // Operation not permitted, some other thread is accessing the canvas. |
| break; |
| } |
| if (canvas.transferControlToOffscreen) { |
| #if GL_DEBUG |
| Module['printErr']('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(12); |
| {{{ makeSetValue('canvas.canvasSharedPtr', 0, 'canvas.width', 'i32') }}}; |
| {{{ makeSetValue('canvas.canvasSharedPtr', 4, 'canvas.height', 'i32') }}}; |
| {{{ makeSetValue('canvas.canvasSharedPtr', 8, 0, 'i32') }}}; // 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 |
| Module['printErr']('pthread_create: Build with -s OFFSCREEN_FRAMEBUFFER=1 to enable fallback proxying of GL commands from pthread to main thread.'); |
| return {{{ cDefine('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 {{{ cDefine('EINVAL') }}}; // Hitting this might indicate an implementation bug or some other internal error |
| } |
| } |
| #endif |
| |
| // 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 _emscripten_sync_run_in_main_thread_4({{{ cDefine('EM_PROXIED_PTHREAD_CREATE') }}}, pthread_ptr, attr, start_routine, arg); |
| } |
| |
| // If on the main thread, and accessing Canvas/OffscreenCanvas failed, abort with the detected error. |
| if (error) return error; |
| |
| var stackSize = 0; |
| var stackBase = 0; |
| var detached = 0; // Default thread attr is PTHREAD_CREATE_JOINABLE, i.e. start as not detached. |
| var schedPolicy = 0; /*SCHED_OTHER*/ |
| var schedPrio = 0; |
| if (attr) { |
| stackSize = {{{ makeGetValue('attr', 0, 'i32') }}}; |
| // Musl has a convention that the stack size that is stored to the pthread attribute structure is always musl's #define DEFAULT_STACK_SIZE |
| // smaller than the actual created stack size. That is, stored stack size of 0 would mean a stack of DEFAULT_STACK_SIZE in size. All musl |
| // functions hide this impl detail, and offset the size transparently, so pthread_*() API user does not see this offset when operating with |
| // the pthread API. When reading the structure directly on JS side however, we need to offset the size manually here. |
| stackSize += 81920 /*DEFAULT_STACK_SIZE*/; |
| stackBase = {{{ makeGetValue('attr', 8, 'i32') }}}; |
| detached = {{{ makeGetValue('attr', 12/*_a_detach*/, 'i32') }}} != 0/*PTHREAD_CREATE_JOINABLE*/; |
| var inheritSched = {{{ makeGetValue('attr', 16/*_a_sched*/, 'i32') }}} == 0/*PTHREAD_INHERIT_SCHED*/; |
| if (inheritSched) { |
| var prevSchedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}}; |
| var prevSchedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}}; |
| // If we are inheriting the scheduling properties from the parent thread, we need to identify the parent thread properly - this function call may |
| // be getting proxied, in which case _pthread_self() will point to the thread performing the proxying, not the thread that initiated the call. |
| var parentThreadPtr = PThread.currentProxiedOperationCallerThread ? PThread.currentProxiedOperationCallerThread : _pthread_self(); |
| _pthread_getschedparam(parentThreadPtr, attr + 20, attr + 24); |
| schedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}}; |
| schedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}}; |
| {{{ makeSetValue('attr', 20/*_a_policy*/, 'prevSchedPolicy', 'i32') }}}; |
| {{{ makeSetValue('attr', 24/*_a_prio*/, 'prevSchedPrio', 'i32') }}}; |
| } else { |
| schedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}}; |
| schedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}}; |
| } |
| } else { |
| // According to http://man7.org/linux/man-pages/man3/pthread_create.3.html, default stack size if not specified is 2 MB, so follow that convention. |
| stackSize = {{{ DEFAULT_PTHREAD_STACK_SIZE }}}; |
| } |
| var allocatedOwnStack = stackBase == 0; // If allocatedOwnStack == true, then the pthread impl maintains the stack allocation. |
| if (allocatedOwnStack) { |
| stackBase = _malloc(stackSize); // Allocate a stack if the user doesn't want to place the stack in a custom memory area. |
| } else { |
| // Musl stores the stack base address assuming stack grows downwards, so adjust it to Emscripten convention that the |
| // stack grows upwards instead. |
| stackBase -= stackSize; |
| assert(stackBase > 0); |
| } |
| |
| // Allocate thread block (pthread_t structure). |
| var threadInfoStruct = _malloc({{{ C_STRUCTS.pthread.__size__ }}}); |
| for (var i = 0; i < {{{ C_STRUCTS.pthread.__size__ }}} >> 2; ++i) HEAPU32[(threadInfoStruct>>2) + i] = 0; // zero-initialize thread structure. |
| {{{ makeSetValue('pthread_ptr', 0, 'threadInfoStruct', 'i32') }}}; |
| |
| // The pthread struct has a field that points to itself - this is used as a magic ID to detect whether the pthread_t |
| // structure is 'alive'. |
| {{{ makeSetValue('threadInfoStruct', C_STRUCTS.pthread.self, 'threadInfoStruct', 'i32') }}}; |
| |
| // pthread struct robust_list head should point to itself. |
| var headPtr = threadInfoStruct + {{{ C_STRUCTS.pthread.robust_list }}}; |
| {{{ makeSetValue('headPtr', 0, 'headPtr', 'i32') }}}; |
| |
| #if OFFSCREENCANVAS_SUPPORT |
| // Register for each of the transferred canvases that the new thread now owns the OffscreenCanvas. |
| for (var i in offscreenCanvases) { |
| {{{ makeSetValue('offscreenCanvases[i].canvasSharedPtr', 8, 'threadInfoStruct', 'i32') }}}; // pthread ptr to the thread that owns this canvas. |
| } |
| #endif |
| |
| var threadParams = { |
| stackBase: stackBase, |
| stackSize: stackSize, |
| allocatedOwnStack: allocatedOwnStack, |
| schedPolicy: schedPolicy, |
| schedPrio: schedPrio, |
| detached: detached, |
| startRoutine: start_routine, |
| pthread_ptr: threadInfoStruct, |
| parent_pthread_ptr: _pthread_self(), |
| arg: arg, |
| #if OFFSCREENCANVAS_SUPPORT |
| moduleCanvasId: moduleCanvasId, |
| offscreenCanvases: offscreenCanvases, |
| #endif |
| transferList: 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); |
| } else { |
| // We are the main thread, so we have the pthread warmup pool in this thread and can fire off JS thread creation |
| // directly ourselves. |
| __spawn_thread(threadParams); |
| } |
| |
| return 0; |
| }, |
| |
| // TODO HACK! Remove this function, it is a JS side copy of the function pthread_testcancel() in library_pthread.c. |
| // Just call pthread_testcancel() everywhere. |
| _pthread_testcancel_js: function() { |
| if (!ENVIRONMENT_IS_PTHREAD) return; |
| if (!threadInfoStruct) return; |
| var cancelDisabled = Atomics.load(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2); |
| if (cancelDisabled) return; |
| var canceled = Atomics.load(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2); |
| if (canceled == 2) throw 'Canceled!'; |
| }, |
| |
| pthread_join__deps: ['_cleanup_thread', '_pthread_testcancel_js', 'emscripten_main_thread_process_queued_calls'], |
| pthread_join: function(thread, status) { |
| if (!thread) { |
| err('pthread_join attempted on a null thread pointer!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| if (ENVIRONMENT_IS_PTHREAD && selfThreadId == thread) { |
| err('PThread ' + thread + ' is attempting to join to itself!'); |
| return ERRNO_CODES.EDEADLK; |
| } |
| else if (!ENVIRONMENT_IS_PTHREAD && PThread.mainThreadBlock == thread) { |
| err('Main thread ' + thread + ' is attempting to join to itself!'); |
| return ERRNO_CODES.EDEADLK; |
| } |
| var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}}; |
| if (self != thread) { |
| err('pthread_join attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| |
| var detached = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.detached }}} ) >> 2); |
| if (detached) { |
| err('Attempted to join thread ' + thread + ', which was already detached!'); |
| return ERRNO_CODES.EINVAL; // The thread is already detached, can no longer join it! |
| } |
| for (;;) { |
| var threadStatus = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2); |
| if (threadStatus == 1) { // Exited? |
| var threadExitCode = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2); |
| if (status) {{{ makeSetValue('status', 0, 'threadExitCode', 'i32') }}}; |
| Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.detached }}} ) >> 2, 1); // Mark the thread as detached. |
| |
| if (!ENVIRONMENT_IS_PTHREAD) __cleanup_thread(thread); |
| else postMessage({ cmd: 'cleanupThread', thread: thread}); |
| return 0; |
| } |
| // TODO HACK! Replace the _js variant with just _pthread_testcancel: |
| //_pthread_testcancel(); |
| __pthread_testcancel_js(); |
| // In main runtime thread (the thread that initialized the Emscripten C runtime and launched main()), assist pthreads in performing operations |
| // that they need to access the Emscripten main runtime for. |
| if (!ENVIRONMENT_IS_PTHREAD) _emscripten_main_thread_process_queued_calls(); |
| _emscripten_futex_wait(thread + {{{ C_STRUCTS.pthread.threadStatus }}}, threadStatus, ENVIRONMENT_IS_PTHREAD ? 100 : 1); |
| } |
| }, |
| |
| pthread_kill__deps: ['_kill_thread'], |
| pthread_kill: function(thread, signal) { |
| if (signal < 0 || signal >= 65/*_NSIG*/) return ERRNO_CODES.EINVAL; |
| if (thread == PThread.MAIN_THREAD_ID) { |
| if (signal == 0) return 0; // signal == 0 is a no-op. |
| err('Main thread (id=' + thread + ') cannot be killed with pthread_kill!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| if (!thread) { |
| err('pthread_kill attempted on a null thread pointer!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}}; |
| if (self != thread) { |
| err('pthread_kill attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| if (signal != 0) { |
| if (!ENVIRONMENT_IS_PTHREAD) __kill_thread(thread); |
| else postMessage({ cmd: 'killThread', thread: thread}); |
| } |
| return 0; |
| }, |
| |
| pthread_cancel__deps: ['_cancel_thread'], |
| pthread_cancel: function(thread) { |
| if (thread == PThread.MAIN_THREAD_ID) { |
| err('Main thread (id=' + thread + ') cannot be canceled!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| if (!thread) { |
| err('pthread_cancel attempted on a null thread pointer!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}}; |
| if (self != thread) { |
| err('pthread_cancel attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| Atomics.compareExchange(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 0, 2); // Signal the thread that it needs to cancel itself. |
| if (!ENVIRONMENT_IS_PTHREAD) __cancel_thread(thread); |
| else postMessage({ cmd: 'cancelThread', thread: thread}); |
| return 0; |
| }, |
| |
| pthread_detach: function(thread) { |
| if (!thread) { |
| err('pthread_detach attempted on a null thread pointer!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}}; |
| if (self != thread) { |
| err('pthread_detach attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| var threadStatus = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2); |
| // Follow musl convention: detached:0 means not detached, 1 means the thread was created as detached, and 2 means that the thread was detached via pthread_detach. |
| var wasDetached = Atomics.compareExchange(HEAPU32, (thread + {{{ C_STRUCTS.pthread.detached }}} ) >> 2, 0, 2); |
| |
| return wasDetached ? ERRNO_CODES.EINVAL : 0; |
| }, |
| |
| pthread_exit__deps: ['exit'], |
| pthread_exit: function(status) { |
| if (!ENVIRONMENT_IS_PTHREAD) _exit(status); |
| else PThread.threadExit(status); |
| #if WASM_BACKEND |
| // pthread_exit is marked noReturn, so we must not return from it. |
| throw 'pthread_exit'; |
| #endif |
| }, |
| |
| _pthread_ptr: 0, |
| _pthread_is_main_runtime_thread: 0, |
| _pthread_is_main_browser_thread: 0, |
| |
| _register_pthread_ptr__deps: ['_pthread_ptr', '_pthread_is_main_runtime_thread', '_pthread_is_main_browser_thread'], |
| _register_pthread_ptr__asm: true, |
| _register_pthread_ptr__sig: 'viii', |
| _register_pthread_ptr: function(pthreadPtr, isMainBrowserThread, isMainRuntimeThread) { |
| pthreadPtr = pthreadPtr|0; |
| isMainBrowserThread = isMainBrowserThread|0; |
| isMainRuntimeThread = isMainRuntimeThread|0; |
| __pthread_ptr = pthreadPtr; |
| __pthread_is_main_browser_thread = isMainBrowserThread; |
| __pthread_is_main_runtime_thread = isMainRuntimeThread; |
| }, |
| |
| // Public pthread_self() function which returns a unique ID for the thread. |
| pthread_self__deps: ['_pthread_ptr'], |
| pthread_self__asm: true, |
| pthread_self__sig: 'i', |
| pthread_self: function() { |
| return __pthread_ptr|0; |
| }, |
| |
| emscripten_is_main_runtime_thread__asm: true, |
| emscripten_is_main_runtime_thread__sig: 'i', |
| emscripten_is_main_runtime_thread__deps: ['_pthread_is_main_runtime_thread'], |
| emscripten_is_main_runtime_thread: function() { |
| return __pthread_is_main_runtime_thread|0; // Semantically the same as testing "!ENVIRONMENT_IS_PTHREAD" outside the asm.js scope |
| }, |
| |
| emscripten_is_main_browser_thread__asm: true, |
| emscripten_is_main_browser_thread__sig: 'i', |
| emscripten_is_main_browser_thread__deps: ['_pthread_is_main_browser_thread'], |
| emscripten_is_main_browser_thread: function() { |
| return __pthread_is_main_browser_thread|0; // Semantically the same as testing "!ENVIRONMENT_IS_WORKER" outside the asm.js scope |
| }, |
| |
| pthread_getschedparam: function(thread, policy, schedparam) { |
| if (!policy && !schedparam) return ERRNO_CODES.EINVAL; |
| |
| if (!thread) { |
| err('pthread_getschedparam called with a null thread pointer!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}}; |
| if (self != thread) { |
| err('pthread_getschedparam attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| |
| var schedPolicy = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 20 ) >> 2); |
| var schedPrio = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 24 ) >> 2); |
| |
| if (policy) {{{ makeSetValue('policy', 0, 'schedPolicy', 'i32') }}}; |
| if (schedparam) {{{ makeSetValue('schedparam', 0, 'schedPrio', 'i32') }}}; |
| return 0; |
| }, |
| |
| pthread_setschedparam: function(thread, policy, schedparam) { |
| if (!thread) { |
| err('pthread_setschedparam called with a null thread pointer!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}}; |
| if (self != thread) { |
| err('pthread_setschedparam attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| |
| if (!schedparam) return ERRNO_CODES.EINVAL; |
| |
| var newSchedPrio = {{{ makeGetValue('schedparam', 0, 'i32') }}}; |
| if (newSchedPrio < 0) return ERRNO_CODES.EINVAL; |
| if (policy == 1/*SCHED_FIFO*/ || policy == 2/*SCHED_RR*/) { |
| if (newSchedPrio > 99) return ERRNO_CODES.EINVAL; |
| } else { |
| if (newSchedPrio > 1) return ERRNO_CODES.EINVAL; |
| } |
| |
| Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 20) >> 2, policy); |
| Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 24) >> 2, newSchedPrio); |
| return 0; |
| }, |
| |
| // Marked as obsolescent in pthreads specification: http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html |
| pthread_getconcurrency: function() { |
| return 0; |
| }, |
| |
| // Marked as obsolescent in pthreads specification. |
| pthread_setconcurrency: function(new_level) { |
| // no-op |
| return 0; |
| }, |
| |
| pthread_mutexattr_getprioceiling: function(attr, prioceiling) { |
| // Not supported either in Emscripten or musl, return a faked value. |
| if (prioceiling) {{{ makeSetValue('prioceiling', 0, 99, 'i32') }}}; |
| return 0; |
| }, |
| |
| pthread_mutexattr_setprioceiling: function(attr, prioceiling) { |
| // Not supported either in Emscripten or musl, return an error. |
| return ERRNO_CODES.EPERM; |
| }, |
| |
| pthread_getcpuclockid: function(thread, clock_id) { |
| return ERRNO_CODES.ENOENT; // pthread API recommends returning this error when "Per-thread CPU time clocks are not supported by the system." |
| }, |
| |
| pthread_setschedprio: function(thread, prio) { |
| if (!thread) { |
| err('pthread_setschedprio called with a null thread pointer!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}}; |
| if (self != thread) { |
| err('pthread_setschedprio attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!'); |
| return ERRNO_CODES.ESRCH; |
| } |
| if (prio < 0) return ERRNO_CODES.EINVAL; |
| |
| var schedPolicy = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 20 ) >> 2); |
| if (schedPolicy == 1/*SCHED_FIFO*/ || schedPolicy == 2/*SCHED_RR*/) { |
| if (prio > 99) return ERRNO_CODES.EINVAL; |
| } else { |
| if (prio > 1) return ERRNO_CODES.EINVAL; |
| } |
| |
| Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 24) >> 2, prio); |
| return 0; |
| }, |
| |
| pthread_cleanup_push: function(routine, arg) { |
| if (PThread.exitHandlers === null) { |
| PThread.exitHandlers = []; |
| if (!ENVIRONMENT_IS_PTHREAD) { |
| __ATEXIT__.push(function() { PThread.runExitHandlers(); }); |
| } |
| } |
| PThread.exitHandlers.push(function() { {{{ makeDynCall('vi') }}}(routine, arg) }); |
| }, |
| |
| pthread_cleanup_pop: function(execute) { |
| var routine = PThread.exitHandlers.pop(); |
| if (execute) routine(); |
| }, |
| |
| // pthread_sigmask - examine and change mask of blocked signals |
| pthread_sigmask: function(how, set, oldset) { |
| err('pthread_sigmask() is not supported: this is a no-op.'); |
| return 0; |
| }, |
| |
| pthread_atfork: function(prepare, parent, child) { |
| err('fork() is not supported: pthread_atfork is a no-op.'); |
| return 0; |
| }, |
| |
| // Stores the memory address that the main thread is waiting on, if any. |
| _main_thread_futex_wait_address: '; if (ENVIRONMENT_IS_PTHREAD) __main_thread_futex_wait_address = PthreadWorkerInit.__main_thread_futex_wait_address; else PthreadWorkerInit.__main_thread_futex_wait_address = __main_thread_futex_wait_address = {{{ makeStaticAlloc(4) }}}', |
| |
| // Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error. |
| emscripten_futex_wait__deps: ['_main_thread_futex_wait_address', 'emscripten_main_thread_process_queued_calls'], |
| emscripten_futex_wait: function(addr, val, timeout) { |
| if (addr <= 0 || addr > HEAP8.length || addr&3 != 0) return -{{{ cDefine('EINVAL') }}}; |
| // dump('futex_wait addr:' + addr + ' by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n'); |
| if (ENVIRONMENT_IS_WORKER) { |
| #if PTHREADS_PROFILING |
| PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}}); |
| #endif |
| var ret = Atomics.wait(HEAP32, addr >> 2, val, timeout); |
| // dump('futex_wait done by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n'); |
| #if PTHREADS_PROFILING |
| PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}}, {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}); |
| #endif |
| if (ret === 'timed-out') return -{{{ cDefine('ETIMEDOUT') }}}; |
| if (ret === 'not-equal') return -{{{ cDefine('EWOULDBLOCK') }}}; |
| if (ret === 'ok') return 0; |
| throw 'Atomics.wait returned an unexpected value ' + ret; |
| } else { |
| // Atomics.wait is not available in the main browser thread, so simulate it via busy spinning. |
| var loadedVal = Atomics.load(HEAP32, addr >> 2); |
| if (val != loadedVal) return -{{{ cDefine('EWOULDBLOCK') }}}; |
| |
| var tNow = performance.now(); |
| var tEnd = tNow + timeout; |
| |
| #if PTHREADS_PROFILING |
| PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}}); |
| #endif |
| |
| // Register globally which address the main thread is simulating to be waiting on. When zero, main thread is not waiting on anything, |
| // and on nonzero, the contents of address pointed by __main_thread_futex_wait_address tell which address the main thread is simulating its wait on. |
| Atomics.store(HEAP32, __main_thread_futex_wait_address >> 2, addr); |
| var ourWaitAddress = addr; // We may recursively re-enter this function while processing queued calls, in which case we'll do a spurious wakeup of the older wait operation. |
| while (addr == ourWaitAddress) { |
| tNow = performance.now(); |
| if (tNow > tEnd) { |
| #if PTHREADS_PROFILING |
| PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}}); |
| #endif |
| return -{{{ cDefine('ETIMEDOUT') }}}; |
| } |
| _emscripten_main_thread_process_queued_calls(); // We are performing a blocking loop here, so must pump any pthreads if they want to perform operations that are proxied. |
| addr = Atomics.load(HEAP32, __main_thread_futex_wait_address >> 2); // Look for a worker thread waking us up. |
| } |
| #if PTHREADS_PROFILING |
| PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}}); |
| #endif |
| return 0; |
| } |
| }, |
| |
| // Returns the number of threads (>= 0) woken up, or the value -EINVAL on error. |
| // Pass count == INT_MAX to wake up all threads. |
| emscripten_futex_wake__deps: ['_main_thread_futex_wait_address'], |
| emscripten_futex_wake: function(addr, count) { |
| if (addr <= 0 || addr > HEAP8.length || addr&3 != 0 || count < 0) return -{{{ cDefine('EINVAL') }}}; |
| if (count == 0) return 0; |
| // dump('futex_wake addr:' + addr + ' by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n'); |
| |
| // See if main thread is waiting on this address? If so, wake it up by resetting its wake location to zero. |
| // Note that this is not a fair procedure, since we always wake main thread first before any workers, so |
| // this scheme does not adhere to real queue-based waiting. |
| var mainThreadWaitAddress = Atomics.load(HEAP32, __main_thread_futex_wait_address >> 2); |
| var mainThreadWoken = 0; |
| if (mainThreadWaitAddress == addr) { |
| var loadedAddr = Atomics.compareExchange(HEAP32, __main_thread_futex_wait_address >> 2, mainThreadWaitAddress, 0); |
| if (loadedAddr == mainThreadWaitAddress) { |
| --count; |
| mainThreadWoken = 1; |
| if (count <= 0) return 1; |
| } |
| } |
| |
| // Wake any workers waiting on this address. |
| var ret = Atomics.wake(HEAP32, addr >> 2, count); |
| if (ret >= 0) return ret + mainThreadWoken; |
| throw 'Atomics.wake returned an unexpected value ' + ret; |
| }, |
| |
| __atomic_is_lock_free: function(size, ptr) { |
| return size <= 4 && (size & (size-1)) == 0 && (ptr&(size-1)) == 0; |
| }, |
| |
| __call_main: function(argc, argv) { |
| var returnCode = _main(argc, argv); |
| if (!Module['noExitRuntime']) postMessage({ cmd: 'exitProcess', returnCode: returnCode }); |
| return returnCode; |
| }, |
| |
| emscripten_conditional_set_current_thread_status_js: function(expectedStatus, newStatus) { |
| #if PTHREADS_PROFILING |
| PThread.setThreadStatusConditional(_pthread_self(), expectedStatus, newStatus); |
| #endif |
| }, |
| |
| emscripten_set_current_thread_status_js: function(newStatus) { |
| #if PTHREADS_PROFILING |
| PThread.setThreadStatus(_pthread_self(), newStatus); |
| #endif |
| }, |
| |
| emscripten_set_thread_name_js: function(threadId, name) { |
| #if PTHREADS_PROFILING |
| PThread.setThreadName(threadId, UTF8ToString(name)); |
| #endif |
| }, |
| |
| // The profiler setters are defined twice, here in asm.js so that they can be #ifdeffed out |
| // without having to pay the impact of a FFI transition for a no-op in non-profiling builds. |
| emscripten_conditional_set_current_thread_status__asm: true, |
| emscripten_conditional_set_current_thread_status__sig: 'vii', |
| emscripten_conditional_set_current_thread_status__deps: ['emscripten_conditional_set_current_thread_status_js'], |
| emscripten_conditional_set_current_thread_status: function(expectedStatus, newStatus) { |
| expectedStatus = expectedStatus|0; |
| newStatus = newStatus|0; |
| #if PTHREADS_PROFILING |
| _emscripten_conditional_set_current_thread_status_js(expectedStatus|0, newStatus|0); |
| #endif |
| }, |
| |
| emscripten_set_current_thread_status__asm: true, |
| emscripten_set_current_thread_status__sig: 'vi', |
| emscripten_set_current_thread_status__deps: ['emscripten_set_current_thread_status_js'], |
| emscripten_set_current_thread_status: function(newStatus) { |
| newStatus = newStatus|0; |
| #if PTHREADS_PROFILING |
| _emscripten_set_current_thread_status_js(newStatus|0); |
| #endif |
| }, |
| |
| emscripten_set_thread_name__asm: true, |
| emscripten_set_thread_name__sig: 'vii', |
| emscripten_set_thread_name__deps: ['emscripten_set_thread_name_js'], |
| emscripten_set_thread_name: function(threadId, name) { |
| threadId = threadId|0; |
| name = name|0; |
| #if PTHREADS_PROFILING |
| _emscripten_set_thread_name_js(threadId|0, name|0); |
| #endif |
| }, |
| |
| emscripten_proxy_to_main_thread_js: function(index, sync) { |
| // Additional arguments are passed after those two, which are the actual |
| // function arguments. |
| // The serialization buffer contains the number of call params, and then |
| // all the args here. |
| // We also pass 'sync' to C separately, since C needs to look at it. |
| var numCallArgs = arguments.length - 2; |
| // Allocate a buffer, which will be copied by the C code. |
| var stack = stackSave(); |
| var buffer = stackAlloc(numCallArgs * 8); |
| for (var i = 0; i < numCallArgs; i++) { |
| HEAPF64[(buffer >> 3) + i] = arguments[2 + i]; |
| } |
| var ret = _emscripten_run_in_main_runtime_thread_js(index, numCallArgs, buffer, sync); |
| stackRestore(stack); |
| return ret; |
| }, |
| |
| emscripten_receive_on_main_thread_js__deps: ['emscripten_proxy_to_main_thread_js'], |
| emscripten_receive_on_main_thread_js: function(index, numCallArgs, buffer) { |
| // Avoid garbage by reusing a single JS array for call arguments. |
| if (!_emscripten_receive_on_main_thread_js.callArgs) { |
| _emscripten_receive_on_main_thread_js.callArgs = []; |
| } |
| var callArgs = _emscripten_receive_on_main_thread_js.callArgs; |
| callArgs.length = numCallArgs; |
| for (var i = 0; i < numCallArgs; i++) { |
| callArgs[i] = HEAPF64[(buffer >> 3) + i]; |
| } |
| // Proxied JS library funcs are encoded as positive values, and |
| // EM_ASMs as negative values (see include_asm_consts) |
| var func; |
| if (index > 0) { |
| func = proxiedFunctionTable[index]; |
| } else { |
| func = ASM_CONSTS[-index - 1]; |
| } |
| #if ASSERTIONS |
| assert(func.length == numCallArgs); |
| #endif |
| return func.apply(null, callArgs); |
| }, |
| |
| #if MODULARIZE |
| $establishStackSpaceInJsModule: function(stackBase, stackMax) { |
| STACK_BASE = STACKTOP = stackBase; |
| STACK_MAX = stackMax; |
| }, |
| #endif |
| }; |
| |
| autoAddDeps(LibraryPThread, '$PThread'); |
| mergeInto(LibraryManager.library, LibraryPThread); |