From 5c90c389c3cbce761d711bd2b5a635be196e48a4 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 21 Jun 2021 08:42:58 +0430 Subject: LibWeb: Implement the WebAssembly Memory object and Memory imports --- .../WebAssembly/WebAssemblyMemoryConstructor.cpp | 79 ++++++++++++++++ .../WebAssembly/WebAssemblyMemoryConstructor.h | 28 ++++++ .../WebAssembly/WebAssemblyMemoryPrototype.cpp | 63 +++++++++++++ .../WebAssembly/WebAssemblyMemoryPrototype.h | 34 +++++++ .../LibWeb/WebAssembly/WebAssemblyObject.cpp | 101 ++++++++++----------- .../LibWeb/WebAssembly/WebAssemblyObject.h | 6 +- 6 files changed, 255 insertions(+), 56 deletions(-) create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.cpp create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.h create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.cpp create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h (limited to 'Userland/Libraries/LibWeb/WebAssembly') diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.cpp new file mode 100644 index 0000000000..4a8ce4a786 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::Bindings { + +WebAssemblyMemoryConstructor::WebAssemblyMemoryConstructor(JS::GlobalObject& global_object) + : NativeFunction(*global_object.function_prototype()) +{ +} + +WebAssemblyMemoryConstructor::~WebAssemblyMemoryConstructor() +{ +} + +JS::Value WebAssemblyMemoryConstructor::call() +{ + vm().throw_exception(global_object(), JS::ErrorType::ConstructorWithoutNew, "WebAssemblyMemory"); + return {}; +} + +JS::Value WebAssemblyMemoryConstructor::construct(Function&) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + auto descriptor = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + + auto initial_value = descriptor->get_own_property("initial", {}, JS::AllowSideEffects::No); + auto maximum_value = descriptor->get_own_property("maximum", {}, JS::AllowSideEffects::No); + + if (initial_value.is_empty()) { + vm.throw_exception(global_object, JS::ErrorType::NotA, "Number"); + return {}; + } + + auto initial = initial_value.to_u32(global_object); + if (vm.exception()) + return {}; + + Optional maximum; + + if (!maximum_value.is_empty()) { + maximum = maximum_value.to_u32(global_object); + if (vm.exception()) + return {}; + } + + auto address = WebAssemblyObject::s_abstract_machine.store().allocate(Wasm::MemoryType { Wasm::Limits { initial, maximum } }); + if (!address.has_value()) { + vm.throw_exception(global_object, "Wasm Memory allocation failed"); + return {}; + } + + return vm.heap().allocate(global_object, global_object, *address); +} + +void WebAssemblyMemoryConstructor::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("WebAssembly.Memory")); + define_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable); +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.h new file mode 100644 index 0000000000..ecf8896449 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::Bindings { + +class WebAssemblyMemoryConstructor : public JS::NativeFunction { + JS_OBJECT(WebAssemblyMemoryConstructor, JS::NativeFunction); + +public: + explicit WebAssemblyMemoryConstructor(JS::GlobalObject&); + virtual void initialize(JS::GlobalObject&) override; + virtual ~WebAssemblyMemoryConstructor() override; + + virtual JS::Value call() override; + virtual JS::Value construct(JS::Function& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.cpp new file mode 100644 index 0000000000..c5f32d0591 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::Bindings { + +void WebAssemblyMemoryPrototype::initialize(JS::GlobalObject& global_object) +{ + Object::initialize(global_object); + define_native_property("buffer", buffer_getter, nullptr); + define_native_function("grow", grow); +} + +JS_DEFINE_NATIVE_FUNCTION(WebAssemblyMemoryPrototype::grow) +{ + auto page_count = vm.argument(0).to_u32(global_object); + if (vm.exception()) + return {}; + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object || !is(this_object)) { + vm.throw_exception(global_object, JS::ErrorType::NotA, "Memory"); + return {}; + } + auto* memory_object = static_cast(this_object); + auto address = memory_object->address(); + auto* memory = WebAssemblyObject::s_abstract_machine.store().get(address); + if (!memory) + return JS::js_undefined(); + + auto previous_size = memory->size() / Wasm::Constants::page_size; + if (!memory->grow(page_count * Wasm::Constants::page_size)) { + vm.throw_exception(global_object, "Memory.grow() grows past the stated limit of the memory instance"); + return {}; + } + + return JS::Value(static_cast(previous_size)); +} + +JS_DEFINE_NATIVE_GETTER(WebAssemblyMemoryPrototype::buffer_getter) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object || !is(this_object)) { + vm.throw_exception(global_object, JS::ErrorType::NotA, "Memory"); + return {}; + } + auto* memory_object = static_cast(this_object); + auto address = memory_object->address(); + auto* memory = WebAssemblyObject::s_abstract_machine.store().get(address); + if (!memory) + return JS::js_undefined(); + + auto array_buffer = JS::ArrayBuffer::create(global_object, &memory->data()); + array_buffer->set_detach_key(JS::js_string(vm, "WebAssembly.Memory")); + return array_buffer; +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h new file mode 100644 index 0000000000..bcfbb1e88c --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "WebAssemblyMemoryConstructor.h" +#include +#include +#include +#include +#include + +namespace Web::Bindings { + +class WebAssemblyMemoryPrototype final : public JS::Object { + JS_OBJECT(WebAssemblyMemoryPrototype, JS::Object); + +public: + explicit WebAssemblyMemoryPrototype(JS::GlobalObject& global_object) + : JS::Object(global_object) + { + } + + virtual void initialize(JS::GlobalObject&) override; + +private: + JS_DECLARE_NATIVE_FUNCTION(grow); + JS_DECLARE_NATIVE_GETTER(buffer_getter); +}; + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp index a0ffd7e035..e593564458 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include "WebAssemblyMemoryPrototype.h" #include #include #include @@ -31,6 +32,15 @@ void WebAssemblyObject::initialize(JS::GlobalObject& global_object) define_native_function("validate", validate, 1); define_native_function("compile", compile, 1); define_native_function("instantiate", instantiate, 1); + + auto& vm = global_object.vm(); + + auto& window = static_cast(global_object); + auto& memory_constructor = window.ensure_web_prototype("WebAssembly.Memory"); + memory_constructor.define_property(vm.names.name, js_string(vm, "WebAssembly.Memory"), JS::Attribute::Configurable); + 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); } NonnullOwnPtrVector WebAssemblyObject::s_compiled_modules; @@ -213,6 +223,45 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) resolved_imports.set(import_name, Wasm::ExternValue { Wasm::FunctionAddress { *address } }); }, + [&](Wasm::GlobalType const& type) { + Optional address; + // https://webassembly.github.io/spec/js-api/#read-the-imports step 5.1 + if (import_.is_number() || import_.is_bigint()) { + if (import_.is_number() && type.type().kind() == Wasm::ValueType::I64) { + // FIXME: Throw a LinkError instead. + vm.throw_exception(global_object, "LinkError: Import resolution attempted to cast a Number to a BigInteger"); + return; + } + if (import_.is_bigint() && type.type().kind() != Wasm::ValueType::I64) { + // FIXME: Throw a LinkError instead. + vm.throw_exception(global_object, "LinkError: Import resolution attempted to cast a BigInteger to a Number"); + return; + } + auto cast_value = to_webassembly_value(import_, type.type(), global_object); + if (!cast_value.has_value()) + return; + address = s_abstract_machine.store().allocate({ type.type(), false }, cast_value.release_value()); + } else { + // FIXME: https://webassembly.github.io/spec/js-api/#read-the-imports step 5.2 + // if v implements Global + // let globaladdr be v.[[Global]] + + // FIXME: Throw a LinkError instead + vm.throw_exception(global_object, "LinkError: Invalid value for global type"); + return; + } + + resolved_imports.set(import_name, Wasm::ExternValue { *address }); + }, + [&](Wasm::MemoryType const&) { + if (!import_.is_object() || !is(import_.as_object())) { + // FIXME: Throw a LinkError instead + vm.throw_exception(global_object, "LinkError: Expected an instance of WebAssembly.Memory for a memory import"); + return; + } + auto address = static_cast(import_.as_object()).address(); + resolved_imports.set(import_name, Wasm::ExternValue { address }); + }, [&](const auto&) { // FIXME: Implement these. dbgln("Unimplemented import of non-function attempted"); @@ -420,59 +469,9 @@ void WebAssemblyInstanceObject::visit_edges(Cell::Visitor& visitor) } WebAssemblyMemoryObject::WebAssemblyMemoryObject(JS::GlobalObject& global_object, Wasm::MemoryAddress address) - : JS::Object(global_object) + : Object(static_cast(global_object).ensure_web_prototype(class_name())) , m_address(address) { } -void WebAssemblyMemoryObject::initialize(JS::GlobalObject& global_object) -{ - Object::initialize(global_object); - define_native_function("grow", grow, 1); - define_native_property("buffer", buffer, nullptr); -} - -JS_DEFINE_NATIVE_FUNCTION(WebAssemblyMemoryObject::grow) -{ - auto page_count = vm.argument(0).to_u32(global_object); - if (vm.exception()) - return {}; - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object || !is(this_object)) { - vm.throw_exception(global_object, JS::ErrorType::NotA, "Memory"); - return {}; - } - auto* memory_object = static_cast(this_object); - auto address = memory_object->m_address; - auto* memory = WebAssemblyObject::s_abstract_machine.store().get(address); - if (!memory) - return JS::js_undefined(); - - auto previous_size = memory->size() / Wasm::Constants::page_size; - if (!memory->grow(page_count * Wasm::Constants::page_size)) { - vm.throw_exception(global_object, "Memory.grow() grows past the stated limit of the memory instance"); - return {}; - } - - return JS::Value(static_cast(previous_size)); -} - -JS_DEFINE_NATIVE_GETTER(WebAssemblyMemoryObject::buffer) -{ - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object || !is(this_object)) { - vm.throw_exception(global_object, JS::ErrorType::NotA, "Memory"); - return {}; - } - auto* memory_object = static_cast(this_object); - auto address = memory_object->m_address; - auto* memory = WebAssemblyObject::s_abstract_machine.store().get(address); - if (!memory) - return JS::js_undefined(); - - auto array_buffer = JS::ArrayBuffer::create(global_object, &memory->data()); - array_buffer->set_detach_key(JS::js_string(vm, "WebAssembly.Memory")); - return array_buffer; -} - } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h index 3758ab9d39..99f762c859 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h @@ -95,19 +95,15 @@ private: }; class WebAssemblyMemoryObject final : public JS::Object { - JS_OBJECT(WebAssemblyModuleObject, JS::Object); + JS_OBJECT(WebAssemblyMemoryObject, JS::Object); public: explicit WebAssemblyMemoryObject(JS::GlobalObject&, Wasm::MemoryAddress); - virtual void initialize(JS::GlobalObject&) override; virtual ~WebAssemblyMemoryObject() override = default; auto address() const { return m_address; } private: - JS_DECLARE_NATIVE_FUNCTION(grow); - JS_DECLARE_NATIVE_GETTER(buffer); - Wasm::MemoryAddress m_address; }; -- cgit v1.2.3