mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:27:45 +00:00
LibJS: Stub out Atomics.wait and Atomics.waitAsync
We don't have the facilities to implement these methods fully (namely, a fully realized SharedArrayBuffer). But we can implement enough to validate the values passed in by the user.
This commit is contained in:
parent
a7073c3f1f
commit
78edaad97d
6 changed files with 241 additions and 0 deletions
|
@ -16,10 +16,12 @@
|
|||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibJS/Runtime/Agent.h>
|
||||
#include <LibJS/Runtime/AtomicsObject.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -110,6 +112,61 @@ static ThrowCompletionOr<Value> atomic_read_modify_write(VM& vm, TypedArrayBase&
|
|||
return typed_array.get_modify_set_value_in_buffer(indexed_position, value_to_set, move(operation));
|
||||
}
|
||||
|
||||
enum class WaitMode {
|
||||
Sync,
|
||||
Async,
|
||||
};
|
||||
|
||||
// 25.4.3.14 DoWait ( mode, typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-dowait
|
||||
static ThrowCompletionOr<Value> do_wait(VM& vm, WaitMode mode, TypedArrayBase& typed_array, Value index_value, Value expected_value, Value timeout_value)
|
||||
{
|
||||
// 1. Let iieoRecord be ? ValidateIntegerTypedArray(typedArray, true).
|
||||
// 2. Let buffer be iieoRecord.[[Object]].[[ViewedArrayBuffer]].
|
||||
// FIXME: An IIEO record is a new structure from the resizable array buffer proposal. Use it when the proposal is implemented.
|
||||
auto* buffer = TRY(validate_integer_typed_array(vm, typed_array, true));
|
||||
|
||||
// 3. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
|
||||
if (!buffer->is_shared_array_buffer())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotASharedArrayBuffer);
|
||||
|
||||
// 4. Let i be ? ValidateAtomicAccess(iieoRecord, index).
|
||||
auto index = TRY(validate_atomic_access(vm, typed_array, index_value));
|
||||
|
||||
// 5. Let arrayTypeName be typedArray.[[TypedArrayName]].
|
||||
auto const& array_type_name = typed_array.element_name();
|
||||
|
||||
// 6. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value).
|
||||
i64 value = 0;
|
||||
if (array_type_name == vm.names.BigInt64Array.as_string())
|
||||
value = TRY(expected_value.to_bigint_int64(vm));
|
||||
// 7. Else, let v be ? ToInt32(value).
|
||||
else
|
||||
value = TRY(expected_value.to_i32(vm));
|
||||
|
||||
// 8. Let q be ? ToNumber(timeout).
|
||||
auto timeout_number = TRY(timeout_value.to_number(vm));
|
||||
|
||||
// 9. If q is either NaN or +∞𝔽, let t be +∞; else if q is -∞𝔽, let t be 0; else let t be max(ℝ(q), 0).
|
||||
double timeout = 0;
|
||||
if (timeout_number.is_nan() || timeout_number.is_positive_infinity())
|
||||
timeout = js_infinity().as_double();
|
||||
else if (timeout_number.is_negative_infinity())
|
||||
timeout = 0.0;
|
||||
else
|
||||
timeout = max(timeout_number.as_double(), 0.0);
|
||||
|
||||
// 10. If mode is sync and AgentCanSuspend() is false, throw a TypeError exception.
|
||||
if (mode == WaitMode::Sync && !agent_can_suspend())
|
||||
return vm.throw_completion<TypeError>(ErrorType::AgentCannotSuspend);
|
||||
|
||||
// FIXME: Implement the remaining steps when we support SharedArrayBuffer.
|
||||
(void)index;
|
||||
(void)value;
|
||||
(void)timeout;
|
||||
|
||||
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "SharedArrayBuffer"sv);
|
||||
}
|
||||
|
||||
template<typename T, typename AtomicFunction>
|
||||
static ThrowCompletionOr<Value> perform_atomic_operation(VM& vm, TypedArrayBase& typed_array, AtomicFunction&& operation)
|
||||
{
|
||||
|
@ -154,6 +211,8 @@ void AtomicsObject::initialize(Realm& realm)
|
|||
define_native_function(realm, vm.names.or_, or_, 3, attr);
|
||||
define_native_function(realm, vm.names.store, store, 3, attr);
|
||||
define_native_function(realm, vm.names.sub, sub, 3, attr);
|
||||
define_native_function(realm, vm.names.wait, wait, 4, attr);
|
||||
define_native_function(realm, vm.names.waitAsync, wait_async, 4, attr);
|
||||
define_native_function(realm, vm.names.xor_, xor_, 3, attr);
|
||||
|
||||
// 25.4.15 Atomics [ @@toStringTag ], https://tc39.es/ecma262/#sec-atomics-@@tostringtag
|
||||
|
@ -403,6 +462,30 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::sub)
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// 25.4.13 Atomics.wait ( typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-atomics.wait
|
||||
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::wait)
|
||||
{
|
||||
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
|
||||
auto index = vm.argument(1);
|
||||
auto value = vm.argument(2);
|
||||
auto timeout = vm.argument(3);
|
||||
|
||||
// 1. Return ? DoWait(sync, typedArray, index, value, timeout).
|
||||
return TRY(do_wait(vm, WaitMode::Sync, *typed_array, index, value, timeout));
|
||||
}
|
||||
|
||||
// 25.4.14 Atomics.waitAsync ( typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-atomics.waitasync
|
||||
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::wait_async)
|
||||
{
|
||||
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
|
||||
auto index = vm.argument(1);
|
||||
auto value = vm.argument(2);
|
||||
auto timeout = vm.argument(3);
|
||||
|
||||
// 1. Return ? DoWait(async, typedArray, index, value, timeout).
|
||||
return TRY(do_wait(vm, WaitMode::Async, *typed_array, index, value, timeout));
|
||||
}
|
||||
|
||||
// 25.4.14 Atomics.xor ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.xor
|
||||
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::xor_)
|
||||
{
|
||||
|
|
|
@ -30,6 +30,8 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(or_);
|
||||
JS_DECLARE_NATIVE_FUNCTION(store);
|
||||
JS_DECLARE_NATIVE_FUNCTION(sub);
|
||||
JS_DECLARE_NATIVE_FUNCTION(wait);
|
||||
JS_DECLARE_NATIVE_FUNCTION(wait_async);
|
||||
JS_DECLARE_NATIVE_FUNCTION(xor_);
|
||||
};
|
||||
|
||||
|
|
|
@ -575,6 +575,8 @@ namespace JS {
|
|||
P(value) \
|
||||
P(valueOf) \
|
||||
P(values) \
|
||||
P(wait) \
|
||||
P(waitAsync) \
|
||||
P(warn) \
|
||||
P(weekOfYear) \
|
||||
P(weekday) \
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
M(ArrayMaxSize, "Maximum array size exceeded") \
|
||||
M(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \
|
||||
M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \
|
||||
M(AgentCannotSuspend, "Agent is not allowed to suspend") \
|
||||
M(BigIntBadOperator, "Cannot use {} operator with BigInt") \
|
||||
M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type") \
|
||||
M(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt") \
|
||||
|
@ -94,6 +95,7 @@
|
|||
M(NotAnObjectOfType, "Not an object of type {}") \
|
||||
M(NotAnObjectOrNull, "{} is neither an object nor null") \
|
||||
M(NotAnObjectOrString, "{} is neither an object nor a string") \
|
||||
M(NotASharedArrayBuffer, "The TypedArray's underlying buffer must be a SharedArrayBuffer") \
|
||||
M(NotAString, "{} is not a string") \
|
||||
M(NotASymbol, "{} is not a symbol") \
|
||||
M(NotImplemented, "TODO({} is not implemented in LibJS)") \
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
describe("errors", () => {
|
||||
test("called on non-TypedArray", () => {
|
||||
expect(() => {
|
||||
Atomics.wait(Symbol.hasInstance, 0, 0, 0);
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type TypedArray");
|
||||
});
|
||||
|
||||
test("detached buffer", () => {
|
||||
expect(() => {
|
||||
const typedArray = new Int32Array(4);
|
||||
detachArrayBuffer(typedArray.buffer);
|
||||
|
||||
Atomics.wait(typedArray, 0, 0, 0);
|
||||
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached");
|
||||
});
|
||||
|
||||
test("invalid TypedArray type", () => {
|
||||
expect(() => {
|
||||
const typedArray = new Float32Array(4);
|
||||
Atomics.wait(typedArray, 0, 0, 0);
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"Typed array Float32Array element type is not Int32 or BigInt64"
|
||||
);
|
||||
});
|
||||
|
||||
test("non-shared ArrayBuffer", () => {
|
||||
expect(() => {
|
||||
const typedArray = new Int32Array(4);
|
||||
Atomics.wait(typedArray, 0, 0, 0);
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"The TypedArray's underlying buffer must be a SharedArrayBuffer"
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid index", () => {
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new Int32Array(buffer);
|
||||
|
||||
Atomics.wait(typedArray, 4, 0, 0);
|
||||
}).toThrowWithMessage(RangeError, "Index 4 is out of range of array length 4");
|
||||
});
|
||||
|
||||
test("invalid value", () => {
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new Int32Array(buffer);
|
||||
|
||||
Atomics.wait(typedArray, 0, Symbol.hasInstance, 0);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
|
||||
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * BigInt64Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new BigInt64Array(buffer);
|
||||
|
||||
Atomics.wait(typedArray, 0, Symbol.hasInstance, 0);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert symbol to BigInt");
|
||||
});
|
||||
|
||||
test("invalid timeout", () => {
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new Int32Array(buffer);
|
||||
|
||||
Atomics.wait(typedArray, 0, 0, Symbol.hasInstance);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
|
||||
});
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
test("invariants", () => {
|
||||
expect(Atomics.wait).toHaveLength(4);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
describe("errors", () => {
|
||||
test("called on non-TypedArray", () => {
|
||||
expect(() => {
|
||||
Atomics.waitAsync(Symbol.hasInstance, 0, 0, 0);
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type TypedArray");
|
||||
});
|
||||
|
||||
test("detached buffer", () => {
|
||||
expect(() => {
|
||||
const typedArray = new Int32Array(4);
|
||||
detachArrayBuffer(typedArray.buffer);
|
||||
|
||||
Atomics.waitAsync(typedArray, 0, 0, 0);
|
||||
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached");
|
||||
});
|
||||
|
||||
test("invalid TypedArray type", () => {
|
||||
expect(() => {
|
||||
const typedArray = new Float32Array(4);
|
||||
Atomics.waitAsync(typedArray, 0, 0, 0);
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"Typed array Float32Array element type is not Int32 or BigInt64"
|
||||
);
|
||||
});
|
||||
|
||||
test("non-shared ArrayBuffer", () => {
|
||||
expect(() => {
|
||||
const typedArray = new Int32Array(4);
|
||||
Atomics.waitAsync(typedArray, 0, 0, 0);
|
||||
}).toThrowWithMessage(
|
||||
TypeError,
|
||||
"The TypedArray's underlying buffer must be a SharedArrayBuffer"
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid index", () => {
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new Int32Array(buffer);
|
||||
|
||||
Atomics.waitAsync(typedArray, 4, 0, 0);
|
||||
}).toThrowWithMessage(RangeError, "Index 4 is out of range of array length 4");
|
||||
});
|
||||
|
||||
test("invalid value", () => {
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new Int32Array(buffer);
|
||||
|
||||
Atomics.waitAsync(typedArray, 0, Symbol.hasInstance, 0);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
|
||||
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * BigInt64Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new BigInt64Array(buffer);
|
||||
|
||||
Atomics.waitAsync(typedArray, 0, Symbol.hasInstance, 0);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert symbol to BigInt");
|
||||
});
|
||||
|
||||
test("invalid timeout", () => {
|
||||
expect(() => {
|
||||
const buffer = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
|
||||
const typedArray = new Int32Array(buffer);
|
||||
|
||||
Atomics.waitAsync(typedArray, 0, 0, Symbol.hasInstance);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
|
||||
});
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
test("invariants", () => {
|
||||
expect(Atomics.waitAsync).toHaveLength(4);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue