scran.js
Version:
Single cell RNA-seq analysis in Javascript
1,383 lines (1,288 loc) • 223 kB
JavaScript
// 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_