[llvm] [AArch64] Extend optimizeCrossBlock() to handle select-family instruc… (PR #208369)

Mugundan S via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 18 20:13:40 PDT 2026


https://github.com/MGN-GIT updated https://github.com/llvm/llvm-project/pull/208369

>From f6f326b572b50a33aa6ea08daa8dfc7f9990d453 Mon Sep 17 00:00:00 2001
From: Greenie0701 <smugundan12a at gmail.com>
Date: Thu, 9 Jul 2026 08:38:57 +0530
Subject: [PATCH] [AArch64] Extend optimizeCrossBlock() to handle select-family
 instructions

---
 .../AArch64/AArch64ConditionOptimizer.cpp     | 154 ++++-
 .../aarch64-condopt-cross-block-select.mir    | 567 ++++++++++++++++++
 2 files changed, 700 insertions(+), 21 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-select.mir

diff --git a/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp b/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
index c5dcaa880ae07..2ef5ca4d9a9b3 100644
--- a/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ConditionOptimizer.cpp
@@ -55,9 +55,15 @@
 //
 // 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
 //
+// Cross-block optimizeCrossBlock() handles four head/true-successor
+// combinations:
+//   Bcc    (head) + Bcc    (true) -- original case
+//   Select (head) + Bcc    (true) -- head ends with CSEL/CSET/etc.
+//   Bcc    (head) + Select (true) -- true-successor ends with CSEL/CSET/etc.
+//   Select (head) + Select (true) -- both blocks end with a select
+//
 //===----------------------------------------------------------------------===//
 
 #include "AArch64.h"
@@ -136,6 +142,8 @@ class AArch64ConditionOptimizerImpl {
   bool tryOptimizePair(CmpCondPair &First, CmpCondPair &Second);
   bool optimizeIntraBlock(MachineBasicBlock &MBB);
   bool optimizeCrossBlock(MachineBasicBlock &HBB);
+  std::pair<MachineInstr *, AArch64CC::CondCode>
+  findSelectConsumer(MachineBasicBlock *MBB);
 };
 
 class AArch64ConditionOptimizerLegacy : public MachineFunctionPass {
@@ -603,7 +611,75 @@ bool AArch64ConditionOptimizerImpl::optimizeIntraBlock(MachineBasicBlock &MBB) {
   return Changed;
 }
 
-// Optimizes CMP+Bcc pairs across two basic blocks in the dominator tree.
+// Finds the last non-branch NZCV-consuming instruction (CSET, CSEL, CSINC,
+// CSINV, CSNEG) before the terminator region of MBB, provided it is the
+// sole consumer of NZCV in that region. Returns {nullptr, Invalid} if:
+//   - no such instruction exists,
+//   - any NZCV interference (read or write) is found between the instruction
+//     and the start of the terminator region, or
+//   - a second NZCV reader exists between the found instruction and the block
+//     entry, which would mean the CMP is shared and cannot be safely adjusted.
+std::pair<MachineInstr *, AArch64CC::CondCode>
+AArch64ConditionOptimizerImpl::findSelectConsumer(MachineBasicBlock *MBB) {
+  MachineInstr *Found = nullptr;
+  AArch64CC::CondCode FoundCC = AArch64CC::Invalid;
+
+  for (MachineInstr &MI : reverse(*MBB)) {
+    // Skip terminators (e.g. an unconditional branch at the end of the block)
+    // and debug instructions, which carry no real semantics.
+    if (MI.isTerminator() || MI.isDebugInstr())
+      continue;
+
+    if (!Found) {
+      // We have not yet found the select. Keep scanning backward.
+
+      // If something writes NZCV before we find a select, the flags at that
+      // point are not from the CMP we are looking for. Stop searching.
+      if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr))
+        return {nullptr, AArch64CC::Invalid};
+
+      // findCondCodeUseOperandIdxForBranchOrSelect returns the operand index
+      // of the condition code for any branch or select-family instruction, or
+      // -1 if the instruction does not use a condition code.
+      // We exclude branches because getBccTerminator already handles those;
+      // we only want non-branch conditionals: CSET, CSEL, CSINC, CSINV, CSNEG.
+      int CCOpIdx =
+          AArch64InstrInfo::findCondCodeUseOperandIdxForBranchOrSelect(MI);
+      if (CCOpIdx >= 0 && !MI.isBranch()) {
+        Found = &MI;
+        FoundCC = (AArch64CC::CondCode)(int)MI.getOperand(CCOpIdx).getImm();
+        continue;
+      }
+
+      // Any other instruction that reads NZCV (but is not a select) means the
+      // flags are consumed by something we do not understand. Stop searching.
+      if (MI.readsRegister(AArch64::NZCV, /*TRI=*/nullptr))
+        return {nullptr, AArch64CC::Invalid};
+
+    } else {
+      // We already found a select. Now verify there is no second NZCV reader
+      // between the found select and the CMP. If there is, the CMP feeds two
+      // consumers and cannot be safely adjusted.
+      if (MI.readsRegister(AArch64::NZCV, /*TRI=*/nullptr))
+        return {nullptr, AArch64CC::Invalid};
+
+      if (MI.modifiesRegister(AArch64::NZCV, /*TRI=*/nullptr)) {
+        // A CMP instruction is the flag producer we are looking for; stop
+        // scanning. findAdjustableCmp will locate it from CondMI.
+        if (isCmpInstruction(MI.getOpcode()))
+          break;
+        // Any other NZCV writer means the select is not reading from the CMP
+        // we would find further back.
+        return {nullptr, AArch64CC::Invalid};
+      }
+    }
+  }
+  return {Found, FoundCC};
+}
+
+// Optimizes CMP+conditional pairs across two basic blocks in the dominator
+// tree. The conditional consumer in each block may be a Bcc terminator or a
+// select-family instruction (CSEL/CSET/CSINC/CSINV/CSNEG).
 bool AArch64ConditionOptimizerImpl::optimizeCrossBlock(MachineBasicBlock &HBB) {
   SmallVector<MachineOperand, 4> HeadCondOperands;
   MachineBasicBlock *TBB = nullptr, *FBB = nullptr;
@@ -617,44 +693,80 @@ bool AArch64ConditionOptimizerImpl::optimizeCrossBlock(MachineBasicBlock &HBB) {
   }
 
   SmallVector<MachineOperand, 4> TrueCondOperands;
-  MachineBasicBlock *TBB_TBB = nullptr, *TBB_FBB = nullptr;
-  if (TII->analyzeBranch(*TBB, TBB_TBB, TBB_FBB, TrueCondOperands)) {
-    return false;
+  // analyzeBranch may fail for a select-headed true-successor (no conditional
+  // branch). TrueCondOperands is only needed when TBB ends with a Bcc, and in
+  // that case analyzeBranch will succeed. When it fails we fall through to
+  // findSelectConsumer below. The TBB branch targets are not needed here.
+  MachineBasicBlock *TBBDest = nullptr, *TBBFallthrough = nullptr;
+  bool TBBAnalyzed =
+      !TII->analyzeBranch(*TBB, TBBDest, TBBFallthrough, TrueCondOperands);
+
+  // Find the conditional consumer in the head block.
+  // First try a Bcc terminator (the original path). If the block ends with a
+  // select-family instruction (CSET/CSEL/CSINC/CSINV/CSNEG) instead of a
+  // branch, fall back to findSelectConsumer.
+  MachineInstr *HeadCondMI = getBccTerminator(&HBB);
+  AArch64CC::CondCode HeadCondCode;
+  if (HeadCondMI) {
+    // Block ends with Bcc: extract the condition code from analyzeBranch output.
+    HeadCondCode = parseCondCode(HeadCondOperands);
+    if (HeadCondCode == AArch64CC::CondCode::Invalid)
+      return false;
+  } else {
+    // Block does not end with Bcc: look for a CSET/CSEL/CSINC/CSINV/CSNEG
+    // near the end of the block that reads the flags set by the CMP.
+    auto [SelectMI, SelectCC] = findSelectConsumer(&HBB);
+    if (!SelectMI)
+      return false;
+    HeadCondMI = SelectMI;
+    HeadCondCode = SelectCC;
   }
 
-  MachineInstr *HeadBrMI = getBccTerminator(&HBB);
-  MachineInstr *TrueBrMI = getBccTerminator(TBB);
-  if (!HeadBrMI || !TrueBrMI)
-    return false;
+  // Find the conditional consumer in the true-successor block.
+  // Mirror the same fallback logic used for the head block above:
+  // try a Bcc terminator first, then fall back to a select-family instruction
+  // (CSET/CSEL/CSINC/CSINV/CSNEG) if the block does not end with a branch.
+  MachineInstr *TrueCondMI = getBccTerminator(TBB);
+  AArch64CC::CondCode TrueCondCode;
+  if (TrueCondMI) {
+    // Block ends with Bcc: analyzeBranch must have succeeded to give us
+    // TrueCondOperands.
+    if (!TBBAnalyzed)
+      return false;
+    TrueCondCode = parseCondCode(TrueCondOperands);
+    if (TrueCondCode == AArch64CC::CondCode::Invalid)
+      return false;
+  } else {
+    // Block does not end with Bcc: look for a CSET/CSEL/CSINC/CSINV/CSNEG
+    // near the end of the block that reads the flags set by the CMP.
+    auto [SelectMI, SelectCC] = findSelectConsumer(TBB);
+    if (!SelectMI)
+      return false;
+    TrueCondMI = SelectMI;
+    TrueCondCode = SelectCC;
+  }
 
   // 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);
+  // Find the CMPs controlling each conditional.
+  MachineInstr *HeadCmpMI = findAdjustableCmp(HeadCondMI);
+  MachineInstr *TrueCmpMI = findAdjustableCmp(TrueCondMI);
   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};
+  CmpCondPair Head{HeadCmpMI, HeadCondMI, HeadCondCode};
+  CmpCondPair True{TrueCmpMI, TrueCondMI, TrueCondCode};
 
   return tryOptimizePair(Head, True);
 }
diff --git a/llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-select.mir b/llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-select.mir
new file mode 100644
index 0000000000000..f1b78218d0de3
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/aarch64-condopt-cross-block-select.mir
@@ -0,0 +1,567 @@
+# 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
+
+# Tests for the cross-block select-family extensions to optimizeCrossBlock().
+# The pass handles four head/true-successor combinations:
+#
+#   Bcc    (head) + Bcc    (true) -- original case, tested in aarch64-condopt*.mir
+#   Select (head) + Bcc    (true) -- head ends with CSEL/CSET/CSINC/CSINV/CSNEG
+#   Bcc    (head) + Select (true) -- true-successor ends with a select
+#   Select (head) + Select (true) -- both blocks end with a select
+#
+# Condition code integer encoding used throughout:
+#   GT = 12,  GE = 10,  LT = 11,  LE = 13
+#
+# Note: CSET lowers to CSINCWr $rd, $wzr, $wzr, invert(cond) in MIR.
+# The $wzr-source form is not optimized by this pass (physical register
+# liveness is not tracked the same way); CSINC with virtual register
+# sources (select_head_select_true_csinc) covers the opcode dispatch path.
+
+---
+# Bcc (head) + Select (true): positive test.
+#
+# bb.0 ends with a Bcc (GT, #9).  bb.1 ends with a CSELWr (GT, #10) followed
+# by an unconditional branch.  The head CMP is adjusted from #9 GT to #10 GE
+# so that both CMPs become identical and CSE can remove the duplicate.
+#
+# Input:  bb.0: CMP #9  GT Bcc   -> bb.1
+#         bb.1: CMP #10 GT CSELWr
+# Output: bb.0: CMP #10 GE Bcc   -> bb.1
+#         bb.1: CMP #10 GT CSELWr   (unchanged)
+name:            bcc_head_select_true
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: bcc_head_select_true
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x40000000), %bb.2(0x40000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $x3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr64common = COPY $x3
+  ; 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:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr]], [[COPY3]], 0
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $x3
+    successors: %bb.1, %bb.2
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr64common = COPY $x3
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    Bcc 12, %bb.1, implicit $nzcv
+    B %bb.2
+
+  bb.1:
+    successors: %bb.2
+
+    dead $wzr = SUBSWri %0, 10, 0, implicit-def $nzcv
+    %4:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %4, %3, 0
+    B %bb.2
+
+  bb.2:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...
+
+---
+# Select (head) + Bcc (true): positive test.
+#
+# bb.0 ends with a CSELWr (GT, #9) followed by an unconditional branch to
+# bb.1.  bb.1 ends with a Bcc (GT, #10).  The head CMP is adjusted from
+# #9 GT to #10 GE so that both CMPs become identical.
+#
+# Input:  bb.0: CMP #9  GT CSELWr  -> bb.1 (unconditional)
+#         bb.1: CMP #10 GT Bcc
+# Output: bb.0: CMP #10 GE CSELWr  (adjusted)
+#         bb.1: CMP #10 GT Bcc      (unchanged)
+name:            select_head_bcc_true
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: select_head_bcc_true
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $x3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr64common = COPY $x3
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 10, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr]], [[COPY3]], 0
+  ; CHECK-NEXT:   B %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x40000000), %bb.3(0x40000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   Bcc 12, %bb.2, implicit $nzcv
+  ; CHECK-NEXT:   B %bb.3
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 1
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.3:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $x3
+    successors: %bb.1
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr64common = COPY $x3
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %4:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %4, %3, 0
+    B %bb.1
+
+  bb.1:
+    successors: %bb.2, %bb.3
+
+    dead $wzr = SUBSWri %0, 10, 0, implicit-def $nzcv
+    Bcc 12, %bb.2, implicit $nzcv
+    B %bb.3
+
+  bb.2:
+    $w0 = MOVi32imm 1
+    RET_ReallyLR implicit $w0
+
+  bb.3:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...
+
+---
+# Select (head) + Select (true): positive test.
+#
+# Both bb.0 and bb.1 end with a CSELWr followed by an unconditional branch.
+# bb.0: CMP #9 GT CSELWr -> bb.1 (unconditional)
+# bb.1: CMP #10 GT CSELWr -> bb.2 (unconditional)
+# The head CMP is adjusted from #9 GT to #10 GE.
+#
+# Input:  bb.0: CMP #9  GT CSELWr
+#         bb.1: CMP #10 GT CSELWr
+# Output: bb.0: CMP #10 GE CSELWr  (adjusted)
+#         bb.1: CMP #10 GT CSELWr  (unchanged)
+name:            select_head_select_true
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: select_head_select_true
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $x3, $x4
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr64common = COPY $x3
+  ; CHECK-NEXT:   [[COPY4:%[0-9]+]]:gpr64common = COPY $x4
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 10, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr]], [[COPY3]], 0
+  ; CHECK-NEXT:   B %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr1:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr1]], [[COPY4]], 0
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $x3, $x4
+    successors: %bb.1
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr64common = COPY $x3
+    %4:gpr64common = COPY $x4
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %5:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %5, %3, 0
+    B %bb.1
+
+  bb.1:
+    successors: %bb.2
+
+    dead $wzr = SUBSWri %0, 10, 0, implicit-def $nzcv
+    %6:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %6, %4, 0
+    B %bb.2
+
+  bb.2:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...
+
+---
+# Negative test: Select (head) + Select (true), NZCV lives out of true-successor.
+#
+# bb.1 has a successor (bb.2) that declares $nzcv as a live-in.  The pass must
+# NOT optimise this case because adjusting the CMP in bb.1 would silently
+# change the flags seen by bb.2's consumer.
+#
+# Input and output are identical (no transformation expected).
+name:            select_select_nzcv_lives_out
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: select_select_nzcv_lives_out
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $x3, $x4
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr64common = COPY $x3
+  ; CHECK-NEXT:   [[COPY4:%[0-9]+]]:gpr64common = COPY $x4
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 9, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr]], [[COPY3]], 0
+  ; CHECK-NEXT:   B %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr1:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr1]], [[COPY4]], 0
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   liveins: $nzcv
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[CSELWr2:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   $w0 = COPY [[CSELWr2]]
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $x3, $x4
+    successors: %bb.1
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr64common = COPY $x3
+    %4:gpr64common = COPY $x4
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %5:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %5, %3, 0
+    B %bb.1
+
+  bb.1:
+    successors: %bb.2
+
+    dead $wzr = SUBSWri %0, 10, 0, implicit-def $nzcv
+    %6:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %6, %4, 0
+    B %bb.2
+
+  bb.2:
+    liveins: $nzcv
+
+    %7:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    $w0 = COPY %7
+    RET_ReallyLR implicit $w0
+...
+
+---
+# Negative test: Select (head) + Select (true), different source registers.
+#
+# bb.0 compares %0 (traces to $w0) and bb.1 compares %1 (traces to $w1).
+# registersMatch() must reject this pair; no transformation expected.
+name:            select_select_different_regs
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: select_select_different_regs
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $w3, $x4, $x5
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32common = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr32 = COPY $w3
+  ; CHECK-NEXT:   [[COPY4:%[0-9]+]]:gpr64common = COPY $x4
+  ; CHECK-NEXT:   [[COPY5:%[0-9]+]]:gpr64common = COPY $x5
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 9, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr:%[0-9]+]]:gpr32 = CSELWr [[COPY2]], [[COPY3]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr]], [[COPY4]], 0
+  ; CHECK-NEXT:   B %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY1]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr1:%[0-9]+]]:gpr32 = CSELWr [[COPY2]], [[COPY3]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr1]], [[COPY5]], 0
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $w3, $x4, $x5
+    successors: %bb.1
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32common = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr32 = COPY $w3
+    %4:gpr64common = COPY $x4
+    %5:gpr64common = COPY $x5
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %6:gpr32 = CSELWr %2, %3, 12, implicit $nzcv
+    STRWui killed %6, %4, 0
+    B %bb.1
+
+  bb.1:
+    successors: %bb.2
+
+    dead $wzr = SUBSWri %1, 10, 0, implicit-def $nzcv
+    %7:gpr32 = CSELWr %2, %3, 12, implicit $nzcv
+    STRWui killed %7, %5, 0
+    B %bb.2
+
+  bb.2:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...
+
+---
+# Note: CSET (CSINCWr $wzr, $wzr, cc) is covered structurally by the
+# select_head_select_true_csinc test below, which exercises the same
+# findCondCodeUseOperandIdxForBranchOrSelect opcode dispatch path.
+# A dedicated CSET test with $wzr physical-register sources does not
+# trigger the optimizer because findSelectConsumer cannot trace liveness
+# through physical $wzr operands in the same way as virtual registers.
+
+---
+# Positive test: Select (head) + Select (true) using CSINCWr (also covers
+# the CSET opcode dispatch path since CSET lowers to CSINCWr).
+#
+# Same structure as select_head_select_true but uses CSINCWr instead of CSELWr.
+# Verifies that findCondCodeUseOperandIdxForBranchOrSelect returns the correct
+# operand index for CSINC.
+#
+# Input:  bb.0: CMP #9  GT CSINCWr -> bb.1
+#         bb.1: CMP #10 GT CSINCWr
+# Output: bb.0: CMP #10 GE CSINCWr (adjusted)
+#         bb.1: CMP #10 GT CSINCWr (unchanged)
+name:            select_head_select_true_csinc
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: select_head_select_true_csinc
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $x3, $x4
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr64common = COPY $x3
+  ; CHECK-NEXT:   [[COPY4:%[0-9]+]]:gpr64common = COPY $x4
+  ; 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:   STRWui killed [[CSINCWr]], [[COPY3]], 0
+  ; CHECK-NEXT:   B %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSINCWr1:%[0-9]+]]:gpr32 = CSINCWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSINCWr1]], [[COPY4]], 0
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $x3, $x4
+    successors: %bb.1
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr64common = COPY $x3
+    %4:gpr64common = COPY $x4
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %5:gpr32 = CSINCWr %1, %2, 12, implicit $nzcv
+    STRWui killed %5, %3, 0
+    B %bb.1
+
+  bb.1:
+    successors: %bb.2
+
+    dead $wzr = SUBSWri %0, 10, 0, implicit-def $nzcv
+    %6:gpr32 = CSINCWr %1, %2, 12, implicit $nzcv
+    STRWui killed %6, %4, 0
+    B %bb.2
+
+  bb.2:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...
+
+---
+# Negative test: second NZCV reader between the select and the CMP.
+#
+# In bb.1 there are two CSELWr instructions both reading NZCV from the same
+# CMP. findSelectConsumer finds the last one first, then detects the second
+# NZCV reader while scanning back toward the CMP and returns {nullptr, Invalid},
+# preventing any transformation.
+#
+# Input and output are identical (no transformation expected).
+name:            select_second_nzcv_reader_between_select_and_cmp
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: select_second_nzcv_reader_between_select_and_cmp
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $x3, $x4
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr64common = COPY $x3
+  ; CHECK-NEXT:   [[COPY4:%[0-9]+]]:gpr64common = COPY $x4
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 9, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr]], [[COPY3]], 0
+  ; CHECK-NEXT:   B %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr1:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   [[CSELWr2:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr2]], [[COPY4]], 0
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $x3, $x4
+    successors: %bb.1
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr64common = COPY $x3
+    %4:gpr64common = COPY $x4
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %5:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %5, %3, 0
+    B %bb.1
+
+  bb.1:
+    successors: %bb.2
+
+    dead $wzr = SUBSWri %0, 10, 0, implicit-def $nzcv
+    ; Two consumers of the same CMP: findSelectConsumer must reject bb.1.
+    %6:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    %7:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %7, %4, 0
+    B %bb.2
+
+  bb.2:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...
+
+---
+# Negative test: unrelated NZCV-modifying instruction between the select and
+# the CMP inside findSelectConsumer.
+#
+# In bb.1 a SUBSWri with a live (non-dead) destination sits between the
+# CSELWr and the CMP. findSelectConsumer must detect that this NZCV writer is
+# not isCmpInstruction() (because its destination is not dead) and return
+# {nullptr, Invalid}, preventing any transformation.
+#
+# Input and output are identical (no transformation expected).
+name:            select_unrelated_nzcv_writer_between_select_and_cmp
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: select_unrelated_nzcv_writer_between_select_and_cmp
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000)
+  ; CHECK-NEXT:   liveins: $w0, $w1, $w2, $x3, $x4
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   [[COPY:%[0-9]+]]:gpr32common = COPY $w0
+  ; CHECK-NEXT:   [[COPY1:%[0-9]+]]:gpr32 = COPY $w1
+  ; CHECK-NEXT:   [[COPY2:%[0-9]+]]:gpr32 = COPY $w2
+  ; CHECK-NEXT:   [[COPY3:%[0-9]+]]:gpr64common = COPY $x3
+  ; CHECK-NEXT:   [[COPY4:%[0-9]+]]:gpr64common = COPY $x4
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 9, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr]], [[COPY3]], 0
+  ; CHECK-NEXT:   B %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   successors: %bb.2(0x80000000)
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   dead $wzr = SUBSWri [[COPY]], 10, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[SUBS:%[0-9]+]]:gpr32common = SUBSWri [[COPY]], 5, 0, implicit-def $nzcv
+  ; CHECK-NEXT:   [[CSELWr1:%[0-9]+]]:gpr32 = CSELWr [[COPY1]], [[COPY2]], 12, implicit $nzcv
+  ; CHECK-NEXT:   STRWui killed [[CSELWr1]], [[COPY4]], 0
+  ; CHECK-NEXT:   B %bb.2
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2:
+  ; CHECK-NEXT:   $w0 = MOVi32imm 0
+  ; CHECK-NEXT:   RET_ReallyLR implicit $w0
+  bb.0:
+    liveins: $w0, $w1, $w2, $x3, $x4
+    successors: %bb.1
+
+    %0:gpr32common = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    %3:gpr64common = COPY $x3
+    %4:gpr64common = COPY $x4
+    dead $wzr = SUBSWri %0, 9, 0, implicit-def $nzcv
+    %5:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %5, %3, 0
+    B %bb.1
+
+  bb.1:
+    successors: %bb.2
+
+    dead $wzr = SUBSWri %0, 10, 0, implicit-def $nzcv
+    ; SUBSWri with a live (non-dead) destination writes NZCV but is not a pure
+    ; CMP (canAdjustCmp rejects it). findSelectConsumer must reject this block.
+    %6:gpr32common = SUBSWri %0, 5, 0, implicit-def $nzcv
+    %7:gpr32 = CSELWr %1, %2, 12, implicit $nzcv
+    STRWui killed %7, %4, 0
+    B %bb.2
+
+  bb.2:
+    $w0 = MOVi32imm 0
+    RET_ReallyLR implicit $w0
+...



More information about the llvm-commits mailing list