v8/test/cctest/wasm/test-wasm-breakpoints.cc
Clemens Backes a7a201b260 [wasm][debug][cleanup] Remove redundant Isolate parameter
The Isolate is only used to access the wasm engine, and the accounting
allocating. The latter is also linked directly from the wasm engine, and
the engine is linked from the native module, to which the DebugInfoImpl
already has access.
Hence, this CL removes the redundant Isolate pointers, and just accesses
the engine and the allocator via the NativeModule.

R=thibaudm@chromium.org

Change-Id: Ib51cee2d166443a34e22fa02e8ad1549328aaa7f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2214827
Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67966}
2020-05-26 12:55:40 +00:00

547 lines
20 KiB
C++

// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/codegen/assembler-inl.h"
#include "src/debug/debug-interface.h"
#include "src/execution/frames-inl.h"
#include "src/objects/property-descriptor.h"
#include "src/utils/utils.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/value-helper.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
debug::Location TranslateLocation(WasmRunnerBase* runner,
const debug::Location& loc) {
// Convert locations from {func_index, offset_in_func} to
// {0, offset_in_module}.
int func_index = loc.GetLineNumber();
int func_offset = runner->builder().GetFunctionAt(func_index)->code.offset();
int offset = loc.GetColumnNumber() + func_offset;
return {0, offset};
}
void CheckLocations(
WasmRunnerBase* runner, NativeModule* native_module, debug::Location start,
debug::Location end,
std::initializer_list<debug::Location> expected_locations_init) {
std::vector<debug::BreakLocation> locations;
std::vector<debug::Location> expected_locations;
for (auto loc : expected_locations_init) {
expected_locations.push_back(TranslateLocation(runner, loc));
}
bool success = WasmScript::GetPossibleBreakpoints(
native_module, TranslateLocation(runner, start),
TranslateLocation(runner, end), &locations);
CHECK(success);
printf("got %d locations: ", static_cast<int>(locations.size()));
for (size_t i = 0, e = locations.size(); i != e; ++i) {
printf("%s<%d,%d>", i == 0 ? "" : ", ", locations[i].GetLineNumber(),
locations[i].GetColumnNumber());
}
printf("\n");
CHECK_EQ(expected_locations.size(), locations.size());
for (size_t i = 0, e = locations.size(); i != e; ++i) {
CHECK_EQ(expected_locations[i].GetLineNumber(),
locations[i].GetLineNumber());
CHECK_EQ(expected_locations[i].GetColumnNumber(),
locations[i].GetColumnNumber());
}
}
void CheckLocationsFail(WasmRunnerBase* runner, NativeModule* native_module,
debug::Location start, debug::Location end) {
std::vector<debug::BreakLocation> locations;
bool success = WasmScript::GetPossibleBreakpoints(
native_module, TranslateLocation(runner, start),
TranslateLocation(runner, end), &locations);
CHECK(!success);
}
class BreakHandler : public debug::DebugDelegate {
public:
enum Action {
Continue = StepAction::LastStepAction + 1,
StepNext = StepAction::StepNext,
StepIn = StepAction::StepIn,
StepOut = StepAction::StepOut
};
struct BreakPoint {
int position;
Action action;
std::function<void(void)> pre_action;
BreakPoint(int position, Action action)
: position(position), action(action), pre_action([]() {}) {}
BreakPoint(int position, Action action,
std::function<void(void)> pre_action)
: position(position), action(action), pre_action(pre_action) {}
};
explicit BreakHandler(Isolate* isolate,
std::initializer_list<BreakPoint> expected_breaks)
: isolate_(isolate), expected_breaks_(expected_breaks) {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
}
~BreakHandler() override {
// Check that all expected breakpoints have been hit.
CHECK_EQ(count_, expected_breaks_.size());
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
nullptr);
}
int count() const { return count_; }
private:
Isolate* isolate_;
int count_ = 0;
std::vector<BreakPoint> expected_breaks_;
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<int>&) override {
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()).AsWasm();
CHECK_EQ(expected_breaks_[count_].position, summ.byte_offset());
expected_breaks_[count_].pre_action();
Action next_action = expected_breaks_[count_].action;
switch (next_action) {
case Continue:
break;
case StepNext:
case StepIn:
case StepOut:
isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action));
break;
default:
UNREACHABLE();
}
++count_;
}
};
Handle<BreakPoint> SetBreakpoint(WasmRunnerBase* runner, int function_index,
int byte_offset,
int expected_set_byte_offset = -1) {
runner->TierDown();
int func_offset =
runner->builder().GetFunctionAt(function_index)->code.offset();
int code_offset = func_offset + byte_offset;
if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset;
Handle<WasmInstanceObject> instance = runner->builder().instance_object();
Handle<Script> script(instance->module_object().script(),
runner->main_isolate());
static int break_index = 0;
Handle<BreakPoint> break_point =
runner->main_isolate()->factory()->NewBreakPoint(
break_index++, runner->main_isolate()->factory()->empty_string());
CHECK(WasmScript::SetBreakPoint(script, &code_offset, break_point));
return break_point;
}
void ClearBreakpoint(WasmRunnerBase* runner, int function_index,
int byte_offset, Handle<BreakPoint> break_point) {
int func_offset =
runner->builder().GetFunctionAt(function_index)->code.offset();
int code_offset = func_offset + byte_offset;
Handle<WasmInstanceObject> instance = runner->builder().instance_object();
Handle<Script> script(instance->module_object().script(),
runner->main_isolate());
CHECK(WasmScript::ClearBreakPoint(script, code_offset, break_point));
}
// Wrapper with operator<<.
struct WasmValWrapper {
WasmValue val;
bool operator==(const WasmValWrapper& other) const {
return val == other.val;
}
};
// Only needed in debug builds. Avoid unused warning otherwise.
#ifdef DEBUG
std::ostream& operator<<(std::ostream& out, const WasmValWrapper& wrapper) {
switch (wrapper.val.type().kind()) {
case ValueType::kI32:
out << "i32: " << wrapper.val.to<int32_t>();
break;
case ValueType::kI64:
out << "i64: " << wrapper.val.to<int64_t>();
break;
case ValueType::kF32:
out << "f32: " << wrapper.val.to<float>();
break;
case ValueType::kF64:
out << "f64: " << wrapper.val.to<double>();
break;
default:
UNIMPLEMENTED();
}
return out;
}
#endif
class CollectValuesBreakHandler : public debug::DebugDelegate {
public:
struct BreakpointValues {
std::vector<WasmValue> locals;
std::vector<WasmValue> stack;
};
explicit CollectValuesBreakHandler(
Isolate* isolate, std::initializer_list<BreakpointValues> expected_values)
: isolate_(isolate), expected_values_(expected_values) {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
}
~CollectValuesBreakHandler() override {
v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
nullptr);
}
private:
Isolate* isolate_;
int count_ = 0;
std::vector<BreakpointValues> expected_values_;
void BreakProgramRequested(v8::Local<v8::Context> paused_context,
const std::vector<int>&) override {
printf("Break #%d\n", count_);
CHECK_GT(expected_values_.size(), count_);
auto& expected = expected_values_[count_];
++count_;
HandleScope handles(isolate_);
StackTraceFrameIterator frame_it(isolate_);
WasmFrame* frame = WasmFrame::cast(frame_it.frame());
DebugInfo* debug_info = frame->native_module()->GetDebugInfo();
int num_locals = debug_info->GetNumLocals(frame->pc());
CHECK_EQ(expected.locals.size(), num_locals);
for (int i = 0; i < num_locals; ++i) {
WasmValue local_value = debug_info->GetLocalValue(
i, frame->pc(), frame->fp(), frame->callee_fp());
CHECK_EQ(WasmValWrapper{expected.locals[i]}, WasmValWrapper{local_value});
}
int stack_depth = debug_info->GetStackDepth(frame->pc());
CHECK_EQ(expected.stack.size(), stack_depth);
for (int i = 0; i < stack_depth; ++i) {
WasmValue stack_value = debug_info->GetStackValue(
i, frame->pc(), frame->fp(), frame->callee_fp());
CHECK_EQ(WasmValWrapper{expected.stack[i]}, WasmValWrapper{stack_value});
}
isolate_->debug()->PrepareStep(StepAction::StepIn);
}
};
// Special template to explicitly cast to WasmValue.
template <typename Arg>
WasmValue MakeWasmVal(Arg arg) {
return WasmValue(arg);
}
// Translate long to i64 (ambiguous otherwise).
template <>
WasmValue MakeWasmVal(long arg) { // NOLINT: allow long parameter
return WasmValue(static_cast<int64_t>(arg));
}
template <typename... Args>
std::vector<WasmValue> wasmVec(Args... args) {
std::array<WasmValue, sizeof...(args)> arr{{MakeWasmVal(args)...}};
return std::vector<WasmValue>{arr.begin(), arr.end()};
}
int GetIntReturnValue(MaybeHandle<Object> retval) {
CHECK(!retval.is_null());
int result;
CHECK(retval.ToHandleChecked()->ToInt32(&result));
return result;
}
} // namespace
WASM_COMPILED_EXEC_TEST(WasmCollectPossibleBreakpoints) {
WasmRunner<int> runner(execution_tier);
BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE));
WasmInstanceObject instance = *runner.builder().instance_object();
NativeModule* native_module = instance.module_object().native_module();
std::vector<debug::Location> locations;
// Check all locations for function 0.
CheckLocations(&runner, native_module, {0, 0}, {0, 10},
{{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}});
// Check a range ending at an instruction.
CheckLocations(&runner, native_module, {0, 2}, {0, 4}, {{0, 2}});
// Check a range ending one behind an instruction.
CheckLocations(&runner, native_module, {0, 2}, {0, 5}, {{0, 2}, {0, 4}});
// Check a range starting at an instruction.
CheckLocations(&runner, native_module, {0, 7}, {0, 8}, {{0, 7}});
// Check from an instruction to beginning of next function.
CheckLocations(&runner, native_module, {0, 7}, {0, 10}, {{0, 7}});
// Check from end of one function (no valid instruction position) to beginning
// of next function. Must be empty, but not fail.
CheckLocations(&runner, native_module, {0, 8}, {0, 10}, {});
// Check from one after the end of the function. Must fail.
CheckLocationsFail(&runner, native_module, {0, 9}, {0, 10});
}
WASM_COMPILED_EXEC_TEST(WasmSimpleBreak) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
Handle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 4, 4);
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_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmNonBreakablePosition) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
BUILD(runner, WASM_RETURN1(WASM_I32V_2(1024)));
Handle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 2, 4);
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_EQ(1024, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmSimpleStepping) {
WasmRunner<int> runner(execution_tier);
BUILD(runner, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
Isolate* isolate = runner.main_isolate();
Handle<JSFunction> main_fun_wrapper =
runner.builder().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 =
Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmStepInAndOut) {
WasmRunner<int, int> runner(execution_tier);
runner.TierDown();
WasmFunctionCompiler& f2 = runner.NewFunction<void>();
f2.AllocateLocal(kWasmI32);
// 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_DROP, WASM_BR(1)));
Isolate* isolate = runner.main_isolate();
Handle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(f2.function_index());
// Set first breakpoint on the LocalGet (offset 19) before the Call.
SetBreakpoint(&runner, f2.function_index(), 19, 19);
BreakHandler count_breaks(isolate,
{
{19, BreakHandler::StepIn}, // LocalGet
{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());
}
WASM_COMPILED_EXEC_TEST(WasmGetLocalsAndStack) {
WasmRunner<void, int> runner(execution_tier);
runner.AllocateLocal(kWasmI64);
runner.AllocateLocal(kWasmF32);
runner.AllocateLocal(kWasmF64);
BUILD(runner,
// set [1] to 17
WASM_SET_LOCAL(1, WASM_I64V_1(17)),
// set [2] to <arg0> = 7
WASM_SET_LOCAL(2, WASM_F32_SCONVERT_I32(WASM_GET_LOCAL(0))),
// set [3] to <arg1>/2 = 8.5
WASM_SET_LOCAL(3, WASM_F64_DIV(WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(1)),
WASM_F64(2))));
Isolate* isolate = runner.main_isolate();
Handle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
// Set breakpoint at the first instruction (7 bytes for local decls: num
// entries + 3x<count, type>).
SetBreakpoint(&runner, runner.function_index(), 7, 7);
CollectValuesBreakHandler break_handler(
isolate,
{
// params + locals stack
{wasmVec(7, 0L, 0.f, 0.), wasmVec()}, // 0: i64.const[17]
{wasmVec(7, 0L, 0.f, 0.), wasmVec(17L)}, // 1: set_local[1]
{wasmVec(7, 17L, 0.f, 0.), wasmVec()}, // 2: get_local[0]
{wasmVec(7, 17L, 0.f, 0.), wasmVec(7)}, // 3: f32.convert_s
{wasmVec(7, 17L, 0.f, 0.), wasmVec(7.f)}, // 4: set_local[2]
{wasmVec(7, 17L, 7.f, 0.), wasmVec()}, // 5: get_local[1]
{wasmVec(7, 17L, 7.f, 0.), wasmVec(17L)}, // 6: f64.convert_s
{wasmVec(7, 17L, 7.f, 0.), wasmVec(17.)}, // 7: f64.const[2]
{wasmVec(7, 17L, 7.f, 0.), wasmVec(17., 2.)}, // 8: f64.div
{wasmVec(7, 17L, 7.f, 0.), wasmVec(8.5)}, // 9: set_local[3]
{wasmVec(7, 17L, 7.f, 8.5), wasmVec()}, // 10: end
});
Handle<Object> global(isolate->context().global_object(), isolate);
Handle<Object> args[]{handle(Smi::FromInt(7), isolate)};
CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 1, args).is_null());
}
WASM_COMPILED_EXEC_TEST(WasmRemoveBreakPoint) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
BUILD(runner, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP,
WASM_I32V_1(14));
Handle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 1, 1);
SetBreakpoint(&runner, runner.function_index(), 2, 2);
Handle<BreakPoint> to_delete =
SetBreakpoint(&runner, runner.function_index(), 3, 3);
SetBreakpoint(&runner, runner.function_index(), 4, 4);
BreakHandler count_breaks(isolate, {{1, BreakHandler::Continue},
{2, BreakHandler::Continue,
[&runner, &to_delete]() {
ClearBreakpoint(
&runner, runner.function_index(),
3, to_delete);
}},
{4, BreakHandler::Continue}});
Handle<Object> global(isolate->context().global_object(), isolate);
MaybeHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmRemoveLastBreakPoint) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
BUILD(runner, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP,
WASM_I32V_1(14));
Handle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
SetBreakpoint(&runner, runner.function_index(), 1, 1);
SetBreakpoint(&runner, runner.function_index(), 2, 2);
Handle<BreakPoint> to_delete =
SetBreakpoint(&runner, runner.function_index(), 3, 3);
BreakHandler count_breaks(
isolate, {{1, BreakHandler::Continue},
{2, BreakHandler::Continue, [&runner, &to_delete]() {
ClearBreakpoint(&runner, runner.function_index(), 3,
to_delete);
}}});
Handle<Object> global(isolate->context().global_object(), isolate);
MaybeHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
CHECK_EQ(14, GetIntReturnValue(retval));
}
WASM_COMPILED_EXEC_TEST(WasmRemoveAllBreakPoint) {
WasmRunner<int> runner(execution_tier);
Isolate* isolate = runner.main_isolate();
BUILD(runner, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP, WASM_NOP,
WASM_I32V_1(14));
Handle<JSFunction> main_fun_wrapper =
runner.builder().WrapCode(runner.function_index());
Handle<BreakPoint> bp1 =
SetBreakpoint(&runner, runner.function_index(), 1, 1);
Handle<BreakPoint> bp2 =
SetBreakpoint(&runner, runner.function_index(), 2, 2);
Handle<BreakPoint> bp3 =
SetBreakpoint(&runner, runner.function_index(), 3, 3);
BreakHandler count_breaks(
isolate, {{1, BreakHandler::Continue, [&runner, &bp1, &bp2, &bp3]() {
ClearBreakpoint(&runner, runner.function_index(), 1, bp1);
ClearBreakpoint(&runner, runner.function_index(), 3, bp3);
ClearBreakpoint(&runner, runner.function_index(), 2, bp2);
}}});
Handle<Object> global(isolate->context().global_object(), isolate);
MaybeHandle<Object> retval =
Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
CHECK_EQ(14, GetIntReturnValue(retval));
}
} // namespace wasm
} // namespace internal
} // namespace v8