test-wasm-breakpoints.cc 14.6 KB
Newer Older
1 2 3 4
// 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.

5
#include "src/codegen/assembler-inl.h"
6
#include "src/debug/debug-interface.h"
7
#include "src/execution/frames-inl.h"
8
#include "src/objects/property-descriptor.h"
9
#include "src/utils.h"
10
#include "src/wasm/wasm-objects-inl.h"
11 12 13 14 15

#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"
16
#include "test/common/wasm/wasm-macro-gen.h"
17

18 19 20
namespace v8 {
namespace internal {
namespace wasm {
21 22 23 24

namespace {

void CheckLocations(
25
    WasmModuleObject module_object, debug::Location start, debug::Location end,
26
    std::initializer_list<debug::Location> expected_locations_init) {
27
  std::vector<debug::BreakLocation> locations;
28
  bool success = module_object.GetPossibleBreakpoints(start, end, &locations);
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
  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");

  std::vector<debug::Location> expected_locations(expected_locations_init);
  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());
  }
}
47

48
void CheckLocationsFail(WasmModuleObject module_object, debug::Location start,
49
                        debug::Location end) {
50
  std::vector<debug::BreakLocation> locations;
51
  bool success = module_object.GetPossibleBreakpoints(start, end, &locations);
52 53 54
  CHECK(!success);
}

55
class BreakHandler : public debug::DebugDelegate {
56
 public:
57 58 59 60
  enum Action {
    Continue = StepAction::LastStepAction + 1,
    StepNext = StepAction::StepNext,
    StepIn = StepAction::StepIn,
61
    StepOut = StepAction::StepOut
62 63 64 65 66 67 68 69 70 71 72
  };
  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) {
73
    v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
74
  }
75
  ~BreakHandler() override {
76 77
    // Check that all expected breakpoints have been hit.
    CHECK_EQ(count_, expected_breaks_.size());
78 79
    v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
                                nullptr);
80 81 82 83 84 85 86
  }

  int count() const { return count_; }

 private:
  Isolate* isolate_;
  int count_ = 0;
87
  std::vector<BreakPoint> expected_breaks_;
88

89
  void BreakProgramRequested(v8::Local<v8::Context> paused_context,
90
                             const std::vector<int>&) override {
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    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:
        isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action));
        break;
      default:
        UNREACHABLE();
    }
    ++count_;
  }
113 114 115 116 117 118
};


void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset,
                   int expected_set_byte_offset = -1) {
  int func_offset =
119
      runner.builder().GetFunctionAt(function_index)->code.offset();
120 121
  int code_offset = func_offset + byte_offset;
  if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset;
122
  Handle<WasmInstanceObject> instance = runner.builder().instance_object();
123 124
  Handle<WasmModuleObject> module_object(instance->module_object(),
                                         runner.main_isolate());
125 126 127 128
  static int break_index = 0;
  Handle<BreakPoint> break_point =
      runner.main_isolate()->factory()->NewBreakPoint(
          break_index++, runner.main_isolate()->factory()->empty_string());
129 130
  CHECK(WasmModuleObject::SetBreakPoint(module_object, &code_offset,
                                        break_point));
131 132 133 134 135 136 137 138 139
  int set_byte_offset = code_offset - func_offset;
  CHECK_EQ(expected_set_byte_offset, set_byte_offset);
  // Also set breakpoint on the debug info of the instance directly, since the
  // instance chain is not setup properly in tests.
  Handle<WasmDebugInfo> debug_info =
      WasmInstanceObject::GetOrCreateDebugInfo(instance);
  WasmDebugInfo::SetBreakpoint(debug_info, function_index, set_byte_offset);
}

140 141
// Wrapper with operator<<.
struct WasmValWrapper {
142
  WasmValue val;
143 144 145 146 147 148 149 150 151

  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) {
152
  switch (wrapper.val.type()) {
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
    case kWasmI32:
      out << "i32: " << wrapper.val.to<int32_t>();
      break;
    case kWasmI64:
      out << "i64: " << wrapper.val.to<int64_t>();
      break;
    case kWasmF32:
      out << "f32: " << wrapper.val.to<float>();
      break;
    case kWasmF64:
      out << "f64: " << wrapper.val.to<double>();
      break;
    default:
      UNIMPLEMENTED();
  }
  return out;
}
#endif

class CollectValuesBreakHandler : public debug::DebugDelegate {
 public:
  struct BreakpointValues {
175 176
    std::vector<WasmValue> locals;
    std::vector<WasmValue> stack;
177 178 179 180 181 182 183
  };

  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);
  }
184
  ~CollectValuesBreakHandler() override {
185 186 187 188 189 190 191 192 193 194
    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,
195
                             const std::vector<int>&) override {
196 197 198 199 200 201 202 203 204 205 206 207
    printf("Break #%d\n", count_);
    CHECK_GT(expected_values_.size(), count_);
    auto& expected = expected_values_[count_];
    ++count_;

    HandleScope handles(isolate_);

    StackTraceFrameIterator frame_it(isolate_);
    auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
    Handle<WasmInstanceObject> instance = summ.wasm_instance();

    auto frame =
208
        instance->debug_info().GetInterpretedFrame(frame_it.frame()->fp(), 0);
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    CHECK_EQ(expected.locals.size(), frame->GetLocalCount());
    for (int i = 0; i < frame->GetLocalCount(); ++i) {
      CHECK_EQ(WasmValWrapper{expected.locals[i]},
               WasmValWrapper{frame->GetLocalValue(i)});
    }

    CHECK_EQ(expected.stack.size(), frame->GetStackHeight());
    for (int i = 0; i < frame->GetStackHeight(); ++i) {
      CHECK_EQ(WasmValWrapper{expected.stack[i]},
               WasmValWrapper{frame->GetStackValue(i)});
    }

    isolate_->debug()->PrepareStep(StepAction::StepIn);
  }
};

225
// Special template to explicitly cast to WasmValue.
226
template <typename Arg>
227 228
WasmValue MakeWasmVal(Arg arg) {
  return WasmValue(arg);
229 230 231
}
// Translate long to i64 (ambiguous otherwise).
template <>
232 233
WasmValue MakeWasmVal(long arg) {  // NOLINT: allow long parameter
  return WasmValue(static_cast<int64_t>(arg));
234 235 236
}

template <typename... Args>
237 238 239
std::vector<WasmValue> wasmVec(Args... args) {
  std::array<WasmValue, sizeof...(args)> arr{{MakeWasmVal(args)...}};
  return std::vector<WasmValue>{arr.begin(), arr.end()};
240 241
}

242 243
}  // namespace

244
WASM_COMPILED_EXEC_TEST(WasmCollectPossibleBreakpoints) {
245
  WasmRunner<int> runner(execution_tier);
246 247 248

  BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE));

249
  WasmInstanceObject instance = *runner.builder().instance_object();
250
  WasmModuleObject module_object = instance.module_object();
251

252
  std::vector<debug::Location> locations;
253
  // Check all locations for function 0.
254
  CheckLocations(module_object, {0, 0}, {1, 0},
255
                 {{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}});
256
  // Check a range ending at an instruction.
257
  CheckLocations(module_object, {0, 2}, {0, 4}, {{0, 2}});
258
  // Check a range ending one behind an instruction.
259
  CheckLocations(module_object, {0, 2}, {0, 5}, {{0, 2}, {0, 4}});
260
  // Check a range starting at an instruction.
261
  CheckLocations(module_object, {0, 7}, {0, 8}, {{0, 7}});
262
  // Check from an instruction to beginning of next function.
263
  CheckLocations(module_object, {0, 7}, {1, 0}, {{0, 7}});
264 265
  // Check from end of one function (no valid instruction position) to beginning
  // of next function. Must be empty, but not fail.
266
  CheckLocations(module_object, {0, 8}, {1, 0}, {});
267
  // Check from one after the end of the function. Must fail.
268
  CheckLocationsFail(module_object, {0, 9}, {1, 0});
269
}
270

271
WASM_COMPILED_EXEC_TEST(WasmSimpleBreak) {
272
  WasmRunner<int> runner(execution_tier);
273 274 275 276 277
  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 =
278
      runner.builder().WrapCode(runner.function_index());
279 280
  SetBreakpoint(runner, runner.function_index(), 4, 4);

281 282
  BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});

283
  Handle<Object> global(isolate->context().global_object(), isolate);
284 285 286 287 288 289 290 291
  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);
}

292
WASM_COMPILED_EXEC_TEST(WasmSimpleStepping) {
293
  WasmRunner<int> runner(execution_tier);
294 295 296 297
  BUILD(runner, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));

  Isolate* isolate = runner.main_isolate();
  Handle<JSFunction> main_fun_wrapper =
298
      runner.builder().WrapCode(runner.function_index());
299 300 301 302 303 304 305 306 307 308

  // 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
                            });
309

310
  Handle<Object> global(isolate->context().global_object(), isolate);
311 312 313 314 315 316
  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);
317 318
}

319
WASM_COMPILED_EXEC_TEST(WasmStepInAndOut) {
320
  WasmRunner<int, int> runner(execution_tier);
321
  WasmFunctionCompiler& f2 = runner.NewFunction<void>();
322
  f2.AllocateLocal(kWasmI32);
323

324 325 326 327 328 329 330 331 332 333 334 335 336
  // 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)),
337
                WASM_DROP, WASM_BR(1)));
338 339 340

  Isolate* isolate = runner.main_isolate();
  Handle<JSFunction> main_fun_wrapper =
341
      runner.builder().WrapCode(f2.function_index());
342 343 344 345 346 347 348 349 350 351 352 353

  // 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
                            });

354
  Handle<Object> global(isolate->context().global_object(), isolate);
355 356
  CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr)
             .is_null());
357
}
358

359
WASM_COMPILED_EXEC_TEST(WasmGetLocalsAndStack) {
360
  WasmRunner<void, int> runner(execution_tier);
361 362 363
  runner.AllocateLocal(kWasmI64);
  runner.AllocateLocal(kWasmF32);
  runner.AllocateLocal(kWasmF64);
364 365 366 367 368 369 370 371 372 373 374 375

  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 =
376
      runner.builder().WrapCode(runner.function_index());
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398

  // 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
      });

399
  Handle<Object> global(isolate->context().global_object(), isolate);
400 401 402
  Handle<Object> args[]{handle(Smi::FromInt(7), isolate)};
  CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 1, args).is_null());
}
403 404 405 406

}  // namespace wasm
}  // namespace internal
}  // namespace v8