// 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/codegen/optimized-compilation-info.h" #include "src/compiler/backend/instruction-selector.h" #include "src/compiler/pipeline.h" #include "src/compiler/raw-machine-assembler.h" #include "src/execution/simulator.h" #include "test/cctest/cctest.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: template <typename... ParamMachTypes> explicit RawMachineAssemblerTester(ParamMachTypes... p) : HandleAndZoneScope(), CallHelper<ReturnType>( main_isolate(), CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...)), RawMachineAssembler( main_isolate(), new (main_zone()) Graph(main_zone()), Linkage::GetSimplifiedCDescriptor( main_zone(), CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...), true), MachineType::PointerRepresentation(), InstructionSelector::SupportedMachineOperatorFlags(), InstructionSelector::AlignmentRequirements()) {} template <typename... ParamMachTypes> RawMachineAssemblerTester(Code::Kind kind, ParamMachTypes... p) : HandleAndZoneScope(), CallHelper<ReturnType>( main_isolate(), CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...)), RawMachineAssembler( main_isolate(), new (main_zone()) Graph(main_zone()), Linkage::GetSimplifiedCDescriptor( main_zone(), CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...), true), MachineType::PointerRepresentation(), InstructionSelector::SupportedMachineOperatorFlags(), InstructionSelector::AlignmentRequirements()), kind_(kind) {} ~RawMachineAssemblerTester() override = default; 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(); } Handle<Code> GetCode() { Generate(); return code_.ToHandleChecked(); } protected: Address Generate() override { if (code_.is_null()) { Schedule* schedule = this->Export(); auto call_descriptor = this->call_descriptor(); Graph* graph = this->graph(); OptimizedCompilationInfo info(ArrayVector("testing"), main_zone(), kind_); code_ = Pipeline::GenerateCodeForTesting( &info, main_isolate(), call_descriptor, graph, AssemblerOptions::Default(main_isolate()), schedule); } return this->code_.ToHandleChecked()->entry(); } private: Code::Kind kind_ = Code::Kind::STUB; MaybeHandle<Code> code_; }; template <typename ReturnType> class BufferedRawMachineAssemblerTester : public RawMachineAssemblerTester<int32_t> { public: template <typename... ParamMachTypes> explicit BufferedRawMachineAssemblerTester(ParamMachTypes... p) : RawMachineAssemblerTester<int32_t>( MachineType::Pointer(), ((void)p, MachineType::Pointer())...), test_graph_signature_( CSignature::New(this->main_zone(), MachineType::Int32(), p...)), return_parameter_index_(sizeof...(p)) { static_assert(sizeof...(p) <= arraysize(parameter_nodes_), "increase parameter_nodes_ array"); std::array<MachineType, sizeof...(p)> p_arr{{p...}}; for (size_t i = 0; i < p_arr.size(); ++i) { parameter_nodes_[i] = Load(p_arr[i], RawMachineAssembler::Parameter(i)); } } Address Generate() override { return RawMachineAssemblerTester::Generate(); } // The BufferedRawMachineAssemblerTester does not pass parameters directly // to the constructed IR graph. Instead it passes a pointer to the parameter // to the IR graph, and adds Load nodes to the IR graph to load the // parameters from memory. Thereby it is possible to pass 64 bit parameters // to the IR graph. Node* Parameter(size_t index) { CHECK_GT(arraysize(parameter_nodes_), index); return parameter_nodes_[index]; } // The BufferedRawMachineAssemblerTester adds a Store node to the IR graph // to store the graph's return value in memory. The memory address for the // Store node is provided as a parameter. By storing the return value in // memory it is possible to return 64 bit values. void Return(Node* input) { if (COMPRESS_POINTERS_BOOL && MachineTypeForC<ReturnType>().IsTagged()) { // Since we are returning values via storing to off-heap location // generate full-word store here. Store(MachineType::PointerRepresentation(), RawMachineAssembler::Parameter(return_parameter_index_), BitcastTaggedToWord(input), kNoWriteBarrier); } else { Store(MachineTypeForC<ReturnType>().representation(), RawMachineAssembler::Parameter(return_parameter_index_), input, kNoWriteBarrier); } RawMachineAssembler::Return(Int32Constant(1234)); } template <typename... Params> ReturnType Call(Params... p) { uintptr_t zap_data[] = {kZapValue, kZapValue}; ReturnType return_value; STATIC_ASSERT(sizeof(return_value) <= sizeof(zap_data)); MemCopy(&return_value, &zap_data, sizeof(return_value)); CSignature::VerifyParams<Params...>(test_graph_signature_); CallHelper<int32_t>::Call(reinterpret_cast<void*>(&p)..., reinterpret_cast<void*>(&return_value)); return return_value; } private: CSignature* test_graph_signature_; Node* parameter_nodes_[4]; uint32_t return_parameter_index_; }; template <> class BufferedRawMachineAssemblerTester<void> : public RawMachineAssemblerTester<void> { public: template <typename... ParamMachTypes> explicit BufferedRawMachineAssemblerTester(ParamMachTypes... p) : RawMachineAssemblerTester<void>(((void)p, MachineType::Pointer())...), test_graph_signature_( CSignature::New(RawMachineAssemblerTester<void>::main_zone(), MachineType::None(), p...)) { static_assert(sizeof...(p) <= arraysize(parameter_nodes_), "increase parameter_nodes_ array"); std::array<MachineType, sizeof...(p)> p_arr{{p...}}; for (size_t i = 0; i < p_arr.size(); ++i) { parameter_nodes_[i] = Load(p_arr[i], RawMachineAssembler::Parameter(i)); } } Address Generate() override { return RawMachineAssemblerTester::Generate(); } // The BufferedRawMachineAssemblerTester does not pass parameters directly // to the constructed IR graph. Instead it passes a pointer to the parameter // to the IR graph, and adds Load nodes to the IR graph to load the // parameters from memory. Thereby it is possible to pass 64 bit parameters // to the IR graph. Node* Parameter(size_t index) { CHECK_GT(arraysize(parameter_nodes_), index); return parameter_nodes_[index]; } template <typename... Params> void Call(Params... p) { CSignature::VerifyParams<Params...>(test_graph_signature_); CallHelper<void>::Call(reinterpret_cast<void*>(&p)...); } private: CSignature* test_graph_signature_; Node* parameter_nodes_[4]; }; 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, bool use_result_buffer> class BinopTester { public: explicit BinopTester(RawMachineAssemblerTester<int32_t>* tester, MachineType type) : T(tester), param0(T->LoadFromPointer(&p0, type)), param1(T->LoadFromPointer(&p1, type)), type(type), 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(type.representation(), T->PointerConstant(&result), T->Int32Constant(0), val, kNoWriteBarrier); 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: MachineType type; 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, USE_RETURN_REGISTER> { public: explicit Int32BinopTester(RawMachineAssemblerTester<int32_t>* tester) : BinopTester<int32_t, USE_RETURN_REGISTER>(tester, MachineType::Int32()) {} }; // A helper class for testing code sequences that take two int parameters and // return an int value. class Int64BinopTester : public BinopTester<int64_t, USE_RETURN_REGISTER> { public: explicit Int64BinopTester(RawMachineAssemblerTester<int32_t>* tester) : BinopTester<int64_t, USE_RETURN_REGISTER>(tester, MachineType::Int64()) {} }; // A helper class for testing code sequences that take two uint parameters and // return an uint value. class Uint32BinopTester : public BinopTester<uint32_t, USE_RETURN_REGISTER> { public: explicit Uint32BinopTester(RawMachineAssemblerTester<int32_t>* tester) : BinopTester<uint32_t, USE_RETURN_REGISTER>(tester, MachineType::Uint32()) {} 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. class Float32BinopTester : public BinopTester<float, USE_RESULT_BUFFER> { public: explicit Float32BinopTester(RawMachineAssemblerTester<int32_t>* tester) : BinopTester<float, USE_RESULT_BUFFER>(tester, MachineType::Float32()) {} }; // A helper class for testing code sequences that take two double parameters and // return a double value. class Float64BinopTester : public BinopTester<double, USE_RESULT_BUFFER> { public: explicit Float64BinopTester(RawMachineAssemblerTester<int32_t>* tester) : BinopTester<double, USE_RESULT_BUFFER>(tester, MachineType::Float64()) { } }; // 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, USE_RETURN_REGISTER> { public: explicit PointerBinopTester(RawMachineAssemblerTester<int32_t>* tester) : BinopTester<Type, USE_RETURN_REGISTER>(tester, MachineType::Pointer()) { } }; // 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, USE_RETURN_REGISTER> { public: explicit TaggedBinopTester(RawMachineAssemblerTester<int32_t>* tester) : BinopTester<Type, USE_RETURN_REGISTER>(tester, MachineType::AnyTagged()) {} }; // 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->AddNode(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 nullptr; } 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() = default; }; // 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), input_a(0), input_b(0) {} 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); }; } // namespace compiler } // namespace internal } // namespace v8 #endif // V8_CCTEST_COMPILER_CODEGEN_TESTER_H_