1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-26 13:58:49 +00:00

Support BigInt Atomics waiters

This commit is contained in:
Basilisk-Dev
2026-05-15 20:22:42 -04:00
committed by roytam1
parent 366476589f
commit 8b09714bbc
2 changed files with 205 additions and 15 deletions
+165 -15
View File
@@ -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<TypedArrayObject*> view,
return true;
}
static bool
IsWaitableTypedArray(Scalar::Type viewType)
{
return viewType == Scalar::Int32 || viewType == Scalar::BigInt64;
}
static uint32_t
GetWaiterByteOffset(Handle<TypedArrayObject*> 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<int64_t>(bits))
: BigInt::createFromUint64(cx, bits);
if (!bigint)
return false;
result.setBigInt(bigint);
return true;
}
static bool
WaitValueMatches(Handle<TypedArrayObject*> view, uint32_t offset, uint64_t expected)
{
SharedMem<void*> viewData = view->viewDataShared();
if (view->type() == Scalar::BigInt64) {
return jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<uint64_t*>() + offset) ==
expected;
}
return uint32_t(jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<int32_t*>() + offset)) ==
uint32_t(expected);
}
static int32_t
CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
SharedMem<void*> 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<uint64_t*>() + 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<int64_t*>() + 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<uint64_t*>() + 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<uint64_t*>() + offset,
value);
r.setBigInt(bigint);
return true;
}
uint64_t result = jit::AtomicOperations::exchangeSeqCst(
view->viewDataShared().cast<uint64_t*>() + 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<uint64_t*> addr = view->viewDataShared().cast<uint64_t*>() + 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<TypedArrayObject*> 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<mozilla::TimeDuration> 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<int32_t*> addr = view->viewDataShared().cast<int32_t*>() + 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<SharedArrayBufferObject*> 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<TypedArrayObject*> 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<mozilla::TimeDuration> timeout;
if (!GetWaitTimeout(cx, timeoutv, &timeout))
return false;
@@ -1070,8 +1219,7 @@ js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp)
{
AutoLockFutexAPI lock;
SharedMem<int32_t*> addr = view->viewDataShared().cast<int32_t*>() + 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<TypedArrayObject*> 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<double>();
+40
View File
@@ -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")