diff options
-rw-r--r-- | Userland/Libraries/LibWeb/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/Window.cpp | 8 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 139 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h | 27 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl | 17 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp | 148 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h | 15 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/idl_files.cmake | 1 |
8 files changed, 186 insertions, 170 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 9c53a5e023..79d0db46f4 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -447,6 +447,7 @@ set(SOURCES WebAssembly/Memory.cpp WebAssembly/Module.cpp WebAssembly/Table.cpp + WebAssembly/WebAssembly.cpp WebAssembly/WebAssemblyObject.cpp WebDriver/Capabilities.cpp WebDriver/Client.cpp diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index 88a78e22b0..9527cc8c5c 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -53,7 +53,6 @@ #include <LibWeb/Page/Page.h> #include <LibWeb/RequestIdleCallback/IdleDeadline.h> #include <LibWeb/Selection/Selection.h> -#include <LibWeb/WebAssembly/WebAssemblyObject.h> #include <LibWeb/WebIDL/AbstractOperations.h> namespace Web::HTML { @@ -747,13 +746,6 @@ WebIDL::ExceptionOr<void> Window::initialize_web_interfaces(Badge<WindowEnvironm MUST_OR_THROW_OOM(Bindings::WindowGlobalMixin::initialize(realm, *this)); - // https://webidl.spec.whatwg.org/#define-the-global-property-references - // 5. For every namespace namespace that is exposed in realm: - // 1. Let id be namespace’s identifier. - // 3. Let namespaceObject be the result of creating a namespace object for namespace in realm. - // 3. Perform CreateMethodProperty(target, id, namespaceObject). - create_method_property("WebAssembly", MUST_OR_THROW_OOM(heap().allocate<Bindings::WebAssemblyObject>(realm, realm))); - return {}; } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp new file mode 100644 index 0000000000..f5445f08bd --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/ScopeGuard.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/Promise.h> +#include <LibJS/Runtime/VM.h> +#include <LibWasm/AbstractMachine/AbstractMachine.h> +#include <LibWasm/AbstractMachine/Validator.h> +#include <LibWeb/WebAssembly/Instance.h> +#include <LibWeb/WebAssembly/Memory.h> +#include <LibWeb/WebAssembly/Module.h> +#include <LibWeb/WebAssembly/Table.h> +#include <LibWeb/WebAssembly/WebAssembly.h> +#include <LibWeb/WebAssembly/WebAssemblyObject.h> + +namespace Web::WebAssembly { + +void visit_edges(JS::Cell::Visitor& visitor) +{ + for (auto& entry : Bindings::WebAssemblyObject::s_global_cache.function_instances) + visitor.visit(entry.value); + + for (auto& module_cache : Bindings::WebAssemblyObject::s_module_caches) { + 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); + } +} + +// https://webassembly.github.io/spec/js-api/#dom-webassembly-validate +bool validate(JS::VM& vm, JS::Handle<JS::Object>& bytes) +{ + // 1. Let stableBytes be a copy of the bytes held by the buffer bytes. + // 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. + auto maybe_module = Bindings::parse_module(vm, bytes.cell()); + + // 3. If module is error, return false. + if (maybe_module.is_error()) + return false; + + // Drop the module from the cache, we're never going to refer to it. + ScopeGuard drop_from_cache { [&] { (void)Bindings::WebAssemblyObject::s_compiled_modules.take_last(); } }; + + // 3 continued - our "compile" step is lazy with validation, explicitly do the validation. + if (Bindings::WebAssemblyObject::s_abstract_machine.validate(Bindings::WebAssemblyObject::s_compiled_modules[maybe_module.value()]->module).is_error()) + return false; + + // 4. Return true. + return true; +} + +// https://webassembly.github.io/spec/js-api/#dom-webassembly-compile +WebIDL::ExceptionOr<JS::Value> compile(JS::VM& vm, JS::Handle<JS::Object>& bytes) +{ + auto& realm = *vm.current_realm(); + + // FIXME: This shouldn't block! + auto module = Bindings::parse_module(vm, bytes.cell()); + auto promise = JS::Promise::create(realm); + + if (module.is_error()) { + promise->reject(*module.release_error().value()); + } else { + auto module_object = MUST_OR_THROW_OOM(vm.heap().allocate<Module>(realm, realm, module.release_value())); + promise->fulfill(module_object); + } + + return promise; +} + +// https://webassembly.github.io/spec/js-api/#dom-webassembly-instantiate +WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM& vm, JS::Handle<JS::Object>& bytes, Optional<JS::Handle<JS::Object>>& import_object) +{ + // FIXME: Implement the importObject parameter. + (void)import_object; + + auto& realm = *vm.current_realm(); + + // FIXME: This shouldn't block! + auto module = Bindings::parse_module(vm, bytes.cell()); + auto promise = JS::Promise::create(realm); + + if (module.is_error()) { + promise->reject(*module.release_error().value()); + return promise; + } + + auto const& compiled_module = Bindings::WebAssemblyObject::s_compiled_modules.at(module.release_value())->module; + auto result = Bindings::WebAssemblyObject::instantiate_module(vm, compiled_module); + + if (result.is_error()) { + promise->reject(*result.release_error().value()); + } else { + auto module_object = MUST_OR_THROW_OOM(vm.heap().allocate<Module>(realm, realm, Bindings::WebAssemblyObject::s_compiled_modules.size() - 1)); + auto instance_object = MUST_OR_THROW_OOM(vm.heap().allocate<Instance>(realm, realm, result.release_value())); + + auto object = JS::Object::create(realm, nullptr); + object->define_direct_property("module", module_object, JS::default_attributes); + object->define_direct_property("instance", instance_object, JS::default_attributes); + promise->fulfill(object); + } + + return promise; +} + +// https://webassembly.github.io/spec/js-api/#dom-webassembly-instantiate-moduleobject-importobject +WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM& vm, Module const& module_object, Optional<JS::Handle<JS::Object>>& import_object) +{ + // FIXME: Implement the importObject parameter. + (void)import_object; + + auto& realm = *vm.current_realm(); + auto promise = JS::Promise::create(realm); + + auto const& compiled_module = module_object.module(); + auto result = Bindings::WebAssemblyObject::instantiate_module(vm, compiled_module); + + if (result.is_error()) { + promise->reject(*result.release_error().value()); + } else { + auto instance_object = MUST_OR_THROW_OOM(vm.heap().allocate<Instance>(realm, realm, result.release_value())); + promise->fulfill(instance_object); + } + + return promise; +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h new file mode 100644 index 0000000000..782f1d73a2 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Optional.h> +#include <LibJS/Forward.h> +#include <LibJS/Heap/Handle.h> +#include <LibJS/Runtime/Value.h> +#include <LibWeb/Bindings/ExceptionOrUtils.h> +#include <LibWeb/Forward.h> + +namespace Web::WebAssembly { + +void visit_edges(JS::Cell::Visitor&); + +bool validate(JS::VM&, JS::Handle<JS::Object>& bytes); +WebIDL::ExceptionOr<JS::Value> compile(JS::VM&, JS::Handle<JS::Object>& bytes); + +WebIDL::ExceptionOr<JS::Value> instantiate(JS::VM&, JS::Handle<JS::Object>& bytes, 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); + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl new file mode 100644 index 0000000000..593cd3f32d --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.idl @@ -0,0 +1,17 @@ +#import <WebAssembly/Instance.idl> +#import <WebAssembly/Module.idl> + +dictionary WebAssemblyInstantiatedSource { + required Module module; + required Instance instance; +}; + +// https://webassembly.github.io/spec/js-api/#webassembly-namespace +[Exposed=*, WithGCVistor] +namespace WebAssembly { + boolean validate(BufferSource bytes); + Promise<Module> compile(BufferSource bytes); + + Promise<WebAssemblyInstantiatedSource> instantiate(BufferSource bytes, optional object importObject); + Promise<Instance> instantiate(Module moduleObject, optional object importObject); +}; diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp index 9ae7b5a66b..135600f6c7 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp @@ -28,86 +28,12 @@ namespace Web::Bindings { -WebAssemblyObject::WebAssemblyObject(JS::Realm& realm) - : Object(ConstructWithPrototypeTag::Tag, *realm.intrinsics().object_prototype()) -{ - s_abstract_machine.enable_instruction_count_limit(); -} - -JS::ThrowCompletionOr<void> WebAssemblyObject::initialize(JS::Realm& realm) -{ - MUST_OR_THROW_OOM(Object::initialize(realm)); - - u8 attr = JS::Attribute::Configurable | JS::Attribute::Writable | JS::Attribute::Enumerable; - define_native_function(realm, "validate", validate, 1, attr); - define_native_function(realm, "compile", compile, 1, attr); - define_native_function(realm, "instantiate", instantiate, 1, attr); - - auto& memory_constructor = Bindings::ensure_web_constructor<MemoryPrototype>(realm, "WebAssembly.Memory"sv); - define_direct_property("Memory", &memory_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); - - auto& instance_constructor = Bindings::ensure_web_constructor<InstancePrototype>(realm, "WebAssembly.Instance"sv); - define_direct_property("Instance", &instance_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); - - auto& module_constructor = Bindings::ensure_web_constructor<ModulePrototype>(realm, "WebAssembly.Module"sv); - define_direct_property("Module", &module_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); - - auto& table_constructor = Bindings::ensure_web_constructor<TablePrototype>(realm, "WebAssembly.Table"sv); - define_direct_property("Table", &table_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); - - return {}; -} - Vector<NonnullOwnPtr<WebAssemblyObject::CompiledWebAssemblyModule>> WebAssemblyObject::s_compiled_modules; Vector<NonnullOwnPtr<Wasm::ModuleInstance>> WebAssemblyObject::s_instantiated_modules; Vector<WebAssemblyObject::ModuleCache> WebAssemblyObject::s_module_caches; WebAssemblyObject::GlobalModuleCache WebAssemblyObject::s_global_cache; Wasm::AbstractMachine WebAssemblyObject::s_abstract_machine; -void WebAssemblyObject::visit_edges(Visitor& visitor) -{ - Base::visit_edges(visitor); - - for (auto& entry : s_global_cache.function_instances) - visitor.visit(entry.value); - for (auto& module_cache : s_module_caches) { - 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); - } -} - -JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::validate) -{ - // 1. Let stableBytes be a copy of the bytes held by the buffer bytes. - // Note: There's no need to copy the bytes here as the buffer data cannot change while we're compiling the module. - auto buffer = TRY(vm.argument(0).to_object(vm)); - - // 2. Compile stableBytes as a WebAssembly module and store the results as module. - auto maybe_module = parse_module(vm, buffer); - - // 3. If module is error, return false. - if (maybe_module.is_error()) - return JS::Value(false); - - // Drop the module from the cache, we're never going to refer to it. - ScopeGuard drop_from_cache { - [&] { - (void)s_compiled_modules.take_last(); - } - }; - - // 3 continued - our "compile" step is lazy with validation, explicitly do the validation. - if (s_abstract_machine.validate(s_compiled_modules[maybe_module.value()]->module).is_error()) - return JS::Value(false); - - // 4. Return true. - return JS::Value(true); -} - JS::ThrowCompletionOr<size_t> parse_module(JS::VM& vm, JS::Object* buffer_object) { ReadonlyBytes data; @@ -139,30 +65,6 @@ JS::ThrowCompletionOr<size_t> parse_module(JS::VM& vm, JS::Object* buffer_object return WebAssemblyObject::s_compiled_modules.size() - 1; } -JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::compile) -{ - auto& realm = *vm.current_realm(); - - // FIXME: This shouldn't block! - auto buffer_or_error = vm.argument(0).to_object(vm); - JS::Value rejection_value; - if (buffer_or_error.is_error()) - rejection_value = *buffer_or_error.throw_completion().value(); - - auto promise = JS::Promise::create(realm); - if (!rejection_value.is_empty()) { - promise->reject(rejection_value); - return promise; - } - auto* buffer = buffer_or_error.release_value(); - auto result = parse_module(vm, buffer); - if (result.is_error()) - promise->reject(*result.release_error().value()); - else - promise->fulfill(MUST_OR_THROW_OOM(vm.heap().allocate<WebAssembly::Module>(realm, realm, result.release_value()))); - return promise; -} - JS::ThrowCompletionOr<size_t> WebAssemblyObject::instantiate_module(JS::VM& vm, Wasm::Module const& module) { Wasm::Linker linker { module }; @@ -309,56 +211,6 @@ JS::ThrowCompletionOr<size_t> WebAssemblyObject::instantiate_module(JS::VM& vm, return s_instantiated_modules.size() - 1; } -JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) -{ - auto& realm = *vm.current_realm(); - - // FIXME: This shouldn't block! - auto buffer_or_error = vm.argument(0).to_object(vm); - auto promise = JS::Promise::create(realm); - bool should_return_module = false; - if (buffer_or_error.is_error()) { - auto rejection_value = *buffer_or_error.throw_completion().value(); - promise->reject(rejection_value); - return promise; - } - auto* buffer = buffer_or_error.release_value(); - - Wasm::Module const* module { nullptr }; - if (is<JS::ArrayBuffer>(buffer) || is<JS::TypedArrayBase>(buffer)) { - auto result = parse_module(vm, buffer); - if (result.is_error()) { - promise->reject(*result.release_error().value()); - return promise; - } - module = &WebAssemblyObject::s_compiled_modules.at(result.release_value())->module; - should_return_module = true; - } else if (is<WebAssembly::Module>(buffer)) { - module = &static_cast<WebAssembly::Module*>(buffer)->module(); - } else { - auto error = JS::TypeError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted("{} is not an ArrayBuffer or a Module", buffer->class_name()))); - promise->reject(error); - return promise; - } - VERIFY(module); - - auto result = instantiate_module(vm, *module); - if (result.is_error()) { - promise->reject(*result.release_error().value()); - } else { - auto instance_object = MUST_OR_THROW_OOM(vm.heap().allocate<WebAssembly::Instance>(realm, realm, result.release_value())); - if (should_return_module) { - auto object = JS::Object::create(realm, nullptr); - object->define_direct_property("module", MUST_OR_THROW_OOM(vm.heap().allocate<WebAssembly::Module>(realm, realm, s_compiled_modules.size() - 1)), JS::default_attributes); - object->define_direct_property("instance", instance_object, JS::default_attributes); - promise->fulfill(object); - } else { - promise->fulfill(instance_object); - } - } - return promise; -} - JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value) { auto& realm = *vm.current_realm(); diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h index bf465f56bb..9f627fbbe6 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h @@ -17,16 +17,8 @@ JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress addres JS::Value to_js_value(JS::VM&, Wasm::Value& wasm_value); JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM&, JS::Value value, Wasm::ValueType const& type); -class WebAssemblyObject final : public JS::Object { - JS_OBJECT(WebAssemblyObject, JS::Object); - +class WebAssemblyObject final { public: - explicit WebAssemblyObject(JS::Realm&); - virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override; - virtual ~WebAssemblyObject() override = default; - - virtual void visit_edges(Cell::Visitor&) override; - static JS::ThrowCompletionOr<size_t> instantiate_module(JS::VM&, Wasm::Module const&); struct CompiledWebAssemblyModule { @@ -57,11 +49,6 @@ public: static GlobalModuleCache s_global_cache; static Wasm::AbstractMachine s_abstract_machine; - -private: - JS_DECLARE_NATIVE_FUNCTION(validate); - JS_DECLARE_NATIVE_FUNCTION(compile); - JS_DECLARE_NATIVE_FUNCTION(instantiate); }; } diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 1fe22bee79..e84c90cb3b 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -204,6 +204,7 @@ libweb_js_bindings(WebAssembly/Instance) libweb_js_bindings(WebAssembly/Memory) libweb_js_bindings(WebAssembly/Module) libweb_js_bindings(WebAssembly/Table) +libweb_js_bindings(WebAssembly/WebAssembly NAMESPACE) libweb_js_bindings(WebGL/WebGLContextEvent) libweb_js_bindings(WebGL/WebGLRenderingContext) libweb_js_bindings(WebIDL/DOMException) |