UNPKG

scran.js

Version:

Single cell RNA-seq analysis in Javascript

1,314 lines (1,213 loc) 247 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"); if (ENVIRONMENT_IS_NODE) { // When building an ES module `require` is not normally available. // We need to use `createRequire()` to construct the require()` function. const {createRequire} = await import("module"); /** @suppress{duplicate} */ var require = createRequire(import.meta.url); var worker_threads = require("worker_threads"); global.Worker = worker_threads.Worker; ENVIRONMENT_IS_WORKER = !worker_threads.isMainThread; // Under node we set `workerData` to `em-pthread` to signal that the worker // is hosting a pthread. ENVIRONMENT_IS_PTHREAD = ENVIRONMENT_IS_WORKER && worker_threads["workerData"] == "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; if (ENVIRONMENT_IS_NODE) { // These modules will usually be used on Node.js. Load them eagerly to avoid // the complexity of lazy-loading. var fs = require("fs"); if (_scriptName.startsWith("file:")) { scriptDirectory = require("path").dirname(require("url").fileURLToPath(_scriptName)) + "/"; } // include: node_shell_read.js readBinary = filename => { // We need to re-wrap `file://` strings to URLs. filename = isFileURI(filename) ? new URL(filename) : filename; var ret = fs.readFileSync(filename); return ret; }; readAsync = async (filename, binary = true) => { // See the comment in the `readBinary` function. filename = isFileURI(filename) ? new URL(filename) : filename; var ret = fs.readFileSync(filename, binary ? undefined : "utf8"); return ret; }; // end include: node_shell_read.js if (process.argv.length > 1) { thisProgram = process.argv[1].replace(/\\/g, "/"); } arguments_ = process.argv.slice(2); quit_ = (status, toThrow) => { process.exitCode = status; throw toThrow; }; } else // 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 {} // Differentiate the Web Worker from the Node Worker case, as reading must // be done differently. if (!ENVIRONMENT_IS_NODE) { // 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 {} // Set up the out() and err() hooks, which are how we can print to stdout or // stderr, respectively. // Normally just binding console.log/console.error here works fine, but // under node (with workers) we see missing/out-of-order messages so route // directly to stdout and stderr. // See https://github.com/emscripten-core/emscripten/issues/14804 var defaultPrint = console.log.bind(console); var defaultPrintErr = console.error.bind(console); if (ENVIRONMENT_IS_NODE) { var utils = require("util"); var stringify = a => typeof a == "object" ? utils.inspect(a) : a; defaultPrint = (...args) => fs.writeSync(1, args.map(stringify).join(" ") + "\n"); defaultPrintErr = (...args) => fs.writeSync(2, args.map(stringify).join(" ") + "\n"); } var out = defaultPrint; var err = defaultPrintErr; // 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; /** * Indicates whether filename is delivered via file protocol (as opposed to http/https) * @noinline */ var isFileURI = filename => filename.startsWith("file://"); // 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; if (ENVIRONMENT_IS_NODE && (ENVIRONMENT_IS_PTHREAD)) { // Create as web-worker-like an environment as we can. var parentPort = worker_threads["parentPort"]; parentPort.on("message", msg => global.onmessage?.({ data: msg })); Object.assign(globalThis, { self: global, postMessage: msg => parentPort["postMessage"](msg) }); // Node.js Workers do not pass postMessage()s and uncaught exception events to the parent // thread necessarily in the same order where they were generated in sequential program order. // See https://github.com/nodejs/node/issues/59617 // To remedy this, capture all uncaughtExceptions in the Worker, and sequentialize those over // to the same postMessage pipe that other messages use. process.on("uncaughtException", err => { postMessage({ cmd: "uncaughtException", error: err }); // Also shut down the Worker to match the same semantics as if this uncaughtException // handler was not registered. // (n.b. this will not shut down the whole Node.js app process, but just the Worker) process.exit(1); }); } // 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 && !ENVIRONMENT_IS_NODE) { 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 }; 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(); } // 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; // 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(); } onFinishedLoading(worker); } else if (d.target === "setimmediate") { // Worker wants to postMessage() to itself to implement setImmediate() // emulation. worker.postMessage(d); } else if (cmd === "uncaughtException") { // Message handler for Node.js specific out-of-order behavior: // https://github.com/nodejs/node/issues/59617 // A pthread sent an uncaught exception event. Re-raise it on the main thread. worker.onerror(d.error); } 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; }; if (ENVIRONMENT_IS_NODE) { worker.on("message", data => worker.onmessage({ data })); worker.on("error", e => worker.onerror(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 node worker that it is hosting // a pthread. "workerData": "em-pthread", // 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 node worker that it is hosting // a pthread. "workerData": "em-pthread", // 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 nodePath = require("path"); var PATH = { isAbs: nodePath.isAbsolute, normalize: nodePath.normalize, dirname: nodePath.dirname, basename: nodePath.basename, join: nodePath.join, join2: nodePath.join }; var initRandomFill = () => view => view.set(crypto.getRandomValues(new Uint8Array(view.byteLength))); var randomFill = view => { // Lazily init on the first invocation. (randomFill = initRandomFill())(view); }; /** @type{{resolve: function(...*)}} */ var PATH_FS = { resolve: (...paths) => { paths.unshift(FS.cwd()); return nodePath.posix.resolve(...paths); }, relative: (from, to) => nodePath.posix.relative(from || FS.cwd(), to || FS.cwd()) }; 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 (ENVIRONMENT_IS_NODE) { // we will read data by chunks of BUFSIZE var BUFSIZE = 256; var buf = Buffer.alloc(BUFSIZE); var bytesRead = 0; // For some reason we must suppress a closure warning here, even though // fd definitely exists on process.stdin, and is even the proper way to // get the fd of stdin, // https://github.com/nodejs/help/issues/2136#issuecomment-523649904 // This star