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

[debug] Implement 'PrepareRestartFrame'

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

This CL adds the V8 debugger part of the restart frame logic as well
as some bits for the inspector.

The CL is centered around two key pieces: When the user requests a
restart, we stash the stack frame ID (aka the stack pointer) and
optionally the inlined frame index for optimized frames, and then
continue execution. Once execution bubbles back into JS land,
we throw a termination exception when a frame restart was requested.

Note that the CL doesn't hook up the logic yet to CDP and the CL
also does not the actual handling of the termination exception
in the unwinder.

R=bmeurer@chromium.org, kimanh@chromium.org

Bug: chromium:1303521
Change-Id: I12cfb408c66072dd19f8180e530f84c987d1374d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3613383Reviewed-by: 's avatarKim-Anh Tran <kimanh@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80272}
parent 445190bf
......@@ -13,6 +13,7 @@
#include "src/debug/debug-coverage.h"
#include "src/debug/debug-evaluate.h"
#include "src/debug/debug-property-iterator.h"
#include "src/debug/debug-stack-trace-iterator.h"
#include "src/debug/debug-type-profile.h"
#include "src/debug/debug.h"
#include "src/execution/vm-state-inl.h"
......@@ -315,6 +316,20 @@ void PrepareStep(Isolate* v8_isolate, StepAction action) {
isolate->debug()->PrepareStep(static_cast<i::StepAction>(action));
}
bool PrepareRestartFrame(Isolate* v8_isolate, int callFrameOrdinal) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8_BASIC(isolate);
CHECK(isolate->debug()->CheckExecutionState());
i::DebugStackTraceIterator it(isolate, callFrameOrdinal);
if (it.Done() || !it.CanBeRestarted()) return false;
// Clear all current stepping setup.
isolate->debug()->ClearStepping();
it.PrepareRestart();
return true;
}
void ClearStepping(Isolate* v8_isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate);
......
......@@ -135,6 +135,7 @@ enum class BreakReason : uint8_t {
typedef base::EnumSet<BreakReason> BreakReasons;
void PrepareStep(Isolate* isolate, StepAction action);
bool PrepareRestartFrame(Isolate* isolate, int callFrameOrdinal);
void ClearStepping(Isolate* isolate);
V8_EXPORT_PRIVATE void BreakRightNow(
Isolate* isolate, base::EnumSet<BreakReason> break_reason = {});
......
......@@ -226,5 +226,13 @@ v8::MaybeLocal<v8::Value> DebugStackTraceIterator::Evaluate(
return Utils::ToLocal(value);
}
void DebugStackTraceIterator::PrepareRestart() {
CHECK(!Done());
CHECK(CanBeRestarted());
isolate_->debug()->PrepareRestartFrame(iterator_.javascript_frame(),
inlined_frame_index_);
}
} // namespace internal
} // namespace v8
......@@ -34,6 +34,7 @@ class DebugStackTraceIterator final : public debug::StackTraceIterator {
v8::MaybeLocal<v8::Value> Evaluate(v8::Local<v8::String> source,
bool throw_on_side_effect) override;
void PrepareRestart();
private:
void UpdateInlineFrameIndexAndResumableFnOnStack();
......
......@@ -385,6 +385,8 @@ void Debug::ThreadInit() {
thread_local_.target_frame_count_ = -1;
thread_local_.return_value_ = Smi::zero();
thread_local_.last_breakpoint_id_ = 0;
thread_local_.restart_frame_id_ = StackFrameId::NO_ID;
thread_local_.restart_inline_frame_index_ = -1;
clear_suspended_generator();
base::Relaxed_Store(&thread_local_.current_debug_scope_,
static_cast<base::AtomicWord>(0));
......@@ -1379,6 +1381,8 @@ void Debug::ClearStepping() {
thread_local_.last_frame_count_ = -1;
thread_local_.target_frame_count_ = -1;
thread_local_.break_on_next_function_call_ = false;
thread_local_.restart_frame_id_ = StackFrameId::NO_ID;
thread_local_.restart_inline_frame_index_ = -1;
UpdateHookOnFunctionCall();
}
......@@ -2911,5 +2915,18 @@ bool Debug::GetTemporaryObjectTrackingDisabled() const {
return false;
}
void Debug::PrepareRestartFrame(JavaScriptFrame* frame,
int inlined_frame_index) {
if (frame->is_optimized()) Deoptimizer::DeoptimizeFunction(frame->function());
thread_local_.restart_frame_id_ = frame->id();
thread_local_.restart_inline_frame_index_ = inlined_frame_index;
// TODO(crbug.com/1303521): A full "StepInto" is probably not needed. Get the
// necessary bits out of PrepareSTep into a separate method or fold them
// into Debug::PrepareRestartFrame.
PrepareStep(StepInto);
}
} // namespace internal
} // namespace v8
......@@ -274,6 +274,7 @@ class V8_EXPORT_PRIVATE Debug {
void PrepareStepInSuspendedGenerator();
void PrepareStepOnThrow();
void ClearStepping();
void PrepareRestartFrame(JavaScriptFrame* frame, int inlined_frame_index);
void SetBreakOnNextFunctionCall();
void ClearBreakOnNextFunctionCall();
......@@ -393,6 +394,10 @@ class V8_EXPORT_PRIVATE Debug {
return thread_local_.break_on_next_function_call_;
}
bool ShouldRestartFrame() const {
return thread_local_.restart_frame_id_ != StackFrameId::NO_ID;
}
inline bool break_disabled() const { return break_disabled_; }
DebugFeatureTracker* feature_tracker() { return &feature_tracker_; }
......@@ -571,6 +576,17 @@ class V8_EXPORT_PRIVATE Debug {
// Throwing an exception may cause a Promise rejection. For this purpose
// we keep track of a stack of nested promises.
Object promise_stack_;
// Frame ID for the frame that needs to be restarted. StackFrameId::NO_ID
// otherwise. The unwinder uses the id to restart execution in this frame
// instead of any potential catch handler.
StackFrameId restart_frame_id_;
// If `restart_frame_id_` is an optimized frame, then this index denotes
// which of the inlined frames we actually want to restart. The
// deoptimizer uses the info to materialize and drop execution into the
// right frame.
int restart_inline_frame_index_;
};
static void Iterate(RootVisitor* v, ThreadLocal* thread_local_data);
......
......@@ -340,6 +340,18 @@ Response V8Debugger::continueToLocation(
}
}
bool V8Debugger::restartFrame(int targetContextGroupId, int callFrameOrdinal) {
DCHECK(isPaused());
DCHECK(targetContextGroupId);
m_targetContextGroupId = targetContextGroupId;
if (v8::debug::PrepareRestartFrame(m_isolate, callFrameOrdinal)) {
continueProgram(targetContextGroupId);
return true;
}
return false;
}
bool V8Debugger::shouldContinueToCurrentLocation() {
if (m_continueToLocationTargetCallFrames ==
protocol::Debugger::ContinueToLocation::TargetCallFramesEnum::Any) {
......
......@@ -75,6 +75,7 @@ class V8Debugger : public v8::debug::DebugDelegate,
V8DebuggerScript* script,
std::unique_ptr<protocol::Debugger::Location>,
const String16& targetCallFramess);
bool restartFrame(int targetContextGroupId, int callFrameOrdinal);
// Each script inherits debug data from v8::Context where it has been
// compiled.
......
......@@ -62,6 +62,14 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) {
handle(it.frame()->function(), isolate));
}
// If the user requested to restart a frame, there is no need
// to get the return value or check the bytecode for side-effects.
if (isolate->debug()->ShouldRestartFrame()) {
Object exception = isolate->TerminateExecution();
return MakePair(exception,
Smi::FromInt(static_cast<uint8_t>(Bytecode::kIllegal)));
}
// Return the handler from the original bytecode array.
DCHECK(it.frame()->is_interpreted());
InterpretedFrame* interpreted_frame =
......@@ -140,6 +148,9 @@ RUNTIME_FUNCTION(Runtime_HandleDebuggerStatement) {
isolate->debug()->HandleDebugBreak(
kIgnoreIfTopFrameBlackboxed,
v8::debug::BreakReasons({v8::debug::BreakReason::kDebuggerStatement}));
if (isolate->debug()->ShouldRestartFrame()) {
return isolate->TerminateExecution();
}
}
return isolate->stack_guard()->HandleInterrupts();
}
......
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