/* * Copyright (c) 2021, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Wasm { template using NativeIntegralType = Conditional>>>; template using NativeFloatingType = Conditional>; template typename SetSign, typename ElementType = SetSign>> using NativeVectorType __attribute__((vector_size(N * sizeof(ElementType)))) = ElementType; template> using NativeFloatingVectorType __attribute__((vector_size(N * sizeof(ElementType)))) = ElementType; template typename SetSign> using Native128ByteVectorOf = NativeVectorType; enum class ParseError { UnexpectedEof, UnknownInstruction, ExpectedFloatingImmediate, ExpectedIndex, ExpectedKindTag, ExpectedSignedImmediate, ExpectedSize, ExpectedValueOrTerminator, InvalidImmediate, InvalidIndex, InvalidInput, InvalidModuleMagic, InvalidModuleVersion, InvalidSize, InvalidTag, InvalidType, HugeAllocationRequested, OutOfMemory, SectionSizeMismatch, InvalidUtf8, DuplicateSection, SectionOutOfOrder, }; WASM_API ByteString parse_error_to_byte_string(ParseError); template using ParseResult = ErrorOr; AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, TypeIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, FunctionIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, TableIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, ElementIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, MemoryIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, TagIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, LocalIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, GlobalIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, LabelIndex); AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, DataIndex); AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, InstructionPointer, Arithmetic, Comparison, Flags, Increment); ParseError with_eof_check(Stream const& stream, ParseError error_if_not_eof); template struct GenericIndexParser { static ParseResult parse(Stream& stream) { auto value_or_error = stream.read_value>(); if (value_or_error.is_error()) return with_eof_check(stream, ParseError::ExpectedIndex); size_t value = value_or_error.release_value(); return T { value }; } }; class ReconsumableStream : public Stream { public: explicit ReconsumableStream(Stream& stream) : m_stream(stream) { } void unread(ReadonlyBytes data) { m_buffer.append(data.data(), data.size()); } private: virtual ErrorOr read_some(Bytes bytes) override { auto original_bytes = bytes; size_t bytes_read_from_buffer = 0; if (!m_buffer.is_empty()) { auto read_size = min(bytes.size(), m_buffer.size()); m_buffer.span().slice(0, read_size).copy_to(bytes); bytes = bytes.slice(read_size); for (size_t i = 0; i < read_size; ++i) m_buffer.take_first(); bytes_read_from_buffer = read_size; } return original_bytes.trim(TRY(m_stream.read_some(bytes)).size() + bytes_read_from_buffer); } virtual bool is_eof() const override { return m_buffer.is_empty() && m_stream.is_eof(); } virtual ErrorOr discard(size_t count) override { size_t bytes_discarded_from_buffer = 0; if (!m_buffer.is_empty()) { auto read_size = min(count, m_buffer.size()); for (size_t i = 0; i < read_size; ++i) m_buffer.take_first(); bytes_discarded_from_buffer = read_size; } return m_stream.discard(count - bytes_discarded_from_buffer); } virtual ErrorOr write_some(ReadonlyBytes) override { return Error::from_errno(EBADF); } virtual bool is_open() const override { return m_stream.is_open(); } virtual void close() override { m_stream.close(); } Stream& m_stream; Vector m_buffer; }; // https://webassembly.github.io/spec/core/bikeshed/#value-types%E2%91%A2 class ValueType { public: enum Kind { I32, I64, F32, F64, V128, FunctionReference, ExternReference, ExceptionReference, UnsupportedHeapReference, // Stub for wasm-gc proposal's reference types. }; explicit ValueType(Kind kind) : m_kind(kind) { } bool operator==(ValueType const&) const = default; auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference || m_kind == UnsupportedHeapReference; } auto is_vector() const { return m_kind == V128; } auto is_numeric() const { return !is_reference() && !is_vector(); } auto kind() const { return m_kind; } static ParseResult parse(Stream& stream); static ByteString kind_name(Kind kind) { switch (kind) { case I32: return "i32"; case I64: return "i64"; case F32: return "f32"; case F64: return "f64"; case V128: return "v128"; case FunctionReference: return "funcref"; case ExternReference: return "externref"; case ExceptionReference: return "exnref"; case UnsupportedHeapReference: return "todo.heapref"; } VERIFY_NOT_REACHED(); } private: Kind m_kind; }; // https://webassembly.github.io/spec/core/bikeshed/#result-types%E2%91%A2 class ResultType { public: explicit ResultType(Vector types) : m_types(move(types)) { } auto const& types() const { return m_types; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_types; }; // https://webassembly.github.io/spec/core/bikeshed/#function-types%E2%91%A4 class FunctionType { public: FunctionType(Vector parameters, Vector results) : m_parameters(move(parameters)) , m_results(move(results)) { } auto& parameters() const { return m_parameters; } auto& results() const { return m_results; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_parameters; Vector m_results; }; // https://webassembly.github.io/memory64/core/bikeshed/#address-type%E2%91%A0 enum class AddressType : u8 { I32, I64, }; // https://webassembly.github.io/spec/core/bikeshed/#limits%E2%91%A5 class Limits { public: explicit Limits(AddressType address_type, u64 min, Optional max = {}) : m_address_type(address_type) , m_min(min) , m_max(move(max)) { } ValueType address_value_type() const { return m_address_type == AddressType::I32 ? ValueType(ValueType::I32) : ValueType(ValueType::I64); } auto address_type() const { return m_address_type; } auto min() const { return m_min; } auto& max() const { return m_max; } bool is_subset_of(Limits other) const { return m_min >= other.min() && (!other.max().has_value() || (m_max.has_value() && *m_max <= *other.max())) && m_address_type == other.m_address_type; } static ParseResult parse(ConstrainedStream& stream); private: AddressType m_address_type { AddressType::I32 }; u64 m_min { 0 }; Optional m_max; }; // https://webassembly.github.io/spec/core/bikeshed/#memory-types%E2%91%A4 class MemoryType { public: explicit MemoryType(Limits limits) : m_limits(move(limits)) { } auto& limits() const { return m_limits; } static ParseResult parse(ConstrainedStream& stream); private: Limits m_limits; }; // https://webassembly.github.io/spec/core/bikeshed/#table-types%E2%91%A4 class TableType { public: explicit TableType(ValueType element_type, Limits limits) : m_element_type(element_type) , m_limits(move(limits)) { VERIFY(m_element_type.is_reference()); } auto& limits() const { return m_limits; } auto& element_type() const { return m_element_type; } static ParseResult parse(ConstrainedStream& stream); private: ValueType m_element_type; Limits m_limits; }; // https://webassembly.github.io/spec/core/bikeshed/#global-types%E2%91%A4 class GlobalType { public: GlobalType(ValueType type, bool is_mutable) : m_type(type) , m_is_mutable(is_mutable) { } auto& type() const { return m_type; } auto is_mutable() const { return m_is_mutable; } static ParseResult parse(ConstrainedStream& stream); private: ValueType m_type; bool m_is_mutable { false }; }; // https://webassembly.github.io/exception-handling/core/binary/types.html#tag-types class TagType { public: enum Flags : u8 { None = 0 }; TagType(TypeIndex type, Flags flags) : m_flags(flags) , m_type(type) { } auto& type() const { return m_type; } auto flags() const { return m_flags; } static ParseResult parse(ConstrainedStream& stream); private: Flags m_flags { None }; TypeIndex m_type; }; // https://webassembly.github.io/spec/core/bikeshed/#binary-blocktype class BlockType { public: enum Kind { Empty, Type, Index, }; BlockType() : m_kind(Empty) , m_empty(0) { } explicit BlockType(ValueType type) : m_kind(Type) , m_value_type(type) { } explicit BlockType(TypeIndex index) : m_kind(Index) , m_type_index(index) { } auto kind() const { return m_kind; } auto& value_type() const { VERIFY(kind() == Type); return m_value_type; } auto& type_index() const { VERIFY(kind() == Index); return m_type_index; } static ParseResult parse(ConstrainedStream& stream); private: Kind m_kind { Empty }; union { ValueType m_value_type; TypeIndex m_type_index; u8 m_empty; }; }; // Proposal "exception-handling" // https://webassembly.github.io/exception-handling/core/binary/instructions.html class Catch { public: Catch(bool ref, TagIndex index, LabelIndex label) // catch[_ref] x l : m_matching_tag_index(index) , m_target_label(label) , m_is_ref(ref) { } explicit Catch(bool ref, LabelIndex label) // catch_all[_ref] l : m_target_label(label) , m_is_ref(ref) { } auto& matching_tag_index() const { return m_matching_tag_index; } auto& target_label() const { return m_target_label; } auto is_ref() const { return m_is_ref; } static ParseResult parse(ConstrainedStream& stream); private: Optional m_matching_tag_index; // None for catch_all LabelIndex m_target_label; bool m_is_ref = false; // true if catch*_ref }; // https://webassembly.github.io/spec/core/bikeshed/#binary-instr // https://webassembly.github.io/spec/core/bikeshed/#reference-instructions%E2%91%A6 // https://webassembly.github.io/spec/core/bikeshed/#parametric-instructions%E2%91%A6 // https://webassembly.github.io/spec/core/bikeshed/#variable-instructions%E2%91%A6 // https://webassembly.github.io/spec/core/bikeshed/#table-instructions%E2%91%A6 // https://webassembly.github.io/spec/core/bikeshed/#memory-instructions%E2%91%A6 // https://webassembly.github.io/spec/core/bikeshed/#numeric-instructions%E2%91%A6 class Instruction { public: explicit Instruction(OpCode opcode) : m_opcode(opcode) , m_arguments(static_cast(0)) { } struct TableElementArgs { ElementIndex element_index; TableIndex table_index; }; struct TableTableArgs { TableIndex lhs; TableIndex rhs; }; struct StructuredInstructionArgs { BlockType block_type; InstructionPointer end_ip; Optional else_ip; }; struct TableBranchArgs { Vector labels; LabelIndex default_; }; struct IndirectCallArgs { TypeIndex type; TableIndex table; }; struct MemoryArgument { u32 align; u64 offset; MemoryIndex memory_index { 0 }; }; struct MemoryAndLaneArgument { MemoryArgument memory; u8 lane; }; struct LaneIndex { u8 lane; }; // Proposal "multi-memory" struct MemoryCopyArgs { MemoryIndex src_index; MemoryIndex dst_index; }; struct MemoryInitArgs { DataIndex data_index; MemoryIndex memory_index; }; struct MemoryIndexArgument { MemoryIndex memory_index; }; // Proposal "exception-handling" struct TryTableArgs { StructuredInstructionArgs try_; // "else" unused. Vector catches; }; struct ShuffleArgument { explicit ShuffleArgument(u8 (&lanes)[16]) : lanes { lanes[0], lanes[1], lanes[2], lanes[3], lanes[4], lanes[5], lanes[6], lanes[7], lanes[8], lanes[9], lanes[10], lanes[11], lanes[12], lanes[13], lanes[14], lanes[15] } { } u8 lanes[16]; }; template explicit Instruction(OpCode opcode, T argument) : m_opcode(opcode) , m_arguments(move(argument)) { } explicit Instruction(OpCode opcode, LocalIndex argument) : m_opcode(opcode) , m_local_index(argument) , m_arguments(static_cast(0)) { } template explicit Instruction(OpCode opcode, LocalIndex argument0, Arg1&& argument1) : m_opcode(opcode) , m_local_index(argument0) , m_arguments(forward(argument1)) { } static ParseResult parse(ConstrainedStream& stream); auto& opcode() const { return m_opcode; } auto& arguments() const { return m_arguments; } auto& arguments() { return m_arguments; } LocalIndex local_index() const { return m_local_index; } private: OpCode m_opcode { 0 }; LocalIndex m_local_index; Variant< BlockType, DataIndex, ElementIndex, FunctionIndex, GlobalIndex, TagIndex, IndirectCallArgs, LabelIndex, LaneIndex, LocalIndex, // Only used by instructions that take more than one local index (currently only fused ops). MemoryArgument, MemoryAndLaneArgument, MemoryCopyArgs, MemoryIndexArgument, MemoryInitArgs, StructuredInstructionArgs, ShuffleArgument, TableBranchArgs, TableElementArgs, TableIndex, TableTableArgs, TryTableArgs, ValueType, Vector, double, float, i32, i64, u128, u8> // Empty state m_arguments; }; struct Dispatch { enum RegisterOrStack : u8 { R0, R1, R2, R3, R4, R5, R6, R7, CountRegisters, Stack = CountRegisters, }; union { OpCode instruction_opcode; FlatPtr handler_ptr; }; Instruction const* instruction { nullptr }; union { struct { RegisterOrStack sources[3]; RegisterOrStack destination; }; u32 sources_and_destination; }; }; struct CompiledInstructions { Vector dispatches; Vector extra_instruction_storage; bool direct = false; // true if all dispatches contain handler_ptr, otherwise false and all contain instruction_opcode. }; template consteval auto as_ordered() { using Type = CommonType; Array result; [&](IntegerSequence) { (void)((result[to_underlying(Vs)] = Is), ...); }(MakeIntegerSequence(sizeof...(Vs))>()); return result; } struct SectionId { public: enum class SectionIdKind : u8 { Custom, Type, Import, Function, Table, Memory, Global, Export, Start, Element, DataCount, Code, Data, Tag, }; constexpr inline static auto section_order = as_ordered< SectionIdKind::Type, SectionIdKind::Import, SectionIdKind::Function, SectionIdKind::Table, SectionIdKind::Memory, SectionIdKind::Tag, SectionIdKind::Global, SectionIdKind::Export, SectionIdKind::Start, SectionIdKind::Element, SectionIdKind::DataCount, SectionIdKind::Code, SectionIdKind::Data, SectionIdKind::Custom>(); explicit SectionId(SectionIdKind kind) : m_kind(kind) { } bool can_appear_after(SectionIdKind other) const { if (kind() == SectionIdKind::Custom || other == SectionIdKind::Custom) return true; auto index = section_order[to_underlying(kind())]; auto other_index = section_order[to_underlying(other)]; return index >= other_index; } SectionIdKind kind() const { return m_kind; } static ParseResult parse(Stream& stream); private: SectionIdKind m_kind; }; class CustomSection { public: CustomSection(ByteString name, ByteBuffer contents) : m_name(move(name)) , m_contents(move(contents)) { } auto& name() const { return m_name; } auto& contents() const { return m_contents; } static ParseResult parse(ConstrainedStream& stream); private: ByteString m_name; ByteBuffer m_contents; }; class TypeSection { public: TypeSection() = default; explicit TypeSection(Vector types) : m_types(move(types)) { } auto& types() const { return m_types; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_types; }; class ImportSection { public: class Import { public: using ImportDesc = Variant; Import(ByteString module, ByteString name, ImportDesc description) : m_module(move(module)) , m_name(move(name)) , m_description(move(description)) { } auto& module() const { return m_module; } auto& name() const { return m_name; } auto& description() const { return m_description; } static ParseResult parse(ConstrainedStream& stream); private: template static ParseResult parse_with_type(auto&& stream, auto&& module, auto&& name) { auto result = TRY(T::parse(stream)); return Import { module, name, result }; } ByteString m_module; ByteString m_name; ImportDesc m_description; }; public: ImportSection() = default; explicit ImportSection(Vector imports) : m_imports(move(imports)) { } auto& imports() const { return m_imports; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_imports; }; class FunctionSection { public: FunctionSection() = default; explicit FunctionSection(Vector types) : m_types(move(types)) { } auto& types() const { return m_types; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_types; }; class TableSection { public: class Table { public: explicit Table(TableType type) : m_type(move(type)) { } auto& type() const { return m_type; } static ParseResult parse(ConstrainedStream& stream); private: TableType m_type; }; public: TableSection() = default; explicit TableSection(Vector
tables) : m_tables(move(tables)) { } auto& tables() const { return m_tables; } static ParseResult parse(ConstrainedStream& stream); private: Vector
m_tables; }; class MemorySection { public: class Memory { public: explicit Memory(MemoryType type) : m_type(move(type)) { } auto& type() const { return m_type; } static ParseResult parse(ConstrainedStream& stream); private: MemoryType m_type; }; public: MemorySection() = default; explicit MemorySection(Vector memories) : m_memories(move(memories)) { } auto& memories() const { return m_memories; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_memories; }; class Expression { public: explicit Expression(Vector instructions) : m_instructions(move(instructions)) { } auto& instructions() const { return m_instructions; } static ParseResult parse(ConstrainedStream& stream, Optional size_hint = {}); void set_stack_usage_hint(size_t value) const { m_stack_usage_hint = value; } auto stack_usage_hint() const { return m_stack_usage_hint; } void set_frame_usage_hint(size_t value) const { m_frame_usage_hint = value; } auto frame_usage_hint() const { return m_frame_usage_hint; } mutable CompiledInstructions compiled_instructions; private: Vector m_instructions; mutable Optional m_stack_usage_hint; mutable Optional m_frame_usage_hint; }; class GlobalSection { public: class Global { public: explicit Global(GlobalType type, Expression expression) : m_type(move(type)) , m_expression(move(expression)) { } auto& type() const { return m_type; } auto& expression() const { return m_expression; } static ParseResult parse(ConstrainedStream& stream); private: GlobalType m_type; Expression m_expression; }; public: GlobalSection() = default; explicit GlobalSection(Vector entries) : m_entries(move(entries)) { } auto& entries() const { return m_entries; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_entries; }; class ExportSection { private: using ExportDesc = Variant; public: class Export { public: explicit Export(ByteString name, ExportDesc description) : m_name(move(name)) , m_description(move(description)) { } auto& name() const { return m_name; } auto& description() const { return m_description; } static ParseResult parse(ConstrainedStream& stream); private: ByteString m_name; ExportDesc m_description; }; ExportSection() = default; explicit ExportSection(Vector entries) : m_entries(move(entries)) { } auto& entries() const { return m_entries; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_entries; }; class StartSection { public: class StartFunction { public: explicit StartFunction(FunctionIndex index) : m_index(index) { } auto& index() const { return m_index; } static ParseResult parse(ConstrainedStream& stream); private: FunctionIndex m_index; }; StartSection() = default; explicit StartSection(Optional func) : m_function(move(func)) { } auto& function() const { return m_function; } static ParseResult parse(ConstrainedStream& stream); private: Optional m_function; }; class ElementSection { public: struct Active { TableIndex index; Expression expression; }; struct Declarative { }; struct Passive { }; struct Element { static ParseResult parse(ConstrainedStream&); ValueType type; Vector init; Variant mode; }; ElementSection() = default; explicit ElementSection(Vector segs) : m_segments(move(segs)) { } auto& segments() const { return m_segments; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_segments; }; class Locals { public: explicit Locals(u32 n, ValueType type) : m_n(n) , m_type(type) { } // Yikes... auto n() const { return m_n; } auto& type() const { return m_type; } static ParseResult parse(ConstrainedStream& stream); private: u32 m_n { 0 }; ValueType m_type; }; class CodeSection { public: // https://webassembly.github.io/spec/core/bikeshed/#binary-func class Func { public: explicit Func(Vector locals, Expression body) : m_locals(move(locals)) , m_body(move(body)) { } auto& locals() const { return m_locals; } auto& body() const { return m_body; } static ParseResult parse(ConstrainedStream& stream, size_t size_hint); private: Vector m_locals; Expression m_body; }; class Code { public: explicit Code(u32 size, Func func) : m_size(size) , m_func(move(func)) { } auto size() const { return m_size; } auto& func() const { return m_func; } static ParseResult parse(ConstrainedStream& stream); private: u32 m_size { 0 }; Func m_func; }; CodeSection() = default; explicit CodeSection(Vector funcs) : m_functions(move(funcs)) { } auto& functions() const { return m_functions; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_functions; }; class DataSection { public: class Data { public: struct Passive { Vector init; }; struct Active { Vector init; MemoryIndex index; Expression offset; }; using Value = Variant; explicit Data(Value value) : m_value(move(value)) { } auto& value() const { return m_value; } static ParseResult parse(ConstrainedStream& stream); private: Value m_value; }; DataSection() = default; explicit DataSection(Vector data) : m_data(move(data)) { } auto& data() const { return m_data; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_data; }; class DataCountSection { public: DataCountSection() = default; explicit DataCountSection(Optional count) : m_count(move(count)) { } auto& count() const { return m_count; } static ParseResult parse(ConstrainedStream& stream); private: Optional m_count; }; class TagSection { public: class Tag { public: using Flags = TagType::Flags; Tag(TypeIndex type, Flags flags) : m_type(type) , m_flags(flags) { } auto type() const { return m_type; } auto flags() const { return m_flags; } static ParseResult parse(ConstrainedStream& stream); private: TypeIndex m_type; Flags m_flags { Flags::None }; }; TagSection() = default; explicit TagSection(Vector tags) : m_tags(move(tags)) { } auto& tags() const { return m_tags; } static ParseResult parse(ConstrainedStream& stream); private: Vector m_tags; }; class WASM_API Module : public RefCounted , public Weakable { public: enum class ValidationStatus { Unchecked, Invalid, Valid, }; static constexpr Array wasm_magic { 0, 'a', 's', 'm' }; static constexpr Array wasm_version { 1, 0, 0, 0 }; Module() = default; auto& custom_sections() { return m_custom_sections; } auto& custom_sections() const { return m_custom_sections; } auto& type_section() const { return m_type_section; } auto& type_section() { return m_type_section; } auto& import_section() const { return m_import_section; } auto& import_section() { return m_import_section; } auto& function_section() { return m_function_section; } auto& function_section() const { return m_function_section; } auto& table_section() { return m_table_section; } auto& table_section() const { return m_table_section; } auto& memory_section() { return m_memory_section; } auto& memory_section() const { return m_memory_section; } auto& global_section() { return m_global_section; } auto& global_section() const { return m_global_section; } auto& export_section() { return m_export_section; } auto& export_section() const { return m_export_section; } auto& start_section() { return m_start_section; } auto& start_section() const { return m_start_section; } auto& element_section() { return m_element_section; } auto& element_section() const { return m_element_section; } auto& code_section() { return m_code_section; } auto& code_section() const { return m_code_section; } auto& data_section() { return m_data_section; } auto& data_section() const { return m_data_section; } auto& data_count_section() { return m_data_count_section; } auto& data_count_section() const { return m_data_count_section; } auto& tag_section() { return m_tag_section; } auto& tag_section() const { return m_tag_section; } void set_validation_status(ValidationStatus status, Badge) { set_validation_status(status); } ValidationStatus validation_status() const { return m_validation_status; } StringView validation_error() const LIFETIME_BOUND { return *m_validation_error; } void set_validation_error(ByteString error) { m_validation_error = move(error); } static ParseResult> parse(Stream& stream); private: void set_validation_status(ValidationStatus status) { m_validation_status = status; } Vector m_custom_sections; TypeSection m_type_section; ImportSection m_import_section; FunctionSection m_function_section; TableSection m_table_section; MemorySection m_memory_section; GlobalSection m_global_section; ExportSection m_export_section; StartSection m_start_section; ElementSection m_element_section; CodeSection m_code_section; DataSection m_data_section; DataCountSection m_data_count_section; TagSection m_tag_section; ValidationStatus m_validation_status { ValidationStatus::Unchecked }; Optional m_validation_error; }; CompiledInstructions try_compile_instructions(Expression const&, Span functions); }