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(
sig->return_count() == 0 ? 0 : 1 << ElementSizeLog2Of(sig->GetReturn(0));
// Get a stack slot for the arguments.
Node* arg_buffer = graph()->NewNode(jsgraph()->machine()->StackSlot(
std::max(args_size_bytes, return_size_bytes)));
Node* arg_buffer = args_size_bytes == 0 && return_size_bytes == 0
? jsgraph()->IntPtrConstant(0)
: graph()->NewNode(jsgraph()->machine()->StackSlot(
std::max(args_size_bytes, return_size_bytes)));
// Now store all our arguments to the buffer.
int param_index = 0;
......
......@@ -984,24 +984,36 @@ void Debug::PrepareStep(StepAction step_action) {
// If there is no JavaScript stack don't do anything.
if (frame_id == StackFrame::NO_ID) return;
JavaScriptFrameIterator frames_it(isolate_, frame_id);
JavaScriptFrame* frame = frames_it.frame();
StackTraceFrameIterator frames_it(isolate_, frame_id);
StandardFrame* frame = frames_it.frame();
feature_tracker()->Track(DebugFeatureTracker::kStepping);
thread_local_.last_step_action_ = step_action;
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
// be the case when calling unknown function and having the debugger stopped
// in an unhandled exception.
if (!frame->function()->IsJSFunction()) {
if (!js_frame->function()->IsJSFunction()) {
// Step out: Find the calling JavaScript frame and flood it with
// breakpoints.
frames_it.Advance();
// Fill the function to return to with one-shot break points.
JSFunction* function = frames_it.frame()->function();
FloodWithOneShot(Handle<JSFunction>(function));
JSFunction* function = JavaScriptFrame::cast(frames_it.frame())->function();
FloodWithOneShot(handle(function, isolate_));
return;
}
......@@ -1015,7 +1027,7 @@ void Debug::PrepareStep(StepAction step_action) {
}
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.
if (location.IsReturn()) step_action = StepOut;
......@@ -1037,22 +1049,27 @@ void Debug::PrepareStep(StepAction step_action) {
case StepOut:
// Advance to caller frame.
frames_it.Advance();
// Skip native and extension functions on the stack.
while (
!frames_it.done() &&
(!frames_it.frame()->function()->shared()->IsSubjectToDebugging() ||
IsBlackboxed(frames_it.frame()->function()->shared()))) {
// Find top-most function which is subject to debugging.
while (!frames_it.done()) {
StandardFrame* caller_frame = frames_it.frame();
if (caller_frame->is_wasm()) {
// 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
// deoptimized to include checks for step-in at call sites.
Deoptimizer::DeoptimizeFunction(frames_it.frame()->function());
Deoptimizer::DeoptimizeFunction(*js_caller_function);
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.
thread_local_.last_statement_position_ = kNoSourcePosition;
thread_local_.last_fp_ = 0;
......@@ -1062,9 +1079,11 @@ void Debug::PrepareStep(StepAction step_action) {
FloodWithOneShot(function);
break;
case StepIn:
// TODO(clemensh): Implement stepping from JS into WASM.
FloodWithOneShot(function);
break;
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
// to leave the current frame.
FloodWithOneShot(function, CALLS_AND_RETURNS);
......
......@@ -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() {
do {
iterator_.Advance();
......
......@@ -1462,8 +1462,6 @@ class JavaScriptFrameIterator BASE_EMBEDDED {
public:
inline explicit JavaScriptFrameIterator(Isolate* isolate);
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;
......@@ -1485,6 +1483,7 @@ class JavaScriptFrameIterator BASE_EMBEDDED {
class StackTraceFrameIterator BASE_EMBEDDED {
public:
explicit StackTraceFrameIterator(Isolate* isolate);
// Skip frames until the frame with the given id is reached.
StackTraceFrameIterator(Isolate* isolate, StackFrame::Id id);
bool done() const { return iterator_.done(); }
void Advance();
......
......@@ -751,8 +751,10 @@ RUNTIME_FUNCTION(Runtime_GetScopeCount) {
// Get the frame where the debugging is performed.
StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator it(isolate, id);
JavaScriptFrame* frame = it.frame();
StackTraceFrameIterator it(isolate, id);
StandardFrame* frame = it.frame();
if (it.frame()->is_wasm()) return 0;
FrameInspector frame_inspector(frame, 0, isolate);
// Count the visible scopes.
......@@ -786,8 +788,9 @@ RUNTIME_FUNCTION(Runtime_GetScopeDetails) {
// Get the frame where the debugging is performed.
StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id);
JavaScriptFrame* frame = frame_it.frame();
StackTraceFrameIterator frame_it(isolate, id);
// Wasm has no scopes, this must be javascript.
JavaScriptFrame* frame = JavaScriptFrame::cast(frame_it.frame());
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
// Find the requested scope.
......@@ -975,8 +978,9 @@ RUNTIME_FUNCTION(Runtime_SetScopeVariableValue) {
// Get the frame where the debugging is performed.
StackFrame::Id id = DebugFrameHelper::UnwrapFrameId(wrapped_id);
JavaScriptFrameIterator frame_it(isolate, id);
JavaScriptFrame* frame = frame_it.frame();
StackTraceFrameIterator frame_it(isolate, id);
// Wasm has no scopes, this must be javascript.
JavaScriptFrame* frame = JavaScriptFrame::cast(frame_it.frame());
FrameInspector frame_inspector(frame, inlined_jsframe_index, isolate);
ScopeIterator it(isolate, &frame_inspector);
......
......@@ -30,6 +30,8 @@ class InterpreterHandle {
WasmInstance instance_;
WasmInterpreter interpreter_;
Isolate* isolate_;
StepAction next_step_action_ = StepNone;
int last_step_stack_depth_ = 0;
public:
// Initialize in the right order, using helper methods to make this possible.
......@@ -63,6 +65,18 @@ class InterpreterHandle {
WasmInterpreter* interpreter() { return &interpreter_; }
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) {
DCHECK_GE(module()->functions.size(), func_index);
FunctionSig* sig = module()->functions[func_index].sig;
......@@ -96,15 +110,17 @@ class InterpreterHandle {
thread->state() == WasmInterpreter::FINISHED);
thread->Reset();
thread->PushFrame(&module()->functions[func_index], wasm_args.start());
WasmInterpreter::State state;
do {
state = thread->Run();
bool finished = false;
while (!finished) {
// TODO(clemensh): Add occasional StackChecks.
WasmInterpreter::State state = ContinueExecution(thread);
switch (state) {
case WasmInterpreter::State::PAUSED:
NotifyDebugEventListeners();
NotifyDebugEventListeners(thread);
break;
case WasmInterpreter::State::FINISHED:
// Perfect, just break the switch and exit the loop.
finished = true;
break;
case WasmInterpreter::State::TRAPPED:
// TODO(clemensh): Generate appropriate JS exception.
......@@ -116,7 +132,7 @@ class InterpreterHandle {
default:
UNREACHABLE();
}
} while (state != WasmInterpreter::State::FINISHED);
}
// Copy back the return value
DCHECK_GE(kV8MaxWasmFunctionReturns, sig->return_count());
......@@ -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() {
StackTraceFrameIterator it(isolate_);
WasmInterpreterEntryFrame* frame =
......@@ -150,7 +193,7 @@ class InterpreterHandle {
return instance_obj;
}
void NotifyDebugEventListeners() {
void NotifyDebugEventListeners(WasmInterpreter::Thread* thread) {
// Enter the debugger.
DebugScope debug_scope(isolate_->debug());
if (debug_scope.failed()) return;
......@@ -158,27 +201,47 @@ class InterpreterHandle {
// Postpone interrupt during breakpoint processing.
PostponeInterruptsScope postpone(isolate_);
// If we are paused on a breakpoint, clear all stepping and notify the
// listeners.
Handle<WasmCompiledModule> compiled_module(
GetInstanceObject()->compiled_module(), isolate_);
int position = GetTopPosition(compiled_module);
MaybeHandle<FixedArray> hit_breakpoints;
// Check whether we hit a breakpoint.
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
// pass undefined.
Handle<Object> hit_breakpoints_js;
if (hit_breakpoints.is_null()) {
hit_breakpoints_js = isolate_->factory()->undefined_value();
} else {
hit_breakpoints_js = isolate_->factory()->NewJSArrayWithElements(
hit_breakpoints.ToHandleChecked());
// We did not hit a breakpoint, so maybe this pause is related to stepping.
bool hit_step = false;
switch (next_step_action_) {
case StepNone:
break;
case StepIn:
hit_step = true;
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();
}
isolate_->debug()->OnDebugBreak(hit_breakpoints_js);
if (!hit_step) return;
ClearStepping();
isolate_->debug()->OnDebugBreak(isolate_->factory()->undefined_value());
}
int GetTopPosition(Handle<WasmCompiledModule> compiled_module) {
......@@ -352,6 +415,10 @@ void WasmDebugInfo::SetBreakpoint(Handle<WasmDebugInfo> debug_info,
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) {
DCHECK_LE(0, func_index);
GetInterpreterHandle(this)->Execute(static_cast<uint32_t>(func_index),
......
......@@ -925,11 +925,7 @@ class ThreadImpl {
instance_(instance),
stack_(zone),
frames_(zone),
blocks_(zone),
state_(WasmInterpreter::STOPPED),
break_pc_(kInvalidPc),
trap_reason_(kTrapCount),
possible_nondeterminism_(false) {}
blocks_(zone) {}
//==========================================================================
// Implementation of public interface for WasmInterpreter::Thread.
......@@ -1014,6 +1010,10 @@ class ThreadImpl {
bool PossibleNondeterminism() { return possible_nondeterminism_; }
void AddBreakFlags(uint8_t flags) { break_flags_ |= flags; }
void ClearBreakFlags() { break_flags_ = WasmInterpreter::BreakFlag::None; }
private:
// Entries on the stack of functions being evaluated.
struct Frame {
......@@ -1040,10 +1040,11 @@ class ThreadImpl {
ZoneVector<WasmVal> stack_;
ZoneVector<Frame> frames_;
ZoneVector<Block> blocks_;
WasmInterpreter::State state_;
pc_t break_pc_;
TrapReason trap_reason_;
bool possible_nondeterminism_;
WasmInterpreter::State state_ = WasmInterpreter::STOPPED;
pc_t break_pc_ = kInvalidPc;
TrapReason trap_reason_ = kTrapCount;
bool possible_nondeterminism_ = false;
uint8_t break_flags_ = 0; // a combination of WasmInterpreter::BreakFlag
CodeMap* codemap() { return codemap_; }
WasmInstance* instance() { return instance_; }
......@@ -1178,12 +1179,9 @@ class ThreadImpl {
void Execute(InterpreterCode* code, pc_t pc, int max) {
Decoder decoder(code->start, code->end);
pc_t limit = code->end - code->start;
while (true) {
if (max-- <= 0) {
// Maximum number of instructions reached.
state_ = WasmInterpreter::PAUSED;
return CommitPc(pc);
}
while (--max >= 0) {
#define PAUSE_IF_BREAK_FLAG(flag) \
if (V8_UNLIKELY(break_flags_ & WasmInterpreter::BreakFlag::flag)) max = 0;
if (pc >= limit) {
// Fell off end of code; do an implicit return.
......@@ -1191,6 +1189,7 @@ class ThreadImpl {
if (!DoReturn(&code, &pc, &limit, code->function->sig->return_count()))
return;
decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterReturn);
continue;
}
......@@ -1198,7 +1197,7 @@ class ThreadImpl {
int len = 1;
byte opcode = code->start[pc];
byte orig = opcode;
if (opcode == kInternalBreakpoint) {
if (V8_UNLIKELY(opcode == kInternalBreakpoint)) {
orig = code->orig_start[pc];
if (SkipBreakpoint(code, pc)) {
// skip breakpoint by switching on original code.
......@@ -1300,6 +1299,7 @@ class ThreadImpl {
size_t arity = code->function->sig->return_count();
if (!DoReturn(&code, &pc, &limit, arity)) return;
decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterReturn);
continue;
}
case kExprUnreachable: {
......@@ -1365,6 +1365,7 @@ class ThreadImpl {
DoCall(target, &pc, pc + 1 + operand.length, &limit);
code = target;
decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterCall);
continue;
}
case kExprCallIndirect: {
......@@ -1391,6 +1392,7 @@ class ThreadImpl {
DoCall(target, &pc, pc + 1 + operand.length, &limit);
code = target;
decoder.Reset(code->start, code->end);
PAUSE_IF_BREAK_FLAG(AfterCall);
continue;
}
case kExprGetGlobal: {
......@@ -1636,7 +1638,8 @@ class ThreadImpl {
pc += len;
}
UNREACHABLE(); // above decoding loop should run forever.
state_ = WasmInterpreter::PAUSED;
CommitPc(pc);
}
WasmVal Pop() {
......@@ -1676,6 +1679,7 @@ class ThreadImpl {
}
void TraceValueStack() {
#ifdef DEBUG
Frame* top = frames_.size() > 0 ? &frames_.back() : nullptr;
sp_t sp = top ? top->sp : 0;
sp_t plimit = top ? top->plimit() : 0;
......@@ -1711,6 +1715,7 @@ class ThreadImpl {
}
}
}
#endif // DEBUG
}
};
......@@ -1770,6 +1775,12 @@ WasmVal WasmInterpreter::Thread::GetReturnValue(int index) {
bool WasmInterpreter::Thread::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.
......
......@@ -120,6 +120,13 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// +------------- Finish -------------> FINISHED
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.
class V8_EXPORT_PRIVATE Thread {
// Don't instante Threads; they will be allocated as ThreadImpl in the
......@@ -150,6 +157,9 @@ class V8_EXPORT_PRIVATE WasmInterpreter {
// TODO(wasm): Implement this once we support multiple threads.
// bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled);
// bool GetBreakpoint(const WasmFunction* function, int pc);
void AddBreakFlags(uint8_t flags);
void ClearBreakFlags();
};
WasmInterpreter(const ModuleBytesEnv& env, AccountingAllocator* allocator);
......
......@@ -423,6 +423,8 @@ class WasmDebugInfo : public FixedArray {
static void SetBreakpoint(Handle<WasmDebugInfo>, int func_index, int offset);
void PrepareStep(StepAction);
void RunInterpreter(int func_index, uint8_t* arg_buffer);
// Get the stack of the wasm interpreter as pairs of <function index, byte
......
......@@ -3,7 +3,9 @@
// found in the LICENSE file.
#include "src/debug/debug-interface.h"
#include "src/frames-inl.h"
#include "src/property-descriptor.h"
#include "src/utils.h"
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-objects.h"
......@@ -53,12 +55,31 @@ void CheckLocationsFail(WasmCompiledModule *compiled_module,
class BreakHandler {
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;
v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate),
DebugEventListener);
}
~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);
current_handler = nullptr;
v8::Debug::SetDebugEventListener(reinterpret_cast<v8::Isolate*>(isolate_),
......@@ -70,17 +91,40 @@ class BreakHandler {
private:
Isolate* isolate_;
int count_ = 0;
std::vector<BreakPoint> expected_breaks_;
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) {
if (event_details.GetEvent() != v8::DebugEvent::Break) return;
printf("break!\n");
CHECK_NOT_NULL(current_handler);
current_handler->count_ += 1;
// Don't run into an endless loop.
CHECK_GT(100, current_handler->count_);
current_handler->HandleBreak();
}
};
......@@ -131,7 +175,7 @@ void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset,
} // namespace
TEST(CollectPossibleBreakpoints) {
TEST(WasmCollectPossibleBreakpoints) {
WasmRunner<int> runner(kExecuteCompiled);
BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE));
......@@ -156,7 +200,7 @@ TEST(CollectPossibleBreakpoints) {
CheckLocationsFail(instance->compiled_module(), {0, 9}, {1, 0});
}
TEST(TestSimpleBreak) {
TEST(WasmSimpleBreak) {
WasmRunner<int> runner(kExecuteCompiled);
Isolate* isolate = runner.main_isolate();
......@@ -166,8 +210,34 @@ TEST(TestSimpleBreak) {
runner.module().WrapCode(runner.function_index());
SetBreakpoint(runner, runner.function_index(), 4, 4);
BreakHandler count_breaks(isolate);
CHECK_EQ(0, count_breaks.count());
BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});
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);
MaybeHandle<Object> retval =
......@@ -176,6 +246,44 @@ TEST(TestSimpleBreak) {
int result;
CHECK(retval.ToHandleChecked()->ToInt32(&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