[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:
Guillaume Chatelet
2018-06-20 08:52:30 +00:00
parent 2df331b0f7
commit ef6cef5b57
9 changed files with 228 additions and 170 deletions

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;

View File

@@ -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