LibJS: Isolate ArrayBuffer backing stores
Move owned ArrayBuffer and SharedArrayBuffer data blocks into the ArrayBuffer heap partition. Keep unowned and host storage explicit, so Wasm memory and external LibWeb buffers stay outside this partition. Introduce DataBlock::OwnedBackingStore as the LibJS-owned byte storage representation. Expose byte spans instead of a ByteBuffer object, giving ArrayBuffer one allocation boundary that can later grow toward guarded or caged storage. Let callers that need ByteBuffer data copy from backing-store bytes. Keep TransferArrayBuffer zero-copy by moving the DataBlock directly instead of materializing a ByteBuffer in between. Update the Wasm typed-array test helper to compare viewed byte ranges after ArrayBuffer stops exposing ByteBuffer identity.
This commit is contained in:
parent
058c0ca6ed
commit
45c499d185
15 changed files with 227 additions and 84 deletions
|
|
@ -128,6 +128,9 @@ static mi_heap_t* heap_for_partition(HeapPartition partition)
|
|||
switch (partition) {
|
||||
case HeapPartition::General:
|
||||
return mi_heap_get_default();
|
||||
case HeapPartition::ArrayBuffer:
|
||||
static mi_heap_t* array_buffer_heap = mi_heap_new();
|
||||
return array_buffer_heap;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
enum class HeapPartition {
|
||||
General,
|
||||
ArrayBuffer,
|
||||
};
|
||||
|
||||
[[nodiscard]] void* ak_kcalloc(size_t count, size_t size);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ static GC::Ref<Object> prototype_for_shared_state(Realm& realm, DataBlock::Share
|
|||
|
||||
ThrowCompletionOr<GC::Ref<ArrayBuffer>> ArrayBuffer::create(Realm& realm, size_t byte_length, DataBlock::Shared is_shared)
|
||||
{
|
||||
auto buffer = ByteBuffer::create_zeroed(byte_length);
|
||||
auto buffer = DataBlock::OwnedBackingStore::create_zeroed(byte_length);
|
||||
if (buffer.is_error())
|
||||
return realm.vm().throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, byte_length);
|
||||
|
||||
|
|
@ -33,7 +33,11 @@ ThrowCompletionOr<GC::Ref<ArrayBuffer>> ArrayBuffer::create(Realm& realm, size_t
|
|||
|
||||
GC::Ref<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer buffer, DataBlock::Shared is_shared)
|
||||
{
|
||||
auto array_buffer = realm.create<ArrayBuffer>(move(buffer), is_shared, prototype_for_shared_state(realm, is_shared));
|
||||
auto owned_buffer = MUST(DataBlock::OwnedBackingStore::create_uninitialized(buffer.size()));
|
||||
if (!buffer.is_empty())
|
||||
__builtin_memcpy(owned_buffer.data(), buffer.data(), buffer.size());
|
||||
|
||||
auto array_buffer = realm.create<ArrayBuffer>(move(owned_buffer), is_shared, prototype_for_shared_state(realm, is_shared));
|
||||
realm.vm().heap().did_allocate_external_memory(array_buffer->external_memory_size());
|
||||
return array_buffer;
|
||||
}
|
||||
|
|
@ -43,12 +47,20 @@ GC::Ref<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer* buffer, DataB
|
|||
return realm.create<ArrayBuffer>(buffer, is_shared, prototype_for_shared_state(realm, is_shared));
|
||||
}
|
||||
|
||||
GC::Ref<ArrayBuffer> ArrayBuffer::create(Realm& realm, DataBlock block)
|
||||
{
|
||||
auto is_shared = block.is_shared;
|
||||
auto array_buffer = realm.create<ArrayBuffer>(static_cast<ByteBuffer*>(nullptr), is_shared, prototype_for_shared_state(realm, is_shared));
|
||||
array_buffer->set_data_block(move(block));
|
||||
return array_buffer;
|
||||
}
|
||||
|
||||
GC::Ref<ArrayBuffer> ArrayBuffer::create(Realm& realm, DataBlock::UnownedExternalBuffer buffer, DataBlock::Shared is_shared)
|
||||
{
|
||||
return realm.create<ArrayBuffer>(buffer, is_shared, prototype_for_shared_state(realm, is_shared));
|
||||
}
|
||||
|
||||
ArrayBuffer::ArrayBuffer(ByteBuffer buffer, DataBlock::Shared is_shared, Object& prototype)
|
||||
ArrayBuffer::ArrayBuffer(DataBlock::OwnedBackingStore buffer, DataBlock::Shared is_shared, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_data_block(DataBlock { move(buffer), is_shared })
|
||||
, m_detach_key(js_undefined())
|
||||
|
|
@ -91,6 +103,22 @@ void ArrayBuffer::did_change_data_block_capacity(size_t old_external_memory_size
|
|||
account_external_memory_change(old_external_memory_size, external_memory_size());
|
||||
}
|
||||
|
||||
ErrorOr<void> ArrayBuffer::try_resize(size_t new_size, DataBlock::ZeroFillNewBytes zero_fill_new_bytes)
|
||||
{
|
||||
auto old_external_memory_size = external_memory_size();
|
||||
TRY(m_data_block.try_resize(new_size, zero_fill_new_bytes));
|
||||
did_change_data_block_capacity(old_external_memory_size);
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> ArrayBuffer::try_ensure_capacity(size_t new_capacity)
|
||||
{
|
||||
auto old_external_memory_size = external_memory_size();
|
||||
TRY(m_data_block.try_ensure_capacity(new_capacity));
|
||||
did_change_data_block_capacity(old_external_memory_size);
|
||||
return {};
|
||||
}
|
||||
|
||||
void ArrayBuffer::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
|
|
@ -108,7 +136,7 @@ ThrowCompletionOr<DataBlock> create_byte_data_block(VM& vm, size_t size)
|
|||
|
||||
// 2. Let db be a new Data Block value consisting of size bytes. If it is impossible to create such a Data Block, throw a RangeError exception.
|
||||
// 3. Set all of the bytes of db to 0.
|
||||
auto data_block = ByteBuffer::create_zeroed(size);
|
||||
auto data_block = DataBlock::OwnedBackingStore::create_zeroed(size);
|
||||
if (data_block.is_error())
|
||||
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, size);
|
||||
|
||||
|
|
@ -121,7 +149,7 @@ ThrowCompletionOr<DataBlock> create_byte_data_block(VM& vm, size_t size)
|
|||
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);
|
||||
auto data_block = DataBlock::OwnedBackingStore::create_zeroed(size);
|
||||
if (data_block.is_error())
|
||||
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, size);
|
||||
|
||||
|
|
@ -135,10 +163,11 @@ static ThrowCompletionOr<DataBlock> create_shared_byte_data_block(VM& vm, size_t
|
|||
}
|
||||
|
||||
// 6.2.9.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count ), https://tc39.es/ecma262/#sec-copydatablockbytes
|
||||
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(Bytes to_block, u64 to_index, ReadonlyBytes from_block, u64 from_index, u64 count)
|
||||
{
|
||||
// 1. Assert: fromBlock and toBlock are distinct values.
|
||||
VERIFY(&to_block != &from_block);
|
||||
if (count > 0)
|
||||
VERIFY(to_block.data() != from_block.data());
|
||||
|
||||
// 2. Let fromSize be the number of bytes in fromBlock.
|
||||
auto from_size = from_block.size();
|
||||
|
|
@ -223,10 +252,8 @@ ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM& vm, FunctionObject& co
|
|||
if (allocating_resizable_buffer) {
|
||||
// a. If it is not possible to create a Data Block block consisting of maxByteLength bytes, throw a RangeError exception.
|
||||
// b. NOTE: Resizable ArrayBuffers are designed to be implementable with in-place growth. Implementations may throw if, for example, virtual memory cannot be reserved up front.
|
||||
auto old_external_memory_size = obj->external_memory_size();
|
||||
if (auto result = obj->buffer().try_ensure_capacity(*max_byte_length); result.is_error())
|
||||
if (auto result = obj->try_ensure_capacity(*max_byte_length); result.is_error())
|
||||
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, *max_byte_length);
|
||||
obj->did_change_data_block_capacity(old_external_memory_size);
|
||||
|
||||
// c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength.
|
||||
obj->set_max_byte_length(*max_byte_length);
|
||||
|
|
@ -305,7 +332,7 @@ void ArrayBuffer::detach_buffer()
|
|||
account_external_memory_change(old_external_memory_size, 0);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<ByteBuffer> ArrayBuffer::detach_and_take_bytes(VM& vm)
|
||||
ThrowCompletionOr<DataBlock> ArrayBuffer::detach_and_take_data_block(VM& vm)
|
||||
{
|
||||
VERIFY(!is_shared_array_buffer());
|
||||
|
||||
|
|
@ -313,11 +340,7 @@ ThrowCompletionOr<ByteBuffer> ArrayBuffer::detach_and_take_bytes(VM& vm)
|
|||
return vm.throw_completion<TypeError>(ErrorType::DetachKeyMismatch, js_undefined(), detach_key());
|
||||
|
||||
auto old_external_memory_size = external_memory_size();
|
||||
ByteBuffer bytes;
|
||||
if (auto* buffer = m_data_block.byte_buffer.get_pointer<ByteBuffer>())
|
||||
bytes = move(*buffer);
|
||||
else
|
||||
bytes = MUST(ByteBuffer::copy(span()));
|
||||
auto block = move(m_data_block);
|
||||
for (auto& cached_view : m_cached_views) {
|
||||
auto& view = static_cast<TypedArrayBase&>(cached_view);
|
||||
if (view.viewed_array_buffer() == this)
|
||||
|
|
@ -326,7 +349,7 @@ ThrowCompletionOr<ByteBuffer> ArrayBuffer::detach_and_take_bytes(VM& vm)
|
|||
m_cached_views.clear();
|
||||
m_data_block.byte_buffer = Empty {};
|
||||
account_external_memory_change(old_external_memory_size, 0);
|
||||
return bytes;
|
||||
return block;
|
||||
}
|
||||
|
||||
void ArrayBuffer::register_cached_typed_array_view(TypedArrayBase& view)
|
||||
|
|
@ -425,7 +448,7 @@ ThrowCompletionOr<GC::Ref<ArrayBuffer>> allocate_shared_array_buffer(VM& vm, Fun
|
|||
// 7. Let block be ? CreateSharedByteDataBlock(allocLength).
|
||||
// AD-HOC: We track [[ArrayBufferByteLength(Data)]] via the length of the Data Block, so shrink it down to byteLength.
|
||||
auto block = TRY(create_shared_byte_data_block(vm, alloc_length));
|
||||
block.buffer().set_size(byte_length);
|
||||
block.set_size(byte_length);
|
||||
|
||||
// 8. Set obj.[[ArrayBufferData]] to block.
|
||||
obj->set_data_block(move(block));
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <AK/Function.h>
|
||||
#include <AK/IntrusiveList.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/kmalloc.h>
|
||||
#include <LibJS/Export.h>
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
|
|
@ -52,6 +53,99 @@ struct DataBlock {
|
|||
Yes,
|
||||
};
|
||||
|
||||
enum class ZeroFillNewBytes {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
class OwnedBackingStore {
|
||||
public:
|
||||
OwnedBackingStore() = default;
|
||||
~OwnedBackingStore() { kfree(m_data); }
|
||||
|
||||
OwnedBackingStore(OwnedBackingStore&& other)
|
||||
{
|
||||
move_from(move(other));
|
||||
}
|
||||
|
||||
OwnedBackingStore& operator=(OwnedBackingStore&& other)
|
||||
{
|
||||
if (this != &other) {
|
||||
kfree(m_data);
|
||||
move_from(move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
OwnedBackingStore(OwnedBackingStore const&) = delete;
|
||||
OwnedBackingStore& operator=(OwnedBackingStore const&) = delete;
|
||||
|
||||
static ErrorOr<OwnedBackingStore> create_zeroed(size_t size)
|
||||
{
|
||||
OwnedBackingStore buffer;
|
||||
TRY(buffer.try_resize(size, ZeroFillNewBytes::Yes));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static ErrorOr<OwnedBackingStore> create_uninitialized(size_t size)
|
||||
{
|
||||
OwnedBackingStore buffer;
|
||||
TRY(buffer.try_resize(size, ZeroFillNewBytes::No));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
u8* data() { return m_data; }
|
||||
u8 const* data() const { return m_data; }
|
||||
size_t size() const { return m_size; }
|
||||
size_t capacity() const { return m_capacity; }
|
||||
Bytes bytes() { return { data(), size() }; }
|
||||
ReadonlyBytes bytes() const { return { data(), size() }; }
|
||||
|
||||
void set_size(size_t new_size, ZeroFillNewBytes zero_fill_new_bytes = ZeroFillNewBytes::No)
|
||||
{
|
||||
VERIFY(new_size <= capacity());
|
||||
if (zero_fill_new_bytes == ZeroFillNewBytes::Yes && new_size > m_size)
|
||||
__builtin_memset(data() + m_size, 0, new_size - m_size);
|
||||
m_size = new_size;
|
||||
}
|
||||
|
||||
ErrorOr<void> try_resize(size_t new_size, ZeroFillNewBytes zero_fill_new_bytes = ZeroFillNewBytes::No)
|
||||
{
|
||||
if (new_size <= m_size) {
|
||||
m_size = new_size;
|
||||
return {};
|
||||
}
|
||||
if (new_size > capacity())
|
||||
TRY(try_ensure_capacity(new_size));
|
||||
set_size(new_size, zero_fill_new_bytes);
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> try_ensure_capacity(size_t new_capacity)
|
||||
{
|
||||
if (new_capacity <= capacity())
|
||||
return {};
|
||||
auto* new_data = static_cast<u8*>(krealloc(HeapPartition::ArrayBuffer, m_data, new_capacity));
|
||||
if (!new_data)
|
||||
return AK::Error::from_errno(ENOMEM);
|
||||
m_data = new_data;
|
||||
m_capacity = new_capacity;
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
void move_from(OwnedBackingStore&& other)
|
||||
{
|
||||
m_data = exchange(other.m_data, nullptr);
|
||||
m_size = exchange(other.m_size, 0);
|
||||
m_capacity = exchange(other.m_capacity, 0);
|
||||
}
|
||||
|
||||
u8* m_data { nullptr };
|
||||
size_t m_size { 0 };
|
||||
size_t m_capacity { 0 };
|
||||
};
|
||||
|
||||
struct UnownedFixedLengthByteBuffer {
|
||||
explicit UnownedFixedLengthByteBuffer(ByteBuffer* buffer)
|
||||
: buffer(buffer)
|
||||
|
|
@ -93,21 +187,11 @@ struct DataBlock {
|
|||
GC::Ref<GC::Cell> owner;
|
||||
};
|
||||
|
||||
ByteBuffer& buffer()
|
||||
{
|
||||
return byte_buffer.visit(
|
||||
[&](Empty) -> ByteBuffer& { VERIFY_NOT_REACHED(); },
|
||||
[&](ByteBuffer& value) -> ByteBuffer& { return value; },
|
||||
[&](UnownedFixedLengthByteBuffer& value) -> ByteBuffer& { return *value.buffer; },
|
||||
[&](UnownedExternalBuffer&) -> ByteBuffer& { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
ByteBuffer const& buffer() const { return const_cast<DataBlock*>(this)->buffer(); }
|
||||
|
||||
u8* data()
|
||||
{
|
||||
return byte_buffer.visit(
|
||||
[](Empty) -> u8* { VERIFY_NOT_REACHED(); },
|
||||
[](ByteBuffer& value) -> u8* { return value.data(); },
|
||||
[](OwnedBackingStore& value) -> u8* { return value.data(); },
|
||||
[](UnownedFixedLengthByteBuffer& value) -> u8* { return value.buffer->data(); },
|
||||
[](UnownedExternalBuffer& value) -> u8* { return value.data ? value.data(value.context) : nullptr; });
|
||||
}
|
||||
|
|
@ -124,11 +208,44 @@ struct DataBlock {
|
|||
__builtin_memcpy(data() + offset, source, count);
|
||||
}
|
||||
|
||||
void set_size(size_t new_size, ZeroFillNewBytes zero_fill_new_bytes = ZeroFillNewBytes::No)
|
||||
{
|
||||
auto byte_buffer_zero_fill = zero_fill_new_bytes == ZeroFillNewBytes::Yes
|
||||
? ByteBuffer::ZeroFillNewElements::Yes
|
||||
: ByteBuffer::ZeroFillNewElements::No;
|
||||
byte_buffer.visit(
|
||||
[&](Empty) { VERIFY_NOT_REACHED(); },
|
||||
[&](OwnedBackingStore& value) { value.set_size(new_size, zero_fill_new_bytes); },
|
||||
[&](UnownedFixedLengthByteBuffer& value) { value.buffer->set_size(new_size, byte_buffer_zero_fill); },
|
||||
[&](UnownedExternalBuffer&) { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
ErrorOr<void> try_resize(size_t new_size, ZeroFillNewBytes zero_fill_new_bytes = ZeroFillNewBytes::No)
|
||||
{
|
||||
auto byte_buffer_zero_fill = zero_fill_new_bytes == ZeroFillNewBytes::Yes
|
||||
? ByteBuffer::ZeroFillNewElements::Yes
|
||||
: ByteBuffer::ZeroFillNewElements::No;
|
||||
return byte_buffer.visit(
|
||||
[&](Empty) -> ErrorOr<void> { VERIFY_NOT_REACHED(); },
|
||||
[&](OwnedBackingStore& value) { return value.try_resize(new_size, zero_fill_new_bytes); },
|
||||
[&](UnownedFixedLengthByteBuffer& value) { return value.buffer->try_resize(new_size, byte_buffer_zero_fill); },
|
||||
[&](UnownedExternalBuffer&) -> ErrorOr<void> { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
ErrorOr<void> try_ensure_capacity(size_t new_capacity)
|
||||
{
|
||||
return byte_buffer.visit(
|
||||
[&](Empty) -> ErrorOr<void> { VERIFY_NOT_REACHED(); },
|
||||
[&](OwnedBackingStore& value) { return value.try_ensure_capacity(new_capacity); },
|
||||
[&](UnownedFixedLengthByteBuffer& value) { return value.buffer->try_ensure_capacity(new_capacity); },
|
||||
[&](UnownedExternalBuffer&) -> ErrorOr<void> { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return byte_buffer.visit(
|
||||
[](Empty) -> size_t { return 0u; },
|
||||
[](ByteBuffer const& buffer) { return buffer.size(); },
|
||||
[](OwnedBackingStore const& buffer) { return buffer.size(); },
|
||||
[](UnownedFixedLengthByteBuffer const& value) { return value.size; },
|
||||
[](UnownedExternalBuffer const& value) {
|
||||
return value.size.visit(
|
||||
|
|
@ -141,14 +258,14 @@ struct DataBlock {
|
|||
{
|
||||
return byte_buffer.visit(
|
||||
[](Empty) -> size_t { return 0; },
|
||||
[](ByteBuffer const& buffer) { return buffer.is_inline() ? 0 : buffer.capacity(); },
|
||||
[](OwnedBackingStore const& buffer) { return buffer.capacity(); },
|
||||
[](UnownedFixedLengthByteBuffer const&) -> size_t { return 0; },
|
||||
[](UnownedExternalBuffer const&) -> size_t { return 0; });
|
||||
}
|
||||
|
||||
bool is_external() const { return byte_buffer.has<UnownedExternalBuffer>(); }
|
||||
|
||||
Variant<Empty, ByteBuffer, UnownedFixedLengthByteBuffer, UnownedExternalBuffer> byte_buffer;
|
||||
Variant<Empty, OwnedBackingStore, UnownedFixedLengthByteBuffer, UnownedExternalBuffer> byte_buffer;
|
||||
Shared is_shared = { Shared::No };
|
||||
};
|
||||
|
||||
|
|
@ -160,6 +277,7 @@ public:
|
|||
static ThrowCompletionOr<GC::Ref<ArrayBuffer>> create(Realm&, size_t, DataBlock::Shared = DataBlock::Shared::No);
|
||||
static GC::Ref<ArrayBuffer> create(Realm&, ByteBuffer, DataBlock::Shared = DataBlock::Shared::No);
|
||||
static GC::Ref<ArrayBuffer> create(Realm&, ByteBuffer*, DataBlock::Shared = DataBlock::Shared::No);
|
||||
static GC::Ref<ArrayBuffer> create(Realm&, DataBlock);
|
||||
static GC::Ref<ArrayBuffer> create(Realm&, DataBlock::UnownedExternalBuffer, DataBlock::Shared = DataBlock::Shared::No);
|
||||
|
||||
virtual ~ArrayBuffer() override = default;
|
||||
|
|
@ -168,8 +286,6 @@ public:
|
|||
virtual size_t external_memory_size() const override { return m_data_block.external_memory_size(); }
|
||||
|
||||
// [[ArrayBufferData]]
|
||||
ByteBuffer& buffer() { return m_data_block.buffer(); }
|
||||
ByteBuffer const& buffer() const { return m_data_block.buffer(); }
|
||||
u8* data() { return m_data_block.data(); }
|
||||
u8 const* data() const { return m_data_block.data(); }
|
||||
Bytes span() { return m_data_block.span(); }
|
||||
|
|
@ -179,10 +295,9 @@ public:
|
|||
void overwrite(size_t offset, void const* source, size_t count) { m_data_block.overwrite(offset, source, count); }
|
||||
bool is_external() const { return m_data_block.is_external(); }
|
||||
|
||||
// Detaches this ArrayBuffer and returns its underlying bytes as a ByteBuffer for use in a TransferArrayBuffer-like
|
||||
// operation. Moves the storage when we own it and copies it for externally-owned buffers (e.g. Wasm memory).
|
||||
// Detaches this ArrayBuffer and returns its underlying DataBlock for use in a TransferArrayBuffer-like operation.
|
||||
// If detach fails, the underlying storage is left untouched.
|
||||
ThrowCompletionOr<ByteBuffer> detach_and_take_bytes(VM&);
|
||||
ThrowCompletionOr<DataBlock> detach_and_take_data_block(VM&);
|
||||
|
||||
// [[ArrayBufferMaxByteLength]]
|
||||
size_t max_byte_length() const { return m_max_byte_length.value(); }
|
||||
|
|
@ -191,6 +306,8 @@ public:
|
|||
// Used by allocate_array_buffer() to attach the data block after construction
|
||||
void set_data_block(DataBlock);
|
||||
void did_change_data_block_capacity(size_t old_external_memory_size);
|
||||
ErrorOr<void> try_resize(size_t, DataBlock::ZeroFillNewBytes = DataBlock::ZeroFillNewBytes::No);
|
||||
ErrorOr<void> try_ensure_capacity(size_t);
|
||||
|
||||
Value detach_key() const { return m_detach_key; }
|
||||
void set_detach_key(Value detach_key) { m_detach_key = detach_key; }
|
||||
|
|
@ -221,7 +338,7 @@ public:
|
|||
|
||||
bool can_cache_typed_array_view_data_pointer() const
|
||||
{
|
||||
return !is_detached() && is_fixed_length() && m_data_block.byte_buffer.has<ByteBuffer>();
|
||||
return !is_detached() && is_fixed_length() && m_data_block.byte_buffer.has<DataBlock::OwnedBackingStore>();
|
||||
}
|
||||
|
||||
// 25.2.2.2 IsSharedArrayBuffer ( obj ), https://tc39.es/ecma262/#sec-issharedarraybuffer
|
||||
|
|
@ -252,7 +369,7 @@ public:
|
|||
Value get_modify_set_value(size_t byte_index, Value value, ReadWriteModifyFunction operation, bool is_little_endian = true);
|
||||
|
||||
private:
|
||||
ArrayBuffer(ByteBuffer buffer, DataBlock::Shared, Object& prototype);
|
||||
ArrayBuffer(DataBlock::OwnedBackingStore buffer, DataBlock::Shared, Object& prototype);
|
||||
ArrayBuffer(ByteBuffer* buffer, DataBlock::Shared, Object& prototype);
|
||||
ArrayBuffer(DataBlock::UnownedExternalBuffer buffer, DataBlock::Shared, Object& prototype);
|
||||
|
||||
|
|
@ -275,7 +392,7 @@ template<>
|
|||
inline bool Object::fast_is<ArrayBuffer>() const { return is_array_buffer(); }
|
||||
|
||||
JS_API ThrowCompletionOr<DataBlock> create_byte_data_block(VM& vm, size_t size);
|
||||
JS_API void copy_data_block_bytes(ByteBuffer& to_block, u64 to_index, ByteBuffer const& from_block, u64 from_index, u64 count);
|
||||
JS_API void copy_data_block_bytes(Bytes to_block, u64 to_index, ReadonlyBytes from_block, u64 from_index, u64 count);
|
||||
ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length, Optional<size_t> const& max_byte_length = {});
|
||||
ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM&, ArrayBuffer& array_buffer, Value new_length, PreserveResizability preserve_resizability);
|
||||
JS_API ThrowCompletionOr<void> detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional<Value> key = {});
|
||||
|
|
|
|||
|
|
@ -163,10 +163,9 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::resize)
|
|||
auto copy_length = min(new_byte_length, array_buffer_object->byte_length());
|
||||
|
||||
// 12. Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength).
|
||||
if (array_buffer_object->is_external())
|
||||
new_block.overwrite(0, array_buffer_object->data(), copy_length);
|
||||
else
|
||||
copy_data_block_bytes(new_block.buffer(), 0, array_buffer_object->buffer(), 0, copy_length);
|
||||
auto new_block_bytes = new_block.bytes();
|
||||
auto old_block_bytes = array_buffer_object->bytes();
|
||||
copy_data_block_bytes(new_block_bytes, 0, old_block_bytes, 0, copy_length);
|
||||
|
||||
// 13. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. Implementations may implement this method as in-place growth or shrinkage.
|
||||
|
||||
|
|
@ -278,11 +277,14 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice)
|
|||
// a. Let count be min(newLen, currentLen - first).
|
||||
auto count = min(new_length, current_length - first);
|
||||
|
||||
// Let fromBuf be O.[[ArrayBufferData]].
|
||||
auto from_buf = array_buffer_object->bytes();
|
||||
|
||||
// Let toBuf be new.[[ArrayBufferData]].
|
||||
auto to_buf = new_array_buffer_object->bytes();
|
||||
|
||||
// b. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, count).
|
||||
if (array_buffer_object->is_external())
|
||||
new_array_buffer_object->overwrite(0, array_buffer_object->data() + (size_t)first, (size_t)count);
|
||||
else
|
||||
copy_data_block_bytes(new_array_buffer_object->buffer(), 0, array_buffer_object->buffer(), first, count);
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first, count);
|
||||
}
|
||||
|
||||
// 28. Return new.
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ static ThrowCompletionOr<Value> atomic_compare_exchange_impl(VM& vm, TypedArrayB
|
|||
auto* buffer = typed_array.viewed_array_buffer();
|
||||
|
||||
// 3. Let block be buffer.[[ArrayBufferData]].
|
||||
auto& block = buffer->buffer();
|
||||
auto block = buffer->bytes();
|
||||
|
||||
// 7. Let elementType be TypedArrayElementType(typedArray).
|
||||
// 8. Let elementSize be TypedArrayElementSize(typedArray).
|
||||
|
|
@ -342,8 +342,7 @@ static ThrowCompletionOr<Value> atomic_compare_exchange_impl(VM& vm, TypedArrayB
|
|||
// 13. Else,
|
||||
|
||||
// a. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[byteIndexInBuffer].
|
||||
// FIXME: Propagate errors.
|
||||
auto raw_bytes_read = MUST(block.slice(byte_index_in_buffer, sizeof(T)));
|
||||
auto raw_bytes_read = MUST(ByteBuffer::copy(block.slice(byte_index_in_buffer, sizeof(T))));
|
||||
|
||||
// b. If ByteListEqual(rawBytesRead, expectedBytes) is true, then
|
||||
// i. Store the individual bytes of replacementBytes into block, starting at block[byteIndexInBuffer].
|
||||
|
|
@ -352,7 +351,7 @@ static ThrowCompletionOr<Value> atomic_compare_exchange_impl(VM& vm, TypedArrayB
|
|||
} else {
|
||||
using U = Conditional<IsSame<ClampedU8, T>, u8, T>;
|
||||
|
||||
auto* v = reinterpret_cast<U*>(block.span().slice(byte_index_in_buffer).data());
|
||||
auto* v = reinterpret_cast<U*>(block.slice(byte_index_in_buffer).data());
|
||||
auto* e = reinterpret_cast<U*>(expected_bytes.data());
|
||||
auto* r = reinterpret_cast<U*>(replacement_bytes.data());
|
||||
(void)AK::atomic_compare_exchange_strong(v, *e, *r);
|
||||
|
|
@ -576,7 +575,7 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::notify)
|
|||
auto* buffer = typed_array->viewed_array_buffer();
|
||||
|
||||
// 5. Let block be buffer.[[ArrayBufferData]].
|
||||
auto& block = buffer->buffer();
|
||||
auto block = buffer->bytes();
|
||||
|
||||
// 6. If IsSharedArrayBuffer(buffer) is false, return +0𝔽.
|
||||
if (!buffer->is_shared_array_buffer())
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::grow)
|
|||
// FIXME: i. If ByteListEqual(readByteLengthRawBytes, currentByteLengthRawBytes) is true, return undefined.
|
||||
// FIXME: j. Set currentByteLengthRawBytes to readByteLengthRawBytes.
|
||||
|
||||
if (auto result = array_buffer_object->buffer().try_resize(new_byte_length, ByteBuffer::ZeroFillNewElements::Yes); result.is_error())
|
||||
if (auto result = array_buffer_object->try_resize(new_byte_length, DataBlock::ZeroFillNewBytes::Yes); result.is_error())
|
||||
return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, new_byte_length);
|
||||
|
||||
return js_undefined();
|
||||
|
|
@ -220,10 +220,10 @@ JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::slice)
|
|||
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();
|
||||
auto from_buf = array_buffer_object->bytes();
|
||||
|
||||
// 21. Let toBuf be new.[[ArrayBufferData]].
|
||||
auto& to_buf = new_array_buffer_object->buffer();
|
||||
auto to_buf = new_array_buffer_object->bytes();
|
||||
|
||||
// 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first, new_length);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public:
|
|||
ContentType content_type() const { return m_content_type; }
|
||||
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
|
||||
|
||||
// Cached raw pointer: viewed_array_buffer->buffer().data() + byte_offset.
|
||||
// Cached raw pointer: viewed_array_buffer->data() + byte_offset.
|
||||
// nullptr means "not cached, use slow path". This is only safe for
|
||||
// fixed-length ArrayBuffers that own stable backing storage.
|
||||
u8* cached_data_ptr() const { return m_data; }
|
||||
|
|
|
|||
|
|
@ -194,10 +194,8 @@ VM::VM(ErrorMessages error_messages)
|
|||
|
||||
// The default implementation of HostResizeArrayBuffer is to return NormalCompletion(unhandled).
|
||||
|
||||
auto old_external_memory_size = buffer.external_memory_size();
|
||||
if (auto result = buffer.buffer().try_resize(new_byte_length, ByteBuffer::ZeroFillNewElements::Yes); result.is_error())
|
||||
if (auto result = buffer.try_resize(new_byte_length, DataBlock::ZeroFillNewBytes::Yes); result.is_error())
|
||||
return throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, new_byte_length);
|
||||
buffer.did_change_data_block_capacity(old_external_memory_size);
|
||||
|
||||
return HandledByHost::Handled;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -989,7 +989,7 @@ GC::Ref<WebIDL::Promise> SubtleCrypto::wrap_key(Bindings::KeyFormat format, GC::
|
|||
|| format == Bindings::KeyFormat::Pkcs8
|
||||
|| format == Bindings::KeyFormat::Spki) {
|
||||
// Let bytes be exportedKey.
|
||||
bytes = as<JS::ArrayBuffer>(*exported_key).buffer();
|
||||
bytes = MUST(ByteBuffer::copy(as<JS::ArrayBuffer>(*exported_key).bytes()));
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
|
@ -1153,7 +1153,7 @@ GC::Ref<WebIDL::Promise> SubtleCrypto::unwrap_key(Bindings::KeyFormat format, We
|
|||
|| format == Bindings::KeyFormat::Pkcs8
|
||||
|| format == Bindings::KeyFormat::Spki) {
|
||||
// Let key be bytes.
|
||||
key = bytes->buffer();
|
||||
key = MUST(ByteBuffer::copy(bytes->bytes()));
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,14 +144,14 @@ static WebIDL::ExceptionOr<void> serialize_array_buffer(JS::VM& vm, TransferData
|
|||
// [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]],
|
||||
// FIXME: [[AgentCluster]]: the surrounding agent's agent cluster }.
|
||||
data_holder.encode(ValueTag::GrowableSharedArrayBuffer);
|
||||
data_holder.encode(array_buffer.buffer());
|
||||
data_holder.encode(MUST(ByteBuffer::copy(array_buffer.bytes())));
|
||||
data_holder.encode(array_buffer.max_byte_length());
|
||||
} else {
|
||||
// 4. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value.[[ArrayBufferData]],
|
||||
// [[ArrayBufferByteLength]]: value.[[ArrayBufferByteLength]],
|
||||
// FIXME: [[AgentCluster]]: the surrounding agent's agent cluster }.
|
||||
data_holder.encode(ValueTag::SharedArrayBuffer);
|
||||
data_holder.encode(array_buffer.buffer());
|
||||
data_holder.encode(MUST(ByteBuffer::copy(array_buffer.bytes())));
|
||||
}
|
||||
}
|
||||
// 2. Otherwise:
|
||||
|
|
@ -168,22 +168,21 @@ static WebIDL::ExceptionOr<void> serialize_array_buffer(JS::VM& vm, TransferData
|
|||
auto data_copy = TRY(JS::create_byte_data_block(vm, size));
|
||||
|
||||
// 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size).
|
||||
if (array_buffer.is_external())
|
||||
data_copy.overwrite(0, array_buffer.data(), size);
|
||||
else
|
||||
JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size);
|
||||
auto data_copy_bytes = data_copy.bytes();
|
||||
auto array_buffer_bytes = array_buffer.bytes();
|
||||
JS::copy_data_block_bytes(data_copy_bytes, 0, array_buffer_bytes, 0, size);
|
||||
|
||||
// 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer",
|
||||
// [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }.
|
||||
if (!array_buffer.is_fixed_length()) {
|
||||
data_holder.encode(ValueTag::ResizeableArrayBuffer);
|
||||
data_holder.encode(data_copy.buffer());
|
||||
data_holder.encode(MUST(ByteBuffer::copy(data_copy.bytes())));
|
||||
data_holder.encode(array_buffer.max_byte_length());
|
||||
}
|
||||
// 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }.
|
||||
else {
|
||||
data_holder.encode(ValueTag::ArrayBuffer);
|
||||
data_holder.encode(data_copy.buffer());
|
||||
data_holder.encode(MUST(ByteBuffer::copy(data_copy.bytes())));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
|
@ -1050,9 +1049,7 @@ WebIDL::ExceptionOr<SerializedTransferRecord> structured_serialize_with_transfer
|
|||
// 4. If transferable has an [[ArrayBufferData]] internal slot, then:
|
||||
if (array_buffer) {
|
||||
// 1. If transferable has an [[ArrayBufferMaxByteLength]] internal slot, then:
|
||||
auto buffer_data = array_buffer->is_external()
|
||||
? MUST(ByteBuffer::copy(array_buffer->bytes()))
|
||||
: ByteBuffer(array_buffer->buffer());
|
||||
auto buffer_data = MUST(ByteBuffer::copy(array_buffer->bytes()));
|
||||
|
||||
if (!array_buffer->is_fixed_length()) {
|
||||
// 1. Set dataHolder.[[Type]] to "ResizableArrayBuffer".
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ WebIDL::ExceptionOr<void> SourceBuffer::append_buffer(WebIDL::BufferSource data)
|
|||
|
||||
// 2. Add data to the end of the [[input buffer]].
|
||||
if (auto array_buffer = data.viewed_array_buffer(); array_buffer && !array_buffer->is_detached()) {
|
||||
auto bytes = array_buffer->buffer().bytes().slice(data.byte_offset(), data.byte_length());
|
||||
auto bytes = array_buffer->bytes().slice(data.byte_offset(), data.byte_length());
|
||||
m_processor->append_to_input_buffer(bytes);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -465,11 +465,11 @@ WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> transfer_array_buffer(JS::Realm& r
|
|||
// 2. Let arrayBufferData be O.[[ArrayBufferData]].
|
||||
// 3. Let arrayBufferByteLength be O.[[ArrayBufferByteLength]].
|
||||
// 4. Perform ? DetachArrayBuffer(O).
|
||||
// NB: We steal the underlying bytes and detach atomically so the transfer is zero-copy.
|
||||
auto array_buffer = TRY(buffer.detach_and_take_bytes(vm));
|
||||
// NB: We steal the underlying data block and detach atomically so the transfer is zero-copy.
|
||||
auto block = TRY(buffer.detach_and_take_data_block(vm));
|
||||
|
||||
// 5. Return a new ArrayBuffer object, created in the current Realm, whose [[ArrayBufferData]] internal slot value is arrayBufferData and whose [[ArrayBufferByteLength]] internal slot value is arrayBufferByteLength.
|
||||
return JS::ArrayBuffer::create(realm, move(array_buffer));
|
||||
return JS::ArrayBuffer::create(realm, move(block));
|
||||
}
|
||||
|
||||
// https://streams.spec.whatwg.org/#abstract-opdef-cloneasuint8array
|
||||
|
|
|
|||
|
|
@ -2106,10 +2106,9 @@ bool readable_byte_stream_controller_fill_pull_into_descriptor_from_queue(Readab
|
|||
|
||||
// 8. Perform ! CopyDataBlockBytes(pullIntoDescriptor’s buffer.[[ArrayBufferData]], destStart, headOfQueue’s buffer.[[ArrayBufferData]], headOfQueue’s byte offset, bytesToCopy).
|
||||
// NOTE: Stream buffers are never externally backed, so copy_data_block_bytes is safe here.
|
||||
if (pull_into_descriptor.buffer->is_external() || head_of_queue.buffer->is_external())
|
||||
pull_into_descriptor.buffer->overwrite(dest_start, head_of_queue.buffer->data() + head_of_queue.byte_offset, bytes_to_copy);
|
||||
else
|
||||
JS::copy_data_block_bytes(pull_into_descriptor.buffer->buffer(), dest_start, head_of_queue.buffer->buffer(), head_of_queue.byte_offset, bytes_to_copy);
|
||||
auto descriptor_buffer_bytes = pull_into_descriptor.buffer->bytes();
|
||||
auto queue_buffer_bytes = head_of_queue.buffer->bytes();
|
||||
JS::copy_data_block_bytes(descriptor_buffer_bytes, dest_start, queue_buffer_bytes, head_of_queue.byte_offset, bytes_to_copy);
|
||||
|
||||
// 9. If headOfQueue’s byte length is bytesToCopy,
|
||||
if (head_of_queue.byte_length == bytes_to_copy) {
|
||||
|
|
|
|||
|
|
@ -214,7 +214,11 @@ TESTJS_GLOBAL_FUNCTION(compare_typed_arrays, compareTypedArrays)
|
|||
if (!is<JS::TypedArrayBase>(*rhs))
|
||||
return vm.throw_completion<JS::TypeError>("Expected a TypedArray"sv);
|
||||
auto& rhs_array = static_cast<JS::TypedArrayBase&>(*rhs);
|
||||
return JS::Value(lhs_array.viewed_array_buffer()->buffer() == rhs_array.viewed_array_buffer()->buffer());
|
||||
auto lhs_record = JS::make_typed_array_with_buffer_witness_record(lhs_array, JS::ArrayBuffer::Order::SeqCst);
|
||||
auto rhs_record = JS::make_typed_array_with_buffer_witness_record(rhs_array, JS::ArrayBuffer::Order::SeqCst);
|
||||
auto lhs_bytes = lhs_array.viewed_array_buffer()->bytes().slice(lhs_array.byte_offset(), JS::typed_array_byte_length(lhs_record));
|
||||
auto rhs_bytes = rhs_array.viewed_array_buffer()->bytes().slice(rhs_array.byte_offset(), JS::typed_array_byte_length(rhs_record));
|
||||
return JS::Value(lhs_bytes == rhs_bytes);
|
||||
}
|
||||
|
||||
static bool _is_canonical_nan32(u32 value)
|
||||
|
|
@ -413,7 +417,7 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
|
|||
auto& array = static_cast<JS::TypedArrayBase&>(*object);
|
||||
u128 bits = 0;
|
||||
auto* ptr = bit_cast<u8*>(&bits);
|
||||
memcpy(ptr, array.viewed_array_buffer()->buffer().data(), 16);
|
||||
memcpy(ptr, array.viewed_array_buffer()->data(), 16);
|
||||
arguments.append(Wasm::Value(bits));
|
||||
break;
|
||||
}
|
||||
|
|
@ -475,7 +479,7 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
|
|||
u128 val = value.to<u128>();
|
||||
// FIXME: remove the MUST here
|
||||
auto buf = MUST(JS::ArrayBuffer::create(*vm.current_realm(), 16));
|
||||
memcpy(buf->buffer().data(), val.bytes().data(), 16);
|
||||
memcpy(buf->data(), val.bytes().data(), 16);
|
||||
return JS::Value(buf);
|
||||
}
|
||||
case Wasm::ValueType::FunctionReference:
|
||||
|
|
|
|||
Loading…
Reference in a new issue