@ 261106.14
current
/**
 * @license
 * Copyright 2015 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 *
 * Because only modern JS engines support SAB we can use modern JS language
 * features within this file (ES2020).
 */

#if !PTHREADS
#error "Internal error! PTHREADS should be enabled when including library_pthread.js."
#endif
#if !SHARED_MEMORY
#error "Internal error! SHARED_MEMORY should be enabled when including library_pthread.js."
#endif
#if PTHREADS == 2
#error "PTHREADS=2 is no longer supported"
#endif
#if BUILD_AS_WORKER
#error "pthreads + BUILD_AS_WORKER require separate modes that don't work together, see https://github.com/emscripten-core/emscripten/issues/8854"
#endif
#if EVAL_CTORS
#error "EVAL_CTORS is not compatible with pthreads yet (passive segments)"
#endif

{{{
#if WASM_ESM_INTEGRATION
const pthreadWorkerScript = TARGET_BASENAME + '.pthread.mjs';
#else
const pthreadWorkerScript = TARGET_JS_NAME;
#endif

// Use a macro to avoid duplicating pthread worker options.
// We cannot use a normal JS variable since the vite bundler requires that worker
// options be inline.
// See https://github.com/emscripten-core/emscripten/issues/22394
const pthreadWorkerOptions = `{
#if EXPORT_ES6
        'type': 'module',
#endif
#if ENVIRONMENT_MAY_BE_NODE
        // This is the way that we signal to the node worker that it is hosting
        // a pthread.
        'workerData': 'em-pthread',
#if WASMFS
        // In WasmFS, close() is not proxied to the main thread. Suppress
        // warnings when a thread closes a file descriptor it didn't open.
        // See: https://github.com/emscripten-core/emscripten/issues/24731
        'trackUnmanagedFds': false,
#endif
#endif
#if ENVIRONMENT_MAY_BE_WEB || ENVIRONMENT_MAY_BE_WORKER
        // This is the way that we signal to the Web Worker that it is hosting
        // a pthread.
#if ASSERTIONS
        'name': 'em-pthread-' + PThread.nextWorkerID,
#else
        'name': 'em-pthread',
#endif
#endif
}`;
}}}

var LibraryPThread = {
  // Message IDs used when communicating with workers via postMessage.
  ...defineInternalConsts({
    $CMD_LOAD: 1,
    $CMD_RUN: 2,
    $CMD_LOADED: 3,
    $CMD_CHECK_MAILBOX: 4,
    $CMD_SPAWN_THREAD: 5,
    $CMD_CLEANUP_THREAD: 6,
    $CMD_MARK_AS_FINISHED: 7,
    $CMD_UNCAUGHT_EXN: 8,
    $CMD_CALL_HANDLER: 9,
#if OFFSCREENCANVASES_TO_PTHREAD
    $MAX_PTR: MEMORY64 ? Number((2n ** 64n) - 1n) : (2 ** 32) - 1,
#endif
  }),

  $PThread__postset: 'PThread.init();',
  $PThread__deps: [
    '_emscripten_thread_init',
    '_emscripten_thread_free_data',
    'exit',
    'pthread_self',
    '__set_thread_state',
  ],
  $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: [],
    tlsInitFunctions: [],
#if MAIN_MODULE
    tlsFreeFunctions: [],
#endif
    // Maps pthread_t pointers to the workers on which they are running.  For
    // the reverse mapping, each worker has a `pthread_ptr` when its running a
    // pthread.
    pthreads: {},
#if MAIN_MODULE
    outstandingPromises: {},
    // Finished threads are threads that have finished running but we are not yet
    // joined.
    finishedThreads: new Set(),
#endif
#if ASSERTIONS
    nextWorkerID: 1,
#endif
#if TRUSTED_TYPES
    // Cached Trusted Types policy for pthread Worker creation. Per the
    // Trusted Types spec, createPolicy() with the same name throws on the
    // second call unless CSP uses 'allow-duplicates'.
    trustedWorkerPolicy: null,
#endif
    init() {
      if ({{{ ENVIRONMENT_IS_MAIN_THREAD() }}}) {
        PThread.initMainThread();
      }
    },
    initMainThread() {
#if PTHREAD_POOL_SIZE
      var pthreadPoolSize = {{{ PTHREAD_POOL_SIZE }}};
      // Start loading up the Worker pool, if requested.
      while (pthreadPoolSize--) {
        PThread.allocateUnusedWorker();
      }
#if !MINIMAL_RUNTIME
      // MINIMAL_RUNTIME takes care of calling loadWasmModuleToAllWorkers
      // in postamble_minimal.js
      addOnPreRun(async () => {
        var pthreadPoolReady = PThread.loadWasmModuleToAllWorkers();
#if !PTHREAD_POOL_DELAY_LOAD
        addRunDependency('loading-workers');
        await pthreadPoolReady;
        removeRunDependency('loading-workers');
#endif // PTHREAD_POOL_DELAY_LOAD
      });
#endif // !MINIMAL_RUNTIME
#endif // PTHREAD_POOL_SIZE
    },

#if PTHREADS_PROFILING
    getThreadName(pthreadPtr) {
      var profilerBlock = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, '*') }}};
      if (!profilerBlock) return '';
      return UTF8ToString(profilerBlock + {{{ C_STRUCTS.thread_profiler_block.name }}});
    },

    threadStatusToString(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(pthreadPtr) {
      var profilerBlock = {{{ makeGetValue('pthreadPtr', C_STRUCTS.pthread.profilerBlock, '*') }}};
      var status = (profilerBlock == 0) ? 0 : Atomics.load(HEAPU32, {{{ getHeapOffset('profilerBlock + ' + C_STRUCTS.thread_profiler_block.threadStatus, 'i32') }}});
      return PThread.threadStatusToString(status);
    },
#endif

    terminateAllThreads: () => {
#if ASSERTIONS
      assert(!ENVIRONMENT_IS_PTHREAD, 'terminateAllThreads() should only be called from the main thread');
#endif
#if PTHREADS_DEBUG
      dbg('terminateAllThreads');
#endif
      // 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 is 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 Object.values(PThread.pthreads)) {
        terminateWorker(worker);
      }
      for (var worker of PThread.unusedWorkers) {
        terminateWorker(worker);
      }
      PThread.unusedWorkers = [];
      PThread.pthreads = {};
    },

    clearMailboxAwait: (pthread_ptr) => {
      if (!waitAsyncPolyfilled) {
        Atomics.notify(HEAP32, {{{ getHeapOffset('pthread_ptr', 'i32') }}});
      }
    },

    terminateRuntime: () => {
#if ASSERTIONS
      assert(!ENVIRONMENT_IS_PTHREAD, 'terminateRuntime() should only be called from the main thread');
#endif
      PThread.terminateAllThreads();
      var pthread_ptr = _pthread_self();
      ___set_thread_state(0, 0, 0, 1);
      PThread.clearMailboxAwait(pthread_ptr);
    },

    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() until 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);
      // Not a running Worker anymore
      // Detach the worker from the pthread object, and return it to the
      // worker pool as an unused worker.
      worker.pthread_ptr = 0;

#if ENVIRONMENT_MAY_BE_NODE && PROXY_TO_PTHREAD
      if (ENVIRONMENT_IS_NODE) {
        // Once the proxied main thread has finished, mark it as weakly
        // referenced so that its existence does not prevent Node.js from
        // exiting.  This has no effect if the worker is already weakly
        // referenced.
        worker.unref();
      }
#endif

      // Clear any pending waitAsync waiter armed on this thread's struct
      // BEFORE freeing the memory so that memory recycled by malloc in another
      // thread will not have a window where a stale async waiter is still active.
      PThread.clearMailboxAwait(pthread_ptr);

      // Finally, free the underlying (and now-unused) pthread structure in
      // linear memory.
      __emscripten_thread_free_data(pthread_ptr);
    },
#if OFFSCREENCANVAS_SUPPORT
    receiveOffscreenCanvases(data) {
      if (typeof GL !== 'undefined') {
        Object.assign(GL.offscreenCanvases, data.offscreenCanvases);
        if (!Module['canvas'] && data.moduleCanvasId && GL.offscreenCanvases[data.moduleCanvasId]) {
          Module['canvas'] = GL.offscreenCanvases[data.moduleCanvasId].offscreenCanvas;
          Module['canvas'].id = data.moduleCanvasId;
        }
      }
    },
#endif
    // Called by worker.js each time a thread is started.
    threadInitTLS() {
#if PTHREADS_DEBUG
      dbg('threadInitTLS');
#endif
      // Call thread init functions (these are the _emscripten_tls_init for each
      // module loaded.
      PThread.tlsInitFunctions.forEach((f) => f());
    },
    // Loads the WebAssembly module into the given Worker.
    // onFinishedLoading: A callback function that will be called once all of
    //                    the workers have been initialized and are
    //                    ready to host pthreads.
    loadWasmModuleToWorker: (worker) => new Promise((onFinishedLoading) => {
      worker.onmessage = (e) => {
        var d = e.data;
        var cmd = d.cmd;
#if PTHREADS_DEBUG
        dbg(`main thread: received message '${cmd}' from worker. ${d}`);
#endif

        // If this message is intended to a recipient that is not the main
        // thread, forward it to the target thread. This is currently only
        // used by `CMD_CHECK_MAILBOX`.
        if (d.targetThread && d.targetThread != _pthread_self()) {
          var targetWorker = PThread.pthreads[d.targetThread];
#if ASSERTIONS
          if (!targetWorker) err(`worker sent message (${cmd}) to pthread (${d.targetThread}) that no longer exists`);
#endif
          targetWorker?.postMessage(d);
          return;
        }

        if (d === 'setimmediate' || d === '_si') {
          // Worker wants to postMessage() to itself to implement setImmediate()
          // emulation.
          worker.postMessage(d);
          return;
        }

        switch (cmd) {
          case CMD_CHECK_MAILBOX:
            checkMailbox();
            break;
          case CMD_SPAWN_THREAD:
            spawnThread(d);
            break;
          case CMD_CLEANUP_THREAD:
            // 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));
            break;
#if MAIN_MODULE
          case CMD_MARK_AS_FINISHED:
            markAsFinished(d.thread);
            break;
#endif
          case CMD_LOADED:
#if ENVIRONMENT_MAY_BE_NODE
            if (ENVIRONMENT_IS_NODE && !worker.strongref) {
              // Once worker is loaded & idle, mark it as weakly referenced,
              // so that mere existence of a Worker in the pool does not prevent
              // Node.js from exiting the app.
              worker.unref();
            }
#endif
            onFinishedLoading(worker);
            break;
#if ENVIRONMENT_MAY_BE_NODE
          case CMD_UNCAUGHT_EXN:
            // Message handler for Node.js specific out-of-order behavior:
            // https://github.com/nodejs/node/issues/59617
            // A pthread sent an uncaught exception event. Re-raise it on the main thread.
            worker.onerror(d.error);
            break;
#endif
          case CMD_CALL_HANDLER:
            Module[d.handler](...d.args);
            break;
          default:
            // 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:
            if (cmd) err(`worker sent an unknown command ${cmd}`);
        }
      };

      worker.onerror = (e) => {
        var message = 'worker sent an error!';
#if ASSERTIONS
        if (worker.pthread_ptr) {
          message = `Pthread ${ptrToString(worker.pthread_ptr)} sent an error!`;
        }
#endif
        err(`${message} ${e.filename}:${e.lineno}: ${e.message}`);
        throw e;
      };

#if ENVIRONMENT_MAY_BE_NODE
      if (ENVIRONMENT_IS_NODE) {
        worker.on('message', (data) => worker.onmessage({ data: data }));
        worker.on('error', (e) => worker.onerror(e));

#if PTHREADS_DEBUG
        worker.on('exit', (code) => {
          if (worker.pthread_ptr) dbg(`Worker hosting pthread ${ptrToString(worker.pthread_ptr)} has terminated with code ${code}.`);
          else dbg(`Worker has terminated with code ${code}.`);
        });
#endif
      }
#endif

#if ASSERTIONS
      assert(wasmMemory instanceof WebAssembly.Memory, 'wasmMemory should have been loaded by now');
#if !WASM_ESM_INTEGRATION
      assert(wasmModule instanceof WebAssembly.Module, 'wasmModule should have been loaded by now');
#endif
#endif

      // When running on a pthread, none of the incoming parameters on the module
      // object are present. Proxy known handlers back to the main thread if specified.
      var handlers = [];
      var knownHandlers = [
#if expectToReceiveOnModule('onExit')
        'onExit',
#endif
#if expectToReceiveOnModule('onAbort')
        'onAbort',
#endif
#if expectToReceiveOnModule('print')
        'print',
#endif
#if expectToReceiveOnModule('printErr')
        'printErr',
#endif
#if expectToReceiveOnModule('onMalloc')
        'onMalloc',
#endif
#if expectToReceiveOnModule('onRealloc')
        'onRealloc',
#endif
#if expectToReceiveOnModule('onFree')
        'onFree',
#endif
#if expectToReceiveOnModule('onSbrkGrow')
        'onSbrkGrow',
#endif
      ];
      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: CMD_LOAD,
        handlers: handlers,
#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,
#endif // WASM2JS
#if !WASM_ESM_INTEGRATION
        wasmModule,
#endif
#if LOAD_SOURCE_MAP
        wasmSourceMap,
#endif
#if MAIN_MODULE
        dynamicLibraries,
        // Share all modules that have been loaded so far.  New workers
        // won't start running threads until these are all loaded.
        sharedModules,
#endif
#if ASSERTIONS
        workerID: worker.workerID,
#endif
      });
    }),

#if PTHREAD_POOL_SIZE
    async loadWasmModuleToAllWorkers() {
      // Instantiation is synchronous in pthreads.
      if (
        ENVIRONMENT_IS_PTHREAD
#if WASM_WORKERS
        || ENVIRONMENT_IS_WASM_WORKER
#endif
      ) {
        return;
      }

      let pthreadPoolReady = Promise.all(PThread.unusedWorkers.map(PThread.loadWasmModuleToWorker));
#if PTHREAD_POOL_DELAY_LOAD
      // PTHREAD_POOL_DELAY_LOAD means we want to proceed synchronously without
      // waiting for the pthread pool during the startup phase.
      // If the user wants to wait on it elsewhere, they can do so via the
      // Module['pthreadPoolReady'] promise.
      Module['pthreadPoolReady'] = pthreadPoolReady;
      return;
#else
      return pthreadPoolReady;
#endif
    },
#endif // PTHREAD_POOL_SIZE

    // Creates a new web Worker and places it in the unused worker pool to wait for its use.
    allocateUnusedWorker() {
      var worker;
#if EXPORT_ES6
      // If we're using module output, use bundler-friendly pattern.
#if PTHREADS_DEBUG
      dbg(`Allocating a new web worker from ${import.meta.url}`);
#endif
#if TRUSTED_TYPES
      // Use Trusted Types compatible wrappers.
      if (globalThis.trustedTypes?.createPolicy) {
        PThread.trustedWorkerPolicy ??= trustedTypes.createPolicy('emscripten#workerPolicy', { createScriptURL: (url) => url });
        worker = new Worker(PThread.trustedWorkerPolicy.createScriptURL(new URL('{{{ pthreadWorkerScript }}}', import.meta.url)), {{{ pthreadWorkerOptions }}});
      } else
#endif
#if expectToReceiveOnModule('mainScriptUrlOrBlob')
        if (Module['mainScriptUrlOrBlob']) {
          var pthreadMainJs = Module['mainScriptUrlOrBlob'];
          if (typeof pthreadMainJs !== 'string') {
            pthreadMainJs = URL.createObjectURL(pthreadMainJs);
          }
          worker = new Worker(pthreadMainJs, {{{ pthreadWorkerOptions }}});
        } else
#endif
#if CROSS_ORIGIN && ENVIRONMENT_MAY_BE_WEB
      // Support cross-origin loading by creating a new Blob URL to actually
      // perform the `import`.  Without this the `new Worker` would fail
      // due to CORS restrictions.
      // https://github.com/emscripten-core/emscripten/issues/21937
      if (ENVIRONMENT_IS_WEB) {
        var url = URL.createObjectURL(new Blob([`import '${import.meta.url}'`], { type: 'application/javascript' }));
        worker = new Worker(url, {{{ pthreadWorkerOptions }}});
      } else
#endif
      // 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 bundlers 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('{{{ pthreadWorkerScript }}}', import.meta.url), {{{ pthreadWorkerOptions }}});
#else // EXPORT_ES6
      var pthreadMainJs = _scriptName;
#if CROSS_ORIGIN && ENVIRONMENT_MAY_BE_WEB
      // In order to support cross origin loading of worker threads load the
      // worker via a tiny inline `importScripts` call.   For some reason it's
      // fine to `importScripts` across origins, in cases where new Worker
      // itself does not allow this.
      // https://github.com/emscripten-core/emscripten/issues/21937
      if (ENVIRONMENT_IS_WEB) {
        pthreadMainJs = URL.createObjectURL(new Blob([`importScripts('${_scriptName}')`], { type: 'application/javascript' }));
      }
#endif
#if expectToReceiveOnModule('mainScriptUrlOrBlob')
      // We can't use makeModuleReceiveWithVar here since we want to also
      // call URL.createObjectURL on the mainScriptUrlOrBlob.
      if (Module['mainScriptUrlOrBlob']) {
        pthreadMainJs = Module['mainScriptUrlOrBlob'];
        if (typeof pthreadMainJs !== 'string') {
          pthreadMainJs = URL.createObjectURL(pthreadMainJs);
        }
      }
#endif
#if PTHREADS_DEBUG
      dbg(`Allocating a new web worker from ${pthreadMainJs}`);
#endif
#if TRUSTED_TYPES
      // Use Trusted Types compatible wrappers.
      if (globalThis.trustedTypes?.createPolicy) {
        PThread.trustedWorkerPolicy ??= trustedTypes.createPolicy('emscripten#workerPolicy', { createScriptURL: (url) => url });
        worker = new Worker(PThread.trustedWorkerPolicy.createScriptURL(pthreadMainJs), {{{ pthreadWorkerOptions }}});
      } else
#endif
      worker = new Worker(pthreadMainJs, {{{ pthreadWorkerOptions }}});
#endif // EXPORT_ES6
#if ASSERTIONS
      worker.workerID = PThread.nextWorkerID++;
#endif
      PThread.unusedWorkers.push(worker);
      return 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.
#if (PTHREAD_POOL_SIZE_STRICT && ASSERTIONS) || PTHREAD_POOL_SIZE_STRICT == 2
// However, if we're in Node.js, then we can create new workers on the fly and PTHREAD_POOL_SIZE_STRICT
// should be ignored altogether.
#if ENVIRONMENT_MAY_BE_NODE
        if (!ENVIRONMENT_IS_NODE) {
#endif
#if ASSERTIONS
            err('Tried to spawn a new thread, but the thread pool is exhausted.\n' +
            'This might result in a deadlock unless some threads eventually exit or the code explicitly breaks out to the event loop.\n' +
            'If you want to increase the pool size, use setting `-sPTHREAD_POOL_SIZE=...`.'
#if PTHREAD_POOL_SIZE_STRICT == 1
              + '\nIf you want to throw an explicit error instead of the risk of deadlocking in those cases, use setting `-sPTHREAD_POOL_SIZE_STRICT=2`.'
#endif
            );
#endif // ASSERTIONS
#if PTHREAD_POOL_SIZE_STRICT == 2
            return;
#endif
#if ENVIRONMENT_MAY_BE_NODE
        }
#endif
#endif // (PTHREAD_POOL_SIZE_STRICT && ASSERTIONS) || PTHREAD_POOL_SIZE_STRICT == 2
#if PTHREAD_POOL_SIZE_STRICT < 2 || ENVIRONMENT_MAY_BE_NODE
        var newWorker = PThread.allocateUnusedWorker();
        PThread.loadWasmModuleToWorker(newWorker);
#endif
      }
      return PThread.unusedWorkers.pop();
    }
  },

  $terminateWorker: (worker) => {
#if PTHREADS_DEBUG
    dbg(`terminateWorker: ${worker.workerID}`);
#endif
    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 is 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 a valid worker.
    worker.onmessage = (e) => {
#if ASSERTIONS
      var cmd = e.data.cmd;
      err(`received "${cmd}" command from terminated worker: ${worker.workerID}`);
#endif
    };
  },

  _emscripten_thread_cleanup: (thread) => {
    // Called when a thread needs to be cleaned up so it can be reused.
    // A thread is considered reusable when it either returns from its
    // entry point, calls pthread_exit, or acts upon a cancellation.
    // Detached threads are responsible for calling this themselves,
    // otherwise pthread_join is responsible for calling this.
#if PTHREADS_DEBUG
    dbg(`_emscripten_thread_cleanup: ${ptrToString(thread)}`)
#endif
    if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread);
    else postMessage({ cmd: CMD_CLEANUP_THREAD, thread });
  },

  _emscripten_thread_set_strongref: (thread) => {
    // Called when a thread needs to be strongly referenced.
    // Currently only used for:
    // - keeping the "main" thread alive in PROXY_TO_PTHREAD mode;
    // - crashed threads that need to propagate the uncaught exception
    //   back to the main thread.
#if ENVIRONMENT_MAY_BE_NODE
    if (ENVIRONMENT_IS_NODE) {
      var worker = PThread.pthreads[thread];
      worker.ref();
      // Also, record that we called strongref, in case this function is called
      // bafore the 'loaded' callback from the thread (where we would normally
      // `unref` it.
      worker.strongref = 1;
    }
#endif
  },

  $cleanupThread: (pthread_ptr) => {
#if PTHREADS_DEBUG
    dbg(`cleanupThread: ${ptrToString(pthread_ptr)}`)
#endif
#if ASSERTIONS
    assert(!ENVIRONMENT_IS_PTHREAD, 'cleanupThread() should only be called from the main thread');
    assert(pthread_ptr, 'null pthread_ptr passed to cleanupThread');
#endif
    var worker = PThread.pthreads[pthread_ptr];
#if MAIN_MODULE
    PThread.finishedThreads.delete(pthread_ptr);
    if (pthread_ptr in PThread.outstandingPromises) {
      PThread.outstandingPromises[pthread_ptr].resolve();
    }
#endif
#if ASSERTIONS
    assert(worker);
#endif
    PThread.returnWorkerToPool(worker);
  },

#if MAIN_MODULE
  $registerTLSInit: (tlsInitFunc, moduleExports, metadata) => {
#if DYLINK_DEBUG
    dbg('registerTLSInit:', tlsInitFunc, metadata?.tlsExports);
#endif
    // In relocatable builds, we use the result of calling tlsInitFunc
    // (`_emscripten_tls_init`) to relocate the TLS exports of the module
    // according to this new __tls_base.
    function tlsInitWrapper() {
      var __tls_base = tlsInitFunc();
#if DYLINK_DEBUG
      dbg(`tlsInit -> ${__tls_base}`);
#endif
      if (!__tls_base) {
#if ASSERTIONS
        // __tls_base should never be zero if there are tls exports
        assert(__tls_base || metadata.tlsExports.size == 0);
#endif
        return;
      }
      var tlsExports = {};
      metadata.tlsExports.forEach((s) => tlsExports[s] = moduleExports[s]);
      updateGOT(relocateExports(tlsExports, __tls_base), /*replace=*/true);
    }

    // Register this function so that its gets called for each thread on
    // startup.
    PThread.tlsInitFunctions.push(tlsInitWrapper);
    var tlsFreeFunc = moduleExports?.['_emscripten_tls_free'];
    if (tlsFreeFunc) {
      PThread.tlsFreeFunctions.push(tlsFreeFunc);
    }

    // If the main thread is already running we also need to call this function
    // now.  If the main thread is not yet running this will happen when it
    // is initialized and processes `PThread.tlsInitFunctions`.
    if (runtimeInitialized) {
      tlsInitWrapper();
    }
  },
#else
  $registerTLSInit: (tlsInitFunc) => PThread.tlsInitFunctions.push(tlsInitFunc),
#endif

  $spawnThread: (threadParams) => {
#if ASSERTIONS
    assert(!ENVIRONMENT_IS_PTHREAD, 'spawnThread() should only be called from the main thread');
    assert(threadParams.pthread_ptr, 'spawnThread called with null pthread ptr');
#endif

    var worker = PThread.getNewWorker();
    if (!worker) {
      // No available workers in the PThread pool.
      return EAGAIN;
    }
#if ASSERTIONS
    assert(!worker.pthread_ptr);
#endif

    // Add to pthreads map
    PThread.pthreads[threadParams.pthread_ptr] = worker;

    worker.pthread_ptr = threadParams.pthread_ptr;
    var msg = {
        cmd: CMD_RUN,
        start_routine: threadParams.startRoutine,
        arg: threadParams.arg,
        pthread_ptr: threadParams.pthread_ptr,
    };
#if OFFSCREENCANVAS_SUPPORT
    // Note that we do not need to quote these names because they are only used
    // in this file, and not from the external worker.js.
    msg.moduleCanvasId = threadParams.moduleCanvasId;
    msg.offscreenCanvases = threadParams.offscreenCanvases;
#endif
    // Ask the worker to start executing its pthread entry point function.
    worker.postMessage(msg, threadParams.transferList);
    return 0;
  },

  _emscripten_init_main_thread_js: (tb) => {
    var can_block = !ENVIRONMENT_IS_WEB;
#if ENVIRONMENT_MAY_BE_WEB
    // Feature detect whether the main thread can block.
    try {
      Atomics.wait(HEAP32, 0, 0, 0)
      can_block = true;
    } catch (e) {}
#endif
    // Pass the thread address to the native code where they are 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,
      /*is_main=*/!ENVIRONMENT_IS_WORKER,
      /*is_runtime=*/1,
      can_block,
      /*default_stacksize=*/{{{ DEFAULT_PTHREAD_STACK_SIZE }}},
#if PTHREADS_PROFILING
      /*start_profiling=*/true,
#else
      /*start_profiling=*/false,
#endif
    );
    PThread.threadInitTLS();
  },

  $pthreadCreateProxied__internal: true,
  $pthreadCreateProxied__proxy: 'sync',
  $pthreadCreateProxied: (pthread_ptr, attr, startRoutine, arg) => ___pthread_create_js(pthread_ptr, attr, startRoutine, arg),

#if OFFSCREENCANVAS_SUPPORT
  // ASan wraps the emscripten_builtin_pthread_create call in
  // __lsan::ScopedInterceptorDisabler.  Unfortunately, that only disables it on
  // the thread that made the call.  __pthread_create_js gets proxied to the
  // main thread, where LSan is not disabled. This makes it necessary for us to
  // disable LSan here (using __noleakcheck), so that it does not detect
  // pthread's internal allocations as leaks.  If/when we remove all the
  // allocations from __pthread_create_js we could also remove this.
  __pthread_create_js__noleakcheck: true,
#endif
  __pthread_create_js__deps: [
#if OFFSCREENCANVAS_SUPPORT
    'malloc',
#endif
  ],
  __pthread_create_js: (pthread_ptr, attr, startRoutine, arg) => {
    if (!_emscripten_has_threading_support()) {
#if ASSERTIONS
      dbg('pthread_create: environment does not support SharedArrayBuffer, pthreads are not available');
#endif
      return EAGAIN;
    }
#if PTHREADS_DEBUG
    dbg('createThread: ' + ptrToString(pthread_ptr));
#endif

    // List of JS objects that will transfer ownership to the Worker hosting the thread
    var transferList = [];
    var error = 0;

#if OFFSCREENCANVAS_SUPPORT
    // Deduce which WebGL canvases (HTMLCanvasElements or OffscreenCanvases) should be passed over to the
    // Worker that hosts the spawned pthread.
    // Comma-delimited list of CSS selectors that must identify canvases by IDs: "#canvas1, #canvas2, ..."
    var transferredCanvasNames = attr ? {{{ makeGetValue('attr', C_STRUCTS.pthread_attr_t._a_transferredcanvases, '*') }}} : 0;
#if OFFSCREENCANVASES_TO_PTHREAD
    // Proxied canvases string pointer -1/MAX_PTR is used as a special token to
    // fetch whatever canvases were passed to build in
    // -sOFFSCREENCANVASES_TO_PTHREAD= command line.
    if (transferredCanvasNames == MAX_PTR) {
      transferredCanvasNames = '{{{ OFFSCREENCANVASES_TO_PTHREAD }}}';
    } else
#endif
    {
      transferredCanvasNames = UTF8ToString(transferredCanvasNames).trim();
    }
    transferredCanvasNames = transferredCanvasNames ? transferredCanvasNames.split(',') : [];
#if GL_DEBUG
    dbg(`pthread_create: transferredCanvasNames="${transferredCanvasNames}"`);
#endif

    var offscreenCanvases = {}; // Dictionary of OffscreenCanvas objects we'll transfer to the created thread to own
    var moduleCanvasId = Module['canvas']?.id ?? '';
    // Note that transferredCanvasNames might be null (so we cannot do a for-of loop).
    for (var name of transferredCanvasNames) {
      name = name.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 = EINVAL;
            break;
          }
          name = Module['canvas'].id;
        }
#if ASSERTIONS
        assert(typeof GL === 'object', 'OFFSCREENCANVAS_SUPPORT assumes GL is in use (you can force-include it with \'-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$GL\')');
#endif
        if (GL.offscreenCanvases[name]) {
          offscreenCanvasInfo = GL.offscreenCanvases[name];
          GL.offscreenCanvases[name] = null; // This thread no longer owns this canvas.
          if (Module['canvas'] instanceof OffscreenCanvas && name === Module['canvas'].id) Module['canvas'] = null;
        } else if (!ENVIRONMENT_IS_PTHREAD) {
          var canvas = (Module['canvas'] && Module['canvas'].id === name) ? Module['canvas'] : document.querySelector(name);
          if (!canvas) {
            err(`pthread_create: could not find canvas with ID "${name}" to transfer to thread!`);
            error = 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 = EPERM; // Operation not permitted, some other thread is accessing the canvas.
            break;
          }
          if (canvas.transferControlToOffscreen) {
#if GL_DEBUG
            dbg(`pthread_create: canvas.transferControlToOffscreen(), transferring canvas by name "${name}" (DOM id="${canvas.id}") from main thread to pthread`);
#endif
            // Create a shared information block in heap so that we can control
            // the canvas size from any thread.
            if (!canvas.canvasSharedPtr) {
              canvas.canvasSharedPtr = _malloc({{{ 8 + POINTER_SIZE }}});
              {{{ makeSetValue('canvas.canvasSharedPtr', 0, 'canvas.width', 'i32') }}};
              {{{ makeSetValue('canvas.canvasSharedPtr', 4, 'canvas.height', 'i32') }}};
              {{{ makeSetValue('canvas.canvasSharedPtr', 8, 0, '*') }}}; // pthread ptr to the thread that owns this canvas, filled in below.
            }
            offscreenCanvasInfo = {
              offscreenCanvas: canvas.transferControlToOffscreen(),
              canvasSharedPtr: canvas.canvasSharedPtr,
              id: canvas.id
            }
            // After calling canvas.transferControlToOffscreen(), it is no
            // longer possible to access certain operations on the canvas, such
            // as resizing it or obtaining GL contexts via it.
            // Use this field to remember that we have permanently converted
            // this Canvas to be controlled via an OffscreenCanvas (there is no
            // way to undo this in the spec)
            canvas.controlTransferredOffscreen = true;
          } else {
            err(`pthread_create: cannot transfer control of canvas "${name}" to pthread, because current browser does not support OffscreenCanvas!`);
            // If building with OFFSCREEN_FRAMEBUFFER=1 mode, we don't need to
            // be able to transfer control to offscreen, but WebGL can be
            // proxied from worker to main thread.
#if !OFFSCREEN_FRAMEBUFFER
            err('pthread_create: Build with -sOFFSCREEN_FRAMEBUFFER to enable fallback proxying of GL commands from pthread to main thread.');
            return 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 EINVAL; // Hitting this might indicate an implementation bug or some other internal error
      }
    }
#endif // OFFSCREENCANVAS_SUPPORT

    // Synchronously proxy the thread creation to main thread if possible. If we
    // need to transfer ownership of objects, then proxy asynchronously via
    // postMessage.
    if (ENVIRONMENT_IS_PTHREAD && (!transferList.length || error)) {
      return pthreadCreateProxied(pthread_ptr, attr, startRoutine, arg);
    }

    // If on the main thread, and accessing Canvas/OffscreenCanvas failed, abort
    // with the detected error.
    if (error) return error;

#if OFFSCREENCANVAS_SUPPORT
    // Register for each of the transferred canvases that the new thread now
    // owns the OffscreenCanvas.
    for (var canvas of Object.values(offscreenCanvases)) {
      // pthread ptr to the thread that owns this canvas.
      {{{ makeSetValue('canvas.canvasSharedPtr', 8, 'pthread_ptr', '*') }}};
    }
#endif

    var threadParams = {
      startRoutine,
      pthread_ptr,
      arg,
#if OFFSCREENCANVAS_SUPPORT
      moduleCanvasId,
      offscreenCanvases,
#endif
      transferList,
    };

    if (ENVIRONMENT_IS_PTHREAD) {
      // The prepopulated pool of web workers that can host pthreads is stored
      // in the main JS thread. Therefore if a pthread is attempting to spawn a
      // new thread, the thread creation must be deferred to the main JS thread.
      threadParams.cmd = CMD_SPAWN_THREAD;
      postMessage(threadParams, transferList);
      // When we defer thread creation this way, we have no way to detect thread
      // creation synchronously today, so we have to assume success and return 0.
      return 0;
    }

    // We are the main thread, so we have the pthread warmup pool in this
    // thread and can fire off JS thread creation directly ourselves.
    return spawnThread(threadParams);
  },

  emscripten_check_blocking_allowed: () => {
#if (ASSERTIONS || !ALLOW_BLOCKING_ON_MAIN_THREAD) && !MINIMAL_RUNTIME
#if ENVIRONMENT_MAY_BE_NODE
    if (ENVIRONMENT_IS_NODE) return;
#endif

    if (ENVIRONMENT_IS_WORKER) return; // Blocking in a worker/pthread is fine.

    warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
#if !ALLOW_BLOCKING_ON_MAIN_THREAD
    abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
#endif

#endif
  },

  // This function is called by a pthread to signal that exit() was called and
  // that the entire process should exit.
  // This function is always called from a pthread, but is executed on the
  // main thread due to the __proxy attribute.
  $exitOnMainThread__proxy: 'async',
  $exitOnMainThread: (returnCode) => {
#if PTHREADS_DEBUG
    dbg('exitOnMainThread');
#endif
#if PROXY_TO_PTHREAD
    {{{ runtimeKeepalivePop() }}};
#endif
    _exit(returnCode);
  },

#if MEMORY64
  // Calls proxyToMainThread but returns a bigint rather than a number
  $proxyToMainThreadPtr: (...args) => BigInt(proxyToMainThread(...args)),
#endif

  $proxyToMainThread__deps: ['_emscripten_run_js_on_main_thread'],
  $proxyToMainThread__docs: '/** @type{function(number, (number|boolean), ...number)} */',
  $proxyToMainThread: (funcIndex, emAsmAddr, proxyMode, ...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 'proxyMode' to C separately, since C needs to look at it.
    //
    // Allocate a buffer (on the stack), which will be copied if necessary 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 bufSize = 8 * callArgs.length {{{ WASM_BIGINT ? "* 2" : "" }}};
    var sp = stackSave();
    var args = stackAlloc(bufSize);
    var b = {{{ getHeapOffset('args', 'i64') }}};
    for (var arg of callArgs) {
#if WASM_BIGINT
      if (typeof arg === 'bigint') {
        // The prefix is non-zero to indicate a bigint.
        HEAP64[b++] = 1n;
        HEAP64[b++] = arg;
      } else {
        // The prefix is zero to indicate a JS Number.
        HEAP64[b++] = 0n;
        HEAPF64[b++] = arg;
      }
#else
      HEAPF64[b++] = arg;
#endif
    }
    var rtn = __emscripten_run_js_on_main_thread(funcIndex, emAsmAddr, bufSize, args, proxyMode);
    stackRestore(sp);
    return rtn;
  },

  // Reuse global JS array to avoid creating JS garbage for each proxied call
  $proxiedJSCallArgs: [],

  _emscripten_receive_on_main_thread_js__deps: [
    '$proxyToMainThread',
    '_emscripten_run_js_on_main_thread_done',
  ],
  _emscripten_receive_on_main_thread_js: (funcIndex, emAsmAddr, callingThread, bufSize, args, ctx, ctxArgs) => {
    // 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.
    proxiedJSCallArgs.length = 0;
    var b = {{{ getHeapOffset('args', 'i64') }}};
    var end = {{{ getHeapOffset('args + bufSize', 'i64') }}};
    while (b < end) {
#if WASM_BIGINT
      var arg;
      if (HEAP64[b++]) {
        // It's a BigInt.
        arg = HEAP64[b++];
      } else {
        // It's a Number.
        arg = HEAPF64[b++];
      }
#else
      var arg = HEAPF64[b++];
#endif
      proxiedJSCallArgs.push(arg);
    }
    // Proxied JS library funcs use funcIndex and EM_ASM functions use emAsmAddr
#if HAVE_EM_ASM
    var func = emAsmAddr ? ASM_CONSTS[emAsmAddr] : proxiedFunctionTable[funcIndex];
#else
#if ASSERTIONS
    assert(!emAsmAddr);
#endif
    var func = proxiedFunctionTable[funcIndex];
#endif
#if ASSERTIONS
    assert(!(funcIndex && emAsmAddr));
    assert(func.length == proxiedJSCallArgs.length, 'Call args mismatch in _emscripten_receive_on_main_thread_js');
#endif
    PThread.currentProxiedOperationCallerThread = callingThread;
    var rtn = func(...proxiedJSCallArgs);
    PThread.currentProxiedOperationCallerThread = 0;
#if MEMORY64
    // In memory64 mode some proxied functions return bigint/pointer but
    // our return type is i53/double.
    var coerceRtn = (rtn) => typeof rtn === 'bigint' ? bigintToI53Checked(rtn) : rtn;
#else
    var coerceRtn = (rtn) => rtn;
#endif
    if (ctx) {
      // A PROXY_SYNC_ASYNC function may complete synchronously with a plain value.
      Promise.resolve(rtn).then((rtn) => __emscripten_run_js_on_main_thread_done(ctx, ctxArgs, coerceRtn(rtn)));
      return;
    }
    rtn = coerceRtn(rtn);
#if ASSERTIONS
    // Proxied functions can return any type except bigint.  All other types
    // coerce to f64/double (the return type of this function in C) but not
    // bigint.
    assert(typeof rtn !== 'bigint');
#endif
    return rtn;
  },

  $establishStackSpace__internal: true,
  $establishStackSpace__deps: ['emscripten_stack_set_limits'],
  $establishStackSpace: function (pthread_ptr) {
    var stackHigh = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack, '*') }}};
    var stackSize = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack_size, '*') }}};
    var stackLow = stackHigh - stackSize;
#if PTHREADS_DEBUG
    dbg(`establishStackSpace: ${ptrToString(stackHigh)} -> ${ptrToString(stackLow)}`);
#endif
#if ASSERTIONS
    assert(stackHigh != 0);
    assert(stackLow != 0);
    assert(stackHigh > stackLow, 'stackHigh must be higher then stackLow');
#endif
    // Set stack limits used by `emscripten/stack.h` function.  These limits are
    // cached in wasm-side globals to make checks as fast as possible.
    _emscripten_stack_set_limits(stackHigh, stackLow);

#if STACK_OVERFLOW_CHECK >= 2
    setStackLimits();
#endif STACK_OVERFLOW_CHECK

    // Call inside wasm module to set up the stack frame for this pthread in wasm module scope
    stackRestore(stackHigh);

#if STACK_OVERFLOW_CHECK
    // Write the stack cookie last, after we have set up the proper bounds and
    // current position of the stack.
    writeStackCookie();
#endif
  },

  $invokeEntryPoint__deps: [
    '_emscripten_thread_exit',
  ],
  $invokeEntryPoint: {{{ asyncIf(ASYNCIFY == 2) }}}(ptr, arg) => {
#if PTHREADS_DEBUG
    dbg(`invokeEntryPoint: ${ptrToString(ptr)}`);
#endif
#if !MINIMAL_RUNTIME
    // An old thread on this worker may have been canceled without returning the
    // `runtimeKeepaliveCounter` to zero. Reset it now so the new thread won't
    // be affected.
    runtimeKeepaliveCounter = 0;

#if isSymbolNeeded('$noExitRuntime')
    // 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;
#endif
#endif

#if MAIN_MODULE
    // Before we call the thread entry point, make sure any shared libraries
    // have been loaded on this thread.  Otherwise our table might be not be
    // in sync and might not contain the function pointer `ptr` at all.
    __emscripten_dlsync_self();
#endif
    // pthread entry points are always of signature 'void *ThreadMain(void *arg)'
    // Native codebases sometimes spawn threads with other thread entry point
    // signatures, such as void ThreadMain(void *arg), void *ThreadMain(), or
    // void ThreadMain().  That is not acceptable per C/C++ specification, but
    // x86 compiler ABI extensions enable that to work. If you find the
    // following line to crash, either change the signature to "proper" void
    // *ThreadMain(void *arg) form, or try linking with the Emscripten linker
    // flag -sEMULATE_FUNCTION_POINTER_CASTS to add in emulation for this x86
    // ABI extension.

    var result = {{{ makeDynCall('pp', 'ptr', ASYNCIFY == 2) }}}(arg);

#if STACK_OVERFLOW_CHECK
    checkStackCookie();
#endif
    function finish(result) {
#if !MINIMAL_RUNTIME
      // In MINIMAL_RUNTIME the noExitRuntime concept does not apply to
      // pthreads. To exit a pthread with live runtime, use the function
      // emscripten_unwind_to_js_event_loop() in the pthread body.
      if (keepRuntimeAlive()) {
        EXITSTATUS = result;
        return;
      }
#endif
      __emscripten_thread_exit(result);
    }
#if ASYNCIFY == 2
    result = await result;
#endif
    finish(result);
  },

#if MAIN_MODULE
  _emscripten_thread_exit_joinable: (thread) => {
    // Called when a thread exits and is joinable.  We mark these threads
    // as finished, which means they are in state where are no longer actually
    // running, but remain around waiting to be joined.  In this state they
    // cannot run any more proxied work.
    if (!ENVIRONMENT_IS_PTHREAD) markAsFinished(thread);
    else postMessage({ cmd: CMD_MARK_AS_FINISHED, thread });
  },

  _emscripten_thread_free_tls: () => PThread.tlsFreeFunctions.forEach((f) => f()),

  $markAsFinished: (pthread_ptr) => {
#if PTHREADS_DEBUG
    dbg(`markAsFinished: ${ptrToString(pthread_ptr)}`);
#endif
    PThread.finishedThreads.add(pthread_ptr);
    if (pthread_ptr in PThread.outstandingPromises) {
      PThread.outstandingPromises[pthread_ptr].resolve();
    }
  },

  // Asynchronous version _emscripten_dlsync_threads.
  // This is always called on the main thread. This work happens asynchronously.
  $dlsyncThreadsAsync__deps: ['_emscripten_proxy_dlsync_async'],
  $dlsyncThreadsAsync: async () => {
    const caller = PThread.currentProxiedOperationCallerThread;
#if PTHREADS_DEBUG
    dbg('dlsyncThreadsAsync caller=' + ptrToString(caller));
#endif
#if ASSERTIONS
    assert(!ENVIRONMENT_IS_PTHREAD, 'dlsyncThreadsAsync() should only be called from the main thread');
    assert(!Object.keys(PThread.outstandingPromises).length);
#endif

    const promises = [];

    // This first promise resolves once the main thread has loaded all modules.
    var info = makePromise();
    promises.push(info.promise);
    __emscripten_dlsync_self_async(info.id);


    // We then create a sequence of promises, one per thread, that resolve once
    // each thread has performed its sync using _emscripten_proxy_dlsync.
    // Any new threads that are created after this call will automatically be
    // in sync because we call `__emscripten_dlsync_self` in
    // invokeEntryPoint before the threads entry point is called.
    for (const ptr of Object.keys(PThread.pthreads)) {
      const pthread_ptr = Number(ptr);
      if (pthread_ptr !== caller && !PThread.finishedThreads.has(pthread_ptr)) {
        info = makePromise();
        __emscripten_proxy_dlsync_async(pthread_ptr, info.id);
        PThread.outstandingPromises[pthread_ptr] = info;
        promises.push(info.promise);
      }
    }

#if PTHREADS_DEBUG
    dbg(`dlsyncThreadsAsync: waiting on ${promises.length} promises`);
#endif
    await Promise.all(promises);

    PThread.outstandingPromises = {};
#if PTHREADS_DEBUG
    dbg('dlsyncThreadsAsync done');
#endif
  },

  // Synchronous version of dlsync_threads. This is only needed for the case when
  // the main thread call dlopen and in that case we have no choice but to
  // synchronously block the main thread until all other threads are in sync.
  // When `dlopen` is called from a worker, the worker itself is blocked but
  // the operation its waiting on (on the main thread) can be async.
  _emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync'],
  _emscripten_dlsync_threads__async: 'auto',
  _emscripten_dlsync_threads__proxy: 'sync',
  _emscripten_dlsync_threads: () => {
    const callingThread = PThread.currentProxiedOperationCallerThread;
    if (callingThread) {
      return dlsyncThreadsAsync();
    }
    for (const ptr of Object.keys(PThread.pthreads)) {
      const pthread_ptr = Number(ptr);
      if (!PThread.finishedThreads.has(pthread_ptr)) {
        __emscripten_proxy_dlsync(pthread_ptr);
      }
    }
  },
#endif // MAIN_MODULE

  $checkMailbox__deps: [
                        'pthread_self',
                        '_emscripten_check_mailbox',
  ],
  $checkMailbox: () => {
    // checkMailbox can be called after the pthread has shut down. See
    // Pthread.terminateRuntime().
    // In this case we return silently without re-registering using waitAsync.
    // Perhaps there is a more universal way we can detect runtime has exited.
    // TODO(https://github.com/emscripten-core/emscripten/issues/25076)
#if ABORT_ON_WASM_EXCEPTIONS
    if (ABORT) return;
#endif
    var pthread_ptr = _pthread_self();
    if (!pthread_ptr) return;
    callUserCallback(() => {
      // If we are using Atomics.waitAsync as our notification mechanism, wait
      // for a notification before processing the mailbox to avoid missing any
      // work that could otherwise arrive after we've finished processing the
      // mailbox and before we're ready for the next notification.
      __emscripten_thread_mailbox_await(pthread_ptr);
      __emscripten_check_mailbox();
    });
  },

  _emscripten_thread_mailbox_await: (pthread_ptr) => {
    if (!waitAsyncPolyfilled) {
      // Wait on the pthread's initial self-pointer field because it is easy and
      // safe to access from sending threads that need to notify the waiting
      // thread.
      // Note: Under wasm64 only the low 32-bit of the pthread_ptr are
      // read/compared here, but we don't actually care about the exact values
      // here as long as they match.
      var wait = Atomics.waitAsync(HEAP32, {{{ getHeapOffset('pthread_ptr', 'i32') }}}, pthread_ptr);
#if ASSERTIONS
      assert(wait.async);
#endif
      wait.value.then(checkMailbox);
      var waitingAsync = pthread_ptr + {{{ C_STRUCTS.pthread.waiting_async }}};
      Atomics.store(HEAP32, {{{ getHeapOffset('waitingAsync', 'i32') }}}, 1);
    }
    // If `Atomics.waitAsync` is not implemented, then we will always fall back
    // to postMessage and there is no need to do anything here.
  },

  // PostMessage is used to notify threads instead of Atomics.notify whenever
  // the environment does not implement Atomics.waitAsync or when messaging a
  // new thread that has not had a chance to initialize itself and execute
  // Atomics.waitAsync to prepare for the notification.
  _emscripten_notify_mailbox_postmessage: (targetThread, currThreadId) => {
    if (targetThread == currThreadId) {
      setTimeout(checkMailbox);
    } else if (ENVIRONMENT_IS_PTHREAD) {
      postMessage({targetThread, cmd: CMD_CHECK_MAILBOX});
    } else {
      var worker = PThread.pthreads[targetThread];
      if (!worker) {
#if ASSERTIONS
        err(`Cannot send message to thread with ID ${targetThread}, unknown thread ID!`);
#endif
        return;
      }
      worker.postMessage({cmd: CMD_CHECK_MAILBOX});
    }
  }
};

addToLibrary(LibraryPThread);