| // Copyright 2014 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. |
| |
| #ifndef V8_CCTEST_COMPILER_CODEGEN_TESTER_H_ |
| #define V8_CCTEST_COMPILER_CODEGEN_TESTER_H_ |
| |
| #include "src/v8.h" |
| |
| #include "src/compiler/instruction-selector.h" |
| #include "src/compiler/pipeline.h" |
| #include "src/compiler/raw-machine-assembler.h" |
| #include "src/simulator.h" |
| #include "test/cctest/compiler/call-tester.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| template <typename ReturnType> |
| class RawMachineAssemblerTester : public HandleAndZoneScope, |
| public CallHelper<ReturnType>, |
| public RawMachineAssembler { |
| public: |
| RawMachineAssemblerTester(MachineType p0 = kMachNone, |
| MachineType p1 = kMachNone, |
| MachineType p2 = kMachNone, |
| MachineType p3 = kMachNone, |
| MachineType p4 = kMachNone) |
| : HandleAndZoneScope(), |
| CallHelper<ReturnType>( |
| main_isolate(), |
| CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p0, p1, |
| p2, p3, p4)), |
| RawMachineAssembler( |
| main_isolate(), new (main_zone()) Graph(main_zone()), |
| CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p0, p1, |
| p2, p3, p4), |
| kMachPtr, InstructionSelector::SupportedMachineOperatorFlags()) {} |
| |
| void CheckNumber(double expected, Object* number) { |
| CHECK(this->isolate()->factory()->NewNumber(expected)->SameValue(number)); |
| } |
| |
| void CheckString(const char* expected, Object* string) { |
| CHECK( |
| this->isolate()->factory()->InternalizeUtf8String(expected)->SameValue( |
| string)); |
| } |
| |
| void GenerateCode() { Generate(); } |
| |
| protected: |
| virtual byte* Generate() { |
| if (code_.is_null()) { |
| Schedule* schedule = this->Export(); |
| CallDescriptor* call_descriptor = this->call_descriptor(); |
| Graph* graph = this->graph(); |
| code_ = Pipeline::GenerateCodeForTesting(this->isolate(), call_descriptor, |
| graph, schedule); |
| } |
| return this->code_.ToHandleChecked()->entry(); |
| } |
| |
| private: |
| MaybeHandle<Code> code_; |
| }; |
| |
| |
| static const bool USE_RESULT_BUFFER = true; |
| static const bool USE_RETURN_REGISTER = false; |
| static const int32_t CHECK_VALUE = 0x99BEEDCE; |
| |
| |
| // TODO(titzer): use the C-style calling convention, or any register-based |
| // calling convention for binop tests. |
| template <typename CType, MachineType rep, bool use_result_buffer> |
| class BinopTester { |
| public: |
| explicit BinopTester(RawMachineAssemblerTester<int32_t>* tester) |
| : T(tester), |
| param0(T->LoadFromPointer(&p0, rep)), |
| param1(T->LoadFromPointer(&p1, rep)), |
| p0(static_cast<CType>(0)), |
| p1(static_cast<CType>(0)), |
| result(static_cast<CType>(0)) {} |
| |
| RawMachineAssemblerTester<int32_t>* T; |
| Node* param0; |
| Node* param1; |
| |
| CType call(CType a0, CType a1) { |
| p0 = a0; |
| p1 = a1; |
| if (use_result_buffer) { |
| CHECK_EQ(CHECK_VALUE, T->Call()); |
| return result; |
| } else { |
| return static_cast<CType>(T->Call()); |
| } |
| } |
| |
| void AddReturn(Node* val) { |
| if (use_result_buffer) { |
| T->Store(rep, T->PointerConstant(&result), T->Int32Constant(0), val); |
| T->Return(T->Int32Constant(CHECK_VALUE)); |
| } else { |
| T->Return(val); |
| } |
| } |
| |
| template <typename Ci, typename Cj, typename Fn> |
| void Run(const Ci& ci, const Cj& cj, const Fn& fn) { |
| typename Ci::const_iterator i; |
| typename Cj::const_iterator j; |
| for (i = ci.begin(); i != ci.end(); ++i) { |
| for (j = cj.begin(); j != cj.end(); ++j) { |
| CHECK_EQ(fn(*i, *j), this->call(*i, *j)); |
| } |
| } |
| } |
| |
| protected: |
| CType p0; |
| CType p1; |
| CType result; |
| }; |
| |
| |
| // A helper class for testing code sequences that take two int parameters and |
| // return an int value. |
| class Int32BinopTester |
| : public BinopTester<int32_t, kMachInt32, USE_RETURN_REGISTER> { |
| public: |
| explicit Int32BinopTester(RawMachineAssemblerTester<int32_t>* tester) |
| : BinopTester<int32_t, kMachInt32, USE_RETURN_REGISTER>(tester) {} |
| }; |
| |
| |
| // A helper class for testing code sequences that take two uint parameters and |
| // return an uint value. |
| class Uint32BinopTester |
| : public BinopTester<uint32_t, kMachUint32, USE_RETURN_REGISTER> { |
| public: |
| explicit Uint32BinopTester(RawMachineAssemblerTester<int32_t>* tester) |
| : BinopTester<uint32_t, kMachUint32, USE_RETURN_REGISTER>(tester) {} |
| |
| uint32_t call(uint32_t a0, uint32_t a1) { |
| p0 = a0; |
| p1 = a1; |
| return static_cast<uint32_t>(T->Call()); |
| } |
| }; |
| |
| |
| // A helper class for testing code sequences that take two float parameters and |
| // return a float value. |
| // TODO(titzer): figure out how to return floats correctly on ia32. |
| class Float32BinopTester |
| : public BinopTester<float, kMachFloat32, USE_RESULT_BUFFER> { |
| public: |
| explicit Float32BinopTester(RawMachineAssemblerTester<int32_t>* tester) |
| : BinopTester<float, kMachFloat32, USE_RESULT_BUFFER>(tester) {} |
| }; |
| |
| |
| // A helper class for testing code sequences that take two double parameters and |
| // return a double value. |
| // TODO(titzer): figure out how to return doubles correctly on ia32. |
| class Float64BinopTester |
| : public BinopTester<double, kMachFloat64, USE_RESULT_BUFFER> { |
| public: |
| explicit Float64BinopTester(RawMachineAssemblerTester<int32_t>* tester) |
| : BinopTester<double, kMachFloat64, USE_RESULT_BUFFER>(tester) {} |
| }; |
| |
| |
| // A helper class for testing code sequences that take two pointer parameters |
| // and return a pointer value. |
| // TODO(titzer): pick word size of pointers based on V8_TARGET. |
| template <typename Type> |
| class PointerBinopTester |
| : public BinopTester<Type*, kMachPtr, USE_RETURN_REGISTER> { |
| public: |
| explicit PointerBinopTester(RawMachineAssemblerTester<int32_t>* tester) |
| : BinopTester<Type*, kMachPtr, USE_RETURN_REGISTER>(tester) {} |
| }; |
| |
| |
| // A helper class for testing code sequences that take two tagged parameters and |
| // return a tagged value. |
| template <typename Type> |
| class TaggedBinopTester |
| : public BinopTester<Type*, kMachAnyTagged, USE_RETURN_REGISTER> { |
| public: |
| explicit TaggedBinopTester(RawMachineAssemblerTester<int32_t>* tester) |
| : BinopTester<Type*, kMachAnyTagged, USE_RETURN_REGISTER>(tester) {} |
| }; |
| |
| // A helper class for testing compares. Wraps a machine opcode and provides |
| // evaluation routines and the operators. |
| class CompareWrapper { |
| public: |
| explicit CompareWrapper(IrOpcode::Value op) : opcode(op) {} |
| |
| Node* MakeNode(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) { |
| return m->NewNode(op(m->machine()), a, b); |
| } |
| |
| const Operator* op(MachineOperatorBuilder* machine) { |
| switch (opcode) { |
| case IrOpcode::kWord32Equal: |
| return machine->Word32Equal(); |
| case IrOpcode::kInt32LessThan: |
| return machine->Int32LessThan(); |
| case IrOpcode::kInt32LessThanOrEqual: |
| return machine->Int32LessThanOrEqual(); |
| case IrOpcode::kUint32LessThan: |
| return machine->Uint32LessThan(); |
| case IrOpcode::kUint32LessThanOrEqual: |
| return machine->Uint32LessThanOrEqual(); |
| case IrOpcode::kFloat64Equal: |
| return machine->Float64Equal(); |
| case IrOpcode::kFloat64LessThan: |
| return machine->Float64LessThan(); |
| case IrOpcode::kFloat64LessThanOrEqual: |
| return machine->Float64LessThanOrEqual(); |
| default: |
| UNREACHABLE(); |
| } |
| return NULL; |
| } |
| |
| bool Int32Compare(int32_t a, int32_t b) { |
| switch (opcode) { |
| case IrOpcode::kWord32Equal: |
| return a == b; |
| case IrOpcode::kInt32LessThan: |
| return a < b; |
| case IrOpcode::kInt32LessThanOrEqual: |
| return a <= b; |
| case IrOpcode::kUint32LessThan: |
| return static_cast<uint32_t>(a) < static_cast<uint32_t>(b); |
| case IrOpcode::kUint32LessThanOrEqual: |
| return static_cast<uint32_t>(a) <= static_cast<uint32_t>(b); |
| default: |
| UNREACHABLE(); |
| } |
| return false; |
| } |
| |
| bool Float64Compare(double a, double b) { |
| switch (opcode) { |
| case IrOpcode::kFloat64Equal: |
| return a == b; |
| case IrOpcode::kFloat64LessThan: |
| return a < b; |
| case IrOpcode::kFloat64LessThanOrEqual: |
| return a <= b; |
| default: |
| UNREACHABLE(); |
| } |
| return false; |
| } |
| |
| IrOpcode::Value opcode; |
| }; |
| |
| |
| // A small closure class to generate code for a function of two inputs that |
| // produces a single output so that it can be used in many different contexts. |
| // The {expected()} method should compute the expected output for a given |
| // pair of inputs. |
| template <typename T> |
| class BinopGen { |
| public: |
| virtual void gen(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) = 0; |
| virtual T expected(T a, T b) = 0; |
| virtual ~BinopGen() {} |
| }; |
| |
| // A helper class to generate various combination of input shape combinations |
| // and run the generated code to ensure it produces the correct results. |
| class Int32BinopInputShapeTester { |
| public: |
| explicit Int32BinopInputShapeTester(BinopGen<int32_t>* g) : gen(g) {} |
| |
| void TestAllInputShapes(); |
| |
| private: |
| BinopGen<int32_t>* gen; |
| int32_t input_a; |
| int32_t input_b; |
| |
| void Run(RawMachineAssemblerTester<int32_t>* m); |
| void RunLeft(RawMachineAssemblerTester<int32_t>* m); |
| void RunRight(RawMachineAssemblerTester<int32_t>* m); |
| }; |
| |
| // TODO(bmeurer): Drop this crap once we switch to GTest/Gmock. |
| static inline void CheckFloatEq(volatile float x, volatile float y) { |
| if (std::isnan(x)) { |
| CHECK(std::isnan(y)); |
| } else { |
| CHECK(x == y); |
| } |
| } |
| |
| static inline void CheckDoubleEq(volatile double x, volatile double y) { |
| if (std::isnan(x)) { |
| CHECK(std::isnan(y)); |
| } else { |
| CHECK_EQ(x, y); |
| } |
| } |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_CCTEST_COMPILER_CODEGEN_TESTER_H_ |