Commit 96774fa5 authored by titzer's avatar titzer Committed by Commit bot

[wasm] Add more tests for interpreter breakpoints.

R=binji@chromium.org,ahaas@chromium.org
BUG=

Review-Url: https://codereview.chromium.org/2014373003
Cr-Commit-Position: refs/heads/master@{#36577}
parent eff24bef
...@@ -927,6 +927,7 @@ class ThreadImpl : public WasmInterpreter::Thread { ...@@ -927,6 +927,7 @@ class ThreadImpl : public WasmInterpreter::Thread {
stack_(zone), stack_(zone),
frames_(zone), frames_(zone),
state_(WasmInterpreter::STOPPED), state_(WasmInterpreter::STOPPED),
break_pc_(kInvalidPc),
trap_reason_(kTrapCount) {} trap_reason_(kTrapCount) {}
virtual ~ThreadImpl() {} virtual ~ThreadImpl() {}
...@@ -945,12 +946,13 @@ class ThreadImpl : public WasmInterpreter::Thread { ...@@ -945,12 +946,13 @@ class ThreadImpl : public WasmInterpreter::Thread {
stack_.push_back(args[i]); stack_.push_back(args[i]);
} }
frames_.back().ret_pc = InitLocals(code); frames_.back().ret_pc = InitLocals(code);
TRACE(" => push func#%u @%zu\n", code->function->func_index, TRACE(" => PushFrame(#%u @%zu)\n", code->function->func_index,
frames_.back().ret_pc); frames_.back().ret_pc);
} }
virtual WasmInterpreter::State Run() { virtual WasmInterpreter::State Run() {
do { do {
TRACE(" => Run()\n");
if (state_ == WasmInterpreter::STOPPED || if (state_ == WasmInterpreter::STOPPED ||
state_ == WasmInterpreter::PAUSED) { state_ == WasmInterpreter::PAUSED) {
state_ = WasmInterpreter::RUNNING; state_ = WasmInterpreter::RUNNING;
...@@ -961,8 +963,13 @@ class ThreadImpl : public WasmInterpreter::Thread { ...@@ -961,8 +963,13 @@ class ThreadImpl : public WasmInterpreter::Thread {
} }
virtual WasmInterpreter::State Step() { virtual WasmInterpreter::State Step() {
UNIMPLEMENTED(); TRACE(" => Step()\n");
return WasmInterpreter::STOPPED; if (state_ == WasmInterpreter::STOPPED ||
state_ == WasmInterpreter::PAUSED) {
state_ = WasmInterpreter::RUNNING;
Execute(frames_.back().code, frames_.back().ret_pc, 1);
}
return state_;
} }
virtual void Pause() { UNIMPLEMENTED(); } virtual void Pause() { UNIMPLEMENTED(); }
...@@ -994,6 +1001,8 @@ class ThreadImpl : public WasmInterpreter::Thread { ...@@ -994,6 +1001,8 @@ class ThreadImpl : public WasmInterpreter::Thread {
return stack_[0]; return stack_[0];
} }
virtual pc_t GetBreakpointPc() { return break_pc_; }
bool Terminated() { bool Terminated() {
return state_ == WasmInterpreter::TRAPPED || return state_ == WasmInterpreter::TRAPPED ||
state_ == WasmInterpreter::FINISHED; state_ == WasmInterpreter::FINISHED;
...@@ -1018,6 +1027,7 @@ class ThreadImpl : public WasmInterpreter::Thread { ...@@ -1018,6 +1027,7 @@ class ThreadImpl : public WasmInterpreter::Thread {
ZoneVector<WasmVal> stack_; ZoneVector<WasmVal> stack_;
ZoneVector<Frame> frames_; ZoneVector<Frame> frames_;
WasmInterpreter::State state_; WasmInterpreter::State state_;
pc_t break_pc_;
TrapReason trap_reason_; TrapReason trap_reason_;
CodeMap* codemap() { return codemap_; } CodeMap* codemap() { return codemap_; }
...@@ -1077,8 +1087,10 @@ class ThreadImpl : public WasmInterpreter::Thread { ...@@ -1077,8 +1087,10 @@ class ThreadImpl : public WasmInterpreter::Thread {
} }
bool SkipBreakpoint(InterpreterCode* code, pc_t pc) { bool SkipBreakpoint(InterpreterCode* code, pc_t pc) {
// TODO(titzer): skip a breakpoint if we are resuming from it, or it if (pc == break_pc_) {
// is set for another thread only. break_pc_ = kInvalidPc;
return true;
}
return false; return false;
} }
...@@ -1165,17 +1177,22 @@ class ThreadImpl : public WasmInterpreter::Thread { ...@@ -1165,17 +1177,22 @@ class ThreadImpl : public WasmInterpreter::Thread {
continue; continue;
} }
const char* skip = ""; const char* skip = " ";
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 (opcode == kInternalBreakpoint) {
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.
orig = code->orig_start[pc]; skip = "[skip] ";
skip = "[skip] ";
} else { } else {
state_ = WasmInterpreter::PAUSED; state_ = WasmInterpreter::PAUSED;
TRACE("@%-3zu: [break] %-24s:", pc,
WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(orig)));
TraceValueStack();
TRACE("\n");
break_pc_ = pc;
return CommitPc(pc); return CommitPc(pc);
} }
} }
...@@ -1684,13 +1701,13 @@ void WasmInterpreter::Run() { internals_->threads_[0].Run(); } ...@@ -1684,13 +1701,13 @@ void WasmInterpreter::Run() { internals_->threads_[0].Run(); }
void WasmInterpreter::Pause() { internals_->threads_[0].Pause(); } void WasmInterpreter::Pause() { internals_->threads_[0].Pause(); }
bool WasmInterpreter::SetBreakpoint(const WasmFunction* function, int pc, bool WasmInterpreter::SetBreakpoint(const WasmFunction* function, pc_t pc,
bool enabled) { bool enabled) {
InterpreterCode* code = internals_->codemap_.FindCode(function); InterpreterCode* code = internals_->codemap_.FindCode(function);
if (!code) return false; if (!code) return false;
int size = static_cast<int>(code->end - code->start); size_t size = static_cast<size_t>(code->end - code->start);
// Check bounds for {pc}. // Check bounds for {pc}.
if (pc < 0 || pc >= size) return false; if (pc < code->locals.decls_encoded_size || pc >= size) return false;
// Make a copy of the code before enabling a breakpoint. // Make a copy of the code before enabling a breakpoint.
if (enabled && code->orig_start == code->start) { if (enabled && code->orig_start == code->start) {
code->start = reinterpret_cast<byte*>(zone_.New(size)); code->start = reinterpret_cast<byte*>(zone_.New(size));
...@@ -1706,12 +1723,12 @@ bool WasmInterpreter::SetBreakpoint(const WasmFunction* function, int pc, ...@@ -1706,12 +1723,12 @@ bool WasmInterpreter::SetBreakpoint(const WasmFunction* function, int pc,
return prev; return prev;
} }
bool WasmInterpreter::GetBreakpoint(const WasmFunction* function, int pc) { bool WasmInterpreter::GetBreakpoint(const WasmFunction* function, pc_t pc) {
InterpreterCode* code = internals_->codemap_.FindCode(function); InterpreterCode* code = internals_->codemap_.FindCode(function);
if (!code) return false; if (!code) return false;
int size = static_cast<int>(code->end - code->start); size_t size = static_cast<size_t>(code->end - code->start);
// Check bounds for {pc}. // Check bounds for {pc}.
if (pc < 0 || pc >= size) return false; if (pc < code->locals.decls_encoded_size || pc >= size) return false;
// Check if a breakpoint is present at that place in the code. // Check if a breakpoint is present at that place in the code.
return code->start[pc] == kInternalBreakpoint; return code->start[pc] == kInternalBreakpoint;
} }
......
...@@ -26,6 +26,8 @@ typedef size_t sp_t; ...@@ -26,6 +26,8 @@ typedef size_t sp_t;
typedef int32_t pcdiff_t; typedef int32_t pcdiff_t;
typedef uint32_t spdiff_t; typedef uint32_t spdiff_t;
const pc_t kInvalidPc = 0x80000000;
// Visible for testing. A {ControlTransfer} helps the interpreter figure out // Visible for testing. A {ControlTransfer} helps the interpreter figure out
// the target program counter and stack manipulations for a branch. // the target program counter and stack manipulations for a branch.
struct ControlTransfer { struct ControlTransfer {
...@@ -126,6 +128,7 @@ class WasmInterpreter { ...@@ -126,6 +128,7 @@ class WasmInterpreter {
virtual ~Thread() {} virtual ~Thread() {}
// Stack inspection and modification. // Stack inspection and modification.
virtual pc_t GetBreakpointPc() = 0;
virtual int GetFrameCount() = 0; virtual int GetFrameCount() = 0;
virtual const WasmFrame* GetFrame(int index) = 0; virtual const WasmFrame* GetFrame(int index) = 0;
virtual WasmFrame* GetMutableFrame(int index) = 0; virtual WasmFrame* GetMutableFrame(int index) = 0;
...@@ -148,10 +151,10 @@ class WasmInterpreter { ...@@ -148,10 +151,10 @@ class WasmInterpreter {
// Set a breakpoint at {pc} in {function} to be {enabled}. Returns the // Set a breakpoint at {pc} in {function} to be {enabled}. Returns the
// previous state of the breakpoint at {pc}. // previous state of the breakpoint at {pc}.
bool SetBreakpoint(const WasmFunction* function, int pc, bool enabled); bool SetBreakpoint(const WasmFunction* function, pc_t pc, bool enabled);
// Gets the current state of the breakpoint at {function}. // Gets the current state of the breakpoint at {function}.
bool GetBreakpoint(const WasmFunction* function, int pc); bool GetBreakpoint(const WasmFunction* function, pc_t pc);
// Enable or disable tracing for {function}. Return the previous state. // Enable or disable tracing for {function}. Return the previous state.
bool SetTracing(const WasmFunction* function, bool enabled); bool SetTracing(const WasmFunction* function, bool enabled);
......
...@@ -135,17 +135,153 @@ TEST(Run_Wasm_nested_ifs_i) { ...@@ -135,17 +135,153 @@ TEST(Run_Wasm_nested_ifs_i) {
CHECK_EQ(14, r.Call(0, 0)); CHECK_EQ(14, r.Call(0, 0));
} }
TEST(Step_I32Add) { // Make tests more robust by not hard-coding offsets of various operations.
WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Int32(), // The {Find} method finds the offsets for the given bytecodes, returning
MachineType::Int32()); // the offsets in an array.
BUILD(r, WASM_I32_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))); SmartArrayPointer<int> Find(byte* code, size_t code_size, int n, ...) {
va_list vl;
va_start(vl, n);
SmartArrayPointer<int> offsets(new int[n]);
for (int i = 0; i < n; i++) {
offsets[i] = -1;
}
int pos = 0;
WasmOpcode current = static_cast<WasmOpcode>(va_arg(vl, int));
for (size_t i = 0; i < code_size; i++) {
if (code[i] == current) {
offsets[pos++] = static_cast<int>(i);
if (pos == n) break;
current = static_cast<WasmOpcode>(va_arg(vl, int));
}
}
va_end(vl);
return offsets;
}
TEST(Breakpoint_I32Add) {
static const int kLocalsDeclSize = 1;
static const int kNumBreakpoints = 3;
byte code[] = {WASM_I32_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))};
SmartArrayPointer<int> offsets =
Find(code, sizeof(code), kNumBreakpoints, kExprGetLocal, kExprGetLocal,
kExprI32Add);
WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Uint32(),
MachineType::Uint32());
r.Build(code, code + arraysize(code));
WasmInterpreter* interpreter = r.interpreter(); WasmInterpreter* interpreter = r.interpreter();
interpreter->SetBreakpoint(r.function(), 0, true); WasmInterpreter::Thread& thread = interpreter->GetThread(0);
for (int i = 0; i < kNumBreakpoints; i++) {
interpreter->SetBreakpoint(r.function(), kLocalsDeclSize + offsets[i],
true);
}
FOR_UINT32_INPUTS(a) {
for (uint32_t b = 11; b < 3000000000u; b += 1000000000u) {
thread.Reset();
WasmVal args[] = {WasmVal(*a), WasmVal(b)};
thread.PushFrame(r.function(), args);
for (int i = 0; i < kNumBreakpoints; i++) {
thread.Run(); // run to next breakpoint
// Check the thread stopped at the right pc.
CHECK_EQ(WasmInterpreter::PAUSED, thread.state());
CHECK_EQ(kLocalsDeclSize + offsets[i], thread.GetBreakpointPc());
}
thread.Run(); // run to completion
// Check the thread finished with the right value.
CHECK_EQ(WasmInterpreter::FINISHED, thread.state());
uint32_t expected = (*a) + (b);
CHECK_EQ(expected, thread.GetReturnValue().to<uint32_t>());
}
}
}
TEST(Step_I32Mul) {
static const int kTraceLength = 4;
byte code[] = {WASM_I32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))};
r.Call(1, 1); WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Uint32(),
interpreter->Run(); MachineType::Uint32());
CHECK_EQ(2, interpreter->GetThread(0).GetReturnValue().to<int32_t>());
r.Build(code, code + arraysize(code));
WasmInterpreter* interpreter = r.interpreter();
WasmInterpreter::Thread& thread = interpreter->GetThread(0);
FOR_UINT32_INPUTS(a) {
for (uint32_t b = 33; b < 3000000000u; b += 1000000000u) {
thread.Reset();
WasmVal args[] = {WasmVal(*a), WasmVal(b)};
thread.PushFrame(r.function(), args);
// Run instructions one by one.
for (int i = 0; i < kTraceLength - 1; i++) {
thread.Step();
// Check the thread stopped.
CHECK_EQ(WasmInterpreter::PAUSED, thread.state());
}
// Run last instruction.
thread.Step();
// Check the thread finished with the right value.
CHECK_EQ(WasmInterpreter::FINISHED, thread.state());
uint32_t expected = (*a) * (b);
CHECK_EQ(expected, thread.GetReturnValue().to<uint32_t>());
}
}
}
TEST(Breakpoint_I32And_disable) {
static const int kLocalsDeclSize = 1;
static const int kNumBreakpoints = 1;
byte code[] = {WASM_I32_AND(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))};
SmartArrayPointer<int> offsets =
Find(code, sizeof(code), kNumBreakpoints, kExprI32And);
WasmRunner<int32_t> r(kExecuteInterpreted, MachineType::Uint32(),
MachineType::Uint32());
r.Build(code, code + arraysize(code));
WasmInterpreter* interpreter = r.interpreter();
WasmInterpreter::Thread& thread = interpreter->GetThread(0);
FOR_UINT32_INPUTS(a) {
for (uint32_t b = 11; b < 3000000000u; b += 1000000000u) {
// Run with and without breakpoints.
for (int do_break = 0; do_break < 2; do_break++) {
interpreter->SetBreakpoint(r.function(), kLocalsDeclSize + offsets[0],
do_break);
thread.Reset();
WasmVal args[] = {WasmVal(*a), WasmVal(b)};
thread.PushFrame(r.function(), args);
if (do_break) {
thread.Run(); // run to next breakpoint
// Check the thread stopped at the right pc.
CHECK_EQ(WasmInterpreter::PAUSED, thread.state());
CHECK_EQ(kLocalsDeclSize + offsets[0], thread.GetBreakpointPc());
}
thread.Run(); // run to completion
// Check the thread finished with the right value.
CHECK_EQ(WasmInterpreter::FINISHED, thread.state());
uint32_t expected = (*a) & (b);
CHECK_EQ(expected, thread.GetReturnValue().to<uint32_t>());
}
}
}
} }
} // namespace wasm } // namespace wasm
......
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