[llvm] [AArch64] Extend redundant copy elimination for reg-equal and GPR-zero copies (PR #203714)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 13 09:18:57 PDT 2026


https://github.com/mike-goutokuji created https://github.com/llvm/llvm-project/pull/203714

None

>From 471c63440773185bbc5445eff69a3bfe630bcbcf Mon Sep 17 00:00:00 2001
From: Mike-Goutokuji <gfunni234 at gmail.com>
Date: Sat, 13 Jun 2026 12:18:16 -0400
Subject: [PATCH] [AArch64] Extend redundant copy elimination for reg-equal and
 GPR-zero copies

---
 .../AArch64RedundantCopyElimination.cpp       | 251 +++++++++++++++---
 .../CodeGen/AArch64/machine-copy-remove.mir   | 101 +++++++
 2 files changed, 308 insertions(+), 44 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64RedundantCopyElimination.cpp b/llvm/lib/Target/AArch64/AArch64RedundantCopyElimination.cpp
index 414f5501bb18a..c538c90bda592 100644
--- a/llvm/lib/Target/AArch64/AArch64RedundantCopyElimination.cpp
+++ b/llvm/lib/Target/AArch64/AArch64RedundantCopyElimination.cpp
@@ -7,7 +7,7 @@
 // This pass removes unnecessary copies/moves in BBs based on a dominating
 // condition.
 //
-// We handle three cases:
+// We handle four cases:
 // 1. For BBs that are targets of CBZ/CBNZ instructions, we know the value of
 //    the CBZ/CBNZ source register is zero on the taken/not-taken path. For
 //    instance, the copy instruction in the code below can be removed because
@@ -40,6 +40,17 @@
 //  .LBB0_1:
 //    orr x0, xzr, #0x1  ; <-- redundant
 //
+// 4. If the flag setting instruction is a register comparison (i.e.,
+// SUBS[W|X]rr
+//    to WZR/XZR), we can remove a redundant copy between the compared registers
+//    on the EQ path.
+//
+//  %bb.0:
+//    subs xzr, x0, x1
+//    b.eq .LBB0_1
+//  .LBB0_1:
+//    mov x0, x1  ; <-- redundant
+//
 // This pass should be run after register allocation.
 //
 // FIXME: This could also be extended to check the whole dominance subtree below
@@ -66,6 +77,18 @@ using namespace llvm;
 STATISTIC(NumCopiesRemoved, "Number of copies removed.");
 
 namespace {
+struct RegImm {
+  MCPhysReg Reg;
+  int32_t Imm;
+  RegImm(MCPhysReg Reg, int32_t Imm) : Reg(Reg), Imm(Imm) {}
+};
+
+struct RegEqual {
+  MCPhysReg Reg1;
+  MCPhysReg Reg2;
+  RegEqual(MCPhysReg Reg1, MCPhysReg Reg2) : Reg1(Reg1), Reg2(Reg2) {}
+};
+
 class AArch64RedundantCopyEliminationImpl {
 public:
   bool run(MachineFunction &MF);
@@ -81,14 +104,9 @@ class AArch64RedundantCopyEliminationImpl {
   // OptBBClobberedRegs is used when optimizing away redundant copies/moves.
   LiveRegUnits OptBBClobberedRegs, OptBBUsedRegs;
 
-  struct RegImm {
-    MCPhysReg Reg;
-    int32_t Imm;
-    RegImm(MCPhysReg Reg, int32_t Imm) : Reg(Reg), Imm(Imm) {}
-  };
-
   bool knownRegValInBlock(MachineInstr &CondBr, MachineBasicBlock *MBB,
                           SmallVectorImpl<RegImm> &KnownRegs,
+                          SmallVectorImpl<RegEqual> &KnownEqualRegs,
                           MachineBasicBlock::iterator &FirstUse);
   bool optimizeBlock(MachineBasicBlock *MBB);
 };
@@ -113,6 +131,73 @@ char AArch64RedundantCopyEliminationLegacy::ID = 0;
 INITIALIZE_PASS(AArch64RedundantCopyEliminationLegacy, "aarch64-copyelim",
                 "AArch64 redundant copy elimination pass", false, false)
 
+static bool isCompareReg(const MachineInstr &MI, MCPhysReg &Rn, MCPhysReg &Rm) {
+  MCPhysReg DstReg;
+  switch (MI.getOpcode()) {
+  case AArch64::SUBSWrr:
+  case AArch64::SUBSXrr:
+    DstReg = MI.getOperand(0).getReg();
+    if (DstReg != AArch64::WZR && DstReg != AArch64::XZR)
+      return false;
+    Rn = MI.getOperand(1).getReg();
+    Rm = MI.getOperand(2).getReg();
+    return true;
+  default:
+    return false;
+  }
+}
+
+static bool isKnownZeroSource(Register SrcReg, ArrayRef<RegImm> KnownRegs) {
+  for (const RegImm &KnownReg : KnownRegs) {
+    if (KnownReg.Imm == 0 && KnownReg.Reg == SrcReg)
+      return true;
+  }
+  return false;
+}
+
+static bool isRedundantEqualRegAssign(const MachineInstr &MI,
+                                      ArrayRef<RegEqual> KnownEqualRegs) {
+  if (MI.getNumOperands() < 2 || !MI.getOperand(0).isReg() ||
+      !MI.getOperand(1).isReg())
+    return false;
+  MCPhysReg DefReg = MI.getOperand(0).getReg();
+  MCPhysReg SrcReg = MI.getOperand(1).getReg();
+  for (const RegEqual &Eq : KnownEqualRegs) {
+    if ((DefReg == Eq.Reg1 && SrcReg == Eq.Reg2) ||
+        (DefReg == Eq.Reg2 && SrcReg == Eq.Reg1))
+      return true;
+  }
+  return false;
+}
+
+static void propagateEqualRegThroughCopy(MCPhysReg CopyDstReg,
+                                         MCPhysReg CopySrcReg,
+                                         ArrayRef<RegEqual> KnownEqualRegs,
+                                         SmallVectorImpl<RegEqual> &Out) {
+  Out.push_back(RegEqual(CopyDstReg, CopySrcReg));
+  for (const RegEqual &Eq : KnownEqualRegs) {
+    if (CopySrcReg == Eq.Reg1)
+      Out.push_back(RegEqual(CopyDstReg, Eq.Reg2));
+    else if (CopySrcReg == Eq.Reg2)
+      Out.push_back(RegEqual(CopyDstReg, Eq.Reg1));
+    else if (CopyDstReg == Eq.Reg1)
+      Out.push_back(RegEqual(CopySrcReg, Eq.Reg2));
+    else if (CopyDstReg == Eq.Reg2)
+      Out.push_back(RegEqual(CopySrcReg, Eq.Reg1));
+  }
+}
+
+static bool shouldStopPredScan(ArrayRef<RegImm> KnownRegs,
+                               ArrayRef<RegEqual> KnownEqualRegs,
+                               const LiveRegUnits &Clobbered) {
+  bool AnyKnownReg = any_of(
+      KnownRegs, [&](const RegImm &K) { return Clobbered.available(K.Reg); });
+  bool AnyKnownEqual = any_of(KnownEqualRegs, [&](const RegEqual &Eq) {
+    return Clobbered.available(Eq.Reg1) && Clobbered.available(Eq.Reg2);
+  });
+  return !AnyKnownReg && !AnyKnownEqual;
+}
+
 /// It's possible to determine the value of a register based on a dominating
 /// condition.  To do so, this function checks to see if the basic block \p MBB
 /// is the target of a conditional branch \p CondBr with an equality comparison.
@@ -122,12 +207,16 @@ INITIALIZE_PASS(AArch64RedundantCopyEliminationLegacy, "aarch64-copyelim",
 /// other than WZR/XZR, we know the value of the destination register is zero in
 /// \p MMB for some cases.  In addition, if the NZCV setting instruction is
 /// comparing against a constant we know the other source register is equal to
-/// the constant in \p MBB for some cases.  If we find any constant values, push
-/// a physical register and constant value pair onto the KnownRegs vector and
-/// return true.  Otherwise, return false if no known values were found.
+/// the constant in \p MBB for some cases.  If the NZCV setting instruction is a
+/// register comparison, we know the compared registers are equal in \p MBB for
+/// some cases.  If we find any known values, push them onto the KnownRegs or
+/// KnownEqualRegs vectors and return true.  Otherwise, return false if no known
+/// values were found.
 bool AArch64RedundantCopyEliminationImpl::knownRegValInBlock(
     MachineInstr &CondBr, MachineBasicBlock *MBB,
-    SmallVectorImpl<RegImm> &KnownRegs, MachineBasicBlock::iterator &FirstUse) {
+    SmallVectorImpl<RegImm> &KnownRegs,
+    SmallVectorImpl<RegEqual> &KnownEqualRegs,
+    MachineBasicBlock::iterator &FirstUse) {
   unsigned Opc = CondBr.getOpcode();
 
   // Check if the current basic block is the target block to which the
@@ -170,6 +259,19 @@ bool AArch64RedundantCopyEliminationImpl::knownRegValInBlock(
   // Find compare instruction that sets NZCV used by CondBr.
   MachineBasicBlock::reverse_iterator RIt = CondBr.getReverseIterator();
   for (MachineInstr &PredI : make_range(std::next(RIt), PredMBB->rend())) {
+    MCPhysReg Rn;
+    MCPhysReg Rm;
+    if (isCompareReg(PredI, Rn, Rm)) {
+      // If both source registers of the compare are not modified between the
+      // compare and conditional branch we know they are equal.
+      if (DomBBClobberedRegs.available(Rn) &&
+          DomBBClobberedRegs.available(Rm)) {
+        FirstUse = PredI;
+        KnownEqualRegs.push_back(RegEqual(Rn, Rm));
+        return true;
+      }
+      return false;
+    }
 
     bool IsCMN = false;
     switch (PredI.getOpcode()) {
@@ -307,12 +409,13 @@ bool AArch64RedundantCopyEliminationImpl::optimizeBlock(
   bool SeenFirstUse = false;
   // Registers that contain a known value at the start of MBB.
   SmallVector<RegImm, 4> KnownRegs;
+  SmallVector<RegEqual, 2> KnownEqualRegs;
 
   MachineBasicBlock::iterator Itr = std::next(CondBr);
   do {
     --Itr;
 
-    if (!knownRegValInBlock(*Itr, MBB, KnownRegs, FirstUse))
+    if (!knownRegValInBlock(*Itr, MBB, KnownRegs, KnownEqualRegs, FirstUse))
       continue;
 
     // Reset the clobbered and used register units.
@@ -350,6 +453,17 @@ bool AArch64RedundantCopyEliminationImpl::optimizeBlock(
             break;
           }
         }
+        SmallVector<RegEqual, 2> NewEqual;
+        if (OptBBClobberedRegs.available(CopyDstReg) &&
+            OptBBClobberedRegs.available(CopySrcReg)) {
+          propagateEqualRegThroughCopy(CopyDstReg, CopySrcReg, KnownEqualRegs,
+                                       NewEqual);
+        }
+        if (!NewEqual.empty()) {
+          KnownEqualRegs.append(NewEqual.begin(), NewEqual.end());
+          if (SeenFirstUse)
+            FirstUse = PredI;
+        }
       }
 
       // Stop if we get to the beginning of PredMBB.
@@ -358,18 +472,16 @@ bool AArch64RedundantCopyEliminationImpl::optimizeBlock(
 
       LiveRegUnits::accumulateUsedDefed(*PredI, OptBBClobberedRegs,
                                         OptBBUsedRegs, TRI);
-      // Stop if all of the known-zero regs have been clobbered.
-      if (all_of(KnownRegs, [&](RegImm KnownReg) {
-            return !OptBBClobberedRegs.available(KnownReg.Reg);
-          }))
+      // Stop if all of the known regs have been clobbered.
+      if (shouldStopPredScan(KnownRegs, KnownEqualRegs, OptBBClobberedRegs))
         break;
     }
     break;
 
   } while (Itr != PredMBB->begin() && Itr->isTerminator());
 
-  // We've not found a registers with a known value, time to bail out.
-  if (KnownRegs.empty())
+  // We've not found registers with a known value, time to bail out.
+  if (KnownRegs.empty() && KnownEqualRegs.empty())
     return false;
 
   bool Changed = false;
@@ -383,23 +495,65 @@ bool AArch64RedundantCopyEliminationImpl::optimizeBlock(
     bool RemovedMI = false;
     bool IsCopy = MI->isCopy();
     bool IsMoveImm = MI->isMoveImmediate();
+    Register SrcReg = IsCopy ? MI->getOperand(1).getReg() : Register();
+    int64_t SrcImm = IsMoveImm ? MI->getOperand(1).getImm() : 0;
     if (IsCopy || IsMoveImm) {
       Register DefReg = MI->getOperand(0).getReg();
-      Register SrcReg = IsCopy ? MI->getOperand(1).getReg() : Register();
-      int64_t SrcImm = IsMoveImm ? MI->getOperand(1).getImm() : 0;
-      if (!MRI->isReserved(DefReg) &&
-          ((IsCopy && (SrcReg == AArch64::XZR || SrcReg == AArch64::WZR)) ||
-           IsMoveImm)) {
-        for (RegImm &KnownReg : KnownRegs) {
-          if (KnownReg.Reg != DefReg &&
-              !TRI->isSuperRegister(DefReg, KnownReg.Reg))
+      if (!MRI->isReserved(DefReg)) {
+        if (IsCopy && !KnownEqualRegs.empty() &&
+            isRedundantEqualRegAssign(*MI, KnownEqualRegs)) {
+          // Don't remove an instruction that has other live defs.
+          if (any_of(MI->operands(), [DefReg](MachineOperand &O) {
+                return O.isReg() && O.isDef() && !O.isDead() &&
+                       O.getReg() != DefReg;
+              }))
             continue;
 
-          // For a copy, the known value must be a zero.
-          if (IsCopy && KnownReg.Imm != 0)
-            continue;
+          for (const RegEqual &Eq : KnownEqualRegs) {
+            if (MI->getOperand(0).getReg() == Eq.Reg1 ||
+                MI->getOperand(0).getReg() == Eq.Reg2 ||
+                MI->getOperand(1).getReg() == Eq.Reg1 ||
+                MI->getOperand(1).getReg() == Eq.Reg2) {
+              UsedKnownRegs.insert(Eq.Reg1);
+              UsedKnownRegs.insert(Eq.Reg2);
+            }
+          }
+          LLVM_DEBUG(dbgs() << "Remove redundant equal-reg copy: " << *MI);
+          MI->eraseFromParent();
+          Changed = true;
+          LastChange = I;
+          NumCopiesRemoved++;
+          RemovedMI = true;
+        }
+
+        if (!RemovedMI && IsCopy) {
+          bool SrcIsZero = (SrcReg == AArch64::XZR || SrcReg == AArch64::WZR ||
+                            isKnownZeroSource(SrcReg, KnownRegs));
+          if (SrcIsZero) {
+            for (RegImm &KnownReg : KnownRegs) {
+              if (KnownReg.Imm != 0)
+                continue;
+              if (KnownReg.Reg != DefReg &&
+                  !TRI->isSuperRegister(DefReg, KnownReg.Reg))
+                continue;
+              LLVM_DEBUG(dbgs() << "Remove redundant Copy : " << *MI);
+              MI->eraseFromParent();
+              Changed = true;
+              LastChange = I;
+              NumCopiesRemoved++;
+              UsedKnownRegs.insert(KnownReg.Reg);
+              RemovedMI = true;
+              break;
+            }
+          }
+        }
+
+        if (!RemovedMI && IsMoveImm) {
+          for (RegImm &KnownReg : KnownRegs) {
+            if (KnownReg.Reg != DefReg &&
+                !TRI->isSuperRegister(DefReg, KnownReg.Reg))
+              continue;
 
-          if (IsMoveImm) {
             // For a move immediate, the known immediate must match the source
             // immediate.
             if (KnownReg.Imm != SrcImm)
@@ -418,20 +572,17 @@ bool AArch64RedundantCopyEliminationImpl::optimizeBlock(
             // bits as different.
             if (TRI->isSuperRegister(DefReg, KnownReg.Reg) && KnownReg.Imm < 0)
               continue;
-          }
 
-          if (IsCopy)
-            LLVM_DEBUG(dbgs() << "Remove redundant Copy : " << *MI);
-          else
             LLVM_DEBUG(dbgs() << "Remove redundant Move : " << *MI);
 
-          MI->eraseFromParent();
-          Changed = true;
-          LastChange = I;
-          NumCopiesRemoved++;
-          UsedKnownRegs.insert(KnownReg.Reg);
-          RemovedMI = true;
-          break;
+            MI->eraseFromParent();
+            Changed = true;
+            LastChange = I;
+            NumCopiesRemoved++;
+            UsedKnownRegs.insert(KnownReg.Reg);
+            RemovedMI = true;
+            break;
+          }
         }
       }
     }
@@ -440,7 +591,7 @@ bool AArch64RedundantCopyEliminationImpl::optimizeBlock(
     if (RemovedMI)
       continue;
 
-    // Remove any regs the MI clobbers from the KnownConstRegs set.
+    // Remove any regs the MI clobbers from the KnownRegs set.
     for (unsigned RI = 0; RI < KnownRegs.size();)
       if (MI->modifiesRegister(KnownRegs[RI].Reg, TRI)) {
         std::swap(KnownRegs[RI], KnownRegs[KnownRegs.size() - 1]);
@@ -451,8 +602,20 @@ bool AArch64RedundantCopyEliminationImpl::optimizeBlock(
         ++RI;
       }
 
-    // Continue until the KnownRegs set is empty.
-    if (KnownRegs.empty())
+    // Remove any equal-reg pairs the MI clobbers from the KnownEqualRegs set.
+    for (unsigned RI = 0; RI < KnownEqualRegs.size();) {
+      if (MI->modifiesRegister(KnownEqualRegs[RI].Reg1, TRI) ||
+          MI->modifiesRegister(KnownEqualRegs[RI].Reg2, TRI)) {
+        std::swap(KnownEqualRegs[RI],
+                  KnownEqualRegs[KnownEqualRegs.size() - 1]);
+        KnownEqualRegs.pop_back();
+      } else {
+        ++RI;
+      }
+    }
+
+    // Continue until the KnownRegs and KnownEqualRegs sets are empty.
+    if (KnownRegs.empty() && KnownEqualRegs.empty())
       break;
   }
 
diff --git a/llvm/test/CodeGen/AArch64/machine-copy-remove.mir b/llvm/test/CodeGen/AArch64/machine-copy-remove.mir
index 731f86af102b5..263d41be59c88 100644
--- a/llvm/test/CodeGen/AArch64/machine-copy-remove.mir
+++ b/llvm/test/CodeGen/AArch64/machine-copy-remove.mir
@@ -609,3 +609,104 @@ body:             |
 
   bb.2:
     RET_ReallyLR
+...
+# CHECK-LABEL: name: cmp_rr
+# CHECK: SUBSWrr
+# CHECK: bb.1:
+# CHECK-NOT: COPY
+---
+name:            cmp_rr
+tracksRegLiveness: true
+body:             |
+  bb.0.entry:
+    liveins: $w0, $w1, $x2
+
+    dead $wzr = SUBSWrr $w0, $w1, implicit-def $nzcv
+    Bcc 0, %bb.1, implicit killed $nzcv
+    B %bb.2
+
+  bb.1:
+    liveins: $w1, $x2
+
+    $w0 = COPY $w1
+    STRWui killed $w0, killed $x2, 0
+
+  bb.2:
+    RET_ReallyLR
+
+...
+# CHECK-LABEL: name: cmp_copy_prop
+# CHECK: SUBSWrr
+# CHECK: bb.1:
+# CHECK-NOT: COPY
+---
+name:            cmp_copy_prop
+tracksRegLiveness: true
+body:             |
+  bb.0.entry:
+    liveins: $w0, $w1, $x2
+
+    $w2 = COPY $w0
+    dead $wzr = SUBSWrr $w0, $w1, implicit-def $nzcv
+    Bcc 0, %bb.1, implicit killed $nzcv
+    B %bb.2
+
+  bb.1:
+    liveins: $w1, $x2
+
+    $w0 = COPY $w2
+    STRWui killed $w0, killed $x2, 0
+
+  bb.2:
+    RET_ReallyLR
+
+...
+# CHECK-LABEL: name: gpr_zero_copy
+# CHECK: CBNZW
+# CHECK: bb.1:
+# CHECK-NOT: COPY
+---
+name:            gpr_zero_copy
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $w0, $w1, $x2
+
+    $w1 = COPY $wzr
+    CBNZW $w1, %bb.2
+
+  bb.1:
+    liveins: $w1, $x2
+
+    $w0 = COPY $w1
+    STRWui killed $w0, killed $x2, 0
+
+  bb.2:
+    RET_ReallyLR
+
+...
+# CHECK-LABEL: name: cmp_rr_clobber
+# CHECK: SUBSWrr
+# CHECK: bb.1:
+# CHECK: COPY
+---
+name:            cmp_rr_clobber
+tracksRegLiveness: true
+body:             |
+  bb.0.entry:
+    liveins: $w0, $w1, $x2
+
+    dead $wzr = SUBSWrr $w0, $w1, implicit-def $nzcv
+    $w0 = LDRWui $x2, 0
+    STRWui killed $w0, killed $x2, 0
+    Bcc 0, %bb.1, implicit killed $nzcv
+    B %bb.2
+
+  bb.1:
+    liveins: $w1, $x2
+
+    $w0 = COPY $w1
+    STRWui killed $w0, killed $x2, 4
+
+  bb.2:
+    RET_ReallyLR



More information about the llvm-commits mailing list