Line data Source code
1 : // Copyright 2016 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include "src/assembler-inl.h"
6 : #include "src/debug/debug-interface.h"
7 : #include "src/frames-inl.h"
8 : #include "src/property-descriptor.h"
9 : #include "src/utils.h"
10 : #include "src/wasm/wasm-objects-inl.h"
11 :
12 : #include "test/cctest/cctest.h"
13 : #include "test/cctest/compiler/value-helper.h"
14 : #include "test/cctest/wasm/wasm-run-utils.h"
15 : #include "test/common/wasm/test-signatures.h"
16 : #include "test/common/wasm/wasm-macro-gen.h"
17 :
18 : namespace v8 {
19 : namespace internal {
20 : namespace wasm {
21 :
22 : namespace {
23 :
24 48 : 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 48 : bool success = module_object->GetPossibleBreakpoints(start, end, &locations);
29 48 : CHECK(success);
30 :
31 48 : printf("got %d locations: ", static_cast<int>(locations.size()));
32 208 : for (size_t i = 0, e = locations.size(); i != e; ++i) {
33 160 : printf("%s<%d,%d>", i == 0 ? "" : ", ", locations[i].GetLineNumber(),
34 : locations[i].GetColumnNumber());
35 : }
36 : printf("\n");
37 :
38 : std::vector<debug::Location> expected_locations(expected_locations_init);
39 48 : CHECK_EQ(expected_locations.size(), locations.size());
40 208 : for (size_t i = 0, e = locations.size(); i != e; ++i) {
41 160 : CHECK_EQ(expected_locations[i].GetLineNumber(),
42 : locations[i].GetLineNumber());
43 160 : CHECK_EQ(expected_locations[i].GetColumnNumber(),
44 : locations[i].GetColumnNumber());
45 : }
46 48 : }
47 :
48 8 : void CheckLocationsFail(WasmModuleObject module_object, debug::Location start,
49 : debug::Location end) {
50 : std::vector<debug::BreakLocation> locations;
51 8 : bool success = module_object->GetPossibleBreakpoints(start, end, &locations);
52 8 : CHECK(!success);
53 8 : }
54 :
55 : class BreakHandler : public debug::DebugDelegate {
56 : public:
57 : enum Action {
58 : Continue = StepAction::LastStepAction + 1,
59 : StepNext = StepAction::StepNext,
60 : StepIn = StepAction::StepIn,
61 : StepOut = StepAction::StepOut
62 : };
63 : struct BreakPoint {
64 : int position;
65 : Action action;
66 : BreakPoint(int position, Action action)
67 24 : : position(position), action(action) {}
68 : };
69 :
70 24 : explicit BreakHandler(Isolate* isolate,
71 : std::initializer_list<BreakPoint> expected_breaks)
72 24 : : isolate_(isolate), expected_breaks_(expected_breaks) {
73 24 : v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
74 24 : }
75 48 : ~BreakHandler() override {
76 : // Check that all expected breakpoints have been hit.
77 48 : CHECK_EQ(count_, expected_breaks_.size());
78 24 : v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
79 24 : nullptr);
80 24 : }
81 :
82 : int count() const { return count_; }
83 :
84 : private:
85 : Isolate* isolate_;
86 : int count_ = 0;
87 : std::vector<BreakPoint> expected_breaks_;
88 :
89 64 : void BreakProgramRequested(v8::Local<v8::Context> paused_context,
90 : const std::vector<int>&) override {
91 64 : printf("Break #%d\n", count_);
92 128 : CHECK_GT(expected_breaks_.size(), count_);
93 :
94 : // Check the current position.
95 64 : StackTraceFrameIterator frame_it(isolate_);
96 64 : auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
97 128 : CHECK_EQ(expected_breaks_[count_].position, summ.byte_offset());
98 :
99 64 : Action next_action = expected_breaks_[count_].action;
100 64 : switch (next_action) {
101 : case Continue:
102 : break;
103 : case StepNext:
104 : case StepIn:
105 : case StepOut:
106 40 : isolate_->debug()->PrepareStep(static_cast<StepAction>(next_action));
107 40 : break;
108 : default:
109 0 : UNREACHABLE();
110 : }
111 64 : ++count_;
112 64 : }
113 : };
114 :
115 :
116 32 : void SetBreakpoint(WasmRunnerBase& runner, int function_index, int byte_offset,
117 : int expected_set_byte_offset = -1) {
118 : int func_offset =
119 32 : runner.builder().GetFunctionAt(function_index)->code.offset();
120 32 : int code_offset = func_offset + byte_offset;
121 32 : if (expected_set_byte_offset == -1) expected_set_byte_offset = byte_offset;
122 : Handle<WasmInstanceObject> instance = runner.builder().instance_object();
123 : Handle<WasmModuleObject> module_object(instance->module_object(),
124 : runner.main_isolate());
125 : static int break_index = 0;
126 : Handle<BreakPoint> break_point =
127 : runner.main_isolate()->factory()->NewBreakPoint(
128 32 : break_index++, runner.main_isolate()->factory()->empty_string());
129 32 : CHECK(WasmModuleObject::SetBreakPoint(module_object, &code_offset,
130 : break_point));
131 32 : int set_byte_offset = code_offset - func_offset;
132 32 : CHECK_EQ(expected_set_byte_offset, set_byte_offset);
133 : // Also set breakpoint on the debug info of the instance directly, since the
134 : // instance chain is not setup properly in tests.
135 : Handle<WasmDebugInfo> debug_info =
136 32 : WasmInstanceObject::GetOrCreateDebugInfo(instance);
137 32 : WasmDebugInfo::SetBreakpoint(debug_info, function_index, set_byte_offset);
138 32 : }
139 :
140 : // Wrapper with operator<<.
141 : struct WasmValWrapper {
142 : WasmValue val;
143 :
144 : bool operator==(const WasmValWrapper& other) const {
145 : return val == other.val;
146 : }
147 : };
148 :
149 : // Only needed in debug builds. Avoid unused warning otherwise.
150 : #ifdef DEBUG
151 : std::ostream& operator<<(std::ostream& out, const WasmValWrapper& wrapper) {
152 : switch (wrapper.val.type()) {
153 : case kWasmI32:
154 : out << "i32: " << wrapper.val.to<int32_t>();
155 : break;
156 : case kWasmI64:
157 : out << "i64: " << wrapper.val.to<int64_t>();
158 : break;
159 : case kWasmF32:
160 : out << "f32: " << wrapper.val.to<float>();
161 : break;
162 : case kWasmF64:
163 : out << "f64: " << wrapper.val.to<double>();
164 : break;
165 : default:
166 : UNIMPLEMENTED();
167 : }
168 : return out;
169 : }
170 : #endif
171 :
172 : class CollectValuesBreakHandler : public debug::DebugDelegate {
173 : public:
174 440 : struct BreakpointValues {
175 : std::vector<WasmValue> locals;
176 : std::vector<WasmValue> stack;
177 : };
178 :
179 8 : explicit CollectValuesBreakHandler(
180 : Isolate* isolate, std::initializer_list<BreakpointValues> expected_values)
181 8 : : isolate_(isolate), expected_values_(expected_values) {
182 8 : v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_), this);
183 8 : }
184 24 : ~CollectValuesBreakHandler() override {
185 8 : v8::debug::SetDebugDelegate(reinterpret_cast<v8::Isolate*>(isolate_),
186 8 : nullptr);
187 8 : }
188 :
189 : private:
190 : Isolate* isolate_;
191 : int count_ = 0;
192 : std::vector<BreakpointValues> expected_values_;
193 :
194 88 : void BreakProgramRequested(v8::Local<v8::Context> paused_context,
195 : const std::vector<int>&) override {
196 88 : printf("Break #%d\n", count_);
197 176 : CHECK_GT(expected_values_.size(), count_);
198 88 : auto& expected = expected_values_[count_];
199 88 : ++count_;
200 :
201 88 : HandleScope handles(isolate_);
202 :
203 88 : StackTraceFrameIterator frame_it(isolate_);
204 88 : auto summ = FrameSummary::GetTop(frame_it.frame()).AsWasmInterpreted();
205 : Handle<WasmInstanceObject> instance = summ.wasm_instance();
206 :
207 : auto frame =
208 88 : instance->debug_info()->GetInterpretedFrame(frame_it.frame()->fp(), 0);
209 176 : CHECK_EQ(expected.locals.size(), frame->GetLocalCount());
210 792 : for (int i = 0; i < frame->GetLocalCount(); ++i) {
211 1056 : CHECK_EQ(WasmValWrapper{expected.locals[i]},
212 : WasmValWrapper{frame->GetLocalValue(i)});
213 : }
214 :
215 176 : CHECK_EQ(expected.stack.size(), frame->GetStackHeight());
216 216 : for (int i = 0; i < frame->GetStackHeight(); ++i) {
217 192 : CHECK_EQ(WasmValWrapper{expected.stack[i]},
218 : WasmValWrapper{frame->GetStackValue(i)});
219 : }
220 :
221 88 : isolate_->debug()->PrepareStep(StepAction::StepIn);
222 88 : }
223 : };
224 :
225 : // Special template to explicitly cast to WasmValue.
226 : template <typename Arg>
227 : WasmValue MakeWasmVal(Arg arg) {
228 : return WasmValue(arg);
229 : }
230 : // Translate long to i64 (ambiguous otherwise).
231 : template <>
232 : WasmValue MakeWasmVal(long arg) { // NOLINT: allow long parameter
233 : return WasmValue(static_cast<int64_t>(arg));
234 : }
235 :
236 : template <typename... Args>
237 96 : std::vector<WasmValue> wasmVec(Args... args) {
238 : std::array<WasmValue, sizeof...(args)> arr{{MakeWasmVal(args)...}};
239 96 : return std::vector<WasmValue>{arr.begin(), arr.end()};
240 : }
241 :
242 : } // namespace
243 :
244 26672 : WASM_COMPILED_EXEC_TEST(WasmCollectPossibleBreakpoints) {
245 16 : WasmRunner<int> runner(execution_tier);
246 :
247 8 : BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_ZERO, WASM_ONE));
248 :
249 : WasmInstanceObject instance = *runner.builder().instance_object();
250 8 : WasmModuleObject module_object = instance->module_object();
251 :
252 : std::vector<debug::Location> locations;
253 : // Check all locations for function 0.
254 16 : CheckLocations(module_object, {0, 0}, {1, 0},
255 24 : {{0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 7}});
256 : // Check a range ending at an instruction.
257 8 : CheckLocations(module_object, {0, 2}, {0, 4}, {{0, 2}});
258 : // Check a range ending one behind an instruction.
259 8 : CheckLocations(module_object, {0, 2}, {0, 5}, {{0, 2}, {0, 4}});
260 : // Check a range starting at an instruction.
261 8 : CheckLocations(module_object, {0, 7}, {0, 8}, {{0, 7}});
262 : // Check from an instruction to beginning of next function.
263 8 : CheckLocations(module_object, {0, 7}, {1, 0}, {{0, 7}});
264 : // Check from end of one function (no valid instruction position) to beginning
265 : // of next function. Must be empty, but not fail.
266 8 : CheckLocations(module_object, {0, 8}, {1, 0}, {});
267 : // Check from one after the end of the function. Must fail.
268 8 : CheckLocationsFail(module_object, {0, 9}, {1, 0});
269 8 : }
270 :
271 26672 : WASM_COMPILED_EXEC_TEST(WasmSimpleBreak) {
272 16 : WasmRunner<int> runner(execution_tier);
273 : Isolate* isolate = runner.main_isolate();
274 :
275 8 : BUILD(runner, WASM_NOP, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
276 :
277 : Handle<JSFunction> main_fun_wrapper =
278 8 : runner.builder().WrapCode(runner.function_index());
279 8 : SetBreakpoint(runner, runner.function_index(), 4, 4);
280 :
281 16 : BreakHandler count_breaks(isolate, {{4, BreakHandler::Continue}});
282 :
283 16 : Handle<Object> global(isolate->context()->global_object(), isolate);
284 : MaybeHandle<Object> retval =
285 8 : Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
286 8 : CHECK(!retval.is_null());
287 : int result;
288 8 : CHECK(retval.ToHandleChecked()->ToInt32(&result));
289 8 : CHECK_EQ(14, result);
290 8 : }
291 :
292 26672 : WASM_COMPILED_EXEC_TEST(WasmSimpleStepping) {
293 16 : WasmRunner<int> runner(execution_tier);
294 8 : BUILD(runner, WASM_I32_ADD(WASM_I32V_1(11), WASM_I32V_1(3)));
295 :
296 : Isolate* isolate = runner.main_isolate();
297 : Handle<JSFunction> main_fun_wrapper =
298 8 : runner.builder().WrapCode(runner.function_index());
299 :
300 : // Set breakpoint at the first I32Const.
301 8 : SetBreakpoint(runner, runner.function_index(), 1, 1);
302 :
303 : BreakHandler count_breaks(isolate,
304 : {
305 : {1, BreakHandler::StepNext}, // I32Const
306 : {3, BreakHandler::StepNext}, // I32Const
307 : {5, BreakHandler::Continue} // I32Add
308 16 : });
309 :
310 16 : Handle<Object> global(isolate->context()->global_object(), isolate);
311 : MaybeHandle<Object> retval =
312 8 : Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr);
313 8 : CHECK(!retval.is_null());
314 : int result;
315 8 : CHECK(retval.ToHandleChecked()->ToInt32(&result));
316 8 : CHECK_EQ(14, result);
317 8 : }
318 :
319 26672 : WASM_COMPILED_EXEC_TEST(WasmStepInAndOut) {
320 16 : WasmRunner<int, int> runner(execution_tier);
321 8 : WasmFunctionCompiler& f2 = runner.NewFunction<void>();
322 : f2.AllocateLocal(kWasmI32);
323 :
324 : // Call f2 via indirect call, because a direct call requires f2 to exist when
325 : // we compile main, but we need to compile main first so that the order of
326 : // functions in the code section matches the function indexes.
327 :
328 : // return arg0
329 8 : BUILD(runner, WASM_RETURN1(WASM_GET_LOCAL(0)));
330 : // for (int i = 0; i < 10; ++i) { f2(i); }
331 16 : BUILD(f2, WASM_LOOP(
332 : WASM_BR_IF(0, WASM_BINOP(kExprI32GeU, WASM_GET_LOCAL(0),
333 : WASM_I32V_1(10))),
334 : WASM_SET_LOCAL(
335 : 0, WASM_BINOP(kExprI32Sub, WASM_GET_LOCAL(0), WASM_ONE)),
336 : WASM_CALL_FUNCTION(runner.function_index(), WASM_GET_LOCAL(0)),
337 : WASM_DROP, WASM_BR(1)));
338 :
339 : Isolate* isolate = runner.main_isolate();
340 : Handle<JSFunction> main_fun_wrapper =
341 8 : runner.builder().WrapCode(f2.function_index());
342 :
343 : // Set first breakpoint on the GetLocal (offset 19) before the Call.
344 8 : SetBreakpoint(runner, f2.function_index(), 19, 19);
345 :
346 : BreakHandler count_breaks(isolate,
347 : {
348 : {19, BreakHandler::StepIn}, // GetLocal
349 : {21, BreakHandler::StepIn}, // Call
350 : {1, BreakHandler::StepOut}, // in f2
351 : {23, BreakHandler::Continue} // After Call
352 16 : });
353 :
354 16 : Handle<Object> global(isolate->context()->global_object(), isolate);
355 16 : CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 0, nullptr)
356 : .is_null());
357 8 : }
358 :
359 26672 : WASM_COMPILED_EXEC_TEST(WasmGetLocalsAndStack) {
360 16 : WasmRunner<void, int> runner(execution_tier);
361 : runner.AllocateLocal(kWasmI64);
362 : runner.AllocateLocal(kWasmF32);
363 : runner.AllocateLocal(kWasmF64);
364 :
365 8 : BUILD(runner,
366 : // set [1] to 17
367 : WASM_SET_LOCAL(1, WASM_I64V_1(17)),
368 : // set [2] to <arg0> = 7
369 : WASM_SET_LOCAL(2, WASM_F32_SCONVERT_I32(WASM_GET_LOCAL(0))),
370 : // set [3] to <arg1>/2 = 8.5
371 : WASM_SET_LOCAL(3, WASM_F64_DIV(WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(1)),
372 : WASM_F64(2))));
373 :
374 : Isolate* isolate = runner.main_isolate();
375 : Handle<JSFunction> main_fun_wrapper =
376 8 : runner.builder().WrapCode(runner.function_index());
377 :
378 : // Set breakpoint at the first instruction (7 bytes for local decls: num
379 : // entries + 3x<count, type>).
380 8 : SetBreakpoint(runner, runner.function_index(), 7, 7);
381 :
382 : CollectValuesBreakHandler break_handler(
383 : isolate,
384 : {
385 : // params + locals stack
386 : {wasmVec(7, 0L, 0.f, 0.), wasmVec()}, // 0: i64.const[17]
387 : {wasmVec(7, 0L, 0.f, 0.), wasmVec(17L)}, // 1: set_local[1]
388 : {wasmVec(7, 17L, 0.f, 0.), wasmVec()}, // 2: get_local[0]
389 : {wasmVec(7, 17L, 0.f, 0.), wasmVec(7)}, // 3: f32.convert_s
390 : {wasmVec(7, 17L, 0.f, 0.), wasmVec(7.f)}, // 4: set_local[2]
391 : {wasmVec(7, 17L, 7.f, 0.), wasmVec()}, // 5: get_local[1]
392 : {wasmVec(7, 17L, 7.f, 0.), wasmVec(17L)}, // 6: f64.convert_s
393 : {wasmVec(7, 17L, 7.f, 0.), wasmVec(17.)}, // 7: f64.const[2]
394 : {wasmVec(7, 17L, 7.f, 0.), wasmVec(17., 2.)}, // 8: f64.div
395 : {wasmVec(7, 17L, 7.f, 0.), wasmVec(8.5)}, // 9: set_local[3]
396 : {wasmVec(7, 17L, 7.f, 8.5), wasmVec()}, // 10: end
397 96 : });
398 :
399 16 : Handle<Object> global(isolate->context()->global_object(), isolate);
400 : Handle<Object> args[]{handle(Smi::FromInt(7), isolate)};
401 16 : CHECK(!Execution::Call(isolate, main_fun_wrapper, global, 1, args).is_null());
402 8 : }
403 :
404 : } // namespace wasm
405 : } // namespace internal
406 79968 : } // namespace v8
|