mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:17: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/ByteBuffer.h>
|
||||||
#include <AK/Endian.h>
|
#include <AK/Endian.h>
|
||||||
#include <AK/TypeCasts.h>
|
#include <AK/TypeCasts.h>
|
||||||
|
#include <LibJS/Runtime/Agent.h>
|
||||||
#include <LibJS/Runtime/AtomicsObject.h>
|
#include <LibJS/Runtime/AtomicsObject.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/TypedArray.h>
|
#include <LibJS/Runtime/TypedArray.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
|
#include <LibJS/Runtime/ValueInlines.h>
|
||||||
|
|
||||||
namespace JS {
|
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));
|
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>
|
template<typename T, typename AtomicFunction>
|
||||||
static ThrowCompletionOr<Value> perform_atomic_operation(VM& vm, TypedArrayBase& typed_array, AtomicFunction&& operation)
|
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.or_, or_, 3, attr);
|
||||||
define_native_function(realm, vm.names.store, store, 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.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);
|
define_native_function(realm, vm.names.xor_, xor_, 3, attr);
|
||||||
|
|
||||||
// 25.4.15 Atomics [ @@toStringTag ], https://tc39.es/ecma262/#sec-atomics-@@tostringtag
|
// 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();
|
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
|
// 25.4.14 Atomics.xor ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.xor
|
||||||
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::xor_)
|
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::xor_)
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,6 +30,8 @@ private:
|
||||||
JS_DECLARE_NATIVE_FUNCTION(or_);
|
JS_DECLARE_NATIVE_FUNCTION(or_);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(store);
|
JS_DECLARE_NATIVE_FUNCTION(store);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(sub);
|
JS_DECLARE_NATIVE_FUNCTION(sub);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(wait);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(wait_async);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(xor_);
|
JS_DECLARE_NATIVE_FUNCTION(xor_);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -575,6 +575,8 @@ namespace JS {
|
||||||
P(value) \
|
P(value) \
|
||||||
P(valueOf) \
|
P(valueOf) \
|
||||||
P(values) \
|
P(values) \
|
||||||
|
P(wait) \
|
||||||
|
P(waitAsync) \
|
||||||
P(warn) \
|
P(warn) \
|
||||||
P(weekOfYear) \
|
P(weekOfYear) \
|
||||||
P(weekday) \
|
P(weekday) \
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
M(ArrayMaxSize, "Maximum array size exceeded") \
|
M(ArrayMaxSize, "Maximum array size exceeded") \
|
||||||
M(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \
|
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(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(BigIntBadOperator, "Cannot use {} operator with BigInt") \
|
||||||
M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type") \
|
M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type") \
|
||||||
M(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt") \
|
M(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt") \
|
||||||
|
@ -94,6 +95,7 @@
|
||||||
M(NotAnObjectOfType, "Not an object of type {}") \
|
M(NotAnObjectOfType, "Not an object of type {}") \
|
||||||
M(NotAnObjectOrNull, "{} is neither an object nor null") \
|
M(NotAnObjectOrNull, "{} is neither an object nor null") \
|
||||||
M(NotAnObjectOrString, "{} is neither an object nor a string") \
|
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(NotAString, "{} is not a string") \
|
||||||
M(NotASymbol, "{} is not a symbol") \
|
M(NotASymbol, "{} is not a symbol") \
|
||||||
M(NotImplemented, "TODO({} is not implemented in LibJS)") \
|
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