blob: dbb39a30de080e7b31b87eeb0388c0abcf6b7500 [file] [log] [blame]
/**
* @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
var LibraryPThread = {
$PThread__postset: 'PThread.init();',
$PThread__deps: ['_emscripten_thread_init',
'$killThread',
'$cancelThread', '$cleanupThread', '$zeroMemory',
#if MAIN_MODULE
'$markAsFinshed',
#endif
'$spawnThread',
'_emscripten_thread_free_data',
'exit',
#if PTHREADS_DEBUG || ASSERTIONS
'$ptrToString',
#endif
#if !MINIMAL_RUNTIME
'$handleException',
#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: {},
shuttingDown: false,
#if ASSERTIONS
nextWorkerID: 1,
debugInit: function() {
function pthreadLogPrefix() {
var t = 0;
if (runtimeInitialized && typeof _pthread_self != 'undefined'
#if EXIT_RUNTIME
&& !runtimeExited
#endif
) {
t = _pthread_self();
}
return 'w:' + (Module['workerID'] || 0) + ',t:' + ptrToString(t) + ': ';
}
// Prefix all err()/dbg() messages with the calling thread ID.
var origDbg = dbg;
dbg = (message) => origDbg(pthreadLogPrefix() + message);
#if PTHREADS_DEBUG
// With PTHREADS_DEBUG also prefix all err() messages.
var origErr = err;
err = (message) => origErr(pthreadLogPrefix() + message);
#endif
},
#endif
init: function() {
#if ASSERTIONS
PThread.debugInit();
#endif
if (ENVIRONMENT_IS_PTHREAD
#if AUDIO_WORKLET
|| ENVIRONMENT_IS_AUDIO_WORKLET
#endif
) {
PThread.initWorker();
} else {
PThread.initMainThread();
}
},
initMainThread: function() {
#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
// MINIMAL_RUNTIME takes care of calling loadWasmModuleToAllWorkers
// in postamble_minimal.js
addOnPreRun(() => {
addRunDependency('loading-workers')
PThread.loadWasmModuleToAllWorkers(() => removeRunDependency('loading-workers'));
});
#endif
#if MAIN_MODULE
PThread.outstandingPromises = {};
// Finished threads are threads that have finished running but we not yet
// joined.
PThread.finishedThreads = new Set();
#endif
},
initWorker: function() {
#if USE_CLOSURE_COMPILER
// worker.js is not compiled together with us, and must access certain
// things.
PThread['receiveObjectTransfer'] = PThread.receiveObjectTransfer;
PThread['threadInitTLS'] = PThread.threadInitTLS;
#if !MINIMAL_RUNTIME
PThread['setExitStatus'] = PThread.setExitStatus;
#endif
#endif
#if !MINIMAL_RUNTIME
// The default behaviour for pthreads is always to exit once they return
// from their entry point (or call pthread_exit). If we set noExitRuntime
// to true here on pthreads they would never complete and attempt 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/worker.js for more.
noExitRuntime = false;
#endif
},
#if PTHREADS_PROFILING
getThreadName: function(pthreadPtr) {
var profilerBlock = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, POINTER_TYPE) }}};
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 = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, POINTER_TYPE) }}};
var status = (profilerBlock == 0) ? 0 : Atomics.load(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.threadStatus }}} ) >> 2);
return PThread.threadStatusToString(status);
},
#endif
#if !MINIMAL_RUNTIME
setExitStatus: function(status) {
EXITSTATUS = status;
},
#endif
terminateAllThreads__deps: ['$terminateWorker'],
terminateAllThreads: function() {
if (PThread.shuttingDown) return Promise.resolve();
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! terminateAllThreads() can only ever be called from main application thread!');
#endif
#if RUNTIME_DEBUG
dbg('terminateAllThreads');
#endif
PThread.shuttingDown = true;
var promises = [];
// 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 in 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) {
promises.push(terminateWorker(worker));
}
for (var worker of PThread.unusedWorkers) {
promises.push(terminateWorker(worker));
}
return Promise.all(promises).then(() => {
#if RUNTIME_DEBUG
dbg('terminateAllThreads done');
#endif
PThread.unusedWorkers = [];
PThread.runningWorkers = [];
PThread.pthreads = [];
});
},
returnWorkerToPool: function(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() til 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);
},
receiveObjectTransfer: function(data) {
#if OFFSCREENCANVAS_SUPPORT
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: function() {
#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 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('Internal error! Worker sent a message "' + cmd + '" to target pthread ' + d['targetThread'] + ', but that thread no longer exists!');
}
return;
}
if (cmd === 'checkMailbox') {
checkMailbox();
} else if (cmd === 'spawnThread') {
spawnThread(d);
} else if (cmd === 'doneShutdown') {
#if RUNTIME_DEBUG
dbg('doneShutdown');
#endif
clearTimeout(worker.shutdownTimer);
worker.shutdownComplete();
} else if (cmd === 'cleanupThread') {
cleanupThread(d['thread']);
#if MAIN_MODULE
} else if (cmd === 'markAsFinshed') {
markAsFinshed(d['thread']);
#endif
} else if (cmd === 'killThread') {
killThread(d['thread']);
} else if (cmd === 'cancelThread') {
cancelThread(d['thread']);
} 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 (cmd === 'alert') {
alert('Thread ' + d['threadId'] + ': ' + d['text']);
} else if (d.target === 'setimmediate') {
// Worker wants to postMessage() to itself to implement setImmediate()
// emulation.
worker.postMessage(d);
} 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', function(data) {
worker.onmessage({ data: data });
});
worker.on('error', function(e) {
worker.onerror(e);
});
}
#endif
#if ASSERTIONS
assert(wasmMemory instanceof WebAssembly.Memory, 'WebAssembly memory should have been loaded by now!');
assert(wasmModule instanceof WebAssembly.Module, 'WebAssembly Module should have been loaded by now!');
#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
];
for (var handler of knownHandlers) {
if (Module.hasOwnProperty(handler)) {
handlers.push(handler);
}
}
#if ASSERTIONS
worker.workerID = PThread.nextWorkerID++;
#endif
// Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation.
worker.postMessage({
'cmd': 'load',
'handlers': handlers,
// 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']
#if !EXPORT_ES6
|| _scriptDir
#endif
,
#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': wasmMemory,
#endif // WASM2JS
'wasmModule': wasmModule,
#if LOAD_SOURCE_MAP
'wasmSourceMap': wasmSourceMap,
#endif
#if USE_OFFSET_CONVERTER
'wasmOffsetConverter': wasmOffsetConverter,
#endif
#if MAIN_MODULE
// Share all modules that have been loaded so far. New workers
// won't start running threads until these are all loaded.
'sharedModules': sharedModules,
#endif
#if ASSERTIONS
'workerID': worker.workerID,
#endif
});
}),
loadWasmModuleToAllWorkers: function(onMaybeReady) {
#if !PTHREAD_POOL_SIZE
onMaybeReady();
#else
// Instantiation is synchronous in pthreads.
if (
ENVIRONMENT_IS_PTHREAD
#if WASM_WORKERS
|| ENVIRONMENT_IS_WASM_WORKER
#endif
) {
return onMaybeReady();
}
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;
onMaybeReady();
#else
pthreadPoolReady.then(onMaybeReady);
#endif // PTHREAD_POOL_DELAY_LOAD
#endif // PTHREAD_POOL_SIZE
},
// Creates a new web Worker and places it in the unused worker pool to wait for its use.
allocateUnusedWorker: function() {
var worker;
#if MINIMAL_RUNTIME
var pthreadMainJs = Module['worker'];
#else
#if EXPORT_ES6 && USE_ES6_IMPORT_META
// If we're using module output and there's no explicit override, use bundler-friendly pattern.
if (!Module['locateFile']) {
#if PTHREADS_DEBUG
dbg('Allocating a new web worker from ' + new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url));
#endif
#if TRUSTED_TYPES
// Use Trusted Types compatible wrappers.
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
var p = trustedTypes.createPolicy(
'emscripten#workerPolicy1',
{
createScriptURL: function(ignored) {
return new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url);
}
}
);
worker = new Worker(p.createScriptURL('ignored'));
} else
#endif
worker = new Worker(new URL('{{{ PTHREAD_WORKER_FILE }}}', import.meta.url));
} else {
#endif
// 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.
var pthreadMainJs = locateFile('{{{ PTHREAD_WORKER_FILE }}}');
#endif
#if PTHREADS_DEBUG
dbg('Allocating a new web worker from ' + pthreadMainJs);
#endif
#if TRUSTED_TYPES
// Use Trusted Types compatible wrappers.
if (typeof trustedTypes != 'undefined' && trustedTypes.createPolicy) {
var p = trustedTypes.createPolicy('emscripten#workerPolicy2', { createScriptURL: function(ignored) { return pthreadMainJs } });
worker = new Worker(p.createScriptURL('ignored'));
} else
#endif
worker = new Worker(pthreadMainJs);
#if EXPORT_ES6 && USE_ES6_IMPORT_META
}
#endif
PThread.unusedWorkers.push(worker);
},
getNewWorker: function() {
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: function(worker) {
#if PTHREADS_DEBUG
dbg('terminateWorker: ' + worker.workerID);
#endif
// Returns a promise that resoves once the worker has been shut down.
return new Promise((resolve) => {
// Give the worker 200ms to voluntarily shutdown, and if it fails to
// response we `terminate()` it with prejudice.
worker.shutdownComplete = () => resolve();
worker.shutdownTimer = setTimeout(() => {
err('worker failed to respond for cooperative shutdown: ' + worker.workerID);
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 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 valid worker.
worker.onmessage = (e) => {
#if ASSERTIONS
var cmd = e['data']['cmd'];
err('received "' + cmd + '" command from terminated worker: ' + worker.workerID);
#endif
}
worker.shutdownComplete();
}, 200);
worker.postMessage({ 'cmd': 'shutdown' });
});
},
$killThread__deps: ['_emscripten_thread_free_data', '$terminateWorker'],
$killThread: function(pthread_ptr) {
#if PTHREADS_DEBUG
dbg('killThread ' + ptrToString(pthread_ptr));
#endif
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! killThread() can only ever be called from main application thread!');
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in killThread!');
#endif
var worker = PThread.pthreads[pthread_ptr];
terminateWorker(worker).then(() => {
delete PThread.pthreads[pthread_ptr];
__emscripten_thread_free_data(pthread_ptr);
// 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(worker), 1); // Not a running Worker anymore.
worker.pthread_ptr = 0;
});
},
__emscripten_thread_cleanup: function(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': thread });
},
_emscripten_thread_set_strongref: function(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 needs 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: function(pthread_ptr) {
#if PTHREADS_DEBUG
dbg('cleanupThread: ' + ptrToString(pthread_ptr))
#endif
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cleanupThread() can only ever be called from main application thread!');
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in 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
assert(worker);
PThread.returnWorkerToPool(worker);
},
#if MAIN_MODULE
$registerTLSInit: function(tlsInitFunc, moduleExports, metadata) {
#if DYLINK_DEBUG
dbg("registerTLSInit: " + tlsInitFunc);
#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]);
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: function(tlsInitFunc) {
PThread.tlsInitFunctions.push(tlsInitFunc);
},
#endif
$cancelThread: function(pthread_ptr) {
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cancelThread() can only ever be called from main application thread!');
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cancelThread!');
#endif
var worker = PThread.pthreads[pthread_ptr];
worker.postMessage({ 'cmd': 'cancel' });
},
$spawnThread: function(threadParams) {
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! spawnThread() can only ever be called from main application thread!');
assert(threadParams.pthread_ptr, 'Internal error, no 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, 'Internal error!');
#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_has_threading_support: function() {
return typeof SharedArrayBuffer != 'undefined';
},
emscripten_num_logical_cores: function() {
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) return require('os').cpus().length;
#endif
return navigator['hardwareConcurrency'];
},
__emscripten_init_main_thread_js: function(tb) {
// Pass the thread address to the native code where they 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,
/*isMainBrowserThread=*/!ENVIRONMENT_IS_WORKER,
/*isMainRuntimeThread=*/1,
/*canBlock=*/!ENVIRONMENT_IS_WEB,
{{{ DEFAULT_PTHREAD_STACK_SIZE }}},
#if PTHREADS_PROFILING
/*start_profiling=*/true
#endif
);
PThread.threadInitTLS();
},
$pthreadCreateProxied__internal: true,
$pthreadCreateProxied__proxy: 'sync',
$pthreadCreateProxied__deps: ['__pthread_create_js'],
$pthreadCreateProxied: function(pthread_ptr, attr, startRoutine, arg) {
return ___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', 'pthread_self', '$pthreadCreateProxied',
#if OFFSCREENCANVAS_SUPPORT
'malloc',
#endif
],
__pthread_create_js: function(pthread_ptr, attr, startRoutine, arg) {
if (typeof SharedArrayBuffer == 'undefined') {
err('Current environment does not support SharedArrayBuffer, pthreads are not available!');
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, POINTER_TYPE) }}} : 0;
#if OFFSCREENCANVASES_TO_PTHREAD
// Proxied canvases string pointer -1 is used as a special token to fetch
// whatever canvases were passed to build in -s
// OFFSCREENCANVASES_TO_PTHREAD= command line.
if (transferredCanvasNames == (-1 >>> 0)) transferredCanvasNames = '{{{ OFFSCREENCANVASES_TO_PTHREAD }}}';
else
#endif
if (transferredCanvasNames) transferredCanvasNames = UTF8ToString(transferredCanvasNames).trim();
if (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'] ? Module['canvas'].id : '';
// Note that transferredCanvasNames might be null (so we cannot do a for-of loop).
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 = {{{ 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(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
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', 'i32') }}};
}
#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);
},
emscripten_check_blocking_allowed__deps: ['$warnOnce'],
emscripten_check_blocking_allowed: function() {
#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
},
__pthread_kill_js: function(thread, signal) {
if (signal === {{{ cDefs.SIGCANCEL }}}) { // Used by pthread_cancel in musl
if (!ENVIRONMENT_IS_PTHREAD) cancelThread(thread);
else postMessage({ 'cmd': 'cancelThread', 'thread': thread });
} else {
if (!ENVIRONMENT_IS_PTHREAD) killThread(thread);
else postMessage({ 'cmd': 'killThread', 'thread': thread });
}
return 0;
},
// This function is call 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 the __proxy attribute.
$exitOnMainThread__deps: ['exit',
#if !MINIMAL_RUNTIME
'$handleException',
#endif
],
$exitOnMainThread__proxy: 'async',
$exitOnMainThread: function(returnCode) {
#if PTHREADS_DEBUG
dbg('exitOnMainThread');
#endif
#if PROXY_TO_PTHREAD
{{{ runtimeKeepalivePop() }}};
#endif
_exit(returnCode);
},
$proxyToMainThread__deps: ['$withStackSave', '_emscripten_run_in_main_runtime_thread_js'],
$proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...(number|boolean))} */',
$proxyToMainThread: 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;
var outerArgs = arguments;
#if ASSERTIONS
var maxArgs = {{{ cDefs.EM_QUEUED_JS_CALL_MAX_ARGS - 1 }}};
if (numCallArgs > maxArgs) {
throw 'proxyToMainThread: Too many arguments ' + numCallArgs + ' to proxied function idx=' + index + ', maximum supported is ' + maxArgs;
}
#endif
// Allocate a buffer, which will be copied by the C code.
return withStackSave(() => {
// 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 serializedNumCallArgs = numCallArgs {{{ WASM_BIGINT ? "* 2" : "" }}};
var args = stackAlloc(serializedNumCallArgs * 8);
var b = args >> 3;
for (var i = 0; i < numCallArgs; i++) {
var arg = outerArgs[2 + i];
#if WASM_BIGINT
if (typeof arg == 'bigint') {
// The prefix is non-zero to indicate a bigint.
HEAP64[b + 2*i] = 1n;
HEAP64[b + 2*i + 1] = arg;
} else {
// The prefix is zero to indicate a JS Number.
HEAP64[b + 2*i] = 0n;
HEAPF64[b + 2*i + 1] = arg;
}
#else
HEAPF64[b + i] = arg;
#endif
}
return __emscripten_run_in_main_runtime_thread_js(index, serializedNumCallArgs, args, sync);
});
},
$emscripten_receive_on_main_thread_js_callArgs: '=[]',
emscripten_receive_on_main_thread_js__deps: [
'$proxyToMainThread',
'$emscripten_receive_on_main_thread_js_callArgs'],
emscripten_receive_on_main_thread_js: function(index, callingThread, numCallArgs, args) {
// 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.
PThread.currentProxiedOperationCallerThread = callingThread;
#if WASM_BIGINT
numCallArgs /= 2;
#endif
emscripten_receive_on_main_thread_js_callArgs.length = numCallArgs;
var b = args >> 3;
for (var i = 0; i < numCallArgs; i++) {
#if WASM_BIGINT
if (HEAP64[b + 2*i]) {
// It's a BigInt.
emscripten_receive_on_main_thread_js_callArgs[i] = HEAP64[b + 2*i + 1];
} else {
// It's a Number.
emscripten_receive_on_main_thread_js_callArgs[i] = HEAPF64[b + 2*i + 1];
}
#else
emscripten_receive_on_main_thread_js_callArgs[i] = HEAPF64[b + i];
#endif
}
// Proxied JS library funcs are encoded as positive values, and
// EM_ASMs as negative values (see include_asm_consts)
#if HAVE_EM_ASM
var isEmAsmConst = index < 0;
var func = !isEmAsmConst ? proxiedFunctionTable[index] : ASM_CONSTS[-index - 1];
#else
var func = proxiedFunctionTable[index];
#endif
#if ASSERTIONS
assert(func.length == numCallArgs, 'Call args mismatch in emscripten_receive_on_main_thread_js');
#endif
return func.apply(null, emscripten_receive_on_main_thread_js_callArgs);
},
$establishStackSpace__internal: true,
$establishStackSpace: function() {
var pthread_ptr = _pthread_self();
var stackHigh = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack, 'i32') }}};
var stackSize = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack_size, 'i32') }}};
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'],
$invokeEntryPoint: function(ptr, arg) {
#if PTHREADS_DEBUG
dbg('invokeEntryPoint: ' + ptrToString(ptr));
#endif
#if EXIT_RUNTIME && !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;
#endif
#if MAIN_MODULE
// Before we call the thread entry point, make sure any shared libraries
// have been loaded on this there. Otherwise our table migth 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') }}}(arg);
#if STACK_OVERFLOW_CHECK
checkStackCookie();
#endif
#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.
__emscripten_thread_exit(result);
#else
if (keepRuntimeAlive()) {
PThread.setExitStatus(result);
} else {
__emscripten_thread_exit(result);
}
#endif
},
#if MAIN_MODULE
_emscripten_thread_exit_joinable: function(thread) {
// Called when a thread exits and is joinable. We mark these threads
// as finished, which means that are in state where are no longer actually
// runnning, but remain around waiting to be joined. In this state they
// cannot run any more proxied work.
if (!ENVIRONMENT_IS_PTHREAD) markAsFinshed(thread);
else postMessage({ 'cmd': 'markAsFinshed', 'thread': thread });
},
$markAsFinshed: function(pthread_ptr) {
#if PTHREADS_DEBUG
dbg('markAsFinshed: ' + ptrToString(pthread_ptr));
#endif
PThread.finishedThreads.add(pthread_ptr);
if (pthread_ptr in PThread.outstandingPromises) {
PThread.outstandingPromises[pthread_ptr].resolve();
}
},
// Asynchronous version dlsync_threads. Always run on the main thread.
// This work happens asynchronously. The `callback` is called once this work
// is completed, passing the ctx.
// TODO(sbc): Should we make a new form of __proxy attribute for JS library
// function that run asynchronously like but blocks the caller until they are
// done. Perhaps "sync_with_ctx"?
_emscripten_dlsync_threads_async__deps: ['_emscripten_proxy_dlsync_async', 'emscripten_promise_create', '$getPromise'],
_emscripten_dlsync_threads_async: function(caller, callback, ctx) {
#if PTHREADS_DEBUG
dbg("_emscripten_dlsync_threads_async caller=" + ptrToString(caller));
#endif
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads_async() can only ever be called from main thread');
#endif
const promises = [];
assert(Object.keys(PThread.outstandingPromises).length === 0);
// 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 automaticaly 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(`_emscripten_dlsync_threads_async: waiting on ${promises.length} promises`);
#endif
// Once all promises are resolved then we know all threads are in sync and
// we can call the callback.
Promise.all(promises).then(() => {
PThread.outstandingPromises = {};
#if PTHREADS_DEBUG
dbg('_emscripten_dlsync_threads_async done: calling callback');
#endif
{{{ makeDynCall('vp', 'callback') }}}(ctx);
});
},
// Synchronous version dlsync_threads. This is only needed for the case then
// the main thread call dlopen and in that case we have not 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'],
_emscripten_dlsync_threads: function() {
#if ASSERTIONS
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads() can only ever be called from main thread');
#endif
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',
'_emscripten_thread_mailbox_await'],
$checkMailbox: function() {
// Only check the mailbox if we have a live pthread runtime. We implement
// pthread_self to return 0 if there is no live runtime.
var pthread_ptr = _pthread_self();
if (pthread_ptr) {
// If we are using Atomics.waitAsync as our notification mechanism, wait for
// a notification before processing the mailbox to avoid missing any work.
__emscripten_thread_mailbox_await(pthread_ptr);
callUserCallback(() => __emscripten_check_mailbox());
}
},
_emscripten_thread_mailbox_await__deps: ['$checkMailbox'],
_emscripten_thread_mailbox_await: function(pthread_ptr) {
if (typeof Atomics.waitAsync === 'function') {
// TODO: How to make this work with wasm64?
var wait = Atomics.waitAsync(HEAP32, pthread_ptr >> 2, pthread_ptr);
#if ASSERTIONS
assert(wait.async);
#endif
wait.value.then(checkMailbox);
var waitingAsync = pthread_ptr + {{{ C_STRUCTS.pthread.waiting_async }}};
Atomics.store(HEAP32, waitingAsync >> 2, 1);
}
},
_emscripten_notify_mailbox_postmessage__deps: ['$checkMailbox'],
_emscripten_notify_mailbox_postmessage: function(targetThreadId,
currThreadId,
mainThreadId) {
if (targetThreadId == currThreadId) {
setTimeout(() => checkMailbox());
} else if (ENVIRONMENT_IS_PTHREAD) {
postMessage({'targetThread' : targetThreadId, 'cmd' : 'checkMailbox'});
} else {
var worker = PThread.pthreads[targetThreadId];
if (!worker) {
#if ASSERTIONS
err('Cannot send message to thread with ID ' + targetThreadId + ', unknown thread ID!');
#endif
return;
}
worker.postMessage({'cmd' : 'checkMailbox'});
}
}
};
autoAddDeps(LibraryPThread, '$PThread');
mergeInto(LibraryManager.library, LibraryPThread);