LibJS: Add initial implementation for SharedArrayBuffer

None of the actual sharing is implemented yet, but this is enough for
most basic functionality.

Diff Tests:
    +260     -262    +2 💀
This commit is contained in:
Shannon Booth 2023-07-01 16:07:26 +12:00 committed by Linus Groh
parent 1c1aa2c0d0
commit 3781948f0c
Notes: sideshowbarker 2024-07-17 05:02:35 +09:00
13 changed files with 347 additions and 0 deletions

View file

@ -196,6 +196,8 @@ set(SOURCES
Runtime/ShadowRealmConstructor.cpp
Runtime/ShadowRealmPrototype.cpp
Runtime/Shape.cpp
Runtime/SharedArrayBufferConstructor.cpp
Runtime/SharedArrayBufferPrototype.cpp
Runtime/StringConstructor.cpp
Runtime/StringIterator.cpp
Runtime/StringIteratorPrototype.cpp

View file

@ -40,6 +40,7 @@
__JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor, void) \
__JS_ENUMERATE(Set, set, SetPrototype, SetConstructor, void) \
__JS_ENUMERATE(ShadowRealm, shadow_realm, ShadowRealmPrototype, ShadowRealmConstructor, void) \
__JS_ENUMERATE(SharedArrayBuffer, shared_array_buffer, SharedArrayBufferPrototype, SharedArrayBufferConstructor, void) \
__JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor, void) \
__JS_ENUMERATE(SuppressedError, suppressed_error, SuppressedErrorPrototype, SuppressedErrorConstructor, void) \
__JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor, void) \

View file

@ -229,4 +229,21 @@ ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM& vm, ArrayBuffer
return new_buffer;
}
// 25.2.1.1 AllocateSharedArrayBuffer ( constructor, byteLength ), https://tc39.es/ecma262/#sec-allocatesharedarraybuffer
ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> allocate_shared_array_buffer(VM& vm, FunctionObject& constructor, size_t byte_length)
{
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]] »).
auto obj = TRY(ordinary_create_from_constructor<ArrayBuffer>(vm, constructor, &Intrinsics::shared_array_buffer_prototype, nullptr));
// FIXME: 2. Let block be ? CreateSharedByteDataBlock(byteLength).
auto block = TRY(create_byte_data_block(vm, byte_length));
// 3. Set obj.[[ArrayBufferData]] to block.
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
obj->set_buffer(move(block));
// 5. Return obj.
return obj;
}
}

View file

@ -104,6 +104,7 @@ ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& const
ThrowCompletionOr<void> detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional<Value> key = {});
ThrowCompletionOr<ArrayBuffer*> clone_array_buffer(VM&, ArrayBuffer& source_buffer, size_t source_byte_offset, size_t source_length);
ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM&, ArrayBuffer& array_buffer, Value new_length, PreserveResizability preserve_resizability);
ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> allocate_shared_array_buffer(VM&, FunctionObject& constructor, size_t byte_length);
// 25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian ), https://tc39.es/ecma262/#sec-rawbytestonumeric
template<typename T>

View file

@ -63,6 +63,7 @@
#include <LibJS/Runtime/SetConstructor.h>
#include <LibJS/Runtime/ShadowRealmConstructor.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
#include <LibJS/Runtime/StringConstructor.h>
#include <LibJS/Runtime/StringPrototype.h>
#include <LibJS/Runtime/SuppressedErrorConstructor.h>
@ -158,6 +159,7 @@ Object& set_default_global_bindings(Realm& realm)
global.define_intrinsic_accessor(vm.names.RegExp, attr, [](auto& realm) -> Value { return realm.intrinsics().regexp_constructor(); });
global.define_intrinsic_accessor(vm.names.Set, attr, [](auto& realm) -> Value { return realm.intrinsics().set_constructor(); });
global.define_intrinsic_accessor(vm.names.ShadowRealm, attr, [](auto& realm) -> Value { return realm.intrinsics().shadow_realm_constructor(); });
global.define_intrinsic_accessor(vm.names.SharedArrayBuffer, attr, [](auto& realm) -> Value { return realm.intrinsics().shared_array_buffer_constructor(); });
global.define_intrinsic_accessor(vm.names.String, attr, [](auto& realm) -> Value { return realm.intrinsics().string_constructor(); });
global.define_intrinsic_accessor(vm.names.SuppressedError, attr, [](auto& realm) -> Value { return realm.intrinsics().suppressed_error_constructor(); });
global.define_intrinsic_accessor(vm.names.Symbol, attr, [](auto& realm) -> Value { return realm.intrinsics().symbol_constructor(); });

View file

@ -90,6 +90,8 @@
#include <LibJS/Runtime/ShadowRealmConstructor.h>
#include <LibJS/Runtime/ShadowRealmPrototype.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
#include <LibJS/Runtime/SharedArrayBufferPrototype.h>
#include <LibJS/Runtime/StringConstructor.h>
#include <LibJS/Runtime/StringIteratorPrototype.h>
#include <LibJS/Runtime/StringPrototype.h>

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
#include <LibJS/Runtime/TypedArray.h>
namespace JS {
SharedArrayBufferConstructor::SharedArrayBufferConstructor(Realm& realm)
: NativeFunction(realm.vm().names.SharedArrayBuffer.as_string(), realm.intrinsics().function_prototype())
{
}
ThrowCompletionOr<void> SharedArrayBufferConstructor::initialize(Realm& realm)
{
auto& vm = this->vm();
MUST_OR_THROW_OOM(NativeFunction::initialize(realm));
// 25.2.4.2 SharedArrayBuffer.prototype, https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype
define_direct_property(vm.names.prototype, realm.intrinsics().shared_array_buffer_prototype(), 0);
// 25.2.4.4 SharedArrayBuffer.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.toString
define_native_accessor(realm, vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable);
define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
return {};
}
// 25.2.2.1 SharedArrayBuffer ( length ), https://tc39.es/ecma262/#sec-sharedarraybuffer-length
ThrowCompletionOr<Value> SharedArrayBufferConstructor::call()
{
auto& vm = this->vm();
// 1. If NewTarget is undefined, throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, vm.names.SharedArrayBuffer);
}
// 25.2.2.1 SharedArrayBuffer ( length ), https://tc39.es/ecma262/#sec-sharedarraybuffer-length
ThrowCompletionOr<NonnullGCPtr<Object>> SharedArrayBufferConstructor::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
// 2. Let byteLength be ? ToIndex(length).
auto byte_length_or_error = vm.argument(0).to_index(vm);
if (byte_length_or_error.is_error()) {
auto error = byte_length_or_error.release_error();
if (error.value()->is_object() && is<RangeError>(error.value()->as_object())) {
// Re-throw more specific RangeError
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "shared array buffer");
}
return error;
}
// 3. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength).
return *TRY(allocate_shared_array_buffer(vm, new_target, byte_length_or_error.release_value()));
}
// 25.2.3.2 get SharedArrayBuffer [ @@species ], https://tc39.es/ecma262/#sec-sharedarraybuffer-@@species
JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferConstructor::symbol_species_getter)
{
// 1. Return the this value.
return vm.this_value();
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/NativeFunction.h>
namespace JS {
class SharedArrayBufferConstructor final : public NativeFunction {
JS_OBJECT(SharedArrayBufferConstructor, NativeFunction);
public:
virtual ThrowCompletionOr<void> initialize(Realm&) override;
virtual ~SharedArrayBufferConstructor() override = default;
virtual ThrowCompletionOr<Value> call() override;
virtual ThrowCompletionOr<NonnullGCPtr<Object>> construct(FunctionObject& new_target) override;
private:
explicit SharedArrayBufferConstructor(Realm&);
virtual bool has_constructor() const override { return true; }
JS_DECLARE_NATIVE_FUNCTION(symbol_species_getter);
};
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Function.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/SharedArrayBufferConstructor.h>
#include <LibJS/Runtime/SharedArrayBufferPrototype.h>
namespace JS {
SharedArrayBufferPrototype::SharedArrayBufferPrototype(Realm& realm)
: PrototypeObject(realm.intrinsics().object_prototype())
{
}
ThrowCompletionOr<void> SharedArrayBufferPrototype::initialize(Realm& realm)
{
auto& vm = this->vm();
MUST_OR_THROW_OOM(Base::initialize(realm));
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_accessor(realm, vm.names.byteLength, byte_length_getter, {}, Attribute::Configurable);
define_native_function(realm, vm.names.slice, slice, 2, attr);
// 25.2.4.4 SharedArrayBuffer.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.toString
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.SharedArrayBuffer.as_string()), Attribute::Configurable);
return {};
}
// 25.2.4.1 get SharedArrayBuffer.prototype.byteLength, https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength
JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::byte_length_getter)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
auto array_buffer_object = TRY(typed_this_value(vm));
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
// FIXME: Check for shared buffer
// 4. Let length be O.[[ArrayBufferByteLength]].
// 5. Return 𝔽(length).
return Value(array_buffer_object->byte_length());
}
// 25.2.4.3 SharedArrayBuffer.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice
JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::slice)
{
auto& realm = *vm.current_realm();
auto start = vm.argument(0);
auto end = vm.argument(1);
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
auto array_buffer_object = TRY(typed_this_value(vm));
// 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception.
// FIXME: Check for shared buffer
// 4. Let len be O.[[ArrayBufferByteLength]].
auto length = array_buffer_object->byte_length();
// 5. Let relativeStart be ? ToIntegerOrInfinity(start).
auto relative_start = TRY(start.to_integer_or_infinity(vm));
double first;
// 6. If relativeStart is -∞, let first be 0.
if (Value(relative_start).is_negative_infinity())
first = 0;
// 7. Else if relativeStart < 0, let first be max(len + relativeStart, 0).
else if (relative_start < 0)
first = max(length + relative_start, 0.0);
// 8. Else, let first be min(relativeStart, len).
else
first = min(relative_start, (double)length);
// 9. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
auto relative_end = end.is_undefined() ? length : TRY(end.to_integer_or_infinity(vm));
double final;
// 10. If relativeEnd is -∞, let final be 0.
if (Value(relative_end).is_negative_infinity())
final = 0;
// 11. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
else if (relative_end < 0)
final = max(length + relative_end, 0.0);
// 12. Else, let final be min(relativeEnd, len).
else
final = min(relative_end, (double)length);
// 13. Let newLen be max(final - first, 0).
auto new_length = max(final - first, 0.0);
// 14. Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%).
auto* constructor = TRY(species_constructor(vm, array_buffer_object, realm.intrinsics().shared_array_buffer_constructor()));
// 15. Let new be ? Construct(ctor, « 𝔽(newLen) »).
auto new_array_buffer = TRY(construct(vm, *constructor, Value(new_length)));
// 16. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
if (!is<ArrayBuffer>(new_array_buffer.ptr()))
return vm.throw_completion<TypeError>(ErrorType::SpeciesConstructorDidNotCreate, "an ArrayBuffer");
auto* new_array_buffer_object = static_cast<ArrayBuffer*>(new_array_buffer.ptr());
// 17. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
// FIXME: Check for shared buffer
// 18. If new.[[ArrayBufferData]] is O.[[ArrayBufferData]], throw a TypeError exception.
if (new_array_buffer == array_buffer_object)
return vm.throw_completion<TypeError>(ErrorType::SpeciesConstructorReturned, "same ArrayBuffer instance");
// 19. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
if (new_array_buffer_object->byte_length() < new_length)
return vm.throw_completion<TypeError>(ErrorType::SpeciesConstructorReturned, "an ArrayBuffer smaller than requested");
// 20. Let fromBuf be O.[[ArrayBufferData]].
auto& from_buf = array_buffer_object->buffer();
// 21. Let toBuf be new.[[ArrayBufferData]].
auto& to_buf = new_array_buffer_object->buffer();
// 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
copy_data_block_bytes(to_buf, 0, from_buf, first, new_length);
// 23. Return new.
return new_array_buffer_object;
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023, Shannon Booth <shannon.ml.booth@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/PrototypeObject.h>
namespace JS {
class SharedArrayBufferPrototype final : public PrototypeObject<SharedArrayBufferPrototype, ArrayBuffer> {
JS_PROTOTYPE_OBJECT(SharedArrayBufferPrototype, ArrayBuffer, SharedArrayBuffer);
public:
virtual ThrowCompletionOr<void> initialize(Realm&) override;
virtual ~SharedArrayBufferPrototype() override = default;
private:
explicit SharedArrayBufferPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(byte_length_getter);
JS_DECLARE_NATIVE_FUNCTION(slice);
};
}

View file

@ -0,0 +1,19 @@
test("basic functionality", () => {
expect(SharedArrayBuffer).toHaveLength(1);
expect(SharedArrayBuffer.name).toBe("SharedArrayBuffer");
expect(SharedArrayBuffer.prototype.constructor).toBe(SharedArrayBuffer);
expect(new SharedArrayBuffer()).toBeInstanceOf(SharedArrayBuffer);
expect(typeof new SharedArrayBuffer()).toBe("object");
});
test("SharedArrayBuffer constructor must be invoked with 'new'", () => {
expect(() => {
SharedArrayBuffer();
}).toThrowWithMessage(TypeError, "SharedArrayBuffer constructor must be called with 'new'");
});
test("SharedArrayBuffer size limit", () => {
expect(() => {
new SharedArrayBuffer(2 ** 53);
}).toThrowWithMessage(RangeError, "Invalid shared array buffer length");
});

View file

@ -0,0 +1,6 @@
test("basic functionality", () => {
expect(new SharedArrayBuffer().byteLength).toBe(0);
expect(new SharedArrayBuffer(1).byteLength).toBe(1);
expect(new SharedArrayBuffer(64).byteLength).toBe(64);
expect(new SharedArrayBuffer(123).byteLength).toBe(123);
});

View file

@ -0,0 +1,29 @@
test("single parameter", () => {
const buffer = new SharedArrayBuffer(16);
const fullView = new Int32Array(buffer);
// modify some value that we can check in the sliced buffer
fullView[3] = 7;
// slice the buffer and use a new int32 view to perform basic checks
const slicedBuffer = buffer.slice(12);
const slicedView = new Int32Array(slicedBuffer);
expect(slicedView).toHaveLength(1);
expect(slicedView[0]).toBe(7);
});
test("both parameters", () => {
const buffer = new SharedArrayBuffer(16);
const fullView = new Int32Array(buffer);
// modify some value that we can check in the sliced buffer
fullView[1] = 12;
// slice the buffer and use a new int32 view to perform basic checks
const slicedBuffer = buffer.slice(4, 8);
const slicedView = new Int32Array(slicedBuffer);
expect(slicedView).toHaveLength(1);
expect(slicedView[0]).toBe(12);
});