1 // Copyright 2014 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 #ifndef V8_CCTEST_COMPILER_CODEGEN_TESTER_H_
6 #define V8_CCTEST_COMPILER_CODEGEN_TESTER_H_
7
8 #include "src/compiler/instruction-selector.h"
9 #include "src/compiler/pipeline.h"
10 #include "src/compiler/raw-machine-assembler.h"
11 #include "src/simulator.h"
12 #include "test/cctest/compiler/call-tester.h"
13
14 namespace v8 {
15 namespace internal {
16 namespace compiler {
17
18 template <typename ReturnType>
19 class RawMachineAssemblerTester : public HandleAndZoneScope,
20 public CallHelper<ReturnType>,
21 public RawMachineAssembler {
22 public:
23 RawMachineAssemblerTester(MachineType p0 = MachineType::None(),
24 MachineType p1 = MachineType::None(),
25 MachineType p2 = MachineType::None(),
26 MachineType p3 = MachineType::None(),
27 MachineType p4 = MachineType::None())
HandleAndZoneScope()28 : HandleAndZoneScope(),
29 CallHelper<ReturnType>(
30 main_isolate(),
31 CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p0, p1,
32 p2, p3, p4)),
33 RawMachineAssembler(
34 main_isolate(), new (main_zone()) Graph(main_zone()),
35 Linkage::GetSimplifiedCDescriptor(
36 main_zone(),
37 CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p0,
38 p1, p2, p3, p4)),
39 MachineType::PointerRepresentation(),
40 InstructionSelector::SupportedMachineOperatorFlags()) {}
41
CheckNumber(double expected,Object * number)42 void CheckNumber(double expected, Object* number) {
43 CHECK(this->isolate()->factory()->NewNumber(expected)->SameValue(number));
44 }
45
CheckString(const char * expected,Object * string)46 void CheckString(const char* expected, Object* string) {
47 CHECK(
48 this->isolate()->factory()->InternalizeUtf8String(expected)->SameValue(
49 string));
50 }
51
GenerateCode()52 void GenerateCode() { Generate(); }
53
GetCode()54 Handle<Code> GetCode() {
55 Generate();
56 return code_.ToHandleChecked();
57 }
58
59 protected:
Generate()60 virtual byte* Generate() {
61 if (code_.is_null()) {
62 Schedule* schedule = this->Export();
63 CallDescriptor* call_descriptor = this->call_descriptor();
64 Graph* graph = this->graph();
65 CompilationInfo info("testing", main_isolate(), main_zone());
66 code_ = Pipeline::GenerateCodeForTesting(&info, call_descriptor, graph,
67 schedule);
68 }
69 return this->code_.ToHandleChecked()->entry();
70 }
71
72 private:
73 MaybeHandle<Code> code_;
74 };
75
76
77 template <typename ReturnType>
78 class BufferedRawMachineAssemblerTester
79 : public RawMachineAssemblerTester<int32_t> {
80 public:
81 BufferedRawMachineAssemblerTester(MachineType p0 = MachineType::None(),
82 MachineType p1 = MachineType::None(),
83 MachineType p2 = MachineType::None(),
84 MachineType p3 = MachineType::None())
BufferedRawMachineAssemblerTester(ComputeParameterCount (p0,p1,p2,p3),p0,p1,p2,p3)85 : BufferedRawMachineAssemblerTester(ComputeParameterCount(p0, p1, p2, p3),
86 p0, p1, p2, p3) {}
87
88
89 // The BufferedRawMachineAssemblerTester does not pass parameters directly
90 // to the constructed IR graph. Instead it passes a pointer to the parameter
91 // to the IR graph, and adds Load nodes to the IR graph to load the
92 // parameters from memory. Thereby it is possible to pass 64 bit parameters
93 // to the IR graph.
Parameter(size_t index)94 Node* Parameter(size_t index) {
95 CHECK(index >= 0 && index < 4);
96 return parameter_nodes_[index];
97 }
98
99
100 // The BufferedRawMachineAssemblerTester adds a Store node to the IR graph
101 // to store the graph's return value in memory. The memory address for the
102 // Store node is provided as a parameter. By storing the return value in
103 // memory it is possible to return 64 bit values.
Return(Node * input)104 void Return(Node* input) {
105 Store(MachineTypeForC<ReturnType>().representation(),
106 RawMachineAssembler::Parameter(return_parameter_index_), input,
107 kNoWriteBarrier);
108 RawMachineAssembler::Return(Int32Constant(1234));
109 }
110
Call()111 ReturnType Call() {
112 ReturnType return_value;
113 test_graph_signature_->VerifyParams();
114 CallHelper<int32_t>::Call(reinterpret_cast<void*>(&return_value));
115 return return_value;
116 }
117
118 template <typename P0>
Call(P0 p0)119 ReturnType Call(P0 p0) {
120 ReturnType return_value;
121 test_graph_signature_->VerifyParams<P0>();
122 CallHelper<int32_t>::Call(reinterpret_cast<void*>(&p0),
123 reinterpret_cast<void*>(&return_value));
124 return return_value;
125 }
126
127 template <typename P0, typename P1>
Call(P0 p0,P1 p1)128 ReturnType Call(P0 p0, P1 p1) {
129 ReturnType return_value;
130 test_graph_signature_->VerifyParams<P0, P1>();
131 CallHelper<int32_t>::Call(reinterpret_cast<void*>(&p0),
132 reinterpret_cast<void*>(&p1),
133 reinterpret_cast<void*>(&return_value));
134 return return_value;
135 }
136
137 template <typename P0, typename P1, typename P2>
Call(P0 p0,P1 p1,P2 p2)138 ReturnType Call(P0 p0, P1 p1, P2 p2) {
139 ReturnType return_value;
140 test_graph_signature_->VerifyParams<P0, P1, P2>();
141 CallHelper<int32_t>::Call(
142 reinterpret_cast<void*>(&p0), reinterpret_cast<void*>(&p1),
143 reinterpret_cast<void*>(&p2), reinterpret_cast<void*>(&return_value));
144 return return_value;
145 }
146
147 template <typename P0, typename P1, typename P2, typename P3>
Call(P0 p0,P1 p1,P2 p2,P3 p3)148 ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) {
149 ReturnType return_value;
150 test_graph_signature_->VerifyParams<P0, P1, P2, P3>();
151 CallHelper<int32_t>::Call(
152 reinterpret_cast<void*>(&p0), reinterpret_cast<void*>(&p1),
153 reinterpret_cast<void*>(&p2), reinterpret_cast<void*>(&p3),
154 reinterpret_cast<void*>(&return_value));
155 return return_value;
156 }
157
158 private:
BufferedRawMachineAssemblerTester(uint32_t return_parameter_index,MachineType p0,MachineType p1,MachineType p2,MachineType p3)159 BufferedRawMachineAssemblerTester(uint32_t return_parameter_index,
160 MachineType p0, MachineType p1,
161 MachineType p2, MachineType p3)
162 : RawMachineAssemblerTester<int32_t>(
163 MachineType::Pointer(),
164 p0 == MachineType::None() ? MachineType::None()
165 : MachineType::Pointer(),
166 p1 == MachineType::None() ? MachineType::None()
167 : MachineType::Pointer(),
168 p2 == MachineType::None() ? MachineType::None()
169 : MachineType::Pointer(),
170 p3 == MachineType::None() ? MachineType::None()
171 : MachineType::Pointer()),
172 test_graph_signature_(
173 CSignature::New(main_zone(), MachineType::Int32(), p0, p1, p2, p3)),
174 return_parameter_index_(return_parameter_index) {
175 parameter_nodes_[0] = p0 == MachineType::None()
176 ? nullptr
177 : Load(p0, RawMachineAssembler::Parameter(0));
178 parameter_nodes_[1] = p1 == MachineType::None()
179 ? nullptr
180 : Load(p1, RawMachineAssembler::Parameter(1));
181 parameter_nodes_[2] = p2 == MachineType::None()
182 ? nullptr
183 : Load(p2, RawMachineAssembler::Parameter(2));
184 parameter_nodes_[3] = p3 == MachineType::None()
185 ? nullptr
186 : Load(p3, RawMachineAssembler::Parameter(3));
187 }
188
189
ComputeParameterCount(MachineType p0,MachineType p1,MachineType p2,MachineType p3)190 static uint32_t ComputeParameterCount(MachineType p0, MachineType p1,
191 MachineType p2, MachineType p3) {
192 if (p0 == MachineType::None()) {
193 return 0;
194 }
195 if (p1 == MachineType::None()) {
196 return 1;
197 }
198 if (p2 == MachineType::None()) {
199 return 2;
200 }
201 if (p3 == MachineType::None()) {
202 return 3;
203 }
204 return 4;
205 }
206
207
208 CSignature* test_graph_signature_;
209 Node* parameter_nodes_[4];
210 uint32_t return_parameter_index_;
211 };
212
213
214 template <>
215 class BufferedRawMachineAssemblerTester<void>
216 : public RawMachineAssemblerTester<void> {
217 public:
218 BufferedRawMachineAssemblerTester(MachineType p0 = MachineType::None(),
219 MachineType p1 = MachineType::None(),
220 MachineType p2 = MachineType::None(),
221 MachineType p3 = MachineType::None())
222 : RawMachineAssemblerTester<void>(
223 p0 == MachineType::None() ? MachineType::None()
224 : MachineType::Pointer(),
225 p1 == MachineType::None() ? MachineType::None()
226 : MachineType::Pointer(),
227 p2 == MachineType::None() ? MachineType::None()
228 : MachineType::Pointer(),
229 p3 == MachineType::None() ? MachineType::None()
230 : MachineType::Pointer()),
231 test_graph_signature_(
232 CSignature::New(RawMachineAssemblerTester<void>::main_zone(),
233 MachineType::None(), p0, p1, p2, p3)) {
234 parameter_nodes_[0] = p0 == MachineType::None()
235 ? nullptr
236 : Load(p0, RawMachineAssembler::Parameter(0));
237 parameter_nodes_[1] = p1 == MachineType::None()
238 ? nullptr
239 : Load(p1, RawMachineAssembler::Parameter(1));
240 parameter_nodes_[2] = p2 == MachineType::None()
241 ? nullptr
242 : Load(p2, RawMachineAssembler::Parameter(2));
243 parameter_nodes_[3] = p3 == MachineType::None()
244 ? nullptr
245 : Load(p3, RawMachineAssembler::Parameter(3));
246 }
247
248
249 // The BufferedRawMachineAssemblerTester does not pass parameters directly
250 // to the constructed IR graph. Instead it passes a pointer to the parameter
251 // to the IR graph, and adds Load nodes to the IR graph to load the
252 // parameters from memory. Thereby it is possible to pass 64 bit parameters
253 // to the IR graph.
Parameter(size_t index)254 Node* Parameter(size_t index) {
255 CHECK(index >= 0 && index < 4);
256 return parameter_nodes_[index];
257 }
258
259
Call()260 void Call() {
261 test_graph_signature_->VerifyParams();
262 CallHelper<void>::Call();
263 }
264
265 template <typename P0>
Call(P0 p0)266 void Call(P0 p0) {
267 test_graph_signature_->VerifyParams<P0>();
268 CallHelper<void>::Call(reinterpret_cast<void*>(&p0));
269 }
270
271 template <typename P0, typename P1>
Call(P0 p0,P1 p1)272 void Call(P0 p0, P1 p1) {
273 test_graph_signature_->VerifyParams<P0, P1>();
274 CallHelper<void>::Call(reinterpret_cast<void*>(&p0),
275 reinterpret_cast<void*>(&p1));
276 }
277
278 template <typename P0, typename P1, typename P2>
Call(P0 p0,P1 p1,P2 p2)279 void Call(P0 p0, P1 p1, P2 p2) {
280 test_graph_signature_->VerifyParams<P0, P1, P2>();
281 CallHelper<void>::Call(reinterpret_cast<void*>(&p0),
282 reinterpret_cast<void*>(&p1),
283 reinterpret_cast<void*>(&p2));
284 }
285
286 template <typename P0, typename P1, typename P2, typename P3>
Call(P0 p0,P1 p1,P2 p2,P3 p3)287 void Call(P0 p0, P1 p1, P2 p2, P3 p3) {
288 test_graph_signature_->VerifyParams<P0, P1, P2, P3>();
289 CallHelper<void>::Call(
290 reinterpret_cast<void*>(&p0), reinterpret_cast<void*>(&p1),
291 reinterpret_cast<void*>(&p2), reinterpret_cast<void*>(&p3));
292 }
293
294 private:
295 CSignature* test_graph_signature_;
296 Node* parameter_nodes_[4];
297 };
298 static const bool USE_RESULT_BUFFER = true;
299 static const bool USE_RETURN_REGISTER = false;
300 static const int32_t CHECK_VALUE = 0x99BEEDCE;
301
302
303 // TODO(titzer): use the C-style calling convention, or any register-based
304 // calling convention for binop tests.
305 template <typename CType, bool use_result_buffer>
306 class BinopTester {
307 public:
BinopTester(RawMachineAssemblerTester<int32_t> * tester,MachineType rep)308 explicit BinopTester(RawMachineAssemblerTester<int32_t>* tester,
309 MachineType rep)
310 : T(tester),
311 param0(T->LoadFromPointer(&p0, rep)),
312 param1(T->LoadFromPointer(&p1, rep)),
313 rep(rep),
314 p0(static_cast<CType>(0)),
315 p1(static_cast<CType>(0)),
316 result(static_cast<CType>(0)) {}
317
318 RawMachineAssemblerTester<int32_t>* T;
319 Node* param0;
320 Node* param1;
321
call(CType a0,CType a1)322 CType call(CType a0, CType a1) {
323 p0 = a0;
324 p1 = a1;
325 if (use_result_buffer) {
326 CHECK_EQ(CHECK_VALUE, T->Call());
327 return result;
328 } else {
329 return static_cast<CType>(T->Call());
330 }
331 }
332
AddReturn(Node * val)333 void AddReturn(Node* val) {
334 if (use_result_buffer) {
335 T->Store(rep.representation(), T->PointerConstant(&result),
336 T->Int32Constant(0), val, kNoWriteBarrier);
337 T->Return(T->Int32Constant(CHECK_VALUE));
338 } else {
339 T->Return(val);
340 }
341 }
342
343 template <typename Ci, typename Cj, typename Fn>
Run(const Ci & ci,const Cj & cj,const Fn & fn)344 void Run(const Ci& ci, const Cj& cj, const Fn& fn) {
345 typename Ci::const_iterator i;
346 typename Cj::const_iterator j;
347 for (i = ci.begin(); i != ci.end(); ++i) {
348 for (j = cj.begin(); j != cj.end(); ++j) {
349 CHECK_EQ(fn(*i, *j), this->call(*i, *j));
350 }
351 }
352 }
353
354 protected:
355 MachineType rep;
356 CType p0;
357 CType p1;
358 CType result;
359 };
360
361
362 // A helper class for testing code sequences that take two int parameters and
363 // return an int value.
364 class Int32BinopTester : public BinopTester<int32_t, USE_RETURN_REGISTER> {
365 public:
Int32BinopTester(RawMachineAssemblerTester<int32_t> * tester)366 explicit Int32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
367 : BinopTester<int32_t, USE_RETURN_REGISTER>(tester,
368 MachineType::Int32()) {}
369 };
370
371
372 // A helper class for testing code sequences that take two int parameters and
373 // return an int value.
374 class Int64BinopTester : public BinopTester<int64_t, USE_RETURN_REGISTER> {
375 public:
Int64BinopTester(RawMachineAssemblerTester<int32_t> * tester)376 explicit Int64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
377 : BinopTester<int64_t, USE_RETURN_REGISTER>(tester,
378 MachineType::Int64()) {}
379 };
380
381
382 // A helper class for testing code sequences that take two uint parameters and
383 // return an uint value.
384 class Uint32BinopTester : public BinopTester<uint32_t, USE_RETURN_REGISTER> {
385 public:
Uint32BinopTester(RawMachineAssemblerTester<int32_t> * tester)386 explicit Uint32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
387 : BinopTester<uint32_t, USE_RETURN_REGISTER>(tester,
388 MachineType::Uint32()) {}
389
call(uint32_t a0,uint32_t a1)390 uint32_t call(uint32_t a0, uint32_t a1) {
391 p0 = a0;
392 p1 = a1;
393 return static_cast<uint32_t>(T->Call());
394 }
395 };
396
397
398 // A helper class for testing code sequences that take two float parameters and
399 // return a float value.
400 // TODO(titzer): figure out how to return floats correctly on ia32.
401 class Float32BinopTester : public BinopTester<float, USE_RESULT_BUFFER> {
402 public:
Float32BinopTester(RawMachineAssemblerTester<int32_t> * tester)403 explicit Float32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
404 : BinopTester<float, USE_RESULT_BUFFER>(tester, MachineType::Float32()) {}
405 };
406
407
408 // A helper class for testing code sequences that take two double parameters and
409 // return a double value.
410 // TODO(titzer): figure out how to return doubles correctly on ia32.
411 class Float64BinopTester : public BinopTester<double, USE_RESULT_BUFFER> {
412 public:
Float64BinopTester(RawMachineAssemblerTester<int32_t> * tester)413 explicit Float64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
414 : BinopTester<double, USE_RESULT_BUFFER>(tester, MachineType::Float64()) {
415 }
416 };
417
418
419 // A helper class for testing code sequences that take two pointer parameters
420 // and return a pointer value.
421 // TODO(titzer): pick word size of pointers based on V8_TARGET.
422 template <typename Type>
423 class PointerBinopTester : public BinopTester<Type*, USE_RETURN_REGISTER> {
424 public:
PointerBinopTester(RawMachineAssemblerTester<int32_t> * tester)425 explicit PointerBinopTester(RawMachineAssemblerTester<int32_t>* tester)
426 : BinopTester<Type*, USE_RETURN_REGISTER>(tester,
427 MachineType::Pointer()) {}
428 };
429
430
431 // A helper class for testing code sequences that take two tagged parameters and
432 // return a tagged value.
433 template <typename Type>
434 class TaggedBinopTester : public BinopTester<Type*, USE_RETURN_REGISTER> {
435 public:
TaggedBinopTester(RawMachineAssemblerTester<int32_t> * tester)436 explicit TaggedBinopTester(RawMachineAssemblerTester<int32_t>* tester)
437 : BinopTester<Type*, USE_RETURN_REGISTER>(tester,
438 MachineType::AnyTagged()) {}
439 };
440
441 // A helper class for testing compares. Wraps a machine opcode and provides
442 // evaluation routines and the operators.
443 class CompareWrapper {
444 public:
CompareWrapper(IrOpcode::Value op)445 explicit CompareWrapper(IrOpcode::Value op) : opcode(op) {}
446
MakeNode(RawMachineAssemblerTester<int32_t> * m,Node * a,Node * b)447 Node* MakeNode(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) {
448 return m->AddNode(op(m->machine()), a, b);
449 }
450
op(MachineOperatorBuilder * machine)451 const Operator* op(MachineOperatorBuilder* machine) {
452 switch (opcode) {
453 case IrOpcode::kWord32Equal:
454 return machine->Word32Equal();
455 case IrOpcode::kInt32LessThan:
456 return machine->Int32LessThan();
457 case IrOpcode::kInt32LessThanOrEqual:
458 return machine->Int32LessThanOrEqual();
459 case IrOpcode::kUint32LessThan:
460 return machine->Uint32LessThan();
461 case IrOpcode::kUint32LessThanOrEqual:
462 return machine->Uint32LessThanOrEqual();
463 case IrOpcode::kFloat64Equal:
464 return machine->Float64Equal();
465 case IrOpcode::kFloat64LessThan:
466 return machine->Float64LessThan();
467 case IrOpcode::kFloat64LessThanOrEqual:
468 return machine->Float64LessThanOrEqual();
469 default:
470 UNREACHABLE();
471 }
472 return NULL;
473 }
474
Int32Compare(int32_t a,int32_t b)475 bool Int32Compare(int32_t a, int32_t b) {
476 switch (opcode) {
477 case IrOpcode::kWord32Equal:
478 return a == b;
479 case IrOpcode::kInt32LessThan:
480 return a < b;
481 case IrOpcode::kInt32LessThanOrEqual:
482 return a <= b;
483 case IrOpcode::kUint32LessThan:
484 return static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
485 case IrOpcode::kUint32LessThanOrEqual:
486 return static_cast<uint32_t>(a) <= static_cast<uint32_t>(b);
487 default:
488 UNREACHABLE();
489 }
490 return false;
491 }
492
Float64Compare(double a,double b)493 bool Float64Compare(double a, double b) {
494 switch (opcode) {
495 case IrOpcode::kFloat64Equal:
496 return a == b;
497 case IrOpcode::kFloat64LessThan:
498 return a < b;
499 case IrOpcode::kFloat64LessThanOrEqual:
500 return a <= b;
501 default:
502 UNREACHABLE();
503 }
504 return false;
505 }
506
507 IrOpcode::Value opcode;
508 };
509
510
511 // A small closure class to generate code for a function of two inputs that
512 // produces a single output so that it can be used in many different contexts.
513 // The {expected()} method should compute the expected output for a given
514 // pair of inputs.
515 template <typename T>
516 class BinopGen {
517 public:
518 virtual void gen(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) = 0;
519 virtual T expected(T a, T b) = 0;
~BinopGen()520 virtual ~BinopGen() {}
521 };
522
523 // A helper class to generate various combination of input shape combinations
524 // and run the generated code to ensure it produces the correct results.
525 class Int32BinopInputShapeTester {
526 public:
Int32BinopInputShapeTester(BinopGen<int32_t> * g)527 explicit Int32BinopInputShapeTester(BinopGen<int32_t>* g) : gen(g) {}
528
529 void TestAllInputShapes();
530
531 private:
532 BinopGen<int32_t>* gen;
533 int32_t input_a;
534 int32_t input_b;
535
536 void Run(RawMachineAssemblerTester<int32_t>* m);
537 void RunLeft(RawMachineAssemblerTester<int32_t>* m);
538 void RunRight(RawMachineAssemblerTester<int32_t>* m);
539 };
540
541 // TODO(bmeurer): Drop this crap once we switch to GTest/Gmock.
CheckFloatEq(volatile float x,volatile float y)542 static inline void CheckFloatEq(volatile float x, volatile float y) {
543 if (std::isnan(x)) {
544 CHECK(std::isnan(y));
545 } else {
546 CHECK_EQ(x, y);
547 }
548 }
549
CheckDoubleEq(volatile double x,volatile double y)550 static inline void CheckDoubleEq(volatile double x, volatile double y) {
551 if (std::isnan(x)) {
552 CHECK(std::isnan(y));
553 } else {
554 CHECK_EQ(x, y);
555 }
556 }
557
558 } // namespace compiler
559 } // namespace internal
560 } // namespace v8
561
562 #endif // V8_CCTEST_COMPILER_CODEGEN_TESTER_H_
563