From 6a6f747701c1d7367e15d0141d4712a91a447ad5 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 22 Sep 2025 11:38:44 +0200 Subject: [PATCH 1/8] LibWasm: Add support for proposal 'tail-call' --- .../AbstractMachine/BytecodeInterpreter.cpp | 121 ++++++++++++++---- .../AbstractMachine/BytecodeInterpreter.h | 10 +- .../LibWasm/AbstractMachine/Configuration.cpp | 31 +++-- .../LibWasm/AbstractMachine/Configuration.h | 13 +- .../LibWasm/AbstractMachine/Validator.cpp | 46 +++++++ Libraries/LibWasm/Opcode.h | 2 + Libraries/LibWasm/Parser/Parser.cpp | 11 ++ Libraries/LibWasm/Printer/Printer.cpp | 2 + Meta/generate-libwasm-spec-test.py | 2 +- 9 files changed, 195 insertions(+), 43 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index 229c12f49ac..0f707e7549d 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -68,7 +68,7 @@ struct ConvertToRaw { do { \ if (trap_if_not(x, #x##sv __VA_OPT__(, ) __VA_ARGS__)) { \ dbgln_if(WASM_TRACE_DEBUG, "Trapped because {} failed, at line {}", #x, __LINE__); \ - return true; \ + return Outcome::Return; \ } \ } while (false) @@ -98,11 +98,7 @@ void BytecodeInterpreter::interpret(Configuration& configuration) return interpret_impl(configuration, expression); } -enum class Outcome : u64 { - // 0..Constants::max_allowed_executed_instructions_per_call -> next IP. - Continue = Constants::max_allowed_executed_instructions_per_call + 1, - Return, -}; +constexpr static u32 default_sources_and_destination = (to_underlying(Dispatch::RegisterOrStack::Stack) | (to_underlying(Dispatch::RegisterOrStack::Stack) << 2) | (to_underlying(Dispatch::RegisterOrStack::Stack) << 4)); template struct InstructionHandler { }; @@ -1071,7 +1067,7 @@ HANDLE_INSTRUCTION(synthetic_call_00) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1083,7 +1079,7 @@ HANDLE_INSTRUCTION(synthetic_call_01) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1095,7 +1091,7 @@ HANDLE_INSTRUCTION(synthetic_call_10) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1107,7 +1103,7 @@ HANDLE_INSTRUCTION(synthetic_call_11) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1119,7 +1115,7 @@ HANDLE_INSTRUCTION(synthetic_call_20) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1131,7 +1127,7 @@ HANDLE_INSTRUCTION(synthetic_call_21) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1143,7 +1139,7 @@ HANDLE_INSTRUCTION(synthetic_call_30) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1155,7 +1151,7 @@ HANDLE_INSTRUCTION(synthetic_call_31) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call(#{} -> {})", current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; configuration.regs = regs_copy; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); @@ -1321,11 +1317,31 @@ HANDLE_INSTRUCTION(call) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "call({})", address.value()); - if (interpreter.call_address(configuration, address)) + if (interpreter.call_address(configuration, address) == Outcome::Return) return Outcome::Return; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } +HANDLE_INSTRUCTION(return_call) +{ + auto index = instruction->arguments().get(); + auto address = configuration.frame().module().functions()[index.value()]; + configuration.label_stack().shrink(configuration.frame().label_index() + 1, true); + dbgln_if(WASM_TRACE_DEBUG, "tail call({})", address.value()); + switch (auto const outcome = interpreter.call_address(configuration, address, BytecodeInterpreter::CallAddressSource::DirectTailCall)) { + default: + // Some IP we have to continue from. + current_ip_value = to_underlying(outcome) - 1; + addresses = { .sources_and_destination = default_sources_and_destination }; + cc = configuration.frame().expression().compiled_instructions.dispatches.data(); + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + case Outcome::Return: + return Outcome::Return; + } +} + HANDLE_INSTRUCTION(call_indirect) { auto& args = instruction->arguments().get(); @@ -1346,11 +1362,45 @@ HANDLE_INSTRUCTION(call_indirect) TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results()); dbgln_if(WASM_TRACE_DEBUG, "call_indirect({} -> {})", index, address.value()); - if (interpreter.call_address(configuration, address, BytecodeInterpreter::CallAddressSource::IndirectCall)) + if (interpreter.call_address(configuration, address, BytecodeInterpreter::CallAddressSource::IndirectCall) == Outcome::Return) return Outcome::Return; TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } +HANDLE_INSTRUCTION(return_call_indirect) +{ + auto& args = instruction->arguments().get(); + auto table_address = configuration.frame().module().tables()[args.table.value()]; + auto table_instance = configuration.store().get(table_address); + // bounds checked by verifier. + auto index = configuration.take_source(0, addresses.sources).to(); + TRAP_IN_LOOP_IF_NOT(index >= 0); + TRAP_IN_LOOP_IF_NOT(static_cast(index) < table_instance->elements().size()); + auto& element = table_instance->elements()[index]; + TRAP_IN_LOOP_IF_NOT(element.ref().has()); + auto address = element.ref().get().address; + auto const& type_actual = configuration.store().get(address)->visit([](auto& f) -> decltype(auto) { return f.type(); }); + auto const& type_expected = configuration.frame().module().types()[args.type.value()]; + TRAP_IN_LOOP_IF_NOT(type_actual.parameters().size() == type_expected.parameters().size()); + TRAP_IN_LOOP_IF_NOT(type_actual.results().size() == type_expected.results().size()); + TRAP_IN_LOOP_IF_NOT(type_actual.parameters() == type_expected.parameters()); + TRAP_IN_LOOP_IF_NOT(type_actual.results() == type_expected.results()); + + dbgln_if(WASM_TRACE_DEBUG, "tail call_indirect({} -> {})", index, address.value()); + switch (auto const outcome = interpreter.call_address(configuration, address, BytecodeInterpreter::CallAddressSource::IndirectTailCall)) { + default: + // Some IP we have to continue from. + current_ip_value = to_underlying(outcome) - 1; + addresses = { .sources_and_destination = default_sources_and_destination }; + cc = configuration.frame().expression().compiled_instructions.dispatches.data(); + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + case Outcome::Return: + return Outcome::Return; + } +} + HANDLE_INSTRUCTION(i32_load) { if (interpreter.load_and_push(configuration, *instruction, addresses)) @@ -3633,10 +3683,9 @@ FLATTEN void BytecodeInterpreter::interpret_impl(Configuration& configuration, E auto current_ip_value = configuration.ip(); u64 executed_instructions = 0; - constexpr static u32 default_sources_and_destination = (to_underlying(Dispatch::RegisterOrStack::Stack) | (to_underlying(Dispatch::RegisterOrStack::Stack) << 2) | (to_underlying(Dispatch::RegisterOrStack::Stack) << 4)); SourcesAndDestination addresses { .sources_and_destination = default_sources_and_destination }; - auto const cc = expression.compiled_instructions.dispatches.data(); + auto cc = expression.compiled_instructions.dispatches.data(); if constexpr (HaveDirectThreadingInfo) { static_assert(HasCompiledList, "Direct threading requires a compiled instruction list"); @@ -3678,6 +3727,8 @@ FLATTEN void BytecodeInterpreter::interpret_impl(Configuration& configuration, E if (outcome == Outcome::Return) \ return; \ current_ip_value = to_underlying(outcome); \ + if constexpr (Instructions::name == Instructions::return_call || Instructions::name == Instructions::return_call_indirect) \ + cc = configuration.frame().expression().compiled_instructions.dispatches.data(); \ RUN_NEXT_INSTRUCTION(); \ } @@ -3869,14 +3920,14 @@ VectorType BytecodeInterpreter::pop_vector(Configuration& configuration, size_t return bit_cast(configuration.take_source(source, addresses.sources).to()); } -bool BytecodeInterpreter::call_address(Configuration& configuration, FunctionAddress address, CallAddressSource source) +Outcome BytecodeInterpreter::call_address(Configuration& configuration, FunctionAddress address, CallAddressSource source) { TRAP_IF_NOT(m_stack_info.size_free() >= Constants::minimum_stack_space_to_keep_free, "{}: {}", Constants::stack_exhaustion_message); auto instance = configuration.store().get(address); FunctionType const* type { nullptr }; instance->visit([&](auto const& function) { type = &function.type(); }); - if (source == CallAddressSource::IndirectCall) { + if (source == CallAddressSource::IndirectCall || source == CallAddressSource::IndirectTailCall) { TRAP_IF_NOT(type->parameters().size() <= configuration.value_stack().size()); } Vector args; @@ -3890,16 +3941,34 @@ bool BytecodeInterpreter::call_address(Configuration& configuration, FunctionAdd } Result result { Trap::from_string("") }; - if (instance->has()) { - CallFrameHandle handle { *this, configuration }; - result = configuration.call(*this, address, move(args)); + Outcome final_outcome = Outcome::Continue; + + if (source == CallAddressSource::DirectTailCall || source == CallAddressSource::IndirectTailCall) { + auto prep_outcome = configuration.prepare_call(address, args, true); + if (prep_outcome.is_error()) { + m_trap = prep_outcome.release_error(); + return Outcome::Return; + } + + final_outcome = Outcome::Return; // At this point we can only ever return (unless we succeed in tail-calling). + if (prep_outcome.value().has_value()) { + result = prep_outcome.value()->function()(configuration, args); + } else { + configuration.ip() = 0; + return static_cast(0); // Continue from IP 0 in the new frame. + } } else { - result = configuration.call(*this, address, move(args)); + if (instance->has()) { + CallFrameHandle handle { *this, configuration }; + result = configuration.call(*this, address, move(args)); + } else { + result = configuration.call(*this, address, move(args)); + } } if (result.is_trap()) { m_trap = move(result.trap()); - return true; + return Outcome::Return; } if (!result.values().is_empty()) { @@ -3908,7 +3977,7 @@ bool BytecodeInterpreter::call_address(Configuration& configuration, FunctionAdd configuration.value_stack().unchecked_append(entry); } - return false; + return final_outcome; } template diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h index 7f0938a26b1..a2241130718 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h @@ -21,6 +21,12 @@ union SourcesAndDestination { u32 sources_and_destination; }; +enum class Outcome : u64 { + // 0..Constants::max_allowed_executed_instructions_per_call -> next IP. + Continue = Constants::max_allowed_executed_instructions_per_call + 1, + Return, +}; + struct WASM_API BytecodeInterpreter final : public Interpreter { explicit BytecodeInterpreter(StackInfo const& stack_info) : m_stack_info(stack_info) @@ -59,6 +65,8 @@ struct WASM_API BytecodeInterpreter final : public Interpreter { enum class CallAddressSource { DirectCall, IndirectCall, + DirectTailCall, + IndirectTailCall, }; template @@ -88,7 +96,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter { template typename SetSign, typename VectorType = Native128ByteVectorOf> VectorType pop_vector(Configuration&, size_t source, SourcesAndDestination const&); bool store_to_memory(Configuration&, Instruction::MemoryArgument const&, ReadonlyBytes data, u32 base); - bool call_address(Configuration&, FunctionAddress, CallAddressSource = CallAddressSource::DirectCall); + Outcome call_address(Configuration&, FunctionAddress, CallAddressSource = CallAddressSource::DirectCall); template bool store_to_memory(MemoryInstance&, u64 address, T value); diff --git a/Libraries/LibWasm/AbstractMachine/Configuration.cpp b/Libraries/LibWasm/AbstractMachine/Configuration.cpp index e9d0ad12f0e..7b30d9815d3 100644 --- a/Libraries/LibWasm/AbstractMachine/Configuration.cpp +++ b/Libraries/LibWasm/AbstractMachine/Configuration.cpp @@ -11,7 +11,7 @@ namespace Wasm { -void Configuration::unwind(Badge, CallFrameHandle const&) +void Configuration::unwind_impl() { m_frame_stack.take_last(); m_depth--; @@ -19,11 +19,22 @@ void Configuration::unwind(Badge, CallFrameHandle const&) } Result Configuration::call(Interpreter& interpreter, FunctionAddress address, Vector arguments) +{ + if (auto fn = TRY(prepare_call(address, arguments)); fn.has_value()) + return fn->function()(*this, arguments); + m_ip = 0; + return execute(interpreter); +} + +ErrorOr, Trap> Configuration::prepare_call(FunctionAddress address, Vector& arguments, bool is_tailcall) { auto* function = m_store.get(address); if (!function) return Trap::from_string("Attempt to call nonexistent function by address"); + if (auto* wasm_function = function->get_pointer()) { + if (is_tailcall) + unwind_impl(); // Unwind the current frame, the "return" in the tail-called function will unwind the frame we're gonna push now. Vector locals = move(arguments); locals.ensure_capacity(locals.size() + wasm_function->code().func().locals().size()); for (auto& local : wasm_function->code().func().locals()) { @@ -32,18 +43,16 @@ Result Configuration::call(Interpreter& interpreter, FunctionAddress address, Ve } set_frame(Frame { - wasm_function->module(), - move(locals), - wasm_function->code().func().body(), - wasm_function->type().results().size(), - }); - m_ip = 0; - return execute(interpreter); + wasm_function->module(), + move(locals), + wasm_function->code().func().body(), + wasm_function->type().results().size(), + }, + is_tailcall); + return OptionalNone {}; } - // It better be a host function, else something is really wrong. - auto& host_function = function->get(); - return host_function.function()(*this, arguments); + return function->get(); } Result Configuration::execute(Interpreter& interpreter) diff --git a/Libraries/LibWasm/AbstractMachine/Configuration.h b/Libraries/LibWasm/AbstractMachine/Configuration.h index d724775dbee..4f9a4c147be 100644 --- a/Libraries/LibWasm/AbstractMachine/Configuration.h +++ b/Libraries/LibWasm/AbstractMachine/Configuration.h @@ -19,7 +19,7 @@ public: { } - void set_frame(Frame frame) + void set_frame(Frame frame, bool is_tailcall = false) { auto continuation = frame.expression().instructions().size() - 1; if (auto size = frame.expression().compiled_instructions.dispatches.size(); size > 0) @@ -28,8 +28,10 @@ public: frame.label_index() = m_label_stack.size(); if (auto hint = frame.expression().stack_usage_hint(); hint.has_value()) m_value_stack.ensure_capacity(*hint + m_value_stack.size()); - if (auto hint = frame.expression().frame_usage_hint(); hint.has_value()) - m_label_stack.ensure_capacity(*hint + m_label_stack.size()); + if (!is_tailcall) { + if (auto hint = frame.expression().frame_usage_hint(); hint.has_value()) + m_label_stack.ensure_capacity(*hint + m_label_stack.size()); + } m_frame_stack.append(move(frame)); m_label_stack.append(label); m_locals_base = m_frame_stack.unchecked_last().locals().data(); @@ -66,7 +68,8 @@ public: Configuration& configuration; }; - void unwind(Badge, CallFrameHandle const&); + void unwind(Badge, CallFrameHandle const&) { unwind_impl(); } + ErrorOr, Trap> prepare_call(FunctionAddress, Vector& arguments, bool is_tailcall = false); Result call(Interpreter&, FunctionAddress, Vector arguments); Result execute(Interpreter&); @@ -113,6 +116,8 @@ public: }; private: + void unwind_impl(); + Store& m_store; Vector m_value_stack; Vector m_label_stack; diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index 16935674c39..7e0be084bd4 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -2142,6 +2142,52 @@ VALIDATE_INSTRUCTION(call_indirect) return {}; } +VALIDATE_INSTRUCTION(return_call) +{ + auto index = instruction.arguments().get(); + TRY(validate(index)); + + auto& function_type = m_context.functions[index.value()]; + for (size_t i = 0; i < function_type.parameters().size(); ++i) + TRY(stack.take(function_type.parameters()[function_type.parameters().size() - i - 1])); + + auto const& return_types = m_frames.first().type.results(); + if (return_types != function_type.results()) + return Errors::invalid("return_call target"sv, function_type.results(), return_types); + + m_frames.last().unreachable = true; + stack.resize(m_frames.last().initial_size); + + return {}; +} + +VALIDATE_INSTRUCTION(return_call_indirect) +{ + auto& args = instruction.arguments().get(); + TRY(validate(args.table)); + TRY(validate(args.type)); + + auto& table = m_context.tables[args.table.value()]; + if (table.element_type().kind() != ValueType::FunctionReference) + return Errors::invalid("table element type for call.indirect"sv, "a function reference"sv, table.element_type()); + + auto& type = m_context.types[args.type.value()]; + + TRY(stack.take()); + + for (size_t i = 0; i < type.parameters().size(); ++i) + TRY(stack.take(type.parameters()[type.parameters().size() - i - 1])); + + auto& return_types = m_frames.first().type.results(); + if (return_types != type.results()) + return Errors::invalid("return_call_indirect target"sv, type.results(), return_types); + + m_frames.last().unreachable = true; + stack.resize(m_frames.last().initial_size); + + return {}; +} + VALIDATE_INSTRUCTION(v128_load) { auto& arg = instruction.arguments().get(); diff --git a/Libraries/LibWasm/Opcode.h b/Libraries/LibWasm/Opcode.h index 1f65f06b1c5..88e13eda76d 100644 --- a/Libraries/LibWasm/Opcode.h +++ b/Libraries/LibWasm/Opcode.h @@ -29,6 +29,8 @@ namespace Instructions { M(return_, 0x0f, -1, -1) \ M(call, 0x10, -1, -1) \ M(call_indirect, 0x11, -1, -1) \ + M(return_call, 0x12, -1, -1) \ + M(return_call_indirect, 0x13, -1, -1) \ M(drop, 0x1a, 1, 0) \ M(select, 0x1b, 3, 1) \ M(select_typed, 0x1c, 3, 1) \ diff --git a/Libraries/LibWasm/Parser/Parser.cpp b/Libraries/LibWasm/Parser/Parser.cpp index 987b2c883ed..b67f3889467 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -269,6 +269,17 @@ ParseResult Instruction::parse(ConstrainedStream& stream) auto table_index = TRY(GenericIndexParser::parse(stream)); return Instruction { opcode, IndirectCallArgs { type_index, table_index } }; } + case Instructions::return_call.value(): { + // return_call function + auto function_index = TRY(GenericIndexParser::parse(stream)); + return Instruction { opcode, function_index }; + } + case Instructions::return_call_indirect.value(): { + // return_call_indirect type table + auto type_index = TRY(GenericIndexParser::parse(stream)); + auto table_index = TRY(GenericIndexParser::parse(stream)); + return Instruction { opcode, IndirectCallArgs { type_index, table_index } }; + } case Instructions::i32_load.value(): case Instructions::i64_load.value(): case Instructions::f32_load.value(): diff --git a/Libraries/LibWasm/Printer/Printer.cpp b/Libraries/LibWasm/Printer/Printer.cpp index 623bfd4fe03..3fb5552d75a 100644 --- a/Libraries/LibWasm/Printer/Printer.cpp +++ b/Libraries/LibWasm/Printer/Printer.cpp @@ -721,7 +721,9 @@ HashMap Wasm::Names::instruction_names { { Instructions::br_table, "br.table" }, { Instructions::return_, "return" }, { Instructions::call, "call" }, + { Instructions::return_call, "return_call" }, { Instructions::call_indirect, "call.indirect" }, + { Instructions::return_call_indirect, "return_call.indirect" }, { Instructions::drop, "drop" }, { Instructions::select, "select" }, { Instructions::select_typed, "select.typed" }, diff --git a/Meta/generate-libwasm-spec-test.py b/Meta/generate-libwasm-spec-test.py index 49f687d63bb..254236ca591 100644 --- a/Meta/generate-libwasm-spec-test.py +++ b/Meta/generate-libwasm-spec-test.py @@ -197,7 +197,7 @@ def escape(s: str) -> str: def make_description(input_path: Path, name: str, out_path: Path) -> WastDescription: out_json_path = out_path / f"{name}.json" result = subprocess.run( - ["wast2json", input_path, f"--output={out_json_path}", "--no-check"], + ["wast2json", "--enable-all", input_path, f"--output={out_json_path}", "--no-check"], ) result.check_returncode() with open(out_json_path, "r") as f: From 77237af33f9acd7d439c709dec525b334a913761 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Mon, 22 Sep 2025 13:18:26 +0200 Subject: [PATCH 2/8] LibWasm: Add support for proposal 'extended-const' --- Libraries/LibWasm/AbstractMachine/Validator.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index 7e0be084bd4..89891a970d0 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -521,6 +521,7 @@ VALIDATE_INSTRUCTION(i32_add) { TRY((stack.take())); stack.append(ValueType(ValueType::I32)); + is_constant = true; return {}; } @@ -528,6 +529,7 @@ VALIDATE_INSTRUCTION(i32_sub) { TRY((stack.take())); stack.append(ValueType(ValueType::I32)); + is_constant = true; return {}; } @@ -535,6 +537,7 @@ VALIDATE_INSTRUCTION(i32_mul) { TRY((stack.take())); stack.append(ValueType(ValueType::I32)); + is_constant = true; return {}; } @@ -626,6 +629,7 @@ VALIDATE_INSTRUCTION(i64_add) { TRY((stack.take())); stack.append(ValueType(ValueType::I64)); + is_constant = true; return {}; } @@ -633,6 +637,7 @@ VALIDATE_INSTRUCTION(i64_sub) { TRY((stack.take())); stack.append(ValueType(ValueType::I64)); + is_constant = true; return {}; } @@ -640,6 +645,7 @@ VALIDATE_INSTRUCTION(i64_mul) { TRY((stack.take())); stack.append(ValueType(ValueType::I64)); + is_constant = true; return {}; } From d6f3f5fd51e15a72e685ff4019a2aa0d7cd88ea9 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 23 Sep 2025 04:37:20 +0200 Subject: [PATCH 3/8] LibWasm: Implement proposal 'relaxed-simd' --- .../AbstractMachine/BytecodeInterpreter.cpp | 88 ++- .../AbstractMachine/BytecodeInterpreter.h | 2 +- Libraries/LibWasm/AbstractMachine/Operators.h | 35 +- .../LibWasm/AbstractMachine/Validator.cpp | 100 ++++ Libraries/LibWasm/Opcode.h | 532 +++++++++--------- Libraries/LibWasm/Parser/Parser.cpp | 20 + Libraries/LibWasm/Printer/Printer.cpp | 20 + 7 files changed, 536 insertions(+), 261 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index 0f707e7549d..7386c9d8ce9 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -122,6 +122,16 @@ struct InstructionHandler { }; }; \ template \ Outcome InstructionHandler::operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS)) +#define ALIAS_INSTRUCTION(new_name, existing_name) \ + template<> \ + struct InstructionHandler { \ + template \ + static Outcome operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS)) \ + { \ + TAILCALL return InstructionHandler::operator()( \ + HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); \ + } \ + }; struct Continue { static Outcome operator()(BytecodeInterpreter& interpreter, Configuration& configuration, Instruction const*, SourcesAndDestination addresses, u64 current_ip_value, Dispatch const* cc) @@ -3670,6 +3680,80 @@ HANDLE_INSTRUCTION(i32x4_extmul_high_i16x8_s) TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } +ALIAS_INSTRUCTION(i8x16_relaxed_swizzle, i8x16_swizzle) +ALIAS_INSTRUCTION(i32x4_relaxed_trunc_f32x4_s, i32x4_trunc_sat_f32x4_s) +ALIAS_INSTRUCTION(i32x4_relaxed_trunc_f32x4_u, i32x4_trunc_sat_f32x4_u) +ALIAS_INSTRUCTION(i32x4_relaxed_trunc_f64x2_s_zero, i32x4_trunc_sat_f64x2_s_zero) +ALIAS_INSTRUCTION(i32x4_relaxed_trunc_f64x2_u_zero, i32x4_trunc_sat_f64x2_u_zero) + +HANDLE_INSTRUCTION(f32x4_relaxed_madd) +{ + auto a = configuration.take_source(0, addresses.sources).to(); + auto b = configuration.take_source(1, addresses.sources).to(); + auto& c_slot = configuration.source_value(2, addresses.sources); + auto c = c_slot.to(); + c_slot = Value { Operators::VectorMultiplyAdd<4> {}(a, b, c) }; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +HANDLE_INSTRUCTION(f32x4_relaxed_nmadd) +{ + auto a = configuration.take_source(0, addresses.sources).to(); + auto b = configuration.take_source(1, addresses.sources).to(); + auto& c_slot = configuration.source_value(2, addresses.sources); + auto c = c_slot.to(); + c_slot = Value { Operators::VectorMultiplySub<4> {}(a, b, c) }; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +HANDLE_INSTRUCTION(f64x2_relaxed_madd) +{ + auto a = configuration.take_source(0, addresses.sources).to(); + auto b = configuration.take_source(1, addresses.sources).to(); + auto& c_slot = configuration.source_value(2, addresses.sources); + auto c = c_slot.to(); + c_slot = Value { Operators::VectorMultiplyAdd<2> {}(a, b, c) }; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +HANDLE_INSTRUCTION(f64x2_relaxed_nmadd) +{ + auto a = configuration.take_source(0, addresses.sources).to(); + auto b = configuration.take_source(1, addresses.sources).to(); + auto& c_slot = configuration.source_value(2, addresses.sources); + auto c = c_slot.to(); + c_slot = Value { Operators::VectorMultiplySub<2> {}(a, b, c) }; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +ALIAS_INSTRUCTION(i8x16_relaxed_laneselect, v128_bitselect) +ALIAS_INSTRUCTION(i16x8_relaxed_laneselect, v128_bitselect) +ALIAS_INSTRUCTION(i32x4_relaxed_laneselect, v128_bitselect) +ALIAS_INSTRUCTION(i64x2_relaxed_laneselect, v128_bitselect) +ALIAS_INSTRUCTION(f32x4_relaxed_min, f32x4_min) +ALIAS_INSTRUCTION(f32x4_relaxed_max, f32x4_max) +ALIAS_INSTRUCTION(f64x2_relaxed_min, f64x2_min) +ALIAS_INSTRUCTION(f64x2_relaxed_max, f64x2_max) +ALIAS_INSTRUCTION(i16x8_relaxed_q15mulr_s, i16x8_q15mulr_sat_s) + +HANDLE_INSTRUCTION(i16x8_relaxed_dot_i8x16_i7x16_s) +{ + if (interpreter.binary_numeric_operation>(configuration, addresses)) + return Outcome::Return; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +HANDLE_INSTRUCTION(i32x4_relaxed_dot_i8x16_i7x16_add_s) +{ + // do i16x8 dot first, then fold back down to i32, then do the final component add. + auto rhs = configuration.take_source(0, addresses.sources).to(); + auto lhs = configuration.take_source(1, addresses.sources).to(); // bounds checked by verifier. + auto result = Operators::VectorDotProduct<4, Operators::VectorIntegerExtOpPairwise<4, Operators::Add>> {}(lhs, rhs); + auto& c_slot = configuration.source_value(2, addresses.sources); + c_slot = Value { Operators::VectorIntegerBinaryOp<4, Operators::Add, MakeSigned> {}(result, c_slot.to()) }; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + template constexpr static auto handle_instruction(Args&&... a) { @@ -4000,10 +4084,10 @@ bool BytecodeInterpreter::binary_numeric_operation(Configuration& configuration, return false; } -template +template bool BytecodeInterpreter::unary_operation(Configuration& configuration, SourcesAndDestination const& addresses, Args&&... args) { - auto& entry = configuration.source_value(0, addresses.sources); // bounds checked by verifier. + auto& entry = configuration.source_value(input_arg, addresses.sources); // bounds checked by verifier. auto value = entry.to(); auto call_result = Operator { forward(args)... }(value); PushType result; diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h index a2241130718..82e4c3f38da 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h @@ -104,7 +104,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter { template bool binary_numeric_operation(Configuration&, SourcesAndDestination const&, Args&&...); - template + template bool unary_operation(Configuration&, SourcesAndDestination const&, Args&&...); ALWAYS_INLINE bool set_trap(StringView reason) diff --git a/Libraries/LibWasm/AbstractMachine/Operators.h b/Libraries/LibWasm/AbstractMachine/Operators.h index 4d2f19d273d..23ccb7d5e0b 100644 --- a/Libraries/LibWasm/AbstractMachine/Operators.h +++ b/Libraries/LibWasm/AbstractMachine/Operators.h @@ -51,6 +51,10 @@ DEFINE_BINARY_OPERATOR(BitXor, ^); #undef DEFINE_BINARY_OPERATOR +struct Identity { + auto operator()(auto x) const { return x; } +}; + struct Divide { template auto operator()(Lhs lhs, Rhs rhs) const @@ -738,8 +742,35 @@ struct VectorBitmask { }; template +struct VectorMultiplyAdd { + auto operator()(u128 a1, u128 a2, u128 a3) const + { + using VectorInput = NativeFloatingVectorType<128 / VectorSize, VectorSize, NativeFloatingType<128 / VectorSize>>; + auto a = bit_cast(a1); + auto b = bit_cast(a2); + auto c = bit_cast(a3); + // Spec talks about rounding and such, but v8's arm impl just does vmul + vadd with nothing in between. + return bit_cast(a * b + c); + } +}; + +template +struct VectorMultiplySub { + auto operator()(u128 a1, u128 a2, u128 a3) const + { + using VectorInput = NativeFloatingVectorType<128 / VectorSize, VectorSize, NativeFloatingType<128 / VectorSize>>; + auto a = bit_cast(a1); + auto b = bit_cast(a2); + auto c = bit_cast(a3); + // Spec talks about rounding and such, but v8's arm impl just does vmul + vsub with nothing in between. + return bit_cast(a * b - c); + } +}; + +template struct VectorDotProduct { - auto operator()(u128 lhs, u128 rhs) const + template + auto operator()(u128 lhs, u128 rhs, ContinuationArgs&&... args) const { using VectorInput = NativeVectorType<128 / (VectorSize * 2), VectorSize * 2, MakeSigned>; using VectorResult = NativeVectorType<128 / VectorSize, VectorSize, MakeSigned>; @@ -754,7 +785,7 @@ struct VectorDotProduct { result[i] = low + high; } - return bit_cast(result); + return ContinuationOp { forward(args)... }(bit_cast(result)); } static StringView name() { return "dot"sv; } diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index 89891a970d0..a3c8f83bebb 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -3752,6 +3752,106 @@ VALIDATE_INSTRUCTION(f64x2_convert_low_i32x4_u) return stack.take_and_put(ValueType::V128); } +VALIDATE_INSTRUCTION(i8x16_relaxed_swizzle) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i32x4_relaxed_trunc_f32x4_s) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i32x4_relaxed_trunc_f32x4_u) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i32x4_relaxed_trunc_f64x2_s_zero) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i32x4_relaxed_trunc_f64x2_u_zero) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f32x4_relaxed_madd) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f32x4_relaxed_nmadd) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f64x2_relaxed_madd) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f64x2_relaxed_nmadd) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i8x16_relaxed_laneselect) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i16x8_relaxed_laneselect) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i32x4_relaxed_laneselect) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i64x2_relaxed_laneselect) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f32x4_relaxed_min) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f32x4_relaxed_max) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f64x2_relaxed_min) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(f64x2_relaxed_max) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i16x8_relaxed_q15mulr_s) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i16x8_relaxed_dot_i8x16_i7x16_s) +{ + return stack.take_and_put(ValueType::V128); +} + +VALIDATE_INSTRUCTION(i32x4_relaxed_dot_i8x16_i7x16_add_s) +{ + return stack.take_and_put(ValueType::V128); +} + VALIDATE_INSTRUCTION(synthetic_end_expression) { is_constant = true; diff --git a/Libraries/LibWasm/Opcode.h b/Libraries/LibWasm/Opcode.h index 88e13eda76d..d3bce3065ea 100644 --- a/Libraries/LibWasm/Opcode.h +++ b/Libraries/LibWasm/Opcode.h @@ -203,262 +203,282 @@ namespace Instructions { M(ref_func, 0xd2, 0, 1) // These are synthetic opcodes, they are _not_ seen in wasm with these values. -#define ENUMERATE_MULTI_BYTE_WASM_OPCODES(M) \ - M(i32_trunc_sat_f32_s, 0xfc00000000000000ull, 1, 1) \ - M(i32_trunc_sat_f32_u, 0xfc00000000000001ull, 1, 1) \ - M(i32_trunc_sat_f64_s, 0xfc00000000000002ull, 1, 1) \ - M(i32_trunc_sat_f64_u, 0xfc00000000000003ull, 1, 1) \ - M(i64_trunc_sat_f32_s, 0xfc00000000000004ull, 1, 1) \ - M(i64_trunc_sat_f32_u, 0xfc00000000000005ull, 1, 1) \ - M(i64_trunc_sat_f64_s, 0xfc00000000000006ull, 1, 1) \ - M(i64_trunc_sat_f64_u, 0xfc00000000000007ull, 1, 1) \ - M(memory_init, 0xfc00000000000008ull, 3, 0) \ - M(data_drop, 0xfc00000000000009ull, 0, 0) \ - M(memory_copy, 0xfc0000000000000aull, 3, 0) \ - M(memory_fill, 0xfc0000000000000bull, 3, 0) \ - M(table_init, 0xfc0000000000000cull, 3, 0) \ - M(elem_drop, 0xfc0000000000000dull, 0, 0) \ - M(table_copy, 0xfc0000000000000eull, 3, 0) \ - M(table_grow, 0xfc0000000000000full, 2, 1) \ - M(table_size, 0xfc00000000000010ull, 0, 1) \ - M(table_fill, 0xfc00000000000011ull, 3, 0) \ - M(v128_load, 0xfd00000000000000ull, 1, 1) \ - M(v128_load8x8_s, 0xfd00000000000001ull, 1, 1) \ - M(v128_load8x8_u, 0xfd00000000000002ull, 1, 1) \ - M(v128_load16x4_s, 0xfd00000000000003ull, 1, 1) \ - M(v128_load16x4_u, 0xfd00000000000004ull, 1, 1) \ - M(v128_load32x2_s, 0xfd00000000000005ull, 1, 1) \ - M(v128_load32x2_u, 0xfd00000000000006ull, 1, 1) \ - M(v128_load8_splat, 0xfd00000000000007ull, 1, 1) \ - M(v128_load16_splat, 0xfd00000000000008ull, 1, 1) \ - M(v128_load32_splat, 0xfd00000000000009ull, 1, 1) \ - M(v128_load64_splat, 0xfd0000000000000aull, 1, 1) \ - M(v128_store, 0xfd0000000000000bull, 2, 0) \ - M(v128_const, 0xfd0000000000000cull, 0, 1) \ - M(i8x16_shuffle, 0xfd0000000000000dull, 2, 1) \ - M(i8x16_swizzle, 0xfd0000000000000eull, 2, 1) \ - M(i8x16_splat, 0xfd0000000000000full, 1, 1) \ - M(i16x8_splat, 0xfd00000000000010ull, 1, 1) \ - M(i32x4_splat, 0xfd00000000000011ull, 1, 1) \ - M(i64x2_splat, 0xfd00000000000012ull, 1, 1) \ - M(f32x4_splat, 0xfd00000000000013ull, 1, 1) \ - M(f64x2_splat, 0xfd00000000000014ull, 1, 1) \ - M(i8x16_extract_lane_s, 0xfd00000000000015ull, 1, 1) \ - M(i8x16_extract_lane_u, 0xfd00000000000016ull, 1, 1) \ - M(i8x16_replace_lane, 0xfd00000000000017ull, 2, 1) \ - M(i16x8_extract_lane_s, 0xfd00000000000018ull, 1, 1) \ - M(i16x8_extract_lane_u, 0xfd00000000000019ull, 1, 1) \ - M(i16x8_replace_lane, 0xfd0000000000001aull, 2, 1) \ - M(i32x4_extract_lane, 0xfd0000000000001bull, 1, 1) \ - M(i32x4_replace_lane, 0xfd0000000000001cull, 2, 1) \ - M(i64x2_extract_lane, 0xfd0000000000001dull, 1, 1) \ - M(i64x2_replace_lane, 0xfd0000000000001eull, 2, 1) \ - M(f32x4_extract_lane, 0xfd0000000000001full, 1, 1) \ - M(f32x4_replace_lane, 0xfd00000000000020ull, 2, 1) \ - M(f64x2_extract_lane, 0xfd00000000000021ull, 1, 1) \ - M(f64x2_replace_lane, 0xfd00000000000022ull, 2, 1) \ - M(i8x16_eq, 0xfd00000000000023ull, 2, 1) \ - M(i8x16_ne, 0xfd00000000000024ull, 2, 1) \ - M(i8x16_lt_s, 0xfd00000000000025ull, 2, 1) \ - M(i8x16_lt_u, 0xfd00000000000026ull, 2, 1) \ - M(i8x16_gt_s, 0xfd00000000000027ull, 2, 1) \ - M(i8x16_gt_u, 0xfd00000000000028ull, 2, 1) \ - M(i8x16_le_s, 0xfd00000000000029ull, 2, 1) \ - M(i8x16_le_u, 0xfd0000000000002aull, 2, 1) \ - M(i8x16_ge_s, 0xfd0000000000002bull, 2, 1) \ - M(i8x16_ge_u, 0xfd0000000000002cull, 2, 1) \ - M(i16x8_eq, 0xfd0000000000002dull, 2, 1) \ - M(i16x8_ne, 0xfd0000000000002eull, 2, 1) \ - M(i16x8_lt_s, 0xfd0000000000002full, 2, 1) \ - M(i16x8_lt_u, 0xfd00000000000030ull, 2, 1) \ - M(i16x8_gt_s, 0xfd00000000000031ull, 2, 1) \ - M(i16x8_gt_u, 0xfd00000000000032ull, 2, 1) \ - M(i16x8_le_s, 0xfd00000000000033ull, 2, 1) \ - M(i16x8_le_u, 0xfd00000000000034ull, 2, 1) \ - M(i16x8_ge_s, 0xfd00000000000035ull, 2, 1) \ - M(i16x8_ge_u, 0xfd00000000000036ull, 2, 1) \ - M(i32x4_eq, 0xfd00000000000037ull, 2, 1) \ - M(i32x4_ne, 0xfd00000000000038ull, 2, 1) \ - M(i32x4_lt_s, 0xfd00000000000039ull, 2, 1) \ - M(i32x4_lt_u, 0xfd0000000000003aull, 2, 1) \ - M(i32x4_gt_s, 0xfd0000000000003bull, 2, 1) \ - M(i32x4_gt_u, 0xfd0000000000003cull, 2, 1) \ - M(i32x4_le_s, 0xfd0000000000003dull, 2, 1) \ - M(i32x4_le_u, 0xfd0000000000003eull, 2, 1) \ - M(i32x4_ge_s, 0xfd0000000000003full, 2, 1) \ - M(i32x4_ge_u, 0xfd00000000000040ull, 2, 1) \ - M(f32x4_eq, 0xfd00000000000041ull, 2, 1) \ - M(f32x4_ne, 0xfd00000000000042ull, 2, 1) \ - M(f32x4_lt, 0xfd00000000000043ull, 2, 1) \ - M(f32x4_gt, 0xfd00000000000044ull, 2, 1) \ - M(f32x4_le, 0xfd00000000000045ull, 2, 1) \ - M(f32x4_ge, 0xfd00000000000046ull, 2, 1) \ - M(f64x2_eq, 0xfd00000000000047ull, 2, 1) \ - M(f64x2_ne, 0xfd00000000000048ull, 2, 1) \ - M(f64x2_lt, 0xfd00000000000049ull, 2, 1) \ - M(f64x2_gt, 0xfd0000000000004aull, 2, 1) \ - M(f64x2_le, 0xfd0000000000004bull, 2, 1) \ - M(f64x2_ge, 0xfd0000000000004cull, 2, 1) \ - M(v128_not, 0xfd0000000000004dull, 1, 1) \ - M(v128_and, 0xfd0000000000004eull, 2, 1) \ - M(v128_andnot, 0xfd0000000000004full, 2, 1) \ - M(v128_or, 0xfd00000000000050ull, 2, 1) \ - M(v128_xor, 0xfd00000000000051ull, 2, 1) \ - M(v128_bitselect, 0xfd00000000000052ull, 3, 1) \ - M(v128_any_true, 0xfd00000000000053ull, 1, 1) \ - M(v128_load8_lane, 0xfd00000000000054ull, 2, 1) \ - M(v128_load16_lane, 0xfd00000000000055ull, 2, 1) \ - M(v128_load32_lane, 0xfd00000000000056ull, 2, 1) \ - M(v128_load64_lane, 0xfd00000000000057ull, 2, 1) \ - M(v128_store8_lane, 0xfd00000000000058ull, 2, 0) \ - M(v128_store16_lane, 0xfd00000000000059ull, 2, 0) \ - M(v128_store32_lane, 0xfd0000000000005aull, 2, 0) \ - M(v128_store64_lane, 0xfd0000000000005bull, 2, 0) \ - M(v128_load32_zero, 0xfd0000000000005cull, 1, 1) \ - M(v128_load64_zero, 0xfd0000000000005dull, 1, 1) \ - M(f32x4_demote_f64x2_zero, 0xfd0000000000005eull, 1, 1) \ - M(f64x2_promote_low_f32x4, 0xfd0000000000005full, 1, 1) \ - M(i8x16_abs, 0xfd00000000000060ull, 1, 1) \ - M(i8x16_neg, 0xfd00000000000061ull, 1, 1) \ - M(i8x16_popcnt, 0xfd00000000000062ull, 1, 1) \ - M(i8x16_all_true, 0xfd00000000000063ull, 1, 1) \ - M(i8x16_bitmask, 0xfd00000000000064ull, 1, 1) \ - M(i8x16_narrow_i16x8_s, 0xfd00000000000065ull, 2, 1) \ - M(i8x16_narrow_i16x8_u, 0xfd00000000000066ull, 2, 1) \ - M(f32x4_ceil, 0xfd00000000000067ull, 1, 1) \ - M(f32x4_floor, 0xfd00000000000068ull, 1, 1) \ - M(f32x4_trunc, 0xfd00000000000069ull, 1, 1) \ - M(f32x4_nearest, 0xfd0000000000006aull, 1, 1) \ - M(i8x16_shl, 0xfd0000000000006bull, 2, 1) \ - M(i8x16_shr_s, 0xfd0000000000006cull, 2, 1) \ - M(i8x16_shr_u, 0xfd0000000000006dull, 2, 1) \ - M(i8x16_add, 0xfd0000000000006eull, 2, 1) \ - M(i8x16_add_sat_s, 0xfd0000000000006full, 2, 1) \ - M(i8x16_add_sat_u, 0xfd00000000000070ull, 2, 1) \ - M(i8x16_sub, 0xfd00000000000071ull, 2, 1) \ - M(i8x16_sub_sat_s, 0xfd00000000000072ull, 2, 1) \ - M(i8x16_sub_sat_u, 0xfd00000000000073ull, 2, 1) \ - M(f64x2_ceil, 0xfd00000000000074ull, 1, 1) \ - M(f64x2_floor, 0xfd00000000000075ull, 1, 1) \ - M(i8x16_min_s, 0xfd00000000000076ull, 2, 1) \ - M(i8x16_min_u, 0xfd00000000000077ull, 2, 1) \ - M(i8x16_max_s, 0xfd00000000000078ull, 2, 1) \ - M(i8x16_max_u, 0xfd00000000000079ull, 2, 1) \ - M(f64x2_trunc, 0xfd0000000000007aull, 1, 1) \ - M(i8x16_avgr_u, 0xfd0000000000007bull, 2, 1) \ - M(i16x8_extadd_pairwise_i8x16_s, 0xfd0000000000007cull, 1, 1) \ - M(i16x8_extadd_pairwise_i8x16_u, 0xfd0000000000007dull, 1, 1) \ - M(i32x4_extadd_pairwise_i16x8_s, 0xfd0000000000007eull, 1, 1) \ - M(i32x4_extadd_pairwise_i16x8_u, 0xfd0000000000007full, 1, 1) \ - M(i16x8_abs, 0xfd00000000000080ull, 1, 1) \ - M(i16x8_neg, 0xfd00000000000081ull, 1, 1) \ - M(i16x8_q15mulr_sat_s, 0xfd00000000000082ull, 2, 1) \ - M(i16x8_all_true, 0xfd00000000000083ull, 1, 1) \ - M(i16x8_bitmask, 0xfd00000000000084ull, 1, 1) \ - M(i16x8_narrow_i32x4_s, 0xfd00000000000085ull, 2, 1) \ - M(i16x8_narrow_i32x4_u, 0xfd00000000000086ull, 2, 1) \ - M(i16x8_extend_low_i8x16_s, 0xfd00000000000087ull, 1, 1) \ - M(i16x8_extend_high_i8x16_s, 0xfd00000000000088ull, 1, 1) \ - M(i16x8_extend_low_i8x16_u, 0xfd00000000000089ull, 1, 1) \ - M(i16x8_extend_high_i8x16_u, 0xfd0000000000008aull, 1, 1) \ - M(i16x8_shl, 0xfd0000000000008bull, 2, 1) \ - M(i16x8_shr_s, 0xfd0000000000008cull, 2, 1) \ - M(i16x8_shr_u, 0xfd0000000000008dull, 2, 1) \ - M(i16x8_add, 0xfd0000000000008eull, 2, 1) \ - M(i16x8_add_sat_s, 0xfd0000000000008full, 2, 1) \ - M(i16x8_add_sat_u, 0xfd00000000000090ull, 2, 1) \ - M(i16x8_sub, 0xfd00000000000091ull, 2, 1) \ - M(i16x8_sub_sat_s, 0xfd00000000000092ull, 2, 1) \ - M(i16x8_sub_sat_u, 0xfd00000000000093ull, 2, 1) \ - M(f64x2_nearest, 0xfd00000000000094ull, 1, 1) \ - M(i16x8_mul, 0xfd00000000000095ull, 2, 1) \ - M(i16x8_min_s, 0xfd00000000000096ull, 2, 1) \ - M(i16x8_min_u, 0xfd00000000000097ull, 2, 1) \ - M(i16x8_max_s, 0xfd00000000000098ull, 2, 1) \ - M(i16x8_max_u, 0xfd00000000000099ull, 2, 1) \ - M(i16x8_avgr_u, 0xfd0000000000009bull, 2, 1) \ - M(i16x8_extmul_low_i8x16_s, 0xfd0000000000009cull, 2, 1) \ - M(i16x8_extmul_high_i8x16_s, 0xfd0000000000009dull, 2, 1) \ - M(i16x8_extmul_low_i8x16_u, 0xfd0000000000009eull, 2, 1) \ - M(i16x8_extmul_high_i8x16_u, 0xfd0000000000009full, 2, 1) \ - M(i32x4_abs, 0xfd000000000000a0ull, 1, 1) \ - M(i32x4_neg, 0xfd000000000000a1ull, 1, 1) \ - M(i32x4_all_true, 0xfd000000000000a3ull, 1, 1) \ - M(i32x4_bitmask, 0xfd000000000000a4ull, 1, 1) \ - M(i32x4_extend_low_i16x8_s, 0xfd000000000000a7ull, 1, 1) \ - M(i32x4_extend_high_i16x8_s, 0xfd000000000000a8ull, 1, 1) \ - M(i32x4_extend_low_i16x8_u, 0xfd000000000000a9ull, 1, 1) \ - M(i32x4_extend_high_i16x8_u, 0xfd000000000000aaull, 1, 1) \ - M(i32x4_shl, 0xfd000000000000abull, 2, 1) \ - M(i32x4_shr_s, 0xfd000000000000acull, 2, 1) \ - M(i32x4_shr_u, 0xfd000000000000adull, 2, 1) \ - M(i32x4_add, 0xfd000000000000aeull, 2, 1) \ - M(i32x4_sub, 0xfd000000000000b1ull, 2, 1) \ - M(i32x4_mul, 0xfd000000000000b5ull, 2, 1) \ - M(i32x4_min_s, 0xfd000000000000b6ull, 2, 1) \ - M(i32x4_min_u, 0xfd000000000000b7ull, 2, 1) \ - M(i32x4_max_s, 0xfd000000000000b8ull, 2, 1) \ - M(i32x4_max_u, 0xfd000000000000b9ull, 2, 1) \ - M(i32x4_dot_i16x8_s, 0xfd000000000000baull, 2, 1) \ - M(i32x4_extmul_low_i16x8_s, 0xfd000000000000bcull, 2, 1) \ - M(i32x4_extmul_high_i16x8_s, 0xfd000000000000bdull, 2, 1) \ - M(i32x4_extmul_low_i16x8_u, 0xfd000000000000beull, 2, 1) \ - M(i32x4_extmul_high_i16x8_u, 0xfd000000000000bfull, 2, 1) \ - M(i64x2_abs, 0xfd000000000000c0ull, 1, 1) \ - M(i64x2_neg, 0xfd000000000000c1ull, 1, 1) \ - M(i64x2_all_true, 0xfd000000000000c3ull, 1, 1) \ - M(i64x2_bitmask, 0xfd000000000000c4ull, 1, 1) \ - M(i64x2_extend_low_i32x4_s, 0xfd000000000000c7ull, 1, 1) \ - M(i64x2_extend_high_i32x4_s, 0xfd000000000000c8ull, 1, 1) \ - M(i64x2_extend_low_i32x4_u, 0xfd000000000000c9ull, 1, 1) \ - M(i64x2_extend_high_i32x4_u, 0xfd000000000000caull, 1, 1) \ - M(i64x2_shl, 0xfd000000000000cbull, 2, 1) \ - M(i64x2_shr_s, 0xfd000000000000ccull, 2, 1) \ - M(i64x2_shr_u, 0xfd000000000000cdull, 2, 1) \ - M(i64x2_add, 0xfd000000000000ceull, 2, 1) \ - M(i64x2_sub, 0xfd000000000000d1ull, 2, 1) \ - M(i64x2_mul, 0xfd000000000000d5ull, 2, 1) \ - M(i64x2_eq, 0xfd000000000000d6ull, 2, 1) \ - M(i64x2_ne, 0xfd000000000000d7ull, 2, 1) \ - M(i64x2_lt_s, 0xfd000000000000d8ull, 2, 1) \ - M(i64x2_gt_s, 0xfd000000000000d9ull, 2, 1) \ - M(i64x2_le_s, 0xfd000000000000daull, 2, 1) \ - M(i64x2_ge_s, 0xfd000000000000dbull, 2, 1) \ - M(i64x2_extmul_low_i32x4_s, 0xfd000000000000dcull, 2, 1) \ - M(i64x2_extmul_high_i32x4_s, 0xfd000000000000ddull, 2, 1) \ - M(i64x2_extmul_low_i32x4_u, 0xfd000000000000deull, 2, 1) \ - M(i64x2_extmul_high_i32x4_u, 0xfd000000000000dfull, 2, 1) \ - M(f32x4_abs, 0xfd000000000000e0ull, 1, 1) \ - M(f32x4_neg, 0xfd000000000000e1ull, 1, 1) \ - M(f32x4_sqrt, 0xfd000000000000e3ull, 1, 1) \ - M(f32x4_add, 0xfd000000000000e4ull, 2, 1) \ - M(f32x4_sub, 0xfd000000000000e5ull, 2, 1) \ - M(f32x4_mul, 0xfd000000000000e6ull, 2, 1) \ - M(f32x4_div, 0xfd000000000000e7ull, 2, 1) \ - M(f32x4_min, 0xfd000000000000e8ull, 2, 1) \ - M(f32x4_max, 0xfd000000000000e9ull, 2, 1) \ - M(f32x4_pmin, 0xfd000000000000eaull, 2, 1) \ - M(f32x4_pmax, 0xfd000000000000ebull, 2, 1) \ - M(f64x2_abs, 0xfd000000000000ecull, 1, 1) \ - M(f64x2_neg, 0xfd000000000000edull, 1, 1) \ - M(f64x2_sqrt, 0xfd000000000000efull, 1, 1) \ - M(f64x2_add, 0xfd000000000000f0ull, 2, 1) \ - M(f64x2_sub, 0xfd000000000000f1ull, 2, 1) \ - M(f64x2_mul, 0xfd000000000000f2ull, 2, 1) \ - M(f64x2_div, 0xfd000000000000f3ull, 2, 1) \ - M(f64x2_min, 0xfd000000000000f4ull, 2, 1) \ - M(f64x2_max, 0xfd000000000000f5ull, 2, 1) \ - M(f64x2_pmin, 0xfd000000000000f6ull, 2, 1) \ - M(f64x2_pmax, 0xfd000000000000f7ull, 2, 1) \ - M(i32x4_trunc_sat_f32x4_s, 0xfd000000000000f8ull, 1, 1) \ - M(i32x4_trunc_sat_f32x4_u, 0xfd000000000000f9ull, 1, 1) \ - M(f32x4_convert_i32x4_s, 0xfd000000000000faull, 1, 1) \ - M(f32x4_convert_i32x4_u, 0xfd000000000000fbull, 1, 1) \ - M(i32x4_trunc_sat_f64x2_s_zero, 0xfd000000000000fcull, 1, 1) \ - M(i32x4_trunc_sat_f64x2_u_zero, 0xfd000000000000fdull, 1, 1) \ - M(f64x2_convert_low_i32x4_s, 0xfd000000000000feull, 1, 1) \ - M(f64x2_convert_low_i32x4_u, 0xfd000000000000ffull, 1, 1) \ - /* Synthetic fused insns */ \ +#define ENUMERATE_MULTI_BYTE_WASM_OPCODES(M) \ + M(i32_trunc_sat_f32_s, 0xfc00000000000000ull, 1, 1) \ + M(i32_trunc_sat_f32_u, 0xfc00000000000001ull, 1, 1) \ + M(i32_trunc_sat_f64_s, 0xfc00000000000002ull, 1, 1) \ + M(i32_trunc_sat_f64_u, 0xfc00000000000003ull, 1, 1) \ + M(i64_trunc_sat_f32_s, 0xfc00000000000004ull, 1, 1) \ + M(i64_trunc_sat_f32_u, 0xfc00000000000005ull, 1, 1) \ + M(i64_trunc_sat_f64_s, 0xfc00000000000006ull, 1, 1) \ + M(i64_trunc_sat_f64_u, 0xfc00000000000007ull, 1, 1) \ + M(memory_init, 0xfc00000000000008ull, 3, 0) \ + M(data_drop, 0xfc00000000000009ull, 0, 0) \ + M(memory_copy, 0xfc0000000000000aull, 3, 0) \ + M(memory_fill, 0xfc0000000000000bull, 3, 0) \ + M(table_init, 0xfc0000000000000cull, 3, 0) \ + M(elem_drop, 0xfc0000000000000dull, 0, 0) \ + M(table_copy, 0xfc0000000000000eull, 3, 0) \ + M(table_grow, 0xfc0000000000000full, 2, 1) \ + M(table_size, 0xfc00000000000010ull, 0, 1) \ + M(table_fill, 0xfc00000000000011ull, 3, 0) \ + M(v128_load, 0xfd00000000000000ull, 1, 1) \ + M(v128_load8x8_s, 0xfd00000000000001ull, 1, 1) \ + M(v128_load8x8_u, 0xfd00000000000002ull, 1, 1) \ + M(v128_load16x4_s, 0xfd00000000000003ull, 1, 1) \ + M(v128_load16x4_u, 0xfd00000000000004ull, 1, 1) \ + M(v128_load32x2_s, 0xfd00000000000005ull, 1, 1) \ + M(v128_load32x2_u, 0xfd00000000000006ull, 1, 1) \ + M(v128_load8_splat, 0xfd00000000000007ull, 1, 1) \ + M(v128_load16_splat, 0xfd00000000000008ull, 1, 1) \ + M(v128_load32_splat, 0xfd00000000000009ull, 1, 1) \ + M(v128_load64_splat, 0xfd0000000000000aull, 1, 1) \ + M(v128_store, 0xfd0000000000000bull, 2, 0) \ + M(v128_const, 0xfd0000000000000cull, 0, 1) \ + M(i8x16_shuffle, 0xfd0000000000000dull, 2, 1) \ + M(i8x16_swizzle, 0xfd0000000000000eull, 2, 1) \ + M(i8x16_splat, 0xfd0000000000000full, 1, 1) \ + M(i16x8_splat, 0xfd00000000000010ull, 1, 1) \ + M(i32x4_splat, 0xfd00000000000011ull, 1, 1) \ + M(i64x2_splat, 0xfd00000000000012ull, 1, 1) \ + M(f32x4_splat, 0xfd00000000000013ull, 1, 1) \ + M(f64x2_splat, 0xfd00000000000014ull, 1, 1) \ + M(i8x16_extract_lane_s, 0xfd00000000000015ull, 1, 1) \ + M(i8x16_extract_lane_u, 0xfd00000000000016ull, 1, 1) \ + M(i8x16_replace_lane, 0xfd00000000000017ull, 2, 1) \ + M(i16x8_extract_lane_s, 0xfd00000000000018ull, 1, 1) \ + M(i16x8_extract_lane_u, 0xfd00000000000019ull, 1, 1) \ + M(i16x8_replace_lane, 0xfd0000000000001aull, 2, 1) \ + M(i32x4_extract_lane, 0xfd0000000000001bull, 1, 1) \ + M(i32x4_replace_lane, 0xfd0000000000001cull, 2, 1) \ + M(i64x2_extract_lane, 0xfd0000000000001dull, 1, 1) \ + M(i64x2_replace_lane, 0xfd0000000000001eull, 2, 1) \ + M(f32x4_extract_lane, 0xfd0000000000001full, 1, 1) \ + M(f32x4_replace_lane, 0xfd00000000000020ull, 2, 1) \ + M(f64x2_extract_lane, 0xfd00000000000021ull, 1, 1) \ + M(f64x2_replace_lane, 0xfd00000000000022ull, 2, 1) \ + M(i8x16_eq, 0xfd00000000000023ull, 2, 1) \ + M(i8x16_ne, 0xfd00000000000024ull, 2, 1) \ + M(i8x16_lt_s, 0xfd00000000000025ull, 2, 1) \ + M(i8x16_lt_u, 0xfd00000000000026ull, 2, 1) \ + M(i8x16_gt_s, 0xfd00000000000027ull, 2, 1) \ + M(i8x16_gt_u, 0xfd00000000000028ull, 2, 1) \ + M(i8x16_le_s, 0xfd00000000000029ull, 2, 1) \ + M(i8x16_le_u, 0xfd0000000000002aull, 2, 1) \ + M(i8x16_ge_s, 0xfd0000000000002bull, 2, 1) \ + M(i8x16_ge_u, 0xfd0000000000002cull, 2, 1) \ + M(i16x8_eq, 0xfd0000000000002dull, 2, 1) \ + M(i16x8_ne, 0xfd0000000000002eull, 2, 1) \ + M(i16x8_lt_s, 0xfd0000000000002full, 2, 1) \ + M(i16x8_lt_u, 0xfd00000000000030ull, 2, 1) \ + M(i16x8_gt_s, 0xfd00000000000031ull, 2, 1) \ + M(i16x8_gt_u, 0xfd00000000000032ull, 2, 1) \ + M(i16x8_le_s, 0xfd00000000000033ull, 2, 1) \ + M(i16x8_le_u, 0xfd00000000000034ull, 2, 1) \ + M(i16x8_ge_s, 0xfd00000000000035ull, 2, 1) \ + M(i16x8_ge_u, 0xfd00000000000036ull, 2, 1) \ + M(i32x4_eq, 0xfd00000000000037ull, 2, 1) \ + M(i32x4_ne, 0xfd00000000000038ull, 2, 1) \ + M(i32x4_lt_s, 0xfd00000000000039ull, 2, 1) \ + M(i32x4_lt_u, 0xfd0000000000003aull, 2, 1) \ + M(i32x4_gt_s, 0xfd0000000000003bull, 2, 1) \ + M(i32x4_gt_u, 0xfd0000000000003cull, 2, 1) \ + M(i32x4_le_s, 0xfd0000000000003dull, 2, 1) \ + M(i32x4_le_u, 0xfd0000000000003eull, 2, 1) \ + M(i32x4_ge_s, 0xfd0000000000003full, 2, 1) \ + M(i32x4_ge_u, 0xfd00000000000040ull, 2, 1) \ + M(f32x4_eq, 0xfd00000000000041ull, 2, 1) \ + M(f32x4_ne, 0xfd00000000000042ull, 2, 1) \ + M(f32x4_lt, 0xfd00000000000043ull, 2, 1) \ + M(f32x4_gt, 0xfd00000000000044ull, 2, 1) \ + M(f32x4_le, 0xfd00000000000045ull, 2, 1) \ + M(f32x4_ge, 0xfd00000000000046ull, 2, 1) \ + M(f64x2_eq, 0xfd00000000000047ull, 2, 1) \ + M(f64x2_ne, 0xfd00000000000048ull, 2, 1) \ + M(f64x2_lt, 0xfd00000000000049ull, 2, 1) \ + M(f64x2_gt, 0xfd0000000000004aull, 2, 1) \ + M(f64x2_le, 0xfd0000000000004bull, 2, 1) \ + M(f64x2_ge, 0xfd0000000000004cull, 2, 1) \ + M(v128_not, 0xfd0000000000004dull, 1, 1) \ + M(v128_and, 0xfd0000000000004eull, 2, 1) \ + M(v128_andnot, 0xfd0000000000004full, 2, 1) \ + M(v128_or, 0xfd00000000000050ull, 2, 1) \ + M(v128_xor, 0xfd00000000000051ull, 2, 1) \ + M(v128_bitselect, 0xfd00000000000052ull, 3, 1) \ + M(v128_any_true, 0xfd00000000000053ull, 1, 1) \ + M(v128_load8_lane, 0xfd00000000000054ull, 2, 1) \ + M(v128_load16_lane, 0xfd00000000000055ull, 2, 1) \ + M(v128_load32_lane, 0xfd00000000000056ull, 2, 1) \ + M(v128_load64_lane, 0xfd00000000000057ull, 2, 1) \ + M(v128_store8_lane, 0xfd00000000000058ull, 2, 0) \ + M(v128_store16_lane, 0xfd00000000000059ull, 2, 0) \ + M(v128_store32_lane, 0xfd0000000000005aull, 2, 0) \ + M(v128_store64_lane, 0xfd0000000000005bull, 2, 0) \ + M(v128_load32_zero, 0xfd0000000000005cull, 1, 1) \ + M(v128_load64_zero, 0xfd0000000000005dull, 1, 1) \ + M(f32x4_demote_f64x2_zero, 0xfd0000000000005eull, 1, 1) \ + M(f64x2_promote_low_f32x4, 0xfd0000000000005full, 1, 1) \ + M(i8x16_abs, 0xfd00000000000060ull, 1, 1) \ + M(i8x16_neg, 0xfd00000000000061ull, 1, 1) \ + M(i8x16_popcnt, 0xfd00000000000062ull, 1, 1) \ + M(i8x16_all_true, 0xfd00000000000063ull, 1, 1) \ + M(i8x16_bitmask, 0xfd00000000000064ull, 1, 1) \ + M(i8x16_narrow_i16x8_s, 0xfd00000000000065ull, 2, 1) \ + M(i8x16_narrow_i16x8_u, 0xfd00000000000066ull, 2, 1) \ + M(f32x4_ceil, 0xfd00000000000067ull, 1, 1) \ + M(f32x4_floor, 0xfd00000000000068ull, 1, 1) \ + M(f32x4_trunc, 0xfd00000000000069ull, 1, 1) \ + M(f32x4_nearest, 0xfd0000000000006aull, 1, 1) \ + M(i8x16_shl, 0xfd0000000000006bull, 2, 1) \ + M(i8x16_shr_s, 0xfd0000000000006cull, 2, 1) \ + M(i8x16_shr_u, 0xfd0000000000006dull, 2, 1) \ + M(i8x16_add, 0xfd0000000000006eull, 2, 1) \ + M(i8x16_add_sat_s, 0xfd0000000000006full, 2, 1) \ + M(i8x16_add_sat_u, 0xfd00000000000070ull, 2, 1) \ + M(i8x16_sub, 0xfd00000000000071ull, 2, 1) \ + M(i8x16_sub_sat_s, 0xfd00000000000072ull, 2, 1) \ + M(i8x16_sub_sat_u, 0xfd00000000000073ull, 2, 1) \ + M(f64x2_ceil, 0xfd00000000000074ull, 1, 1) \ + M(f64x2_floor, 0xfd00000000000075ull, 1, 1) \ + M(i8x16_min_s, 0xfd00000000000076ull, 2, 1) \ + M(i8x16_min_u, 0xfd00000000000077ull, 2, 1) \ + M(i8x16_max_s, 0xfd00000000000078ull, 2, 1) \ + M(i8x16_max_u, 0xfd00000000000079ull, 2, 1) \ + M(f64x2_trunc, 0xfd0000000000007aull, 1, 1) \ + M(i8x16_avgr_u, 0xfd0000000000007bull, 2, 1) \ + M(i16x8_extadd_pairwise_i8x16_s, 0xfd0000000000007cull, 1, 1) \ + M(i16x8_extadd_pairwise_i8x16_u, 0xfd0000000000007dull, 1, 1) \ + M(i32x4_extadd_pairwise_i16x8_s, 0xfd0000000000007eull, 1, 1) \ + M(i32x4_extadd_pairwise_i16x8_u, 0xfd0000000000007full, 1, 1) \ + M(i16x8_abs, 0xfd00000000000080ull, 1, 1) \ + M(i16x8_neg, 0xfd00000000000081ull, 1, 1) \ + M(i16x8_q15mulr_sat_s, 0xfd00000000000082ull, 2, 1) \ + M(i16x8_all_true, 0xfd00000000000083ull, 1, 1) \ + M(i16x8_bitmask, 0xfd00000000000084ull, 1, 1) \ + M(i16x8_narrow_i32x4_s, 0xfd00000000000085ull, 2, 1) \ + M(i16x8_narrow_i32x4_u, 0xfd00000000000086ull, 2, 1) \ + M(i16x8_extend_low_i8x16_s, 0xfd00000000000087ull, 1, 1) \ + M(i16x8_extend_high_i8x16_s, 0xfd00000000000088ull, 1, 1) \ + M(i16x8_extend_low_i8x16_u, 0xfd00000000000089ull, 1, 1) \ + M(i16x8_extend_high_i8x16_u, 0xfd0000000000008aull, 1, 1) \ + M(i16x8_shl, 0xfd0000000000008bull, 2, 1) \ + M(i16x8_shr_s, 0xfd0000000000008cull, 2, 1) \ + M(i16x8_shr_u, 0xfd0000000000008dull, 2, 1) \ + M(i16x8_add, 0xfd0000000000008eull, 2, 1) \ + M(i16x8_add_sat_s, 0xfd0000000000008full, 2, 1) \ + M(i16x8_add_sat_u, 0xfd00000000000090ull, 2, 1) \ + M(i16x8_sub, 0xfd00000000000091ull, 2, 1) \ + M(i16x8_sub_sat_s, 0xfd00000000000092ull, 2, 1) \ + M(i16x8_sub_sat_u, 0xfd00000000000093ull, 2, 1) \ + M(f64x2_nearest, 0xfd00000000000094ull, 1, 1) \ + M(i16x8_mul, 0xfd00000000000095ull, 2, 1) \ + M(i16x8_min_s, 0xfd00000000000096ull, 2, 1) \ + M(i16x8_min_u, 0xfd00000000000097ull, 2, 1) \ + M(i16x8_max_s, 0xfd00000000000098ull, 2, 1) \ + M(i16x8_max_u, 0xfd00000000000099ull, 2, 1) \ + M(i16x8_avgr_u, 0xfd0000000000009bull, 2, 1) \ + M(i16x8_extmul_low_i8x16_s, 0xfd0000000000009cull, 2, 1) \ + M(i16x8_extmul_high_i8x16_s, 0xfd0000000000009dull, 2, 1) \ + M(i16x8_extmul_low_i8x16_u, 0xfd0000000000009eull, 2, 1) \ + M(i16x8_extmul_high_i8x16_u, 0xfd0000000000009full, 2, 1) \ + M(i32x4_abs, 0xfd000000000000a0ull, 1, 1) \ + M(i32x4_neg, 0xfd000000000000a1ull, 1, 1) \ + M(i32x4_all_true, 0xfd000000000000a3ull, 1, 1) \ + M(i32x4_bitmask, 0xfd000000000000a4ull, 1, 1) \ + M(i32x4_extend_low_i16x8_s, 0xfd000000000000a7ull, 1, 1) \ + M(i32x4_extend_high_i16x8_s, 0xfd000000000000a8ull, 1, 1) \ + M(i32x4_extend_low_i16x8_u, 0xfd000000000000a9ull, 1, 1) \ + M(i32x4_extend_high_i16x8_u, 0xfd000000000000aaull, 1, 1) \ + M(i32x4_shl, 0xfd000000000000abull, 2, 1) \ + M(i32x4_shr_s, 0xfd000000000000acull, 2, 1) \ + M(i32x4_shr_u, 0xfd000000000000adull, 2, 1) \ + M(i32x4_add, 0xfd000000000000aeull, 2, 1) \ + M(i32x4_sub, 0xfd000000000000b1ull, 2, 1) \ + M(i32x4_mul, 0xfd000000000000b5ull, 2, 1) \ + M(i32x4_min_s, 0xfd000000000000b6ull, 2, 1) \ + M(i32x4_min_u, 0xfd000000000000b7ull, 2, 1) \ + M(i32x4_max_s, 0xfd000000000000b8ull, 2, 1) \ + M(i32x4_max_u, 0xfd000000000000b9ull, 2, 1) \ + M(i32x4_dot_i16x8_s, 0xfd000000000000baull, 2, 1) \ + M(i32x4_extmul_low_i16x8_s, 0xfd000000000000bcull, 2, 1) \ + M(i32x4_extmul_high_i16x8_s, 0xfd000000000000bdull, 2, 1) \ + M(i32x4_extmul_low_i16x8_u, 0xfd000000000000beull, 2, 1) \ + M(i32x4_extmul_high_i16x8_u, 0xfd000000000000bfull, 2, 1) \ + M(i64x2_abs, 0xfd000000000000c0ull, 1, 1) \ + M(i64x2_neg, 0xfd000000000000c1ull, 1, 1) \ + M(i64x2_all_true, 0xfd000000000000c3ull, 1, 1) \ + M(i64x2_bitmask, 0xfd000000000000c4ull, 1, 1) \ + M(i64x2_extend_low_i32x4_s, 0xfd000000000000c7ull, 1, 1) \ + M(i64x2_extend_high_i32x4_s, 0xfd000000000000c8ull, 1, 1) \ + M(i64x2_extend_low_i32x4_u, 0xfd000000000000c9ull, 1, 1) \ + M(i64x2_extend_high_i32x4_u, 0xfd000000000000caull, 1, 1) \ + M(i64x2_shl, 0xfd000000000000cbull, 2, 1) \ + M(i64x2_shr_s, 0xfd000000000000ccull, 2, 1) \ + M(i64x2_shr_u, 0xfd000000000000cdull, 2, 1) \ + M(i64x2_add, 0xfd000000000000ceull, 2, 1) \ + M(i64x2_sub, 0xfd000000000000d1ull, 2, 1) \ + M(i64x2_mul, 0xfd000000000000d5ull, 2, 1) \ + M(i64x2_eq, 0xfd000000000000d6ull, 2, 1) \ + M(i64x2_ne, 0xfd000000000000d7ull, 2, 1) \ + M(i64x2_lt_s, 0xfd000000000000d8ull, 2, 1) \ + M(i64x2_gt_s, 0xfd000000000000d9ull, 2, 1) \ + M(i64x2_le_s, 0xfd000000000000daull, 2, 1) \ + M(i64x2_ge_s, 0xfd000000000000dbull, 2, 1) \ + M(i64x2_extmul_low_i32x4_s, 0xfd000000000000dcull, 2, 1) \ + M(i64x2_extmul_high_i32x4_s, 0xfd000000000000ddull, 2, 1) \ + M(i64x2_extmul_low_i32x4_u, 0xfd000000000000deull, 2, 1) \ + M(i64x2_extmul_high_i32x4_u, 0xfd000000000000dfull, 2, 1) \ + M(f32x4_abs, 0xfd000000000000e0ull, 1, 1) \ + M(f32x4_neg, 0xfd000000000000e1ull, 1, 1) \ + M(f32x4_sqrt, 0xfd000000000000e3ull, 1, 1) \ + M(f32x4_add, 0xfd000000000000e4ull, 2, 1) \ + M(f32x4_sub, 0xfd000000000000e5ull, 2, 1) \ + M(f32x4_mul, 0xfd000000000000e6ull, 2, 1) \ + M(f32x4_div, 0xfd000000000000e7ull, 2, 1) \ + M(f32x4_min, 0xfd000000000000e8ull, 2, 1) \ + M(f32x4_max, 0xfd000000000000e9ull, 2, 1) \ + M(f32x4_pmin, 0xfd000000000000eaull, 2, 1) \ + M(f32x4_pmax, 0xfd000000000000ebull, 2, 1) \ + M(f64x2_abs, 0xfd000000000000ecull, 1, 1) \ + M(f64x2_neg, 0xfd000000000000edull, 1, 1) \ + M(f64x2_sqrt, 0xfd000000000000efull, 1, 1) \ + M(f64x2_add, 0xfd000000000000f0ull, 2, 1) \ + M(f64x2_sub, 0xfd000000000000f1ull, 2, 1) \ + M(f64x2_mul, 0xfd000000000000f2ull, 2, 1) \ + M(f64x2_div, 0xfd000000000000f3ull, 2, 1) \ + M(f64x2_min, 0xfd000000000000f4ull, 2, 1) \ + M(f64x2_max, 0xfd000000000000f5ull, 2, 1) \ + M(f64x2_pmin, 0xfd000000000000f6ull, 2, 1) \ + M(f64x2_pmax, 0xfd000000000000f7ull, 2, 1) \ + M(i32x4_trunc_sat_f32x4_s, 0xfd000000000000f8ull, 1, 1) \ + M(i32x4_trunc_sat_f32x4_u, 0xfd000000000000f9ull, 1, 1) \ + M(f32x4_convert_i32x4_s, 0xfd000000000000faull, 1, 1) \ + M(f32x4_convert_i32x4_u, 0xfd000000000000fbull, 1, 1) \ + M(i32x4_trunc_sat_f64x2_s_zero, 0xfd000000000000fcull, 1, 1) \ + M(i32x4_trunc_sat_f64x2_u_zero, 0xfd000000000000fdull, 1, 1) \ + M(f64x2_convert_low_i32x4_s, 0xfd000000000000feull, 1, 1) \ + M(f64x2_convert_low_i32x4_u, 0xfd000000000000ffull, 1, 1) \ + M(i8x16_relaxed_swizzle, 0xfd00000000000100, 2, 1) \ + M(i32x4_relaxed_trunc_f32x4_s, 0xfd00000000000101, 1, 1) \ + M(i32x4_relaxed_trunc_f32x4_u, 0xfd00000000000102, 1, 1) \ + M(i32x4_relaxed_trunc_f64x2_s_zero, 0xfd00000000000103, 1, 1) \ + M(i32x4_relaxed_trunc_f64x2_u_zero, 0xfd00000000000104, 1, 1) \ + M(f32x4_relaxed_madd, 0xfd00000000000105, 3, 1) \ + M(f32x4_relaxed_nmadd, 0xfd00000000000106, 3, 1) \ + M(f64x2_relaxed_madd, 0xfd00000000000107, 3, 1) \ + M(f64x2_relaxed_nmadd, 0xfd00000000000108, 3, 1) \ + M(i8x16_relaxed_laneselect, 0xfd00000000000109, 3, 1) \ + M(i16x8_relaxed_laneselect, 0xfd0000000000010a, 3, 1) \ + M(i32x4_relaxed_laneselect, 0xfd0000000000010b, 3, 1) \ + M(i64x2_relaxed_laneselect, 0xfd0000000000010c, 3, 1) \ + M(f32x4_relaxed_min, 0xfd0000000000010d, 2, 1) \ + M(f32x4_relaxed_max, 0xfd0000000000010e, 2, 1) \ + M(f64x2_relaxed_min, 0xfd0000000000010f, 2, 1) \ + M(f64x2_relaxed_max, 0xfd00000000000110, 2, 1) \ + M(i16x8_relaxed_q15mulr_s, 0xfd00000000000111, 2, 1) \ + M(i16x8_relaxed_dot_i8x16_i7x16_s, 0xfd00000000000112, 2, 1) \ + M(i32x4_relaxed_dot_i8x16_i7x16_add_s, 0xfd00000000000113, 3, 1) \ + /* Synthetic fused insns */ \ ENUMERATE_SYNTHETIC_INSTRUCTION_OPCODES(M) #define ENUMERATE_SYNTHETIC_INSTRUCTION_OPCODES(M) \ diff --git a/Libraries/LibWasm/Parser/Parser.cpp b/Libraries/LibWasm/Parser/Parser.cpp index b67f3889467..f9507afcb71 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -861,6 +861,26 @@ ParseResult Instruction::parse(ConstrainedStream& stream) case Instructions::i32x4_trunc_sat_f64x2_u_zero.value(): case Instructions::f64x2_convert_low_i32x4_s.value(): case Instructions::f64x2_convert_low_i32x4_u.value(): + case Instructions::i8x16_relaxed_swizzle.value(): + case Instructions::i32x4_relaxed_trunc_f32x4_s.value(): + case Instructions::i32x4_relaxed_trunc_f32x4_u.value(): + case Instructions::i32x4_relaxed_trunc_f64x2_s_zero.value(): + case Instructions::i32x4_relaxed_trunc_f64x2_u_zero.value(): + case Instructions::f32x4_relaxed_madd.value(): + case Instructions::f32x4_relaxed_nmadd.value(): + case Instructions::f64x2_relaxed_madd.value(): + case Instructions::f64x2_relaxed_nmadd.value(): + case Instructions::i8x16_relaxed_laneselect.value(): + case Instructions::i16x8_relaxed_laneselect.value(): + case Instructions::i32x4_relaxed_laneselect.value(): + case Instructions::i64x2_relaxed_laneselect.value(): + case Instructions::f32x4_relaxed_min.value(): + case Instructions::f32x4_relaxed_max.value(): + case Instructions::f64x2_relaxed_min.value(): + case Instructions::f64x2_relaxed_max.value(): + case Instructions::i16x8_relaxed_q15mulr_s.value(): + case Instructions::i16x8_relaxed_dot_i8x16_i7x16_s.value(): + case Instructions::i32x4_relaxed_dot_i8x16_i7x16_add_s.value(): // op return Instruction { full_opcode }; default: diff --git a/Libraries/LibWasm/Printer/Printer.cpp b/Libraries/LibWasm/Printer/Printer.cpp index 3fb5552d75a..2a12f07d09e 100644 --- a/Libraries/LibWasm/Printer/Printer.cpp +++ b/Libraries/LibWasm/Printer/Printer.cpp @@ -1148,6 +1148,26 @@ HashMap Wasm::Names::instruction_names { { Instructions::i32x4_trunc_sat_f64x2_u_zero, "i32x4.trunc_sat_f64x2_u_zero" }, { Instructions::f64x2_convert_low_i32x4_s, "f64x2.convert_low_i32x4_s" }, { Instructions::f64x2_convert_low_i32x4_u, "f64x2.convert_low_i32x4_u" }, + { Instructions::i8x16_relaxed_swizzle, "i8x16.relaxed_swizzle" }, + { Instructions::i32x4_relaxed_trunc_f32x4_s, "i32x4.relaxed_trunc_f32x4_s" }, + { Instructions::i32x4_relaxed_trunc_f32x4_u, "i32x4.relaxed_trunc_f32x4_u" }, + { Instructions::i32x4_relaxed_trunc_f64x2_s_zero, "i32x4.relaxed_trunc_f64x2_s_zero" }, + { Instructions::i32x4_relaxed_trunc_f64x2_u_zero, "i32x4.relaxed_trunc_f64x2_u_zero" }, + { Instructions::f32x4_relaxed_madd, "f32x4.relaxed_madd" }, + { Instructions::f32x4_relaxed_nmadd, "f32x4.relaxed_nmadd" }, + { Instructions::f64x2_relaxed_madd, "f64x2.relaxed_madd" }, + { Instructions::f64x2_relaxed_nmadd, "f64x2.relaxed_nmadd" }, + { Instructions::i8x16_relaxed_laneselect, "i8x16.relaxed_laneselect" }, + { Instructions::i16x8_relaxed_laneselect, "i16x8.relaxed_laneselect" }, + { Instructions::i32x4_relaxed_laneselect, "i32x4.relaxed_laneselect" }, + { Instructions::i64x2_relaxed_laneselect, "i64x2.relaxed_laneselect" }, + { Instructions::f32x4_relaxed_min, "f32x4.relaxed_min" }, + { Instructions::f32x4_relaxed_max, "f32x4.relaxed_max" }, + { Instructions::f64x2_relaxed_min, "f64x2.relaxed_min" }, + { Instructions::f64x2_relaxed_max, "f64x2.relaxed_max" }, + { Instructions::i16x8_relaxed_q15mulr_s, "i16x8.relaxed_q15mulr_s" }, + { Instructions::i16x8_relaxed_dot_i8x16_i7x16_s, "i16x8.relaxed_dot_i8x16_i7x16_s" }, + { Instructions::i32x4_relaxed_dot_i8x16_i7x16_add_s, "i32x4.relaxed_dot_i8x16_i7x16_add_s" }, { Instructions::structured_else, "synthetic:else" }, { Instructions::structured_end, "synthetic:end" }, { Instructions::synthetic_i32_add2local, "synthetic:i32.add2local" }, From ddb35dcb5fb850c13fd00ca0a7039c1b36b48fba Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 23 Sep 2025 05:52:12 +0200 Subject: [PATCH 4/8] LibWasm: Accept proposal 'memory64' (but don't actually run it) This is a WIP implementation. --- .../LibWasm/AbstractMachine/AbstractMachine.h | 4 +- .../LibWasm/AbstractMachine/Validator.cpp | 281 ++++++++++-------- Libraries/LibWasm/AbstractMachine/Validator.h | 21 +- Libraries/LibWasm/Parser/Parser.cpp | 22 +- Libraries/LibWasm/Types.h | 26 +- Meta/CMake/wasm_spec_tests.cmake | 2 +- Tests/LibWasm/test-wasm.cpp | 4 +- 7 files changed, 220 insertions(+), 140 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 0fd54a8101b..7ad0e4b37dd 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -411,7 +411,7 @@ public: for (size_t i = previous_size; i < m_elements.size(); ++i) m_elements[i] = fill_value; - m_type = TableType { m_type.element_type(), Limits(m_type.limits().min() + size_to_grow, m_type.limits().max()) }; + m_type = TableType { m_type.element_type(), Limits(m_type.limits().address_type(), m_type.limits().min() + size_to_grow, m_type.limits().max()) }; return true; } @@ -477,7 +477,7 @@ public: // // See relevant spec link: // https://www.w3.org/TR/wasm-core-2/#growing-memories%E2%91%A0 - m_type = MemoryType { Limits(m_type.limits().min() + size_to_grow / Constants::page_size, m_type.limits().max()) }; + m_type = MemoryType { Limits(m_type.limits().address_type(), m_type.limits().min() + size_to_grow / Constants::page_size, m_type.limits().max()) }; } return true; diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index a3c8f83bebb..db5bb81864f 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -152,7 +152,7 @@ ErrorOr Validator::validate(ImportSection const& section) ErrorOr Validator::validate(ExportSection const& section) { for (auto& export_ : section.entries()) - TRY(export_.description().visit([&](auto& entry) { return validate(entry); })); + TRY(export_.description().visit([&](auto& entry) -> ErrorOr { TRY(validate(entry)); return {}; })); return {}; } @@ -175,15 +175,16 @@ ErrorOr Validator::validate(DataSection const& section) TRY(entry.value().visit( [](DataSection::Data::Passive const&) { return ErrorOr {}; }, [&](DataSection::Data::Active const& active) -> ErrorOr { - TRY(validate(active.index)); + auto memory = TRY(validate(active.index)); + auto const at = memory.limits().address_value_type(); - auto expression_result = TRY(validate(active.offset, { ValueType(ValueType::I32) })); + auto expression_result = TRY(validate(active.offset, { at })); if (!expression_result.is_constant) return Errors::invalid("active data initializer"sv); - if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(ValueType::I32)) - return Errors::invalid("active data initializer type"sv, ValueType(ValueType::I32), expression_result.result_types); + if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(at.kind())) + return Errors::invalid("active data initializer type"sv, at, expression_result.result_types); return {}; })); @@ -203,11 +204,12 @@ ErrorOr Validator::validate(ElementSection const& section auto table = m_context.tables[active.index.value()]; if (table.element_type() != segment.type) return Errors::invalid("active element reference type"sv); - auto expression_result = TRY(validate(active.expression, { ValueType(ValueType::I32) })); + auto at = table.limits().address_value_type(); + auto expression_result = TRY(validate(active.expression, { at })); if (!expression_result.is_constant) return Errors::invalid("active element initializer"sv); - if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(ValueType::I32)) - return Errors::invalid("active element initializer type"sv, ValueType(ValueType::I32), expression_result.result_types); + if (expression_result.result_types.size() != 1 || !expression_result.result_types.first().is_of_kind(at.kind())) + return Errors::invalid("active element initializer type"sv, at, expression_result.result_types); return {}; })); @@ -1405,10 +1407,9 @@ VALIDATE_INSTRUCTION(global_set) VALIDATE_INSTRUCTION(table_get) { auto index = instruction.arguments().get(); - TRY(validate(index)); + auto table = TRY(validate(index)); - auto& table = m_context.tables[index.value()]; - TRY(stack.take()); + TRY(stack.take(table.limits().address_value_type())); stack.append(table.element_type()); return {}; } @@ -1416,12 +1417,11 @@ VALIDATE_INSTRUCTION(table_get) VALIDATE_INSTRUCTION(table_set) { auto index = instruction.arguments().get(); - TRY(validate(index)); + auto table = TRY(validate(index)); - auto& table = m_context.tables[index.value()]; TRY(stack.take(table.element_type())); - TRY(stack.take()); + TRY(stack.take(table.limits().address_value_type())); return {}; } @@ -1429,36 +1429,34 @@ VALIDATE_INSTRUCTION(table_set) VALIDATE_INSTRUCTION(table_size) { auto index = instruction.arguments().get(); - TRY(validate(index)); + auto table = TRY(validate(index)); - stack.append(ValueType(ValueType::I32)); + stack.append(table.limits().address_value_type()); return {}; } VALIDATE_INSTRUCTION(table_grow) { auto index = instruction.arguments().get(); - TRY(validate(index)); + auto table = TRY(validate(index)); - auto& table = m_context.tables[index.value()]; + auto const at = table.limits().address_value_type(); - TRY(stack.take()); + TRY(stack.take(at)); TRY(stack.take(table.element_type())); - stack.append(ValueType(ValueType::I32)); + stack.append(at); return {}; } VALIDATE_INSTRUCTION(table_fill) { auto index = instruction.arguments().get(); - TRY(validate(index)); + auto table = TRY(validate(index)); - auto& table = m_context.tables[index.value()]; - - TRY(stack.take()); + TRY(stack.take(table.limits().address_value_type())); TRY(stack.take(table.element_type())); - TRY(stack.take()); + TRY(stack.take(table.limits().address_value_type())); return {}; } @@ -1467,11 +1465,8 @@ VALIDATE_INSTRUCTION(table_copy) { auto& args = instruction.arguments().get(); - TRY(validate(args.lhs)); - TRY(validate(args.rhs)); - - auto& lhs_table = m_context.tables[args.lhs.value()]; - auto& rhs_table = m_context.tables[args.rhs.value()]; + auto lhs_table = TRY(validate(args.lhs)); + auto rhs_table = TRY(validate(args.rhs)); if (lhs_table.element_type() != rhs_table.element_type()) return Errors::non_conforming_types("table.copy"sv, lhs_table.element_type(), rhs_table.element_type()); @@ -1479,7 +1474,13 @@ VALIDATE_INSTRUCTION(table_copy) if (!lhs_table.element_type().is_reference()) return Errors::invalid("table.copy element type"sv, "a reference type"sv, lhs_table.element_type()); - TRY((stack.take())); + auto const lhs_at = lhs_table.limits().address_value_type(); + auto const rhs_at = rhs_table.limits().address_value_type(); + auto const size_type = ValueType(lhs_at.kind() == ValueType::I32 || rhs_at.kind() == ValueType::I32 ? ValueType::I32 : ValueType::I64); + + TRY(stack.take(size_type)); + TRY(stack.take(rhs_at)); + TRY(stack.take(lhs_at)); return {}; } @@ -1488,16 +1489,16 @@ VALIDATE_INSTRUCTION(table_init) { auto& args = instruction.arguments().get(); - TRY(validate(args.table_index)); + auto table = 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"sv, table.element_type(), element_type); - TRY((stack.take())); + TRY((stack.take())); + TRY((stack.take(table.limits().address_value_type()))); return {}; } @@ -1515,12 +1516,13 @@ VALIDATE_INSTRUCTION(i32_load) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(i32)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(i32)); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); + stack.append(ValueType(ValueType::I32)); return {}; } @@ -1529,12 +1531,12 @@ VALIDATE_INSTRUCTION(i64_load) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(i64)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(i64)); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I64)); return {}; } @@ -1543,12 +1545,12 @@ VALIDATE_INSTRUCTION(f32_load) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(float)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(float)); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::F32)); return {}; } @@ -1557,12 +1559,12 @@ VALIDATE_INSTRUCTION(f64_load) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(double)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(double)); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::F64)); return {}; } @@ -1571,12 +1573,12 @@ VALIDATE_INSTRUCTION(i32_load16_s) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 16 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 16 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I32)); return {}; } @@ -1585,12 +1587,12 @@ VALIDATE_INSTRUCTION(i32_load16_u) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 16 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 16 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I32)); return {}; } @@ -1599,12 +1601,12 @@ VALIDATE_INSTRUCTION(i32_load8_s) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 8 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 8 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I32)); return {}; } @@ -1613,12 +1615,12 @@ VALIDATE_INSTRUCTION(i32_load8_u) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 8 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 8 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I32)); return {}; } @@ -1627,12 +1629,12 @@ VALIDATE_INSTRUCTION(i64_load32_s) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 32 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 32 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I64)); return {}; } @@ -1641,12 +1643,12 @@ VALIDATE_INSTRUCTION(i64_load32_u) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 32 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 32 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I64)); return {}; } @@ -1655,12 +1657,12 @@ VALIDATE_INSTRUCTION(i64_load16_s) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 16 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 16 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I64)); return {}; } @@ -1669,12 +1671,12 @@ VALIDATE_INSTRUCTION(i64_load16_u) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 16 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 16 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I64)); return {}; } @@ -1683,12 +1685,12 @@ VALIDATE_INSTRUCTION(i64_load8_s) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 8 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 8 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I64)); return {}; } @@ -1697,12 +1699,12 @@ VALIDATE_INSTRUCTION(i64_load8_u) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 8 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 8 / 8); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); stack.append(ValueType(ValueType::I64)); return {}; } @@ -1711,12 +1713,13 @@ VALIDATE_INSTRUCTION(i32_store) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(i32)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(i32)); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1725,12 +1728,13 @@ VALIDATE_INSTRUCTION(i64_store) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(i64)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(i64)); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1739,12 +1743,13 @@ VALIDATE_INSTRUCTION(f32_store) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(float)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(float)); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1753,12 +1758,13 @@ VALIDATE_INSTRUCTION(f64_store) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(double)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(double)); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1767,12 +1773,13 @@ VALIDATE_INSTRUCTION(i32_store16) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 16 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 16 / 8); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1781,12 +1788,13 @@ VALIDATE_INSTRUCTION(i32_store8) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 8 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 8 / 8); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1795,12 +1803,13 @@ VALIDATE_INSTRUCTION(i64_store32) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 32 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 32 / 8); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1809,12 +1818,13 @@ VALIDATE_INSTRUCTION(i64_store16) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 16 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 16 / 8); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } @@ -1823,39 +1833,43 @@ VALIDATE_INSTRUCTION(i64_store8) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > 8 / 8) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, 8 / 8); - TRY((stack.take())); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg))); return {}; } VALIDATE_INSTRUCTION(memory_size) { - TRY(validate(instruction.arguments().get().memory_index)); + auto memory = TRY(validate(instruction.arguments().get().memory_index)); - stack.append(ValueType(ValueType::I32)); + stack.append(memory.limits().address_value_type()); return {}; } VALIDATE_INSTRUCTION(memory_grow) { - TRY(validate(instruction.arguments().get().memory_index)); + auto memory = TRY(validate(instruction.arguments().get().memory_index)); - TRY((stack.take())); - stack.append(ValueType(ValueType::I32)); + auto const at = memory.limits().address_value_type(); + TRY((stack.take(at))); + stack.append(at); return {}; } VALIDATE_INSTRUCTION(memory_fill) { - TRY(validate(instruction.arguments().get().memory_index)); + auto memory = TRY(validate(instruction.arguments().get().memory_index)); - TRY((stack.take())); + TRY((take_memory_address(stack, memory, { 0, 0 }))); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, { 0, 0 }))); return {}; } @@ -1863,10 +1877,16 @@ VALIDATE_INSTRUCTION(memory_fill) VALIDATE_INSTRUCTION(memory_copy) { auto& args = instruction.arguments().get(); - TRY(validate(args.src_index)); - TRY(validate(args.dst_index)); + auto src_memory = TRY(validate(args.src_index)); + auto dst_memory = TRY(validate(args.dst_index)); - TRY((stack.take())); + auto const src_at = ValueType(src_memory.limits().address_value_type()); + auto const dst_at = ValueType(dst_memory.limits().address_value_type()); + auto const size_at = ValueType(src_at.kind() == ValueType::I32 || dst_at.kind() == ValueType::I32 ? ValueType::I32 : ValueType::I64); + + TRY((stack.take(size_at))); + TRY((stack.take(src_at))); + TRY((stack.take(dst_at))); return {}; } @@ -1878,10 +1898,13 @@ VALIDATE_INSTRUCTION(memory_init) auto& args = instruction.arguments().get(); - TRY(validate(args.memory_index)); + auto memory = TRY(validate(args.memory_index)); TRY(validate(args.data_index)); - TRY((stack.take())); + auto const at = memory.limits().address_value_type(); + + TRY((stack.take())); + TRY((stack.take(at))); return {}; } @@ -2128,16 +2151,15 @@ VALIDATE_INSTRUCTION(call) VALIDATE_INSTRUCTION(call_indirect) { auto& args = instruction.arguments().get(); - TRY(validate(args.table)); + auto table = TRY(validate(args.table)); TRY(validate(args.type)); - auto& table = m_context.tables[args.table.value()]; if (table.element_type().kind() != ValueType::FunctionReference) return Errors::invalid("table element type for call.indirect"sv, "a function reference"sv, table.element_type()); auto& type = m_context.types[args.type.value()]; - TRY(stack.take()); + TRY(stack.take(table.limits().address_value_type())); for (size_t i = 0; i < type.parameters().size(); ++i) TRY(stack.take(type.parameters()[type.parameters().size() - i - 1])); @@ -2198,12 +2220,13 @@ VALIDATE_INSTRUCTION(v128_load) { auto& arg = instruction.arguments().get(); - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1ull << arg.align) > sizeof(u128)) return Errors::out_of_bounds("memory op alignment"sv, 1ull << arg.align, 0, sizeof(u128)); - TRY((stack.take_and_put(ValueType::V128))); + TRY((take_memory_address(stack, memory, arg))); + stack.append(ValueType(ValueType::V128)); return {}; } @@ -2880,12 +2903,15 @@ VALIDATE_INSTRUCTION(v128_load8_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take_and_put(ValueType::V128); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + stack.append(ValueType(ValueType::V128)); + return {}; } VALIDATE_INSTRUCTION(v128_load16_lane) @@ -2898,12 +2924,15 @@ VALIDATE_INSTRUCTION(v128_load16_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take_and_put(ValueType::V128); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + stack.append(ValueType(ValueType::V128)); + return {}; } VALIDATE_INSTRUCTION(v128_load32_lane) @@ -2916,12 +2945,15 @@ VALIDATE_INSTRUCTION(v128_load32_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take_and_put(ValueType::V128); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + stack.append(ValueType(ValueType::V128)); + return {}; } VALIDATE_INSTRUCTION(v128_load64_lane) @@ -2934,12 +2966,15 @@ VALIDATE_INSTRUCTION(v128_load64_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take_and_put(ValueType::V128); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + stack.append(ValueType(ValueType::V128)); + return {}; } VALIDATE_INSTRUCTION(v128_store8_lane) @@ -2952,12 +2987,14 @@ VALIDATE_INSTRUCTION(v128_store8_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take(); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + return {}; } VALIDATE_INSTRUCTION(v128_store16_lane) @@ -2970,12 +3007,14 @@ VALIDATE_INSTRUCTION(v128_store16_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take(); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + return {}; } VALIDATE_INSTRUCTION(v128_store32_lane) @@ -2988,12 +3027,14 @@ VALIDATE_INSTRUCTION(v128_store32_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take(); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + return {}; } VALIDATE_INSTRUCTION(v128_store64_lane) @@ -3006,12 +3047,14 @@ VALIDATE_INSTRUCTION(v128_store64_lane) if (arg.lane >= max_lane) return Errors::out_of_bounds("lane index"sv, arg.lane, 0u, max_lane); - TRY(validate(arg.memory.memory_index)); + auto memory = TRY(validate(arg.memory.memory_index)); if ((1 << arg.memory.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.memory.align, 0u, max_alignment); - return stack.take(); + TRY((stack.take())); + TRY((take_memory_address(stack, memory, arg.memory))); + return {}; } VALIDATE_INSTRUCTION(v128_load32_zero) @@ -3020,12 +3063,14 @@ VALIDATE_INSTRUCTION(v128_load32_zero) constexpr auto N = 32; constexpr auto max_alignment = N / 8; - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1 << arg.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.align, 0u, max_alignment); - return stack.take_and_put(ValueType::V128); + TRY((take_memory_address(stack, memory, arg))); + stack.append(ValueType(ValueType::V128)); + return {}; } VALIDATE_INSTRUCTION(v128_load64_zero) @@ -3034,12 +3079,14 @@ VALIDATE_INSTRUCTION(v128_load64_zero) constexpr auto N = 64; constexpr auto max_alignment = N / 8; - TRY(validate(arg.memory_index)); + auto memory = TRY(validate(arg.memory_index)); if ((1 << arg.align) > max_alignment) return Errors::out_of_bounds("memory op alignment"sv, 1 << arg.align, 0u, max_alignment); - return stack.take_and_put(ValueType::V128); + TRY((take_memory_address(stack, memory, arg))); + stack.append(ValueType(ValueType::V128)); + return {}; } VALIDATE_INSTRUCTION(f32x4_demote_f64x2_zero) diff --git a/Libraries/LibWasm/AbstractMachine/Validator.h b/Libraries/LibWasm/AbstractMachine/Validator.h index 7d348f11ac1..5564b19b106 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.h +++ b/Libraries/LibWasm/AbstractMachine/Validator.h @@ -87,10 +87,10 @@ public: return Errors::invalid("FunctionIndex"sv); } - ErrorOr validate(MemoryIndex index) const + ErrorOr validate(MemoryIndex index) const { if (index.value() < m_context.memories.size()) - return {}; + return m_context.memories[index.value()]; return Errors::invalid("MemoryIndex"sv); } @@ -129,10 +129,10 @@ public: return Errors::invalid("LocalIndex"sv); } - ErrorOr validate(TableIndex index) const + ErrorOr validate(TableIndex index) const { if (index.value() < m_context.tables.size()) - return {}; + return m_context.tables[index.value()]; return Errors::invalid("TableIndex"sv); } @@ -294,6 +294,19 @@ public: ErrorOr validate(MemoryType const&); ErrorOr validate(GlobalType const&) { return {}; } + // Proposal 'memory64' + ErrorOr take_memory_address(Stack& stack, MemoryType const& memory, Instruction::MemoryArgument const& arg) + { + if (memory.limits().address_type() == AddressType::I64) { + TRY((stack.take())); + } else { + if (arg.offset > NumericLimits::max()) + return Errors::out_of_bounds("memory op offset"sv, arg.offset, 0, NumericLimits::max()); + TRY((stack.take())); + } + return {}; + } + private: explicit Validator(Context context) : m_context(move(context)) diff --git a/Libraries/LibWasm/Parser/Parser.cpp b/Libraries/LibWasm/Parser/Parser.cpp index f9507afcb71..661460cf079 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -151,23 +151,26 @@ ParseResult Limits::parse(ConstrainedStream& stream) ScopeLogger logger("Limits"sv); auto flag = TRY_READ(stream, u8, ParseError::ExpectedKindTag); - if (flag > 1) + // Proposal 'memory64': flags 0/1 refer to 32-bit limits, flags 4/5 refer to 64-bit limits. + if (flag & ~0b00000101) return with_eof_check(stream, ParseError::InvalidTag); + auto address_type = (flag & 0b00000100) ? AddressType::I64 : AddressType::I32; + auto min_or_error = stream.read_value>(); if (min_or_error.is_error()) return with_eof_check(stream, ParseError::ExpectedSize); size_t min = min_or_error.release_value(); - Optional max; - if (flag) { - auto value_or_error = stream.read_value>(); + Optional max; + if (flag & 1) { + auto value_or_error = stream.read_value>(); if (value_or_error.is_error()) return with_eof_check(stream, ParseError::ExpectedSize); max = value_or_error.release_value(); } - return Limits { static_cast(min), move(max) }; + return Limits { address_type, static_cast(min), move(max) }; } ParseResult MemoryType::parse(ConstrainedStream& stream) @@ -313,7 +316,8 @@ ParseResult Instruction::parse(ConstrainedStream& stream) memory_index = TRY_READ(stream, LEB128, ParseError::InvalidInput); } - auto offset = TRY_READ(stream, LEB128, ParseError::InvalidInput); + // Proposal 'memory64': memarg offsets are u64 instead of u32. + auto offset = TRY_READ(stream, LEB128, ParseError::InvalidInput); return Instruction { opcode, MemoryArgument { align, offset, MemoryIndex(memory_index) } }; } @@ -604,7 +608,8 @@ ParseResult Instruction::parse(ConstrainedStream& stream) memory_index = TRY_READ(stream, LEB128, ParseError::InvalidInput); } - auto offset = TRY_READ(stream, LEB128, ParseError::ExpectedIndex); + // Proposal 'memory64': memarg offsets are u64 instead of u32. + auto offset = TRY_READ(stream, LEB128, ParseError::ExpectedIndex); return Instruction { full_opcode, MemoryArgument { align, offset, MemoryIndex(memory_index) } }; } @@ -626,7 +631,8 @@ ParseResult Instruction::parse(ConstrainedStream& stream) memory_index = TRY_READ(stream, LEB128, ParseError::InvalidInput); } - auto offset = TRY_READ(stream, LEB128, ParseError::ExpectedIndex); + // Proposal 'memory64': memarg offsets are u64 instead of u32. + auto offset = TRY_READ(stream, LEB128, ParseError::ExpectedIndex); auto index = TRY_READ(stream, u8, ParseError::InvalidInput); return Instruction { full_opcode, MemoryAndLaneArgument { { align, offset, MemoryIndex(memory_index) }, index } }; diff --git a/Libraries/LibWasm/Types.h b/Libraries/LibWasm/Types.h index 91f0eee121c..2cfa2a43ab1 100644 --- a/Libraries/LibWasm/Types.h +++ b/Libraries/LibWasm/Types.h @@ -243,28 +243,42 @@ private: 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(u32 min, Optional max = {}) - : m_min(min) + 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())); + && (!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: - u32 m_min { 0 }; - Optional m_max; + 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 @@ -415,7 +429,7 @@ public: struct MemoryArgument { u32 align; - u32 offset; + u64 offset; MemoryIndex memory_index { 0 }; }; diff --git a/Meta/CMake/wasm_spec_tests.cmake b/Meta/CMake/wasm_spec_tests.cmake index 3710c886759..9cbd435e6ab 100644 --- a/Meta/CMake/wasm_spec_tests.cmake +++ b/Meta/CMake/wasm_spec_tests.cmake @@ -3,7 +3,7 @@ # if(INCLUDE_WASM_SPEC_TESTS) - set(WASM_SPEC_TEST_COMMIT a8101597d3c3c660086c3cd1eedee608ff18d3c3) # 2025-09-10 + set(WASM_SPEC_TEST_COMMIT 4b24564c844e3d34bf46dfcb3c774ee5163e31cc) # 2025-09-10 set(WASM_SPEC_TEST_GZ_URL https://github.com/WebAssembly/testsuite/archive/${WASM_SPEC_TEST_COMMIT}.tar.gz) set(WASM_SPEC_TEST_GZ_PATH ${CMAKE_BINARY_DIR}/wasm-spec-testsuite.tar.gz CACHE PATH "") set(WASM_SPEC_TEST_PATH ${CMAKE_CURRENT_BINARY_DIR}/Tests/Fixtures/SpecTests CACHE PATH "") diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp index c15951d8f3d..f8ab16e883e 100644 --- a/Tests/LibWasm/test-wasm.cpp +++ b/Tests/LibWasm/test-wasm.cpp @@ -109,11 +109,11 @@ private: auto address_f64_f64 = alloc_noop_function(print_f64_f64_type); s_spec_test_namespace.set({ "spectest", "print_f64_f64", print_f64_f64_type }, Wasm::ExternValue { *address_f64_f64 }); - Wasm::TableType table_type { Wasm::ValueType(Wasm::ValueType::FunctionReference), Wasm::Limits(10, 20) }; + Wasm::TableType table_type { Wasm::ValueType(Wasm::ValueType::FunctionReference), Wasm::Limits(Wasm::AddressType::I32, 10, 20) }; auto table_address = m_machine.store().allocate(table_type); s_spec_test_namespace.set({ "spectest", "table", table_type }, Wasm::ExternValue { *table_address }); - Wasm::MemoryType memory_type { Wasm::Limits(1, 2) }; + Wasm::MemoryType memory_type { Wasm::Limits(Wasm::AddressType::I32, 1, 2) }; auto memory_address = m_machine.store().allocate(memory_type); s_spec_test_namespace.set({ "spectest", "memory", memory_type }, Wasm::ExternValue { *memory_address }); From 8138c2f48b43731ec06e4af55495a3a6c0ed98c2 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Wed, 24 Sep 2025 12:19:23 +0200 Subject: [PATCH 5/8] LibWasm: Follow the updated spec on instantiation The spec now permits access to all globals for all segment initializers, as well as previously-defined globals for the global initializers. --- .../AbstractMachine/AbstractMachine.cpp | 7 ++--- .../LibWasm/AbstractMachine/Validator.cpp | 26 ++++--------------- Libraries/LibWasm/AbstractMachine/Validator.h | 8 +++++- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 90d1c3f92e2..11ad4fdbc3e 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -255,6 +255,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector {}, entry, entry.instructions().size() - 1, @@ -302,7 +303,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector {}, active_ptr->expression, 1, @@ -337,7 +338,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector {}, data.offset, 1, diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index db5bb81864f..300e25f9b42 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -79,8 +79,6 @@ ErrorOr Validator::validate(Module& module) for (auto& memory : module.memory_section().memories()) m_context.memories.append(memory.type()); - auto imported_globals = m_context.globals; - m_context.globals.ensure_capacity(m_context.globals.size() + module.global_section().entries().size()); for (auto& global : module.global_section().entries()) m_context.globals.append(global.type()); @@ -119,23 +117,11 @@ ErrorOr Validator::validate(Module& module) TRY(validate(module.import_section())); TRY(validate(module.export_section())); TRY(validate(module.start_section())); - { - // Let C′ be the same context as C, except that C′.globals is just the sequence globals(it∗). - - // Under the context C′: - // For each table_i in module.tables, the definition table_i must be val_i with a table type tt_i. - // For each mem_i in module.mems, the definition mem_i must be val_i with a memory type mt_i. - // For each global_i in module.globals, the definition global_i must be val_i with a global type gt_i. - // For each elem_i in module.elems, the segment elem_i must be val_i with reference type rt_i. - // For each data_i in module.datas, the segment data_i must be val_i. - - TemporaryChange omit_internal_globals { m_context.globals, imported_globals }; - TRY(validate(module.data_section())); - TRY(validate(module.element_section())); - TRY(validate(module.global_section())); - TRY(validate(module.memory_section())); - TRY(validate(module.table_section())); - } + TRY(validate(module.data_section())); + TRY(validate(module.element_section())); + TRY(validate(module.global_section())); + TRY(validate(module.memory_section())); + TRY(validate(module.table_section())); TRY(validate(module.code_section())); module.set_validation_status(Module::ValidationStatus::Valid, {}); @@ -226,8 +212,6 @@ ErrorOr Validator::validate(ElementSection const& section ErrorOr Validator::validate(GlobalSection const& section) { - TemporaryChange omit_internal_globals { m_context.globals, m_globals_without_internal_globals }; - for (auto& entry : section.entries()) { auto& type = entry.type(); TRY(validate(type)); diff --git a/Libraries/LibWasm/AbstractMachine/Validator.h b/Libraries/LibWasm/AbstractMachine/Validator.h index 5564b19b106..a5c63332b35 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.h +++ b/Libraries/LibWasm/AbstractMachine/Validator.h @@ -314,7 +314,13 @@ private: } struct Errors { - static ValidationError invalid(StringView name) { return ByteString::formatted("Invalid {}", name); } + static ValidationError invalid(StringView name, SourceLocation location = SourceLocation::current()) + { + if constexpr (WASM_VALIDATOR_DEBUG) + return ByteString::formatted("Invalid {} in {}", name, find_instruction_name(location)); + else + return ByteString::formatted("Invalid {}", name); + } template static ValidationError invalid(StringView name, Expected expected, Given given, SourceLocation location = SourceLocation::current()) From d99f663b1a3b84865180ada3502c253284c8c6ca Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Thu, 25 Sep 2025 03:35:34 +0200 Subject: [PATCH 6/8] LibWasm: Implement parsing/validation for proposal exception-handling Actual execution traps for now. --- .../AbstractMachine/AbstractMachine.cpp | 63 +++++++- .../LibWasm/AbstractMachine/AbstractMachine.h | 70 +++++++- .../AbstractMachine/BytecodeInterpreter.cpp | 30 ++++ .../LibWasm/AbstractMachine/Validator.cpp | 98 ++++++++++++ Libraries/LibWasm/AbstractMachine/Validator.h | 10 ++ Libraries/LibWasm/Constants.h | 1 + Libraries/LibWasm/Opcode.h | 3 + Libraries/LibWasm/Parser/Parser.cpp | 105 +++++++++++- Libraries/LibWasm/Printer/Printer.cpp | 79 ++++++++- Libraries/LibWasm/Printer/Printer.h | 5 + Libraries/LibWasm/Types.h | 151 +++++++++++++++++- Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 5 + Tests/LibWasm/test-wasm.cpp | 12 +- Utilities/wasm.cpp | 1 + 14 files changed, 614 insertions(+), 19 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 11ad4fdbc3e..0fe96028e28 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -76,6 +76,20 @@ Optional Store::allocate(ValueType const& type, Vector Store::allocate(FunctionType const& type, TagType::Flags flags) +{ + TagAddress address { m_tags.size() }; + m_tags.append({ type, flags }); + return address; +} + +Optional Store::allocate(TagInstance const& tag_instance, Vector params) +{ + ExceptionAddress address { m_exceptions.size() }; + m_exceptions.append(ExceptionInstance { tag_instance, move(params) }); + return address; +} + FunctionInstance* Store::get(FunctionAddress address) { auto value = address.value(); @@ -132,6 +146,22 @@ DataInstance* Store::get(DataAddress address) return &m_datas[value]; } +TagInstance* Store::get(TagAddress address) +{ + auto value = address.value(); + if (m_tags.size() <= value) + return nullptr; + return &m_tags[value]; +} + +ExceptionInstance* Store::get(ExceptionAddress address) +{ + auto value = address.value(); + if (m_exceptions.size() <= value) + return nullptr; + return &m_exceptions[value]; +} + ErrorOr AbstractMachine::validate(Module& module) { if (module.validation_status() != Module::ValidationStatus::Unchecked) { @@ -203,6 +233,19 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector Optional { + if (!extern_.has()) + return "Expected tag import"sv; + auto other_tag_instance = m_store.get(extern_.get()); + if (other_tag_instance->flags() != type.flags()) + return "Tag import and extern do not match"sv; + + auto& this_type = module.type_section().types()[type.type().value()]; + + if (other_tag_instance->type().parameters() != this_type.parameters()) + return "Tag import and extern do not match"sv; + return {}; + }, [&](TypeIndex type_index) -> Optional { if (!extern_.has()) return "Expected function import"sv; @@ -407,7 +450,8 @@ Optional AbstractMachine::allocate_all_initial_phase(Module [&](FunctionAddress const& address) { module_instance.functions().append(address); }, [&](TableAddress const& address) { module_instance.tables().append(address); }, [&](MemoryAddress const& address) { module_instance.memories().append(address); }, - [&](GlobalAddress const& address) { module_instance.globals().append(address); }); + [&](GlobalAddress const& address) { module_instance.globals().append(address); }, + [&](TagAddress const& address) { module_instance.tags().append(address); }); } module_instance.functions().extend(own_functions); @@ -434,8 +478,15 @@ Optional AbstractMachine::allocate_all_initial_phase(Module index++; } + for (auto& entry : module.tag_section().tags()) { + auto& type = module.type_section().types()[entry.type().value()]; + auto address = m_store.allocate(type, entry.flags()); + VERIFY(address.has_value()); + module_instance.tags().append(*address); + } + for (auto& entry : module.export_section().entries()) { - Variant address {}; + Variant address {}; entry.description().visit( [&](FunctionIndex const& index) { if (module_instance.functions().size() > index.value()) @@ -460,6 +511,12 @@ Optional AbstractMachine::allocate_all_initial_phase(Module address = GlobalAddress { module_instance.globals()[index.value()] }; else dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), module_instance.globals().size()); + }, + [&](TagIndex const& index) { + if (module_instance.tags().size() > index.value()) + address = TagAddress { module_instance.tags()[index.value()] }; + else + dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), module_instance.tags().size()); }); if (address.has()) { @@ -469,7 +526,7 @@ Optional AbstractMachine::allocate_all_initial_phase(Module module_instance.exports().append(ExportInstance { entry.name(), - move(address).downcast(), + move(address).downcast(), }); } diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 7ad0e4b37dd..6cb257b9d0d 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -38,6 +38,8 @@ AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, GlobalAddress, Arithmetic, Comparison, AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, ElementAddress, Arithmetic, Comparison, Increment); AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, DataAddress, Arithmetic, Comparison, Increment); AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, MemoryAddress, Arithmetic, Comparison, Increment); +AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, TagAddress, Arithmetic, Comparison, Increment); +AK_TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, ExceptionAddress, Arithmetic, Comparison, Increment); // FIXME: These should probably be made generic/virtual if/when we decide to do something more // fancy than just a dumb interpreter. @@ -53,8 +55,11 @@ public: struct Extern { ExternAddress address; }; + struct Exception { + ExceptionAddress address; + }; - using RefType = Variant; + using RefType = Variant; explicit Reference(RefType ref) : m_ref(move(ref)) { @@ -90,6 +95,10 @@ public: // ref.null externref m_value = u128(0, 3); break; + case ValueType::ExceptionReference: + // ref.null exnref + m_value = u128(0, 4); + break; } } @@ -136,10 +145,14 @@ public: // 1: externref // 2: null funcref // 3: null externref + // 4: null exnref + // 5: exnref ref.ref().visit( [&](Reference::Func const& func) { m_value = u128(bit_cast(func.address), bit_cast(func.source_module.ptr())); }, [&](Reference::Extern const& func) { m_value = u128(bit_cast(func.address), 1); }, - [&](Reference::Null const& null) { m_value = u128(0, null.type.kind() == ValueType::Kind::FunctionReference ? 2 : 3); }); + [&](Reference::Null const& null) { m_value = u128(0, null.type.kind() == ValueType::Kind::FunctionReference ? 2 : null.type.kind() == ValueType::Kind::ExceptionReference ? 4 + : 3); }, + [&](Reference::Exception const& exn) { m_value = u128(bit_cast(exn.address), 5); }); } template T> @@ -184,6 +197,10 @@ public: return Reference { Reference::Null { ValueType(ValueType::Kind::FunctionReference) } }; case 3: return Reference { Reference::Null { ValueType(ValueType::Kind::ExternReference) } }; + case 4: + return Reference { Reference::Null { ValueType(ValueType::Kind::ExceptionReference) } }; + case 5: + return Reference { Reference::Exception { bit_cast(m_value.low()) } }; } } VERIFY_NOT_REACHED(); @@ -273,7 +290,7 @@ struct InstantiationError { InstantiationErrorSource source { InstantiationErrorSource::Linking }; }; -using ExternValue = Variant; +using ExternValue = Variant; class ExportInstance { public: @@ -296,13 +313,16 @@ public: explicit ModuleInstance( Vector types, Vector function_addresses, Vector table_addresses, Vector memory_addresses, Vector global_addresses, Vector data_addresses, + Vector tag_addresses, Vector tag_types, Vector exports) : m_types(move(types)) + , m_tag_types(move(tag_types)) , m_functions(move(function_addresses)) , m_tables(move(table_addresses)) , m_memories(move(memory_addresses)) , m_globals(move(global_addresses)) , m_datas(move(data_addresses)) + , m_tags(move(tag_addresses)) , m_exports(move(exports)) { } @@ -317,6 +337,8 @@ public: auto& elements() const { return m_elements; } auto& datas() const { return m_datas; } auto& exports() const { return m_exports; } + auto& tags() const { return m_tags; } + auto& tag_types() const { return m_tag_types; } auto& types() { return m_types; } auto& functions() { return m_functions; } @@ -326,15 +348,19 @@ public: auto& elements() { return m_elements; } auto& datas() { return m_datas; } auto& exports() { return m_exports; } + auto& tags() { return m_tags; } + auto& tag_types() { return m_tag_types; } private: Vector m_types; + Vector m_tag_types; Vector m_functions; Vector m_tables; Vector m_memories; Vector m_globals; Vector m_elements; Vector m_datas; + Vector m_tags; Vector m_exports; }; @@ -552,6 +578,38 @@ private: Vector m_references; }; +class TagInstance { +public: + TagInstance(FunctionType const& type, TagType::Flags flags) + : m_type(type) + , m_flags(flags) + { + } + + auto& type() const { return m_type; } + auto flags() const { return m_flags; } + +private: + FunctionType m_type; + TagType::Flags m_flags; +}; + +class ExceptionInstance { +public: + explicit ExceptionInstance(TagInstance const& type, Vector params) + : m_type(type) + , m_params(move(params)) + { + } + + auto& type() const { return m_type; } + auto& params() const { return m_params; } + +private: + TagInstance m_type; + Vector m_params; +}; + class WASM_API Store { public: Store() = default; @@ -563,6 +621,8 @@ public: Optional allocate_data(Vector); Optional allocate(GlobalType const&, Value); Optional allocate(ValueType const&, Vector); + Optional allocate(FunctionType const&, TagType::Flags); + Optional allocate(TagInstance const&, Vector); Module const* get_module_for(FunctionAddress); FunctionInstance* get(FunctionAddress); @@ -571,6 +631,8 @@ public: GlobalInstance* get(GlobalAddress); DataInstance* get(DataAddress); ElementInstance* get(ElementAddress); + TagInstance* get(TagAddress); + ExceptionInstance* get(ExceptionAddress); MemoryInstance* unsafe_get(MemoryAddress address) { return &m_memories.data()[address.value()]; } @@ -581,6 +643,8 @@ private: Vector m_globals; Vector m_elements; Vector m_datas; + Vector m_tags; + Vector m_exceptions; }; class Label { diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index 7386c9d8ce9..867ae7a1519 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -3754,6 +3754,36 @@ HANDLE_INSTRUCTION(i32x4_relaxed_dot_i8x16_i7x16_add_s) TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } +HANDLE_INSTRUCTION(throw_ref) +{ + interpreter.set_trap("Not Implemented: Proposal 'Exception-handling'"sv); + return Outcome::Return; +} + +HANDLE_INSTRUCTION(throw_) +{ + { + auto tag_address = configuration.frame().module().tags()[instruction->arguments().get().value()]; + auto& tag_instance = *configuration.store().get(tag_address); + auto& type = tag_instance.type(); + auto values = Vector(configuration.value_stack().span().slice_from_end(type.parameters().size())); + configuration.value_stack().shrink(configuration.value_stack().size() - type.parameters().size()); + auto exception_address = configuration.store().allocate(tag_instance, move(values)); + if (!exception_address.has_value()) { + interpreter.set_trap("Out of memory"sv); + return Outcome::Return; + } + configuration.value_stack().append(Value(Reference { Reference::Exception { *exception_address } })); + } + TAILCALL return InstructionHandler::operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); +} + +HANDLE_INSTRUCTION(try_table) +{ + interpreter.set_trap("Not Implemented: Proposal 'Exception-handling'"sv); + return Outcome::Return; +} + template constexpr static auto handle_instruction(Args&&... a) { diff --git a/Libraries/LibWasm/AbstractMachine/Validator.cpp b/Libraries/LibWasm/AbstractMachine/Validator.cpp index 300e25f9b42..1d435140d12 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.cpp +++ b/Libraries/LibWasm/AbstractMachine/Validator.cpp @@ -58,6 +58,10 @@ ErrorOr Validator::validate(Module& module) m_globals_without_internal_globals.append(type); m_context.globals.append(type); return {}; + }, + [&](TagType const&) -> ErrorOr { + m_context.tags.append(import_.description().get()); + return {}; })); } @@ -89,6 +93,10 @@ ErrorOr Validator::validate(Module& module) m_context.datas.resize(module.data_section().data().size()); + m_context.tags.ensure_capacity(m_context.tags.size() + module.tag_section().tags().size()); + for (auto& tag : module.tag_section().tags()) + m_context.tags.append(TagType(tag.type(), tag.flags())); + // We need to build the set of declared functions to check that `ref.func` uses a specific set of predetermined functions, found in: // - Element initializer expressions // - Global initializer expressions @@ -277,6 +285,17 @@ ErrorOr Validator::validate(MemoryType const& type) return validate(type.limits(), 1 << 16); } +ErrorOr Validator::validate(Wasm::TagType const& tag_type) +{ + // The function type t1^n -> t2^m must be valid + TRY(validate(tag_type.type())); + auto& type = m_context.types[tag_type.type().value()]; + // The type sequence t2^m must be empty + if (!type.results().is_empty()) + return Errors::invalid("TagType"sv); + return {}; +} + ErrorOr Validator::validate(BlockType const& type) { if (type.kind() == BlockType::Index) { @@ -2030,6 +2049,85 @@ VALIDATE_INSTRUCTION(if_) return {}; } +// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-x +VALIDATE_INSTRUCTION(throw_) +{ + auto tag_index = instruction.arguments().get(); + TRY(validate(tag_index)); + + auto tag_type = m_context.tags[tag_index.value()]; + auto& type = m_context.types[tag_type.type().value()]; + if (!type.results().is_empty()) + return Errors::invalid("throw type"sv, "empty"sv, type.results()); + + for (auto const& parameter : type.parameters().in_reverse()) + TRY(stack.take(parameter)); + + m_frames.last().unreachable = true; + stack.resize(m_frames.last().initial_size); + + return {}; +} + +// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-ref +VALIDATE_INSTRUCTION(throw_ref) +{ + TRY(stack.take()); + m_frames.last().unreachable = true; + stack.resize(m_frames.last().initial_size); + return {}; +} + +// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-try-table-xref-syntax-instructions-syntax-blocktype-mathit-blocktype-xref-syntax-instructions-syntax-catch-mathit-catch-ast-xref-syntax-instructions-syntax-instr-mathit-instr-ast-xref-syntax-instructions-syntax-instr-control-mathsf-end +// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-x-l +// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-ref-x-l +// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-all-l +// https://webassembly.github.io/exception-handling/core/valid/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-catch-all-ref-l +VALIDATE_INSTRUCTION(try_table) +{ + auto& args = instruction.arguments().get(); + auto block_type = TRY(validate(args.try_.block_type)); + for (auto& catch_ : args.catches) { + auto label = catch_.target_label(); + TRY(validate(label)); + auto& target_label_type = m_frames[(m_frames.size() - 1) - label.value()].labels(); + + if (auto tag = catch_.matching_tag_index(); tag.has_value()) { + TRY(validate(tag.value())); + auto tag_type = m_context.tags[tag->value()]; + auto& type = m_context.types[tag_type.type().value()]; + if (!type.results().is_empty()) + return Errors::invalid("catch type"sv, "empty"sv, type.results()); + + Span parameters_to_check = type.parameters().span(); + if (catch_.is_ref()) { + // catch_ref x l + auto& parameters = type.parameters(); + if (parameters.is_empty() || parameters.last().kind() != ValueType::ExceptionReference) + return Errors::invalid("catch_ref type"sv, "[..., exnref]"sv, parameters); + parameters_to_check = parameters_to_check.slice(0, parameters.size() - 1); + } else { + // catch x l + // (noop here) + } + + if (parameters_to_check != target_label_type.span()) + return Errors::non_conforming_types("catch"sv, parameters_to_check, target_label_type.span()); + } else { + if (catch_.is_ref()) { + // catch_all_ref l + if (target_label_type.size() != 1 || target_label_type[0].kind() != ValueType::ExceptionReference) + return Errors::invalid("catch_all_ref type"sv, "[exnref]"sv, target_label_type); + } else { + // catch_all l + if (!target_label_type.is_empty()) + return Errors::invalid("catch_all type"sv, "empty"sv, target_label_type); + } + } + } + return {}; +} + VALIDATE_INSTRUCTION(br) { auto label = instruction.arguments().get(); diff --git a/Libraries/LibWasm/AbstractMachine/Validator.h b/Libraries/LibWasm/AbstractMachine/Validator.h index a5c63332b35..9027271a862 100644 --- a/Libraries/LibWasm/AbstractMachine/Validator.h +++ b/Libraries/LibWasm/AbstractMachine/Validator.h @@ -30,6 +30,7 @@ struct Context { COWVector elements; COWVector datas; COWVector locals; + COWVector tags; Optional data_count; RefPtr references { make_ref_counted() }; size_t imported_function_count { 0 }; @@ -68,6 +69,7 @@ public: ErrorOr validate(MemorySection const&); ErrorOr validate(TableSection const&); ErrorOr validate(CodeSection const&); + ErrorOr validate(TagSection const&); ErrorOr validate(FunctionSection const&) { return {}; } ErrorOr validate(DataCountSection const&) { return {}; } ErrorOr validate(TypeSection const&) { return {}; } @@ -136,6 +138,13 @@ public: return Errors::invalid("TableIndex"sv); } + ErrorOr validate(TagIndex index) const + { + if (index.value() < m_context.tags.size()) + return {}; + return Errors::invalid("TagIndex"sv); + } + enum class FrameKind { Block, Loop, @@ -293,6 +302,7 @@ public: ErrorOr validate(TableType const&); ErrorOr validate(MemoryType const&); ErrorOr validate(GlobalType const&) { return {}; } + ErrorOr validate(TagType const&); // Proposal 'memory64' ErrorOr take_memory_address(Stack& stack, MemoryType const& memory, Instruction::MemoryArgument const& arg) diff --git a/Libraries/LibWasm/Constants.h b/Libraries/LibWasm/Constants.h index 95e261472b7..81ab5ac5fdf 100644 --- a/Libraries/LibWasm/Constants.h +++ b/Libraries/LibWasm/Constants.h @@ -34,6 +34,7 @@ static constexpr auto extern_function_tag = 0x00; static constexpr auto extern_table_tag = 0x01; static constexpr auto extern_memory_tag = 0x02; static constexpr auto extern_global_tag = 0x03; +static constexpr auto extern_tag_tag = 0x04; // Proposal "exception-handling" static constexpr auto page_size = 64 * KiB; diff --git a/Libraries/LibWasm/Opcode.h b/Libraries/LibWasm/Opcode.h index d3bce3065ea..010b6c9b720 100644 --- a/Libraries/LibWasm/Opcode.h +++ b/Libraries/LibWasm/Opcode.h @@ -22,6 +22,8 @@ namespace Instructions { M(loop, 0x03, 0, -1) \ M(if_, 0x04, 1, -1) \ M(structured_else, 0x05, -1, -1) \ + M(throw_, 0x08, -1, -1) \ + M(throw_ref, 0x0a, 1, -1) \ M(structured_end, 0x0b, -1, -1) \ M(br, 0x0c, 0, -1) \ M(br_if, 0x0d, 1, -1) \ @@ -34,6 +36,7 @@ namespace Instructions { M(drop, 0x1a, 1, 0) \ M(select, 0x1b, 3, 1) \ M(select_typed, 0x1c, 3, 1) \ + M(try_table, 0x1f, 0, 0) \ M(local_get, 0x20, 0, 1) \ M(local_set, 0x21, 1, 0) \ M(local_tee, 0x22, 1, 1) \ diff --git a/Libraries/LibWasm/Parser/Parser.cpp b/Libraries/LibWasm/Parser/Parser.cpp index 661460cf079..c5fd7a713dc 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -202,6 +202,16 @@ ParseResult GlobalType::parse(ConstrainedStream& stream) return GlobalType { type_result, mutable_ == 0x01 }; } +ParseResult TagType::parse(ConstrainedStream& stream) +{ + ScopeLogger logger("TagType"sv); + auto flags = TRY_READ(stream, u8, ParseError::ExpectedKindTag); + if (flags != 0) + return ParseError::InvalidTag; + auto index = TRY(GenericIndexParser::parse(stream)); + return TagType { index, static_cast(flags) }; +} + ParseResult BlockType::parse(ConstrainedStream& stream) { ScopeLogger logger("BlockType"sv); @@ -231,6 +241,38 @@ ParseResult BlockType::parse(ConstrainedStream& stream) return BlockType { TypeIndex(index_value) }; } +ParseResult Catch::parse(ConstrainedStream& stream) +{ + ScopeLogger logger("Catch"sv); + auto kind = TRY_READ(stream, u8, ParseError::ExpectedKindTag); + switch (kind) { + case 0: { + // catch x l + auto tag_index = TRY(GenericIndexParser::parse(stream)); + auto label_index = TRY(GenericIndexParser::parse(stream)); + return Catch { false, tag_index, label_index }; + } + case 1: { + // catch_ref x l + auto tag_index = TRY(GenericIndexParser::parse(stream)); + auto label_index = TRY(GenericIndexParser::parse(stream)); + return Catch { true, tag_index, label_index }; + } + case 2: { + // catch_all l + auto label_index = TRY(GenericIndexParser::parse(stream)); + return Catch { false, {}, label_index }; + } + case 3: { + // catch_all_ref l + auto label_index = TRY(GenericIndexParser::parse(stream)); + return Catch { true, {}, label_index }; + } + default: + return ParseError::InvalidTag; + } +} + ParseResult Instruction::parse(ConstrainedStream& stream) { ScopeLogger logger("Instruction"sv); @@ -249,6 +291,19 @@ ParseResult Instruction::parse(ConstrainedStream& stream) opcode, StructuredInstructionArgs { block_type, {}, {} } }; } + case Instructions::try_table.value(): { + // try_table block_type (catch*) (instruction*) end + auto block_type = TRY(BlockType::parse(stream)); + auto catch_types = TRY(parse_vector(stream)); + auto structured_args = StructuredInstructionArgs { block_type, {}, {} }; + return Instruction { + opcode, TryTableArgs { move(structured_args), move(catch_types) } + }; + } + case Instructions::throw_.value(): { + auto tag_index = TRY(GenericIndexParser::parse(stream)); + return Instruction { opcode, tag_index }; + } case Instructions::br.value(): case Instructions::br_if.value(): { // branches with a single label immediate @@ -384,6 +439,7 @@ ParseResult Instruction::parse(ConstrainedStream& stream) auto index = TRY(GenericIndexParser::parse(stream)); return Instruction { opcode, index }; } + case Instructions::throw_ref.value(): case Instructions::structured_end.value(): case Instructions::structured_else.value(): case Instructions::ref_is_null.value(): @@ -944,6 +1000,8 @@ ParseResult ImportSection::Import::parse(ConstrainedStrea return parse_with_type(stream, module, name); case Constants::extern_global_tag: return parse_with_type(stream, module, name); + case Constants::extern_tag_tag: + return parse_with_type(stream, module, name); default: return ParseError::InvalidTag; } @@ -1012,6 +1070,7 @@ ParseResult Expression::parse(ConstrainedStream& stream, Optional Expression::parse(ConstrainedStream& stream, Optional(); // Patch the end_ip of the last structured instruction - args.end_ip = ip + (args.else_ip.has_value() ? 1 : 0); + auto entry = stack.take_last(); + instructions[entry.value()].arguments().visit( + [&](Instruction::StructuredInstructionArgs& args) { + args.end_ip = ip + (args.else_ip.has_value() ? 1 : 0); + }, + [&](Instruction::TryTableArgs& args) { + args.try_.end_ip = ip + 1; + }, + [](auto&) { VERIFY_NOT_REACHED(); }); break; } case Instructions::structured_else.value(): { @@ -1075,6 +1140,8 @@ ParseResult ExportSection::Export::parse(ConstrainedStrea return Export { name, ExportDesc { MemoryIndex { index } } }; case Constants::extern_global_tag: return Export { name, ExportDesc { GlobalIndex { index } } }; + case Constants::extern_tag_tag: + return Export { name, ExportDesc { TagIndex { index } } }; default: return ParseError::InvalidTag; } @@ -1256,6 +1323,25 @@ ParseResult DataCountSection::parse(ConstrainedStream& stream) return DataCountSection { value }; } +ParseResult TagSection::parse(ConstrainedStream& stream) +{ + ScopeLogger logger("TagSection"sv); + // https://webassembly.github.io/exception-handling/core/binary/modules.html#binary-tagsec + auto tags = TRY(parse_vector(stream)); + return TagSection { move(tags) }; +} + +ParseResult TagSection::Tag::parse(ConstrainedStream& stream) +{ + // https://webassembly.github.io/exception-handling/core/binary/modules.html#binary-tagsec + ScopeLogger logger("Tag"sv); + auto flag = TRY_READ(stream, u8, ParseError::ExpectedKindTag); + if (flag != 0) + return ParseError::InvalidTag; // currently the only valid flag is 0 + auto type_index = TRY(GenericIndexParser::parse(stream)); + return TagSection::Tag { type_index, static_cast(flag) }; +} + ParseResult SectionId::parse(Stream& stream) { u8 id = TRY_READ(stream, u8, ParseError::ExpectedIndex); @@ -1286,6 +1372,8 @@ ParseResult SectionId::parse(Stream& stream) return SectionId(SectionIdKind::Data); case 0x0c: return SectionId(SectionIdKind::DataCount); + case 0x0d: + return SectionId(SectionIdKind::Tag); default: return ParseError::InvalidIndex; } @@ -1357,14 +1445,15 @@ ParseResult> Module::parse(Stream& stream) case SectionId::SectionIdKind::DataCount: module.data_count_section() = TRY(DataCountSection::parse(section_stream)); break; + case SectionId::SectionIdKind::Tag: + module.tag_section() = TRY(TagSection::parse(section_stream)); + break; default: return ParseError::InvalidIndex; } - if (section_id.kind() != SectionId::SectionIdKind::Custom) { - if (section_id.kind() < last_section_id) - return ParseError::SectionOutOfOrder; - last_section_id = section_id.kind(); - } + if (!section_id.can_appear_after(last_section_id)) + return ParseError::SectionOutOfOrder; + last_section_id = section_id.kind(); if (section_stream.remaining() != 0) return ParseError::SectionSizeMismatch; } diff --git a/Libraries/LibWasm/Printer/Printer.cpp b/Libraries/LibWasm/Printer/Printer.cpp index 2a12f07d09e..e24458afc87 100644 --- a/Libraries/LibWasm/Printer/Printer.cpp +++ b/Libraries/LibWasm/Printer/Printer.cpp @@ -252,7 +252,8 @@ void Printer::print(Wasm::ExportSection::Export const& entry) [this](FunctionIndex const& index) { print("(function index {})\n", index.value()); }, [this](TableIndex const& index) { print("(table index {})\n", index.value()); }, [this](MemoryIndex const& index) { print("(memory index {})\n", index.value()); }, - [this](GlobalIndex const& index) { print("(global index {})\n", index.value()); }); + [this](GlobalIndex const& index) { print("(global index {})\n", index.value()); }, + [this](TagIndex const& index) { print("(tag index {})\n", index.value()); }); } print_indent(); print(")\n"); @@ -398,6 +399,18 @@ void Printer::print(Wasm::GlobalType const& type) print(")\n"); } +void Printer::print(Wasm::TagType const& type) +{ + print_indent(); + print("(type tag\n"); + { + TemporaryChange change { m_indent, m_indent + 1 }; + print(type.type()); + } + print_indent(); + print(")\n"); +} + void Printer::print(Wasm::ImportSection const& section) { if (section.imports().is_empty()) @@ -432,6 +445,35 @@ void Printer::print(Wasm::ImportSection::Import const& import) print(")\n"); } +void Printer::print(Wasm::TagSection const& section) +{ + // (section tag\n[ ](tag type)*) + if (section.tags().is_empty()) + return; + + print_indent(); + print("(section tag\n"); + { + TemporaryChange change { m_indent, m_indent + 1 }; + for (auto& tag : section.tags()) + print(tag); + } + print_indent(); + print(")\n"); +} + +void Printer::print(Wasm::TagSection::Tag const& tag) +{ + print_indent(); + print("(tag (type index {}))\n", tag.type().value()); +} + +void Printer::print(Wasm::TypeIndex const& index) +{ + print_indent(); + print("(type index {})\n", index.value()); +} + void Printer::print(Wasm::Instruction const& instruction) { print_indent(); @@ -472,6 +514,16 @@ void Printer::print(Wasm::Instruction const& instruction) print_indent(); print("(else {}) (end {}))", args.else_ip.has_value() ? ByteString::number(args.else_ip->value()) : "(none)", args.end_ip.value()); }, + [&](Instruction::TryTableArgs const& args) { + print("(try_table "); + print(args.try_.block_type); + print(" (catches\n"); + TemporaryChange change { m_indent, m_indent + 1 }; + for (auto& catch_ : args.catches) + print(catch_); + print_indent(); + print(") (end {}))", args.try_.end_ip.value()); + }, [&](Instruction::TableBranchArgs const& args) { print("(table_branch"); for (auto& label : args.labels) @@ -491,6 +543,24 @@ void Printer::print(Wasm::Instruction const& instruction) } } +void Printer::print(Catch const& catch_) +{ + print_indent(); + StringBuilder name_builder; + name_builder.appendff("catch{}{}", catch_.matching_tag_index().has_value() ? ""sv : "_all"sv, catch_.is_ref() ? "_ref"sv : ""sv); + print("({} ", name_builder.string_view()); + if (auto index = catch_.matching_tag_index(); index.has_value()) + print("(tag index {})", index.value()); + print("\n"); + { + TemporaryChange change { m_indent, m_indent + 1 }; + print_indent(); + print("(label index {})\n", catch_.target_label().value()); + } + print_indent(); + print(")\n"); +} + void Printer::print(Wasm::Limits const& limits) { print_indent(); @@ -566,6 +636,7 @@ void Printer::print(Wasm::Module const& module) print(module.function_section()); print(module.table_section()); print(module.memory_section()); + print(module.tag_section()); print(module.global_section()); print(module.export_section()); print(module.start_section()); @@ -682,9 +753,11 @@ void Printer::print(Wasm::Value const& value, Wasm::ValueType const& type) break; case ValueType::FunctionReference: case ValueType::ExternReference: + case ValueType::ExceptionReference: print("addr({})", value.to().ref().visit( [](Wasm::Reference::Null const&) { return ByteString("null"); }, + [](Wasm::Reference::Exception const&) { return ByteString("exception"); }, [](auto const& ref) { return ByteString::number(ref.address.value()); })); break; } @@ -705,6 +778,7 @@ void Printer::print(Wasm::Reference const& value) "addr({})\n", value.ref().visit( [](Wasm::Reference::Null const&) { return ByteString("null"); }, + [](Wasm::Reference::Exception const&) { return ByteString("exception"); }, [](auto const& ref) { return ByteString::number(ref.address.value()); })); } @@ -716,6 +790,9 @@ HashMap Wasm::Names::instruction_names { { Instructions::block, "block" }, { Instructions::loop, "loop" }, { Instructions::if_, "if" }, + { Instructions::try_table, "try_table" }, + { Instructions::throw_, "throw" }, + { Instructions::throw_ref, "throw_ref" }, { Instructions::br, "br" }, { Instructions::br_if, "br.if" }, { Instructions::br_table, "br.table" }, diff --git a/Libraries/LibWasm/Printer/Printer.h b/Libraries/LibWasm/Printer/Printer.h index 0f44b1b6c6e..03618030234 100644 --- a/Libraries/LibWasm/Printer/Printer.h +++ b/Libraries/LibWasm/Printer/Printer.h @@ -44,6 +44,8 @@ struct WASM_API Printer { void print(Wasm::GlobalType const&); void print(Wasm::ImportSection const&); void print(Wasm::ImportSection::Import const&); + void print(Wasm::TagSection const&); + void print(Wasm::TagSection::Tag const&); void print(Wasm::Instruction const&); void print(Wasm::Limits const&); void print(Wasm::Locals const&); @@ -59,8 +61,11 @@ struct WASM_API Printer { void print(Wasm::TableType const&); void print(Wasm::TypeSection const&); void print(Wasm::ValueType const&); + void print(Wasm::TagType const&); void print(Wasm::Value const&); void print(Wasm::Value const&, ValueType const&); + void print(Wasm::Catch const&); + void print(Wasm::TypeIndex const&); private: void print_indent(); diff --git a/Libraries/LibWasm/Types.h b/Libraries/LibWasm/Types.h index 2cfa2a43ab1..f62f728b6a8 100644 --- a/Libraries/LibWasm/Types.h +++ b/Libraries/LibWasm/Types.h @@ -72,6 +72,7 @@ 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); @@ -167,6 +168,7 @@ public: V128, FunctionReference, ExternReference, + ExceptionReference, }; explicit ValueType(Kind kind) @@ -200,6 +202,8 @@ public: return "funcref"; case ExternReference: return "externref"; + case ExceptionReference: + return "exnref"; } VERIFY_NOT_REACHED(); } @@ -336,6 +340,29 @@ private: 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: @@ -386,6 +413,35 @@ private: }; }; +// 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 @@ -457,6 +513,12 @@ public: MemoryIndex memory_index; }; + // Proposal "exception-handling" + struct TryTableArgs { + StructuredInstructionArgs try_; // "else" unused. + Vector catches; + }; + struct ShuffleArgument { explicit ShuffleArgument(u8 (&lanes)[16]) : lanes { @@ -509,6 +571,7 @@ private: ElementIndex, FunctionIndex, GlobalIndex, + TagIndex, IndirectCallArgs, LabelIndex, LaneIndex, @@ -524,6 +587,7 @@ private: TableElementArgs, TableIndex, TableTableArgs, + TryTableArgs, ValueType, Vector, double, @@ -568,6 +632,17 @@ struct CompiledInstructions { 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 { @@ -584,14 +659,44 @@ public: 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) { } - SectionIdKind kind() const { return m_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); @@ -638,7 +743,7 @@ class ImportSection { public: class Import { public: - using ImportDesc = Variant; + using ImportDesc = Variant; Import(ByteString module, ByteString name, ImportDesc description) : m_module(move(module)) , m_name(move(name)) @@ -826,7 +931,7 @@ private: class ExportSection { private: - using ExportDesc = Variant; + using ExportDesc = Variant; public: class Export { @@ -1059,6 +1164,43 @@ 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: @@ -1099,6 +1241,8 @@ public: 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; } @@ -1123,6 +1267,7 @@ private: 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; diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 16cd48248a5..c6a1867ba59 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -617,6 +617,8 @@ JS::ThrowCompletionOr to_webassembly_value(JS::VM& vm, JS::Value va cache.add_extern_value(extern_addr, value); return Wasm::Value { Wasm::Reference { Wasm::Reference::Extern { extern_addr } } }; } + case Wasm::ValueType::ExceptionReference: + return Wasm::Value(Wasm::ValueType { Wasm::ValueType::Kind::ExceptionReference }); case Wasm::ValueType::V128: return vm.throw_completion("Cannot convert a vector value to a javascript value"sv); } @@ -636,6 +638,8 @@ Wasm::Value default_webassembly_value(JS::VM& vm, Wasm::ValueType type) return Wasm::Value(type); case Wasm::ValueType::ExternReference: return MUST(to_webassembly_value(vm, JS::js_undefined(), type)); + case Wasm::ValueType::ExceptionReference: + return Wasm::Value(type); } VERIFY_NOT_REACHED(); } @@ -680,6 +684,7 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type) return value.release_value(); } case Wasm::ValueType::V128: + case Wasm::ValueType::ExceptionReference: VERIFY_NOT_REACHED(); } VERIFY_NOT_REACHED(); diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp index f8ab16e883e..a54fa346319 100644 --- a/Tests/LibWasm/test-wasm.cpp +++ b/Tests/LibWasm/test-wasm.cpp @@ -314,9 +314,11 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export) } case Wasm::ValueType::FunctionReference: case Wasm::ValueType::ExternReference: + case Wasm::ValueType::ExceptionReference: auto ref = global->value().to(); return ref.ref().visit( [&](Wasm::Reference::Null const&) -> JS::Value { return JS::js_null(); }, + [](Wasm::Reference::Exception const&) -> JS::Value { return JS::js_undefined(); }, [&](auto const& ref) -> JS::Value { return JS::Value(static_cast(ref.address.value())); }); } } @@ -398,6 +400,12 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) } arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Extern { static_cast(double_value) } })); break; + case Wasm::ValueType::Kind::ExceptionReference: + if (argument.is_null()) + arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExceptionReference) } })); + else + return vm.throw_completion("Exception references are not supported"sv); + break; } } @@ -431,7 +439,9 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) } case Wasm::ValueType::FunctionReference: case Wasm::ValueType::ExternReference: - return (value.to()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](auto const& ref) { return JS::Value(static_cast(ref.address.value())); }); + return (value.to()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast(ref.address.value())); }); + case Wasm::ValueType::ExceptionReference: + return JS::js_null(); } VERIFY_NOT_REACHED(); }; diff --git a/Utilities/wasm.cpp b/Utilities/wasm.cpp index 6500f03df8c..921a8cde0f4 100644 --- a/Utilities/wasm.cpp +++ b/Utilities/wasm.cpp @@ -233,6 +233,7 @@ static ErrorOr parse_value(StringView spec) case Wasm::ValueType::V128: case Wasm::ValueType::FunctionReference: case Wasm::ValueType::ExternReference: + case Wasm::ValueType::ExceptionReference: VERIFY_NOT_REACHED(); } last_value = parsed.value.value(); From 33d2959a4c0287970ab8f75cee5c9a95f6746270 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Fri, 3 Oct 2025 20:24:09 +0200 Subject: [PATCH 7/8] LibWeb: Stub wasm exceptions and memory64 API modifications --- Libraries/LibWeb/WebAssembly/Instance.cpp | 3 ++- Libraries/LibWeb/WebAssembly/Memory.cpp | 2 +- Libraries/LibWeb/WebAssembly/Module.cpp | 16 ++++++++++++++-- Libraries/LibWeb/WebAssembly/Table.cpp | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Libraries/LibWeb/WebAssembly/Instance.cpp b/Libraries/LibWeb/WebAssembly/Instance.cpp index 48ddaba28d0..b7415ae58bc 100644 --- a/Libraries/LibWeb/WebAssembly/Instance.cpp +++ b/Libraries/LibWeb/WebAssembly/Instance.cpp @@ -88,7 +88,8 @@ void Instance::initialize(JS::Realm& realm) } m_exports->define_direct_property(name, *object, JS::default_attributes); - }); + }, + [&](Wasm::TagAddress const&) { dbgln("Not yet implemented: tags"); }); } MUST(m_exports->set_integrity_level(IntegrityLevel::Frozen)); diff --git a/Libraries/LibWeb/WebAssembly/Memory.cpp b/Libraries/LibWeb/WebAssembly/Memory.cpp index 8053827d5d8..5678ca2d2f7 100644 --- a/Libraries/LibWeb/WebAssembly/Memory.cpp +++ b/Libraries/LibWeb/WebAssembly/Memory.cpp @@ -34,7 +34,7 @@ WebIDL::ExceptionOr> Memory::construct_impl(JS::Realm& realm, Me if (shared && !descriptor.maximum.has_value()) return vm.throw_completion("Maximum has to be specified for shared memory."sv); - Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) }; + Wasm::Limits limits { Wasm::AddressType::I32, descriptor.initial, descriptor.maximum.map([](auto x) -> u64 { return x; }) }; Wasm::MemoryType memory_type { move(limits) }; auto& cache = Detail::get_cache(realm); diff --git a/Libraries/LibWeb/WebAssembly/Module.cpp b/Libraries/LibWeb/WebAssembly/Module.cpp index 080bc50b486..d37ebf44404 100644 --- a/Libraries/LibWeb/WebAssembly/Module.cpp +++ b/Libraries/LibWeb/WebAssembly/Module.cpp @@ -45,13 +45,19 @@ WebIDL::ExceptionOr> Module::imports(JS::VM&, GC: auto& imports = module_object->m_compiled_module->module->import_section().imports(); import_objects.ensure_capacity(imports.size()); for (auto& import : imports) { + if (import.description().has()) { + dbgln("Not yet implemented: importing tags"); + continue; + } + // 3.1. Let kind be the string value of the extern type type. auto const kind = import.description().visit( [](Wasm::TypeIndex) { return Bindings::ImportExportKind::Function; }, [](Wasm::TableType) { return Bindings::ImportExportKind::Table; }, [](Wasm::MemoryType) { return Bindings::ImportExportKind::Memory; }, [](Wasm::GlobalType) { return Bindings::ImportExportKind::Global; }, - [](Wasm::FunctionType) { return Bindings::ImportExportKind::Function; }); + [](Wasm::FunctionType) { return Bindings::ImportExportKind::Function; }, + [](Wasm::TagType) -> Bindings::ImportExportKind { TODO(); }); // 3.2. Let obj be «[ "module" → moduleName, "name" → name, "kind" → kind ]». ModuleImportDescriptor descriptor { @@ -78,12 +84,18 @@ WebIDL::ExceptionOr> Module::exports(JS::VM&, GC: auto& exports = module_object->m_compiled_module->module->export_section().entries(); export_objects.ensure_capacity(exports.size()); for (auto& entry : exports) { + if (entry.description().has()) { + dbgln("Not yet implemented: exporting tags"); + continue; + } + // 3.1. Let kind be the string value of the extern type type. auto const kind = entry.description().visit( [](Wasm::FunctionIndex) { return Bindings::ImportExportKind::Function; }, [](Wasm::TableIndex) { return Bindings::ImportExportKind::Table; }, [](Wasm::MemoryIndex) { return Bindings::ImportExportKind::Memory; }, - [](Wasm::GlobalIndex) { return Bindings::ImportExportKind::Global; }); + [](Wasm::GlobalIndex) { return Bindings::ImportExportKind::Global; }, + [](Wasm::TagIndex) -> Bindings::ImportExportKind { TODO(); }); // 3.2. Let obj be «[ "name" → name, "kind" → kind ]». ModuleExportDescriptor descriptor { .name = String::from_utf8_with_replacement_character(entry.name()), diff --git a/Libraries/LibWeb/WebAssembly/Table.cpp b/Libraries/LibWeb/WebAssembly/Table.cpp index 5a1aaad34ae..0896d92666e 100644 --- a/Libraries/LibWeb/WebAssembly/Table.cpp +++ b/Libraries/LibWeb/WebAssembly/Table.cpp @@ -41,7 +41,7 @@ WebIDL::ExceptionOr> Table::construct_impl(JS::Realm& realm, Tabl if (descriptor.maximum.has_value() && descriptor.maximum.value() < descriptor.initial) return vm.throw_completion("Maximum should not be less than initial in table type"sv); - Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) }; + Wasm::Limits limits { Wasm::AddressType::I32, descriptor.initial, descriptor.maximum.map([](auto x) -> u64 { return x; }) }; Wasm::TableType table_type { reference_type, move(limits) }; auto& cache = Detail::get_cache(realm); From 92c0cbc453d7f0251e2fc0f594aeaa2e00b003c4 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 14 Oct 2025 17:21:33 +0200 Subject: [PATCH 8/8] LibWasm+LibWeb: Stub wasm-gc's heap reference types WPT inserts these into all modules regardless of whether they're used, so let's just parse and ignore them. --- .../LibWasm/AbstractMachine/AbstractMachine.h | 4 ++++ Libraries/LibWasm/Constants.h | 14 ++++++++++++++ Libraries/LibWasm/Parser/Parser.cpp | 16 ++++++++++++++++ Libraries/LibWasm/Printer/Printer.cpp | 3 +++ Libraries/LibWasm/Types.h | 5 ++++- Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 5 +++++ Tests/LibWasm/test-wasm.cpp | 13 ++++++++++--- Utilities/wasm.cpp | 1 + 8 files changed, 57 insertions(+), 4 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 6cb257b9d0d..13985bb8b74 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -99,6 +99,10 @@ public: // ref.null exnref m_value = u128(0, 4); break; + case ValueType::UnsupportedHeapReference: + // ref.null (todo) + m_value = u128(0, 5); + break; } } diff --git a/Libraries/LibWasm/Constants.h b/Libraries/LibWasm/Constants.h index 81ab5ac5fdf..653668cea37 100644 --- a/Libraries/LibWasm/Constants.h +++ b/Libraries/LibWasm/Constants.h @@ -19,6 +19,20 @@ static constexpr auto v128_tag = 0x7b; static constexpr auto function_reference_tag = 0x70; static constexpr auto extern_reference_tag = 0x6f; +// wasm-gc references +static constexpr auto array_reference_tag = 0x6a; +static constexpr auto struct_reference_tag = 0x6b; +static constexpr auto i31_reference_tag = 0x6c; +static constexpr auto eq_reference_tag = 0x6d; +static constexpr auto any_reference_tag = 0x6e; +static constexpr auto none_reference_tag = 0x71; +static constexpr auto noextern_reference_tag = 0x72; +static constexpr auto nofunc_reference_tag = 0x73; +static constexpr auto noexn_heap_reference_tag = 0x74; + +static constexpr auto nullable_reference_tag_tag = 0x63; +static constexpr auto non_nullable_reference_tag_tag = 0x64; + // Function static constexpr auto function_signature_tag = 0x60; diff --git a/Libraries/LibWasm/Parser/Parser.cpp b/Libraries/LibWasm/Parser/Parser.cpp index c5fd7a713dc..e8da4fd9c89 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -118,6 +118,22 @@ ParseResult ValueType::parse(Stream& stream) return ValueType(FunctionReference); case Constants::extern_reference_tag: return ValueType(ExternReference); + case Constants::array_reference_tag: + case Constants::struct_reference_tag: + case Constants::i31_reference_tag: + case Constants::eq_reference_tag: + case Constants::any_reference_tag: + case Constants::none_reference_tag: + case Constants::noextern_reference_tag: + case Constants::nofunc_reference_tag: + case Constants::noexn_heap_reference_tag: + // FIXME: Implement these when we support wasm-gc properly. + return ValueType(UnsupportedHeapReference); + case Constants::nullable_reference_tag_tag: + case Constants::non_nullable_reference_tag_tag: + tag = TRY_READ(stream, u8, ParseError::ExpectedKindTag); + (void)tag; + return ValueType(UnsupportedHeapReference); default: return ParseError::InvalidTag; } diff --git a/Libraries/LibWasm/Printer/Printer.cpp b/Libraries/LibWasm/Printer/Printer.cpp index e24458afc87..8beaf5f1be9 100644 --- a/Libraries/LibWasm/Printer/Printer.cpp +++ b/Libraries/LibWasm/Printer/Printer.cpp @@ -760,6 +760,9 @@ void Printer::print(Wasm::Value const& value, Wasm::ValueType const& type) [](Wasm::Reference::Exception const&) { return ByteString("exception"); }, [](auto const& ref) { return ByteString::number(ref.address.value()); })); break; + case ValueType::UnsupportedHeapReference: + print("unsupported-heap-ref"); + break; } TemporaryChange change { m_indent, 0 }; } diff --git a/Libraries/LibWasm/Types.h b/Libraries/LibWasm/Types.h index f62f728b6a8..511ba812c69 100644 --- a/Libraries/LibWasm/Types.h +++ b/Libraries/LibWasm/Types.h @@ -169,6 +169,7 @@ public: FunctionReference, ExternReference, ExceptionReference, + UnsupportedHeapReference, // Stub for wasm-gc proposal's reference types. }; explicit ValueType(Kind kind) @@ -178,7 +179,7 @@ public: bool operator==(ValueType const&) const = default; - auto is_reference() const { return m_kind == ExternReference || m_kind == FunctionReference; } + 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; } @@ -204,6 +205,8 @@ public: return "externref"; case ExceptionReference: return "exnref"; + case UnsupportedHeapReference: + return "todo.heapref"; } VERIFY_NOT_REACHED(); } diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index c6a1867ba59..6539fc7799a 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -621,6 +621,8 @@ JS::ThrowCompletionOr to_webassembly_value(JS::VM& vm, JS::Value va return Wasm::Value(Wasm::ValueType { Wasm::ValueType::Kind::ExceptionReference }); case Wasm::ValueType::V128: return vm.throw_completion("Cannot convert a vector value to a javascript value"sv); + case Wasm::ValueType::UnsupportedHeapReference: + return vm.throw_completion("Unsupported heap reference"sv); } VERIFY_NOT_REACHED(); @@ -640,6 +642,8 @@ Wasm::Value default_webassembly_value(JS::VM& vm, Wasm::ValueType type) return MUST(to_webassembly_value(vm, JS::js_undefined(), type)); case Wasm::ValueType::ExceptionReference: return Wasm::Value(type); + case Wasm::ValueType::UnsupportedHeapReference: + return Wasm::Value(type); } VERIFY_NOT_REACHED(); } @@ -685,6 +689,7 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type) } case Wasm::ValueType::V128: case Wasm::ValueType::ExceptionReference: + case Wasm::ValueType::UnsupportedHeapReference: VERIFY_NOT_REACHED(); } VERIFY_NOT_REACHED(); diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp index a54fa346319..6591e27b972 100644 --- a/Tests/LibWasm/test-wasm.cpp +++ b/Tests/LibWasm/test-wasm.cpp @@ -314,13 +314,16 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::get_export) } case Wasm::ValueType::FunctionReference: case Wasm::ValueType::ExternReference: - case Wasm::ValueType::ExceptionReference: + case Wasm::ValueType::ExceptionReference: { auto ref = global->value().to(); return ref.ref().visit( [&](Wasm::Reference::Null const&) -> JS::Value { return JS::js_null(); }, [](Wasm::Reference::Exception const&) -> JS::Value { return JS::js_undefined(); }, [&](auto const& ref) -> JS::Value { return JS::Value(static_cast(ref.address.value())); }); } + case Wasm::ValueType::UnsupportedHeapReference: + return vm.throw_completion("Unsupported heap reference"sv); + } } return vm.throw_completion(TRY_OR_THROW_OOM(vm, String::formatted("'{}' does not refer to a function or a global", name))); } @@ -406,6 +409,8 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) else return vm.throw_completion("Exception references are not supported"sv); break; + case Wasm::ValueType::Kind::UnsupportedHeapReference: + return vm.throw_completion("GC Heap references are not supported"sv); } } @@ -420,7 +425,7 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) if (result.values().is_empty()) return JS::js_null(); - auto to_js_value = [&](Wasm::Value const& value, Wasm::ValueType type) { + auto to_js_value = [&](Wasm::Value const& value, Wasm::ValueType type) -> JS::ThrowCompletionOr { switch (type.kind()) { case Wasm::ValueType::I32: return JS::Value(static_cast(value.to())); @@ -442,6 +447,8 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) return (value.to()).ref().visit([&](Wasm::Reference::Null) { return JS::js_null(); }, [&](Wasm::Reference::Exception) { return JS::Value(); }, [&](auto const& ref) { return JS::Value(static_cast(ref.address.value())); }); case Wasm::ValueType::ExceptionReference: return JS::js_null(); + case Wasm::ValueType::UnsupportedHeapReference: + return vm.throw_completion("Unsupported heap reference"sv); } VERIFY_NOT_REACHED(); }; @@ -452,6 +459,6 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke) size_t i = 0; return JS::Array::create_from(*vm.current_realm(), result.values(), [&](Wasm::Value value) { auto value_type = type->results()[i++]; - return to_js_value(value, value_type); + return MUST(to_js_value(value, value_type)); }); } diff --git a/Utilities/wasm.cpp b/Utilities/wasm.cpp index 921a8cde0f4..42e5fc3694b 100644 --- a/Utilities/wasm.cpp +++ b/Utilities/wasm.cpp @@ -234,6 +234,7 @@ static ErrorOr parse_value(StringView spec) case Wasm::ValueType::FunctionReference: case Wasm::ValueType::ExternReference: case Wasm::ValueType::ExceptionReference: + case Wasm::ValueType::UnsupportedHeapReference: VERIFY_NOT_REACHED(); } last_value = parsed.value.value();