mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-20 16:28:54 +00:00
LibWeb: Implement the WebAssembly "Memory object cache" + other changes
This cache is referenced by a few parts of the JS API spec, including the threads spec (such as in toFixedLengthBuffer), as well as the "refresh the Memory buffer" algorithm, which was implemented as a method of Memory before this change. Now, this algorithm can be implemented in a spec-like fashion (though it mostly seems to add extra complexity). This change also fixes a bug where memories that were re-exported from an imported WebAssembly.Memory were given a distinct WebAssembly.Memory object, since the caching that existed in Instance.cpp was instance-local, not global to the realm. We also make Memory::m_buffer non-lazy, since we have to implement "initialize a memory object" correctly anyway.
This commit is contained in:
parent
a2dc6c4bbb
commit
f2a170bcfb
Notes:
github-actions[bot]
2025-08-23 06:27:54 +00:00
Author: https://github.com/CountBleck
Commit: f2a170bcfb
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5889
Reviewed-by: https://github.com/alimpfard ✅
6 changed files with 55 additions and 33 deletions
|
@ -71,10 +71,11 @@ void Instance::initialize(JS::Realm& realm)
|
|||
m_exports->define_direct_property(name, *object, JS::default_attributes);
|
||||
},
|
||||
[&](Wasm::MemoryAddress const& address) {
|
||||
Optional<GC::Ptr<Memory>> object = m_memory_instances.get(address);
|
||||
Optional<GC::Ptr<Memory>> object = cache.get_memory_instance(address);
|
||||
if (!object.has_value()) {
|
||||
// FIXME: Once LibWasm implements the threads/atomics proposal, the shared-ness should be
|
||||
// obtained from the Wasm::MemoryInstance's type.
|
||||
object = realm.create<Memory>(realm, address, Memory::Shared::No);
|
||||
m_memory_instances.set(address, *object);
|
||||
}
|
||||
|
||||
m_exports->define_direct_property(name, *object, JS::default_attributes);
|
||||
|
@ -98,7 +99,6 @@ void Instance::visit_edges(Visitor& visitor)
|
|||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_exports);
|
||||
visitor.visit(m_function_instances);
|
||||
visitor.visit(m_memory_instances);
|
||||
visitor.visit(m_table_instances);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ private:
|
|||
GC::Ref<Object> m_exports;
|
||||
NonnullOwnPtr<Wasm::ModuleInstance> m_module_instance;
|
||||
HashMap<Wasm::FunctionAddress, GC::Ptr<JS::FunctionObject>> m_function_instances;
|
||||
HashMap<Wasm::MemoryAddress, GC::Ptr<WebAssembly::Memory>> m_memory_instances;
|
||||
HashMap<Wasm::TableAddress, GC::Ptr<WebAssembly::Table>> m_table_instances;
|
||||
};
|
||||
|
||||
|
|
|
@ -49,15 +49,37 @@ Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address, Shared shared)
|
|||
{
|
||||
auto& cache = Detail::get_cache(realm);
|
||||
|
||||
cache.abstract_machine().store().get(address)->successful_grow_hook = [this] {
|
||||
MUST(reset_the_memory_buffer());
|
||||
cache.abstract_machine().store().get(address)->successful_grow_hook = [realm = GC::Ref(realm), address] {
|
||||
refresh_the_memory_buffer(realm->vm(), realm, address);
|
||||
};
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#initialize-a-memory-object
|
||||
void Memory::initialize(JS::Realm& realm)
|
||||
{
|
||||
WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Memory, WebAssembly.Memory);
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = realm.vm();
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#initialize-a-memory-object
|
||||
// 1. Let map be the surrounding agent’s associated Memory object cache.
|
||||
// 2. Assert: map[memaddr] doesn’t exist.
|
||||
auto& cache = Detail::get_cache(realm);
|
||||
auto exists = cache.memory_instances().contains(m_address);
|
||||
VERIFY(!exists);
|
||||
|
||||
// 3. Let buffer be the result of creating a fixed length memory buffer from memaddr.
|
||||
auto buffer = create_a_fixed_length_memory_buffer(vm, realm, m_address, m_shared);
|
||||
|
||||
// 4. Set memory.[[Memory]] to memaddr.
|
||||
// NOTE: This is already set by the Memory constructor.
|
||||
|
||||
// 5. Set memory.[[BufferObject]] to buffer.
|
||||
m_buffer = buffer;
|
||||
|
||||
// 6. Set map[memaddr] to memory.
|
||||
cache.add_memory_instance(m_address, *this);
|
||||
}
|
||||
|
||||
void Memory::visit_edges(Visitor& visitor)
|
||||
|
@ -73,62 +95,58 @@ WebIDL::ExceptionOr<u32> Memory::grow(u32 delta)
|
|||
|
||||
auto& context = Detail::get_cache(realm());
|
||||
auto* memory = context.abstract_machine().store().get(address());
|
||||
if (!memory)
|
||||
return vm.throw_completion<JS::RangeError>("Could not find the memory instance to grow"sv);
|
||||
VERIFY(memory);
|
||||
|
||||
auto previous_size = memory->size() / Wasm::Constants::page_size;
|
||||
if (!memory->grow(delta * Wasm::Constants::page_size, Wasm::MemoryInstance::GrowType::No, Wasm::MemoryInstance::InhibitGrowCallback::Yes))
|
||||
return vm.throw_completion<JS::RangeError>("Memory.grow() grows past the stated limit of the memory instance"sv);
|
||||
|
||||
TRY(reset_the_memory_buffer());
|
||||
refresh_the_memory_buffer(vm, realm(), m_address);
|
||||
|
||||
return previous_size;
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#refresh-the-memory-buffer
|
||||
// FIXME: `refresh-the-memory-buffer` is a global abstract operation.
|
||||
// Implement it as a static function to align with the spec.
|
||||
WebIDL::ExceptionOr<void> Memory::reset_the_memory_buffer()
|
||||
void Memory::refresh_the_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address)
|
||||
{
|
||||
if (!m_buffer)
|
||||
return {};
|
||||
// 1. Let map be the surrounding agent’s associated Memory object cache.
|
||||
// 2. Assert: map[memaddr] exists.
|
||||
// 3. Let memory be map[memaddr].
|
||||
auto& cache = Detail::get_cache(realm);
|
||||
auto memory = cache.get_memory_instance(address);
|
||||
VERIFY(memory.has_value());
|
||||
|
||||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
// 4. Let buffer be memory.[[BufferObject]].
|
||||
auto& buffer = memory.value()->m_buffer;
|
||||
|
||||
if (m_buffer->is_fixed_length()) {
|
||||
// 5. If IsFixedLengthArrayBuffer(buffer) is true,
|
||||
if (buffer->is_fixed_length()) {
|
||||
// https://webassembly.github.io/threads/js-api/index.html#refresh-the-memory-buffer
|
||||
// 1. If IsSharedArrayBuffer(buffer) is false,
|
||||
if (!m_buffer->is_shared_array_buffer()) {
|
||||
if (!buffer->is_shared_array_buffer()) {
|
||||
// 1. Perform ! DetachArrayBuffer(buffer, "WebAssembly.Memory").
|
||||
MUST(JS::detach_array_buffer(vm, *m_buffer, JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)));
|
||||
MUST(JS::detach_array_buffer(vm, *buffer, JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)));
|
||||
}
|
||||
|
||||
// 2. Let newBuffer be the result of creating a fixed length memory buffer from memaddr.
|
||||
// 3. Set memory.[[BufferObject]] to newBuffer.
|
||||
}
|
||||
|
||||
m_buffer = TRY(create_a_fixed_length_memory_buffer(vm, realm, m_address, m_shared));
|
||||
|
||||
return {};
|
||||
buffer = create_a_fixed_length_memory_buffer(vm, realm, address, memory.value()->m_shared);
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#dom-memory-buffer
|
||||
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::buffer() const
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
if (!m_buffer)
|
||||
m_buffer = TRY(create_a_fixed_length_memory_buffer(vm, realm, m_address, m_shared));
|
||||
|
||||
return GC::Ref(*m_buffer);
|
||||
}
|
||||
|
||||
// https://webassembly.github.io/spec/js-api/#create-a-fixed-length-memory-buffer
|
||||
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::create_a_fixed_length_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address, Shared shared)
|
||||
GC::Ref<JS::ArrayBuffer> Memory::create_a_fixed_length_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address, Shared shared)
|
||||
{
|
||||
auto& context = Detail::get_cache(realm);
|
||||
auto* memory = context.abstract_machine().store().get(address);
|
||||
if (!memory)
|
||||
return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv);
|
||||
VERIFY(memory);
|
||||
|
||||
JS::ArrayBuffer* array_buffer;
|
||||
// https://webassembly.github.io/threads/js-api/index.html#create-a-fixed-length-memory-buffer
|
||||
|
|
|
@ -46,8 +46,8 @@ private:
|
|||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
WebIDL::ExceptionOr<void> reset_the_memory_buffer();
|
||||
static WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> create_a_fixed_length_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress, Shared shared);
|
||||
static void refresh_the_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress);
|
||||
static GC::Ref<JS::ArrayBuffer> create_a_fixed_length_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress, Shared shared);
|
||||
|
||||
Wasm::MemoryAddress m_address;
|
||||
Shared m_shared { Shared::No };
|
||||
|
|
|
@ -63,6 +63,7 @@ void visit_edges(JS::Object& object, JS::Cell::Visitor& visitor)
|
|||
visitor.visit(cache.imported_objects());
|
||||
visitor.visit(cache.extern_values());
|
||||
visitor.visit(cache.global_instances());
|
||||
visitor.visit(cache.memory_instances());
|
||||
cache.abstract_machine().visit_external_resources({ .visit_trap = [&visitor](Wasm::ExternallyManagedTrap const& trap) {
|
||||
auto& completion = trap.unsafe_external_object_as<JS::Completion>();
|
||||
visitor.visit(completion.value());
|
||||
|
|
|
@ -55,15 +55,18 @@ public:
|
|||
m_inverse_extern_values.set(value, address);
|
||||
}
|
||||
void add_global_instance(Wasm::GlobalAddress address, GC::Ptr<WebAssembly::Global> global) { m_global_instances.set(address, global); }
|
||||
void add_memory_instance(Wasm::MemoryAddress address, GC::Ptr<WebAssembly::Memory> memory) { m_memory_instances.set(address, memory); }
|
||||
|
||||
Optional<GC::Ptr<JS::NativeFunction>> get_function_instance(Wasm::FunctionAddress address) { return m_function_instances.get(address); }
|
||||
Optional<JS::Value> get_extern_value(Wasm::ExternAddress address) { return m_extern_values.get(address); }
|
||||
Optional<GC::Ptr<WebAssembly::Global>> get_global_instance(Wasm::GlobalAddress address) { return m_global_instances.get(address); }
|
||||
Optional<GC::Ptr<WebAssembly::Memory>> get_memory_instance(Wasm::MemoryAddress address) { return m_memory_instances.get(address); }
|
||||
|
||||
HashMap<Wasm::FunctionAddress, GC::Ptr<JS::NativeFunction>> const& function_instances() const { return m_function_instances; }
|
||||
HashMap<Wasm::ExternAddress, JS::Value> const& extern_values() const { return m_extern_values; }
|
||||
HashMap<JS::Value, Wasm::ExternAddress> const& inverse_extern_values() const { return m_inverse_extern_values; }
|
||||
HashMap<Wasm::GlobalAddress, GC::Ptr<WebAssembly::Global>> const& global_instances() const { return m_global_instances; }
|
||||
HashMap<Wasm::MemoryAddress, GC::Ptr<WebAssembly::Memory>> const& memory_instances() const { return m_memory_instances; }
|
||||
HashTable<GC::Ptr<JS::Object>> const& imported_objects() const { return m_imported_objects; }
|
||||
Wasm::AbstractMachine& abstract_machine() { return m_abstract_machine; }
|
||||
|
||||
|
@ -72,6 +75,7 @@ private:
|
|||
HashMap<Wasm::ExternAddress, JS::Value> m_extern_values;
|
||||
HashMap<JS::Value, Wasm::ExternAddress> m_inverse_extern_values;
|
||||
HashMap<Wasm::GlobalAddress, GC::Ptr<WebAssembly::Global>> m_global_instances;
|
||||
HashMap<Wasm::MemoryAddress, GC::Ptr<WebAssembly::Memory>> m_memory_instances;
|
||||
Vector<NonnullRefPtr<CompiledWebAssemblyModule>> m_compiled_modules;
|
||||
HashTable<GC::Ptr<JS::Object>> m_imported_objects;
|
||||
Wasm::AbstractMachine m_abstract_machine;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue