[llvm] [AArch64] InsertSelect: Fold both diamond arms into chained CSEL family instructions (PR #212033)

Mugundan S via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 2 04:10:58 PDT 2026


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

>From decaaa41fa02258e2e2abc71839b6491777b4d79 Mon Sep 17 00:00:00 2001
From: Greenie0701 <smugundan12a at gmail.com>
Date: Sat, 25 Jul 2026 21:38:02 +0530
Subject: [PATCH] [AArch64] InsertSelect: fold both diamond arms into chained
 CSEL family instructions

---
 llvm/lib/Target/AArch64/AArch64InstrInfo.cpp  |  48 +++--
 .../early-ifcvt-insert-select-fold.mir        | 202 ++++++++++++++++++
 2 files changed, 234 insertions(+), 16 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/early-ifcvt-insert-select-fold.mir

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
index b40188e97a71e..6e9c0ebde922b 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
@@ -1039,7 +1039,6 @@ bool AArch64InstrInfo::canInsertSelect(const MachineBasicBlock &MBB,
   unsigned ExtraCondLat = Cond.size() != 1;
 
   // GPRs are handled by csel.
-  // FIXME: Fold in x+1, -x, and ~x when applicable.
   if (AArch64::GPR64allRegClass.hasSubClassEq(RC) ||
       AArch64::GPR32allRegClass.hasSubClassEq(RC)) {
     // Single-cycle csel, csinc, csinv, and csneg.
@@ -1047,7 +1046,7 @@ bool AArch64InstrInfo::canInsertSelect(const MachineBasicBlock &MBB,
     TrueCycles = FalseCycles = 1;
     if (canFoldIntoCSel(MRI, TrueReg))
       TrueCycles = 0;
-    else if (canFoldIntoCSel(MRI, FalseReg))
+    if (canFoldIntoCSel(MRI, FalseReg))
       FalseCycles = 0;
     return true;
   }
@@ -1291,22 +1290,39 @@ void AArch64InstrInfo::insertSelect(MachineBasicBlock &MBB,
 
   // Try folding simple instructions into the csel.
   if (TryFold) {
-    unsigned NewReg = 0;
-    unsigned FoldedOpc = canFoldIntoCSel(MRI, TrueReg, &NewReg);
-    if (FoldedOpc) {
-      // The folded opcodes csinc, csinc and csneg apply the operation to
-      // FalseReg, so we need to invert the condition.
+    unsigned NewTrueReg = 0, NewFalseReg = 0;
+    unsigned TrueFoldOpc = canFoldIntoCSel(MRI, TrueReg, &NewTrueReg);
+    unsigned FalseFoldOpc = canFoldIntoCSel(MRI, FalseReg, &NewFalseReg);
+
+    if (TrueFoldOpc && FalseFoldOpc) {
+      // Both sides are foldable --> Emit two chained CSEL-family instructions:
+      //   TmpReg = TrueFoldOpc(FalseReg, NewTrueReg, InvCC) --> folds TrueReg
+      //   DstReg = FalseFoldOpc(TmpReg,  NewFalseReg, CC)   --> folds FalseReg
+      Register TmpReg = MRI.createVirtualRegister(RC);
+      AArch64CC::CondCode InvCC = AArch64CC::getInvertedCondCode(CC);
+      BuildMI(MBB, I, DL, get(TrueFoldOpc), TmpReg)
+          .addReg(FalseReg)
+          .addReg(NewTrueReg)
+          .addImm(InvCC);
+      MRI.clearKillFlags(NewTrueReg);
+      // Set up the second instruction to fold FalseReg.
+      TrueReg = TmpReg;
+      FalseReg = NewFalseReg;
+      Opc = FalseFoldOpc;
+      MRI.clearKillFlags(NewFalseReg);
+    } else if (TrueFoldOpc) {
+      // Only TrueReg is foldable. The fold operation applies to Rm (slot 2),
+      // so invert the condition and swap Rn/Rm to place it correctly.
       CC = AArch64CC::getInvertedCondCode(CC);
       TrueReg = FalseReg;
-    } else
-      FoldedOpc = canFoldIntoCSel(MRI, FalseReg, &NewReg);
-
-    // Fold the operation. Leave any dead instructions for DCE to clean up.
-    if (FoldedOpc) {
-      FalseReg = NewReg;
-      Opc = FoldedOpc;
-      // Extend the live range of NewReg.
-      MRI.clearKillFlags(NewReg);
+      FalseReg = NewTrueReg;
+      Opc = TrueFoldOpc;
+      MRI.clearKillFlags(NewTrueReg);
+    } else if (FalseFoldOpc) {
+      // Only FalseReg is foldable. Direct fold into Rm, no swap needed.
+      FalseReg = NewFalseReg;
+      Opc = FalseFoldOpc;
+      MRI.clearKillFlags(NewFalseReg);
     }
   }
 
diff --git a/llvm/test/CodeGen/AArch64/early-ifcvt-insert-select-fold.mir b/llvm/test/CodeGen/AArch64/early-ifcvt-insert-select-fold.mir
new file mode 100644
index 0000000000000..9f7f4e6289fe6
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/early-ifcvt-insert-select-fold.mir
@@ -0,0 +1,202 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=aarch64-linux-gnu -run-pass=early-ifcvt -verify-machineinstrs %s -o - | FileCheck %s
+
+# Test insertSelect folding in EarlyIfConversion for the both-foldable case
+# (x+1 -> CSINC, -x -> CSNEG, ~x -> CSINV) and single-side-only cases.
+#
+# Positive tests: both sides foldable -> two chained CSELs, no PHI.
+# Negative tests: only one side foldable -> single CSEL-family, no PHI.
+
+--- |
+  define i32 @test_inc_neg(i32 %cond, i32 %a, i32 %b) {
+  entry:
+    br i1 undef, label %btrue, label %bfalse
+  btrue:
+    br label %tail
+  bfalse:
+    br label %tail
+  tail:
+    ret i32 undef
+  }
+  define i32 @test_inc_inv(i32 %cond, i32 %a, i32 %b) {
+  entry:
+    br i1 undef, label %btrue, label %bfalse
+  btrue:
+    br label %tail
+  bfalse:
+    br label %tail
+  tail:
+    ret i32 undef
+  }
+  define i32 @test_only_true_inc(i32 %cond, i32 %a, i32 %b) {
+  entry:
+    br i1 undef, label %btrue, label %bfalse
+  btrue:
+    br label %tail
+  bfalse:
+    br label %tail
+  tail:
+    ret i32 undef
+  }
+  define i32 @test_only_false_neg(i32 %cond, i32 %a, i32 %b) {
+  entry:
+    br i1 undef, label %btrue, label %bfalse
+  btrue:
+    br label %tail
+  bfalse:
+    br label %tail
+  tail:
+    ret i32 undef
+  }
+...
+
+---
+# Positive: both sides foldable.
+# true = a+1 (CSINC), false = -b (CSNEG).
+# Expected: CSINCWr + CSNEGWr chained, no PHI, no plain CSEL.
+name:            test_inc_neg
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: test_inc_neg
+  ; CHECK:      bb.0.entry:
+  ; CHECK-NOT:  PHI
+  ; CHECK-NOT:  CSELWr
+  ; CHECK:      CSINCWr
+  ; CHECK:      CSNEGWr
+  bb.0.entry:
+    successors: %bb.1(0x50000000), %bb.2(0x30000000)
+    liveins: $w0, $w1, $w2
+    %0:gpr32 = COPY $w0
+    %1:gpr32common = COPY $w1
+    %2:gpr32 = COPY $w2
+    CBNZW %0, %bb.1
+    B %bb.2
+
+  bb.1.btrue:
+    successors: %bb.3(0x80000000)
+    %3:gpr32sp = nsw ADDWri %1, 1, 0
+    %6:gpr32 = COPY %3
+    B %bb.3
+
+  bb.2.bfalse:
+    successors: %bb.3(0x80000000)
+    %4:gpr32 = SUBWrr $wzr, %2
+    B %bb.3
+
+  bb.3.tail:
+    %5:gpr32 = PHI %6, %bb.1, %4, %bb.2
+    $w0 = COPY %5
+    RET_ReallyLR implicit $w0
+
+---
+# Positive: both sides foldable.
+# true = a+1 (CSINC), false = ~b (CSINV).
+# Expected: CSINCWr + CSINVWr chained, no PHI, no plain CSEL.
+name:            test_inc_inv
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: test_inc_inv
+  ; CHECK:      bb.0.entry:
+  ; CHECK-NOT:  PHI
+  ; CHECK-NOT:  CSELWr
+  ; CHECK:      CSINCWr
+  ; CHECK:      CSINVWr
+  bb.0.entry:
+    successors: %bb.1(0x50000000), %bb.2(0x30000000)
+    liveins: $w0, $w1, $w2
+    %0:gpr32 = COPY $w0
+    %1:gpr32common = COPY $w1
+    %2:gpr32 = COPY $w2
+    CBNZW %0, %bb.1
+    B %bb.2
+
+  bb.1.btrue:
+    successors: %bb.3(0x80000000)
+    %3:gpr32sp = nsw ADDWri %1, 1, 0
+    %6:gpr32 = COPY %3
+    B %bb.3
+
+  bb.2.bfalse:
+    successors: %bb.3(0x80000000)
+    %4:gpr32 = ORNWrr $wzr, %2
+    B %bb.3
+
+  bb.3.tail:
+    %5:gpr32 = PHI %6, %bb.1, %4, %bb.2
+    $w0 = COPY %5
+    RET_ReallyLR implicit $w0
+
+---
+# Negative: only TrueReg foldable.
+# true = a+1 (CSINC), false = plain reg (not foldable).
+# Expected: single CSINCWr with inverted CC, no PHI, no CSNEGWr/CSINVWr.
+name:            test_only_true_inc
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: test_only_true_inc
+  ; CHECK:      bb.0.entry:
+  ; CHECK-NOT:  PHI
+  ; CHECK-NOT:  CSNEGWr
+  ; CHECK-NOT:  CSINVWr
+  ; CHECK:      CSINCWr
+  bb.0.entry:
+    successors: %bb.1(0x50000000), %bb.2(0x30000000)
+    liveins: $w0, $w1, $w2
+    %0:gpr32 = COPY $w0
+    %1:gpr32common = COPY $w1
+    %2:gpr32 = COPY $w2
+    CBNZW %0, %bb.1
+    B %bb.2
+
+  bb.1.btrue:
+    successors: %bb.3(0x80000000)
+    %3:gpr32sp = nsw ADDWri %1, 1, 0
+    %6:gpr32 = COPY %3
+    B %bb.3
+
+  bb.2.bfalse:
+    successors: %bb.3(0x80000000)
+    %4:gpr32 = COPY %2
+    B %bb.3
+
+  bb.3.tail:
+    %5:gpr32 = PHI %6, %bb.1, %4, %bb.2
+    $w0 = COPY %5
+    RET_ReallyLR implicit $w0
+
+---
+# Negative: only FalseReg foldable.
+# true = plain reg (not foldable), false = -b (CSNEG).
+# Expected: single CSNEGWr, no PHI, no CSINCWr/CSINVWr.
+name:            test_only_false_neg
+tracksRegLiveness: true
+body:             |
+  ; CHECK-LABEL: name: test_only_false_neg
+  ; CHECK:      bb.0.entry:
+  ; CHECK-NOT:  PHI
+  ; CHECK-NOT:  CSINCWr
+  ; CHECK-NOT:  CSINVWr
+  ; CHECK:      CSNEGWr
+  bb.0.entry:
+    successors: %bb.1(0x50000000), %bb.2(0x30000000)
+    liveins: $w0, $w1, $w2
+    %0:gpr32 = COPY $w0
+    %1:gpr32 = COPY $w1
+    %2:gpr32 = COPY $w2
+    CBNZW %0, %bb.1
+    B %bb.2
+
+  bb.1.btrue:
+    successors: %bb.3(0x80000000)
+    %6:gpr32 = COPY %1
+    B %bb.3
+
+  bb.2.bfalse:
+    successors: %bb.3(0x80000000)
+    %4:gpr32 = SUBWrr $wzr, %2
+    B %bb.3
+
+  bb.3.tail:
+    %5:gpr32 = PHI %6, %bb.1, %4, %bb.2
+    $w0 = COPY %5
+    RET_ReallyLR implicit $w0



More information about the llvm-commits mailing list