mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03:00
LibJS+LibWeb: Track SharedArrayBuffers' shared state
ArrayBuffer no longer stores a plain ByteBuffer internally, but a DataBlock instead, which encapsulated the ByteBuffer together with information if it is shared or not.
This commit is contained in:
parent
67e07fa4e2
commit
9267e24741
Notes:
sideshowbarker
2024-07-17 06:51:48 +09:00
Author: https://github.com/tobiassoppa 🔰 Commit: https://github.com/SerenityOS/serenity/commit/9267e24741 Pull-request: https://github.com/SerenityOS/serenity/pull/21027 Reviewed-by: https://github.com/ADKaster ✅
@ -32,14 +32,14 @@ NonnullGCPtr<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer* buffer)
|
||||
|
||||
ArrayBuffer::ArrayBuffer(ByteBuffer buffer, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_buffer(move(buffer))
|
||||
, m_data_block(DataBlock { move(buffer), DataBlock::Shared::No })
|
||||
, m_detach_key(js_undefined())
|
||||
{
|
||||
}
|
||||
|
||||
ArrayBuffer::ArrayBuffer(ByteBuffer* buffer, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_buffer(buffer)
|
||||
, m_data_block(DataBlock { buffer, DataBlock::Shared::No })
|
||||
, m_detach_key(js_undefined())
|
||||
{
|
||||
}
|
||||
@ -51,7 +51,7 @@ void ArrayBuffer::visit_edges(Cell::Visitor& visitor)
|
||||
}
|
||||
|
||||
// 6.2.9.1 CreateByteDataBlock ( size ), https://tc39.es/ecma262/#sec-createbytedatablock
|
||||
ThrowCompletionOr<ByteBuffer> create_byte_data_block(VM& vm, size_t size)
|
||||
ThrowCompletionOr<DataBlock> create_byte_data_block(VM& vm, size_t size)
|
||||
{
|
||||
// 1. If size > 2^53 - 1, throw a RangeError exception.
|
||||
if (size > MAX_ARRAY_LIKE_INDEX)
|
||||
@ -64,7 +64,25 @@ ThrowCompletionOr<ByteBuffer> create_byte_data_block(VM& vm, size_t size)
|
||||
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, size);
|
||||
|
||||
// 4. Return db.
|
||||
return data_block.release_value();
|
||||
return DataBlock { data_block.release_value(), DataBlock::Shared::No };
|
||||
}
|
||||
|
||||
// FIXME: The returned DataBlock is not shared in the sense that the standard specifies it.
|
||||
// 6.2.9.2 CreateSharedByteDataBlock ( size ), https://tc39.es/ecma262/#sec-createsharedbytedatablock
|
||||
static ThrowCompletionOr<DataBlock> create_shared_byte_data_block(VM& vm, size_t size)
|
||||
{
|
||||
// 1. Let db be a new Shared Data Block value consisting of size bytes. If it is impossible to create such a Shared Data Block, throw a RangeError exception.
|
||||
auto data_block = ByteBuffer::create_zeroed(size);
|
||||
if (data_block.is_error())
|
||||
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, size);
|
||||
|
||||
// 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
|
||||
// 3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
|
||||
// 4. Let zero be « 0 ».
|
||||
// 5. For each index i of db, do
|
||||
// a. Append WriteSharedMemory { [[Order]]: init, [[NoTear]]: true, [[Block]]: db, [[ByteIndex]]: i, [[ElementSize]]: 1, [[Payload]]: zero } to eventsRecord.[[EventList]].
|
||||
// 6. Return db.
|
||||
return DataBlock { data_block.release_value(), DataBlock::Shared::Yes };
|
||||
}
|
||||
|
||||
// 6.2.9.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count ), https://tc39.es/ecma262/#sec-copydatablockbytes
|
||||
@ -128,7 +146,7 @@ ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM& vm, FunctionObject& co
|
||||
auto block = TRY(create_byte_data_block(vm, byte_length));
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
obj->set_buffer(move(block));
|
||||
obj->set_data_block(move(block));
|
||||
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
|
||||
@ -235,12 +253,12 @@ ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> allocate_shared_array_buffer(VM& vm
|
||||
// 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));
|
||||
// 2. Let block be ? CreateSharedByteDataBlock(byteLength).
|
||||
auto block = TRY(create_shared_byte_data_block(vm, byte_length));
|
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
obj->set_buffer(move(block));
|
||||
obj->set_data_block(move(block));
|
||||
|
||||
// 5. Return obj.
|
||||
return obj;
|
||||
|
@ -27,6 +27,25 @@ enum class PreserveResizability {
|
||||
PreserveResizability
|
||||
};
|
||||
|
||||
// 6.2.9 Data Blocks, https://tc39.es/ecma262/#sec-data-blocks
|
||||
struct DataBlock {
|
||||
enum class Shared {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
ByteBuffer& buffer()
|
||||
{
|
||||
ByteBuffer* ptr { nullptr };
|
||||
byte_buffer.visit([&](Empty) { VERIFY_NOT_REACHED(); }, [&](auto* pointer) { ptr = pointer; }, [&](auto& value) { ptr = &value; });
|
||||
return *ptr;
|
||||
}
|
||||
ByteBuffer const& buffer() const { return const_cast<DataBlock*>(this)->buffer(); }
|
||||
|
||||
Variant<Empty, ByteBuffer, ByteBuffer*> byte_buffer;
|
||||
Shared is_shared = { Shared::No };
|
||||
};
|
||||
|
||||
class ArrayBuffer : public Object {
|
||||
JS_OBJECT(ArrayBuffer, Object);
|
||||
|
||||
@ -42,31 +61,47 @@ public:
|
||||
if (is_detached())
|
||||
return 0;
|
||||
|
||||
return buffer_impl().size();
|
||||
return m_data_block.buffer().size();
|
||||
}
|
||||
|
||||
// [[ArrayBufferData]]
|
||||
ByteBuffer& buffer() { return buffer_impl(); }
|
||||
ByteBuffer const& buffer() const { return buffer_impl(); }
|
||||
ByteBuffer& buffer() { return m_data_block.buffer(); }
|
||||
ByteBuffer const& buffer() const { return m_data_block.buffer(); }
|
||||
|
||||
// Used by allocate_array_buffer() to attach the data block after construction
|
||||
void set_buffer(ByteBuffer buffer) { m_buffer = move(buffer); }
|
||||
void set_data_block(DataBlock block) { m_data_block = move(block); }
|
||||
|
||||
Value detach_key() const { return m_detach_key; }
|
||||
void set_detach_key(Value detach_key) { m_detach_key = detach_key; }
|
||||
|
||||
void detach_buffer() { m_buffer = Empty {}; }
|
||||
void detach_buffer() { m_data_block.byte_buffer = Empty {}; }
|
||||
|
||||
// 25.1.2.2 IsDetachedBuffer ( arrayBuffer ), https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||
bool is_detached() const
|
||||
{
|
||||
// 1. If arrayBuffer.[[ArrayBufferData]] is null, return true.
|
||||
if (m_buffer.has<Empty>())
|
||||
if (m_data_block.byte_buffer.has<Empty>())
|
||||
return true;
|
||||
// 2. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
// 25.2.1.2 IsSharedArrayBuffer ( obj ), https://tc39.es/ecma262/#sec-issharedarraybuffer
|
||||
bool is_shared_array_buffer() const
|
||||
{
|
||||
// 1. Let bufferData be obj.[[ArrayBufferData]].
|
||||
// 2. If bufferData is null, return false.
|
||||
if (m_data_block.byte_buffer.has<Empty>())
|
||||
return false;
|
||||
// 3. If bufferData is a Data Block, return false.
|
||||
if (m_data_block.is_shared == DataBlock::Shared::No)
|
||||
return false;
|
||||
// 4. Assert: bufferData is a Shared Data Block.
|
||||
VERIFY(m_data_block.is_shared == DataBlock::Shared::Yes);
|
||||
// 5. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
enum Order {
|
||||
SeqCst,
|
||||
Unordered
|
||||
@ -84,22 +119,13 @@ private:
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
ByteBuffer& buffer_impl()
|
||||
{
|
||||
ByteBuffer* ptr { nullptr };
|
||||
m_buffer.visit([&](Empty) { VERIFY_NOT_REACHED(); }, [&](auto* pointer) { ptr = pointer; }, [&](auto& value) { ptr = &value; });
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
ByteBuffer const& buffer_impl() const { return const_cast<ArrayBuffer*>(this)->buffer_impl(); }
|
||||
|
||||
Variant<Empty, ByteBuffer, ByteBuffer*> m_buffer;
|
||||
DataBlock m_data_block;
|
||||
// The various detach related members of ArrayBuffer are not used by any ECMA262 functionality,
|
||||
// but are required to be available for the use of various harnesses like the Test262 test runner.
|
||||
Value m_detach_key;
|
||||
};
|
||||
|
||||
ThrowCompletionOr<ByteBuffer> create_byte_data_block(VM& vm, size_t size);
|
||||
ThrowCompletionOr<DataBlock> create_byte_data_block(VM& vm, size_t size);
|
||||
void copy_data_block_bytes(ByteBuffer& to_block, u64 to_index, ByteBuffer const& from_block, u64 from_index, u64 count);
|
||||
ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length);
|
||||
ThrowCompletionOr<void> detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional<Value> key = {});
|
||||
@ -188,10 +214,10 @@ ThrowCompletionOr<Value> ArrayBuffer::get_value(size_t byte_index, [[maybe_unuse
|
||||
VERIFY(!is_detached());
|
||||
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
VERIFY(buffer().bytes().slice(byte_index).size() >= sizeof(T));
|
||||
VERIFY(m_data_block.buffer().bytes().slice(byte_index).size() >= sizeof(T));
|
||||
|
||||
// 3. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
auto& block = buffer();
|
||||
auto& block = m_data_block.buffer();
|
||||
|
||||
// 4. Let elementSize be the Element Size value specified in Table 70 for Element Type type.
|
||||
auto element_size = sizeof(T);
|
||||
@ -300,7 +326,7 @@ ThrowCompletionOr<void> ArrayBuffer::set_value(size_t byte_index, Value value, [
|
||||
VERIFY(!is_detached());
|
||||
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
VERIFY(buffer().bytes().slice(byte_index).size() >= sizeof(T));
|
||||
VERIFY(m_data_block.buffer().bytes().slice(byte_index).size() >= sizeof(T));
|
||||
|
||||
// 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number.
|
||||
if constexpr (IsIntegral<T> && sizeof(T) == 8)
|
||||
@ -309,7 +335,7 @@ ThrowCompletionOr<void> ArrayBuffer::set_value(size_t byte_index, Value value, [
|
||||
VERIFY(value.is_number());
|
||||
|
||||
// 4. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
auto& block = buffer();
|
||||
auto& block = m_data_block.buffer();
|
||||
|
||||
// FIXME: 5. Let elementSize be the Element Size value specified in Table 70 for Element Type type.
|
||||
|
||||
@ -346,9 +372,9 @@ ThrowCompletionOr<Value> ArrayBuffer::get_modify_set_value(size_t byte_index, Va
|
||||
|
||||
// FIXME: Check for shared buffer
|
||||
|
||||
auto raw_bytes_read = TRY_OR_THROW_OOM(vm, buffer_impl().slice(byte_index, sizeof(T)));
|
||||
auto raw_bytes_read = TRY_OR_THROW_OOM(vm, m_data_block.buffer().slice(byte_index, sizeof(T)));
|
||||
auto raw_bytes_modified = operation(raw_bytes_read, raw_bytes);
|
||||
raw_bytes_modified.span().copy_to(buffer_impl().span().slice(byte_index));
|
||||
raw_bytes_modified.span().copy_to(m_data_block.buffer().span().slice(byte_index));
|
||||
|
||||
return raw_bytes_to_numeric<T>(vm, raw_bytes_read, is_little_endian);
|
||||
}
|
||||
|
@ -139,7 +139,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::byte_length_getter)
|
||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
// FIXME: Check for shared buffer
|
||||
if (array_buffer_object->is_shared_array_buffer())
|
||||
return vm.throw_completion<TypeError>(ErrorType::ThisCannotBeSharedArrayBuffer);
|
||||
|
||||
// NOTE: These steps are done in byte_length()
|
||||
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
||||
|
@ -293,6 +293,7 @@
|
||||
M(TemporalUnknownCriticalAnnotation, "Unknown annotation key in critical annotation: '{}'") \
|
||||
M(TemporalZonedDateTimeRoundZeroOrNegativeLengthDay, "Cannot round a ZonedDateTime in a calendar or time zone that has zero or " \
|
||||
"negative length days") \
|
||||
M(ThisCannotBeSharedArrayBuffer, "|this| cannot be a SharedArrayBuffer") \
|
||||
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
||||
M(ThisIsAlreadyInitialized, "|this| is already initialized") \
|
||||
M(ToObjectNullOrUndefined, "ToObject on null or undefined") \
|
||||
|
@ -37,7 +37,7 @@ JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::byte_length_getter)
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
// 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception.
|
||||
// FIXME: Check for shared buffer
|
||||
|
||||
// 4. Let length be O.[[ArrayBufferByteLength]].
|
||||
|
@ -309,7 +309,7 @@ private:
|
||||
auto data_copy = TRY(JS::create_byte_data_block(m_vm, size));
|
||||
|
||||
// 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size).
|
||||
JS::copy_data_block_bytes(data_copy, 0, array_buffer.buffer(), 0, size);
|
||||
JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size);
|
||||
|
||||
// FIXME: 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer",
|
||||
// [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }.
|
||||
@ -318,7 +318,7 @@ private:
|
||||
// 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }.
|
||||
else {
|
||||
vector.append(ValueTag::ArrayBuffer);
|
||||
TRY(serialize_bytes(vector, data_copy.bytes()));
|
||||
TRY(serialize_bytes(vector, data_copy.buffer().bytes()));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
Loading…
Reference in New Issue
Block a user