UNPKG

scran.js

Version:

Single cell RNA-seq analysis in Javascript

1,383 lines (1,288 loc) 223 kB
// This code implements the `-sMODULARIZE` settings by taking the generated // JS program code (INNER_JS_CODE) and wrapping it in a factory function. // When targetting node and ES6 we use `await import ..` in the generated code // so the outer function needs to be marked as async. async function loadScran(moduleArg = {}) { var moduleRtn; // include: shell.js // include: minimum_runtime_check.js // end include: minimum_runtime_check.js // The Module object: Our interface to the outside world. We import // and export values on it. There are various ways Module can be used: // 1. Not defined. We create it here // 2. A function parameter, function(moduleArg) => Promise<Module> // 3. pre-run appended it, var Module = {}; ..generated code.. // 4. External script tag defines var Module. // We need to check if Module already exists (e.g. case 3 above). // Substitution will be replaced with actual code on later stage of the build, // this way Closure Compiler will not mangle it (e.g. case 4. above). // Note that if you want to run closure, and also to use Module // after the generated code, you will need to define var Module = {}; // before the code. Then that object will be used in the code, and you // can continue to use Module afterwards as well. var Module = moduleArg; // Determine the runtime environment we are in. You can customize this by // setting the ENVIRONMENT setting at compile time (see settings.js). // Attempt to auto-detect the environment var ENVIRONMENT_IS_WEB = !!globalThis.window; var ENVIRONMENT_IS_WORKER = !!globalThis.WorkerGlobalScope; // N.b. Electron.js environment is simultaneously a NODE-environment, but // also a web environment. var ENVIRONMENT_IS_NODE = globalThis.process?.versions?.node && globalThis.process?.type != "renderer"; // Three configurations we can be running in: // 1) We could be the application main() thread running in the main JS UI thread. (ENVIRONMENT_IS_WORKER == false and ENVIRONMENT_IS_PTHREAD == false) // 2) We could be the application main() thread proxied to worker. (with Emscripten -sPROXY_TO_WORKER) (ENVIRONMENT_IS_WORKER == true, ENVIRONMENT_IS_PTHREAD == false) // 3) We could be an application pthread running in a worker. (ENVIRONMENT_IS_WORKER == true and ENVIRONMENT_IS_PTHREAD == true) // The way we signal to a worker that it is hosting a pthread is to construct // it with a specific name. var ENVIRONMENT_IS_PTHREAD = ENVIRONMENT_IS_WORKER && self.name?.startsWith("em-pthread"); // --pre-jses are emitted after the Module integration code, so that they can // refer to Module (if they choose; they can also define Module) var arguments_ = []; var thisProgram = "./this.program"; var quit_ = (status, toThrow) => { throw toThrow; }; var _scriptName = import.meta.url; // `/` should be present at the end if `scriptDirectory` is not empty var scriptDirectory = ""; function locateFile(path) { if (Module["locateFile"]) { return Module["locateFile"](path, scriptDirectory); } return scriptDirectory + path; } // Hooks that are implemented differently in different runtime environments. var readAsync, readBinary; // Note that this includes Node.js workers when relevant (pthreads is enabled). // Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and // ENVIRONMENT_IS_NODE. if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { try { scriptDirectory = new URL(".", _scriptName).href; } catch {} { // include: web_or_worker_shell_read.js if (ENVIRONMENT_IS_WORKER) { readBinary = url => { var xhr = new XMLHttpRequest; xhr.open("GET", url, false); xhr.responseType = "arraybuffer"; xhr.send(null); return new Uint8Array(/** @type{!ArrayBuffer} */ (xhr.response)); }; } readAsync = async url => { var response = await fetch(url, { credentials: "same-origin" }); if (response.ok) { return response.arrayBuffer(); } throw new Error(response.status + " : " + response.url); }; } } else {} var out = console.log.bind(console); var err = console.error.bind(console); // end include: shell.js // include: preamble.js // === Preamble library stuff === // Documentation for the public APIs defined in this file must be updated in: // site/source/docs/api_reference/preamble.js.rst // A prebuilt local version of the documentation is available at: // site/build/text/docs/api_reference/preamble.js.txt // You can also build docs locally as HTML or other formats in site/ // An online HTML version (which may be of a different version of Emscripten) // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html var wasmBinary; // Wasm globals // For sending to workers. var wasmModule; //======================================== // Runtime essentials //======================================== // whether we are quitting the application. no code should run after this. // set in exit() and abort() var ABORT = false; // set by exit() and abort(). Passed to 'onExit' handler. // NOTE: This is also used as the process return code code in shell environments // but only when noExitRuntime is false. var EXITSTATUS; // include: runtime_common.js // include: runtime_stack_check.js // end include: runtime_stack_check.js // include: runtime_exceptions.js // end include: runtime_exceptions.js // include: runtime_debug.js // end include: runtime_debug.js // Support for growable heap + pthreads, where the buffer may change, so JS views // must be updated. function growMemViews() { // `updateMemoryViews` updates all the views simultaneously, so it's enough to check any of them. if (wasmMemory.buffer != HEAP8.buffer) { updateMemoryViews(); } } var readyPromiseResolve, readyPromiseReject; // include: runtime_pthread.js // Pthread Web Worker handling code. // This code runs only on pthread web workers and handles pthread setup // and communication with the main thread via postMessage. var startWorker; if (ENVIRONMENT_IS_PTHREAD) { // Thread-local guard variable for one-time init of the JS state var initializedJS = false; // Turn unhandled rejected promises into errors so that the main thread will be // notified about them. self.onunhandledrejection = e => { throw e.reason || e; }; function handleMessage(e) { try { var msgData = e["data"]; //dbg('msgData: ' + Object.keys(msgData)); var cmd = msgData.cmd; if (cmd === "load") { // Preload command that is called once per worker to parse and load the Emscripten code. // Until we initialize the runtime, queue up any further incoming messages. let messageQueue = []; self.onmessage = e => messageQueue.push(e); // And add a callback for when the runtime is initialized. startWorker = () => { // Notify the main thread that this thread has loaded. postMessage({ cmd: "loaded" }); // Process any messages that were queued before the thread was ready. for (let msg of messageQueue) { handleMessage(msg); } // Restore the real message handler. self.onmessage = handleMessage; }; // Use `const` here to ensure that the variable is scoped only to // that iteration, allowing safe reference from a closure. for (const handler of msgData.handlers) { // The the main module has a handler for a certain even, but no // handler exists on the pthread worker, then proxy that handler // back to the main thread. if (!Module[handler] || Module[handler].proxy) { Module[handler] = (...args) => { postMessage({ cmd: "callHandler", handler, args }); }; // Rebind the out / err handlers if needed if (handler == "print") out = Module[handler]; if (handler == "printErr") err = Module[handler]; } } wasmMemory = msgData.wasmMemory; updateMemoryViews(); wasmModule = msgData.wasmModule; createWasm(); run(); } else if (cmd === "run") { // Call inside JS module to set up the stack frame for this pthread in JS module scope. // This needs to be the first thing that we do, as we cannot call to any C/C++ functions // until the thread stack is initialized. establishStackSpace(msgData.pthread_ptr); // Pass the thread address to wasm to store it for fast access. __emscripten_thread_init(msgData.pthread_ptr, /*is_main=*/ 0, /*is_runtime=*/ 0, /*can_block=*/ 1, 0, 0); PThread.threadInitTLS(); // Await mailbox notifications with `Atomics.waitAsync` so we can start // using the fast `Atomics.notify` notification path. __emscripten_thread_mailbox_await(msgData.pthread_ptr); if (!initializedJS) { // Embind must initialize itself on all threads, as it generates support JS. // We only do this once per worker since they get reused __embind_initialize_bindings(); initializedJS = true; } try { invokeEntryPoint(msgData.start_routine, msgData.arg); } catch (ex) { if (ex != "unwind") { // The pthread "crashed". Do not call `_emscripten_thread_exit` (which // would make this thread joinable). Instead, re-throw the exception // and let the top level handler propagate it back to the main thread. throw ex; } } } else if (msgData.target === "setimmediate") {} else if (cmd === "checkMailbox") { if (initializedJS) { checkMailbox(); } } else if (cmd) { // The received message looks like something that should be handled by this message // handler, (since there is a cmd field present), but is not one of the // recognized commands: err(`worker: received unknown command ${cmd}`); err(msgData); } } catch (ex) { __emscripten_thread_crashed(); throw ex; } } self.onmessage = handleMessage; } // ENVIRONMENT_IS_PTHREAD // end include: runtime_pthread.js // Memory management var /** @type {!Int8Array} */ HEAP8, /** @type {!Uint8Array} */ HEAPU8, /** @type {!Int16Array} */ HEAP16, /** @type {!Uint16Array} */ HEAPU16, /** @type {!Int32Array} */ HEAP32, /** @type {!Uint32Array} */ HEAPU32, /** @type {!Float32Array} */ HEAPF32, /** @type {!Float64Array} */ HEAPF64; // BigInt64Array type is not correctly defined in closure var /** not-@type {!BigInt64Array} */ HEAP64, /* BigUint64Array type is not correctly defined in closure /** not-@type {!BigUint64Array} */ HEAPU64; var runtimeInitialized = false; function updateMemoryViews() { var b = wasmMemory.buffer; Module["HEAP8"] = HEAP8 = new Int8Array(b); HEAP16 = new Int16Array(b); HEAPU8 = new Uint8Array(b); HEAPU16 = new Uint16Array(b); HEAP32 = new Int32Array(b); HEAPU32 = new Uint32Array(b); HEAPF32 = new Float32Array(b); HEAPF64 = new Float64Array(b); HEAP64 = new BigInt64Array(b); HEAPU64 = new BigUint64Array(b); } // In non-standalone/normal mode, we create the memory here. // include: runtime_init_memory.js // Create the wasm memory. (Note: this only applies if IMPORTED_MEMORY is defined) // check for full engine support (use string 'subarray' to avoid closure compiler confusion) function initMemory() { if ((ENVIRONMENT_IS_PTHREAD)) { return; } if (Module["wasmMemory"]) { wasmMemory = Module["wasmMemory"]; } else { var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 16777216; /** @suppress {checkTypes} */ wasmMemory = new WebAssembly.Memory({ "initial": BigInt(INITIAL_MEMORY / 65536), // In theory we should not need to emit the maximum if we want "unlimited" // or 4GB of memory, but VMs error on that atm, see // https://github.com/emscripten-core/emscripten/issues/14130 // And in the pthreads case we definitely need to emit a maximum. So // always emit one. "maximum": 262144n, "shared": true, "address": "i64" }); } updateMemoryViews(); } // end include: runtime_init_memory.js // include: memoryprofiler.js // end include: memoryprofiler.js // end include: runtime_common.js function preRun() { if (Module["preRun"]) { if (typeof Module["preRun"] == "function") Module["preRun"] = [ Module["preRun"] ]; while (Module["preRun"].length) { addOnPreRun(Module["preRun"].shift()); } } // Begin ATPRERUNS hooks callRuntimeCallbacks(onPreRuns); } function initRuntime() { runtimeInitialized = true; if (ENVIRONMENT_IS_PTHREAD) return startWorker(); // Begin ATINITS hooks if (!Module["noFSInit"] && !FS.initialized) FS.init(); TTY.init(); // End ATINITS hooks wasmExports["sa"](); // Begin ATPOSTCTORS hooks FS.ignorePermissions = false; } function postRun() { if ((ENVIRONMENT_IS_PTHREAD)) { return; } // PThreads reuse the runtime from the main thread. if (Module["postRun"]) { if (typeof Module["postRun"] == "function") Module["postRun"] = [ Module["postRun"] ]; while (Module["postRun"].length) { addOnPostRun(Module["postRun"].shift()); } } // Begin ATPOSTRUNS hooks callRuntimeCallbacks(onPostRuns); } /** @param {string|number=} what */ function abort(what) { Module["onAbort"]?.(what); what = "Aborted(" + what + ")"; // TODO(sbc): Should we remove printing and leave it up to whoever // catches the exception? err(what); ABORT = true; what += ". Build with -sASSERTIONS for more info."; // Use a wasm runtime error, because a JS error might be seen as a foreign // exception, which means we'd run destructors on it. We need the error to // simply make the program stop. // FIXME This approach does not work in Wasm EH because it currently does not assume // all RuntimeErrors are from traps; it decides whether a RuntimeError is from // a trap or not based on a hidden field within the object. So at the moment // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that // allows this in the wasm spec. // Suppress closure compiler warning here. Closure compiler's builtin extern // definition for WebAssembly.RuntimeError claims it takes no arguments even // though it can. // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what); readyPromiseReject?.(e); // Throw the error whether or not MODULARIZE is set because abort is used // in code paths apart from instantiation where an exception is expected // to be thrown when abort is called. throw e; } var wasmBinaryFile; function findWasmBinary() { if (Module["locateFile"]) { return locateFile("scran.wasm"); } // Use bundler-friendly `new URL(..., import.meta.url)` pattern; works in browsers too. return new URL("scran.wasm", import.meta.url).href; } function getBinarySync(file) { if (file == wasmBinaryFile && wasmBinary) { return new Uint8Array(wasmBinary); } if (readBinary) { return readBinary(file); } // Throwing a plain string here, even though it not normally adviables since // this gets turning into an `abort` in instantiateArrayBuffer. throw "both async and sync fetching of the wasm failed"; } async function getWasmBinary(binaryFile) { // If we don't have the binary yet, load it asynchronously using readAsync. if (!wasmBinary) { // Fetch the binary using readAsync try { var response = await readAsync(binaryFile); return new Uint8Array(response); } catch {} } // Otherwise, getBinarySync should be able to get it synchronously return getBinarySync(binaryFile); } async function instantiateArrayBuffer(binaryFile, imports) { try { var binary = await getWasmBinary(binaryFile); var instance = await WebAssembly.instantiate(binary, imports); return instance; } catch (reason) { err(`failed to asynchronously prepare wasm: ${reason}`); abort(reason); } } async function instantiateAsync(binary, binaryFile, imports) { if (!binary) { try { var response = fetch(binaryFile, { credentials: "same-origin" }); var instantiationResult = await WebAssembly.instantiateStreaming(response, imports); return instantiationResult; } catch (reason) { // We expect the most common failure cause to be a bad MIME type for the binary, // in which case falling back to ArrayBuffer instantiation should work. err(`wasm streaming compile failed: ${reason}`); err("falling back to ArrayBuffer instantiation"); } } return instantiateArrayBuffer(binaryFile, imports); } function getWasmImports() { assignWasmImports(); // prepare imports var imports = { "a": wasmImports }; return imports; } // Create the wasm instance. // Receives the wasm imports, returns the exports. async function createWasm() { // Load the wasm module and create an instance of using native support in the JS engine. // handle a generated wasm instance, receiving its exports and // performing other necessary setup /** @param {WebAssembly.Module=} module*/ function receiveInstance(instance, module) { wasmExports = instance.exports; wasmExports = applySignatureConversions(wasmExports); registerTLSInit(wasmExports["za"]); assignWasmExports(wasmExports); // We now have the Wasm module loaded up, keep a reference to the compiled module so we can post it to the workers. wasmModule = module; return wasmExports; } // Prefer streaming instantiation if available. function receiveInstantiationResult(result) { // 'result' is a ResultObject object which has both the module and instance. // receiveInstance() will swap in the exports (to Module.asm) so they can be called return receiveInstance(result["instance"], result["module"]); } var info = getWasmImports(); // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback // to manually instantiate the Wasm module themselves. This allows pages to // run the instantiation parallel to any other async startup actions they are // performing. // Also pthreads and wasm workers initialize the wasm instance through this // path. if (Module["instantiateWasm"]) { return new Promise((resolve, reject) => { Module["instantiateWasm"](info, (inst, mod) => { resolve(receiveInstance(inst, mod)); }); }); } if ((ENVIRONMENT_IS_PTHREAD)) { // Instantiate from the module that was recieved via postMessage from // the main thread. We can just use sync instantiation in the worker. var instance = new WebAssembly.Instance(wasmModule, getWasmImports()); return receiveInstance(instance, wasmModule); } wasmBinaryFile ??= findWasmBinary(); var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); var exports = receiveInstantiationResult(result); return exports; } // end include: preamble.js // Begin JS library code class ExitStatus { name="ExitStatus"; constructor(status) { this.message = `Program terminated with exit(${status})`; this.status = status; } } var terminateWorker = worker => { 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 => {}; }; var cleanupThread = pthread_ptr => { var worker = PThread.pthreads[pthread_ptr]; PThread.returnWorkerToPool(worker); }; var callRuntimeCallbacks = callbacks => { while (callbacks.length > 0) { // Pass the module as the first argument. callbacks.shift()(Module); } }; var onPreRuns = []; var addOnPreRun = cb => onPreRuns.push(cb); var runDependencies = 0; var dependenciesFulfilled = null; var removeRunDependency = id => { runDependencies--; Module["monitorRunDependencies"]?.(runDependencies); if (runDependencies == 0) { if (dependenciesFulfilled) { var callback = dependenciesFulfilled; dependenciesFulfilled = null; callback(); } } }; var addRunDependency = id => { runDependencies++; Module["monitorRunDependencies"]?.(runDependencies); }; var spawnThread = threadParams => { var worker = PThread.getNewWorker(); if (!worker) { // No available workers in the PThread pool. return 6; } 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 }; // Ask the worker to start executing its pthread entry point function. worker.postMessage(msg, threadParams.transferList); return 0; }; var runtimeKeepaliveCounter = 0; var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; var stackSave = () => _emscripten_stack_get_current(); var stackRestore = val => __emscripten_stack_restore(val); var stackAlloc = sz => __emscripten_stack_alloc(sz); /** @type{function(number, (number|boolean), ...number)} */ var proxyToMainThread = (funcIndex, emAsmAddr, sync, ...callArgs) => { // EM_ASM proxying is done by passing a pointer to the address of the EM_ASM // content as `emAsmAddr`. JS library proxying is done by passing an index // into `proxiedJSCallArgs` as `funcIndex`. If `emAsmAddr` is non-zero then // `funcIndex` will be ignored. // Additional arguments are passed after the first three are the actual // function arguments. // The serialization buffer contains the number of call params, and then // all the args here. // We also pass 'sync' to C separately, since C needs to look at it. // Allocate a buffer, which will be copied by the C code. // First passed parameter specifies the number of arguments to the function. // When BigInt support is enabled, we must handle types in a more complex // way, detecting at runtime if a value is a BigInt or not (as we have no // type info here). To do that, add a "prefix" before each value that // indicates if it is a BigInt, which effectively doubles the number of // values we serialize for proxying. TODO: pack this? var serializedNumCallArgs = callArgs.length * 2; var sp = stackSave(); var args = stackAlloc(serializedNumCallArgs * 8); var b = ((args) / 8); for (var i = 0; i < callArgs.length; i++) { var arg = callArgs[i]; if (typeof arg == "bigint") { // The prefix is non-zero to indicate a bigint. (growMemViews(), HEAP64)[b + 2 * i] = 1n; (growMemViews(), HEAP64)[b + 2 * i + 1] = arg; } else { // The prefix is zero to indicate a JS Number. (growMemViews(), HEAP64)[b + 2 * i] = 0n; (growMemViews(), HEAPF64)[b + 2 * i + 1] = arg; } } var rtn = __emscripten_run_js_on_main_thread(funcIndex, emAsmAddr, serializedNumCallArgs, args, sync); stackRestore(sp); return rtn; }; function _proc_exit(code) { if (ENVIRONMENT_IS_PTHREAD) return proxyToMainThread(0, 0, 1, code); EXITSTATUS = code; if (!keepRuntimeAlive()) { PThread.terminateAllThreads(); Module["onExit"]?.(code); ABORT = true; } quit_(code, new ExitStatus(code)); } function exitOnMainThread(returnCode) { if (ENVIRONMENT_IS_PTHREAD) return proxyToMainThread(1, 0, 0, returnCode); _exit(returnCode); } /** @param {boolean|number=} implicit */ var exitJS = (status, implicit) => { EXITSTATUS = status; if (ENVIRONMENT_IS_PTHREAD) { // implicit exit can never happen on a pthread // When running in a pthread we propagate the exit back to the main thread // where it can decide if the whole process should be shut down or not. // The pthread may have decided not to exit its own runtime, for example // because it runs a main loop, but that doesn't affect the main thread. exitOnMainThread(status); throw "unwind"; } _proc_exit(status); }; var _exit = exitJS; var PThread = { unusedWorkers: [], runningWorkers: [], tlsInitFunctions: [], pthreads: {}, init() { if ((!(ENVIRONMENT_IS_PTHREAD))) { PThread.initMainThread(); } }, initMainThread() { var pthreadPoolSize = Module.scran_custom_nthreads; // Start loading up the Worker pool, if requested. while (pthreadPoolSize--) { PThread.allocateUnusedWorker(); } // MINIMAL_RUNTIME takes care of calling loadWasmModuleToAllWorkers // in postamble_minimal.js addOnPreRun(async () => { var pthreadPoolReady = PThread.loadWasmModuleToAllWorkers(); addRunDependency("loading-workers"); await pthreadPoolReady; removeRunDependency("loading-workers"); }); }, terminateAllThreads: () => { // 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) { terminateWorker(worker); } for (var worker of PThread.unusedWorkers) { terminateWorker(worker); } PThread.unusedWorkers = []; PThread.runningWorkers = []; PThread.pthreads = {}; }, returnWorkerToPool: worker => { // We don't want to run main thread queued calls here, since we are doing // some operations that leave the worker queue in an invalid state until // we are completely done (it would be bad if free() ends up calling a // queued pthread_create which looks at the global data structures we are // modifying). To achieve that, defer the free() 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; // Finally, free the underlying (and now-unused) pthread structure in // linear memory. __emscripten_thread_free_data(pthread_ptr); }, threadInitTLS() { // Call thread init functions (these are the _emscripten_tls_init for each // module loaded. PThread.tlsInitFunctions.forEach(f => f()); }, 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 === "cleanupThread") { // cleanupThread needs to be run via callUserCallback since it calls // back into user code to free thread data. Without this it's possible // the unwind or ExitStatus exception could escape here. callUserCallback(() => cleanupThread(d.thread)); } else if (cmd === "loaded") { worker.loaded = true; onFinishedLoading(worker); } 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!"; err(`${message} ${e.filename}:${e.lineno}: ${e.message}`); throw e; }; // 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 = [ "onExit", "onAbort", "print", "printErr" ]; for (var handler of knownHandlers) { if (Module.propertyIsEnumerable(handler)) { handlers.push(handler); } } // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation. worker.postMessage({ cmd: "load", handlers, wasmMemory, wasmModule }); }), async loadWasmModuleToAllWorkers() { // Instantiation is synchronous in pthreads. if (ENVIRONMENT_IS_PTHREAD) { return; } let pthreadPoolReady = Promise.all(PThread.unusedWorkers.map(PThread.loadWasmModuleToWorker)); return pthreadPoolReady; }, allocateUnusedWorker() { var worker; // If we're using module output, use bundler-friendly pattern. if (Module["mainScriptUrlOrBlob"]) { var pthreadMainJs = Module["mainScriptUrlOrBlob"]; if (typeof pthreadMainJs != "string") { pthreadMainJs = URL.createObjectURL(pthreadMainJs); } worker = new Worker(pthreadMainJs, { "type": "module", // This is the way that we signal to the Web Worker that it is hosting // a pthread. "name": "em-pthread" }); } else // We need to generate the URL with import.meta.url as the base URL of the JS file // instead of just using new URL(import.meta.url) because bundler's only recognize // the first case in their bundling step. The latter ends up producing an invalid // URL to import from the server (e.g., for webpack the file:// path). // See https://github.com/webpack/webpack/issues/12638 worker = new Worker(new URL("scran.js", import.meta.url), { "type": "module", // This is the way that we signal to the Web Worker that it is hosting // a pthread. "name": "em-pthread" }); PThread.unusedWorkers.push(worker); }, getNewWorker() { if (PThread.unusedWorkers.length == 0) { // PTHREAD_POOL_SIZE_STRICT should show a warning and, if set to level `2`, return from the function. PThread.allocateUnusedWorker(); PThread.loadWasmModuleToWorker(PThread.unusedWorkers[0]); } return PThread.unusedWorkers.pop(); } }; var onPostRuns = []; var addOnPostRun = cb => onPostRuns.push(cb); function establishStackSpace(pthread_ptr) { var stackHigh = Number((growMemViews(), HEAPU64)[(((pthread_ptr) + (88)) / 8)]); var stackSize = Number((growMemViews(), HEAPU64)[(((pthread_ptr) + (96)) / 8)]); var stackLow = stackHigh - stackSize; // 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); // Call inside wasm module to set up the stack frame for this pthread in wasm module scope stackRestore(stackHigh); } var wasmTableMirror = []; var getWasmTableEntry = funcPtr => { // Function pointers should show up as numbers, even under wasm64, but // we still have some places where bigint values can flow here. // https://github.com/emscripten-core/emscripten/issues/18200 funcPtr = Number(funcPtr); var func = wasmTableMirror[funcPtr]; if (!func) { /** @suppress {checkTypes} */ wasmTableMirror[funcPtr] = func = wasmTable.get(BigInt(funcPtr)); } return func; }; var invokeEntryPoint = (ptr, arg) => { // 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; // Same for noExitRuntime. The default for pthreads should always be false // otherwise pthreads would never complete and attempts to pthread_join to // them would block forever. // pthreads can still choose to set `noExitRuntime` explicitly, or // call emscripten_unwind_to_js_event_loop to extend their lifetime beyond // their main function. See comment in src/runtime_pthread.js for more. noExitRuntime = 0; // 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 = (a1 => getWasmTableEntry(ptr).call(null, BigInt(a1)))(arg); function finish(result) { // In MINIMAL_RUNTIME the noExitRuntime concept does not apply to // pthreads. To exit a pthread with live runtime, use the function // emscripten_unwind_to_js_event_loop() in the pthread body. if (keepRuntimeAlive()) { EXITSTATUS = result; return; } __emscripten_thread_exit(result); } finish(result); }; var noExitRuntime = true; var registerTLSInit = tlsInitFunc => PThread.tlsInitFunctions.push(tlsInitFunc); var wasmMemory; class ExceptionInfo { // excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it. constructor(excPtr) { this.excPtr = excPtr; this.ptr = excPtr - 48; } set_type(type) { (growMemViews(), HEAPU64)[(((this.ptr) + (8)) / 8)] = BigInt(type); } get_type() { return Number((growMemViews(), HEAPU64)[(((this.ptr) + (8)) / 8)]); } set_destructor(destructor) { (growMemViews(), HEAPU64)[(((this.ptr) + (16)) / 8)] = BigInt(destructor); } get_destructor() { return Number((growMemViews(), HEAPU64)[(((this.ptr) + (16)) / 8)]); } set_caught(caught) { caught = caught ? 1 : 0; (growMemViews(), HEAP8)[(this.ptr) + (24)] = caught; } get_caught() { return (growMemViews(), HEAP8)[(this.ptr) + (24)] != 0; } set_rethrown(rethrown) { rethrown = rethrown ? 1 : 0; (growMemViews(), HEAP8)[(this.ptr) + (25)] = rethrown; } get_rethrown() { return (growMemViews(), HEAP8)[(this.ptr) + (25)] != 0; } // Initialize native structure fields. Should be called once after allocated. init(type, destructor) { this.set_adjusted_ptr(0); this.set_type(type); this.set_destructor(destructor); } set_adjusted_ptr(adjustedPtr) { (growMemViews(), HEAPU64)[(((this.ptr) + (32)) / 8)] = BigInt(adjustedPtr); } get_adjusted_ptr() { return Number((growMemViews(), HEAPU64)[(((this.ptr) + (32)) / 8)]); } } var exceptionLast = 0; var uncaughtExceptionCount = 0; var INT53_MAX = 9007199254740992; var INT53_MIN = -9007199254740992; var bigintToI53Checked = num => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); function ___cxa_throw(ptr, type, destructor) { ptr = bigintToI53Checked(ptr); type = bigintToI53Checked(type); destructor = bigintToI53Checked(destructor); var info = new ExceptionInfo(ptr); // Initialize ExceptionInfo content after it was allocated in __cxa_allocate_exception. info.init(type, destructor); exceptionLast = ptr; uncaughtExceptionCount++; throw exceptionLast; } function pthreadCreateProxied(pthread_ptr, attr, startRoutine, arg) { if (ENVIRONMENT_IS_PTHREAD) return proxyToMainThread(2, 0, 1, pthread_ptr, attr, startRoutine, arg); return ___pthread_create_js(pthread_ptr, attr, startRoutine, arg); } var _emscripten_has_threading_support = () => !!globalThis.SharedArrayBuffer; function ___pthread_create_js(pthread_ptr, attr, startRoutine, arg) { pthread_ptr = bigintToI53Checked(pthread_ptr); attr = bigintToI53Checked(attr); startRoutine = bigintToI53Checked(startRoutine); arg = bigintToI53Checked(arg); if (!_emscripten_has_threading_support()) { return 6; } // List of JS objects that will transfer ownership to the Worker hosting the thread var transferList = []; var error = 0; // 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; var threadParams = { startRoutine, pthread_ptr, arg, 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); } var PATH = { isAbs: path => path.charAt(0) === "/", splitPath: filename => { var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; return splitPathRe.exec(filename).slice(1); }, normalizeArray: (parts, allowAboveRoot) => { // if the path tries to go above the root, `up` ends up > 0 var up = 0; for (var i = parts.length - 1; i >= 0; i--) { var last = parts[i]; if (last === ".") { parts.splice(i, 1); } else if (last === "..") { parts.splice(i, 1); up++; } else if (up) { parts.splice(i, 1); up--; } } // if the path is allowed to go above the root, restore leading ..s if (allowAboveRoot) { for (;up; up--) { parts.unshift(".."); } } return parts; }, normalize: path => { var isAbsolute = PATH.isAbs(path), trailingSlash = path.slice(-1) === "/"; // Normalize the path path = PATH.normalizeArray(path.split("/").filter(p => !!p), !isAbsolute).join("/"); if (!path && !isAbsolute) { path = "."; } if (path && trailingSlash) { path += "/"; } return (isAbsolute ? "/" : "") + path; }, dirname: path => { var result = PATH.splitPath(path), root = result[0], dir = result[1]; if (!root && !dir) { // No dirname whatsoever return "."; } if (dir) { // It has a dirname, strip trailing slash dir = dir.slice(0, -1); } return root + dir; }, basename: path => path && path.match(/([^\/]+|\/)\/*$/)[1], join: (...paths) => PATH.normalize(paths.join("/")), join2: (l, r) => PATH.normalize(l + "/" + r) }; var initRandomFill = () => view => view.set(crypto.getRandomValues(new Uint8Array(view.byteLength))); var randomFill = view => { // Lazily init on the first invocation. (randomFill = initRandomFill())(view); }; var PATH_FS = { resolve: (...args) => { var resolvedPath = "", resolvedAbsolute = false; for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { var path = (i >= 0) ? args[i] : FS.cwd(); // Skip empty and invalid entries if (typeof path != "string") { throw new TypeError("Arguments to path.resolve must be strings"); } else if (!path) { return ""; } resolvedPath = path + "/" + resolvedPath; resolvedAbsolute = PATH.isAbs(path); } // At this point the path should be resolved to a full absolute path, but // handle relative paths to be safe (might happen when process.cwd() fails) resolvedPath = PATH.normalizeArray(resolvedPath.split("/").filter(p => !!p), !resolvedAbsolute).join("/"); return ((resolvedAbsolute ? "/" : "") + resolvedPath) || "."; }, relative: (from, to) => { from = PATH_FS.resolve(from).slice(1); to = PATH_FS.resolve(to).slice(1); function trim(arr) { var start = 0; for (;start < arr.length; start++) { if (arr[start] !== "") break; } var end = arr.length - 1; for (;end >= 0; end--) { if (arr[end] !== "") break; } if (start > end) return []; return arr.slice(start, end - start + 1); } var fromParts = trim(from.split("/")); var toParts = trim(to.split("/")); var length = Math.min(fromParts.length, toParts.length); var samePartsLength = length; for (var i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break; } } var outputParts = []; for (var i = samePartsLength; i < fromParts.length; i++) { outputParts.push(".."); } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join("/"); } }; var UTF8Decoder = globalThis.TextDecoder && new TextDecoder; var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { var maxIdx = idx + maxBytesToRead; if (ignoreNul) return maxIdx; // TextDecoder needs to know the byte length in advance, it doesn't stop on // null terminator by itself. // As a tiny code save trick, compare idx against maxIdx using a negation, // so that maxBytesToRead=undefined/NaN means Infinity. while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; return idx; }; /** * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given * array that contains uint8 values, returns a copy of that string as a * Javascript String object. * heapOrArray is either a regular array, or a JavaScript typed array view. * @param {number=} idx * @param {number=} maxBytesToRead * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. * @return {string} */ var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead, ignoreNul) => { var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); // When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it. if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { return UTF8Decoder.decode(heapOrArray.buffer instanceof ArrayBuffer ? heapOrArray.subarray(idx, endPtr) : heapOrArray.slice(idx, endPtr)); } var str = ""; while (idx < endPtr) { // For UTF8 byte structure, see: // http://en.wikipedia.org/wiki/UTF-8#Description // https://www.ietf.org/rfc/rfc2279.txt // https://tools.ietf.org/html/rfc3629 var u0 = heapOrArray[idx++]; if (!(u0 & 128)) { str += String.fromCharCode(u0); continue; } var u1 = heapOrArray[idx++] & 63; if ((u0 & 224) == 192) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } var u2 = heapOrArray[idx++] & 63; if ((u0 & 240) == 224) { u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; } else { u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); } if (u0 < 65536) { str += String.fromCharCode(u0); } else { var ch = u0 - 65536; str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023)); } } return str; }; var FS_stdin_getChar_buffer = []; var lengthBytesUTF8 = str => { var len = 0; for (var i = 0; i < str.length; ++i) { // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code // unit, not a Unicode code point of the character! So decode // UTF16->UTF32->UTF8. // See http://unicode.org/faq/utf_bom.html#utf16-3 var c = str.charCodeAt(i); // possibly a lead surrogate if (c <= 127) { len++; } else if (c <= 2047) { len += 2; } else if (c >= 55296 && c <= 57343) { len += 4; ++i; } else { len += 3; } } return len; }; var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { // Parameter maxBytesToWrite is not optional. Negative values, 0, null, // undefined and false each don't write out any bytes. if (!(maxBytesToWrite > 0)) return 0; var startIdx = outIdx; var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator. for (var i = 0; i < str.length; ++i) { // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description // and https://www.ietf.org/rfc/rfc2279.txt // and https://tools.ietf.org/html/rfc3629 var u = str.codePointAt(i); if (u <= 127) { if (outIdx >= endIdx) break; heap[outIdx++] = u; } else if (u <= 2047) { if (outIdx + 1 >= endIdx) break; heap[outIdx++] = 192 | (u >> 6); heap[outIdx++] = 128 | (u & 63); } else if (u <= 65535) { if (outIdx + 2 >= endIdx) break; heap[outIdx++] = 224 | (u >> 12); heap[outIdx++] = 128 | ((u >> 6) & 63); heap[outIdx++] = 128 | (u & 63); } else { if (outIdx + 3 >= endIdx) break; heap[outIdx++] = 240 | (u >> 18); heap[outIdx++] = 128 | ((u >> 12) & 63); heap[outIdx++] = 128 | ((u >> 6) & 63); heap[outIdx++] = 128 | (u & 63); // Gotcha: if codePoint is over 0xFFFF, it is represented as a surrogate pair in UTF-16. // We need to manually skip over the second code unit for correct iteration. i++; } } // Null-terminate the pointer to the buffer. heap[outIdx] = 0; return outIdx - startIdx; }; /** @type {function(string, boolean=, number=)} */ var intArrayFromString = (stringy, dontAddNull, length) => { var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; var u8array = new Array(len); var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); if (dontAddNull) u8array.length = numBytesWritten; return u8array; }; var FS_stdin_getChar = () => { if (!FS_stdin_getChar_buffer.length) { var result = null; if (globalThis.window?.prompt) { // Browser. result = window.prompt("Input: "); // returns null on cancel if (result !== null) { result += "\n"; } } else {} if (!result) { return null; } FS_stdin_getChar_buffer = intArrayFromString(result, true); } return FS_stdin_getChar_buffer.shift(); }; var TTY = { ttys: [], init() {}, shutdown() {}, register(dev, ops) { TTY.ttys[dev] = { input: [], output: [], ops }; FS.registerDevice(dev, TTY.stream_ops); }, stream_ops: { open(stream) { var tty = TTY.ttys[stream.node.rdev]; if (!tty) { throw new FS.ErrnoError(43); } stream.tty = tty; stream.seekable = false; }, close(stream) { // flush any pending line data stream.tty.ops.fsync(stream.tty); }, fsync(stream) { stream.tty.ops.fsync(stream.tty); }, read(stream, buffer, offset, length, pos) { if (!stream.tty || !stream.tty.ops.get_char) { throw new FS.ErrnoError(60); } var bytesRead = 0; for (var i = 0; i < length; i++) { var result; try { result = stream.tty.ops.get_char(stream.tty); } catch (e) { throw new FS.ErrnoError(29); } if (result === undefined && bytesRead === 0) { throw new FS.ErrnoError(6); } if (result === null || result === undefined) break; bytesRead++; buffer[offset + i] = result; } if (bytesRead) { stream.node.atime = Date.now(); } return bytesRead; }, write(stream, buffer, offset, length, pos) { if (!stream.tty || !stream.tty.ops.put_char) { throw new FS.ErrnoError(60); } try { for (var i = 0; i < length; i++) { stream.tty.ops.put_char(stream.tty, buffer[offset + i]); } } catch (e) { throw new FS.ErrnoError(29); } if (length) { stream.node.mtime = stream.node.ctime = Date.now(); } return i; } }, default_tty_ops: { get_char(tty) { return FS_stdin_getChar(); }, put_char(tty, val) { if (val === null || val === 10) { out(UTF8ArrayToString(tty.output)); tty.output = []; } else { if (val != 0) tty.output.push(val); } }, fsync(tty) { if (tty.output?.length > 0) { out(UTF8ArrayToString(tty.output)); tty.output = []; } }, ioctl_tcgets(tty) { // typical setting return { c_iflag: 25856, c_oflag: 5, c_cflag: 191, c_lflag: 35387, c_