Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/builtin/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 63 kB image not shown  

Quelle  AtomicsObject.cpp

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/*
 * JS Atomics pseudo-module.
 *
 * See chapter 24.4 "The Atomics Object" and chapter 27 "Memory Model" in
 * ECMAScript 2021 for the full specification.
 */


#include "builtin/AtomicsObject.h"

#include "mozilla/Atomics.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"

#include "builtin/Number.h"
#include "builtin/Promise.h"
#include "jit/AtomicOperations.h"
#include "jit/InlinableNatives.h"
#include "js/Class.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/PropertySpec.h"
#include "js/Result.h"
#include "js/WaitCallbacks.h"
#include "vm/GlobalObject.h"
#include "vm/HelperThreads.h"                 // AutoLockHelperThreadState
#include "vm/OffThreadPromiseRuntimeState.h"  // OffthreadPromiseTask
#include "vm/TypedArrayObject.h"

#include "vm/Compartment-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/Realm-inl.h"

using namespace js;

static bool ReportBadArrayType(JSContext* cx) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_ATOMICS_BAD_ARRAY);
  return false;
}

static bool ReportDetachedArrayBuffer(JSContext* cx) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_TYPED_ARRAY_DETACHED);
  return false;
}

static bool ReportImmutableBuffer(JSContext* cx) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_ARRAYBUFFER_IMMUTABLE);
  return false;
}

static bool ReportResizedArrayBuffer(JSContext* cx) {
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                            JSMSG_TYPED_ARRAY_RESIZED_BOUNDS);
  return false;
}

static bool ReportOutOfRange(JSContext* cx) {
  // Use JSMSG_BAD_INDEX here, it is what ToIndex uses for some cases that it
  // reports directly.
  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
  return false;
}

enum class AccessMode { Read, Write };

// ES2026 draft rev affcec07523a45d40fb668689c07657412e772ac
// Plus: https://tc39.es/proposal-immutable-arraybuffer/
// 25.4.3.1 ValidateIntegerTypedArray ( typedArray, waitable )
static bool ValidateIntegerTypedArray(
    JSContext* cx, HandleValue typedArray, bool waitable, AccessMode accessMode,
    MutableHandle<TypedArrayObject*> unwrappedTypedArray) {
  // Steps 1-2.
  auto* unwrapped = UnwrapAndTypeCheckValue<TypedArrayObject>(
      cx, typedArray, [cx]() { ReportBadArrayType(cx); });
  if (!unwrapped) {
    return false;
  }

  if (unwrapped->hasDetachedBuffer()) {
    return ReportDetachedArrayBuffer(cx);
  }

  if (accessMode == AccessMode::Write &&
      unwrapped->is<ImmutableTypedArrayObject>()) {
    return ReportImmutableBuffer(cx);
  }

  // Steps 3-4.
  if (waitable) {
    switch (unwrapped->type()) {
      case Scalar::Int32:
      case Scalar::BigInt64:
        break;
      default:
        return ReportBadArrayType(cx);
    }
  } else {
    switch (unwrapped->type()) {
      case Scalar::Int8:
      case Scalar::Uint8:
      case Scalar::Int16:
      case Scalar::Uint16:
      case Scalar::Int32:
      case Scalar::Uint32:
      case Scalar::BigInt64:
      case Scalar::BigUint64:
        break;
      default:
        return ReportBadArrayType(cx);
    }
  }

  // Step 5 (modified to return the TypedArray).
  unwrappedTypedArray.set(unwrapped);
  return true;
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.1.2 ValidateAtomicAccess ( typedArray, requestIndex )
static bool ValidateAtomicAccess(JSContext* cx,
                                 Handle<TypedArrayObject*> typedArray,
                                 HandleValue requestIndex, size_t* index) {
  MOZ_ASSERT(!typedArray->hasDetachedBuffer());

  // Steps 1-2.
  mozilla::Maybe<size_t> length = typedArray->length();
  if (!length) {
    // ValidateIntegerTypedArray doesn't check for out-of-bounds in our
    // implementation, so we have to handle this case here.
    return ReportResizedArrayBuffer(cx);
  }

  // Steps 3-4.
  uint64_t accessIndex;
  if (!ToIndex(cx, requestIndex, &accessIndex)) {
    return false;
  }

  // Step 5.
  if (accessIndex >= *length) {
    return ReportOutOfRange(cx);
  }

  // Steps 6-9.
  *index = size_t(accessIndex);
  return true;
}

template <typename T>
struct ArrayOps {
  using Type = T;

  static JS::Result<T> convertValue(JSContext* cx, HandleValue v) {
    int32_t n;
    if (!ToInt32(cx, v, &n)) {
      return cx->alreadyReportedError();
    }
    return static_cast<T>(n);
  }

  static JS::Result<T> convertValue(JSContext* cx, HandleValue v,
                                    MutableHandleValue result) {
    double d;
    if (!ToInteger(cx, v, &d)) {
      return cx->alreadyReportedError();
    }
    result.setNumber(d);
    return static_cast<T>(JS::ToInt32(d));
  }

  static JS::Result<> storeResult(JSContext* cx, T v,
                                  MutableHandleValue result) {
    result.setInt32(v);
    return Ok();
  }
};

template <>
JS::Result<> ArrayOps<uint32_t>::storeResult(JSContext* cx, uint32_t v,
                                             MutableHandleValue result) {
  // Always double typed so that the JITs can assume the types are stable.
  result.setDouble(v);
  return Ok();
}

template <>
struct ArrayOps<int64_t> {
  using Type = int64_t;

  static JS::Result<int64_t> convertValue(JSContext* cx, HandleValue v) {
    BigInt* bi = ToBigInt(cx, v);
    if (!bi) {
      return cx->alreadyReportedError();
    }
    return BigInt::toInt64(bi);
  }

  static JS::Result<int64_t> convertValue(JSContext* cx, HandleValue v,
                                          MutableHandleValue result) {
    BigInt* bi = ToBigInt(cx, v);
    if (!bi) {
      return cx->alreadyReportedError();
    }
    result.setBigInt(bi);
    return BigInt::toInt64(bi);
  }

  static JS::Result<> storeResult(JSContext* cx, int64_t v,
                                  MutableHandleValue result) {
    BigInt* bi = BigInt::createFromInt64(cx, v);
    if (!bi) {
      return cx->alreadyReportedError();
    }
    result.setBigInt(bi);
    return Ok();
  }
};

template <>
struct ArrayOps<uint64_t> {
  using Type = uint64_t;

  static JS::Result<uint64_t> convertValue(JSContext* cx, HandleValue v) {
    BigInt* bi = ToBigInt(cx, v);
    if (!bi) {
      return cx->alreadyReportedError();
    }
    return BigInt::toUint64(bi);
  }

  static JS::Result<uint64_t> convertValue(JSContext* cx, HandleValue v,
                                           MutableHandleValue result) {
    BigInt* bi = ToBigInt(cx, v);
    if (!bi) {
      return cx->alreadyReportedError();
    }
    result.setBigInt(bi);
    return BigInt::toUint64(bi);
  }

  static JS::Result<> storeResult(JSContext* cx, uint64_t v,
                                  MutableHandleValue result) {
    BigInt* bi = BigInt::createFromUint64(cx, v);
    if (!bi) {
      return cx->alreadyReportedError();
    }
    result.setBigInt(bi);
    return Ok();
  }
};

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.1.11 AtomicReadModifyWrite ( typedArray, index, value, op ), steps 1-2.
// 24.4.1.12 AtomicLoad ( typedArray, index ), steps 1-2.
// 24.4.4 Atomics.compareExchange ( typedArray, index, ... ), steps 1-2.
// 24.4.9 Atomics.store ( typedArray, index, value ), steps 1-2.
template <typename Op>
static bool AtomicAccess(JSContext* cx, HandleValue obj, HandleValue index,
                         AccessMode accessMode, Op op) {
  // Step 1.
  Rooted<TypedArrayObject*> unwrappedTypedArray(cx);
  if (!ValidateIntegerTypedArray(cx, obj, false, accessMode,
                                 &unwrappedTypedArray)) {
    return false;
  }

  // Step 2.
  size_t intIndex;
  if (!ValidateAtomicAccess(cx, unwrappedTypedArray, index, &intIndex)) {
    return false;
  }

  switch (unwrappedTypedArray->type()) {
    case Scalar::Int8:
      return op(ArrayOps<int8_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::Uint8:
      return op(ArrayOps<uint8_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::Int16:
      return op(ArrayOps<int16_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::Uint16:
      return op(ArrayOps<uint16_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::Int32:
      return op(ArrayOps<int32_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::Uint32:
      return op(ArrayOps<uint32_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::BigInt64:
      return op(ArrayOps<int64_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::BigUint64:
      return op(ArrayOps<uint64_t>{}, unwrappedTypedArray, intIndex);
    case Scalar::Float16:
    case Scalar::Float32:
    case Scalar::Float64:
    case Scalar::Uint8Clamped:
    case Scalar::MaxTypedArrayViewType:
    case Scalar::Int64:
    case Scalar::Simd128:
      break;
  }
  MOZ_CRASH("Unsupported TypedArray type");
}

template <typename T>
static SharedMem<T*> TypedArrayData(JSContext* cx, TypedArrayObject* typedArray,
                                    size_t index) {
  // RevalidateAtomicAccess, steps 1-3.
  mozilla::Maybe<size_t> length = typedArray->length();

  // RevalidateAtomicAccess, step 4.
  if (!length) {
    ReportDetachedArrayBuffer(cx);
    return {};
  }

  // RevalidateAtomicAccess, step 5.
  if (index >= *length) {
    ReportOutOfRange(cx);
    return {};
  }

  SharedMem<void*> typedArrayData = typedArray->dataPointerEither();
  return typedArrayData.cast<T*>() + index;
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.4 Atomics.compareExchange ( typedArray, index, expectedValue,
//                                  replacementValue )
static bool atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  HandleValue typedArray = args.get(0);
  HandleValue index = args.get(1);

  return AtomicAccess(
      cx, typedArray, index, AccessMode::Write,
      [cx, &args](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
                  size_t index) {
        using T = typename decltype(ops)::Type;

        HandleValue expectedValue = args.get(2);
        HandleValue replacementValue = args.get(3);

        T oldval;
        JS_TRY_VAR_OR_RETURN_FALSE(cx, oldval,
                                   ops.convertValue(cx, expectedValue));

        T newval;
        JS_TRY_VAR_OR_RETURN_FALSE(cx, newval,
                                   ops.convertValue(cx, replacementValue));

        SharedMem<T*> addr = TypedArrayData<T>(cx, unwrappedTypedArray, index);
        if (!addr) {
          return false;
        }

        oldval =
            jit::AtomicOperations::compareExchangeSeqCst(addr, oldval, newval);

        JS_TRY_OR_RETURN_FALSE(cx, ops.storeResult(cx, oldval, args.rval()));
        return true;
      });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.7 Atomics.load ( typedArray, index )
static bool atomics_load(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  HandleValue typedArray = args.get(0);
  HandleValue index = args.get(1);

  return AtomicAccess(
      cx, typedArray, index, AccessMode::Read,
      [cx, &args](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
                  size_t index) {
        using T = typename decltype(ops)::Type;

        SharedMem<T*> addr = TypedArrayData<T>(cx, unwrappedTypedArray, index);
        if (!addr) {
          return false;
        }

        T v = jit::AtomicOperations::loadSeqCst(addr);

        JS_TRY_OR_RETURN_FALSE(cx, ops.storeResult(cx, v, args.rval()));
        return true;
      });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.9 Atomics.store ( typedArray, index, value )
static bool atomics_store(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  HandleValue typedArray = args.get(0);
  HandleValue index = args.get(1);

  return AtomicAccess(
      cx, typedArray, index, AccessMode::Write,
      [cx, &args](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
                  size_t index) {
        using T = typename decltype(ops)::Type;

        HandleValue value = args.get(2);

        T v;
        JS_TRY_VAR_OR_RETURN_FALSE(cx, v,
                                   ops.convertValue(cx, value, args.rval()));

        SharedMem<T*> addr = TypedArrayData<T>(cx, unwrappedTypedArray, index);
        if (!addr) {
          return false;
        }

        jit::AtomicOperations::storeSeqCst(addr, v);
        return true;
      });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.1.11 AtomicReadModifyWrite ( typedArray, index, value, op )
template <typename AtomicOp>
static bool AtomicReadModifyWrite(JSContext* cx, const CallArgs& args,
                                  AtomicOp op) {
  HandleValue typedArray = args.get(0);
  HandleValue index = args.get(1);

  return AtomicAccess(
      cx, typedArray, index, AccessMode::Write,
      [cx, &args, op](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
                      size_t index) {
        using T = typename decltype(ops)::Type;

        HandleValue value = args.get(2);

        T v;
        JS_TRY_VAR_OR_RETURN_FALSE(cx, v, ops.convertValue(cx, value));

        SharedMem<T*> addr = TypedArrayData<T>(cx, unwrappedTypedArray, index);
        if (!addr) {
          return false;
        }

        v = op(addr, v);

        JS_TRY_OR_RETURN_FALSE(cx, ops.storeResult(cx, v, args.rval()));
        return true;
      });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.5 Atomics.exchange ( typedArray, index, value )
static bool atomics_exchange(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  return AtomicReadModifyWrite(cx, args, [](auto addr, auto val) {
    return jit::AtomicOperations::exchangeSeqCst(addr, val);
  });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.2 Atomics.add ( typedArray, index, value )
static bool atomics_add(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  return AtomicReadModifyWrite(cx, args, [](auto addr, auto val) {
    return jit::AtomicOperations::fetchAddSeqCst(addr, val);
  });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.10 Atomics.sub ( typedArray, index, value )
static bool atomics_sub(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  return AtomicReadModifyWrite(cx, args, [](auto addr, auto val) {
    return jit::AtomicOperations::fetchSubSeqCst(addr, val);
  });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.3 Atomics.and ( typedArray, index, value )
static bool atomics_and(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  return AtomicReadModifyWrite(cx, args, [](auto addr, auto val) {
    return jit::AtomicOperations::fetchAndSeqCst(addr, val);
  });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.8 Atomics.or ( typedArray, index, value )
static bool atomics_or(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  return AtomicReadModifyWrite(cx, args, [](auto addr, auto val) {
    return jit::AtomicOperations::fetchOrSeqCst(addr, val);
  });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.13 Atomics.xor ( typedArray, index, value )
static bool atomics_xor(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  return AtomicReadModifyWrite(cx, args, [](auto addr, auto val) {
    return jit::AtomicOperations::fetchXorSeqCst(addr, val);
  });
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.6 Atomics.isLockFree ( size )
static bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  HandleValue v = args.get(0);

  // Step 1.
  int32_t size;
  if (v.isInt32()) {
    size = v.toInt32();
  } else {
    double dsize;
    if (!ToInteger(cx, v, &dsize)) {
      return false;
    }

    // Step 7 (non-integer case only).
    if (!mozilla::NumberEqualsInt32(dsize, &size)) {
      args.rval().setBoolean(false);
      return true;
    }
  }

  // Steps 2-7.
  args.rval().setBoolean(jit::AtomicOperations::isLockfreeJS(size));
  return true;
}

namespace js {

/*
 * [SMDOC] Atomics.wait, Atomics.waitAsync, and Atomics.notify
 *
 * `wait`, `waitAsync`, and `notify` are provided as low-level primitives for
 * thread synchronization. The primary use case is to take code that looks like
 * this:
 *
 *     const ValueIndex = 0;
 *     const FlagIndex = 1;
 *
 *     THREAD A:
 *       // Write a value.
 *       Atomics.store(sharedBuffer, ValueIndex, value);
 *       // Update a flag to indicate that the value was written.
 *       Atomics.store(sharedBuffer, FlagIndex, 1);
 *
 *     THREAD B:
 *       // Busy-wait for the flag to be updated.
 *       while (Atomics.load(sharedBuffer, FlagIndex) == 0) {}
 *       // Load the value.
 *       let value = Atomics.load(sharedBuffer, ValueIndex);
 *
 * ...and replace the busy-wait:
 *
 *     THREAD A':
 *       // Write the value and update the flag.
 *       Atomics.store(sharedBuffer, ValueIndex, value);
 *       Atomics.store(sharedBuffer, FlagIndex, 1);
 *       // Notify that the flag has been written.
 *       Atomics.notify(sharedBuffer, FlagIndex);
 *
 *     THREAD B':
 *       // Wait until the flag is notified.
 *       // If it's already non-zero, no wait occurs.
 *       Atomics.wait(sharedBuffer, FlagIndex, 0);
 *       // Load the value.
 *       let value = Atomics.load(sharedBuffer, ValueIndex);
 *
 * `wait` puts the calling thread to sleep until it is notified (or an optional
 * timeout expires). This can't be used on the main thread.
 *
 * `waitAsync` instead creates a Promise which will be resolved when the
 * position is notified (or an optional timeout expires).
 *
 * When `wait` or `waitAsync` is called, a waiter is created and registered with
 * the SharedArrayBuffer. Waiter instances for a SharedArrayRawBuffer are
 * connected in a circular doubly-linked list, containing both sync and async
 * waiters. Sync waiters are stack allocated in the stack frame of the waiting
 * thread. Async waiters are heap-allocated. The `waiters` field of the
 * SharedArrayRawBuffer is a dedicated list head node for the list. Waiters are
 * awoken in a first-in-first-out order. The `next` field of the list head node
 * points to the highest priority waiter. The `prev` field points to the lowest
 * priority waiter. This list is traversed when `notify` is called to find the
 * waiters that should be woken up.
 *
 * Synchronous waits are implemented using a per-context condition variable. See
 * FutexThread::wait.
 *
 * Asynchronous waits are more complicated, particularly with respect to
 * timeouts. In addition to the AsyncFutexWaiter that is added to the list of
 * waiters, we also create:
 *
 *   1. A Promise object to return to the caller. The promise will be resolved
 *      when the waiter is notified or times out.
 *   2. A WaitAsyncNotifyTask (derived from OffThreadPromiseTask) wrapping that
 *      promise. `notify` can be called from any thread, but the promise must be
 *      resolved on the thread that owns it. To resolve the promise, we dispatch
 *      the task to enqueue a promise resolution task in the target's event
 *      loop. The notify task is stored in the AsyncFutexWaiter.
 *   3. If there is a non-zero timeout, a WaitAsyncTimeoutTask (derived from
 *      JS::Dispatchable) containing a pointer to the async waiter. We dispatch
 *      this task to the embedding's event loop, with a delay. When the timeout
 *      expires and the task runs, if the promise has not yet been resolved, we
 *      resolve it with "timed-out".
 *
 * `waitAsync` Lifetimes
 * ---------------------
 *           
 *            SAB 
 *           
 *          bi-directional linked list
 *                   
 *                   
 *      *waiter      *waiter
 *                   
 *                   
 *         *  
 *              
 *      
 *      AsyncFutexWaiter 
 *                   
 *                                    
 *               borrow                borrow
 *                                    
 *             
 *      WaitAsyncTimeoutTask       WaitAsyncNotifyTask 
 *                    
 *                                                            
 *                                                             (transferred)
 *               own                                           own
 *         
 *     DelayedJSDispatchableHandler PromiseObject   JSDispatchableHandler
 *         
 *                                                   
 *           
 *                         own (initialized)
 *      TimeoutManager           JSContext
 *                     Cancellable List
 *                                             
 *          Runtime (MainThread or Worker)     
 *     
 *
 *
 * The data representing an async wait is divided between the JSContext in which
 * it was created and the SharedArrayBuffer being waited on. There are three
 * potential components:
 *
 * A) The AsyncFutexWaiter itself (shared by the SharedArrayRawBuffer,
 *    WaitAsyncNotifyTask, and WaitAsyncTimeoutTask if it exists). It
 *    will be cleaned up manually.
 * B) The corresponding WaitAsyncNotifyTask (owned by the JS::Context). It
 *    destroys itself on run.
 * C) The WaitAsyncTimeoutTask (owned by the embedding's job queue). It
 *    destroys itself on run.
 *
 * WaitAsyncNotifyTask and WaitAsyncTimeoutTask (if it exists) delete
 * themselves. When either task is run or destroyed, they also trigger the
 * destruction and unlinking of the AsyncFutexWaiter. There are
 * four scenarios:
 *
 * 1. A call to `Atomics.notify` notifies the waiter (atomics_notify_impl)
 *    from another thread.
 *    A) The async waiter is removed from the list.
 *    B) The notify task is removed from OffThreadPromiseRuntimeState's
 *       cancelable list and is dispatched to resolve the promise with "ok".
 *       The task then destroys itself.
 *    C) The WaitAsyncTimeoutTask is disabled. It will fire and do nothing.
 *       See AsyncFutexWaiter::maybeClearTimeout in atomics_notify_impl.
 *    D) The async waiter is destroyed.
 *
 * 2. A call to `Atomics.notify` notifies the waiter (atomics_notify_impl)
 *    from the same thread.
 *    A) The async waiter is removed from the list.
 *    B) The notify task is cancelled. The promise is extracted and resolved
 *        directly.
 *    C) The WaitAsyncTimeoutTask is disabled. It will fire and do nothing.
 *       See AsyncFutexWaiter::maybeClearTimeout in atomics_notify_impl.
 *    D) The async waiter is destroyed.
 *
 * 3. The timeout expires without notification (WaitAsyncTimeoutTask::run)
 *    A) The async waiter is removed from the list.
 *    B) The notify task is dispatched to resolve the promise with "timed-out"
 *       and destroys itself..
 *    C) The timeout task is running and will be destroyed when it's done.
 *    D) The async waiter is destroyed.
 *
 * 4. The context is destroyed (OffThreadPromiseRuntimeState::shutdown):
 *    A) The async waiter is removed and destroyed by
 *       WaitAsyncNotifyTask::prepareForCancel.
 *    B) The notify task is cancelled and destroyed by
 *       OffThreadPromiseRuntimeState::shutdown.
 *    C) The WaitAsyncTimeoutTask is disabled.
 *       See AsyncFutexWaiter::maybeClearTimeout in prepareForCancel.
 *
 * 5. The SharedArrayBuffer is collected by the GC (~FutexWaiterListHead)
 *    A) Async waiters without timeouts can no longer resolve. They are removed.
 *    B) If no timeout task exists, the notify task is dispatched and
 *       destroys itself, without resolving the promise.
 *    C) If there is an enqueued timeout, the waiter can still be resolved.
 *       In this case it will not be destroyed until it times out.
 *
 * The UniquePtr can be thought of as a "runnable handle" that gives exclusive
 * access to executing a runnable by a given owner. The runnable will still
 * delete itself (via js_delete, see OffThreadPromiseRuntimeState.cpp
 * implementation of OffThreadPromiseTask::run). If somehow the UniquePtr is not
 * passed to embedding code that will run the code, the task is released from
 * the pointer. We then use the list of raw pointers in
 * OffThreadPromiseRuntimeState's cancellable and dead lists are used to
 * identify which were never dispatched, and which failed to dispatch, and clear
 * them when the engine has an opportunity to do so (i.e. shutdown).
 */


class WaitAsyncNotifyTask;
class WaitAsyncTimeoutTask;

class AutoLockFutexAPI {
  // We have to wrap this in a Maybe because of the way loading
  // mozilla::Atomic pointers works.
  mozilla::Maybe<js::UniqueLock<js::Mutex>> unique_;

 public:
  AutoLockFutexAPI() {
    js::Mutex* lock = FutexThread::lock_;
    unique_.emplace(*lock);
  }

  ~AutoLockFutexAPI() { unique_.reset(); }

  js::UniqueLock<js::Mutex>& unique() { return *unique_; }
};

// Represents one waiter. This is the abstract base class for SyncFutexWaiter
// and AsyncFutexWaiter.
class FutexWaiter : public FutexWaiterListNode {
 protected:
  FutexWaiter(JSContext* cx, size_t offset, FutexWaiterKind kind)
      : FutexWaiterListNode(kind), offset_(offset), cx_(cx) {}

  size_t offset_;  // Element index within the SharedArrayBuffer
  JSContext* cx_;  // The thread that called `wait` or `waitAsync`.

 public:
  bool isSync() const { return kind_ == FutexWaiterKind::Sync; }
  SyncFutexWaiter* asSync() {
    MOZ_ASSERT(isSync());
    return reinterpret_cast<SyncFutexWaiter*>(this);
  }

  bool isAsync() const { return kind_ == FutexWaiterKind::Async; }
  AsyncFutexWaiter* asAsync() {
    MOZ_ASSERT(isAsync());
    return reinterpret_cast<AsyncFutexWaiter*>(this);
  }
  size_t offset() const { return offset_; }
  JSContext* cx() { return cx_; }
};

// Represents a worker blocked while calling |Atomics.wait|.
// Instances of js::SyncFutexWaiter are stack-allocated and linked
// onto the waiter list across a call to FutexThread::wait().
// When this waiter is notified, the worker will resume execution.
class MOZ_STACK_CLASS SyncFutexWaiter : public FutexWaiter {
 public:
  SyncFutexWaiter(JSContext* cx, size_t offset)
      : FutexWaiter(cx, offset, FutexWaiterKind::Sync) {}
};

// Represents a waiter asynchronously waiting after calling |Atomics.waitAsync|.
// Instances of js::AsyncFutexWaiter are heap-allocated.
// When this waiter is notified, the promise it holds will be resolved.
class AsyncFutexWaiter : public FutexWaiter {
 public:
  AsyncFutexWaiter(JSContext* cx, size_t offset)
      : FutexWaiter(cx, offset, FutexWaiterKind::Async) {}

  // NOTE: AsyncFutexWaiter is deleted only by UniquePtr<AsyncFutexWaiter>,
  //       and thus the destructor is not virtual.
  ~AsyncFutexWaiter();

  WaitAsyncNotifyTask* notifyTask() { return notifyTask_; }

  void setNotifyTask(WaitAsyncNotifyTask* task) {
    MOZ_ASSERT(!notifyTask_);
    notifyTask_ = task;
  }

  void resetNotifyTask() { notifyTask_ = nullptr; }

  void setTimeoutTask(WaitAsyncTimeoutTask* task) {
    MOZ_ASSERT(!timeoutTask_);
    timeoutTask_ = task;
  }

  void resetTimeoutTask() { timeoutTask_ = nullptr; }

  bool hasTimeout() const { return !!timeoutTask_; }
  WaitAsyncTimeoutTask* timeoutTask() const { return timeoutTask_; }

  void maybeClearTimeout(AutoLockFutexAPI& lock);

 private:
  // Both of these pointers are borrowed pointers. The notifyTask is owned by
  // the runtime's cancellable list, while the timeout task (if it exists) is
  // owned by the embedding's timeout manager.
  //
  // Set by setNotifyTask immediately after construction, and reset by
  // resetNotifyTask when the notify task is getting deleted.
  // WaitAsyncNotifyTask is responsible for calling resetNotifyTask
  WaitAsyncNotifyTask* notifyTask_ = nullptr;

  // Set by setTimeoutTask immediately after construction, and reset by
  // resetTimeoutTask when the timeout task is getting deleted.
  // WaitAsyncTimeoutTask is responsible for calling resetTimeoutTask
  WaitAsyncTimeoutTask* timeoutTask_ = nullptr;
};

// When an async waiter from a different context is notified, this
// task is queued to resolve the promise on the thread to which it
// belongs.
//
// WaitAsyncNotifyTask (derived from OffThreadPromiseTask) is wrapping that
// promise. `Atomics.notify` can be called from any thread, but the promise must
// be resolved on the thread that owns it. To resolve the promise, we dispatch
// the task to enqueue a promise resolution task in the target's event
// loop.
//
// See [SMDOC] Atomics.wait for more details.
class WaitAsyncNotifyTask : public OffThreadPromiseTask {
 public:
  enum class Result { Ok, TimedOut, Dead };

 private:
  Result result_ = Result::Ok;

  // A back-edge to the waiter so that it can be cleaned up when the
  // Notify Task is dispatched and destroyed.
  //
  // Set by setWaiter immediately after construction, and reset by resetWaiter
  // when the waiter is getting deleted.  AsyncFutexWaiter is responsible for
  // calling resetWaiter.
  AsyncFutexWaiter* waiter_ = nullptr;

 public:
  WaitAsyncNotifyTask(JSContext* cx, Handle<PromiseObject*> promise)
      : OffThreadPromiseTask(cx, promise) {}

  ~WaitAsyncNotifyTask() override {
    if (waiter_) {
      waiter_->resetNotifyTask();
    }
  }

  void setWaiter(AsyncFutexWaiter* waiter) {
    MOZ_ASSERT(!waiter_);
    waiter_ = waiter;
  }
  void resetWaiter() { waiter_ = nullptr; }

  void setResult(Result result, AutoLockFutexAPI& lock) { result_ = result; }

  bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
    RootedValue resultMsg(cx);
    switch (result_) {
      case Result::Ok:
        resultMsg = StringValue(cx->names().ok);
        break;
      case Result::TimedOut:
        resultMsg = StringValue(cx->names().timed_out_);
        break;
      case Result::Dead:
        // The underlying SharedArrayBuffer is no longer reachable, and no
        // timeout is associated with this waiter. The promise will never
        // resolve. There's nothing to do here.
        return true;
    }
    return PromiseObject::resolve(cx, promise, resultMsg);
  }

  void prepareForCancel() override;
};

// WaitAsyncNotifyTask (derived from OffThreadPromiseTask) is wrapping that
// promise. `notify` can be called from any thread, but the promise must be
// resolved on the thread that owns it. To resolve the promise, we dispatch
// the task to enqueue a promise resolution task in the target's event
// loop.
//
// See [SMDOC] Atomics.wait for more details.
class WaitAsyncTimeoutTask : public JS::Dispatchable {
  // Set by the constructor, and reset by resetWaiter when the waiter is getting
  // deleted. AsyncFutexWaiter is responsible for calling resetWaiter.
  AsyncFutexWaiter* waiter_;

 public:
  explicit WaitAsyncTimeoutTask(AsyncFutexWaiter* waiter) : waiter_(waiter) {
    MOZ_ASSERT(waiter_);
  }
  ~WaitAsyncTimeoutTask() {
    if (waiter_) {
      waiter_->resetTimeoutTask();
    }
  }

  void resetWaiter() { waiter_ = nullptr; }

  void clear(AutoLockFutexAPI&) {
    if (waiter_) {
      waiter_->resetTimeoutTask();
    }
    waiter_ = nullptr;
  }
  bool cleared(AutoLockFutexAPI&) { return !waiter_; }

  void run(JSContext*, MaybeShuttingDown maybeshuttingdown) final;
  void transferToRuntime() final;
};

AsyncFutexWaiter::~AsyncFutexWaiter() {
  if (notifyTask_) {
    notifyTask_->resetWaiter();
  }
  if (timeoutTask_) {
    timeoutTask_->resetWaiter();
  }
}

}  // namespace js

// https://tc39.es/ecma262/#sec-addwaiter
static void AddWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* node,
                      AutoLockFutexAPI&) {
  FutexWaiterListNode* listHead = sarb->waiters();

  // Step 3: Append waiterRecord to WL.[[Waiters]].
  node->setNext(listHead);
  node->setPrev(listHead->prev());
  listHead->prev()->setNext(node);
  listHead->setPrev(node);
}

// https://tc39.es/ecma262/#sec-removewaiter
static void RemoveWaiterImpl(FutexWaiterListNode* node, AutoLockFutexAPI&) {
  if (!node->prev()) {
    MOZ_ASSERT(!node->next());
    return;
  }

  node->prev()->setNext(node->next());
  node->next()->setPrev(node->prev());

  node->setNext(nullptr);
  node->setPrev(nullptr);
}

// Sync waiters are stack allocated and can simply be removed from the list.
static void RemoveSyncWaiter(SyncFutexWaiter* waiter, AutoLockFutexAPI& lock) {
  RemoveWaiterImpl(waiter, lock);
}

// Async waiters are heap allocated. After removing the waiter, the caller
// is responsible for freeing it. Return the waiter to help enforce this.
[[nodiscard]] AsyncFutexWaiter* RemoveAsyncWaiter(AsyncFutexWaiter* waiter,
                                                  AutoLockFutexAPI& lock) {
  RemoveWaiterImpl(waiter, lock);
  return waiter;
}

FutexWaiterListHead::~FutexWaiterListHead() {
  // Cleanup steps from 5. in SMDOC for Atomics.waitAsync
  // When a SharedArrayRawBuffer is no longer reachable, the contents of its
  // waiters list can no longer be notified. However, they can still resolve if
  // they have an associated timeout. When the list head goes away, we walk
  // through the remaining waiters and clean up the ones that don't have
  // timeouts. We leave the remaining waiters in a free-floating linked list;
  // they will remove themselves as the timeouts fire or the associated runtime
  // shuts down.
  AutoLockHelperThreadState helperLock;
  AutoLockFutexAPI lock;

  FutexWaiterListNode* iter = next();
  while (iter && iter != this) {
    // All remaining FutexWaiters must be async. A sync waiter can only exist if
    // a thread is waiting, and that thread must have a reference to the shared
    // array buffer it's waiting on, so that buffer can't be freed.

    FutexWaiterListNode* next = iter->next();
    AsyncFutexWaiter* removedWaiter =
        RemoveAsyncWaiter(iter->toWaiter()->asAsync(), lock);
    iter = next;

    if (removedWaiter->hasTimeout() &&
        !removedWaiter->timeoutTask()->cleared(lock)) {
      // If a timeout task exists,  allow it to clean up the notify task when it
      // runs. See the comment in WaitAsyncTimeoutTask::run() or the the SMDOC
      // in this file.
      continue;
    }
    // In the case that a timeout task does not exist, the two live raw
    // pointers at this point are WaitAsyncNotifyTask and the
    // AsyncFutexWaiter. We can clean them up here as there is no way to
    // notify them without the SAB or without waiting for the shutdown of the
    // JS::Context. In order to do this, we store the removed waiter in a
    // unique ptr, so that it is cleared after this function, and dispatch and
    // destroy the notify task.
    UniquePtr<AsyncFutexWaiter> ownedWaiter(removedWaiter);
    WaitAsyncNotifyTask* task = ownedWaiter->notifyTask();
    task->setResult(WaitAsyncNotifyTask::Result::Dead, lock);
    task->removeFromCancellableListAndDispatch(helperLock);
  }

  RemoveWaiterImpl(this, lock);
}

// Creates an object to use as the return value of Atomics.waitAsync.
static PlainObject* CreateAsyncResultObject(JSContext* cx, bool async,
                                            HandleValue promiseOrString) {
  Rooted<PlainObject*> resultObject(cx, NewPlainObject(cx));
  if (!resultObject) {
    return nullptr;
  }

  RootedValue isAsync(cx, BooleanValue(async));
  if (!NativeDefineDataProperty(cx, resultObject, cx->names().async, isAsync,
                                JSPROP_ENUMERATE)) {
    return nullptr;
  }

  MOZ_ASSERT_IF(!async, promiseOrString.isString());
  MOZ_ASSERT_IF(async, promiseOrString.isObject() &&
                           promiseOrString.toObject().is<PromiseObject>());
  if (!NativeDefineDataProperty(cx, resultObject, cx->names().value,
                                promiseOrString, JSPROP_ENUMERATE)) {
    return nullptr;
  }

  return resultObject;
}

void WaitAsyncNotifyTask::prepareForCancel() {
  AutoLockFutexAPI lock;
  UniquePtr<AsyncFutexWaiter> waiter(RemoveAsyncWaiter(waiter_, lock));
  waiter->maybeClearTimeout(lock);
}

void WaitAsyncTimeoutTask::run(JSContext* cx,
                               MaybeShuttingDown maybeShuttingDown) {
  AutoLockHelperThreadState helperLock;
  AutoLockFutexAPI lock;

  // If the waiter was notified while this task was enqueued, do nothing.
  if (cleared(lock)) {
    js_delete(this);
    return;
  }

  // Cleanup steps from 3. and 5. lifecycle in SMDOC for Atomics.waitAsync
  // Take ownership of the async waiter, so that it will be freed
  // when we return.
  UniquePtr<AsyncFutexWaiter> asyncWaiter(RemoveAsyncWaiter(waiter_, lock));
  asyncWaiter->resetTimeoutTask();

  // Dispatch a task to resolve the promise with value "timed-out".
  WaitAsyncNotifyTask* task = asyncWaiter->notifyTask();
  task->setResult(WaitAsyncNotifyTask::Result::TimedOut, lock);
  task->removeFromCancellableListAndDispatch(helperLock);
  js_delete(this);
}

void WaitAsyncTimeoutTask::transferToRuntime() {
  // Clear and delete. Clearing this task will result in the cancellable
  // notify task being cleaned up on shutdown, as it can no longer be triggered.
  // In as sense, the task "transfered" for cleanup is the notify task.
  {
    AutoLockFutexAPI lock;
    clear(lock);
  }
  // As we are not managing any state, the runtime is not tracking this task,
  // and we have nothing to run, we can delete.
  js_delete(this);
}

void AsyncFutexWaiter::maybeClearTimeout(AutoLockFutexAPI& lock) {
  if (timeoutTask_) {
    timeoutTask_->clear(lock);
    timeoutTask_ = nullptr;
  }
}

// DoWait Steps 17-31
// https://tc39.es/ecma262/#sec-dowait
template <typename T>
static FutexThread::WaitResult AtomicsWaitAsyncCriticalSection(
    JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, T value,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout,
    Handle<PromiseObject*> promise) {
  // Step 17: Enter critical section.
  // We need to initialize an OffThreadPromiseTask inside this critical section.
  // To avoid deadlock, we claim the helper thread lock first.
  AutoLockHelperThreadState helperThreadLock;

  UniquePtr<WaitAsyncTimeoutTask> timeoutTask;
  {
    AutoLockFutexAPI futexLock;

    // Steps 18-20:
    SharedMem<T*> addr =
        sarb->dataPointerShared().cast<T*>() + (byteOffset / sizeof(T));
    if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
      return FutexThread::WaitResult::NotEqual;
    }

    // Step 21
    bool hasTimeout = timeout.isSome();
    if (hasTimeout && timeout.value().IsZero()) {
      return FutexThread::WaitResult::TimedOut;
    }

    // Steps 22-30
    // To handle potential failures, we split this up into two phases:
    // First, we allocate everything: the notify task, the waiter, and
    // (if necessary) the timeout task. The allocations are managed
    // using unique pointers, which will free them on failure. This
    // phase has no external side-effects.

    // Second, we transfer ownership of the allocations to the right places:
    // the waiter owns the notify task, the shared array buffer owns the waiter,
    // and the event loop owns the timeout task. This phase is infallible.
    auto notifyTask = js::MakeUnique<WaitAsyncNotifyTask>(cx, promise);
    if (!notifyTask) {
      JS_ReportOutOfMemory(cx);
      return FutexThread::WaitResult::Error;
    }
    auto waiter = js::MakeUnique<AsyncFutexWaiter>(cx, byteOffset);
    if (!waiter) {
      JS_ReportOutOfMemory(cx);
      return FutexThread::WaitResult::Error;
    }

    notifyTask->setWaiter(waiter.get());
    waiter->setNotifyTask(notifyTask.get());

    if (hasTimeout) {
      timeoutTask = js::MakeUnique<WaitAsyncTimeoutTask>(waiter.get());
      if (!timeoutTask) {
        JS_ReportOutOfMemory(cx);
        return FutexThread::WaitResult::Error;
      }
      waiter->setTimeoutTask(timeoutTask.get());
    }

    // This is the last fallible operation. If it fails, all allocations
    // will be freed. init has no side-effects if it fails.
    if (!js::OffThreadPromiseTask::InitCancellable(cx, helperThreadLock,
                                                   std::move(notifyTask))) {
      return FutexThread::WaitResult::Error;
    }

    // Below this point, everything is infallible.
    AddWaiter(sarb, waiter.release(), futexLock);
  }  // End of futexLock critical section

  // We dispatch the task after leaving the critical section to avoid
  // potential deadlock if the dispatch callback has internal locking.
  // See bug 1980271.
  if (timeoutTask) {
    OffThreadPromiseRuntimeState& state =
        cx->runtime()->offThreadPromiseState.ref();
    // We are not tracking the dispatch of the timeout task using the
    // OffThreadPromiseRuntimeState, so we ignore the return value. If this
    // fails, the embeddings should call transferToRuntime on timeoutTask
    // which will clear itself, and set the notify task to be cleaned on
    // shutdown.
    (void)state.delayedDispatchToEventLoop(std::move(timeoutTask),
                                           timeout.value().ToMilliseconds());
  }

  // Step 31: Leave critical section.
  return FutexThread::WaitResult::OK;
}

// DoWait steps 12-35
// https://tc39.es/ecma262/#sec-dowait
// This implements the mode=ASYNC case.
template <typename T>
static PlainObject* AtomicsWaitAsync(
    JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, T value,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
  // Step 16a.
  Rooted<PromiseObject*> promiseObject(
      cx, CreatePromiseObjectWithoutResolutionFunctions(cx));
  if (!promiseObject) {
    return nullptr;
  }

  // Steps 17-31
  switch (AtomicsWaitAsyncCriticalSection(cx, sarb, byteOffset, value, timeout,
                                          promiseObject)) {
    case FutexThread::WaitResult::NotEqual: {
      // Steps 16b, 20c-e
      RootedValue msg(cx, StringValue(cx->names().not_equal_));
      return CreateAsyncResultObject(cx, false, msg);
    }
    case FutexThread::WaitResult::TimedOut: {
      // Steps 16b, 21c-e
      RootedValue msg(cx, StringValue(cx->names().timed_out_));
      return CreateAsyncResultObject(cx, false, msg);
    }
    case FutexThread::WaitResult::Error:
      return nullptr;
    case FutexThread::WaitResult::OK:
      break;
  }

  // Steps 15b, 33-35
  RootedValue objectValue(cx, ObjectValue(*promiseObject));
  return CreateAsyncResultObject(cx, true, objectValue);
}

// DoWait steps 12-32
// https://tc39.es/ecma262/#sec-dowait
// This implements the mode=SYNC case.
template <typename T>
static FutexThread::WaitResult AtomicsWait(
    JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, T value,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
  // Validation and other guards should ensure that this does not happen.
  MOZ_ASSERT(sarb, "wait is only applicable to shared memory");

  SharedMem<T*> addr =
      sarb->dataPointerShared().cast<T*>() + (byteOffset / sizeof(T));

  // Steps 17 and 31 (through destructor).
  // This lock also protects the "waiters" field on SharedArrayRawBuffer,
  // and it provides the necessary memory fence.
  AutoLockFutexAPI lock;

  // Steps 18-20.
  if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
    return FutexThread::WaitResult::NotEqual;
  }

  // Steps 14, 22-27
  SyncFutexWaiter w(cx, byteOffset);

  // Steps 28-29
  AddWaiter(sarb, &w, lock);
  FutexThread::WaitResult retval = cx->fx.wait(cx, lock.unique(), timeout);
  RemoveSyncWaiter(&w, lock);

  // Step 32
  return retval;
}

FutexThread::WaitResult js::atomics_wait_impl(
    JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, int32_t value,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
  return AtomicsWait(cx, sarb, byteOffset, value, timeout);
}

FutexThread::WaitResult js::atomics_wait_impl(
    JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, int64_t value,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
  return AtomicsWait(cx, sarb, byteOffset, value, timeout);
}

PlainObject* js::atomics_wait_async_impl(
    JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, int32_t value,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
  return AtomicsWaitAsync(cx, sarb, byteOffset, value, timeout);
}

PlainObject* js::atomics_wait_async_impl(
    JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, int64_t value,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
  return AtomicsWaitAsync(cx, sarb, byteOffset, value, timeout);
}

// https://tc39.es/ecma262/#sec-dowait
// DoWait ( mode, typedArray, index, value, timeout ), steps 8-35.
template <typename T>
static bool DoAtomicsWait(JSContext* cx, bool isAsync,
                          Handle<TypedArrayObject*> unwrappedTypedArray,
                          size_t index, T value, HandleValue timeoutv,
                          MutableHandleValue r) {
  mozilla::Maybe<mozilla::TimeDuration> timeout;
  if (!timeoutv.isUndefined()) {
    // Step 8.
    double timeout_ms;
    if (!ToNumber(cx, timeoutv, &timeout_ms)) {
      return false;
    }

    // Step 9.
    if (!std::isnan(timeout_ms)) {
      if (timeout_ms < 0) {
        timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
      } else if (!std::isinf(timeout_ms)) {
        timeout =
            mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
      }
    }
  }

  // Step 10.
  if (!isAsync && !cx->fx.canWait()) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
    return false;
  }

  // Step 11.
  Rooted<SharedArrayBufferObject*> unwrappedSab(
      cx, unwrappedTypedArray->bufferShared());

  // Step 12
  mozilla::Maybe<size_t> offset = unwrappedTypedArray->byteOffset();
  MOZ_ASSERT(
      offset,
      "offset can't become invalid because shared buffers can only grow");

  // Step 13.
  // The computation will not overflow because range checks have been
  // performed.
  size_t byteIndexInBuffer = index * sizeof(T) + *offset;

  // Steps 14-35.
  if (isAsync) {
    PlainObject* resultObject = atomics_wait_async_impl(
        cx, unwrappedSab->rawBufferObject(), byteIndexInBuffer, value, timeout);
    if (!resultObject) {
      return false;
    }
    r.setObject(*resultObject);
    return true;
  }

  switch (atomics_wait_impl(cx, unwrappedSab->rawBufferObject(),
                            byteIndexInBuffer, value, timeout)) {
    case FutexThread::WaitResult::NotEqual:
      r.setString(cx->names().not_equal_);
      return true;
    case FutexThread::WaitResult::OK:
      r.setString(cx->names().ok);
      return true;
    case FutexThread::WaitResult::TimedOut:
      r.setString(cx->names().timed_out_);
      return true;
    case FutexThread::WaitResult::Error:
      return false;
    default:
      MOZ_CRASH("Should not happen");
  }
}

// https://tc39.es/ecma262/#sec-dowait
// DoWait ( mode, typedArray, index, value, timeout )
static bool DoWait(JSContext* cx, bool isAsync, HandleValue objv,
                   HandleValue index, HandleValue valv, HandleValue timeoutv,
                   MutableHandleValue r) {
  // Steps 1-2.
  Rooted<TypedArrayObject*> unwrappedTypedArray(cx);
  if (!ValidateIntegerTypedArray(cx, objv, true, AccessMode::Read,
                                 &unwrappedTypedArray)) {
    return false;
  }
  MOZ_ASSERT(unwrappedTypedArray->type() == Scalar::Int32 ||
             unwrappedTypedArray->type() == Scalar::BigInt64);

  // Step 3
  if (!unwrappedTypedArray->isSharedMemory()) {
    return ReportBadArrayType(cx);
  }

  // Step 4.
  size_t intIndex;
  if (!ValidateAtomicAccess(cx, unwrappedTypedArray, index, &intIndex)) {
    return false;
  }

  // Step 5
  if (unwrappedTypedArray->type() == Scalar::Int32) {
    // Step 7.
    int32_t value;
    if (!ToInt32(cx, valv, &value)) {
      return false;
    }

    // Steps 8-35.
    return DoAtomicsWait(cx, isAsync, unwrappedTypedArray, intIndex, value,
                         timeoutv, r);
  }

  MOZ_ASSERT(unwrappedTypedArray->type() == Scalar::BigInt64);

  // Step 6.
  RootedBigInt value(cx, ToBigInt(cx, valv));
  if (!value) {
    return false;
  }

  // Steps 8-35.
  return DoAtomicsWait(cx, isAsync, unwrappedTypedArray, intIndex,
                       BigInt::toInt64(value), timeoutv, r);
}

// 24.4.11 Atomics.wait ( typedArray, index, value, timeout )
// https://tc39.es/ecma262/#sec-atomics.wait
static bool atomics_wait(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  HandleValue objv = args.get(0);
  HandleValue index = args.get(1);
  HandleValue valv = args.get(2);
  HandleValue timeoutv = args.get(3);
  MutableHandleValue r = args.rval();

  return DoWait(cx, /*isAsync = */ false, objv, index, valv, timeoutv, r);
}

// Atomics.waitAsync ( typedArray, index, value, timeout )
// https://tc39.es/ecma262/#sec-atomics.waitasync
static bool atomics_wait_async(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  HandleValue objv = args.get(0);
  HandleValue index = args.get(1);
  HandleValue valv = args.get(2);
  HandleValue timeoutv = args.get(3);
  MutableHandleValue r = args.rval();

  return DoWait(cx, /*isAsync = */ true, objv, index, valv, timeoutv, r);
}

// Atomics.notify ( typedArray, index, count ), steps 8-13.
// https://tc39.es/ecma262/#sec-atomics.notify
bool js::atomics_notify_impl(JSContext* cx, SharedArrayRawBuffer* sarb,
                             size_t byteOffset, int64_t count, int64_t* woken) {
  MOZ_ASSERT(woken);

  // Validation should ensure this does not happen.
  MOZ_ASSERT(sarb, "notify is only applicable to shared memory");

  // Step 8
  *woken = 0;

  Rooted<GCVector<PromiseObject*>> promisesToResolve(
      cx, GCVector<PromiseObject*>(cx));
  {
    // Steps 9, 12 (through destructor).
    AutoLockHelperThreadState helperLock;
    AutoLockFutexAPI lock;
    // Steps 10-11
    FutexWaiterListNode* waiterListHead = sarb->waiters();
    FutexWaiterListNode* iter = waiterListHead->next();
    while (count && iter != waiterListHead) {
      FutexWaiter* waiter = iter->toWaiter();
      iter = iter->next();
      if (byteOffset != waiter->offset()) {
        continue;
      }
      if (waiter->isSync()) {
        // For sync waits, the context to notify is currently sleeping.
        // We notify that context (unless it's already been notified by
        // another thread).
        if (!waiter->cx()->fx.isWaiting()) {
          continue;
        }
        waiter->cx()->fx.notify(FutexThread::NotifyExplicit);
      } else {
        // For async waits, we resolve a promise.

        // Steps to clean up case 1. and 2. in SMDOC for Atomics.waitAsync
        // Take ownership of the async waiter, so that it will be
        // freed at the end of this block.
        UniquePtr<AsyncFutexWaiter> asyncWaiter(
            RemoveAsyncWaiter(waiter->asAsync(), lock));
        asyncWaiter->maybeClearTimeout(lock);
        // If we are notifying a waiter that was created by the current
        // context, we resolve the promise directly instead of dispatching
        // a task to the event loop.
        OffThreadPromiseTask* task = asyncWaiter->notifyTask();
        if (waiter->cx() == cx) {
          // Add the promise to a list to resolve as soon as we've left the
          // critical section.
          PromiseObject* promise =
              OffThreadPromiseTask::ExtractAndForget(task, helperLock);
          if (!promisesToResolve.append(promise)) {
            return false;
          }
        } else {
          // Dispatch a task to resolve the promise with value "ok".
          task->removeFromCancellableListAndDispatch(helperLock);
        }
      }
      // Overflow will be a problem only in two cases:
      // (1) 128-bit systems with substantially more than 2^64 bytes of
      //     memory per process, and a very lightweight
      //     Atomics.waitAsync().  Obviously a future problem.
      // (2) Bugs.
      MOZ_RELEASE_ASSERT(*woken < INT64_MAX);
      (*woken)++;
      if (count > 0) {
        --count;
      }
    }
  }

  // Step 10 (reordered)
  // We resolve same-thread promises after we've left the critical section to
  // avoid mutex ordering problems.
  RootedValue resultMsg(cx, StringValue(cx->names().ok));
  for (uint32_t i = 0; i < promisesToResolve.length(); i++) {
    AutoRealm ar(cx, promisesToResolve[i]);
    if (!PromiseObject::resolve(cx, promisesToResolve[i], resultMsg)) {
      MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed());
      return false;
    }
  }

  // Step 13.
  return true;
}

// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.12 Atomics.notify ( typedArray, index, count )
static bool atomics_notify(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);
  HandleValue objv = args.get(0);
  HandleValue index = args.get(1);
  HandleValue countv = args.get(2);
  MutableHandleValue r = args.rval();

  // Step 1.
  Rooted<TypedArrayObject*> unwrappedTypedArray(cx);
  if (!ValidateIntegerTypedArray(cx, objv, true, AccessMode::Read,
                                 &unwrappedTypedArray)) {
    return false;
  }
  MOZ_ASSERT(unwrappedTypedArray->type() == Scalar::Int32 ||
             unwrappedTypedArray->type() == Scalar::BigInt64);

  // Step 2.
  size_t intIndex;
  if (!ValidateAtomicAccess(cx, unwrappedTypedArray, index, &intIndex)) {
    return false;
  }

  // Steps 3-4.
  int64_t count;
  if (countv.isUndefined()) {
    count = -1;
  } else {
    double dcount;
    if (!ToInteger(cx, countv, &dcount)) {
      return false;
    }
    if (dcount < 0.0) {
      dcount = 0.0;
    }
    count = dcount < double(1ULL << 63) ? int64_t(dcount) : -1;
  }

  // https://github.com/tc39/ecma262/pull/1908
  if (!unwrappedTypedArray->isSharedMemory()) {
    r.setInt32(0);
    return true;
  }

  // Step 5.
  Rooted<SharedArrayBufferObject*> unwrappedSab(
      cx, unwrappedTypedArray->bufferShared());

  // Step 6.
  mozilla::Maybe<size_t> offset = unwrappedTypedArray->byteOffset();
  MOZ_ASSERT(
      offset,
      "offset can't become invalid because shared buffers can only grow");

  // Steps 7-9.
  // The computation will not overflow because range checks have been
  // performed.
  size_t elementSize = Scalar::byteSize(unwrappedTypedArray->type());
  size_t indexedPosition = intIndex * elementSize + *offset;

  // Steps 10-16.

  int64_t woken = 0;
  if (!atomics_notify_impl(cx, unwrappedSab->rawBufferObject(), indexedPosition,
                           count, &woken)) {
    return false;
  }

  r.setNumber(double(woken));

  return true;
}

/**
 * Atomics.pause ( [ N ] )
 *
 * https://tc39.es/proposal-atomics-microwait/
 */

static bool atomics_pause(JSContext* cx, unsigned argc, Value* vp) {
  CallArgs args = CallArgsFromVp(argc, vp);

  // Step 1.
  if (args.hasDefined(0)) {
    if (!args[0].isNumber() || !IsInteger(args[0].toNumber())) {
      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                JSMSG_ATOMICS_PAUSE_BAD_COUNT);
      return false;
    }
  }

  // Step 2.
  //
  // We ignore the iteration count when not inlining this operation.
  jit::AtomicOperations::pause();

  // Step 3.
  args.rval().setUndefined();
  return true;
}

/* static */
bool js::FutexThread::initialize() {
  MOZ_ASSERT(!lock_);
  lock_ = js_new<js::Mutex>(mutexid::FutexThread);
  return lock_ != nullptr;
}

/* static */
void js::FutexThread::destroy() {
  if (lock_) {
    js::Mutex* lock = lock_;
    js_delete(lock);
    lock_ = nullptr;
  }
}

/* static */
void js::FutexThread::lock() {
  // Load the atomic pointer.
  js::Mutex* lock = lock_;

  lock->lock();
}

/* static */ mozilla::Atomic<js::Mutex*, mozilla::SequentiallyConsistent>
    FutexThread::lock_;

/* static */
void js::FutexThread::unlock() {
  // Load the atomic pointer.
  js::Mutex* lock = lock_;

  lock->unlock();
}

js::FutexThread::FutexThread()
    : cond_(nullptr), state_(Idle), canWait_(false) {}

bool js::FutexThread::initInstance() {
  MOZ_ASSERT(lock_);
  cond_ = js_new<js::ConditionVariable>();
  return cond_ != nullptr;
}

void js::FutexThread::destroyInstance() {
  if (cond_) {
    js_delete(cond_);
  }
}

bool js::FutexThread::isWaiting() {
  // When a worker is awoken for an interrupt it goes into state
  // WaitingNotifiedForInterrupt for a short time before it actually
  // wakes up and goes into WaitingInterrupted.  In those states the
  // worker is still waiting, and if an explicit notify arrives the
  // worker transitions to Woken.  See further comments in
  // FutexThread::wait().
  return state_ == Waiting || state_ == WaitingInterrupted ||
         state_ == WaitingNotifiedForInterrupt;
}

FutexThread::WaitResult js::FutexThread::wait(
    JSContext* cx, js::UniqueLock<js::Mutex>& locked,
    const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
  MOZ_ASSERT(&cx->fx == this);
  MOZ_ASSERT(cx->fx.canWait());
  MOZ_ASSERT(state_ == Idle || state_ == WaitingInterrupted);

  // Disallow waiting when a runtime is processing an interrupt.
  // See explanation below.

  if (state_ == WaitingInterrupted) {
    UnlockGuard unlock(locked);
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_ATOMICS_WAIT_NOT_ALLOWED);
    return WaitResult::Error;
  }

  // Go back to Idle after returning.
  auto onFinish = mozilla::MakeScopeExit([&] { state_ = Idle; });

  const bool isTimed = timeout.isSome();

  auto finalEnd = timeout.map([](const mozilla::TimeDuration& timeout) {
    return mozilla::TimeStamp::Now() + timeout;
  });

  // 4000s is about the longest timeout slice that is guaranteed to
  // work cross-platform.
  auto maxSlice = mozilla::TimeDuration::FromSeconds(4000.0);

  for (;;) {
    // If we are doing a timed wait, calculate the end time for this wait
    // slice.
    auto sliceEnd = finalEnd.map([&](mozilla::TimeStamp& finalEnd) {
      auto sliceEnd = mozilla::TimeStamp::Now() + maxSlice;
      if (finalEnd < sliceEnd) {
        sliceEnd = finalEnd;
      }
      return sliceEnd;
    });

    state_ = Waiting;

    MOZ_ASSERT((cx->runtime()->beforeWaitCallback == nullptr) ==
               (cx->runtime()->afterWaitCallback == nullptr));
    mozilla::DebugOnly<bool> callbacksPresent =
        cx->runtime()->beforeWaitCallback != nullptr;

    void* cookie = nullptr;
    uint8_t clientMemory[JS::WAIT_CALLBACK_CLIENT_MAXMEM];
    if (cx->runtime()->beforeWaitCallback) {
      cookie = (*cx->runtime()->beforeWaitCallback)(clientMemory);
    }

    if (isTimed) {
      (void)cond_->wait_until(locked, *sliceEnd);
    } else {
      cond_->wait(locked);
    }

    MOZ_ASSERT((cx->runtime()->afterWaitCallback != nullptr) ==
               callbacksPresent);
    if (cx->runtime()->afterWaitCallback) {
      (*cx->runtime()->afterWaitCallback)(cookie);
    }

    switch (state_) {
      case FutexThread::Waiting:
        // Timeout or spurious wakeup.
        if (isTimed) {
          auto now = mozilla::TimeStamp::Now();
          if (now >= *finalEnd) {
            return WaitResult::TimedOut;
          }
        }
        break;

      case FutexThread::Woken:
        return WaitResult::OK;

      case FutexThread::WaitingNotifiedForInterrupt:
        // The interrupt handler may reenter the engine.  In that case
        // there are two complications:
        //
        // - The waiting thread is not actually waiting on the
        //   condition variable so we have to record that it
        //   should be woken when the interrupt handler returns.
        //   To that end, we flag the thread as interrupted around
        //   the interrupt and check state_ when the interrupt
        //   handler returns.  A notify() call that reaches the
        //   runtime during the interrupt sets state_ to Woken.
        //
        // - It is in principle possible for wait() to be
        //   reentered on the same thread/runtime and waiting on the
        //   same location and to yet again be interrupted and enter
        //   the interrupt handler.  In this case, it is important
        //   that when another agent notifies waiters, all waiters using
        //   the same runtime on the same location are woken in LIFO
        //   order; FIFO may be the required order, but FIFO would
        //   fail to wake up the innermost call.  Interrupts are
        //   outside any spec anyway.  Also, several such suspended
        //   waiters may be woken at a time.
        //
        //   For the time being we disallow waiting from within code
        //   that runs from within an interrupt handler; this may
        //   occasionally (very rarely) be surprising but is
        //   expedient.  Other solutions exist, see bug #1131943.  The
        //   code that performs the check is above, at the head of
        //   this function.

        state_ = WaitingInterrupted;
        {
          UnlockGuard unlock(locked);
          if (!cx->handleInterrupt()) {
            return WaitResult::Error;
          }
        }
        if (state_ == Woken) {
          return WaitResult::OK;
        }
        break;

      default:
        MOZ_CRASH("Bad FutexState in wait()");
    }
  }
}

void js::FutexThread::notify(NotifyReason reason) {
  MOZ_ASSERT(isWaiting());

  if ((state_ == WaitingInterrupted || state_ == WaitingNotifiedForInterrupt) &&
      reason == NotifyExplicit) {
    state_ = Woken;
    return;
  }
  switch (reason) {
    case NotifyExplicit:
      state_ = Woken;
      break;
    case NotifyForJSInterrupt:
      if (state_ == WaitingNotifiedForInterrupt) {
        return;
      }
      state_ = WaitingNotifiedForInterrupt;
      break;
    default:
      MOZ_CRASH("bad NotifyReason in FutexThread::notify()");
  }
  cond_->notify_all();
}

const JSFunctionSpec AtomicsMethods[] = {
    JS_INLINABLE_FN("compareExchange", atomics_compareExchange, 40,
                    AtomicsCompareExchange),
    JS_INLINABLE_FN("load", atomics_load, 20, AtomicsLoad),
    JS_INLINABLE_FN("store", atomics_store, 30, AtomicsStore),
    JS_INLINABLE_FN("exchange", atomics_exchange, 30, AtomicsExchange),
    JS_INLINABLE_FN("add", atomics_add, 30, AtomicsAdd),
    JS_INLINABLE_FN("sub", atomics_sub, 30, AtomicsSub),
    JS_INLINABLE_FN("and", atomics_and, 30, AtomicsAnd),
    JS_INLINABLE_FN("or", atomics_or, 30, AtomicsOr),
    JS_INLINABLE_FN("xor", atomics_xor, 30, AtomicsXor),
    JS_INLINABLE_FN("isLockFree", atomics_isLockFree, 10, AtomicsIsLockFree),
    JS_FN("wait", atomics_wait, 40),
    JS_FN("waitAsync", atomics_wait_async, 40),
    JS_FN("notify", atomics_notify, 30),
    JS_FN("wake", atomics_notify, 30),  // Legacy name
    JS_INLINABLE_FN("pause", atomics_pause, 00, AtomicsPause),
    JS_FS_END,
};

static const JSPropertySpec AtomicsProperties[] = {
    JS_STRING_SYM_PS(toStringTag, "Atomics", JSPROP_READONLY),
    JS_PS_END,
};

static JSObject* CreateAtomicsObject(JSContext* cx, JSProtoKey key) {
  RootedObject proto(cx, &cx->global()->getObjectPrototype());
  return NewTenuredObjectWithGivenProto(cx, &AtomicsObject::class_, proto);
}

static const ClassSpec AtomicsClassSpec = {
    CreateAtomicsObject,
    nullptr,
    AtomicsMethods,
    AtomicsProperties,
};

const JSClass AtomicsObject::class_ = {
    "Atomics",
    JSCLASS_HAS_CACHED_PROTO(JSProto_Atomics),
    JS_NULL_CLASS_OPS,
    &AtomicsClassSpec,
};

Messung V0.5 in Prozent
C=87 H=95 G=90

¤ Dauer der Verarbeitung: 0.36 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.