diff --git a/js/src/builtin/AtomicsObject.cpp b/js/src/builtin/AtomicsObject.cpp index 72b5ccdcc5..f917db9b21 100644 --- a/js/src/builtin/AtomicsObject.cpp +++ b/js/src/builtin/AtomicsObject.cpp @@ -47,6 +47,7 @@ #include "builtin/AtomicsObject.h" #include "mozilla/Atomics.h" +#include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/Unused.h" @@ -59,6 +60,7 @@ #include "jit/AtomicOperations.h" #include "jit/InlinableNatives.h" #include "js/Class.h" +#include "vm/BigIntType.h" #include "vm/GlobalObject.h" #include "vm/HelperThreads.h" #include "vm/Time.h" @@ -123,6 +125,68 @@ GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle view, return true; } +static bool +IsWaitableTypedArray(Scalar::Type viewType) +{ + return viewType == Scalar::Int32 || viewType == Scalar::BigInt64; +} + +static uint32_t +GetWaiterByteOffset(Handle view, uint32_t offset) +{ + return view->byteOffset() + offset * TypedArrayElemSize(view->type()); +} + +static uint64_t +BigIntToRawBits(Scalar::Type viewType, BigInt* value) +{ + if (viewType == Scalar::BigInt64) + return uint64_t(BigInt::toInt64(value)); + MOZ_ASSERT(viewType == Scalar::BigUint64); + return BigInt::toUint64(value); +} + +static bool +BigIntToRawBits(JSContext* cx, Scalar::Type viewType, HandleValue value, uint64_t* bits) +{ + MOZ_ASSERT(Scalar::isBigIntType(viewType)); + + RootedBigInt bigint(cx, ToBigInt(cx, value)); + if (!bigint) + return false; + + *bits = BigIntToRawBits(viewType, bigint); + return true; +} + +static bool +SetBigIntResult(JSContext* cx, Scalar::Type viewType, uint64_t bits, MutableHandleValue result) +{ + MOZ_ASSERT(Scalar::isBigIntType(viewType)); + + BigInt* bigint = viewType == Scalar::BigInt64 + ? BigInt::createFromInt64(cx, mozilla::BitwiseCast(bits)) + : BigInt::createFromUint64(cx, bits); + if (!bigint) + return false; + + result.setBigInt(bigint); + return true; +} + +static bool +WaitValueMatches(Handle view, uint32_t offset, uint64_t expected) +{ + SharedMem viewData = view->viewDataShared(); + if (view->type() == Scalar::BigInt64) { + return jit::AtomicOperations::loadSafeWhenRacy(viewData.cast() + offset) == + expected; + } + + return uint32_t(jit::AtomicOperations::loadSafeWhenRacy(viewData.cast() + offset)) == + uint32_t(expected); +} + static int32_t CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate, SharedMem viewData, uint32_t offset, bool* badArrayType = nullptr) @@ -193,6 +257,20 @@ js::atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp) uint32_t offset; if (!GetTypedArrayIndex(cx, idxv, view, &offset)) return false; + + if (Scalar::isBigIntType(view->type())) { + uint64_t oldCandidate; + if (!BigIntToRawBits(cx, view->type(), oldv, &oldCandidate)) + return false; + uint64_t newCandidate; + if (!BigIntToRawBits(cx, view->type(), newv, &newCandidate)) + return false; + + uint64_t result = jit::AtomicOperations::compareExchangeSeqCst( + view->viewDataShared().cast() + offset, oldCandidate, newCandidate); + return SetBigIntResult(cx, view->type(), result, r); + } + int32_t oldCandidate; if (!ToInt32(cx, oldv, &oldCandidate)) return false; @@ -261,6 +339,22 @@ js::atomics_load(JSContext* cx, unsigned argc, Value* vp) r.setNumber(v); return true; } + case Scalar::BigInt64: { + int64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast() + offset); + BigInt* bigint = BigInt::createFromInt64(cx, v); + if (!bigint) + return false; + r.setBigInt(bigint); + return true; + } + case Scalar::BigUint64: { + uint64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast() + offset); + BigInt* bigint = BigInt::createFromUint64(cx, v); + if (!bigint) + return false; + r.setBigInt(bigint); + return true; + } default: return ReportBadArrayType(cx); } @@ -339,6 +433,25 @@ ExchangeOrStore(JSContext* cx, unsigned argc, Value* vp) uint32_t offset; if (!GetTypedArrayIndex(cx, idxv, view, &offset)) return false; + + if (Scalar::isBigIntType(view->type())) { + RootedBigInt bigint(cx, ToBigInt(cx, valv)); + if (!bigint) + return false; + + uint64_t value = BigIntToRawBits(view->type(), bigint); + if (op == DoStore) { + jit::AtomicOperations::storeSeqCst(view->viewDataShared().cast() + offset, + value); + r.setBigInt(bigint); + return true; + } + + uint64_t result = jit::AtomicOperations::exchangeSeqCst( + view->viewDataShared().cast() + offset, value); + return SetBigIntResult(cx, view->type(), result, r); + } + double integerValue; if (!ToInteger(cx, valv, &integerValue)) return false; @@ -382,6 +495,24 @@ AtomicsBinop(JSContext* cx, HandleValue objv, HandleValue idxv, HandleValue valv uint32_t offset; if (!GetTypedArrayIndex(cx, idxv, view, &offset)) return false; + + if (Scalar::isBigIntType(view->type())) { + uint64_t value; + if (!BigIntToRawBits(cx, view->type(), valv, &value)) + return false; + + SharedMem addr = view->viewDataShared().cast() + offset; + uint64_t old = jit::AtomicOperations::loadSeqCst(addr); + for (;;) { + uint64_t replacement = T::operate64(old, value); + uint64_t observed = jit::AtomicOperations::compareExchangeSeqCst(addr, old, + replacement); + if (observed == old) + return SetBigIntResult(cx, view->type(), old, r); + old = observed; + } + } + int32_t numberValue; if (!ToInt32(cx, valv, &numberValue)) return false; @@ -436,6 +567,7 @@ class PerformAdd public: INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst) static int32_t perform(int32_t x, int32_t y) { return x + y; } + static uint64_t operate64(uint64_t x, uint64_t y) { return x + y; } }; bool @@ -450,6 +582,7 @@ class PerformSub public: INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst) static int32_t perform(int32_t x, int32_t y) { return x - y; } + static uint64_t operate64(uint64_t x, uint64_t y) { return x - y; } }; bool @@ -464,6 +597,7 @@ class PerformAnd public: INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst) static int32_t perform(int32_t x, int32_t y) { return x & y; } + static uint64_t operate64(uint64_t x, uint64_t y) { return x & y; } }; bool @@ -478,6 +612,7 @@ class PerformOr public: INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst) static int32_t perform(int32_t x, int32_t y) { return x | y; } + static uint64_t operate64(uint64_t x, uint64_t y) { return x | y; } }; bool @@ -492,6 +627,7 @@ class PerformXor public: INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst) static int32_t perform(int32_t x, int32_t y) { return x ^ y; } + static uint64_t operate64(uint64_t x, uint64_t y) { return x ^ y; } }; bool @@ -736,7 +872,7 @@ class FutexWaiter bool isWaiting() const; void notify(); - uint32_t offset; // int32 element index within the SharedArrayBuffer + uint32_t offset; // byte offset within the SharedArrayBuffer enum WaiterKind { Sync, Async @@ -970,14 +1106,21 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp) Rooted view(cx, nullptr); if (!GetSharedTypedArray(cx, objv, &view)) return false; - if (view->type() != Scalar::Int32) + if (!IsWaitableTypedArray(view->type())) return ReportBadArrayType(cx); uint32_t offset; if (!GetTypedArrayIndex(cx, idxv, view, &offset)) return false; - int32_t value; - if (!ToInt32(cx, valv, &value)) - return false; + uint64_t value = 0; + if (view->type() == Scalar::BigInt64) { + if (!BigIntToRawBits(cx, view->type(), valv, &value)) + return false; + } else { + int32_t int32Value; + if (!ToInt32(cx, valv, &int32Value)) + return false; + value = uint32_t(int32Value); + } mozilla::Maybe timeout; if (!GetWaitTimeout(cx, timeoutv, &timeout)) return false; @@ -989,8 +1132,7 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp) // and it provides the necessary memory fence. AutoLockFutexAPI lock; - SharedMem addr = view->viewDataShared().cast() + offset; - if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) { + if (!WaitValueMatches(view, offset, value)) { r.setString(cx->names().futexNotEqual); return true; } @@ -998,7 +1140,7 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp) Rooted sab(cx, view->bufferShared()); SharedArrayRawBuffer* sarb = sab->rawBufferObject(); - FutexWaiter w(offset, rt); + FutexWaiter w(GetWaiterByteOffset(view, offset), rt); AddWaiter(sarb, &w); FutexRuntime::WaitResult result = FutexRuntime::FutexOK; @@ -1031,14 +1173,21 @@ js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp) Rooted view(cx, nullptr); if (!GetSharedTypedArray(cx, objv, &view)) return false; - if (view->type() != Scalar::Int32) + if (!IsWaitableTypedArray(view->type())) return ReportBadArrayType(cx); uint32_t offset; if (!GetTypedArrayIndex(cx, idxv, view, &offset)) return false; - int32_t value; - if (!ToInt32(cx, valv, &value)) - return false; + uint64_t value = 0; + if (view->type() == Scalar::BigInt64) { + if (!BigIntToRawBits(cx, view->type(), valv, &value)) + return false; + } else { + int32_t int32Value; + if (!ToInt32(cx, valv, &int32Value)) + return false; + value = uint32_t(int32Value); + } mozilla::Maybe timeout; if (!GetWaitTimeout(cx, timeoutv, &timeout)) return false; @@ -1070,8 +1219,7 @@ js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp) { AutoLockFutexAPI lock; - SharedMem addr = view->viewDataShared().cast() + offset; - if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) { + if (!WaitValueMatches(view, offset, value)) { immediateResult.setString(cx->names().futexNotEqual); } else if (timeout.isSome() && timeout->ToMilliseconds() == 0.0) { immediateResult.setString(cx->names().futexTimedOut); @@ -1083,6 +1231,7 @@ js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp) return false; } + task->waiter()->offset = GetWaiterByteOffset(view, offset); AddWaiter(sarb, task->waiter()); task->setInWaiterList(); isAsync = true; @@ -1111,11 +1260,12 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp) Rooted view(cx, nullptr); if (!GetSharedTypedArray(cx, objv, &view)) return false; - if (view->type() != Scalar::Int32) + if (!IsWaitableTypedArray(view->type())) return ReportBadArrayType(cx); uint32_t offset; if (!GetTypedArrayIndex(cx, idxv, view, &offset)) return false; + offset = GetWaiterByteOffset(view, offset); double count; if (countv.isUndefined()) { count = mozilla::PositiveInfinity(); diff --git a/js/src/tests/non262/Atomics/waitAsync.js b/js/src/tests/non262/Atomics/waitAsync.js index ae5c876e3d..a2b23a4f80 100644 --- a/js/src/tests/non262/Atomics/waitAsync.js +++ b/js/src/tests/non262/Atomics/waitAsync.js @@ -31,6 +31,46 @@ if (typeof SharedArrayBuffer === "function" && typeof Atomics === "object" && }); drainJobQueue(); assertEq(timedOut, "timed-out"); + + if (typeof BigInt64Array === "function") { + const bigSab = new SharedArrayBuffer(16); + const i64 = new BigInt64Array(bigSab); + const u64 = new BigUint64Array(bigSab); + + assertEq(Atomics.store(i64, 0, -1n), -1n); + assertEq(Atomics.load(i64, 0), -1n); + assertEq(Atomics.load(u64, 0), 18446744073709551615n); + assertEq(Atomics.exchange(i64, 0, 7n), -1n); + assertEq(Atomics.compareExchange(i64, 0, 7n, 10n), 7n); + assertEq(Atomics.add(i64, 0, 5n), 10n); + assertEq(Atomics.load(i64, 0), 15n); + assertEq(Atomics.sub(i64, 0, 20n), 15n); + assertEq(Atomics.load(i64, 0), -5n); + assertEq(Atomics.and(u64, 0, 7n), 18446744073709551611n); + assertEq(Atomics.or(u64, 0, 8n), 3n); + assertEq(Atomics.xor(u64, 0, 15n), 11n); + + assertThrowsInstanceOf(() => Atomics.waitAsync(u64, 0, 0n), TypeError); + + result = Atomics.waitAsync(i64, 1, 1n, 10); + assertEq(result.async, false); + assertEq(result.value, "not-equal"); + + result = Atomics.waitAsync(i64, 1, 0n, 0); + assertEq(result.async, false); + assertEq(result.value, "timed-out"); + + let offsetView = new BigInt64Array(bigSab, 8); + result = Atomics.waitAsync(offsetView, 0, 0n); + assertEq(result.async, true); + notified = undefined; + result.value.then(value => { + notified = value; + }); + assertEq(Atomics.notify(i64, 1, 1), 1); + drainJobQueue(); + assertEq(notified, "ok"); + } } if (typeof reportCompare === "function")