LibWeb: Isolate WebAssembly cache by global object

This change moves WebAssembly related data that was previously globally
accessible into the `WebAssemblyCache` object and creates one of these
per global object. This ensures that WebAssembly data cannot be
accessed across realms.
This commit is contained in:
Tim Ledbetter 2024-04-25 19:09:34 +01:00 committed by Ali Mohammad Pur
commit 6d4b8bde55
Notes: sideshowbarker 2024-07-17 02:57:43 +09:00
9 changed files with 121 additions and 116 deletions

View file

@ -3830,7 +3830,7 @@ void @namespace_class@::initialize(JS::Realm& realm)
void @namespace_class@::visit_edges(JS::Cell::Visitor& visitor) void @namespace_class@::visit_edges(JS::Cell::Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
@name@::visit_edges(visitor); @name@::visit_edges(*this, visitor);
} }
)~~~"); )~~~");
} }

View file

@ -9,7 +9,6 @@
#include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/Realm.h> #include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
#include <LibWasm/AbstractMachine/AbstractMachine.h>
#include <LibWeb/Bindings/InstancePrototype.h> #include <LibWeb/Bindings/InstancePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebAssembly/Instance.h> #include <LibWeb/WebAssembly/Instance.h>
@ -29,14 +28,14 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Instance>> Instance::construct_impl(JS::Rea
auto& vm = realm.vm(); auto& vm = realm.vm();
auto index = TRY(Detail::instantiate_module(vm, module.module())); auto module_instance = TRY(Detail::instantiate_module(vm, module.compiled_module()->module));
return vm.heap().allocate<Instance>(realm, realm, index); return vm.heap().allocate<Instance>(realm, realm, move(module_instance));
} }
Instance::Instance(JS::Realm& realm, size_t index) Instance::Instance(JS::Realm& realm, NonnullOwnPtr<Wasm::ModuleInstance> module_instance)
: Bindings::PlatformObject(realm) : Bindings::PlatformObject(realm)
, m_exports(Object::create(realm, nullptr)) , m_exports(Object::create(realm, nullptr))
, m_index(index) , m_module_instance(move(module_instance))
{ {
} }
@ -47,34 +46,31 @@ void Instance::initialize(JS::Realm& realm)
Base::initialize(realm); Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Instance, WebAssembly.Instance); WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Instance, WebAssembly.Instance);
auto& instance = *Detail::s_instantiated_modules[m_index]; for (auto& export_ : m_module_instance->exports()) {
auto& cache = Detail::s_module_caches.at(m_index);
for (auto& export_ : instance.exports()) {
export_.value().visit( export_.value().visit(
[&](Wasm::FunctionAddress const& address) { [&](Wasm::FunctionAddress const& address) {
Optional<JS::GCPtr<JS::FunctionObject>> object = cache.function_instances.get(address); Optional<JS::GCPtr<JS::FunctionObject>> object = m_function_instances.get(address);
if (!object.has_value()) { if (!object.has_value()) {
object = Detail::create_native_function(vm, address, export_.name()); object = Detail::create_native_function(vm, address, export_.name());
cache.function_instances.set(address, *object); m_function_instances.set(address, *object);
} }
m_exports->define_direct_property(export_.name(), *object, JS::default_attributes); m_exports->define_direct_property(export_.name(), *object, JS::default_attributes);
}, },
[&](Wasm::MemoryAddress const& address) { [&](Wasm::MemoryAddress const& address) {
Optional<JS::GCPtr<Memory>> object = cache.memory_instances.get(address); Optional<JS::GCPtr<Memory>> object = m_memory_instances.get(address);
if (!object.has_value()) { if (!object.has_value()) {
object = heap().allocate<Memory>(realm, realm, address); object = heap().allocate<Memory>(realm, realm, address);
cache.memory_instances.set(address, *object); m_memory_instances.set(address, *object);
} }
m_exports->define_direct_property(export_.name(), *object, JS::default_attributes); m_exports->define_direct_property(export_.name(), *object, JS::default_attributes);
}, },
[&](Wasm::TableAddress const& address) { [&](Wasm::TableAddress const& address) {
Optional<JS::GCPtr<Table>> object = cache.table_instances.get(address); Optional<JS::GCPtr<Table>> object = m_table_instances.get(address);
if (!object.has_value()) { if (!object.has_value()) {
object = heap().allocate<Table>(realm, realm, address); object = heap().allocate<Table>(realm, realm, address);
cache.table_instances.set(address, *object); m_table_instances.set(address, *object);
} }
m_exports->define_direct_property(export_.name(), *object, JS::default_attributes); m_exports->define_direct_property(export_.name(), *object, JS::default_attributes);
@ -91,6 +87,9 @@ 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_memory_instances);
visitor.visit(m_table_instances);
} }
} }

View file

@ -11,8 +11,10 @@
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibJS/Heap/GCPtr.h> #include <LibJS/Heap/GCPtr.h>
#include <LibJS/Heap/Handle.h> #include <LibJS/Heap/Handle.h>
#include <LibWasm/AbstractMachine/AbstractMachine.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h> #include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/PlatformObject.h> #include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/WebAssembly/WebAssembly.h>
namespace Web::WebAssembly { namespace Web::WebAssembly {
@ -26,13 +28,16 @@ public:
Object const* exports() const { return m_exports.ptr(); } Object const* exports() const { return m_exports.ptr(); }
private: private:
Instance(JS::Realm&, size_t index); Instance(JS::Realm&, NonnullOwnPtr<Wasm::ModuleInstance>);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;
JS::NonnullGCPtr<Object> m_exports; JS::NonnullGCPtr<Object> m_exports;
size_t m_index { 0 }; NonnullOwnPtr<Wasm::ModuleInstance> m_module_instance;
HashMap<Wasm::FunctionAddress, JS::GCPtr<JS::FunctionObject>> m_function_instances;
HashMap<Wasm::MemoryAddress, JS::GCPtr<WebAssembly::Memory>> m_memory_instances;
HashMap<Wasm::TableAddress, JS::GCPtr<WebAssembly::Table>> m_table_instances;
}; };
} }

View file

@ -23,12 +23,13 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Memory>> Memory::construct_impl(JS::Realm&
Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) }; Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) };
Wasm::MemoryType memory_type { move(limits) }; Wasm::MemoryType memory_type { move(limits) };
auto address = Detail::s_abstract_machine.store().allocate(memory_type); auto& cache = Detail::get_cache(realm);
auto address = cache.abstract_machine().store().allocate(memory_type);
if (!address.has_value()) if (!address.has_value())
return vm.throw_completion<JS::TypeError>("Wasm Memory allocation failed"sv); return vm.throw_completion<JS::TypeError>("Wasm Memory allocation failed"sv);
auto memory_object = vm.heap().allocate<Memory>(realm, realm, *address); auto memory_object = vm.heap().allocate<Memory>(realm, realm, *address);
Detail::s_abstract_machine.store().get(*address)->successful_grow_hook = [memory_object] { cache.abstract_machine().store().get(*address)->successful_grow_hook = [memory_object] {
MUST(memory_object->reset_the_memory_buffer()); MUST(memory_object->reset_the_memory_buffer());
}; };
@ -58,7 +59,8 @@ WebIDL::ExceptionOr<u32> Memory::grow(u32 delta)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
auto* memory = Detail::s_abstract_machine.store().get(address()); auto& context = Detail::get_cache(realm());
auto* memory = context.abstract_machine().store().get(address());
if (!memory) if (!memory)
return vm.throw_completion<JS::RangeError>("Could not find the memory instance to grow"sv); return vm.throw_completion<JS::RangeError>("Could not find the memory instance to grow"sv);
@ -103,7 +105,8 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> Memory::buffer() const
// https://webassembly.github.io/spec/js-api/#create-a-memory-buffer // https://webassembly.github.io/spec/js-api/#create-a-memory-buffer
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> Memory::create_a_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address) WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> Memory::create_a_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address)
{ {
auto* memory = Detail::s_abstract_machine.store().get(address); auto& context = Detail::get_cache(realm);
auto* memory = context.abstract_machine().store().get(address);
if (!memory) if (!memory)
return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv); return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv);

View file

@ -21,13 +21,13 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Module>> Module::construct_impl(JS::Realm&
{ {
auto& vm = realm.vm(); auto& vm = realm.vm();
auto index = TRY(Detail::parse_module(vm, bytes->raw_object())); auto compiled_module = TRY(Detail::parse_module(vm, bytes->raw_object()));
return vm.heap().allocate<Module>(realm, realm, index); return vm.heap().allocate<Module>(realm, realm, move(compiled_module));
} }
Module::Module(JS::Realm& realm, size_t index) Module::Module(JS::Realm& realm, NonnullRefPtr<Detail::CompiledWebAssemblyModule> compiled_module)
: Bindings::PlatformObject(realm) : Bindings::PlatformObject(realm)
, m_index(index) , m_compiled_module(move(compiled_module))
{ {
} }
@ -37,9 +37,4 @@ void Module::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Module, WebAssembly.Module); WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Module, WebAssembly.Module);
} }
Wasm::Module const& Module::module() const
{
return Detail::s_compiled_modules.at(index())->module;
}
} }

View file

@ -13,6 +13,7 @@
#include <LibWasm/Types.h> #include <LibWasm/Types.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h> #include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/PlatformObject.h> #include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/WebAssembly/WebAssembly.h>
namespace Web::WebAssembly { namespace Web::WebAssembly {
@ -23,15 +24,14 @@ class Module : public Bindings::PlatformObject {
public: public:
static WebIDL::ExceptionOr<JS::NonnullGCPtr<Module>> construct_impl(JS::Realm&, JS::Handle<WebIDL::BufferSource>& bytes); static WebIDL::ExceptionOr<JS::NonnullGCPtr<Module>> construct_impl(JS::Realm&, JS::Handle<WebIDL::BufferSource>& bytes);
size_t index() const { return m_index; } NonnullRefPtr<Detail::CompiledWebAssemblyModule> compiled_module() const { return m_compiled_module; }
Wasm::Module const& module() const;
private: private:
Module(JS::Realm&, size_t index); Module(JS::Realm&, NonnullRefPtr<Detail::CompiledWebAssemblyModule>);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
size_t m_index { 0 }; NonnullRefPtr<Detail::CompiledWebAssemblyModule> m_compiled_module;
}; };
} }

View file

@ -45,12 +45,13 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Table>> Table::construct_impl(JS::Realm& re
Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) }; Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) };
Wasm::TableType table_type { reference_type, move(limits) }; Wasm::TableType table_type { reference_type, move(limits) };
auto address = Detail::s_abstract_machine.store().allocate(table_type); auto& cache = Detail::get_cache(realm);
auto address = cache.abstract_machine().store().allocate(table_type);
if (!address.has_value()) if (!address.has_value())
return vm.throw_completion<JS::TypeError>("Wasm Table allocation failed"sv); return vm.throw_completion<JS::TypeError>("Wasm Table allocation failed"sv);
auto const& reference = reference_value.value().get<Wasm::Reference>(); auto const& reference = reference_value.value().get<Wasm::Reference>();
auto& table = *Detail::s_abstract_machine.store().get(*address); auto& table = *cache.abstract_machine().store().get(*address);
for (auto& element : table.elements()) for (auto& element : table.elements())
element = reference; element = reference;
@ -74,7 +75,8 @@ WebIDL::ExceptionOr<u32> Table::grow(u32 delta, JS::Value value)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
auto* table = Detail::s_abstract_machine.store().get(address()); auto& cache = Detail::get_cache(realm());
auto* table = cache.abstract_machine().store().get(address());
if (!table) if (!table)
return vm.throw_completion<JS::RangeError>("Could not find the memory table to grow"sv); return vm.throw_completion<JS::RangeError>("Could not find the memory table to grow"sv);
@ -94,7 +96,8 @@ WebIDL::ExceptionOr<JS::Value> Table::get(u32 index) const
{ {
auto& vm = this->vm(); auto& vm = this->vm();
auto* table = Detail::s_abstract_machine.store().get(address()); auto& cache = Detail::get_cache(realm());
auto* table = cache.abstract_machine().store().get(address());
if (!table) if (!table)
return vm.throw_completion<JS::RangeError>("Could not find the memory table"sv); return vm.throw_completion<JS::RangeError>("Could not find the memory table"sv);
@ -114,7 +117,8 @@ WebIDL::ExceptionOr<void> Table::set(u32 index, JS::Value value)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
auto* table = Detail::s_abstract_machine.store().get(address()); auto& cache = Detail::get_cache(realm());
auto* table = cache.abstract_machine().store().get(address());
if (!table) if (!table)
return vm.throw_completion<JS::RangeError>("Could not find the memory table"sv); return vm.throw_completion<JS::RangeError>("Could not find the memory table"sv);
@ -134,7 +138,8 @@ WebIDL::ExceptionOr<u32> Table::length() const
{ {
auto& vm = this->vm(); auto& vm = this->vm();
auto* table = Detail::s_abstract_machine.store().get(address()); auto& cache = Detail::get_cache(realm());
auto* table = cache.abstract_machine().store().get(address());
if (!table) if (!table)
return vm.throw_completion<JS::RangeError>("Could not find the memory table"sv); return vm.throw_completion<JS::RangeError>("Could not find the memory table"sv);

View file

@ -30,26 +30,21 @@ namespace Web::WebAssembly {
namespace Detail { namespace Detail {
Vector<NonnullOwnPtr<CompiledWebAssemblyModule>> s_compiled_modules; HashMap<JS::GCPtr<JS::Object>, WebAssemblyCache> s_caches;
Vector<NonnullOwnPtr<Wasm::ModuleInstance>> s_instantiated_modules;
Vector<ModuleCache> s_module_caches; WebAssemblyCache& get_cache(JS::Realm& realm)
GlobalModuleCache s_global_cache; {
Wasm::AbstractMachine s_abstract_machine; return s_caches.ensure(realm.global_object());
}
} }
void visit_edges(JS::Cell::Visitor& visitor) void visit_edges(JS::Object& object, JS::Cell::Visitor& visitor)
{ {
for (auto& entry : Detail::s_global_cache.function_instances) auto& global_object = HTML::relevant_global_object(object);
visitor.visit(entry.value); if (auto maybe_cache = Detail::s_caches.get(global_object); maybe_cache.has_value()) {
auto& cache = maybe_cache.release_value();
for (auto& module_cache : Detail::s_module_caches) { visitor.visit(cache.function_instances());
for (auto& entry : module_cache.function_instances)
visitor.visit(entry.value);
for (auto& entry : module_cache.memory_instances)
visitor.visit(entry.value);
for (auto& entry : module_cache.table_instances)
visitor.visit(entry.value);
} }
} }
@ -60,17 +55,16 @@ bool validate(JS::VM& vm, JS::Handle<WebIDL::BufferSource>& bytes)
// Note: There's no need to copy the bytes here as the buffer data cannot change while we're compiling the module. // Note: There's no need to copy the bytes here as the buffer data cannot change while we're compiling the module.
// 2. Compile stableBytes as a WebAssembly module and store the results as module. // 2. Compile stableBytes as a WebAssembly module and store the results as module.
auto maybe_module = Detail::parse_module(vm, bytes->raw_object()); auto module_or_error = Detail::parse_module(vm, bytes->raw_object());
// 3. If module is error, return false. // 3. If module is error, return false.
if (maybe_module.is_error()) if (module_or_error.is_error())
return false; return false;
// Drop the module from the cache, we're never going to refer to it.
ScopeGuard drop_from_cache { [&] { (void)Detail::s_compiled_modules.take_last(); } };
// 3 continued - our "compile" step is lazy with validation, explicitly do the validation. // 3 continued - our "compile" step is lazy with validation, explicitly do the validation.
if (Detail::s_abstract_machine.validate(Detail::s_compiled_modules[maybe_module.value()]->module).is_error()) auto compiled_module = module_or_error.release_value();
auto& cache = Detail::get_cache(*vm.current_realm());
if (cache.abstract_machine().validate(compiled_module->module).is_error())
return false; return false;
// 4. Return true. // 4. Return true.
@ -83,13 +77,13 @@ WebIDL::ExceptionOr<JS::Value> compile(JS::VM& vm, JS::Handle<WebIDL::BufferSour
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
// FIXME: This shouldn't block! // FIXME: This shouldn't block!
auto module = Detail::parse_module(vm, bytes->raw_object()); auto compiled_module_or_error = Detail::parse_module(vm, bytes->raw_object());
auto promise = JS::Promise::create(realm); auto promise = JS::Promise::create(realm);
if (module.is_error()) { if (compiled_module_or_error.is_error()) {
promise->reject(*module.release_error().value()); promise->reject(*compiled_module_or_error.release_error().value());
} else { } else {
auto module_object = vm.heap().allocate<Module>(realm, realm, module.release_value()); auto module_object = vm.heap().allocate<Module>(realm, realm, compiled_module_or_error.release_value());
promise->fulfill(module_object); promise->fulfill(module_object);
} }
@ -105,21 +99,21 @@ WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM& vm, JS::Handle<WebIDL::Buffer
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
// FIXME: This shouldn't block! // FIXME: This shouldn't block!
auto module = Detail::parse_module(vm, bytes->raw_object()); auto compiled_module_or_error = Detail::parse_module(vm, bytes->raw_object());
auto promise = JS::Promise::create(realm); auto promise = JS::Promise::create(realm);
if (module.is_error()) { if (compiled_module_or_error.is_error()) {
promise->reject(*module.release_error().value()); promise->reject(*compiled_module_or_error.release_error().value());
return promise; return promise;
} }
auto const& compiled_module = Detail::s_compiled_modules.at(module.release_value())->module; auto compiled_module = compiled_module_or_error.release_value();
auto result = Detail::instantiate_module(vm, compiled_module); auto result = Detail::instantiate_module(vm, compiled_module->module);
if (result.is_error()) { if (result.is_error()) {
promise->reject(*result.release_error().value()); promise->reject(*result.release_error().value());
} else { } else {
auto module_object = vm.heap().allocate<Module>(realm, realm, Detail::s_compiled_modules.size() - 1); auto module_object = vm.heap().allocate<Module>(realm, realm, move(compiled_module));
auto instance_object = vm.heap().allocate<Instance>(realm, realm, result.release_value()); auto instance_object = vm.heap().allocate<Instance>(realm, realm, result.release_value());
auto object = JS::Object::create(realm, nullptr); auto object = JS::Object::create(realm, nullptr);
@ -140,8 +134,8 @@ WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM& vm, Module const& module_obje
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
auto promise = JS::Promise::create(realm); auto promise = JS::Promise::create(realm);
auto const& compiled_module = module_object.module(); auto const& compiled_module = module_object.compiled_module();
auto result = Detail::instantiate_module(vm, compiled_module); auto result = Detail::instantiate_module(vm, compiled_module->module);
if (result.is_error()) { if (result.is_error()) {
promise->reject(*result.release_error().value()); promise->reject(*result.release_error().value());
@ -155,11 +149,12 @@ WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM& vm, Module const& module_obje
namespace Detail { namespace Detail {
JS::ThrowCompletionOr<size_t> instantiate_module(JS::VM& vm, Wasm::Module const& module) JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM& vm, Wasm::Module const& module)
{ {
Wasm::Linker linker { module }; Wasm::Linker linker { module };
HashMap<Wasm::Linker::Name, Wasm::ExternValue> resolved_imports; HashMap<Wasm::Linker::Name, Wasm::ExternValue> resolved_imports;
auto import_argument = vm.argument(1); auto import_argument = vm.argument(1);
auto& cache = get_cache(*vm.current_realm());
if (!import_argument.is_undefined()) { if (!import_argument.is_undefined()) {
auto import_object = TRY(import_argument.to_object(vm)); auto import_object = TRY(import_argument.to_object(vm));
dbgln("Trying to resolve stuff because import object was specified"); dbgln("Trying to resolve stuff because import object was specified");
@ -220,7 +215,7 @@ JS::ThrowCompletionOr<size_t> instantiate_module(JS::VM& vm, Wasm::Module const&
}, },
type type
}; };
auto address = s_abstract_machine.store().allocate(move(host_function)); auto address = cache.abstract_machine().store().allocate(move(host_function));
dbgln("Resolved to {}", address->value()); dbgln("Resolved to {}", address->value());
// FIXME: LinkError instead. // FIXME: LinkError instead.
VERIFY(address.has_value()); VERIFY(address.has_value());
@ -241,7 +236,7 @@ JS::ThrowCompletionOr<size_t> instantiate_module(JS::VM& vm, Wasm::Module const&
return vm.throw_completion<JS::TypeError>("LinkError: Import resolution attempted to cast a BigInteger to a Number"sv); return vm.throw_completion<JS::TypeError>("LinkError: Import resolution attempted to cast a BigInteger to a Number"sv);
} }
auto cast_value = TRY(to_webassembly_value(vm, import_, type.type())); auto cast_value = TRY(to_webassembly_value(vm, import_, type.type()));
address = s_abstract_machine.store().allocate({ type.type(), false }, cast_value); address = cache.abstract_machine().store().allocate({ type.type(), false }, cast_value);
} else { } else {
// FIXME: https://webassembly.github.io/spec/js-api/#read-the-imports step 5.2 // FIXME: https://webassembly.github.io/spec/js-api/#read-the-imports step 5.2
// if v implements Global // if v implements Global
@ -290,18 +285,16 @@ JS::ThrowCompletionOr<size_t> instantiate_module(JS::VM& vm, Wasm::Module const&
return vm.throw_completion<JS::TypeError>(MUST(builder.to_string())); return vm.throw_completion<JS::TypeError>(MUST(builder.to_string()));
} }
auto instance_result = s_abstract_machine.instantiate(module, link_result.release_value()); auto instance_result = cache.abstract_machine().instantiate(module, link_result.release_value());
if (instance_result.is_error()) { if (instance_result.is_error()) {
// FIXME: Throw a LinkError instead. // FIXME: Throw a LinkError instead.
return vm.throw_completion<JS::TypeError>(instance_result.error().error); return vm.throw_completion<JS::TypeError>(instance_result.error().error);
} }
s_instantiated_modules.append(instance_result.release_value()); return instance_result.release_value();
s_module_caches.empend();
return s_instantiated_modules.size() - 1;
} }
JS::ThrowCompletionOr<size_t> parse_module(JS::VM& vm, JS::Object* buffer_object) JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> parse_module(JS::VM& vm, JS::Object* buffer_object)
{ {
ReadonlyBytes data; ReadonlyBytes data;
if (is<JS::ArrayBuffer>(buffer_object)) { if (is<JS::ArrayBuffer>(buffer_object)) {
@ -333,21 +326,23 @@ JS::ThrowCompletionOr<size_t> parse_module(JS::VM& vm, JS::Object* buffer_object
return vm.throw_completion<JS::TypeError>(Wasm::parse_error_to_byte_string(module_result.error())); return vm.throw_completion<JS::TypeError>(Wasm::parse_error_to_byte_string(module_result.error()));
} }
if (auto validation_result = s_abstract_machine.validate(module_result.value()); validation_result.is_error()) { auto& cache = get_cache(*vm.current_realm());
if (auto validation_result = cache.abstract_machine().validate(module_result.value()); validation_result.is_error()) {
// FIXME: Throw CompileError instead. // FIXME: Throw CompileError instead.
return vm.throw_completion<JS::TypeError>(validation_result.error().error_string); return vm.throw_completion<JS::TypeError>(validation_result.error().error_string);
} }
auto compiled_module = make_ref_counted<CompiledWebAssemblyModule>(module_result.release_value());
s_compiled_modules.append(make<CompiledWebAssemblyModule>(module_result.release_value())); cache.add_compiled_module(compiled_module);
return s_compiled_modules.size() - 1; return compiled_module;
} }
JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress address, ByteString const& name) JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress address, ByteString const& name)
{ {
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
Optional<Wasm::FunctionType> type; Optional<Wasm::FunctionType> type;
s_abstract_machine.store().get(address)->visit([&](auto const& value) { type = value.type(); }); auto& cache = get_cache(realm);
if (auto entry = s_global_cache.function_instances.get(address); entry.has_value()) cache.abstract_machine().store().get(address)->visit([&](auto const& value) { type = value.type(); });
if (auto entry = cache.get_function_instance(address); entry.has_value())
return *entry; return *entry;
auto function = JS::NativeFunction::create( auto function = JS::NativeFunction::create(
@ -363,7 +358,8 @@ JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress add
for (auto& type : type.parameters()) for (auto& type : type.parameters())
values.append(TRY(to_webassembly_value(vm, vm.argument(index++), type))); values.append(TRY(to_webassembly_value(vm, vm.argument(index++), type)));
auto result = s_abstract_machine.invoke(address, move(values)); auto& cache = get_cache(realm);
auto result = cache.abstract_machine().invoke(address, move(values));
// FIXME: Use the convoluted mapping of errors defined in the spec. // FIXME: Use the convoluted mapping of errors defined in the spec.
if (result.is_trap()) if (result.is_trap())
return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Wasm execution trapped (WIP): {}", result.trap().reason))); return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Wasm execution trapped (WIP): {}", result.trap().reason)));
@ -379,7 +375,7 @@ JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress add
})); }));
}); });
s_global_cache.function_instances.set(address, function); cache.add_function_instance(address, function);
return function; return function;
} }
@ -416,7 +412,8 @@ JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM& vm, JS::Value va
if (value.is_function()) { if (value.is_function()) {
auto& function = value.as_function(); auto& function = value.as_function();
for (auto& entry : s_global_cache.function_instances) { auto& cache = get_cache(*vm.current_realm());
for (auto& entry : cache.function_instances()) {
if (entry.value == &function) if (entry.value == &function)
return Wasm::Value { Wasm::Reference { Wasm::Reference::Func { entry.key } } }; return Wasm::Value { Wasm::Reference { Wasm::Reference::Func { entry.key } } };
} }

View file

@ -18,7 +18,7 @@
namespace Web::WebAssembly { namespace Web::WebAssembly {
void visit_edges(JS::Cell::Visitor&); void visit_edges(JS::Object&, JS::Cell::Visitor&);
bool validate(JS::VM&, JS::Handle<WebIDL::BufferSource>& bytes); bool validate(JS::VM&, JS::Handle<WebIDL::BufferSource>& bytes);
WebIDL::ExceptionOr<JS::Value> compile(JS::VM&, JS::Handle<WebIDL::BufferSource>& bytes); WebIDL::ExceptionOr<JS::Value> compile(JS::VM&, JS::Handle<WebIDL::BufferSource>& bytes);
@ -27,14 +27,7 @@ WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM&, JS::Handle<WebIDL::BufferSou
WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM&, Module const& module_object, Optional<JS::Handle<JS::Object>>& import_object); WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM&, Module const& module_object, Optional<JS::Handle<JS::Object>>& import_object);
namespace Detail { namespace Detail {
struct CompiledWebAssemblyModule : public RefCounted<CompiledWebAssemblyModule> {
JS::ThrowCompletionOr<size_t> instantiate_module(JS::VM&, Wasm::Module const&);
JS::ThrowCompletionOr<size_t> parse_module(JS::VM&, JS::Object* buffer);
JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress address, ByteString const& name);
JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM&, JS::Value value, Wasm::ValueType const& type);
JS::Value to_js_value(JS::VM&, Wasm::Value& wasm_value);
struct CompiledWebAssemblyModule {
explicit CompiledWebAssemblyModule(Wasm::Module&& module) explicit CompiledWebAssemblyModule(Wasm::Module&& module)
: module(move(module)) : module(move(module))
{ {
@ -43,23 +36,31 @@ struct CompiledWebAssemblyModule {
Wasm::Module module; Wasm::Module module;
}; };
// FIXME: These should just be members of the module (instance) object, but the module needs to stick class WebAssemblyCache {
// around while its instance is alive so ideally this would be a refcounted object, shared between public:
// WebAssemblyModuleObject's and WebAssemblyInstantiatedModuleObject's. void add_compiled_module(NonnullRefPtr<CompiledWebAssemblyModule> module) { m_compiled_modules.append(module); }
struct ModuleCache { void add_function_instance(Wasm::FunctionAddress address, JS::GCPtr<JS::NativeFunction> function) { m_function_instances.set(address, function); }
HashMap<Wasm::FunctionAddress, JS::GCPtr<JS::FunctionObject>> function_instances;
HashMap<Wasm::MemoryAddress, JS::GCPtr<WebAssembly::Memory>> memory_instances; Optional<JS::GCPtr<JS::NativeFunction>> get_function_instance(Wasm::FunctionAddress address) { return m_function_instances.get(address); }
HashMap<Wasm::TableAddress, JS::GCPtr<WebAssembly::Table>> table_instances;
}; HashMap<Wasm::FunctionAddress, JS::GCPtr<JS::NativeFunction>> function_instances() const { return m_function_instances; }
struct GlobalModuleCache { Wasm::AbstractMachine& abstract_machine() { return m_abstract_machine; }
HashMap<Wasm::FunctionAddress, JS::GCPtr<JS::NativeFunction>> function_instances;
private:
HashMap<Wasm::FunctionAddress, JS::GCPtr<JS::NativeFunction>> m_function_instances;
Vector<NonnullRefPtr<CompiledWebAssemblyModule>> m_compiled_modules;
Wasm::AbstractMachine m_abstract_machine;
}; };
extern Vector<NonnullOwnPtr<CompiledWebAssemblyModule>> s_compiled_modules; WebAssemblyCache& get_cache(JS::Realm&);
extern Vector<NonnullOwnPtr<Wasm::ModuleInstance>> s_instantiated_modules;
extern Vector<ModuleCache> s_module_caches; JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM&, Wasm::Module const&);
extern GlobalModuleCache s_global_cache; JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> parse_module(JS::VM&, JS::Object* buffer);
extern Wasm::AbstractMachine s_abstract_machine; JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress address, ByteString const& name);
JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM&, JS::Value value, Wasm::ValueType const& type);
JS::Value to_js_value(JS::VM&, Wasm::Value& wasm_value);
extern HashMap<JS::GCPtr<JS::Object>, WebAssemblyCache> s_caches;
} }