| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| //===-- BenchmarkRunner.h ---------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// Defines the abstract BenchmarkRunner class for measuring a certain execution | ||
| /// property of instructions (e.g. latency). | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRUNNER_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRUNNER_H | ||
|
|
||
| #include "BenchmarkResult.h" | ||
| #include "InMemoryAssembler.h" | ||
| #include "LlvmState.h" | ||
| #include "llvm/MC/MCInst.h" | ||
| #include "llvm/Support/Error.h" | ||
| #include <vector> | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| // Common code for all benchmark modes. | ||
| class BenchmarkRunner { | ||
| public: | ||
| // Subtargets can disable running benchmarks for some instructions by | ||
| // returning an error here. | ||
| class InstructionFilter { | ||
| public: | ||
| virtual ~InstructionFilter(); | ||
|
|
||
| virtual llvm::Error shouldRun(const LLVMState &State, | ||
| unsigned Opcode) const { | ||
| return llvm::ErrorSuccess(); | ||
| } | ||
| }; | ||
|
|
||
| virtual ~BenchmarkRunner(); | ||
|
|
||
| InstructionBenchmark run(const LLVMState &State, unsigned Opcode, | ||
| unsigned NumRepetitions, | ||
| const InstructionFilter &Filter) const; | ||
|
|
||
| private: | ||
| virtual const char *getDisplayName() const = 0; | ||
|
|
||
| virtual llvm::Expected<std::vector<llvm::MCInst>> | ||
| createCode(const LLVMState &State, unsigned OpcodeIndex, | ||
| unsigned NumRepetitions, | ||
| const JitFunctionContext &Context) const = 0; | ||
|
|
||
| virtual std::vector<BenchmarkMeasure> | ||
| runMeasurements(const LLVMState &State, const JitFunction &Function, | ||
| unsigned NumRepetitions) const = 0; | ||
| }; | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_BENCHMARKRUNNER_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| add_library(LLVMExegesis | ||
| STATIC | ||
| BenchmarkResult.cpp | ||
| BenchmarkRunner.cpp | ||
| InMemoryAssembler.cpp | ||
| InstructionSnippetGenerator.cpp | ||
| Latency.cpp | ||
| LlvmState.cpp | ||
| OperandGraph.cpp | ||
| PerfHelper.cpp | ||
| Uops.cpp | ||
| X86.cpp | ||
| ) | ||
|
|
||
| llvm_update_compile_flags(LLVMExegesis) | ||
| llvm_map_components_to_libnames(libs | ||
| CodeGen | ||
| ExecutionEngine | ||
| MC | ||
| MCJIT | ||
| Support | ||
| ) | ||
|
|
||
| target_link_libraries(LLVMExegesis ${libs}) | ||
| set_target_properties(LLVMExegesis PROPERTIES FOLDER "Libraries") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| //===-- InMemoryAssembler.cpp -----------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "InMemoryAssembler.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/CodeGen/MachineInstrBuilder.h" | ||
| #include "llvm/CodeGen/MachineModuleInfo.h" | ||
| #include "llvm/CodeGen/MachineRegisterInfo.h" | ||
| #include "llvm/CodeGen/TargetInstrInfo.h" | ||
| #include "llvm/CodeGen/TargetPassConfig.h" | ||
| #include "llvm/ExecutionEngine/ExecutionEngine.h" | ||
| #include "llvm/ExecutionEngine/MCJIT.h" | ||
| #include "llvm/ExecutionEngine/SectionMemoryManager.h" | ||
| #include "llvm/IR/LLVMContext.h" | ||
| #include "llvm/IR/LegacyPassManager.h" | ||
| #include "llvm/MC/MCFixup.h" | ||
| #include "llvm/MC/MCInstrDesc.h" | ||
| #include "llvm/Object/Binary.h" | ||
| #include "llvm/Object/ObjectFile.h" | ||
| #include "llvm/PassInfo.h" | ||
| #include "llvm/PassRegistry.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
| #include "llvm/Target/TargetMachine.h" | ||
| #include "llvm/Target/TargetOptions.h" | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| static constexpr const char ModuleID[] = "ExegesisInfoTest"; | ||
| static constexpr const char FunctionID[] = "foo"; | ||
|
|
||
| // Small utility function to add named passes. | ||
| static bool addPass(llvm::PassManagerBase &PM, llvm::StringRef PassName, | ||
| llvm::TargetPassConfig &TPC) { | ||
| const llvm::PassRegistry *PR = llvm::PassRegistry::getPassRegistry(); | ||
| const llvm::PassInfo *PI = PR->getPassInfo(PassName); | ||
| if (!PI) { | ||
| llvm::errs() << " run-pass " << PassName << " is not registered.\n"; | ||
| return true; | ||
| } | ||
|
|
||
| if (!PI->getNormalCtor()) { | ||
| llvm::errs() << " cannot create pass: " << PI->getPassName() << "\n"; | ||
| return true; | ||
| } | ||
| llvm::Pass *P = PI->getNormalCtor()(); | ||
| std::string Banner = std::string("After ") + std::string(P->getPassName()); | ||
| PM.add(P); | ||
| TPC.printAndVerify(Banner); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| // Creates a void MachineFunction with no argument. | ||
| static llvm::MachineFunction & | ||
| createVoidVoidMachineFunction(llvm::StringRef FunctionID, llvm::Module *Module, | ||
| llvm::MachineModuleInfo *MMI) { | ||
| llvm::Type *const ReturnType = llvm::Type::getInt32Ty(Module->getContext()); | ||
| llvm::FunctionType *FunctionType = llvm::FunctionType::get(ReturnType, false); | ||
| llvm::Function *const F = llvm::Function::Create( | ||
| FunctionType, llvm::GlobalValue::InternalLinkage, FunctionID, Module); | ||
| // Making sure we can create a MachineFunction out of this Function even if it | ||
| // contains no IR. | ||
| F->setIsMaterializable(true); | ||
| return MMI->getOrCreateMachineFunction(*F); | ||
| } | ||
|
|
||
| static llvm::object::OwningBinary<llvm::object::ObjectFile> | ||
| assemble(llvm::Module *Module, std::unique_ptr<llvm::MachineModuleInfo> MMI, | ||
| llvm::LLVMTargetMachine *LLVMTM) { | ||
| llvm::legacy::PassManager PM; | ||
| llvm::MCContext &Context = MMI->getContext(); | ||
|
|
||
| llvm::TargetLibraryInfoImpl TLII(llvm::Triple(Module->getTargetTriple())); | ||
| PM.add(new llvm::TargetLibraryInfoWrapperPass(TLII)); | ||
|
|
||
| llvm::TargetPassConfig *TPC = LLVMTM->createPassConfig(PM); | ||
| PM.add(TPC); | ||
| PM.add(MMI.release()); | ||
| TPC->printAndVerify("MachineFunctionGenerator::assemble"); | ||
| // Adding the following passes: | ||
| // - machineverifier: checks that the MachineFunction is well formed. | ||
| // - prologepilog: saves and restore callee saved registers. | ||
| for (const char *PassName : {"machineverifier", "prologepilog"}) | ||
| if (addPass(PM, PassName, *TPC)) | ||
| llvm::report_fatal_error("Unable to add a mandatory pass"); | ||
| TPC->setInitialized(); | ||
|
|
||
| llvm::SmallVector<char, 4096> AsmBuffer; | ||
| llvm::raw_svector_ostream AsmStream(AsmBuffer); | ||
| // AsmPrinter is responsible for generating the assembly into AsmBuffer. | ||
| if (LLVMTM->addAsmPrinter(PM, AsmStream, llvm::TargetMachine::CGFT_ObjectFile, | ||
| Context)) | ||
| llvm::report_fatal_error("Cannot add AsmPrinter passes"); | ||
|
|
||
| PM.run(*Module); // Run all the passes | ||
|
|
||
| // Storing the generated assembly into a MemoryBuffer that owns the memory. | ||
| std::unique_ptr<llvm::MemoryBuffer> Buffer = | ||
| llvm::MemoryBuffer::getMemBufferCopy(AsmStream.str()); | ||
| // Create the ObjectFile from the MemoryBuffer. | ||
| std::unique_ptr<llvm::object::ObjectFile> Obj = llvm::cantFail( | ||
| llvm::object::ObjectFile::createObjectFile(Buffer->getMemBufferRef())); | ||
| // Returning both the MemoryBuffer and the ObjectFile. | ||
| return llvm::object::OwningBinary<llvm::object::ObjectFile>( | ||
| std::move(Obj), std::move(Buffer)); | ||
| } | ||
|
|
||
| static void fillMachineFunction(llvm::MachineFunction &MF, | ||
| llvm::ArrayRef<llvm::MCInst> Instructions) { | ||
| llvm::MachineBasicBlock *MBB = MF.CreateMachineBasicBlock(); | ||
| MF.push_back(MBB); | ||
| const llvm::MCInstrInfo *MCII = MF.getTarget().getMCInstrInfo(); | ||
| const llvm::DebugLoc DL; | ||
| for (const llvm::MCInst &Inst : Instructions) { | ||
| const unsigned Opcode = Inst.getOpcode(); | ||
| const llvm::MCInstrDesc &MCID = MCII->get(Opcode); | ||
| llvm::MachineInstrBuilder Builder = llvm::BuildMI(MBB, DL, MCID); | ||
| for (unsigned OpIndex = 0, E = Inst.getNumOperands(); OpIndex < E; | ||
| ++OpIndex) { | ||
| const llvm::MCOperand &Op = Inst.getOperand(OpIndex); | ||
| if (Op.isReg()) { | ||
| const bool IsDef = OpIndex < MCID.getNumDefs(); | ||
| unsigned Flags = 0; | ||
| const llvm::MCOperandInfo &OpInfo = MCID.operands().begin()[OpIndex]; | ||
| if (IsDef && !OpInfo.isOptionalDef()) | ||
| Flags |= llvm::RegState::Define; | ||
| Builder.addReg(Op.getReg(), Flags); | ||
| } else if (Op.isImm()) { | ||
| Builder.addImm(Op.getImm()); | ||
| } else { | ||
| llvm_unreachable("Not yet implemented"); | ||
| } | ||
| } | ||
| } | ||
| // Adding the Return Opcode. | ||
| const llvm::TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo(); | ||
| llvm::BuildMI(MBB, DL, TII->get(TII->getReturnOpcode())); | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| // Implementation of this class relies on the fact that a single object with a | ||
| // single function will be loaded into memory. | ||
| class TrackingSectionMemoryManager : public llvm::SectionMemoryManager { | ||
| public: | ||
| explicit TrackingSectionMemoryManager(uintptr_t *CodeSize) | ||
| : CodeSize(CodeSize) {} | ||
|
|
||
| uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment, | ||
| unsigned SectionID, | ||
| llvm::StringRef SectionName) override { | ||
| *CodeSize = Size; | ||
| return llvm::SectionMemoryManager::allocateCodeSection( | ||
| Size, Alignment, SectionID, SectionName); | ||
| } | ||
|
|
||
| private: | ||
| uintptr_t *const CodeSize = nullptr; | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| JitFunctionContext::JitFunctionContext( | ||
| std::unique_ptr<llvm::LLVMTargetMachine> TheTM) | ||
| : Context(llvm::make_unique<llvm::LLVMContext>()), TM(std::move(TheTM)), | ||
| MMI(llvm::make_unique<llvm::MachineModuleInfo>(TM.get())), | ||
| Module(llvm::make_unique<llvm::Module>(ModuleID, *Context)) { | ||
| Module->setDataLayout(TM->createDataLayout()); | ||
| MF = &createVoidVoidMachineFunction(FunctionID, Module.get(), MMI.get()); | ||
| // We need to instruct the passes that we're done with SSA and virtual | ||
| // registers. | ||
| auto &Properties = MF->getProperties(); | ||
| Properties.set(llvm::MachineFunctionProperties::Property::NoVRegs); | ||
| Properties.reset(llvm::MachineFunctionProperties::Property::IsSSA); | ||
| Properties.reset(llvm::MachineFunctionProperties::Property::TracksLiveness); | ||
| // prologue/epilogue pass needs the reserved registers to be frozen, this is | ||
| // usually done by the SelectionDAGISel pass. | ||
| MF->getRegInfo().freezeReservedRegs(*MF); | ||
| // Saving reserved registers for client. | ||
| ReservedRegs = MF->getSubtarget().getRegisterInfo()->getReservedRegs(*MF); | ||
| } | ||
|
|
||
| JitFunction::JitFunction(JitFunctionContext &&Context, | ||
| llvm::ArrayRef<llvm::MCInst> Instructions) | ||
| : FunctionContext(std::move(Context)) { | ||
| fillMachineFunction(*FunctionContext.MF, Instructions); | ||
| // We create the pass manager, run the passes and returns the produced | ||
| // ObjectFile. | ||
| llvm::object::OwningBinary<llvm::object::ObjectFile> ObjHolder = | ||
| assemble(FunctionContext.Module.get(), std::move(FunctionContext.MMI), | ||
| FunctionContext.TM.get()); | ||
| assert(ObjHolder.getBinary() && "cannot create object file"); | ||
| // Initializing the execution engine. | ||
| // We need to use the JIT EngineKind to be able to add an object file. | ||
| LLVMLinkInMCJIT(); | ||
| uintptr_t CodeSize = 0; | ||
| std::string Error; | ||
| ExecEngine.reset( | ||
| llvm::EngineBuilder(std::move(FunctionContext.Module)) | ||
| .setErrorStr(&Error) | ||
| .setMCPU(FunctionContext.TM->getTargetCPU()) | ||
| .setEngineKind(llvm::EngineKind::JIT) | ||
| .setMCJITMemoryManager( | ||
| llvm::make_unique<TrackingSectionMemoryManager>(&CodeSize)) | ||
| .create(FunctionContext.TM.release())); | ||
| if (!ExecEngine) | ||
| llvm::report_fatal_error(Error); | ||
| // Adding the generated object file containing the assembled function. | ||
| // The ExecutionEngine makes sure the object file is copied into an | ||
| // executable page. | ||
| ExecEngine->addObjectFile(ObjHolder.takeBinary().first); | ||
| // Setting function | ||
| FunctionBytes = | ||
| llvm::StringRef(reinterpret_cast<const char *>( | ||
| ExecEngine->getFunctionAddress(FunctionID)), | ||
| CodeSize); | ||
| } | ||
|
|
||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| //===-- InMemoryAssembler.h -------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// Defines classes to assemble functions composed of a single basic block of | ||
| /// MCInsts. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_INMEMORYASSEMBLER_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_INMEMORYASSEMBLER_H | ||
|
|
||
| #include "llvm/ADT/BitVector.h" | ||
| #include "llvm/CodeGen/MachineFunction.h" | ||
| #include "llvm/CodeGen/MachineModuleInfo.h" | ||
| #include "llvm/CodeGen/TargetRegisterInfo.h" | ||
| #include "llvm/ExecutionEngine/ExecutionEngine.h" | ||
| #include "llvm/IR/LLVMContext.h" | ||
| #include "llvm/MC/MCInst.h" | ||
| #include <memory> | ||
| #include <vector> | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| // Consumable context for JitFunction below. | ||
| // This temporary object allows for retrieving MachineFunction properties before | ||
| // assembling it. | ||
| class JitFunctionContext { | ||
| public: | ||
| explicit JitFunctionContext(std::unique_ptr<llvm::LLVMTargetMachine> TM); | ||
| // Movable | ||
| JitFunctionContext(JitFunctionContext &&) = default; | ||
| JitFunctionContext &operator=(JitFunctionContext &&) = default; | ||
| // Non copyable | ||
| JitFunctionContext(const JitFunctionContext &) = delete; | ||
| JitFunctionContext &operator=(const JitFunctionContext &) = delete; | ||
|
|
||
| const llvm::BitVector &getReservedRegs() const { return ReservedRegs; } | ||
|
|
||
| private: | ||
| friend class JitFunction; | ||
|
|
||
| std::unique_ptr<llvm::LLVMContext> Context; | ||
| std::unique_ptr<llvm::LLVMTargetMachine> TM; | ||
| std::unique_ptr<llvm::MachineModuleInfo> MMI; | ||
| std::unique_ptr<llvm::Module> Module; | ||
| llvm::MachineFunction *MF = nullptr; | ||
| llvm::BitVector ReservedRegs; | ||
| }; | ||
|
|
||
| // Creates a void() function from a sequence of llvm::MCInst. | ||
| class JitFunction { | ||
| public: | ||
| // Assembles Instructions into an executable function. | ||
| JitFunction(JitFunctionContext &&Context, | ||
| llvm::ArrayRef<llvm::MCInst> Instructions); | ||
|
|
||
| // Retrieves the function as an array of bytes. | ||
| llvm::StringRef getFunctionBytes() const { return FunctionBytes; } | ||
|
|
||
| // Retrieves the callable function. | ||
| void operator()() const { ((void (*)())FunctionBytes.data())(); } | ||
|
|
||
| private: | ||
| JitFunctionContext FunctionContext; | ||
| std::unique_ptr<llvm::ExecutionEngine> ExecEngine; | ||
| llvm::StringRef FunctionBytes; | ||
| }; | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_INMEMORYASSEMBLER_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,355 @@ | ||
| //===-- InstructionSnippetGenerator.cpp -------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "InstructionSnippetGenerator.h" | ||
| #include "llvm/ADT/MapVector.h" | ||
| #include "llvm/ADT/STLExtras.h" | ||
| #include "llvm/ADT/SmallSet.h" | ||
| #include "llvm/MC/MCInstBuilder.h" | ||
| #include <algorithm> | ||
| #include <unordered_map> | ||
| #include <unordered_set> | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| void Variable::print(llvm::raw_ostream &OS, | ||
| const llvm::MCRegisterInfo *RegInfo) const { | ||
| OS << "IsUse=" << IsUse << " IsDef=" << IsDef << " possible regs: {"; | ||
| for (const size_t Reg : PossibleRegisters) { | ||
| if (RegInfo) | ||
| OS << RegInfo->getName(Reg); | ||
| else | ||
| OS << Reg; | ||
| OS << ","; | ||
| } | ||
| OS << "} "; | ||
| if (ExplicitOperands.empty()) { | ||
| OS << "implicit"; | ||
| } else { | ||
| OS << "explicit ops: {"; | ||
| for (const size_t Op : ExplicitOperands) | ||
| OS << Op << ","; | ||
| OS << "}"; | ||
| } | ||
| OS << "\n"; | ||
| } | ||
|
|
||
| // Update the state of a Variable with an explicit operand. | ||
| static void updateExplicitOperandVariable(const llvm::MCRegisterInfo &RegInfo, | ||
| const llvm::MCInstrDesc &InstrInfo, | ||
| const size_t OpIndex, | ||
| const llvm::BitVector &ReservedRegs, | ||
| Variable &Var) { | ||
| const bool IsDef = OpIndex < InstrInfo.getNumDefs(); | ||
| if (IsDef) | ||
| Var.IsDef = true; | ||
| if (!IsDef) | ||
| Var.IsUse = true; | ||
| Var.ExplicitOperands.push_back(OpIndex); | ||
| const llvm::MCOperandInfo &OpInfo = InstrInfo.opInfo_begin()[OpIndex]; | ||
| if (OpInfo.RegClass >= 0) { | ||
| Var.IsReg = true; | ||
| for (const llvm::MCPhysReg &Reg : RegInfo.getRegClass(OpInfo.RegClass)) { | ||
| if (!ReservedRegs[Reg]) | ||
| Var.PossibleRegisters.insert(Reg); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| static Variable &findVariableWithOperand(llvm::SmallVector<Variable, 8> &Vars, | ||
| size_t OpIndex) { | ||
| // Vars.size() is small (<10) so a linear scan is good enough. | ||
| for (Variable &Var : Vars) { | ||
| if (llvm::is_contained(Var.ExplicitOperands, OpIndex)) | ||
| return Var; | ||
| } | ||
| assert(false && "Illegal state"); | ||
| static Variable *const EmptyVariable = new Variable(); | ||
| return *EmptyVariable; | ||
| } | ||
|
|
||
| llvm::SmallVector<Variable, 8> | ||
| getVariables(const llvm::MCRegisterInfo &RegInfo, | ||
| const llvm::MCInstrDesc &InstrInfo, | ||
| const llvm::BitVector &ReservedRegs) { | ||
| llvm::SmallVector<Variable, 8> Vars; | ||
| // For each operand, its "tied to" operand or -1. | ||
| llvm::SmallVector<int, 10> TiedToMap; | ||
| for (size_t I = 0, E = InstrInfo.getNumOperands(); I < E; ++I) { | ||
| TiedToMap.push_back(InstrInfo.getOperandConstraint(I, llvm::MCOI::TIED_TO)); | ||
| } | ||
| // Adding non tied operands. | ||
| for (size_t I = 0, E = InstrInfo.getNumOperands(); I < E; ++I) { | ||
| if (TiedToMap[I] >= 0) | ||
| continue; // dropping tied ones. | ||
| Vars.emplace_back(); | ||
| updateExplicitOperandVariable(RegInfo, InstrInfo, I, ReservedRegs, | ||
| Vars.back()); | ||
| } | ||
| // Adding tied operands to existing variables. | ||
| for (size_t I = 0, E = InstrInfo.getNumOperands(); I < E; ++I) { | ||
| if (TiedToMap[I] < 0) | ||
| continue; // dropping non-tied ones. | ||
| updateExplicitOperandVariable(RegInfo, InstrInfo, I, ReservedRegs, | ||
| findVariableWithOperand(Vars, TiedToMap[I])); | ||
| } | ||
| // Adding implicit defs. | ||
| for (size_t I = 0, E = InstrInfo.getNumImplicitDefs(); I < E; ++I) { | ||
| Vars.emplace_back(); | ||
| Variable &Var = Vars.back(); | ||
| const llvm::MCPhysReg Reg = InstrInfo.getImplicitDefs()[I]; | ||
| assert(!ReservedRegs[Reg] && "implicit def of reserved register"); | ||
| Var.PossibleRegisters.insert(Reg); | ||
| Var.IsDef = true; | ||
| Var.IsReg = true; | ||
| } | ||
| // Adding implicit uses. | ||
| for (size_t I = 0, E = InstrInfo.getNumImplicitUses(); I < E; ++I) { | ||
| Vars.emplace_back(); | ||
| Variable &Var = Vars.back(); | ||
| const llvm::MCPhysReg Reg = InstrInfo.getImplicitUses()[I]; | ||
| assert(!ReservedRegs[Reg] && "implicit use of reserved register"); | ||
| Var.PossibleRegisters.insert(Reg); | ||
| Var.IsUse = true; | ||
| Var.IsReg = true; | ||
| } | ||
|
|
||
| return Vars; | ||
| } | ||
|
|
||
| VariableAssignment::VariableAssignment(size_t VarIdx, | ||
| llvm::MCPhysReg AssignedReg) | ||
| : VarIdx(VarIdx), AssignedReg(AssignedReg) {} | ||
|
|
||
| bool VariableAssignment::operator==(const VariableAssignment &Other) const { | ||
| return std::tie(VarIdx, AssignedReg) == | ||
| std::tie(Other.VarIdx, Other.AssignedReg); | ||
| } | ||
|
|
||
| bool VariableAssignment::operator<(const VariableAssignment &Other) const { | ||
| return std::tie(VarIdx, AssignedReg) < | ||
| std::tie(Other.VarIdx, Other.AssignedReg); | ||
| } | ||
|
|
||
| void dumpAssignmentChain(const llvm::MCRegisterInfo &RegInfo, | ||
| const AssignmentChain &Chain) { | ||
| for (const VariableAssignment &Assignment : Chain) { | ||
| llvm::outs() << llvm::format("(%d %s) ", Assignment.VarIdx, | ||
| RegInfo.getName(Assignment.AssignedReg)); | ||
| } | ||
| llvm::outs() << "\n"; | ||
| } | ||
|
|
||
| std::vector<AssignmentChain> | ||
| computeSequentialAssignmentChains(const llvm::MCRegisterInfo &RegInfo, | ||
| llvm::ArrayRef<Variable> Vars) { | ||
| using graph::Node; | ||
| graph::Graph Graph; | ||
|
|
||
| // Add register aliasing to the graph. | ||
| setupRegisterAliasing(RegInfo, Graph); | ||
|
|
||
| // Adding variables to the graph. | ||
| for (size_t I = 0, E = Vars.size(); I < E; ++I) { | ||
| const Variable &Var = Vars[I]; | ||
| const Node N = Node::Var(I); | ||
| if (Var.IsDef) { | ||
| Graph.connect(Node::In(), N); | ||
| for (const size_t Reg : Var.PossibleRegisters) | ||
| Graph.connect(N, Node::Reg(Reg)); | ||
| } | ||
| if (Var.IsUse) { | ||
| Graph.connect(N, Node::Out()); | ||
| for (const size_t Reg : Var.PossibleRegisters) | ||
| Graph.connect(Node::Reg(Reg), N); | ||
| } | ||
| } | ||
|
|
||
| // Find all possible dependency chains (aka all possible paths from In to Out | ||
| // node). | ||
| std::vector<AssignmentChain> AllChains; | ||
| for (;;) { | ||
| const auto Path = Graph.getPathFrom(Node::In(), Node::Out()); | ||
| if (Path.empty()) | ||
| break; | ||
| switch (Path.size()) { | ||
| case 0: | ||
| case 1: | ||
| case 2: | ||
| case 4: | ||
| assert(false && "Illegal state"); | ||
| break; | ||
| case 3: { // IN -> variable -> OUT | ||
| const size_t VarIdx = Path[1].varValue(); | ||
| for (size_t Reg : Vars[VarIdx].PossibleRegisters) { | ||
| AllChains.emplace_back(); | ||
| AllChains.back().emplace(VarIdx, Reg); | ||
| } | ||
| Graph.disconnect(Path[0], Path[1]); // IN -> variable | ||
| Graph.disconnect(Path[1], Path[2]); // variable -> OUT | ||
| break; | ||
| } | ||
| default: { // IN -> var1 -> Reg[...] -> var2 -> OUT | ||
| const size_t Last = Path.size() - 1; | ||
| const size_t Var1 = Path[1].varValue(); | ||
| const llvm::MCPhysReg Reg1 = Path[2].regValue(); | ||
| const llvm::MCPhysReg Reg2 = Path[Last - 2].regValue(); | ||
| const size_t Var2 = Path[Last - 1].varValue(); | ||
| AllChains.emplace_back(); | ||
| AllChains.back().emplace(Var1, Reg1); | ||
| AllChains.back().emplace(Var2, Reg2); | ||
| Graph.disconnect(Path[1], Path[2]); // Var1 -> Reg[0] | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return AllChains; | ||
| } | ||
|
|
||
| std::vector<llvm::MCPhysReg> | ||
| getRandomAssignment(llvm::ArrayRef<Variable> Vars, | ||
| llvm::ArrayRef<AssignmentChain> Chains, | ||
| const std::function<size_t(size_t)> &RandomIndexForSize) { | ||
| // Registers are initialized with 0 (aka NoRegister). | ||
| std::vector<llvm::MCPhysReg> Registers(Vars.size(), 0); | ||
| if (Chains.empty()) | ||
| return Registers; | ||
| // Pick one of the chains and set Registers that are fully constrained (have | ||
| // no degrees of freedom). | ||
| const size_t ChainIndex = RandomIndexForSize(Chains.size()); | ||
| for (const VariableAssignment Assignment : Chains[ChainIndex]) | ||
| Registers[Assignment.VarIdx] = Assignment.AssignedReg; | ||
| // Registers with remaining degrees of freedom are assigned randomly. | ||
| for (size_t I = 0, E = Vars.size(); I < E; ++I) { | ||
| llvm::MCPhysReg &Reg = Registers[I]; | ||
| const Variable &Var = Vars[I]; | ||
| const auto &PossibleRegisters = Var.PossibleRegisters; | ||
| if (Reg > 0 || PossibleRegisters.empty()) | ||
| continue; | ||
| Reg = PossibleRegisters[RandomIndexForSize(PossibleRegisters.size())]; | ||
| } | ||
| return Registers; | ||
| } | ||
|
|
||
| // Finds a matching register `reg` for variable `VarIdx` and sets | ||
| // `RegAssignments[r]` to `VarIdx`. Returns false if no matching can be found. | ||
| // `seen.count(r)` is 1 if register `reg` has been processed. | ||
| static bool findMatchingRegister( | ||
| llvm::ArrayRef<Variable> Vars, const size_t VarIdx, | ||
| std::unordered_set<llvm::MCPhysReg> &Seen, | ||
| std::unordered_map<llvm::MCPhysReg, size_t> &RegAssignments) { | ||
| for (const llvm::MCPhysReg Reg : Vars[VarIdx].PossibleRegisters) { | ||
| if (!Seen.count(Reg)) { | ||
| Seen.insert(Reg); // Mark `Reg` as seen. | ||
| // If `Reg` is not assigned to a variable, or if `Reg` was assigned to a | ||
| // variable which has an alternate possible register, assign `Reg` to | ||
| // variable `VarIdx`. Since `Reg` is marked as assigned in the above line, | ||
| // `RegAssignments[r]` in the following recursive call will not get | ||
| // assigned `Reg` again. | ||
| const auto AssignedVarIt = RegAssignments.find(Reg); | ||
| if (AssignedVarIt == RegAssignments.end() || | ||
| findMatchingRegister(Vars, AssignedVarIt->second, Seen, | ||
| RegAssignments)) { | ||
| RegAssignments[Reg] = VarIdx; | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // This is actually a maximum bipartite matching problem: | ||
| // https://en.wikipedia.org/wiki/Matching_(graph_theory)#Bipartite_matching | ||
| // The graph has variables on the left and registers on the right, with an edge | ||
| // between variable `I` and register `Reg` iff | ||
| // `Vars[I].PossibleRegisters.count(A)`. | ||
| // Note that a greedy approach won't work for cases like: | ||
| // Vars[0] PossibleRegisters={C,B} | ||
| // Vars[1] PossibleRegisters={A,B} | ||
| // Vars[2] PossibleRegisters={A,C} | ||
| // There is a feasible solution {0->B, 1->A, 2->C}, but the greedy solution is | ||
| // {0->C, 1->A, oops}. | ||
| std::vector<llvm::MCPhysReg> | ||
| getExclusiveAssignment(llvm::ArrayRef<Variable> Vars) { | ||
| // `RegAssignments[r]` is the variable id that was assigned register `Reg`. | ||
| std::unordered_map<llvm::MCPhysReg, size_t> RegAssignments; | ||
|
|
||
| for (size_t VarIdx = 0, E = Vars.size(); VarIdx < E; ++VarIdx) { | ||
| if (!Vars[VarIdx].IsReg) | ||
| continue; | ||
| std::unordered_set<llvm::MCPhysReg> Seen; | ||
| if (!findMatchingRegister(Vars, VarIdx, Seen, RegAssignments)) | ||
| return {}; // Infeasible. | ||
| } | ||
|
|
||
| std::vector<llvm::MCPhysReg> Registers(Vars.size(), 0); | ||
| for (const auto &RegVarIdx : RegAssignments) | ||
| Registers[RegVarIdx.second] = RegVarIdx.first; | ||
| return Registers; | ||
| } | ||
|
|
||
| std::vector<llvm::MCPhysReg> | ||
| getGreedyAssignment(llvm::ArrayRef<Variable> Vars) { | ||
| std::vector<llvm::MCPhysReg> Registers(Vars.size(), 0); | ||
| llvm::SmallSet<llvm::MCPhysReg, 8> Assigned; | ||
| for (size_t VarIdx = 0, E = Vars.size(); VarIdx < E; ++VarIdx) { | ||
| const auto &Var = Vars[VarIdx]; | ||
| if (!Var.IsReg) | ||
| continue; | ||
| if (Var.PossibleRegisters.empty()) | ||
| return {}; | ||
| // Try possible registers until an unassigned one is found. | ||
| for (const auto Reg : Var.PossibleRegisters) { | ||
| if (Assigned.insert(Reg).second) { | ||
| Registers[VarIdx] = Reg; | ||
| break; | ||
| } | ||
| } | ||
| // Fallback to first possible register. | ||
| if (Registers[VarIdx] == 0) | ||
| Registers[VarIdx] = Var.PossibleRegisters[0]; | ||
| } | ||
| return Registers; | ||
| } | ||
|
|
||
| llvm::MCInst generateMCInst(const llvm::MCInstrDesc &InstrInfo, | ||
| llvm::ArrayRef<Variable> Vars, | ||
| llvm::ArrayRef<llvm::MCPhysReg> VarRegs) { | ||
| const size_t NumOperands = InstrInfo.getNumOperands(); | ||
| llvm::SmallVector<llvm::MCPhysReg, 16> OperandToRegister(NumOperands, 0); | ||
|
|
||
| // We browse the variable and for each explicit operands we set the selected | ||
| // register in the OperandToRegister array. | ||
| for (size_t I = 0, E = Vars.size(); I < E; ++I) { | ||
| for (const size_t OpIndex : Vars[I].ExplicitOperands) { | ||
| OperandToRegister[OpIndex] = VarRegs[I]; | ||
| } | ||
| } | ||
|
|
||
| // Building the instruction. | ||
| llvm::MCInstBuilder Builder(InstrInfo.getOpcode()); | ||
| for (size_t I = 0, E = InstrInfo.getNumOperands(); I < E; ++I) { | ||
| const llvm::MCOperandInfo &OpInfo = InstrInfo.opInfo_begin()[I]; | ||
| switch (OpInfo.OperandType) { | ||
| case llvm::MCOI::OperandType::OPERAND_REGISTER: | ||
| Builder.addReg(OperandToRegister[I]); | ||
| break; | ||
| case llvm::MCOI::OperandType::OPERAND_IMMEDIATE: | ||
| Builder.addImm(1); | ||
| break; | ||
| default: | ||
| Builder.addOperand(llvm::MCOperand()); | ||
| } | ||
| } | ||
|
|
||
| return Builder; | ||
| } | ||
|
|
||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| //===-- InstructionSnippetGenerator.h ---------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// Defines helper classes to generate code snippets, in particular register | ||
| /// assignment. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_INSTRUCTIONSNIPPETGENERATOR_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_INSTRUCTIONSNIPPETGENERATOR_H | ||
|
|
||
| #include "OperandGraph.h" | ||
| #include "llvm/ADT/BitVector.h" | ||
| #include "llvm/ADT/SetVector.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/MC/MCInst.h" | ||
| #include "llvm/MC/MCInstrDesc.h" | ||
| #include "llvm/MC/MCRegisterInfo.h" | ||
| #include <vector> | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| // A Variable represents a set of possible values that we need to choose from. | ||
| // It may represent one or more explicit operands that are tied together, or one | ||
| // implicit operand. | ||
| class Variable final { | ||
| public: | ||
| bool IsUse = false; | ||
| bool IsDef = false; | ||
| bool IsReg = false; | ||
|
|
||
| // Lists all the explicit operand indices that are tied to this variable. | ||
| // Empty if Variable represents an implicit operand. | ||
| llvm::SmallVector<size_t, 8> ExplicitOperands; | ||
|
|
||
| // - In case of explicit operands, PossibleRegisters is the expansion of the | ||
| // operands's RegClass registers. Please note that tied together explicit | ||
| // operands share the same RegClass. | ||
| // - In case of implicit operands, PossibleRegisters is a singleton MCPhysReg. | ||
| llvm::SmallSetVector<llvm::MCPhysReg, 16> PossibleRegisters; | ||
|
|
||
| // If RegInfo is null, register names won't get resolved. | ||
| void print(llvm::raw_ostream &OS, const llvm::MCRegisterInfo *RegInfo) const; | ||
| }; | ||
|
|
||
| // Builds a model of implicit and explicit operands for InstrDesc into | ||
| // Variables. | ||
| llvm::SmallVector<Variable, 8> | ||
| getVariables(const llvm::MCRegisterInfo &RegInfo, | ||
| const llvm::MCInstrDesc &InstrDesc, | ||
| const llvm::BitVector &ReservedRegs); | ||
|
|
||
| // A simple object to represent a Variable assignement. | ||
| struct VariableAssignment { | ||
| VariableAssignment(size_t VarIdx, llvm::MCPhysReg AssignedReg); | ||
|
|
||
| size_t VarIdx; | ||
| llvm::MCPhysReg AssignedReg; | ||
|
|
||
| bool operator==(const VariableAssignment &) const; | ||
| bool operator<(const VariableAssignment &) const; | ||
| }; | ||
|
|
||
| // An AssignmentChain is a set of assignement realizing a dependency chain. | ||
| // We inherit from std::set to leverage uniqueness of elements. | ||
| using AssignmentChain = std::set<VariableAssignment>; | ||
|
|
||
| // Debug function to print an assignment chain. | ||
| void dumpAssignmentChain(const llvm::MCRegisterInfo &RegInfo, | ||
| const AssignmentChain &Chain); | ||
|
|
||
| // Inserts Variables into a graph representing register aliasing and finds all | ||
| // the possible dependency chains for this instruction, i.e. all the possible | ||
| // assignement of operands that would make execution of the instruction | ||
| // sequential. | ||
| std::vector<AssignmentChain> | ||
| computeSequentialAssignmentChains(const llvm::MCRegisterInfo &RegInfo, | ||
| llvm::ArrayRef<Variable> Vars); | ||
|
|
||
| // Selects a random configuration leading to a dependency chain. | ||
| // The result is a vector of the same size as `Vars`. | ||
| // `random_index_for_size` is a functor giving a random value in [0, arg[. | ||
| std::vector<llvm::MCPhysReg> | ||
| getRandomAssignment(llvm::ArrayRef<Variable> Vars, | ||
| llvm::ArrayRef<AssignmentChain> Chains, | ||
| const std::function<size_t(size_t)> &RandomIndexForSize); | ||
|
|
||
| // Finds an assignment of registers to variables such that no two variables are | ||
| // assigned the same register. | ||
| // The result is a vector of the same size as `Vars`, or `{}` if the | ||
| // assignment is not feasible. | ||
| std::vector<llvm::MCPhysReg> | ||
| getExclusiveAssignment(llvm::ArrayRef<Variable> Vars); | ||
|
|
||
| // Finds a greedy assignment of registers to variables. Each variable gets | ||
| // assigned the first possible register that is not already assigned to a | ||
| // previous variable. If there is no such register, the variable gets assigned | ||
| // the first possible register. | ||
| // The result is a vector of the same size as `Vars`, or `{}` if the | ||
| // assignment is not feasible. | ||
| std::vector<llvm::MCPhysReg> | ||
| getGreedyAssignment(llvm::ArrayRef<Variable> Vars); | ||
|
|
||
| // Generates an LLVM MCInst with the previously computed variables. | ||
| // Immediate values are set to 1. | ||
| llvm::MCInst generateMCInst(const llvm::MCInstrDesc &InstrDesc, | ||
| llvm::ArrayRef<Variable> Vars, | ||
| llvm::ArrayRef<llvm::MCPhysReg> VarRegs); | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_INSTRUCTIONSNIPPETGENERATOR_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| ;===- ./tools/llvm-exegesis/lib/LLVMBuild.txt ------------------*- Conf -*--===; | ||
| ; | ||
| ; The LLVM Compiler Infrastructure | ||
| ; | ||
| ; This file is distributed under the University of Illinois Open Source | ||
| ; License. See LICENSE.TXT for details. | ||
| ; | ||
| ;===------------------------------------------------------------------------===; | ||
| ; | ||
| ; This is an LLVMBuild description file for the components in this subdirectory. | ||
| ; | ||
| ; For more information on the LLVMBuild system, please see: | ||
| ; | ||
| ; http://llvm.org/docs/LLVMBuild.html | ||
| ; | ||
| ;===------------------------------------------------------------------------===; | ||
|
|
||
| [component_0] | ||
| type = Library | ||
| name = Exegesis | ||
| parent = Libraries | ||
| required_libraries = CodeGen ExecutionEngine MC MCJIT Object Support |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| //===-- Latency.cpp ---------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "Latency.h" | ||
| #include "BenchmarkResult.h" | ||
| #include "InstructionSnippetGenerator.h" | ||
| #include "PerfHelper.h" | ||
| #include "llvm/MC/MCInstrDesc.h" | ||
| #include "llvm/Support/Error.h" | ||
| #include <algorithm> | ||
| #include <random> | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| // FIXME: Handle memory, see PR36905. | ||
| static bool isInvalidOperand(const llvm::MCOperandInfo &OpInfo) { | ||
| switch (OpInfo.OperandType) { | ||
| default: | ||
| return true; | ||
| case llvm::MCOI::OPERAND_IMMEDIATE: | ||
| case llvm::MCOI::OPERAND_REGISTER: | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| static llvm::Error makeError(llvm::Twine Msg) { | ||
| return llvm::make_error<llvm::StringError>(Msg, | ||
| llvm::inconvertibleErrorCode()); | ||
| } | ||
|
|
||
| LatencyBenchmarkRunner::~LatencyBenchmarkRunner() = default; | ||
|
|
||
| const char *LatencyBenchmarkRunner::getDisplayName() const { return "latency"; } | ||
|
|
||
| llvm::Expected<std::vector<llvm::MCInst>> LatencyBenchmarkRunner::createCode( | ||
| const LLVMState &State, const unsigned OpcodeIndex, | ||
| const unsigned NumRepetitions, const JitFunctionContext &Context) const { | ||
| std::default_random_engine RandomEngine; | ||
| const auto GetRandomIndex = [&RandomEngine](size_t Size) { | ||
| assert(Size > 0 && "trying to get select a random element of an empty set"); | ||
| return std::uniform_int_distribution<>(0, Size - 1)(RandomEngine); | ||
| }; | ||
|
|
||
| const auto &InstrInfo = State.getInstrInfo(); | ||
| const auto &RegInfo = State.getRegInfo(); | ||
| const llvm::MCInstrDesc &InstrDesc = InstrInfo.get(OpcodeIndex); | ||
| for (const llvm::MCOperandInfo &OpInfo : InstrDesc.operands()) { | ||
| if (isInvalidOperand(OpInfo)) | ||
| return makeError("Only registers and immediates are supported"); | ||
| } | ||
|
|
||
| const auto Vars = getVariables(RegInfo, InstrDesc, Context.getReservedRegs()); | ||
| const std::vector<AssignmentChain> AssignmentChains = | ||
| computeSequentialAssignmentChains(RegInfo, Vars); | ||
| if (AssignmentChains.empty()) | ||
| return makeError("Unable to find a dependency chain."); | ||
| const std::vector<llvm::MCPhysReg> Regs = | ||
| getRandomAssignment(Vars, AssignmentChains, GetRandomIndex); | ||
| const llvm::MCInst Inst = generateMCInst(InstrDesc, Vars, Regs); | ||
| if (!State.canAssemble(Inst)) | ||
| return makeError("MCInst does not assemble."); | ||
| return std::vector<llvm::MCInst>(NumRepetitions, Inst); | ||
| } | ||
|
|
||
| std::vector<BenchmarkMeasure> | ||
| LatencyBenchmarkRunner::runMeasurements(const LLVMState &State, | ||
| const JitFunction &Function, | ||
| const unsigned NumRepetitions) const { | ||
| // Cycle measurements include some overhead from the kernel. Repeat the | ||
| // measure several times and take the minimum value. | ||
| constexpr const int NumMeasurements = 30; | ||
| int64_t MinLatency = std::numeric_limits<int64_t>::max(); | ||
| // FIXME: Read the perf event from the MCSchedModel (see PR36984). | ||
| const pfm::PerfEvent CyclesPerfEvent("UNHALTED_CORE_CYCLES"); | ||
| if (!CyclesPerfEvent.valid()) | ||
| llvm::report_fatal_error("invalid perf event 'UNHALTED_CORE_CYCLES'"); | ||
| for (size_t I = 0; I < NumMeasurements; ++I) { | ||
| pfm::Counter Counter(CyclesPerfEvent); | ||
| Counter.start(); | ||
| Function(); | ||
| Counter.stop(); | ||
| const int64_t Value = Counter.read(); | ||
| if (Value < MinLatency) | ||
| MinLatency = Value; | ||
| } | ||
| return {{"latency", static_cast<double>(MinLatency) / NumRepetitions}}; | ||
| } | ||
|
|
||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //===-- Latency.h -----------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// A BenchmarkRunner implementation to measure instruction latencies. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_LATENCY_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_LATENCY_H | ||
|
|
||
| #include "BenchmarkRunner.h" | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| class LatencyBenchmarkRunner : public BenchmarkRunner { | ||
| public: | ||
| ~LatencyBenchmarkRunner() override; | ||
|
|
||
| private: | ||
| const char *getDisplayName() const override; | ||
|
|
||
| llvm::Expected<std::vector<llvm::MCInst>> | ||
| createCode(const LLVMState &State, unsigned OpcodeIndex, | ||
| unsigned NumRepetitions, | ||
| const JitFunctionContext &Context) const override; | ||
|
|
||
| std::vector<BenchmarkMeasure> | ||
| runMeasurements(const LLVMState &State, const JitFunction &Function, | ||
| unsigned NumRepetitions) const override; | ||
| }; | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_LATENCY_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| //===-- LlvmState.cpp -------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "LlvmState.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/MC/MCCodeEmitter.h" | ||
| #include "llvm/MC/MCContext.h" | ||
| #include "llvm/MC/MCFixup.h" | ||
| #include "llvm/MC/MCObjectFileInfo.h" | ||
| #include "llvm/Support/TargetRegistry.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
| #include "llvm/Target/TargetMachine.h" | ||
| #include "llvm/Target/TargetOptions.h" | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| LLVMState::LLVMState() | ||
| : TheTriple(llvm::sys::getProcessTriple()), | ||
| CpuName(llvm::sys::getHostCPUName().str()) { | ||
| std::string Error; | ||
| TheTarget = llvm::TargetRegistry::lookupTarget(TheTriple, Error); | ||
| assert(TheTarget && "unknown target for host"); | ||
| SubtargetInfo.reset( | ||
| TheTarget->createMCSubtargetInfo(TheTriple, CpuName, Features)); | ||
| InstrInfo.reset(TheTarget->createMCInstrInfo()); | ||
| RegInfo.reset(TheTarget->createMCRegInfo(TheTriple)); | ||
| AsmInfo.reset(TheTarget->createMCAsmInfo(*RegInfo, TheTriple)); | ||
| } | ||
|
|
||
| std::unique_ptr<llvm::LLVMTargetMachine> | ||
| LLVMState::createTargetMachine() const { | ||
| const llvm::TargetOptions Options; | ||
| return std::unique_ptr<llvm::LLVMTargetMachine>( | ||
| static_cast<llvm::LLVMTargetMachine *>(TheTarget->createTargetMachine( | ||
| TheTriple, CpuName, Features, Options, llvm::Reloc::Model::Static))); | ||
| } | ||
|
|
||
| bool LLVMState::canAssemble(const llvm::MCInst &Inst) const { | ||
| llvm::MCObjectFileInfo ObjectFileInfo; | ||
| llvm::MCContext Context(AsmInfo.get(), RegInfo.get(), &ObjectFileInfo); | ||
| std::unique_ptr<const llvm::MCCodeEmitter> CodeEmitter( | ||
| TheTarget->createMCCodeEmitter(*InstrInfo, *RegInfo, Context)); | ||
| llvm::SmallVector<char, 16> Tmp; | ||
| llvm::raw_svector_ostream OS(Tmp); | ||
| llvm::SmallVector<llvm::MCFixup, 4> Fixups; | ||
| CodeEmitter->encodeInstruction(Inst, OS, Fixups, *SubtargetInfo); | ||
| return Tmp.size() > 0; | ||
| } | ||
|
|
||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| //===-- LlvmState.h ---------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_LLVMSTATE_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_LLVMSTATE_H | ||
|
|
||
| #include "llvm/MC/MCAsmInfo.h" | ||
| #include "llvm/MC/MCInst.h" | ||
| #include "llvm/MC/MCInstrInfo.h" | ||
| #include "llvm/MC/MCRegisterInfo.h" | ||
| #include "llvm/MC/MCSubtargetInfo.h" | ||
| #include "llvm/Target/TargetMachine.h" | ||
| #include <memory> | ||
| #include <string> | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| // An object to initialize LLVM and prepare objects needed to run the | ||
| // measurements. | ||
| class LLVMState { | ||
| public: | ||
| LLVMState(); | ||
|
|
||
| llvm::StringRef getTriple() const { return TheTriple; } | ||
| llvm::StringRef getCpuName() const { return CpuName; } | ||
| llvm::StringRef getFeatures() const { return Features; } | ||
|
|
||
| const llvm::MCInstrInfo &getInstrInfo() const { return *InstrInfo; } | ||
|
|
||
| const llvm::MCRegisterInfo &getRegInfo() const { return *RegInfo; } | ||
|
|
||
| const llvm::MCSubtargetInfo &getSubtargetInfo() const { | ||
| return *SubtargetInfo; | ||
| } | ||
|
|
||
| std::unique_ptr<llvm::LLVMTargetMachine> createTargetMachine() const; | ||
|
|
||
| bool canAssemble(const llvm::MCInst &mc_inst) const; | ||
|
|
||
| private: | ||
| std::string TheTriple; | ||
| std::string CpuName; | ||
| std::string Features; | ||
| const llvm::Target *TheTarget = nullptr; | ||
| std::unique_ptr<const llvm::MCSubtargetInfo> SubtargetInfo; | ||
| std::unique_ptr<const llvm::MCInstrInfo> InstrInfo; | ||
| std::unique_ptr<const llvm::MCRegisterInfo> RegInfo; | ||
| std::unique_ptr<const llvm::MCAsmInfo> AsmInfo; | ||
| }; | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_LLVMSTATE_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| //===-- OperandGraph.cpp ----------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "OperandGraph.h" | ||
| #include "llvm/MC/MCRegisterInfo.h" | ||
|
|
||
| namespace exegesis { | ||
| namespace graph { | ||
|
|
||
| void Node::dump(const llvm::MCRegisterInfo &RegInfo) const { | ||
| switch (type()) { | ||
| case NodeType::VARIABLE: | ||
| printf(" %d", varValue()); | ||
| break; | ||
| case NodeType::REG: | ||
| printf(" %s", RegInfo.getName(regValue())); | ||
| break; | ||
| case NodeType::IN: | ||
| printf(" IN"); | ||
| break; | ||
| case NodeType::OUT: | ||
| printf(" OUT"); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| NodeType Node::type() const { return first; } | ||
|
|
||
| int Node::regValue() const { | ||
| assert(first == NodeType::REG && "regValue() called on non-reg"); | ||
| return second; | ||
| } | ||
|
|
||
| int Node::varValue() const { | ||
| assert(first == NodeType::VARIABLE && "varValue() called on non-var"); | ||
| return second; | ||
| } | ||
|
|
||
| void Graph::connect(const Node From, const Node To) { | ||
| AdjacencyLists[From].insert(To); | ||
| } | ||
|
|
||
| void Graph::disconnect(const Node From, const Node To) { | ||
| AdjacencyLists[From].erase(To); | ||
| } | ||
|
|
||
| std::vector<Node> Graph::getPathFrom(const Node From, const Node To) const { | ||
| std::vector<Node> Path; | ||
| NodeSet Seen; | ||
| dfs(From, To, Path, Seen); | ||
| return Path; | ||
| } | ||
|
|
||
| // DFS is implemented recursively, this is fine as graph size is small (~250 | ||
| // nodes, ~200 edges, longuest path depth < 10). | ||
| bool Graph::dfs(const Node Current, const Node Sentinel, | ||
| std::vector<Node> &Path, NodeSet &Seen) const { | ||
| Path.push_back(Current); | ||
| Seen.insert(Current); | ||
| if (Current == Sentinel) | ||
| return true; | ||
| if (AdjacencyLists.count(Current)) { | ||
| for (const Node Next : AdjacencyLists.find(Current)->second) { | ||
| if (Seen.count(Next)) | ||
| continue; | ||
| if (dfs(Next, Sentinel, Path, Seen)) | ||
| return true; | ||
| } | ||
| } | ||
| Path.pop_back(); | ||
| return false; | ||
| } | ||
|
|
||
| // For each Register Units we walk up their parents. | ||
| // Let's take the case of the A register family: | ||
| // | ||
| // RAX | ||
| // ^ | ||
| // EAX | ||
| // ^ | ||
| // AX | ||
| // ^ ^ | ||
| // AH AL | ||
| // | ||
| // Register Units are AH and AL. | ||
| // Walking them up gives the following lists: | ||
| // AH->AX->EAX->RAX and AL->AX->EAX->RAX | ||
| // When walking the lists we add connect current to parent both ways leading to | ||
| // the following connections: | ||
| // | ||
| // AL<->AX, AH<->AX, AX<->EAX, EAX<->RAX | ||
| // We repeat this process for all Unit Registers to cover all connections. | ||
| void setupRegisterAliasing(const llvm::MCRegisterInfo &RegInfo, | ||
| Graph &TheGraph) { | ||
| using SuperItr = llvm::MCSuperRegIterator; | ||
| for (size_t Reg = 0, E = RegInfo.getNumRegUnits(); Reg < E; ++Reg) { | ||
| size_t Current = Reg; | ||
| for (SuperItr Super(Reg, &RegInfo); Super.isValid(); ++Super) { | ||
| const Node A = Node::Reg(Current); | ||
| const Node B = Node::Reg(*Super); | ||
| TheGraph.connect(A, B); | ||
| TheGraph.connect(B, A); | ||
| Current = *Super; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } // namespace graph | ||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| //===-- OperandGraph.h ------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// A collection of tools to model register aliasing and instruction operand. | ||
| /// This is used to find an aliasing between the input and output registers of | ||
| /// an instruction. It allows us to repeat an instruction and make sure that | ||
| /// successive instances are executed sequentially. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_OPERANDGRAPH_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_OPERANDGRAPH_H | ||
|
|
||
| #include "llvm/MC/MCRegisterInfo.h" | ||
| #include <map> | ||
| #include <set> | ||
| #include <tuple> | ||
| #include <vector> | ||
|
|
||
| namespace exegesis { | ||
| namespace graph { | ||
|
|
||
| enum class NodeType { | ||
| VARIABLE, // An set of "tied together operands" to resolve. | ||
| REG, // A particular register. | ||
| IN, // The input node. | ||
| OUT // The output node. | ||
| }; | ||
|
|
||
| // A Node in the graph, it has a type and an int value. | ||
| struct Node : public std::pair<NodeType, int> { | ||
| using std::pair<NodeType, int>::pair; | ||
|
|
||
| static Node Reg(int Value) { return {NodeType::REG, Value}; } | ||
| static Node Var(int Value) { return {NodeType::VARIABLE, Value}; } | ||
| static Node In() { return {NodeType::IN, 0}; } | ||
| static Node Out() { return {NodeType::OUT, 0}; } | ||
|
|
||
| NodeType type() const; | ||
| int regValue() const; // checks that type==REG and returns value. | ||
| int varValue() const; // checks that type==VARIABLE and returns value. | ||
|
|
||
| void dump(const llvm::MCRegisterInfo &RegInfo) const; | ||
| }; | ||
|
|
||
| // Graph represents the connectivity of registers for a particular instruction. | ||
| // This object is used to select registers that would create a dependency chain | ||
| // between instruction's input and output. | ||
| struct Graph { | ||
| public: | ||
| void connect(const Node From, const Node To); | ||
| void disconnect(const Node From, const Node To); | ||
|
|
||
| // Tries to find a path between 'From' and 'To' nodes. | ||
| // Returns empty if no path is found. | ||
| std::vector<Node> getPathFrom(const Node From, const Node To) const; | ||
|
|
||
| private: | ||
| // We use std::set to keep the implementation simple, using an unordered_set | ||
| // requires the definition of a hasher. | ||
| using NodeSet = std::set<Node>; | ||
|
|
||
| // Performs a Depth First Search from 'current' node up until 'sentinel' node | ||
| // is found. 'path' is the recording of the traversed nodes, 'seen' is the | ||
| // collection of nodes seen so far. | ||
| bool dfs(const Node Current, const Node Sentinel, std::vector<Node> &Path, | ||
| NodeSet &Seen) const; | ||
|
|
||
| // We use std::map to keep the implementation simple, using an unordered_map | ||
| // requires the definition of a hasher. | ||
| std::map<Node, NodeSet> AdjacencyLists; | ||
| }; | ||
|
|
||
| // Add register nodes to graph and connect them when they alias. Connection is | ||
| // both ways. | ||
| void setupRegisterAliasing(const llvm::MCRegisterInfo &RegInfo, | ||
| Graph &TheGraph); | ||
|
|
||
| } // namespace graph | ||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_OPERANDGRAPH_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| //===-- PerfHelper.cpp ------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "PerfHelper.h" | ||
| #include "llvm/Config/config.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
| #ifdef HAVE_LIBPFM | ||
| #include "perfmon/perf_event.h" | ||
| #include "perfmon/pfmlib.h" | ||
| #include "perfmon/pfmlib_perf_event.h" | ||
| #endif | ||
|
|
||
| namespace exegesis { | ||
| namespace pfm { | ||
|
|
||
| #ifdef HAVE_LIBPFM | ||
| static bool isPfmError(int Code) { return Code != PFM_SUCCESS; } | ||
| #endif | ||
|
|
||
| bool pfmInitialize() { | ||
| #ifdef HAVE_LIBPFM | ||
| return isPfmError(pfm_initialize()); | ||
| #else | ||
| return true; | ||
| #endif | ||
| } | ||
|
|
||
| void pfmTerminate() { | ||
| #ifdef HAVE_LIBPFM | ||
| pfm_terminate(); | ||
| #endif | ||
| } | ||
|
|
||
| PerfEvent::~PerfEvent() { | ||
| #ifdef HAVE_LIBPFM | ||
| delete Attr; | ||
| ; | ||
| #endif | ||
| } | ||
|
|
||
| PerfEvent::PerfEvent(PerfEvent &&Other) | ||
| : EventString(std::move(Other.EventString)), | ||
| FullQualifiedEventString(std::move(Other.FullQualifiedEventString)), | ||
| Attr(Other.Attr) { | ||
| Other.Attr = nullptr; | ||
| } | ||
|
|
||
| PerfEvent::PerfEvent(llvm::StringRef PfmEventString) | ||
| : EventString(PfmEventString.str()), Attr(nullptr) { | ||
| #ifdef HAVE_LIBPFM | ||
| char *Fstr = nullptr; | ||
| pfm_perf_encode_arg_t Arg = {}; | ||
| Attr = new perf_event_attr(); | ||
| Arg.attr = Attr; | ||
| Arg.fstr = &Fstr; | ||
| Arg.size = sizeof(pfm_perf_encode_arg_t); | ||
| const int Result = pfm_get_os_event_encoding(EventString.c_str(), PFM_PLM3, | ||
| PFM_OS_PERF_EVENT, &Arg); | ||
| if (isPfmError(Result)) { | ||
| // We don't know beforehand which counters are available (e.g. 6 uops ports | ||
| // on Sandybridge but 8 on Haswell) so we report the missing counter without | ||
| // crashing. | ||
| llvm::errs() << pfm_strerror(Result) << " - cannot create event " | ||
| << EventString; | ||
| } | ||
| if (Fstr) { | ||
| FullQualifiedEventString = Fstr; | ||
| free(Fstr); | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
| llvm::StringRef PerfEvent::name() const { return EventString; } | ||
|
|
||
| bool PerfEvent::valid() const { return !FullQualifiedEventString.empty(); } | ||
|
|
||
| const perf_event_attr *PerfEvent::attribute() const { return Attr; } | ||
|
|
||
| llvm::StringRef PerfEvent::getPfmEventString() const { | ||
| return FullQualifiedEventString; | ||
| } | ||
|
|
||
| #ifdef HAVE_LIBPFM | ||
| Counter::Counter(const PerfEvent &Event) { | ||
| const pid_t Pid = 0; // measure current process/thread. | ||
| const int Cpu = -1; // measure any processor. | ||
| const int GroupFd = -1; // no grouping of counters. | ||
| const uint32_t Flags = 0; | ||
| perf_event_attr AttrCopy = *Event.attribute(); | ||
| FileDescriptor = perf_event_open(&AttrCopy, Pid, Cpu, GroupFd, Flags); | ||
| assert(FileDescriptor != -1 && | ||
| "Unable to open event, make sure your kernel allows user space perf " | ||
| "monitoring."); | ||
| } | ||
|
|
||
| Counter::~Counter() { close(FileDescriptor); } | ||
|
|
||
| void Counter::start() { ioctl(FileDescriptor, PERF_EVENT_IOC_RESET, 0); } | ||
|
|
||
| void Counter::stop() { ioctl(FileDescriptor, PERF_EVENT_IOC_DISABLE, 0); } | ||
|
|
||
| int64_t Counter::read() const { | ||
| int64_t Count = 0; | ||
| ::read(FileDescriptor, &Count, sizeof(Count)); | ||
| return Count; | ||
| } | ||
|
|
||
| #else | ||
|
|
||
| Counter::Counter(const PerfEvent &Event) : FileDescriptor(-1) {} | ||
|
|
||
| Counter::~Counter() = default; | ||
|
|
||
| void Counter::start() {} | ||
|
|
||
| void Counter::stop() {} | ||
|
|
||
| int64_t Counter::read() const { return 42; } | ||
|
|
||
| #endif | ||
|
|
||
| } // namespace pfm | ||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| //===-- PerfHelper.h ------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// Helpers for measuring perf events. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_PERFHELPER_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_PERFHELPER_H | ||
|
|
||
| #include "llvm/ADT/ArrayRef.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include <functional> | ||
| #include <memory> | ||
|
|
||
| struct perf_event_attr; | ||
|
|
||
| namespace exegesis { | ||
| namespace pfm { | ||
|
|
||
| // Returns true on error. | ||
| bool pfmInitialize(); | ||
| void pfmTerminate(); | ||
|
|
||
| // Retrieves the encoding for the event described by pfm_event_string. | ||
| // NOTE: pfm_initialize() must be called before creating PerfEvent objects. | ||
| class PerfEvent { | ||
| public: | ||
| // http://perfmon2.sourceforge.net/manv4/libpfm.html | ||
| // Events are expressed as strings. e.g. "INSTRUCTION_RETIRED" | ||
| explicit PerfEvent(llvm::StringRef pfm_event_string); | ||
|
|
||
| PerfEvent(const PerfEvent &) = delete; | ||
| PerfEvent(PerfEvent &&other); | ||
| ~PerfEvent(); | ||
|
|
||
| // The pfm_event_string passed at construction time. | ||
| llvm::StringRef name() const; | ||
|
|
||
| // Whether the event was successfully created. | ||
| bool valid() const; | ||
|
|
||
| // The encoded event to be passed to the Kernel. | ||
| const perf_event_attr *attribute() const; | ||
|
|
||
| // The fully qualified name for the event. | ||
| // e.g. "snb_ep::INSTRUCTION_RETIRED:e=0:i=0:c=0:t=0:u=1:k=0:mg=0:mh=1" | ||
| llvm::StringRef getPfmEventString() const; | ||
|
|
||
| private: | ||
| const std::string EventString; | ||
| std::string FullQualifiedEventString; | ||
| perf_event_attr *Attr; | ||
| }; | ||
|
|
||
| // Uses a valid PerfEvent to configure the Kernel so we can measure the | ||
| // underlying event. | ||
| struct Counter { | ||
| // event: the PerfEvent to measure. | ||
| explicit Counter(const PerfEvent &event); | ||
|
|
||
| Counter(const Counter &) = delete; | ||
| Counter(Counter &&other) = default; | ||
|
|
||
| ~Counter(); | ||
|
|
||
| void start(); // Starts the measurement of the event. | ||
| void stop(); // Stops the measurement of the event. | ||
| int64_t read() const; // Return the current value of the counter. | ||
|
|
||
| private: | ||
| int FileDescriptor = -1; | ||
| }; | ||
|
|
||
| // Helper to measure a list of PerfEvent for a particular function. | ||
| // callback is called for each successful measure (PerfEvent needs to be valid). | ||
| template <typename Function> | ||
| void Measure( | ||
| llvm::ArrayRef<PerfEvent> Events, | ||
| const std::function<void(const PerfEvent &Event, int64_t Value)> &Callback, | ||
| Function Fn) { | ||
| for (const auto &Event : Events) { | ||
| if (!Event.valid()) | ||
| continue; | ||
| Counter Cnt(Event); | ||
| Cnt.start(); | ||
| Fn(); | ||
| Cnt.stop(); | ||
| Callback(Event, Cnt.read()); | ||
| } | ||
| } | ||
|
|
||
| } // namespace pfm | ||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_PERFHELPER_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| //===-- Uops.cpp ------------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "Uops.h" | ||
| #include "BenchmarkResult.h" | ||
| #include "InstructionSnippetGenerator.h" | ||
| #include "PerfHelper.h" | ||
| #include "llvm/ADT/StringExtras.h" | ||
| #include "llvm/MC/MCInstrDesc.h" | ||
| #include "llvm/MC/MCSchedule.h" | ||
| #include "llvm/Support/Error.h" | ||
| #include <algorithm> | ||
| #include <random> | ||
| #include <unordered_map> | ||
| #include <unordered_set> | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| // FIXME: Handle memory (see PR36906) | ||
| static bool isInvalidOperand(const llvm::MCOperandInfo &OpInfo) { | ||
| switch (OpInfo.OperandType) { | ||
| default: | ||
| return true; | ||
| case llvm::MCOI::OPERAND_IMMEDIATE: | ||
| case llvm::MCOI::OPERAND_REGISTER: | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| static llvm::Error makeError(llvm::Twine Msg) { | ||
| return llvm::make_error<llvm::StringError>(Msg, | ||
| llvm::inconvertibleErrorCode()); | ||
| } | ||
|
|
||
| // FIXME: Read the counter names from the ProcResourceUnits when PR36984 is | ||
| // fixed. | ||
| static const std::string *getEventNameFromProcResName(const char *ProcResName) { | ||
| static const std::unordered_map<std::string, std::string> Entries = { | ||
| {"SBPort0", "UOPS_DISPATCHED_PORT:PORT_0"}, | ||
| {"SBPort1", "UOPS_DISPATCHED_PORT:PORT_1"}, | ||
| {"SBPort4", "UOPS_DISPATCHED_PORT:PORT_4"}, | ||
| {"SBPort5", "UOPS_DISPATCHED_PORT:PORT_5"}, | ||
| {"HWPort0", "UOPS_DISPATCHED_PORT:PORT_0"}, | ||
| {"HWPort1", "UOPS_DISPATCHED_PORT:PORT_1"}, | ||
| {"HWPort2", "UOPS_DISPATCHED_PORT:PORT_2"}, | ||
| {"HWPort3", "UOPS_DISPATCHED_PORT:PORT_3"}, | ||
| {"HWPort4", "UOPS_DISPATCHED_PORT:PORT_4"}, | ||
| {"HWPort5", "UOPS_DISPATCHED_PORT:PORT_5"}, | ||
| {"HWPort6", "UOPS_DISPATCHED_PORT:PORT_6"}, | ||
| {"HWPort7", "UOPS_DISPATCHED_PORT:PORT_7"}, | ||
| {"SKLPort0", "UOPS_DISPATCHED_PORT:PORT_0"}, | ||
| {"SKLPort1", "UOPS_DISPATCHED_PORT:PORT_1"}, | ||
| {"SKLPort2", "UOPS_DISPATCHED_PORT:PORT_2"}, | ||
| {"SKLPort3", "UOPS_DISPATCHED_PORT:PORT_3"}, | ||
| {"SKLPort4", "UOPS_DISPATCHED_PORT:PORT_4"}, | ||
| {"SKLPort5", "UOPS_DISPATCHED_PORT:PORT_5"}, | ||
| {"SKLPort6", "UOPS_DISPATCHED_PORT:PORT_6"}, | ||
| {"SKXPort7", "UOPS_DISPATCHED_PORT:PORT_7"}, | ||
| {"SKXPort0", "UOPS_DISPATCHED_PORT:PORT_0"}, | ||
| {"SKXPort1", "UOPS_DISPATCHED_PORT:PORT_1"}, | ||
| {"SKXPort2", "UOPS_DISPATCHED_PORT:PORT_2"}, | ||
| {"SKXPort3", "UOPS_DISPATCHED_PORT:PORT_3"}, | ||
| {"SKXPort4", "UOPS_DISPATCHED_PORT:PORT_4"}, | ||
| {"SKXPort5", "UOPS_DISPATCHED_PORT:PORT_5"}, | ||
| {"SKXPort6", "UOPS_DISPATCHED_PORT:PORT_6"}, | ||
| {"SKXPort7", "UOPS_DISPATCHED_PORT:PORT_7"}, | ||
| }; | ||
| const auto It = Entries.find(ProcResName); | ||
| return It == Entries.end() ? nullptr : &It->second; | ||
| } | ||
|
|
||
| static std::vector<llvm::MCInst> generateIndependentAssignments( | ||
| const LLVMState &State, const llvm::MCInstrDesc &InstrDesc, | ||
| llvm::SmallVector<Variable, 8> Vars, int MaxAssignments) { | ||
| std::unordered_set<llvm::MCPhysReg> IsUsedByAnyVar; | ||
| for (const Variable &Var : Vars) { | ||
| if (Var.IsUse) { | ||
| IsUsedByAnyVar.insert(Var.PossibleRegisters.begin(), | ||
| Var.PossibleRegisters.end()); | ||
| } | ||
| } | ||
|
|
||
| std::vector<llvm::MCInst> Pattern; | ||
| for (int A = 0; A < MaxAssignments; ++A) { | ||
| // FIXME: This is a bit pessimistic. We should get away with an | ||
| // assignment that ensures that the set of assigned registers for uses and | ||
| // the set of assigned registers for defs do not intersect (registers | ||
| // for uses (resp defs) do not have to be all distinct). | ||
| const std::vector<llvm::MCPhysReg> Regs = getExclusiveAssignment(Vars); | ||
| if (Regs.empty()) | ||
| break; | ||
| // Remove all assigned registers defs that are used by at least one other | ||
| // variable from the list of possible variable registers. This ensures that | ||
| // we never create a RAW hazard that would lead to serialization. | ||
| for (size_t I = 0, E = Vars.size(); I < E; ++I) { | ||
| llvm::MCPhysReg Reg = Regs[I]; | ||
| if (Vars[I].IsDef && IsUsedByAnyVar.count(Reg)) { | ||
| Vars[I].PossibleRegisters.remove(Reg); | ||
| } | ||
| } | ||
| // Create an MCInst and check assembly. | ||
| llvm::MCInst Inst = generateMCInst(InstrDesc, Vars, Regs); | ||
| if (!State.canAssemble(Inst)) | ||
| continue; | ||
| Pattern.push_back(std::move(Inst)); | ||
| } | ||
| return Pattern; | ||
| } | ||
|
|
||
| UopsBenchmarkRunner::~UopsBenchmarkRunner() = default; | ||
|
|
||
| const char *UopsBenchmarkRunner::getDisplayName() const { return "uops"; } | ||
|
|
||
| llvm::Expected<std::vector<llvm::MCInst>> UopsBenchmarkRunner::createCode( | ||
| const LLVMState &State, const unsigned OpcodeIndex, | ||
| const unsigned NumRepetitions, const JitFunctionContext &Context) const { | ||
| const auto &InstrInfo = State.getInstrInfo(); | ||
| const auto &RegInfo = State.getRegInfo(); | ||
| const llvm::MCInstrDesc &InstrDesc = InstrInfo.get(OpcodeIndex); | ||
| for (const llvm::MCOperandInfo &OpInfo : InstrDesc.operands()) { | ||
| if (isInvalidOperand(OpInfo)) | ||
| return makeError("Only registers and immediates are supported"); | ||
| } | ||
|
|
||
| // FIXME: Load constants into registers (e.g. with fld1) to not break | ||
| // instructions like x87. | ||
|
|
||
| // Ideally we would like the only limitation on executing uops to be the issue | ||
| // ports. Maximizing port pressure increases the likelihood that the load is | ||
| // distributed evenly across possible ports. | ||
|
|
||
| // To achieve that, one approach is to generate instructions that do not have | ||
| // data dependencies between them. | ||
| // | ||
| // For some instructions, this is trivial: | ||
| // mov rax, qword ptr [rsi] | ||
| // mov rax, qword ptr [rsi] | ||
| // mov rax, qword ptr [rsi] | ||
| // mov rax, qword ptr [rsi] | ||
| // For the above snippet, haswell just renames rax four times and executes the | ||
| // four instructions two at a time on P23 and P0126. | ||
| // | ||
| // For some instructions, we just need to make sure that the source is | ||
| // different from the destination. For example, IDIV8r reads from GPR and | ||
| // writes to AX. We just need to ensure that the variable is assigned a | ||
| // register which is different from AX: | ||
| // idiv bx | ||
| // idiv bx | ||
| // idiv bx | ||
| // idiv bx | ||
| // The above snippet will be able to fully saturate the ports, while the same | ||
| // with ax would issue one uop every `latency(IDIV8r)` cycles. | ||
| // | ||
| // Some instructions make this harder because they both read and write from | ||
| // the same register: | ||
| // inc rax | ||
| // inc rax | ||
| // inc rax | ||
| // inc rax | ||
| // This has a data dependency from each instruction to the next, limit the | ||
| // number of instructions that can be issued in parallel. | ||
| // It turns out that this is not a big issue on recent Intel CPUs because they | ||
| // have heuristics to balance port pressure. In the snippet above, subsequent | ||
| // instructions will end up evenly distributed on {P0,P1,P5,P6}, but some CPUs | ||
| // might end up executing them all on P0 (just because they can), or try | ||
| // avoiding P5 because it's usually under high pressure from vector | ||
| // instructions. | ||
| // This issue is even more important for high-latency instructions because | ||
| // they increase the idle time of the CPU, e.g. : | ||
| // imul rax, rbx | ||
| // imul rax, rbx | ||
| // imul rax, rbx | ||
| // imul rax, rbx | ||
| // | ||
| // To avoid that, we do the renaming statically by generating as many | ||
| // independent exclusive assignments as possible (until all possible registers | ||
| // are exhausted) e.g.: | ||
| // imul rax, rbx | ||
| // imul rcx, rbx | ||
| // imul rdx, rbx | ||
| // imul r8, rbx | ||
| // | ||
| // Some instruction even make the above static renaming impossible because | ||
| // they implicitly read and write from the same operand, e.g. ADC16rr reads | ||
| // and writes from EFLAGS. | ||
| // In that case we just use a greedy register assignment and hope for the | ||
| // best. | ||
|
|
||
| const auto Vars = getVariables(RegInfo, InstrDesc, Context.getReservedRegs()); | ||
|
|
||
| // Generate as many independent exclusive assignments as possible. | ||
| constexpr const int MaxStaticRenames = 20; | ||
| std::vector<llvm::MCInst> Pattern = | ||
| generateIndependentAssignments(State, InstrDesc, Vars, MaxStaticRenames); | ||
| if (Pattern.empty()) { | ||
| // We don't even have a single exclusive assignment, fallback to a greedy | ||
| // assignment. | ||
| // FIXME: Tell the user about this decision to help debugging. | ||
| const std::vector<llvm::MCPhysReg> Regs = getGreedyAssignment(Vars); | ||
| if (!Vars.empty() && Regs.empty()) | ||
| return makeError("No feasible greedy assignment"); | ||
| llvm::MCInst Inst = generateMCInst(InstrDesc, Vars, Regs); | ||
| if (!State.canAssemble(Inst)) | ||
| return makeError("Cannot assemble greedy assignment"); | ||
| Pattern.push_back(std::move(Inst)); | ||
| } | ||
|
|
||
| // Generate repetitions of the pattern until benchmark_iterations is reached. | ||
| std::vector<llvm::MCInst> Result; | ||
| Result.reserve(NumRepetitions); | ||
| for (unsigned I = 0; I < NumRepetitions; ++I) | ||
| Result.push_back(Pattern[I % Pattern.size()]); | ||
| return Result; | ||
| } | ||
|
|
||
| std::vector<BenchmarkMeasure> | ||
| UopsBenchmarkRunner::runMeasurements(const LLVMState &State, | ||
| const JitFunction &Function, | ||
| const unsigned NumRepetitions) const { | ||
| const auto &SchedModel = State.getSubtargetInfo().getSchedModel(); | ||
|
|
||
| std::vector<BenchmarkMeasure> Result; | ||
| for (unsigned ProcResIdx = 1; | ||
| ProcResIdx < SchedModel.getNumProcResourceKinds(); ++ProcResIdx) { | ||
| const llvm::MCProcResourceDesc &ProcRes = | ||
| *SchedModel.getProcResource(ProcResIdx); | ||
| const std::string *const EventName = | ||
| getEventNameFromProcResName(ProcRes.Name); | ||
| if (!EventName) | ||
| continue; | ||
| pfm::Counter Counter{pfm::PerfEvent(*EventName)}; | ||
| Counter.start(); | ||
| Function(); | ||
| Counter.stop(); | ||
| Result.push_back({llvm::itostr(ProcResIdx), | ||
| static_cast<double>(Counter.read()) / NumRepetitions, | ||
| ProcRes.Name}); | ||
| } | ||
| return Result; | ||
| } | ||
|
|
||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //===-- Uops.h --------------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// A BenchmarkRunner implementation to measure uop decomposition. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_UOPS_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_UOPS_H | ||
|
|
||
| #include "BenchmarkRunner.h" | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| class UopsBenchmarkRunner : public BenchmarkRunner { | ||
| public: | ||
| ~UopsBenchmarkRunner() override; | ||
|
|
||
| private: | ||
| const char *getDisplayName() const override; | ||
|
|
||
| llvm::Expected<std::vector<llvm::MCInst>> | ||
| createCode(const LLVMState &State, unsigned OpcodeIndex, | ||
| unsigned NumRepetitions, | ||
| const JitFunctionContext &Context) const override; | ||
|
|
||
| std::vector<BenchmarkMeasure> | ||
| runMeasurements(const LLVMState &State, const JitFunction &Function, | ||
| unsigned NumRepetitions) const override; | ||
| }; | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_UOPS_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| //===-- X86.cpp --------------------------------------------------*- C++-*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "X86.h" | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| static llvm::Error makeError(llvm::Twine Msg) { | ||
| return llvm::make_error<llvm::StringError>(Msg, | ||
| llvm::inconvertibleErrorCode()); | ||
| } | ||
|
|
||
| X86Filter::~X86Filter() = default; | ||
|
|
||
| // Test whether we can generate a snippet for this instruction. | ||
| llvm::Error X86Filter::shouldRun(const LLVMState &State, | ||
| const unsigned Opcode) const { | ||
| const auto &InstrInfo = State.getInstrInfo(); | ||
| const llvm::MCInstrDesc &InstrDesc = InstrInfo.get(Opcode); | ||
| if (InstrDesc.isBranch() || InstrDesc.isIndirectBranch()) | ||
| return makeError("Unsupported opcode: isBranch/isIndirectBranch"); | ||
| if (InstrDesc.isCall() || InstrDesc.isReturn()) | ||
| return makeError("Unsupported opcode: isCall/isReturn"); | ||
| const auto OpcodeName = InstrInfo.getName(Opcode); | ||
| if (OpcodeName.startswith("POPF") || OpcodeName.startswith("PUSHF") || | ||
| OpcodeName.startswith("ADJCALLSTACK")) { | ||
| return makeError("Unsupported opcode: Push/Pop/AdjCallStack"); | ||
| } | ||
| return llvm::ErrorSuccess(); | ||
| } | ||
|
|
||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| //===-- X86.h ---------------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// X86 target-specific setup. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_TOOLS_LLVM_EXEGESIS_X86_H | ||
| #define LLVM_TOOLS_LLVM_EXEGESIS_X86_H | ||
|
|
||
| #include "BenchmarkRunner.h" | ||
| #include "LlvmState.h" | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| class X86Filter : public BenchmarkRunner::InstructionFilter { | ||
| public: | ||
| ~X86Filter() override; | ||
|
|
||
| llvm::Error shouldRun(const LLVMState &State, unsigned Opcode) const override; | ||
| }; | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| #endif // LLVM_TOOLS_LLVM_EXEGESIS_X86_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| //===-- llvm-exegesis.cpp ---------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// Measures execution properties (latencies/uops) of an instruction. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "lib/BenchmarkResult.h" | ||
| #include "lib/BenchmarkRunner.h" | ||
| #include "lib/Latency.h" | ||
| #include "lib/LlvmState.h" | ||
| #include "lib/PerfHelper.h" | ||
| #include "lib/Uops.h" | ||
| #include "lib/X86.h" | ||
| #include "llvm/ADT/StringExtras.h" | ||
| #include "llvm/ADT/Twine.h" | ||
| #include "llvm/MC/MCInstBuilder.h" | ||
| #include "llvm/MC/MCRegisterInfo.h" | ||
| #include "llvm/Support/CommandLine.h" | ||
| #include "llvm/Support/Path.h" | ||
| #include <algorithm> | ||
| #include <random> | ||
| #include <string> | ||
| #include <unordered_map> | ||
|
|
||
| static llvm::cl::opt<unsigned> | ||
| OpcodeIndex("opcode-index", llvm::cl::desc("opcode to measure, by index"), | ||
| llvm::cl::init(0)); | ||
|
|
||
| static llvm::cl::opt<std::string> | ||
| OpcodeName("opcode-name", llvm::cl::desc("opcode to measure, by name"), | ||
| llvm::cl::init("")); | ||
|
|
||
| enum class BenchmarkModeE { Latency, Uops }; | ||
| static llvm::cl::opt<BenchmarkModeE> | ||
| BenchmarkMode("benchmark-mode", llvm::cl::desc("the benchmark mode to run"), | ||
| llvm::cl::values(clEnumValN(BenchmarkModeE::Latency, | ||
| "latency", "Instruction Latency"), | ||
| clEnumValN(BenchmarkModeE::Uops, "uops", | ||
| "Uop Decomposition"))); | ||
|
|
||
| static llvm::cl::opt<unsigned> | ||
| NumRepetitions("num-repetitions", | ||
| llvm::cl::desc("number of time to repeat the asm snippet"), | ||
| llvm::cl::init(10000)); | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| void main() { | ||
| if (OpcodeName.empty() == (OpcodeIndex == 0)) { | ||
| llvm::report_fatal_error( | ||
| "please provide one and only one of 'opcode-index' or 'opcode-name' "); | ||
| } | ||
|
|
||
| LLVMInitializeX86Target(); | ||
| LLVMInitializeX86TargetInfo(); | ||
| LLVMInitializeX86TargetMC(); | ||
| LLVMInitializeX86AsmPrinter(); | ||
|
|
||
| // FIXME: Target-specific filter. | ||
| X86Filter Filter; | ||
|
|
||
| const LLVMState State; | ||
|
|
||
| unsigned Opcode = OpcodeIndex; | ||
| if (Opcode == 0) { | ||
| // Resolve opcode name -> opcode. | ||
| for (unsigned I = 0, E = State.getInstrInfo().getNumOpcodes(); I < E; ++I) { | ||
| if (State.getInstrInfo().getName(I) == OpcodeName) { | ||
| Opcode = I; | ||
| break; | ||
| } | ||
| } | ||
| if (Opcode == 0) { | ||
| llvm::report_fatal_error( | ||
| llvm::Twine("unknown opcode ").concat(OpcodeName)); | ||
| } | ||
| } | ||
|
|
||
| std::unique_ptr<BenchmarkRunner> Runner; | ||
| switch (BenchmarkMode) { | ||
| case BenchmarkModeE::Latency: | ||
| Runner = llvm::make_unique<LatencyBenchmarkRunner>(); | ||
| break; | ||
| case BenchmarkModeE::Uops: | ||
| Runner = llvm::make_unique<UopsBenchmarkRunner>(); | ||
| break; | ||
| } | ||
|
|
||
| Runner->run(State, Opcode, NumRepetitions > 0 ? NumRepetitions : 1, Filter) | ||
| .writeYamlOrDie("-"); | ||
| } | ||
|
|
||
| } // namespace exegesis | ||
|
|
||
| int main(int Argc, char **Argv) { | ||
| llvm::cl::ParseCommandLineOptions(Argc, Argv, ""); | ||
|
|
||
| if (exegesis::pfm::pfmInitialize()) { | ||
| llvm::errs() << "cannot initialize libpfm\n"; | ||
| return EXIT_FAILURE; | ||
| } | ||
|
|
||
| exegesis::main(); | ||
|
|
||
| exegesis::pfm::pfmTerminate(); | ||
| return EXIT_SUCCESS; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,10 @@ | ||
| if(LLVM_TARGETS_TO_BUILD MATCHES "X86") | ||
| add_subdirectory( | ||
| llvm-cfi-verify | ||
| ) | ||
| endif() | ||
|
|
||
| add_subdirectory( | ||
| llvm-exegesis | ||
| ) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| //===-- BenchmarkResultTest.cpp ---------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "BenchmarkResult.h" | ||
| #include "llvm/ADT/SmallString.h" | ||
| #include "llvm/Support/Error.h" | ||
| #include "llvm/Support/YAMLTraits.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| namespace exegesis { | ||
|
|
||
| bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) { | ||
| return std::tie(A.Key, A.Value) == std::tie(B.Key, B.Value); | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| TEST(BenchmarkResultTest, WriteToAndReadFromDisk) { | ||
| InstructionBenchmark ToDisk; | ||
|
|
||
| ToDisk.AsmTmpl.Name = "name"; | ||
| ToDisk.CpuName = "cpu_name"; | ||
| ToDisk.LLVMTriple = "llvm_triple"; | ||
| ToDisk.NumRepetitions = 1; | ||
| ToDisk.Measurements.push_back(BenchmarkMeasure{"a", 1, "debug a"}); | ||
| ToDisk.Measurements.push_back(BenchmarkMeasure{"b", 2}); | ||
| ToDisk.Error = "error"; | ||
|
|
||
| const llvm::StringRef Filename("data.yaml"); | ||
|
|
||
| ToDisk.writeYamlOrDie(Filename); | ||
|
|
||
| { | ||
| const auto FromDisk = InstructionBenchmark::readYamlOrDie(Filename); | ||
|
|
||
| EXPECT_EQ(FromDisk.AsmTmpl.Name, ToDisk.AsmTmpl.Name); | ||
| EXPECT_EQ(FromDisk.CpuName, ToDisk.CpuName); | ||
| EXPECT_EQ(FromDisk.LLVMTriple, ToDisk.LLVMTriple); | ||
| EXPECT_EQ(FromDisk.NumRepetitions, ToDisk.NumRepetitions); | ||
| EXPECT_THAT(FromDisk.Measurements, ToDisk.Measurements); | ||
| EXPECT_THAT(FromDisk.Error, ToDisk.Error); | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| include_directories( | ||
| ${CMAKE_SOURCE_DIR}/lib/Target/X86 | ||
| ${CMAKE_BINARY_DIR}/lib/Target/X86 | ||
| ${CMAKE_SOURCE_DIR}/tools/llvm-exegesis/lib | ||
| ) | ||
|
|
||
| set(LLVM_LINK_COMPONENTS | ||
| X86Desc | ||
| X86Info | ||
| X86 | ||
| MC | ||
| MCParser | ||
| Object | ||
| Support | ||
| Symbolize | ||
| ) | ||
|
|
||
| add_llvm_unittest(LLVMExegesisTests | ||
| BenchmarkResultTest.cpp | ||
| InMemoryAssemblerTest.cpp | ||
| InstructionSnippetGeneratorTest.cpp | ||
| OperandGraphTest.cpp | ||
| PerfHelperTest.cpp | ||
| ) | ||
| target_link_libraries(LLVMExegesisTests PRIVATE LLVMExegesis) | ||
|
|
||
| if(HAVE_LIBPFM) | ||
| target_link_libraries(LLVMExegesisTests PRIVATE pfm) | ||
| endif() | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| //===-- InMemoryAssemblerTest.cpp -------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "InMemoryAssembler.h" | ||
| #include "X86InstrInfo.h" | ||
| #include "llvm/ADT/ArrayRef.h" | ||
| #include "llvm/CodeGen/MachineInstrBuilder.h" | ||
| #include "llvm/CodeGen/TargetInstrInfo.h" | ||
| #include "llvm/CodeGen/TargetSubtargetInfo.h" | ||
| #include "llvm/MC/MCInstBuilder.h" | ||
| #include "llvm/Support/Host.h" | ||
| #include "llvm/Support/TargetRegistry.h" | ||
| #include "llvm/Support/TargetSelect.h" | ||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
| #include <memory> | ||
|
|
||
| namespace exegesis { | ||
| namespace { | ||
|
|
||
| using llvm::MCInstBuilder; | ||
| using llvm::X86::EAX; | ||
| using llvm::X86::MOV32ri; | ||
| using llvm::X86::MOV64ri32; | ||
| using llvm::X86::RAX; | ||
| using llvm::X86::XOR32rr; | ||
| using testing::ElementsAre; | ||
|
|
||
| class MachineFunctionGeneratorTest : public ::testing::Test { | ||
| protected: | ||
| MachineFunctionGeneratorTest() | ||
| : TT(llvm::sys::getProcessTriple()), | ||
| CpuName(llvm::sys::getHostCPUName().str()) {} | ||
|
|
||
| static void SetUpTestCase() { | ||
| LLVMInitializeX86Target(); | ||
| LLVMInitializeX86TargetInfo(); | ||
| LLVMInitializeX86TargetMC(); | ||
| LLVMInitializeX86AsmPrinter(); | ||
| } | ||
|
|
||
| std::unique_ptr<llvm::LLVMTargetMachine> createTargetMachine() { | ||
| std::string Error; | ||
| const llvm::Target *TheTarget = | ||
| llvm::TargetRegistry::lookupTarget(TT, Error); | ||
| assert(TheTarget); | ||
| const llvm::TargetOptions Options; | ||
| return std::unique_ptr<llvm::LLVMTargetMachine>( | ||
| static_cast<llvm::LLVMTargetMachine *>(TheTarget->createTargetMachine( | ||
| TT, CpuName, "", Options, llvm::Reloc::Model::Static))); | ||
| } | ||
|
|
||
| private: | ||
| const std::string TT; | ||
| const std::string CpuName; | ||
| }; | ||
|
|
||
| TEST_F(MachineFunctionGeneratorTest, JitFunction) { | ||
| JitFunctionContext Context(createTargetMachine()); | ||
| JitFunction Function(std::move(Context), {}); | ||
| ASSERT_THAT(Function.getFunctionBytes().str(), ElementsAre(0xc3)); | ||
| Function(); | ||
| } | ||
|
|
||
| TEST_F(MachineFunctionGeneratorTest, JitFunctionXOR32rr) { | ||
| JitFunctionContext Context(createTargetMachine()); | ||
| JitFunction Function( | ||
| std::move(Context), | ||
| {MCInstBuilder(XOR32rr).addReg(EAX).addReg(EAX).addReg(EAX)}); | ||
| ASSERT_THAT(Function.getFunctionBytes().str(), ElementsAre(0x31, 0xc0, 0xc3)); | ||
| Function(); | ||
| } | ||
|
|
||
| TEST_F(MachineFunctionGeneratorTest, JitFunctionMOV64ri) { | ||
| JitFunctionContext Context(createTargetMachine()); | ||
| JitFunction Function(std::move(Context), | ||
| {MCInstBuilder(MOV64ri32).addReg(RAX).addImm(42)}); | ||
| ASSERT_THAT(Function.getFunctionBytes().str(), | ||
| ElementsAre(0x48, 0xc7, 0xc0, 0x2a, 0x00, 0x00, 0x00, 0xc3)); | ||
| Function(); | ||
| } | ||
|
|
||
| TEST_F(MachineFunctionGeneratorTest, JitFunctionMOV32ri) { | ||
| JitFunctionContext Context(createTargetMachine()); | ||
| JitFunction Function(std::move(Context), | ||
| {MCInstBuilder(MOV32ri).addReg(EAX).addImm(42)}); | ||
| ASSERT_THAT(Function.getFunctionBytes().str(), | ||
| ElementsAre(0xb8, 0x2a, 0x00, 0x00, 0x00, 0xc3)); | ||
| Function(); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,309 @@ | ||
| //===-- InstructionSnippetGeneratorTest.cpp ---------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "InstructionSnippetGenerator.h" | ||
| #include "X86InstrInfo.h" | ||
| #include "llvm/MC/MCInstBuilder.h" | ||
| #include "llvm/Support/Host.h" | ||
| #include "llvm/Support/TargetRegistry.h" | ||
| #include "llvm/Support/TargetSelect.h" | ||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
| #include <memory> | ||
| #include <set> | ||
|
|
||
| namespace llvm { | ||
|
|
||
| bool operator==(const MCOperand &A, const MCOperand &B) { | ||
| if ((A.isValid() == false) && (B.isValid() == false)) | ||
| return true; | ||
| if (A.isReg() && B.isReg()) | ||
| return A.getReg() == B.getReg(); | ||
| if (A.isImm() && B.isImm()) | ||
| return A.getImm() == B.getImm(); | ||
| return false; | ||
| } | ||
|
|
||
| } // namespace llvm | ||
|
|
||
| namespace exegesis { | ||
| namespace { | ||
|
|
||
| using testing::_; | ||
| using testing::AllOf; | ||
| using testing::AnyOf; | ||
| using testing::Contains; | ||
| using testing::ElementsAre; | ||
| using testing::Eq; | ||
| using testing::Field; | ||
| using testing::Not; | ||
| using testing::SizeIs; | ||
| using testing::UnorderedElementsAre; | ||
| using testing::Value; | ||
|
|
||
| using llvm::X86::AL; | ||
| using llvm::X86::AX; | ||
| using llvm::X86::EFLAGS; | ||
| using llvm::X86::RAX; | ||
|
|
||
| class MCInstrDescViewTest : public ::testing::Test { | ||
| protected: | ||
| MCInstrDescViewTest() | ||
| : TheTriple(llvm::sys::getProcessTriple()), | ||
| CpuName(llvm::sys::getHostCPUName().str()) {} | ||
|
|
||
| void SetUp() override { | ||
| LLVMInitializeX86Target(); | ||
| LLVMInitializeX86TargetInfo(); | ||
| LLVMInitializeX86TargetMC(); | ||
|
|
||
| std::string Error; | ||
| const auto *Target = llvm::TargetRegistry::lookupTarget(TheTriple, Error); | ||
| InstrInfo.reset(Target->createMCInstrInfo()); | ||
| RegInfo.reset(Target->createMCRegInfo(TheTriple)); | ||
| } | ||
|
|
||
| const std::string TheTriple; | ||
| const std::string CpuName; | ||
| std::unique_ptr<const llvm::MCInstrInfo> InstrInfo; | ||
| std::unique_ptr<const llvm::MCRegisterInfo> RegInfo; | ||
| }; | ||
|
|
||
| MATCHER(IsDef, "") { return arg.IsDef; } | ||
| MATCHER(IsUse, "") { return arg.IsUse; } | ||
| MATCHER_P2(EqVarAssignement, VariableIndexMatcher, AssignedRegisterMatcher, | ||
| "") { | ||
| return Value( | ||
| arg, | ||
| AllOf(Field(&VariableAssignment::VarIdx, VariableIndexMatcher), | ||
| Field(&VariableAssignment::AssignedReg, AssignedRegisterMatcher))); | ||
| } | ||
|
|
||
| size_t returnIndexZero(const size_t UpperBound) { return 0; } | ||
|
|
||
| TEST_F(MCInstrDescViewTest, XOR64rr) { | ||
| const llvm::MCInstrDesc &InstrDesc = InstrInfo->get(llvm::X86::XOR64rr); | ||
| const auto Vars = | ||
| getVariables(*RegInfo, InstrDesc, llvm::BitVector(RegInfo->getNumRegs())); | ||
|
|
||
| // XOR64rr has the following operands: | ||
| // 0. out register | ||
| // 1. in register (tied to out) | ||
| // 2. in register | ||
| // 3. out EFLAGS (implicit) | ||
| // | ||
| // This translates to 3 variables, one for 0 and 1, one for 2, one for 3. | ||
| ASSERT_THAT(Vars, SizeIs(3)); | ||
|
|
||
| EXPECT_THAT(Vars[0].ExplicitOperands, ElementsAre(0, 1)); | ||
| EXPECT_THAT(Vars[1].ExplicitOperands, ElementsAre(2)); | ||
| EXPECT_THAT(Vars[2].ExplicitOperands, ElementsAre()); // implicit | ||
|
|
||
| EXPECT_THAT(Vars[0], AllOf(IsUse(), IsDef())); | ||
| EXPECT_THAT(Vars[1], AllOf(IsUse(), Not(IsDef()))); | ||
| EXPECT_THAT(Vars[2], AllOf(Not(IsUse()), IsDef())); | ||
|
|
||
| EXPECT_THAT(Vars[0].PossibleRegisters, Contains(RAX)); | ||
| EXPECT_THAT(Vars[1].PossibleRegisters, Contains(RAX)); | ||
| EXPECT_THAT(Vars[2].PossibleRegisters, ElementsAre(EFLAGS)); | ||
|
|
||
| // Computing chains. | ||
| const auto Chains = computeSequentialAssignmentChains(*RegInfo, Vars); | ||
|
|
||
| // Because operands 0 and 1 are tied together any possible value for variable | ||
| // 0 would do. | ||
| for (const auto &Reg : Vars[0].PossibleRegisters) { | ||
| EXPECT_THAT(Chains, Contains(ElementsAre(EqVarAssignement(0, Reg)))); | ||
| } | ||
|
|
||
| // We also have chains going through operand 0 to 2 (i.e. Vars 0 and 1). | ||
| EXPECT_THAT(Vars[0].PossibleRegisters, Eq(Vars[1].PossibleRegisters)) | ||
| << "Variables 0 and 1 are of the same class"; | ||
| for (const auto &Reg : Vars[0].PossibleRegisters) { | ||
| EXPECT_THAT(Chains, | ||
| Contains(UnorderedElementsAre(EqVarAssignement(0, Reg), | ||
| EqVarAssignement(1, Reg)))); | ||
| } | ||
|
|
||
| // EFLAGS does not appear as an input therefore no chain can contain EFLAGS. | ||
| EXPECT_THAT(Chains, Not(Contains(Contains(EqVarAssignement(_, EFLAGS))))); | ||
|
|
||
| // Computing assignment. | ||
| const auto Regs = getRandomAssignment(Vars, Chains, &returnIndexZero); | ||
| EXPECT_THAT(Regs, ElementsAre(RAX, RAX, EFLAGS)); | ||
|
|
||
| // Generating assembler representation. | ||
| const llvm::MCInst Inst = generateMCInst(InstrDesc, Vars, Regs); | ||
| EXPECT_THAT(Inst.getOpcode(), llvm::X86::XOR64rr); | ||
| EXPECT_THAT(Inst.getNumOperands(), 3); | ||
| EXPECT_THAT(Inst.getOperand(0), llvm::MCOperand::createReg(RAX)); | ||
| EXPECT_THAT(Inst.getOperand(1), llvm::MCOperand::createReg(RAX)); | ||
| EXPECT_THAT(Inst.getOperand(2), llvm::MCOperand::createReg(RAX)); | ||
| } | ||
|
|
||
| TEST_F(MCInstrDescViewTest, AAA) { | ||
| const llvm::MCInstrDesc &InstrDesc = InstrInfo->get(llvm::X86::AAA); | ||
| const auto Vars = | ||
| getVariables(*RegInfo, InstrDesc, llvm::BitVector(RegInfo->getNumRegs())); | ||
|
|
||
| // AAA has the following operands: | ||
| // 0. out AX (implicit) | ||
| // 1. out EFLAGS (implicit) | ||
| // 2. in AL (implicit) | ||
| // 3. in EFLAGS (implicit) | ||
| // | ||
| // This translates to 4 Vars (non are tied together). | ||
| ASSERT_THAT(Vars, SizeIs(4)); | ||
|
|
||
| EXPECT_THAT(Vars[0].ExplicitOperands, ElementsAre()); // implicit | ||
| EXPECT_THAT(Vars[1].ExplicitOperands, ElementsAre()); // implicit | ||
| EXPECT_THAT(Vars[2].ExplicitOperands, ElementsAre()); // implicit | ||
| EXPECT_THAT(Vars[3].ExplicitOperands, ElementsAre()); // implicit | ||
|
|
||
| EXPECT_THAT(Vars[0], AllOf(Not(IsUse()), IsDef())); | ||
| EXPECT_THAT(Vars[1], AllOf(Not(IsUse()), IsDef())); | ||
| EXPECT_THAT(Vars[2], AllOf(IsUse(), Not(IsDef()))); | ||
| EXPECT_THAT(Vars[3], AllOf(IsUse(), Not(IsDef()))); | ||
|
|
||
| EXPECT_THAT(Vars[0].PossibleRegisters, ElementsAre(AX)); | ||
| EXPECT_THAT(Vars[1].PossibleRegisters, ElementsAre(EFLAGS)); | ||
| EXPECT_THAT(Vars[2].PossibleRegisters, ElementsAre(AL)); | ||
| EXPECT_THAT(Vars[3].PossibleRegisters, ElementsAre(EFLAGS)); | ||
|
|
||
| const auto Chains = computeSequentialAssignmentChains(*RegInfo, Vars); | ||
| EXPECT_THAT(Chains, | ||
| ElementsAre(UnorderedElementsAre(EqVarAssignement(0, AX), | ||
| EqVarAssignement(2, AL)), | ||
| UnorderedElementsAre(EqVarAssignement(1, EFLAGS), | ||
| EqVarAssignement(3, EFLAGS)))); | ||
|
|
||
| // Computing assignment. | ||
| const auto Regs = getRandomAssignment(Vars, Chains, &returnIndexZero); | ||
| EXPECT_THAT(Regs, ElementsAre(AX, EFLAGS, AL, EFLAGS)); | ||
|
|
||
| // Generating assembler representation. | ||
| const llvm::MCInst Inst = generateMCInst(InstrDesc, Vars, Regs); | ||
| EXPECT_THAT(Inst.getOpcode(), llvm::X86::AAA); | ||
| EXPECT_THAT(Inst.getNumOperands(), 0) << "All operands are implicit"; | ||
| } | ||
|
|
||
| TEST_F(MCInstrDescViewTest, ReservedRegisters) { | ||
| llvm::BitVector ReservedRegisters(RegInfo->getNumRegs()); | ||
|
|
||
| const llvm::MCInstrDesc &InstrDesc = InstrInfo->get(llvm::X86::XOR64rr); | ||
| { | ||
| const auto Vars = getVariables(*RegInfo, InstrDesc, ReservedRegisters); | ||
| ASSERT_THAT(Vars, SizeIs(3)); | ||
| EXPECT_THAT(Vars[0].PossibleRegisters, Contains(RAX)); | ||
| EXPECT_THAT(Vars[1].PossibleRegisters, Contains(RAX)); | ||
| } | ||
|
|
||
| // Disable RAX. | ||
| ReservedRegisters.set(RAX); | ||
| { | ||
| const auto Vars = getVariables(*RegInfo, InstrDesc, ReservedRegisters); | ||
| ASSERT_THAT(Vars, SizeIs(3)); | ||
| EXPECT_THAT(Vars[0].PossibleRegisters, Not(Contains(RAX))); | ||
| EXPECT_THAT(Vars[1].PossibleRegisters, Not(Contains(RAX))); | ||
| } | ||
| } | ||
|
|
||
| Variable makeVariableWithRegisters(bool IsReg, | ||
| std::initializer_list<int> Regs) { | ||
| assert((IsReg || (Regs.size() == 0)) && "IsReg => !(Regs.size() == 0)"); | ||
| Variable Var; | ||
| Var.IsReg = IsReg; | ||
| Var.PossibleRegisters.insert(Regs.begin(), Regs.end()); | ||
| return Var; | ||
| } | ||
|
|
||
| TEST(getExclusiveAssignment, TriviallyFeasible) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(true, {3}), | ||
| makeVariableWithRegisters(false, {}), | ||
| makeVariableWithRegisters(true, {4}), | ||
| makeVariableWithRegisters(true, {5}), | ||
| }; | ||
| const auto Regs = getExclusiveAssignment(Vars); | ||
| EXPECT_THAT(Regs, ElementsAre(3, 0, 4, 5)); | ||
| } | ||
|
|
||
| TEST(getExclusiveAssignment, TriviallyInfeasible1) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(true, {3}), | ||
| makeVariableWithRegisters(true, {}), | ||
| makeVariableWithRegisters(true, {4}), | ||
| makeVariableWithRegisters(true, {5}), | ||
| }; | ||
| const auto Regs = getExclusiveAssignment(Vars); | ||
| EXPECT_THAT(Regs, ElementsAre()); | ||
| } | ||
|
|
||
| TEST(getExclusiveAssignment, TriviallyInfeasible) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(true, {4}), | ||
| makeVariableWithRegisters(true, {4}), | ||
| }; | ||
| const auto Regs = getExclusiveAssignment(Vars); | ||
| EXPECT_THAT(Regs, ElementsAre()); | ||
| } | ||
|
|
||
| TEST(getExclusiveAssignment, Feasible1) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(true, {4, 3}), | ||
| makeVariableWithRegisters(true, {6, 3}), | ||
| makeVariableWithRegisters(true, {6, 4}), | ||
| }; | ||
| const auto Regs = getExclusiveAssignment(Vars); | ||
| ASSERT_THAT(Regs, AnyOf(ElementsAre(3, 6, 4), ElementsAre(4, 3, 6))); | ||
| } | ||
|
|
||
| TEST(getExclusiveAssignment, Feasible2) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(true, {1, 2}), | ||
| makeVariableWithRegisters(true, {3, 4}), | ||
| }; | ||
| const auto Regs = getExclusiveAssignment(Vars); | ||
| ASSERT_THAT(Regs, AnyOf(ElementsAre(1, 3), ElementsAre(1, 4), | ||
| ElementsAre(2, 3), ElementsAre(2, 4))); | ||
| } | ||
|
|
||
| TEST(getGreedyAssignment, Infeasible) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(true, {}), | ||
| makeVariableWithRegisters(true, {1, 2}), | ||
| }; | ||
| const auto Regs = getGreedyAssignment(Vars); | ||
| ASSERT_THAT(Regs, ElementsAre()); | ||
| } | ||
|
|
||
| TEST(getGreedyAssignment, FeasibleNoFallback) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(true, {1, 2}), | ||
| makeVariableWithRegisters(false, {}), | ||
| makeVariableWithRegisters(true, {2, 3}), | ||
| }; | ||
| const auto Regs = getGreedyAssignment(Vars); | ||
| ASSERT_THAT(Regs, ElementsAre(1, 0, 2)); | ||
| } | ||
|
|
||
| TEST(getGreedyAssignment, Feasible) { | ||
| const std::vector<Variable> Vars = { | ||
| makeVariableWithRegisters(false, {}), | ||
| makeVariableWithRegisters(true, {1, 2}), | ||
| makeVariableWithRegisters(true, {2, 3}), | ||
| makeVariableWithRegisters(true, {2, 3}), | ||
| makeVariableWithRegisters(true, {2, 3}), | ||
| }; | ||
| const auto Regs = getGreedyAssignment(Vars); | ||
| ASSERT_THAT(Regs, ElementsAre(0, 1, 2, 3, 2)); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| //===-- OperandGraphTest.cpp ------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "OperandGraph.h" | ||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| using testing::ElementsAre; | ||
| using testing::IsEmpty; | ||
| using testing::Not; | ||
|
|
||
| namespace exegesis { | ||
| namespace graph { | ||
| namespace { | ||
|
|
||
| static const auto In = Node::In(); | ||
| static const auto Out = Node::Out(); | ||
|
|
||
| TEST(OperandGraphTest, NoPath) { | ||
| Graph TheGraph; | ||
| EXPECT_THAT(TheGraph.getPathFrom(In, Out), IsEmpty()); | ||
| } | ||
|
|
||
| TEST(OperandGraphTest, Connecting) { | ||
| Graph TheGraph; | ||
| TheGraph.connect(In, Out); | ||
| EXPECT_THAT(TheGraph.getPathFrom(In, Out), Not(IsEmpty())); | ||
| EXPECT_THAT(TheGraph.getPathFrom(In, Out), ElementsAre(In, Out)); | ||
| } | ||
|
|
||
| TEST(OperandGraphTest, ConnectingThroughVariable) { | ||
| const Node Var = Node::Var(1); | ||
| Graph TheGraph; | ||
| TheGraph.connect(In, Var); | ||
| TheGraph.connect(Var, Out); | ||
| EXPECT_THAT(TheGraph.getPathFrom(In, Out), Not(IsEmpty())); | ||
| EXPECT_THAT(TheGraph.getPathFrom(In, Out), ElementsAre(In, Var, Out)); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace graph | ||
| } // namespace exegesis |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| //===-- PerfHelperTest.cpp --------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "PerfHelper.h" | ||
| #include "llvm/Config/config.h" | ||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| namespace exegesis { | ||
| namespace pfm { | ||
| namespace { | ||
|
|
||
| using ::testing::IsEmpty; | ||
| using ::testing::Not; | ||
|
|
||
| TEST(PerfHelperTest, FunctionalTest) { | ||
| #ifdef HAVE_LIBPFM | ||
| ASSERT_FALSE(pfmInitialize()); | ||
| const PerfEvent SingleEvent("CYCLES:u"); | ||
| const auto &EmptyFn = []() {}; | ||
| std::string CallbackEventName; | ||
| std::string CallbackEventNameFullyQualifed; | ||
| int64_t CallbackEventCycles; | ||
| Measure(llvm::makeArrayRef(SingleEvent), | ||
| [&](const PerfEvent &Event, int64_t Value) { | ||
| CallbackEventName = Event.name(); | ||
| CallbackEventNameFullyQualifed = Event.getPfmEventString(); | ||
| CallbackEventCycles = Value; | ||
| }, | ||
| EmptyFn); | ||
| EXPECT_EQ(CallbackEventName, "CYCLES:u"); | ||
| EXPECT_THAT(CallbackEventNameFullyQualifed, Not(IsEmpty())); | ||
| pfmTerminate(); | ||
| #else | ||
| ASSERT_TRUE(PfmInitialize()); | ||
| #endif | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace pfm | ||
| } // namespace exegesis |