diff --git a/llvm/tools/llvm-exegesis/lib/CodeTemplate.h b/llvm/tools/llvm-exegesis/lib/CodeTemplate.h --- a/llvm/tools/llvm-exegesis/lib/CodeTemplate.h +++ b/llvm/tools/llvm-exegesis/lib/CodeTemplate.h @@ -119,8 +119,8 @@ CodeTemplate(CodeTemplate &&); // default CodeTemplate &operator=(CodeTemplate &&); // default - CodeTemplate(const CodeTemplate &) = delete; - CodeTemplate &operator=(const CodeTemplate &) = delete; + + CodeTemplate clone() const; ExecutionMode Execution = ExecutionMode::UNKNOWN; // See InstructionBenchmarkKey.::Config. @@ -132,6 +132,10 @@ // If the template uses the provided scratch memory, the register in which // the pointer to this memory is passed in to the function. unsigned ScratchSpacePointerInReg = 0; + +private: + CodeTemplate(const CodeTemplate &); // default + CodeTemplate &operator=(const CodeTemplate &); // default }; } // namespace exegesis diff --git a/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp b/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp --- a/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp +++ b/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp @@ -11,10 +11,19 @@ namespace llvm { namespace exegesis { +CodeTemplate::CodeTemplate(const CodeTemplate &) = default; + CodeTemplate::CodeTemplate(CodeTemplate &&) = default; CodeTemplate &CodeTemplate::operator=(CodeTemplate &&) = default; +CodeTemplate &CodeTemplate::operator=(const CodeTemplate &) = default; + +CodeTemplate CodeTemplate::clone() const { + CodeTemplate CT = *this; + return CT; +} + InstructionTemplate::InstructionTemplate(const Instruction *Instr) : Instr(Instr), VariableValues(Instr->Variables.size()) {} diff --git a/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp b/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp --- a/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp +++ b/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp @@ -79,13 +79,12 @@ namespace llvm { namespace exegesis { -static SmallVector -getVariablesWithTiedOperands(const Instruction &Instr) { +static bool hasVariablesWithTiedOperands(const Instruction &Instr) { SmallVector Result; for (const auto &Var : Instr.Variables) if (Var.hasTiedOperands()) - Result.push_back(&Var); - return Result; + return true; + return false; } ParallelSnippetGenerator::~ParallelSnippetGenerator() = default; @@ -114,43 +113,181 @@ "not enough scratch space"); } -static std::vector generateSnippetUsingStaticRenaming( - const LLVMState &State, const InstructionTemplate &IT, - const ArrayRef TiedVariables, - const BitVector &ForbiddenRegisters) { - std::vector 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 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 { + 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 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 +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 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 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 +generateSingleSnippetForInstrAvoidingDefUseOverlap( + const LLVMState &State, const BitVector &ForbiddenRegisters, + const BitVector &ImplicitUseAliases, const BitVector &ImplicitDefAliases, + BitVector &Uses, BitVector &Defs, InstructionTemplate IT, + const ArrayRef 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 R = + generateSingleRegisterForInstrAvoidingDefUseOverlap( + State, ForbiddenRegisters, ImplicitUseAliases, ImplicitDefAliases, + Uses, Defs, IT, Op, Instructions, S); + + if (std::holds_alternative(R)) + return {}; + + MCOperand MCOp; + if (std::holds_alternative(R)) + MCOp = std::get(R); + else { + Register RandomReg = std::get(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 +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 Instructions; + + while (true) { + std::optional 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?"); } } @@ -177,78 +314,41 @@ 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( - 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 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 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( + 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; diff --git a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h --- a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h +++ b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h @@ -92,6 +92,9 @@ // Precondition: Vector must have at least one bit set. size_t randomBit(const BitVector &Vector); +// Picks a first bit that is common to these two vectors. +std::optional getFirstCommonBit(const BitVector &A, const BitVector &B); + // Picks a random configuration, then selects a random def and a random use from // it and finally set the selected values in the provided InstructionInstances. void setRandomAliasing(const AliasingConfigurations &AliasingConfigurations, diff --git a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp --- a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp +++ b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp @@ -213,6 +213,15 @@ return *Itr; } +std::optional getFirstCommonBit(const BitVector &A, const BitVector &B) { + BitVector Intersect = A; + Intersect &= B; + int idx = Intersect.find_first(); + if (idx != -1) + return idx; + return {}; +} + void setRandomAliasing(const AliasingConfigurations &AliasingConfigurations, InstructionTemplate &DefIB, InstructionTemplate &UseIB) { assert(!AliasingConfigurations.empty()); diff --git a/llvm/unittests/tools/llvm-exegesis/PowerPC/SnippetGeneratorTest.cpp b/llvm/unittests/tools/llvm-exegesis/PowerPC/SnippetGeneratorTest.cpp --- a/llvm/unittests/tools/llvm-exegesis/PowerPC/SnippetGeneratorTest.cpp +++ b/llvm/unittests/tools/llvm-exegesis/PowerPC/SnippetGeneratorTest.cpp @@ -116,17 +116,17 @@ const unsigned Opcode = PPC::LDX; const auto CodeTemplates = checkAndGetCodeTemplates(Opcode); ASSERT_THAT(CodeTemplates, SizeIs(1)); - const auto &CT = CodeTemplates[0]; - EXPECT_THAT(CT.Info, HasSubstr("instruction has no tied variables picking " - "Uses different from defs")); - 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(3)); - EXPECT_EQ(IT.getVariableValues()[1].getReg(), PPC::X1); - EXPECT_EQ(IT.getVariableValues()[2].getReg(), PPC::X13); + for (const auto &CT : CodeTemplates) { + EXPECT_THAT(CT.Info, HasSubstr("instruction has 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(3)); + EXPECT_EQ(IT.getVariableValues()[1].getReg(), PPC::X1); + EXPECT_EQ(IT.getVariableValues()[2].getReg(), PPC::X13); + } } } // namespace diff --git a/llvm/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp b/llvm/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp --- a/llvm/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp +++ b/llvm/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp @@ -14,6 +14,7 @@ #include "SerialSnippetGenerator.h" #include "TestBase.h" #include "X86InstrInfo.h" +#include "llvm/ADT/SetOperations.h" #include @@ -26,8 +27,10 @@ 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; @@ -232,7 +235,7 @@ 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. @@ -248,19 +251,69 @@ // - 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 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 AllDefRegisters; + std::unordered_set 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 AllDefRegisters; + std::unordered_set 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) { @@ -280,21 +333,22 @@ // - 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) { @@ -317,18 +371,19 @@ 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) { @@ -362,7 +417,7 @@ 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 {