text: /**
   * @license
   * Copyright 2015 The Emscripten Authors
   * SPDX-License-Identifier: MIT
   */
  
  var LibraryPThread = {
    $PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock();',
    $PThread__deps: ['_emscripten_thread_init',
                     'emscripten_register_main_browser_thread_id',
                     '$ERRNO_CODES', 'emscripten_futex_wake', '$killThread',
                     '$cancelThread', '$cleanupThread',
  #if USE_ASAN || USE_LSAN
                     , '$withBuiltinMalloc'
  #endif
                     ],
    $PThread: {
      // Contains all Workers that are idle/unused and not currently hosting an
      // executing pthread.  Unused Workers can either be pooled up before page
      // startup, but also when a pthread quits, its hosting Worker is not
      // terminated, but is returned to this pool as an optimization so that
      // starting the next thread is faster.
      unusedWorkers: [],
      // Contains all Workers that are currently hosting an active pthread.
      runningWorkers: [],
      initMainThreadBlock: function() {
  #if ASSERTIONS
        assert(!ENVIRONMENT_IS_PTHREAD);
  #endif
  
  #if PTHREAD_POOL_SIZE
        var pthreadPoolSize = {{{ PTHREAD_POOL_SIZE }}};
        // Start loading up the Worker pool, if requested.
        for(var i = 0; i < pthreadPoolSize; ++i) {
          PThread.allocateUnusedWorker();
        }
  #endif
      },
      initRuntime: function() {
  #if USE_ASAN || USE_LSAN
        // When sanitizers are enabled, malloc is normally instrumented to call
        // sanitizer code that checks some things about pthreads. As we are just
        // setting up the main thread here, and are not ready for such calls,
        // call malloc directly.
        withBuiltinMalloc(function () {
  #endif
  
        var tb = _malloc({{{ C_STRUCTS.pthread.__size__ }}});
  
        for (var i = 0; i < {{{ C_STRUCTS.pthread.__size__ }}}/4; ++i) HEAPU32[tb/4+i] = 0;
  
        // The pthread struct has a field that points to itself - this is used as
          // a magic ID to detect whether the pthread_t structure is 'alive'.
        {{{ makeSetValue('tb', C_STRUCTS.pthread.self, 'tb', 'i32') }}};
  
        // pthread struct robust_list head should point to itself.
        var headPtr = tb + {{{ C_STRUCTS.pthread.robust_list }}};
        {{{ makeSetValue('headPtr', 0, 'headPtr', 'i32') }}};
  
        // Allocate memory for thread-local storage.
        var tlsMemory = _malloc({{{ cDefine('PTHREAD_KEYS_MAX') * 4 }}});
        for (var i = 0; i < {{{ cDefine('PTHREAD_KEYS_MAX') }}}; ++i) HEAPU32[tlsMemory/4+i] = 0;
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.tsd }}} ) >> 2, tlsMemory); // Init thread-local-storage memory array.
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.tid }}} ) >> 2, tb); // Main thread ID.
  
  #if PTHREADS_PROFILING
        PThread.createProfilerBlock(tb);
        PThread.setThreadName(tb, "Browser main thread");
        PThread.setThreadStatus(tb, {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}});
  #endif
  
        // Pass the thread address to the native code where they stored in wasm
        // globals which act as a form of TLS. Global constructors trying
        // to access this value will read the wrong value, but that is UB anyway.
        __emscripten_thread_init(tb, /*isMainBrowserThread=*/!ENVIRONMENT_IS_WORKER, /*isMainRuntimeThread=*/1);
        _emscripten_register_main_browser_thread_id(tb);
  #if ASSERTIONS
        PThread.mainRuntimeThread = true;
  #endif
  
  #if USE_ASAN || USE_LSAN
        });
  #endif
      },
      initWorker: function() {
  #if USE_CLOSURE_COMPILER
        // worker.js is not compiled together with us, and must access certain
        // things.
        PThread['receiveObjectTransfer'] = PThread.receiveObjectTransfer;
        PThread['setThreadStatus'] = PThread.setThreadStatus;
        PThread['threadCancel'] = PThread.threadCancel;
        PThread['threadExit'] = PThread.threadExit;
  #endif
      },
      // Maps pthread_t to pthread info objects
      pthreads: {},
      threadExitHandlers: [], // An array of C functions to run when this thread exits.
  
  #if PTHREADS_PROFILING
      createProfilerBlock: function(pthreadPtr) {
        var profilerBlock = _malloc({{{ C_STRUCTS.thread_profiler_block.__size__ }}});
        Atomics.store(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, profilerBlock);
  
        // Zero fill contents at startup.
        for (var i = 0; i < {{{ C_STRUCTS.thread_profiler_block.__size__ }}}; i += 4) Atomics.store(HEAPU32, (profilerBlock + i) >> 2, 0);
        Atomics.store(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.currentStatusStartTime }}} ) >> 2, performance.now());
      },
  
      // Sets the current thread status, but only if it was in the given expected state before. This is used
      // to allow high-level control flow "override" the thread status before low-level (futex wait) operations set it.
      setThreadStatusConditional: function(pthreadPtr, expectedStatus, newStatus) {
        var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2);
        if (!profilerBlock) return;
  
        var prevStatus = Atomics.load(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.threadStatus }}} ) >> 2);
  
        if (prevStatus != newStatus && (prevStatus == expectedStatus || expectedStatus == -1)) {
          var now = performance.now();
          var startState = HEAPF64[(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.currentStatusStartTime }}} ) >> 3];
          var duration = now - startState;
  
          HEAPF64[((profilerBlock + {{{ C_STRUCTS.thread_profiler_block.timeSpentInStatus }}} ) >> 3) + prevStatus] += duration;
          Atomics.store(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.threadStatus }}} ) >> 2, newStatus);
          HEAPF64[(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.currentStatusStartTime }}} ) >> 3] = now;
        }
      },
  
      // Unconditionally sets the thread status.
      setThreadStatus: function(pthreadPtr, newStatus) {
        PThread.setThreadStatusConditional(pthreadPtr, -1, newStatus);
      },
  
      setThreadName: function(pthreadPtr, name) {
        var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2);
        if (!profilerBlock) return;
        stringToUTF8(name, profilerBlock + {{{ C_STRUCTS.thread_profiler_block.name }}}, 32);
      },
  
      getThreadName: function(pthreadPtr) {
        var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2);
        if (!profilerBlock) return "";
        return UTF8ToString(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.name }}});
      },
  
      threadStatusToString: function(threadStatus) {
        switch(threadStatus) {
          case 0: return "not yet started";
          case 1: return "running";
          case 2: return "sleeping";
          case 3: return "waiting for a futex";
          case 4: return "waiting for a mutex";
          case 5: return "waiting for a proxied operation";
          case 6: return "finished execution";
          default: return "unknown (corrupt?!)";
        }
      },
  
      threadStatusAsString: function(pthreadPtr) {
        var profilerBlock = Atomics.load(HEAPU32, (pthreadPtr + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2);
        var status = (profilerBlock == 0) ? 0 : Atomics.load(HEAPU32, (profilerBlock + {{{ C_STRUCTS.thread_profiler_block.threadStatus }}} ) >> 2);
        return PThread.threadStatusToString(status);
      },
  #else
      setThreadStatus: function() {},
  #endif
  
      runExitHandlers: function() {
        while (PThread.threadExitHandlers.length > 0) {
          PThread.threadExitHandlers.pop()();
        }
  
        // Call into the musl function that runs destructors of all thread-specific data.
        if (ENVIRONMENT_IS_PTHREAD && _pthread_self()) ___pthread_tsd_run_dtors();
      },
  
      runExitHandlersAndDeinitThread: function(tb, exitCode) {
  #if PTHREADS_PROFILING
        var profilerBlock = Atomics.load(HEAPU32, (tb + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2);
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, 0);
        _free(profilerBlock);
  #endif
  
        // Disable all cancellation so that executing the cleanup handlers won't trigger another JS
        // canceled exception to be thrown.
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2, 1/*PTHREAD_CANCEL_DISABLE*/);
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.cancelasync }}} ) >> 2, 0/*PTHREAD_CANCEL_DEFERRED*/);
        PThread.runExitHandlers();
  
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode);
        // When we publish this, the main thread is free to deallocate the thread object and we are done.
        // Therefore set _pthread_self = 0; above to 'release' the object in this worker thread.
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running.
  
        _emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads
  
        // Not hosting a pthread anymore in this worker, reset the info structures to null.
        __emscripten_thread_init(0, 0, 0); // Unregister the thread block inside the wasm module.
      },
  
      // Called when we are performing a pthread_exit(), either explicitly called
      // by programmer, or implicitly when leaving the thread main function.
      threadExit: function(exitCode) {
        var tb = _pthread_self();
        if (tb) { // If we haven't yet exited?
  #if ASSERTIONS
          err('Pthread 0x' + tb.toString(16) + ' exited.');
  #endif
          PThread.runExitHandlersAndDeinitThread(tb, exitCode);
  
          if (ENVIRONMENT_IS_PTHREAD) {
            // Note: in theory we would like to return any offscreen canvases back to the main thread,
            // but if we ever fetched a rendering context for them that would not be valid, so we don't try.
            postMessage({ 'cmd': 'exit' });
          }
        }
      },
  
      threadCancel: function() {
        PThread.runExitHandlersAndDeinitThread(_pthread_self(), -1/*PTHREAD_CANCELED*/);
        postMessage({ 'cmd': 'cancelDone' });
      },
  
      terminateAllThreads: function() {
        for (var t in PThread.pthreads) {
          var pthread = PThread.pthreads[t];
          if (pthread && pthread.worker) {
            PThread.returnWorkerToPool(pthread.worker);
          }
        }
        PThread.pthreads = {};
  
        for (var i = 0; i < PThread.unusedWorkers.length; ++i) {
          var worker = PThread.unusedWorkers[i];
  #if ASSERTIONS
          assert(!worker.pthread); // This Worker should not be hosting a pthread at this time.
  #endif
          worker.terminate();
        }
        PThread.unusedWorkers = [];
  
        for (var i = 0; i < PThread.runningWorkers.length; ++i) {
          var worker = PThread.runningWorkers[i];
          var pthread = worker.pthread;
  #if ASSERTIONS
          assert(pthread, 'This Worker should have a pthread it is executing');
  #endif
          PThread.freeThreadData(pthread);
          worker.terminate();
        }
        PThread.runningWorkers = [];
      },
      freeThreadData: function(pthread) {
        if (!pthread) return;
        if (pthread.threadInfoStruct) {
          var tlsMemory = {{{ makeGetValue('pthread.threadInfoStruct', C_STRUCTS.pthread.tsd, 'i32') }}};
          {{{ makeSetValue('pthread.threadInfoStruct', C_STRUCTS.pthread.tsd, 0, 'i32') }}};
          _free(tlsMemory);
          _free(pthread.threadInfoStruct);
        }
        pthread.threadInfoStruct = 0;
        if (pthread.allocatedOwnStack && pthread.stackBase) _free(pthread.stackBase);
        pthread.stackBase = 0;
        if (pthread.worker) pthread.worker.pthread = null;
      },
      returnWorkerToPool: function(worker) {
        // We don't want to run main thread queued calls here, since we are doing
        // some operations that leave the worker queue in an invalid state until
        // we are completely done (it would be bad if free() ends up calling a
        // queued pthread_create which looks at the global data structures we are
        // modifying).
        PThread.runWithoutMainThreadQueuedCalls(function() {
          delete PThread.pthreads[worker.pthread.threadInfoStruct];
          //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
          PThread.freeThreadData(worker.pthread);
          // Detach the worker from the pthread object, and return it to the worker pool as an unused worker.
          worker.pthread = undefined;
        });
      },
      // Runs a function with processing of queued calls to the main thread
      // disabled. This is useful to avoid something like free() ending up waiting
      // for a lock, then running processing events, and those events can end up
      // doing things that interfere with what we were doing before (for example,
      // if we are tearing down a thread, calling free to erase its data could
      // end up calling a proxied pthread_create, which gets a free worker, and
      // can interfere).
      // This is only safe to call if we do not need queued calls to run. That is
      // the case when doing something like free(), which just needs the malloc
      // lock to be released.
      runWithoutMainThreadQueuedCalls: function(func) {
  #if ASSERTIONS
        assert(PThread.mainRuntimeThread, 'runWithoutMainThreadQueuedCalls must be done on the main runtime thread');
        assert(__emscripten_allow_main_runtime_queued_calls);
  #endif
        HEAP32[__emscripten_allow_main_runtime_queued_calls >> 2] = 0;
        try {
          func();
        } finally {
          HEAP32[__emscripten_allow_main_runtime_queued_calls >> 2] = 1;
        }
      },
      receiveObjectTransfer: function(data) {
  #if OFFSCREENCANVAS_SUPPORT
        if (typeof GL !== 'undefined') {
          for (var i in data.offscreenCanvases) {
            GL.offscreenCanvases[i] = data.offscreenCanvases[i];
          }
          if (!Module['canvas'] && data.moduleCanvasId && GL.offscreenCanvases[data.moduleCanvasId]) {
            Module['canvas'] = GL.offscreenCanvases[data.moduleCanvasId].offscreenCanvas;
            Module['canvas'].id = data.moduleCanvasId;
          }
        }
  #endif
      },
  
      // Loads the WebAssembly module into the given list of Workers.
      // onFinishedLoading: A callback function that will be called once all of
      //                    the workers have been initialized and are
      //                    ready to host pthreads.
      loadWasmModuleToWorker: function(worker, onFinishedLoading) {
        worker.onmessage = function(e) {
          var d = e['data'];
          var cmd = d['cmd'];
          // Sometimes we need to backproxy events to the calling thread (e.g.
          // HTML5 DOM events handlers such as
          // emscripten_set_mousemove_callback()), so keep track in a globally
          // accessible variable about the thread that initiated the proxying.
          if (worker.pthread) PThread.currentProxiedOperationCallerThread = worker.pthread.threadInfoStruct;
  
          // If this message is intended to a recipient that is not the main thread, forward it to the target thread.
          if (d['targetThread'] && d['targetThread'] != _pthread_self()) {
            var thread = PThread.pthreads[d.targetThread];
            if (thread) {
              thread.worker.postMessage(e.data, d['transferList']);
            } else {
              console.error('Internal error! Worker sent a message "' + cmd + '" to target pthread ' + d['targetThread'] + ', but that thread no longer exists!');
            }
            PThread.currentProxiedOperationCallerThread = undefined;
            return;
          }
  
          if (cmd === 'processQueuedMainThreadWork') {
            // TODO: Must post message to main Emscripten thread in PROXY_TO_WORKER mode.
            _emscripten_main_thread_process_queued_calls();
          } else if (cmd === 'spawnThread') {
            spawnThread(e.data);
          } else if (cmd === 'cleanupThread') {
            cleanupThread(d['thread']);
          } else if (cmd === 'killThread') {
            killThread(d['thread']);
          } else if (cmd === 'cancelThread') {
            cancelThread(d['thread']);
          } else if (cmd === 'loaded') {
            worker.loaded = true;
            if (onFinishedLoading) onFinishedLoading(worker);
            // If this Worker is already pending to start running a thread, launch the thread now
            if (worker.runPthread) {
              worker.runPthread();
              delete worker.runPthread;
            }
          } else if (cmd === 'print') {
            out('Thread ' + d['threadId'] + ': ' + d['text']);
          } else if (cmd === 'printErr') {
            err('Thread ' + d['threadId'] + ': ' + d['text']);
          } else if (cmd === 'alert') {
            alert('Thread ' + d['threadId'] + ': ' + d['text']);
          } else if (cmd === 'exit') {
            var detached = worker.pthread && Atomics.load(HEAPU32, (worker.pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.detached }}}) >> 2);
            if (detached) {
              PThread.returnWorkerToPool(worker);
            }
          } else if (cmd === 'exitProcess') {
            // A pthread has requested to exit the whole application process (runtime).
  #if ASSERTIONS
            err("exitProcess requested by worker");
  #endif
            try {
              exit(d['returnCode']);
            } catch (e) {
              if (e instanceof ExitStatus) return;
              throw e;
            }
          } else if (cmd === 'cancelDone') {
            PThread.returnWorkerToPool(worker);
          } else if (cmd === 'objectTransfer') {
            PThread.receiveObjectTransfer(e.data);
          } else if (e.data.target === 'setimmediate') {
            worker.postMessage(e.data); // Worker wants to postMessage() to itself to implement setImmediate() emulation.
          } else {
            err("worker sent an unknown command " + cmd);
          }
          PThread.currentProxiedOperationCallerThread = undefined;
        };
  
        worker.onerror = function(e) {
          err('pthread sent an error! ' + e.filename + ':' + e.lineno + ': ' + e.message);
        };
  
  #if ENVIRONMENT_MAY_BE_NODE
        if (ENVIRONMENT_IS_NODE) {
          worker.on('message', function(data) {
            worker.onmessage({ data: data });
          });
          worker.on('error', function(data) {
            worker.onerror(data);
          });
          worker.on('exit', function(data) {
            // TODO: update the worker queue?
            // See: https://github.com/emscripten-core/emscripten/issues/9763
          });
        }
  #endif
  
  #if ASSERTIONS
        assert(wasmMemory instanceof WebAssembly.Memory, 'WebAssembly memory should have been loaded by now!');
        assert(wasmModule instanceof WebAssembly.Module, 'WebAssembly Module should have been loaded by now!');
  #endif
  
        // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation.
        worker.postMessage({
          'cmd': 'load',
          // If the application main .js file was loaded from a Blob, then it is not possible
          // to access the URL of the current script that could be passed to a Web Worker so that
          // it could load up the same file. In that case, developer must either deliver the Blob
          // object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can
          // independently load up the same main application file.
          'urlOrBlob': Module['mainScriptUrlOrBlob'] || _scriptDir,
  #if WASM2JS
          // the polyfill WebAssembly.Memory instance has function properties,
          // which will fail in postMessage, so just send a custom object with the
          // property we need, the buffer
          'wasmMemory': { 'buffer': wasmMemory.buffer },
  #else // WASM2JS
          'wasmMemory': wasmMemory,
  #endif // WASM2JS
          'wasmModule': wasmModule,
  #if LOAD_SOURCE_MAP
          'wasmSourceMap': wasmSourceMap,
  #endif
  #if USE_OFFSET_CONVERTER
          'wasmOffsetConverter': wasmOffsetConverter,
  #endif
  #if MAIN_MODULE
          'dynamicLibraries': Module['dynamicLibraries'],
  #endif
        });
      },
  
      // Creates a new web Worker and places it in the unused worker pool to wait for its use.
      allocateUnusedWorker: function() {
  #if MINIMAL_RUNTIME
        var pthreadMainJs = Module['worker'];
  #else
        // Allow HTML module to configure the location where the 'worker.js' file will be loaded from,
        // via Module.locateFile() function. If not specified, then the default URL 'worker.js' relative
        // to the main html file is loaded.
        var pthreadMainJs = locateFile('{{{ PTHREAD_WORKER_FILE }}}');
  #endif
  #if PTHREADS_DEBUG
        out('Allocating a new web worker from ' + pthreadMainJs);
  #endif
        PThread.unusedWorkers.push(new Worker(pthreadMainJs));
      },
  
      getNewWorker: function() {
        if (PThread.unusedWorkers.length == 0) {
          PThread.allocateUnusedWorker();
          PThread.loadWasmModuleToWorker(PThread.unusedWorkers[0]);
        }
        if (PThread.unusedWorkers.length > 0) return PThread.unusedWorkers.pop();
        else return null;
      },
  
      busySpinWait: function(msecs) {
        var t = performance.now() + msecs;
        while(performance.now() < t) {
          ;
        }
      }
    },
  
    $killThread: function(pthread_ptr) {
      if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! killThread() can only ever be called from main application thread!';
      if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in killThread!';
      {{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
      var pthread = PThread.pthreads[pthread_ptr];
      pthread.worker.terminate();
      PThread.freeThreadData(pthread);
      // The worker was completely nuked (not just the pthread execution it was hosting), so remove it from running workers
      // but don't put it back to the pool.
      PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(pthread.worker), 1); // Not a running Worker anymore.
      pthread.worker.pthread = undefined;
    },
  
    $cleanupThread: function(pthread_ptr) {
      if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! cleanupThread() can only ever be called from main application thread!';
      if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in cleanupThread!';
      var pthread = PThread.pthreads[pthread_ptr];
      // If pthread has been removed from this map this also means that pthread_ptr points
      // to already freed data. Such situation may occur in following circumstances:
      // 1. Joining cancelled thread - in such situation it may happen that pthread data will
      //    already be removed by handling 'cancelDone' message.
      // 2. Joining thread from non-main browser thread (this also includes thread running main()
      //    when compiled with `PROXY_TO_PTHREAD`) - in such situation it may happen that following
      //    code flow occur (MB - Main Browser Thread, S1, S2 - Worker Threads):
      //    S2: thread ends, 'exit' message is sent to MB
      //    S1: calls pthread_join(S2), this causes:
      //        a. S2 is marked as detached,
      //        b. 'cleanupThread' message is sent to MB.
      //    MB: handles 'exit' message, as thread is detached, so returnWorkerToPool()
      //        is called and all thread related structs are freed/released.
      //    MB: handles 'cleanupThread' message which calls this function.
      if (pthread) {
        {{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
        var worker = pthread.worker;
        PThread.returnWorkerToPool(worker);
      }
    },
  
    $cancelThread: function(pthread_ptr) {
      if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! cancelThread() can only ever be called from main application thread!';
      if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in cancelThread!';
      var pthread = PThread.pthreads[pthread_ptr];
      pthread.worker.postMessage({ 'cmd': 'cancel' });
    },
  
    $spawnThread: function(threadParams) {
      if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! spawnThread() can only ever be called from main application thread!';
  
      var worker = PThread.getNewWorker();
  
      if (worker.pthread !== undefined) throw 'Internal error!';
      if (!threadParams.pthread_ptr) throw 'Internal error, no pthread ptr!';
      PThread.runningWorkers.push(worker);
  
      // Allocate memory for thread-local storage and initialize it to zero.
      var tlsMemory = _malloc({{{ cDefine('PTHREAD_KEYS_MAX') }}} * 4);
      for (var i = 0; i < {{{ cDefine('PTHREAD_KEYS_MAX') }}}; ++i) {
        {{{ makeSetValue('tlsMemory', 'i*4', 0, 'i32') }}};
      }
  
      var stackHigh = threadParams.stackBase + threadParams.stackSize;
  
      // Create a pthread info object to represent this thread.
      var pthread = PThread.pthreads[threadParams.pthread_ptr] = {
        worker: worker,
        stackBase: threadParams.stackBase,
        stackSize: threadParams.stackSize,
        allocatedOwnStack: threadParams.allocatedOwnStack,
        // Info area for this thread in Emscripten HEAP (shared)
        threadInfoStruct: threadParams.pthread_ptr
      };
      var tis = pthread.threadInfoStruct >> 2;
      // spawnThread is always called with a zero-initialized thread struct so
      // no need to set any valudes to zero here.
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.detached }}} >> 2), threadParams.detached);
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.tsd }}} >> 2), tlsMemory); // Init thread-local-storage memory array.
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.tid }}} >> 2), pthread.threadInfoStruct); // Main thread ID.
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.stack_size }}} >> 2), threadParams.stackSize);
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.stack }}} >> 2), stackHigh);
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.attr }}} >> 2), threadParams.stackSize);
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.attr }}} + 8 >> 2), stackHigh);
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.attr }}} + 12 >> 2), threadParams.detached);
  
      var global_libc = _emscripten_get_global_libc();
      var global_locale = global_libc + {{{ C_STRUCTS.libc.global_locale }}};
      Atomics.store(HEAPU32, tis + ({{{ C_STRUCTS.pthread.locale }}} >> 2), global_locale);
  
  #if PTHREADS_PROFILING
      PThread.createProfilerBlock(pthread.threadInfoStruct);
  #endif
  
      worker.pthread = pthread;
      var msg = {
          'cmd': 'run',
          'start_routine': threadParams.startRoutine,
          'arg': threadParams.arg,
          'threadInfoStruct': threadParams.pthread_ptr,
          'stackBase': threadParams.stackBase,
          'stackSize': threadParams.stackSize
      };
  #if OFFSCREENCANVAS_SUPPORT
      // Note that we do not need to quote these names because they are only used
      // in this file, and not from the external worker.js.
      msg.moduleCanvasId = threadParams.moduleCanvasId;
      msg.offscreenCanvases = threadParams.offscreenCanvases;
  #endif
      worker.runPthread = function() {
        // Ask the worker to start executing its pthread entry point function.
        msg.time = performance.now();
        worker.postMessage(msg, threadParams.transferList);
      };
      if (worker.loaded) {
        worker.runPthread();
        delete worker.runPthread;
      }
    },
  
    emscripten_has_threading_support: function() {
      return typeof SharedArrayBuffer !== 'undefined';
    },
  
    emscripten_num_logical_cores: function() {
  #if ENVIRONMENT_MAY_BE_NODE
      if (ENVIRONMENT_IS_NODE) return require('os').cpus().length;
  #endif
      return navigator['hardwareConcurrency'];
    },
  
    {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create__deps: ['$spawnThread', 'pthread_self', 'memalign', 'emscripten_sync_run_in_main_thread_4'],
    {{{ USE_LSAN || USE_ASAN ? 'emscripten_builtin_' : '' }}}pthread_create: function(pthread_ptr, attr, start_routine, arg) {
      if (typeof SharedArrayBuffer === 'undefined') {
        err('Current environment does not support SharedArrayBuffer, pthreads are not available!');
        return {{{ cDefine('EAGAIN') }}};
      }
      if (!pthread_ptr) {
        err('pthread_create called with a null thread pointer!');
        return {{{ cDefine('EINVAL') }}};
      }
  
      // List of JS objects that will transfer ownership to the Worker hosting the thread
      var transferList = [];
      var error = 0;
  
  #if OFFSCREENCANVAS_SUPPORT
      // Deduce which WebGL canvases (HTMLCanvasElements or OffscreenCanvases) should be passed over to the
      // Worker that hosts the spawned pthread.
      // Comma-delimited list of CSS selectors that must identify canvases by IDs: "#canvas1, #canvas2, ..."
      var transferredCanvasNames = attr ? {{{ makeGetValue('attr', 36, 'i32') }}} : 0;
  #if OFFSCREENCANVASES_TO_PTHREAD
      // Proxied canvases string pointer -1 is used as a special token to fetch
      // whatever canvases were passed to build in -s
      // OFFSCREENCANVASES_TO_PTHREAD= command line.
      if (transferredCanvasNames == -1) transferredCanvasNames = '{{{ OFFSCREENCANVASES_TO_PTHREAD }}}';
      else
  #endif
      if (transferredCanvasNames) transferredCanvasNames = UTF8ToString(transferredCanvasNames).trim();
      if (transferredCanvasNames) transferredCanvasNames = transferredCanvasNames.split(',');
  #if GL_DEBUG
      console.log('pthread_create: transferredCanvasNames="' + transferredCanvasNames + '"');
  #endif
  
      var offscreenCanvases = {}; // Dictionary of OffscreenCanvas objects we'll transfer to the created thread to own
      var moduleCanvasId = Module['canvas'] ? Module['canvas'].id : '';
      for (var i in transferredCanvasNames) {
        var name = transferredCanvasNames[i].trim();
        var offscreenCanvasInfo;
        try {
          if (name == '#canvas') {
            if (!Module['canvas']) {
              err('pthread_create: could not find canvas with ID "' + name + '" to transfer to thread!');
              error = {{{ cDefine('EINVAL') }}};
              break;
            }
            name = Module['canvas'].id;
          }
  #if ASSERTIONS
          assert(typeof GL === 'object', 'OFFSCREENCANVAS_SUPPORT assumes GL is in use (you can force-include it with -s \'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=["$GL"]\')');
  #endif
          if (GL.offscreenCanvases[name]) {
            offscreenCanvasInfo = GL.offscreenCanvases[name];
            GL.offscreenCanvases[name] = null; // This thread no longer owns this canvas.
            if (Module['canvas'] instanceof OffscreenCanvas && name === Module['canvas'].id) Module['canvas'] = null;
          } else if (!ENVIRONMENT_IS_PTHREAD) {
            var canvas = (Module['canvas'] && Module['canvas'].id === name) ? Module['canvas'] : document.querySelector(name);
            if (!canvas) {
              err('pthread_create: could not find canvas with ID "' + name + '" to transfer to thread!');
              error = {{{ cDefine('EINVAL') }}};
              break;
            }
            if (canvas.controlTransferredOffscreen) {
              err('pthread_create: cannot transfer canvas with ID "' + name + '" to thread, since the current thread does not have control over it!');
              error = {{{ cDefine('EPERM') }}}; // Operation not permitted, some other thread is accessing the canvas.
              break;
            }
            if (canvas.transferControlToOffscreen) {
  #if GL_DEBUG
              err('pthread_create: canvas.transferControlToOffscreen(), transferring canvas by name "' + name + '" (DOM id="' + canvas.id + '") from main thread to pthread');
  #endif
              // Create a shared information block in heap so that we can control
              // the canvas size from any thread.
              if (!canvas.canvasSharedPtr) {
                canvas.canvasSharedPtr = _malloc(12);
                {{{ makeSetValue('canvas.canvasSharedPtr', 0, 'canvas.width', 'i32') }}};
                {{{ makeSetValue('canvas.canvasSharedPtr', 4, 'canvas.height', 'i32') }}};
                {{{ makeSetValue('canvas.canvasSharedPtr', 8, 0, 'i32') }}}; // pthread ptr to the thread that owns this canvas, filled in below.
              }
              offscreenCanvasInfo = {
                offscreenCanvas: canvas.transferControlToOffscreen(),
                canvasSharedPtr: canvas.canvasSharedPtr,
                id: canvas.id
              }
              // After calling canvas.transferControlToOffscreen(), it is no
              // longer possible to access certain operations on the canvas, such
              // as resizing it or obtaining GL contexts via it.
              // Use this field to remember that we have permanently converted
              // this Canvas to be controlled via an OffscreenCanvas (there is no
              // way to undo this in the spec)
              canvas.controlTransferredOffscreen = true;
            } else {
              err('pthread_create: cannot transfer control of canvas "' + name + '" to pthread, because current browser does not support OffscreenCanvas!');
              // If building with OFFSCREEN_FRAMEBUFFER=1 mode, we don't need to
              // be able to transfer control to offscreen, but WebGL can be
              // proxied from worker to main thread.
  #if !OFFSCREEN_FRAMEBUFFER
              err('pthread_create: Build with -s OFFSCREEN_FRAMEBUFFER=1 to enable fallback proxying of GL commands from pthread to main thread.');
              return {{{ cDefine('ENOSYS') }}}; // Function not implemented, browser doesn't have support for this.
  #endif
            }
          }
          if (offscreenCanvasInfo) {
            transferList.push(offscreenCanvasInfo.offscreenCanvas);
            offscreenCanvases[offscreenCanvasInfo.id] = offscreenCanvasInfo;
          }
        } catch(e) {
          err('pthread_create: failed to transfer control of canvas "' + name + '" to OffscreenCanvas! Error: ' + e);
          return {{{ cDefine('EINVAL') }}}; // Hitting this might indicate an implementation bug or some other internal error
        }
      }
  #endif
  
      // Synchronously proxy the thread creation to main thread if possible. If we
      // need to transfer ownership of objects, then proxy asynchronously via
      // postMessage.
      if (ENVIRONMENT_IS_PTHREAD && (transferList.length === 0 || error)) {
        return _emscripten_sync_run_in_main_thread_4({{{ cDefine('EM_PROXIED_PTHREAD_CREATE') }}}, pthread_ptr, attr, start_routine, arg);
      }
  
      // If on the main thread, and accessing Canvas/OffscreenCanvas failed, abort
      // with the detected error.
      if (error) return error;
  
      var stackSize = 0;
      var stackBase = 0;
      // Default thread attr is PTHREAD_CREATE_JOINABLE, i.e. start as not detached.
      var detached = 0;
      // When musl creates C11 threads it passes __ATTRP_C11_THREAD (-1) which
      // treat as if it was NULL.
      if (attr && attr != {{{ cDefine('__ATTRP_C11_THREAD') }}}) {
        stackSize = {{{ makeGetValue('attr', 0, 'i32') }}};
        // Musl has a convention that the stack size that is stored to the pthread
        // attribute structure is always musl's #define DEFAULT_STACK_SIZE
        // smaller than the actual created stack size. That is, stored stack size
        // of 0 would mean a stack of DEFAULT_STACK_SIZE in size. All musl
        // functions hide this impl detail, and offset the size transparently, so
        // pthread_*() API user does not see this offset when operating with
        // the pthread API. When reading the structure directly on JS side
        // however, we need to offset the size manually here.
        stackSize += 81920 /*DEFAULT_STACK_SIZE*/;
        stackBase = {{{ makeGetValue('attr', 8, 'i32') }}};
        detached = {{{ makeGetValue('attr', 12/*_a_detach*/, 'i32') }}} !== 0/*PTHREAD_CREATE_JOINABLE*/;
      } else {
        // According to
        // http://man7.org/linux/man-pages/man3/pthread_create.3.html, default
        // stack size if not specified is 2 MB, so follow that convention.
        stackSize = {{{ DEFAULT_PTHREAD_STACK_SIZE }}};
      }
      // If allocatedOwnStack == true, then the pthread impl maintains the stack allocation.
      var allocatedOwnStack = stackBase == 0;
      if (allocatedOwnStack) {
        // Allocate a stack if the user doesn't want to place the stack in a
        // custom memory area.
        stackBase = _memalign({{{ STACK_ALIGN }}}, stackSize);
      } else {
        // Musl stores the stack base address assuming stack grows downwards, so
        // adjust it to Emscripten convention that the
        // stack grows upwards instead.
        stackBase -= stackSize;
        assert(stackBase > 0);
      }
  
      // Allocate thread block (pthread_t structure).
      var threadInfoStruct = _malloc({{{ C_STRUCTS.pthread.__size__ }}});
      // zero-initialize thread structure.
      for (var i = 0; i < {{{ C_STRUCTS.pthread.__size__ }}} >> 2; ++i) HEAPU32[(threadInfoStruct>>2) + i] = 0;
      {{{ makeSetValue('pthread_ptr', 0, 'threadInfoStruct', 'i32') }}};
  
      // The pthread struct has a field that points to itself - this is used as a
      // magic ID to detect whether the pthread_t structure is 'alive'.
      {{{ makeSetValue('threadInfoStruct', C_STRUCTS.pthread.self, 'threadInfoStruct', 'i32') }}};
  
      // pthread struct robust_list head should point to itself.
      var headPtr = threadInfoStruct + {{{ C_STRUCTS.pthread.robust_list }}};
      {{{ makeSetValue('headPtr', 0, 'headPtr', 'i32') }}};
  
  #if OFFSCREENCANVAS_SUPPORT
      // Register for each of the transferred canvases that the new thread now
      // owns the OffscreenCanvas.
      for (var i in offscreenCanvases) {
        // pthread ptr to the thread that owns this canvas.
        {{{ makeSetValue('offscreenCanvases[i].canvasSharedPtr', 8, 'threadInfoStruct', 'i32') }}};
      }
  #endif
  
      var threadParams = {
        stackBase: stackBase,
        stackSize: stackSize,
        allocatedOwnStack: allocatedOwnStack,
        detached: detached,
        startRoutine: start_routine,
        pthread_ptr: threadInfoStruct,
        arg: arg,
  #if OFFSCREENCANVAS_SUPPORT
        moduleCanvasId: moduleCanvasId,
        offscreenCanvases: offscreenCanvases,
  #endif
        transferList: transferList
      };
  
      if (ENVIRONMENT_IS_PTHREAD) {
        // The prepopulated pool of web workers that can host pthreads is stored
        // in the main JS thread. Therefore if a pthread is attempting to spawn a
        // new thread, the thread creation must be deferred to the main JS thread.
        threadParams.cmd = 'spawnThread';
        postMessage(threadParams, transferList);
      } else {
        // We are the main thread, so we have the pthread warmup pool in this
        // thread and can fire off JS thread creation directly ourselves.
        spawnThread(threadParams);
      }
  
      return 0;
    },
  
    // TODO HACK! Remove this function, it is a JS side copy of the function
    // pthread_testcancel() in library_pthread.c.  Just call pthread_testcancel()
    // everywhere.
    _pthread_testcancel_js: function() {
      if (!ENVIRONMENT_IS_PTHREAD) return;
      var tb = _pthread_self();
      if (!tb) return;
      var cancelDisabled = Atomics.load(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2);
      if (cancelDisabled) return;
      var canceled = Atomics.load(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2);
      if (canceled == 2) throw 'Canceled!';
    },
  
  #if MINIMAL_RUNTIME
    emscripten_check_blocking_allowed__deps: ['$warnOnce'],
  #endif
    emscripten_check_blocking_allowed: function() {
  #if ASSERTIONS || IN_TEST_HARNESS || !MINIMAL_RUNTIME || !ALLOW_BLOCKING_ON_MAIN_THREAD
  #if ENVIRONMENT_MAY_BE_NODE
      if (ENVIRONMENT_IS_NODE) return;
  #endif
  
      if (ENVIRONMENT_IS_WORKER) return; // Blocking in a worker/pthread is fine.
  
      warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
  #if !ALLOW_BLOCKING_ON_MAIN_THREAD
      abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
  #endif
  
  #endif
    },
  
    _emscripten_do_pthread_join__deps: ['$cleanupThread', '_pthread_testcancel_js', 'emscripten_main_thread_process_queued_calls', 'emscripten_futex_wait', 'pthread_self', 'emscripten_main_browser_thread_id',
  #if ASSERTIONS || IN_TEST_HARNESS || !MINIMAL_RUNTIME || !ALLOW_BLOCKING_ON_MAIN_THREAD
    'emscripten_check_blocking_allowed'
  #endif
    ],
    _emscripten_do_pthread_join: function(thread, status, block) {
      if (!thread) {
        err('pthread_join attempted on a null thread pointer!');
        return ERRNO_CODES.ESRCH;
      }
      if (ENVIRONMENT_IS_PTHREAD && _pthread_self() == thread) {
        err('PThread ' + thread + ' is attempting to join to itself!');
        return ERRNO_CODES.EDEADLK;
      }
      else if (!ENVIRONMENT_IS_PTHREAD && _emscripten_main_browser_thread_id() == thread) {
        err('Main thread ' + thread + ' is attempting to join to itself!');
        return ERRNO_CODES.EDEADLK;
      }
      var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
      if (self !== thread) {
        err('pthread_join attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
        return ERRNO_CODES.ESRCH;
      }
  
      var detached = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.detached }}} ) >> 2);
      if (detached) {
        err('Attempted to join thread ' + thread + ', which was already detached!');
        return ERRNO_CODES.EINVAL; // The thread is already detached, can no longer join it!
      }
  
  #if ASSERTIONS || IN_TEST_HARNESS || !MINIMAL_RUNTIME || !ALLOW_BLOCKING_ON_MAIN_THREAD
      if (block) {
        _emscripten_check_blocking_allowed();
      }
  #endif
  
      for (;;) {
        var threadStatus = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2);
        if (threadStatus == 1) { // Exited?
          var threadExitCode = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2);
          if (status) {{{ makeSetValue('status', 0, 'threadExitCode', 'i32') }}};
          Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.detached }}} ) >> 2, 1); // Mark the thread as detached.
  
          if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread);
          else postMessage({ 'cmd': 'cleanupThread', 'thread': thread });
          return 0;
        }
        if (!block) {
          return ERRNO_CODES.EBUSY;
        }
        // TODO HACK! Replace the _js variant with just _pthread_testcancel:
        //_pthread_testcancel();
        __pthread_testcancel_js();
        // In main runtime thread (the thread that initialized the Emscripten C
        // runtime and launched main()), assist pthreads in performing operations
        // that they need to access the Emscripten main runtime for.
        if (!ENVIRONMENT_IS_PTHREAD) _emscripten_main_thread_process_queued_calls();
        _emscripten_futex_wait(thread + {{{ C_STRUCTS.pthread.threadStatus }}}, threadStatus, ENVIRONMENT_IS_PTHREAD ? 100 : 1);
      }
    },
  
    {{{ USE_LSAN ? 'emscripten_builtin_' : '' }}}pthread_join__deps: ['_emscripten_do_pthread_join'],
    {{{ USE_LSAN ? 'emscripten_builtin_' : '' }}}pthread_join: function(thread, status) {
      return __emscripten_do_pthread_join(thread, status, true);
    },
  
    pthread_tryjoin_np__deps: ['_emscripten_do_pthread_join'],
    pthread_tryjoin_np: function(thread, status) {
      return __emscripten_do_pthread_join(thread, status, false);
    },
  
    pthread_kill__deps: ['$killThread', 'emscripten_main_browser_thread_id'],
    pthread_kill: function(thread, signal) {
      if (signal < 0 || signal >= 65/*_NSIG*/) return ERRNO_CODES.EINVAL;
      if (thread === _emscripten_main_browser_thread_id()) {
        if (signal == 0) return 0; // signal == 0 is a no-op.
        err('Main thread (id=' + thread + ') cannot be killed with pthread_kill!');
        return ERRNO_CODES.ESRCH;
      }
      if (!thread) {
        err('pthread_kill attempted on a null thread pointer!');
        return ERRNO_CODES.ESRCH;
      }
      var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
      if (self !== thread) {
        err('pthread_kill attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
        return ERRNO_CODES.ESRCH;
      }
      if (signal != 0) {
        if (!ENVIRONMENT_IS_PTHREAD) killThread(thread);
        else postMessage({ 'cmd': 'killThread', 'thread': thread});
      }
      return 0;
    },
  
    pthread_cancel__deps: ['$cancelThread', 'emscripten_main_browser_thread_id'],
    pthread_cancel: function(thread) {
      if (thread === _emscripten_main_browser_thread_id()) {
        err('Main thread (id=' + thread + ') cannot be canceled!');
        return ERRNO_CODES.ESRCH;
      }
      if (!thread) {
        err('pthread_cancel attempted on a null thread pointer!');
        return ERRNO_CODES.ESRCH;
      }
      var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
      if (self !== thread) {
        err('pthread_cancel attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
        return ERRNO_CODES.ESRCH;
      }
      Atomics.compareExchange(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 0, 2); // Signal the thread that it needs to cancel itself.
      if (!ENVIRONMENT_IS_PTHREAD) cancelThread(thread);
      else postMessage({ 'cmd': 'cancelThread', 'thread': thread});
      return 0;
    },
  
    pthread_detach__sig: 'vi',
    pthread_detach: function(thread) {
      if (!thread) {
        err('pthread_detach attempted on a null thread pointer!');
        return ERRNO_CODES.ESRCH;
      }
      var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
      if (self !== thread) {
        err('pthread_detach attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
        return ERRNO_CODES.ESRCH;
      }
      // Follow musl convention: detached:0 means not detached, 1 means the thread
      // was created as detached, and 2 means that the thread was detached via
      // pthread_detach.
      var wasDetached = Atomics.compareExchange(HEAPU32, (thread + {{{ C_STRUCTS.pthread.detached }}} ) >> 2, 0, 2);
  
      return wasDetached ? ERRNO_CODES.EINVAL : 0;
    },
  
    // C11 threads function.
    // TODO: remove this in favor or compiling musl/src/thread/pthread_detach.c
    thrd_detach: 'pthread_detach',
  
    pthread_exit__deps: ['exit'],
    pthread_exit: function(status) {
      if (!ENVIRONMENT_IS_PTHREAD) _exit(status);
      else PThread.threadExit(status);
      // pthread_exit is marked noReturn, so we must not return from it.
      if (ENVIRONMENT_IS_NODE) {
        // exit the pthread properly on node, as a normal JS exception will halt
        // the entire application.
        process.exit(status);
      }
      throw 'unwind';
    },
  
    pthread_cleanup_push__sig: 'vii',
    pthread_cleanup_push: function(routine, arg) {
      PThread.threadExitHandlers.push(function() { {{{ makeDynCall('vi', 'routine') }}}(arg) });
    },
  
    pthread_cleanup_pop: function(execute) {
      var routine = PThread.threadExitHandlers.pop();
      if (execute) routine();
    },
  
    // Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error.
    emscripten_futex_wait__deps: ['emscripten_main_thread_process_queued_calls'],
    emscripten_futex_wait: function(addr, val, timeout) {
      if (addr <= 0 || addr > HEAP8.length || addr&3 != 0) return -{{{ cDefine('EINVAL') }}};
      // We can do a normal blocking wait anywhere but on the main browser thread.
      if (!ENVIRONMENT_IS_WEB) {
  #if PTHREADS_PROFILING
        PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
  #endif
        var ret = Atomics.wait(HEAP32, addr >> 2, val, timeout);
  #if PTHREADS_PROFILING
        PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}}, {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}});
  #endif
        if (ret === 'timed-out') return -{{{ cDefine('ETIMEDOUT') }}};
        if (ret === 'not-equal') return -{{{ cDefine('EWOULDBLOCK') }}};
        if (ret === 'ok') return 0;
        throw 'Atomics.wait returned an unexpected value ' + ret;
      } else {
        // First, check if the value is correct for us to wait on.
        if (Atomics.load(HEAP32, addr >> 2) != val) {
          return -{{{ cDefine('EWOULDBLOCK') }}};
        }
  
        // Atomics.wait is not available in the main browser thread, so simulate it via busy spinning.
        var tNow = performance.now();
        var tEnd = tNow + timeout;
  
  #if PTHREADS_PROFILING
        PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
  #endif
        // Register globally which address the main thread is simulating to be
        // waiting on. When zero, the main thread is not waiting on anything, and on
        // nonzero, the contents of the address pointed by __emscripten_main_thread_futex
        // tell which address the main thread is simulating its wait on.
        // We need to be careful of recursion here: If we wait on a futex, and
        // then call _emscripten_main_thread_process_queued_calls() below, that
        // will call code that takes the proxying mutex - which can once more
        // reach this code in a nested call. To avoid interference between the
        // two (there is just a single __emscripten_main_thread_futex at a time), unmark
        // ourselves before calling the potentially-recursive call. See below for
        // how we handle the case of our futex being notified during the time in
        // between when we are not set as the value of __emscripten_main_thread_futex.
  #if ASSERTIONS
        assert(__emscripten_main_thread_futex > 0);
  #endif
        var lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, addr);
  #if ASSERTIONS
        // We must not have already been waiting.
        assert(lastAddr == 0);
  #endif
  
        while (1) {
          // Check for a timeout.
          tNow = performance.now();
          if (tNow > tEnd) {
  #if PTHREADS_PROFILING
            PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
  #endif
            // We timed out, so stop marking ourselves as waiting.
            lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, 0);
  #if ASSERTIONS
            // The current value must have been our address which we set, or
            // in a race it was set to 0 which means another thread just allowed
            // us to run, but (tragically) that happened just a bit too late.
            assert(lastAddr == addr || lastAddr == 0);
  #endif
            return -{{{ cDefine('ETIMEDOUT') }}};
          }
          // We are performing a blocking loop here, so we must handle proxied
          // events from pthreads, to avoid deadlocks.
          // Note that we have to do so carefully, as we may take a lock while
          // doing so, which can recurse into this function; stop marking
          // ourselves as waiting while we do so.
          lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, 0);
  #if ASSERTIONS
          assert(lastAddr == addr || lastAddr == 0);
  #endif
          if (lastAddr == 0) {
            // We were told to stop waiting, so stop.
            break;
          }
          _emscripten_main_thread_process_queued_calls();
  
          // Check the value, as if we were starting the futex all over again.
          // This handles the following case:
          //
          //  * wait on futex A
          //  * recurse into emscripten_main_thread_process_queued_calls(),
          //    which waits on futex B. that sets the __emscripten_main_thread_futex address to
          //    futex B, and there is no longer any mention of futex A.
          //  * a worker is done with futex A. it checks __emscripten_main_thread_futex but does
          //    not see A, so it does nothing special for the main thread.
          //  * a worker is done with futex B. it flips mainThreadMutex from B
          //    to 0, ending the wait on futex B.
          //  * we return to the wait on futex A. __emscripten_main_thread_futex is 0, but that
          //    is because of futex B being done - we can't tell from
          //    __emscripten_main_thread_futex whether A is done or not. therefore, check the
          //    memory value of the futex.
          //
          // That case motivates the design here. Given that, checking the memory
          // address is also necessary for other reasons: we unset and re-set our
          // address in __emscripten_main_thread_futex around calls to
          // emscripten_main_thread_process_queued_calls(), and a worker could
          // attempt to wake us up right before/after such times.
          //
          // Note that checking the memory value of the futex is valid to do: we
          // could easily have been delayed (relative to the worker holding on
          // to futex A), which means we could be starting all of our work at the
          // later time when there is no need to block. The only "odd" thing is
          // that we may have caused side effects in that "delay" time. But the
          // only side effects we can have are to call
          // emscripten_main_thread_process_queued_calls(). That is always ok to
          // do on the main thread (it's why it is ok for us to call it in the
          // middle of this function, and elsewhere). So if we check the value
          // here and return, it's the same is if what happened on the main thread
          // was the same as calling emscripten_main_thread_process_queued_calls()
          // a few times times before calling emscripten_futex_wait().
          if (Atomics.load(HEAP32, addr >> 2) != val) {
            return -{{{ cDefine('EWOULDBLOCK') }}};
          }
  
          // Mark us as waiting once more, and continue the loop.
          lastAddr = Atomics.exchange(HEAP32, __emscripten_main_thread_futex >> 2, addr);
  #if ASSERTIONS
          assert(lastAddr == 0);
  #endif
        }
  #if PTHREADS_PROFILING
        PThread.setThreadStatusConditional(_pthread_self(), {{{ cDefine('EM_THREAD_STATUS_RUNNING') }}}, {{{ cDefine('EM_THREAD_STATUS_WAITFUTEX') }}});
  #endif
        return 0;
      }
    },
  
    // Returns the number of threads (>= 0) woken up, or the value -EINVAL on error.
    // Pass count == INT_MAX to wake up all threads.
    emscripten_futex_wake: function(addr, count) {
      if (addr <= 0 || addr > HEAP8.length || addr&3 != 0 || count < 0) return -{{{ cDefine('EINVAL') }}};
      if (count == 0) return 0;
      // Waking (at least) INT_MAX waiters is defined to mean wake all callers.
      // For Atomics.notify() API Infinity is to be passed in that case.
      if (count >= {{{ cDefine('INT_MAX') }}}) count = Infinity;
  
      // See if main thread is waiting on this address? If so, wake it up by resetting its wake location to zero.
      // Note that this is not a fair procedure, since we always wake main thread first before any workers, so
      // this scheme does not adhere to real queue-based waiting.
  #if ASSERTIONS
      assert(__emscripten_main_thread_futex > 0);
  #endif
      var mainThreadWaitAddress = Atomics.load(HEAP32, __emscripten_main_thread_futex >> 2);
      var mainThreadWoken = 0;
      if (mainThreadWaitAddress == addr) {
  #if ASSERTIONS
        // We only use __emscripten_main_thread_futex on the main browser thread, where we
        // cannot block while we wait. Therefore we should only see it set from
        // other threads, and not on the main thread itself. In other words, the
        // main thread must never try to wake itself up!
        assert(!ENVIRONMENT_IS_WEB);
  #endif
        var loadedAddr = Atomics.compareExchange(HEAP32, __emscripten_main_thread_futex >> 2, mainThreadWaitAddress, 0);
        if (loadedAddr == mainThreadWaitAddress) {
          --count;
          mainThreadWoken = 1;
          if (count <= 0) return 1;
        }
      }
  
      // Wake any workers waiting on this address.
      var ret = Atomics.notify(HEAP32, addr >> 2, count);
      if (ret >= 0) return ret + mainThreadWoken;
      throw 'Atomics.notify returned an unexpected value ' + ret;
    },
  
    __atomic_is_lock_free: function(size, ptr) {
      return size <= 4 && (size & (size-1)) == 0 && (ptr&(size-1)) == 0;
    },
  
    __call_main: function(argc, argv) {
      var returnCode = {{{ exportedAsmFunc('_main') }}}(argc, argv);
  #if EXIT_RUNTIME
      if (!noExitRuntime) {
        // exitRuntime enabled, proxied main() finished in a pthread, shut down the process.
  #if ASSERTIONS
        out('Proxied main thread 0x' + _pthread_self().toString(16) + ' finished with return code ' + returnCode + '. EXIT_RUNTIME=1 set, quitting process.');
  #endif
        postMessage({ 'cmd': 'exitProcess', 'returnCode': returnCode });
        return returnCode;
      }
  #else
      // EXIT_RUNTIME==0 set on command line, keeping main thread alive.
  #if ASSERTIONS
      out('Proxied main thread 0x' + _pthread_self().toString(16) + ' finished with return code ' + returnCode + '. EXIT_RUNTIME=0 set, so keeping main thread alive for asynchronous event operations.');
  #endif
  #endif
    },
  
    emscripten_conditional_set_current_thread_status_js: function(expectedStatus, newStatus) {
  #if PTHREADS_PROFILING
      PThread.setThreadStatusConditional(_pthread_self(), expectedStatus, newStatus);
  #endif
    },
  
    emscripten_set_current_thread_status_js: function(newStatus) {
  #if PTHREADS_PROFILING
      PThread.setThreadStatus(_pthread_self(), newStatus);
  #endif
    },
  
    // The profiler setters are defined twice, here in asm.js so that they can be #if'ed out
    // without having to pay the impact of a FFI transition for a no-op in non-profiling builds.
    emscripten_conditional_set_current_thread_status__asm: true,
    emscripten_conditional_set_current_thread_status__sig: 'vii',
    emscripten_conditional_set_current_thread_status__deps: ['emscripten_conditional_set_current_thread_status_js'],
    emscripten_conditional_set_current_thread_status: function(expectedStatus, newStatus) {
  #if PTHREADS_PROFILING
      expectedStatus = expectedStatus|0;
      newStatus = newStatus|0;
      _emscripten_conditional_set_current_thread_status_js(expectedStatus|0, newStatus|0);
  #endif
    },
  
    emscripten_set_current_thread_status__asm: true,
    emscripten_set_current_thread_status__sig: 'vi',
    emscripten_set_current_thread_status__deps: ['emscripten_set_current_thread_status_js'],
    emscripten_set_current_thread_status: function(newStatus) {
  #if PTHREADS_PROFILING
      newStatus = newStatus|0;
      _emscripten_set_current_thread_status_js(newStatus|0);
  #endif
    },
  
    emscripten_set_thread_name__asm: true,
    emscripten_set_thread_name__sig: 'vii',
    emscripten_set_thread_name: function(threadId, name) {
  #if PTHREADS_PROFILING
      threadId = threadId|0;
      name = name|0;
      PThread.setThreadName(threadId, UTF8ToString(name));
  #endif
    },
  
    emscripten_proxy_to_main_thread_js__deps: ['emscripten_run_in_main_runtime_thread_js'],
    emscripten_proxy_to_main_thread_js__docs: '/** @type{function(number, (number|boolean), ...(number|boolean))} */',
    emscripten_proxy_to_main_thread_js: function(index, sync) {
      // Additional arguments are passed after those two, which are the actual
      // function arguments.
      // The serialization buffer contains the number of call params, and then
      // all the args here.
      // We also pass 'sync' to C separately, since C needs to look at it.
      var numCallArgs = arguments.length - 2;
  #if ASSERTIONS
      if (numCallArgs > {{{ cDefine('EM_QUEUED_JS_CALL_MAX_ARGS') }}}-1) throw 'emscripten_proxy_to_main_thread_js: Too many arguments ' + numCallArgs + ' to proxied function idx=' + index + ', maximum supported is ' + ({{{ cDefine('EM_QUEUED_JS_CALL_MAX_ARGS') }}}-1) + '!';
  #endif
      // Allocate a buffer, which will be copied by the C code.
      var stack = stackSave();
      // First passed parameter specifies the number of arguments to the function.
      // When BigInt support is enabled, we must handle types in a more complex
      // way, detecting at runtime if a value is a BigInt or not (as we have no
      // type info here). To do that, add a "prefix" before each value that
      // indicates if it is a BigInt, which effectively doubles the number of
      // values we serialize for proxying. TODO: pack this?
      var serializedNumCallArgs = numCallArgs {{{ WASM_BIGINT ? "* 2" : "" }}};
      var args = stackAlloc(serializedNumCallArgs * 8);
      var b = args >> 3;
      for (var i = 0; i < numCallArgs; i++) {
        var arg = arguments[2 + i];
  #if WASM_BIGINT
        if (typeof arg === 'bigint') {
          // The prefix is non-zero to indicate a bigint.
          HEAP64[b + 2*i] = BigInt(1);
          HEAP64[b + 2*i + 1] = arg;
        } else {
          // The prefix is zero to indicate a JS Number.
          HEAP64[b + 2*i] = BigInt(0);
          HEAPF64[b + 2*i + 1] = arg;
        }
  #else
        HEAPF64[b + i] = arg;
  #endif
      }
      var ret = _emscripten_run_in_main_runtime_thread_js(index, serializedNumCallArgs, args, sync);
      stackRestore(stack);
      return ret;
    },
  
    emscripten_receive_on_main_thread_js_callArgs: '=[]',
  
    emscripten_receive_on_main_thread_js__deps: [
      'emscripten_proxy_to_main_thread_js',
      'emscripten_receive_on_main_thread_js_callArgs',
      '$readAsmConstArgs'],
    emscripten_receive_on_main_thread_js: function(index, numCallArgs, args) {
  #if WASM_BIGINT
      numCallArgs /= 2;
  #endif
      _emscripten_receive_on_main_thread_js_callArgs.length = numCallArgs;
      var b = args >> 3;
      for (var i = 0; i < numCallArgs; i++) {
  #if WASM_BIGINT
        if (HEAP64[b + 2*i]) {
          // It's a BigInt.
          _emscripten_receive_on_main_thread_js_callArgs[i] = HEAP64[b + 2*i + 1];
        } else {
          // It's a Number.
          _emscripten_receive_on_main_thread_js_callArgs[i] = HEAPF64[b + 2*i + 1];
        }
  #else
        _emscripten_receive_on_main_thread_js_callArgs[i] = HEAPF64[b + i];
  #endif
      }
      // Proxied JS library funcs are encoded as positive values, and
      // EM_ASMs as negative values (see include_asm_consts)
      var isEmAsmConst = index < 0;
      var func = !isEmAsmConst ? proxiedFunctionTable[index] : ASM_CONSTS[-index - 1];
  #if ASSERTIONS
      assert(func.length == numCallArgs, 'Call args mismatch in emscripten_receive_on_main_thread_js');
  #endif
      return func.apply(null, _emscripten_receive_on_main_thread_js_callArgs);
    },
  
    $establishStackSpace: function(stackTop, stackMax) {
      _emscripten_stack_set_limits(stackTop, stackMax);
  #if STACK_OVERFLOW_CHECK >= 2
      ___set_stack_limits(_emscripten_stack_get_base(), _emscripten_stack_get_end());
  #endif
  
      // Call inside wasm module to set up the stack frame for this pthread in wasm module scope
      stackRestore(stackTop);
  
  #if STACK_OVERFLOW_CHECK
      // Write the stack cookie last, after we have set up the proper bounds and
      // current position of the stack.
      writeStackCookie();
  #endif
    },
  
    // allow pthreads to check if noExitRuntime from worker.js
    $getNoExitRuntime: function() {
      return noExitRuntime;
    },
  
    $invokeEntryPoint: function(ptr, arg) {
      return {{{ makeDynCall('ii', 'ptr') }}}(arg);
    },
  
    // This function is called internally to notify target thread ID that it has messages it needs to
    // process in its message queue inside the Wasm heap. As a helper, the caller must also pass the
    // ID of the main browser thread to this function, to avoid needlessly ping-ponging between JS and
    // Wasm boundaries.
    _emscripten_notify_thread_queue: function(targetThreadId, mainThreadId) {
      if (targetThreadId == mainThreadId) {
        postMessage({'cmd' : 'processQueuedMainThreadWork'});
      } else if (ENVIRONMENT_IS_PTHREAD) {
        postMessage({'targetThread': targetThreadId, 'cmd': 'processThreadQueue'});
      } else {
        var pthread = PThread.pthreads[targetThreadId];
        var worker = pthread && pthread.worker;
        if (!worker) {
  #if ASSERTIONS
          err('Cannot send message to thread with ID ' + targetThreadId + ', unknown thread ID!');
  #endif
          return /*0*/;
        }
        worker.postMessage({'cmd' : 'processThreadQueue'});
      }
      return 1;
    }
  };
  
  autoAddDeps(LibraryPThread, '$PThread');
  mergeInto(LibraryManager.library, LibraryPThread);
  