LibWasm: Partially implement the (new) exception handling proposal

This implements try_table and throw_ref execution in the interpreter.
This commit is contained in:
Ali Mohammad Pur 2026-06-11 08:52:02 +02:00 committed by Ali Mohammad Pur
parent 16d1486cc9
commit 8a74f52571
7 changed files with 251 additions and 64 deletions

View file

@ -429,10 +429,10 @@ Optional<TagAddress> Store::allocate(FunctionType const& type, DefinedType const
return address;
}
Optional<ExceptionAddress> Store::allocate(TagInstance const& tag_instance, Vector<Value> params)
Optional<ExceptionAddress> Store::allocate(TagAddress tag_address, Vector<Value> params)
{
ExceptionAddress address { m_exceptions.size() };
m_exceptions.append(ExceptionInstance { tag_instance, move(params) });
m_exceptions.append(ExceptionInstance { tag_address, move(params) });
return address;
}

View file

@ -307,13 +307,20 @@ struct ExternallyManagedTrap {
}
};
// https://webassembly.github.io/spec/core/exec/runtime.html#results
struct UncaughtException {
ExceptionAddress address;
};
struct Trap {
Variant<ByteString, ExternallyManagedTrap> data;
Variant<ByteString, ExternallyManagedTrap, UncaughtException> data;
ByteString format() const
{
if (auto const* ptr = data.get_pointer<ByteString>())
return *ptr;
if (data.has<UncaughtException>())
return "uncaught exception";
return "<Externally managed Trap Data>";
}
@ -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<Value> params)
: m_type(type)
explicit ExceptionInstance(TagAddress tag, Vector<Value> 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<Value> m_params;
};
@ -806,7 +814,7 @@ public:
Optional<GlobalAddress> allocate(GlobalType const&, Value);
Optional<ElementAddress> allocate(ValueType const&, Vector<Reference>);
Optional<TagAddress> allocate(FunctionType const&, DefinedType const*, TagType::Flags);
Optional<ExceptionAddress> allocate(TagInstance const&, Vector<Value>);
Optional<ExceptionAddress> allocate(TagAddress, Vector<Value>);
Module const* get_module_for(FunctionAddress);
RefPtr<ModuleInstance const> 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 {

View file

@ -2012,9 +2012,16 @@ HANDLE_INSTRUCTION(synthetic_call_00)
auto index = instruction->arguments().get<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<FunctionIndex>();
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<source_address_mix>(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<ExceptionAddress>(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<Value>(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<Instructions::throw_ref.value()>::operator()<HasDynamicInsnLimit, Continue, SourceAddressMix::Any>(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<Instruction::TryTableArgs>();
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<InstructionPointer> 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<Instruction::TryTableArgs>();
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<typename ReadType, typename PushType, SourceAddressMix mix>
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<UncaughtException>(); 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<Outcome>(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<Instruction::StructuredInstructionArgs>()) {
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<Instruction::TryTableArgs>()) {
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<Instruction::StructuredInstructionArgs>();
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<Instruction::StructuredInstructionArgs>()) {
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<Instruction::TryTableArgs>()) {
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);

View file

@ -72,6 +72,7 @@ struct WASM_API BytecodeInterpreter final : public Interpreter {
template<bool NeedsStackAdjustment>
InstructionPointer branch_to_label(Configuration&, LabelIndex, InstructionPointer current_ip, bool actually_branching = true);
Optional<InstructionPointer> unwind_to_throw_handler(Configuration&, ExceptionAddress);
template<typename ReadT, typename PushT, SourceAddressMix>
bool load_and_push(Configuration&, Instruction const&, SourcesAndDestination const&);
template<typename PopT, typename StoreT>

View file

@ -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) \

View file

@ -406,12 +406,12 @@ ParseResult<Catch> Catch::parse(ConstrainedStream& stream)
case 2: {
// catch_all l
auto label_index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Catch { false, {}, label_index };
return Catch { false, label_index };
}
case 3: {
// catch_all_ref l
auto label_index = TRY(GenericIndexParser<LabelIndex>::parse(stream));
return Catch { true, {}, label_index };
return Catch { true, label_index };
}
default:
return ParseError::InvalidTag;
@ -1453,7 +1453,7 @@ ParseResult<Expression> Expression::parse(ConstrainedStream& stream, Optional<si
return true;
},
[&](Instruction::TryTableArgs& args) {
args.end_ip = ip + 1;
args.end_ip = ip;
return true;
},
[](auto&) { return false; });

View file

@ -702,7 +702,7 @@ JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress add
if (result.is_trap()) {
if (auto ptr = result.trap().data.get_pointer<Wasm::ExternallyManagedTrap>())
return ptr->unsafe_external_object_as<JS::Completion>();
auto& trap = result.trap().data.get<ByteString>();
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))