mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 13:27:34 +00: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
6 changed files with 81 additions and 35 deletions
|
@ -32,14 +32,14 @@ NonnullGCPtr<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer* buffer)
|
||||||
|
|
||||||
ArrayBuffer::ArrayBuffer(ByteBuffer buffer, Object& prototype)
|
ArrayBuffer::ArrayBuffer(ByteBuffer buffer, Object& prototype)
|
||||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||||
, m_buffer(move(buffer))
|
, m_data_block(DataBlock { move(buffer), DataBlock::Shared::No })
|
||||||
, m_detach_key(js_undefined())
|
, m_detach_key(js_undefined())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayBuffer::ArrayBuffer(ByteBuffer* buffer, Object& prototype)
|
ArrayBuffer::ArrayBuffer(ByteBuffer* buffer, Object& prototype)
|
||||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||||
, m_buffer(buffer)
|
, m_data_block(DataBlock { buffer, DataBlock::Shared::No })
|
||||||
, m_detach_key(js_undefined())
|
, 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
|
// 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.
|
// 1. If size > 2^53 - 1, throw a RangeError exception.
|
||||||
if (size > MAX_ARRAY_LIKE_INDEX)
|
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);
|
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, size);
|
||||||
|
|
||||||
// 4. Return db.
|
// 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
|
// 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));
|
auto block = TRY(create_byte_data_block(vm, byte_length));
|
||||||
|
|
||||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||||
obj->set_buffer(move(block));
|
obj->set_data_block(move(block));
|
||||||
|
|
||||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
// 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]] »).
|
// 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));
|
auto obj = TRY(ordinary_create_from_constructor<ArrayBuffer>(vm, constructor, &Intrinsics::shared_array_buffer_prototype, nullptr));
|
||||||
|
|
||||||
// FIXME: 2. Let block be ? CreateSharedByteDataBlock(byteLength).
|
// 2. Let block be ? CreateSharedByteDataBlock(byteLength).
|
||||||
auto block = TRY(create_byte_data_block(vm, byte_length));
|
auto block = TRY(create_shared_byte_data_block(vm, byte_length));
|
||||||
|
|
||||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||||
obj->set_buffer(move(block));
|
obj->set_data_block(move(block));
|
||||||
|
|
||||||
// 5. Return obj.
|
// 5. Return obj.
|
||||||
return obj;
|
return obj;
|
||||||
|
|
|
@ -27,6 +27,25 @@ enum class PreserveResizability {
|
||||||
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 {
|
class ArrayBuffer : public Object {
|
||||||
JS_OBJECT(ArrayBuffer, Object);
|
JS_OBJECT(ArrayBuffer, Object);
|
||||||
|
|
||||||
|
@ -42,31 +61,47 @@ public:
|
||||||
if (is_detached())
|
if (is_detached())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return buffer_impl().size();
|
return m_data_block.buffer().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
// [[ArrayBufferData]]
|
// [[ArrayBufferData]]
|
||||||
ByteBuffer& buffer() { return buffer_impl(); }
|
ByteBuffer& buffer() { return m_data_block.buffer(); }
|
||||||
ByteBuffer const& buffer() const { return buffer_impl(); }
|
ByteBuffer const& buffer() const { return m_data_block.buffer(); }
|
||||||
|
|
||||||
// Used by allocate_array_buffer() to attach the data block after construction
|
// 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; }
|
Value detach_key() const { return m_detach_key; }
|
||||||
void set_detach_key(Value detach_key) { m_detach_key = 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
|
// 25.1.2.2 IsDetachedBuffer ( arrayBuffer ), https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||||
bool is_detached() const
|
bool is_detached() const
|
||||||
{
|
{
|
||||||
// 1. If arrayBuffer.[[ArrayBufferData]] is null, return true.
|
// 1. If arrayBuffer.[[ArrayBufferData]] is null, return true.
|
||||||
if (m_buffer.has<Empty>())
|
if (m_data_block.byte_buffer.has<Empty>())
|
||||||
return true;
|
return true;
|
||||||
// 2. Return false.
|
// 2. Return false.
|
||||||
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 {
|
enum Order {
|
||||||
SeqCst,
|
SeqCst,
|
||||||
Unordered
|
Unordered
|
||||||
|
@ -84,22 +119,13 @@ private:
|
||||||
|
|
||||||
virtual void visit_edges(Visitor&) override;
|
virtual void visit_edges(Visitor&) override;
|
||||||
|
|
||||||
ByteBuffer& buffer_impl()
|
DataBlock m_data_block;
|
||||||
{
|
|
||||||
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;
|
|
||||||
// The various detach related members of ArrayBuffer are not used by any ECMA262 functionality,
|
// 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.
|
// but are required to be available for the use of various harnesses like the Test262 test runner.
|
||||||
Value m_detach_key;
|
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);
|
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<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length);
|
||||||
ThrowCompletionOr<void> detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional<Value> key = {});
|
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());
|
VERIFY(!is_detached());
|
||||||
|
|
||||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
// 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]].
|
// 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.
|
// 4. Let elementSize be the Element Size value specified in Table 70 for Element Type type.
|
||||||
auto element_size = sizeof(T);
|
auto element_size = sizeof(T);
|
||||||
|
@ -300,7 +326,7 @@ ThrowCompletionOr<void> ArrayBuffer::set_value(size_t byte_index, Value value, [
|
||||||
VERIFY(!is_detached());
|
VERIFY(!is_detached());
|
||||||
|
|
||||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
// 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.
|
// 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number.
|
||||||
if constexpr (IsIntegral<T> && sizeof(T) == 8)
|
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());
|
VERIFY(value.is_number());
|
||||||
|
|
||||||
// 4. Let block be arrayBuffer.[[ArrayBufferData]].
|
// 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.
|
// 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
|
// 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);
|
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);
|
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));
|
auto array_buffer_object = TRY(typed_this_value(vm));
|
||||||
|
|
||||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
// 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()
|
// NOTE: These steps are done in byte_length()
|
||||||
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
||||||
|
|
|
@ -293,6 +293,7 @@
|
||||||
M(TemporalUnknownCriticalAnnotation, "Unknown annotation key in critical annotation: '{}'") \
|
M(TemporalUnknownCriticalAnnotation, "Unknown annotation key in critical annotation: '{}'") \
|
||||||
M(TemporalZonedDateTimeRoundZeroOrNegativeLengthDay, "Cannot round a ZonedDateTime in a calendar or time zone that has zero or " \
|
M(TemporalZonedDateTimeRoundZeroOrNegativeLengthDay, "Cannot round a ZonedDateTime in a calendar or time zone that has zero or " \
|
||||||
"negative length days") \
|
"negative length days") \
|
||||||
|
M(ThisCannotBeSharedArrayBuffer, "|this| cannot be a SharedArrayBuffer") \
|
||||||
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
|
||||||
M(ThisIsAlreadyInitialized, "|this| is already initialized") \
|
M(ThisIsAlreadyInitialized, "|this| is already initialized") \
|
||||||
M(ToObjectNullOrUndefined, "ToObject on null or undefined") \
|
M(ToObjectNullOrUndefined, "ToObject on null or undefined") \
|
||||||
|
|
|
@ -37,7 +37,7 @@ JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::byte_length_getter)
|
||||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||||
auto array_buffer_object = TRY(typed_this_value(vm));
|
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
|
// FIXME: Check for shared buffer
|
||||||
|
|
||||||
// 4. Let length be O.[[ArrayBufferByteLength]].
|
// 4. Let length be O.[[ArrayBufferByteLength]].
|
||||||
|
|
|
@ -309,7 +309,7 @@ private:
|
||||||
auto data_copy = TRY(JS::create_byte_data_block(m_vm, size));
|
auto data_copy = TRY(JS::create_byte_data_block(m_vm, size));
|
||||||
|
|
||||||
// 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, 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",
|
// FIXME: 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer",
|
||||||
// [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }.
|
// [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }.
|
||||||
|
@ -318,7 +318,7 @@ private:
|
||||||
// 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }.
|
// 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }.
|
||||||
else {
|
else {
|
||||||
vector.append(ValueTag::ArrayBuffer);
|
vector.append(ValueTag::ArrayBuffer);
|
||||||
TRY(serialize_bytes(vector, data_copy.bytes()));
|
TRY(serialize_bytes(vector, data_copy.buffer().bytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue