namespace llvm {
namespace exegesis {
-static SmallVector<const Variable *, 8>
-getVariablesWithTiedOperands(const Instruction &Instr) {
+static bool hasVariablesWithTiedOperands(const Instruction &Instr) {
SmallVector<const Variable *, 8> Result;
for (const auto &Var : Instr.Variables)
if (Var.hasTiedOperands())
- Result.push_back(&Var);
- return Result;
+ return true;
+ return false;
}
ParallelSnippetGenerator::~ParallelSnippetGenerator() = default;
"not enough scratch space");
}
-static std::vector<InstructionTemplate> generateSnippetUsingStaticRenaming(
- const LLVMState &State, const InstructionTemplate &IT,
- const ArrayRef<const Variable *> TiedVariables,
- const BitVector &ForbiddenRegisters) {
- std::vector<InstructionTemplate> Instructions;
- // Assign registers to variables in a round-robin manner. This is simple but
- // ensures that the most register-constrained variable does not get starved.
- std::vector<BitVector> PossibleRegsForVar;
- for (const Variable *Var : TiedVariables) {
- assert(Var);
- const Operand &Op = IT.getInstr().getPrimaryOperand(*Var);
- assert(Op.isReg());
- BitVector PossibleRegs = Op.getRegisterAliasing().sourceBits();
- remove(PossibleRegs, ForbiddenRegisters);
- PossibleRegsForVar.push_back(std::move(PossibleRegs));
+enum class RegRandomizationStrategy : uint8_t {
+ PickRandomRegs,
+ SingleStaticRegPerOperand,
+ SingleStaticReg,
+
+ FIRST = PickRandomRegs,
+ LAST = SingleStaticReg,
+};
+
+} // namespace exegesis
+
+template <> struct enum_iteration_traits<exegesis::RegRandomizationStrategy> {
+ static constexpr bool is_iterable = true;
+};
+
+namespace exegesis {
+
+const char *getDescription(RegRandomizationStrategy S) {
+ switch (S) {
+ case RegRandomizationStrategy::PickRandomRegs:
+ return "randomizing registers";
+ case RegRandomizationStrategy::SingleStaticRegPerOperand:
+ return "one unique register for each position";
+ case RegRandomizationStrategy::SingleStaticReg:
+ return "reusing the same register for all positions";
}
- SmallVector<int, 2> Iterators(TiedVariables.size(), 0);
- while (true) {
- InstructionTemplate TmpIT = IT;
- // Find a possible register for each variable in turn, marking the
- // register as taken.
- for (size_t VarId = 0; VarId < TiedVariables.size(); ++VarId) {
- const int NextPossibleReg =
- PossibleRegsForVar[VarId].find_next(Iterators[VarId]);
- if (NextPossibleReg <= 0) {
- return Instructions;
- }
- TmpIT.getValueFor(*TiedVariables[VarId]) =
- MCOperand::createReg(NextPossibleReg);
- // Bump iterator.
- Iterators[VarId] = NextPossibleReg;
- // Prevent other variables from using the register.
- for (BitVector &OtherPossibleRegs : PossibleRegsForVar) {
- OtherPossibleRegs.reset(NextPossibleReg);
+ llvm_unreachable("Unknown UseRegRandomizationStrategy enum");
+}
+
+static std::variant<std::nullopt_t, MCOperand, Register>
+generateSingleRegisterForInstrAvoidingDefUseOverlap(
+ const LLVMState &State, const BitVector &ForbiddenRegisters,
+ const BitVector &ImplicitUseAliases, const BitVector &ImplicitDefAliases,
+ const BitVector &Uses, const BitVector &Defs, const InstructionTemplate &IT,
+ const Operand &Op, const ArrayRef<InstructionTemplate> Instructions,
+ RegRandomizationStrategy S) {
+ const Instruction &Instr = IT.getInstr();
+ assert(Op.isReg() && Op.isExplicit() && !Op.isMemory() &&
+ !IT.getValueFor(Op).isValid());
+ assert((!Op.isUse() || !Op.isTied()) &&
+ "Not expecting to see a tied use reg");
+
+ if (Op.isUse()) {
+ switch (S) {
+ case RegRandomizationStrategy::PickRandomRegs:
+ break;
+ case RegRandomizationStrategy::SingleStaticReg:
+ case RegRandomizationStrategy::SingleStaticRegPerOperand: {
+ if (!Instructions.empty())
+ return Instructions.front().getValueFor(Op);
+ if (S != RegRandomizationStrategy::SingleStaticReg)
+ break;
+ BitVector PossibleRegisters = Op.getRegisterAliasing().sourceBits();
+ const BitVector UseAliases = getAliasedBits(State.getRegInfo(), Uses);
+ if (std::optional<int> CommonBit =
+ getFirstCommonBit(PossibleRegisters, UseAliases))
+ return *CommonBit;
+ break;
+ }
+ }
+ }
+
+ BitVector PossibleRegisters = Op.getRegisterAliasing().sourceBits();
+ remove(PossibleRegisters, ForbiddenRegisters);
+
+ if (Op.isDef()) {
+ remove(PossibleRegisters, ImplicitUseAliases);
+ const BitVector UseAliases = getAliasedBits(State.getRegInfo(), Uses);
+ remove(PossibleRegisters, UseAliases);
+ }
+
+ if (Op.isUse()) {
+ remove(PossibleRegisters, ImplicitDefAliases);
+ // NOTE: in general, using same reg for multiple Use's is fine.
+ if (S == RegRandomizationStrategy::SingleStaticRegPerOperand) {
+ const BitVector UseAliases = getAliasedBits(State.getRegInfo(), Uses);
+ remove(PossibleRegisters, UseAliases);
+ }
+ }
+
+ bool IsDefWithTiedUse =
+ Instr.Variables[Op.getVariableIndex()].hasTiedOperands();
+ if (Op.isUse() || IsDefWithTiedUse) {
+ // Now, important bit: if we have used some register for def,
+ // then we can not use that same register for *any* use,
+ // be it either an untied use, or an use tied to a def.
+ // But def-ing same regs is fine, as long as there are no uses!
+ const BitVector DefsAliases = getAliasedBits(State.getRegInfo(), Defs);
+ remove(PossibleRegisters, DefsAliases);
+ }
+
+ if (!PossibleRegisters.any())
+ return std::nullopt;
+
+ return randomBit(PossibleRegisters);
+}
+
+static std::optional<InstructionTemplate>
+generateSingleSnippetForInstrAvoidingDefUseOverlap(
+ const LLVMState &State, const BitVector &ForbiddenRegisters,
+ const BitVector &ImplicitUseAliases, const BitVector &ImplicitDefAliases,
+ BitVector &Uses, BitVector &Defs, InstructionTemplate IT,
+ const ArrayRef<InstructionTemplate> Instructions,
+ RegRandomizationStrategy S) {
+ const Instruction &Instr = IT.getInstr();
+ for (const Operand &Op : Instr.Operands) {
+ if (!Op.isReg() || !Op.isExplicit() || Op.isMemory() ||
+ IT.getValueFor(Op).isValid())
+ continue;
+ assert((!Op.isUse() || !Op.isTied()) && "Will not get tied uses.");
+
+ std::variant<std::nullopt_t, MCOperand, Register> R =
+ generateSingleRegisterForInstrAvoidingDefUseOverlap(
+ State, ForbiddenRegisters, ImplicitUseAliases, ImplicitDefAliases,
+ Uses, Defs, IT, Op, Instructions, S);
+
+ if (std::holds_alternative<std::nullopt_t>(R))
+ return {};
+
+ MCOperand MCOp;
+ if (std::holds_alternative<MCOperand>(R))
+ MCOp = std::get<MCOperand>(R);
+ else {
+ Register RandomReg = std::get<Register>(R);
+ if (Op.isDef())
+ Defs.set(RandomReg);
+ if (Op.isUse())
+ Uses.set(RandomReg);
+ MCOp = MCOperand::createReg(RandomReg);
+ }
+ IT.getValueFor(Op) = MCOp;
+ }
+ return IT;
+}
+
+static std::vector<InstructionTemplate>
+generateSnippetForInstrAvoidingDefUseOverlap(
+ const LLVMState &State, const InstructionTemplate &IT,
+ RegRandomizationStrategy S, const BitVector &ForbiddenRegisters) {
+ // We don't want to accidentally serialize the instruction,
+ // so we must be sure that we don't pick a def that is an implicit use,
+ // or a use that is an implicit def, so record implicit regs now.
+ BitVector ImplicitUses(State.getRegInfo().getNumRegs());
+ BitVector ImplicitDefs(State.getRegInfo().getNumRegs());
+ for (const auto &Op : IT.getInstr().Operands) {
+ if (Op.isReg() && Op.isImplicit() && !Op.isMemory()) {
+ assert(Op.isImplicitReg() && "Not an implicit register operand?");
+ if (Op.isUse())
+ ImplicitUses.set(Op.getImplicitReg());
+ else {
+ assert(Op.isDef() && "Not a use and not a def?");
+ ImplicitDefs.set(Op.getImplicitReg());
}
}
- Instructions.push_back(std::move(TmpIT));
+ }
+ const BitVector ImplicitUseAliases =
+ getAliasedBits(State.getRegInfo(), ImplicitUses);
+ const BitVector ImplicitDefAliases =
+ getAliasedBits(State.getRegInfo(), ImplicitDefs);
+
+ BitVector Defs(State.getRegInfo().getNumRegs());
+ BitVector Uses(State.getRegInfo().getNumRegs());
+ std::vector<InstructionTemplate> Instructions;
+
+ while (true) {
+ std::optional<InstructionTemplate> TmpIT =
+ generateSingleSnippetForInstrAvoidingDefUseOverlap(
+ State, ForbiddenRegisters, ImplicitUseAliases, ImplicitDefAliases,
+ Uses, Defs, IT, Instructions, S);
+ if (!TmpIT)
+ return Instructions;
+ Instructions.push_back(std::move(*TmpIT));
+ if (!hasVariablesWithTiedOperands(IT.getInstr()))
+ return Instructions;
+ assert(Instructions.size() <= 128 && "Stuck in endless loop?");
}
}
instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
return getSingleton(std::move(CT));
}
- const auto TiedVariables = getVariablesWithTiedOperands(Instr);
- if (!TiedVariables.empty()) {
- CT.Info = "instruction has tied variables, using static renaming.";
- CT.Instructions = generateSnippetUsingStaticRenaming(
- State, Variant, TiedVariables, ForbiddenRegisters);
- instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
- return getSingleton(std::move(CT));
- }
- // No tied variables, we pick random values for defs.
-
- // We don't want to accidentally serialize the instruction,
- // so we must be sure that we don't pick a def that is an implicit use,
- // or a use that is an implicit def, so record implicit regs now.
- BitVector ImplicitUses(State.getRegInfo().getNumRegs());
- BitVector ImplicitDefs(State.getRegInfo().getNumRegs());
- for (const auto &Op : Instr.Operands) {
- if (Op.isReg() && Op.isImplicit() && !Op.isMemory()) {
- assert(Op.isImplicitReg() && "Not an implicit register operand?");
- if (Op.isUse())
- ImplicitUses.set(Op.getImplicitReg());
- else {
- assert(Op.isDef() && "Not a use and not a def?");
- ImplicitDefs.set(Op.getImplicitReg());
- }
- }
- }
- const auto ImplicitUseAliases =
- getAliasedBits(State.getRegInfo(), ImplicitUses);
- const auto ImplicitDefAliases =
- getAliasedBits(State.getRegInfo(), ImplicitDefs);
- BitVector Defs(State.getRegInfo().getNumRegs());
- for (const auto &Op : Instr.Operands) {
- if (Op.isReg() && Op.isExplicit() && Op.isDef() && !Op.isMemory()) {
- auto PossibleRegisters = Op.getRegisterAliasing().sourceBits();
- // Do not use forbidden registers and regs that are implicitly used.
- // Note that we don't try to avoid using implicit defs explicitly.
- remove(PossibleRegisters, ForbiddenRegisters);
- remove(PossibleRegisters, ImplicitUseAliases);
- if (!PossibleRegisters.any())
- return make_error<StringError>(
- Twine("no available registers:\ncandidates:\n")
- .concat(debugString(State.getRegInfo(),
- Op.getRegisterAliasing().sourceBits()))
- .concat("\nforbidden:\n")
- .concat(debugString(State.getRegInfo(), ForbiddenRegisters))
- .concat("\nimplicit use:\n")
- .concat(debugString(State.getRegInfo(), ImplicitUseAliases)),
- inconvertibleErrorCode());
- const auto RandomReg = randomBit(PossibleRegisters);
- Defs.set(RandomReg);
- Variant.getValueFor(Op) = MCOperand::createReg(RandomReg);
- }
- }
- // And pick random use values that are not reserved and don't alias with defs.
- // Note that we don't try to avoid using implicit uses explicitly.
- const auto DefAliases = getAliasedBits(State.getRegInfo(), Defs);
- for (const auto &Op : Instr.Operands) {
- if (Op.isReg() && Op.isExplicit() && Op.isUse() && !Op.isMemory()) {
- auto PossibleRegisters = Op.getRegisterAliasing().sourceBits();
- remove(PossibleRegisters, ForbiddenRegisters);
- remove(PossibleRegisters, DefAliases);
- remove(PossibleRegisters, ImplicitDefAliases);
- assert(PossibleRegisters.any() && "No register left to choose from");
- const auto RandomReg = randomBit(PossibleRegisters);
- Variant.getValueFor(Op) = MCOperand::createReg(RandomReg);
- }
+ std::vector<CodeTemplate> Result;
+ bool HasTiedOperands = hasVariablesWithTiedOperands(Instr);
+ // If there are no tied operands, then we don't want to "saturate backedge",
+ // and the template we will produce will have only a single instruction.
+ unsigned NumUntiedUseRegs = count_if(Instr.Operands, [](const Operand &Op) {
+ return Op.isReg() && Op.isExplicit() && !Op.isMemory() && Op.isUse() &&
+ !Op.isTied();
+ });
+ SmallVector<RegRandomizationStrategy, 3> Strategies;
+ if (HasTiedOperands || NumUntiedUseRegs >= 3)
+ Strategies.push_back(RegRandomizationStrategy::PickRandomRegs);
+ if (HasTiedOperands || NumUntiedUseRegs >= 2)
+ Strategies.push_back(RegRandomizationStrategy::SingleStaticRegPerOperand);
+ Strategies.push_back(RegRandomizationStrategy::SingleStaticReg);
+ for (RegRandomizationStrategy S : Strategies) {
+ CodeTemplate CurrCT = CT.clone();
+ CurrCT.Info =
+ Twine("instruction has ")
+ .concat(HasTiedOperands ? "" : "no ")
+ .concat("tied variables, avoiding "
+ "Read-After-Write issue, picking random def and use "
+ "registers not aliasing each other, for uses, ")
+ .concat(getDescription(S))
+ .str();
+ CurrCT.Instructions = generateSnippetForInstrAvoidingDefUseOverlap(
+ State, Variant, S, ForbiddenRegisters);
+ if (CurrCT.Instructions.empty())
+ return make_error<StringError>(
+ Twine("Failed to produce any snippet via: ").concat(CurrCT.Info),
+ inconvertibleErrorCode());
+ instantiateMemoryOperands(CurrCT.ScratchSpacePointerInReg,
+ CurrCT.Instructions);
+ Result.push_back(std::move(CurrCT));
}
- CT.Info =
- "instruction has no tied variables picking Uses different from defs";
- CT.Instructions.push_back(std::move(Variant));
- instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
- return getSingleton(std::move(CT));
+ return Result;
}
constexpr const size_t ParallelSnippetGenerator::kMinNumDifferentAddresses;
#include "SerialSnippetGenerator.h"
#include "TestBase.h"
#include "X86InstrInfo.h"
+#include "llvm/ADT/SetOperations.h"
#include <unordered_set>
using testing::AnyOf;
using testing::ElementsAre;
+using testing::Ge;
using testing::Gt;
using testing::HasSubstr;
+using testing::IsEmpty;
using testing::Not;
using testing::SizeIs;
using testing::UnorderedElementsAre;
ASSERT_THAT(IT.getVariableValues(), SizeIs(0));
}
-TEST_F(X86ParallelSnippetGeneratorTest, StaticRenaming) {
+TEST_F(X86ParallelSnippetGeneratorTest, ReadAfterWrite_CMOV32rr) {
// CMOV32rr has tied variables, we enumerate the possible values to execute
// as many in parallel as possible.
// - hasAliasingRegisters
const unsigned Opcode = X86::CMOV32rr;
const auto CodeTemplates = checkAndGetCodeTemplates(Opcode);
- ASSERT_THAT(CodeTemplates, SizeIs(1));
- const auto &CT = CodeTemplates[0];
- EXPECT_THAT(CT.Info, HasSubstr("static renaming"));
- EXPECT_THAT(CT.Execution, ExecutionMode::UNKNOWN);
- constexpr const unsigned kInstructionCount = 15;
- ASSERT_THAT(CT.Instructions, SizeIs(kInstructionCount));
- std::unordered_set<unsigned> AllDefRegisters;
- for (const auto &IT : CT.Instructions) {
- ASSERT_THAT(IT.getVariableValues(), SizeIs(3));
- AllDefRegisters.insert(IT.getVariableValues()[0].getReg());
+ ASSERT_THAT(CodeTemplates, SizeIs(3));
+ for (const auto &CT : CodeTemplates) {
+ EXPECT_THAT(CT.Info, HasSubstr("avoiding Read-After-Write issue"));
+ EXPECT_THAT(CT.Execution, ExecutionMode::UNKNOWN);
+ ASSERT_GT(CT.Instructions.size(), 1U);
+ std::unordered_set<unsigned> AllDefRegisters;
+ std::unordered_set<unsigned> AllUseRegisters;
+ for (const auto &IT : CT.Instructions) {
+ ASSERT_THAT(IT.getVariableValues(), SizeIs(3));
+ AllDefRegisters.insert(IT.getVariableValues()[0].getReg());
+ AllUseRegisters.insert(IT.getVariableValues()[1].getReg());
+ }
+ EXPECT_THAT(AllDefRegisters, SizeIs(CT.Instructions.size()))
+ << "Each instruction writes to a different register";
+ EXPECT_THAT(AllUseRegisters, Not(IsEmpty()))
+ << "In total, some other registers are used";
+ auto AllDefAndUseRegs = AllUseRegisters;
+ llvm::set_intersect(AllDefAndUseRegs, AllDefRegisters); // A := A ^ B
+ EXPECT_THAT(AllDefAndUseRegs, IsEmpty())
+ << "No instruction uses any register defined by any of the "
+ "instructions";
+ }
+}
+
+TEST_F(X86ParallelSnippetGeneratorTest, ReadAfterWrite_VFMADD132PDr) {
+ // VFMADD132PDr has tied variables, we enumerate the possible values
+ // to execute as many in parallel as possible.
+
+ // - VFMADD132PDr
+ // - Op0 Explicit Def RegClass(XMM)
+ // - Op1 Explicit Use RegClass(XMM) TiedToOp0
+ // - Op2 Explicit Use RegClass(XMM)
+ // - Op3 Explicit Use RegClass(XMM)
+ // - Var0 [Op0,Op1]
+ // - Var1 [Op2]
+ // - Var2 [Op3]
+ // - hasTiedRegisters (execution is always serial)
+ // - hasAliasingRegisters
+ const unsigned Opcode = X86::VFMADD132PDr;
+ const auto CodeTemplates = checkAndGetCodeTemplates(Opcode);
+ ASSERT_THAT(CodeTemplates, SizeIs(3));
+ for (const auto &CT : CodeTemplates) {
+ EXPECT_THAT(CT.Info, HasSubstr("avoiding Read-After-Write issue"));
+ EXPECT_THAT(CT.Execution, ExecutionMode::UNKNOWN);
+ ASSERT_GT(CT.Instructions.size(), 1U);
+ std::unordered_set<unsigned> AllDefRegisters;
+ std::unordered_set<unsigned> AllUseRegisters;
+ for (const auto &IT : CT.Instructions) {
+ ASSERT_THAT(IT.getVariableValues(), SizeIs(3));
+ AllDefRegisters.insert(IT.getVariableValues()[0].getReg());
+ AllUseRegisters.insert(IT.getVariableValues()[1].getReg());
+ AllUseRegisters.insert(IT.getVariableValues()[2].getReg());
+ }
+ EXPECT_THAT(AllDefRegisters, SizeIs(CT.Instructions.size()))
+ << "Each instruction writes to a different register";
+ EXPECT_THAT(AllUseRegisters, Not(IsEmpty()))
+ << "In total, some other registers are used";
+ auto AllDefAndUseRegs = AllUseRegisters;
+ llvm::set_intersect(AllDefAndUseRegs, AllDefRegisters); // A := A ^ B
+ EXPECT_THAT(AllDefAndUseRegs, IsEmpty())
+ << "No instruction uses any register defined by any of the "
+ "instructions";
}
- EXPECT_THAT(AllDefRegisters, SizeIs(kInstructionCount))
- << "Each instruction writes to a different register";
}
TEST_F(X86ParallelSnippetGeneratorTest, NoTiedVariables) {
// - hasAliasingRegisters
const unsigned Opcode = X86::CMOV_GR32;
const auto CodeTemplates = checkAndGetCodeTemplates(Opcode);
- ASSERT_THAT(CodeTemplates, SizeIs(1));
- const auto &CT = CodeTemplates[0];
- EXPECT_THAT(CT.Info, HasSubstr("no tied variables"));
- EXPECT_THAT(CT.Execution, ExecutionMode::UNKNOWN);
- ASSERT_THAT(CT.Instructions, SizeIs(1));
- const InstructionTemplate &IT = CT.Instructions[0];
- EXPECT_THAT(IT.getOpcode(), Opcode);
- ASSERT_THAT(IT.getVariableValues(), SizeIs(4));
- EXPECT_THAT(IT.getVariableValues()[0].getReg(),
- Not(IT.getVariableValues()[1].getReg()))
- << "Def is different from first Use";
- EXPECT_THAT(IT.getVariableValues()[0].getReg(),
- Not(IT.getVariableValues()[2].getReg()))
- << "Def is different from second Use";
- EXPECT_THAT(IT.getVariableValues()[3], IsInvalid());
+ ASSERT_THAT(CodeTemplates, SizeIs(2));
+ for (const auto &CT : CodeTemplates) {
+ EXPECT_THAT(CT.Info, HasSubstr("no tied variables"));
+ EXPECT_THAT(CT.Execution, ExecutionMode::UNKNOWN);
+ ASSERT_THAT(CT.Instructions, SizeIs(1));
+ const InstructionTemplate &IT = CT.Instructions[0];
+ EXPECT_THAT(IT.getOpcode(), Opcode);
+ ASSERT_THAT(IT.getVariableValues(), SizeIs(4));
+ EXPECT_THAT(IT.getVariableValues()[0].getReg(),
+ Not(IT.getVariableValues()[1].getReg()))
+ << "Def is different from first Use";
+ EXPECT_THAT(IT.getVariableValues()[0].getReg(),
+ Not(IT.getVariableValues()[2].getReg()))
+ << "Def is different from second Use";
+ EXPECT_THAT(IT.getVariableValues()[3], IsInvalid());
+ }
}
TEST_F(X86ParallelSnippetGeneratorTest, MemoryUse) {
const unsigned Opcode = X86::MOV32rm;
const auto CodeTemplates = checkAndGetCodeTemplates(Opcode);
ASSERT_THAT(CodeTemplates, SizeIs(1));
- const auto &CT = CodeTemplates[0];
- EXPECT_THAT(CT.Info, HasSubstr("no tied variables"));
- EXPECT_THAT(CT.Execution, ExecutionMode::UNKNOWN);
- ASSERT_THAT(CT.Instructions,
- SizeIs(ParallelSnippetGenerator::kMinNumDifferentAddresses));
- const InstructionTemplate &IT = CT.Instructions[0];
- EXPECT_THAT(IT.getOpcode(), Opcode);
- ASSERT_THAT(IT.getVariableValues(), SizeIs(6));
- EXPECT_EQ(IT.getVariableValues()[2].getImm(), 1);
- EXPECT_EQ(IT.getVariableValues()[3].getReg(), 0u);
- EXPECT_EQ(IT.getVariableValues()[4].getImm(), 0);
- EXPECT_EQ(IT.getVariableValues()[5].getReg(), 0u);
+ for (const auto &CT : CodeTemplates) {
+ EXPECT_THAT(CT.Info, HasSubstr("no tied variables"));
+ EXPECT_THAT(CT.Execution, ExecutionMode::UNKNOWN);
+ ASSERT_THAT(CT.Instructions,
+ SizeIs(ParallelSnippetGenerator::kMinNumDifferentAddresses));
+ const InstructionTemplate &IT = CT.Instructions[0];
+ EXPECT_THAT(IT.getOpcode(), Opcode);
+ ASSERT_THAT(IT.getVariableValues(), SizeIs(6));
+ EXPECT_EQ(IT.getVariableValues()[2].getImm(), 1);
+ EXPECT_EQ(IT.getVariableValues()[3].getReg(), 0u);
+ EXPECT_EQ(IT.getVariableValues()[4].getImm(), 0);
+ EXPECT_EQ(IT.getVariableValues()[5].getReg(), 0u);
+ }
}
TEST_F(X86ParallelSnippetGeneratorTest, MOV16ms) {
auto Err = Generator.generateCodeTemplates(&Instr, AllRegisters);
EXPECT_FALSE((bool)Err);
EXPECT_THAT(toString(Err.takeError()),
- testing::HasSubstr("no available registers"));
+ testing::HasSubstr("Failed to produce any snippet"));
}
class X86FakeSnippetGenerator : public SnippetGenerator {