Commit d5e0d1f7 authored by Thibaud Michaud's avatar Thibaud Michaud Committed by Commit Bot

[wasm][debug] Handle OSR edge case

When the top frame is paused at a breakpoint, and this breakpoint is
being removed or was already removed, introduce a "dead breakpoint" in
the new code. This ensures that:
- The source position for the new frame is correct, otherwise it would
just pick the source position of the previous call,
- The offset between the source position and return address is the same
in the new and old code, which is necessary for OSR to find the correct
return address.

R=clemensb@chromium.org

Bug: v8:10337
Change-Id: I400886ff14846d3973d0634592c05960c05de738
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2377686
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69731}
parent 58164026
...@@ -339,7 +339,7 @@ class LiftoffCompiler { ...@@ -339,7 +339,7 @@ class LiftoffCompiler {
std::unique_ptr<AssemblerBuffer> buffer, std::unique_ptr<AssemblerBuffer> buffer,
DebugSideTableBuilder* debug_sidetable_builder, DebugSideTableBuilder* debug_sidetable_builder,
ForDebugging for_debugging, int func_index, ForDebugging for_debugging, int func_index,
Vector<int> breakpoints = {}) Vector<int> breakpoints = {}, int dead_breakpoint = 0)
: asm_(std::move(buffer)), : asm_(std::move(buffer)),
descriptor_( descriptor_(
GetLoweredCallDescriptor(compilation_zone, call_descriptor)), GetLoweredCallDescriptor(compilation_zone, call_descriptor)),
...@@ -353,7 +353,8 @@ class LiftoffCompiler { ...@@ -353,7 +353,8 @@ class LiftoffCompiler {
compilation_zone_(compilation_zone), compilation_zone_(compilation_zone),
safepoint_table_builder_(compilation_zone_), safepoint_table_builder_(compilation_zone_),
next_breakpoint_ptr_(breakpoints.begin()), next_breakpoint_ptr_(breakpoints.begin()),
next_breakpoint_end_(breakpoints.end()) { next_breakpoint_end_(breakpoints.end()),
dead_breakpoint_(dead_breakpoint) {
if (breakpoints.empty()) { if (breakpoints.empty()) {
next_breakpoint_ptr_ = next_breakpoint_end_ = nullptr; next_breakpoint_ptr_ = next_breakpoint_end_ = nullptr;
} }
...@@ -819,6 +820,18 @@ class LiftoffCompiler { ...@@ -819,6 +820,18 @@ class LiftoffCompiler {
} }
} }
} }
if (dead_breakpoint_ == decoder->position()) {
DCHECK(!next_breakpoint_ptr_ ||
*next_breakpoint_ptr_ != dead_breakpoint_);
// The top frame is paused at this position, but the breakpoint was
// removed. Adding a dead breakpoint here ensures that the source position
// exists, and that the offset to the return address is the same as in the
// old code.
Label cont;
__ emit_jump(&cont);
EmitBreakpoint(decoder);
__ bind(&cont);
}
} }
void NextInstruction(FullDecoder* decoder, WasmOpcode opcode) { void NextInstruction(FullDecoder* decoder, WasmOpcode opcode) {
...@@ -3919,6 +3932,10 @@ class LiftoffCompiler { ...@@ -3919,6 +3932,10 @@ class LiftoffCompiler {
int* next_breakpoint_ptr_ = nullptr; int* next_breakpoint_ptr_ = nullptr;
int* next_breakpoint_end_ = nullptr; int* next_breakpoint_end_ = nullptr;
// Introduce a dead breakpoint to ensure that the calculation of the return
// address in OSR is correct.
int dead_breakpoint_ = 0;
bool has_outstanding_op() const { bool has_outstanding_op() const {
return outstanding_op_ != kNoOutstandingOp; return outstanding_op_ != kNoOutstandingOp;
} }
...@@ -3953,7 +3970,7 @@ WasmCompilationResult ExecuteLiftoffCompilation( ...@@ -3953,7 +3970,7 @@ WasmCompilationResult ExecuteLiftoffCompilation(
AccountingAllocator* allocator, CompilationEnv* env, AccountingAllocator* allocator, CompilationEnv* env,
const FunctionBody& func_body, int func_index, ForDebugging for_debugging, const FunctionBody& func_body, int func_index, ForDebugging for_debugging,
Counters* counters, WasmFeatures* detected, Vector<int> breakpoints, Counters* counters, WasmFeatures* detected, Vector<int> breakpoints,
std::unique_ptr<DebugSideTable>* debug_sidetable) { std::unique_ptr<DebugSideTable>* debug_sidetable, int dead_breakpoint) {
int func_body_size = static_cast<int>(func_body.end - func_body.start); int func_body_size = static_cast<int>(func_body.end - func_body.start);
TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"), TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.wasm.detailed"),
"wasm.CompileBaseline", "func_index", func_index, "body_size", "wasm.CompileBaseline", "func_index", func_index, "body_size",
...@@ -3980,7 +3997,8 @@ WasmCompilationResult ExecuteLiftoffCompilation( ...@@ -3980,7 +3997,8 @@ WasmCompilationResult ExecuteLiftoffCompilation(
WasmFullDecoder<Decoder::kValidate, LiftoffCompiler> decoder( WasmFullDecoder<Decoder::kValidate, LiftoffCompiler> decoder(
&zone, env->module, env->enabled_features, detected, func_body, &zone, env->module, env->enabled_features, detected, func_body,
call_descriptor, env, &zone, instruction_buffer->CreateView(), call_descriptor, env, &zone, instruction_buffer->CreateView(),
debug_sidetable_builder.get(), for_debugging, func_index, breakpoints); debug_sidetable_builder.get(), for_debugging, func_index, breakpoints,
dead_breakpoint);
decoder.Decode(); decoder.Decode();
liftoff_compile_time_scope.reset(); liftoff_compile_time_scope.reset();
LiftoffCompiler* compiler = &decoder.interface(); LiftoffCompiler* compiler = &decoder.interface();
......
...@@ -56,7 +56,8 @@ enum LiftoffBailoutReason : int8_t { ...@@ -56,7 +56,8 @@ enum LiftoffBailoutReason : int8_t {
V8_EXPORT_PRIVATE WasmCompilationResult ExecuteLiftoffCompilation( V8_EXPORT_PRIVATE WasmCompilationResult ExecuteLiftoffCompilation(
AccountingAllocator*, CompilationEnv*, const FunctionBody&, int func_index, AccountingAllocator*, CompilationEnv*, const FunctionBody&, int func_index,
ForDebugging, Counters*, WasmFeatures* detected_features, ForDebugging, Counters*, WasmFeatures* detected_features,
Vector<int> breakpoints = {}, std::unique_ptr<DebugSideTable>* = nullptr); Vector<int> breakpoints = {}, std::unique_ptr<DebugSideTable>* = nullptr,
int dead_breakpoint = 0);
V8_EXPORT_PRIVATE std::unique_ptr<DebugSideTable> GenerateLiftoffDebugSideTable( V8_EXPORT_PRIVATE std::unique_ptr<DebugSideTable> GenerateLiftoffDebugSideTable(
AccountingAllocator*, CompilationEnv*, const FunctionBody&, int func_index); AccountingAllocator*, CompilationEnv*, const FunctionBody&, int func_index);
......
...@@ -402,8 +402,25 @@ class DebugInfoImpl { ...@@ -402,8 +402,25 @@ class DebugInfoImpl {
return local_names_->GetName(func_index, local_index); return local_names_->GetName(func_index, local_index);
} }
WasmCode* RecompileLiftoffWithBreakpoints(int func_index, // If the top frame is a Wasm frame and its position is not in the list of
Vector<int> offsets) { // breakpoints, return that position. Return 0 otherwise.
// This is used to generate a "dead breakpoint" in Liftoff, which is necessary
// for OSR to find the correct return address.
int DeadBreakpoint(int func_index, std::vector<int>& breakpoints,
Isolate* isolate) {
StackTraceFrameIterator it(isolate);
if (it.done() || !it.is_wasm()) return 0;
WasmFrame* frame = WasmFrame::cast(it.frame());
const auto& function = native_module_->module()->functions[func_index];
int offset = frame->position() - function.code.offset();
if (std::binary_search(breakpoints.begin(), breakpoints.end(), offset)) {
return 0;
}
return offset;
}
WasmCode* RecompileLiftoffWithBreakpoints(int func_index, Vector<int> offsets,
int dead_breakpoint) {
DCHECK(!mutex_.TryLock()); // Mutex is held externally. DCHECK(!mutex_.TryLock()); // Mutex is held externally.
// Recompile the function with Liftoff, setting the new breakpoints. // Recompile the function with Liftoff, setting the new breakpoints.
// Not thread-safe. The caller is responsible for locking {mutex_}. // Not thread-safe. The caller is responsible for locking {mutex_}.
...@@ -422,7 +439,8 @@ class DebugInfoImpl { ...@@ -422,7 +439,8 @@ class DebugInfoImpl {
WasmFeatures unused_detected; WasmFeatures unused_detected;
WasmCompilationResult result = ExecuteLiftoffCompilation( WasmCompilationResult result = ExecuteLiftoffCompilation(
native_module_->engine()->allocator(), &env, body, func_index, native_module_->engine()->allocator(), &env, body, func_index,
for_debugging, counters, &unused_detected, offsets, &debug_sidetable); for_debugging, counters, &unused_detected, offsets, &debug_sidetable,
dead_breakpoint);
// Liftoff compilation failure is a FATAL error. We rely on complete Liftoff // Liftoff compilation failure is a FATAL error. We rely on complete Liftoff
// support for debugging. // support for debugging.
if (!result.succeeded()) FATAL("Liftoff compilation failed"); if (!result.succeeded()) FATAL("Liftoff compilation failed");
...@@ -479,8 +497,10 @@ class DebugInfoImpl { ...@@ -479,8 +497,10 @@ class DebugInfoImpl {
new_code = native_module_->GetCode(func_index); new_code = native_module_->GetCode(func_index);
} else { } else {
all_breakpoints.insert(insertion_point, offset); all_breakpoints.insert(insertion_point, offset);
new_code = RecompileLiftoffWithBreakpoints(func_index, int dead_breakpoint =
VectorOf(all_breakpoints)); DeadBreakpoint(func_index, all_breakpoints, isolate);
new_code = RecompileLiftoffWithBreakpoints(
func_index, VectorOf(all_breakpoints), dead_breakpoint);
} }
UpdateReturnAddresses(isolate, new_code, isolate_data.stepping_frame); UpdateReturnAddresses(isolate, new_code, isolate_data.stepping_frame);
} }
...@@ -497,10 +517,11 @@ class DebugInfoImpl { ...@@ -497,10 +517,11 @@ class DebugInfoImpl {
} }
void UpdateBreakpoints(int func_index, Vector<int> breakpoints, void UpdateBreakpoints(int func_index, Vector<int> breakpoints,
Isolate* isolate, StackFrameId stepping_frame) { Isolate* isolate, StackFrameId stepping_frame,
int dead_breakpoint) {
DCHECK(!mutex_.TryLock()); // Mutex is held externally. DCHECK(!mutex_.TryLock()); // Mutex is held externally.
WasmCode* new_code = WasmCode* new_code = RecompileLiftoffWithBreakpoints(
RecompileLiftoffWithBreakpoints(func_index, breakpoints); func_index, breakpoints, dead_breakpoint);
UpdateReturnAddresses(isolate, new_code, stepping_frame); UpdateReturnAddresses(isolate, new_code, stepping_frame);
} }
...@@ -512,7 +533,7 @@ class DebugInfoImpl { ...@@ -512,7 +533,7 @@ class DebugInfoImpl {
// Generate an additional source position for the current byte offset. // Generate an additional source position for the current byte offset.
base::MutexGuard guard(&mutex_); base::MutexGuard guard(&mutex_);
WasmCode* new_code = RecompileLiftoffWithBreakpoints( WasmCode* new_code = RecompileLiftoffWithBreakpoints(
frame->function_index(), VectorOf(&offset, 1)); frame->function_index(), VectorOf(&offset, 1), 0);
UpdateReturnAddress(frame, new_code, return_location); UpdateReturnAddress(frame, new_code, return_location);
} }
...@@ -558,18 +579,6 @@ class DebugInfoImpl { ...@@ -558,18 +579,6 @@ class DebugInfoImpl {
} }
void RemoveBreakpoint(int func_index, int position, Isolate* isolate) { void RemoveBreakpoint(int func_index, int position, Isolate* isolate) {
// TODO(thibaudm): Cannot remove the breakpoint we are currently paused at,
// because the new code would be missing the call instruction and the
// corresponding source position that we rely on for OSR.
StackTraceFrameIterator it(isolate);
if (!it.done() && it.is_wasm()) {
WasmFrame* frame = WasmFrame::cast(it.frame());
if (static_cast<int32_t>(frame->function_index()) == func_index &&
frame->position() == position) {
return;
}
}
// Put the code ref scope outside of the mutex, so we don't unnecessarily // Put the code ref scope outside of the mutex, so we don't unnecessarily
// hold the mutex while freeing code. // hold the mutex while freeing code.
WasmCodeRefScope wasm_code_ref_scope; WasmCodeRefScope wasm_code_ref_scope;
...@@ -595,8 +604,9 @@ class DebugInfoImpl { ...@@ -595,8 +604,9 @@ class DebugInfoImpl {
// If the breakpoint is still set in another isolate, don't remove it. // If the breakpoint is still set in another isolate, don't remove it.
DCHECK(std::is_sorted(remaining.begin(), remaining.end())); DCHECK(std::is_sorted(remaining.begin(), remaining.end()));
if (std::binary_search(remaining.begin(), remaining.end(), offset)) return; if (std::binary_search(remaining.begin(), remaining.end(), offset)) return;
int dead_breakpoint = DeadBreakpoint(func_index, remaining, isolate);
UpdateBreakpoints(func_index, VectorOf(remaining), isolate, UpdateBreakpoints(func_index, VectorOf(remaining), isolate,
isolate_data.stepping_frame); isolate_data.stepping_frame, dead_breakpoint);
} }
void RemoveDebugSideTables(Vector<WasmCode* const> codes) { void RemoveDebugSideTables(Vector<WasmCode* const> codes) {
...@@ -640,7 +650,7 @@ class DebugInfoImpl { ...@@ -640,7 +650,7 @@ class DebugInfoImpl {
std::vector<int>& removed = entry.second; std::vector<int>& removed = entry.second;
std::vector<int> remaining = FindAllBreakpoints(func_index); std::vector<int> remaining = FindAllBreakpoints(func_index);
if (HasRemovedBreakpoints(removed, remaining)) { if (HasRemovedBreakpoints(removed, remaining)) {
RecompileLiftoffWithBreakpoints(func_index, VectorOf(remaining)); RecompileLiftoffWithBreakpoints(func_index, VectorOf(remaining), 0);
} }
} }
} }
......
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