mirror of
https://github.com/intel/llvm.git
synced 2026-01-19 01:15:50 +08:00
[llvm-exegesis] Use a Prototype to defer picking a value for free vars.
Summary: Introducing a Prototype object to capture Variables that must be set but keeps degrees of freedom as Invalid. This allows exploring non constraint variables later on. Reviewers: courbet Subscribers: tschuett, llvm-commits Differential Revision: https://reviews.llvm.org/D48316 llvm-svn: 335105
This commit is contained in:
@@ -47,7 +47,7 @@ BenchmarkRunner::run(unsigned Opcode, const InstructionFilter &Filter,
|
||||
return std::move(E);
|
||||
|
||||
llvm::Expected<std::vector<BenchmarkConfiguration>> ConfigurationOrError =
|
||||
createConfigurations(Opcode);
|
||||
generateConfigurations(Opcode);
|
||||
|
||||
if (llvm::Error E = ConfigurationOrError.takeError())
|
||||
return std::move(E);
|
||||
@@ -113,6 +113,20 @@ BenchmarkRunner::runOne(const BenchmarkConfiguration &Configuration,
|
||||
return InstrBenchmark;
|
||||
}
|
||||
|
||||
llvm::Expected<std::vector<BenchmarkConfiguration>>
|
||||
BenchmarkRunner::generateConfigurations(unsigned Opcode) const {
|
||||
if (auto E = generatePrototype(Opcode)) {
|
||||
SnippetPrototype &Prototype = E.get();
|
||||
// TODO: Generate as many configurations as needed here.
|
||||
BenchmarkConfiguration Configuration;
|
||||
Configuration.Info = Prototype.Explanation;
|
||||
for (InstructionInstance &II : Prototype.Snippet)
|
||||
Configuration.Snippet.push_back(II.randomizeUnsetVariablesAndBuild());
|
||||
return std::vector<BenchmarkConfiguration>{Configuration};
|
||||
} else
|
||||
return E.takeError();
|
||||
}
|
||||
|
||||
llvm::Expected<std::string>
|
||||
BenchmarkRunner::writeObjectFile(llvm::ArrayRef<llvm::MCInst> Code) const {
|
||||
int ResultFD = 0;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "Assembler.h"
|
||||
#include "BenchmarkResult.h"
|
||||
#include "LlvmState.h"
|
||||
#include "MCInstrDescView.h"
|
||||
#include "RegisterAliasing.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
@@ -80,10 +81,15 @@ private:
|
||||
InstructionBenchmark runOne(const BenchmarkConfiguration &Configuration,
|
||||
unsigned Opcode, unsigned NumRepetitions) const;
|
||||
|
||||
// Calls generatePrototype and expands the SnippetPrototype into one or more
|
||||
// BenchmarkConfiguration.
|
||||
llvm::Expected<std::vector<BenchmarkConfiguration>>
|
||||
generateConfigurations(unsigned Opcode) const;
|
||||
|
||||
virtual InstructionBenchmark::ModeE getMode() const = 0;
|
||||
|
||||
virtual llvm::Expected<std::vector<BenchmarkConfiguration>>
|
||||
createConfigurations(unsigned Opcode) const = 0;
|
||||
virtual llvm::Expected<SnippetPrototype>
|
||||
generatePrototype(unsigned Opcode) const = 0;
|
||||
|
||||
virtual std::vector<BenchmarkMeasure>
|
||||
runMeasurements(const ExecutableFunction &EF,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/MC/MCInstBuilder.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
|
||||
namespace exegesis {
|
||||
|
||||
@@ -47,26 +48,27 @@ llvm::Error LatencyBenchmarkRunner::isInfeasible(
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration>
|
||||
LatencyBenchmarkRunner::generateSelfAliasingConfiguration(
|
||||
llvm::Expected<SnippetPrototype>
|
||||
LatencyBenchmarkRunner::generateSelfAliasingPrototype(
|
||||
const Instruction &Instr,
|
||||
const AliasingConfigurations &SelfAliasing) const {
|
||||
BenchmarkConfiguration Conf;
|
||||
SnippetPrototype Prototype;
|
||||
InstructionInstance II(Instr);
|
||||
if (SelfAliasing.hasImplicitAliasing()) {
|
||||
Conf.Info = "implicit Self cycles, picking random values.";
|
||||
Prototype.Explanation = "implicit Self cycles, picking random values.";
|
||||
} else {
|
||||
Conf.Info = "explicit self cycles, selecting one aliasing Conf.";
|
||||
Prototype.Explanation =
|
||||
"explicit self cycles, selecting one aliasing Conf.";
|
||||
// This is a self aliasing instruction so defs and uses are from the same
|
||||
// instance, hence twice II in the following call.
|
||||
setRandomAliasing(SelfAliasing, II, II);
|
||||
}
|
||||
Conf.Snippet = {II.randomizeUnsetVariablesAndBuild()};
|
||||
return Conf;
|
||||
Prototype.Snippet.push_back(std::move(II));
|
||||
return Prototype;
|
||||
}
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration>
|
||||
LatencyBenchmarkRunner::generateTwoInstructionConfiguration(
|
||||
llvm::Expected<SnippetPrototype>
|
||||
LatencyBenchmarkRunner::generateTwoInstructionPrototype(
|
||||
const Instruction &Instr,
|
||||
const AliasingConfigurations &SelfAliasing) const {
|
||||
std::vector<unsigned> Opcodes;
|
||||
@@ -74,7 +76,7 @@ LatencyBenchmarkRunner::generateTwoInstructionConfiguration(
|
||||
std::iota(Opcodes.begin(), Opcodes.end(), 0U);
|
||||
std::shuffle(Opcodes.begin(), Opcodes.end(), randomGenerator());
|
||||
for (const unsigned OtherOpcode : Opcodes) {
|
||||
if (OtherOpcode == Instr.Description.Opcode)
|
||||
if (OtherOpcode == Instr.Description->Opcode)
|
||||
continue;
|
||||
const auto &OtherInstrDesc = MCInstrInfo.get(OtherOpcode);
|
||||
if (auto E = isInfeasible(OtherInstrDesc)) {
|
||||
@@ -92,21 +94,19 @@ LatencyBenchmarkRunner::generateTwoInstructionConfiguration(
|
||||
setRandomAliasing(Forward, ThisII, OtherII);
|
||||
if (!Back.hasImplicitAliasing())
|
||||
setRandomAliasing(Back, OtherII, ThisII);
|
||||
BenchmarkConfiguration Conf;
|
||||
Conf.Info = llvm::Twine("creating cycle through ")
|
||||
.concat(MCInstrInfo.getName(OtherOpcode))
|
||||
.concat(".")
|
||||
.str();
|
||||
Conf.Snippet.push_back(ThisII.randomizeUnsetVariablesAndBuild());
|
||||
Conf.Snippet.push_back(OtherII.randomizeUnsetVariablesAndBuild());
|
||||
return Conf;
|
||||
SnippetPrototype Prototype;
|
||||
Prototype.Explanation = llvm::formatv("creating cycle through {0}.",
|
||||
MCInstrInfo.getName(OtherOpcode));
|
||||
Prototype.Snippet.push_back(std::move(ThisII));
|
||||
Prototype.Snippet.push_back(std::move(OtherII));
|
||||
return Prototype;
|
||||
}
|
||||
return llvm::make_error<BenchmarkFailure>(
|
||||
"Infeasible : Didn't find any scheme to make the instruction serial");
|
||||
}
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration>
|
||||
LatencyBenchmarkRunner::generateConfiguration(unsigned Opcode) const {
|
||||
llvm::Expected<SnippetPrototype>
|
||||
LatencyBenchmarkRunner::generatePrototype(unsigned Opcode) const {
|
||||
const auto &InstrDesc = MCInstrInfo.get(Opcode);
|
||||
if (auto E = isInfeasible(InstrDesc))
|
||||
return std::move(E);
|
||||
@@ -114,20 +114,12 @@ LatencyBenchmarkRunner::generateConfiguration(unsigned Opcode) const {
|
||||
const AliasingConfigurations SelfAliasing(Instr, Instr);
|
||||
if (SelfAliasing.empty()) {
|
||||
// No self aliasing, trying to create a dependency through another opcode.
|
||||
return generateTwoInstructionConfiguration(Instr, SelfAliasing);
|
||||
return generateTwoInstructionPrototype(Instr, SelfAliasing);
|
||||
} else {
|
||||
return generateSelfAliasingConfiguration(Instr, SelfAliasing);
|
||||
return generateSelfAliasingPrototype(Instr, SelfAliasing);
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Expected<std::vector<BenchmarkConfiguration>>
|
||||
LatencyBenchmarkRunner::createConfigurations(unsigned Opcode) const {
|
||||
if (auto E = generateConfiguration(Opcode))
|
||||
return std::vector<BenchmarkConfiguration>{E.get()};
|
||||
else
|
||||
return E.takeError();
|
||||
}
|
||||
|
||||
std::vector<BenchmarkMeasure>
|
||||
LatencyBenchmarkRunner::runMeasurements(const ExecutableFunction &Function,
|
||||
const unsigned NumRepetitions) const {
|
||||
|
||||
@@ -25,25 +25,22 @@ public:
|
||||
using BenchmarkRunner::BenchmarkRunner;
|
||||
~LatencyBenchmarkRunner() override;
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration>
|
||||
generateConfiguration(unsigned Opcode) const;
|
||||
llvm::Expected<SnippetPrototype>
|
||||
generatePrototype(unsigned Opcode) const override;
|
||||
|
||||
private:
|
||||
llvm::Error isInfeasible(const llvm::MCInstrDesc &MCInstrDesc) const;
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration> generateSelfAliasingConfiguration(
|
||||
llvm::Expected<SnippetPrototype> generateSelfAliasingPrototype(
|
||||
const Instruction &Instr,
|
||||
const AliasingConfigurations &SelfAliasing) const;
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration> generateTwoInstructionConfiguration(
|
||||
llvm::Expected<SnippetPrototype> generateTwoInstructionPrototype(
|
||||
const Instruction &Instr,
|
||||
const AliasingConfigurations &SelfAliasing) const;
|
||||
|
||||
InstructionBenchmark::ModeE getMode() const override;
|
||||
|
||||
llvm::Expected<std::vector<BenchmarkConfiguration>>
|
||||
createConfigurations(unsigned OpcodeIndex) const override;
|
||||
|
||||
std::vector<BenchmarkMeasure>
|
||||
runMeasurements(const ExecutableFunction &EF,
|
||||
const unsigned NumRepetitions) const override;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace exegesis {
|
||||
|
||||
Instruction::Instruction(const llvm::MCInstrDesc &MCInstrDesc,
|
||||
const RegisterAliasingTrackerCache &RATC)
|
||||
: Description(MCInstrDesc) {
|
||||
: Description(&MCInstrDesc) {
|
||||
unsigned OpIndex = 0;
|
||||
for (; OpIndex < MCInstrDesc.getNumOperands(); ++OpIndex) {
|
||||
const auto &OpInfo = MCInstrDesc.opInfo_begin()[OpIndex];
|
||||
@@ -71,7 +71,7 @@ Instruction::Instruction(const llvm::MCInstrDesc &MCInstrDesc,
|
||||
// Assigning Operands to Variables.
|
||||
for (auto &Op : Operands)
|
||||
if (Op.VariableIndex >= 0)
|
||||
Variables[Op.VariableIndex].TiedOperands.push_back(&Op);
|
||||
Variables[Op.VariableIndex].TiedOperands.push_back(Op.Index);
|
||||
// Processing Aliasing.
|
||||
DefRegisters = RATC.emptyRegisters();
|
||||
UseRegisters = RATC.emptyRegisters();
|
||||
@@ -86,6 +86,16 @@ Instruction::Instruction(const llvm::MCInstrDesc &MCInstrDesc,
|
||||
InstructionInstance::InstructionInstance(const Instruction &Instr)
|
||||
: Instr(Instr), VariableValues(Instr.Variables.size()) {}
|
||||
|
||||
InstructionInstance::InstructionInstance(InstructionInstance &&) noexcept =
|
||||
default;
|
||||
|
||||
InstructionInstance &InstructionInstance::
|
||||
operator=(InstructionInstance &&) noexcept = default;
|
||||
|
||||
unsigned InstructionInstance::getOpcode() const {
|
||||
return Instr.Description->getOpcode();
|
||||
}
|
||||
|
||||
llvm::MCOperand &InstructionInstance::getValueFor(const Variable &Var) {
|
||||
return VariableValues[Var.Index];
|
||||
}
|
||||
@@ -96,22 +106,37 @@ llvm::MCOperand &InstructionInstance::getValueFor(const Operand &Op) {
|
||||
}
|
||||
|
||||
// forward declaration.
|
||||
static void randomize(const Variable &Var, llvm::MCOperand &AssignedValue);
|
||||
static void randomize(const Instruction &Instr, const Variable &Var,
|
||||
llvm::MCOperand &AssignedValue);
|
||||
|
||||
bool InstructionInstance::hasImmediateVariables() const {
|
||||
return llvm::any_of(Instr.Variables, [this](const Variable &Var) {
|
||||
assert(!Var.TiedOperands.empty());
|
||||
const unsigned OpIndex = Var.TiedOperands[0];
|
||||
const Operand &Op = Instr.Operands[OpIndex];
|
||||
assert(Op.Info);
|
||||
return Op.Info->OperandType == llvm::MCOI::OPERAND_IMMEDIATE;
|
||||
});
|
||||
}
|
||||
|
||||
llvm::MCInst InstructionInstance::randomizeUnsetVariablesAndBuild() {
|
||||
for (const Variable &Var : Instr.Variables) {
|
||||
llvm::MCOperand &AssignedValue = getValueFor(Var);
|
||||
if (!AssignedValue.isValid())
|
||||
randomize(Var, AssignedValue);
|
||||
randomize(Instr, Var, AssignedValue);
|
||||
}
|
||||
llvm::MCInst Result;
|
||||
Result.setOpcode(Instr.Description.Opcode);
|
||||
Result.setOpcode(Instr.Description->Opcode);
|
||||
for (const auto &Op : Instr.Operands)
|
||||
if (Op.IsExplicit)
|
||||
Result.addOperand(getValueFor(Op));
|
||||
return Result;
|
||||
}
|
||||
|
||||
SnippetPrototype::SnippetPrototype(SnippetPrototype &&) = default;
|
||||
|
||||
SnippetPrototype &SnippetPrototype::operator=(SnippetPrototype &&) = default;
|
||||
|
||||
bool RegisterOperandAssignment::
|
||||
operator==(const RegisterOperandAssignment &Other) const {
|
||||
return std::tie(Op, Reg) == std::tie(Other.Op, Other.Reg);
|
||||
@@ -183,10 +208,10 @@ static auto randomElement(const C &Container) -> decltype(Container[0]) {
|
||||
return Container[randomIndex(Container.size())];
|
||||
}
|
||||
|
||||
static void randomize(const Variable &Var, llvm::MCOperand &AssignedValue) {
|
||||
static void randomize(const Instruction &Instr, const Variable &Var,
|
||||
llvm::MCOperand &AssignedValue) {
|
||||
assert(!Var.TiedOperands.empty());
|
||||
assert(Var.TiedOperands.front() != nullptr);
|
||||
const Operand &Op = *Var.TiedOperands.front();
|
||||
const Operand &Op = Instr.Operands[Var.TiedOperands.front()];
|
||||
assert(Op.Info != nullptr);
|
||||
const auto &OpInfo = *Op.Info;
|
||||
switch (OpInfo.OperandType) {
|
||||
|
||||
@@ -35,7 +35,8 @@ struct Operand; // forward declaration.
|
||||
// A variable represents the value associated to an Operand or a set of Operands
|
||||
// if they are tied together.
|
||||
struct Variable {
|
||||
llvm::SmallVector<const Operand *, 2> TiedOperands;
|
||||
// The indices of the operands tied to this Variable.
|
||||
llvm::SmallVector<unsigned, 2> TiedOperands;
|
||||
llvm::MCOperand AssignedValue;
|
||||
// The index of this Variable in Instruction.Variables and its associated
|
||||
// Value in InstructionInstance.VariableValues.
|
||||
@@ -71,7 +72,7 @@ struct Instruction {
|
||||
Instruction(const llvm::MCInstrDesc &MCInstrDesc,
|
||||
const RegisterAliasingTrackerCache &ATC);
|
||||
|
||||
const llvm::MCInstrDesc &Description;
|
||||
const llvm::MCInstrDesc *Description; // Never nullptr.
|
||||
llvm::SmallVector<Operand, 8> Operands;
|
||||
llvm::SmallVector<Variable, 4> Variables;
|
||||
llvm::BitVector DefRegisters; // The union of the aliased def registers.
|
||||
@@ -82,17 +83,46 @@ struct Instruction {
|
||||
struct InstructionInstance {
|
||||
InstructionInstance(const Instruction &Instr);
|
||||
|
||||
// No copy.
|
||||
InstructionInstance(const InstructionInstance &) = delete;
|
||||
InstructionInstance &operator=(const InstructionInstance &) = delete;
|
||||
|
||||
// Moving is OK.
|
||||
InstructionInstance(InstructionInstance &&) noexcept;
|
||||
InstructionInstance &operator=(InstructionInstance &&) noexcept;
|
||||
|
||||
unsigned getOpcode() const;
|
||||
llvm::MCOperand &getValueFor(const Variable &Var);
|
||||
llvm::MCOperand &getValueFor(const Operand &Op);
|
||||
bool hasImmediateVariables() const;
|
||||
|
||||
// Assigns a Random Value to all Variables that are still Invalid and returns
|
||||
// the instance as an llvm::MCInst.
|
||||
llvm::MCInst randomizeUnsetVariablesAndBuild();
|
||||
|
||||
const Instruction &Instr;
|
||||
Instruction Instr;
|
||||
llvm::SmallVector<llvm::MCOperand, 4> VariableValues;
|
||||
};
|
||||
|
||||
// A prototype is a set of InstructionInstances with an explanation of how
|
||||
// it's been built. The prototype can then be randomized to exercice several
|
||||
// immediate values. It is also used to gather the used registers and define
|
||||
// their initial values.
|
||||
struct SnippetPrototype {
|
||||
SnippetPrototype() = default;
|
||||
|
||||
// No copy.
|
||||
SnippetPrototype(const SnippetPrototype &) = delete;
|
||||
SnippetPrototype &operator=(const SnippetPrototype &) = delete;
|
||||
|
||||
// Moving is OK.
|
||||
SnippetPrototype(SnippetPrototype &&) noexcept;
|
||||
SnippetPrototype &operator=(SnippetPrototype &&) noexcept;
|
||||
|
||||
std::string Explanation;
|
||||
std::vector<InstructionInstance> Snippet;
|
||||
};
|
||||
|
||||
// Represents the assignment of a Register to an Operand.
|
||||
struct RegisterOperandAssignment {
|
||||
RegisterOperandAssignment(const Operand *Operand, llvm::MCPhysReg Reg)
|
||||
|
||||
@@ -102,11 +102,12 @@ UopsBenchmarkRunner::isInfeasible(const llvm::MCInstrDesc &MCInstrDesc) const {
|
||||
}
|
||||
|
||||
// Returns whether this Variable ties Use and Def operands together.
|
||||
static bool hasTiedOperands(const Variable &Var) {
|
||||
static bool hasTiedOperands(const Instruction &Instr, const Variable &Var) {
|
||||
bool HasUse = false;
|
||||
bool HasDef = false;
|
||||
for (const Operand *Op : Var.TiedOperands) {
|
||||
if (Op->IsDef)
|
||||
for (const unsigned OpIndex : Var.TiedOperands) {
|
||||
const Operand &Op = Instr.Operands[OpIndex];
|
||||
if (Op.IsDef)
|
||||
HasDef = true;
|
||||
else
|
||||
HasUse = true;
|
||||
@@ -118,7 +119,7 @@ static llvm::SmallVector<const Variable *, 8>
|
||||
getTiedVariables(const Instruction &Instr) {
|
||||
llvm::SmallVector<const Variable *, 8> Result;
|
||||
for (const auto &Var : Instr.Variables)
|
||||
if (hasTiedOperands(Var))
|
||||
if (hasTiedOperands(Instr, Var))
|
||||
Result.push_back(&Var);
|
||||
return Result;
|
||||
}
|
||||
@@ -135,26 +136,24 @@ InstructionBenchmark::ModeE UopsBenchmarkRunner::getMode() const {
|
||||
return InstructionBenchmark::Uops;
|
||||
}
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration>
|
||||
UopsBenchmarkRunner::generateConfiguration(unsigned Opcode) const {
|
||||
llvm::Expected<SnippetPrototype>
|
||||
UopsBenchmarkRunner::generatePrototype(unsigned Opcode) const {
|
||||
const auto &InstrDesc = MCInstrInfo.get(Opcode);
|
||||
if (auto E = isInfeasible(InstrDesc))
|
||||
return std::move(E);
|
||||
const Instruction Instr(InstrDesc, RATC);
|
||||
const AliasingConfigurations SelfAliasing(Instr, Instr);
|
||||
if (SelfAliasing.empty()) {
|
||||
InstructionInstance II(Instr);
|
||||
BenchmarkConfiguration Conf;
|
||||
Conf.Info = "instruction is parallel, repeating a random one.";
|
||||
Conf.Snippet = {II.randomizeUnsetVariablesAndBuild()};
|
||||
return Conf;
|
||||
SnippetPrototype Prototype;
|
||||
Prototype.Explanation = "instruction is parallel, repeating a random one.";
|
||||
Prototype.Snippet.emplace_back(Instr);
|
||||
return Prototype;
|
||||
}
|
||||
if (SelfAliasing.hasImplicitAliasing()) {
|
||||
InstructionInstance II(Instr);
|
||||
BenchmarkConfiguration Conf;
|
||||
Conf.Info = "instruction is serial, repeating a random one.";
|
||||
Conf.Snippet = {II.randomizeUnsetVariablesAndBuild()};
|
||||
return Conf;
|
||||
SnippetPrototype Prototype;
|
||||
Prototype.Explanation = "instruction is serial, repeating a random one.";
|
||||
Prototype.Snippet.emplace_back(Instr);
|
||||
return Prototype;
|
||||
}
|
||||
const auto TiedVariables = getTiedVariables(Instr);
|
||||
if (!TiedVariables.empty()) {
|
||||
@@ -162,19 +161,20 @@ UopsBenchmarkRunner::generateConfiguration(unsigned Opcode) const {
|
||||
return llvm::make_error<llvm::StringError>(
|
||||
"Infeasible : don't know how to handle several tied variables",
|
||||
llvm::inconvertibleErrorCode());
|
||||
BenchmarkConfiguration Conf;
|
||||
Conf.Info = "instruction has tied variables using static renaming.";
|
||||
const Variable *Var = TiedVariables.front();
|
||||
assert(Var);
|
||||
assert(!Var->TiedOperands.empty());
|
||||
const Operand &Operand = *Var->TiedOperands.front();
|
||||
assert(Operand.Tracker);
|
||||
for (const llvm::MCPhysReg Reg : Operand.Tracker->sourceBits().set_bits()) {
|
||||
InstructionInstance II(Instr);
|
||||
II.getValueFor(*Var) = llvm::MCOperand::createReg(Reg);
|
||||
Conf.Snippet.push_back(II.randomizeUnsetVariablesAndBuild());
|
||||
const Operand &Op = Instr.Operands[Var->TiedOperands.front()];
|
||||
assert(Op.Tracker);
|
||||
SnippetPrototype Prototype;
|
||||
Prototype.Explanation =
|
||||
"instruction has tied variables using static renaming.";
|
||||
for (const llvm::MCPhysReg Reg : Op.Tracker->sourceBits().set_bits()) {
|
||||
Prototype.Snippet.emplace_back(Instr);
|
||||
Prototype.Snippet.back().getValueFor(*Var) =
|
||||
llvm::MCOperand::createReg(Reg);
|
||||
}
|
||||
return Conf;
|
||||
return Prototype;
|
||||
}
|
||||
InstructionInstance II(Instr);
|
||||
// No tied variables, we pick random values for defs.
|
||||
@@ -201,19 +201,11 @@ UopsBenchmarkRunner::generateConfiguration(unsigned Opcode) const {
|
||||
II.getValueFor(Op) = llvm::MCOperand::createReg(RandomReg);
|
||||
}
|
||||
}
|
||||
BenchmarkConfiguration Conf;
|
||||
Conf.Info =
|
||||
SnippetPrototype Prototype;
|
||||
Prototype.Explanation =
|
||||
"instruction has no tied variables picking Uses different from defs";
|
||||
Conf.Snippet = {II.randomizeUnsetVariablesAndBuild()};
|
||||
return Conf;
|
||||
}
|
||||
|
||||
llvm::Expected<std::vector<BenchmarkConfiguration>>
|
||||
UopsBenchmarkRunner::createConfigurations(unsigned Opcode) const {
|
||||
if (auto E = generateConfiguration(Opcode))
|
||||
return std::vector<BenchmarkConfiguration>{E.get()};
|
||||
else
|
||||
return E.takeError();
|
||||
Prototype.Snippet.push_back(std::move(II));
|
||||
return Prototype;
|
||||
}
|
||||
|
||||
std::vector<BenchmarkMeasure>
|
||||
|
||||
@@ -24,17 +24,14 @@ public:
|
||||
using BenchmarkRunner::BenchmarkRunner;
|
||||
~UopsBenchmarkRunner() override;
|
||||
|
||||
llvm::Expected<BenchmarkConfiguration>
|
||||
generateConfiguration(unsigned Opcode) const;
|
||||
llvm::Expected<SnippetPrototype>
|
||||
generatePrototype(unsigned Opcode) const override;
|
||||
|
||||
private:
|
||||
llvm::Error isInfeasible(const llvm::MCInstrDesc &MCInstrDesc) const;
|
||||
|
||||
InstructionBenchmark::ModeE getMode() const override;
|
||||
|
||||
llvm::Expected<std::vector<BenchmarkConfiguration>>
|
||||
createConfigurations(unsigned Opcode) const override;
|
||||
|
||||
std::vector<BenchmarkMeasure>
|
||||
runMeasurements(const ExecutableFunction &EF,
|
||||
const unsigned NumRepetitions) const override;
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
namespace exegesis {
|
||||
namespace {
|
||||
|
||||
using testing::HasSubstr;
|
||||
using testing::Not;
|
||||
using testing::SizeIs;
|
||||
|
||||
MATCHER(IsInvalid, "") { return !arg.isValid(); }
|
||||
MATCHER(IsReg, "") { return arg.isReg(); }
|
||||
|
||||
class X86SnippetGeneratorTest : public ::testing::Test {
|
||||
protected:
|
||||
X86SnippetGeneratorTest()
|
||||
@@ -38,20 +45,26 @@ protected:
|
||||
const llvm::MCRegisterInfo &MCRegisterInfo;
|
||||
};
|
||||
|
||||
class LatencySnippetGeneratorTest : public X86SnippetGeneratorTest {
|
||||
template <typename BenchmarkRunner>
|
||||
class SnippetGeneratorTest : public X86SnippetGeneratorTest {
|
||||
protected:
|
||||
LatencySnippetGeneratorTest() : Runner(State) {}
|
||||
SnippetGeneratorTest() : Runner(State) {}
|
||||
|
||||
BenchmarkConfiguration checkAndGetConfiguration(unsigned Opcode) {
|
||||
SnippetPrototype checkAndGetConfigurations(unsigned Opcode) {
|
||||
randomGenerator().seed(0); // Initialize seed.
|
||||
auto ConfOrError = Runner.generateConfiguration(Opcode);
|
||||
EXPECT_FALSE(ConfOrError.takeError()); // Valid configuration.
|
||||
return ConfOrError.get();
|
||||
auto ProtoOrError = Runner.generatePrototype(Opcode);
|
||||
EXPECT_FALSE(ProtoOrError.takeError()); // Valid configuration.
|
||||
return std::move(ProtoOrError.get());
|
||||
}
|
||||
|
||||
LatencyBenchmarkRunner Runner;
|
||||
BenchmarkRunner Runner;
|
||||
};
|
||||
|
||||
using LatencySnippetGeneratorTest =
|
||||
SnippetGeneratorTest<LatencyBenchmarkRunner>;
|
||||
|
||||
using UopsSnippetGeneratorTest = SnippetGeneratorTest<UopsBenchmarkRunner>;
|
||||
|
||||
TEST_F(LatencySnippetGeneratorTest, ImplicitSelfDependency) {
|
||||
// ADC16i16 self alias because of implicit use and def.
|
||||
|
||||
@@ -61,17 +74,17 @@ TEST_F(LatencySnippetGeneratorTest, ImplicitSelfDependency) {
|
||||
// implicit use : AX
|
||||
// implicit use : EFLAGS
|
||||
const unsigned Opcode = llvm::X86::ADC16i16;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("implicit"));
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(1));
|
||||
const llvm::MCInst Instr = Conf.Snippet[0];
|
||||
EXPECT_THAT(Instr.getOpcode(), Opcode);
|
||||
EXPECT_THAT(Instr.getNumOperands(), 1);
|
||||
EXPECT_TRUE(Instr.getOperand(0).isImm()); // Use
|
||||
EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::AX);
|
||||
EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[1], llvm::X86::EFLAGS);
|
||||
EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[0], llvm::X86::AX);
|
||||
EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[1], llvm::X86::EFLAGS);
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("implicit"));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(1));
|
||||
const InstructionInstance &II = Proto.Snippet[0];
|
||||
EXPECT_THAT(II.getOpcode(), Opcode);
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(1)); // Imm.
|
||||
EXPECT_THAT(II.VariableValues[0], IsInvalid()) << "Immediate is not set";
|
||||
}
|
||||
|
||||
TEST_F(LatencySnippetGeneratorTest, ExplicitSelfDependency) {
|
||||
@@ -82,18 +95,15 @@ TEST_F(LatencySnippetGeneratorTest, ExplicitSelfDependency) {
|
||||
// explicit use 2 : imm
|
||||
// implicit def : EFLAGS
|
||||
const unsigned Opcode = llvm::X86::ADD16ri;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("explicit"));
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(1));
|
||||
const llvm::MCInst Instr = Conf.Snippet[0];
|
||||
EXPECT_THAT(Instr.getOpcode(), Opcode);
|
||||
EXPECT_THAT(Instr.getNumOperands(), 3);
|
||||
EXPECT_TRUE(Instr.getOperand(0).isReg());
|
||||
EXPECT_TRUE(Instr.getOperand(1).isReg());
|
||||
EXPECT_THAT(Instr.getOperand(0).getReg(), Instr.getOperand(1).getReg())
|
||||
<< "Op0 and Op1 should have the same value";
|
||||
EXPECT_TRUE(Instr.getOperand(2).isImm());
|
||||
EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::EFLAGS);
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("explicit"));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(1));
|
||||
const InstructionInstance &II = Proto.Snippet[0];
|
||||
EXPECT_THAT(II.getOpcode(), Opcode);
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(2));
|
||||
EXPECT_THAT(II.VariableValues[0], IsReg()) << "Operand 0 and 1";
|
||||
EXPECT_THAT(II.VariableValues[1], IsInvalid()) << "Operand 2 is not set";
|
||||
}
|
||||
|
||||
TEST_F(LatencySnippetGeneratorTest, DependencyThroughOtherOpcode) {
|
||||
@@ -103,49 +113,43 @@ TEST_F(LatencySnippetGeneratorTest, DependencyThroughOtherOpcode) {
|
||||
// implicit def : EFLAGS
|
||||
|
||||
const unsigned Opcode = llvm::X86::CMP64rr;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("cycle through"));
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(2));
|
||||
const llvm::MCInst Instr = Conf.Snippet[0];
|
||||
EXPECT_THAT(Instr.getOpcode(), Opcode);
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through"));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(2));
|
||||
const InstructionInstance &II = Proto.Snippet[0];
|
||||
EXPECT_THAT(II.getOpcode(), Opcode);
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(2));
|
||||
EXPECT_THAT(II.VariableValues[0], IsReg());
|
||||
EXPECT_THAT(II.VariableValues[1], IsInvalid());
|
||||
EXPECT_THAT(Proto.Snippet[1].getOpcode(), Not(Opcode));
|
||||
// TODO: check that the two instructions alias each other.
|
||||
}
|
||||
|
||||
TEST_F(LatencySnippetGeneratorTest, LAHF) {
|
||||
const unsigned Opcode = llvm::X86::LAHF;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("cycle through"));
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(2));
|
||||
const llvm::MCInst Instr = Conf.Snippet[0];
|
||||
EXPECT_THAT(Instr.getOpcode(), Opcode);
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through"));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(2));
|
||||
const InstructionInstance &II = Proto.Snippet[0];
|
||||
EXPECT_THAT(II.getOpcode(), Opcode);
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(0));
|
||||
}
|
||||
|
||||
class UopsSnippetGeneratorTest : public X86SnippetGeneratorTest {
|
||||
protected:
|
||||
UopsSnippetGeneratorTest() : Runner(State) {}
|
||||
|
||||
BenchmarkConfiguration checkAndGetConfiguration(unsigned Opcode) {
|
||||
randomGenerator().seed(0); // Initialize seed.
|
||||
auto ConfOrError = Runner.generateConfiguration(Opcode);
|
||||
EXPECT_FALSE(ConfOrError.takeError()); // Valid configuration.
|
||||
return ConfOrError.get();
|
||||
}
|
||||
|
||||
UopsBenchmarkRunner Runner;
|
||||
};
|
||||
|
||||
TEST_F(UopsSnippetGeneratorTest, ParallelInstruction) {
|
||||
// BNDCL32rr is parallelno matter what.
|
||||
// BNDCL32rr is parallel no matter what.
|
||||
|
||||
// explicit use 0 : reg RegClass=BNDR
|
||||
// explicit use 1 : reg RegClass=GR32
|
||||
|
||||
const unsigned Opcode = llvm::X86::BNDCL32rr;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("parallel"));
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(1));
|
||||
const llvm::MCInst Instr = Conf.Snippet[0];
|
||||
EXPECT_THAT(Instr.getOpcode(), Opcode);
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("parallel"));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(1));
|
||||
const InstructionInstance &II = Proto.Snippet[0];
|
||||
EXPECT_THAT(II.getOpcode(), Opcode);
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(2));
|
||||
EXPECT_THAT(II.VariableValues[0], IsInvalid());
|
||||
EXPECT_THAT(II.VariableValues[1], IsInvalid());
|
||||
}
|
||||
|
||||
TEST_F(UopsSnippetGeneratorTest, SerialInstruction) {
|
||||
@@ -155,11 +159,12 @@ TEST_F(UopsSnippetGeneratorTest, SerialInstruction) {
|
||||
// implicit def : EDX
|
||||
// implicit use : EAX
|
||||
const unsigned Opcode = llvm::X86::CDQ;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("serial"));
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(1));
|
||||
const llvm::MCInst Instr = Conf.Snippet[0];
|
||||
EXPECT_THAT(Instr.getOpcode(), Opcode);
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("serial"));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(1));
|
||||
const InstructionInstance &II = Proto.Snippet[0];
|
||||
EXPECT_THAT(II.getOpcode(), Opcode);
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(0));
|
||||
}
|
||||
|
||||
TEST_F(UopsSnippetGeneratorTest, StaticRenaming) {
|
||||
@@ -171,14 +176,16 @@ TEST_F(UopsSnippetGeneratorTest, StaticRenaming) {
|
||||
// explicit use 2 : reg RegClass=GR32
|
||||
// implicit use : EFLAGS
|
||||
const unsigned Opcode = llvm::X86::CMOVA32rr;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("static renaming"));
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("static renaming"));
|
||||
constexpr const unsigned kInstructionCount = 15;
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(kInstructionCount));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(kInstructionCount));
|
||||
std::unordered_set<unsigned> AllDefRegisters;
|
||||
for (const auto &Inst : Conf.Snippet)
|
||||
AllDefRegisters.insert(Inst.getOperand(0).getReg());
|
||||
EXPECT_THAT(AllDefRegisters, testing::SizeIs(kInstructionCount))
|
||||
for (const auto &II : Proto.Snippet) {
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(2));
|
||||
AllDefRegisters.insert(II.VariableValues[0].getReg());
|
||||
}
|
||||
EXPECT_THAT(AllDefRegisters, SizeIs(kInstructionCount))
|
||||
<< "Each instruction writes to a different register";
|
||||
}
|
||||
|
||||
@@ -192,19 +199,17 @@ TEST_F(UopsSnippetGeneratorTest, NoTiedVariables) {
|
||||
// explicit use 3 : imm
|
||||
// implicit use : EFLAGS
|
||||
const unsigned Opcode = llvm::X86::CMOV_GR32;
|
||||
auto Conf = checkAndGetConfiguration(Opcode);
|
||||
EXPECT_THAT(Conf.Info, testing::HasSubstr("no tied variables"));
|
||||
ASSERT_THAT(Conf.Snippet, testing::SizeIs(1));
|
||||
const llvm::MCInst Instr = Conf.Snippet[0];
|
||||
EXPECT_THAT(Instr.getOpcode(), Opcode);
|
||||
EXPECT_THAT(Instr.getNumOperands(), 4);
|
||||
EXPECT_THAT(Instr.getOperand(0).getReg(),
|
||||
testing::Not(Instr.getOperand(1).getReg()))
|
||||
const SnippetPrototype Proto = checkAndGetConfigurations(Opcode);
|
||||
EXPECT_THAT(Proto.Explanation, HasSubstr("no tied variables"));
|
||||
ASSERT_THAT(Proto.Snippet, SizeIs(1));
|
||||
const InstructionInstance &II = Proto.Snippet[0];
|
||||
EXPECT_THAT(II.getOpcode(), Opcode);
|
||||
ASSERT_THAT(II.VariableValues, SizeIs(4));
|
||||
EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[1].getReg()))
|
||||
<< "Def is different from first Use";
|
||||
EXPECT_THAT(Instr.getOperand(0).getReg(),
|
||||
testing::Not(Instr.getOperand(2).getReg()))
|
||||
EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[2].getReg()))
|
||||
<< "Def is different from second Use";
|
||||
EXPECT_THAT(Instr.getOperand(3).getImm(), 1);
|
||||
EXPECT_THAT(II.VariableValues[3], IsInvalid());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user