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:
Tobias Soppa 2023-09-11 10:35:03 +02:00 committed by Andrew Kaster
parent 67e07fa4e2
commit 9267e24741
Notes: sideshowbarker 2024-07-17 06:51:48 +09:00
6 changed files with 81 additions and 35 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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𝔽.

View File

@ -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") \

View File

@ -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]].

View File

@ -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 {};