From be62e4d1d7a8c5e58f864dd893dc6042c96e330f Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Fri, 4 Jun 2021 03:30:09 +0430 Subject: [PATCH] LibWasm: Load and instantiate tables This commit is a fairly large refactor, mainly because it unified the two different ways that existed to represent references. Now Reference values are also a kind of value. It also implements a printer for values/references instead of copying the implementation everywhere. --- Tests/LibWasm/test-wasm.cpp | 16 +- .../AbstractMachine/AbstractMachine.cpp | 164 ++++++++++++++++-- .../LibWasm/AbstractMachine/AbstractMachine.h | 120 +++++++------ .../LibWasm/AbstractMachine/Configuration.cpp | 32 ++-- .../LibWasm/AbstractMachine/Interpreter.cpp | 19 +- Userland/Libraries/LibWasm/Parser/Parser.cpp | 28 +-- .../Libraries/LibWasm/Printer/Printer.cpp | 102 +++++++---- Userland/Libraries/LibWasm/Printer/Printer.h | 13 +- Userland/Libraries/LibWasm/Types.h | 23 +-- Userland/Utilities/wasm.cpp | 25 +-- 10 files changed, 350 insertions(+), 192 deletions(-) diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp index d1143bf1c62..086203251b9 100644 --- a/Tests/LibWasm/test-wasm.cpp +++ b/Tests/LibWasm/test-wasm.cpp @@ -185,16 +185,16 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) arguments.append(Wasm::Value(static_cast(value))); break; case Wasm::ValueType::Kind::FunctionReference: - arguments.append(Wasm::Value(Wasm::FunctionAddress { static_cast(value) })); + arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast(value) } })); break; case Wasm::ValueType::Kind::ExternReference: - arguments.append(Wasm::Value(Wasm::ExternAddress { static_cast(value) })); + arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast(value) } })); break; case Wasm::ValueType::Kind::NullFunctionReference: - arguments.append(Wasm::Value(Wasm::Value::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) })); + arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } })); break; case Wasm::ValueType::Kind::NullExternReference: - arguments.append(Wasm::Value(Wasm::Value::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExternReference) })); + arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExternReference) } })); break; } } @@ -211,8 +211,10 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) JS::Value return_value; result.values().first().value().visit( [&](const auto& value) { return_value = JS::Value(static_cast(value)); }, - [&](const Wasm::FunctionAddress& index) { return_value = JS::Value(static_cast(index.value())); }, - [&](const Wasm::ExternAddress& index) { return_value = JS::Value(static_cast(index.value())); }, - [&](const Wasm::Value::Null&) { return_value = JS::js_null(); }); + [&](const Wasm::Reference& reference) { + reference.ref().visit( + [&](const Wasm::Reference::Null&) { return_value = JS::js_null(); }, + [&](const auto& ref) { return_value = JS::Value(static_cast(ref.address.value())); }); + }); return return_value; } diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 592c90223cb..34d53b1c54b 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -52,6 +52,13 @@ Optional Store::allocate(const GlobalType& type, Value value) return address; } +Optional Store::allocate(const ValueType& type, Vector references) +{ + ElementAddress address { m_elements.size() }; + m_elements.append(ElementInstance { type, move(references) }); + return address; +} + FunctionInstance* Store::get(FunctionAddress address) { auto value = address.value(); @@ -84,6 +91,14 @@ GlobalInstance* Store::get(GlobalAddress address) return &m_globals[value]; } +ElementInstance* Store::get(ElementAddress address) +{ + auto value = address.value(); + if (m_elements.size() <= value) + return nullptr; + return &m_elements[value]; +} + InstantiationResult AbstractMachine::instantiate(const Module& module, Vector externs) { auto main_module_instance_pointer = make(); @@ -97,6 +112,7 @@ InstantiationResult AbstractMachine::instantiate(const Module& module, Vector global_values; + Vector> elements; ModuleInstance auxiliary_instance; // FIXME: Check that imports/extern match @@ -118,7 +134,6 @@ InstantiationResult AbstractMachine::instantiate(const Module& module, Vector([&](const ElementSection&) { - // FIXME: Implement me - // https://webassembly.github.io/spec/core/bikeshed/#element-segments%E2%91%A0 - // https://webassembly.github.io/spec/core/bikeshed/#instantiation%E2%91%A1 step 9 + if (auto result = allocate_all_initial_phase(module, main_module_instance, externs, global_values); result.has_value()) + return result.release_value(); + + module.for_each_section_of_type([&](const ElementSection& section) { + for (auto& segment : section.segments()) { + Vector references; + for (auto& entry : segment.init) { + Configuration config { m_store }; + config.set_frame(Frame { + main_module_instance, + Vector {}, + entry, + entry.instructions().size(), + }); + auto result = config.execute(interpreter); + if (result.is_trap()) { + instantiation_result = InstantiationError { "Element construction trapped" }; + return IterationDecision::Continue; + } + + for (auto& value : result.values()) { + if (!value.type().is_reference()) { + instantiation_result = InstantiationError { "Evaluated element entry is not a reference" }; + return IterationDecision::Continue; + } + auto reference = value.to(); + if (!reference.has_value()) { + instantiation_result = InstantiationError { "Evaluated element entry does not contain a reference" }; + return IterationDecision::Continue; + } + // FIXME: type-check the reference. + references.prepend(reference.release_value()); + } + } + elements.append(move(references)); + } + + return IterationDecision::Continue; }); + if (instantiation_result.has_value()) + return instantiation_result.release_value(); + + if (auto result = allocate_all_final_phase(module, main_module_instance, elements); result.has_value()) + return result.release_value(); + + module.for_each_section_of_type([&](const ElementSection& section) { + size_t index = 0; + for (auto& segment : section.segments()) { + auto current_index = index; + ++index; + auto active_ptr = segment.mode.get_pointer(); + if (!active_ptr) + continue; + if (active_ptr->index.value() != 0) { + instantiation_result = InstantiationError { "Non-zero table referenced by active element segment" }; + return IterationDecision::Break; + } + Configuration config { m_store }; + config.set_frame(Frame { + main_module_instance, + Vector {}, + active_ptr->expression, + 1, + }); + auto result = config.execute(interpreter); + if (result.is_trap()) { + instantiation_result = InstantiationError { "Element section initialisation trapped" }; + return IterationDecision::Break; + } + auto d = result.values().first().to(); + if (!d.has_value()) { + instantiation_result = InstantiationError { "Element section initialisation returned invalid table initial offset" }; + return IterationDecision::Break; + } + if (main_module_instance.tables().size() < 1) { + instantiation_result = InstantiationError { "Element section initialisation references nonexistent table" }; + return IterationDecision::Break; + } + auto table_instance = m_store.get(main_module_instance.tables()[0]); + if (current_index >= main_module_instance.elements().size()) { + instantiation_result = InstantiationError { "Invalid element referenced by active element segment" }; + return IterationDecision::Break; + } + auto elem_instance = m_store.get(main_module_instance.elements()[current_index]); + if (!table_instance || !elem_instance) { + instantiation_result = InstantiationError { "Invalid element referenced by active element segment" }; + return IterationDecision::Break; + } + + auto total_required_size = elem_instance->references().size() + d.value(); + + if (table_instance->type().limits().max().value_or(total_required_size) < total_required_size) { + instantiation_result = InstantiationError { "Table limit overflow in active element segment" }; + return IterationDecision::Break; + } + + if (table_instance->elements().size() < total_required_size) + table_instance->elements().resize(total_required_size); + + size_t i = 0; + for (auto it = elem_instance->references().begin(); it < elem_instance->references().end(); ++i, ++it) { + table_instance->elements()[i + d.value()] = *it; + } + } + + return IterationDecision::Continue; + }); + + if (instantiation_result.has_value()) + return instantiation_result.release_value(); + module.for_each_section_of_type([&](const DataSection& data_section) { for (auto& segment : data_section.data()) { segment.value().visit( @@ -148,12 +268,14 @@ InstantiationResult AbstractMachine::instantiate(const Module& module, Vectoris_error()) return; if (main_module_instance.memories().size() <= data.index.value()) { @@ -193,7 +315,7 @@ InstantiationResult AbstractMachine::instantiate(const Module& module, Vector AbstractMachine::allocate_all(const Module& module, ModuleInstance& module_instance, Vector& externs, Vector& global_values) +Optional AbstractMachine::allocate_all_initial_phase(const Module& module, ModuleInstance& module_instance, Vector& externs, Vector& global_values) { Optional result; @@ -232,13 +354,12 @@ Optional AbstractMachine::allocate_all(const Module& module, 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]); + auto address = m_store.allocate(entry.type(), move(global_values[index])); VERIFY(address.has_value()); module_instance.globals().append(*address); index++; } }); - module.for_each_section_of_type([&](const ExportSection& section) { for (auto& entry : section.entries()) { Variant address { Empty {} }; @@ -283,6 +404,21 @@ Optional AbstractMachine::allocate_all(const Module& module, return result; } +Optional AbstractMachine::allocate_all_final_phase(const Module& module, ModuleInstance& module_instance, Vector>& elements) +{ + module.for_each_section_of_type([&](const ElementSection& section) { + size_t index = 0; + for (auto& segment : section.segments()) { + auto address = m_store.allocate(segment.type, move(elements[index])); + VERIFY(address.has_value()); + module_instance.elements().append(*address); + index++; + } + }); + + return {}; +} + Result AbstractMachine::invoke(FunctionAddress address, Vector arguments) { BytecodeInterpreter interpreter; diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index b7b49b664b2..ee3edd66cdb 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -33,10 +33,35 @@ TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, Fun 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, ElementAddress); 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 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 Value { public: Value() @@ -45,11 +70,7 @@ public: { } - struct Null { - ValueType type; - }; - - using AnyValueType = Variant; + using AnyValueType = Variant; explicit Value(AnyValueType value) : m_value(move(value)) , m_type(ValueType::I32) @@ -62,12 +83,12 @@ public: m_type = ValueType { ValueType::F32 }; else if (m_value.has()) m_type = ValueType { ValueType::F64 }; - else if (m_value.has()) + else if (m_value.has() && m_value.get().ref().has()) m_type = ValueType { ValueType::FunctionReference }; - else if (m_value.has()) + else if (m_value.has() && m_value.get().ref().has()) m_type = ValueType { ValueType::ExternReference }; - else if (m_value.has()) - m_type = ValueType { m_value.get().type.kind() == ValueType::ExternReference ? ValueType::NullExternReference : ValueType::NullFunctionReference }; + else if (m_value.has()) + m_type = m_value.get().ref().get().type; else VERIFY_NOT_REACHED(); } @@ -79,10 +100,10 @@ public: { switch (type.kind()) { case ValueType::Kind::ExternReference: - m_value = ExternAddress { bit_cast(raw_value) }; + m_value = Reference { Reference::Extern { { bit_cast(raw_value) } } }; break; case ValueType::Kind::FunctionReference: - m_value = FunctionAddress { bit_cast(raw_value) }; + m_value = Reference { Reference::Func { { bit_cast(raw_value) } } }; break; case ValueType::Kind::I32: m_value = static_cast(bit_cast(raw_value)); @@ -98,11 +119,11 @@ public: break; case ValueType::Kind::NullFunctionReference: VERIFY(raw_value == 0); - m_value = Null { ValueType(ValueType::Kind::FunctionReference) }; + m_value = Reference { Reference::Null { ValueType(ValueType::Kind::FunctionReference) } }; break; case ValueType::Kind::NullExternReference: VERIFY(raw_value == 0); - m_value = Null { ValueType(ValueType::Kind::ExternReference) }; + m_value = Reference { Reference::Null { ValueType(ValueType::Kind::ExternReference) } }; break; default: VERIFY_NOT_REACHED(); @@ -146,17 +167,19 @@ public: else if constexpr (!IsFloatingPoint && IsSame>) result = value; }, - [&](const FunctionAddress& address) { - if constexpr (IsSame) - result = address; - }, - [&](const ExternAddress& address) { - if constexpr (IsSame) - result = address; - }, - [&](const Null& null) { - if constexpr (IsSame) - result = null; + [&](const Reference& value) { + if constexpr (IsSame) { + result = value; + } else if constexpr (IsSame) { + if (auto ptr = value.ref().template get_pointer()) + result = *ptr; + } else if constexpr (IsSame) { + if (auto ptr = value.ref().template get_pointer()) + result = *ptr; + } else if constexpr (IsSame) { + if (auto ptr = value.ref().template get_pointer()) + result = *ptr; + } }); return result; } @@ -233,6 +256,7 @@ public: auto& tables() const { return m_tables; } auto& memories() const { return m_memories; } auto& globals() const { return m_globals; } + auto& elements() const { return m_elements; } auto& exports() const { return m_exports; } auto& types() { return m_types; } @@ -240,6 +264,7 @@ public: auto& tables() { return m_tables; } auto& memories() { return m_memories; } auto& globals() { return m_globals; } + auto& elements() { return m_elements; } auto& exports() { return m_exports; } private: @@ -248,6 +273,7 @@ private: Vector m_tables; Vector m_memories; Vector m_globals; + Vector m_elements; Vector m_exports; }; @@ -288,30 +314,6 @@ private: 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) @@ -384,6 +386,22 @@ private: Value m_value; }; +class ElementInstance { +public: + explicit ElementInstance(ValueType type, Vector references) + : m_type(move(type)) + , m_references(move(references)) + { + } + + auto& type() const { return m_type; } + auto& references() const { return m_references; } + +private: + ValueType m_type; + Vector m_references; +}; + class Store { public: Store() = default; @@ -393,17 +411,20 @@ public: Optional allocate(const TableType&); Optional allocate(const MemoryType&); Optional allocate(const GlobalType&, Value); + Optional allocate(const ValueType&, Vector); FunctionInstance* get(FunctionAddress); TableInstance* get(TableAddress); MemoryInstance* get(MemoryAddress); GlobalInstance* get(GlobalAddress); + ElementInstance* get(ElementAddress); private: Vector m_functions; Vector m_tables; Vector m_memories; Vector m_globals; + Vector m_elements; }; class Label { @@ -479,7 +500,8 @@ public: auto& store() { return m_store; } private: - Optional allocate_all(const Module&, ModuleInstance&, Vector&, Vector& global_values); + Optional allocate_all_initial_phase(const Module&, ModuleInstance&, Vector&, Vector& global_values); + Optional allocate_all_final_phase(const Module&, ModuleInstance&, Vector>& elements); Store m_store; }; diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp index ac93f775074..9823f2093e8 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace Wasm { @@ -24,6 +25,9 @@ Optional