@ 261106.14
current
/**
 * @license
 * Copyright 2010 The Emscripten Authors
 * SPDX-License-Identifier: MIT
 */

//"use strict";

// An implementation of basic necessary libraries for the web. This integrates
// with a compiled libc and with the rest of the JS runtime.
//
// We search the Library object when there is an external function. If the
// entry in the Library is a function, we insert it. If it is a string, we
// do another lookup in the library (a simple way to write a function once,
// if it can be called by different names). We also allow dependencies,
// using __deps. Initialization code to be run after allocating all
// global constants can be defined by __postset.
//
// Note that the full function name will be '_' + the name in the Library
// object. For convenience, the short name appears here. Note that if you add a
// new function with an '_', it will not be found.

addToLibrary({
  // HEAP definitions are here to help with TypeScript type generation.
  $HEAP8__docs: '/** @type {!Int8Array} */',
  $HEAP8: undefined,
  $HEAPU8__docs: '/** @type {!Uint8Array} */',
  $HEAPU8: undefined,
  $HEAP16__docs: '/** @type {!Int16Array} */',
  $HEAP16: undefined,
  $HEAPU16__docs: '/** @type {!Uint16Array} */',
  $HEAPU16: undefined,
  $HEAP32__docs: '/** @type {!Int32Array} */',
  $HEAP32: undefined,
  $HEAPU32__docs: '/** @type {!Uint32Array} */',
  $HEAPU32: undefined,
  $HEAPF32__docs: '/** @type {!Float32Array} */',
  $HEAPF32: undefined,
  $HEAPF64__docs: '/** @type {!Float64Array} */',
  $HEAPF64: undefined,
#if WASM_BIGINT
  // BigInt64Array type is not correctly defined in closure
  $HEAP64__docs: '/** not-@type {!BigInt64Array} */',
  $HEAP64: undefined,
  $HEAPU64__docs: '/** not-@type {!BigUint64Array} */',
  $HEAPU64: undefined,
#endif

  // JS aliases for native stack manipulation functions and tempret handling
  $stackSave__deps: ['emscripten_stack_get_current'],
  $stackSave: () => _emscripten_stack_get_current(),
  $stackRestore__deps: ['_emscripten_stack_restore'],
  $stackRestore: (val) => __emscripten_stack_restore(val),
  $stackAlloc__deps: ['_emscripten_stack_alloc'],
  $stackAlloc: (sz) => __emscripten_stack_alloc(sz),
  $getTempRet0__deps: ['_emscripten_tempret_get'],
  $getTempRet0: (val) => __emscripten_tempret_get(),
  $setTempRet0__deps: ['_emscripten_tempret_set'],
  $setTempRet0: (val) => __emscripten_tempret_set(val),

  // Aliases that allow legacy names (without leading $) for the
  // functions to continue to work in `__deps` entries.
  stackAlloc: '$stackAlloc',
  stackSave: '$stackSave',
  stackRestore: '$stackRestore',
  setTempRet0: '$setTempRet0',
  getTempRet0: '$getTempRet0',


  // Assign a name to a given function. This is mostly useful for debugging
  // purposes in cases where new functions are created at runtime.
  $createNamedFunction: (name, func) => Object.defineProperty(func, 'name', { value: name }),

  // This function is referenced *very* early on in some configurations
  // (e.g WASM_WORKERS + RUNTIME_DEBUG) so we explictly use a function here
  // rather than an arrow function so that it gets hoisted to the top of the
  // scope.
  $ptrToString: function(ptr) {
#if ASSERTIONS
    assert(typeof ptr === 'number', `ptrToString expects a number, got ${typeof ptr}`);
#endif
#if MEMORY64
    // Convert to 64-bit unsigned value.  We need to use BigInt here since
    // Number cannot represent the full 64-bit range.
    if (ptr < 0) ptr = 2n**64n + BigInt(ptr);
#else
    // Convert to 32-bit unsigned value
    ptr >>>= 0;
#endif
    return '0x' + ptr.toString(16).padStart({{{ POINTER_SIZE * 2 }}}, '0');
  },

  $zeroMemory: (ptr, size) => HEAPU8.fill(0, ptr, ptr + size),

#if SAFE_HEAP
  // Trivial wrappers around runtime functions that make these symbols available
  // to native code.
  segfault: '=segfault',
  alignfault: '=alignfault',
#endif

  // ==========================================================================
  // JavaScript <-> C string interop
  // ==========================================================================

#if !MINIMAL_RUNTIME
  $exitJS__docs: '/** @param {boolean|number=} implicit */',
  $exitJS: (status, implicit) => {
    EXITSTATUS = status;

#if ASSERTIONS && !EXIT_RUNTIME
    checkUnflushedContent();
#endif // ASSERTIONS && !EXIT_RUNTIME

#if PTHREADS
    if (ENVIRONMENT_IS_PTHREAD) {
      // implicit exit can never happen on a pthread
#if ASSERTIONS
      assert(!implicit);
#endif
#if PTHREADS_DEBUG
      dbg(`Pthread ${ptrToString(_pthread_self())} called exit(${status}), posting exitOnMainThread.`);
#endif
      // When running in a pthread we propagate the exit back to the main thread
      // where it can decide if the whole process should be shut down or not.
      // The pthread may have decided not to exit its own runtime, for example
      // because it runs a main loop, but that doesn't affect the main thread.
      exitOnMainThread(status);
      throw 'unwind';
    }
#if PTHREADS_DEBUG
    err(`main thread called exit(${status}): keepRuntimeAlive=${keepRuntimeAlive()} (counter=${runtimeKeepaliveCounter})`);
#endif // PTHREADS_DEBUG
#endif // PTHREADS

#if EXIT_RUNTIME
    if (!keepRuntimeAlive()) {
      exitRuntime();
    }
#endif

#if ASSERTIONS
    // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down
    if (keepRuntimeAlive() && !implicit) {
      var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`;
      err(msg);
    }
#endif // ASSERTIONS

    _proc_exit(status);
  },
#endif

#if MINIMAL_RUNTIME
  // minimal runtime doesn't do any exit cleanup handling so just
  // map exit directly to the lower-level proc_exit syscall.
  exit: 'proc_exit',
#else
  exit: '$exitJS',
#endif

  // Returns a pointer ('p'), which means an i32 on wasm32 and an i64 wasm64
  // We have a separate JS version `getHeapMax()` which can be called directly
  // avoiding any wrapper added for wasm64.
  emscripten_get_heap_max: () => getHeapMax(),

  $getHeapMax: () =>
#if ALLOW_MEMORY_GROWTH
#if MEMORY64 == 1
    {{{ MAXIMUM_MEMORY }}},
#else
    // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate
    // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side
    // for any code that deals with heap sizes, which would require special
    // casing all heap size related code to treat 0 specially.
    {{{ Math.min(MAXIMUM_MEMORY, FOUR_GB - WASM_PAGE_SIZE) }}},
#endif
#else // no growth
    HEAPU8.length,
#endif

#if ABORTING_MALLOC
  $abortOnCannotGrowMemory: (requestedSize) => {
#if ASSERTIONS
#if ALLOW_MEMORY_GROWTH
    abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). If you want malloc to return NULL (0) instead of this abort, do not link with -sABORTING_MALLOC (that is, the default when growth is enabled is to not abort, but you have overridden that)`);
#else // ALLOW_MEMORY_GROWTH
    abort(`Cannot enlarge memory arrays to size ${requestedSize} bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ${HEAP8.length}, (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0`);
#endif // ALLOW_MEMORY_GROWTH
#else // ASSERTIONS
    abort('OOM');
#endif // ASSERTIONS
  },
#endif // ABORTING_MALLOC

  // Grows the wasm memory to the given byte size, and updates the JS views to
  // it. Returns 1 on success, 0 on error.
  $growMemory: (size) => {
    var oldHeapSize = wasmMemory.buffer.byteLength;
    var pages = ((size - oldHeapSize + {{{ WASM_PAGE_SIZE - 1 }}}) / {{{ WASM_PAGE_SIZE }}}) | 0;
#if RUNTIME_DEBUG
    dbg(`growMemory: ${size} (+${size - oldHeapSize} bytes / ${pages} pages)`);
#endif
    try {
      // round size grow request up to wasm page size (fixed 64KB per spec)
      wasmMemory.grow({{{ toIndexType('pages') }}}); // .grow() takes a delta compared to the previous size
#if GROWABLE_ARRAYBUFFERS != 2
      updateMemoryViews();
#endif
#if MEMORYPROFILER
      if (typeof emscriptenMemoryProfiler !== 'undefined') {
        emscriptenMemoryProfiler.onMemoryResize(oldHeapSize, wasmMemory.buffer.byteLength);
      }
#endif
      return 1 /*success*/;
    } catch(e) {
#if ASSERTIONS
      err(`growMemory: Attempted to grow heap from ${oldHeapSize} bytes to ${size} bytes, but got error: ${e}`);
#endif
    }
    // implicit 0 return to save code size (caller will cast 'undefined' into 0
    // anyhow)
  },

  emscripten_resize_heap: 'ip',
  emscripten_resize_heap: (requestedSize) => {
    var oldSize = HEAPU8.length;
#if !MEMORY64 && !CAN_ADDRESS_2GB
    // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned.
    requestedSize >>>= 0;
#endif
#if ALLOW_MEMORY_GROWTH == 0
#if ABORTING_MALLOC
    abortOnCannotGrowMemory(requestedSize);
#else
    return false; // malloc will report failure
#endif // ABORTING_MALLOC
#else // ALLOW_MEMORY_GROWTH == 0
    // With multithreaded builds, races can happen (another thread might increase the size
    // in between), so return a failure, and let the caller retry.
#if SHARED_MEMORY
    if (requestedSize <= oldSize) {
      return false;
    }
#elif ASSERTIONS
    assert(requestedSize > oldSize);
#endif

#if EMSCRIPTEN_TRACING
    // Report old layout one last time
    _emscripten_trace_report_memory_layout();
#endif

    // Memory resize rules:
    // 1.  Always increase heap size to at least the requested size, rounded up
    //     to next page multiple.
    // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap
    //     geometrically: increase the heap size according to
    //     MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most
    //     overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB).
    // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap
    //     linearly: increase the heap size by at least
    //     MEMORY_GROWTH_LINEAR_STEP bytes.
    // 3.  Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by
    //     MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest
    // 4.  If we were unable to allocate as much memory, it may be due to
    //     over-eager decision to excessively reserve due to (3) above.
    //     Hence if an allocation fails, cut down on the amount of excess
    //     growth, in an attempt to succeed to perform a smaller allocation.

    // A limit is set for how much we can grow. We should not exceed that
    // (the wasm binary specifies it, so if we tried, we'd fail anyhow).
    var maxHeapSize = getHeapMax();
    if (requestedSize > maxHeapSize) {
#if ASSERTIONS
      err(`Cannot enlarge memory, requested ${requestedSize} bytes, but the limit is ${maxHeapSize} bytes!`);
#endif
#if ABORTING_MALLOC
      abortOnCannotGrowMemory(requestedSize);
#else
      return false;
#endif
    }

    // Loop through potential heap size increases. If we attempt a too eager
    // reservation that fails, cut down on the attempted size and reserve a
    // smaller bump instead. (max 3 times, chosen somewhat arbitrarily)
    for (var cutDown = 1; cutDown <= 4; cutDown *= 2) {
#if MEMORY_GROWTH_LINEAR_STEP == -1
      var overGrownHeapSize = oldSize * (1 + {{{ MEMORY_GROWTH_GEOMETRIC_STEP }}} / cutDown); // ensure geometric growth
#if MEMORY_GROWTH_GEOMETRIC_CAP
      // but limit overreserving (default to capping at +96MB overgrowth at most)
      overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + {{{ MEMORY_GROWTH_GEOMETRIC_CAP }}} );
#endif

#else
      var overGrownHeapSize = oldSize + {{{ MEMORY_GROWTH_LINEAR_STEP }}} / cutDown; // ensure linear growth
#endif

      var newSize = Math.min(maxHeapSize, alignMemory(Math.max(requestedSize, overGrownHeapSize), {{{ WASM_PAGE_SIZE }}}));

#if ASSERTIONS == 2
      var t0 = _emscripten_get_now();
#endif
      var replacement = growMemory(newSize);
#if ASSERTIONS == 2
      var t1 = _emscripten_get_now();
      dbg(`Heap resize call from ${oldSize} to ${newSize} took ${(t1 - t0)} msecs. Success: ${!!replacement}`);
#endif
      if (replacement) {
#if ASSERTIONS && WASM2JS
        err('Warning: Enlarging memory arrays, this is not fast! ' + [oldSize, newSize]);
#endif

#if EMSCRIPTEN_TRACING
        traceLogMessage('Emscripten', `Enlarging memory arrays from ${oldSize} to ${newSize}`);
        // And now report the new layout
        _emscripten_trace_report_memory_layout();
#endif
        return true;
      }
    }
#if ASSERTIONS
    err(`Failed to grow the heap from ${oldSize} bytes to ${newSize} bytes, not enough memory!`);
#endif
#if ABORTING_MALLOC
    abortOnCannotGrowMemory(requestedSize);
#else
    return false;
#endif
#endif // ALLOW_MEMORY_GROWTH
  },

#if GROWABLE_ARRAYBUFFERS != 2
  // Called after wasm grows memory. At that time we need to update the views.
  // Without this notification, we'd need to check the buffer in JS every time
  // we return from any wasm, which adds overhead. See
  // https://github.com/WebAssembly/WASI/issues/82
  emscripten_notify_memory_growth: (memoryIndex) => {
#if ASSERTIONS
    assert(memoryIndex == 0);
#endif
    updateMemoryViews();
  },
#endif

  _emscripten_system: (command) => {
#if ENVIRONMENT_MAY_BE_NODE
    if (ENVIRONMENT_IS_NODE) {
      if (!command) return 1; // shell is available

      var cmdstr = UTF8ToString(command);
      if (!cmdstr.length) return 0; // this is what glibc seems to do (shell works test?)

      var cp = require('node:child_process');
      var ret = cp.spawnSync(cmdstr, [], {shell:true, stdio:'inherit'});

      var _W_EXITCODE = (ret, sig) => ((ret) << 8 | (sig));

      // this really only can happen if process is killed by signal
      if (ret.status === null) {
        // sadly node doesn't expose such function
        var signalToNumber = (sig) => {
          // implement only the most common ones, and fallback to SIGINT
          switch (sig) {
            case 'SIGHUP': return SIGHUP;
            case 'SIGQUIT': return SIGQUIT;
            case 'SIGFPE': return SIGFPE;
            case 'SIGKILL': return SIGKILL;
            case 'SIGALRM': return SIGALRM;
            case 'SIGTERM': return SIGTERM;
            default: return SIGINT;
          }
        }
        return _W_EXITCODE(0, signalToNumber(ret.signal));
      }

      return _W_EXITCODE(ret.status, 0);
    }
#endif // ENVIRONMENT_MAY_BE_NODE
    // int system(const char *command);
    // http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
    // Can't call external programs.
    if (!command) return 0; // no shell available
    return -ENOSYS;
  },

  // ==========================================================================
  // stdlib.h
  // ==========================================================================

#if !STANDALONE_WASM
  // Used to implement the native `abort` symbol.  Note that we use the
  // JavaScript `abort` helper in order to implement this function, but we use a
  // distinct name here to avoid confusing the two.
  _abort_js: () =>
#if ASSERTIONS
    abort('native code called abort()'),
#else
    abort(''),
#endif
#endif

  // This object can be modified by the user during startup, which affects
  // the initial values of the environment accessible by getenv.
  $ENV: {},

#if !STANDALONE_WASM
  // ==========================================================================
  // assert.h
  // ==========================================================================

  __assert_fail: (condition, filename, line, func) =>
    abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']),
#endif

#if STACK_OVERFLOW_CHECK >= 2
  // Set stack limits used by binaryen's `StackCheck` pass.
  $setStackLimits: () => {
    var stackLow = _emscripten_stack_get_base();
    var stackHigh = _emscripten_stack_get_end();
#if RUNTIME_DEBUG
    dbg(`setStackLimits: ${ptrToString(stackLow)}, ${ptrToString(stackHigh)}`);
#endif
#if MAIN_MODULE
    // With dynamic linking we could have any number of pre-loaded libraries
    // that each need to have their stack limits set.
    setDylinkStackLimits(stackLow, stackHigh);
#else
    ___set_stack_limits(stackLow, stackHigh);
#endif
  },
#endif

  $withStackSave: (f) => {
    var stack = stackSave();
    var ret = f();
    stackRestore(stack);
    return ret;
  },

  // ==========================================================================
  // setjmp.h
  // ==========================================================================

#if SUPPORT_LONGJMP == 'emscripten'
  // In WebAssemblyLowerEmscriptenEHSjLj pass in the LLVM backend, function
  // calls that exist in the same function with setjmp are converted to a code
  // sequence that includes invokes, malloc, free, saveSetjmp, and
  // emscripten_longjmp.  setThrew is called from invokes, but we don't have
  // any way to express that dependency so we use emscripten_throw_longjmp as
  // a proxy and declare the dependency here.
  _emscripten_throw_longjmp__deps: ['setThrew'],
  _emscripten_throw_longjmp: () => {
    throw new EmscriptenSjLj;
  },
#elif !SUPPORT_LONGJMP
#if !INCLUDE_FULL_LIBRARY
  // These are in order to print helpful error messages when either longjmp or
  // setjmp is used.
  longjmp__deps: [() => {
    error('longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)');
  }],
  get setjmp__deps() {
    return this.longjmp__deps;
  },
  // This is to print the correct error message when a program is built with
  // SUPPORT_LONGJMP=1 but linked with SUPPORT_LONGJMP=0. When a program is
  // built with SUPPORT_LONGJMP=1, the object file contains references of not
  // longjmp but _emscripten_throw_longjmp, which is called from
  // emscripten_longjmp.
  get _emscripten_throw_longjmp__deps() {
    return this.longjmp__deps;
  },
#endif
  _emscripten_throw_longjmp: () => {
    error('longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)');
  },
  // will never be emitted, as the dep errors at compile time
  longjmp: (env, value) => {
    abort('longjmp not supported (build with -s SUPPORT_LONGJMP)');
  },
  setjmp: (env) => {
    abort('setjmp not supported (build with -s SUPPORT_LONGJMP)');
  },
#endif

  // ==========================================================================
  // errno.h
  // ==========================================================================

  // We use a string literal here to avoid the string quotes on the object
  // keys being removed when processed by jsifier.
  $ERRNO_CODES: `{
    'EPERM': {{{ cDefs.EPERM }}},
    'ENOENT': {{{ cDefs.ENOENT }}},
    'ESRCH': {{{ cDefs.ESRCH }}},
    'EINTR': {{{ cDefs.EINTR }}},
    'EIO': {{{ cDefs.EIO }}},
    'ENXIO': {{{ cDefs.ENXIO }}},
    'E2BIG': {{{ cDefs.E2BIG }}},
    'ENOEXEC': {{{ cDefs.ENOEXEC }}},
    'EBADF': {{{ cDefs.EBADF }}},
    'ECHILD': {{{ cDefs.ECHILD }}},
    'EAGAIN': {{{ cDefs.EAGAIN }}},
    'EWOULDBLOCK': {{{ cDefs.EWOULDBLOCK }}},
    'ENOMEM': {{{ cDefs.ENOMEM }}},
    'EACCES': {{{ cDefs.EACCES }}},
    'EFAULT': {{{ cDefs.EFAULT }}},
    'ENOTBLK': {{{ cDefs.ENOTBLK }}},
    'EBUSY': {{{ cDefs.EBUSY }}},
    'EEXIST': {{{ cDefs.EEXIST }}},
    'EXDEV': {{{ cDefs.EXDEV }}},
    'ENODEV': {{{ cDefs.ENODEV }}},
    'ENOTDIR': {{{ cDefs.ENOTDIR }}},
    'EISDIR': {{{ cDefs.EISDIR }}},
    'EINVAL': {{{ cDefs.EINVAL }}},
    'ENFILE': {{{ cDefs.ENFILE }}},
    'EMFILE': {{{ cDefs.EMFILE }}},
    'ENOTTY': {{{ cDefs.ENOTTY }}},
    'ETXTBSY': {{{ cDefs.ETXTBSY }}},
    'EFBIG': {{{ cDefs.EFBIG }}},
    'ENOSPC': {{{ cDefs.ENOSPC }}},
    'ESPIPE': {{{ cDefs.ESPIPE }}},
    'EROFS': {{{ cDefs.EROFS }}},
    'EMLINK': {{{ cDefs.EMLINK }}},
    'EPIPE': {{{ cDefs.EPIPE }}},
    'EDOM': {{{ cDefs.EDOM }}},
    'ERANGE': {{{ cDefs.ERANGE }}},
    'ENOMSG': {{{ cDefs.ENOMSG }}},
    'EIDRM': {{{ cDefs.EIDRM }}},
    'ECHRNG': {{{ cDefs.ECHRNG }}},
    'EL2NSYNC': {{{ cDefs.EL2NSYNC }}},
    'EL3HLT': {{{ cDefs.EL3HLT }}},
    'EL3RST': {{{ cDefs.EL3RST }}},
    'ELNRNG': {{{ cDefs.ELNRNG }}},
    'EUNATCH': {{{ cDefs.EUNATCH }}},
    'ENOCSI': {{{ cDefs.ENOCSI }}},
    'EL2HLT': {{{ cDefs.EL2HLT }}},
    'EDEADLK': {{{ cDefs.EDEADLK }}},
    'ENOLCK': {{{ cDefs.ENOLCK }}},
    'EBADE': {{{ cDefs.EBADE }}},
    'EBADR': {{{ cDefs.EBADR }}},
    'EXFULL': {{{ cDefs.EXFULL }}},
    'ENOANO': {{{ cDefs.ENOANO }}},
    'EBADRQC': {{{ cDefs.EBADRQC }}},
    'EBADSLT': {{{ cDefs.EBADSLT }}},
    'EDEADLOCK': {{{ cDefs.EDEADLOCK }}},
    'EBFONT': {{{ cDefs.EBFONT }}},
    'ENOSTR': {{{ cDefs.ENOSTR }}},
    'ENODATA': {{{ cDefs.ENODATA }}},
    'ETIME': {{{ cDefs.ETIME }}},
    'ENOSR': {{{ cDefs.ENOSR }}},
    'ENONET': {{{ cDefs.ENONET }}},
    'ENOPKG': {{{ cDefs.ENOPKG }}},
    'EREMOTE': {{{ cDefs.EREMOTE }}},
    'ENOLINK': {{{ cDefs.ENOLINK }}},
    'EADV': {{{ cDefs.EADV }}},
    'ESRMNT': {{{ cDefs.ESRMNT }}},
    'ECOMM': {{{ cDefs.ECOMM }}},
    'EPROTO': {{{ cDefs.EPROTO }}},
    'EMULTIHOP': {{{ cDefs.EMULTIHOP }}},
    'EDOTDOT': {{{ cDefs.EDOTDOT }}},
    'EBADMSG': {{{ cDefs.EBADMSG }}},
    'ENOTUNIQ': {{{ cDefs.ENOTUNIQ }}},
    'EBADFD': {{{ cDefs.EBADFD }}},
    'EREMCHG': {{{ cDefs.EREMCHG }}},
    'ELIBACC': {{{ cDefs.ELIBACC }}},
    'ELIBBAD': {{{ cDefs.ELIBBAD }}},
    'ELIBSCN': {{{ cDefs.ELIBSCN }}},
    'ELIBMAX': {{{ cDefs.ELIBMAX }}},
    'ELIBEXEC': {{{ cDefs.ELIBEXEC }}},
    'ENOSYS': {{{ cDefs.ENOSYS }}},
    'ENOTEMPTY': {{{ cDefs.ENOTEMPTY }}},
    'ENAMETOOLONG': {{{ cDefs.ENAMETOOLONG }}},
    'ELOOP': {{{ cDefs.ELOOP }}},
    'EOPNOTSUPP': {{{ cDefs.EOPNOTSUPP }}},
    'EPFNOSUPPORT': {{{ cDefs.EPFNOSUPPORT }}},
    'ECONNRESET': {{{ cDefs.ECONNRESET }}},
    'ENOBUFS': {{{ cDefs.ENOBUFS }}},
    'EAFNOSUPPORT': {{{ cDefs.EAFNOSUPPORT }}},
    'EPROTOTYPE': {{{ cDefs.EPROTOTYPE }}},
    'ENOTSOCK': {{{ cDefs.ENOTSOCK }}},
    'ENOPROTOOPT': {{{ cDefs.ENOPROTOOPT }}},
    'ESHUTDOWN': {{{ cDefs.ESHUTDOWN }}},
    'ECONNREFUSED': {{{ cDefs.ECONNREFUSED }}},
    'EADDRINUSE': {{{ cDefs.EADDRINUSE }}},
    'ECONNABORTED': {{{ cDefs.ECONNABORTED }}},
    'ENETUNREACH': {{{ cDefs.ENETUNREACH }}},
    'ENETDOWN': {{{ cDefs.ENETDOWN }}},
    'ETIMEDOUT': {{{ cDefs.ETIMEDOUT }}},
    'EHOSTDOWN': {{{ cDefs.EHOSTDOWN }}},
    'EHOSTUNREACH': {{{ cDefs.EHOSTUNREACH }}},
    'EINPROGRESS': {{{ cDefs.EINPROGRESS }}},
    'EALREADY': {{{ cDefs.EALREADY }}},
    'EDESTADDRREQ': {{{ cDefs.EDESTADDRREQ }}},
    'EMSGSIZE': {{{ cDefs.EMSGSIZE }}},
    'EPROTONOSUPPORT': {{{ cDefs.EPROTONOSUPPORT }}},
    'ESOCKTNOSUPPORT': {{{ cDefs.ESOCKTNOSUPPORT }}},
    'EADDRNOTAVAIL': {{{ cDefs.EADDRNOTAVAIL }}},
    'ENETRESET': {{{ cDefs.ENETRESET }}},
    'EISCONN': {{{ cDefs.EISCONN }}},
    'ENOTCONN': {{{ cDefs.ENOTCONN }}},
    'ETOOMANYREFS': {{{ cDefs.ETOOMANYREFS }}},
    'EUSERS': {{{ cDefs.EUSERS }}},
    'EDQUOT': {{{ cDefs.EDQUOT }}},
    'ESTALE': {{{ cDefs.ESTALE }}},
    'ENOTSUP': {{{ cDefs.ENOTSUP }}},
    'ENOMEDIUM': {{{ cDefs.ENOMEDIUM }}},
    'EILSEQ': {{{ cDefs.EILSEQ }}},
    'EOVERFLOW': {{{ cDefs.EOVERFLOW }}},
    'ECANCELED': {{{ cDefs.ECANCELED }}},
    'ENOTRECOVERABLE': {{{ cDefs.ENOTRECOVERABLE }}},
    'EOWNERDEAD': {{{ cDefs.EOWNERDEAD }}},
    'ESTRPIPE': {{{ cDefs.ESTRPIPE }}},
  }`,

#if PURE_WASI
  $strError: (errno) => errno + '',
#else
  $strError__deps: ['strerror'],
  $strError: (errno) => UTF8ToString(_strerror(errno)),
#endif

#if PROXY_POSIX_SOCKETS == 0
  // ==========================================================================
  // netdb.h
  // ==========================================================================

  $inetPton4: (str) => {
    var b = str.split('.');
    for (var i = 0; i < 4; i++) {
      var tmp = Number(b[i]);
      if (isNaN(tmp)) return null;
      b[i] = tmp;
    }
    return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0;
  },
  $inetNtop4: (addr) =>
    (addr & 0xff) + '.' + ((addr >> 8) & 0xff) + '.' + ((addr >> 16) & 0xff) + '.' + ((addr >> 24) & 0xff),
  $inetPton6__deps: ['htons'],
  $inetPton6: (str) => {
    var words;
    var w, offset, z, i;
    /* http://home.deds.nl/~aeron/regex/ */
    var valid6regx = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i
    var parts = [];
    if (!valid6regx.test(str)) {
      return null;
    }
    if (str === '::') {
      return [0, 0, 0, 0, 0, 0, 0, 0];
    }
    // Z placeholder to keep track of zeros when splitting the string on ':'
    if (str.startsWith('::')) {
      str = str.replace('::', 'Z:'); // leading zeros case
    } else {
      str = str.replace('::', ':Z:');
    }

    if (str.indexOf('.') > 0) {
      // parse IPv4 embedded address
      str = str.replace(new RegExp('[.]', 'g'), ':');
      words = str.split(':');
      words[words.length-4] = Number(words[words.length-4]) + Number(words[words.length-3])*256;
      words[words.length-3] = Number(words[words.length-2]) + Number(words[words.length-1])*256;
      words = words.slice(0, words.length-2);
    } else {
      words = str.split(':');
    }

    offset = 0; z = 0;
    for (w=0; w < words.length; w++) {
      if (typeof words[w] === 'string') {
        if (words[w] === 'Z') {
          // compressed zeros - write appropriate number of zero words
          for (z = 0; z < (8 - words.length+1); z++) {
            parts[w+z] = 0;
          }
          offset = z-1;
        } else {
          // parse hex field to 16-bit value and write it in network byte-order
          parts[w+offset] = _htons(parseInt(words[w],16));
        }
      } else {
        // parsed IPv4 words
        parts[w+offset] = words[w];
      }
    }
    return [
      (parts[1] << 16) | parts[0],
      (parts[3] << 16) | parts[2],
      (parts[5] << 16) | parts[4],
      (parts[7] << 16) | parts[6]
    ];
  },
  $inetNtop6__deps: ['ntohs'],
  $inetNtop6: (ints) => {
    //  ref:  http://www.ietf.org/rfc/rfc2373.txt - section 2.5.4
    //  Format for IPv4 compatible and mapped  128-bit IPv6 Addresses
    //  128-bits are split into eight 16-bit words
    //  stored in network byte order (big-endian)
    //  |                80 bits               | 16 |      32 bits        |
    //  +-----------------------------------------------------------------+
    //  |               10 bytes               |  2 |      4 bytes        |
    //  +--------------------------------------+--------------------------+
    //  +               5 words                |  1 |      2 words        |
    //  +--------------------------------------+--------------------------+
    //  |0000..............................0000|0000|    IPv4 ADDRESS     | (compatible)
    //  +--------------------------------------+----+---------------------+
    //  |0000..............................0000|FFFF|    IPv4 ADDRESS     | (mapped)
    //  +--------------------------------------+----+---------------------+
    var str = '';
    var word = 0;
    var longest = 0;
    var lastzero = 0;
    var zstart = 0;
    var len = 0;
    var i = 0;
    var parts = [
      ints[0] & 0xffff,
      (ints[0] >> 16),
      ints[1] & 0xffff,
      (ints[1] >> 16),
      ints[2] & 0xffff,
      (ints[2] >> 16),
      ints[3] & 0xffff,
      (ints[3] >> 16)
    ];

    // Handle IPv4-compatible, IPv4-mapped, loopback and any/unspecified addresses

    var hasipv4 = true;
    var v4part = '';
    // check if the 10 high-order bytes are all zeros (first 5 words)
    for (i = 0; i < 5; i++) {
      if (parts[i]) {
        hasipv4 = false;
        break;
      }
    }

    if (hasipv4) {
      // low-order 32-bits store an IPv4 address (bytes 13 to 16) (last 2 words)
      v4part = inetNtop4(parts[6] | (parts[7] << 16));
      // IPv4-mapped IPv6 address if 16-bit value (bytes 11 and 12) == 0xFFFF (6th word)
      if (parts[5] === -1) {
        str = '::ffff:';
        str += v4part;
        return str;
      }
      // IPv4-compatible IPv6 address if 16-bit value (bytes 11 and 12) == 0x0000 (6th word)
      if (!parts[5]) {
        str = '::';
        // special case IPv6 addresses
        if (v4part === '0.0.0.0') v4part = ''; // any/unspecified address
        if (v4part === '0.0.0.1') v4part = '1';// loopback address
        str += v4part;
        return str;
      }
    }

    // Handle all other IPv6 addresses

    // first run to find the longest contiguous zero words
    for (word = 0; word < 8; word++) {
      if (!parts[word]) {
        if (word - lastzero > 1) {
          len = 0;
        }
        lastzero = word;
        len++;
      }
      if (len > longest) {
        longest = len;
        zstart = word - longest + 1;
      }
    }

    for (word = 0; word < 8; word++) {
      if (longest > 1) {
        // compress contiguous zeros - to produce '::'
        if (!parts[word] && word >= zstart && word < (zstart + longest) ) {
          if (word === zstart) {
            str += ':';
            if (!zstart) str += ':'; //leading zeros case
          }
          continue;
        }
      }
      // converts 16-bit words from big-endian to little-endian before converting to hex string
      str += Number(_ntohs(parts[word] & 0xffff)).toString(16);
      str += word < 7 ? ':' : '';
    }
    return str;
  },

  $readSockaddr__deps: [
    'ntohs'
  ],
  $readSockaddr: (sa, salen) => {
    // family / port offsets are common to both sockaddr_in and sockaddr_in6
    var family = {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in.sin_family, 'i16') }}};
    var port = _ntohs({{{ makeGetValue('sa', C_STRUCTS.sockaddr_in.sin_port, 'u16') }}});
    var addr;

    switch (family) {
#if NODERAWSOCKETS
      case AF_UNIX: {
        // sun_path runs from offsetof(sun_path) to salen. An address of only the
        // family (salen <= offset) is the unnamed/autobind case -> empty path. A
        // leading NUL marks the Linux abstract namespace; keep the NUL so the
        // path round-trips and never collides with a filesystem path.
        var pathStart = sa + {{{ C_STRUCTS.sockaddr_un.sun_path }}};
        var pathLen = salen - {{{ C_STRUCTS.sockaddr_un.sun_path }}};
        var path = '';
        if (pathLen > 0) {
          if (!HEAPU8[pathStart]) {
            path = '\0' + UTF8ToString(pathStart + 1, pathLen - 1, /*ignoreNul=*/true);
          } else {
            // A pathname address is NUL-terminated; stop at the first NUL.
            path = UTF8ToString(pathStart, pathLen);
          }
        }
        return { family, addr: path, port: 0 };
      }
#endif
      case AF_INET:
        if (salen !== {{{ C_STRUCTS.sockaddr_in.__size__ }}}) {
          return { errno: EINVAL };
        }
        addr = {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in.sin_addr.s_addr, 'i32') }}};
        addr = inetNtop4(addr);
        break;
      case AF_INET6:
        if (salen !== {{{ C_STRUCTS.sockaddr_in6.__size__ }}}) {
          return { errno: EINVAL };
        }
        addr = [
          {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+0, 'i32') }}},
          {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+4, 'i32') }}},
          {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+8, 'i32') }}},
          {{{ makeGetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+12, 'i32') }}}
        ];
        addr = inetNtop6(addr);
        break;
      default:
        return { errno: EAFNOSUPPORT };
    }

    return { family: family, addr: addr, port: port };
  },
  $writeSockaddr__docs: '/** @param {number=} addrlen */',
  $writeSockaddr__deps: [
    'htons',
  ],
  $writeSockaddr: (sa, family, addr, port, addrlen) => {
    switch (family) {
#if NODERAWSOCKETS
      case AF_UNIX: {
        // addr is the JS path string produced by readSockaddr: a leading '\0'
        // marks the abstract namespace (its bytes are written verbatim, no NUL
        // terminator), any other path is written NUL-terminated. An empty path
        // is the unnamed address (family only).
        addr ||= '';
        var abstract = !addr.charCodeAt(0);
        // Pathname addresses include the trailing NUL in the reported length;
        // abstract addresses do not; an empty address is family-only (unnamed).
        var bytes = addr ? lengthBytesUTF8(addr) + (abstract ? 0 : 1) : 0;
        var total = {{{ C_STRUCTS.sockaddr_un.sun_path }}} + bytes;
        zeroMemory(sa, total);
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_un.sun_family, 'family', 'i16') }}};
        if (addr) {
          // stringToUTF8 NUL-terminates and needs room for it, so hand it a
          // budget one past the path bytes; the terminating NUL lands on the
          // zeroed byte just past the reported length and is harmless.
          stringToUTF8(addr, sa + {{{ C_STRUCTS.sockaddr_un.sun_path }}}, bytes + 1);
        }
        if (addrlen) {
          {{{ makeSetValue('addrlen', 0, 'total', 'i32') }}};
        }
        break;
      }
#endif
      case AF_INET:
        // The address may still be an unresolved hostname (e.g. a peer name
        // recorded at connect time); map it to its fake IP here so callers can
        // pass names and IPs alike. Real sockets only ever see numeric addresses.
        addr = inetPton4({{{ NODERAWSOCKETS ? 'addr' : 'DNS.lookup_name(addr)' }}});
        zeroMemory(sa, {{{ C_STRUCTS.sockaddr_in.__size__ }}});
        if (addrlen) {
          {{{ makeSetValue('addrlen', 0, C_STRUCTS.sockaddr_in.__size__, 'i32') }}};
        }
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_family, 'family', 'i16') }}};
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_addr.s_addr, 'addr', 'i32') }}};
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in.sin_port, '_htons(port)', 'i16') }}};
        break;
      case AF_INET6:
        addr = inetPton6({{{ NODERAWSOCKETS ? 'addr' : 'DNS.lookup_name(addr)' }}});
        zeroMemory(sa, {{{ C_STRUCTS.sockaddr_in6.__size__ }}});
        if (addrlen) {
          {{{ makeSetValue('addrlen', 0, C_STRUCTS.sockaddr_in6.__size__, 'i32') }}};
        }
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_family, 'family', 'i32') }}};
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+0, 'addr[0]', 'i32') }}};
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+4, 'addr[1]', 'i32') }}};
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+8, 'addr[2]', 'i32') }}};
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_addr.__in6_union.__s6_addr+12, 'addr[3]', 'i32') }}};
        {{{ makeSetValue('sa', C_STRUCTS.sockaddr_in6.sin6_port, '_htons(port)', 'i16') }}};
        break;
      default:
        return EAFNOSUPPORT;
    }
    return 0;
  },

  // We can't actually resolve hostnames in the browser, so instead
  // we're generating fake IP addresses with lookup_name that we can
  // resolve later on with lookup_addr.
  // We do the aliasing in 172.29.*.*, giving us 65536 possibilities.
  $DNS: {
    address_map: {
      id: 1,
      addrs: {},
      names: {}
    },

    lookup_name(name) {
      // If the name is already a valid ipv4 / ipv6 address, don't generate a fake one.
      var res = inetPton4(name);
      if (res !== null) {
        return name;
      }
      res = inetPton6(name);
      if (res !== null) {
        return name;
      }

      // See if this name is already mapped.
      var addr;

      if (DNS.address_map.addrs[name]) {
        addr = DNS.address_map.addrs[name];
      } else {
        var id = DNS.address_map.id++;
#if ASSERTIONS
        assert(id < 65535, 'exceeded max address mappings of 65535');
#endif

        addr = '172.29.' + (id & 0xff) + '.' + (id & 0xff00);

        DNS.address_map.names[addr] = name;
        DNS.address_map.addrs[name] = addr;
      }

      return addr;
    },

    lookup_addr(addr) {
      if (DNS.address_map.names[addr]) {
        return DNS.address_map.names[addr];
      }

      return null;
    }
  },

  getaddrinfo__deps: ['malloc', 'htonl'],
  getaddrinfo__proxy: 'sync',
#if NODERAWSOCKETS && (PTHREADS || ASYNCIFY)
  // Returns an EAI_* code synchronously, or - for a hostname needing a real
  // DNS lookup - a Promise of one, which a sync-proxied pthread awaits and
  // ASYNCIFY/JSPI suspend on.
  getaddrinfo__async: true,
#endif
  getaddrinfo: (node, service, hint, out) => {
    // Note getaddrinfo currently only returns a single addrinfo with ai_next defaulting to NULL. When NULL
    // hints are specified or ai_family set to AF_UNSPEC or ai_socktype or ai_protocol set to 0 then we
    // really should provide a linked list of suitable addrinfo values.
    var addrs = [];
    var canon = null;
    var addr = 0;
    var port = 0;
    var flags = 0;
    var family = AF_UNSPEC;
    var type = 0;
    var proto = 0;
    var ai, last;

    function allocaddrinfo(family, type, proto, canon, addr, port) {
      var sa, salen, ai;
      var errno;

      salen = family === AF_INET6 ?
        {{{ C_STRUCTS.sockaddr_in6.__size__ }}} :
        {{{ C_STRUCTS.sockaddr_in.__size__ }}};
      addr = family === AF_INET6 ?
        inetNtop6(addr) :
        inetNtop4(addr);
      sa = _malloc(salen);
      errno = writeSockaddr(sa, family, addr, port);
#if ASSERTIONS
      assert(!errno);
#endif

      ai = _malloc({{{ C_STRUCTS.addrinfo.__size__ }}});
      {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_family, 'family', 'i32') }}};
      {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_socktype, 'type', 'i32') }}};
      {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_protocol, 'proto', 'i32') }}};
      {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_canonname, 'canon', '*') }}};
      {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_addr, 'sa', '*') }}};
      if (family === AF_INET6) {
        {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_addrlen, C_STRUCTS.sockaddr_in6.__size__, 'i32') }}};
      } else {
        {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_addrlen, C_STRUCTS.sockaddr_in.__size__, 'i32') }}};
      }
      {{{ makeSetValue('ai', C_STRUCTS.addrinfo.ai_next, '0', 'i32') }}};

      return ai;
    }

#if NODERAWSOCKETS
    // Resolve via node:dns (which honors the host's /etc/hosts), chaining one
    // addrinfo per {family, addr} result into *out.
    async function lookupHostname() {
      var entries = await nodeSockHelpers.lookupHost(node, family);
      if (typeof entries === 'number') return entries;
      var head = 0, prev = 0;
      for (var entry of entries) {
        var ai = allocaddrinfo(entry.family, type, proto, null, entry.addr, port);
        if (prev) {
          {{{ makeSetValue('prev', C_STRUCTS.addrinfo.ai_next, 'ai', '*') }}};
        } else {
          head = ai;
        }
        prev = ai;
      }
      {{{ makeSetValue('out', '0', 'head', '*') }}};
      return 0;
    }
#endif

    if (hint) {
      flags = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_flags, 'i32') }}};
      family = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_family, 'i32') }}};
      type = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_socktype, 'i32') }}};
      proto = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_protocol, 'i32') }}};
    }
    if (type && !proto) {
      proto = type === SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
    }
    if (!type && proto) {
      type = proto === IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM;
    }

    // If type or proto are set to zero in hints we should really be returning multiple addrinfo values, but for
    // now default to a TCP STREAM socket so we can at least return a sensible addrinfo given NULL hints.
    if (!proto) {
      proto = IPPROTO_TCP;
    }
    if (!type) {
      type = SOCK_STREAM;
    }

    if (!node && !service) {
      return EAI_NONAME;
    }
    if (flags & ~(AI_PASSIVE|AI_CANONNAME|AI_NUMERICHOST|
        AI_NUMERICSERV|AI_V4MAPPED|AI_ALL|AI_ADDRCONFIG)) {
      return EAI_BADFLAGS;
    }
    if (hint && ({{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_flags, 'i32') }}} & AI_CANONNAME) && !node) {
      return EAI_BADFLAGS;
    }
    if (flags & AI_ADDRCONFIG) {
      // TODO
      return EAI_NONAME;
    }
    if (type && type !== SOCK_STREAM && type !== SOCK_DGRAM) {
      return EAI_SOCKTYPE;
    }
    if (family !== AF_UNSPEC && family !== AF_INET && family !== AF_INET6) {
      return EAI_FAMILY;
    }

    if (service) {
      service = UTF8ToString(service);
      port = parseInt(service, 10);

      if (isNaN(port)) {
        if (flags & AI_NUMERICSERV) {
          return EAI_NONAME;
        }
        // TODO support resolving well-known service names from:
        // http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt
        return EAI_SERVICE;
      }
    }

    if (!node) {
      if (family === AF_UNSPEC) {
        family = AF_INET;
      }
      if (!(flags & AI_PASSIVE)) {
        if (family === AF_INET) {
          addr = _htonl(INADDR_LOOPBACK);
        } else {
          addr = [0, 0, 0, _htonl(1)];
        }
      }
      ai = allocaddrinfo(family, type, proto, null, addr, port);
      {{{ makeSetValue('out', '0', 'ai', '*') }}};
      return 0;
    }

    //
    // try as a numeric address
    //
    node = UTF8ToString(node);
    addr = inetPton4(node);
    if (addr !== null) {
      // incoming node is a valid ipv4 address
      if (family === AF_UNSPEC || family === AF_INET) {
        family = AF_INET;
      }
      else if (family === AF_INET6 && (flags & AI_V4MAPPED)) {
        addr = [0, 0, _htonl(0xffff), addr];
        family = AF_INET6;
      } else {
        return EAI_NONAME;
      }
    } else {
      addr = inetPton6(node);
      if (addr !== null) {
        // incoming node is a valid ipv6 address
        if (family === AF_UNSPEC || family === AF_INET6) {
          family = AF_INET6;
        } else {
          return EAI_NONAME;
        }
      }
    }
    if (addr != null) {
      ai = allocaddrinfo(family, type, proto, node, addr, port);
      {{{ makeSetValue('out', '0', 'ai', '*') }}};
      return 0;
    }
    if (flags & AI_NUMERICHOST) {
      return EAI_NONAME;
    }

    //
    // try as a hostname
    //
#if NODERAWSOCKETS
    // The lookup is asynchronous, so only start it where the calling stack can
    // wait on the Promise: a sync-proxied pthread (PROXY_SYNC_ASYNC) awaits it,
    // ASYNCIFY/JSPI suspend on it. Otherwise (the event-loop thread itself) it
    // must not start at all, since it would write to *out after we have
    // returned.
#if PTHREADS
    if (PThread.currentProxiedOperationCallerThread) return lookupHostname();
#endif
#if ASYNCIFY
    // handleAsync holds a runtime keepalive across the suspension, so a user
    // callback completing meanwhile does not exit the runtime under main().
    // Everything above this point is pure, so the ASYNCIFY rewind re-running
    // this body reaches handleAsync again and takes the stored result.
    return Asyncify.handleAsync(lookupHostname);
#else
    return EAI_AGAIN;
#endif
#else
    // resolve the hostname to a temporary fake address
    node = DNS.lookup_name(node);
    addr = inetPton4(node);
    if (family === AF_UNSPEC) {
      family = AF_INET;
    } else if (family === AF_INET6) {
      addr = [0, 0, _htonl(0xffff), addr];
    }
    ai = allocaddrinfo(family, type, proto, null, addr, port);
    {{{ makeSetValue('out', '0', 'ai', '*') }}};
    return 0;
#endif
  },

  getnameinfo__proxy: 'sync',
#if NODERAWSOCKETS && (PTHREADS || ASYNCIFY)
  // As getaddrinfo: a reverse lookup node:dns has to answer is a Promise,
  // awaited by a sync-proxied pthread or suspended on by ASYNCIFY/JSPI.
  getnameinfo__async: true,
#endif
  getnameinfo: (sa, salen, node, nodelen, serv, servlen, flags) => {
    var info = readSockaddr(sa, salen);
    if (info.errno) {
      return EAI_FAMILY;
    }
    var port = info.port;
    var addr = info.addr;

    // Write the result, with `lookup` the host name for addr if one is wanted
    // and known.
    function write(lookup) {
      var overflowed = false;

      if (node && nodelen) {
        if (!lookup) {
          if (flags & NI_NAMEREQD) {
            return EAI_NONAME;
          }
        } else {
          addr = lookup;
        }
        var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen);

        if (numBytesWrittenExclNull+1 >= nodelen) {
          overflowed = true;
        }
      }

      if (serv && servlen) {
        port = '' + port;
        var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen);

        if (numBytesWrittenExclNull+1 >= servlen) {
          overflowed = true;
        }
      }

      if (overflowed) {
        // Note: even when we overflow, getnameinfo() is specced to write out the truncated results.
        return EAI_OVERFLOW;
      }

      return 0;
    }

    if (node && nodelen && !(flags & NI_NUMERICHOST)) {
#if NODERAWSOCKETS
      // A node:dns reverse lookup, where the calling stack can wait on it (see
      // getaddrinfo). Where it cannot, the numeric form is the answer unless a
      // name is required.
      var lookupService = async () => write(await nodeSockHelpers.lookupService(addr, port));
#if PTHREADS
      if (PThread.currentProxiedOperationCallerThread) return lookupService();
#endif
#if ASYNCIFY
      return Asyncify.handleAsync(lookupService);
#else
      if (flags & NI_NAMEREQD) {
        return EAI_AGAIN;
      }
      return write(null);
#endif
#else
      return write(DNS.lookup_addr(addr));
#endif
    }
    return write(null);
  },

  // Implement netdb.h protocol entry (getprotoent, getprotobyname, getprotobynumber, setprotoent, endprotoent)
  // http://pubs.opengroup.org/onlinepubs/9699919799/functions/getprotobyname.html
  // The Protocols object holds our 'fake' protocols 'database'.
  $Protocols: {
    list: [],
    map: {}
  },
  setprotoent__deps: ['malloc'],
  setprotoent: (stayopen) => {
    // void setprotoent(int stayopen);

    // Allocate and populate a protoent structure given a name, protocol number and array of aliases
    function allocprotoent(name, proto, aliases) {
      // write name into buffer
      var nameBuf = _malloc(name.length + 1);
      stringToAscii(name, nameBuf);

      // write aliases into buffer
      var j = 0;
      var length = aliases.length;
      var aliasListBuf = _malloc((length + 1) * 4); // Use length + 1 so we have space for the terminating NULL ptr.

      for (var i = 0; i < length; i++, j += 4) {
        var alias = aliases[i];
        var aliasBuf = _malloc(alias.length + 1);
        stringToAscii(alias, aliasBuf);
        {{{ makeSetValue('aliasListBuf', 'j', 'aliasBuf', '*') }}};
      }
      {{{ makeSetValue('aliasListBuf', 'j', '0', '*') }}}; // Terminating NULL pointer.

      // generate protoent
      var pe = _malloc({{{ C_STRUCTS.protoent.__size__ }}});
      {{{ makeSetValue('pe', C_STRUCTS.protoent.p_name, 'nameBuf', '*') }}};
      {{{ makeSetValue('pe', C_STRUCTS.protoent.p_aliases, 'aliasListBuf', '*') }}};
      {{{ makeSetValue('pe', C_STRUCTS.protoent.p_proto, 'proto', 'i32') }}};
      return pe;
    };

    // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial
    // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful.
    var list = Protocols.list;
    var map  = Protocols.map;
    if (!list.length) {
        var entry = allocprotoent('tcp', 6, ['TCP']);
        list.push(entry);
        map['tcp'] = map['6'] = entry;
        entry = allocprotoent('udp', 17, ['UDP']);
        list.push(entry);
        map['udp'] = map['17'] = entry;
    }

    _setprotoent.index = 0;
  },

  endprotoent: () => {
    // void endprotoent(void);
    // We're not using a real protocol database so we don't do a real close.
  },

  getprotoent: (number) => {
    // struct protoent *getprotoent(void);
    // reads the  next  entry  from  the  protocols 'database' or return NULL if 'eof'
    if (_setprotoent.index === Protocols.list.length) {
      return 0;
    }
    var result = Protocols.list[_setprotoent.index++];
    return result;
  },

  getprotobyname: (name) => {
    // struct protoent *getprotobyname(const char *);
    name = UTF8ToString(name);
    _setprotoent(true);
    var result = Protocols.map[name];
    return result;
  },

  getprotobynumber: (number) => {
    // struct protoent *getprotobynumber(int proto);
    _setprotoent(true);
    var result = Protocols.map[number];
    return result;
  },

#endif // PROXY_POSIX_SOCKETS == 0

  $timers: {},

  $clearTimers__internal: true,
  $clearTimers: () => {
    for (var t of Object.values(timers)) {
      clearTimeout(t.id);
    }
  },

  // Helper function for setitimer that registers timers with the eventloop.
  // Timers always fire on the main thread, either directly from JS (here) or
  // or when the main thread is busy waiting calling _emscripten_yield.
  _setitimer_js__postset: () => addAtExit('clearTimers();'),
  _setitimer_js__proxy: 'sync',
  _setitimer_js__deps: ['$clearTimers', '_emscripten_timeout'],
  _setitimer_js: (which, timeout_ms) => {
#if RUNTIME_DEBUG
    dbg(`setitimer_js ${which} timeout=${timeout_ms}`);
#endif
    // First, clear any existing timer.
    if (timers[which]) {
      clearTimeout(timers[which].id);
      delete timers[which];
    }

    // A timeout of zero simply cancels the current timeout so we have nothing
    // more to do.
    if (!timeout_ms) return 0;

    var id = setTimeout(() => {
#if ASSERTIONS
      assert(which in timers);
#endif
      delete timers[which];
#if RUNTIME_DEBUG
      dbg(`itimer fired: ${which}`);
#endif
      callUserCallback(__emscripten_timeout, which, _emscripten_get_now());
    }, timeout_ms);
    timers[which] = { id, timeout_ms };
    return 0;
  },

  // Helper for raise() to avoid signature mismatch failures:
  // https://github.com/emscripten-core/posixtestsuite/issues/6
  __call_sighandler: (fp, sig) => {{{ makeDynCall('vi', 'fp') }}}(sig),

  // ==========================================================================
  // emscripten.h
  // ==========================================================================

  emscripten_run_script: (ptr) => {
    {{{ makeEval('eval(UTF8ToString(ptr));') }}}
  },

  emscripten_run_script_int__docs: '/** @suppress{checkTypes} */',
  emscripten_run_script_int: (ptr) => {
    {{{ makeEval('return eval(UTF8ToString(ptr))|0;') }}}
  },

  // Mark as `noleakcheck` otherwise lsan will report the last returned string
  // as a leak.
  emscripten_run_script_string__noleakcheck: true,
  emscripten_run_script_string__deps: ['realloc'],
  emscripten_run_script_string: (ptr) => {
    {{{ makeEval('var s = eval(UTF8ToString(ptr));') }}}
    if (s == null) {
      return 0;
    }
    s += '';
    var me = _emscripten_run_script_string;
    me.bufferSize = lengthBytesUTF8(s) + 1;
    me.buffer = _realloc(me.buffer ?? 0, me.bufferSize)
    stringToUTF8(s, me.buffer, me.bufferSize);
    return me.buffer;
  },

  emscripten_random: () => Math.random(),

  emscripten_date_now: () => Date.now(),

  emscripten_performance_now: () => performance.now(),

#if PTHREADS && !AUDIO_WORKLET
  // Pthreads need their clocks synchronized to the execution of the main
  // thread, so, when using them, make sure to adjust all timings to the
  // respective time origins.
  emscripten_get_now: () => performance.timeOrigin + performance.now(),
#else
#if AUDIO_WORKLET // https://github.com/WebAudio/web-audio-api/issues/2413
  emscripten_get_now: `;
    // AudioWorkletGlobalScope does not have performance.now()
    // (https://github.com/WebAudio/web-audio-api/issues/2527), so if building
    // with
    // Audio Worklets enabled, do a dynamic check for its presence.
    if (globalThis.performance?.now) {
#if PTHREADS
      _emscripten_get_now = () => performance.timeOrigin + performance.now();
#else
      _emscripten_get_now = () => performance.now();
#endif
    } else {
      _emscripten_get_now = Date.now;
    }
`,
#else
  // Modern environment where performance.now() is supported:
  // N.B. a shorter form '_emscripten_get_now = performance.now;' is
  // unfortunately not allowed even in current browsers (e.g. FF Nightly 75).
  emscripten_get_now: () => performance.now(),
#endif
#endif

  emscripten_get_now_res: () => { // return resolution of get_now, in nanoseconds
#if ENVIRONMENT_MAY_BE_NODE
    if (ENVIRONMENT_IS_NODE) {
      return 1; // nanoseconds
    }
#endif
#if AUDIO_WORKLET // https://github.com/WebAudio/web-audio-api/issues/2413
    if (globalThis.performance?.now) {
      return 1000; // microseconds (1/1000 of a millisecond)
    }
    return 1_000_000; // milliseconds
#else
    // Modern environment where performance.now() is supported:
    return 1000; // microseconds (1/1000 of a millisecond)
#endif
  },

  // Represents whether emscripten_get_now is guaranteed monotonic; the Date.now
  // implementation is not :(
  ...defineInternalConsts({
#if AUDIO_WORKLET // https://github.com/WebAudio/web-audio-api/issues/2413
    $nowIsMonotonic: '=!!globalThis.performance?.now',
#else
    // Modern environment where performance.now() is supported
    $nowIsMonotonic: 1,
#endif
  }),

  _emscripten_get_now_is_monotonic__internal: true,
  _emscripten_get_now_is_monotonic: () => nowIsMonotonic,

  $warnOnce: (text) => {
    warnOnce.shown ||= {};
    if (!warnOnce.shown[text]) {
      warnOnce.shown[text] = 1;
#if ENVIRONMENT_MAY_BE_NODE
      if (ENVIRONMENT_IS_NODE) text = 'warning: ' + text;
#endif
      err(text);
    }
  },

  _emscripten_log_formatted: (flags, str) => {
    str = UTF8ToString(str);

    if (flags & (EM_LOG_C_STACK | EM_LOG_JS_STACK)) {
      str = str.replace(/\s+$/, ''); // Ensure the message and the callstack are joined cleanly with exactly one newline.
      str += (str.length > 0 ? '\n' : '') + getCallstack(flags);
    }

    if (flags & EM_LOG_CONSOLE) {
      if (flags & EM_LOG_ERROR) {
        console.error(str);
      } else if (flags & EM_LOG_WARN) {
        console.warn(str);
      } else if (flags & EM_LOG_INFO) {
        console.info(str);
      } else if (flags & EM_LOG_DEBUG) {
        console.debug(str);
      } else {
        console.log(str);
      }
    } else if (flags & (EM_LOG_ERROR | EM_LOG_WARN)) {
      err(str);
    } else {
      out(str);
    }
  },

  // We never free the return values of this function so we need to allocate
  // using builtin_malloc to avoid LSan reporting these as leaks.
#if RETAIN_COMPILER_SETTINGS
  emscripten_get_compiler_setting__noleakcheck: true,
  emscripten_get_compiler_setting: (name) => {
    name = UTF8ToString(name);

    var ret = getCompilerSetting(name);
    if (typeof ret === 'number' || typeof ret === 'boolean') return ret;

    var cache = _emscripten_get_compiler_setting.cache ??= {};
    var fullret = cache[name];
    if (fullret) return fullret;
    return cache[name] = stringToNewUTF8(ret);
  },
#else
  emscripten_get_compiler_setting: (name) => abort('You must build with -sRETAIN_COMPILER_SETTINGS for getCompilerSetting or emscripten_get_compiler_setting to work'),
#endif

  emscripten_has_asyncify: () => {{{ ASYNCIFY }}},

  emscripten_debugger: () => { debugger },

  emscripten_print_double: (x, to, max) => {
    var str = x + '';
    if (to) return stringToUTF8(str, to, max);
    else return lengthBytesUTF8(str);
  },

#if USE_ASAN || USE_LSAN
  // When lsan is enabled noLeakCheck will temporarily disable leak checking
  // for the duration of the function.
  $noLeakCheck__deps: ['__lsan_enable', '__lsan_disable'],
  $noLeakCheck__docs: '/** @suppress{checkTypes} */',
  $noLeakCheck: (func) => {
    if (runtimeInitialized) ___lsan_disable();
    try {
      return func();
    } finally {
      if (runtimeInitialized) ___lsan_enable();
    }
  },
#endif

#if USE_ASAN || USE_LSAN || UBSAN_RUNTIME
  _emscripten_sanitizer_use_colors: () => {
    var setting = Module['printWithColors'];
    if (setting !== undefined) {
      return setting;
    }
    return ENVIRONMENT_IS_NODE && process.stderr.isTTY;
  },

  _emscripten_sanitizer_get_option__sig: 'pp',
  _emscripten_sanitizer_get_option: (name) => stringToNewUTF8(Module[UTF8ToString(name)] ?? ''),
#endif

  $readEmAsmArgsArray: [],
  $readEmAsmArgs: (sigPtr, buf) => {
#if ASSERTIONS
    // Nobody should have mutated _readEmAsmArgsArray underneath us to be something else than an array.
    assert(Array.isArray(readEmAsmArgsArray));
    // The input buffer is allocated on the stack, so it must be stack-aligned.
    assert(buf % STACK_ALIGN == 0);
#endif
    readEmAsmArgsArray.length = 0;
    var ch;
    // Most arguments are i32s, so shift the buffer pointer so it is a plain
    // index into HEAP32.
    while (ch = HEAPU8[sigPtr++]) {
#if ASSERTIONS
      var chr = String.fromCharCode(ch);
      var validChars = ['d', 'f', 'i', 'p'];
#if WASM_BIGINT
      // In WASM_BIGINT mode we support passing i64 values as bigint.
      validChars.push('j');
#endif
      assert(validChars.includes(chr), `Invalid character ${ch}("${chr}") in readEmAsmArgs! Use only [${validChars}], and do not specify "v" for void return argument.`);
#endif
      // Floats are always passed as doubles, so all types except for 'i'
      // are 8 bytes and require alignment.
      var wide = (ch != {{{ charCode('i') }}});
#if !MEMORY64
      wide &= (ch != {{{ charCode('p') }}});
#endif
      buf += wide && (buf % 8) ? 4 : 0;
      readEmAsmArgsArray.push(
        // Special case for pointers under wasm64 or CAN_ADDRESS_2GB mode.
        ch == {{{ charCode('p') }}} ? {{{ makeGetValue('buf', 0, '*') }}} :
#if WASM_BIGINT
        ch == {{{ charCode('j') }}} ? {{{ makeGetValue('buf', 0, 'i64') }}} :
#endif
        ch == {{{ charCode('i') }}} ?
          {{{ makeGetValue('buf', 0, 'i32') }}} :
          {{{ makeGetValue('buf', 0, 'double') }}}
      );
      buf += wide ? 8 : 4;
    }
    return readEmAsmArgsArray;
  },

  $hasOwn__internal: true,
#if MIN_FIREFOX_VERSION < 92 || MIN_CHROME_VERSION < 93 || MIN_SAFARI_VERSION < 150400
  // https://caniuse.com/mdn-javascript_builtins_object_hasown
  $hasOwn__docs: '/** @noinline */',
  $hasOwn: (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop),
#else
  // Closure's built-in extern for `Object.hasOwn` is typed as
  // `function(!Object, (string|symbol)): boolean`, which rejects nullable
  // `Object` types (such as `Module`) and numeric property keys.
  $hasOwn__docs: '/** @type {function(?, ?): boolean} */',
  $hasOwn: 'Object.hasOwn',
#endif

#if HAVE_EM_ASM
  $runEmAsmFunction: (code, sigPtr, argbuf) => {
    var args = readEmAsmArgs(sigPtr, argbuf);
#if ASSERTIONS
    assert(hasOwn(ASM_CONSTS, code), `No EM_ASM constant found at address ${code}.  The loaded WebAssembly file is likely out of sync with the generated JavaScript.`);
#endif
    return ASM_CONSTS[code](...args);
  },

  emscripten_asm_const_int: (code, sigPtr, argbuf) => {
    return runEmAsmFunction(code, sigPtr, argbuf);
  },
  emscripten_asm_const_double: (code, sigPtr, argbuf) => {
    return runEmAsmFunction(code, sigPtr, argbuf);
  },

  emscripten_asm_const_ptr: (code, sigPtr, argbuf) => {
    return runEmAsmFunction(code, sigPtr, argbuf);
  },

  $runMainThreadEmAsm: (emAsmAddr, sigPtr, argbuf, sync) => {
    var args = readEmAsmArgs(sigPtr, argbuf);
#if PTHREADS
    if (ENVIRONMENT_IS_PTHREAD) {
      // EM_ASM functions are variadic, receiving the actual arguments as a buffer
      // in memory. the last parameter (argBuf) points to that data. We need to
      // always un-variadify that, *before proxying*, as in the async case this
      // is a stack allocation that LLVM made, which may go away before the main
      // thread gets the message. For that reason we handle proxying *after* the
      // call to readEmAsmArgs, and therefore we do that manually here instead
      // of using __proxy. (And for simplicity, do the same in the sync
      // case as well, even though it's not strictly necessary, to keep the two
      // code paths as similar as possible on both sides.)
      return proxyToMainThread(0, emAsmAddr, sync, ...args);
    }
#endif
#if ASSERTIONS
    assert(hasOwn(ASM_CONSTS, emAsmAddr), `No EM_ASM constant found at address ${emAsmAddr}.  The loaded WebAssembly file is likely out of sync with the generated JavaScript.`);
#endif
    return ASM_CONSTS[emAsmAddr](...args);
  },
  emscripten_asm_const_int_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),

  emscripten_asm_const_ptr_sync_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 1),

  emscripten_asm_const_double_sync_on_main_thread: 'emscripten_asm_const_int_sync_on_main_thread',
  emscripten_asm_const_async_on_main_thread: (emAsmAddr, sigPtr, argbuf) => runMainThreadEmAsm(emAsmAddr, sigPtr, argbuf, 0),
#endif

#if !DECLARE_ASM_MODULE_EXPORTS
  // When DECLARE_ASM_MODULE_EXPORTS is set, this function is programmatically
  // created during linking.  See `create_receiving` in `emscripten.py`.
  // When DECLARE_ASM_MODULE_EXPORTS=0 is set, `assignWasmExports` is instead
  // defined here as a normal JS library function.
  $assignWasmExports: (wasmExports) => {
    // Use `for...in` rather than `Object.entries` here since, after
    // `applySignatureConversions`, some exports are inherited rather than own
    // properties.
    for (var name in wasmExports) {
      var exportedSymbol = wasmExports[name];
      name = asmjsMangle(name);
#if DYNCALLS || !WASM_BIGINT
      if (name.startsWith('dynCall_')) {
        dynCalls[name.substr(8)] = exportedSymbol;
      }
#endif
      // Special handling for Wasm globals.  See `create_receiving` for the
      // static version of this code.
      if (typeof exportedSymbol.value !== 'undefined') {
#if MEMORY64
        exportedSymbol = Number(exportedSymbol.value);
#else
        exportedSymbol = exportedSymbol.value
#endif
      }
#if MINIMAL_RUNTIME
      globalThis[name] = exportedSymbol;
#else
      globalThis[name] = Module[name] = exportedSymbol;
#endif
    }
    exportAliases(wasmExports);
  },
#endif

  // Parses as much of the given JS string to an integer, with quiet error
  // handling (returns a NaN on error). E.g. jstoi_q('123abc') returns 123.
  // Note that 'smart' radix handling is employed for input string:
  // '0314' is parsed as octal, and '0x1234' is parsed as base-16.
  $jstoi_q__docs: '/** @suppress {checkTypes} */',
  $jstoi_q: (str) => parseInt(str),



  // special runtime support

#if STACK_OVERFLOW_CHECK
  // Used by wasm-emscripten-finalize to implement STACK_OVERFLOW_CHECK
  __handle_stack_overflow__deps: ['emscripten_stack_get_base', 'emscripten_stack_get_end'],
  __handle_stack_overflow: (requested) => {
    var base = _emscripten_stack_get_base();
    var end = _emscripten_stack_get_end();
    abort(`stack overflow (Attempt to set SP to ${ptrToString(requested)}` +
          `, with stack limits [${ptrToString(end)} - ${ptrToString(base)}` +
          ']). If you require more stack space build with -sSTACK_SIZE=<bytes>');
  },
#endif

#if MINIMAL_RUNTIME // MINIMAL_RUNTIME does not have a global runtime variable thisProgram
  $getExecutableName: () => {
#if ENVIRONMENT_MAY_BE_NODE
    if (ENVIRONMENT_IS_NODE && process.argv.length > 1) {
      return process.argv[1].replace(/\\/g, '/');
    }
#endif
    return './this.program';
  },
#else
  $getExecutableName: () => thisProgram,
#endif

  // Receives a Web Audio context plus a set of elements to listen for user
  // input events on, and registers a context resume() for them. This lets
  // audio work properly in an automatic way, as browsers won't let audio run
  // without user interaction.
  $autoResumeAudioContext: (ctx) => {
    for (var event of ['keydown', 'mousedown', 'touchstart']) {
      for (var element of [document, document.getElementById('canvas')]) {
        element?.addEventListener(event, () => {
          if (ctx.state === 'suspended') ctx.resume();
        }, { 'once': true });
      }
    }
  },

#if DYNCALLS || !WASM_BIGINT
  $dynCalls__internal: true,
  $dynCalls: {},
  $dynCallLegacy: (sig, ptr, args) => {
    sig = sig.replace(/p/g, {{{ MEMORY64 ? "'j'" : "'i'" }}})
#if ASSERTIONS
    assert(sig in dynCalls, `bad function pointer type - sig is not in dynCalls: '${sig}'`);
    if (args?.length) {
#if WASM_BIGINT
      // j (64-bit integer) is fine, and is implemented as a BigInt. Without
      // legalization, the number of parameters should match (j is not expanded
      // into two i's).
      assert(args.length === sig.length - 1);
#else
      // j (64-bit integer) must be passed in as two numbers [low 32, high 32].
      assert(args.length === sig.substring(1).replace(/j/g, '--').length);
#endif
    } else {
      assert(sig.length == 1);
    }
#endif
    var f = dynCalls[sig];
    return f(ptr, ...args);
  },
#endif

  // Used in library code to get JS function from wasm function pointer.
  // All callers should use direct table access where possible and only fall
  // back to this function if needed.
  $getDynCaller: (sig, ptr, promising = false) => {
#if ASSERTIONS && !DYNCALLS
    assert(sig.includes('j') || sig.includes('p'), 'getDynCaller should only be called with i64 sigs')
#endif
    return (...args) => dynCall(sig, ptr, args, promising);
  },

  $dynCall: (sig, ptr, args = [], promising = false) => {
#if ASSERTIONS
    assert(ptr, `null function pointer in dynCall`);
#endif
#if ASSERTIONS && (DYNCALLS || !WASM_BIGINT || !JSPI)
    assert(!promising, 'async dynCall is not supported in this mode')
#endif
#if MEMORY64
    // With MEMORY64 we have an additional step to convert `p` arguments to
    // bigint. This is the runtime equivalent of the wrappers we create for wasm
    // exports in `emscripten.py:create_wasm64_wrappers`.
    for (var i = 1; i < sig.length; ++i) {
      if (sig[i] == 'p') args[i-1] = BigInt(args[i-1]);
    }
#endif
#if DYNCALLS
    var rtn = dynCallLegacy(sig, ptr, args);
#else
#if !WASM_BIGINT
    // Without WASM_BIGINT support we cannot directly call function with i64 as
    // part of their signature, so we rely on the dynCall functions generated by
    // wasm-emscripten-finalize
    if (sig.includes('j')) {
      return dynCallLegacy(sig, ptr, args);
    }
#endif
#if ASSERTIONS
    assert(getWasmTableEntry(ptr), `missing table entry in dynCall: ${ptr}`);
#endif
    var func = getWasmTableEntry(ptr);
#if JSPI
    if (promising) {
      func = WebAssembly.promising(func);
    }
#endif
    var rtn = func(...args);
#endif // DYNCALLS

    function convert(rtn) {
#if MEMORY64
      return sig[0] == 'p' ? Number(rtn) : rtn;
#elif CAN_ADDRESS_2GB
      return sig[0] == 'p' ? rtn >>> 0 : rtn;
#else
      return rtn;
#endif
    }

#if JSPI
    if (promising) {
      return rtn.then(convert);
    }
#endif
    return convert(rtn);
  },

  $callRuntimeCallbacks__internal: true,
  $callRuntimeCallbacks: (callbacks) => {
    while (callbacks.length > 0) {
      // Pass the module as the first argument.
      callbacks.shift()(Module);
    }
  },

#if SHRINK_LEVEL == 0 || ASYNCIFY == 2
  // A mirror copy of contents of wasmTable in JS side, to avoid relatively
  // slow wasmTable.get() call. Only used when not compiling with -Os, -Oz, or
  // JSPI which needs to instrument the functions.
  $wasmTableMirror__internal: true,
  $wasmTableMirror: [],

  $setWasmTableEntry__internal: true,
  $setWasmTableEntry: (idx, func) => {
    /** @suppress {checkTypes} */
    wasmTable.set({{{ toIndexType('idx') }}}, func);
    // With ABORT_ON_WASM_EXCEPTIONS wasmTable.get is overridden to return wrapped
    // functions so we need to call it here to retrieve the potential wrapper correctly
    // instead of just storing 'func' directly into wasmTableMirror
    /** @suppress {checkTypes} */
    wasmTableMirror[idx] = wasmTable.get({{{ toIndexType('idx') }}});
  },

  $getWasmTableEntry: (funcPtr) => {
#if MEMORY64
    // Function pointers should show up as numbers, even under wasm64, but
    // we still have some places where bigint values can flow here.
    // https://github.com/emscripten-core/emscripten/issues/18200
    funcPtr = Number(funcPtr);
#endif
    var func = wasmTableMirror[funcPtr];
    if (!func) {
      /** @suppress {checkTypes} */
      wasmTableMirror[funcPtr] = func = wasmTable.get({{{ toIndexType('funcPtr') }}});
#if ASYNCIFY == 2
      if (Asyncify.isAsyncExport(func)) {
        wasmTableMirror[funcPtr] = func = Asyncify.makeAsyncFunction(func);
      }
#endif
    }
#if ASSERTIONS && ASYNCIFY != 2 // With JSPI the function stored in the table will be a wrapper.
    /** @suppress {checkTypes} */
    assert(wasmTable.get({{{ toIndexType('funcPtr') }}}) == func, 'table mirror is out of date');
#endif
    return func;
  },

#else

  $setWasmTableEntry__docs: '/** @suppress{checkTypes} */',
  $setWasmTableEntry: (idx, func) => wasmTable.set({{{ toIndexType('idx') }}}, func),

  $getWasmTableEntry__docs: '/** @suppress{checkTypes} */',
  $getWasmTableEntry: (funcPtr) => {
    // In -Os and -Oz builds, do not implement a JS side wasm table mirror for small
    // code size, but directly access wasmTable, which is a bit slower as uncached.
    return wasmTable.get({{{ toIndexType('funcPtr') }}});
  },
#endif // SHRINK_LEVEL == 0 || ASYNCIFY == 2

  // Callable in pthread without __proxy needed.
  emscripten_exit_with_live_runtime: () => {
    {{{ runtimeKeepalivePush() }}}
    throw 'unwind';
  },

  _emscripten_runtime_keepalive_clear: () => {
#if isSymbolNeeded('$noExitRuntime')
    noExitRuntime = false;
#endif
#if !MINIMAL_RUNTIME
    runtimeKeepaliveCounter = 0;
#endif
  },

  emscripten_force_exit__proxy: 'sync',
  emscripten_force_exit: (status) => {
#if RUNTIME_DEBUG
    dbg('emscripten_force_exit');
#endif
#if !EXIT_RUNTIME && ASSERTIONS
    warnOnce('emscripten_force_exit cannot actually shut down the runtime, as the build does not have EXIT_RUNTIME set');
#endif
    __emscripten_runtime_keepalive_clear();
    _exit(status);
  },

  emscripten_out: (str) => out(UTF8ToString(str)),
  emscripten_outn: (str, len) => out(UTF8ToString(str, len)),

  emscripten_err: (str) => err(UTF8ToString(str)),
  emscripten_errn: (str, len) => err(UTF8ToString(str, len)),

#if ASSERTIONS || RUNTIME_DEBUG
  emscripten_dbg: (str) => dbg(UTF8ToString(str)),
  emscripten_dbgn: (str, len) => dbg(UTF8ToString(str, len)),

  emscripten_dbg_backtrace: (str) => {
    dbg(UTF8ToString(str) + '\n' + new Error().stack);
  },
#endif

  // Use program_invocation_short_name and program_invocation_name in compiled
  // programs. This function is for implementing them.
  _emscripten_get_progname: (str, len) => stringToUTF8(getExecutableName(), str, len),

  // These single-line arrow functions use curly braces since otherwise closure
  // compiler will inject a extra `return` keyword when inlining.
  // https://github.com/emscripten-core/emscripten/issues/26922
  emscripten_console_log: (str) => { console.log(UTF8ToString(str)) },
  emscripten_console_warn: (str) => { console.warn(UTF8ToString(str)) },
  emscripten_console_error: (str) => { console.error(UTF8ToString(str)) },
  emscripten_console_trace: (str) => { console.trace(UTF8ToString(str)) },

  emscripten_throw_number: (number) => { throw number; },

  emscripten_throw_string: (str) => { throw UTF8ToString(str); },

#if !MINIMAL_RUNTIME
#if STACK_OVERFLOW_CHECK
  $handleException__deps: ['emscripten_stack_get_current'],
#endif
  $handleException: (e) => {
    // Certain exception types we do not treat as errors since they are used for
    // internal control flow.
    // 1. ExitStatus, which is thrown by exit()
    // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others
    //    that wish to return to JS event loop.
    if (e instanceof ExitStatus || e == 'unwind') {
#if RUNTIME_DEBUG
      dbg(`handleException: unwinding: EXITSTATUS=${EXITSTATUS}`);
#endif
      return EXITSTATUS;
    }
#if STACK_OVERFLOW_CHECK
    checkStackCookie();
    if (e instanceof WebAssembly.RuntimeError) {
      if (_emscripten_stack_get_current() <= 0) {
        err('Stack overflow detected.  You can try increasing -sSTACK_SIZE (currently set to {{{ STACK_SIZE }}})');
      }
    }
#endif
#if RUNTIME_DEBUG
    dbg(`handleException: got unexpected exception ${e}, calling quit_`)
#endif
    quit_(1, e);
  },

  $runtimeKeepaliveCounter__internal: true,
  $runtimeKeepaliveCounter: 0,

#if isSymbolNeeded('$noExitRuntime')
  // If the `noExitRuntime` symbol is included in the build then
  // keepRuntimeAlive is always conditional since its state can change
  // at runtime.
  $keepRuntimeAlive: () => noExitRuntime || runtimeKeepaliveCounter > 0,
#elif !EXIT_RUNTIME && !PTHREADS
  // When `noExitRuntime` is not included and EXIT_RUNTIME=0 then we know the
  // runtime can never exit (i.e. should always be kept alive).
  // However, since pthreads themselves always need to be able to exit we
  // have to track `runtimeKeepaliveCounter` in that case.
  $keepRuntimeAlive: () => true,
#else
  $keepRuntimeAlive: () => runtimeKeepaliveCounter > 0,
#endif

  // Callable in pthread without __proxy needed.
  $runtimeKeepalivePush__sig: 'v',
  $runtimeKeepalivePush: () => {
    runtimeKeepaliveCounter += 1;
#if RUNTIME_DEBUG
    dbg(`runtimeKeepalivePush -> counter=${runtimeKeepaliveCounter}`);
#endif
  },

  $runtimeKeepalivePop__sig: 'v',
  $runtimeKeepalivePop: () => {
#if ASSERTIONS
    assert(runtimeKeepaliveCounter > 0);
#endif
    runtimeKeepaliveCounter -= 1;
#if RUNTIME_DEBUG
    dbg(`runtimeKeepalivePop -> counter=${runtimeKeepaliveCounter}`);
#endif
  },

  emscripten_runtime_keepalive_push: '$runtimeKeepalivePush',
  emscripten_runtime_keepalive_pop: '$runtimeKeepalivePop',
  emscripten_runtime_keepalive_check: '$keepRuntimeAlive',

  // Used to call user callbacks from the embedder / event loop.  For example
  // setTimeout or any other kind of event handler that calls into user case
  // needs to use this wrapper.
  //
  // The job of this wrapper is the handle emscripten-specific exceptions such
  // as ExitStatus and 'unwind' and prevent these from escaping to the top
  // level.
  $callUserCallback: (func, ...args) => {
#if EXIT_RUNTIME
    if (runtimeExited || ABORT) {
#else
    if (ABORT) {
#endif
#if ASSERTIONS
      err('user callback triggered after runtime exited or application aborted.  Ignoring.');
#endif
      return;
    }
    try {
      return func(...args);
    } catch (e) {
      handleException(e);
    } finally {
      maybeExit();
    }
  },

  $maybeExit__deps: [
#if PTHREADS
    '_emscripten_thread_exit',
#endif
  ],
  $maybeExit: () => {
#if EXIT_RUNTIME
    if (runtimeExited) {
      return;
    }
#endif
#if RUNTIME_DEBUG >= 2
    dbg(`maybeExit: user callback done: runtimeKeepaliveCounter=${runtimeKeepaliveCounter}`);
#endif
    if (!keepRuntimeAlive()) {
#if RUNTIME_DEBUG
      dbg(`maybeExit: calling exit() implicitly after user callback completed: ${EXITSTATUS}`);
#endif
      try {
#if PTHREADS
        if (ENVIRONMENT_IS_PTHREAD) {
          // exit the current thread, but only if there is one active.
          // TODO(https://github.com/emscripten-core/emscripten/issues/25076):
          // Unify this check with the runtimeExited check above
          if (_pthread_self()) __emscripten_thread_exit(EXITSTATUS);
          return;
        }
#endif
        _exit(EXITSTATUS);
      } catch (e) {
        handleException(e);
      }
    }
  },

  $asyncLoad: async (url) => {
    var arrayBuffer = await readAsync(url);
  #if ASSERTIONS
    assert(arrayBuffer, `Loading data file "${url}" failed (no arrayBuffer).`);
  #endif
    return new Uint8Array(arrayBuffer);
  },

#else // MINIMAL_RUNTIME
  $callUserCallback: (func, ...args) => {
    // MINIMAL_RUNTIME doesn't support the runtimeKeepalive stuff, but under
    // some circumstances it supportes `runtimeExited`
#if EXIT_RUNTIME
    if (runtimeExited) {
#if ASSERTIONS
      err('user callback triggered after runtime exited or application aborted.  Ignoring.');
#endif
      return;
    }
#endif
    func(...args);
  },
#endif // MINIMAL_RUNTIME

  $asmjsMangle: (x) => {
    if (x == '__main_argc_argv') {
      x = 'main';
    }
#if DYNCALLS
    return x.startsWith('dynCall_') ? x : '_' + x;
#else
    return '_' + x;
#endif
  },

  $alignMemory: (size, alignment) => {
#if ASSERTIONS
    assert(alignment, 'alignment argument is required');
#endif
    return Math.ceil(size / alignment) * alignment;
  },

  // Allocate memory for an mmap operation. This allocates space of the right
  // page-aligned size, and clears the allocated space.
  $mmapAlloc: (size) => {
#if hasExportedSymbol('emscripten_builtin_memalign')
    size = alignMemory(size, {{{ WASM_PAGE_SIZE }}});
    var ptr = _emscripten_builtin_memalign({{{ WASM_PAGE_SIZE }}}, size);
    if (ptr) zeroMemory(ptr, size);
    return ptr;
#elif ASSERTIONS
    abort('internal error: mmapAlloc called but `emscripten_builtin_memalign` native symbol not exported');
#else
    abort();
#endif
  },

  _emscripten_fs_load_embedded_files: (ptr) => {
#if RUNTIME_DEBUG
    dbg('preloading data files');
#endif
    do {
      var name_addr = {{{ makeGetValue('ptr', '0', '*') }}};
      ptr += {{{ POINTER_SIZE }}};
      var len = {{{ makeGetValue('ptr', '0', '*') }}};
      ptr += {{{ POINTER_SIZE }}};
      var content = {{{ makeGetValue('ptr', '0', '*') }}};
      ptr += {{{ POINTER_SIZE }}};
      var name = UTF8ToString(name_addr)
#if RUNTIME_DEBUG
      dbg(`preloading files: ${name}`);
#endif
      FS.createPath('/', PATH.dirname(name), true, true);
      // canOwn this data in the filesystem, it is a slice of wasm memory that will never change
      FS.createDataFile(name, null, HEAP8.subarray(content, content + len), true, true, /*canOwn=*/true);
    } while ({{{ makeGetValue('ptr', '0', '*') }}});
#if RUNTIME_DEBUG
    dbg('done preloading data files');
#endif
  },

  $HandleAllocator: class {
    allocated = [undefined];
    freelist = [];
    get(id) {
#if ASSERTIONS
      assert(this.allocated[id] !== undefined, `invalid handle: ${id}`);
#endif
      return this.allocated[id];
    }
    has(id) {
      return this.allocated[id] !== undefined;
    }
    allocate(handle) {
      var id = this.freelist.pop() ?? this.allocated.length;
      this.allocated[id] = handle;
      return id;
    }
    free(id) {
#if ASSERTIONS
      assert(this.allocated[id] !== undefined);
#endif
      // Set the slot to `undefined` rather than using `delete` here since
      // apparently arrays with holes in them can be less efficient.
      this.allocated[id] = undefined;
      this.freelist.push(id);
    }
  },

  // `wasmTable` is a JS alias for the Wasm `__indirect_function_table` export
  $wasmTable__docs: '/** @type {WebAssembly.Table} */',
  $wasmTable: '__indirect_function_table',

#if IMPORTED_MEMORY
  // This gets defined in src/runtime_init_memory.js
  $wasmMemory: undefined,
#else
  // `wasmMemory` is a JS alias for the Wasm `memory` export
  $wasmMemory: 'memory',
#endif

  $getUniqueRunDependency: (id) => {
#if ASSERTIONS
    var orig = id;
    while (1) {
      if (!runDependencyTracking[id]) return id;
      id = orig + Math.random();
    }
#else
    return id;
#endif
  },

  $noExitRuntime__postset: () => addAtModule(makeModuleReceive('noExitRuntime')),
  $noExitRuntime: {{{ !EXIT_RUNTIME }}},

#if !MINIMAL_RUNTIME
  // A counter of dependencies for calling run(). If we need to
  // do asynchronous work before running, increment this and
  // decrement it. Incrementing must happen in a place like
  // Module.preRun (used by emcc to add file preloading).
  // Note that you can add dependencies in preRun, even though
  // it happens right before run - run will be postponed until
  // the dependencies are met.
  $runDependencies__internal: true,
  $runDependencies__deps: ['$resolveRunDependencies'],
  $runDependencies: 0,
  $dependenciesPromise__internal: true,
  $dependenciesPromise: null,
  $dependenciesPromiseResolve__internal: true,
  $dependenciesPromiseResolve: null,
  $resolveRunDependencies__internal: true,
  $resolveRunDependencies: async () => dependenciesPromise,
#if ASSERTIONS
  $runDependencyTracking__internal: true,
  $runDependencyTracking: {},
  $runDependencyWatcher__internal: true,
  $runDependencyWatcher: null,
#endif

  $addRunDependency__deps: [
    '$removeRunDependency',
  ],
  $addRunDependency: (id) => {
    if (!runDependencies) {
      dependenciesPromise = new Promise((resolve) => dependenciesPromiseResolve = resolve);
    }
    runDependencies++;

#if expectToReceiveOnModule('monitorRunDependencies')
    Module['monitorRunDependencies']?.(runDependencies);
#endif

#if ASSERTIONS
#if RUNTIME_DEBUG
    dbg('addRunDependency', id);
#endif
    assert(id, 'addRunDependency requires an ID')
    assert(!runDependencyTracking[id]);
    runDependencyTracking[id] = 1;
    if (!runDependencyWatcher && globalThis.setInterval) {
      // Check for missing dependencies every few seconds
      runDependencyWatcher = setInterval(() => {
        if (ABORT) {
          clearInterval(runDependencyWatcher);
          runDependencyWatcher = null;
          return;
        }
        var shown = false;
        for (var dep in runDependencyTracking) {
          if (!shown) {
            shown = true;
            err('still waiting on run dependencies:');
          }
          err(`dependency: ${dep}`);
        }
        if (shown) {
          err('(end of list)');
        }
      }, 10_000);
#if ENVIRONMENT_MAY_BE_NODE
      // Prevent this timer from keeping the runtime alive if nothing
      // else is.
      runDependencyWatcher.unref?.()
#endif
    }
#endif
  },

  $removeRunDependency: (id) => {
    runDependencies--;

#if expectToReceiveOnModule('monitorRunDependencies')
    Module['monitorRunDependencies']?.(runDependencies);
#endif

#if ASSERTIONS
#if RUNTIME_DEBUG
    dbg('removeRunDependency', id);
#endif
    assert(id, 'removeRunDependency requires an ID');
    assert(runDependencyTracking[id]);
    delete runDependencyTracking[id];
#endif
    if (!runDependencies) {
#if ASSERTIONS
      if (runDependencyWatcher !== null) {
        clearInterval(runDependencyWatcher);
        runDependencyWatcher = null;
      }
#endif
      dependenciesPromiseResolve();
    }
  },
#endif

  // The following addOn<X> functions are for adding runtime callbacks at
  // various executions points. Each addOn<X> function has a corresponding
  // compiled time version named addAt<X> that will instead inline during
  // compilation (see parseTools.mjs).
  // Note: if there are both runtime and compile time code, the runtime
  // callbacks will be invoked before the compile time code.

  // See ATPRERUNS in parseTools.mjs for more information.
  $onPreRuns: [],
  $onPreRuns__internal: true,
  $onPreRuns__deps: ['$callRuntimeCallbacks'],
  $onPreRuns__postset: () => {
    ATPRERUNS.unshift('callRuntimeCallbacks(onPreRuns);');
  },
  $addOnPreRun: (cb) => onPreRuns.push(cb),
  // See ATINITS in parseTools.mjs for more information.
  $onInits: [],
  $onInits__internal: true,
  $onInits__deps: ['$callRuntimeCallbacks'],
  $onInits__postset: () => {
    ATINITS.unshift('callRuntimeCallbacks(onInits);');
  },
  $addOnInit: (cb) => onInits.push(cb),
  // See ATPOSTCTORS in parseTools.mjs for more information.
  $onPostCtors: [],
  $onPostCtors__internal: true,
  $onPostCtors__deps: ['$callRuntimeCallbacks'],
  $onPostCtors__postset: () =>  {
    ATPOSTCTORS.unshift('callRuntimeCallbacks(onPostCtors);');
  },
  $addOnPostCtor: (cb) => {
#if ASSERTIONS
    assert(!runtimeInitialized, 'addOnPostCtor called too late: ctors have already run');
#endif
    onPostCtors.push(cb);
  },
  // See ATMAINS in parseTools.mjs for more information.
  $onMains: [],
  $onMains__internal: true,
  $onMains__deps: ['$callRuntimeCallbacks'],
  $onMains__postset: () => {
    ATMAINS.unshift('callRuntimeCallbacks(onMains);');
  },
  $addOnPreMain: (cb) => onMains.push(cb),
  // See ATEXITS in parseTools.mjs for more information.
  $onExits: [],
  $onExits__internal: true,
  $onExits__deps: ['$callRuntimeCallbacks'],
  $onExits__postset: () => {
    ATEXITS.unshift('callRuntimeCallbacks(onExits);');
  },
  $addOnExit: (cb) => onExits.push(cb),
  // See ATPOSTRUNS in parseTools.mjs for more information.
  $onPostRuns: [],
  $onPostRuns__internal: true,
  $onPostRuns__deps: ['$callRuntimeCallbacks'],
  $onPostRuns__postset: () => {
    ATPOSTRUNS.unshift('callRuntimeCallbacks(onPostRuns);');
  },
  $addOnPostRun: (cb) => onPostRuns.push(cb),

  // We used to define these globals unconditionally in support code.
  // Instead, we now define them here so folks can pull it in explicitly, on
  // demand.
  ...defineConsts({
    $STACK_SIZE: {{{ STACK_SIZE }}},
    $STACK_ALIGN: 16,
    $POINTER_SIZE: {{{ POINTER_SIZE }}},
    $ASSERTIONS: {{{ ASSERTIONS }}},
  }),
});

function autoAddDeps(lib, name) {
  for (const item of Object.keys(lib)) {
    if (!isDecorator(item) && item != name && !lib[item + '__const']) {
      lib[item + '__deps'] ??= [];
      lib[item + '__deps'].push(name);
    }
  }
}

#if LEGACY_RUNTIME
// Library functions that were previously included as runtime functions are
// automatically included when `LEGACY_RUNTIME` is set.
for (const symbol of [
  '$addFunction',
  '$removeFunction',
  '$AsciiToString',
  '$stringToAscii',
  '$UTF16ToString',
  '$stringToUTF16',
  '$lengthBytesUTF16',
  '$UTF32ToString',
  '$stringToUTF32',
  '$lengthBytesUTF32',
  '$stringToNewUTF8',
  '$stringToUTF8OnStack',
  '$writeStringToMemory',
  '$writeArrayToMemory',
  '$writeAsciiToMemory',
  '$intArrayFromString',
  '$intArrayToString',
  '$warnOnce',
  '$ccall',
  '$cwrap',
  '$ExitStatus',
  '$UTF8ArrayToString',
  '$UTF8ToString',
  '$stringToUTF8Array',
  '$stringToUTF8',
  '$lengthBytesUTF8',
]) {
  addToLibrary({[symbol + '__force']: true}, {allowMissing: true});
}
#endif

function wrapSyscallFunction(x, library, isWasi) {
  if (isJsOnlySymbol(x) || isDecorator(x)) {
    return;
  }

  var t = library[x];
  if (typeof t === 'string') return;
  t = t.toString();

  // If a syscall uses FS, but !SYSCALLS_REQUIRE_FILESYSTEM, then the user
  // has disabled the filesystem or we have proven some other way that this will
  // not be called in practice, and do not need that code.
  if (!SYSCALLS_REQUIRE_FILESYSTEM && t.includes('FS.')) {
    library[x + '__deps'] = [];
    t = modifyJSFunction(t, (args, body) => {
      return `(${args}) => {\n` +
             (ASSERTIONS ? "abort('it should not be possible to operate on streams when !SYSCALLS_REQUIRE_FILESYSTEM');\n" : '') +
             '}';
    });
  }

  var isVariadic = !isWasi && t.includes(', varargs');
#if SYSCALLS_REQUIRE_FILESYSTEM
  var canThrow = library[x + '__nothrow'] !== true;
#else
  var canThrow = false;
#endif

#if PURE_WASI && GROWABLE_ARRAYBUFFERS != 2
  // In PURE_WASI mode we can't assume the wasm binary was built by emscripten
  // and politely notify us on memory growth.  Instead we have to check for
  // possible memory growth on each syscall.
  var pre = '\nif (!HEAPU8.byteLength) _emscripten_notify_memory_growth(0);\n'
#else
  var pre = '';
#endif
  var post = '';
  if (isVariadic) {
    pre += 'SYSCALLS.varargs = varargs;\n';
  }

#if SYSCALL_DEBUG
  if (isVariadic) {
    if (canThrow) {
      post += 'finally { SYSCALLS.varargs = undefined; }\n';
    } else {
      post += 'SYSCALLS.varargs = undefined;\n';
    }
  }
  pre += `dbg('syscall! ${x}: [' + Array.from(arguments) + ']');\n`;
  pre += 'var canWarn = true;\n';
  pre += 'var ret = (() => {';
  post += '})();\n';
  post += 'if (ret && ret < 0 && canWarn) {\n';
  post += '  dbg(`error: syscall may have failed with ${-ret} (${strError(-ret)})`);\n';
  post += '}\n';
  post += 'dbg(`syscall return: ${ret}`);\n';
  post += 'return ret;\n';
#endif
  delete library[x + '__nothrow'];
  var handler = '';
  if (canThrow) {
    pre += 'try {\n';
    handler +=
    '} catch (e) {\n' +
    "  if (typeof FS === 'undefined' || !(e.name === 'ErrnoError')) throw e;\n";
#if SYSCALL_DEBUG
    handler +=
    '  dbg(`error: syscall failed with ${e.errno} (${strError(e.errno)})`);\n' +
    '  canWarn = false;\n';
#endif
    // Musl syscalls are negated.
    if (isWasi) {
      handler += '  return e.errno;\n';
    } else {
      // Musl syscalls are negated.
      handler += '  return -e.errno;\n';
    }
    handler += '}\n';
  }
  post = handler + post;

  if (pre || post) {
    t = modifyJSFunction(t, (args, body, async_) => `${async_}function (${args}) {\n${pre}${body}${post}}\n`);
  }

  library[x] = t;
#if PTHREADS
  // Most syscalls need to happen on the main JS thread (e.g. because the
  // filesystem is in JS and on that thread). Proxy synchronously to there.
  // There are some exceptions, syscalls that we know are ok to just run in
  // any thread; those are marked as not being proxied with
  //  __proxy: false
  // A syscall without a return value could perhaps be proxied asynchronously
  // instead of synchronously, and marked with
  //  __proxy: 'async'
  // (but essentially all syscalls do have return values).
  library[x + '__proxy'] ??= 'sync';
#endif
}