var LibraryPThread = {
  $PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock();',
  $PThread__deps: ['$PROCINFO'],
  $PThread: {
    MAIN_THREAD_ID: 1, // A special constant that identifies the main JS thread ID.
    mainThreadInfo: {
      schedPolicy: 0/*SCHED_OTHER*/,
      schedPrio: 0
    },
    // Since creating a new Web Worker is so heavy (it must reload the whole compiled script page!), maintain a pool of such
    // workers that have already parsed and loaded the scripts.
    unusedWorkerPool: [],
    // The currently executing pthreads.
    runningWorkers: [],
    // Points to a pthread_t structure in the Emscripten main heap, allocated on demand if/when first needed.
    // mainThreadBlock: undefined,
    initMainThreadBlock: function() {
      if (ENVIRONMENT_IS_PTHREAD) return undefined;
      PThread.mainThreadBlock = allocate({{{ C_STRUCTS.pthread.__size__ }}}, "i32*", ALLOC_STATIC);
      for (var i = 0; i < {{{ C_STRUCTS.pthread.__size__ }}}/4; ++i) HEAPU32[PThread.mainThreadBlock/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('PThread.mainThreadBlock', C_STRUCTS.pthread.self, 'PThread.mainThreadBlock', 'i32') }}};

      // Allocate memory for thread-local storage.
      var tlsMemory = allocate({{{ cDefine('PTHREAD_KEYS_MAX') }}} * 4, "i32*", ALLOC_STATIC);
      for (var i = 0; i < {{{ cDefine('PTHREAD_KEYS_MAX') }}}; ++i) HEAPU32[tlsMemory/4+i] = 0;
      Atomics.store(HEAPU32, (PThread.mainThreadBlock + {{{ C_STRUCTS.pthread.tsd }}} ) >> 2, tlsMemory); // Init thread-local-storage memory array.
      Atomics.store(HEAPU32, (PThread.mainThreadBlock + {{{ C_STRUCTS.pthread.tid }}} ) >> 2, PThread.mainThreadBlock); // Main thread ID.
      Atomics.store(HEAPU32, (PThread.mainThreadBlock + {{{ C_STRUCTS.pthread.pid }}} ) >> 2, PROCINFO.pid); // Process ID.
    },
    // Maps pthread_t to pthread info objects
    pthreads: {},
    pthreadIdCounter: 2, // 0: invalid thread, 1: main JS UI thread, 2+: IDs for pthreads

    exitHandlers: null, // An array of C functions to run when this thread exits.

    runExitHandlers: function() {
      if (PThread.exitHandlers !== null) {
        while (PThread.exitHandlers.length > 0) {
          PThread.exitHandlers.pop()();
        }
        PThread.exitHandlers = null;
      }

      // Call into the musl function that runs destructors of all thread-specific data.
      if (ENVIRONMENT_IS_PTHREAD && threadInfoStruct) ___pthread_tsd_run_dtors();
    },

    // 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?
        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 threadInfoStruct = 0; above to 'release' the object in this worker thread.
        Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1);

        // 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();

        _emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}});
        threadInfoStruct = 0;
        if (ENVIRONMENT_IS_PTHREAD) {
          postMessage({ cmd: 'exit' });
        }
      }
    },

    threadCancel: function() {
      PThread.runExitHandlers();
      Atomics.store(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, -1/*PTHREAD_CANCELED*/);
      Atomics.store(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running.
      _emscripten_futex_wake(threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads
      threadInfoStruct = selfThreadId = 0; // Not hosting a pthread anymore in this worker, reset the info structures to null.
      postMessage({ cmd: 'cancelDone' });
    },

    terminateAllThreads: function() {
      for (var t in PThread.pthreads) {
        var pthread = PThread.pthreads[t];
        if (pthread) {
          PThread.freeThreadData(pthread);
          if (pthread.worker) pthread.worker.terminate();
        }
      }
      PThread.pthreads = {};
      for (var t in PThread.unusedWorkerPool) {
        var pthread = PThread.unusedWorkerPool[t];
        if (pthread) {
          PThread.freeThreadData(pthread);
          if (pthread.worker) pthread.worker.terminate();
        }
      }
      PThread.unusedWorkerPool = [];
      for (var t in PThread.runningWorkers) {
        var pthread = PThread.runningWorkers[t];
        if (pthread) {
          PThread.freeThreadData(pthread);
          if (pthread.worker) 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(pthread.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;
    },

    // Allocates the given amount of new web workers and stores them in the pool of unused workers.
    // onFinishedLoading: A callback function that will be called once all of the workers have been initialized and are
    //                    ready to host pthreads. Optional. This is used to mitigate bug https://bugzilla.mozilla.org/show_bug.cgi?id=1049079
    allocateUnusedWorkers: function(numWorkers, onFinishedLoading) {
      Module['print']('Preallocating ' + numWorkers + ' workers for a pthread spawn pool.');

      var numWorkersLoaded = 0;
      for (var i = 0; i < numWorkers; ++i) {
        var worker = new Worker('pthread-main.js');

        worker.onmessage = function(e) {
          if (e.data.cmd === 'processQueuedMainThreadWork') {
            // TODO: Must post message to main Emscripten thread in PROXY_TO_WORKER mode.
            _emscripten_main_thread_process_queued_calls();
          } else if (e.data.cmd === 'spawnThread') {
            __spawn_thread(e.data);
          } else if (e.data.cmd === 'cleanupThread') {
            __cleanup_thread(e.data.thread);
          } else if (e.data.cmd === 'killThread') {
            __kill_thread(e.data.thread);
          } else if (e.data.cmd === 'cancelThread') {
            __cancel_thread(e.data.thread);
          } else if (e.data.cmd === 'loaded') {
            ++numWorkersLoaded;
            if (numWorkersLoaded === numWorkers && onFinishedLoading) {
              onFinishedLoading();
            }
          } else if (e.data.cmd === 'print') {
            Module['print']('Thread ' + e.data.threadId + ': ' + e.data.text);
          } else if (e.data.cmd === 'printErr') {
            Module['printErr']('Thread ' + e.data.threadId + ': ' + e.data.text);
          } else if (e.data.cmd === 'exit') {
            // todo 
          } else if (e.data.cmd === 'cancelDone') {
              PThread.freeThreadData(worker.pthread);
              worker.pthread = undefined; // Detach the worker from the pthread object, and return it to the worker pool as an unused worker.
              PThread.unusedWorkerPool.push(worker);
              // TODO: Free if detached.
              PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker.pthread), 1); // Not a running Worker anymore.
          } else {
            Module['printErr']("worker sent an unknown command " + e.data.cmd);
          }
        };

        worker.onerror = function(e) {
          Module['printErr']('pthread sent an error! ' + e.message);
        };

        // Allocate tempDoublePtr for the worker. This is done here on the worker's behalf, since allocate()
        // is not thread-safe.
        var tempDoublePtr = Runtime.alignMemory(allocate(12, "i8", ALLOC_NORMAL), 8); // TODO: leaks. Cleanup after worker terminates.

        // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation.
        worker.postMessage({
            cmd: 'load',
            url: currentScriptUrl,
            buffer: HEAPU8.buffer,
            tempDoublePtr: tempDoublePtr,
            PthreadWorkerInit: PthreadWorkerInit
          }, [HEAPU8.buffer]);
        PThread.unusedWorkerPool.push(worker);
      }
    },

    getNewWorker: function() {
      if (PThread.unusedWorkerPool.length == 0) PThread.allocateUnusedWorkers(1);
      if (PThread.unusedWorkerPool.length > 0) return PThread.unusedWorkerPool.pop();
      else return null;
    },

    busySpinWait: function(msecs) {
      var t = performance.now() + msecs;
      while(performance.now() < t) {
        ;
      }
    }
  },

  _kill_thread: function(pthread_ptr) {
    if (ENVIRONMENT_IS_WORKER || ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _kill_thread() can only ever be called from main JS thread!';
    if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in _kill_thread!';
    {{{ 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.pthread), 1); // Not a running Worker anymore.
    pthread.worker.pthread = undefined;
  },

  _cleanup_thread: function(pthread_ptr) {
    if (ENVIRONMENT_IS_WORKER || ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _cleanup_thread() can only ever be called from main JS thread!';
    if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in _cleanup_thread!';
    {{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
    var pthread = PThread.pthreads[pthread_ptr];
    var worker = pthread.worker;
    PThread.freeThreadData(pthread);
    worker.pthread = undefined; // Detach the worker from the pthread object, and return it to the worker pool as an unused worker.
    PThread.unusedWorkerPool.push(worker);
    PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker.pthread), 1); // Not a running Worker anymore.
  },

  _cancel_thread: function(pthread_ptr) {
    if (ENVIRONMENT_IS_WORKER || ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _cancel_thread() can only ever be called from main JS thread!';
    if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in _cancel_thread!';
    var pthread = PThread.pthreads[pthread_ptr];
    pthread.worker.postMessage({ cmd: 'cancel' });
  },

  _spawn_thread: function(threadParams) {
    if (ENVIRONMENT_IS_WORKER || ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! _spawn_thread() can only ever be called from main JS 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 pthread = PThread.pthreads[threadParams.pthread_ptr] = { // Create a pthread info object to represent this thread.
      worker: worker,
      stackBase: threadParams.stackBase,
      stackSize: threadParams.stackSize,
      allocatedOwnStack: threadParams.allocatedOwnStack,
      thread: threadParams.pthread_ptr,
      threadInfoStruct: threadParams.pthread_ptr // Info area for this thread in Emscripten HEAP (shared)
    };
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 0); // threadStatus <- 0, meaning not yet exited.
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, 0); // threadExitCode <- 0.
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.detached }}} ) >> 2, threadParams.detached);
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.tsd }}} ) >> 2, tlsMemory); // Init thread-local-storage memory array.
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.tsd_used }}} ) >> 2, 0); // Mark initial status to unused.
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.tid }}} ) >> 2, pthread.threadInfoStruct); // Main thread ID.
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.pid }}} ) >> 2, PROCINFO.pid); // Process ID.

    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}}) >> 2, threadParams.stackSize);
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.stack_size }}}) >> 2, threadParams.stackSize);
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.stack }}}) >> 2, threadParams.stackBase);
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 8) >> 2, threadParams.stackBase);
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 12) >> 2, threadParams.detached);
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 20) >> 2, threadParams.schedPolicy);
    Atomics.store(HEAPU32, (pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.attr }}} + 24) >> 2, threadParams.schedPrio);

    worker.pthread = pthread;

    // Ask the worker to start executing its pthread entry point function.
    worker.postMessage({
      cmd: 'run',
      start_routine: threadParams.startRoutine,
      arg: threadParams.arg,
      threadInfoStruct: threadParams.pthread_ptr,
      selfThreadId: threadParams.pthread_ptr, // TODO: Remove this since thread ID is now the same as the thread address.
      stackBase: threadParams.stackBase,
      stackSize: threadParams.stackSize
    });
  },

#if USE_PTHREADS
  _num_logical_cores__deps: ['emscripten_force_num_logical_cores'],
  _num_logical_cores: '; if (ENVIRONMENT_IS_PTHREAD) __num_logical_cores = PthreadWorkerInit.__num_logical_cores; else { PthreadWorkerInit.__num_logical_cores = __num_logical_cores = allocate(1, "i32*", ALLOC_STATIC); HEAPU32[__num_logical_cores>>2] = navigator["hardwareConcurrency"] || ' + {{{ PTHREAD_HINT_NUM_CORES }}} + '; }',
#else
  _num_logical_cores: 'allocate(1, "i32*", ALLOC_STATIC)',
#endif

  emscripten_num_logical_cores__deps: ['_num_logical_cores'],
  emscripten_num_logical_cores: function() {
    return {{{ makeGetValue('__num_logical_cores', 0, 'i32') }}};
  },

  emscripten_force_num_logical_cores: function(cores) {
    {{{ makeSetValue('__num_logical_cores', 0, 'cores', 'i32') }}};
  },

  pthread_create__deps: ['_spawn_thread', 'pthread_getschedparam', 'pthread_self'],
  pthread_create: function(pthread_ptr, attr, start_routine, arg) {
    // When running in PROXY_TO_WORKER=1 mode, the pthread creation needs to be forwarded to the main browser thread, and not the main C runtime thread,
    // so the following is valid only when in non-proxy-to-worker mode.
#if !PROXY_TO_WORKER
    if (ENVIRONMENT_IS_PTHREAD) return _emscripten_sync_run_in_main_thread_4({{{ cDefine('EM_PROXIED_PTHREAD_CREATE') }}}, pthread_ptr, attr, start_routine, arg);
#endif

    if (!HEAPU8.buffer instanceof SharedArrayBuffer) {
      Module['printErr']('Current environment does not support SharedArrayBuffer, pthreads are not available!');
      return 1;
    }
    if (!pthread_ptr) {
      Module['printErr']('pthread_create called with a null thread pointer!');
      return 1;
    }
    var stackSize = 0;
    var stackBase = 0;
    var detached = 0; // Default thread attr is PTHREAD_CREATE_JOINABLE, i.e. start as not detached.
    var schedPolicy = 0; /*SCHED_OTHER*/
    var schedPrio = 0;
    if (attr) {
      stackSize = {{{ makeGetValue('attr', 0, 'i32') }}};
      stackBase = {{{ makeGetValue('attr', 8, 'i32') }}};
      detached = {{{ makeGetValue('attr', 12/*_a_detach*/, 'i32') }}} != 0/*PTHREAD_CREATE_JOINABLE*/;
      var inheritSched = {{{ makeGetValue('attr', 16/*_a_sched*/, 'i32') }}} == 0/*PTHREAD_INHERIT_SCHED*/;
      if (inheritSched) {
        var prevSchedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}};
        var prevSchedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}};
        _pthread_getschedparam(_pthread_self(), attr + 20, attr + 24);
        schedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}};
        schedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}};
        {{{ makeSetValue('attr', 20/*_a_policy*/, 'prevSchedPolicy', 'i32') }}};
        {{{ makeSetValue('attr', 24/*_a_prio*/, 'prevSchedPrio', 'i32') }}};
      } else {
        schedPolicy = {{{ makeGetValue('attr', 20/*_a_policy*/, 'i32') }}};
        schedPrio = {{{ makeGetValue('attr', 24/*_a_prio*/, 'i32') }}};
      }
    }
    stackSize += 81920 /*DEFAULT_STACK_SIZE*/;
    var allocatedOwnStack = stackBase == 0; // If allocatedOwnStack == true, then the pthread impl maintains the stack allocation.
    if (allocatedOwnStack) {
      stackBase = _malloc(stackSize); // Allocate a stack if the user doesn't want to place the stack in a custom memory area.
    } 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__ }}});
    for (var i = 0; i < {{{ C_STRUCTS.pthread.__size__ }}} >> 2; ++i) HEAPU32[(threadInfoStruct>>2) + i] = 0; // zero-initialize thread structure.
    {{{ 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') }}};

    var threadParams = {
      stackBase: stackBase,
      stackSize: stackSize,
      allocatedOwnStack: allocatedOwnStack,
      schedPolicy: schedPolicy,
      schedPrio: schedPrio,
      detached: detached,
      startRoutine: start_routine,
      pthread_ptr: threadInfoStruct,
      arg: arg,
    };

    if (ENVIRONMENT_IS_WORKER) {
      // 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);
    } 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.
      __spawn_thread(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;
    if (!threadInfoStruct) return;
    var cancelDisabled = Atomics.load(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2);
    if (cancelDisabled) return;
    var canceled = Atomics.load(HEAPU32, (threadInfoStruct + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2);
    if (canceled == 2) throw 'Canceled!';
  },

  pthread_join__deps: ['_cleanup_thread', '_pthread_testcancel_js'],
  pthread_join: function(thread, status) {
    if (!thread) {
      Module['printErr']('pthread_join attempted on a null thread pointer!');
      return ERRNO_CODES.ESRCH;
    }
    if (ENVIRONMENT_IS_PTHREAD && selfThreadId == thread) {
      Module['printErr']('PThread ' + thread + ' is attempting to join to itself!');
      return ERRNO_CODES.EDEADLK;
    }
    else if (!ENVIRONMENT_IS_PTHREAD && PThread.mainThreadBlock == thread) {
      Module['printErr']('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) {
      Module['printErr']('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) {
      Module['printErr']('Attempted to join thread ' + thread + ', which was already detached!');
      return ERRNO_CODES.EINVAL; // The thread is already detached, can no longer join it!
    }
    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_WORKER) __cleanup_thread(thread);
        else postMessage({ cmd: 'cleanupThread', thread: thread});
        return 0;
      }
      // 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, 100);
    }
  },

  pthread_kill__deps: ['_kill_thread'],
  pthread_kill: function(thread, signal) {
    if (signal < 0 || signal >= 65/*_NSIG*/) return ERRNO_CODES.EINVAL;
    if (thread == PThread.MAIN_THREAD_ID) {
      if (signal == 0) return 0; // signal == 0 is a no-op.
      Module['printErr']('Main thread (id=' + thread + ') cannot be killed with pthread_kill!');
      return ERRNO_CODES.ESRCH;
    }
    if (!thread) {
      Module['printErr']('pthread_kill attempted on a null thread pointer!');
      return ERRNO_CODES.ESRCH;
    }
    var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
    if (self != thread) {
      Module['printErr']('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_WORKER) __kill_thread(thread);
      else postMessage({ cmd: 'killThread', thread: thread});
    }
    return 0;
  },

  pthread_cancel__deps: ['_cancel_thread'],
  pthread_cancel: function(thread) {
    if (thread == PThread.MAIN_THREAD_ID) {
      Module['printErr']('Main thread (id=' + thread + ') cannot be canceled!');
      return ERRNO_CODES.ESRCH;
    }
    if (!thread) {
      Module['printErr']('pthread_cancel attempted on a null thread pointer!');
      return ERRNO_CODES.ESRCH;
    }
    var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
    if (self != thread) {
      Module['printErr']('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_WORKER) __cancel_thread(thread);
    else postMessage({ cmd: 'cancelThread', thread: thread});
    return 0;
  },

  pthread_detach: function(thread) {
    if (!thread) {
      Module['printErr']('pthread_detach attempted on a null thread pointer!');
      return ERRNO_CODES.ESRCH;
    }
    var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
    if (self != thread) {
      Module['printErr']('pthread_detach attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
      return ERRNO_CODES.ESRCH;
    }
    var threadStatus = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2);
    // 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 ? (threadStatus == 0/*running*/ ? ERRNO_CODES.EINVAL : ERRNO_CODES.ESRCH) : 0;
  },

  pthread_exit__deps: ['exit'],
  pthread_exit: function(status) {
    if (!ENVIRONMENT_IS_PTHREAD) _exit(status);
    else PThread.threadExit(status);
  },

  // Public pthread_self() function which returns a unique ID for the thread.
  pthread_self: function() {
    if (ENVIRONMENT_IS_PTHREAD) return threadInfoStruct;
    return PThread.mainThreadBlock; // Main JS thread.
  },

  emscripten_is_main_runtime_thread: function() {
    return !ENVIRONMENT_IS_PTHREAD;
  },

  emscripten_is_main_browser_thread: function() {
    return !ENVIRONMENT_IS_WORKER;
  },

  pthread_getschedparam: function(thread, policy, schedparam) {
    if (!policy && !schedparam) return ERRNO_CODES.EINVAL;

    if (!thread) {
      Module['printErr']('pthread_getschedparam called with a null thread pointer!');
      return ERRNO_CODES.ESRCH;
    }
    var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
    if (self != thread) {
      Module['printErr']('pthread_getschedparam attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
      return ERRNO_CODES.ESRCH;
    }

    var schedPolicy = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 20 ) >> 2);
    var schedPrio = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 24 ) >> 2);

    if (policy) {{{ makeSetValue('policy', 0, 'schedPolicy', 'i32') }}};
    if (schedparam) {{{ makeSetValue('schedparam', 0, 'schedPrio', 'i32') }}};
    return 0;
  },

  pthread_setschedparam: function(thread, policy, schedparam) {
    if (!thread) {
      Module['printErr']('pthread_setschedparam called with a null thread pointer!');
      return ERRNO_CODES.ESRCH;
    }
    var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
    if (self != thread) {
      Module['printErr']('pthread_setschedparam attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
      return ERRNO_CODES.ESRCH;
    }

    if (!schedparam) return ERRNO_CODES.EINVAL;

    var newSchedPrio = {{{ makeGetValue('schedparam', 0, 'i32') }}};
    if (newSchedPrio < 0) return ERRNO_CODES.EINVAL;
    if (policy == 1/*SCHED_FIFO*/ || policy == 2/*SCHED_RR*/) {
      if (newSchedPrio > 99) return ERRNO_CODES.EINVAL;
    } else {
      if (newSchedPrio > 1) return ERRNO_CODES.EINVAL;
    }

    Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 20) >> 2, policy);
    Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 24) >> 2, newSchedPrio);
    return 0;
  },

  // Marked as obsolescent in pthreads specification: http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html
  pthread_getconcurrency: function() {
    return 0;
  },

  // Marked as obsolescent in pthreads specification.
  pthread_setconcurrency: function(new_level) {
    // no-op
    return 0;
  },

  pthread_mutexattr_getprioceiling: function(attr, prioceiling) {
    // Not supported either in Emscripten or musl, return a faked value.
    if (prioceiling) {{{ makeSetValue('prioceiling', 0, 99, 'i32') }}};
    return 0;
  },

  pthread_mutexattr_setprioceiling: function(attr, prioceiling) {
    // Not supported either in Emscripten or musl, return an error.
    return ERRNO_CODES.EPERM;
  },

  pthread_getcpuclockid: function(thread, clock_id) {
    return ERRNO_CODES.ENOENT; // pthread API recommends returning this error when "Per-thread CPU time clocks are not supported by the system."
  },

  pthread_setschedprio: function(thread, prio) {
    if (!thread) {
      Module['printErr']('pthread_setschedprio called with a null thread pointer!');
      return ERRNO_CODES.ESRCH;
    }
    var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
    if (self != thread) {
      Module['printErr']('pthread_setschedprio attempted on thread ' + thread + ', which does not point to a valid thread, or does not exist anymore!');
      return ERRNO_CODES.ESRCH;
    }
    if (prio < 0) return ERRNO_CODES.EINVAL;

    var schedPolicy = Atomics.load(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 20 ) >> 2);
    if (schedPolicy == 1/*SCHED_FIFO*/ || schedPolicy == 2/*SCHED_RR*/) {
      if (prio > 99) return ERRNO_CODES.EINVAL;
    } else {
      if (prio > 1) return ERRNO_CODES.EINVAL;
    }

    Atomics.store(HEAPU32, (thread + {{{ C_STRUCTS.pthread.attr }}} + 24) >> 2, prio);
    return 0;
  },

  pthread_cleanup_push: function(routine, arg) {
    if (PThread.exitHandlers === null) {
      PThread.exitHandlers = [];
      if (!ENVIRONMENT_IS_PTHREAD) {
        __ATEXIT__.push(function() { PThread.runExitHandlers(); });
      }
    }
    PThread.exitHandlers.push(function() { Runtime.dynCall('vi', routine, [arg]) });
  },

  pthread_cleanup_pop: function(execute) {
    var routine = PThread.exitHandlers.pop();
    if (execute) routine();
  },

  // pthread_sigmask - examine and change mask of blocked signals
  pthread_sigmask: function(how, set, oldset) {
    Module['printErr']('pthread_sigmask() is not supported: this is a no-op.');
    return 0;
  },

  pthread_atfork: function(prepare, parent, child) {
    Module['printErr']('fork() is not supported: pthread_atfork is a no-op.');
    return 0;
  },

  // Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error.
  emscripten_futex_wait: function(addr, val, timeout) {
    if (addr <= 0 || addr > HEAP8.length || addr&3 != 0) return -{{{ cDefine('EINVAL') }}};
//    dump('futex_wait addr:' + addr + ' by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n');
    var ret = Atomics.futexWait(HEAP32, addr >> 2, val, timeout);
//    dump('futex_wait done by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n');
    if (ret == Atomics.TIMEDOUT) return -{{{ cDefine('ETIMEDOUT') }}};
    if (ret == Atomics.NOTEQUAL) return -{{{ cDefine('EWOULDBLOCK') }}};
    if (ret == 0) return 0;
    throw 'Atomics.futexWait returned an unexpected value ' + ret;
  },

  // 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') }}};
//    dump('futex_wake addr:' + addr + ' by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n');
    var ret = Atomics.futexWake(HEAP32, addr >> 2, count);
    if (ret >= 0) return ret;
    throw 'Atomics.futexWake returned an unexpected value ' + ret;
  },

  // Returns the number of threads (>= 0) woken up, or one of the values -EINVAL or -EAGAIN on error.
  emscripten_futex_wake_or_requeue: function(addr, count, cmpValue, addr2) {
    if (addr <= 0 || addr2 <= 0 || addr >= HEAP8.length || addr2 >= HEAP8.length || count < 0
      || addr&3 != 0 || addr2&3 != 0) {
      return -{{{ cDefine('EINVAL') }}};
    }
    var ret = Atomics.futexWakeOrRequeue(HEAP32, addr >> 2, count, cmpValue, addr >> 2);
    if (ret == Atomics.NOTEQUAL) return -{{{ cDefine('EAGAIN') }}};
    if (ret >= 0) return ret;
    throw 'Atomics.futexWakeOrRequeue returned an unexpected value ' + ret;
  },
};

autoAddDeps(LibraryPThread, '$PThread');
mergeInto(LibraryManager.library, LibraryPThread);
