[llvm] [AArch64] ConditionOptimizer: replace per-block DenseMap with ScopedHashTable traversal (PR #196746)

Hussam Alhassan via llvm-commits llvm-commits at lists.llvm.org
Wed May 13 02:30:57 PDT 2026


https://github.com/hussam-alhassan updated https://github.com/llvm/llvm-project/pull/196746

>From 06b80fa4065c9c2b6d86356534a717d8d8c08158 Mon Sep 17 00:00:00 2001
From: Hussam Alhassan <hsm.link at proton.me>
Date: Mon, 13 Apr 2026 00:59:30 +0100
Subject: [PATCH 1/2] [AArch64] ConditionOptimizer: replace intra-block
 DenseMap with ScopedHashTable traversal

The intra-block path used a DenseMap cleared at each block boundary, so
pairs from dominating blocks were never visible to descendants. Replace
it and the separate cross-block path with a unified recursive domtree
walk using a ScopedHashTable. Any dominating block's pair is now a
candidate, not just pairs within the same block.

Rename optimizeIntraBlock to optimizeBlock and remove dead code
---
 .../AArch64/AArch64ConditionOptimizer.cpp     | 242 ++++--------------
 .../aarch64-condopt-cross-block-domtree.mir   | 118 +++++++++
 2 files changed, 167 insertions(+), 193 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-domtree.mir

diff --git a/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp b/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
index c5dcaa880ae07..9e110629e9ccd 100644
--- a/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
@@ -6,17 +6,28 @@
 //
 //===----------------------------------------------------------------------===//
 //
-//
 // This pass tries to make consecutive comparisons of values use the same
-// operands to allow the CSE pass to remove duplicate instructions. It adjusts
-// comparisons with immediate values by converting between inclusive and
-// exclusive forms (GE <-> GT, LE <-> LT) and correcting immediate values to
-// make them equal.
+// operands to allow the CSE pass to remove duplicate instructions.
+//
+// For two producer-consumer pairs P1 = (v, N1, CC1) and P2 = (v, N2, CC2) over
+// F where:
+// - v is a shared operand
+// - Nx are the immediates being compared such that 1 <= |N1 - N2| <= 2
+// - CCx are ordered conditions tested by the consumers (GT/LT/GE/LE)
+// - (Nx, CCx) are predicates over v
+// - F is a flag register, implicitly defined by the producers and tested by the
+// consumers
+// - No instruction between P1 and P2 reads or writes F
 //
-// The pass handles:
-//  * Cross-block: SUBS/ADDS followed by conditional branches
-//  * Intra-block: Select-family conditional instructions
+// Apply the following transformation:
+// 1. Find an immediate N' reachable from N1 and N2 via +-1 adjustment
+// 2. Derive CC1' and CC2' such that P1' = (v, N', CC1') and P2' = (v, N', CC2')
+// are
+//    semantically equivalent to the originals
+// 3. Modify the producers to compare against N'
+// 4. Modify the consumers to test CC1' and CC2', respectively
 //
+// This enables CSE to eliminate one of the compares.
 //
 // Consider the following example in C:
 //
@@ -51,12 +62,9 @@
 //     b.le     .LBB0_6
 //     ...
 //
-// See optimizeCrossBlock() and optimizeIntraBlock() for implementation details.
+// See optimizeBlock() for implementation details.
 //
 // TODO: maybe handle TBNZ/TBZ the same way as CMP when used instead for "a < 0"
-// TODO: For cross-block:
-//   - handle other conditional instructions (e.g. CSET)
-//   - allow second branching to be anything if it doesn't require adjusting
 //
 //===----------------------------------------------------------------------===//
 
@@ -64,9 +72,7 @@
 #include "AArch64Subtarget.h"
 #include "MCTargetDesc/AArch64AddressingModes.h"
 #include "Utils/AArch64BaseInfo.h"
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/DepthFirstIterator.h"
-#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/ScopedHashTable.h"
 #include "llvm/ADT/Statistic.h"
 #include "llvm/CodeGen/MachineBasicBlock.h"
 #include "llvm/CodeGen/MachineDominators.h"
@@ -105,37 +111,36 @@ class AArch64ConditionOptimizerImpl {
   /// Represents a comparison instruction paired with its consuming
   /// conditional instruction
   struct CmpCondPair {
-    MachineInstr *CmpMI;
-    MachineInstr *CondMI;
-    AArch64CC::CondCode CC;
+    MachineInstr *CmpMI = nullptr;
+    MachineInstr *CondMI = nullptr;
+    AArch64CC::CondCode CC = AArch64CC::Invalid;
 
     int getImm() const { return CmpMI->getOperand(2).getImm(); }
     unsigned getOpc() const { return CmpMI->getOpcode(); }
   };
 
+  using PairSHT = ScopedHashTable<Register, CmpCondPair>;
+
   const AArch64InstrInfo *TII;
   const TargetRegisterInfo *TRI;
   MachineDominatorTree *DomTree;
   const MachineRegisterInfo *MRI;
+  PairSHT BlockPairs;
 
 public:
   bool run(MachineFunction &MF, MachineDominatorTree &MDT);
 
 private:
   bool canAdjustCmp(MachineInstr &CmpMI);
-  bool registersMatch(MachineInstr *FirstMI, MachineInstr *SecondMI);
   bool nzcvLivesOut(MachineBasicBlock *MBB);
-  MachineInstr *getBccTerminator(MachineBasicBlock *MBB);
-  MachineInstr *findAdjustableCmp(MachineInstr *CondMI);
   CmpInfo getAdjustedCmpInfo(MachineInstr *CmpMI, AArch64CC::CondCode Cmp);
   void updateCmpInstr(MachineInstr *CmpMI, int NewImm, unsigned NewOpc);
   void updateCondInstr(MachineInstr *CondMI, AArch64CC::CondCode NewCC);
   void applyCmpAdjustment(CmpCondPair &Pair, const CmpInfo &Info);
-  bool commitPendingPair(std::optional<CmpCondPair> &PendingPair,
-                         SmallDenseMap<Register, CmpCondPair> &PairsByReg);
+  bool commitPendingPair(std::optional<CmpCondPair> &PendingPair);
   bool tryOptimizePair(CmpCondPair &First, CmpCondPair &Second);
-  bool optimizeIntraBlock(MachineBasicBlock &MBB);
-  bool optimizeCrossBlock(MachineBasicBlock &HBB);
+  bool optimizeBlock(MachineBasicBlock &MBB);
+  void visitBlock(MachineDomTreeNode *Node, bool &Changed);
 };
 
 class AArch64ConditionOptimizerLegacy : public MachineFunctionPass {
@@ -191,23 +196,6 @@ bool AArch64ConditionOptimizerImpl::canAdjustCmp(MachineInstr &CmpMI) {
   return true;
 }
 
-// Ensure both compare MIs use the same register, tracing through copies.
-bool AArch64ConditionOptimizerImpl::registersMatch(MachineInstr *FirstMI,
-                                                   MachineInstr *SecondMI) {
-  Register FirstReg = FirstMI->getOperand(1).getReg();
-  Register SecondReg = SecondMI->getOperand(1).getReg();
-  Register FirstCmpReg =
-      FirstReg.isVirtual() ? TRI->lookThruCopyLike(FirstReg, MRI) : FirstReg;
-  Register SecondCmpReg =
-      SecondReg.isVirtual() ? TRI->lookThruCopyLike(SecondReg, MRI) : SecondReg;
-  if (FirstCmpReg != SecondCmpReg) {
-    LLVM_DEBUG(dbgs() << "CMPs compare different registers\n");
-    return false;
-  }
-
-  return true;
-}
-
 // Check if NZCV lives out to any successor block.
 bool AArch64ConditionOptimizerImpl::nzcvLivesOut(MachineBasicBlock *MBB) {
   for (auto *SuccBB : MBB->successors()) {
@@ -236,61 +224,6 @@ static bool isCmpInstruction(unsigned Opc) {
   }
 }
 
-// Returns the Bcc terminator if present, otherwise nullptr.
-MachineInstr *
-AArch64ConditionOptimizerImpl::getBccTerminator(MachineBasicBlock *MBB) {
-  MachineBasicBlock::iterator Term = MBB->getFirstTerminator();
-  if (Term == MBB->end()) {
-    LLVM_DEBUG(dbgs() << "No terminator in " << printMBBReference(*MBB)
-                      << '\n');
-    return nullptr;
-  }
-
-  if (Term->getOpcode() != AArch64::Bcc) {
-    LLVM_DEBUG(dbgs() << "Non-Bcc terminator in " << printMBBReference(*MBB)
-                      << ": " << *Term);
-    return nullptr;
-  }
-
-  return &*Term;
-}
-
-// Find the CMP instruction controlling the given conditional instruction and
-// ensure it can be adjusted for CSE optimization. Searches backward from
-// CondMI, ensuring no NZCV interference. Returns nullptr if no suitable CMP
-// is found or if adjustments are not safe.
-MachineInstr *
-AArch64ConditionOptimizerImpl::findAdjustableCmp(MachineInstr *CondMI) {
-  assert(CondMI && "CondMI cannot be null");
-  MachineBasicBlock *MBB = CondMI->getParent();
-
-  // Search backward from the conditional to find the instruction controlling
-  // it.
-  for (MachineBasicBlock::iterator B = MBB->begin(),
-                                   It = MachineBasicBlock::iterator(CondMI);
-       It != B;) {
-    It = prev_nodbg(It, B);
-    MachineInstr &I = *It;
-    assert(!I.isTerminator() && "Spurious terminator");
-    // Ensure there is no use of NZCV between CMP and conditional.
-    if (I.readsRegister(AArch64::NZCV, /*TRI=*/nullptr))
-      return nullptr;
-
-    if (isCmpInstruction(I.getOpcode())) {
-      if (!canAdjustCmp(I)) {
-        return nullptr;
-      }
-      return &I;
-    }
-
-    if (I.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr))
-      return nullptr;
-  }
-  LLVM_DEBUG(dbgs() << "Flags not defined in " << printMBBReference(*MBB)
-                    << '\n');
-  return nullptr;
-}
-
 // Changes opcode adds <-> subs considering register operand width.
 static int getComplementOpc(int Opc) {
   switch (Opc) {
@@ -394,18 +327,6 @@ void AArch64ConditionOptimizerImpl::applyCmpAdjustment(CmpCondPair &Pair,
   Pair.CC = Info.CC;
 }
 
-// Extracts the condition code from the result of analyzeBranch.
-// Returns the CondCode or Invalid if the format is not a simple br.cond.
-static AArch64CC::CondCode parseCondCode(ArrayRef<MachineOperand> Cond) {
-  assert(!Cond.empty() && "Expected non-empty condition from analyzeBranch");
-  // A normal br.cond simply has the condition code.
-  if (Cond[0].getImm() != -1) {
-    assert(Cond.size() == 1 && "Unknown Cond array format");
-    return (AArch64CC::CondCode)(int)Cond[0].getImm();
-  }
-  return AArch64CC::CondCode::Invalid;
-}
-
 static bool isGreaterThan(AArch64CC::CondCode Cmp) {
   return Cmp == AArch64CC::GT || Cmp == AArch64CC::HI;
 }
@@ -507,19 +428,17 @@ bool AArch64ConditionOptimizerImpl::tryOptimizePair(CmpCondPair &First,
 }
 
 bool AArch64ConditionOptimizerImpl::commitPendingPair(
-    std::optional<CmpCondPair> &PendingPair,
-    SmallDenseMap<Register, CmpCondPair> &PairsByReg) {
+    std::optional<CmpCondPair> &PendingPair) {
   if (!PendingPair)
     return false;
 
   Register Reg = PendingPair->CmpMI->getOperand(1).getReg();
   Register Key = Reg.isVirtual() ? TRI->lookThruCopyLike(Reg, MRI) : Reg;
 
-  auto MatchingPair = PairsByReg.find(Key);
-  bool Changed = MatchingPair != PairsByReg.end() &&
-                 tryOptimizePair(MatchingPair->second, *PendingPair);
+  CmpCondPair Prior = BlockPairs.lookup(Key);
+  bool Changed = Prior.CmpMI && tryOptimizePair(Prior, *PendingPair);
 
-  PairsByReg[Key] = *PendingPair;
+  BlockPairs.insert(Key, *PendingPair);
   PendingPair = std::nullopt;
   return Changed;
 }
@@ -544,8 +463,7 @@ bool AArch64ConditionOptimizerImpl::commitPendingPair(
 //   cmp w8, #10              ; <- CSE can remove the redundant cmp
 //   csinc w10, w0, w1, ge    ; w10 = (w8 >= 10) ? w0 : w1+1
 //
-bool AArch64ConditionOptimizerImpl::optimizeIntraBlock(MachineBasicBlock &MBB) {
-  SmallDenseMap<Register, CmpCondPair> PairsByReg;
+bool AArch64ConditionOptimizerImpl::optimizeBlock(MachineBasicBlock &MBB) {
   std::optional<CmpCondPair> PendingPair;
   MachineInstr *ActiveCmp = nullptr;
   bool Changed = false;
@@ -555,22 +473,19 @@ bool AArch64ConditionOptimizerImpl::optimizeIntraBlock(MachineBasicBlock &MBB) {
       continue;
 
     if (isCmpInstruction(MI.getOpcode()) && canAdjustCmp(MI)) {
-      Changed |= commitPendingPair(PendingPair, PairsByReg);
+      Changed |= commitPendingPair(PendingPair);
       ActiveCmp = &MI;
       continue;
     }
 
     if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr)) {
-      // Non-CMP clobber: commit any pending pair and reset all state, since
-      // unknown flag state at this point invalidates all prior pairs
-      Changed |= commitPendingPair(PendingPair, PairsByReg);
+      // Non-CMP clobber: commit any pending pair and reset local state.
+      Changed |= commitPendingPair(PendingPair);
       ActiveCmp = nullptr;
-      PairsByReg.clear();
       continue;
     }
 
-    if (AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(MI) >= 0 &&
-        !MI.isBranch()) {
+    if (AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(MI) >= 0) {
       if (PendingPair) {
         // A second conditional consuming the same CMP would invalidate any
         // optimization: modifying the CMP would silently change what both
@@ -598,67 +513,11 @@ bool AArch64ConditionOptimizerImpl::optimizeIntraBlock(MachineBasicBlock &MBB) {
   // Only commit the final pending pair if NZCV doesn't live out: a cross-block
   // consumer would be affected by any CMP adjustment we make.
   if (!nzcvLivesOut(&MBB))
-    Changed |= commitPendingPair(PendingPair, PairsByReg);
+    Changed |= commitPendingPair(PendingPair);
 
   return Changed;
 }
 
-// Optimizes CMP+Bcc pairs across two basic blocks in the dominator tree.
-bool AArch64ConditionOptimizerImpl::optimizeCrossBlock(MachineBasicBlock &HBB) {
-  SmallVector<MachineOperand, 4> HeadCondOperands;
-  MachineBasicBlock *TBB = nullptr, *FBB = nullptr;
-  if (TII->analyzeBranch(HBB, TBB, FBB, HeadCondOperands)) {
-    return false;
-  }
-
-  // Equivalence check is to skip loops.
-  if (!TBB || TBB == &HBB) {
-    return false;
-  }
-
-  SmallVector<MachineOperand, 4> TrueCondOperands;
-  MachineBasicBlock *TBB_TBB = nullptr, *TBB_FBB = nullptr;
-  if (TII->analyzeBranch(*TBB, TBB_TBB, TBB_FBB, TrueCondOperands)) {
-    return false;
-  }
-
-  MachineInstr *HeadBrMI = getBccTerminator(&HBB);
-  MachineInstr *TrueBrMI = getBccTerminator(TBB);
-  if (!HeadBrMI || !TrueBrMI)
-    return false;
-
-  // Since we may modify cmps in these blocks, make sure NZCV does not live out.
-  if (nzcvLivesOut(&HBB) || nzcvLivesOut(TBB))
-    return false;
-
-  // Find the CMPs controlling each branch
-  MachineInstr *HeadCmpMI = findAdjustableCmp(HeadBrMI);
-  MachineInstr *TrueCmpMI = findAdjustableCmp(TrueBrMI);
-  if (!HeadCmpMI || !TrueCmpMI)
-    return false;
-
-  if (!registersMatch(HeadCmpMI, TrueCmpMI))
-    return false;
-
-  AArch64CC::CondCode HeadCondCode = parseCondCode(HeadCondOperands);
-  AArch64CC::CondCode TrueCondCode = parseCondCode(TrueCondOperands);
-  if (HeadCondCode == AArch64CC::CondCode::Invalid ||
-      TrueCondCode == AArch64CC::CondCode::Invalid) {
-    return false;
-  }
-
-  LLVM_DEBUG(dbgs() << "Checking cross-block pair: "
-                    << AArch64CC::getCondCodeName(HeadCondCode) << " #"
-                    << HeadCmpMI->getOperand(2).getImm() << ", "
-                    << AArch64CC::getCondCodeName(TrueCondCode) << " #"
-                    << TrueCmpMI->getOperand(2).getImm() << '\n');
-
-  CmpCondPair Head{HeadCmpMI, HeadBrMI, HeadCondCode};
-  CmpCondPair True{TrueCmpMI, TrueBrMI, TrueCondCode};
-
-  return tryOptimizePair(Head, True);
-}
-
 bool AArch64ConditionOptimizerLegacy::runOnMachineFunction(
     MachineFunction &MF) {
   if (skipFunction(MF.getFunction()))
@@ -668,6 +527,14 @@ bool AArch64ConditionOptimizerLegacy::runOnMachineFunction(
   return AArch64ConditionOptimizerImpl().run(MF, MDT);
 }
 
+void AArch64ConditionOptimizerImpl::visitBlock(MachineDomTreeNode *Node,
+                                               bool &Changed) {
+  ScopedHashTableScope<Register, CmpCondPair> Scope(BlockPairs);
+  Changed |= optimizeBlock(*Node->getBlock());
+  for (auto *Child : Node->children())
+    visitBlock(Child, Changed);
+}
+
 bool AArch64ConditionOptimizerImpl::run(MachineFunction &MF,
                                         MachineDominatorTree &MDT) {
   LLVM_DEBUG(dbgs() << "********** AArch64 Conditional Compares **********\n"
@@ -679,18 +546,7 @@ bool AArch64ConditionOptimizerImpl::run(MachineFunction &MF,
   MRI = &MF.getRegInfo();
 
   bool Changed = false;
-
-  // Visit blocks in dominator tree pre-order. The pre-order enables multiple
-  // cmp-conversions from the same head block.
-  // Note that updateDomTree() modifies the children of the DomTree node
-  // currently being visited. The df_iterator supports that; it doesn't look at
-  // child_begin() / child_end() until after a node has been visited.
-  for (MachineDomTreeNode *I : depth_first(DomTree)) {
-    MachineBasicBlock *HBB = I->getBlock();
-    Changed |= optimizeIntraBlock(*HBB);
-    Changed |= optimizeCrossBlock(*HBB);
-  }
-
+  visitBlock(DomTree->getRootNode(), Changed);
   return Changed;
 }
 
diff --git a/llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-domtree.mir b/llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-domtree.mir
new file mode 100644
index 0000000000000..65b54e8c46d94
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-domtree.mir
@@ -0,0 +1,118 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=aarch64-linux-gnu -run-pass=aarch64-condopt -verify-machineinstrs %s -o - | FileCheck %s
+# RUN: llc -mtriple=aarch64-linux-gnu -passes=aarch64-condopt %s -o - | FileCheck %s
+
+---
+# Non-adjacent dominator pair: bb.1 sits between bb.0 and bb.2 in the CFG, but
+# bb.0 dominates bb.2, so the SHT makes bb.0's pair visible when processing bb.2.
+#
+# Input:  bb.0: CMP #9 GT; bb.2: CMP #11 LT
+# Output: bb.0: CMP #10 GE; bb.2: CMP #10 LE
+name:            non_adjacent_dominator_pair
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gpr32sp }
+body:             |
+  ; CHECK-LABEL: name: non_adjacent_dominator_pair
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x40000000), %bb.2(0x40000000)
+  ; CHECK-NEXT:   liveins: $w0
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32sp = COPY $w0
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   Bcc 10, %bb.1, implicit $nzcv
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   successors: %bb.3(0x40000000), %bb.4(0x40000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   Bcc 13, %bb.3, implicit $nzcv
+  ; CHECK-NEXT:   B %bb.4
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 1
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.4:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0
+    successors: %bb.1, %bb.2
+
+    %0:gpr32sp = COPY $w0
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    Bcc 12, %bb.1, implicit $nzcv
+    B %bb.2
+
+  bb.1:
+    successors: %bb.2
+    B %bb.2
+
+  bb.2:
+    successors: %bb.3, %bb.4
+
+    dead $wzr = SUBSWri %0, 11, 0, implicit-def $nzcv
+    Bcc 11, %bb.3, implicit $nzcv
+    B %bb.4
+
+  bb.3:
+    $w0 = MOVi32imm 1
+    RET_ReallyLR implicit $w0
+
+  bb.4:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...
+---
+# Cross-block select-pair: bb.0 dominates bb.1, so the SHT makes bb.0's pair
+# visible when processing bb.1.
+#
+# Input:  bb.0: CMP #9 GT CSINC;  bb.1: CMP #11 LT CSINC
+# Output: bb.0: CMP #10 GE CSINC; bb.1: CMP #10 LE CSINC
+name:            cross_block_select_pair
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gpr32sp }
+  - { id: 1, class: gpr32 }
+  - { id: 2, class: gpr32 }
+  - { id: 3, class: gpr32 }
+body:             |
+  ; CHECK-LABEL: name: cross_block_select_pair
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32sp = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSINCWr:%[0-9]+]]:gpr32 = CSINCWr [[COPY1]], [[COPY2]], 10, implicit $nzcv
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSINCWr1:%[0-9]+]]:gpr32 = CSINCWr [[CSINCWr]], [[COPY2]], 13, implicit $nzcv
+  ; CHECK-NEXT:   $w0 = COPY [[CSINCWr1]]
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2
+    successors: %bb.1
+
+    %0:gpr32sp = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %3:gpr32 = CSINCWr %1, %2, 12, implicit $nzcv
+
+  bb.1:
+    dead $wzr = SUBSWri %0, 11, 0, implicit-def $nzcv
+    %4:gpr32 = CSINCWr %3, %2, 11, implicit $nzcv
+    $w0 = COPY %4
+    RET_ReallyLR implicit $w0
+...

>From b2139e40662927a94d5ec26ea6a26114262463dd Mon Sep 17 00:00:00 2001
From: Hussam Alhassan <hsm.link at proton.me>
Date: Wed, 13 May 2026 10:28:26 +0100
Subject: [PATCH 2/2] Fix misaligned comment

---
 llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp b/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
index 9e110629e9ccd..c59efedaab613 100644
--- a/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
@@ -22,8 +22,7 @@
 // Apply the following transformation:
 // 1. Find an immediate N' reachable from N1 and N2 via +-1 adjustment
 // 2. Derive CC1' and CC2' such that P1' = (v, N', CC1') and P2' = (v, N', CC2')
-// are
-//    semantically equivalent to the originals
+//    are semantically equivalent to the originals
 // 3. Modify the producers to compare against N'
 // 4. Modify the consumers to test CC1' and CC2', respectively
 //



More information about the llvm-commits mailing list