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:
CountBleck 2025-08-18 21:13:58 -07:00 committed by Ali Mohammad Pur
commit f2a170bcfb
Notes: github-actions[bot] 2025-08-23 06:27:54 +00:00
6 changed files with 55 additions and 33 deletions

View file

@ -71,10 +71,11 @@ void Instance::initialize(JS::Realm& realm)
m_exports->define_direct_property(name, *object, JS::default_attributes); m_exports->define_direct_property(name, *object, JS::default_attributes);
}, },
[&](Wasm::MemoryAddress const& address) { [&](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()) { 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); 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); m_exports->define_direct_property(name, *object, JS::default_attributes);
@ -98,7 +99,6 @@ void Instance::visit_edges(Visitor& visitor)
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_exports); visitor.visit(m_exports);
visitor.visit(m_function_instances); visitor.visit(m_function_instances);
visitor.visit(m_memory_instances);
visitor.visit(m_table_instances); visitor.visit(m_table_instances);
} }

View file

@ -36,7 +36,6 @@ private:
GC::Ref<Object> m_exports; GC::Ref<Object> m_exports;
NonnullOwnPtr<Wasm::ModuleInstance> m_module_instance; NonnullOwnPtr<Wasm::ModuleInstance> m_module_instance;
HashMap<Wasm::FunctionAddress, GC::Ptr<JS::FunctionObject>> m_function_instances; 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; HashMap<Wasm::TableAddress, GC::Ptr<WebAssembly::Table>> m_table_instances;
}; };

View file

@ -49,15 +49,37 @@ Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address, Shared shared)
{ {
auto& cache = Detail::get_cache(realm); auto& cache = Detail::get_cache(realm);
cache.abstract_machine().store().get(address)->successful_grow_hook = [this] { cache.abstract_machine().store().get(address)->successful_grow_hook = [realm = GC::Ref(realm), address] {
MUST(reset_the_memory_buffer()); 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) void Memory::initialize(JS::Realm& realm)
{ {
WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Memory, WebAssembly.Memory); WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Memory, WebAssembly.Memory);
Base::initialize(realm); Base::initialize(realm);
auto& vm = realm.vm();
// https://webassembly.github.io/spec/js-api/#initialize-a-memory-object
// 1. Let map be the surrounding agents associated Memory object cache.
// 2. Assert: map[memaddr] doesnt 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) void Memory::visit_edges(Visitor& visitor)
@ -73,62 +95,58 @@ WebIDL::ExceptionOr<u32> Memory::grow(u32 delta)
auto& context = Detail::get_cache(realm()); auto& context = Detail::get_cache(realm());
auto* memory = context.abstract_machine().store().get(address()); auto* memory = context.abstract_machine().store().get(address());
if (!memory) VERIFY(memory);
return vm.throw_completion<JS::RangeError>("Could not find the memory instance to grow"sv);
auto previous_size = memory->size() / Wasm::Constants::page_size; 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)) 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); 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; return previous_size;
} }
// https://webassembly.github.io/spec/js-api/#refresh-the-memory-buffer // https://webassembly.github.io/spec/js-api/#refresh-the-memory-buffer
// FIXME: `refresh-the-memory-buffer` is a global abstract operation. void Memory::refresh_the_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address)
// Implement it as a static function to align with the spec.
WebIDL::ExceptionOr<void> Memory::reset_the_memory_buffer()
{ {
if (!m_buffer) // 1. Let map be the surrounding agents associated Memory object cache.
return {}; // 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(); // 4. Let buffer be memory.[[BufferObject]].
auto& realm = *vm.current_realm(); 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 // https://webassembly.github.io/threads/js-api/index.html#refresh-the-memory-buffer
// 1. If IsSharedArrayBuffer(buffer) is false, // 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"). // 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)); buffer = create_a_fixed_length_memory_buffer(vm, realm, address, memory.value()->m_shared);
return {};
} }
// https://webassembly.github.io/spec/js-api/#dom-memory-buffer // https://webassembly.github.io/spec/js-api/#dom-memory-buffer
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> Memory::buffer() const 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); return GC::Ref(*m_buffer);
} }
// https://webassembly.github.io/spec/js-api/#create-a-fixed-length-memory-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& context = Detail::get_cache(realm);
auto* memory = context.abstract_machine().store().get(address); auto* memory = context.abstract_machine().store().get(address);
if (!memory) VERIFY(memory);
return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv);
JS::ArrayBuffer* array_buffer; JS::ArrayBuffer* array_buffer;
// https://webassembly.github.io/threads/js-api/index.html#create-a-fixed-length-memory-buffer // https://webassembly.github.io/threads/js-api/index.html#create-a-fixed-length-memory-buffer

View file

@ -46,8 +46,8 @@ private:
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;
WebIDL::ExceptionOr<void> reset_the_memory_buffer(); static void refresh_the_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress);
static WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> create_a_fixed_length_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress, Shared shared); static GC::Ref<JS::ArrayBuffer> create_a_fixed_length_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress, Shared shared);
Wasm::MemoryAddress m_address; Wasm::MemoryAddress m_address;
Shared m_shared { Shared::No }; Shared m_shared { Shared::No };

View file

@ -63,6 +63,7 @@ void visit_edges(JS::Object& object, JS::Cell::Visitor& visitor)
visitor.visit(cache.imported_objects()); visitor.visit(cache.imported_objects());
visitor.visit(cache.extern_values()); visitor.visit(cache.extern_values());
visitor.visit(cache.global_instances()); visitor.visit(cache.global_instances());
visitor.visit(cache.memory_instances());
cache.abstract_machine().visit_external_resources({ .visit_trap = [&visitor](Wasm::ExternallyManagedTrap const& trap) { cache.abstract_machine().visit_external_resources({ .visit_trap = [&visitor](Wasm::ExternallyManagedTrap const& trap) {
auto& completion = trap.unsafe_external_object_as<JS::Completion>(); auto& completion = trap.unsafe_external_object_as<JS::Completion>();
visitor.visit(completion.value()); visitor.visit(completion.value());

View file

@ -55,15 +55,18 @@ public:
m_inverse_extern_values.set(value, address); 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_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<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<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::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::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<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<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::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; } HashTable<GC::Ptr<JS::Object>> const& imported_objects() const { return m_imported_objects; }
Wasm::AbstractMachine& abstract_machine() { return m_abstract_machine; } Wasm::AbstractMachine& abstract_machine() { return m_abstract_machine; }
@ -72,6 +75,7 @@ private:
HashMap<Wasm::ExternAddress, JS::Value> m_extern_values; HashMap<Wasm::ExternAddress, JS::Value> m_extern_values;
HashMap<JS::Value, Wasm::ExternAddress> m_inverse_extern_values; HashMap<JS::Value, Wasm::ExternAddress> m_inverse_extern_values;
HashMap<Wasm::GlobalAddress, GC::Ptr<WebAssembly::Global>> m_global_instances; 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; Vector<NonnullRefPtr<CompiledWebAssemblyModule>> m_compiled_modules;
HashTable<GC::Ptr<JS::Object>> m_imported_objects; HashTable<GC::Ptr<JS::Object>> m_imported_objects;
Wasm::AbstractMachine m_abstract_machine; Wasm::AbstractMachine m_abstract_machine;