Bug 1674994 part 1 - Make Atomics work with large ArrayBuffers. r=lth

The remaining three uint32_t uses in AtomicsObject.h/cpp are unrelated.

This also fixes the byteLength getter on ArrayBuffer and SharedArrayBuffer.

Differential Revision: https://phabricator.services.mozilla.com/D95688
This commit is contained in:
Jan de Mooij 2020-11-05 11:11:30 +00:00
Родитель 5957a1ba9c
Коммит 9503e4fa46
7 изменённых файлов: 94 добавлений и 34 удалений

Просмотреть файл

@ -1233,8 +1233,10 @@ class MutableWrappedPtrOperations<JS::Value, Wrapper>
void setInfinity() { set(JS::InfinityValue()); }
void setBoolean(bool b) { set(JS::BooleanValue(b)); }
void setMagic(JSWhyMagic why) { set(JS::MagicValue(why)); }
void setNumber(uint32_t ui) { set(JS::NumberValue(ui)); }
void setNumber(double d) { set(JS::NumberValue(d)); }
template <typename T>
void setNumber(T t) {
set(JS::NumberValue(t));
}
void setString(JSString* str) { set(JS::StringValue(str)); }
void setSymbol(JS::Symbol* sym) { set(JS::SymbolValue(sym)); }
void setBigInt(JS::BigInt* bi) { set(JS::BigIntValue(bi)); }

Просмотреть файл

@ -112,11 +112,11 @@ static bool ValidateIntegerTypedArray(
// 24.4.1.2 ValidateAtomicAccess ( typedArray, requestIndex )
static bool ValidateAtomicAccess(JSContext* cx,
Handle<TypedArrayObject*> typedArray,
HandleValue requestIndex, uint32_t* index) {
HandleValue requestIndex, size_t* index) {
// Step 1 (implicit).
MOZ_ASSERT(!typedArray->hasDetachedBuffer());
uint32_t length = typedArray->length().deprecatedGetUint32();
size_t length = typedArray->length().get();
// Step 2.
uint64_t accessIndex;
@ -130,7 +130,7 @@ static bool ValidateAtomicAccess(JSContext* cx,
}
// Step 6.
*index = uint32_t(accessIndex);
*index = size_t(accessIndex);
return true;
}
@ -251,7 +251,7 @@ bool AtomicAccess(JSContext* cx, HandleValue obj, HandleValue index, Op op) {
}
// Step 2.
uint32_t intIndex;
size_t intIndex;
if (!ValidateAtomicAccess(cx, unwrappedTypedArray, index, &intIndex)) {
return false;
}
@ -286,7 +286,7 @@ bool AtomicAccess(JSContext* cx, HandleValue obj, HandleValue index, Op op) {
template <typename T>
static SharedMem<T*> TypedArrayData(JSContext* cx, TypedArrayObject* typedArray,
uint32_t index) {
size_t index) {
if (typedArray->hasDetachedBuffer()) {
ReportDetachedArrayBuffer(cx);
return {};
@ -307,7 +307,7 @@ static bool atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp) {
return AtomicAccess(
cx, typedArray, index,
[cx, &args](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
uint32_t index) {
size_t index) {
using T = typename decltype(ops)::Type;
HandleValue expectedValue = args.get(2);
@ -344,7 +344,7 @@ static bool atomics_load(JSContext* cx, unsigned argc, Value* vp) {
return AtomicAccess(
cx, typedArray, index,
[cx, &args](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
uint32_t index) {
size_t index) {
using T = typename decltype(ops)::Type;
SharedMem<T*> addr = TypedArrayData<T>(cx, unwrappedTypedArray, index);
@ -369,7 +369,7 @@ static bool atomics_store(JSContext* cx, unsigned argc, Value* vp) {
return AtomicAccess(
cx, typedArray, index,
[cx, &args](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
uint32_t index) {
size_t index) {
using T = typename decltype(ops)::Type;
HandleValue value = args.get(2);
@ -399,7 +399,7 @@ static bool AtomicReadModifyWrite(JSContext* cx, const CallArgs& args,
return AtomicAccess(
cx, typedArray, index,
[cx, &args, op](auto ops, Handle<TypedArrayObject*> unwrappedTypedArray,
uint32_t index) {
size_t index) {
using T = typename decltype(ops)::Type;
HandleValue value = args.get(2);
@ -524,10 +524,10 @@ namespace js {
class FutexWaiter {
public:
FutexWaiter(uint32_t offset, JSContext* cx)
FutexWaiter(size_t offset, JSContext* cx)
: offset(offset), cx(cx), lower_pri(nullptr), back(nullptr) {}
uint32_t offset; // int32 element index within the SharedArrayBuffer
size_t offset; // int32 element index within the SharedArrayBuffer
JSContext* cx; // The waiting thread
FutexWaiter* lower_pri; // Lower priority nodes in circular doubly-linked
// list of waiters
@ -556,7 +556,7 @@ class AutoLockFutexAPI {
// 24.4.11 Atomics.wait ( typedArray, index, value, timeout ), steps 8-9, 14-25.
template <typename T>
static FutexThread::WaitResult AtomicsWait(
JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset, T value,
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");
@ -610,14 +610,14 @@ static FutexThread::WaitResult AtomicsWait(
}
FutexThread::WaitResult js::atomics_wait_impl(
JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset,
int32_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
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, uint32_t byteOffset,
int64_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, int64_t value,
const mozilla::Maybe<mozilla::TimeDuration>& timeout) {
return AtomicsWait(cx, sarb, byteOffset, value, timeout);
}
@ -626,7 +626,7 @@ FutexThread::WaitResult js::atomics_wait_impl(
template <typename T>
static bool DoAtomicsWait(JSContext* cx,
Handle<TypedArrayObject*> unwrappedTypedArray,
uint32_t index, T value, HandleValue timeoutv,
size_t index, T value, HandleValue timeoutv,
MutableHandleValue r) {
mozilla::Maybe<mozilla::TimeDuration> timeout;
if (!timeoutv.isUndefined()) {
@ -652,12 +652,12 @@ static bool DoAtomicsWait(JSContext* cx,
cx, unwrappedTypedArray->bufferShared());
// Step 11.
uint32_t offset = unwrappedTypedArray->byteOffset().deprecatedGetUint32();
size_t offset = unwrappedTypedArray->byteOffset().get();
// Steps 12-13.
// The computation will not overflow because range checks have been
// performed.
uint32_t indexedPosition = index * sizeof(T) + offset;
size_t indexedPosition = index * sizeof(T) + offset;
// Steps 8-9, 14-25.
switch (atomics_wait_impl(cx, unwrappedSab->rawBufferObject(),
@ -702,7 +702,7 @@ static bool atomics_wait(JSContext* cx, unsigned argc, Value* vp) {
}
// Step 2.
uint32_t intIndex;
size_t intIndex;
if (!ValidateAtomicAccess(cx, unwrappedTypedArray, index, &intIndex)) {
return false;
}
@ -733,7 +733,7 @@ static bool atomics_wait(JSContext* cx, unsigned argc, Value* vp) {
// ES2021 draft rev bd868f20b8c574ad6689fba014b62a1dba819e56
// 24.4.12 Atomics.notify ( typedArray, index, count ), steps 10-16.
int64_t js::atomics_notify_impl(SharedArrayRawBuffer* sarb, uint32_t byteOffset,
int64_t js::atomics_notify_impl(SharedArrayRawBuffer* sarb, size_t byteOffset,
int64_t count) {
// Validation should ensure this does not happen.
MOZ_ASSERT(sarb, "notify is only applicable to shared memory");
@ -790,7 +790,7 @@ static bool atomics_notify(JSContext* cx, unsigned argc, Value* vp) {
unwrappedTypedArray->type() == Scalar::BigInt64);
// Step 2.
uint32_t intIndex;
size_t intIndex;
if (!ValidateAtomicAccess(cx, unwrappedTypedArray, index, &intIndex)) {
return false;
}
@ -821,13 +821,13 @@ static bool atomics_notify(JSContext* cx, unsigned argc, Value* vp) {
cx, unwrappedTypedArray->bufferShared());
// Step 6.
uint32_t offset = unwrappedTypedArray->byteOffset().deprecatedGetUint32();
size_t offset = unwrappedTypedArray->byteOffset().get();
// Steps 7-9.
// The computation will not overflow because range checks have been
// performed.
uint32_t elementSize = Scalar::byteSize(unwrappedTypedArray->type());
uint32_t indexedPosition = intIndex * elementSize + offset;
size_t elementSize = Scalar::byteSize(unwrappedTypedArray->type());
size_t indexedPosition = intIndex * elementSize + offset;
// Steps 10-16.
r.setNumber(double(atomics_notify_impl(unwrappedSab->rawBufferObject(),

Просмотреть файл

@ -123,20 +123,20 @@ class FutexThread {
// Go to sleep if the int32_t value at the given address equals `value`.
MOZ_MUST_USE FutexThread::WaitResult atomics_wait_impl(
JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset,
int32_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout);
JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, int32_t value,
const mozilla::Maybe<mozilla::TimeDuration>& timeout);
// Go to sleep if the int64_t value at the given address equals `value`.
MOZ_MUST_USE FutexThread::WaitResult atomics_wait_impl(
JSContext* cx, SharedArrayRawBuffer* sarb, uint32_t byteOffset,
int64_t value, const mozilla::Maybe<mozilla::TimeDuration>& timeout);
JSContext* cx, SharedArrayRawBuffer* sarb, size_t byteOffset, int64_t value,
const mozilla::Maybe<mozilla::TimeDuration>& timeout);
// Notify some waiters on the given address. If `count` is negative then notify
// all. The return value is nonnegative and is the number of waiters woken. If
// the number of waiters woken exceeds INT64_MAX then this never returns. If
// `count` is nonnegative then the return value is never greater than `count`.
MOZ_MUST_USE int64_t atomics_notify_impl(SharedArrayRawBuffer* sarb,
uint32_t byteOffset, int64_t count);
size_t byteOffset, int64_t count);
} /* namespace js */

Просмотреть файл

@ -0,0 +1,57 @@
// |jit-test| skip-if: !this.SharedArrayBuffer || !this.Atomics
let gb = 1 * 1024 * 1024 * 1024;
let buflen = 4 * gb + 64;
let sab = new SharedArrayBuffer(buflen);
assertEq(sab.byteLength, buflen);
function testBasic(base) {
var uint8 = new Uint8Array(sab);
var uint8Part = new Uint8Array(sab, base, 64);
for (var i = 0; i < 50; i++) {
var index = base + i;
uint8Part[i] = 123;
assertEq(uint8[index], 123);
// Binary ops.
assertEq(Atomics.add(uint8, index, 1), 123);
assertEq(Atomics.and(uint8, index, 0xf), 124);
assertEq(Atomics.or(uint8, index, 0xf), 12);
assertEq(Atomics.xor(uint8, index, 0xee), 0xf);
assertEq(Atomics.sub(uint8, index, 100), 225);
assertEq(uint8Part[i], 125);
// compareExchange.
assertEq(Atomics.compareExchange(uint8, index, 125, 90), 125);
assertEq(Atomics.compareExchange(uint8, index, 125, 90), 90);
assertEq(uint8Part[i], 90);
// exchange.
assertEq(Atomics.exchange(uint8, index, 42), 90);
assertEq(uint8Part[i], 42);
// load/store.
assertEq(Atomics.load(uint8, index), 42);
assertEq(Atomics.store(uint8, index, 99), 99);
assertEq(uint8Part[i], 99);
}
}
for (let i = 0; i <= 4; i++) {
testBasic(i * gb);
}
function testWait() {
let int32 = new Int32Array(sab);
let index = int32.length - 1;
assertEq(int32[index], 0);
assertEq(Atomics.wait(int32, index, 1), "not-equal");
int32[index] = 12345;
assertEq(Atomics.wait(int32, index, 12345, 1), "timed-out");
assertEq(Atomics.notify(int32, index), 0);
let int32WithOffset = new Int32Array(sab, int32.byteLength - 4);
assertEq(int32WithOffset[0], 12345);
assertEq(Atomics.wait(int32WithOffset, 0, 12345, 1), "timed-out");
}
testWait();

Просмотреть файл

@ -4,6 +4,7 @@ let gb = 1 * 1024 * 1024 * 1024;
function test1() {
let ab = new ArrayBuffer(7 * gb);
assertEq(ab.byteLength, 7 * gb);
let taInt16 = new Int16Array(ab);
assertEq(taInt16.byteOffset, 0);

Просмотреть файл

@ -369,7 +369,7 @@ MOZ_ALWAYS_INLINE bool ArrayBufferObject::byteLengthGetterImpl(
JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
auto* buffer = &args.thisv().toObject().as<ArrayBufferObject>();
args.rval().setInt32(buffer->byteLength().deprecatedGetUint32());
args.rval().setNumber(buffer->byteLength().get());
return true;
}

Просмотреть файл

@ -176,7 +176,7 @@ MOZ_ALWAYS_INLINE bool SharedArrayBufferObject::byteLengthGetterImpl(
JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
auto* buffer = &args.thisv().toObject().as<SharedArrayBufferObject>();
args.rval().setInt32(buffer->byteLength().deprecatedGetUint32());
args.rval().setNumber(buffer->byteLength().get());
return true;
}