From cc3b96743a295f029ba5938157bbc7529648c95f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 11 Jul 2021 11:29:35 -0400 Subject: [PATCH] LibJS: Implement Atomics.add --- .../Libraries/LibJS/Runtime/AtomicsObject.cpp | 119 +++++++++++++++++- .../Libraries/LibJS/Runtime/AtomicsObject.h | 3 + Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 2 + .../Tests/builtins/Atomics/Atomics.add.js | 60 +++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.add.js diff --git a/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp b/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp index a875043fe49..69cd8773c4f 100644 --- a/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp @@ -4,11 +4,108 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include +#include +#include namespace JS { +// 25.4.2.1 ValidateIntegerTypedArray ( typedArray [ , waitable ] ), https://tc39.es/ecma262/#sec-validateintegertypedarray +static void validate_integer_typed_array(GlobalObject& global_object, TypedArrayBase& typed_array, bool waitable = false) +{ + auto& vm = global_object.vm(); + + validate_typed_array(global_object, typed_array); + if (vm.exception()) + return; + + auto type_name = typed_array.element_name(); + + if (waitable) { + if ((type_name != "Int32Array"sv) && (type_name != "BigInt64Array"sv)) + vm.throw_exception(global_object, ErrorType::TypedArrayTypeIsNot, type_name, "Int32 or BigInt64"sv); + } else { + if (!typed_array.is_unclamped_integer_element_type() && !typed_array.is_bigint_element_type()) + vm.throw_exception(global_object, ErrorType::TypedArrayTypeIsNot, type_name, "an unclamped integer or BigInt"sv); + } +} + +// 25.4.2.2 ValidateAtomicAccess ( typedArray, requestIndex ), https://tc39.es/ecma262/#sec-validateatomicaccess +static Optional validate_atomic_access(GlobalObject& global_object, TypedArrayBase& typed_array, Value request_index) +{ + auto& vm = global_object.vm(); + + auto access_index = request_index.to_index(global_object); + if (vm.exception()) + return {}; + + if (access_index >= typed_array.array_length()) { + vm.throw_exception(global_object, ErrorType::IndexOutOfRange, access_index, typed_array.array_length()); + return {}; + } + + return access_index * typed_array.element_size() + typed_array.byte_offset(); +} + +// 25.4.2.11 AtomicReadModifyWrite ( typedArray, index, value, op ), https://tc39.es/ecma262/#sec-atomicreadmodifywrite +static Value atomic_read_modify_write(GlobalObject& global_object, TypedArrayBase& typed_array, Value index, Value value, ReadWriteModifyFunction operation) +{ + auto& vm = global_object.vm(); + + validate_integer_typed_array(global_object, typed_array); + if (vm.exception()) + return {}; + + auto byte_index = validate_atomic_access(global_object, typed_array, index); + if (!byte_index.has_value()) + return {}; + + Value value_to_set; + if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt) { + value_to_set = value.to_bigint(global_object); + if (vm.exception()) + return {}; + } else { + value_to_set = Value(value.to_integer_or_infinity(global_object)); + if (vm.exception()) + return {}; + } + + if (typed_array.viewed_array_buffer()->is_detached()) { + vm.throw_exception(global_object, ErrorType::DetachedArrayBuffer); + return {}; + } + + return typed_array.get_modify_set_value_in_buffer(*byte_index, value_to_set, move(operation)); +} + +template +static Value perform_atomic_operation(GlobalObject& global_object, TypedArrayBase& typed_array, AtomicFunction&& operation) +{ + auto& vm = global_object.vm(); + auto index = vm.argument(1); + auto value = vm.argument(2); + + auto operation_wrapper = [&, operation = forward(operation)](ByteBuffer x_bytes, ByteBuffer y_bytes) -> ByteBuffer { + if constexpr (IsFloatingPoint) { + VERIFY_NOT_REACHED(); + } else { + using U = Conditional, u8, T>; + + auto* x = reinterpret_cast(x_bytes.data()); + auto* y = reinterpret_cast(y_bytes.data()); + operation(x, *y); + + return x_bytes; + } + }; + + return atomic_read_modify_write(global_object, typed_array, index, value, move(operation_wrapper)); +} + AtomicsObject::AtomicsObject(GlobalObject& global_object) : Object(*global_object.object_prototype()) { @@ -17,11 +114,31 @@ AtomicsObject::AtomicsObject(GlobalObject& global_object) void AtomicsObject::initialize(GlobalObject& global_object) { Object::initialize(global_object); - auto& vm = this->vm(); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.add, add, 3, attr); + // 25.4.15 Atomics [ @@toStringTag ], https://tc39.es/ecma262/#sec-atomics-@@tostringtag define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "Atomics"), Attribute::Configurable); } +// 25.4.3 Atomics.add ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.add +JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::add) +{ + auto* typed_array = typed_array_from(global_object, vm.argument(0)); + if (!typed_array) + return {}; + + auto atomic_add = [](auto* storage, auto value) { return AK::atomic_fetch_add(storage, value); }; + +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ + if (is(typed_array)) \ + return perform_atomic_operation(global_object, *typed_array, move(atomic_add)); + JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE + + VERIFY_NOT_REACHED(); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/AtomicsObject.h b/Userland/Libraries/LibJS/Runtime/AtomicsObject.h index db393e12138..2e33dbf0012 100644 --- a/Userland/Libraries/LibJS/Runtime/AtomicsObject.h +++ b/Userland/Libraries/LibJS/Runtime/AtomicsObject.h @@ -17,6 +17,9 @@ public: explicit AtomicsObject(GlobalObject&); virtual void initialize(GlobalObject&) override; virtual ~AtomicsObject() override = default; + +private: + JS_DECLARE_NATIVE_FUNCTION(add); }; } diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 2d24646fa81..ee685a50938 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -27,6 +27,7 @@ M(DivisionByZero, "Division by zero") \ M(FinalizationRegistrySameTargetAndValue, "Target and held value must not be the same") \ M(GetCapabilitiesExecutorCalledMultipleTimes, "GetCapabilitiesExecutor was called multiple times") \ + M(IndexOutOfRange, "Index {} is out of range of array length {}") \ M(InOperatorWithObject, "'in' operator must be used on an object") \ M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object") \ M(InvalidAssignToConst, "Invalid assignment to const variable") \ @@ -180,6 +181,7 @@ M(TypedArrayOutOfRangeByteOffsetOrLength, "Typed array range {}:{} is out of range for buffer with length {}") \ M(TypedArrayPrototypeOneArg, "TypedArray.prototype.{}() requires at least one argument") \ M(TypedArrayFailedSettingIndex, "Failed setting value of index {} of typed array") \ + M(TypedArrayTypeIsNot, "Typed array {} element type is not {}") \ M(UnknownIdentifier, "'{}' is not defined") \ M(UnsupportedDeleteSuperProperty, "Can't delete a property on 'super'") \ M(URIMalformed, "URI malformed") /* LibWeb bindings */ \ diff --git a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.add.js b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.add.js new file mode 100644 index 00000000000..39d959d0f7a --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.add.js @@ -0,0 +1,60 @@ +test("invariants", () => { + expect(Atomics.add).toHaveLength(3); +}); + +test("error cases", () => { + expect(() => { + Atomics.add("not an array", 0, 1); + }).toThrow(TypeError); + + expect(() => { + const bad_array_type = new Float32Array(4); + Atomics.add(bad_array_type, 0, 1); + }).toThrow(TypeError); + + expect(() => { + const bad_array_type = new Uint8ClampedArray(4); + Atomics.add(bad_array_type, 0, 1); + }).toThrow(TypeError); + + expect(() => { + const array = new Int32Array(4); + Atomics.add(array, 100, 1); + }).toThrow(RangeError); +}); + +test("basic functionality (non-BigInt)", () => { + [Int8Array, Int16Array, Int32Array, Uint8Array, Uint16Array, Uint32Array].forEach(ArrayType => { + const array = new ArrayType(4); + + expect(Atomics.add(array, 0, 1)).toBe(0); + expect(array).toEqual([1, 0, 0, 0]); + + expect(Atomics.add(array, 0, 1)).toBe(1); + expect(array).toEqual([2, 0, 0, 0]); + + expect(Atomics.add(array, 2, 3.14)).toBe(0); + expect(array).toEqual([2, 0, 3, 0]); + + expect(Atomics.add(array, 3, "1")).toBe(0); + expect(array).toEqual([2, 0, 3, 1]); + }); +}); + +test("basic functionality (BigInt)", () => { + [BigInt64Array, BigUint64Array].forEach(ArrayType => { + const array = new ArrayType(4); + + expect(Atomics.add(array, 0, 1n)).toBe(0n); + expect(array).toEqual([1n, 0n, 0n, 0n]); + + expect(Atomics.add(array, 0, 1n)).toBe(1n); + expect(array).toEqual([2n, 0n, 0n, 0n]); + + expect(Atomics.add(array, 2, 3n)).toBe(0n); + expect(array).toEqual([2n, 0n, 3n, 0n]); + + expect(Atomics.add(array, 3, 4n)).toBe(0n); + expect(array).toEqual([2n, 0n, 3n, 4n]); + }); +});