diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2021-05-01 01:08:51 +0430 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-05-13 19:44:32 +0100 |
commit | 4d9246ac9decfd22c3f6ceee0a90877f38679ca2 (patch) | |
tree | 7c225a55d26d4f0e569df016a5b2c5d501e9ab55 /Userland | |
parent | 2b755f1fbfd96efb9e053780a332b68371a17e6e (diff) | |
download | serenity-4d9246ac9decfd22c3f6ceee0a90877f38679ca2.zip |
LibWasm: Add basic support for module instantiation and execution stubs
This adds very basic support for module instantiation/allocation, as
well as a stub for an interpreter (and executions APIs).
The 'wasm' utility is further expanded to instantiate, and attempt
executing the first non-imported function in the module.
Note that as the execution is a stub, the expected result is a zero.
Regardless, this will allow future commits to implement the JS
WebAssembly API. :^)
Diffstat (limited to 'Userland')
8 files changed, 938 insertions, 1 deletions
diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp new file mode 100644 index 0000000000..c22d60d379 --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWasm/AbstractMachine/AbstractMachine.h> +#include <LibWasm/AbstractMachine/Configuration.h> +#include <LibWasm/Types.h> + +namespace Wasm { + +Optional<FunctionAddress> Store::allocate(ModuleInstance& module, const Module::Function& function) +{ + FunctionAddress address { m_functions.size() }; + if (function.type().value() > module.types().size()) + return {}; + + auto& type = module.types()[function.type().value()]; + m_functions.empend(WasmFunction { type, module, function }); + return address; +} + +Optional<FunctionAddress> Store::allocate(const HostFunction& function) +{ + FunctionAddress address { m_functions.size() }; + m_functions.empend(HostFunction { function }); + return address; +} + +Optional<TableAddress> Store::allocate(const TableType& type) +{ + TableAddress address { m_tables.size() }; + Vector<Optional<Reference>> elements; + elements.resize(type.limits().min()); + m_tables.empend(TableInstance { type, move(elements) }); + return address; +} + +Optional<MemoryAddress> Store::allocate(const MemoryType& type) +{ + MemoryAddress address { m_memories.size() }; + m_memories.empend(MemoryInstance { type }); + return address; +} + +Optional<GlobalAddress> Store::allocate(const GlobalType& type, Value value) +{ + GlobalAddress address { m_globals.size() }; + m_globals.append(GlobalInstance { move(value), type.is_mutable() }); + return address; +} + +FunctionInstance* Store::get(FunctionAddress address) +{ + auto value = address.value(); + if (m_functions.size() <= value) + return nullptr; + return &m_functions[value]; +} + +TableInstance* Store::get(TableAddress address) +{ + auto value = address.value(); + if (m_tables.size() <= value) + return nullptr; + return &m_tables[value]; +} + +MemoryInstance* Store::get(MemoryAddress address) +{ + auto value = address.value(); + if (m_memories.size() <= value) + return nullptr; + return &m_memories[value]; +} + +GlobalInstance* Store::get(GlobalAddress address) +{ + auto value = address.value(); + if (m_globals.size() <= value) + return nullptr; + return &m_globals[value]; +} + +InstantiationResult AbstractMachine::instantiate(const Module& module, Vector<ExternValue> externs) +{ + Optional<InstantiationResult> instantiation_result; + + module.for_each_section_of_type<TypeSection>([&](const TypeSection& section) { + m_module_instance.types() = section.types(); + }); + + // TODO: Validate stuff + + Vector<Value> global_values; + ModuleInstance auxiliary_instance; + + // FIXME: Check that imports/extern match + + for (auto& entry : externs) { + if (auto* ptr = entry.get_pointer<GlobalAddress>()) + auxiliary_instance.globals().append(*ptr); + } + + module.for_each_section_of_type<GlobalSection>([&](auto& global_section) { + for (auto& entry : global_section.entries()) { + auto frame = make<Frame>( + auxiliary_instance, + Vector<Value> {}, + entry.expression(), + 1); + Configuration config { m_store }; + config.set_frame(move(frame)); + auto result = config.execute(); + // What if this traps? + if (result.is_trap()) + instantiation_result = InstantiationError { "Global value construction trapped" }; + else + global_values.append(result.values().first()); + } + }); + + if (auto result = allocate_all(module, externs, global_values); result.is_error()) { + return result.error(); + } + + module.for_each_section_of_type<ElementSection>([&](const ElementSection& element_section) { + auto frame = make<Frame>( + m_module_instance, + Vector<Value> {}, + element_section.function().offset(), + 1); + Configuration config { m_store }; + config.set_frame(move(frame)); + auto result = config.execute(); + // What if this traps? + VERIFY(!result.is_trap()); + size_t offset = 0; + result.values().first().value().visit( + [&](const auto& value) { offset = value; }, + [&](const FunctionAddress&) { VERIFY_NOT_REACHED(); }, + [&](const ExternAddress&) { VERIFY_NOT_REACHED(); }); + // FIXME: Module::get(*Index) + auto table_address = m_module_instance.tables().at(element_section.function().table().value()); + if (auto table_instance = m_store.get(table_address)) { + auto& init = element_section.function().init(); + for (size_t i = 0; i < init.size(); ++i) + table_instance->elements()[offset + i] = Reference { Reference::Func { init[i].value() } }; // HACK! + } + }); + + module.for_each_section_of_type<DataSection>([&](const DataSection& data_section) { + for (auto& segment : data_section.data()) { + segment.value().visit( + [&](const DataSection::Data::Active& data) { + auto frame = make<Frame>( + m_module_instance, + Vector<Value> {}, + data.offset, + 1); + Configuration config { m_store }; + config.set_frame(move(frame)); + auto result = config.execute(); + size_t offset = 0; + result.values().first().value().visit( + [&](const auto& value) { offset = value; }, + [&](const FunctionAddress&) { instantiation_result = InstantiationError { "Data segment offset returned an address" }; }, + [&](const ExternAddress&) { instantiation_result = InstantiationError { "Data segment offset returned an address" }; }); + if (instantiation_result.has_value() && instantiation_result->is_error()) + return; + if (m_module_instance.memories().size() <= data.index.value()) { + instantiation_result = InstantiationError { String::formatted("Data segment referenced out-of-bounds memory ({}) of max {} entries", data.index.value(), m_module_instance.memories().size()) }; + return; + } + auto address = m_module_instance.memories()[data.index.value()]; + if (auto instance = m_store.get(address)) { + if (instance->type().limits().max().value_or(data.init.size() + offset + 1) <= data.init.size() + offset) { + instantiation_result = InstantiationError { String::formatted("Data segment attempted to write to out-of-bounds memory ({}) of max {} bytes", data.init.size() + offset, instance->type().limits().max().value()) }; + return; + } + instance->grow(data.init.size() + offset - instance->size()); + instance->data().overwrite(offset, data.init.data(), data.init.size()); + } + }, + [&](const DataSection::Data::Passive&) { + // FIXME: What do we do here? + }); + } + }); + + module.for_each_section_of_type<StartSection>([&](auto&) { + instantiation_result = InstantiationError { "Start section not yet implemented" }; + // FIXME: Invoke the start function. + }); + + return instantiation_result.value_or({}); +} + +InstantiationResult AbstractMachine::allocate_all(const Module& module, Vector<ExternValue>& externs, Vector<Value>& global_values) +{ + Optional<InstantiationResult> result; + + for (auto& entry : externs) { + entry.visit( + [&](const FunctionAddress& address) { m_module_instance.functions().append(address); }, + [&](const TableAddress& address) { m_module_instance.tables().append(address); }, + [&](const MemoryAddress& address) { m_module_instance.memories().append(address); }, + [&](const GlobalAddress& address) { m_module_instance.globals().append(address); }); + } + + // FIXME: What if this fails? + + for (auto& func : module.functions()) { + auto address = m_store.allocate(m_module_instance, func); + VERIFY(address.has_value()); + m_module_instance.functions().append(*address); + } + + module.for_each_section_of_type<TableSection>([&](const TableSection& section) { + for (auto& table : section.tables()) { + auto table_address = m_store.allocate(table.type()); + VERIFY(table_address.has_value()); + m_module_instance.tables().append(*table_address); + } + }); + + module.for_each_section_of_type<MemorySection>([&](const MemorySection& section) { + for (auto& memory : section.memories()) { + auto memory_address = m_store.allocate(memory.type()); + VERIFY(memory_address.has_value()); + m_module_instance.memories().append(*memory_address); + } + }); + + module.for_each_section_of_type<GlobalSection>([&](const GlobalSection& section) { + size_t index = 0; + for (auto& entry : section.entries()) { + auto address = m_store.allocate(entry.type(), global_values[index]); + VERIFY(address.has_value()); + m_module_instance.globals().append(*address); + index++; + } + }); + + module.for_each_section_of_type<ExportSection>([&](const ExportSection& section) { + for (auto& entry : section.entries()) { + Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress, Empty> address { Empty {} }; + entry.description().visit( + [&](const FunctionIndex& index) { + if (m_module_instance.functions().size() > index.value()) + address = FunctionAddress { m_module_instance.functions()[index.value()] }; + else + dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.functions().size()); + }, + [&](const TableIndex& index) { + if (m_module_instance.tables().size() > index.value()) + address = TableAddress { m_module_instance.tables()[index.value()] }; + else + dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.tables().size()); + }, + [&](const MemoryIndex& index) { + if (m_module_instance.memories().size() > index.value()) + address = MemoryAddress { m_module_instance.memories()[index.value()] }; + else + dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.memories().size()); + }, + [&](const GlobalIndex& index) { + if (m_module_instance.globals().size() > index.value()) + address = GlobalAddress { m_module_instance.globals()[index.value()] }; + else + dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.globals().size()); + }); + + if (address.has<Empty>()) { + result = InstantiationError { "An export could not be resolved" }; + continue; + } + + m_module_instance.exports().append(ExportInstance { + entry.name(), + move(address).downcast<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>(), + }); + } + }); + + return result.value_or({}); +} + +Result AbstractMachine::invoke(FunctionAddress address, Vector<Value> arguments) +{ + return Configuration { m_store }.call(address, move(arguments)); +} + +} diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h new file mode 100644 index 0000000000..f97b747a1f --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/OwnPtr.h> +#include <AK/Result.h> +#include <LibWasm/Types.h> + +namespace Wasm { + +struct InstantiationError { + String error { "Unknown error" }; +}; +using InstantiationResult = Result<void, InstantiationError>; + +TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, FunctionAddress); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, ExternAddress); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, TableAddress); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, GlobalAddress); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, MemoryAddress); + +// FIXME: These should probably be made generic/virtual if/when we decide to do something more +// fancy than just a dumb interpreter. +class Value { +public: + using AnyValueType = Variant<i32, i64, float, double, FunctionAddress, ExternAddress>; + explicit Value(AnyValueType value) + : m_value(move(value)) + , m_type(ValueType::I32) + { + if (m_value.has<i32>()) + m_type = ValueType { ValueType::I32 }; + else if (m_value.has<i64>()) + m_type = ValueType { ValueType::I64 }; + else if (m_value.has<float>()) + m_type = ValueType { ValueType::F32 }; + else if (m_value.has<double>()) + m_type = ValueType { ValueType::F64 }; + else if (m_value.has<FunctionAddress>()) + m_type = ValueType { ValueType::FunctionReference }; + else if (m_value.has<ExternAddress>()) + m_type = ValueType { ValueType::ExternReference }; + else + VERIFY_NOT_REACHED(); + } + + template<typename T> + requires(sizeof(T) <= sizeof(u64)) explicit Value(ValueType type, T raw_value) + : m_value(0) + , m_type(type) + { + switch (type.kind()) { + case ValueType::Kind::ExternReference: + m_value = ExternAddress { bit_cast<u64>(raw_value) }; + break; + case ValueType::Kind::FunctionReference: + m_value = FunctionAddress { bit_cast<u64>(raw_value) }; + break; + case ValueType::Kind::I32: + m_value = static_cast<i32>(bit_cast<i64>(raw_value)); + break; + case ValueType::Kind::I64: + m_value = static_cast<i64>(bit_cast<u64>(raw_value)); + break; + case ValueType::Kind::F32: + m_value = static_cast<float>(bit_cast<double>(raw_value)); + break; + case ValueType::Kind::F64: + m_value = bit_cast<double>(raw_value); + break; + default: + VERIFY_NOT_REACHED(); + } + } + + Value(const Value& value) + : m_value(AnyValueType { value.m_value }) + , m_type(value.m_type) + { + } + + Value(Value&& value) + : m_value(move(value.m_value)) + , m_type(move(value.m_type)) + { + } + + auto& type() const { return m_type; } + auto& value() const { return m_value; } + +private: + AnyValueType m_value; + ValueType m_type; +}; + +struct Trap { + // Empty value type +}; + +class Result { +public: + explicit Result(Vector<Value> values) + : m_values(move(values)) + { + } + + Result(Trap) + : m_is_trap(true) + { + } + + auto& values() const { return m_values; } + auto is_trap() const { return m_is_trap; } + +private: + Vector<Value> m_values; + bool m_is_trap { false }; +}; + +using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>; + +class ExportInstance { +public: + explicit ExportInstance(String name, ExternValue value) + : m_name(move(name)) + , m_value(move(value)) + { + } + + auto& name() const { return m_name; } + auto& value() const { return m_value; } + +private: + String m_name; + ExternValue m_value; +}; + +class ModuleInstance { +public: + explicit ModuleInstance( + Vector<FunctionType> types, Vector<FunctionAddress> function_addresses, Vector<TableAddress> table_addresses, + Vector<MemoryAddress> memory_addresses, Vector<GlobalAddress> global_addresses, Vector<ExportInstance> exports) + : m_types(move(types)) + , m_functions(move(function_addresses)) + , m_tables(move(table_addresses)) + , m_memories(move(memory_addresses)) + , m_globals(move(global_addresses)) + , m_exports(move(exports)) + { + } + + ModuleInstance() = default; + + auto& types() const { return m_types; } + auto& functions() const { return m_functions; } + auto& tables() const { return m_tables; } + auto& memories() const { return m_memories; } + auto& globals() const { return m_globals; } + auto& exports() const { return m_exports; } + + auto& types() { return m_types; } + auto& functions() { return m_functions; } + auto& tables() { return m_tables; } + auto& memories() { return m_memories; } + auto& globals() { return m_globals; } + auto& exports() { return m_exports; } + +private: + Vector<FunctionType> m_types; + Vector<FunctionAddress> m_functions; + Vector<TableAddress> m_tables; + Vector<MemoryAddress> m_memories; + Vector<GlobalAddress> m_globals; + Vector<ExportInstance> m_exports; +}; + +class WasmFunction { +public: + explicit WasmFunction(const FunctionType& type, const ModuleInstance& module, const Module::Function& code) + : m_type(type) + , m_module(module) + , m_code(code) + { + } + + auto& type() const { return m_type; } + auto& module() const { return m_module; } + auto& code() const { return m_code; } + +private: + const FunctionType& m_type; + const ModuleInstance& m_module; + const Module::Function& m_code; +}; + +class HostFunction { +public: + explicit HostFunction(FlatPtr ptr, const FunctionType& type) + : m_ptr(ptr) + , m_type(type) + { + } + + auto ptr() const { return m_ptr; } + auto& type() const { return m_type; } + +private: + FlatPtr m_ptr { 0 }; + const FunctionType& m_type; +}; + +using FunctionInstance = Variant<WasmFunction, HostFunction>; + +class Reference { +public: + struct Null { + ValueType type; + }; + struct Func { + FunctionAddress address; + }; + struct Extern { + ExternAddress address; + }; + + using RefType = Variant<Null, Func, Extern>; + explicit Reference(RefType ref) + : m_ref(move(ref)) + { + } + + auto& ref() const { return m_ref; } + +private: + RefType m_ref; +}; + +class TableInstance { +public: + explicit TableInstance(const TableType& type, Vector<Optional<Reference>> elements) + : m_elements(move(elements)) + , m_type(type) + { + } + + auto& elements() const { return m_elements; } + auto& elements() { return m_elements; } + auto& type() const { return m_type; } + +private: + Vector<Optional<Reference>> m_elements; + const TableType& m_type; +}; + +class MemoryInstance { +public: + explicit MemoryInstance(const MemoryType& type) + : m_type(type) + { + grow(m_type.limits().min()); + } + + auto& type() const { return m_type; } + auto size() const { return m_size; } + auto& data() const { return m_data; } + auto& data() { return m_data; } + + void grow(size_t new_size) { m_data.grow(new_size); } + +private: + const MemoryType& m_type; + size_t m_size { 0 }; + ByteBuffer m_data; +}; + +class GlobalInstance { +public: + explicit GlobalInstance(Value value, bool is_mutable) + : m_mutable(is_mutable) + , m_value(move(value)) + { + } + + auto is_mutable() const { return m_mutable; } + auto& value() const { return m_value; } + +private: + bool m_mutable { false }; + Value m_value; +}; + +class Store { +public: + Store() = default; + + Optional<FunctionAddress> allocate(ModuleInstance& module, const Module::Function& function); + Optional<FunctionAddress> allocate(const HostFunction&); + Optional<TableAddress> allocate(const TableType&); + Optional<MemoryAddress> allocate(const MemoryType&); + Optional<GlobalAddress> allocate(const GlobalType&, Value); + + FunctionInstance* get(FunctionAddress); + TableInstance* get(TableAddress); + MemoryInstance* get(MemoryAddress); + GlobalInstance* get(GlobalAddress); + +private: + Vector<FunctionInstance> m_functions; + Vector<TableInstance> m_tables; + Vector<MemoryInstance> m_memories; + Vector<GlobalInstance> m_globals; +}; + +class Label { +public: + explicit Label(InstructionPointer continuation) + : m_continuation(continuation) + { + } + + auto continuation() const { return m_continuation; } + +private: + InstructionPointer m_continuation; +}; + +class Frame { + AK_MAKE_NONCOPYABLE(Frame); + +public: + explicit Frame(const ModuleInstance& module, Vector<Value> locals, const Expression& expression, size_t arity) + : m_module(module) + , m_locals(move(locals)) + , m_expression(expression) + , m_arity(arity) + { + } + + auto& module() const { return m_module; } + auto& locals() const { return m_locals; } + auto& expression() const { return m_expression; } + auto arity() const { return m_arity; } + +private: + const ModuleInstance& m_module; + Vector<Value> m_locals; + const Expression& m_expression; + size_t m_arity { 0 }; +}; + +class Stack { +public: + using EntryType = Variant<NonnullOwnPtr<Value>, NonnullOwnPtr<Label>, NonnullOwnPtr<Frame>>; + Stack() = default; + + [[nodiscard]] bool is_empty() const { return m_data.is_empty(); } + void push(EntryType entry) { m_data.append(move(entry)); } + auto pop() { return m_data.take_last(); } + auto& last() { return m_data.last(); } + + auto size() const { return m_data.size(); } + auto& entries() const { return m_data; } + +private: + Vector<EntryType> m_data; +}; + +class AbstractMachine { +public: + explicit AbstractMachine() = default; + + // Load and instantiate a module, and link it into this interpreter. + InstantiationResult instantiate(const Module&, Vector<ExternValue>); + Result invoke(FunctionAddress, Vector<Value>); + + auto& module_instance() const { return m_module_instance; } + auto& module_instance() { return m_module_instance; } + auto& store() const { return m_store; } + auto& store() { return m_store; } + +private: + InstantiationResult allocate_all(const Module&, Vector<ExternValue>&, Vector<Value>& global_values); + ModuleInstance m_module_instance; + Store m_store; +}; + +} diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp new file mode 100644 index 0000000000..6666f8598f --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWasm/AbstractMachine/Configuration.h> +#include <LibWasm/AbstractMachine/Interpreter.h> + +namespace Wasm { + +Optional<Label> Configuration::nth_label(size_t i) +{ + for (auto& entry : m_stack.entries()) { + if (auto ptr = entry.get_pointer<NonnullOwnPtr<Label>>()) { + if (i == 0) + return **ptr; + --i; + } + } + return {}; +} + +Result Configuration::call(FunctionAddress address, Vector<Value> arguments) +{ + auto* function = m_store.get(address); + if (!function) + return Trap {}; + if (auto* wasm_function = function->get_pointer<WasmFunction>()) { + Vector<Value> locals; + locals.ensure_capacity(arguments.size() + wasm_function->code().locals().size()); + for (auto& value : arguments) + locals.append(Value { value }); + for (auto& type : wasm_function->code().locals()) + locals.empend(type, 0ull); + + auto frame = make<Frame>( + wasm_function->module(), + move(locals), + wasm_function->code().body(), + wasm_function->type().results().size()); + + set_frame(move(frame)); + return execute(); + } + + // It better be a host function, else something is really wrong. + auto& host_function = function->get<HostFunction>(); + auto result = bit_cast<HostFunctionType>(host_function.ptr())(m_store, arguments); + auto count = host_function.type().results().size(); + if (count == 0) + return Result { Vector<Value> {} }; + if (count == 1) + return Result { Vector<Value> { Value { host_function.type().results().first(), result } } }; + TODO(); +} + +Result Configuration::execute() +{ + Interpreter interpreter; + interpreter.interpret(*this); + + Vector<NonnullOwnPtr<Value>> results; + for (size_t i = 0; i < m_current_frame->arity(); ++i) + results.append(move(stack().pop().get<NonnullOwnPtr<Value>>())); + auto label = stack().pop(); + // ASSERT: label == current frame + if (!label.has<NonnullOwnPtr<Label>>()) + return Trap {}; + Vector<Value> results_moved; + results_moved.ensure_capacity(results.size()); + for (auto& entry : results) + results_moved.unchecked_append(move(*entry)); + return Result { move(results_moved) }; +} + +} diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.h b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.h new file mode 100644 index 0000000000..c8a944dffc --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWasm/AbstractMachine/AbstractMachine.h> + +namespace Wasm { + +typedef u64 (*HostFunctionType)(Store&, Vector<Value>&); + +class Configuration { +public: + explicit Configuration(Store& store) + : m_store(store) + { + } + + Optional<Label> nth_label(size_t); + void set_frame(NonnullOwnPtr<Frame> frame) + { + m_current_frame = frame.ptr(); + m_stack.push(move(frame)); + m_stack.push(make<Label>(m_current_frame->expression().instructions().size() - 1)); + } + auto& frame() const { return m_current_frame; } + auto& frame() { return m_current_frame; } + auto& ip() const { return m_ip; } + auto& ip() { return m_ip; } + auto& depth() const { return m_depth; } + auto& depth() { return m_depth; } + auto& stack() const { return m_stack; } + auto& stack() { return m_stack; } + + Result call(FunctionAddress, Vector<Value> arguments); + Result execute(); + +private: + Store& m_store; + Frame* m_current_frame { nullptr }; + Stack m_stack; + size_t m_depth { 0 }; + InstructionPointer m_ip; +}; + +} diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp new file mode 100644 index 0000000000..da4148b2e7 --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibWasm/AbstractMachine/AbstractMachine.h> +#include <LibWasm/AbstractMachine/Configuration.h> +#include <LibWasm/AbstractMachine/Interpreter.h> +#include <LibWasm/Opcode.h> + +namespace Wasm { + +void Interpreter::interpret(Configuration& configuration) +{ + // FIXME: Interpret stuff + dbgln("FIXME: Interpret stuff!"); + // Push some dummy values + for (size_t i = 0; i < configuration.frame()->arity(); ++i) + configuration.stack().push(make<Value>(0)); +} + +} diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h new file mode 100644 index 0000000000..2c3426fc84 --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibWasm/AbstractMachine/Configuration.h> + +namespace Wasm { + +struct Interpreter { + void interpret(Configuration&); +}; + +} diff --git a/Userland/Libraries/LibWasm/CMakeLists.txt b/Userland/Libraries/LibWasm/CMakeLists.txt index 0c2ef0754f..ae57176a39 100644 --- a/Userland/Libraries/LibWasm/CMakeLists.txt +++ b/Userland/Libraries/LibWasm/CMakeLists.txt @@ -1,4 +1,7 @@ set(SOURCES + AbstractMachine/AbstractMachine.cpp + AbstractMachine/Configuration.cpp + AbstractMachine/Interpreter.cpp Parser/Parser.cpp Printer/Printer.cpp ) diff --git a/Userland/Utilities/wasm.cpp b/Userland/Utilities/wasm.cpp index f583741aee..2f8516ec82 100644 --- a/Userland/Utilities/wasm.cpp +++ b/Userland/Utilities/wasm.cpp @@ -7,6 +7,7 @@ #include <LibCore/ArgsParser.h> #include <LibCore/File.h> #include <LibCore/FileStream.h> +#include <LibWasm/AbstractMachine/AbstractMachine.h> #include <LibWasm/Printer/Printer.h> #include <LibWasm/Types.h> @@ -14,12 +15,19 @@ int main(int argc, char* argv[]) { const char* filename = nullptr; bool print = false; + bool attempt_instantiate = false; + bool attemp_execute = false; Core::ArgsParser parser; parser.add_positional_argument(filename, "File name to parse", "file"); parser.add_option(print, "Print the parsed module", "print", 'p'); + parser.add_option(attempt_instantiate, "Attempt to instantiate the module", "instantiate", 'i'); + parser.add_option(attemp_execute, "Attempt to execute a function from the module (implies -i)", "execute", 'e'); parser.parse(argc, argv); + if (attemp_execute) + attempt_instantiate = true; + auto result = Core::File::open(filename, Core::OpenMode::ReadOnly); if (result.is_error()) { warnln("Failed to open {}: {}", filename, result.error()); @@ -34,11 +42,85 @@ int main(int argc, char* argv[]) return 2; } - if (print) { + if (print && !attempt_instantiate) { auto out_stream = Core::OutputFileStream::standard_output(); Wasm::Printer printer(out_stream); printer.print(parse_result.value()); } + if (attempt_instantiate) { + Wasm::AbstractMachine machine; + auto result = machine.instantiate(parse_result.value(), {}); + if (result.is_error()) { + warnln("Module instantiation failed: {}", result.error().error); + return 1; + } + + auto stream = Core::OutputFileStream::standard_output(); + auto print_func = [&](const auto& address) { + Wasm::FunctionInstance* fn = machine.store().get(address); + stream.write(String::formatted("- Function with address {}, ptr = {}\n", address.value(), fn).bytes()); + if (fn) { + stream.write(String::formatted(" wasm function? {}\n", fn->has<Wasm::WasmFunction>()).bytes()); + fn->visit( + [&](const Wasm::WasmFunction& func) { + Wasm::Printer printer { stream, 3 }; + stream.write(" type:\n"sv.bytes()); + printer.print(func.type()); + stream.write(" code:\n"sv.bytes()); + printer.print(func.code()); + }, + [](const Wasm::HostFunction&) {}); + } + }; + if (print) { + // Now, let's dump the functions! + for (auto& address : machine.module_instance().functions()) { + print_func(address); + } + } + + if (attemp_execute) { + Optional<Wasm::FunctionAddress> run_address; + Vector<Wasm::Value> values; + // Pick a function that takes no args :P + for (auto& address : machine.module_instance().functions()) { + auto fn = machine.store().get(address); + if (!fn) + continue; + if (auto ptr = fn->get_pointer<Wasm::WasmFunction>()) { + const Wasm::FunctionType& ty = ptr->type(); + for (auto& param : ty.parameters()) { + values.append(Wasm::Value { param, 0ull }); + } + run_address = address; + break; + } + } + if (!run_address.has_value()) { + warnln("No nullary function, sorry :("); + return 1; + } + outln("Executing "); + print_func(*run_address); + outln(); + + auto result = machine.invoke(run_address.value(), move(values)); + if (!result.values().is_empty()) + warnln("Returned:"); + for (auto& value : result.values()) { + value.value().visit( + [&](const auto& value) { + if constexpr (requires { value.value(); }) + out(" -> addr{} ", value.value()); + else + out(" -> {} ", value); + }); + Wasm::Printer printer { stream }; + printer.print(value.type()); + } + } + } + return 0; } |