1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-25 04:02:41 +01:00

[llvm-exegesis] Exploring X86::OperandType::OPERAND_COND_CODE

Summary:
Currently, we only have nice exploration for LEA instruction,
while for the rest, we rely on `randomizeUnsetVariables()`
to sometimes generate something interesting.
While that works, it isn't very reliable in coverage :)

Here, i'm making an assumption that while we may want to explore
multi-instruction configs, we are most interested in the
characteristics of the main instruction we were asked about.

Which we can do, by taking the existing `randomizeMCOperand()`,
and turning it on it's head - instead of relying on it to randomly fill
one of the interesting values, let's pregenerate all the possible interesting
values for the variable, and then generate as much `InstructionTemplate`
combinations of these possible values for variables as needed/possible.

Of course, that requires invasive changes to no longer pass just the
naked `Instruction`, but sometimes partially filled `InstructionTemplate`.

As it can be seen from the test, this allows us to explore
`X86::OperandType::OPERAND_COND_CODE` for instructions
that take such an operand.
I'm hoping this will greatly simplify exploration.

Reviewers: courbet, gchatelet

Reviewed By: gchatelet

Subscribers: orodley, mgorny, sdardis, tschuett, jrtc27, atanasyan, mstojanovic, andreadb, RKSimon, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D74156
This commit is contained in:
Roman Lebedev 2020-02-12 20:54:39 +03:00
parent 8658e137b1
commit 92ce5b3319
15 changed files with 523 additions and 85 deletions

View File

@ -0,0 +1,25 @@
# RUN: llvm-exegesis -mode=latency -opcode-name=SETCCr --max-configs-per-opcode=1 | FileCheck %s --check-prefix=CHECK
# RUN: llvm-exegesis -mode=latency -opcode-name=SETCCr --max-configs-per-opcode=256 | FileCheck %s --check-prefix=SWEEP
CHECK: ---
CHECK-NEXT: mode: latency
CHECK-NEXT: key:
CHECK-NEXT: instructions:
CHECK-NEXT: 'SETCCr {{.*}} i_0x{{[0-9a-f]}}'
SWEEP-DAG: 'SETCCr {{.*}} i_0x0'
SWEEP-DAG: 'SETCCr {{.*}} i_0x1'
SWEEP-DAG: 'SETCCr {{.*}} i_0x2'
SWEEP-DAG: 'SETCCr {{.*}} i_0x3'
SWEEP-DAG: 'SETCCr {{.*}} i_0x4'
SWEEP-DAG: 'SETCCr {{.*}} i_0x5'
SWEEP-DAG: 'SETCCr {{.*}} i_0x6'
SWEEP-DAG: 'SETCCr {{.*}} i_0x7'
SWEEP-DAG: 'SETCCr {{.*}} i_0x8'
SWEEP-DAG: 'SETCCr {{.*}} i_0x9'
SWEEP-DAG: 'SETCCr {{.*}} i_0xa'
SWEEP-DAG: 'SETCCr {{.*}} i_0xb'
SWEEP-DAG: 'SETCCr {{.*}} i_0xc'
SWEEP-DAG: 'SETCCr {{.*}} i_0xd'
SWEEP-DAG: 'SETCCr {{.*}} i_0xe'
SWEEP-DAG: 'SETCCr {{.*}} i_0xf'

View File

@ -38,6 +38,11 @@ struct InstructionTemplate {
bool hasImmediateVariables() const;
const Instruction &getInstr() const { return *Instr; }
ArrayRef<MCOperand> getVariableValues() const { return VariableValues; }
void setVariableValues(ArrayRef<MCOperand> NewVariableValues) {
assert(VariableValues.size() == NewVariableValues.size() &&
"Value count mismatch");
VariableValues.assign(NewVariableValues.begin(), NewVariableValues.end());
}
// Builds an MCInst from this InstructionTemplate setting its operands
// to the corresponding variable values. Precondition: All VariableValues must

View File

@ -154,8 +154,10 @@ static std::vector<InstructionTemplate> generateSnippetUsingStaticRenaming(
}
}
Expected<std::vector<CodeTemplate>> ParallelSnippetGenerator::generateCodeTemplates(
const Instruction &Instr, const BitVector &ForbiddenRegisters) const {
Expected<std::vector<CodeTemplate>>
ParallelSnippetGenerator::generateCodeTemplates(
InstructionTemplate Variant, const BitVector &ForbiddenRegisters) const {
const Instruction &Instr = Variant.getInstr();
CodeTemplate CT;
CT.ScratchSpacePointerInReg =
Instr.hasMemoryOperands()
@ -163,16 +165,15 @@ Expected<std::vector<CodeTemplate>> ParallelSnippetGenerator::generateCodeTempla
State.getTargetMachine().getTargetTriple())
: 0;
const AliasingConfigurations SelfAliasing(Instr, Instr);
InstructionTemplate IT(&Instr);
if (SelfAliasing.empty()) {
CT.Info = "instruction is parallel, repeating a random one.";
CT.Instructions.push_back(std::move(IT));
CT.Instructions.push_back(std::move(Variant));
instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
return getSingleton(std::move(CT));
}
if (SelfAliasing.hasImplicitAliasing()) {
CT.Info = "instruction is serial, repeating a random one.";
CT.Instructions.push_back(std::move(IT));
CT.Instructions.push_back(std::move(Variant));
instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
return getSingleton(std::move(CT));
}
@ -180,7 +181,7 @@ Expected<std::vector<CodeTemplate>> ParallelSnippetGenerator::generateCodeTempla
if (!TiedVariables.empty()) {
CT.Info = "instruction has tied variables, using static renaming.";
CT.Instructions = generateSnippetUsingStaticRenaming(
State, IT, TiedVariables, ForbiddenRegisters);
State, Variant, TiedVariables, ForbiddenRegisters);
instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
return getSingleton(std::move(CT));
}
@ -194,7 +195,7 @@ Expected<std::vector<CodeTemplate>> ParallelSnippetGenerator::generateCodeTempla
assert(PossibleRegisters.any() && "No register left to choose from");
const auto RandomReg = randomBit(PossibleRegisters);
Defs.set(RandomReg);
IT.getValueFor(Op) = MCOperand::createReg(RandomReg);
Variant.getValueFor(Op) = MCOperand::createReg(RandomReg);
}
}
// And pick random use values that are not reserved and don't alias with defs.
@ -206,12 +207,12 @@ Expected<std::vector<CodeTemplate>> ParallelSnippetGenerator::generateCodeTempla
remove(PossibleRegisters, DefAliases);
assert(PossibleRegisters.any() && "No register left to choose from");
const auto RandomReg = randomBit(PossibleRegisters);
IT.getValueFor(Op) = MCOperand::createReg(RandomReg);
Variant.getValueFor(Op) = MCOperand::createReg(RandomReg);
}
}
CT.Info =
"instruction has no tied variables picking Uses different from defs";
CT.Instructions.push_back(std::move(IT));
CT.Instructions.push_back(std::move(Variant));
instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
return getSingleton(std::move(CT));
}

View File

@ -25,7 +25,7 @@ public:
~ParallelSnippetGenerator() override;
Expected<std::vector<CodeTemplate>>
generateCodeTemplates(const Instruction &Instr,
generateCodeTemplates(InstructionTemplate Variant,
const BitVector &ForbiddenRegisters) const override;
static constexpr const size_t kMinNumDifferentAddresses = 6;

View File

@ -87,7 +87,7 @@ static ExecutionMode getExecutionModes(const Instruction &Instr,
}
static void appendCodeTemplates(const LLVMState &State,
const Instruction *Instr,
InstructionTemplate Variant,
const BitVector &ForbiddenRegisters,
ExecutionMode ExecutionModeBit,
StringRef ExecutionClassDescription,
@ -103,7 +103,7 @@ static void appendCodeTemplates(const LLVMState &State,
CodeTemplate CT;
CT.Execution = ExecutionModeBit;
CT.Info = std::string(ExecutionClassDescription);
CT.Instructions.push_back(Instr);
CT.Instructions.push_back(std::move(Variant));
CodeTemplates.push_back(std::move(CT));
return;
}
@ -115,27 +115,28 @@ static void appendCodeTemplates(const LLVMState &State,
case ExecutionMode::SERIAL_VIA_EXPLICIT_REGS: {
// Making the execution of this instruction serial by selecting one def
// register to alias with one use register.
const AliasingConfigurations SelfAliasing(*Instr, *Instr);
const AliasingConfigurations SelfAliasing(Variant.getInstr(),
Variant.getInstr());
assert(!SelfAliasing.empty() && !SelfAliasing.hasImplicitAliasing() &&
"Instr must alias itself explicitly");
InstructionTemplate IT(Instr);
// This is a self aliasing instruction so defs and uses are from the same
// instance, hence twice IT in the following call.
setRandomAliasing(SelfAliasing, IT, IT);
// instance, hence twice Variant in the following call.
setRandomAliasing(SelfAliasing, Variant, Variant);
CodeTemplate CT;
CT.Execution = ExecutionModeBit;
CT.Info = std::string(ExecutionClassDescription);
CT.Instructions.push_back(std::move(IT));
CT.Instructions.push_back(std::move(Variant));
CodeTemplates.push_back(std::move(CT));
return;
}
case ExecutionMode::SERIAL_VIA_NON_MEMORY_INSTR: {
const Instruction &Instr = Variant.getInstr();
// Select back-to-back non-memory instruction.
for (const auto *OtherInstr : computeAliasingInstructions(
State, Instr, kMaxAliasingInstructions, ForbiddenRegisters)) {
const AliasingConfigurations Forward(*Instr, *OtherInstr);
const AliasingConfigurations Back(*OtherInstr, *Instr);
InstructionTemplate ThisIT(Instr);
State, &Instr, kMaxAliasingInstructions, ForbiddenRegisters)) {
const AliasingConfigurations Forward(Instr, *OtherInstr);
const AliasingConfigurations Back(*OtherInstr, Instr);
InstructionTemplate ThisIT(Variant);
InstructionTemplate OtherIT(OtherInstr);
if (!Forward.hasImplicitAliasing())
setRandomAliasing(Forward, ThisIT, OtherIT);
@ -159,12 +160,13 @@ SerialSnippetGenerator::~SerialSnippetGenerator() = default;
Expected<std::vector<CodeTemplate>>
SerialSnippetGenerator::generateCodeTemplates(
const Instruction &Instr, const BitVector &ForbiddenRegisters) const {
InstructionTemplate Variant, const BitVector &ForbiddenRegisters) const {
std::vector<CodeTemplate> Results;
const ExecutionMode EM = getExecutionModes(Instr, ForbiddenRegisters);
const ExecutionMode EM =
getExecutionModes(Variant.getInstr(), ForbiddenRegisters);
for (const auto EC : kExecutionClasses) {
for (const auto ExecutionModeBit : getExecutionModeBits(EM & EC.Mask))
appendCodeTemplates(State, &Instr, ForbiddenRegisters, ExecutionModeBit,
appendCodeTemplates(State, Variant, ForbiddenRegisters, ExecutionModeBit,
EC.Description, Results);
if (!Results.empty())
break;

View File

@ -27,7 +27,7 @@ public:
~SerialSnippetGenerator() override;
Expected<std::vector<CodeTemplate>>
generateCodeTemplates(const Instruction &Instr,
generateCodeTemplates(InstructionTemplate Variant,
const BitVector &ForbiddenRegisters) const override;
};

View File

@ -38,13 +38,14 @@ SnippetGenerator::SnippetGenerator(const LLVMState &State, const Options &Opts)
SnippetGenerator::~SnippetGenerator() = default;
Expected<std::vector<BenchmarkCode>> SnippetGenerator::generateConfigurations(
const Instruction &Instr, const BitVector &ExtraForbiddenRegs) const {
Error SnippetGenerator::generateConfigurations(
const InstructionTemplate &Variant, std::vector<BenchmarkCode> &Benchmarks,
const BitVector &ExtraForbiddenRegs) const {
BitVector ForbiddenRegs = State.getRATC().reservedRegisters();
ForbiddenRegs |= ExtraForbiddenRegs;
// If the instruction has memory registers, prevent the generator from
// using the scratch register and its aliasing registers.
if (Instr.hasMemoryOperands()) {
if (Variant.getInstr().hasMemoryOperands()) {
const auto &ET = State.getExegesisTarget();
unsigned ScratchSpacePointerInReg =
ET.getScratchMemoryRegister(State.getTargetMachine().getTargetTriple());
@ -55,7 +56,7 @@ Expected<std::vector<BenchmarkCode>> SnippetGenerator::generateConfigurations(
State.getRATC().getRegister(ScratchSpacePointerInReg).aliasedBits();
// If the instruction implicitly writes to ScratchSpacePointerInReg , abort.
// FIXME: We could make a copy of the scratch register.
for (const auto &Op : Instr.Operands) {
for (const auto &Op : Variant.getInstr().Operands) {
if (Op.isDef() && Op.isImplicitReg() &&
ScratchRegAliases.test(Op.getImplicitReg()))
return make_error<Failure>(
@ -64,16 +65,19 @@ Expected<std::vector<BenchmarkCode>> SnippetGenerator::generateConfigurations(
ForbiddenRegs |= ScratchRegAliases;
}
if (auto E = generateCodeTemplates(Instr, ForbiddenRegs)) {
std::vector<BenchmarkCode> Output;
for (CodeTemplate &CT : E.get()) {
if (auto E = generateCodeTemplates(Variant, ForbiddenRegs)) {
MutableArrayRef<CodeTemplate> Templates = E.get();
// Avoid reallocations in the loop.
Benchmarks.reserve(Benchmarks.size() + Templates.size());
for (CodeTemplate &CT : Templates) {
// TODO: Generate as many BenchmarkCode as needed.
{
BenchmarkCode BC;
BC.Info = CT.Info;
for (InstructionTemplate &IT : CT.Instructions) {
if (auto error = randomizeUnsetVariables(State, ForbiddenRegs, IT))
return std::move(error);
return error;
BC.Key.Instructions.push_back(IT.build());
}
if (CT.ScratchSpacePointerInReg)
@ -81,13 +85,14 @@ Expected<std::vector<BenchmarkCode>> SnippetGenerator::generateConfigurations(
BC.Key.RegisterInitialValues =
computeRegisterInitialValues(CT.Instructions);
BC.Key.Config = CT.Config;
Output.push_back(std::move(BC));
if (Output.size() >= Opts.MaxConfigsPerOpcode)
return Output; // Early exit if we exceeded the number of allowed
// configs.
Benchmarks.emplace_back(std::move(BC));
if (Benchmarks.size() >= Opts.MaxConfigsPerOpcode) {
// We reached the number of allowed configs and return early.
return Error::success();
}
}
}
return Output;
return Error::success();
} else
return E.takeError();
}
@ -135,34 +140,35 @@ std::vector<RegisterValue> SnippetGenerator::computeRegisterInitialValues(
}
Expected<std::vector<CodeTemplate>>
generateSelfAliasingCodeTemplates(const Instruction &Instr) {
const AliasingConfigurations SelfAliasing(Instr, Instr);
generateSelfAliasingCodeTemplates(InstructionTemplate Variant) {
const AliasingConfigurations SelfAliasing(Variant.getInstr(),
Variant.getInstr());
if (SelfAliasing.empty())
return make_error<SnippetGeneratorFailure>("empty self aliasing");
std::vector<CodeTemplate> Result;
Result.emplace_back();
CodeTemplate &CT = Result.back();
InstructionTemplate IT(&Instr);
if (SelfAliasing.hasImplicitAliasing()) {
CT.Info = "implicit Self cycles, picking random values.";
} else {
CT.Info = "explicit self cycles, selecting one aliasing Conf.";
// This is a self aliasing instruction so defs and uses are from the same
// instance, hence twice IT in the following call.
setRandomAliasing(SelfAliasing, IT, IT);
// instance, hence twice Variant in the following call.
setRandomAliasing(SelfAliasing, Variant, Variant);
}
CT.Instructions.push_back(std::move(IT));
CT.Instructions.push_back(std::move(Variant));
return std::move(Result);
}
Expected<std::vector<CodeTemplate>>
generateUnconstrainedCodeTemplates(const Instruction &Instr, StringRef Msg) {
generateUnconstrainedCodeTemplates(const InstructionTemplate &Variant,
StringRef Msg) {
std::vector<CodeTemplate> Result;
Result.emplace_back();
CodeTemplate &CT = Result.back();
CT.Info =
std::string(formatv("{0}, repeating an unconstrained assignment", Msg));
CT.Instructions.emplace_back(&Instr);
CT.Instructions.push_back(std::move(Variant));
return std::move(Result);
}

View File

@ -34,11 +34,12 @@ std::vector<CodeTemplate> getSingleton(CodeTemplate &&CT);
// Generates code templates that has a self-dependency.
Expected<std::vector<CodeTemplate>>
generateSelfAliasingCodeTemplates(const Instruction &Instr);
generateSelfAliasingCodeTemplates(InstructionTemplate Variant);
// Generates code templates without assignment constraints.
Expected<std::vector<CodeTemplate>>
generateUnconstrainedCodeTemplates(const Instruction &Instr, StringRef Msg);
generateUnconstrainedCodeTemplates(const InstructionTemplate &Variant,
StringRef Msg);
// A class representing failures that happened during Benchmark, they are used
// to report informations to the user.
@ -59,9 +60,9 @@ public:
virtual ~SnippetGenerator();
// Calls generateCodeTemplate and expands it into one or more BenchmarkCode.
Expected<std::vector<BenchmarkCode>>
generateConfigurations(const Instruction &Instr,
const BitVector &ExtraForbiddenRegs) const;
Error generateConfigurations(const InstructionTemplate &Variant,
std::vector<BenchmarkCode> &Benchmarks,
const BitVector &ExtraForbiddenRegs) const;
// Given a snippet, computes which registers the setup code needs to define.
std::vector<RegisterValue> computeRegisterInitialValues(
@ -74,7 +75,7 @@ protected:
private:
// API to be implemented by subclasses.
virtual Expected<std::vector<CodeTemplate>>
generateCodeTemplates(const Instruction &Instr,
generateCodeTemplates(InstructionTemplate Variant,
const BitVector &ForbiddenRegisters) const = 0;
};
@ -101,6 +102,132 @@ Error randomizeUnsetVariables(const LLVMState &State,
const BitVector &ForbiddenRegs,
InstructionTemplate &IT);
// Combination generator.
//
// Example: given input {{0, 1}, {2}, {3, 4}} it will produce the following
// combinations: {0, 2, 3}, {0, 2, 4}, {1, 2, 3}, {1, 2, 4}.
//
// It is important to think of input as vector-of-vectors, where the
// outer vector is the variable space, and inner vector is choice space.
// The number of choices for each variable can be different.
//
// As for implementation, it is useful to think of this as a weird number,
// where each digit (==variable) may have different base (==number of choices).
// Thus modelling of 'produce next combination' is exactly analogous to the
// incrementing of an number - increment lowest digit (pick next choice for the
// variable), and if it wrapped to the beginning then increment next digit.
template <typename choice_type, typename choices_storage_type,
int variable_smallsize>
class CombinationGenerator {
template <typename T> struct WrappingIterator {
using value_type = T;
const ArrayRef<value_type> Range;
typename decltype(Range)::const_iterator Position;
// Rewind the tape, placing the position to again point at the beginning.
void rewind() { Position = Range.begin(); }
// Advance position forward, possibly wrapping to the beginning.
// Returns whether the wrap happened.
bool operator++() {
++Position;
bool Wrapped = Position == Range.end();
if (Wrapped)
rewind();
return Wrapped;
}
// Get the value at which we are currently pointing.
operator const value_type &() const { return *Position; }
WrappingIterator(ArrayRef<value_type> Range_) : Range(Range_) {
assert(!Range.empty() && "The range must not be empty.");
rewind();
}
// Only allow using our custom constructor.
WrappingIterator() = delete;
WrappingIterator(const WrappingIterator &) = delete;
WrappingIterator(WrappingIterator &&) = delete;
WrappingIterator &operator=(WrappingIterator) = delete;
WrappingIterator &operator=(const WrappingIterator &) = delete;
WrappingIterator &operator=(WrappingIterator &&) = delete;
};
const ArrayRef<choices_storage_type> VariablesChoices;
const function_ref<bool(ArrayRef<choice_type>)> &Callback;
void performGeneration() const {
SmallVector<WrappingIterator<choice_type>, variable_smallsize>
VariablesState;
// Initialize the per-variable state to refer to the possible choices for
// that variable.
VariablesState.reserve(VariablesChoices.size());
for (ArrayRef<choice_type> VariablesChoices : VariablesChoices)
VariablesState.emplace_back(VariablesChoices);
// Temporary buffer to store each combination before performing Callback.
SmallVector<choice_type, variable_smallsize> CurrentCombination;
CurrentCombination.resize(VariablesState.size());
while (true) {
// Gather the currently-selected variable choices into a vector.
for (auto I : llvm::zip(VariablesState, CurrentCombination))
std::get<1>(I) = std::get<0>(I);
// And pass the new combination into callback, as intended.
if (/*Abort=*/Callback(CurrentCombination))
return;
// 'increment' the whole VariablesState, much like you would increment
// a number: starting from the least significant element, increment it,
// and if it wrapped, then propagate that carry by also incrementing next
// (more significant) element.
for (WrappingIterator<choice_type> &VariableState :
llvm::reverse(VariablesState)) {
bool Wrapped = ++VariableState;
if (!Wrapped)
break;
if (VariablesState.begin() == &VariableState)
return; // The "most significant" variable has wrapped, which means
// that we have produced all the combinations.
// We have carry - increment more significant variable next..
}
}
};
public:
CombinationGenerator(ArrayRef<choices_storage_type> VariablesChoices_,
const function_ref<bool(ArrayRef<choice_type>)> &Cb_)
: VariablesChoices(VariablesChoices_), Callback(Cb_) {
#ifndef NDEBUG
assert(!VariablesChoices.empty() && "There should be some variables.");
llvm::for_each(VariablesChoices, [](ArrayRef<choice_type> VariableChoices) {
assert(!VariableChoices.empty() &&
"There must always be some choice, at least a placeholder one.");
});
#endif
}
// How many combinations can we produce, max?
// This is at most how many times the callback will be called.
size_t numCombinations() const {
size_t NumVariants = 1;
for (ArrayRef<choice_type> VariableChoices : VariablesChoices)
NumVariants *= VariableChoices.size();
assert(NumVariants >= 1 &&
"We should always end up producing at least one combination");
return NumVariants;
}
// Actually perform exhaustive combination generation.
// Each result will be passed into the callback.
void generate() { performGeneration(); }
};
} // namespace exegesis
} // namespace llvm

View File

@ -126,6 +126,16 @@ public:
return true;
}
// For some instructions, it is interesting to measure how it's performance
// characteristics differ depending on it's operands.
// This allows us to produce all the interesting variants.
virtual std::vector<InstructionTemplate>
generateInstructionVariants(const Instruction &Instr,
unsigned MaxConfigsPerOpcode) const {
// By default, we're happy with whatever randomizer will give us.
return {&Instr};
}
// Creates a snippet generator for the given mode.
std::unique_ptr<SnippetGenerator>
createSnippetGenerator(InstructionBenchmark::ModeE Mode,

View File

@ -8,14 +8,15 @@
#include "../Target.h"
#include "../Error.h"
#include "../ParallelSnippetGenerator.h"
#include "../SerialSnippetGenerator.h"
#include "../SnippetGenerator.h"
#include "../ParallelSnippetGenerator.h"
#include "MCTargetDesc/X86BaseInfo.h"
#include "MCTargetDesc/X86MCTargetDesc.h"
#include "X86.h"
#include "X86RegisterInfo.h"
#include "X86Subtarget.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/MC/MCInstBuilder.h"
#include "llvm/Support/FormatVariadic.h"
@ -256,14 +257,16 @@ public:
using SerialSnippetGenerator::SerialSnippetGenerator;
Expected<std::vector<CodeTemplate>>
generateCodeTemplates(const Instruction &Instr,
generateCodeTemplates(InstructionTemplate Variant,
const BitVector &ForbiddenRegisters) const override;
};
} // namespace
Expected<std::vector<CodeTemplate>>
X86SerialSnippetGenerator::generateCodeTemplates(
const Instruction &Instr, const BitVector &ForbiddenRegisters) const {
InstructionTemplate Variant, const BitVector &ForbiddenRegisters) const {
const Instruction &Instr = Variant.getInstr();
if (const auto reason = isInvalidOpcode(Instr))
return make_error<Failure>(reason);
@ -287,8 +290,8 @@ X86SerialSnippetGenerator::generateCodeTemplates(
switch (getX86FPFlags(Instr)) {
case X86II::NotFP:
return SerialSnippetGenerator::generateCodeTemplates(Instr,
ForbiddenRegisters);
return SerialSnippetGenerator::generateCodeTemplates(Variant,
ForbiddenRegisters);
case X86II::ZeroArgFP:
case X86II::OneArgFP:
case X86II::SpecialFP:
@ -301,7 +304,7 @@ X86SerialSnippetGenerator::generateCodeTemplates(
// - `ST(0) = fsqrt(ST(0))` (OneArgFPRW)
// - `ST(0) = ST(0) + ST(i)` (TwoArgFP)
// They are intrinsically serial and do not modify the state of the stack.
return generateSelfAliasingCodeTemplates(Instr);
return generateSelfAliasingCodeTemplates(Variant);
default:
llvm_unreachable("Unknown FP Type!");
}
@ -313,7 +316,7 @@ public:
using ParallelSnippetGenerator::ParallelSnippetGenerator;
Expected<std::vector<CodeTemplate>>
generateCodeTemplates(const Instruction &Instr,
generateCodeTemplates(InstructionTemplate Variant,
const BitVector &ForbiddenRegisters) const override;
};
@ -321,7 +324,9 @@ public:
Expected<std::vector<CodeTemplate>>
X86ParallelSnippetGenerator::generateCodeTemplates(
const Instruction &Instr, const BitVector &ForbiddenRegisters) const {
InstructionTemplate Variant, const BitVector &ForbiddenRegisters) const {
const Instruction &Instr = Variant.getInstr();
if (const auto reason = isInvalidOpcode(Instr))
return make_error<Failure>(reason);
@ -342,8 +347,8 @@ X86ParallelSnippetGenerator::generateCodeTemplates(
switch (getX86FPFlags(Instr)) {
case X86II::NotFP:
return ParallelSnippetGenerator::generateCodeTemplates(Instr,
ForbiddenRegisters);
return ParallelSnippetGenerator::generateCodeTemplates(Variant,
ForbiddenRegisters);
case X86II::ZeroArgFP:
case X86II::OneArgFP:
case X86II::SpecialFP:
@ -355,13 +360,13 @@ X86ParallelSnippetGenerator::generateCodeTemplates(
// - `ST(0) = ST(0) + ST(i)` (TwoArgFP)
// They are intrinsically serial and do not modify the state of the stack.
// We generate the same code for latency and uops.
return generateSelfAliasingCodeTemplates(Instr);
return generateSelfAliasingCodeTemplates(Variant);
case X86II::CompareFP:
case X86II::CondMovFP:
// We can compute uops for any FP instruction that does not grow or shrink
// the stack (either do not touch the stack or push as much as they pop).
return generateUnconstrainedCodeTemplates(
Instr, "instruction does not grow/shrink the FP stack");
Variant, "instruction does not grow/shrink the FP stack");
default:
llvm_unreachable("Unknown FP Type!");
}
@ -592,6 +597,10 @@ private:
Opcode != X86::LEA64_32r && Opcode != X86::LEA16r;
}
std::vector<InstructionTemplate>
generateInstructionVariants(const Instruction &Instr,
unsigned MaxConfigsPerOpcode) const override;
std::unique_ptr<SnippetGenerator> createSerialSnippetGenerator(
const LLVMState &State,
const SnippetGenerator::Options &Opts) const override {
@ -653,10 +662,6 @@ Error ExegesisX86Target::randomizeTargetMCOperand(
AssignedValue =
MCOperand::createImm(randomIndex(X86::STATIC_ROUNDING::TO_ZERO));
return Error::success();
case X86::OperandType::OPERAND_COND_CODE:
AssignedValue =
MCOperand::createImm(randomIndex(X86::CondCode::LAST_VALID_COND));
return Error::success();
default:
break;
}
@ -741,6 +746,63 @@ std::vector<MCInst> ExegesisX86Target::setRegTo(const MCSubtargetInfo &STI,
return {}; // Not yet implemented.
}
// Instruction can have some variable operands, and we may want to see how
// different operands affect performance. So for each operand position,
// precompute all the possible choices we might care about,
// and greedily generate all the possible combinations of choices.
std::vector<InstructionTemplate> ExegesisX86Target::generateInstructionVariants(
const Instruction &Instr, unsigned MaxConfigsPerOpcode) const {
bool Exploration = false;
SmallVector<SmallVector<MCOperand, 1>, 4> VariableChoices;
VariableChoices.resize(Instr.Variables.size());
for (auto I : llvm::zip(Instr.Variables, VariableChoices)) {
const Variable &Var = std::get<0>(I);
SmallVectorImpl<MCOperand> &Choices = std::get<1>(I);
switch (Instr.getPrimaryOperand(Var).getExplicitOperandInfo().OperandType) {
default:
// We don't wish to explicitly explore this variable.
Choices.emplace_back(); // But add invalid MCOperand to simplify logic.
continue;
case X86::OperandType::OPERAND_COND_CODE: {
Exploration = true;
auto CondCodes = seq((int)X86::CondCode::COND_O,
1 + (int)X86::CondCode::LAST_VALID_COND);
Choices.reserve(std::distance(CondCodes.begin(), CondCodes.end()));
for (int CondCode : CondCodes)
Choices.emplace_back(MCOperand::createImm(CondCode));
break;
}
}
}
// If we don't wish to explore any variables, defer to the baseline method.
if (!Exploration)
return ExegesisTarget::generateInstructionVariants(Instr,
MaxConfigsPerOpcode);
std::vector<InstructionTemplate> Variants;
size_t NumVariants;
CombinationGenerator<MCOperand, decltype(VariableChoices)::value_type, 4> G(
VariableChoices, [&](ArrayRef<MCOperand> State) -> bool {
Variants.emplace_back(&Instr);
Variants.back().setVariableValues(State);
// Did we run out of space for variants?
return Variants.size() >= NumVariants;
});
// How many operand combinations can we produce, within the limit?
NumVariants = std::min(G.numCombinations(), (size_t)MaxConfigsPerOpcode);
// And actually produce all the wanted operand combinations.
Variants.reserve(NumVariants);
G.generate();
assert(Variants.size() == NumVariants &&
Variants.size() <= MaxConfigsPerOpcode &&
"Should not produce too many variants");
return Variants;
}
static ExegesisTarget *getTheExegesisX86Target() {
static ExegesisX86Target Target;
return &Target;

View File

@ -238,6 +238,10 @@ generateSnippets(const LLVMState &State, unsigned Opcode,
if (InstrDesc.isCall() || InstrDesc.isReturn())
return make_error<Failure>("Unsupported opcode: isCall/isReturn");
const std::vector<InstructionTemplate> InstructionVariants =
State.getExegesisTarget().generateInstructionVariants(
Instr, MaxConfigsPerOpcode);
SnippetGenerator::Options SnippetOptions;
SnippetOptions.MaxConfigsPerOpcode = MaxConfigsPerOpcode;
const std::unique_ptr<SnippetGenerator> Generator =
@ -245,7 +249,16 @@ generateSnippets(const LLVMState &State, unsigned Opcode,
SnippetOptions);
if (!Generator)
ExitWithError("cannot create snippet generator");
return Generator->generateConfigurations(Instr, ForbiddenRegs);
std::vector<BenchmarkCode> Benchmarks;
for (const InstructionTemplate &Variant : InstructionVariants) {
if (Benchmarks.size() >= MaxConfigsPerOpcode)
break;
if (auto Err = Generator->generateConfigurations(Variant, Benchmarks,
ForbiddenRegs))
return std::move(Err);
}
return Benchmarks;
}
void benchmarkMain() {

View File

@ -15,6 +15,7 @@ add_llvm_unittest(LLVMExegesisTests
ClusteringTest.cpp
PerfHelperTest.cpp
RegisterValueTest.cpp
SnippetGeneratorTest.cpp
)
target_link_libraries(LLVMExegesisTests PRIVATE LLVMExegesis)

View File

@ -40,7 +40,7 @@ protected:
randomGenerator().seed(0); // Initialize seed.
const Instruction &Instr = State.getIC().getInstr(Opcode);
auto CodeTemplateOrError = Generator.generateCodeTemplates(
Instr, State.getRATC().emptyRegisters());
&Instr, State.getRATC().emptyRegisters());
EXPECT_FALSE(CodeTemplateOrError.takeError()); // Valid configuration.
return std::move(CodeTemplateOrError.get());
}
@ -91,7 +91,8 @@ TEST_F(SerialSnippetGeneratorTest,
const Instruction &Instr = State.getIC().getInstr(Mips::XOR);
auto AllRegisters = State.getRATC().emptyRegisters();
AllRegisters.flip();
auto Error = Generator.generateCodeTemplates(Instr, AllRegisters).takeError();
auto Error =
Generator.generateCodeTemplates(&Instr, AllRegisters).takeError();
EXPECT_TRUE((bool)Error);
consumeError(std::move(Error));
}

View File

@ -0,0 +1,183 @@
//===-- SnippetGeneratorTest.cpp --------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "SnippetGenerator.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <initializer_list>
namespace llvm {
namespace exegesis {
namespace {
TEST(CombinationGenerator, Square) {
const std::vector<std::vector<int>> Choices{{0, 1}, {2, 3}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0, 2},
{0, 3},
{1, 2},
{1, 3},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
TEST(CombinationGenerator, MiddleColumn) {
const std::vector<std::vector<int>> Choices{{0}, {1, 2}, {3}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0, 1, 3},
{0, 2, 3},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
TEST(CombinationGenerator, SideColumns) {
const std::vector<std::vector<int>> Choices{{0, 1}, {2}, {3, 4}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0, 2, 3},
{0, 2, 4},
{1, 2, 3},
{1, 2, 4},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
TEST(CombinationGenerator, LeftColumn) {
const std::vector<std::vector<int>> Choices{{0, 1}, {2}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0, 2},
{1, 2},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
TEST(CombinationGenerator, RightColumn) {
const std::vector<std::vector<int>> Choices{{0}, {1, 2}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0, 1},
{0, 2},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
TEST(CombinationGenerator, Column) {
const std::vector<std::vector<int>> Choices{{0, 1}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0},
{1},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
TEST(CombinationGenerator, Row) {
const std::vector<std::vector<int>> Choices{{0}, {1}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0, 1},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
TEST(CombinationGenerator, Singleton) {
const std::vector<std::vector<int>> Choices{{0}};
std::vector<std::vector<int>> Variants;
CombinationGenerator<int, std::vector<int>, 4> G(
Choices, [&](ArrayRef<int> State) -> bool {
Variants.emplace_back(State);
return false; // keep going
});
const size_t NumVariants = G.numCombinations();
G.generate();
const std::vector<std::vector<int>> ExpectedVariants{
{0},
};
ASSERT_THAT(Variants, ::testing::SizeIs(NumVariants));
ASSERT_THAT(Variants, ::testing::ContainerEq(ExpectedVariants));
}
} // namespace
} // namespace exegesis
} // namespace llvm

View File

@ -51,7 +51,7 @@ protected:
randomGenerator().seed(0); // Initialize seed.
const Instruction &Instr = State.getIC().getInstr(Opcode);
auto CodeTemplateOrError = Generator.generateCodeTemplates(
Instr, State.getRATC().emptyRegisters());
&Instr, State.getRATC().emptyRegisters());
EXPECT_FALSE(CodeTemplateOrError.takeError()); // Valid configuration.
return std::move(CodeTemplateOrError.get());
}
@ -153,7 +153,8 @@ TEST_F(SerialSnippetGeneratorTest,
const Instruction &Instr = State.getIC().getInstr(Opcode);
auto AllRegisters = State.getRATC().emptyRegisters();
AllRegisters.flip();
auto Error = Generator.generateCodeTemplates(Instr, AllRegisters).takeError();
auto Error =
Generator.generateCodeTemplates(&Instr, AllRegisters).takeError();
EXPECT_TRUE((bool)Error);
consumeError(std::move(Error));
}
@ -207,11 +208,12 @@ TEST_F(SerialSnippetGeneratorTest, VCVTUSI642SDZrrb_Int) {
// - Op4 Implicit Use Reg(MXSCR)
const unsigned Opcode = X86::VCVTUSI642SDZrrb_Int;
const Instruction &Instr = State.getIC().getInstr(Opcode);
auto Configs =
Generator.generateConfigurations(Instr, State.getRATC().emptyRegisters());
ASSERT_FALSE(Configs.takeError());
ASSERT_THAT(*Configs, SizeIs(1));
const BenchmarkCode &BC = (*Configs)[0];
std::vector<BenchmarkCode> Configs;
auto Error = Generator.generateConfigurations(
&Instr, Configs, State.getRATC().emptyRegisters());
ASSERT_FALSE(Error);
ASSERT_THAT(Configs, SizeIs(1));
const BenchmarkCode &BC = Configs[0];
ASSERT_THAT(BC.Key.Instructions, SizeIs(1));
ASSERT_TRUE(BC.Key.Instructions[0].getOperand(3).isImm());
}
@ -357,9 +359,9 @@ TEST_F(ParallelSnippetGeneratorTest, MemoryUse) {
TEST_F(ParallelSnippetGeneratorTest, MOV16ms) {
const unsigned Opcode = X86::MOV16ms;
const Instruction &Instr = State.getIC().getInstr(Opcode);
auto Err =
Generator.generateConfigurations(Instr, State.getRATC().emptyRegisters())
.takeError();
std::vector<BenchmarkCode> Benchmarks;
auto Err = Generator.generateConfigurations(&Instr, Benchmarks,
State.getRATC().emptyRegisters());
EXPECT_TRUE((bool)Err);
EXPECT_THAT(toString(std::move(Err)),
testing::HasSubstr("no available registers"));
@ -380,7 +382,7 @@ public:
private:
Expected<std::vector<CodeTemplate>>
generateCodeTemplates(const Instruction &, const BitVector &) const override {
generateCodeTemplates(InstructionTemplate, const BitVector &) const override {
return make_error<StringError>("not implemented", inconvertibleErrorCode());
}
};
@ -412,9 +414,9 @@ TEST_F(FakeSnippetGeneratorTest, MemoryUse_Movsb) {
// - hasAliasingRegisters
const unsigned Opcode = X86::MOVSB;
const Instruction &Instr = State.getIC().getInstr(Opcode);
auto Error =
Generator.generateConfigurations(Instr, State.getRATC().emptyRegisters())
.takeError();
std::vector<BenchmarkCode> Benchmarks;
auto Error = Generator.generateConfigurations(
&Instr, Benchmarks, State.getRATC().emptyRegisters());
EXPECT_TRUE((bool)Error);
consumeError(std::move(Error));
}