From 7d1142e2c8cbf03d786ec89cdcc37771a5c9fbce Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 1 Nov 2021 01:36:35 +0330 Subject: [PATCH] LibWasm: Implement module validation --- AK/Debug.h.in | 4 + Meta/CMake/all_the_debug_macros.cmake | 1 + .../AbstractMachine/AbstractMachine.cpp | 24 +- .../LibWasm/AbstractMachine/AbstractMachine.h | 3 + .../LibWasm/AbstractMachine/Validator.cpp | 2737 +++++++++++++++++ .../LibWasm/AbstractMachine/Validator.h | 312 ++ Userland/Libraries/LibWasm/CMakeLists.txt | 1 + Userland/Libraries/LibWasm/Forward.h | 15 + Userland/Libraries/LibWasm/Types.h | 18 + 9 files changed, 3113 insertions(+), 2 deletions(-) create mode 100644 Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp create mode 100644 Userland/Libraries/LibWasm/AbstractMachine/Validator.h create mode 100644 Userland/Libraries/LibWasm/Forward.h diff --git a/AK/Debug.h.in b/AK/Debug.h.in index bd51a04e95..85296bc015 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -442,6 +442,10 @@ #cmakedefine01 WASM_TRACE_DEBUG #endif +#ifndef WASM_VALIDATOR_DEBUG +#cmakedefine01 WASM_VALIDATOR_DEBUG +#endif + #ifndef WEBSERVER_DEBUG #cmakedefine01 WEBSERVER_DEBUG #endif diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index b368759a2c..10350ef254 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -194,6 +194,7 @@ set(WAITBLOCK_DEBUG ON) set(WAITQUEUE_DEBUG ON) set(WASM_BINPARSER_DEBUG ON) set(WASM_TRACE_DEBUG ON) +set(WASM_VALIDATOR_DEBUG ON) set(WEBSERVER_DEBUG ON) set(WINDOWMANAGER_DEBUG ON) set(WSMESSAGELOOP_DEBUG ON) diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 84bf622114..90789d8c09 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace Wasm { @@ -100,8 +101,29 @@ ElementInstance* Store::get(ElementAddress address) return &m_elements[value]; } +ErrorOr AbstractMachine::validate(Module& module) +{ + if (module.validation_status() != Module::ValidationStatus::Unchecked) { + if (module.validation_status() == Module::ValidationStatus::Valid) + return {}; + + return ValidationError { module.validation_error() }; + } + + auto result = Validator {}.validate(module); + if (result.is_error()) { + module.set_validation_error(result.error().error_string); + return result.release_error(); + } + + return {}; +} + InstantiationResult AbstractMachine::instantiate(Module const& module, Vector externs) { + if (auto result = validate(const_cast(module)); result.is_error()) + return InstantiationError { String::formatted("Validation failed: {}", result.error()) }; + auto main_module_instance_pointer = make(); auto& main_module_instance = *main_module_instance_pointer; Optional instantiation_result; @@ -110,8 +132,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector global_values; Vector> elements; ModuleInstance auxiliary_instance; diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index aeec26d1f6..381f7e4e21 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -373,6 +373,7 @@ public: auto is_mutable() const { return m_mutable; } auto& value() const { return m_value; } + GlobalType type() const { return { m_value.type(), is_mutable() }; } void set_value(Value value) { VERIFY(is_mutable()); @@ -489,6 +490,8 @@ class AbstractMachine { public: explicit AbstractMachine() = default; + // Validate a module; permanently sets the module's validity status. + ErrorOr validate(Module&); // Load and instantiate a module, and link it into this interpreter. InstantiationResult instantiate(Module const&, Vector); Result invoke(FunctionAddress, Vector); diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp new file mode 100644 index 0000000000..e3a280d4fc --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -0,0 +1,2737 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Wasm { + +ErrorOr Validator::validate(Module& module) +{ + ErrorOr result {}; + + // Note: The spec performs this after populating the context, but there's no real reason to do so, + // as this has no dependency. + HashTable seen_export_names; + module.for_each_section_of_type([&result, &seen_export_names](ExportSection const& section) { + if (result.is_error()) + return; + for (auto& export_ : section.entries()) { + if (seen_export_names.try_set(export_.name()) != AK::HashSetResult::InsertedNewEntry) + result = Errors::duplicate_export_name(export_.name()); + return; + } + }); + if (result.is_error()) { + module.set_validation_status(Module::ValidationStatus::Invalid, {}); + return result; + } + + m_context = {}; + + module.for_each_section_of_type([this](TypeSection const& section) { + m_context.types = section.types(); + }); + + module.for_each_section_of_type([&](ImportSection const& section) { + for (auto& import_ : section.imports()) { + import_.description().visit( + [this, &result](TypeIndex const& index) { + if (m_context.types.size() > index.value()) + m_context.functions.append(m_context.types[index.value()]); + else + result = Errors::invalid("TypeIndex"sv); + m_context.imported_function_count++; + }, + [this](FunctionType const& type) { + m_context.functions.append(type); + m_context.imported_function_count++; + }, + [this](TableType const& type) { m_context.tables.append(type); }, + [this](MemoryType const& type) { m_context.memories.append(type); }, + [this](GlobalType const& type) { m_context.globals.append(type); }); + } + }); + + if (result.is_error()) { + module.set_validation_status(Module::ValidationStatus::Invalid, {}); + return result; + } + + module.for_each_section_of_type([this, &result](FunctionSection const& section) { + if (result.is_error()) + return; + m_context.functions.ensure_capacity(section.types().size() + m_context.functions.size()); + for (auto& index : section.types()) { + if (m_context.types.size() > index.value()) { + m_context.functions.append(m_context.types[index.value()]); + } else { + result = Errors::invalid("TypeIndex"); + break; + } + } + }); + if (result.is_error()) { + module.set_validation_status(Module::ValidationStatus::Invalid, {}); + return result; + } + + module.for_each_section_of_type([this](TableSection const& section) { + m_context.tables.ensure_capacity(m_context.tables.size() + section.tables().size()); + for (auto& table : section.tables()) + m_context.tables.unchecked_append(table.type()); + }); + module.for_each_section_of_type([this](MemorySection const& section) { + m_context.memories.ensure_capacity(m_context.memories.size() + section.memories().size()); + for (auto& memory : section.memories()) + m_context.memories.unchecked_append(memory.type()); + }); + module.for_each_section_of_type([this](GlobalSection const& section) { + m_context.globals.ensure_capacity(m_context.globals.size() + section.entries().size()); + for (auto& global : section.entries()) + m_context.globals.unchecked_append(global.type()); + }); + module.for_each_section_of_type([this](ElementSection const& section) { + m_context.elements.ensure_capacity(section.segments().size()); + for (auto& segment : section.segments()) + m_context.elements.unchecked_append(segment.type); + }); + + // FIXME: C.refs is the set funcidx(module with funcs=ϵ with start=ϵ), + // i.e., the set of function indices occurring in the module, except in its functions or start function. + // This is rather weird, it seems to ultimately be checking that `ref.func` uses a specific set of predetermined functions: + // The only place where this is accessed is in validate_instruction(), but we *populate* this from the ref.func instructions occurring outside regular functions, + // which limits it to only functions referenced from the elements section. + // so the only reason for this (as I see) is to ensure that ref.func only hands out references that occur within the elements and global sections + // _if_ that is indeed the case, then this should be much more specific about where the "valid" references are, and about the actual purpose of this field. + // + // For now, we simply assume that we need to scan the aforementioned section initializers for (ref.func f). + auto scan_expression_for_function_indices = [&](auto& expression) { + for (auto& instruction : expression.instructions()) { + if (instruction.opcode() == Instructions::ref_func) + m_context.references.set(instruction.arguments().template get()); + } + }; + module.for_each_section_of_type([&](ElementSection const& section) { + for (auto& segment : section.segments()) { + for (auto& expression : segment.init) + scan_expression_for_function_indices(expression); + } + }); + module.for_each_section_of_type([&](GlobalSection const& section) { + for (auto& segment : section.entries()) + scan_expression_for_function_indices(segment.expression()); + }); + + for (auto& section : module.sections()) { + section.visit([this, &result](auto& section) { + result = validate(section); + }); + if (result.is_error()) { + module.set_validation_status(Module::ValidationStatus::Invalid, {}); + return result; + } + } + + if (m_context.memories.size() > 1) { + module.set_validation_status(Module::ValidationStatus::Invalid, {}); + return Errors::out_of_bounds("memory section count"sv, m_context.memories.size(), 1, 1); + } + + module.set_validation_status(Module::ValidationStatus::Valid, {}); + return {}; +} + +ErrorOr Validator::validate(ImportSection const& section) +{ + for (auto& import_ : section.imports()) + TRY(import_.description().visit([&](auto& entry) { return validate(entry); })); + return {}; +} + +ErrorOr Validator::validate(ExportSection const& section) +{ + for (auto& export_ : section.entries()) + TRY(export_.description().visit([&](auto& entry) { return validate(entry); })); + return {}; +} + +ErrorOr Validator::validate(StartSection const& section) +{ + TRY(validate(section.function().index())); + FunctionType const& type = m_context.functions[section.function().index().value()]; + if (!type.parameters().is_empty() || !type.results().is_empty()) + return Errors::invalid("start function signature"); + return {}; +} + +ErrorOr Validator::validate(DataSection const& section) +{ + for (auto& entry : section.data()) { + TRY(entry.value().visit( + [](DataSection::Data::Passive const&) { return ErrorOr {}; }, + [&](DataSection::Data::Active const& active) -> ErrorOr { + TRY(validate(active.index)); + + auto expression_result = TRY(validate(active.offset, { ValueType(ValueType::I32) })); + + if (!expression_result.is_constant) + return Errors::invalid("active data initializer"); + + if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(ValueType::I32)) + return Errors::invalid("active data initializer type", ValueType(ValueType::I32), expression_result.result_types); + + return {}; + })); + } + + return {}; +} + +ErrorOr Validator::validate(ElementSection const& section) +{ + for (auto& segment : section.segments()) { + TRY(segment.mode.visit( + [](ElementSection::Declarative const&) -> ErrorOr { return {}; }, + [](ElementSection::Passive const&) -> ErrorOr { return {}; }, + [&](ElementSection::Active const& active) -> ErrorOr { + TRY(validate(active.index)); + auto expression_result = TRY(validate(active.expression, { ValueType(ValueType::I32) })); + if (!expression_result.is_constant) + return Errors::invalid("active element initializer"); + if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(ValueType::I32)) + return Errors::invalid("active element initializer type", ValueType(ValueType::I32), expression_result.result_types); + return {}; + })); + } + return {}; +} + +ErrorOr Validator::validate(GlobalSection const& section) +{ + for (auto& entry : section.entries()) { + auto& type = entry.type(); + TRY(validate(type)); + auto expression_result = TRY(validate(entry.expression(), { type.type() })); + if (!expression_result.is_constant) + return Errors::invalid("global variable initializer"); + if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(type.type().kind())) + return Errors::invalid("global variable initializer type", ValueType(ValueType::I32), expression_result.result_types); + } + + return {}; +} + +ErrorOr Validator::validate(MemorySection const& section) +{ + for (auto& entry : section.memories()) + TRY(validate(entry.type())); + return {}; +} + +ErrorOr Validator::validate(TableSection const& section) +{ + for (auto& entry : section.tables()) + TRY(validate(entry.type())); + return {}; +} + +ErrorOr Validator::validate(CodeSection const& section) +{ + size_t index = m_context.imported_function_count; + for (auto& entry : section.functions()) { + auto function_index = index++; + TRY(validate(FunctionIndex { function_index })); + auto& function_type = m_context.functions[function_index]; + auto& function = entry.func(); + + auto function_validator = fork(); + function_validator.m_context.locals = {}; + function_validator.m_context.locals.extend(function_type.parameters()); + for (auto& local : function.locals()) { + for (size_t i = 0; i < local.n(); ++i) + function_validator.m_context.locals.append(local.type()); + } + + function_validator.m_context.labels = { ResultType { function_type.results() } }; + function_validator.m_context.return_ = ResultType { function_type.results() }; + + TRY(function_validator.validate(function.body(), function_type.results())); + } + + return {}; +} + +ErrorOr Validator::validate(TableType const& type) +{ + return validate(type.limits(), 32); +} + +ErrorOr Validator::validate(MemoryType const& type) +{ + return validate(type.limits(), 16); +} + +ErrorOr Validator::validate(BlockType const& type) +{ + if (type.kind() == BlockType::Index) { + TRY(validate(type.type_index())); + return m_context.types[type.type_index().value()]; + } + + if (type.kind() == BlockType::Type) { + FunctionType function_type { {}, { type.value_type() } }; + TRY(validate(function_type)); + return function_type; + } + + if (type.kind() == BlockType::Empty) + return FunctionType { {}, {} }; + + return Errors::invalid("BlockType"sv); +} + +ErrorOr Validator::validate(Limits const& limits, size_t k) +{ + auto bound = (1ull << k) - 1; + auto check_bound = [bound](auto value) { + return static_cast(value) < bound; + }; + + if (!check_bound(limits.min())) + return Errors::out_of_bounds("limit minimum"sv, limits.min(), 0, bound); + + if (limits.max().has_value() && (limits.max().value() < limits.min() || !check_bound(*limits.max()))) + return Errors::out_of_bounds("limit maximum"sv, limits.max().value(), limits.min(), bound); + + return {}; +} + +template +ErrorOr Validator::validate_instruction(Instruction const&, Stack&, bool&) +{ + return Errors::invalid("instruction opcode"sv); +} + +#define VALIDATE_INSTRUCTION(name) \ + template<> \ + ErrorOr Validator::validate_instruction([[maybe_unused]] Instruction const& instruction, [[maybe_unused]] Stack& stack, [[maybe_unused]] bool& is_constant) + +// https://webassembly.github.io/spec/core/bikeshed/#-tmathsfhrefsyntax-instr-numericmathsfconstc +VALIDATE_INSTRUCTION(i32_const) +{ + is_constant = true; + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_const) +{ + is_constant = true; + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_const) +{ + is_constant = true; + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_const) +{ + is_constant = true; + stack.append(ValueType(ValueType::F64)); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#-tmathsfhrefsyntax-unopmathitunop +VALIDATE_INSTRUCTION(i32_clz) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i32_ctz) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i32_popcnt) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i64_clz) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i64_ctz) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i64_popcnt) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f32_abs) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f32_neg) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f32_sqrt) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f32_ceil) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f32_floor) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f32_trunc) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f32_nearest) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f64_abs) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f64_neg) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f64_sqrt) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f64_ceil) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f64_floor) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f64_trunc) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(f64_nearest) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i32_extend16_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i32_extend8_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i64_extend32_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i64_extend16_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(i64_extend8_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#-tmathsfhrefsyntax-binopmathitbinop +VALIDATE_INSTRUCTION(i32_add) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_sub) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_mul) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_divs) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_divu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_rems) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_remu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_and) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_or) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_xor) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_shl) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_shrs) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_shru) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_rotl) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_rotr) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_add) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_sub) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_mul) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_divs) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_divu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_rems) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_remu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_and) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_or) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_xor) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_shl) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_shrs) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_shru) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_rotl) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_rotr) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_add) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_sub) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_mul) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_div) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_min) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_max) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_copysign) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_add) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_sub) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_mul) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_div) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_min) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_max) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_copysign) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#-tmathsfhrefsyntax-testopmathittestop +VALIDATE_INSTRUCTION(i32_eqz) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i64_eqz) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#-tmathsfhrefsyntax-relopmathitrelop +VALIDATE_INSTRUCTION(i32_eq) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_ne) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_lts) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_ltu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_gts) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_gtu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_les) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_leu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_ges) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_geu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_eq) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_ne) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_lts) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_ltu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_gts) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_gtu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_les) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_leu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_ges) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_geu) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_eq) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_ne) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_lt) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_le) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_gt) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_ge) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_eq) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_ne) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_lt) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_le) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_gt) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_ge) +{ + if (stack.size() < 2 || stack.take_last() != stack.last() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#-t_2mathsfhrefsyntax-cvtopmathitcvtopmathsf_t_1mathsf_hrefsyntax-sxmathitsx +VALIDATE_INSTRUCTION(i32_wrap_i64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_extend_si32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_extend_ui32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_sf32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_uf32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_sf64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_uf64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_sf32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_uf32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_sf64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_uf64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_sat_f32_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_sat_f32_u) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_sat_f64_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_trunc_sat_f64_u) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_sat_f32_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_sat_f32_u) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_sat_f64_s) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_trunc_sat_f64_u) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_convert_si32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_convert_ui32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_convert_si64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_convert_ui64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_convert_si32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_convert_ui32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_convert_si64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_convert_ui64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_demote_f64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_promote_f32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_reinterpret_i32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_reinterpret_i64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_reinterpret_f32) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_reinterpret_f64) +{ + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#reference-instructions%E2%91%A2 + +VALIDATE_INSTRUCTION(ref_null) +{ + is_constant = true; + stack.append(instruction.arguments().get()); + return {}; +} + +VALIDATE_INSTRUCTION(ref_is_null) +{ + if (stack.is_empty() || !stack.last().is_reference()) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(ref_func) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + if (!m_context.references.contains(index)) + return Errors::invalid("function reference"); + + is_constant = true; + stack.append(ValueType(ValueType::FunctionReference)); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#parametric-instructions%E2%91%A2 +VALIDATE_INSTRUCTION(drop) +{ + if (stack.is_empty()) + return Errors::invalid_stack_state(); + stack.take_last(); + return {}; +} + +VALIDATE_INSTRUCTION(select) +{ + if (stack.size() < 3) + return Errors::invalid_stack_state(); + + auto index_type = stack.take_last(); + auto arg0_type = stack.take_last(); + auto& arg1_type = stack.last(); + if (!index_type.is_of_kind(ValueType::I32)) + return Errors::invalid("select index type", ValueType(ValueType::I32), index_type); + + if (arg0_type != arg1_type) + return Errors::invalid("select argument types", Vector { arg0_type, arg0_type }, Vector { arg0_type, arg1_type }); + + return {}; +} + +VALIDATE_INSTRUCTION(select_typed) +{ + if (stack.size() < 3) + return Errors::invalid_stack_state(); + + auto& required_types = instruction.arguments().get>(); + if (required_types.size() != 1) + return Errors::invalid("select types", "exactly one type", required_types); + + auto index_type = stack.take_last(); + auto arg0_type = stack.take_last(); + auto& arg1_type = stack.last(); + if (!index_type.is_of_kind(ValueType::I32)) + return Errors::invalid("select index type", ValueType(ValueType::I32), index_type); + + if (arg0_type != arg1_type || arg0_type != required_types.first()) + return Errors::invalid("select argument types", Vector { required_types.first(), required_types.first() }, Vector { arg0_type, arg1_type }); + + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#variable-instructions%E2%91%A2 +VALIDATE_INSTRUCTION(local_get) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + stack.append(m_context.locals[index.value()]); + return {}; +} + +VALIDATE_INSTRUCTION(local_set) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& value_type = m_context.locals[index.value()]; + if (stack.take_last() != value_type) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(local_tee) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& value_type = m_context.locals[index.value()]; + if (stack.last() != value_type) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(global_get) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& global = m_context.globals[index.value()]; + + is_constant = !global.is_mutable(); + stack.append(global.type()); + return {}; +} + +VALIDATE_INSTRUCTION(global_set) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& global = m_context.globals[index.value()]; + + if (!global.is_mutable()) + return Errors::invalid("global variable for global.set"); + + stack.append(global.type()); + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#table-instructions%E2%91%A2 +VALIDATE_INSTRUCTION(table_get) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& table = m_context.tables[index.value()]; + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(table.element_type()); + return {}; +} + +VALIDATE_INSTRUCTION(table_set) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& table = m_context.tables[index.value()]; + if (stack.is_empty()) + return Errors::invalid_stack_state(); + + if (stack.take_last() != table.element_type()) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(table_size) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(table_grow) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& table = m_context.tables[index.value()]; + if (stack.is_empty()) + return Errors::invalid_stack_state(); + + if (!stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || stack.take_last() != table.element_type()) + return Errors::invalid_stack_state(); + + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(table_fill) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& table = m_context.tables[index.value()]; + if (stack.is_empty()) + return Errors::invalid_stack_state(); + + if (!stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || stack.take_last() != table.element_type()) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(table_copy) +{ + auto& args = instruction.arguments().get(); + + TRY(validate(args.lhs)); + TRY(validate(args.rhs)); + + auto& lhs_table = m_context.tables[args.lhs.value()]; + auto& rhs_table = m_context.tables[args.rhs.value()]; + + if (lhs_table.element_type() != rhs_table.element_type()) + return Errors::non_conforming_types("table.copy", lhs_table.element_type(), rhs_table.element_type()); + + if (!lhs_table.element_type().is_reference()) + return Errors::invalid("table.copy element type", "a reference type", lhs_table.element_type()); + + if (stack.size() < 3) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < 3; ++i) { + if (!stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + } + + return {}; +} + +VALIDATE_INSTRUCTION(table_init) +{ + auto& args = instruction.arguments().get(); + + TRY(validate(args.table_index)); + TRY(validate(args.element_index)); + + auto& table = m_context.tables[args.table_index.value()]; + auto& element_type = m_context.elements[args.element_index.value()]; + + if (table.element_type() != element_type) + return Errors::non_conforming_types("table.init", table.element_type(), element_type); + + if (stack.size() < 3) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < 3; ++i) { + if (!stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + } + + return {}; +} + +VALIDATE_INSTRUCTION(elem_drop) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#memory-instructions%E2%91%A2 +VALIDATE_INSTRUCTION(i32_load) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(i32)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(i32)); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i64_load) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(i64)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(i64)); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(f32_load) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(float)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(float)); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F32)); + return {}; +} + +VALIDATE_INSTRUCTION(f64_load) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(double)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(double)); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::F64)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_load16_s) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 16 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 16 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_load16_u) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 16 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 16 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_load8_s) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 8 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 8 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_load8_u) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 8 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 8 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_load32_s) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 32 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 32 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_load32_u) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 32 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 32 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_load16_s) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 16 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 16 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_load16_u) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 16 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 16 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_load8_s) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 8 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 8 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i64_load8_u) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 8 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 8 / 8); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + stack.take_last(); + stack.append(ValueType(ValueType::I64)); + return {}; +} + +VALIDATE_INSTRUCTION(i32_store) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(i32)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(i32)); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i64_store) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(i64)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(i64)); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(f32_store) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(float)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(float)); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::F32)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(f64_store) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > sizeof(double)) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, sizeof(double)); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::F64)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i32_store16) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 16 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 16 / 8); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i32_store8) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 8 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 8 / 8); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i64_store32) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 32 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 32 / 8); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i64_store16) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 16 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 16 / 8); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(i64_store8) +{ + TRY(validate(MemoryIndex { 0 })); + + auto& arg = instruction.arguments().get(); + if ((1ull << arg.align) > 8 / 8) + return Errors::out_of_bounds("memory op alignment", 1ull << arg.align, 0, 8 / 8); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I64)) + return Errors::invalid_stack_state(); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + return {}; +} + +VALIDATE_INSTRUCTION(memory_size) +{ + TRY(validate(MemoryIndex { 0 })); + + stack.append(ValueType(ValueType::I32)); + return {}; +} + +VALIDATE_INSTRUCTION(memory_grow) +{ + TRY(validate(MemoryIndex { 0 })); + + if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + return {}; +} + +VALIDATE_INSTRUCTION(memory_fill) +{ + TRY(validate(MemoryIndex { 0 })); + + if (stack.size() < 3) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < 3; ++i) { + if (!stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + } + + return {}; +} + +VALIDATE_INSTRUCTION(memory_init) +{ + TRY(validate(MemoryIndex { 0 })); + + auto index = instruction.arguments().get(); + TRY(validate(index)); + + if (stack.size() < 3) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < 3; ++i) { + if (!stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + } + + return {}; +} + +VALIDATE_INSTRUCTION(data_drop) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + return {}; +} + +// https://webassembly.github.io/spec/core/bikeshed/#control-instructions%E2%91%A2 +VALIDATE_INSTRUCTION(nop) +{ + return {}; +} + +VALIDATE_INSTRUCTION(unreachable) +{ + // https://webassembly.github.io/spec/core/bikeshed/#polymorphism + stack.append(StackEntry()); + + return {}; +} + +// Note: This is responsible for _all_ structured instructions, and is *not* from the spec. +VALIDATE_INSTRUCTION(structured_end) +{ + if (m_entered_scopes.is_empty()) + return Errors::invalid("usage of structured end"); + + auto last_scope = m_entered_scopes.take_last(); + m_context = m_parent_contexts.take_last(); + auto last_block_type = m_entered_blocks.take_last(); + + if (last_scope == ChildScopeKind::Block) { + auto details = m_block_details.take_last(); + // FIXME: Validate the returns. + return {}; + } + + if (last_scope == ChildScopeKind::Else) { + auto details = m_block_details.take_last().details.get(); + if (details.true_branch_stack != stack) + return Errors::invalid("stack configuration after if-else", details.true_branch_stack.release_vector(), stack.release_vector()); + + return {}; + } + + return {}; +} + +// Note: This is *not* from the spec. +VALIDATE_INSTRUCTION(structured_else) +{ + if (m_entered_scopes.is_empty()) + return Errors::invalid("usage of structured else"); + + if (m_entered_scopes.last() != ChildScopeKind::IfWithElse) + return Errors::invalid("usage of structured else"); + + m_entered_scopes.last() = ChildScopeKind::Else; + auto& if_details = m_block_details.last().details.get(); + if_details.true_branch_stack = exchange(stack, move(if_details.initial_stack)); + m_context = m_parent_contexts.last(); + return {}; +} + +VALIDATE_INSTRUCTION(block) +{ + auto& args = instruction.arguments().get(); + auto block_type = TRY(validate(args.block_type)); + + auto& parameters = block_type.parameters(); + if (stack.size() < parameters.size()) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < parameters.size(); ++i) { + if (stack.take_last() != parameters[parameters.size() - i]) + return Errors::invalid_stack_state(); + } + + m_entered_scopes.append(ChildScopeKind::Block); + m_block_details.empend(stack.actual_size(), Empty {}); + m_parent_contexts.append(m_context); + m_entered_blocks.append(block_type); + m_context.labels.prepend(ResultType { block_type.results() }); + return {}; +} + +VALIDATE_INSTRUCTION(loop) +{ + auto& args = instruction.arguments().get(); + auto block_type = TRY(validate(args.block_type)); + + auto& parameters = block_type.parameters(); + if (stack.size() < parameters.size()) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < parameters.size(); ++i) { + if (stack.take_last() != parameters[parameters.size() - i - 1]) + return Errors::invalid_stack_state(); + } + + m_entered_scopes.append(ChildScopeKind::Block); + m_block_details.empend(stack.actual_size(), Empty {}); + m_parent_contexts.append(m_context); + m_entered_blocks.append(block_type); + m_context.labels.prepend(ResultType { block_type.results() }); + return {}; +} + +VALIDATE_INSTRUCTION(if_) +{ + auto& args = instruction.arguments().get(); + auto block_type = TRY(validate(args.block_type)); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + auto& parameters = block_type.parameters(); + if (stack.size() < parameters.size()) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < parameters.size(); ++i) { + if (stack.take_last() != parameters[parameters.size() - i]) + return Errors::invalid_stack_state(); + } + + m_entered_scopes.append(args.else_ip.has_value() ? ChildScopeKind::IfWithElse : ChildScopeKind::IfWithoutElse); + m_block_details.empend(stack.actual_size(), BlockDetails::IfDetails { stack, {} }); + m_parent_contexts.append(m_context); + m_entered_blocks.append(block_type); + m_context.labels.prepend(ResultType { block_type.results() }); + return {}; +} + +VALIDATE_INSTRUCTION(br) +{ + auto label = instruction.arguments().get(); + TRY(validate(label)); + + auto& type = m_context.labels[label.value()]; + if (stack.size() < type.types().size()) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < type.types().size(); ++i) { + if (stack.take_last() != type.types()[type.types().size() - i - 1]) + return Errors::invalid_stack_state(); + } + stack.append(StackEntry()); + return {}; +} + +VALIDATE_INSTRUCTION(br_if) +{ + auto label = instruction.arguments().get(); + TRY(validate(label)); + + auto& type = m_context.labels[label.value()]; + if (stack.size() < type.types().size()) + return Errors::invalid_stack_state(); + + Vector entries; + entries.ensure_capacity(type.types().size()); + + for (size_t i = 0; i < type.types().size(); ++i) { + auto entry = stack.take_last(); + if (entry != type.types()[type.types().size() - i - 1]) + return Errors::invalid_stack_state(); + entries.append(entry); + } + + for (size_t i = 0; i < entries.size(); ++i) + stack.append(entries[entries.size() - i - 1]); + + return {}; +} + +VALIDATE_INSTRUCTION(br_table) +{ + auto& args = instruction.arguments().get(); + TRY(validate(args.default_)); + + for (auto& label : args.labels) + TRY(validate(label)); + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + auto& default_types = m_context.labels[args.default_.value()].types(); + auto arity = default_types.size(); + if (stack.size() < arity) + return Errors::invalid_stack_state(); + + for (auto& label : args.labels) { + auto& label_types = m_context.labels[label.value()].types(); + if (label_types.size() != arity) + return Errors::invalid_stack_state(); + for (size_t i = 0; i < arity; ++i) { + if (stack.at(stack.actual_size() - i - 1) != label_types[label_types.size() - i - 1]) + return Errors::invalid_stack_state(); + } + } + + for (size_t i = 0; i < arity; ++i) { + if (stack.take_last() != default_types[default_types.size() - i - 1]) + return Errors::invalid_stack_state(); + } + + return {}; +} + +VALIDATE_INSTRUCTION(return_) +{ + if (!m_context.return_.has_value()) + return Errors::invalid("use of return outside function"); + + auto& return_types = m_context.return_->types(); + for (size_t i = 0; i < return_types.size(); ++i) { + if (stack.is_empty() || stack.take_last() != return_types[return_types.size() - i - 1]) + return Errors::invalid_stack_state(); + } + + stack.append(StackEntry()); + + return {}; +} + +VALIDATE_INSTRUCTION(call) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& function_type = m_context.functions[index.value()]; + for (size_t i = 0; i < function_type.parameters().size(); ++i) { + if (stack.is_empty() || stack.take_last() != function_type.parameters()[function_type.parameters().size() - i - 1]) + return Errors::invalid_stack_state(); + } + + for (auto& type : function_type.results()) + stack.append(type); + + return {}; +} + +VALIDATE_INSTRUCTION(call_indirect) +{ + auto& args = instruction.arguments().get(); + TRY(validate(args.table)); + TRY(validate(args.type)); + + auto& table = m_context.tables[args.table.value()]; + if (!table.element_type().is_reference()) + return Errors::invalid("table element type for call.indirect", "a reference type", table.element_type()); + + auto& type = m_context.types[args.type.value()]; + + if (stack.is_empty() || !stack.take_last().is_of_kind(ValueType::I32)) + return Errors::invalid_stack_state(); + + for (size_t i = 0; i < type.parameters().size(); ++i) { + if (stack.is_empty() || stack.take_last() != type.parameters()[type.parameters().size() - i - 1]) + return Errors::invalid_stack_state(); + } + + for (auto& type : type.results()) + stack.append(type); + + return {}; +} + +ErrorOr Validator::validate(const Instruction& instruction, Stack& stack, bool& is_constant) +{ + switch (instruction.opcode().value()) { +#define M(name, integer_value) \ + case Instructions::name.value(): \ + return validate_instruction(instruction, stack, is_constant); + + ENUMERATE_WASM_OPCODES(M) + +#undef M + default: + is_constant = false; + return Errors::invalid("instruction opcode"); + } +} + +ErrorOr Validator::validate(Expression const& expression, Vector const& result_types) +{ + Stack stack; + bool is_constant_expression = true; + + for (auto& instruction : expression.instructions()) { + bool is_constant = false; + TRY(validate(instruction, stack, is_constant)); + + is_constant_expression &= is_constant; + } + + auto expected_result_types = result_types; + while (!expected_result_types.is_empty()) { + if (stack.is_empty()) + return Errors::invalid_stack_state(); + + auto stack_type = stack.take_last(); + auto expected_type = expected_result_types.take_last(); + + if (stack_type != expected_type) + return Errors::invalid_stack_state(); + } + + for (auto& type : result_types) + stack.append(type); + + return ExpressionTypeResult { stack.release_vector(), is_constant_expression }; +} + +bool Validator::Stack::operator==(const Stack& other) const +{ + if (!m_did_insert_unknown_entry && !other.m_did_insert_unknown_entry) + return static_cast const&>(*this) == static_cast const&>(other); + + Optional own_last_unknown_entry_index_from_end, other_last_unknown_entry_index_from_end; + auto other_size = static_cast const&>(other).size(); + auto own_size = Vector::size(); + + for (size_t i = 0; i < own_size; ++i) { + if (other_size <= i) + break; + + auto own_entry = at(own_size - i - 1); + auto other_entry = other.at(other_size - i - 1); + if (!own_entry.is_known) { + own_last_unknown_entry_index_from_end = i; + break; + } + + if (!other_entry.is_known) { + other_last_unknown_entry_index_from_end = i; + break; + } + } + + if (!own_last_unknown_entry_index_from_end.has_value() && !other_last_unknown_entry_index_from_end.has_value()) { + if (static_cast const&>(other).is_empty() || Vector::is_empty()) + return true; + + dbgln("Equality check internal error between"); + dbgln("stack:"); + for (auto& entry : *this) + dbgln("- {}", entry.is_known ? Wasm::ValueType::kind_name(entry.concrete_type.kind()) : ""); + dbgln("and stack:"); + for (auto& entry : other) + dbgln("- {}", entry.is_known ? Wasm::ValueType::kind_name(entry.concrete_type.kind()) : ""); + + VERIFY_NOT_REACHED(); + } + + auto index_from_end = max(own_last_unknown_entry_index_from_end.value_or(0), other_last_unknown_entry_index_from_end.value_or(0)); + + for (size_t i = 0; i < index_from_end; ++i) { + if (at(own_size - i - 1) != other.at(other_size - i - 1)) + return false; + } + + return true; +} + +#if WASM_VALIDATOR_DEBUG +ValidationError Validator::Errors::invalid_stack_state(SourceLocation location) +{ + auto index = location.function_name().find('<'); + auto end_index = location.function_name().find('>'); + if (!index.has_value() || !end_index.has_value()) + return ValidationError { "Invalid stack state"sv }; + + auto opcode = location.function_name().substring_view(index.value() + 1, end_index.value() - index.value() - 1).to_uint(); + if (!opcode.has_value()) + return ValidationError { "Invalid stack state"sv }; + + auto name = instruction_name(OpCode { *opcode }); + return String::formatted("Invalid stack state for {}", name); +} +#else +ValidationError Validator::Errors::invalid_stack_state() +{ + return ValidationError { "Invalid stack state"sv }; +} +#endif +} diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Validator.h b/Userland/Libraries/LibWasm/AbstractMachine/Validator.h new file mode 100644 index 0000000000..e4f8b928ef --- /dev/null +++ b/Userland/Libraries/LibWasm/AbstractMachine/Validator.h @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +#if WASM_VALIDATOR_DEBUG +# include +#endif + +namespace Wasm { + +struct Context { + Vector types; + Vector functions; + Vector tables; + Vector memories; + Vector globals; + Vector elements; + Vector datas; + Vector locals; + Vector labels; + Optional return_; + AK::HashTable references; + size_t imported_function_count { 0 }; +}; + +struct ValidationError : public Error { + ValidationError(String error) + : Error(Error::from_string_literal(error)) + , error_string(move(error)) + { + } + + String error_string; +}; + +class Validator { + AK_MAKE_NONCOPYABLE(Validator); + AK_MAKE_NONMOVABLE(Validator); + +public: + Validator() = default; + + [[nodiscard]] Validator fork() const + { + return Validator { m_context }; + } + + // Module + ErrorOr validate(Module&); + ErrorOr validate(ImportSection const&); + ErrorOr validate(ExportSection const&); + ErrorOr validate(StartSection const&); + ErrorOr validate(DataSection const&); + ErrorOr validate(ElementSection const&); + ErrorOr validate(GlobalSection const&); + ErrorOr validate(MemorySection const&); + ErrorOr validate(TableSection const&); + ErrorOr validate(CodeSection const&); + ErrorOr validate(FunctionSection const&) { return {}; } + ErrorOr validate(DataCountSection const&) { return {}; } + ErrorOr validate(TypeSection const&) { return {}; } + ErrorOr validate(CustomSection const&) { return {}; } + + ErrorOr validate(TypeIndex index) const + { + if (index.value() < m_context.types.size()) + return {}; + return Errors::invalid("TypeIndex"sv); + } + + ErrorOr validate(FunctionIndex index) const + { + if (index.value() < m_context.functions.size()) + return {}; + return Errors::invalid("FunctionIndex"sv); + } + + ErrorOr validate(MemoryIndex index) const + { + if (index.value() < m_context.memories.size()) + return {}; + return Errors::invalid("MemoryIndex"sv); + } + + ErrorOr validate(ElementIndex index) const + { + if (index.value() < m_context.elements.size()) + return {}; + return Errors::invalid("ElementIndex"sv); + } + + ErrorOr validate(DataIndex index) const + { + if (index.value() < m_context.datas.size()) + return {}; + return Errors::invalid("DataIndex"sv); + } + + ErrorOr validate(GlobalIndex index) const + { + if (index.value() < m_context.globals.size()) + return {}; + return Errors::invalid("GlobalIndex"sv); + } + + ErrorOr validate(LabelIndex index) const + { + if (index.value() < m_context.labels.size()) + return {}; + return Errors::invalid("LabelIndex"sv); + } + + ErrorOr validate(LocalIndex index) const + { + if (index.value() < m_context.locals.size()) + return {}; + return Errors::invalid("LocalIndex"sv); + } + + ErrorOr validate(TableIndex index) const + { + if (index.value() < m_context.tables.size()) + return {}; + return Errors::invalid("TableIndex"sv); + } + + // Instructions + struct StackEntry { + StackEntry(ValueType type) + : concrete_type(type) + , is_known(true) + { + } + + explicit StackEntry() + : concrete_type(ValueType::I32) + , is_known(false) + { + } + + bool is_of_kind(ValueType::Kind kind) const + { + if (is_known) + return concrete_type.kind() == kind; + return true; + } + + bool is_numeric() const { return !is_known || concrete_type.is_numeric(); } + bool is_reference() const { return !is_known || concrete_type.is_reference(); } + + bool operator==(ValueType const& other) const + { + if (is_known) + return concrete_type == other; + return true; + } + + bool operator==(StackEntry const& other) const + { + if (is_known && other.is_known) + return other.concrete_type == concrete_type; + return true; + } + + ValueType concrete_type; + bool is_known { true }; + }; + + // This is a wrapper that can model "polymorphic" stacks, + // by treating unknown stack entries as a potentially infinite number of entries + class Stack : private Vector { + public: + // The unknown entry will never be popped off, so we can safely use the original `is_empty`. + using Vector::is_empty; + using Vector::last; + using Vector::at; + + StackEntry take_last() + { + if (last().is_known) + return Vector::take_last(); + return last(); + } + void append(StackEntry entry) + { + if (!entry.is_known) + m_did_insert_unknown_entry = true; + Vector::append(entry); + } + + size_t actual_size() const { return Vector::size(); } + size_t size() const { return m_did_insert_unknown_entry ? static_cast(-1) : actual_size(); } + + Vector release_vector() { return exchange(static_cast&>(*this), Vector {}); } + + bool operator==(Stack const& other) const; + + private: + bool m_did_insert_unknown_entry { false }; + }; + + struct ExpressionTypeResult { + Vector result_types; + bool is_constant { false }; + }; + ErrorOr validate(Expression const&, Vector const&); + ErrorOr validate(Instruction const& instruction, Stack& stack, bool& is_constant); + template + ErrorOr validate_instruction(Instruction const&, Stack& stack, bool& is_constant); + + // Types + bool type_is_subtype_of(ValueType const& candidate_subtype, ValueType const& candidate_supertype); + ErrorOr validate(Limits const&, size_t k); // n <= 2^k-1 && m? <= 2^k-1 + ErrorOr validate(BlockType const&); + ErrorOr validate(FunctionType const&) { return {}; } + ErrorOr validate(TableType const&); + ErrorOr validate(MemoryType const&); + ErrorOr validate(GlobalType const&) { return {}; } + +private: + explicit Validator(Context context) + : m_context(move(context)) + { + } + + struct Errors { + static ValidationError invalid(StringView name) { return String::formatted("Invalid {}", name); } + + template + static ValidationError invalid(StringView name, Expected expected, Given given) + { + return String::formatted("Invalid {}, expected {} but got {}", name, expected, given); + } + + template + static ValidationError non_conforming_types(StringView name, Args... args) + { + return String::formatted("Non-conforming types for {}: {}", name, Vector { args... }); + } + + static ValidationError duplicate_export_name(StringView name) { return String::formatted("Duplicate exported name '{}'", name); } + + template + static ValidationError out_of_bounds(StringView name, V value, T min, U max) { return String::formatted("Value {} for {} is out of bounds ({},{})", value, name, min, max); } + +#if WASM_VALIDATOR_DEBUG + static ValidationError invalid_stack_state(SourceLocation location = SourceLocation::current()); +#else + static ValidationError invalid_stack_state(); +#endif + }; + + enum class ChildScopeKind { + Block, + Loop, + IfWithoutElse, + IfWithElse, + Else, + }; + + struct BlockDetails { + size_t initial_stack_size { 0 }; + struct IfDetails { + Stack initial_stack; + Stack true_branch_stack; + }; + Variant details; + }; + + Context m_context; + Vector m_parent_contexts; + Vector m_entered_scopes; + Vector m_block_details; + Vector m_entered_blocks; +}; + +} + +template<> +struct AK::Formatter : public AK::Formatter { + void format(FormatBuilder& builder, Wasm::Validator::StackEntry const& value) + { + if (value.is_known) + return Formatter::format(builder, Wasm::ValueType::kind_name(value.concrete_type.kind())); + + Formatter::format(builder, ""sv); + } +}; + +template<> +struct AK::Formatter : public AK::Formatter { + void format(FormatBuilder& builder, Wasm::ValueType const& value) + { + Formatter::format(builder, Wasm::ValueType::kind_name(value.kind())); + } +}; + +template<> +struct AK::Formatter : public AK::Formatter { + void format(FormatBuilder& builder, Wasm::ValidationError const& error) + { + Formatter::format(builder, error.error_string); + } +}; diff --git a/Userland/Libraries/LibWasm/CMakeLists.txt b/Userland/Libraries/LibWasm/CMakeLists.txt index 748137b6a9..be48143aef 100644 --- a/Userland/Libraries/LibWasm/CMakeLists.txt +++ b/Userland/Libraries/LibWasm/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES AbstractMachine/AbstractMachine.cpp AbstractMachine/BytecodeInterpreter.cpp AbstractMachine/Configuration.cpp + AbstractMachine/Validator.cpp Parser/Parser.cpp Printer/Printer.cpp ) diff --git a/Userland/Libraries/LibWasm/Forward.h b/Userland/Libraries/LibWasm/Forward.h new file mode 100644 index 0000000000..4bb2c85f84 --- /dev/null +++ b/Userland/Libraries/LibWasm/Forward.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace Wasm { + +class AbstractMachine; +class Validator; +struct ValidationError; + +} diff --git a/Userland/Libraries/LibWasm/Types.h b/Userland/Libraries/LibWasm/Types.h index 898d893479..1332333271 100644 --- a/Userland/Libraries/LibWasm/Types.h +++ b/Userland/Libraries/LibWasm/Types.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include namespace Wasm { @@ -180,6 +182,8 @@ public: { } + bool operator==(ValueType const&) const = default; + auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference || m_kind == NullExternReference || m_kind == NullFunctionReference; } auto is_numeric() const { return !is_reference(); } auto kind() const { return m_kind; } @@ -953,6 +957,12 @@ private: class Module { public: + enum class ValidationStatus { + Unchecked, + Invalid, + Valid, + }; + class Function { public: explicit Function(TypeIndex type, Vector local_types, Expression body) @@ -1026,12 +1036,20 @@ public: } } + void set_validation_status(ValidationStatus status, Badge) { set_validation_status(status); } + ValidationStatus validation_status() const { return m_validation_status; } + StringView validation_error() const { return *m_validation_error; } + void set_validation_error(String error) { m_validation_error = move(error); } + static ParseResult parse(InputStream& stream); private: void populate_sections(); + void set_validation_status(ValidationStatus status) { m_validation_status = status; } Vector m_sections; Vector m_functions; + ValidationStatus m_validation_status { ValidationStatus::Unchecked }; + Optional m_validation_error; }; }