diff --git a/AK/kmalloc.cpp b/AK/kmalloc.cpp index b8d361d147..1d9b5d54f5 100644 --- a/AK/kmalloc.cpp +++ b/AK/kmalloc.cpp @@ -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(); } diff --git a/AK/kmalloc.h b/AK/kmalloc.h index 73ae097e87..b7b56c574a 100644 --- a/AK/kmalloc.h +++ b/AK/kmalloc.h @@ -13,6 +13,7 @@ enum class HeapPartition { General, + ArrayBuffer, }; [[nodiscard]] void* ak_kcalloc(size_t count, size_t size); diff --git a/Libraries/LibJS/Runtime/ArrayBuffer.cpp b/Libraries/LibJS/Runtime/ArrayBuffer.cpp index fdfaa8bf6b..acc2d4561d 100644 --- a/Libraries/LibJS/Runtime/ArrayBuffer.cpp +++ b/Libraries/LibJS/Runtime/ArrayBuffer.cpp @@ -24,7 +24,7 @@ static GC::Ref prototype_for_shared_state(Realm& realm, DataBlock::Share ThrowCompletionOr> 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(ErrorType::NotEnoughMemoryToAllocate, byte_length); @@ -33,7 +33,11 @@ ThrowCompletionOr> ArrayBuffer::create(Realm& realm, size_t GC::Ref ArrayBuffer::create(Realm& realm, ByteBuffer buffer, DataBlock::Shared is_shared) { - auto array_buffer = realm.create(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(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::create(Realm& realm, ByteBuffer* buffer, DataB return realm.create(buffer, is_shared, prototype_for_shared_state(realm, is_shared)); } +GC::Ref ArrayBuffer::create(Realm& realm, DataBlock block) +{ + auto is_shared = block.is_shared; + auto array_buffer = realm.create(static_cast(nullptr), is_shared, prototype_for_shared_state(realm, is_shared)); + array_buffer->set_data_block(move(block)); + return array_buffer; +} + GC::Ref ArrayBuffer::create(Realm& realm, DataBlock::UnownedExternalBuffer buffer, DataBlock::Shared is_shared) { return realm.create(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 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 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 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(ErrorType::NotEnoughMemoryToAllocate, size); @@ -121,7 +149,7 @@ ThrowCompletionOr create_byte_data_block(VM& vm, size_t size) static ThrowCompletionOr 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(ErrorType::NotEnoughMemoryToAllocate, size); @@ -135,10 +163,11 @@ static ThrowCompletionOr 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 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(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 ArrayBuffer::detach_and_take_bytes(VM& vm) +ThrowCompletionOr ArrayBuffer::detach_and_take_data_block(VM& vm) { VERIFY(!is_shared_array_buffer()); @@ -313,11 +340,7 @@ ThrowCompletionOr ArrayBuffer::detach_and_take_bytes(VM& vm) return vm.throw_completion(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()) - 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(cached_view); if (view.viewed_array_buffer() == this) @@ -326,7 +349,7 @@ ThrowCompletionOr 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> 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)); diff --git a/Libraries/LibJS/Runtime/ArrayBuffer.h b/Libraries/LibJS/Runtime/ArrayBuffer.h index c7cd02c825..dfe00ecffb 100644 --- a/Libraries/LibJS/Runtime/ArrayBuffer.h +++ b/Libraries/LibJS/Runtime/ArrayBuffer.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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 create_zeroed(size_t size) + { + OwnedBackingStore buffer; + TRY(buffer.try_resize(size, ZeroFillNewBytes::Yes)); + return buffer; + } + + static ErrorOr 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 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 try_ensure_capacity(size_t new_capacity) + { + if (new_capacity <= capacity()) + return {}; + auto* new_data = static_cast(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 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(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 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 { 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 { VERIFY_NOT_REACHED(); }); + } + + ErrorOr try_ensure_capacity(size_t new_capacity) + { + return byte_buffer.visit( + [&](Empty) -> ErrorOr { VERIFY_NOT_REACHED(); }, + [&](OwnedBackingStore& value) { return value.try_ensure_capacity(new_capacity); }, + [&](UnownedFixedLengthByteBuffer& value) { return value.buffer->try_ensure_capacity(new_capacity); }, + [&](UnownedExternalBuffer&) -> ErrorOr { 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(); } - Variant byte_buffer; + Variant byte_buffer; Shared is_shared = { Shared::No }; }; @@ -160,6 +277,7 @@ public: static ThrowCompletionOr> create(Realm&, size_t, DataBlock::Shared = DataBlock::Shared::No); static GC::Ref create(Realm&, ByteBuffer, DataBlock::Shared = DataBlock::Shared::No); static GC::Ref create(Realm&, ByteBuffer*, DataBlock::Shared = DataBlock::Shared::No); + static GC::Ref create(Realm&, DataBlock); static GC::Ref 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 detach_and_take_bytes(VM&); + ThrowCompletionOr 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 try_resize(size_t, DataBlock::ZeroFillNewBytes = DataBlock::ZeroFillNewBytes::No); + ErrorOr 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(); + return !is_detached() && is_fixed_length() && m_data_block.byte_buffer.has(); } // 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() const { return is_array_buffer(); } JS_API ThrowCompletionOr 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 allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length, Optional const& max_byte_length = {}); ThrowCompletionOr array_buffer_copy_and_detach(VM&, ArrayBuffer& array_buffer, Value new_length, PreserveResizability preserve_resizability); JS_API ThrowCompletionOr detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional key = {}); diff --git a/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp b/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp index 3d3437fb06..6b07d77499 100644 --- a/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp +++ b/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp @@ -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. diff --git a/Libraries/LibJS/Runtime/AtomicsObject.cpp b/Libraries/LibJS/Runtime/AtomicsObject.cpp index ed8d1ff323..a08e135805 100644 --- a/Libraries/LibJS/Runtime/AtomicsObject.cpp +++ b/Libraries/LibJS/Runtime/AtomicsObject.cpp @@ -320,7 +320,7 @@ static ThrowCompletionOr 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 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 atomic_compare_exchange_impl(VM& vm, TypedArrayB } else { using U = Conditional, u8, T>; - auto* v = reinterpret_cast(block.span().slice(byte_index_in_buffer).data()); + auto* v = reinterpret_cast(block.slice(byte_index_in_buffer).data()); auto* e = reinterpret_cast(expected_bytes.data()); auto* r = reinterpret_cast(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()) diff --git a/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp b/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp index 16b930d3b1..6c0377bc43 100644 --- a/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp +++ b/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp @@ -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(ErrorType::NotEnoughMemoryToAllocate, new_byte_length); return js_undefined(); @@ -220,10 +220,10 @@ JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::slice) return vm.throw_completion(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); diff --git a/Libraries/LibJS/Runtime/TypedArray.h b/Libraries/LibJS/Runtime/TypedArray.h index 177f6e20a9..23596f07d2 100644 --- a/Libraries/LibJS/Runtime/TypedArray.h +++ b/Libraries/LibJS/Runtime/TypedArray.h @@ -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; } diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index fdcac0d68a..930dd642e2 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -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(ErrorType::NotEnoughMemoryToAllocate, new_byte_length); - buffer.did_change_data_block_capacity(old_external_memory_size); return HandledByHost::Handled; }; diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index dae3a64432..817ac662e9 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -989,7 +989,7 @@ GC::Ref SubtleCrypto::wrap_key(Bindings::KeyFormat format, GC:: || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) { // Let bytes be exportedKey. - bytes = as(*exported_key).buffer(); + bytes = MUST(ByteBuffer::copy(as(*exported_key).bytes())); } else { VERIFY_NOT_REACHED(); } @@ -1153,7 +1153,7 @@ GC::Ref 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(); } diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Libraries/LibWeb/HTML/StructuredSerialize.cpp index a618900e05..6ff7904255 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -144,14 +144,14 @@ static WebIDL::ExceptionOr 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 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 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". diff --git a/Libraries/LibWeb/MediaSourceExtensions/SourceBuffer.cpp b/Libraries/LibWeb/MediaSourceExtensions/SourceBuffer.cpp index fbb8e6c607..9d541e6177 100644 --- a/Libraries/LibWeb/MediaSourceExtensions/SourceBuffer.cpp +++ b/Libraries/LibWeb/MediaSourceExtensions/SourceBuffer.cpp @@ -334,7 +334,7 @@ WebIDL::ExceptionOr 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); } diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index 220b79f7df..8192d58902 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -465,11 +465,11 @@ WebIDL::ExceptionOr> 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 diff --git a/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp b/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp index 2b3055f632..0aef91c5d6 100644 --- a/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp +++ b/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp @@ -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) { diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp index dd81748315..b8c8873db0 100644 --- a/Tests/LibWasm/test-wasm.cpp +++ b/Tests/LibWasm/test-wasm.cpp @@ -214,7 +214,11 @@ TESTJS_GLOBAL_FUNCTION(compare_typed_arrays, compareTypedArrays) if (!is(*rhs)) return vm.throw_completion("Expected a TypedArray"sv); auto& rhs_array = static_cast(*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(*object); u128 bits = 0; auto* ptr = bit_cast(&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(); // 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: