diff --git a/src/wasm/baseline/liftoff-assembler.cc b/src/wasm/baseline/liftoff-assembler.cc index fa6bf24a72..d27c265ce1 100644 --- a/src/wasm/baseline/liftoff-assembler.cc +++ b/src/wasm/baseline/liftoff-assembler.cc @@ -627,6 +627,15 @@ void LiftoffAssembler::DropValues(int count) { } } +void LiftoffAssembler::DropValue(int depth) { + auto* dropped = cache_state_.stack_state.begin() + depth; + if (dropped->is_reg()) { + cache_state_.dec_used(dropped->reg()); + } + std::copy(dropped + 1, cache_state_.stack_state.end(), dropped); + cache_state_.stack_state.pop_back(); +} + void LiftoffAssembler::PrepareLoopArgs(int num) { for (int i = 0; i < num; ++i) { VarState& slot = cache_state_.stack_state.end()[-1 - i]; diff --git a/src/wasm/baseline/liftoff-assembler.h b/src/wasm/baseline/liftoff-assembler.h index 0e45f8e741..7dffc25a7e 100644 --- a/src/wasm/baseline/liftoff-assembler.h +++ b/src/wasm/baseline/liftoff-assembler.h @@ -410,6 +410,8 @@ class LiftoffAssembler : public TurboAssembler { void DropValues(int count); + void DropValue(int depth); + // Ensure that the loop inputs are either in a register or spilled to the // stack, so that we can merge different values on the back-edge. void PrepareLoopArgs(int num); @@ -434,6 +436,16 @@ class LiftoffAssembler : public TurboAssembler { cache_state_.stack_state.emplace_back(kind, reg, NextSpillOffset(kind)); } + // Assumes that the exception is in {kReturnRegister0}. This is where the + // exception is stored by the unwinder after a throwing call. + void PushException() { + LiftoffRegister reg{kReturnRegister0}; + // This is used after a call, so {kReturnRegister0} is not used yet. + DCHECK(cache_state_.is_free(reg)); + cache_state_.inc_used(reg); + cache_state_.stack_state.emplace_back(kRef, reg, NextSpillOffset(kRef)); + } + void PushConstant(ValueKind kind, int32_t i32_const) { DCHECK(kind == kI32 || kind == kI64); cache_state_.stack_state.emplace_back(kind, i32_const, diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc index 6994588c91..f5f5f55ad2 100644 --- a/src/wasm/baseline/liftoff-compiler.cc +++ b/src/wasm/baseline/liftoff-compiler.cc @@ -376,6 +376,8 @@ class LiftoffCompiler { LiftoffAssembler::CacheState label_state; MovableLabel label; TryInfo* try_info = nullptr; + // Number of exceptions on the stack below this control. + int num_exceptions = 0; MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(Control); @@ -920,6 +922,7 @@ class LiftoffCompiler { __ MaybeEmitOutOfLineConstantPool(); // The previous calls may have also generated a bailout. DidAssemblerBailout(decoder); + DCHECK_EQ(num_exceptions_, 0); } void OnFirstError(FullDecoder* decoder) { @@ -1018,7 +1021,13 @@ class LiftoffCompiler { DebugSideTableBuilder::kAllowRegisters); } - void Block(FullDecoder* decoder, Control* block) {} + void PushControl(Control* block) { + // The Liftoff stack includes implicit exception refs stored for catch + // blocks, so that they can be rethrown. + block->num_exceptions = num_exceptions_; + } + + void Block(FullDecoder* decoder, Control* block) { PushControl(block); } void Loop(FullDecoder* decoder, Control* loop) { // Before entering a loop, spill all locals to the stack, in order to free @@ -1040,12 +1049,15 @@ class LiftoffCompiler { // Execute a stack check in the loop header. StackCheck(decoder, decoder->position()); + + PushControl(loop); } void Try(FullDecoder* decoder, Control* block) { block->try_info = compilation_zone_->New(); block->try_info->previous_catch = current_catch_; current_catch_ = static_cast(decoder->control_depth() - 1); + PushControl(block); } void CatchException(FullDecoder* decoder, @@ -1058,8 +1070,23 @@ class LiftoffCompiler { unsupported(decoder, kExceptionHandling, "delegate"); } - void Rethrow(FullDecoder* decoder, Control* block) { - unsupported(decoder, kExceptionHandling, "rethrow"); + void Rethrow(FullDecoder* decoder, Control* try_block) { + int index = try_block->try_info->catch_state.stack_height() - 1; + auto& exception = __ cache_state()->stack_state[index]; + DCHECK_EQ(exception.kind(), kRef); + DEBUG_CODE_COMMENT("call WasmRethrow builtin"); + compiler::CallDescriptor* rethrow_descriptor = + GetBuiltinCallDescriptor(compilation_zone_); + + ValueKind throw_sig_reps[] = {kPointerValueType}; + ValueKindSig rethrow_sig(0, 1, throw_sig_reps); + + __ PrepareBuiltinCall(&rethrow_sig, rethrow_descriptor, {exception}); + source_position_table_builder_.AddPosition( + __ pc_offset(), SourcePosition(decoder->position()), true); + __ CallRuntimeStub(WasmCode::kWasmRethrow); + DefineSafepoint(); + EmitLandingPad(decoder); } void CatchAll(FullDecoder* decoder, Control* block) { @@ -1068,6 +1095,9 @@ class LiftoffCompiler { DCHECK_EQ(decoder->control_at(0), block); current_catch_ = block->try_info->previous_catch; // Pop try scope. + if (!block->is_try_unwind()) { + num_exceptions_++; + } // The catch block is unreachable if no possible throws in the try block // exist. We only build a landing pad if some node in the try block can @@ -1094,12 +1124,15 @@ class LiftoffCompiler { // Store the state (after popping the value) for executing the else branch. if_block->else_state->state.Split(*__ cache_state()); + + PushControl(if_block); } void FallThruTo(FullDecoder* decoder, Control* c) { if (!c->end_merge.reached) { c->label_state.InitMerge(*__ cache_state(), __ num_locals(), - c->end_merge.arity, c->stack_depth); + c->end_merge.arity, + c->stack_depth + c->num_exceptions); } __ MergeFullStackWith(c->label_state, *__ cache_state()); __ emit_jump(c->label.get()); @@ -1125,7 +1158,8 @@ class LiftoffCompiler { // state, then merge the if state into that. DCHECK_EQ(c->start_merge.arity, c->end_merge.arity); c->label_state.InitMerge(c->else_state->state, __ num_locals(), - c->start_merge.arity, c->stack_depth); + c->start_merge.arity, + c->stack_depth + c->num_exceptions); __ MergeFullStackWith(c->label_state, *__ cache_state()); __ emit_jump(c->label.get()); // Merge the else state into the end state. @@ -1139,11 +1173,27 @@ class LiftoffCompiler { } } + void FinishTryCatch(FullDecoder* decoder, Control* c) { + DCHECK(c->is_try_catch() || c->is_try_catchall()); + if (c->reachable()) { + // Drop the implicit exception ref. + if (!c->end_merge.reached) { + __ DropValue(c->stack_depth + c->num_exceptions); + } else { + __ MergeStackWith(c->label_state, c->br_merge()->arity); + __ cache_state()->Steal(c->label_state); + } + } + num_exceptions_--; + } + void PopControl(FullDecoder* decoder, Control* c) { if (c->is_loop()) return; // A loop just falls through. if (c->is_onearmed_if()) { // Special handling for one-armed ifs. FinishOneArmedIf(decoder, c); + } else if (c->is_try_catch() || c->is_try_catchall()) { + FinishTryCatch(decoder, c); } else if (c->end_merge.reached) { // There is a merge already. Merge our state into that, then continue with // that state. @@ -2229,9 +2279,9 @@ class LiftoffCompiler { void BrImpl(Control* target) { if (!target->br_merge()->reached) { - target->label_state.InitMerge(*__ cache_state(), __ num_locals(), - target->br_merge()->arity, - target->stack_depth); + target->label_state.InitMerge( + *__ cache_state(), __ num_locals(), target->br_merge()->arity, + target->stack_depth + target->num_exceptions); } __ MergeStackWith(target->label_state, target->br_merge()->arity); __ jmp(target->label.get()); @@ -2352,7 +2402,8 @@ class LiftoffCompiler { if (c->reachable()) { if (!c->end_merge.reached) { c->label_state.InitMerge(*__ cache_state(), __ num_locals(), - c->end_merge.arity, c->stack_depth); + c->end_merge.arity, + c->stack_depth + c->num_exceptions); } __ MergeFullStackWith(c->label_state, *__ cache_state()); __ emit_jump(c->label.get()); @@ -2826,42 +2877,54 @@ class LiftoffCompiler { auto values = OwnedVector::NewForOverwrite( stack_state.size()); - // The decoder already has the results on the stack, but Liftoff has not. - // Hence {decoder->stack_size()} can be bigger than expected. Just ignore - // that, and use the lower part only. - DCHECK_LE(stack_state.size(), + // For function calls, the decoder still has the arguments on the stack, but + // Liftoff already popped them. Hence {decoder->stack_size()} can be bigger + // than expected. Just ignore that and use the lower part only. + DCHECK_LE(stack_state.size() - num_exceptions_, decoder->num_locals() + decoder->stack_size()); - int index = 0; int decoder_stack_index = decoder->stack_size(); - for (const auto& slot : stack_state) { - auto& value = values[index]; - value.index = index; - ValueType type = index < static_cast(__ num_locals()) - ? decoder->local_type(index) - : decoder->stack_value(decoder_stack_index--)->type; - DCHECK(CheckCompatibleStackSlotTypes(slot.kind(), type.kind())); - value.type = type; - switch (slot.loc()) { - case kIntConst: - value.storage = DebugSideTable::Entry::kConstant; - value.i32_const = slot.i32_const(); - break; - case kRegister: - DCHECK_NE(DebugSideTableBuilder::kDidSpill, assume_spilling); - if (assume_spilling == DebugSideTableBuilder::kAllowRegisters) { - value.storage = DebugSideTable::Entry::kRegister; - value.reg_code = slot.reg().liftoff_code(); + // Iterate the operand stack control block by control block, so that we can + // handle the implicit exception value for try blocks. + for (int j = decoder->control_depth() - 1; j >= 0; j--) { + Control* control = decoder->control_at(j); + Control* next_control = j > 0 ? decoder->control_at(j - 1) : nullptr; + int end_index = next_control ? next_control->stack_depth + + next_control->num_exceptions + : __ cache_state()->stack_height(); + bool exception = control->is_try_catch() || control->is_try_catchall(); + for (; index < end_index; ++index) { + auto& slot = stack_state[index]; + auto& value = values[index]; + value.index = index; + ValueType type = + index < static_cast(__ num_locals()) + ? decoder->local_type(index) + : exception ? ValueType::Ref(HeapType::kExtern, kNonNullable) + : decoder->stack_value(decoder_stack_index--)->type; + DCHECK(CheckCompatibleStackSlotTypes(slot.kind(), type.kind())); + value.type = type; + switch (slot.loc()) { + case kIntConst: + value.storage = DebugSideTable::Entry::kConstant; + value.i32_const = slot.i32_const(); break; - } - DCHECK_EQ(DebugSideTableBuilder::kAssumeSpilling, assume_spilling); - V8_FALLTHROUGH; - case kStack: - value.storage = DebugSideTable::Entry::kStack; - value.stack_offset = slot.offset(); - break; + case kRegister: + DCHECK_NE(DebugSideTableBuilder::kDidSpill, assume_spilling); + if (assume_spilling == DebugSideTableBuilder::kAllowRegisters) { + value.storage = DebugSideTable::Entry::kRegister; + value.reg_code = slot.reg().liftoff_code(); + break; + } + DCHECK_EQ(DebugSideTableBuilder::kAssumeSpilling, assume_spilling); + V8_FALLTHROUGH; + case kStack: + value.storage = DebugSideTable::Entry::kStack; + value.stack_offset = slot.offset(); + break; + } + exception = false; } - ++index; } DCHECK_EQ(values.size(), index); return values; @@ -3680,20 +3743,23 @@ class LiftoffCompiler { // Handler: merge into the catch state, and jump to the catch body. __ bind(handler.get()); __ ExceptionHandler(); + __ PushException(); handlers_.push_back({std::move(handler), handler_offset}); Control* current_try = decoder->control_at(decoder->control_depth() - 1 - current_catch_); DCHECK_NOT_NULL(current_try->try_info); if (!current_try->try_info->catch_reached) { current_try->try_info->catch_state.InitMerge( - *__ cache_state(), __ num_locals(), 0, - current_try->try_info->catch_state.stack_height()); + *__ cache_state(), __ num_locals(), 1, + current_try->stack_depth + current_try->num_exceptions); current_try->try_info->catch_reached = true; } - __ MergeStackWith(current_try->try_info->catch_state, 0); + __ MergeStackWith(current_try->try_info->catch_state, 1); __ emit_jump(¤t_try->try_info->catch_label); __ bind(&skip_handler); + // Drop the exception. + __ DropValues(1); } void Throw(FullDecoder* decoder, const ExceptionIndexImmediate& imm, @@ -3776,9 +3842,6 @@ class LiftoffCompiler { DefineSafepoint(); } - void Rethrow(FullDecoder* decoder, const Value& exception) { - unsupported(decoder, kExceptionHandling, "rethrow"); - } void AtomicStoreMem(FullDecoder* decoder, StoreType type, const MemoryAccessImmediate& imm) { LiftoffRegList pinned; @@ -5711,6 +5774,9 @@ class LiftoffCompiler { ZoneVector handlers_; int handler_table_offset_ = Assembler::kNoHandlerTable; + // Current number of exception refs on the stack. + int num_exceptions_ = 0; + bool has_outstanding_op() const { return outstanding_op_ != kNoOutstandingOp; } diff --git a/test/cctest/wasm/test-liftoff-inspection.cc b/test/cctest/wasm/test-liftoff-inspection.cc index 1b2cc899c6..c6edf1f776 100644 --- a/test/cctest/wasm/test-liftoff-inspection.cc +++ b/test/cctest/wasm/test-liftoff-inspection.cc @@ -6,6 +6,7 @@ #include "src/wasm/wasm-debug.h" #include "test/cctest/cctest.h" #include "test/cctest/wasm/wasm-run-utils.h" +#include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-macro-gen.h" namespace v8 { @@ -89,6 +90,8 @@ class LiftoffCompileEnvironment { return debug_side_table_via_compilation; } + TestingModuleBuilder* builder() { return &wasm_runner_.builder(); } + private: static void CheckTableEquals(const DebugSideTable& a, const DebugSideTable& b) { @@ -423,6 +426,31 @@ TEST(Liftoff_breakpoint_simple) { debug_side_table.get()); } +TEST(Liftoff_debug_side_table_catch_all) { + EXPERIMENTAL_FLAG_SCOPE(eh); + LiftoffCompileEnvironment env; + TestSignatures sigs; + int ex = env.builder()->AddException(sigs.v_v()); + ValueType exception_type = ValueType::Ref(HeapType::kExtern, kNonNullable); + auto debug_side_table = env.GenerateDebugSideTable( + {}, {}, + {WASM_TRY_CATCH_ALL_T(kWasmI32, WASM_STMTS(WASM_I32V(0), WASM_THROW(ex)), + WASM_I32V(1)), + WASM_DROP}, + { + 18 // Break at the end of the try block. + }); + CheckDebugSideTable( + { + // function entry. + {0, {}}, + // breakpoint. + {2, {Register(0, exception_type), Constant(1, kWasmI32, 1)}}, + {0, {}}, + }, + debug_side_table.get()); +} + } // namespace wasm } // namespace internal } // namespace v8 diff --git a/test/mjsunit/wasm/exceptions.js b/test/mjsunit/wasm/exceptions.js index d7539119ab..36f470ac0b 100644 --- a/test/mjsunit/wasm/exceptions.js +++ b/test/mjsunit/wasm/exceptions.js @@ -1032,3 +1032,38 @@ load("test/mjsunit/wasm/exceptions-utils.js"); assertTraps(WebAssembly.RuntimeError, () => instance.exports.test()); assertTraps(WebAssembly.RuntimeError, () => instance.exports.test_unwind()); })(); + +(function TestThrowBeforeUnreachable() { + print(arguments.callee.name); + let builder = new WasmModuleBuilder(); + let except = builder.addException(kSig_v_v); + builder.addFunction('throw_before_unreachable', kSig_i_v) + .addBody([ + kExprTry, kWasmI32, + kExprThrow, except, + kExprUnreachable, + kExprCatchAll, + kExprI32Const, 42, + kExprEnd, + ]).exportFunc(); + + let instance = builder.instantiate(); + assertEquals(42, instance.exports.throw_before_unreachable()); +})(); + +(function TestUnreachableInCatchAll() { + print(arguments.callee.name); + let builder = new WasmModuleBuilder(); + let except = builder.addException(kSig_v_v); + builder.addFunction('throw_before_unreachable', kSig_i_v) + .addBody([ + kExprTry, kWasmI32, + kExprThrow, except, + kExprCatchAll, + kExprUnreachable, + kExprI32Const, 42, + kExprEnd, + ]).exportFunc(); + + let instance = builder.instantiate(); +})();