64 changes: 64 additions & 0 deletions llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h
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
25 changes: 25 additions & 0 deletions llvm/tools/llvm-exegesis/lib/CMakeLists.txt
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")
226 changes: 226 additions & 0 deletions llvm/tools/llvm-exegesis/lib/InMemoryAssembler.cpp
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
78 changes: 78 additions & 0 deletions llvm/tools/llvm-exegesis/lib/InMemoryAssembler.h
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
355 changes: 355 additions & 0 deletions llvm/tools/llvm-exegesis/lib/InstructionSnippetGenerator.cpp
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
119 changes: 119 additions & 0 deletions llvm/tools/llvm-exegesis/lib/InstructionSnippetGenerator.h
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
22 changes: 22 additions & 0 deletions llvm/tools/llvm-exegesis/lib/LLVMBuild.txt
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
95 changes: 95 additions & 0 deletions llvm/tools/llvm-exegesis/lib/Latency.cpp
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
41 changes: 41 additions & 0 deletions llvm/tools/llvm-exegesis/lib/Latency.h
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
56 changes: 56 additions & 0 deletions llvm/tools/llvm-exegesis/lib/LlvmState.cpp
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
59 changes: 59 additions & 0 deletions llvm/tools/llvm-exegesis/lib/LlvmState.h
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
115 changes: 115 additions & 0 deletions llvm/tools/llvm-exegesis/lib/OperandGraph.cpp
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
89 changes: 89 additions & 0 deletions llvm/tools/llvm-exegesis/lib/OperandGraph.h
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
129 changes: 129 additions & 0 deletions llvm/tools/llvm-exegesis/lib/PerfHelper.cpp
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
103 changes: 103 additions & 0 deletions llvm/tools/llvm-exegesis/lib/PerfHelper.h
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
248 changes: 248 additions & 0 deletions llvm/tools/llvm-exegesis/lib/Uops.cpp
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
41 changes: 41 additions & 0 deletions llvm/tools/llvm-exegesis/lib/Uops.h
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
38 changes: 38 additions & 0 deletions llvm/tools/llvm-exegesis/lib/X86.cpp
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
32 changes: 32 additions & 0 deletions llvm/tools/llvm-exegesis/lib/X86.h
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
115 changes: 115 additions & 0 deletions llvm/tools/llvm-exegesis/llvm-exegesis.cpp
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;
}
8 changes: 7 additions & 1 deletion llvm/unittests/tools/CMakeLists.txt
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)
add_subdirectory(
llvm-cfi-verify
)
endif()

add_subdirectory(
llvm-exegesis
)

54 changes: 54 additions & 0 deletions llvm/unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp
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
30 changes: 30 additions & 0 deletions llvm/unittests/tools/llvm-exegesis/CMakeLists.txt
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()

99 changes: 99 additions & 0 deletions llvm/unittests/tools/llvm-exegesis/InMemoryAssemblerTest.cpp
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
309 changes: 309 additions & 0 deletions llvm/unittests/tools/llvm-exegesis/InstructionSnippetGeneratorTest.cpp
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
48 changes: 48 additions & 0 deletions llvm/unittests/tools/llvm-exegesis/OperandGraphTest.cpp
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
47 changes: 47 additions & 0 deletions llvm/unittests/tools/llvm-exegesis/PerfHelperTest.cpp
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