LibJS: Account ArrayBuffer storage as external memory
Report owned ByteBuffer outline capacity from ArrayBuffer as external memory. Update the heap accounting when backing storage is attached, resized, detached, or transferred. Borrowed WebAssembly memory remains uncounted because ArrayBuffer does not own that storage.
This commit is contained in:
parent
8fc7dfad26
commit
f6a45ea9a6
3 changed files with 55 additions and 3 deletions
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/ArrayBuffer.h>
|
||||
#include <LibJS/Runtime/ArrayBufferConstructor.h>
|
||||
|
|
@ -32,7 +33,9 @@ ThrowCompletionOr<GC::Ref<ArrayBuffer>> ArrayBuffer::create(Realm& realm, size_t
|
|||
|
||||
GC::Ref<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer buffer, DataBlock::Shared is_shared)
|
||||
{
|
||||
return realm.create<ArrayBuffer>(move(buffer), is_shared, prototype_for_shared_state(realm, is_shared));
|
||||
auto array_buffer = realm.create<ArrayBuffer>(move(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;
|
||||
}
|
||||
|
||||
GC::Ref<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer* buffer, DataBlock::Shared is_shared)
|
||||
|
|
@ -54,6 +57,28 @@ ArrayBuffer::ArrayBuffer(ByteBuffer* buffer, DataBlock::Shared is_shared, Object
|
|||
{
|
||||
}
|
||||
|
||||
void ArrayBuffer::account_external_memory_change(size_t old_external_memory_size, size_t new_external_memory_size)
|
||||
{
|
||||
if (new_external_memory_size > old_external_memory_size) {
|
||||
heap().did_allocate_external_memory(new_external_memory_size - old_external_memory_size);
|
||||
return;
|
||||
}
|
||||
|
||||
heap().did_free_external_memory(old_external_memory_size - new_external_memory_size);
|
||||
}
|
||||
|
||||
void ArrayBuffer::set_data_block(DataBlock block)
|
||||
{
|
||||
auto old_external_memory_size = external_memory_size();
|
||||
m_data_block = move(block);
|
||||
account_external_memory_change(old_external_memory_size, external_memory_size());
|
||||
}
|
||||
|
||||
void ArrayBuffer::did_change_data_block_capacity(size_t old_external_memory_size)
|
||||
{
|
||||
account_external_memory_change(old_external_memory_size, external_memory_size());
|
||||
}
|
||||
|
||||
void ArrayBuffer::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
|
|
@ -184,8 +209,10 @@ 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())
|
||||
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);
|
||||
|
|
@ -253,6 +280,7 @@ ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM& vm, ArrayBuffer
|
|||
|
||||
void ArrayBuffer::detach_buffer()
|
||||
{
|
||||
auto old_external_memory_size = external_memory_size();
|
||||
for (auto& cached_view : m_cached_views) {
|
||||
auto& view = static_cast<TypedArrayBase&>(cached_view);
|
||||
if (view.viewed_array_buffer() == this)
|
||||
|
|
@ -260,6 +288,7 @@ void ArrayBuffer::detach_buffer()
|
|||
}
|
||||
m_cached_views.clear();
|
||||
m_data_block.byte_buffer = Empty {};
|
||||
account_external_memory_change(old_external_memory_size, 0);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<ByteBuffer> ArrayBuffer::detach_and_take_bytes(VM& vm)
|
||||
|
|
@ -269,11 +298,19 @@ ThrowCompletionOr<ByteBuffer> ArrayBuffer::detach_and_take_bytes(VM& vm)
|
|||
if (!same_value(detach_key(), js_undefined()))
|
||||
return vm.throw_completion<TypeError>(ErrorType::DetachKeyMismatch, js_undefined(), detach_key());
|
||||
|
||||
auto old_external_memory_size = external_memory_size();
|
||||
auto bytes = m_data_block.byte_buffer.visit(
|
||||
[](Empty) -> ByteBuffer { VERIFY_NOT_REACHED(); },
|
||||
[](ByteBuffer& value) -> ByteBuffer { return move(value); },
|
||||
[](DataBlock::UnownedFixedLengthByteBuffer& value) -> ByteBuffer { return MUST(ByteBuffer::copy(value.buffer->span())); });
|
||||
detach_buffer();
|
||||
for (auto& cached_view : m_cached_views) {
|
||||
auto& view = static_cast<TypedArrayBase&>(cached_view);
|
||||
if (view.viewed_array_buffer() == this)
|
||||
view.set_cached_data_ptr(nullptr);
|
||||
}
|
||||
m_cached_views.clear();
|
||||
m_data_block.byte_buffer = Empty {};
|
||||
account_external_memory_change(old_external_memory_size, 0);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,14 @@ struct DataBlock {
|
|||
[](UnownedFixedLengthByteBuffer const& value) { return value.size; });
|
||||
}
|
||||
|
||||
size_t external_memory_size() const
|
||||
{
|
||||
return byte_buffer.visit(
|
||||
[](Empty) -> size_t { return 0; },
|
||||
[](ByteBuffer const& buffer) { return buffer.is_inline() ? 0 : buffer.capacity(); },
|
||||
[](UnownedFixedLengthByteBuffer const&) -> size_t { return 0; });
|
||||
}
|
||||
|
||||
Variant<Empty, ByteBuffer, UnownedFixedLengthByteBuffer> byte_buffer;
|
||||
Shared is_shared = { Shared::No };
|
||||
};
|
||||
|
|
@ -96,6 +104,7 @@ public:
|
|||
virtual ~ArrayBuffer() override = default;
|
||||
|
||||
size_t byte_length() const { return m_data_block.size(); }
|
||||
virtual size_t external_memory_size() const override { return m_data_block.external_memory_size(); }
|
||||
|
||||
// [[ArrayBufferData]]
|
||||
ByteBuffer& buffer() { return m_data_block.buffer(); }
|
||||
|
|
@ -111,7 +120,8 @@ public:
|
|||
void set_max_byte_length(size_t max_byte_length) { m_max_byte_length = max_byte_length; }
|
||||
|
||||
// Used by allocate_array_buffer() to attach the data block after construction
|
||||
void set_data_block(DataBlock block) { m_data_block = move(block); }
|
||||
void set_data_block(DataBlock);
|
||||
void did_change_data_block_capacity(size_t old_external_memory_size);
|
||||
|
||||
Value detach_key() const { return m_detach_key; }
|
||||
void set_detach_key(Value detach_key) { m_detach_key = detach_key; }
|
||||
|
|
@ -180,6 +190,8 @@ private:
|
|||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
void account_external_memory_change(size_t old_external_memory_size, size_t new_external_memory_size);
|
||||
|
||||
DataBlock m_data_block;
|
||||
Optional<size_t> m_max_byte_length;
|
||||
IntrusiveList<&CachedTypedArrayView::m_cached_view_list_node> m_cached_views;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibJS/Bytecode/Executable.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
|
|
@ -192,8 +193,10 @@ 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())
|
||||
return throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, new_byte_length);
|
||||
buffer.did_change_data_block_capacity(old_external_memory_size);
|
||||
|
||||
return HandledByHost::Handled;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue