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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Wasm { + +Optional 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 Store::allocate(const HostFunction& function) +{ + FunctionAddress address { m_functions.size() }; + m_functions.empend(HostFunction { function }); + return address; +} + +Optional Store::allocate(const TableType& type) +{ + TableAddress address { m_tables.size() }; + Vector> elements; + elements.resize(type.limits().min()); + m_tables.empend(TableInstance { type, move(elements) }); + return address; +} + +Optional Store::allocate(const MemoryType& type) +{ + MemoryAddress address { m_memories.size() }; + m_memories.empend(MemoryInstance { type }); + return address; +} + +Optional 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 externs) +{ + Optional instantiation_result; + + module.for_each_section_of_type([&](const TypeSection& section) { + m_module_instance.types() = section.types(); + }); + + // TODO: Validate stuff + + Vector global_values; + ModuleInstance auxiliary_instance; + + // FIXME: Check that imports/extern match + + for (auto& entry : externs) { + if (auto* ptr = entry.get_pointer()) + auxiliary_instance.globals().append(*ptr); + } + + module.for_each_section_of_type([&](auto& global_section) { + for (auto& entry : global_section.entries()) { + auto frame = make( + auxiliary_instance, + Vector {}, + 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([&](const ElementSection& element_section) { + auto frame = make( + m_module_instance, + Vector {}, + 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([&](const DataSection& data_section) { + for (auto& segment : data_section.data()) { + segment.value().visit( + [&](const DataSection::Data::Active& data) { + auto frame = make( + m_module_instance, + Vector {}, + 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([&](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& externs, Vector& global_values) +{ + Optional 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([&](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([&](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([&](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([&](const ExportSection& section) { + for (auto& entry : section.entries()) { + Variant 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()) { + result = InstantiationError { "An export could not be resolved" }; + continue; + } + + m_module_instance.exports().append(ExportInstance { + entry.name(), + move(address).downcast(), + }); + } + }); + + return result.value_or({}); +} + +Result AbstractMachine::invoke(FunctionAddress address, Vector 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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Wasm { + +struct InstantiationError { + String error { "Unknown error" }; +}; +using InstantiationResult = Result; + +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; + explicit Value(AnyValueType value) + : m_value(move(value)) + , m_type(ValueType::I32) + { + if (m_value.has()) + m_type = ValueType { ValueType::I32 }; + else if (m_value.has()) + m_type = ValueType { ValueType::I64 }; + else if (m_value.has()) + m_type = ValueType { ValueType::F32 }; + else if (m_value.has()) + m_type = ValueType { ValueType::F64 }; + else if (m_value.has()) + m_type = ValueType { ValueType::FunctionReference }; + else if (m_value.has()) + m_type = ValueType { ValueType::ExternReference }; + else + VERIFY_NOT_REACHED(); + } + + template + 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(raw_value) }; + break; + case ValueType::Kind::FunctionReference: + m_value = FunctionAddress { bit_cast(raw_value) }; + break; + case ValueType::Kind::I32: + m_value = static_cast(bit_cast(raw_value)); + break; + case ValueType::Kind::I64: + m_value = static_cast(bit_cast(raw_value)); + break; + case ValueType::Kind::F32: + m_value = static_cast(bit_cast(raw_value)); + break; + case ValueType::Kind::F64: + m_value = bit_cast(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 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 m_values; + bool m_is_trap { false }; +}; + +using ExternValue = Variant; + +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 types, Vector function_addresses, Vector table_addresses, + Vector memory_addresses, Vector global_addresses, Vector 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 m_types; + Vector m_functions; + Vector m_tables; + Vector m_memories; + Vector m_globals; + Vector 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; + +class Reference { +public: + struct Null { + ValueType type; + }; + struct Func { + FunctionAddress address; + }; + struct Extern { + ExternAddress address; + }; + + using RefType = Variant; + 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> 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> 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 allocate(ModuleInstance& module, const Module::Function& function); + Optional allocate(const HostFunction&); + Optional allocate(const TableType&); + Optional allocate(const MemoryType&); + Optional allocate(const GlobalType&, Value); + + FunctionInstance* get(FunctionAddress); + TableInstance* get(TableAddress); + MemoryInstance* get(MemoryAddress); + GlobalInstance* get(GlobalAddress); + +private: + Vector m_functions; + Vector m_tables; + Vector m_memories; + Vector 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 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 m_locals; + const Expression& m_expression; + size_t m_arity { 0 }; +}; + +class Stack { +public: + using EntryType = Variant, NonnullOwnPtr