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);
},
[&](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);
}

View file

@ -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;
};

View file

@ -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 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)
@ -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 agents 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

View file

@ -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 };

View file

@ -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());

View file

@ -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;