| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/CodeGen/LiveIntervals.h" |
| #include "llvm/CodeGen/MIRParser/MIRParser.h" |
| #include "llvm/CodeGen/MachineFunction.h" |
| #include "llvm/CodeGen/MachineModuleInfo.h" |
| #include "llvm/CodeGen/TargetRegisterInfo.h" |
| #include "llvm/CodeGen/TargetSubtargetInfo.h" |
| #include "llvm/IR/LegacyPassManager.h" |
| #include "llvm/InitializePasses.h" |
| #include "llvm/MC/TargetRegistry.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/SourceMgr.h" |
| #include "llvm/Support/TargetSelect.h" |
| #include "llvm/Target/TargetMachine.h" |
| #include "llvm/Target/TargetOptions.h" |
| #include "gtest/gtest.h" |
| |
| #include "../lib/CodeGen/RegisterCoalescer.h" |
| |
| using namespace llvm; |
| |
| namespace llvm { |
| void initializeTestPassPass(PassRegistry &); |
| } |
| |
| namespace { |
| |
| void initLLVM() { |
| InitializeAllTargets(); |
| InitializeAllTargetMCs(); |
| InitializeAllAsmPrinters(); |
| InitializeAllAsmParsers(); |
| |
| PassRegistry *Registry = PassRegistry::getPassRegistry(); |
| initializeCore(*Registry); |
| initializeCodeGen(*Registry); |
| } |
| |
| /// Create a TargetMachine. As we lack a dedicated always available target for |
| /// unittests, we go for "AMDGPU" to be able to test normal and subregister |
| /// liveranges. |
| std::unique_ptr<LLVMTargetMachine> createTargetMachine() { |
| Triple TargetTriple("amdgcn--"); |
| std::string Error; |
| const Target *T = TargetRegistry::lookupTarget("", TargetTriple, Error); |
| if (!T) |
| return nullptr; |
| |
| TargetOptions Options; |
| return std::unique_ptr<LLVMTargetMachine>(static_cast<LLVMTargetMachine *>( |
| T->createTargetMachine("AMDGPU", "gfx900", "", Options, std::nullopt, |
| std::nullopt, CodeGenOpt::Aggressive))); |
| } |
| |
| std::unique_ptr<Module> parseMIR(LLVMContext &Context, |
| legacy::PassManagerBase &PM, std::unique_ptr<MIRParser> &MIR, |
| const LLVMTargetMachine &TM, StringRef MIRCode, const char *FuncName) { |
| SMDiagnostic Diagnostic; |
| std::unique_ptr<MemoryBuffer> MBuffer = MemoryBuffer::getMemBuffer(MIRCode); |
| MIR = createMIRParser(std::move(MBuffer), Context); |
| if (!MIR) |
| return nullptr; |
| |
| std::unique_ptr<Module> M = MIR->parseIRModule(); |
| if (!M) |
| return nullptr; |
| |
| M->setDataLayout(TM.createDataLayout()); |
| |
| MachineModuleInfoWrapperPass *MMIWP = new MachineModuleInfoWrapperPass(&TM); |
| if (MIR->parseMachineFunctions(*M, MMIWP->getMMI())) |
| return nullptr; |
| PM.add(MMIWP); |
| |
| return M; |
| } |
| |
| typedef std::function<void(MachineFunction&,LiveIntervals&)> LiveIntervalTest; |
| |
| struct TestPass : public MachineFunctionPass { |
| static char ID; |
| TestPass() : MachineFunctionPass(ID) { |
| // We should never call this but always use PM.add(new TestPass(...)) |
| abort(); |
| } |
| TestPass(LiveIntervalTest T, bool ShouldPass) |
| : MachineFunctionPass(ID), T(T), ShouldPass(ShouldPass) { |
| initializeTestPassPass(*PassRegistry::getPassRegistry()); |
| } |
| |
| bool runOnMachineFunction(MachineFunction &MF) override { |
| LiveIntervals &LIS = getAnalysis<LiveIntervals>(); |
| T(MF, LIS); |
| EXPECT_EQ(MF.verify(this, /* Banner */ nullptr, /* AbortOnError */ false), |
| ShouldPass); |
| return true; |
| } |
| |
| void getAnalysisUsage(AnalysisUsage &AU) const override { |
| AU.setPreservesAll(); |
| AU.addRequired<LiveIntervals>(); |
| AU.addPreserved<LiveIntervals>(); |
| MachineFunctionPass::getAnalysisUsage(AU); |
| } |
| private: |
| LiveIntervalTest T; |
| bool ShouldPass; |
| }; |
| |
| static MachineInstr &getMI(MachineFunction &MF, unsigned At, |
| unsigned BlockNum) { |
| MachineBasicBlock &MBB = *MF.getBlockNumbered(BlockNum); |
| |
| unsigned I = 0; |
| for (MachineInstr &MI : MBB) { |
| if (I == At) |
| return MI; |
| ++I; |
| } |
| llvm_unreachable("Instruction not found"); |
| } |
| |
| /** |
| * Move instruction number \p From in front of instruction number \p To and |
| * update affected liveness intervals with LiveIntervalAnalysis::handleMove(). |
| */ |
| static void testHandleMove(MachineFunction &MF, LiveIntervals &LIS, |
| unsigned From, unsigned To, unsigned BlockNum = 0) { |
| MachineInstr &FromInstr = getMI(MF, From, BlockNum); |
| MachineInstr &ToInstr = getMI(MF, To, BlockNum); |
| |
| MachineBasicBlock &MBB = *FromInstr.getParent(); |
| MBB.splice(ToInstr.getIterator(), &MBB, FromInstr.getIterator()); |
| LIS.handleMove(FromInstr, true); |
| } |
| |
| /** |
| * Move instructions numbered \p From inclusive through instruction number |
| * \p To into a newly formed bundle and update affected liveness intervals |
| * with LiveIntervalAnalysis::handleMoveIntoNewBundle(). |
| */ |
| static void testHandleMoveIntoNewBundle(MachineFunction &MF, LiveIntervals &LIS, |
| unsigned From, unsigned To, |
| unsigned BlockNum = 0) { |
| MachineInstr &FromInstr = getMI(MF, From, BlockNum); |
| MachineInstr &ToInstr = getMI(MF, To, BlockNum); |
| MachineBasicBlock &MBB = *FromInstr.getParent(); |
| MachineBasicBlock::instr_iterator I = FromInstr.getIterator(); |
| |
| // Build bundle |
| finalizeBundle(MBB, I, std::next(ToInstr.getIterator())); |
| |
| // Update LiveIntervals |
| MachineBasicBlock::instr_iterator BundleStart = std::prev(I); |
| LIS.handleMoveIntoNewBundle(*BundleStart, true); |
| } |
| |
| /** |
| * Split block numbered \p BlockNum at instruction \p SplitAt using |
| * MachineBasicBlock::splitAt updating liveness intervals. |
| */ |
| static void testSplitAt(MachineFunction &MF, LiveIntervals &LIS, |
| unsigned SplitAt, unsigned BlockNum) { |
| MachineInstr &SplitInstr = getMI(MF, SplitAt, BlockNum); |
| MachineBasicBlock &MBB = *SplitInstr.getParent(); |
| |
| // Split block and update live intervals |
| MBB.splitAt(SplitInstr, false, &LIS); |
| } |
| |
| /** |
| * Helper function to test for interference between a hard register and a |
| * virtual register live ranges. |
| */ |
| static bool checkRegUnitInterference(LiveIntervals &LIS, |
| const TargetRegisterInfo &TRI, |
| const LiveInterval &VirtReg, |
| MCRegister PhysReg) { |
| if (VirtReg.empty()) |
| return false; |
| CoalescerPair CP(VirtReg.reg(), PhysReg, TRI); |
| |
| for (MCRegUnitIterator Units(PhysReg, &TRI); Units.isValid(); ++Units) { |
| const LiveRange &UnitRange = LIS.getRegUnit(*Units); |
| if (VirtReg.overlaps(UnitRange, CP, *LIS.getSlotIndexes())) |
| return true; |
| } |
| return false; |
| } |
| |
| static void liveIntervalTest(StringRef MIRFunc, LiveIntervalTest T, |
| bool ShouldPass = true) { |
| LLVMContext Context; |
| std::unique_ptr<LLVMTargetMachine> TM = createTargetMachine(); |
| // This test is designed for the X86 backend; stop if it is not available. |
| if (!TM) |
| return; |
| |
| legacy::PassManager PM; |
| |
| SmallString<160> S; |
| StringRef MIRString = (Twine(R"MIR( |
| --- |
| ... |
| name: func |
| registers: |
| - { id: 0, class: sreg_64 } |
| body: | |
| bb.0: |
| )MIR") + Twine(MIRFunc) + Twine("...\n")).toNullTerminatedStringRef(S); |
| std::unique_ptr<MIRParser> MIR; |
| std::unique_ptr<Module> M = parseMIR(Context, PM, MIR, *TM, MIRString, |
| "func"); |
| ASSERT_TRUE(M); |
| |
| PM.add(new TestPass(T, ShouldPass)); |
| |
| PM.run(*M); |
| } |
| |
| } // End of anonymous namespace. |
| |
| char TestPass::ID = 0; |
| INITIALIZE_PASS(TestPass, "testpass", "testpass", false, false) |
| |
| TEST(LiveIntervalTest, MoveUpDef) { |
| // Value defined. |
| liveIntervalTest(R"MIR( |
| S_NOP 0 |
| S_NOP 0 |
| early-clobber %0 = IMPLICIT_DEF |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 2, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveUpRedef) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 2, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveUpEarlyDef) { |
| liveIntervalTest(R"MIR( |
| S_NOP 0 |
| S_NOP 0 |
| early-clobber %0 = IMPLICIT_DEF |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 2, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveUpEarlyRedef) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| early-clobber %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 2, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveUpKill) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 2, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveUpKillFollowing) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 2, 1); |
| }); |
| } |
| |
| // TODO: Construct a situation where we have intervals following a hole |
| // while still having connected components. |
| |
| TEST(LiveIntervalTest, MoveDownDef) { |
| // Value defined. |
| liveIntervalTest(R"MIR( |
| S_NOP 0 |
| early-clobber %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveDownRedef) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveDownEarlyDef) { |
| liveIntervalTest(R"MIR( |
| S_NOP 0 |
| early-clobber %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveDownEarlyRedef) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| early-clobber %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveDownKill) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0, implicit %0 |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveDownKillFollowing) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveUndefUse) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0, implicit undef %0 |
| S_NOP 0, implicit %0 |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 3); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveUpValNos) { |
| // handleMoveUp() had a bug where it would reuse the value number of the |
| // destination segment, even though we have no guarantee that this valno |
| // wasn't used in other segments. |
| liveIntervalTest(R"MIR( |
| successors: %bb.1, %bb.2 |
| %0 = IMPLICIT_DEF |
| S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc |
| S_BRANCH %bb.1 |
| bb.2: |
| S_NOP 0, implicit %0 |
| bb.1: |
| successors: %bb.2 |
| %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| S_BRANCH %bb.2 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 2, 0, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveOverUndefUse0) { |
| // findLastUseBefore() used by handleMoveUp() must ignore undef operands. |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit undef %0 |
| %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 3, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, MoveOverUndefUse1) { |
| // findLastUseBefore() used by handleMoveUp() must ignore undef operands. |
| liveIntervalTest(R"MIR( |
| $sgpr0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit undef $sgpr0 |
| $sgpr0 = IMPLICIT_DEF implicit $sgpr0(tied-def 0) |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 3, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, SubRegMoveDown) { |
| // Subregister ranges can have holes inside a basic block. Check for a |
| // movement of the form 32->150 in a liverange [16, 32) [100,200). |
| liveIntervalTest(R"MIR( |
| successors: %bb.1, %bb.2 |
| %0 = IMPLICIT_DEF |
| S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc |
| S_BRANCH %bb.1 |
| bb.2: |
| successors: %bb.1 |
| S_NOP 0, implicit %0.sub0 |
| S_NOP 0, implicit %0.sub1 |
| S_NOP 0 |
| undef %0.sub0 = IMPLICIT_DEF |
| %0.sub1 = IMPLICIT_DEF |
| bb.1: |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| // Scheduler behaviour: Clear def,read-undef flag and move. |
| MachineInstr &MI = getMI(MF, 3, /*BlockNum=*/1); |
| MI.getOperand(0).setIsUndef(false); |
| testHandleMove(MF, LIS, 1, 4, /*BlockNum=*/1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, SubRegMoveUp) { |
| // handleMoveUp had a bug not updating valno of segment incoming to bb.2 |
| // after swapping subreg definitions. |
| liveIntervalTest(R"MIR( |
| successors: %bb.1, %bb.2 |
| undef %0.sub0 = IMPLICIT_DEF |
| %0.sub1 = IMPLICIT_DEF |
| S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc |
| S_BRANCH %bb.1 |
| bb.1: |
| S_NOP 0, implicit %0.sub1 |
| bb.2: |
| S_NOP 0, implicit %0.sub1 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 0); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, DeadSubRegMoveUp) { |
| // handleMoveUp had a bug where moving a dead subreg def into the middle of |
| // an earlier segment resulted in an invalid live range. |
| liveIntervalTest(R"MIR( |
| undef %125.sub0:vreg_128 = V_MOV_B32_e32 0, implicit $exec |
| %125.sub1:vreg_128 = COPY %125.sub0 |
| %125.sub2:vreg_128 = COPY %125.sub0 |
| undef %51.sub0:vreg_128 = V_MOV_B32_e32 898625526, implicit $exec |
| %51.sub1:vreg_128 = COPY %51.sub0 |
| %51.sub2:vreg_128 = COPY %51.sub0 |
| %52:vgpr_32 = V_MOV_B32_e32 986714345, implicit $exec |
| %54:vgpr_32 = V_MOV_B32_e32 1742342378, implicit $exec |
| %57:vgpr_32 = V_MOV_B32_e32 3168768712, implicit $exec |
| %59:vgpr_32 = V_MOV_B32_e32 1039972644, implicit $exec |
| %60:vgpr_32 = nofpexcept V_MAD_F32_e64 0, %52, 0, undef %61:vgpr_32, 0, %59, 0, 0, implicit $mode, implicit $exec |
| %63:vgpr_32 = nofpexcept V_ADD_F32_e32 %51.sub3, undef %64:vgpr_32, implicit $mode, implicit $exec |
| dead %66:vgpr_32 = nofpexcept V_MAD_F32_e64 0, %60, 0, undef %67:vgpr_32, 0, %125.sub2, 0, 0, implicit $mode, implicit $exec |
| undef %124.sub1:vreg_128 = nofpexcept V_MAD_F32_e64 0, %57, 0, undef %70:vgpr_32, 0, %125.sub1, 0, 0, implicit $mode, implicit $exec |
| %124.sub0:vreg_128 = nofpexcept V_MAD_F32_e64 0, %54, 0, undef %73:vgpr_32, 0, %125.sub0, 0, 0, implicit $mode, implicit $exec |
| dead undef %125.sub3:vreg_128 = nofpexcept V_MAC_F32_e32 %63, undef %76:vgpr_32, %125.sub3, implicit $mode, implicit $exec |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 15, 12); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, EarlyClobberSubRegMoveUp) { |
| // handleMoveUp had a bug where moving an early-clobber subreg def into the |
| // middle of an earlier segment resulted in an invalid live range. |
| liveIntervalTest(R"MIR( |
| %4:sreg_32 = IMPLICIT_DEF |
| %6:sreg_32 = IMPLICIT_DEF |
| undef early-clobber %9.sub0:sreg_64 = STRICT_WWM %4:sreg_32, implicit $exec |
| %5:sreg_32 = S_FLBIT_I32_B32 %9.sub0:sreg_64 |
| early-clobber %9.sub1:sreg_64 = STRICT_WWM %6:sreg_32, implicit $exec |
| %7:sreg_32 = S_FLBIT_I32_B32 %9.sub1:sreg_64 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 4, 3); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, TestMoveSubRegDefAcrossUseDef) { |
| liveIntervalTest(R"MIR( |
| %1:vreg_64 = IMPLICIT_DEF |
| |
| bb.1: |
| %2:vgpr_32 = V_MOV_B32_e32 2, implicit $exec |
| %3:vgpr_32 = V_ADD_U32_e32 %2, %1.sub0, implicit $exec |
| undef %1.sub0:vreg_64 = V_ADD_U32_e32 %2, %2, implicit $exec |
| %1.sub1:vreg_64 = COPY %2 |
| S_NOP 0, implicit %1.sub1 |
| S_BRANCH %bb.1 |
| |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| MachineInstr &UndefSubregDef = getMI(MF, 2, 1); |
| // The scheduler clears undef from subregister defs before moving |
| UndefSubregDef.getOperand(0).setIsUndef(false); |
| testHandleMove(MF, LIS, 3, 1, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, TestMoveSubRegDefAcrossUseDefMulti) { |
| liveIntervalTest(R"MIR( |
| %1:vreg_96 = IMPLICIT_DEF |
| |
| bb.1: |
| %2:vgpr_32 = V_MOV_B32_e32 2, implicit $exec |
| %3:vgpr_32 = V_ADD_U32_e32 %2, %1.sub0, implicit $exec |
| undef %1.sub0:vreg_96 = V_ADD_U32_e32 %2, %2, implicit $exec |
| %1.sub1:vreg_96 = COPY %2 |
| %1.sub2:vreg_96 = COPY %2 |
| S_NOP 0, implicit %1.sub1, implicit %1.sub2 |
| S_BRANCH %bb.1 |
| |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| MachineInstr &UndefSubregDef = getMI(MF, 2, 1); |
| // The scheduler clears undef from subregister defs before moving |
| UndefSubregDef.getOperand(0).setIsUndef(false); |
| testHandleMove(MF, LIS, 4, 1, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, TestMoveSubRegUseAcrossMainRangeHole) { |
| liveIntervalTest(R"MIR( |
| %1:sgpr_128 = IMPLICIT_DEF |
| bb.1: |
| %2:sgpr_32 = COPY %1.sub2 |
| %3:sgpr_32 = COPY %1.sub1 |
| %1.sub2 = COPY %2 |
| undef %1.sub0 = IMPLICIT_DEF |
| %1.sub2 = IMPLICIT_DEF |
| S_CBRANCH_SCC1 %bb.1, implicit undef $scc |
| S_BRANCH %bb.2 |
| bb.2: |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| MachineInstr &MI = getMI(MF, 3, /*BlockNum=*/1); |
| MI.getOperand(0).setIsUndef(false); |
| testHandleMove(MF, LIS, 4, 3, 1); |
| testHandleMove(MF, LIS, 1, 4, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, TestMoveSubRegsOfOneReg) { |
| liveIntervalTest(R"MIR( |
| INLINEASM &"", 0, 1835018, def undef %4.sub0:vreg_64, 1835018, def undef %4.sub1:vreg_64 |
| %1:vreg_64 = COPY %4 |
| undef %2.sub0:vreg_64 = V_MOV_B32_e32 0, implicit $exec |
| %2.sub1:vreg_64 = COPY %2.sub0 |
| %3:vreg_64 = COPY %2 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMove(MF, LIS, 1, 4); |
| testHandleMove(MF, LIS, 0, 3); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, BundleUse) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMoveIntoNewBundle(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, BundleDef) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMoveIntoNewBundle(MF, LIS, 0, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, BundleRedef) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| %0 = IMPLICIT_DEF implicit %0(tied-def 0) |
| S_NOP 0, implicit %0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMoveIntoNewBundle(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, BundleInternalUse) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit %0 |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMoveIntoNewBundle(MF, LIS, 0, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, BundleUndefUse) { |
| liveIntervalTest(R"MIR( |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0, implicit undef %0 |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMoveIntoNewBundle(MF, LIS, 1, 2); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, BundleSubRegUse) { |
| liveIntervalTest(R"MIR( |
| successors: %bb.1, %bb.2 |
| undef %0.sub0 = IMPLICIT_DEF |
| %0.sub1 = IMPLICIT_DEF |
| S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc |
| S_BRANCH %bb.1 |
| bb.1: |
| S_NOP 0 |
| S_NOP 0, implicit %0.sub1 |
| bb.2: |
| S_NOP 0, implicit %0.sub1 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMoveIntoNewBundle(MF, LIS, 0, 1, 1); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, BundleSubRegDef) { |
| liveIntervalTest(R"MIR( |
| successors: %bb.1, %bb.2 |
| undef %0.sub0 = IMPLICIT_DEF |
| %0.sub1 = IMPLICIT_DEF |
| S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc |
| S_BRANCH %bb.1 |
| bb.1: |
| S_NOP 0 |
| S_NOP 0, implicit %0.sub1 |
| bb.2: |
| S_NOP 0, implicit %0.sub1 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testHandleMoveIntoNewBundle(MF, LIS, 0, 1, 0); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, SplitAtOneInstruction) { |
| liveIntervalTest(R"MIR( |
| successors: %bb.1 |
| %0 = IMPLICIT_DEF |
| S_BRANCH %bb.1 |
| bb.1: |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testSplitAt(MF, LIS, 1, 0); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, SplitAtMultiInstruction) { |
| liveIntervalTest(R"MIR( |
| successors: %bb.1 |
| %0 = IMPLICIT_DEF |
| S_NOP 0 |
| S_NOP 0 |
| S_NOP 0 |
| S_NOP 0 |
| S_BRANCH %bb.1 |
| bb.1: |
| S_NOP 0 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| testSplitAt(MF, LIS, 0, 0); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, RepairIntervals) { |
| liveIntervalTest(R"MIR( |
| %1:sgpr_32 = IMPLICIT_DEF |
| dead %2:sgpr_32 = COPY undef %3.sub0:sgpr_128 |
| undef %4.sub2:sgpr_128 = COPY %1:sgpr_32 |
| %5:sgpr_32 = COPY %4.sub2:sgpr_128 |
| )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { |
| MachineInstr &Instr1 = getMI(MF, 1, 0); |
| MachineInstr &Instr2 = getMI(MF, 2, 0); |
| MachineInstr &Instr3 = getMI(MF, 3, 0); |
| LIS.RemoveMachineInstrFromMaps(Instr2); |
| MachineBasicBlock *MBB = Instr1.getParent(); |
| SmallVector<Register> OrigRegs{ |
| Instr1.getOperand(0).getReg(), |
| Instr2.getOperand(0).getReg(), |
| Instr2.getOperand(1).getReg(), |
| }; |
| LIS.repairIntervalsInRange(MBB, Instr2, Instr3, OrigRegs); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, AdjacentIntervals) { |
| liveIntervalTest( |
| R"MIR( |
| successors: %bb.1, %bb.2 |
| |
| $vgpr1 = IMPLICIT_DEF |
| S_NOP 0, implicit $vgpr1 |
| %1:vgpr_32 = IMPLICIT_DEF |
| %2:vgpr_32 = IMPLICIT_DEF |
| S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc |
| S_BRANCH %bb.1 |
| bb.1: |
| $vgpr0, dead renamable $vcc = V_ADD_CO_U32_e64 %1, %2, 0, implicit $exec |
| S_NOP 0, implicit $vgpr0 |
| S_BRANCH %bb.3 |
| bb.2: |
| $vgpr0 = IMPLICIT_DEF |
| $vgpr1, dead renamable $vcc = V_ADD_CO_U32_e64 %1, %2, 0, implicit $exec |
| S_NOP 0, implicit $vgpr0, implicit $vgpr1 |
| S_BRANCH %bb.3 |
| bb.3: |
| )MIR", |
| [](MachineFunction &MF, LiveIntervals &LIS) { |
| const auto &R1 = |
| LIS.getInterval(getMI(MF, 2, 0).getOperand(0).getReg()); |
| const auto &R2 = |
| LIS.getInterval(getMI(MF, 3, 0).getOperand(0).getReg()); |
| MCRegister V1 = getMI(MF, 1, 2).getOperand(0).getReg().asMCReg(); |
| |
| ASSERT_FALSE(checkRegUnitInterference( |
| LIS, *MF.getSubtarget().getRegisterInfo(), R1, V1)); |
| ASSERT_FALSE(checkRegUnitInterference( |
| LIS, *MF.getSubtarget().getRegisterInfo(), R2, V1)); |
| }); |
| } |
| |
| TEST(LiveIntervalTest, LiveThroughSegments) { |
| liveIntervalTest( |
| R"MIR( |
| %0 = IMPLICIT_DEF |
| S_BRANCH %bb.2 |
| bb.1: |
| S_NOP 0, implicit %0 |
| S_ENDPGM 0 |
| bb.2: |
| S_BRANCH %bb.1 |
| )MIR", |
| [](MachineFunction &MF, LiveIntervals &LIS) { |
| MachineInstr &ImpDef = getMI(MF, 0, 0); |
| MachineInstr &Nop = getMI(MF, 0, 1); |
| LiveInterval &LI = LIS.getInterval(ImpDef.getOperand(0).getReg()); |
| SlotIndex OrigIdx = LIS.getInstructionIndex(ImpDef).getRegSlot(); |
| LiveInterval::iterator FirstSeg = LI.FindSegmentContaining(OrigIdx); |
| |
| // %0 is live through bb.2. Move its def into bb.1 and update LIS but do |
| // not remove the segment for bb.2. This should cause machine |
| // verification to fail. |
| LIS.RemoveMachineInstrFromMaps(ImpDef); |
| ImpDef.moveBefore(&Nop); |
| LIS.InsertMachineInstrInMaps(ImpDef); |
| |
| SlotIndex NewIdx = LIS.getInstructionIndex(ImpDef).getRegSlot(); |
| FirstSeg->start = NewIdx; |
| FirstSeg->valno->def = NewIdx; |
| }, |
| false); |
| } |
| |
| int main(int argc, char **argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| initLLVM(); |
| return RUN_ALL_TESTS(); |
| } |