diff --git a/src/wasm/function-body-decoder-impl.h b/src/wasm/function-body-decoder-impl.h index b3f5a4642d..c1dbfe3094 100644 --- a/src/wasm/function-body-decoder-impl.h +++ b/src/wasm/function-body-decoder-impl.h @@ -2034,7 +2034,6 @@ class WasmDecoder : public Decoder { case kExprBrIf: case kExprBrTable: case kExprIf: - case kExprRethrow: return {1, 0}; case kExprLocalGet: case kExprGlobalGet: @@ -2072,6 +2071,7 @@ class WasmDecoder : public Decoder { case kExprTry: case kExprCatch: case kExprDelegate: + case kExprRethrow: case kExprNop: case kExprNopForTestingUnsupportedInLiftoff: case kExprReturn: diff --git a/test/cctest/wasm/test-run-wasm-exceptions.cc b/test/cctest/wasm/test-run-wasm-exceptions.cc index 4b501492ef..393294a84a 100644 --- a/test/cctest/wasm/test-run-wasm-exceptions.cc +++ b/test/cctest/wasm/test-run-wasm-exceptions.cc @@ -187,6 +187,41 @@ WASM_EXEC_TEST(TryDelegate) { } } +WASM_EXEC_TEST(TryCatchRethrow) { + TestSignatures sigs; + EXPERIMENTAL_FLAG_SCOPE(eh); + WasmRunner r(execution_tier); + uint32_t except1 = r.builder().AddException(sigs.v_v()); + uint32_t except2 = r.builder().AddException(sigs.v_v()); + constexpr uint32_t kResult0 = 23; + constexpr uint32_t kResult1 = 42; + constexpr uint32_t kUnreachable = 51; + + // Build the main test function. + BUILD(r, + WASM_TRY_CATCH_CATCH_T( + kWasmI32, + WASM_TRY_CATCH_T( + kWasmI32, WASM_THROW(except2), + WASM_TRY_CATCH_T( + kWasmI32, WASM_THROW(except1), + WASM_STMTS(WASM_I32V(kUnreachable), + WASM_IF_ELSE(WASM_I32_EQZ(WASM_LOCAL_GET(0)), + WASM_RETHROW(1), WASM_RETHROW(2))), + except1), + except2), + except1, WASM_I32V(kResult0), except2, WASM_I32V(kResult1))); + + if (execution_tier != TestExecutionTier::kInterpreter) { + // Need to call through JS to allow for creation of stack traces. + r.CheckCallViaJS(kResult0, 0); + r.CheckCallViaJS(kResult1, 1); + } else { + CHECK_EQ(kResult0, r.CallInterpreter(0)); + CHECK_EQ(kResult1, r.CallInterpreter(1)); + } +} + WASM_EXEC_TEST(TryDelegateToCaller) { TestSignatures sigs; EXPERIMENTAL_FLAG_SCOPE(eh); diff --git a/test/common/wasm/wasm-interpreter.cc b/test/common/wasm/wasm-interpreter.cc index 37687b6b73..55f8efc7b7 100644 --- a/test/common/wasm/wasm-interpreter.cc +++ b/test/common/wasm/wasm-interpreter.cc @@ -584,10 +584,13 @@ struct InterpreterCode { class SideTable : public ZoneObject { public: ControlTransferMap map_; + // Map rethrow instructions to the catch block index they target. + ZoneMap rethrow_map_; int32_t max_stack_height_ = 0; + int32_t max_control_stack_height = 0; SideTable(Zone* zone, const WasmModule* module, InterpreterCode* code) - : map_(zone) { + : map_(zone), rethrow_map_(zone) { // Create a zone for all temporary objects. Zone control_transfer_zone(zone->allocator(), ZONE_NAME); @@ -608,8 +611,13 @@ class SideTable : public ZoneObject { const byte* from_pc; const int32_t stack_height; }; + struct CatchTarget { + int exception_index; + int target_control_index; + const byte* pc; + }; const byte* target = nullptr; - ZoneVector> catch_targets; + ZoneVector catch_targets; int32_t target_stack_height; // Arity when branching to this label. const uint32_t arity; @@ -625,8 +633,8 @@ class SideTable : public ZoneObject { target = pc; } - void Bind(const byte* pc, int exception_index) { - catch_targets.emplace_back(exception_index, pc); + void Bind(const byte* pc, int exception_index, int target_control_index) { + catch_targets.push_back({exception_index, target_control_index, pc}); } // Reference this label from the given location. @@ -657,17 +665,18 @@ class SideTable : public ZoneObject { offset, ZoneVector(zone)); auto& catch_entries = p.first->second; for (auto& p : catch_targets) { - auto pcdiff = static_cast(p.second - ref.from_pc); + auto pcdiff = static_cast(p.pc - ref.from_pc); TRACE( "control transfer @%zu: Δpc %d, stack %u->%u, exn: %d = " "-%u\n", offset, pcdiff, ref.stack_height, target_stack_height, - p.first, spdiff); + p.exception_index, spdiff); CatchControlTransferEntry entry; entry.pc_diff = pcdiff; entry.sp_diff = spdiff; entry.target_arity = arity; - entry.exception_index = p.first; + entry.exception_index = p.exception_index; + entry.target_control_index = p.target_control_index; catch_entries.emplace_back(entry); } } @@ -709,9 +718,8 @@ class SideTable : public ZoneObject { // bytecodes are within the true or false block of an else. ZoneVector control_stack(&control_transfer_zone); // It also maintains a stack of all nested {try} blocks to resolve local - // handler targets for potentially throwing operations. These exceptional - // control transfers are treated just like other branches in the resulting - // map. This stack contains indices into the above control stack. + // handler targets for potentially throwing operations. This stack contains + // indices into the above control stack. ZoneVector exception_stack(zone); int32_t stack_height = 0; uint32_t func_arity = @@ -762,6 +770,8 @@ class SideTable : public ZoneObject { WasmOpcodes::OpcodeName(opcode), static_cast(c->pc - code->start)); } + max_control_stack_height = std::max( + max_control_stack_height, static_cast(control_stack.size())); switch (opcode) { case kExprBlock: case kExprLoop: { @@ -817,9 +827,7 @@ class SideTable : public ZoneObject { if (*c->pc == kExprIf) { copy_unreachable(); TRACE("control @%u: Else\n", i.pc_offset()); - if (!unreachable) { - c->end_label->Ref(i.pc(), stack_height); - } + if (!unreachable) c->end_label->Ref(i.pc(), stack_height); DCHECK_NOT_NULL(c->else_label); c->else_label->Bind(i.pc() + 1); c->else_label->Finish(&map_, code->start); @@ -836,11 +844,11 @@ class SideTable : public ZoneObject { } copy_unreachable(); TRACE("control @%u: CatchAll\n", i.pc_offset()); - if (!unreachable) { - c->end_label->Ref(i.pc(), stack_height); - } + if (!unreachable) c->end_label->Ref(i.pc(), stack_height); DCHECK_NOT_NULL(c->else_label); - c->else_label->Bind(i.pc() + 1, kCatchAllExceptionIndex); + int control_index = static_cast(control_stack.size()) - 1; + c->else_label->Bind(i.pc() + 1, kCatchAllExceptionIndex, + control_index); c->else_label->Finish(&map_, code->start); c->else_label = nullptr; DCHECK_IMPLIES(!unreachable, @@ -867,6 +875,12 @@ class SideTable : public ZoneObject { copy_unreachable(); break; } + case kExprRethrow: { + BranchDepthImmediate imm(&i, i.pc() + 1); + int index = static_cast(control_stack.size()) - 1 - imm.depth; + rethrow_map_.emplace(i.pc() - i.start(), index); + break; + } case kExprCatch: { if (!exception_stack.empty() && exception_stack.back() == control_stack.size() - 1) { @@ -877,12 +891,12 @@ class SideTable : public ZoneObject { Control* c = &control_stack.back(); copy_unreachable(); TRACE("control @%u: Catch\n", i.pc_offset()); - if (!unreachable) { - c->end_label->Ref(i.pc(), stack_height); - } + if (!unreachable) c->end_label->Ref(i.pc(), stack_height); DCHECK_NOT_NULL(c->else_label); - c->else_label->Bind(i.pc() + imm.length + 1, imm.index); + int control_index = static_cast(control_stack.size()) - 1; + c->else_label->Bind(i.pc() + imm.length + 1, imm.index, + control_index); DCHECK_IMPLIES(!unreachable, stack_height >= c->end_label->target_stack_height); @@ -907,7 +921,9 @@ class SideTable : public ZoneObject { DCHECK_EQ(*c->pc, kExprTry); Control* next_try_block = &control_stack[exception_stack.back()]; - c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex); + constexpr int kUnusedControlIndex = -1; + c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex, + kUnusedControlIndex); if (!unreachable) { next_try_block->else_label->Ref( i.pc(), c->else_label->target_stack_height); @@ -931,7 +947,9 @@ class SideTable : public ZoneObject { const size_t new_stack_size = control_stack.size() - 1; const size_t max_depth = new_stack_size - 1; if (imm.depth < max_depth) { - c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex); + constexpr int kUnusedControlIndex = -1; + c->else_label->Bind(i.pc(), kRethrowOrDelegateExceptionIndex, + kUnusedControlIndex); c->else_label->Finish(&map_, code->start); Control* target = &control_stack[max_depth - imm.depth]; DCHECK_EQ(*target->pc, kExprTry); @@ -1233,6 +1251,7 @@ class WasmInterpreterInternals { InterpreterCode* code = frame.code; if (catchable && code->side_table->HasCatchEntryAt(frame.pc)) { TRACE("----- HANDLE -----\n"); + HandleScope scope(isolate_); Handle exception = handle(isolate->pending_exception(), isolate); if (JumpToHandlerDelta(code, exception, &frame.pc)) { @@ -1248,6 +1267,10 @@ class WasmInterpreterInternals { TRACE(" => drop frame #%zu (#%u @%zu)\n", frames_.size() - 1, code->function->func_index, frame.pc); ResetStack(frame.sp); + if (!frame.caught_exception_stack.is_null()) { + isolate_->global_handles()->Destroy( + frame.caught_exception_stack.location()); + } frames_.pop_back(); } TRACE("----- UNWIND -----\n"); @@ -1267,6 +1290,8 @@ class WasmInterpreterInternals { sp_t plimit() { return sp + code->function->sig->parameter_count(); } // Limit of locals. sp_t llimit() { return plimit() + code->locals.type_list.size(); } + + Handle caught_exception_stack; }; // Safety wrapper for values on the operand stack represented as {WasmValue}. @@ -1350,7 +1375,8 @@ class WasmInterpreterInternals { // The parameters will overlap the arguments already on the stack. DCHECK_GE(StackHeight(), arity); - frames_.push_back({code, 0, StackHeight() - arity}); + frames_.push_back( + {code, 0, StackHeight() - arity, Handle::null()}); frames_.back().pc = InitLocals(code); TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1, code->function->func_index, frames_.back().pc); @@ -1410,6 +1436,7 @@ class WasmInterpreterInternals { // No handler in this frame means that we should rethrow to the caller. return false; } + CatchControlTransferEntry* handler = nullptr; for (auto& entry : it->second) { if (entry.exception_index < 0) { ResetStack(StackHeight() - entry.sp_diff); @@ -1419,21 +1446,34 @@ class WasmInterpreterInternals { // (for the implicit rethrow) or in the delegate target. return JumpToHandlerDelta(code, exception_object, pc); } - DCHECK_EQ(entry.exception_index, kCatchAllExceptionIndex); - return true; + handler = &entry; + break; } else if (MatchingExceptionTag(exception_object, entry.exception_index)) { + handler = &entry; const WasmException* exception = &module()->exceptions[entry.exception_index]; const FunctionSig* sig = exception->sig; int catch_in_arity = static_cast(sig->parameter_count()); DoUnpackException(exception, exception_object); DoStackTransfer(entry.sp_diff + catch_in_arity, catch_in_arity); - *pc += entry.pc_diff; - return true; + *pc += handler->pc_diff; + break; } } - return false; + if (!handler) return false; + if (frames_.back().caught_exception_stack.is_null()) { + Handle caught_exception_stack = + isolate_->factory()->NewFixedArray( + code->side_table->max_control_stack_height); + caught_exception_stack->FillWithHoles( + 0, code->side_table->max_control_stack_height); + frames_.back().caught_exception_stack = + isolate_->global_handles()->Create(*caught_exception_stack); + } + frames_.back().caught_exception_stack->set(handler->target_control_index, + *exception_object); + return true; } int DoBreak(InterpreterCode* code, pc_t pc, size_t depth) { @@ -1464,6 +1504,10 @@ class WasmInterpreterInternals { size_t arity) { DCHECK_GT(frames_.size(), 0); spdiff_t sp_diff = static_cast(StackHeight() - frames_.back().sp); + if (!frames_.back().caught_exception_stack.is_null()) { + isolate_->global_handles()->Destroy( + frames_.back().caught_exception_stack.location()); + } frames_.pop_back(); if (frames_.empty()) { // A return from the last frame terminates the execution. @@ -3199,8 +3243,8 @@ class WasmInterpreterInternals { // Throw a given existing exception. Returns true if the exception is being // handled locally by the interpreter, false otherwise (interpreter exits). - bool DoRethrowException(WasmValue exception) { - isolate_->ReThrow(*exception.to_externref()); + bool DoRethrowException(Handle exception) { + isolate_->ReThrow(*exception); return HandleException(isolate_) == WasmInterpreter::HANDLED; } @@ -3407,13 +3451,18 @@ class WasmInterpreterInternals { continue; // Do not bump pc. } case kExprRethrow: { - HandleScope handle_scope(isolate_); // Avoid leaking handles. - WasmValue ex = Pop(); - if (ex.to_externref()->IsNull()) { - return DoTrap(kTrapRethrowNull, pc); - } + BranchDepthImmediate imm(&decoder, + code->at(pc + 1)); + HandleScope scope(isolate_); // Avoid leaking handles. + DCHECK(!frames_.back().caught_exception_stack.is_null()); + int index = code->side_table->rethrow_map_[pc]; + DCHECK_LE(0, index); + DCHECK_LT(index, frames_.back().caught_exception_stack->Size()); + Handle exception = handle( + frames_.back().caught_exception_stack->get(index), isolate_); + DCHECK(!exception->IsTheHole()); CommitPc(pc); // Needed for local unwinding. - if (!DoRethrowException(ex)) return; + if (!DoRethrowException(exception)) return; ReloadFromFrameOnException(&decoder, &code, &pc, &limit); continue; // Do not bump pc. } diff --git a/test/common/wasm/wasm-interpreter.h b/test/common/wasm/wasm-interpreter.h index e0acbf2855..ab89f5dc15 100644 --- a/test/common/wasm/wasm-interpreter.h +++ b/test/common/wasm/wasm-interpreter.h @@ -41,6 +41,7 @@ struct ControlTransferEntry { struct CatchControlTransferEntry : public ControlTransferEntry { int exception_index; + int target_control_index; }; struct ControlTransferMap { diff --git a/test/common/wasm/wasm-macro-gen.h b/test/common/wasm/wasm-macro-gen.h index 2640f78b84..8ebbfb26e0 100644 --- a/test/common/wasm/wasm-macro-gen.h +++ b/test/common/wasm/wasm-macro-gen.h @@ -183,6 +183,10 @@ #define WASM_TRY_CATCH_T(t, trystmt, catchstmt, except) \ kExprTry, static_cast((t).value_type_code()), trystmt, kExprCatch, \ except, catchstmt, kExprEnd +#define WASM_TRY_CATCH_CATCH_T(t, trystmt, except1, catchstmt1, except2, \ + catchstmt2) \ + kExprTry, static_cast((t).value_type_code()), trystmt, kExprCatch, \ + except1, catchstmt1, kExprCatch, except2, catchstmt2, kExprEnd #define WASM_TRY_CATCH_R(t, trystmt, catchstmt) \ kExprTry, WASM_REF_TYPE(t), trystmt, kExprCatch, catchstmt, kExprEnd #define WASM_TRY_CATCH_ALL_T(t, trystmt, catchstmt) \ @@ -468,6 +472,7 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) { index, val, \ static_cast(v8::internal::wasm::LoadStoreOpcodeOf(type, true)), \ alignment, ZERO_OFFSET +#define WASM_RETHROW(index) kExprRethrow, static_cast(index) #define WASM_CALL_FUNCTION0(index) kExprCallFunction, static_cast(index) #define WASM_CALL_FUNCTION(index, ...) \