diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index b391e19e18..249f34d37c 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -301,6 +301,23 @@ public: auto& elements() { return m_elements; } auto& type() const { return m_type; } + bool grow(size_t size_to_grow, Reference const& fill_value) + { + if (size_to_grow == 0) + return true; + auto new_size = m_elements.size() + size_to_grow; + if (auto max = m_type.limits().max(); max.has_value()) { + if (max.value() < new_size) + return false; + } + auto previous_size = m_elements.size(); + if (!m_elements.try_resize(new_size)) + return false; + for (size_t i = previous_size; i < m_elements.size(); ++i) + m_elements[i] = fill_value; + return true; + } + private: Vector> m_elements; TableType const& m_type; diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index e911822397..504b7a1ed7 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -231,6 +231,9 @@ set(SOURCES WebAssembly/WebAssemblyModuleConstructor.cpp WebAssembly/WebAssemblyModuleObject.cpp WebAssembly/WebAssemblyObject.cpp + WebAssembly/WebAssemblyTableConstructor.cpp + WebAssembly/WebAssemblyTableObject.cpp + WebAssembly/WebAssemblyTablePrototype.cpp WebContentClient.cpp XHR/EventNames.cpp XHR/XMLHttpRequest.cpp diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp index d3be78d352..aabe0d27c6 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp @@ -9,6 +9,8 @@ #include "WebAssemblyModuleConstructor.h" #include "WebAssemblyModuleObject.h" #include "WebAssemblyModulePrototype.h" +#include "WebAssemblyTableObject.h" +#include "WebAssemblyTablePrototype.h" #include #include #include @@ -57,6 +59,12 @@ void WebAssemblyObject::initialize(JS::GlobalObject& global_object) auto& module_prototype = window.ensure_web_prototype("WebAssemblyModulePrototype"); module_prototype.define_direct_property(vm.names.constructor, &module_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); define_direct_property("Module", &module_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); + + auto& table_constructor = window.ensure_web_constructor("WebAssembly.Table"); + table_constructor.define_direct_property(vm.names.name, js_string(vm, "WebAssembly.Table"), JS::Attribute::Configurable); + auto& table_prototype = window.ensure_web_prototype("WebAssemblyTablePrototype"); + table_prototype.define_direct_property(vm.names.constructor, &table_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); + define_direct_property("Table", &table_constructor, JS::Attribute::Writable | JS::Attribute::Configurable); } NonnullOwnPtrVector WebAssemblyObject::s_compiled_modules; @@ -249,6 +257,15 @@ Result WebAssemblyObject::instantiate_module(Wasm::Module con auto address = static_cast(import_.as_object()).address(); resolved_imports.set(import_name, Wasm::ExternValue { address }); }, + [&](Wasm::TableType 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.Table for a table 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"); @@ -399,8 +416,22 @@ Optional to_webassembly_value(JS::Value value, const Wasm::ValueTyp return Wasm::Value { static_cast(number) }; } case Wasm::ValueType::FunctionReference: + case Wasm::ValueType::NullFunctionReference: { + if (value.is_null()) + return Wasm::Value { Wasm::ValueType(Wasm::ValueType::NullExternReference), 0ull }; + + if (value.is_function()) { + auto& function = value.as_function(); + for (auto& entry : WebAssemblyObject::s_global_cache.function_instances) { + if (entry.value == &function) + return Wasm::Value { Wasm::Reference { Wasm::Reference::Func { entry.key } } }; + } + } + + vm.throw_exception(global_object, JS::ErrorType::NotAn, "Exported function"); + return {}; + } case Wasm::ValueType::ExternReference: - case Wasm::ValueType::NullFunctionReference: case Wasm::ValueType::NullExternReference: TODO(); } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableConstructor.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableConstructor.cpp new file mode 100644 index 0000000000..480d2dea51 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableConstructor.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Bindings { + +WebAssemblyTableConstructor::WebAssemblyTableConstructor(JS::GlobalObject& global_object) + : NativeFunction(*global_object.function_prototype()) +{ +} + +WebAssemblyTableConstructor::~WebAssemblyTableConstructor() +{ +} + +JS::Value WebAssemblyTableConstructor::call() +{ + vm().throw_exception(global_object(), JS::ErrorType::ConstructorWithoutNew, "WebAssembly.Table"); + return {}; +} + +JS::Value WebAssemblyTableConstructor::construct(FunctionObject&) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + auto descriptor = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + + auto element_value = descriptor->get("element"); + if (vm.exception()) + return {}; + if (!element_value.is_string()) { + vm.throw_exception(global_object, JS::ErrorType::InvalidHint, element_value.to_string_without_side_effects()); + return {}; + } + auto& element = element_value.as_string().string(); + + Optional reference_type; + if (element == "anyfunc"sv) + reference_type = Wasm::ValueType(Wasm::ValueType::FunctionReference); + else if (element == "externref"sv) + reference_type = Wasm::ValueType(Wasm::ValueType::ExternReference); + + if (!reference_type.has_value()) { + vm.throw_exception(global_object, JS::ErrorType::InvalidHint, element); + return {}; + } + + auto initial_value = descriptor->get("initial"); + if (vm.exception()) + return {}; + auto maximum_value = descriptor->get("maximum"); + if (vm.exception()) + return {}; + + auto initial = initial_value.to_u32(global_object); + if (vm.exception()) + return {}; + + Optional maximum; + + if (!maximum_value.is_undefined()) { + maximum = maximum_value.to_u32(global_object); + if (vm.exception()) + return {}; + } + + if (maximum.has_value() && maximum.value() < initial) { + vm.throw_exception(global_object, "maximum should be larger than or equal to initial"); + return {}; + } + + auto value_value = descriptor->get("value"); + if (vm.exception()) + return {}; + + auto reference_value = [&]() -> Optional { + if (value_value.is_empty() || value_value.is_undefined()) + return Wasm::Value(*reference_type, 0ull); + + return to_webassembly_value(value_value, *reference_type, global_object); + }(); + + if (!reference_value.has_value()) + return {}; + + auto& reference = reference_value->value().get(); + + auto address = WebAssemblyObject::s_abstract_machine.store().allocate(Wasm::TableType { *reference_type, Wasm::Limits { initial, maximum } }); + if (!address.has_value()) { + vm.throw_exception(global_object, "Wasm Table allocation failed"); + return {}; + } + + auto& table = *WebAssemblyObject::s_abstract_machine.store().get(*address); + for (auto& element : table.elements()) + element = reference; + + return vm.heap().allocate(global_object, global_object, *address); +} + +void WebAssemblyTableConstructor::initialize(JS::GlobalObject& global_object) +{ + auto& vm = this->vm(); + auto& window = static_cast(global_object); + + NativeFunction::initialize(global_object); + define_direct_property(vm.names.prototype, &window.ensure_web_prototype("WebAssemblyTablePrototype"), 0); + define_direct_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable); +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableConstructor.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableConstructor.h new file mode 100644 index 0000000000..56536a4d58 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::Bindings { + +class WebAssemblyTableConstructor : public JS::NativeFunction { + JS_OBJECT(WebAssemblyTableConstructor, JS::NativeFunction); + +public: + explicit WebAssemblyTableConstructor(JS::GlobalObject&); + virtual void initialize(JS::GlobalObject&) override; + virtual ~WebAssemblyTableConstructor() 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/WebAssemblyTableObject.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableObject.cpp new file mode 100644 index 0000000000..f01f9e9cd9 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableObject.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "WebAssemblyTablePrototype.h" +#include + +namespace Web::Bindings { + +WebAssemblyTableObject::WebAssemblyTableObject(JS::GlobalObject& global_object, Wasm::TableAddress address) + : Object(static_cast(global_object).ensure_web_prototype("WebAssemblyTablePrototype")) + , m_address(address) +{ +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableObject.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableObject.h new file mode 100644 index 0000000000..bb2bbd2cd9 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTableObject.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::Bindings { + +class WebAssemblyTableObject final : public JS::Object { + JS_OBJECT(WebAssemblyTableObject, Object); + +public: + explicit WebAssemblyTableObject(JS::GlobalObject&, Wasm::TableAddress); + virtual ~WebAssemblyTableObject() override = default; + + Wasm::TableAddress address() const { return m_address; } + +private: + Wasm::TableAddress m_address; +}; + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTablePrototype.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTablePrototype.cpp new file mode 100644 index 0000000000..161d413fdf --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTablePrototype.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::Bindings { + +void WebAssemblyTablePrototype::initialize(JS::GlobalObject& global_object) +{ + Object::initialize(global_object); + define_native_accessor("length", length_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable); + define_native_function("grow", grow, 1, JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable); + define_native_function("get", get, 1, JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable); + define_native_function("set", set, 1, JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable); +} + +JS_DEFINE_NATIVE_FUNCTION(WebAssemblyTablePrototype::grow) +{ + auto delta = 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, "WebAssembly.Table"); + return {}; + } + auto* table_object = static_cast(this_object); + auto address = table_object->address(); + auto* table = WebAssemblyObject::s_abstract_machine.store().get(address); + if (!table) + return JS::js_undefined(); + + auto initial_size = table->elements().size(); + auto value_value = vm.argument(1); + auto reference_value = [&]() -> Optional { + if (value_value.is_empty() || value_value.is_undefined()) + return Wasm::Value(table->type().element_type(), 0ull); + + return to_webassembly_value(value_value, table->type().element_type(), global_object); + }(); + + if (!reference_value.has_value()) + return {}; + + auto& reference = reference_value->value().get(); + + if (!table->grow(delta, reference)) { + vm.throw_exception(global_object, "Failed to grow table"); + return {}; + } + + return JS::Value(static_cast(initial_size)); +} + +JS_DEFINE_NATIVE_FUNCTION(WebAssemblyTablePrototype::get) +{ + auto index = 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, "WebAssembly.Table"); + return {}; + } + auto* table_object = static_cast(this_object); + auto address = table_object->address(); + auto* table = WebAssemblyObject::s_abstract_machine.store().get(address); + if (!table) + return JS::js_undefined(); + + if (table->elements().size() <= index) { + vm.throw_exception(global_object, "Table element index out of range"); + return {}; + } + + auto& ref = table->elements()[index]; + if (!ref.has_value()) + return JS::js_undefined(); + + Wasm::Value wasm_value { ref.value() }; + return to_js_value(wasm_value, global_object); +} + +JS_DEFINE_NATIVE_FUNCTION(WebAssemblyTablePrototype::set) +{ + auto index = 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, "WebAssembly.Table"); + return {}; + } + auto* table_object = static_cast(this_object); + auto address = table_object->address(); + auto* table = WebAssemblyObject::s_abstract_machine.store().get(address); + if (!table) + return JS::js_undefined(); + + if (table->elements().size() <= index) { + vm.throw_exception(global_object, "Table element index out of range"); + return {}; + } + + auto value_value = vm.argument(1); + auto reference_value = [&]() -> Optional { + if (value_value.is_empty() || value_value.is_undefined()) + return Wasm::Value(table->type().element_type(), 0ull); + + return to_webassembly_value(value_value, table->type().element_type(), global_object); + }(); + + if (!reference_value.has_value()) + return {}; + + auto& reference = reference_value->value().get(); + table->elements()[index] = reference; + + return JS::js_undefined(); +} + +JS_DEFINE_NATIVE_FUNCTION(WebAssemblyTablePrototype::length_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, "WebAssembly.Table"); + return {}; + } + auto* table_object = static_cast(this_object); + auto address = table_object->address(); + auto* table = WebAssemblyObject::s_abstract_machine.store().get(address); + if (!table) + return JS::js_undefined(); + + return JS::Value(table->elements().size()); +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTablePrototype.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTablePrototype.h new file mode 100644 index 0000000000..e3075c63ae --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyTablePrototype.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "WebAssemblyTableConstructor.h" +#include +#include +#include +#include +#include + +namespace Web::Bindings { + +class WebAssemblyTablePrototype final : public JS::Object { + JS_OBJECT(WebAssemblyTablePrototype, JS::Object); + +public: + explicit WebAssemblyTablePrototype(JS::GlobalObject& global_object) + : JS::Object(global_object) + { + } + + virtual void initialize(JS::GlobalObject& global_object) override; + +private: + JS_DECLARE_NATIVE_FUNCTION(grow); + JS_DECLARE_NATIVE_FUNCTION(get); + JS_DECLARE_NATIVE_FUNCTION(set); + JS_DECLARE_NATIVE_FUNCTION(length_getter); +}; + +}