[llvm] [ARM] Fold AND and Or cases with CSET and CMOV (PR #194667)

via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 28 09:28:53 PDT 2026


https://github.com/LumioseSil updated https://github.com/llvm/llvm-project/pull/194667

>From aa0b1e952e7f14b4b1be01d41cb3a8d12b23eccf Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Tue, 28 Apr 2026 11:56:34 -0400
Subject: [PATCH 1/3] [ARM] Fold Or of CSINC into CSINC

This folds (or x, (csinc 0, 0, cc)) -> (csinc x, 0, cc) in TableGen.
---
 llvm/lib/Target/ARM/ARMInstrThumb2.td         |  3 +++
 llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll | 13 +++++--------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMInstrThumb2.td b/llvm/lib/Target/ARM/ARMInstrThumb2.td
index e79adb370798e..fbe70a0f664d2 100644
--- a/llvm/lib/Target/ARM/ARMInstrThumb2.td
+++ b/llvm/lib/Target/ARM/ARMInstrThumb2.td
@@ -5828,6 +5828,9 @@ let Predicates = [HasV8_1MMainline] in {
   def : T2Pat<(and (topbitsallzero32:$Rn),
                    (ARMcsinc_su (i32 0), (i32 0), imm:$cc, CPSR)),
               (t2CSEL ZR, $Rn, imm:$cc)>;
+  def : T2Pat<(or (topbitsallzero32:$Rn),
+                  (ARMcsinc_su (i32 0), (i32 0), imm:$cc, CPSR)),
+              (t2CSINC $Rn, ZR, imm:$cc)>;
 }
 
 // CS aliases.
diff --git a/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll b/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll
index 5219c74e35817..ba19981d07ffc 100644
--- a/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll
+++ b/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll
@@ -109,10 +109,9 @@ define i32 @andi32_sgt(i8 %x, i8 %y) {
 define i64 @ori64i32_eq(i64 %x, i32 %y) {
 ; CHECK-LABEL: ori64i32_eq:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    cmp r2, #0
 ; CHECK-NEXT:    and r0, r0, #1
-; CHECK-NEXT:    cset r1, eq
-; CHECK-NEXT:    orrs r0, r1
+; CHECK-NEXT:    cmp r2, #0
+; CHECK-NEXT:    csinc r0, r0, zr, ne
 ; CHECK-NEXT:    movs r1, #0
 ; CHECK-NEXT:    bx lr
   %xa = and i64 %x, 1
@@ -127,8 +126,7 @@ define i64 @ori64i64_eq(i64 %x, i64 %y) {
 ; CHECK:       @ %bb.0:
 ; CHECK-NEXT:    orrs.w r1, r2, r3
 ; CHECK-NEXT:    and r0, r0, #1
-; CHECK-NEXT:    cset r1, eq
-; CHECK-NEXT:    orrs r0, r1
+; CHECK-NEXT:    csinc r0, r0, zr, ne
 ; CHECK-NEXT:    movs r1, #0
 ; CHECK-NEXT:    bx lr
   %xa = and i64 %x, 1
@@ -141,10 +139,9 @@ define i64 @ori64i64_eq(i64 %x, i64 %y) {
 define i64 @ori64_eq_c(i64 %x, i32 %y) {
 ; CHECK-LABEL: ori64_eq_c:
 ; CHECK:       @ %bb.0:
-; CHECK-NEXT:    cmp r2, #0
 ; CHECK-NEXT:    and r0, r0, #1
-; CHECK-NEXT:    cset r1, eq
-; CHECK-NEXT:    orrs r0, r1
+; CHECK-NEXT:    cmp r2, #0
+; CHECK-NEXT:    csinc r0, r0, zr, ne
 ; CHECK-NEXT:    movs r1, #0
 ; CHECK-NEXT:    bx lr
   %xa = and i64 %x, 1

>From a33d465d4bb0d5e1e0734eab6e86953c68d49cc4 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Tue, 28 Apr 2026 12:20:26 -0400
Subject: [PATCH 2/3] Update movcc-double.ll (NFC)

---
 llvm/test/CodeGen/ARM/movcc-double.ll | 56 ++++++++++++++++++++-------
 1 file changed, 43 insertions(+), 13 deletions(-)

diff --git a/llvm/test/CodeGen/ARM/movcc-double.ll b/llvm/test/CodeGen/ARM/movcc-double.ll
index c04e779b28a4e..6070388281f25 100644
--- a/llvm/test/CodeGen/ARM/movcc-double.ll
+++ b/llvm/test/CodeGen/ARM/movcc-double.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
 ; RUN: llc -o - %s | FileCheck %s
 target triple = "arm-unknown-unknown"
 
@@ -5,11 +6,18 @@ target triple = "arm-unknown-unknown"
 ; cmovs, not by producing two conditions and using and on them.
 
 define i32 @select_and(i32 %a0, i32 %a1, i32 %a2, i32 %a3, i32 %a4, i32 %a5) {
-; CHECK-LABEL: select_and
-; CHECK-NOT: tst
-; CHECK-NOT: movne
-; CHECK: mov{{lo|hs}}
-; CHECK: mov{{lo|hs}}
+; CHECK-LABEL: select_and:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    push {r11, lr}
+; CHECK-NEXT:    add lr, sp, #12
+; CHECK-NEXT:    add r12, sp, #8
+; CHECK-NEXT:    cmp r2, r3
+; CHECK-NEXT:    movhs r12, lr
+; CHECK-NEXT:    cmp r0, r1
+; CHECK-NEXT:    movhs r12, lr
+; CHECK-NEXT:    ldr r0, [r12]
+; CHECK-NEXT:    pop {r11, lr}
+; CHECK-NEXT:    mov pc, lr
   %cmp0 = icmp ult i32 %a0, %a1
   %cmp1 = icmp ult i32 %a2, %a3
   %and = and i1 %cmp0, %cmp1
@@ -20,11 +28,18 @@ define i32 @select_and(i32 %a0, i32 %a1, i32 %a2, i32 %a3, i32 %a4, i32 %a5) {
 define i32 @select_or(i32 %a0, i32 %a1, i32 %a2, i32 %a3, i32 %a4, i32 %a5) {
 ; select with and i1 condition should be implemented as a series of 2 cmovs, not
 ; by producing two conditions and using and on them.
-; CHECK-LABEL: select_or
-; CHECK-NOT: orss
-; CHECK-NOT: tst
-; CHECK: mov{{lo|hs}}
-; CHECK: mov{{lo|hs}}
+; CHECK-LABEL: select_or:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    push {r11, lr}
+; CHECK-NEXT:    add r12, sp, #8
+; CHECK-NEXT:    add lr, sp, #12
+; CHECK-NEXT:    cmp r2, r3
+; CHECK-NEXT:    movlo lr, r12
+; CHECK-NEXT:    cmp r0, r1
+; CHECK-NEXT:    movlo lr, r12
+; CHECK-NEXT:    ldr r0, [lr]
+; CHECK-NEXT:    pop {r11, lr}
+; CHECK-NEXT:    mov pc, lr
   %cmp0 = icmp ult i32 %a0, %a1
   %cmp1 = icmp ult i32 %a2, %a3
   %and = or i1 %cmp0, %cmp1
@@ -37,9 +52,24 @@ define i32 @select_or(i32 %a0, i32 %a1, i32 %a2, i32 %a3, i32 %a4, i32 %a5) {
 
 @var32 = global i32 0
 define i32 @select_noopt(i32 %a0, i32 %a1, i32 %a2, i32 %a3, i32 %a4) {
-; CHECK-LABEL: select_noopt
-; CHECK: orrs
-; CHECK: movne
+; CHECK-LABEL: select_noopt:
+; CHECK:       @ %bb.0:
+; CHECK-NEXT:    cmp r1, r2
+; CHECK-NEXT:    mov r2, #0
+; CHECK-NEXT:    movlo r2, #1
+; CHECK-NEXT:    cmp r0, r1
+; CHECK-NEXT:    ldr r1, .LCPI2_0
+; CHECK-NEXT:    mov r12, #0
+; CHECK-NEXT:    movlo r12, #1
+; CHECK-NEXT:    orrs r0, r12, r2
+; CHECK-NEXT:    str r0, [r1]
+; CHECK-NEXT:    ldr r0, [sp]
+; CHECK-NEXT:    movne r0, r3
+; CHECK-NEXT:    mov pc, lr
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  @ %bb.1:
+; CHECK-NEXT:  .LCPI2_0:
+; CHECK-NEXT:    .long var32
   %cmp0 = icmp ult i32 %a0, %a1
   %cmp1 = icmp ult i32 %a1, %a2
   %or = or i1 %cmp0, %cmp1

>From c101b287f364521f0faec9af62c311169a625ae2 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Tue, 28 Apr 2026 12:27:11 -0400
Subject: [PATCH 3/3] Fold AND and Or cases with CSET and CMOV

---
 llvm/lib/Target/ARM/ARMISelLowering.cpp       | 55 +++++++++++++++++++
 llvm/test/CodeGen/ARM/movcc-double.ll         | 11 ++--
 llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll |  7 +--
 3 files changed, 63 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index 17d57c1a24407..b2242bed62b20 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -14370,6 +14370,52 @@ static SDValue CombineANDShift(SDNode *N,
   return SDValue();
 }
 
+// Return true if V is a boolean set-like value:
+//   CMOV(0, 1, CC, Flags) or CSINC(0, 0, CC, Flags).
+// In both cases, C == 1 when CC is true, 0 otherwise.
+static bool getSetLikeCondAndFlags(SDValue V, SDValue &Cond, SDValue &Flags) {
+  if (V.getOpcode() == ARMISD::CMOV && isNullConstant(V.getOperand(0)) &&
+      isOneConstant(V.getOperand(1))) {
+    Cond = V.getOperand(2);
+    Flags = V.getOperand(3);
+    return true;
+  }
+  if (V.getOpcode() == ARMISD::CSINC && isNullConstant(V.getOperand(0)) &&
+      isNullConstant(V.getOperand(1))) {
+    Cond = V.getOperand(2);
+    Flags = V.getOperand(3);
+    return true;
+  }
+  return false;
+}
+
+// Given a tree of and/or(setlike(cc0), setlike(cc1)), fold to a CMOV chain
+// without materializing the intermediate and/or:
+//
+// OR(CSET cc0 cmp0, CSET cc1 cmp1)  -> CMOV(CSET cc1 cmp1, 1, cc0, cmp0)
+// AND(CSET cc0 cmp0, CSET cc1 cmp1) -> CMOV(0, CSET cc1 cmp1, cc0, cmp0)
+static SDValue performANDORCMOVCombine(SDNode *N, SelectionDAG &DAG) {
+  EVT VT = N->getValueType(0);
+  if (VT != MVT::i32)
+    return SDValue();
+
+  SDValue Set0 = N->getOperand(0);
+  SDValue Set1 = N->getOperand(1);
+  SDValue Cond0, Flags0;
+  if (!getSetLikeCondAndFlags(Set0, Cond0, Flags0))
+    return SDValue();
+  SDValue UnusedCond, UnusedFlags;
+  if (!getSetLikeCondAndFlags(Set1, UnusedCond, UnusedFlags))
+    return SDValue();
+
+  SDLoc DL(N);
+  SDValue Zero = DAG.getConstant(0, DL, VT);
+  SDValue One = DAG.getConstant(1, DL, VT);
+  if (N->getOpcode() == ISD::OR)
+    return DAG.getNode(ARMISD::CMOV, DL, VT, Set1, One, Cond0, Flags0);
+  return DAG.getNode(ARMISD::CMOV, DL, VT, Zero, Set1, Cond0, Flags0);
+}
+
 static SDValue PerformANDCombine(SDNode *N,
                                  TargetLowering::DAGCombinerInfo &DCI,
                                  const ARMSubtarget *Subtarget) {
@@ -14383,6 +14429,12 @@ static SDValue PerformANDCombine(SDNode *N,
       VT == MVT::v4i1 || VT == MVT::v8i1 || VT == MVT::v16i1)
     return SDValue();
 
+  // Akin to AArch64's ANDS handling, only do this when the AND value itself is
+  // dead, to avoid perturbing value-producing uses just for flag consumers.
+  if (!N->hasAnyUseOfValue(0))
+    if (SDValue Result = performANDORCMOVCombine(N, DAG))
+      return Result;
+
   APInt SplatBits, SplatUndef;
   unsigned SplatBitSize;
   bool HasAnyUndefs;
@@ -14722,6 +14774,9 @@ static SDValue PerformORCombine(SDNode *N, TargetLowering::DAGCombinerInfo &DCI,
   if (!DAG.getTargetLoweringInfo().isTypeLegal(VT))
     return SDValue();
 
+  if (SDValue Result = performANDORCMOVCombine(N, DAG))
+    return Result;
+
   if (Subtarget->hasMVEIntegerOps() && (VT == MVT::v2i1 || VT == MVT::v4i1 ||
                                         VT == MVT::v8i1 || VT == MVT::v16i1))
     return PerformORCombine_i1(N, DAG, Subtarget);
diff --git a/llvm/test/CodeGen/ARM/movcc-double.ll b/llvm/test/CodeGen/ARM/movcc-double.ll
index 6070388281f25..c9687f51e0ff0 100644
--- a/llvm/test/CodeGen/ARM/movcc-double.ll
+++ b/llvm/test/CodeGen/ARM/movcc-double.ll
@@ -54,15 +54,14 @@ define i32 @select_or(i32 %a0, i32 %a1, i32 %a2, i32 %a3, i32 %a4, i32 %a5) {
 define i32 @select_noopt(i32 %a0, i32 %a1, i32 %a2, i32 %a3, i32 %a4) {
 ; CHECK-LABEL: select_noopt:
 ; CHECK:       @ %bb.0:
+; CHECK-NEXT:    mov r12, #0
 ; CHECK-NEXT:    cmp r1, r2
-; CHECK-NEXT:    mov r2, #0
-; CHECK-NEXT:    movlo r2, #1
+; CHECK-NEXT:    movlo r12, #1
 ; CHECK-NEXT:    cmp r0, r1
-; CHECK-NEXT:    ldr r1, .LCPI2_0
-; CHECK-NEXT:    mov r12, #0
+; CHECK-NEXT:    ldr r0, .LCPI2_0
 ; CHECK-NEXT:    movlo r12, #1
-; CHECK-NEXT:    orrs r0, r12, r2
-; CHECK-NEXT:    str r0, [r1]
+; CHECK-NEXT:    cmp r12, #0
+; CHECK-NEXT:    str r12, [r0]
 ; CHECK-NEXT:    ldr r0, [sp]
 ; CHECK-NEXT:    movne r0, r3
 ; CHECK-NEXT:    mov pc, lr
diff --git a/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll b/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll
index ba19981d07ffc..a9b3a2124afa5 100644
--- a/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll
+++ b/llvm/test/CodeGen/Thumb2/csel-andor-onebit.ll
@@ -175,11 +175,10 @@ define i32 @t5(i32 %f.0, i32 %call) {
 ; CHECK-NEXT:    cmp r1, #0
 ; CHECK-NEXT:    cset r1, ne
 ; CHECK-NEXT:    cmp r0, #13
-; CHECK-NEXT:    cset r0, eq
-; CHECK-NEXT:    and.w r2, r0, r1
+; CHECK-NEXT:    csel r0, zr, r1, ne
+; CHECK-NEXT:    csinc r1, r1, zr, eq
+; CHECK-NEXT:    eor r1, r1, #1
 ; CHECK-NEXT:    orrs r0, r1
-; CHECK-NEXT:    eor r0, r0, #1
-; CHECK-NEXT:    orrs r0, r2
 ; CHECK-NEXT:    bx lr
 entry:
   %tobool1.i = icmp ne i32 %call, 0



More information about the llvm-commits mailing list