Commit 30a8616a authored by Simon Zünd's avatar Simon Zünd Committed by V8 LUCI CQ

[unwinder] Support 'restart frame' in the unwinder

Doc: https://bit.ly/revive-restart-frame

This CL implements support for the Debugger's "restart frame"
functionality in the unwinder. When the debugger wants to restart
a frame, we throw a termination exception. The unwinder checks if we
are currently in "restart frame mode", and if so, drops execution
into a special builtin instead of trying to find a catch handler.

Optimized frames are handled similarly, but the deoptimizer has to
materialize the frames first before also dropping into the
special builtin.

Drive-by: Rename `ShouldRestartFrame` to `IsRestartFrameScheduled`, so
we can reuse the name `ShouldRestartFrame` as a query method to check
if a specific frame is the frame we want to restart.

R=jarin@chromium.org, tebbi@chromium.org

Bug: chromium:1303521
Change-Id: Iff4f5761f970b65e22485f78ee0fd85da620ce66
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3613397Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarNico Hartmann <nicohartmann@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80443}
parent 7beb93cd
...@@ -385,8 +385,7 @@ void Debug::ThreadInit() { ...@@ -385,8 +385,7 @@ void Debug::ThreadInit() {
thread_local_.target_frame_count_ = -1; thread_local_.target_frame_count_ = -1;
thread_local_.return_value_ = Smi::zero(); thread_local_.return_value_ = Smi::zero();
thread_local_.last_breakpoint_id_ = 0; thread_local_.last_breakpoint_id_ = 0;
thread_local_.restart_frame_id_ = StackFrameId::NO_ID; clear_restart_frame();
thread_local_.restart_inline_frame_index_ = -1;
clear_suspended_generator(); clear_suspended_generator();
base::Relaxed_Store(&thread_local_.current_debug_scope_, base::Relaxed_Store(&thread_local_.current_debug_scope_,
static_cast<base::AtomicWord>(0)); static_cast<base::AtomicWord>(0));
...@@ -1381,8 +1380,7 @@ void Debug::ClearStepping() { ...@@ -1381,8 +1380,7 @@ void Debug::ClearStepping() {
thread_local_.last_frame_count_ = -1; thread_local_.last_frame_count_ = -1;
thread_local_.target_frame_count_ = -1; thread_local_.target_frame_count_ = -1;
thread_local_.break_on_next_function_call_ = false; thread_local_.break_on_next_function_call_ = false;
thread_local_.restart_frame_id_ = StackFrameId::NO_ID; clear_restart_frame();
thread_local_.restart_inline_frame_index_ = -1;
UpdateHookOnFunctionCall(); UpdateHookOnFunctionCall();
} }
......
...@@ -394,9 +394,16 @@ class V8_EXPORT_PRIVATE Debug { ...@@ -394,9 +394,16 @@ class V8_EXPORT_PRIVATE Debug {
return thread_local_.break_on_next_function_call_; return thread_local_.break_on_next_function_call_;
} }
bool ShouldRestartFrame() const { bool IsRestartFrameScheduled() const {
return thread_local_.restart_frame_id_ != StackFrameId::NO_ID; return thread_local_.restart_frame_id_ != StackFrameId::NO_ID;
} }
bool ShouldRestartFrame(StackFrameId id) const {
return IsRestartFrameScheduled() && thread_local_.restart_frame_id_ == id;
}
void clear_restart_frame() {
thread_local_.restart_frame_id_ = StackFrameId::NO_ID;
thread_local_.restart_inline_frame_index_ = -1;
}
inline bool break_disabled() const { return break_disabled_; } inline bool break_disabled() const { return break_disabled_; }
......
...@@ -482,6 +482,7 @@ Deoptimizer::Deoptimizer(Isolate* isolate, JSFunction function, ...@@ -482,6 +482,7 @@ Deoptimizer::Deoptimizer(Isolate* isolate, JSFunction function,
? new CodeTracer::Scope(isolate->GetCodeTracer()) ? new CodeTracer::Scope(isolate->GetCodeTracer())
: nullptr) { : nullptr) {
if (isolate->deoptimizer_lazy_throw()) { if (isolate->deoptimizer_lazy_throw()) {
CHECK_EQ(kind, DeoptimizeKind::kLazy);
isolate->set_deoptimizer_lazy_throw(false); isolate->set_deoptimizer_lazy_throw(false);
deoptimizing_throw_ = true; deoptimizing_throw_ = true;
} }
......
...@@ -1910,6 +1910,45 @@ Object Isolate::UnwindAndFindHandler() { ...@@ -1910,6 +1910,45 @@ Object Isolate::UnwindAndFindHandler() {
StackFrame* frame = iter.frame(); StackFrame* frame = iter.frame();
// The debugger implements the "restart frame" feature by throwing a
// terminate exception. Check and if we need to restart `frame`,
// jump into the `RestartFrameTrampoline` builtin instead of
// a catch handler.
// Optimized frames take a detour via the deoptimizer before also jumping
// to the `RestartFrameTrampoline` builtin.
if (debug()->ShouldRestartFrame(frame->id())) {
CHECK(!catchable_by_js);
CHECK(frame->is_java_script());
if (frame->is_optimized()) {
Code code = frame->LookupCode();
// The debugger triggers lazy deopt for the "to-be-restarted" frame
// immediately when the CDP event arrives while paused.
CHECK(code.marked_for_deoptimization());
set_deoptimizer_lazy_throw(true);
// Jump directly to the optimized frames return, to immediately fall
// into the deoptimizer.
int offset = code.GetOffsetFromInstructionStart(this, frame->pc());
// Compute the stack pointer from the frame pointer. This ensures that
// argument slots on the stack are dropped as returning would.
// Note: Needed by the deoptimizer to rematerialize frames.
Address return_sp = frame->fp() +
StandardFrameConstants::kFixedFrameSizeAboveFp -
code.stack_slots() * kSystemPointerSize;
return FoundHandler(Context(), code.InstructionStart(this, frame->pc()),
offset, code.constant_pool(), return_sp,
frame->fp(), visited_frames);
}
debug()->clear_restart_frame();
Code code = FromCodeT(builtins()->code(Builtin::kRestartFrameTrampoline));
return FoundHandler(Context(), code.InstructionStart(), 0,
code.constant_pool(), kNullAddress, frame->fp(),
visited_frames);
}
switch (frame->type()) { switch (frame->type()) {
case StackFrame::ENTRY: case StackFrame::ENTRY:
case StackFrame::CONSTRUCT_ENTRY: { case StackFrame::CONSTRUCT_ENTRY: {
......
...@@ -64,7 +64,7 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) { ...@@ -64,7 +64,7 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) {
// If the user requested to restart a frame, there is no need // If the user requested to restart a frame, there is no need
// to get the return value or check the bytecode for side-effects. // to get the return value or check the bytecode for side-effects.
if (isolate->debug()->ShouldRestartFrame()) { if (isolate->debug()->IsRestartFrameScheduled()) {
Object exception = isolate->TerminateExecution(); Object exception = isolate->TerminateExecution();
return MakePair(exception, return MakePair(exception,
Smi::FromInt(static_cast<uint8_t>(Bytecode::kIllegal))); Smi::FromInt(static_cast<uint8_t>(Bytecode::kIllegal)));
...@@ -148,7 +148,7 @@ RUNTIME_FUNCTION(Runtime_HandleDebuggerStatement) { ...@@ -148,7 +148,7 @@ RUNTIME_FUNCTION(Runtime_HandleDebuggerStatement) {
isolate->debug()->HandleDebugBreak( isolate->debug()->HandleDebugBreak(
kIgnoreIfTopFrameBlackboxed, kIgnoreIfTopFrameBlackboxed,
v8::debug::BreakReasons({v8::debug::BreakReason::kDebuggerStatement})); v8::debug::BreakReasons({v8::debug::BreakReason::kDebuggerStatement}));
if (isolate->debug()->ShouldRestartFrame()) { if (isolate->debug()->IsRestartFrameScheduled()) {
return isolate->TerminateExecution(); return isolate->TerminateExecution();
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment