Browse Source

LibWasm: Implement module validation

Ali Mohammad Pur 3 năm trước cách đây
mục cha
commit
7d1142e2c8

+ 4 - 0
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

+ 1 - 0
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)

+ 22 - 2
Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp

@@ -8,6 +8,7 @@
 #include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
 #include <LibWasm/AbstractMachine/Configuration.h>
 #include <LibWasm/AbstractMachine/Interpreter.h>
+#include <LibWasm/AbstractMachine/Validator.h>
 #include <LibWasm/Types.h>
 
 namespace Wasm {
@@ -100,8 +101,29 @@ ElementInstance* Store::get(ElementAddress address)
     return &m_elements[value];
 }
 
+ErrorOr<void, ValidationError> 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<ExternValue> externs)
 {
+    if (auto result = validate(const_cast<Module&>(module)); result.is_error())
+        return InstantiationError { String::formatted("Validation failed: {}", result.error()) };
+
     auto main_module_instance_pointer = make<ModuleInstance>();
     auto& main_module_instance = *main_module_instance_pointer;
     Optional<InstantiationResult> instantiation_result;
@@ -110,8 +132,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
         main_module_instance.types() = section.types();
     });
 
-    // FIXME: Validate stuff
-
     Vector<Value> global_values;
     Vector<Vector<Reference>> elements;
     ModuleInstance auxiliary_instance;

+ 3 - 0
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<void, ValidationError> validate(Module&);
     // Load and instantiate a module, and link it into this interpreter.
     InstantiationResult instantiate(Module const&, Vector<ExternValue>);
     Result invoke(FunctionAddress, Vector<Value>);

+ 2737 - 0
Userland/Libraries/LibWasm/AbstractMachine/Validator.cpp

@@ -0,0 +1,2737 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/HashTable.h>
+#include <AK/Result.h>
+#include <AK/SourceLocation.h>
+#include <AK/Try.h>
+#include <LibWasm/AbstractMachine/Validator.h>
+#include <LibWasm/Printer/Printer.h>
+
+namespace Wasm {
+
+ErrorOr<void, ValidationError> Validator::validate(Module& module)
+{
+    ErrorOr<void, ValidationError> 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<StringView> seen_export_names;
+    module.for_each_section_of_type<ExportSection>([&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<TypeSection>([this](TypeSection const& section) {
+        m_context.types = section.types();
+    });
+
+    module.for_each_section_of_type<ImportSection>([&](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<FunctionSection>([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<TableSection>([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<MemorySection>([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<GlobalSection>([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<ElementSection>([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<ref_func>(), 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<FunctionIndex>());
+        }
+    };
+    module.for_each_section_of_type<ElementSection>([&](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>([&](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<void, ValidationError> Validator::validate(ImportSection const& section)
+{
+    for (auto& import_ : section.imports())
+        TRY(import_.description().visit([&](auto& entry) { return validate(entry); }));
+    return {};
+}
+
+ErrorOr<void, ValidationError> Validator::validate(ExportSection const& section)
+{
+    for (auto& export_ : section.entries())
+        TRY(export_.description().visit([&](auto& entry) { return validate(entry); }));
+    return {};
+}
+
+ErrorOr<void, ValidationError> 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<void, ValidationError> Validator::validate(DataSection const& section)
+{
+    for (auto& entry : section.data()) {
+        TRY(entry.value().visit(
+            [](DataSection::Data::Passive const&) { return ErrorOr<void, ValidationError> {}; },
+            [&](DataSection::Data::Active const& active) -> ErrorOr<void, ValidationError> {
+                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<void, ValidationError> Validator::validate(ElementSection const& section)
+{
+    for (auto& segment : section.segments()) {
+        TRY(segment.mode.visit(
+            [](ElementSection::Declarative const&) -> ErrorOr<void, ValidationError> { return {}; },
+            [](ElementSection::Passive const&) -> ErrorOr<void, ValidationError> { return {}; },
+            [&](ElementSection::Active const& active) -> ErrorOr<void, ValidationError> {
+                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<void, ValidationError> 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<void, ValidationError> Validator::validate(MemorySection const& section)
+{
+    for (auto& entry : section.memories())
+        TRY(validate(entry.type()));
+    return {};
+}
+
+ErrorOr<void, ValidationError> Validator::validate(TableSection const& section)
+{
+    for (auto& entry : section.tables())
+        TRY(validate(entry.type()));
+    return {};
+}
+
+ErrorOr<void, ValidationError> 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<void, ValidationError> Validator::validate(TableType const& type)
+{
+    return validate(type.limits(), 32);
+}
+
+ErrorOr<void, ValidationError> Validator::validate(MemoryType const& type)
+{
+    return validate(type.limits(), 16);
+}
+
+ErrorOr<FunctionType, ValidationError> 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<void, ValidationError> Validator::validate(Limits const& limits, size_t k)
+{
+    auto bound = (1ull << k) - 1;
+    auto check_bound = [bound](auto value) {
+        return static_cast<u64>(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<u32 opcode>
+ErrorOr<void, ValidationError> Validator::validate_instruction(Instruction const&, Stack&, bool&)
+{
+    return Errors::invalid("instruction opcode"sv);
+}
+
+#define VALIDATE_INSTRUCTION(name) \
+    template<>                     \
+    ErrorOr<void, ValidationError> Validator::validate_instruction<Instructions::name.value()>([[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<ValueType>());
+    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<FunctionIndex>();
+    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<Vector<ValueType>>();
+    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<LocalIndex>();
+    TRY(validate(index));
+
+    stack.append(m_context.locals[index.value()]);
+    return {};
+}
+
+VALIDATE_INSTRUCTION(local_set)
+{
+    auto index = instruction.arguments().get<LocalIndex>();
+    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<LocalIndex>();
+    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<GlobalIndex>();
+    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<GlobalIndex>();
+    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<TableIndex>();
+    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<TableIndex>();
+    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<TableIndex>();
+    TRY(validate(index));
+
+    stack.append(ValueType(ValueType::I32));
+    return {};
+}
+
+VALIDATE_INSTRUCTION(table_grow)
+{
+    auto index = instruction.arguments().get<TableIndex>();
+    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<TableIndex>();
+    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<Instruction::TableTableArgs>();
+
+    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<Instruction::TableElementArgs>();
+
+    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<ElementIndex>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<Instruction::MemoryArgument>();
+    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<DataIndex>();
+    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<DataIndex>();
+    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<BlockDetails::IfDetails>();
+        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<BlockDetails::IfDetails>();
+    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<Instruction::StructuredInstructionArgs>();
+    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<Instruction::StructuredInstructionArgs>();
+    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<Instruction::StructuredInstructionArgs>();
+    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<LabelIndex>();
+    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<LabelIndex>();
+    TRY(validate(label));
+
+    auto& type = m_context.labels[label.value()];
+    if (stack.size() < type.types().size())
+        return Errors::invalid_stack_state();
+
+    Vector<StackEntry> 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<Instruction::TableBranchArgs>();
+    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<FunctionIndex>();
+    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<Instruction::IndirectCallArgs>();
+    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<void, ValidationError> 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<integer_value>(instruction, stack, is_constant);
+
+        ENUMERATE_WASM_OPCODES(M)
+
+#undef M
+    default:
+        is_constant = false;
+        return Errors::invalid("instruction opcode");
+    }
+}
+
+ErrorOr<Validator::ExpressionTypeResult, ValidationError> Validator::validate(Expression const& expression, Vector<ValueType> 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<Vector<StackEntry> const&>(*this) == static_cast<Vector<StackEntry> const&>(other);
+
+    Optional<size_t> own_last_unknown_entry_index_from_end, other_last_unknown_entry_index_from_end;
+    auto other_size = static_cast<Vector<StackEntry> const&>(other).size();
+    auto own_size = Vector<StackEntry>::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<Vector<StackEntry> const&>(other).is_empty() || Vector<StackEntry>::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()) : "<unknown>");
+        dbgln("and stack:");
+        for (auto& entry : other)
+            dbgln("- {}", entry.is_known ? Wasm::ValueType::kind_name(entry.concrete_type.kind()) : "<unknown>");
+
+        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
+}

+ 312 - 0
Userland/Libraries/LibWasm/AbstractMachine/Validator.h

@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <LibWasm/Forward.h>
+#include <LibWasm/Types.h>
+
+#if WASM_VALIDATOR_DEBUG
+#    include <AK/SourceLocation.h>
+#endif
+
+namespace Wasm {
+
+struct Context {
+    Vector<FunctionType> types;
+    Vector<FunctionType> functions;
+    Vector<TableType> tables;
+    Vector<MemoryType> memories;
+    Vector<GlobalType> globals;
+    Vector<ValueType> elements;
+    Vector<bool> datas;
+    Vector<ValueType> locals;
+    Vector<ResultType> labels;
+    Optional<ResultType> return_;
+    AK::HashTable<FunctionIndex> 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<void, ValidationError> validate(Module&);
+    ErrorOr<void, ValidationError> validate(ImportSection const&);
+    ErrorOr<void, ValidationError> validate(ExportSection const&);
+    ErrorOr<void, ValidationError> validate(StartSection const&);
+    ErrorOr<void, ValidationError> validate(DataSection const&);
+    ErrorOr<void, ValidationError> validate(ElementSection const&);
+    ErrorOr<void, ValidationError> validate(GlobalSection const&);
+    ErrorOr<void, ValidationError> validate(MemorySection const&);
+    ErrorOr<void, ValidationError> validate(TableSection const&);
+    ErrorOr<void, ValidationError> validate(CodeSection const&);
+    ErrorOr<void, ValidationError> validate(FunctionSection const&) { return {}; }
+    ErrorOr<void, ValidationError> validate(DataCountSection const&) { return {}; }
+    ErrorOr<void, ValidationError> validate(TypeSection const&) { return {}; }
+    ErrorOr<void, ValidationError> validate(CustomSection const&) { return {}; }
+
+    ErrorOr<void, ValidationError> validate(TypeIndex index) const
+    {
+        if (index.value() < m_context.types.size())
+            return {};
+        return Errors::invalid("TypeIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> validate(FunctionIndex index) const
+    {
+        if (index.value() < m_context.functions.size())
+            return {};
+        return Errors::invalid("FunctionIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> validate(MemoryIndex index) const
+    {
+        if (index.value() < m_context.memories.size())
+            return {};
+        return Errors::invalid("MemoryIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> validate(ElementIndex index) const
+    {
+        if (index.value() < m_context.elements.size())
+            return {};
+        return Errors::invalid("ElementIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> validate(DataIndex index) const
+    {
+        if (index.value() < m_context.datas.size())
+            return {};
+        return Errors::invalid("DataIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> validate(GlobalIndex index) const
+    {
+        if (index.value() < m_context.globals.size())
+            return {};
+        return Errors::invalid("GlobalIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> validate(LabelIndex index) const
+    {
+        if (index.value() < m_context.labels.size())
+            return {};
+        return Errors::invalid("LabelIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> validate(LocalIndex index) const
+    {
+        if (index.value() < m_context.locals.size())
+            return {};
+        return Errors::invalid("LocalIndex"sv);
+    }
+
+    ErrorOr<void, ValidationError> 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<StackEntry> {
+    public:
+        // The unknown entry will never be popped off, so we can safely use the original `is_empty`.
+        using Vector<StackEntry>::is_empty;
+        using Vector<StackEntry>::last;
+        using Vector<StackEntry>::at;
+
+        StackEntry take_last()
+        {
+            if (last().is_known)
+                return Vector<StackEntry>::take_last();
+            return last();
+        }
+        void append(StackEntry entry)
+        {
+            if (!entry.is_known)
+                m_did_insert_unknown_entry = true;
+            Vector<StackEntry>::append(entry);
+        }
+
+        size_t actual_size() const { return Vector<StackEntry>::size(); }
+        size_t size() const { return m_did_insert_unknown_entry ? static_cast<size_t>(-1) : actual_size(); }
+
+        Vector<StackEntry> release_vector() { return exchange(static_cast<Vector<StackEntry>&>(*this), Vector<StackEntry> {}); }
+
+        bool operator==(Stack const& other) const;
+
+    private:
+        bool m_did_insert_unknown_entry { false };
+    };
+
+    struct ExpressionTypeResult {
+        Vector<StackEntry> result_types;
+        bool is_constant { false };
+    };
+    ErrorOr<ExpressionTypeResult, ValidationError> validate(Expression const&, Vector<ValueType> const&);
+    ErrorOr<void, ValidationError> validate(Instruction const& instruction, Stack& stack, bool& is_constant);
+    template<u32 opcode>
+    ErrorOr<void, ValidationError> validate_instruction(Instruction const&, Stack& stack, bool& is_constant);
+
+    // Types
+    bool type_is_subtype_of(ValueType const& candidate_subtype, ValueType const& candidate_supertype);
+    ErrorOr<void, ValidationError> validate(Limits const&, size_t k); // n <= 2^k-1 && m? <= 2^k-1
+    ErrorOr<FunctionType, ValidationError> validate(BlockType const&);
+    ErrorOr<void, ValidationError> validate(FunctionType const&) { return {}; }
+    ErrorOr<void, ValidationError> validate(TableType const&);
+    ErrorOr<void, ValidationError> validate(MemoryType const&);
+    ErrorOr<void, ValidationError> 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<typename Expected, typename Given>
+        static ValidationError invalid(StringView name, Expected expected, Given given)
+        {
+            return String::formatted("Invalid {}, expected {} but got {}", name, expected, given);
+        }
+
+        template<typename... Args>
+        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<typename T, typename U, typename V>
+        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<IfDetails, Empty> details;
+    };
+
+    Context m_context;
+    Vector<Context> m_parent_contexts;
+    Vector<ChildScopeKind> m_entered_scopes;
+    Vector<BlockDetails> m_block_details;
+    Vector<FunctionType> m_entered_blocks;
+};
+
+}
+
+template<>
+struct AK::Formatter<Wasm::Validator::StackEntry> : public AK::Formatter<StringView> {
+    void format(FormatBuilder& builder, Wasm::Validator::StackEntry const& value)
+    {
+        if (value.is_known)
+            return Formatter<StringView>::format(builder, Wasm::ValueType::kind_name(value.concrete_type.kind()));
+
+        Formatter<StringView>::format(builder, "<unknown>"sv);
+    }
+};
+
+template<>
+struct AK::Formatter<Wasm::ValueType> : public AK::Formatter<StringView> {
+    void format(FormatBuilder& builder, Wasm::ValueType const& value)
+    {
+        Formatter<StringView>::format(builder, Wasm::ValueType::kind_name(value.kind()));
+    }
+};
+
+template<>
+struct AK::Formatter<Wasm::ValidationError> : public AK::Formatter<StringView> {
+    void format(FormatBuilder& builder, Wasm::ValidationError const& error)
+    {
+        Formatter<StringView>::format(builder, error.error_string);
+    }
+};

+ 1 - 0
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
 )

+ 15 - 0
Userland/Libraries/LibWasm/Forward.h

@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+namespace Wasm {
+
+class AbstractMachine;
+class Validator;
+struct ValidationError;
+
+}

+ 18 - 0
Userland/Libraries/LibWasm/Types.h

@@ -6,6 +6,7 @@
 
 #pragma once
 
+#include <AK/Badge.h>
 #include <AK/Debug.h>
 #include <AK/DistinctNumeric.h>
 #include <AK/MemoryStream.h>
@@ -14,6 +15,7 @@
 #include <AK/String.h>
 #include <AK/Variant.h>
 #include <LibWasm/Constants.h>
+#include <LibWasm/Forward.h>
 #include <LibWasm/Opcode.h>
 
 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<ValueType> local_types, Expression body)
@@ -1026,12 +1036,20 @@ public:
         }
     }
 
+    void set_validation_status(ValidationStatus status, Badge<Validator>) { 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<Module> parse(InputStream& stream);
 
 private:
     void populate_sections();
+    void set_validation_status(ValidationStatus status) { m_validation_status = status; }
 
     Vector<AnySection> m_sections;
     Vector<Function> m_functions;
+    ValidationStatus m_validation_status { ValidationStatus::Unchecked };
+    Optional<String> m_validation_error;
 };
 }