From 8acc8339d19aa76f839d21190d908a43a081d93a Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Thu, 1 Jul 2021 13:41:15 +0430 Subject: [PATCH] LibWeb: Add the WebAssembly.Instance constructor --- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + .../WebAssemblyInstanceConstructor.cpp | 65 +++++++++ .../WebAssemblyInstanceConstructor.h | 28 ++++ .../WebAssembly/WebAssemblyInstanceObject.cpp | 2 +- .../LibWeb/WebAssembly/WebAssemblyObject.cpp | 125 +++++++++++------- .../LibWeb/WebAssembly/WebAssemblyObject.h | 2 + 6 files changed, 173 insertions(+), 50 deletions(-) create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.cpp create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.h diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index c745380574..9e53c09f6d 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -222,6 +222,7 @@ set(SOURCES UIEvents/EventNames.cpp UIEvents/MouseEvent.cpp URLEncoder.cpp + WebAssembly/WebAssemblyInstanceConstructor.cpp WebAssembly/WebAssemblyInstanceObject.cpp WebAssembly/WebAssemblyInstanceObjectPrototype.cpp WebAssembly/WebAssemblyMemoryConstructor.cpp diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.cpp new file mode 100644 index 0000000000..5dafdd35ac --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Bindings { + +WebAssemblyInstanceConstructor::WebAssemblyInstanceConstructor(JS::GlobalObject& global_object) + : NativeFunction(*global_object.function_prototype()) +{ +} + +WebAssemblyInstanceConstructor::~WebAssemblyInstanceConstructor() +{ +} + +JS::Value WebAssemblyInstanceConstructor::call() +{ + vm().throw_exception(global_object(), JS::ErrorType::ConstructorWithoutNew, "WebAssemblyInstance"); + return {}; +} + +JS::Value WebAssemblyInstanceConstructor::construct(FunctionObject&) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + auto module_argument = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + + if (!is(module_argument)) { + vm.throw_exception(global_object, JS::ErrorType::NotA, "WebAssembly.Module"); + return {}; + } + + auto& module_object = static_cast(*module_argument); + auto result = WebAssemblyObject::instantiate_module(module_object.module(), vm, global_object); + if (result.is_error()) { + vm.throw_exception(global_object, result.release_error()); + return {}; + } + return heap().allocate(global_object, global_object, result.value()); +} + +void WebAssemblyInstanceConstructor::initialize(JS::GlobalObject& global_object) +{ + auto& vm = this->vm(); + auto& window = static_cast(global_object); + + NativeFunction::initialize(global_object); + define_property(vm.names.prototype, &window.ensure_web_prototype("WebAssemblyInstancePrototype")); + define_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable); +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.h new file mode 100644 index 0000000000..309ab550a6 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::Bindings { + +class WebAssemblyInstanceConstructor : public JS::NativeFunction { + JS_OBJECT(WebAssemblyInstanceConstructor, JS::NativeFunction); + +public: + explicit WebAssemblyInstanceConstructor(JS::GlobalObject&); + virtual void initialize(JS::GlobalObject&) override; + virtual ~WebAssemblyInstanceConstructor() override; + + virtual JS::Value call() override; + virtual JS::Value construct(JS::FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceObject.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceObject.cpp index d0382e0a9a..96d42f3f41 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceObject.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyInstanceObject.cpp @@ -18,7 +18,7 @@ namespace Web::Bindings { WebAssemblyInstanceObject::WebAssemblyInstanceObject(JS::GlobalObject& global_object, size_t index) - : Object(static_cast(global_object).ensure_web_prototype(class_name())) + : Object(static_cast(global_object).ensure_web_prototype("WebAssemblyInstancePrototype")) , m_index(index) { } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp index 03f0f01261..065697534d 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace Web::Bindings { @@ -38,6 +39,12 @@ void WebAssemblyObject::initialize(JS::GlobalObject& global_object) auto& memory_prototype = window.ensure_web_prototype("WebAssemblyMemoryPrototype"); memory_prototype.define_property(vm.names.constructor, &memory_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); define_property("Memory", &memory_constructor); + + auto& instance_constructor = window.ensure_web_constructor("WebAssembly.Instance"); + instance_constructor.define_property(vm.names.name, js_string(vm, "WebAssembly.Instance"), JS::Attribute::Configurable); + auto& instance_prototype = window.ensure_web_prototype("WebAssemblyInstancePrototype"); + instance_prototype.define_property(vm.names.constructor, &instance_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); + define_property("Instance", &instance_constructor); } NonnullOwnPtrVector WebAssemblyObject::s_compiled_modules; @@ -119,49 +126,17 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::compile) return promise; } -JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) +Result WebAssemblyObject::instantiate_module(Wasm::Module const& module, JS::VM& vm, JS::GlobalObject& global_object) { - // FIXME: This shouldn't block! - auto buffer = vm.argument(0).to_object(global_object); - auto promise = JS::Promise::create(global_object); - auto take_exception_and_reject_if_needed = [&] { - if (vm.exception()) { - auto rejection_value = vm.exception()->value(); - vm.clear_exception(); - promise->reject(rejection_value); - return true; - } - - return false; - }; - - if (take_exception_and_reject_if_needed()) - return promise; - - const Wasm::Module* module { nullptr }; - if (is(buffer) || is(buffer)) { - auto result = parse_module(global_object, buffer); - if (result.is_error()) { - promise->reject(result.error()); - return promise; - } - module = &WebAssemblyObject::s_compiled_modules.at(result.value()).module; - } else if (is(buffer)) { - module = &static_cast(buffer)->module(); - } else { - auto error = JS::TypeError::create(global_object, String::formatted("{} is not an ArrayBuffer or a Module", buffer->class_name())); - promise->reject(error); - return promise; - } - VERIFY(module); - - Wasm::Linker linker { *module }; + Wasm::Linker linker { module }; HashMap resolved_imports; auto import_argument = vm.argument(1); if (!import_argument.is_undefined()) { [[maybe_unused]] auto import_object = import_argument.to_object(global_object); - if (take_exception_and_reject_if_needed()) - return promise; + if (auto exception = vm.exception()) { + vm.clear_exception(); + return exception->value(); + } dbgln("Trying to resolve stuff because import object was specified"); for (const Wasm::Linker::Name& import_name : linker.unresolved_imports()) { @@ -179,7 +154,7 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) import_name.type.visit( [&](Wasm::TypeIndex index) { dbgln("Trying to resolve a function {}::{}, type index {}", import_name.module, import_name.name, index.value()); - auto& type = module->type(index); + auto& type = module.type(index); // FIXME: IsCallable() if (!import_.is_function()) return; @@ -268,8 +243,10 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) break; } - if (take_exception_and_reject_if_needed()) - return promise; + if (auto exception = vm.exception()) { + vm.clear_exception(); + return exception->value(); + } } linker.link(resolved_imports); @@ -279,22 +256,72 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) StringBuilder builder; builder.append("LinkError: Missing "); builder.join(' ', link_result.error().missing_imports); - auto error = JS::TypeError::create(global_object, builder.build()); - promise->reject(error); - return promise; + return JS::Value(JS::TypeError::create(global_object, builder.build())); } - auto instance_result = s_abstract_machine.instantiate(*module, link_result.release_value()); + auto instance_result = s_abstract_machine.instantiate(module, link_result.release_value()); if (instance_result.is_error()) { // FIXME: Throw a LinkError instead. - auto error = JS::TypeError::create(global_object, instance_result.error().error); - promise->reject(error); - return promise; + return JS::Value(JS::TypeError::create(global_object, instance_result.error().error)); } s_instantiated_modules.append(instance_result.release_value()); s_module_caches.empend(); - promise->fulfill(vm.heap().allocate(global_object, global_object, s_instantiated_modules.size() - 1)); + return s_instantiated_modules.size() - 1; +} + +JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) +{ + // FIXME: This shouldn't block! + auto buffer = vm.argument(0).to_object(global_object); + auto promise = JS::Promise::create(global_object); + bool should_return_module = false; + auto take_exception_and_reject_if_needed = [&] { + if (vm.exception()) { + auto rejection_value = vm.exception()->value(); + vm.clear_exception(); + promise->reject(rejection_value); + return true; + } + + return false; + }; + + if (take_exception_and_reject_if_needed()) + return promise; + + const Wasm::Module* module { nullptr }; + if (is(buffer) || is(buffer)) { + auto result = parse_module(global_object, buffer); + if (result.is_error()) { + promise->reject(result.error()); + return promise; + } + module = &WebAssemblyObject::s_compiled_modules.at(result.value()).module; + should_return_module = true; + } else if (is(buffer)) { + module = &static_cast(buffer)->module(); + } else { + auto error = JS::TypeError::create(global_object, String::formatted("{} is not an ArrayBuffer or a Module", buffer->class_name())); + promise->reject(error); + return promise; + } + VERIFY(module); + + auto result = instantiate_module(*module, vm, global_object); + if (result.is_error()) { + promise->reject(result.release_error()); + } else { + auto instance_object = vm.heap().allocate(global_object, global_object, result.value()); + if (should_return_module) { + auto object = JS::Object::create(global_object, nullptr); + object->put("module", vm.heap().allocate(global_object, global_object, s_compiled_modules.size() - 1)); + object->put("instance", instance_object); + promise->fulfill(object); + } else { + promise->fulfill(instance_object); + } + } return promise; } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h index 0a51085a69..4713f25b1d 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h @@ -28,6 +28,8 @@ public: virtual void visit_edges(Cell::Visitor&) override; + static Result instantiate_module(Wasm::Module const&, JS::VM&, JS::GlobalObject&); + struct CompiledWebAssemblyModule { explicit CompiledWebAssemblyModule(Wasm::Module&& module) : module(move(module))