Commit 3dea55b4 authored by clemensh's avatar clemensh Committed by Commit bot

[wasm] Implement stepping in wasm code

Implement stepping by remembering the current step action in the wasm
interpreter handle in WasmDebugInfo, and using it when continuing
execution in the interpreter.
The control flow is as follows: After module compilation, the user sets
a breakpoint in wasm. The respective function is redirected to the
interpreter and the breakpoint is set on the interpreter. When it is
hit, we notify all debug event listeners, which might prepare stepping.
When returning from these listeners, before continuing execution, we
check whether stepping was requested and continue execution in the
interpreter accordingly.

Stepping from Wasm to JS and vice versa will be implemented and tested
in a follow-up CL. Testing this requires breakpoints and stepping in
Wasm to be exposed via the inspector interface, such that we can write
an inspector test. This mixed JS-Wasm-execution is hard to set up in a
cctest.

R=titzer@chromium.org, yangguo@chromium.org
BUG=

Review-Url: https://codereview.chromium.org/2649533002
Cr-Commit-Position: refs/heads/master@{#42624}
parent d9253a2f
...@@ -2956,8 +2956,10 @@ void WasmGraphBuilder::BuildWasmInterpreterEntry( ...@@ -2956,8 +2956,10 @@ void WasmGraphBuilder::BuildWasmInterpreterEntry(
sig->return_count() == 0 ? 0 : 1 << ElementSizeLog2Of(sig->GetReturn(0)); sig->return_count() == 0 ? 0 : 1 << ElementSizeLog2Of(sig->GetReturn(0));
// Get a stack slot for the arguments. // Get a stack slot for the arguments.
Node* arg_buffer = graph()->NewNode(jsgraph()->machine()->StackSlot( Node* arg_buffer = args_size_bytes == 0 && return_size_bytes == 0
std::max(args_size_bytes, return_size_bytes))); ? jsgraph()->IntPtrConstant(0)
: graph()->NewNode(jsgraph()->machine()->StackSlot(
std::max(args_size_bytes, return_size_bytes)));
// Now store all our arguments to the buffer. // Now store all our arguments to the buffer.
int param_index = 0; int param_index = 0;
......
...@@ -984,24 +984,36 @@ void Debug::PrepareStep(StepAction step_action) { ...@@ -984,24 +984,36 @@ void Debug::PrepareStep(StepAction step_action) {
// If there is no JavaScript stack don't do anything. // If there is no JavaScript stack don't do anything.
if (frame_id == StackFrame::NO_ID) return; if (frame_id == StackFrame::NO_ID) return;
JavaScriptFrameIterator frames_it(isolate_, frame_id); StackTraceFrameIterator frames_it(isolate_, frame_id);
JavaScriptFrame* frame = frames_it.frame(); StandardFrame* frame = frames_it.frame();
feature_tracker()->Track(DebugFeatureTracker::kStepping); feature_tracker()->Track(DebugFeatureTracker::kStepping);
thread_local_.last_step_action_ = step_action; thread_local_.last_step_action_ = step_action;
UpdateHookOnFunctionCall(); UpdateHookOnFunctionCall();
// Handle stepping in wasm functions via the wasm interpreter.
if (frame->is_wasm()) {
// If the top frame is compiled, we cannot step.
if (frame->is_wasm_compiled()) return;
WasmInterpreterEntryFrame* wasm_frame =
WasmInterpreterEntryFrame::cast(frame);
wasm_frame->wasm_instance()->debug_info()->PrepareStep(step_action);
return;
}
JavaScriptFrame* js_frame = JavaScriptFrame::cast(frame);
// If the function on the top frame is unresolved perform step out. This will // If the function on the top frame is unresolved perform step out. This will
// be the case when calling unknown function and having the debugger stopped // be the case when calling unknown function and having the debugger stopped
// in an unhandled exception. // in an unhandled exception.
if (!frame->function()->IsJSFunction()) { if (!js_frame->function()->IsJSFunction()) {
// Step out: Find the calling JavaScript frame and flood it with // Step out: Find the calling JavaScript frame and flood it with
// breakpoints. // breakpoints.
frames_it.Advance(); frames_it.Advance();
// Fill the function to return to with one-shot break points. // Fill the function to return to with one-shot break points.
JSFunction* function = frames_it.frame()->function(); JSFunction* function = JavaScriptFrame::cast(frames_it.frame())->function();
FloodWithOneShot(Handle<JSFunction>(function)); FloodWithOneShot(handle(function, isolate_));
return; return;
} }
...@@ -1015,7 +1027,7 @@ void Debug::PrepareStep(StepAction step_action) { ...@@ -1015,7 +1027,7 @@ void Debug::PrepareStep(StepAction step_action) {
} }
Handle<DebugInfo> debug_info(shared->GetDebugInfo()); Handle<DebugInfo> debug_info(shared->GetDebugInfo());
BreakLocation location = BreakLocation::FromFrame(debug_info, frame); BreakLocation location = BreakLocation::FromFrame(debug_info, js_frame);
// Any step at a return is a step-out. // Any step at a return is a step-out.
if (location.IsReturn()) step_action = StepOut; if (location.IsReturn()) step_action = StepOut;
...@@ -1037,22 +1049,27 @@ void Debug::PrepareStep(StepAction step_action) { ...@@ -1037,22 +1049,27 @@ void Debug::PrepareStep(StepAction step_action) {
case StepOut: case StepOut:
// Advance to caller frame. // Advance to caller frame.
frames_it.Advance(); frames_it.Advance();
// Skip native and extension functions on the stack. // Find top-most function which is subject to debugging.
while ( while (!frames_it.done()) {
!frames_it.done() && StandardFrame* caller_frame = frames_it.frame();
(!frames_it.frame()->function()->shared()->IsSubjectToDebugging() || if (caller_frame->is_wasm()) {
IsBlackboxed(frames_it.frame()->function()->shared()))) { // TODO(clemensh): Implement stepping out from JS to WASM.
break;
}
Handle<JSFunction> js_caller_function(
JavaScriptFrame::cast(caller_frame)->function(), isolate_);
if (js_caller_function->shared()->IsSubjectToDebugging() &&
!IsBlackboxed(js_caller_function->shared())) {
// Fill the caller function to return to with one-shot break points.
FloodWithOneShot(js_caller_function);
thread_local_.target_fp_ = frames_it.frame()->UnpaddedFP();
break;
}
// Builtin functions are not subject to stepping, but need to be // Builtin functions are not subject to stepping, but need to be
// deoptimized to include checks for step-in at call sites. // deoptimized to include checks for step-in at call sites.
Deoptimizer::DeoptimizeFunction(frames_it.frame()->function()); Deoptimizer::DeoptimizeFunction(*js_caller_function);
frames_it.Advance(); frames_it.Advance();
} }
if (!frames_it.done()) {
// Fill the caller function to return to with one-shot break points.
Handle<JSFunction> caller_function(frames_it.frame()->function());
FloodWithOneShot(caller_function);
thread_local_.target_fp_ = frames_it.frame()->UnpaddedFP();
}
// Clear last position info. For stepping out it does not matter. // Clear last position info. For stepping out it does not matter.
thread_local_.last_statement_position_ = kNoSourcePosition; thread_local_.last_statement_position_ = kNoSourcePosition;
thread_local_.last_fp_ = 0; thread_local_.last_fp_ = 0;
...@@ -1062,9 +1079,11 @@ void Debug::PrepareStep(StepAction step_action) { ...@@ -1062,9 +1079,11 @@ void Debug::PrepareStep(StepAction step_action) {
FloodWithOneShot(function); FloodWithOneShot(function);
break; break;
case StepIn: case StepIn:
// TODO(clemensh): Implement stepping from JS into WASM.
FloodWithOneShot(function); FloodWithOneShot(function);
break; break;
case StepFrame: case StepFrame:
// TODO(clemensh): Implement stepping from JS into WASM or vice versa.
// No point in setting one-shot breaks at places where we are not about // No point in setting one-shot breaks at places where we are not about
// to leave the current frame. // to leave the current frame.
FloodWithOneShot(function, CALLS_AND_RETURNS); FloodWithOneShot(function, CALLS_AND_RETURNS);
......
...@@ -131,16 +131,6 @@ StackFrame* StackFrameIteratorBase::SingletonFor(StackFrame::Type type) { ...@@ -131,16 +131,6 @@ StackFrame* StackFrameIteratorBase::SingletonFor(StackFrame::Type type) {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
JavaScriptFrameIterator::JavaScriptFrameIterator(Isolate* isolate,
StackFrame::Id id)
: iterator_(isolate) {
while (!done()) {
Advance();
if (frame()->id() == id) return;
}
}
void JavaScriptFrameIterator::Advance() { void JavaScriptFrameIterator::Advance() {
do { do {
iterator_.Advance(); iterator_.Advance();
......
...@@ -1462,8 +1462,6 @@ class JavaScriptFrameIterator BASE_EMBEDDED { ...@@ -1462,8 +1462,6 @@ class JavaScriptFrameIterator BASE_EMBEDDED {
public: public:
inline explicit JavaScriptFrameIterator(Isolate* isolate); inline explicit JavaScriptFrameIterator(Isolate* isolate);
inline JavaScriptFrameIterator(Isolate* isolate, ThreadLocalTop* top); inline JavaScriptFrameIterator(Isolate* isolate, ThreadLocalTop* top);
// Skip frames until the frame with the given id is reached.
JavaScriptFrameIterator(Isolate* isolate, StackFrame::Id id);
inline JavaScriptFrame* frame() const; inline JavaScriptFrame* frame() const;
...@@ -1485,6 +1483,7 @@ class JavaScriptFrameIterator BASE_EMBEDDED { ...@@ -1485,6 +1483,7 @@ class JavaScriptFrameIterator BASE_EMBEDDED {
class StackTraceFrameIterator BASE_EMBEDDED { class StackTraceFrameIterator BASE_EMBEDDED {
public: public:
explicit StackTraceFrameIterator(Isolate* isolate); explicit StackTraceFrameIterator(Isolate* isolate);
// Skip frames until the frame with the given id is reached.
StackTraceFrameIterator(Isolate* isolate, StackFrame::Id id); StackTraceFrameIterator(Isolate* isolate, StackFrame::Id id);
bool done() const { return iterator_.done(); } bool done() const { return iterator_.done(); }
void Advance(); void Advance();
......
...@@ -751,8 +751,10 @@ RUNTIME_FUNCTION(Runtime_GetScopeCount) { ...@@ -751,8 +751,10 @@ RUNTIME_FUNCTION(Runtime_GetScopeCount) {
// Get the frame where the debugging is performed. // Get the frame where the debugging is performed.
StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id); StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator it(isolate, id); StackTraceFrameIterator it(isolate, id);
JavaScriptFrame* frame = it.frame(); StandardFrame* frame = it.frame();
if (it.frame()->is_wasm()) return 0;
FrameInspector frame_inspector(frame, 0, isolate); FrameInspector frame_inspector(frame, 0, isolate);
// Count the visible scopes. // Count the visible scopes.
...@@ -786,8 +788,9 @@ RUNTIME_FUNCTION(Runtime_GetScopeDetails) { ...@@ -786,8 +788,9 @@ RUNTIME_FUNCTION(Runtime_GetScopeDetails) {
// Get the frame where the debugging is performed. // Get the frame where the debugging is performed.
StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id); StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id); StackTraceFrameIterator frame_it(isolate, id);
JavaScriptFrame* frame = frame_it.frame(); // Wasm has no scopes, this must be javascript.
JavaScriptFrame* frame = JavaScriptFrame::cast(frame_it.frame());
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
// Find the requested scope. // Find the requested scope.
...@@ -975,8 +978,9 @@ RUNTIME_FUNCTION(Runtime_SetScopeVariableValue) { ...@@ -975,8 +978,9 @@ RUNTIME_FUNCTION(Runtime_SetScopeVariableValue) {
// Get the frame where the debugging is performed. // Get the frame where the debugging is performed.
StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id); StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id); StackTraceFrameIterator frame_it(isolate, id);
JavaScriptFrame* frame = frame_it.frame(); // Wasm has no scopes, this must be javascript.
JavaScriptFrame* frame = JavaScriptFrame::cast(frame_it.frame());
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate); FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
ScopeIterator it(isolate, &frame_inspector); ScopeIterator it(isolate, &frame_inspector);
......
...@@ -30,6 +30,8 @@ class InterpreterHandle { ...@@ -30,6 +30,8 @@ class InterpreterHandle {
WasmInstance instance_; WasmInstance instance_;
WasmInterpreter interpreter_; WasmInterpreter interpreter_;
Isolate* isolate_; Isolate* isolate_;
StepAction next_step_action_ = StepNone;
int last_step_stack_depth_ = 0;
public: public:
// Initialize in the right order, using helper methods to make this possible. // Initialize in the right order, using helper methods to make this possible.
...@@ -63,6 +65,18 @@ class InterpreterHandle { ...@@ -63,6 +65,18 @@ class InterpreterHandle {
WasmInterpreter* interpreter() { return &interpreter_; } WasmInterpreter* interpreter() { return &interpreter_; }
const WasmModule* module() { return instance_.module; } const WasmModule* module() { return instance_.module; }
void PrepareStep(StepAction step_action) {
next_step_action_ = step_action;
last_step_stack_depth_ = CurrentStackDepth();
}
void ClearStepping() { next_step_action_ = StepNone; }
int CurrentStackDepth() {
DCHECK_EQ(1, interpreter()->GetThreadCount());
return interpreter()->GetThread(0)->GetFrameCount();
}
void Execute(uint32_t func_index, uint8_t* arg_buffer) { void Execute(uint32_t func_index, uint8_t* arg_buffer) {
DCHECK_GE(module()->functions.size(), func_index); DCHECK_GE(module()->functions.size(), func_index);
FunctionSig* sig = module()->functions[func_index].sig; FunctionSig* sig = module()->functions[func_index].sig;
...@@ -96,15 +110,17 @@ class InterpreterHandle { ...@@ -96,15 +110,17 @@ class InterpreterHandle {
thread->state() == WasmInterpreter::FINISHED); thread->state() == WasmInterpreter::FINISHED);
thread->Reset(); thread->Reset();
thread->PushFrame(&module()->functions[func_index], wasm_args.start()); thread->PushFrame(&module()->functions[func_index], wasm_args.start());
WasmInterpreter::State state; bool finished = false;
do { while (!finished) {
state = thread->Run(); // TODO(clemensh): Add occasional StackChecks.
WasmInterpreter::State state = ContinueExecution(thread);
switch (state) { switch (state) {
case WasmInterpreter::State::PAUSED: case WasmInterpreter::State::PAUSED:
NotifyDebugEventListeners(); NotifyDebugEventListeners(thread);
break; break;
case WasmInterpreter::State::FINISHED: case WasmInterpreter::State::FINISHED:
// Perfect, just break the switch and exit the loop. // Perfect, just break the switch and exit the loop.
finished = true;
break; break;
case WasmInterpreter::State::TRAPPED: case WasmInterpreter::State::TRAPPED:
// TODO(clemensh): Generate appropriate JS exception. // TODO(clemensh): Generate appropriate JS exception.
...@@ -116,7 +132,7 @@ class InterpreterHandle { ...@@ -116,7 +132,7 @@ class InterpreterHandle {
default: default:
UNREACHABLE(); UNREACHABLE();
} }
} while (state != WasmInterpreter::State::FINISHED); }
// Copy back the return value // Copy back the return value
DCHECK_GE(kV8MaxWasmFunctionReturns, sig->return_count()); DCHECK_GE(kV8MaxWasmFunctionReturns, sig->return_count());
...@@ -141,6 +157,33 @@ class InterpreterHandle { ...@@ -141,6 +157,33 @@ class InterpreterHandle {
} }
} }
WasmInterpreter::State ContinueExecution(WasmInterpreter::Thread* thread) {
switch (next_step_action_) {
case StepNone:
return thread->Run();
case StepIn:
return thread->Step();
case StepOut:
thread->AddBreakFlags(WasmInterpreter::BreakFlag::AfterReturn);
return thread->Step();
case StepNext: {
int stack_depth = thread->GetFrameCount();
if (stack_depth == last_step_stack_depth_) return thread->Step();
thread->AddBreakFlags(stack_depth > last_step_stack_depth_
? WasmInterpreter::BreakFlag::AfterReturn
: WasmInterpreter::BreakFlag::AfterCall);
return thread->Run();
}
case StepFrame:
thread->AddBreakFlags(WasmInterpreter::BreakFlag::AfterCall |
WasmInterpreter::BreakFlag::AfterReturn);
return thread->Run();
default:
UNREACHABLE();
return WasmInterpreter::STOPPED;
}
}
Handle<WasmInstanceObject> GetInstanceObject() { Handle<WasmInstanceObject> GetInstanceObject() {
StackTraceFrameIterator it(isolate_); StackTraceFrameIterator it(isolate_);
WasmInterpreterEntryFrame* frame = WasmInterpreterEntryFrame* frame =
...@@ -150,7 +193,7 @@ class InterpreterHandle { ...@@ -150,7 +193,7 @@ class InterpreterHandle {
return instance_obj; return instance_obj;
} }
void NotifyDebugEventListeners() { void NotifyDebugEventListeners(WasmInterpreter::Thread* thread) {
// Enter the debugger. // Enter the debugger.
DebugScope debug_scope(isolate_->debug()); DebugScope debug_scope(isolate_->debug());
if (debug_scope.failed()) return; if (debug_scope.failed()) return;
...@@ -158,27 +201,47 @@ class InterpreterHandle { ...@@ -158,27 +201,47 @@ class InterpreterHandle {
// Postpone interrupt during breakpoint processing. // Postpone interrupt during breakpoint processing.
PostponeInterruptsScope postpone(isolate_); PostponeInterruptsScope postpone(isolate_);
// If we are paused on a breakpoint, clear all stepping and notify the // Check whether we hit a breakpoint.
// listeners.
Handle<WasmCompiledModule> compiled_module(
GetInstanceObject()->compiled_module(), isolate_);
int position = GetTopPosition(compiled_module);
MaybeHandle<FixedArray> hit_breakpoints;
if (isolate_->debug()->break_points_active()) { if (isolate_->debug()->break_points_active()) {
hit_breakpoints = compiled_module->CheckBreakPoints(position); Handle<WasmCompiledModule> compiled_module(
GetInstanceObject()->compiled_module(), isolate_);
int position = GetTopPosition(compiled_module);
Handle<FixedArray> breakpoints;
if (compiled_module->CheckBreakPoints(position).ToHandle(&breakpoints)) {
// We hit one or several breakpoints. Clear stepping, notify the
// listeners and return.
ClearStepping();
Handle<Object> hit_breakpoints_js =
isolate_->factory()->NewJSArrayWithElements(breakpoints);
isolate_->debug()->OnDebugBreak(hit_breakpoints_js);
return;
}
} }
// If we hit a breakpoint, pass a JSArray with all breakpoints, otherwise // We did not hit a breakpoint, so maybe this pause is related to stepping.
// pass undefined. bool hit_step = false;
Handle<Object> hit_breakpoints_js; switch (next_step_action_) {
if (hit_breakpoints.is_null()) { case StepNone:
hit_breakpoints_js = isolate_->factory()->undefined_value(); break;
} else { case StepIn:
hit_breakpoints_js = isolate_->factory()->NewJSArrayWithElements( hit_step = true;
hit_breakpoints.ToHandleChecked()); break;
case StepOut:
hit_step = thread->GetFrameCount() < last_step_stack_depth_;
break;
case StepNext: {
hit_step = thread->GetFrameCount() == last_step_stack_depth_;
break;
}
case StepFrame:
hit_step = thread->GetFrameCount() != last_step_stack_depth_;
break;
default:
UNREACHABLE();
} }
if (!hit_step) return;
isolate_->debug()->OnDebugBreak(hit_breakpoints_js); ClearStepping();
isolate_->debug()->OnDebugBreak(isolate_->factory()->undefined_value());
} }
int GetTopPosition(Handle<WasmCompiledModule> compiled_module) { int GetTopPosition(Handle<WasmCompiledModule> compiled_module) {
...@@ -352,6 +415,10 @@ void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info, ...@@ -352,6 +415,10 @@ void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info,
EnsureRedirectToInterpreter(isolate, debug_info, func_index); EnsureRedirectToInterpreter(isolate, debug_info, func_index);
} }
void WasmDebugInfo::PrepareStep(StepAction step_action) {
GetInterpreterHandle(this)->PrepareStep(step_action);
}
void WasmDebugInfo::RunInterpreter(int func_index, uint8_t* arg_buffer) { void WasmDebugInfo::RunInterpreter(int func_index, uint8_t* arg_buffer) {
DCHECK_LE(0, func_index); DCHECK_LE(0, func_index);
GetInterpreterHandle(this)->Execute(static_cast<uint32_t>(func_index), GetInterpreterHandle(this)->Execute(static_cast<uint32_t>(func_index),
......
...@@ -925,11 +925,7 @@ class ThreadImpl { ...@@ -925,11 +925,7 @@ class ThreadImpl {
instance_(instance), instance_(instance),
stack_(zone), stack_(zone),
frames_(zone), frames_(zone),
blocks_(zone), blocks_(zone) {}
state_(WasmInterpreter::STOPPED),
break_pc_(kInvalidPc),
trap_reason_(kTrapCount),
possible_nondeterminism_(false) {}
//========================================================================== //==========================================================================
// Implementation of public interface for WasmInterpreter::Thread. // Implementation of public interface for WasmInterpreter::Thread.
...@@ -1014,6 +1010,10 @@ class ThreadImpl { ...@@ -1014,6 +1010,10 @@ class ThreadImpl {
bool PossibleNondeterminism() { return possible_nondeterminism_; } bool PossibleNondeterminism() { return possible_nondeterminism_; }
void AddBreakFlags(uint8_t flags) { break_flags_ |= flags; }
void ClearBreakFlags() { break_flags_ = WasmInterpreter::BreakFlag::None; }
private: private:
// Entries on the stack of functions being evaluated. // Entries on the stack of functions being evaluated.
struct Frame { struct Frame {
...@@ -1040,10 +1040,11 @@ class ThreadImpl { ...@@ -1040,10 +1040,11 @@ class ThreadImpl {
ZoneVector<WasmVal> stack_; ZoneVector<WasmVal> stack_;
ZoneVector<Frame> frames_; ZoneVector<Frame> frames_;
ZoneVector<Block> blocks_; ZoneVector<Block> blocks_;
WasmInterpreter::State state_; WasmInterpreter::State state_ = WasmInterpreter::STOPPED;
pc_t break_pc_; pc_t break_pc_ = kInvalidPc;
TrapReason trap_reason_; TrapReason trap_reason_ = kTrapCount;
bool possible_nondeterminism_; bool possible_nondeterminism_ = false;
uint8_t break_flags_ = 0; // a combination of WasmInterpreter::BreakFlag
CodeMap* codemap() { return codemap_; } CodeMap* codemap() { return codemap_; }
WasmInstance* instance() { return instance_; } WasmInstance* instance() { return instance_; }
...@@ -1178,12 +1179,9 @@ class ThreadImpl { ...@@ -1178,12 +1179,9 @@ class ThreadImpl {
void Execute(InterpreterCode* code, pc_t pc, int max) { void Execute(InterpreterCode* code, pc_t pc, int max) {
Decoder decoder(code->start, code->end); Decoder decoder(code->start, code->end);
pc_t limit = code->end - code->start; pc_t limit = code->end - code->start;
while (true) { while (--max >= 0) {
if (max-- <= 0) { #define PAUSE_IF_BREAK_FLAG(flag) \
// Maximum number of instructions reached. if (V8_UNLIKELY(break_flags_ & WasmInterpreter::BreakFlag::flag)) max = 0;
state_ = WasmInterpreter::PAUSED;
return CommitPc(pc);
}
if (pc >= limit) { if (pc >= limit) {
// Fell off end of code; do an implicit return. // Fell off end of code; do an implicit return.
...@@ -1191,6 +1189,7 @@ class ThreadImpl { ...@@ -1191,6 +1189,7 @@ class ThreadImpl {
if (!DoReturn(&code, &pc, &limit, code->function->sig->return_count())) if (!DoReturn(&code, &pc, &limit, code->function->sig->return_count()))
return; return;
decoder.Reset(code->start, code->end); decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterReturn);
continue; continue;
} }
...@@ -1198,7 +1197,7 @@ class ThreadImpl { ...@@ -1198,7 +1197,7 @@ class ThreadImpl {
int len = 1; int len = 1;
byte opcode = code->start[pc]; byte opcode = code->start[pc];
byte orig = opcode; byte orig = opcode;
if (opcode == kInternalBreakpoint) { if (V8_UNLIKELY(opcode == kInternalBreakpoint)) {
orig = code->orig_start[pc]; orig = code->orig_start[pc];
if (SkipBreakpoint(code, pc)) { if (SkipBreakpoint(code, pc)) {
// skip breakpoint by switching on original code. // skip breakpoint by switching on original code.
...@@ -1300,6 +1299,7 @@ class ThreadImpl { ...@@ -1300,6 +1299,7 @@ class ThreadImpl {
size_t arity = code->function->sig->return_count(); size_t arity = code->function->sig->return_count();
if (!DoReturn(&code, &pc, &limit, arity)) return; if (!DoReturn(&code, &pc, &limit, arity)) return;
decoder.Reset(code->start, code->end); decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterReturn);
continue; continue;
} }
case kExprUnreachable: { case kExprUnreachable: {
...@@ -1365,6 +1365,7 @@ class ThreadImpl { ...@@ -1365,6 +1365,7 @@ class ThreadImpl {
DoCall(target, &pc, pc + 1 + operand.length, &limit); DoCall(target, &pc, pc + 1 + operand.length, &limit);
code = target; code = target;
decoder.Reset(code->start, code->end); decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterCall);
continue; continue;
} }
case kExprCallIndirect: { case kExprCallIndirect: {
...@@ -1391,6 +1392,7 @@ class ThreadImpl { ...@@ -1391,6 +1392,7 @@ class ThreadImpl {
DoCall(target, &pc, pc + 1 + operand.length, &limit); DoCall(target, &pc, pc + 1 + operand.length, &limit);
code = target; code = target;
decoder.Reset(code->start, code->end); decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterCall);
continue; continue;
} }
case kExprGetGlobal: { case kExprGetGlobal: {
...@@ -1636,7 +1638,8 @@ class ThreadImpl { ...@@ -1636,7 +1638,8 @@ class ThreadImpl {
pc += len; pc += len;
} }
UNREACHABLE(); // above decoding loop should run forever. state_ = WasmInterpreter::PAUSED;
CommitPc(pc);
} }
WasmVal Pop() { WasmVal Pop() {
...@@ -1676,6 +1679,7 @@ class ThreadImpl { ...@@ -1676,6 +1679,7 @@ class ThreadImpl {
} }
void TraceValueStack() { void TraceValueStack() {
#ifdef DEBUG
Frame* top = frames_.size() > 0 ? &frames_.back() : nullptr; Frame* top = frames_.size() > 0 ? &frames_.back() : nullptr;
sp_t sp = top ? top->sp : 0; sp_t sp = top ? top->sp : 0;
sp_t plimit = top ? top->plimit() : 0; sp_t plimit = top ? top->plimit() : 0;
...@@ -1711,6 +1715,7 @@ class ThreadImpl { ...@@ -1711,6 +1715,7 @@ class ThreadImpl {
} }
} }
} }
#endif // DEBUG
} }
}; };
...@@ -1770,6 +1775,12 @@ WasmVal WasmInterpreter::Thread::GetReturnValue(int index) { ...@@ -1770,6 +1775,12 @@ WasmVal WasmInterpreter::Thread::GetReturnValue(int index) {
bool WasmInterpreter::Thread::PossibleNondeterminism() { bool WasmInterpreter::Thread::PossibleNondeterminism() {
return ToImpl(this)->PossibleNondeterminism(); return ToImpl(this)->PossibleNondeterminism();
} }
void WasmInterpreter::Thread::AddBreakFlags(uint8_t flags) {
ToImpl(this)->AddBreakFlags(flags);
}
void WasmInterpreter::Thread::ClearBreakFlags() {
ToImpl(this)->ClearBreakFlags();
}
//============================================================================ //============================================================================
// The implementation details of the interpreter. // The implementation details of the interpreter.
......
...@@ -120,6 +120,13 @@ class V8_EXPORT_PRIVATE WasmInterpreter { ...@@ -120,6 +120,13 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// +------------- Finish -------------> FINISHED // +------------- Finish -------------> FINISHED
enum State { STOPPED, RUNNING, PAUSED, FINISHED, TRAPPED }; enum State { STOPPED, RUNNING, PAUSED, FINISHED, TRAPPED };
// Tells a thread to pause after certain instructions.
enum BreakFlag : uint8_t {
None = 0,
AfterReturn = 1 << 0,
AfterCall = 1 << 1
};
// Representation of a thread in the interpreter. // Representation of a thread in the interpreter.
class V8_EXPORT_PRIVATE Thread { class V8_EXPORT_PRIVATE Thread {
// Don't instante Threads; they will be allocated as ThreadImpl in the // Don't instante Threads; they will be allocated as ThreadImpl in the
...@@ -150,6 +157,9 @@ class V8_EXPORT_PRIVATE WasmInterpreter { ...@@ -150,6 +157,9 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// TODO(wasm): Implement this once we support multiple threads. // TODO(wasm): Implement this once we support multiple threads.
// bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled); // bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled);
// bool GetBreakpoint(const WasmFunction* function, int pc); // bool GetBreakpoint(const WasmFunction* function, int pc);
void AddBreakFlags(uint8_t flags);
void ClearBreakFlags();
}; };
WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator); WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator);
......
...@@ -423,6 +423,8 @@ class WasmDebugInfo : public FixedArray { ...@@ -423,6 +423,8 @@ class WasmDebugInfo : public FixedArray {
static void SetBreakpoint(Handle<WasmDebugInfo>, int func_index, int offset); static void SetBreakpoint(Handle<WasmDebugInfo>, int func_index, int offset);
void PrepareStep(StepAction);
void RunInterpreter(int func_index, uint8_t* arg_buffer); void RunInterpreter(int func_index, uint8_t* arg_buffer);
// Get the stack of the wasm interpreter as pairs of <function index, byte // Get the stack of the wasm interpreter as pairs of <function index, byte
......
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "src/debug/debug-interface.h" #include "src/debug/debug-interface.h"
#include "src/frames-inl.h"
#include "src/property-descriptor.h" #include "src/property-descriptor.h"
#include "src/utils.h"
#include "src/wasm/wasm-macro-gen.h" #include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-objects.h" #include "src/wasm/wasm-objects.h"
...@@ -53,12 +55,31 @@ void CheckLocationsFail(WasmCompiledModule *compiled_module, ...@@ -53,12 +55,31 @@ void CheckLocationsFail(WasmCompiledModule *compiled_module,
class BreakHandler { class BreakHandler {
public: public:
explicit BreakHandler(Isolate* isolate) : isolate_(isolate) { enum Action {
Continue = StepAction::LastStepAction + 1,
StepNext = StepAction::StepNext,
StepIn = StepAction::StepIn,
StepOut = StepAction::StepOut,
StepFrame = StepAction::StepFrame
};
struct BreakPoint {
int position;
Action action;
BreakPoint(int position, Action action)
: position(position), action(action) {}
};
explicit BreakHandler(Isolate* isolate,
std::initializer_list<BreakPoint> expected_breaks)
: isolate_(isolate), expected_breaks_(expected_breaks) {
current_handler = this; current_handler = this;
v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate), v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate),
DebugEventListener); DebugEventListener);
} }
~BreakHandler() { ~BreakHandler() {
// Check that all expected breakpoints have been hit.
CHECK_EQ(count_, expected_breaks_.size());
// BreakHandlers must be correctly stacked.
CHECK_EQ(this, current_handler); CHECK_EQ(this, current_handler);
current_handler = nullptr; current_handler = nullptr;
v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate_), v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate_),
...@@ -70,17 +91,40 @@ class BreakHandler { ...@@ -70,17 +91,40 @@ class BreakHandler {
private: private:
Isolate* isolate_; Isolate* isolate_;
int count_ = 0; int count_ = 0;
std::vector<BreakPoint> expected_breaks_;
static BreakHandler* current_handler; static BreakHandler* current_handler;
void HandleBreak() {
printf("Break #%d\n", count_);
CHECK_GT(expected_breaks_.size(), count_);
// Check the current position.
StackTraceFrameIterator frame_it(isolate_);
auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
CHECK_EQ(expected_breaks_[count_].position, summ.byte_offset());
Action next_action = expected_breaks_[count_].action;
switch (next_action) {
case Continue:
break;
case StepNext:
case StepIn:
case StepOut:
case StepFrame:
isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action));
break;
default:
UNREACHABLE();
}
++count_;
}
static void DebugEventListener(const v8::Debug::EventDetails& event_details) { static void DebugEventListener(const v8::Debug::EventDetails& event_details) {
if (event_details.GetEvent() != v8::DebugEvent::Break) return; if (event_details.GetEvent() != v8::DebugEvent::Break) return;
printf("break!\n");
CHECK_NOT_NULL(current_handler); CHECK_NOT_NULL(current_handler);
current_handler->count_ += 1; current_handler->HandleBreak();
// Don't run into an endless loop.
CHECK_GT(100, current_handler->count_);
} }
}; };
...@@ -131,7 +175,7 @@ void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset, ...@@ -131,7 +175,7 @@ void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset,
} // namespace } // namespace
TEST(CollectPossibleBreakpoints) { TEST(WasmCollectPossibleBreakpoints) {
WasmRunner<int> runner(kExecuteCompiled); WasmRunner<int> runner(kExecuteCompiled);
BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE)); BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE));
...@@ -156,7 +200,7 @@ TEST(CollectPossibleBreakpoints) { ...@@ -156,7 +200,7 @@ TEST(CollectPossibleBreakpoints) {
CheckLocationsFail(instance->compiled_module(), {0, 9}, {1, 0}); CheckLocationsFail(instance->compiled_module(), {0, 9}, {1, 0});
} }
TEST(TestSimpleBreak) { TEST(WasmSimpleBreak) {
WasmRunner<int> runner(kExecuteCompiled); WasmRunner<int> runner(kExecuteCompiled);
Isolate* isolate = runner.main_isolate(); Isolate* isolate = runner.main_isolate();
...@@ -166,8 +210,34 @@ TEST(TestSimpleBreak) { ...@@ -166,8 +210,34 @@ TEST(TestSimpleBreak) {
runner.module().WrapCode(runner.function_index()); runner.module().WrapCode(runner.function_index());
SetBreakpoint(runner, runner.function_index(), 4, 4); SetBreakpoint(runner, runner.function_index(), 4, 4);
BreakHandler count_breaks(isolate); BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});
CHECK_EQ(0, count_breaks.count());
Handle<Object> global(isolate->context()->global_object(), isolate);
MaybeHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
CHECK(!retval.is_null());
int result;
CHECK(retval.ToHandleChecked()->ToInt32(&result));
CHECK_EQ(14, result);
}
TEST(WasmSimpleStepping) {
WasmRunner<int> runner(kExecuteCompiled);
BUILD(runner, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
Isolate* isolate = runner.main_isolate();
Handle<JSFunction> main_fun_wrapper =
runner.module().WrapCode(runner.function_index());
// Set breakpoint at the first I32Const.
SetBreakpoint(runner, runner.function_index(), 1, 1);
BreakHandler count_breaks(isolate,
{
{1, BreakHandler::StepNext}, // I32Const
{3, BreakHandler::StepNext}, // I32Const
{5, BreakHandler::Continue} // I32Add
});
Handle<Object> global(isolate->context()->global_object(), isolate); Handle<Object> global(isolate->context()->global_object(), isolate);
MaybeHandle<Object> retval = MaybeHandle<Object> retval =
...@@ -176,6 +246,44 @@ TEST(TestSimpleBreak) { ...@@ -176,6 +246,44 @@ TEST(TestSimpleBreak) {
int result; int result;
CHECK(retval.ToHandleChecked()->ToInt32(&result)); CHECK(retval.ToHandleChecked()->ToInt32(&result));
CHECK_EQ(14, result); CHECK_EQ(14, result);
}
TEST(WasmStepInAndOut) {
WasmRunner<int, int> runner(kExecuteCompiled);
WasmFunctionCompiler& f2 = runner.NewFunction<void>();
f2.AllocateLocal(ValueType::kWord32);
CHECK_EQ(1, count_breaks.count()); // Call f2 via indirect call, because a direct call requires f2 to exist when
// we compile main, but we need to compile main first so that the order of
// functions in the code section matches the function indexes.
// return arg0
BUILD(runner, WASM_RETURN1(WASM_GET_LOCAL(0)));
// for (int i = 0; i < 10; ++i) { f2(i); }
BUILD(f2, WASM_LOOP(
WASM_BR_IF(0, WASM_BINOP(kExprI32GeU, WASM_GET_LOCAL(0),
WASM_I32V_1(10))),
WASM_SET_LOCAL(
0, WASM_BINOP(kExprI32Sub, WASM_GET_LOCAL(0), WASM_ONE)),
WASM_CALL_FUNCTION(runner.function_index(), WASM_GET_LOCAL(0)),
WASM_BR(1)));
Isolate* isolate = runner.main_isolate();
Handle<JSFunction> main_fun_wrapper =
runner.module().WrapCode(f2.function_index());
// Set first breakpoint on the GetLocal (offset 19) before the Call.
SetBreakpoint(runner, f2.function_index(), 19, 19);
BreakHandler count_breaks(isolate,
{
{19, BreakHandler::StepIn}, // GetLocal
{21, BreakHandler::StepIn}, // Call
{1, BreakHandler::StepOut}, // in f2
{23, BreakHandler::Continue} // After Call
});
Handle<Object> global(isolate->context()->global_object(), isolate);
CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr)
.is_null());
} }
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