From cf8b75c2e572a413b6865c2aec6efb10c50c2179 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 17 May 2021 21:42:56 +0430 Subject: [PATCH] LibWasm+LibWeb: Partially resolve memory exports This allows the JS side to access the wasm memory, assuming it's exported by the module. This can be used to draw stuff on the wasm side and display them from the js side, for example :^) --- .../LibWasm/AbstractMachine/Interpreter.cpp | 32 ++++--- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + .../LibWeb/WebAssembly/WebAssemblyObject.cpp | 92 ++++++++++++++----- .../LibWeb/WebAssembly/WebAssemblyObject.h | 33 ++++--- .../WebAssemblyObjectPrototype.cpp | 32 +++++++ .../WebAssembly/WebAssemblyObjectPrototype.h | 32 +++++++ 6 files changed, 173 insertions(+), 49 deletions(-) create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.cpp create mode 100644 Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.h diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp index 27d458b11c1..6e789c86712 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp @@ -14,10 +14,19 @@ namespace Wasm { -#define TRAP_IF_NOT(x) \ - do { \ - if (trap_if_not(x)) \ - return; \ +#define TRAP_IF_NOT(x) \ + do { \ + if (trap_if_not(x)) { \ + dbgln_if(WASM_TRACE_DEBUG, "Trapped because {} failed, at line {}", #x, __LINE__); \ + return; \ + } \ + } while (false) + +#define TRAP_IF_NOT_NORETURN(x) \ + do { \ + if (trap_if_not(x)) { \ + dbgln_if(WASM_TRACE_DEBUG, "Trapped because {} failed, at line {}", #x, __LINE__); \ + } \ } while (false) void Interpreter::interpret(Configuration& configuration) @@ -56,9 +65,8 @@ void Interpreter::branch_to_label(Configuration& configuration, LabelIndex index configuration.stack().pop(); } - // Push results in reverse - for (size_t i = results.size(); i > 0; --i) - configuration.stack().push(move(static_cast>&>(results)[i - 1])); + for (auto& result : results) + configuration.stack().push(move(result)); configuration.ip() = label->continuation(); } @@ -269,13 +277,12 @@ struct ConvertToRaw { Vector> Interpreter::pop_values(Configuration& configuration, size_t count) { Vector> results; - // Pop results in order for (size_t i = 0; i < count; ++i) { auto top_of_stack = configuration.stack().pop(); if (auto value = top_of_stack.get_pointer>()) - results.append(move(*value)); + results.prepend(move(*value)); else - trap_if_not(value); + TRAP_IF_NOT_NORETURN(value); } return results; } @@ -361,9 +368,8 @@ void Interpreter::interpret(Configuration& configuration, InstructionPointer& ip break; } - // Push results in reverse - for (size_t i = 1; i < results.size() + 1; ++i) - configuration.stack().push(move(static_cast>&>(results)[results.size() - i])); + for (auto& result : results) + configuration.stack().push(move(result)); if (instruction.opcode() == Instructions::structured_end) return; diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index f9d370f7bbc..bc9123ed834 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -222,6 +222,7 @@ set(SOURCES UIEvents/MouseEvent.cpp URLEncoder.cpp WebAssembly/WebAssemblyObject.cpp + WebAssembly/WebAssemblyObjectPrototype.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 1fcf7ef73c8..a8e8f82c8c7 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp @@ -4,11 +4,13 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include #include +#include #include namespace Web::Bindings { @@ -57,6 +59,11 @@ static Result parse_module(JS::GlobalObject& global_object, J } InputMemoryStream stream { *bytes }; auto module_result = Wasm::Module::parse(stream); + ScopeGuard drain_errors { + [&] { + stream.handle_any_error(); + } + }; if (module_result.is_error()) { // FIXME: Throw CompileError instead. auto error = JS::TypeError::create(global_object, Wasm::parse_error_to_string(module_result.error())); @@ -192,7 +199,11 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) }, [&](const auto&) { // FIXME: Implement these. + dbgln("Unimplemented import of non-function attempted"); + vm.throw_exception(global_object, "LinkError: Not Implemented"); }); + if (vm.exception()) + break; } if (take_exception_and_reject_if_needed()) @@ -231,7 +242,7 @@ WebAssemblyModuleObject::WebAssemblyModuleObject(JS::GlobalObject& global_object } WebAssemblyInstanceObject::WebAssemblyInstanceObject(JS::GlobalObject& global_object, size_t index) - : Object(*global_object.object_prototype()) + : Object(static_cast(global_object).ensure_web_prototype(class_name())) , m_index(index) { } @@ -341,12 +352,6 @@ JS::NativeFunction* create_native_function(Wasm::FunctionAddress address, String }); } -void WebAssemblyInstancePrototype::initialize(JS::GlobalObject& global_object) -{ - Object::initialize(global_object); - define_native_property("exports", exports_getter, nullptr); -} - void WebAssemblyInstanceObject::initialize(JS::GlobalObject& global_object) { Object::initialize(global_object); @@ -361,6 +366,11 @@ void WebAssemblyInstanceObject::initialize(JS::GlobalObject& global_object) auto function = create_native_function(address, export_.name(), global_object); m_exports_object->define_property(export_.name(), function); }, + [&](const Wasm::MemoryAddress& address) { + // FIXME: Cache this. + auto memory = heap().allocate(global_object, global_object, address); + m_exports_object->define_property(export_.name(), memory); + }, [&](const auto&) { // FIXME: Implement other exports! }); @@ -369,24 +379,64 @@ void WebAssemblyInstanceObject::initialize(JS::GlobalObject& global_object) m_exports_object->set_integrity_level(IntegrityLevel::Frozen); } -JS_DEFINE_NATIVE_GETTER(WebAssemblyInstancePrototype::exports_getter) -{ - auto this_value = vm.this_value(global_object); - auto this_object = this_value.to_object(global_object); - if (vm.exception()) - return {}; - if (!is(this_object)) { - vm.throw_exception(global_object, JS::ErrorType::NotAn, "WebAssemblyInstance"); - return {}; - } - auto object = static_cast(this_object); - return object->m_exports_object; -} - void WebAssemblyInstanceObject::visit_edges(Cell::Visitor& visitor) { Object::visit_edges(visitor); visitor.visit(m_exports_object); } +WebAssemblyMemoryObject::WebAssemblyMemoryObject(JS::GlobalObject& global_object, Wasm::MemoryAddress address) + : JS::Object(global_object) + , 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(); + + return JS::ArrayBuffer::create(global_object, &memory->data()); +} + } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h index 35b3aa30944..173cf4df7d3 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::Bindings { @@ -58,21 +59,6 @@ private: size_t m_index { 0 }; }; -class WebAssemblyInstancePrototype final : public JS::Object { - JS_OBJECT(WebAssemblyInstancePrototype, JS::Object); - -public: - explicit WebAssemblyInstancePrototype(JS::GlobalObject& global_object) - : JS::Object(global_object) - { - } - - virtual void initialize(JS::GlobalObject&) override; - -private: - JS_DECLARE_NATIVE_GETTER(exports_getter); -}; - class WebAssemblyInstanceObject final : public JS::Object { JS_OBJECT(WebAssemblyInstanceObject, JS::Object); @@ -93,4 +79,21 @@ private: JS::Object* m_exports_object { nullptr }; }; +class WebAssemblyMemoryObject final : public JS::Object { + JS_OBJECT(WebAssemblyModuleObject, 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; +}; + } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.cpp new file mode 100644 index 00000000000..978c2dcad9e --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Bindings { + +void WebAssemblyInstancePrototype::initialize(JS::GlobalObject& global_object) +{ + Object::initialize(global_object); + define_native_property("exports", exports_getter, nullptr); +} + +JS_DEFINE_NATIVE_GETTER(WebAssemblyInstancePrototype::exports_getter) +{ + auto this_value = vm.this_value(global_object); + auto this_object = this_value.to_object(global_object); + if (vm.exception()) + return {}; + if (!is(this_object)) { + vm.throw_exception(global_object, JS::ErrorType::NotAn, "WebAssemblyInstance"); + return {}; + } + auto object = static_cast(this_object); + return object->m_exports_object; +} + +} diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.h b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.h new file mode 100644 index 00000000000..b65759598be --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObjectPrototype.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::Bindings { + +class WebAssemblyInstancePrototype final : public JS::Object { + JS_OBJECT(WebAssemblyInstancePrototype, JS::Object); + +public: + explicit WebAssemblyInstancePrototype(JS::GlobalObject& global_object) + : JS::Object(global_object) + { + } + + virtual void initialize(JS::GlobalObject&) override; + +private: + JS_DECLARE_NATIVE_GETTER(exports_getter); + static JS::Handle s_instance; +}; + +}