[llvm] [ARM] Swap sides of cmp/cmn based on folding ability (PR #191915)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 20 09:11:20 PDT 2026


https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/191915

>From 5d36fb1a0d363d8f32c6d5c8b36d2bc3fa09d4de Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Sat, 20 Jun 2026 12:10:54 -0400
Subject: [PATCH] [ARM] Swap sides of cmp/cmn based on folding ability

If we can fold lsl, lsr, rotr, or asr into the right side of the compare, we should do so.
---
 llvm/lib/Target/ARM/ARMISelLowering.cpp       | 94 ++++++++++++++-----
 llvm/test/CodeGen/ARM/atomic-ops-v8.ll        | 16 ++--
 llvm/test/CodeGen/ARM/cmp-peephole.ll         |  4 +-
 llvm/test/CodeGen/ARM/ssat.ll                 |  9 +-
 llvm/test/CodeGen/ARM/umulo-32.ll             |  2 +-
 llvm/test/CodeGen/ARM/usat.ll                 | 13 ++-
 .../Thumb2/LowOverheadLoops/reductions.ll     | 12 +--
 7 files changed, 95 insertions(+), 55 deletions(-)

diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp
index 248f5b3f9f083..2ff6679132ca0 100644
--- a/llvm/lib/Target/ARM/ARMISelLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp
@@ -4503,11 +4503,43 @@ static bool isCMN(SDValue Op, ISD::CondCode CC, SelectionDAG &DAG) {
           (isSignedIntSetCC(CC) && isSafeSignedCMN(Op, DAG)));
 }
 
+/// Returns how profitable it is to fold a comparison's operand's shift and/or
+/// extension operations.
+static unsigned getCmpOperandFoldingProfit(SDValue Op, const ARMSubtarget &ST) {
+  // Thumb-1 compare does not use the same shifted-second-operand forms as
+  // Thumb-2 / ARM-mode CMP/CMN.
+  if (ST.isThumb1Only())
+    return 0;
+
+  if (!Op.hasOneUse())
+    return 0;
+
+  unsigned Opc = Op.getOpcode();
+  if (Opc == ISD::SHL || Opc == ISD::SRL || Opc == ISD::SRA ||
+      Opc == ISD::ROTR) {
+    if (ConstantSDNode *Amnt = dyn_cast<ConstantSDNode>(Op.getOperand(1))) {
+      unsigned Shift = Amnt->getZExtValue();
+      if (Shift <= 31)
+        return 1;
+      return 0;
+    }
+  }
+
+  // Register-specified shift: ARM-mode CMP/CMN (so_reg_reg). Thumb-2
+  // t2_so_reg is immediate shift only.
+  if (ST.isThumb())
+    return 0;
+
+  return 1;
+}
+
 /// Returns appropriate ARM CMP (cmp) and corresponding condition code for
 /// the given operands.
 SDValue ARMTargetLowering::getARMCmp(SDValue LHS, SDValue RHS, ISD::CondCode CC,
                                      SDValue &ARMcc, SelectionDAG &DAG,
                                      const SDLoc &dl) const {
+
+  bool SwapProfitGuard = false;
   if (ConstantSDNode *RHSC = dyn_cast<ConstantSDNode>(RHS.getNode())) {
     unsigned C = RHSC->getZExtValue();
     if (!isLegalICmpImmediate((int32_t)C)) {
@@ -4544,12 +4576,25 @@ SDValue ARMTargetLowering::getARMCmp(SDValue LHS, SDValue RHS, ISD::CondCode CC,
         break;
       }
     }
-  } else if ((ARM_AM::getShiftOpcForNode(LHS.getOpcode()) != ARM_AM::no_shift) &&
-             (ARM_AM::getShiftOpcForNode(RHS.getOpcode()) == ARM_AM::no_shift)) {
-    // In ARM and Thumb-2, the compare instructions can shift their second
-    // operand.
-    CC = ISD::getSetCCSwappedOperands(CC);
-    std::swap(LHS, RHS);
+  }
+
+  // Prefer folding shifts / CMN into the cmp/cmn second operand (so_reg /
+  // t2_so_reg). When both sides compete, pick the higher
+  // getCmpOperandFoldingProfit (operands are peeled if CMN). Only when RHS
+  // is not a legal icmp immediate: otherwise keep the canonical (reg, imm)
+  // form.
+  bool RHSFitsICmpImmediate = false;
+  if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(RHS.getNode()))
+    RHSFitsICmpImmediate = isLegalICmpImmediate(C->getSExtValue());
+  if (!isa<ConstantSDNode>(RHS) || !RHSFitsICmpImmediate) {
+    SDValue TheLHS = isCMN(LHS, CC, DAG) ? LHS.getOperand(1) : LHS;
+    SDValue TheRHS = isCMN(RHS, CC, DAG) ? RHS.getOperand(1) : RHS;
+
+    if (getCmpOperandFoldingProfit(TheLHS, *Subtarget) >
+        getCmpOperandFoldingProfit(TheRHS, *Subtarget)) {
+      std::swap(LHS, RHS);
+      CC = ISD::getSetCCSwappedOperands(CC);
+    }
   }
 
   // Thumb1 has very limited immediate modes, so turning an "and" into a
@@ -4610,21 +4655,6 @@ SDValue ARMTargetLowering::getARMCmp(SDValue LHS, SDValue RHS, ISD::CondCode CC,
 
   ARMCC::CondCodes CondCode = IntCCToARMCC(CC);
 
-  // If the RHS is a constant zero then the V (overflow) flag will never be
-  // set. This can allow us to simplify GE to PL or LT to MI, which can be
-  // simpler for other passes (like the peephole optimiser) to deal with.
-  if (isNullConstant(RHS)) {
-    switch (CondCode) {
-      default: break;
-      case ARMCC::GE:
-        CondCode = ARMCC::PL;
-        break;
-      case ARMCC::LT:
-        CondCode = ARMCC::MI;
-        break;
-    }
-  }
-
   unsigned CompareType;
   switch (CondCode) {
   default:
@@ -4651,6 +4681,22 @@ SDValue ARMTargetLowering::getARMCmp(SDValue LHS, SDValue RHS, ISD::CondCode CC,
     CondCode = IntCCToARMCC(ISD::getSetCCSwappedOperands(CC));
   }
 
+  // If the RHS is a constant zero then the V (overflow) flag will never be
+  // set. This can allow us to simplify GE to PL or LT to MI, which can be
+  // simpler for other passes (like the peephole optimiser) to deal with.
+  if (isNullConstant(RHS)) {
+    switch (CondCode) {
+    default:
+      break;
+    case ARMCC::GE:
+      CondCode = ARMCC::PL;
+      break;
+    case ARMCC::LT:
+      CondCode = ARMCC::MI;
+      break;
+    }
+  }
+
   ARMcc = DAG.getConstant(CondCode, dl, MVT::i32);
   return DAG.getNode(CompareType, dl, FlagsVT, LHS, RHS);
 }
@@ -13760,8 +13806,6 @@ ARMTargetLowering::isDesirableToCommuteWithShift(const SDNode *N,
     // Avoid making expensive immediates by commuting shifts. (This logic
     // only applies to Thumb1 because ARM and Thumb2 immediates can be shifted
     // for free.)
-    if (N->getOpcode() != ISD::SHL)
-      return true;
     SDValue N1 = N->getOperand(0);
     if (N1->getOpcode() != ISD::ADD && N1->getOpcode() != ISD::AND &&
         N1->getOpcode() != ISD::OR && N1->getOpcode() != ISD::XOR)
@@ -13877,14 +13921,14 @@ static SDValue PerformSHLSimplify(SDNode *N,
   // b + ((a << 1) ^ 510)
   // b + ((a << 1) + 510)
 
-  // Many instructions can  perform the shift for free, but it requires both
+  // Many instructions can perform the shift for free, but it requires both
   // the operands to be registers. If c1 << c2 is too large, a mov immediate
   // instruction will needed. So, unfold back to the original pattern if:
   // - if c1 and c2 are small enough that they don't require mov imms.
   // - the user(s) of the node can perform an shl
 
   // No shifted operands for 16-bit instructions.
-  if (ST->isThumb() && ST->isThumb1Only())
+  if (ST->isThumb1Only())
     return SDValue();
 
   // Check that all the users could perform the shl themselves.
diff --git a/llvm/test/CodeGen/ARM/atomic-ops-v8.ll b/llvm/test/CodeGen/ARM/atomic-ops-v8.ll
index d48b070aa862e..0084f1abc259f 100644
--- a/llvm/test/CodeGen/ARM/atomic-ops-v8.ll
+++ b/llvm/test/CodeGen/ARM/atomic-ops-v8.ll
@@ -590,9 +590,9 @@ define i8 @test_atomic_load_min_i8(i8 signext %offset) nounwind {
 ; CHECK-NEXT: sxtb r[[OLDX:[0-9]+]], r[[OLD]]
   ; r0 below is a reasonable guess but could change: it certainly comes into the
   ;  function there.
-; CHECK-NEXT: cmp r[[OLDX]], r0
+; CHECK-NEXT: cmp {{r[0-9]+}}, r{{[0-9]+}}
 ; Thumb mode: it le
-; CHECK:      movle r[[OLDX]], r[[OLD]]
+; CHECK:      mov{{le|ge}} r[[OLDX]], r[[OLD]]
 ; CHECK-NEXT: strexb [[STATUS:r[0-9]+]], r[[OLDX]], {{.*}}[[ADDR]]]
 ; CHECK-NEXT: cmp [[STATUS]], #0
 ; CHECK-NEXT: bne .LBB{{[0-9]+}}_1
@@ -616,9 +616,9 @@ define i16 @test_atomic_load_min_i16(i16 signext %offset) nounwind {
 ; CHECK-NEXT: sxth r[[OLDX:[0-9]+]], r[[OLD]]
   ; r0 below is a reasonable guess but could change: it certainly comes into the
   ;  function there.
-; CHECK-NEXT: cmp r[[OLDX]], r0
+; CHECK-NEXT: cmp {{r[0-9]+}}, r{{[0-9]+}}
 ; Thumb mode: it le
-; CHECK:      movle r[[OLDX]], r[[OLD]]
+; CHECK:      mov{{le|ge}} r[[OLDX]], r[[OLD]]
 ; CHECK-NEXT: stlexh [[STATUS:r[0-9]+]], r[[OLDX]], {{.*}}[[ADDR]]
 ; CHECK-NEXT: cmp [[STATUS]], #0
 ; CHECK-NEXT: bne .LBB{{[0-9]+}}_1
@@ -700,9 +700,9 @@ define i8 @test_atomic_load_max_i8(i8 signext %offset) nounwind {
 ; CHECK-NEXT: sxtb r[[OLDX:[0-9]+]], r[[OLD]]
   ; r0 below is a reasonable guess but could change: it certainly comes into the
   ;  function there.
-; CHECK-NEXT: cmp r[[OLDX]], r0
+; CHECK-NEXT: cmp {{r[0-9]+}}, r{{[0-9]+}}
 ; Thumb mode: it gt
-; CHECK:      movgt r[[OLDX]], r[[OLD]]
+; CHECK:      mov{{gt|lt}} r[[OLDX]], r[[OLD]]
 ; CHECK-NEXT: stlexb [[STATUS:r[0-9]+]], r[[OLDX]], {{.*}}[[ADDR]]
 ; CHECK-NEXT: cmp [[STATUS]], #0
 ; CHECK-NEXT: bne .LBB{{[0-9]+}}_1
@@ -726,9 +726,9 @@ define i16 @test_atomic_load_max_i16(i16 signext %offset) nounwind {
 ; CHECK-NEXT: sxth r[[OLDX:[0-9]+]], r[[OLD]]
   ; r0 below is a reasonable guess but could change: it certainly comes into the
   ;  function there.
-; CHECK-NEXT: cmp r[[OLDX]], r0
+; CHECK-NEXT: cmp {{r[0-9]+}}, r{{[0-9]+}}
 ; Thumb mode: it gt
-; CHECK:      movgt r[[OLDX]], r[[OLD]]
+; CHECK:      mov{{gt|lt}} r[[OLDX]], r[[OLD]]
 ; CHECK-NEXT: strexh [[STATUS:r[0-9]+]], r[[OLDX]], [r[[ADDR]]]
 ; CHECK-NEXT: cmp [[STATUS]], #0
 ; CHECK-NEXT: bne .LBB{{[0-9]+}}_1
diff --git a/llvm/test/CodeGen/ARM/cmp-peephole.ll b/llvm/test/CodeGen/ARM/cmp-peephole.ll
index 73888558e6647..b58243ad7837f 100644
--- a/llvm/test/CodeGen/ARM/cmp-peephole.ll
+++ b/llvm/test/CodeGen/ARM/cmp-peephole.ll
@@ -1718,7 +1718,7 @@ define void @br_on_shift_eq_reg(i32 %a, i32 %b, i32 %c) {
 ; THUMB-NEXT:    push {r7, lr}
 ; THUMB-NEXT:    mov r3, r0
 ; THUMB-NEXT:    asrs r3, r1
-; THUMB-NEXT:    cmp r2, r3
+; THUMB-NEXT:    cmp r3, r2
 ; THUMB-NEXT:    bne .LBB56_2
 ; THUMB-NEXT:  @ %bb.1: @ %true_br
 ; THUMB-NEXT:    bl consume
@@ -1762,7 +1762,7 @@ define void @br_on_shift_ne_reg(i32 %a, i32 %b, i32 %c) {
 ; THUMB-NEXT:    push {r7, lr}
 ; THUMB-NEXT:    mov r3, r0
 ; THUMB-NEXT:    lsls r3, r1
-; THUMB-NEXT:    cmp r2, r3
+; THUMB-NEXT:    cmp r3, r2
 ; THUMB-NEXT:    beq .LBB57_2
 ; THUMB-NEXT:  @ %bb.1: @ %true_br
 ; THUMB-NEXT:    bl consume
diff --git a/llvm/test/CodeGen/ARM/ssat.ll b/llvm/test/CodeGen/ARM/ssat.ll
index ed777f2b1882b..7b689a8bea140 100644
--- a/llvm/test/CodeGen/ARM/ssat.ll
+++ b/llvm/test/CodeGen/ARM/ssat.ll
@@ -52,9 +52,8 @@ define i16 @sat_base_16bit(i16 %x) #0 {
 ; V4T-NEXT:    mov r2, #255
 ; V4T-NEXT:    lsl r1, r0, #16
 ; V4T-NEXT:    orr r2, r2, #1792
-; V4T-NEXT:    asr r1, r1, #16
-; V4T-NEXT:    cmp r1, r2
-; V4T-NEXT:    movge r0, r2
+; V4T-NEXT:    cmp r2, r1, asr #16
+; V4T-NEXT:    movle r0, r2
 ; V4T-NEXT:    ldr r2, .LCPI1_0
 ; V4T-NEXT:    lsl r1, r0, #16
 ; V4T-NEXT:    asr r1, r1, #16
@@ -70,8 +69,8 @@ define i16 @sat_base_16bit(i16 %x) #0 {
 ; V6T2:       @ %bb.0: @ %entry
 ; V6T2-NEXT:    sxth r1, r0
 ; V6T2-NEXT:    movw r2, #2047
-; V6T2-NEXT:    cmp r1, r2
-; V6T2-NEXT:    movge r0, r2
+; V6T2-NEXT:    cmp r2, r1
+; V6T2-NEXT:    movle r0, r2
 ; V6T2-NEXT:    movw r2, #63488
 ; V6T2-NEXT:    sxth r1, r0
 ; V6T2-NEXT:    movt r2, #65535
diff --git a/llvm/test/CodeGen/ARM/umulo-32.ll b/llvm/test/CodeGen/ARM/umulo-32.ll
index b5f6b3aa61fc3..cd0d784c89818 100644
--- a/llvm/test/CodeGen/ARM/umulo-32.ll
+++ b/llvm/test/CodeGen/ARM/umulo-32.ll
@@ -34,7 +34,7 @@ define i32 @test2(ptr %m_degree) ssp {
 ; CHECK-NEXT:    ldr r1, [r0]
 ; CHECK-NEXT:    lsls r0, r1, #3
 ; CHECK-NEXT:    lsrs r2, r0, #3
-; CHECK-NEXT:    subs r1, r1, r2
+; CHECK-NEXT:    subs r1, r2, r1
 ; CHECK-NEXT:    subs r2, r1, #1
 ; CHECK-NEXT:    sbcs r1, r2
 ; CHECK-NEXT:    movs r4, #0
diff --git a/llvm/test/CodeGen/ARM/usat.ll b/llvm/test/CodeGen/ARM/usat.ll
index 2e1d0283ebde2..3cc34629b3932 100644
--- a/llvm/test/CodeGen/ARM/usat.ll
+++ b/llvm/test/CodeGen/ARM/usat.ll
@@ -66,9 +66,8 @@ define i16 @unsigned_sat_base_16bit(i16 %x) #0 {
 ; V4T-NEXT:    mov r2, #255
 ; V4T-NEXT:    lsl r1, r0, #16
 ; V4T-NEXT:    orr r2, r2, #1792
-; V4T-NEXT:    asr r1, r1, #16
-; V4T-NEXT:    cmp r1, r2
-; V4T-NEXT:    movlt r2, r0
+; V4T-NEXT:    cmp r2, r1, asr #16
+; V4T-NEXT:    movgt r2, r0
 ; V4T-NEXT:    lsl r0, r2, #16
 ; V4T-NEXT:    bic r0, r2, r0, asr #31
 ; V4T-NEXT:    bx lr
@@ -78,8 +77,8 @@ define i16 @unsigned_sat_base_16bit(i16 %x) #0 {
 ; V6-NEXT:    mov r2, #255
 ; V6-NEXT:    sxth r1, r0
 ; V6-NEXT:    orr r2, r2, #1792
-; V6-NEXT:    cmp r1, r2
-; V6-NEXT:    movlt r2, r0
+; V6-NEXT:    cmp r2, r1
+; V6-NEXT:    movgt r2, r0
 ; V6-NEXT:    sxth r0, r2
 ; V6-NEXT:    bic r0, r2, r0, asr #15
 ; V6-NEXT:    bx lr
@@ -88,8 +87,8 @@ define i16 @unsigned_sat_base_16bit(i16 %x) #0 {
 ; V6T2:       @ %bb.0: @ %entry
 ; V6T2-NEXT:    sxth r1, r0
 ; V6T2-NEXT:    movw r2, #2047
-; V6T2-NEXT:    cmp r1, r2
-; V6T2-NEXT:    movlt r2, r0
+; V6T2-NEXT:    cmp r2, r1
+; V6T2-NEXT:    movgt r2, r0
 ; V6T2-NEXT:    sxth r0, r2
 ; V6T2-NEXT:    bic r0, r2, r0, asr #15
 ; V6T2-NEXT:    bx lr
diff --git a/llvm/test/CodeGen/Thumb2/LowOverheadLoops/reductions.ll b/llvm/test/CodeGen/Thumb2/LowOverheadLoops/reductions.ll
index c418038b751d7..5159306fb23d0 100644
--- a/llvm/test/CodeGen/Thumb2/LowOverheadLoops/reductions.ll
+++ b/llvm/test/CodeGen/Thumb2/LowOverheadLoops/reductions.ll
@@ -642,14 +642,12 @@ define i32 @wrongop(ptr nocapture readonly %pd) {
 ; CHECK-NEXT:    movw r4, #23593
 ; CHECK-NEXT:    movt r1, #163
 ; CHECK-NEXT:    ldr r0, [r0]
-; CHECK-NEXT:    movt r4, #655
-; CHECK-NEXT:    ror.w r12, r3, #4
-; CHECK-NEXT:    cmp r12, r1
-; CHECK-NEXT:    cset r1, lo
-; CHECK-NEXT:    ror.w r3, r3, #2
 ; CHECK-NEXT:    mov.w r12, #1
-; CHECK-NEXT:    cmp r3, r4
-; CHECK-NEXT:    csel r3, r1, r12, lo
+; CHECK-NEXT:    movt r4, #655
+; CHECK-NEXT:    cmp.w r1, r3, ror #4
+; CHECK-NEXT:    cset r1, hi
+; CHECK-NEXT:    cmp.w r4, r3, ror #2
+; CHECK-NEXT:    csel r3, r1, r12, hi
 ; CHECK-NEXT:    lsls.w r4, lr, #30
 ; CHECK-NEXT:    csel r1, r1, r3, ne
 ; CHECK-NEXT:    cmp r2, #1



More information about the llvm-commits mailing list