From 8a74f52571a793b9e606b205251928e741efaa9e Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Thu, 11 Jun 2026 08:52:02 +0200 Subject: [PATCH] LibWasm: Partially implement the (new) exception handling proposal This implements try_table and throw_ref execution in the interpreter. --- .../AbstractMachine/AbstractMachine.cpp | 4 +- .../LibWasm/AbstractMachine/AbstractMachine.h | 26 +- .../AbstractMachine/BytecodeInterpreter.cpp | 274 ++++++++++++++---- .../AbstractMachine/BytecodeInterpreter.h | 1 + Libraries/LibWasm/Opcode.h | 2 +- Libraries/LibWasm/Parser/Parser.cpp | 6 +- Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 2 +- 7 files changed, 251 insertions(+), 64 deletions(-) diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 99279927b5..086b8dd3d3 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -429,10 +429,10 @@ Optional Store::allocate(FunctionType const& type, DefinedType const return address; } -Optional Store::allocate(TagInstance const& tag_instance, Vector params) +Optional Store::allocate(TagAddress tag_address, Vector params) { ExceptionAddress address { m_exceptions.size() }; - m_exceptions.append(ExceptionInstance { tag_instance, move(params) }); + m_exceptions.append(ExceptionInstance { tag_address, move(params) }); return address; } diff --git a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index 911ab3e5d0..f57ed0ac5c 100644 --- a/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -307,13 +307,20 @@ struct ExternallyManagedTrap { } }; +// https://webassembly.github.io/spec/core/exec/runtime.html#results +struct UncaughtException { + ExceptionAddress address; +}; + struct Trap { - Variant data; + Variant data; ByteString format() const { if (auto const* ptr = data.get_pointer()) return *ptr; + if (data.has()) + return "uncaught exception"; return ""; } @@ -733,19 +740,20 @@ private: TagType::Flags m_flags; }; +// https://webassembly.github.io/spec/core/exec/runtime.html#exception-instances class ExceptionInstance { public: - explicit ExceptionInstance(TagInstance const& type, Vector params) - : m_type(type) + explicit ExceptionInstance(TagAddress tag, Vector params) + : m_tag(tag) , m_params(move(params)) { } - auto& type() const { return m_type; } + auto tag() const { return m_tag; } auto& params() const { return m_params; } private: - TagInstance m_type; + TagAddress m_tag; Vector m_params; }; @@ -806,7 +814,7 @@ public: Optional allocate(GlobalType const&, Value); Optional allocate(ValueType const&, Vector); Optional allocate(FunctionType const&, DefinedType const*, TagType::Flags); - Optional allocate(TagInstance const&, Vector); + Optional allocate(TagAddress, Vector); Module const* get_module_for(FunctionAddress); RefPtr get_module_instance_for(FunctionAddress); // Obtains strong ref for module. @@ -850,21 +858,25 @@ private: class Label { public: - explicit Label(size_t arity, InstructionPointer continuation, size_t stack_height) + explicit Label(size_t arity, InstructionPointer continuation, size_t stack_height, Instruction const* try_table_instruction = nullptr) : m_arity(arity) , m_stack_height(stack_height) , m_continuation(continuation) + , m_try_table_instruction(try_table_instruction) { } auto continuation() const { return m_continuation; } auto arity() const { return m_arity; } auto stack_height() const { return m_stack_height; } + // https://webassembly.github.io/spec/core/exec/instructions.html#exec-try-table + Instruction const* try_table_instruction() const { return m_try_table_instruction; } private: size_t m_arity { 0 }; size_t m_stack_height { 0 }; InstructionPointer m_continuation { 0 }; + Instruction const* m_try_table_instruction { nullptr }; }; class Frame { diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp index ff88ddb7b3..205729c87c 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp @@ -2012,9 +2012,16 @@ HANDLE_INSTRUCTION(synthetic_call_00) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_00(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_01) @@ -2024,9 +2031,16 @@ HANDLE_INSTRUCTION(synthetic_call_01) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_01(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_10) @@ -2036,9 +2050,16 @@ HANDLE_INSTRUCTION(synthetic_call_10) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_10(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_11) @@ -2048,9 +2069,16 @@ HANDLE_INSTRUCTION(synthetic_call_11) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_11(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_20) @@ -2060,9 +2088,16 @@ HANDLE_INSTRUCTION(synthetic_call_20) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_20(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_21) @@ -2072,9 +2107,16 @@ HANDLE_INSTRUCTION(synthetic_call_21) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_21(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_30) @@ -2084,9 +2126,16 @@ HANDLE_INSTRUCTION(synthetic_call_30) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_30(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_31) @@ -2096,9 +2145,16 @@ HANDLE_INSTRUCTION(synthetic_call_31) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "[{}] call_31(#{} -> {})", short_ip.current_ip_value, index.value(), address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingRegisters)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(unreachable) @@ -2337,9 +2393,16 @@ 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, addresses) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_with_record_0) @@ -2349,9 +2412,16 @@ HANDLE_INSTRUCTION(synthetic_call_with_record_0) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "call.with_record.0({})", address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingCallRecord) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingCallRecord)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(synthetic_call_with_record_1) @@ -2361,9 +2431,16 @@ HANDLE_INSTRUCTION(synthetic_call_with_record_1) auto index = instruction->arguments().get(); auto address = configuration.frame().module().functions()[index.value()]; dbgln_if(WASM_TRACE_DEBUG, "call.with_record.1({})", address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingCallRecord) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::DirectCall, BytecodeInterpreter::CallType::UsingCallRecord)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(return_call) @@ -2410,9 +2487,16 @@ HANDLE_INSTRUCTION(call_indirect) TRAP_IN_LOOP_IF_NOT(type_actual && matches_defined_type(*type_actual, *type_expected)); dbgln_if(WASM_TRACE_DEBUG, "call_indirect({} -> {})", index, address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectCall) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectCall)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(return_call_indirect) @@ -2469,9 +2553,16 @@ HANDLE_INSTRUCTION(call_ref) TRAP_IN_LOOP_IF_NOT(type_actual && matches_defined_type(*type_actual, *type_expected)); dbgln_if(WASM_TRACE_DEBUG, "call_ref({})", address.value()); - if (interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectCall) == Outcome::Return) + switch (auto const outcome = interpreter.call_address(configuration, address, addresses, BytecodeInterpreter::CallAddressSource::IndirectCall)) { + case Outcome::Return: return Outcome::Return; - TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + default: + // A callee's thrown exception was caught by a try_table in this frame; continue at the catch's branch target. + short_ip.current_ip_value = to_underlying(outcome) - 1; + [[fallthrough]]; + case Outcome::Continue: + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } } HANDLE_INSTRUCTION(return_call_ref) @@ -5483,13 +5574,28 @@ HANDLE_INSTRUCTION(i32x4_relaxed_dot_i8x16_i7x16_add_s) TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } +// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-ref HANDLE_INSTRUCTION(throw_ref) { LOG_INSN; - interpreter.set_trap("Not Implemented: Proposal 'Exception-handling'"sv); + LOAD_ADDRESSES(); + auto const value = configuration.take_source(0, addresses.sources); + // 4. If val' is ref.null, then trap. (See the encoding table in Value(Reference const&): 4 is a null exnref.) + if (value.value().high() == 4) [[unlikely]] { + interpreter.set_trap("null exception reference"sv); + return Outcome::Return; + } + auto const exception_address = bit_cast(value.value().low()); + if (auto continuation = interpreter.unwind_to_throw_handler(configuration, exception_address); continuation.has_value()) { + short_ip.current_ip_value = continuation->value() - 1; + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); + } + // 5.5.2. "...the first non-value entry of the stack is not a handler: Throw the exception val' as a result." + interpreter.set_trap(Trap { UncaughtException { exception_address } }); return Outcome::Return; } +// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-x HANDLE_INSTRUCTION(throw_) { LOG_INSN; @@ -5499,7 +5605,7 @@ HANDLE_INSTRUCTION(throw_) 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)); + auto exception_address = configuration.store().allocate(tag_address, move(values)); if (!exception_address.has_value()) { interpreter.set_trap("Out of memory"sv); return Outcome::Return; @@ -5509,11 +5615,15 @@ HANDLE_INSTRUCTION(throw_) TAILCALL return InstructionHandler::operator()(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } +// https://webassembly.github.io/spec/core/exec/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 HANDLE_INSTRUCTION(try_table) { LOG_INSN; - interpreter.set_trap("Not Implemented: Proposal 'Exception-handling'"sv); - return Outcome::Return; + auto& args = instruction->arguments().unsafe_get(); + auto& meta = args.meta; + auto label = Label(meta.arity, args.end_ip, configuration.value_stack().size() - meta.parameter_count, instruction); + configuration.label_stack().unchecked_append(move(label)); + TAILCALL return continue_(HANDLER_PARAMS(DECOMPOSE_PARAMS_NAME_ONLY)); } // Proposal "gc". @@ -6337,6 +6447,50 @@ InstructionPointer BytecodeInterpreter::branch_to_label(Configuration& configura return actually_branching ? label.continuation().value() - 1 : current_ip; } +// https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-ref +Optional BytecodeInterpreter::unwind_to_throw_handler(Configuration& configuration, ExceptionAddress exception_address) +{ + auto& exception = *configuration.store().get(exception_address); + auto& label_stack = configuration.label_stack(); + auto& frame = configuration.frame(); + auto const frame_label_index = frame.label_index(); + for (size_t label_index = label_stack.size(); label_index > frame_label_index + 1;) { + --label_index; + auto const& label = label_stack.data()[label_index]; + auto const* try_table_instruction = label.try_table_instruction(); + if (!try_table_instruction) + continue; + auto& args = try_table_instruction->arguments().unsafe_get(); + for (auto& catch_ : args.catches()) { + // catch x l / catch_ref x l match if exns[a].tag = z.module.tags[x]; + // catch_all l / catch_all_ref l match any exception. + if (auto tag_index = catch_.matching_tag_index(); tag_index.has_value()) { + if (frame.module().tags()[tag_index->value()] != exception.tag()) + continue; + } + // Matched: the handler and its label should be removed, and the exception's fields (plus the exnref for the _ref forms) replace them... + auto& value_stack = configuration.value_stack(); + value_stack.shrink(label.stack_height(), true); + if (catch_.matching_tag_index().has_value()) { + value_stack.ensure_capacity(value_stack.size() + exception.params().size()); + for (auto& field : exception.params()) + value_stack.unchecked_append(field); + } + if (catch_.is_ref()) + value_stack.append(Value(Reference { Reference::Exception { exception_address } })); + label_stack.unsafe_shrink(label_index); + // ...followed by (br l), with l relative to the context outside the try_table. + label_stack.unsafe_shrink(label_stack.size() - catch_.target_label().value()); + auto const& target = label_stack.unsafe_last(); + value_stack.remove(target.stack_height(), value_stack.size() - target.stack_height() - target.arity()); + return target.continuation(); + } + } + // No handler in this frame matched; drop its labels (the frame is being unwound) so that a re-dispatch in the calling frame only ever sees that frame's own (still active) handlers. + label_stack.shrink(frame_label_index, true); + return {}; +} + template bool BytecodeInterpreter::load_and_push(Configuration& configuration, Instruction const& instruction, SourcesAndDestination const& addresses) { @@ -6590,6 +6744,14 @@ Outcome BytecodeInterpreter::call_address(Configuration& configuration, Function } if (result.is_trap()) { + // https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-throw-ref + if (auto const* uncaught_exception = result.trap().data.get_pointer(); uncaught_exception && final_outcome == Outcome::Continue) { + if (auto continuation = unwind_to_throw_handler(configuration, uncaught_exception->address); continuation.has_value()) { + // The callee's interpret() left the exception in m_trap; it's handled now. + m_trap = Empty {}; + return static_cast(continuation->value()); + } + } m_trap = move(result.trap()); return Outcome::Return; } @@ -7195,17 +7357,16 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span } auto& args = result.dispatches[i].instruction->arguments(); + auto offset_to = [&](InstructionPointer ip) { + size_t offset = 0; + auto it = nops_to_remove_it; + while (it != nops_to_remove.end() && it.key() < ip.value()) { + ++offset; + ++it; + } + return offset; + }; if (auto ptr = args.get_pointer()) { - auto offset_to = [&](InstructionPointer ip) { - size_t offset = 0; - auto it = nops_to_remove_it; - while (it != nops_to_remove.end() && it.key() < ip.value()) { - ++offset; - ++it; - } - return offset; - }; - InstructionPointer end_ip = ptr->end_ip.value() - offset_accumulated - offset_to(ptr->end_ip - ptr->else_ip().has_value()); auto else_ip = ptr->else_ip().map([&](InstructionPointer const& ip) -> InstructionPointer { return ip.value() - offset_accumulated - offset_to(ip - 1); }); auto instruction = *result.dispatches[i].instruction; @@ -7213,6 +7374,13 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span auto& extra_instruction = append_extra_instruction(move(instruction)); result.dispatches[i].instruction = &extra_instruction; result.dispatches[i].instruction_opcode = result.dispatches[i].instruction->opcode(); + } else if (auto try_table_ptr = args.get_pointer()) { + InstructionPointer end_ip = try_table_ptr->end_ip.value() - offset_accumulated - offset_to(try_table_ptr->end_ip); + auto instruction = *result.dispatches[i].instruction; + instruction.arguments() = Instruction::TryTableArgs { try_table_ptr->block_type, end_ip, try_table_ptr->catches(), try_table_ptr->meta }; + auto& extra_instruction = append_extra_instruction(move(instruction)); + result.dispatches[i].instruction = &extra_instruction; + result.dispatches[i].instruction_opcode = result.dispatches[i].instruction->opcode(); } } @@ -7265,16 +7433,22 @@ CompiledInstructions try_compile_instructions(Expression const& expression, Span // Re-point absolute IPs in structured args (end_ip / else_ip) past the inserted ops. for (size_t i = 0; i < new_dispatches.size(); ++i) { - auto* sa = new_dispatches[i].instruction->arguments().get_pointer(); - if (!sa) - continue; - InstructionPointer new_end_ip = sa->end_ip.value() + shift_before(sa->end_ip.value()); - auto new_else_ip = sa->else_ip().map([&](InstructionPointer ip) -> InstructionPointer { return ip.value() + shift_before(ip.value()); }); - auto rebuilt = *new_dispatches[i].instruction; - rebuilt.arguments() = Instruction::StructuredInstructionArgs { sa->block_type, new_end_ip, new_else_ip, sa->meta }; - auto& extra_instruction = append_extra_instruction(move(rebuilt)); - new_dispatches[i].instruction = &extra_instruction; - new_dispatches[i].instruction_opcode = extra_instruction.opcode(); + if (auto* sa = new_dispatches[i].instruction->arguments().get_pointer()) { + InstructionPointer new_end_ip = sa->end_ip.value() + shift_before(sa->end_ip.value()); + auto new_else_ip = sa->else_ip().map([&](InstructionPointer ip) -> InstructionPointer { return ip.value() + shift_before(ip.value()); }); + auto rebuilt = *new_dispatches[i].instruction; + rebuilt.arguments() = Instruction::StructuredInstructionArgs { sa->block_type, new_end_ip, new_else_ip, sa->meta }; + auto& extra_instruction = append_extra_instruction(move(rebuilt)); + new_dispatches[i].instruction = &extra_instruction; + new_dispatches[i].instruction_opcode = extra_instruction.opcode(); + } else if (auto* tta = new_dispatches[i].instruction->arguments().get_pointer()) { + InstructionPointer new_end_ip = tta->end_ip.value() + shift_before(tta->end_ip.value()); + auto rebuilt = *new_dispatches[i].instruction; + rebuilt.arguments() = Instruction::TryTableArgs { tta->block_type, new_end_ip, tta->catches(), tta->meta }; + auto& extra_instruction = append_extra_instruction(move(rebuilt)); + new_dispatches[i].instruction = &extra_instruction; + new_dispatches[i].instruction_opcode = extra_instruction.opcode(); + } } result.dispatches = move(new_dispatches); diff --git a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h index 629fd1f865..5631cbd8e8 100644 --- a/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h +++ b/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.h @@ -72,6 +72,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter { template InstructionPointer branch_to_label(Configuration&, LabelIndex, InstructionPointer current_ip, bool actually_branching = true); + Optional unwind_to_throw_handler(Configuration&, ExceptionAddress); template bool load_and_push(Configuration&, Instruction const&, SourcesAndDestination const&); template diff --git a/Libraries/LibWasm/Opcode.h b/Libraries/LibWasm/Opcode.h index 8af411f4d3..3eda091db1 100644 --- a/Libraries/LibWasm/Opcode.h +++ b/Libraries/LibWasm/Opcode.h @@ -38,7 +38,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(try_table, 0x1f, 0, -1) \ 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 538748c65c..dc481b4744 100644 --- a/Libraries/LibWasm/Parser/Parser.cpp +++ b/Libraries/LibWasm/Parser/Parser.cpp @@ -406,12 +406,12 @@ ParseResult Catch::parse(ConstrainedStream& stream) case 2: { // catch_all l auto label_index = TRY(GenericIndexParser::parse(stream)); - return Catch { false, {}, label_index }; + return Catch { false, label_index }; } case 3: { // catch_all_ref l auto label_index = TRY(GenericIndexParser::parse(stream)); - return Catch { true, {}, label_index }; + return Catch { true, label_index }; } default: return ParseError::InvalidTag; @@ -1453,7 +1453,7 @@ ParseResult Expression::parse(ConstrainedStream& stream, Optional()) return ptr->unsafe_external_object_as(); - auto& trap = result.trap().data.get(); + auto trap = result.trap().format(); // https://webassembly.github.io/spec/js-api/#stack-overflow // 6.1. Whenever a stack overflow occurs in WebAssembly code, the same class of exception is thrown as for a stack overflow in JavaScript. if (trap.ends_with(Wasm::Constants::stack_exhaustion_message))