[MachineCopyPropagation] Eliminate spillage copies that might be caused by eviction...
authorKai Luo <lkail@cn.ibm.com>
Wed, 8 Feb 2023 02:14:24 +0000 (02:14 +0000)
committerKai Luo <lkail@cn.ibm.com>
Wed, 8 Feb 2023 03:34:25 +0000 (03:34 +0000)
Remove spill-reload like copy chains. For example
```
r0 = COPY r1
r1 = COPY r2
r2 = COPY r3
r3 = COPY r4
<def-use r4>
r4 = COPY r3
r3 = COPY r2
r2 = COPY r1
r1 = COPY r0
```
will be folded into
```
r0 = COPY r1
r1 = COPY r4
<def-use r4>
r4 = COPY r1
r1 = COPY r0
```

Reviewed By: qcolombet

Differential Revision: https://reviews.llvm.org/D122118

llvm/include/llvm/CodeGen/TargetSubtargetInfo.h
llvm/lib/CodeGen/MachineCopyPropagation.cpp
llvm/lib/Target/PowerPC/PPCSubtarget.h
llvm/test/CodeGen/PowerPC/mcp-elim-eviction-chain.mir [new file with mode: 0644]

index 1312ae6..9b98ef3 100644 (file)
@@ -318,6 +318,11 @@ public:
   classifyGlobalFunctionReference(const GlobalValue *GV) const {
     return 0;
   }
+
+  /// Enable spillage copy elimination in MachineCopyPropagation pass. This
+  /// helps removing redundant copies generated by register allocator when
+  /// handling complex eviction chains.
+  virtual bool enableSpillageCopyElimination() const { return false; }
 };
 
 } // end namespace llvm
index 970659c..bd2d80c 100644 (file)
@@ -80,11 +80,15 @@ using namespace llvm;
 STATISTIC(NumDeletes, "Number of dead copies deleted");
 STATISTIC(NumCopyForwards, "Number of copy uses forwarded");
 STATISTIC(NumCopyBackwardPropagated, "Number of copy defs backward propagated");
+STATISTIC(SpillageChainsLength, "Length of spillage chains");
+STATISTIC(NumSpillageChains, "Number of spillage chains");
 DEBUG_COUNTER(FwdCounter, "machine-cp-fwd",
               "Controls which register COPYs are forwarded");
 
 static cl::opt<bool> MCPUseCopyInstr("mcp-use-is-copy-instr", cl::init(false),
                                      cl::Hidden);
+static cl::opt<cl::boolOrDefault>
+    EnableSpillageCopyElimination("enable-spill-copy-elim", cl::Hidden);
 
 namespace {
 
@@ -103,7 +107,7 @@ static std::optional<DestSourcePair> isCopyInstr(const MachineInstr &MI,
 
 class CopyTracker {
   struct CopyInfo {
-    MachineInstr *MI;
+    MachineInstr *MI, *LastSeenUseInCopy;
     SmallVector<MCRegister, 4> DefRegs;
     bool Avail;
   };
@@ -189,15 +193,16 @@ public:
 
     // Remember Def is defined by the copy.
     for (MCRegUnitIterator RUI(Def, &TRI); RUI.isValid(); ++RUI)
-      Copies[*RUI] = {MI, {}, true};
+      Copies[*RUI] = {MI, nullptr,  {}, true};
 
     // Remember source that's copied to Def. Once it's clobbered, then
     // it's no longer available for copy propagation.
     for (MCRegUnitIterator RUI(Src, &TRI); RUI.isValid(); ++RUI) {
-      auto I = Copies.insert({*RUI, {nullptr, {}, false}});
+      auto I = Copies.insert({*RUI, {nullptr, nullptr, {}, false}});
       auto &Copy = I.first->second;
       if (!is_contained(Copy.DefRegs, Def))
         Copy.DefRegs.push_back(Def);
+      Copy.LastSeenUseInCopy = MI;
     }
   }
 
@@ -286,6 +291,48 @@ public:
     return AvailCopy;
   }
 
+  // Find last COPY that defines Reg before Current MachineInstr.
+  MachineInstr *findLastSeenDefInCopy(const MachineInstr &Current,
+                                      MCRegister Reg,
+                                      const TargetRegisterInfo &TRI,
+                                      const TargetInstrInfo &TII,
+                                      bool UseCopyInstr) {
+    MCRegUnitIterator RUI(Reg, &TRI);
+    auto CI = Copies.find(*RUI);
+    if (CI == Copies.end() || !CI->second.Avail)
+      return nullptr;
+
+    MachineInstr *DefCopy = CI->second.MI;
+    std::optional<DestSourcePair> CopyOperands =
+        isCopyInstr(*DefCopy, TII, UseCopyInstr);
+    Register Def = CopyOperands->Destination->getReg();
+    if (!TRI.isSubRegisterEq(Def, Reg))
+      return nullptr;
+
+    for (const MachineInstr &MI :
+         make_range(static_cast<const MachineInstr *>(DefCopy)->getIterator(),
+                    Current.getIterator()))
+      for (const MachineOperand &MO : MI.operands())
+        if (MO.isRegMask())
+          if (MO.clobbersPhysReg(Def)) {
+            LLVM_DEBUG(dbgs() << "MCP: Removed tracking of "
+                              << printReg(Def, &TRI) << "\n");
+            return nullptr;
+          }
+
+    return DefCopy;
+  }
+
+  // Find last COPY that uses Reg.
+  MachineInstr *findLastSeenUseInCopy(MCRegister Reg,
+                                      const TargetRegisterInfo &TRI) {
+    MCRegUnitIterator RUI(Reg, &TRI);
+    auto CI = Copies.find(*RUI);
+    if (CI == Copies.end())
+      return nullptr;
+    return CI->second.LastSeenUseInCopy;
+  }
+
   void clear() {
     Copies.clear();
   }
@@ -325,6 +372,7 @@ private:
   void ReadRegister(MCRegister Reg, MachineInstr &Reader, DebugType DT);
   void ForwardCopyPropagateBlock(MachineBasicBlock &MBB);
   void BackwardCopyPropagateBlock(MachineBasicBlock &MBB);
+  void EliminateSpillageCopies(MachineBasicBlock &MBB);
   bool eraseIfRedundant(MachineInstr &Copy, MCRegister Src, MCRegister Def);
   void forwardUses(MachineInstr &MI);
   void propagateDefs(MachineInstr &MI);
@@ -1015,10 +1063,345 @@ void MachineCopyPropagation::BackwardCopyPropagateBlock(
   Tracker.clear();
 }
 
+static void LLVM_ATTRIBUTE_UNUSED printSpillReloadChain(
+    DenseMap<MachineInstr *, SmallVector<MachineInstr *>> &SpillChain,
+    DenseMap<MachineInstr *, SmallVector<MachineInstr *>> &ReloadChain,
+    MachineInstr *Leader) {
+  auto &SC = SpillChain[Leader];
+  auto &RC = ReloadChain[Leader];
+  for (auto I = SC.rbegin(), E = SC.rend(); I != E; ++I)
+    (*I)->dump();
+  for (MachineInstr *MI : RC)
+    MI->dump();
+}
+
+// Remove spill-reload like copy chains. For example
+// r0 = COPY r1
+// r1 = COPY r2
+// r2 = COPY r3
+// r3 = COPY r4
+// <def-use r4>
+// r4 = COPY r3
+// r3 = COPY r2
+// r2 = COPY r1
+// r1 = COPY r0
+// will be folded into
+// r0 = COPY r1
+// r1 = COPY r4
+// <def-use r4>
+// r4 = COPY r1
+// r1 = COPY r0
+// TODO: Currently we don't track usage of r0 outside the chain, so we
+// conservatively keep its value as it was before the rewrite.
+//
+// The algorithm is trying to keep
+// property#1: No Def of spill COPY in the chain is used or defined until the
+// paired reload COPY in the chain uses the Def.
+//
+// property#2: NO Source of COPY in the chain is used or defined until the next
+// COPY in the chain defines the Source, except the innermost spill-reload
+// pair.
+//
+// The algorithm is conducted by checking every COPY inside the MBB, assuming
+// the COPY is a reload COPY, then try to find paired spill COPY by searching
+// the COPY defines the Src of the reload COPY backward. If such pair is found,
+// it either belongs to an existing chain or a new chain depends on
+// last available COPY uses the Def of the reload COPY.
+// Implementation notes, we use CopyTracker::findLastDefCopy(Reg, ...) to find
+// out last COPY that defines Reg; we use CopyTracker::findLastUseCopy(Reg, ...)
+// to find out last COPY that uses Reg. When we are encountered with a Non-COPY
+// instruction, we check registers in the operands of this instruction. If this
+// Reg is defined by a COPY, we untrack this Reg via
+// CopyTracker::clobberRegister(Reg, ...).
+void MachineCopyPropagation::EliminateSpillageCopies(MachineBasicBlock &MBB) {
+  // ChainLeader maps MI inside a spill-reload chain to its innermost reload COPY.
+  // Thus we can track if a MI belongs to an existing spill-reload chain.
+  DenseMap<MachineInstr *, MachineInstr *> ChainLeader;
+  // SpillChain maps innermost reload COPY of a spill-reload chain to a sequence
+  // of COPYs that forms spills of a spill-reload chain.
+  // ReloadChain maps innermost reload COPY of a spill-reload chain to a
+  // sequence of COPYs that forms reloads of a spill-reload chain.
+  DenseMap<MachineInstr *, SmallVector<MachineInstr *>> SpillChain, ReloadChain;
+  // If a COPY's Source has use or def until next COPY defines the Source,
+  // we put the COPY in this set to keep property#2.
+  DenseSet<const MachineInstr *> CopySourceInvalid;
+
+  auto TryFoldSpillageCopies =
+      [&, this](const SmallVectorImpl<MachineInstr *> &SC,
+                const SmallVectorImpl<MachineInstr *> &RC) {
+        assert(SC.size() == RC.size() && "Spill-reload should be paired");
+
+        // We need at least 3 pairs of copies for the transformation to apply,
+        // because the first outermost pair cannot be removed since we don't
+        // recolor outside of the chain and that we need at least one temporary
+        // spill slot to shorten the chain. If we only have a chain of two
+        // pairs, we already have the shortest sequence this code can handle:
+        // the outermost pair for the temporary spill slot, and the pair that
+        // use that temporary spill slot for the other end of the chain.
+        // TODO: We might be able to simplify to one spill-reload pair if collecting
+        // more infomation about the outermost COPY.
+        if (SC.size() <= 2)
+          return;
+
+        // If violate property#2, we don't fold the chain.
+        for (const MachineInstr *Spill : make_range(SC.begin() + 1, SC.end()))
+          if (CopySourceInvalid.count(Spill))
+            return;
+
+        for (const MachineInstr *Reload : make_range(RC.begin(), RC.end() - 1))
+          if (CopySourceInvalid.count(Reload))
+            return;
+
+        auto CheckCopyConstraint = [this](Register Def, Register Src) {
+          for (const TargetRegisterClass *RC : TRI->regclasses()) {
+            if (RC->contains(Def) && RC->contains(Src))
+              return true;
+          }
+          return false;
+        };
+
+        auto UpdateReg = [](MachineInstr *MI, const MachineOperand *Old,
+                            const MachineOperand *New) {
+          for (MachineOperand &MO : MI->operands()) {
+            if (&MO == Old)
+              MO.setReg(New->getReg());
+          }
+        };
+
+        std::optional<DestSourcePair> InnerMostSpillCopy =
+            isCopyInstr(*SC[0], *TII, UseCopyInstr);
+        std::optional<DestSourcePair> OuterMostSpillCopy =
+            isCopyInstr(*SC.back(), *TII, UseCopyInstr);
+        std::optional<DestSourcePair> InnerMostReloadCopy =
+            isCopyInstr(*RC[0], *TII, UseCopyInstr);
+        std::optional<DestSourcePair> OuterMostReloadCopy =
+            isCopyInstr(*RC.back(), *TII, UseCopyInstr);
+        if (!CheckCopyConstraint(OuterMostSpillCopy->Source->getReg(),
+                                 InnerMostSpillCopy->Source->getReg()) ||
+            !CheckCopyConstraint(InnerMostReloadCopy->Destination->getReg(),
+                                 OuterMostReloadCopy->Destination->getReg()))
+          return;
+
+        SpillageChainsLength += SC.size() + RC.size();
+        NumSpillageChains += 1;
+        UpdateReg(SC[0], InnerMostSpillCopy->Destination,
+                  OuterMostSpillCopy->Source);
+        UpdateReg(RC[0], InnerMostReloadCopy->Source,
+                  OuterMostReloadCopy->Destination);
+
+        for (size_t I = 1; I < SC.size() - 1; ++I) {
+          SC[I]->eraseFromParent();
+          RC[I]->eraseFromParent();
+          NumDeletes += 2;
+        }
+      };
+
+  auto IsFoldableCopy = [this](const MachineInstr &MaybeCopy) {
+    if (MaybeCopy.getNumImplicitOperands() > 0)
+      return false;
+    std::optional<DestSourcePair> CopyOperands =
+        isCopyInstr(MaybeCopy, *TII, UseCopyInstr);
+    if (!CopyOperands)
+      return false;
+    Register Src = CopyOperands->Source->getReg();
+    Register Def = CopyOperands->Destination->getReg();
+    return Src && Def && !TRI->regsOverlap(Src, Def) &&
+           CopyOperands->Source->isRenamable() &&
+           CopyOperands->Destination->isRenamable();
+  };
+
+  auto IsSpillReloadPair = [&, this](const MachineInstr &Spill,
+                                     const MachineInstr &Reload) {
+    if (!IsFoldableCopy(Spill) || !IsFoldableCopy(Reload))
+      return false;
+    std::optional<DestSourcePair> SpillCopy =
+        isCopyInstr(Spill, *TII, UseCopyInstr);
+    std::optional<DestSourcePair> ReloadCopy =
+        isCopyInstr(Reload, *TII, UseCopyInstr);
+    if (!SpillCopy || !ReloadCopy)
+      return false;
+    return SpillCopy->Source->getReg() == ReloadCopy->Destination->getReg() &&
+           SpillCopy->Destination->getReg() == ReloadCopy->Source->getReg();
+  };
+
+  auto IsChainedCopy = [&, this](const MachineInstr &Prev,
+                                 const MachineInstr &Current) {
+    if (!IsFoldableCopy(Prev) || !IsFoldableCopy(Current))
+      return false;
+    std::optional<DestSourcePair> PrevCopy =
+        isCopyInstr(Prev, *TII, UseCopyInstr);
+    std::optional<DestSourcePair> CurrentCopy =
+        isCopyInstr(Current, *TII, UseCopyInstr);
+    if (!PrevCopy || !CurrentCopy)
+      return false;
+    return PrevCopy->Source->getReg() == CurrentCopy->Destination->getReg();
+  };
+
+  for (MachineInstr &MI : llvm::make_early_inc_range(MBB)) {
+    std::optional<DestSourcePair> CopyOperands =
+        isCopyInstr(MI, *TII, UseCopyInstr);
+
+    // Update track information via non-copy instruction.
+    SmallSet<Register, 8> RegsToClobber;
+    if (!CopyOperands) {
+      for (const MachineOperand &MO : MI.operands()) {
+        if (!MO.isReg())
+          continue;
+        Register Reg = MO.getReg();
+        if (!Reg)
+          continue;
+        MachineInstr *LastUseCopy =
+            Tracker.findLastSeenUseInCopy(Reg.asMCReg(), *TRI);
+        if (LastUseCopy) {
+          LLVM_DEBUG(dbgs() << "MCP: Copy source of\n");
+          LLVM_DEBUG(LastUseCopy->dump());
+          LLVM_DEBUG(dbgs() << "might be invalidated by\n");
+          LLVM_DEBUG(MI.dump());
+          CopySourceInvalid.insert(LastUseCopy);
+        }
+        // Must be noted Tracker.clobberRegister(Reg, ...) removes tracking of
+        // Reg, i.e, COPY that defines Reg is removed from the mapping as well
+        // as marking COPYs that uses Reg unavailable.
+        // We don't invoke CopyTracker::clobberRegister(Reg, ...) if Reg is not
+        // defined by a previous COPY, since we don't want to make COPYs uses
+        // Reg unavailable.
+        if (Tracker.findLastSeenDefInCopy(MI, Reg.asMCReg(), *TRI, *TII,
+                                    UseCopyInstr))
+          // Thus we can keep the property#1.
+          RegsToClobber.insert(Reg);
+      }
+      for (Register Reg : RegsToClobber) {
+        Tracker.clobberRegister(Reg, *TRI, *TII, UseCopyInstr);
+        LLVM_DEBUG(dbgs() << "MCP: Removed tracking of " << printReg(Reg, TRI)
+                          << "\n");
+      }
+      continue;
+    }
+
+    Register Src = CopyOperands->Source->getReg();
+    Register Def = CopyOperands->Destination->getReg();
+    // Check if we can find a pair spill-reload copy.
+    LLVM_DEBUG(dbgs() << "MCP: Searching paired spill for reload: ");
+    LLVM_DEBUG(MI.dump());
+    MachineInstr *MaybeSpill =
+        Tracker.findLastSeenDefInCopy(MI, Src.asMCReg(), *TRI, *TII, UseCopyInstr);
+    bool MaybeSpillIsChained = ChainLeader.count(MaybeSpill);
+    if (!MaybeSpillIsChained && MaybeSpill &&
+        IsSpillReloadPair(*MaybeSpill, MI)) {
+      // Check if we already have an existing chain. Now we have a
+      // spill-reload pair.
+      // L2: r2 = COPY r3
+      // L5: r3 = COPY r2
+      // Looking for a valid COPY before L5 which uses r3.
+      // This can be serverial cases.
+      // Case #1:
+      // No COPY is found, which can be r3 is def-use between (L2, L5), we
+      // create a new chain for L2 and L5.
+      // Case #2:
+      // L2: r2 = COPY r3
+      // L5: r3 = COPY r2
+      // Such COPY is found and is L2, we create a new chain for L2 and L5.
+      // Case #3:
+      // L2: r2 = COPY r3
+      // L3: r1 = COPY r3
+      // L5: r3 = COPY r2
+      // we create a new chain for L2 and L5.
+      // Case #4:
+      // L2: r2 = COPY r3
+      // L3: r1 = COPY r3
+      // L4: r3 = COPY r1
+      // L5: r3 = COPY r2
+      // Such COPY won't be found since L4 defines r3. we create a new chain
+      // for L2 and L5.
+      // Case #5:
+      // L2: r2 = COPY r3
+      // L3: r3 = COPY r1
+      // L4: r1 = COPY r3
+      // L5: r3 = COPY r2
+      // COPY is found and is L4 which belongs to an existing chain, we add
+      // L2 and L5 to this chain.
+      LLVM_DEBUG(dbgs() << "MCP: Found spill: ");
+      LLVM_DEBUG(MaybeSpill->dump());
+      MachineInstr *MaybePrevReload =
+          Tracker.findLastSeenUseInCopy(Def.asMCReg(), *TRI);
+      auto Leader = ChainLeader.find(MaybePrevReload);
+      MachineInstr *L = nullptr;
+      if (Leader == ChainLeader.end() ||
+          (MaybePrevReload && !IsChainedCopy(*MaybePrevReload, MI))) {
+        L = &MI;
+        assert(!SpillChain.count(L) &&
+               "SpillChain should not have contained newly found chain");
+      } else {
+        assert(MaybePrevReload &&
+               "Found a valid leader through nullptr should not happend");
+        L = Leader->second;
+        assert(SpillChain[L].size() > 0 &&
+               "Existing chain's length should be larger than zero");
+      }
+      assert(!ChainLeader.count(&MI) && !ChainLeader.count(MaybeSpill) &&
+             "Newly found paired spill-reload should not belong to any chain "
+             "at this point");
+      ChainLeader.insert({MaybeSpill, L});
+      ChainLeader.insert({&MI, L});
+      SpillChain[L].push_back(MaybeSpill);
+      ReloadChain[L].push_back(&MI);
+      LLVM_DEBUG(dbgs() << "MCP: Chain " << L << " now is:\n");
+      LLVM_DEBUG(printSpillReloadChain(SpillChain, ReloadChain, L));
+    } else if (MaybeSpill && !MaybeSpillIsChained) {
+      // MaybeSpill is unable to pair with MI. That's to say adding MI makes
+      // the chain invalid.
+      // The COPY defines Src is no longer considered as a candidate of a
+      // valid chain. Since we expect the Def of a spill copy isn't used by
+      // any COPY instruction until a reload copy. For example:
+      // L1: r1 = COPY r2
+      // L2: r3 = COPY r1
+      // If we later have
+      // L1: r1 = COPY r2
+      // L2: r3 = COPY r1
+      // L3: r2 = COPY r1
+      // L1 and L3 can't be a valid spill-reload pair.
+      // Thus we keep the property#1.
+      LLVM_DEBUG(dbgs() << "MCP: Not paired spill-reload:\n");
+      LLVM_DEBUG(MaybeSpill->dump());
+      LLVM_DEBUG(MI.dump());
+      Tracker.clobberRegister(Src.asMCReg(), *TRI, *TII, UseCopyInstr);
+      LLVM_DEBUG(dbgs() << "MCP: Removed tracking of " << printReg(Src, TRI)
+                        << "\n");
+    }
+    Tracker.trackCopy(&MI, *TRI, *TII, UseCopyInstr);
+  }
+
+  for (auto I = SpillChain.begin(), E = SpillChain.end(); I != E; ++I) {
+    auto &SC = I->second;
+    assert(ReloadChain.count(I->first) &&
+           "Reload chain of the same leader should exist");
+    auto &RC = ReloadChain[I->first];
+    TryFoldSpillageCopies(SC, RC);
+  }
+
+  MaybeDeadCopies.clear();
+  CopyDbgUsers.clear();
+  Tracker.clear();
+}
+
 bool MachineCopyPropagation::runOnMachineFunction(MachineFunction &MF) {
   if (skipFunction(MF.getFunction()))
     return false;
 
+  bool isSpillageCopyElimEnabled = false;
+  switch (EnableSpillageCopyElimination) {
+  case cl::BOU_UNSET:
+    isSpillageCopyElimEnabled =
+        MF.getSubtarget().enableSpillageCopyElimination();
+    break;
+  case cl::BOU_TRUE:
+    isSpillageCopyElimEnabled = true;
+    break;
+  case cl::BOU_FALSE:
+    isSpillageCopyElimEnabled = false;
+    break;
+  }
+
   Changed = false;
 
   TRI = MF.getSubtarget().getRegisterInfo();
@@ -1026,6 +1409,8 @@ bool MachineCopyPropagation::runOnMachineFunction(MachineFunction &MF) {
   MRI = &MF.getRegInfo();
 
   for (MachineBasicBlock &MBB : MF) {
+    if (isSpillageCopyElimEnabled)
+      EliminateSpillageCopies(MBB);
     BackwardCopyPropagateBlock(MBB);
     ForwardCopyPropagateBlock(MBB);
   }
index aa75f08..5b52335 100644 (file)
@@ -240,6 +240,8 @@ public:
 
   bool enableSubRegLiveness() const override;
 
+  bool enableSpillageCopyElimination() const override { return true; }
+
   /// True if the GV will be accessed via an indirect symbol.
   bool isGVIndirectSymbol(const GlobalValue *GV) const;
 
diff --git a/llvm/test/CodeGen/PowerPC/mcp-elim-eviction-chain.mir b/llvm/test/CodeGen/PowerPC/mcp-elim-eviction-chain.mir
new file mode 100644 (file)
index 0000000..456f26c
--- /dev/null
@@ -0,0 +1,265 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
+# RUN: llc -O3 -verify-machineinstrs -mtriple=powerpc64-unknown-unknown \
+# RUN:   -simplify-mir -run-pass=machine-cp %s -o - | FileCheck %s
+
+--- |
+  declare void @foo()
+  define void @test0() {
+  entry:
+    ret void
+  }
+
+  define void @test1() {
+  entry:
+    ret void
+  }
+
+  define void @test2() {
+  entry:
+    ret void
+  }
+
+  define void @test3() {
+  entry:
+    ret void
+  }
+
+  define void @test4() {
+  entry:
+    ret void
+  }
+
+  define void @test5() {
+  entry:
+    ret void
+  }
+
+  define void @test6() {
+  entry:
+    ret void
+  }
+
+...
+---
+name: test0
+alignment: 4
+tracksRegLiveness: true
+body: |
+  bb.0.entry:
+    liveins: $x4, $x5, $x20, $x21, $x22
+    ; CHECK-LABEL: name: test0
+    ; CHECK: liveins: $x4, $x5, $x20, $x21, $x22
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $x24 = COPY $x4
+    ; CHECK-NEXT: $x23 = COPY renamable $x20
+    ; CHECK-NEXT: renamable $x20 = ADD8 $x4, $x5
+    ; CHECK-NEXT: renamable $x4 = COPY renamable $x20
+    ; CHECK-NEXT: renamable $x20 = COPY $x23
+    ; CHECK-NEXT: renamable $x23 = COPY renamable $x24
+    ; CHECK-NEXT: $x3 = COPY renamable $x4
+    ; CHECK-NEXT: BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x20, implicit $x21, implicit $x22, implicit $x23
+    renamable $x23 = COPY renamable $x4
+    renamable $x24 = COPY renamable $x23
+    renamable $x23 = COPY renamable $x22
+    renamable $x22 = COPY renamable $x21
+    renamable $x21 = COPY renamable $x20
+    renamable $x20 = ADD8 $x4, $x5
+    renamable $x4 = COPY renamable $x20
+    renamable $x20 = COPY renamable $x21
+    renamable $x21 = COPY renamable $x22
+    renamable $x22 = COPY renamable $x23
+    renamable $x23 = COPY renamable $x24
+    $x3 = COPY renamable $x4
+    BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x20, implicit $x21, implicit $x22, implicit $x23
+
+...
+
+# Duplicated pairs.
+---
+name: test1
+alignment: 4
+tracksRegLiveness: true
+body: |
+  bb.0.entry:
+    liveins: $x3, $x20, $x21, $x22, $x23
+    ; CHECK-LABEL: name: test1
+    ; CHECK: liveins: $x3, $x20, $x21, $x22, $x23
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $x24 = COPY $x3
+    ; CHECK-NEXT: renamable $x23 = COPY renamable $x22
+    ; CHECK-NEXT: renamable $x22 = COPY renamable $x21
+    ; CHECK-NEXT: renamable $x21 = COPY renamable $x20
+    ; CHECK-NEXT: BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x20, implicit $x21, implicit $x22, implicit $x23, implicit $x24
+    renamable $x23 = COPY $x3
+    renamable $x24 = COPY renamable $x23
+    renamable $x23 = COPY renamable $x22
+    renamable $x22 = COPY renamable $x21
+    renamable $x21 = COPY renamable $x20
+    renamable $x20 = COPY renamable $x21
+    renamable $x21 = COPY renamable $x22
+    renamable $x22 = COPY renamable $x23
+    renamable $x23 = COPY renamable $x24
+    renamable $x24 = COPY renamable $x23
+    renamable $x23 = COPY renamable $x22
+    renamable $x22 = COPY renamable $x21
+    renamable $x21 = COPY renamable $x20
+    BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x20, implicit $x21, implicit $x22, implicit $x23, implicit $x24
+
+...
+
+# Chain one after one.
+---
+name: test2
+alignment: 4
+tracksRegLiveness: true
+body: |
+  bb.0.entry:
+    liveins: $x3, $x18, $x19, $x20, $x21, $x22, $x23, $x24
+    ; CHECK-LABEL: name: test2
+    ; CHECK: liveins: $x3, $x18, $x19, $x20, $x21, $x22, $x23, $x24
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $x21 = COPY renamable $x20
+    ; CHECK-NEXT: renamable $x20 = COPY renamable $x21
+    ; CHECK-NEXT: renamable $x25 = COPY renamable $x24
+    ; CHECK-NEXT: renamable $x24 = COPY renamable $x25
+    ; CHECK-NEXT: BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x18, implicit $x19, implicit $x20, implicit $x21, implicit $x22, implicit $x23, implicit $x24, implicit $x25
+    renamable $x21 = COPY renamable $x20
+    renamable $x20 = COPY renamable $x19
+    renamable $x19 = COPY renamable $x18
+    renamable $x18 = COPY renamable $x19
+    renamable $x19 = COPY renamable $x20
+    renamable $x20 = COPY renamable $x21
+    renamable $x25 = COPY renamable $x24
+    renamable $x24 = COPY renamable $x23
+    renamable $x23 = COPY renamable $x22
+    renamable $x22 = COPY renamable $x23
+    renamable $x23 = COPY renamable $x24
+    renamable $x24 = COPY renamable $x25
+    BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x18, implicit $x19, implicit $x20, implicit $x21, implicit $x22, implicit $x23, implicit $x24, implicit $x25
+
+...
+
+# Reorder code in test2, thus we have two chains in build simultaneously.
+---
+name: test3
+alignment: 4
+tracksRegLiveness: true
+body: |
+  bb.0.entry:
+    liveins: $x3, $x18, $x19, $x20, $x21, $x22, $x23, $x24
+    ; CHECK-LABEL: name: test3
+    ; CHECK: liveins: $x3, $x18, $x19, $x20, $x21, $x22, $x23, $x24
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $x21 = COPY renamable $x20
+    ; CHECK-NEXT: renamable $x25 = COPY renamable $x24
+    ; CHECK-NEXT: renamable $x20 = COPY renamable $x21
+    ; CHECK-NEXT: renamable $x24 = COPY renamable $x25
+    ; CHECK-NEXT: BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x18, implicit $x19, implicit $x20, implicit $x21, implicit $x22, implicit $x23, implicit $x24, implicit $x25
+    renamable $x21 = COPY renamable $x20
+    renamable $x25 = COPY renamable $x24
+    renamable $x20 = COPY renamable $x19
+    renamable $x24 = COPY renamable $x23
+    renamable $x19 = COPY renamable $x18
+    renamable $x23 = COPY renamable $x22
+    renamable $x18 = COPY renamable $x19
+    renamable $x22 = COPY renamable $x23
+    renamable $x19 = COPY renamable $x20
+    renamable $x23 = COPY renamable $x24
+    renamable $x20 = COPY renamable $x21
+    renamable $x24 = COPY renamable $x25
+    BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x18, implicit $x19, implicit $x20, implicit $x21, implicit $x22, implicit $x23, implicit $x24, implicit $x25
+
+...
+
+---
+name: test4
+alignment: 4
+tracksRegLiveness: true
+body: |
+  bb.0.entry:
+    liveins: $x3, $x4, $x5
+    ; CHECK-LABEL: name: test4
+    ; CHECK: liveins: $x3, $x4, $x5
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: BLR8 implicit $lr8, implicit undef $rm, implicit $x3
+    renamable $x5 = COPY renamable $x3
+    renamable $x4 = COPY renamable $x3
+    renamable $x2 = COPY renamable $x3
+    renamable $x3 = COPY renamable $x2
+    renamable $x3 = COPY renamable $x4
+    renamable $x3 = COPY renamable $x5
+    BLR8 implicit $lr8, implicit undef $rm, implicit $x3
+
+...
+
+# Chain across regmask.
+---
+name: test5
+alignment: 4
+tracksRegLiveness: true
+body: |
+  bb.0.entry:
+    liveins: $x17, $x16, $x15, $x14, $x3
+    ; CHECK-LABEL: name: test5
+    ; CHECK: liveins: $x17, $x16, $x15, $x14, $x3
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $x18 = COPY renamable $x17
+    ; CHECK-NEXT: $x17 = COPY renamable $x3
+    ; CHECK-NEXT: BL8_NOP @foo, csr_ppc64, implicit-def dead $lr8, implicit $rm, implicit-def $x3, implicit $x3
+    ; CHECK-NEXT: renamable $x3 = COPY $x17
+    ; CHECK-NEXT: BLR8 implicit $lr8, implicit undef $rm, implicit $x3
+    renamable $x18 = COPY renamable $x17
+    renamable $x17 = COPY renamable $x16
+    renamable $x16 = COPY renamable $x15
+    renamable $x15 = COPY renamable $x14
+    renamable $x14 = COPY renamable $x3
+    BL8_NOP @foo, csr_ppc64, implicit-def dead $lr8, implicit $rm, implicit-def $x3, implicit $x3
+    renamable $x3 = COPY renamable $x14
+    renamable $x14 = COPY renamable $x15
+    renamable $x15 = COPY renamable $x16
+    renamable $x16 = COPY renamable $x17
+    renamable $x17 = COPY renamable $x18
+    BLR8 implicit $lr8, implicit undef $rm, implicit $x3
+
+...
+
+# Two chains across regmask.
+---
+name: test6
+alignment: 4
+tracksRegLiveness: true
+body: |
+  bb.0.entry:
+    liveins: $x20, $x19, $x17, $x16, $x15, $x14, $x3, $x4
+    ; CHECK-LABEL: name: test6
+    ; CHECK: liveins: $x20, $x19, $x17, $x16, $x15, $x14, $x3, $x4
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $x21 = COPY renamable $x20
+    ; CHECK-NEXT: renamable $x18 = COPY renamable $x17
+    ; CHECK-NEXT: $x17 = COPY renamable $x3
+    ; CHECK-NEXT: $x20 = COPY renamable $x4
+    ; CHECK-NEXT: BL8_NOP @foo, csr_ppc64, implicit-def dead $lr8, implicit $rm, implicit-def $x3, implicit $x3, implicit-def $x4, implicit $x4
+    ; CHECK-NEXT: renamable $x3 = COPY $x17
+    ; CHECK-NEXT: renamable $x4 = COPY $x20
+    ; CHECK-NEXT: BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x4
+    renamable $x21 = COPY renamable $x20
+    renamable $x18 = COPY renamable $x17
+    renamable $x17 = COPY renamable $x16
+    renamable $x16 = COPY renamable $x15
+    renamable $x20 = COPY renamable $x19
+    renamable $x15 = COPY renamable $x14
+    renamable $x14 = COPY renamable $x3
+    renamable $x19 = COPY renamable $x4
+    BL8_NOP @foo, csr_ppc64, implicit-def dead $lr8, implicit $rm, implicit-def $x3, implicit $x3, implicit-def $x4, implicit $x4
+    renamable $x3 = COPY renamable $x14
+    renamable $x14 = COPY renamable $x15
+    renamable $x4 = COPY renamable $x19
+    renamable $x15 = COPY renamable $x16
+    renamable $x19 = COPY renamable $x20
+    renamable $x16 = COPY renamable $x17
+    renamable $x20 = COPY renamable $x21
+    renamable $x17 = COPY renamable $x18
+    BLR8 implicit $lr8, implicit undef $rm, implicit $x3, implicit $x4
+
+...
+