[llvm] [AArch64] Use CMN instruction for negative SUBS immediates (PR #221398)

Mugundan S via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 5 00:05:41 PDT 2026


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

>From b32d31b862d8bb0d1ce0d1308c3be21ec2ac2ef3 Mon Sep 17 00:00:00 2001
From: Mugundan S <smugundan12a at gmail.com>
Date: Sat, 5 Sep 2026 11:05:18 +0530
Subject: [PATCH 1/5] [AArch64] Use CMN for negative SUBS immediates

---
 .../Target/AArch64/AArch64ISelLowering.cpp    | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 71e29b6852772..b553e31580f28 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -4218,6 +4218,17 @@ static bool canEmitConjunction(SelectionDAG &DAG, const SDValue Val,
     // of a SUB operation can be reused.
     PreferFirst = DAG.doesNodeExist(ISD::SUB, DAG.getVTList(VT),
                                     {Val->getOperand(0), Val->getOperand(1)});
+    // The first comparison supports the full 12-bit CMP/CMN immediate range,
+    // while subsequent CCMP/CCMN comparisons only support a 5-bit immediate [0, 31].
+    // Prefer a wider-immediate comparison first to avoid materializing constants.
+    if (!PreferFirst && VT.isInteger()) {
+      if (auto *C = dyn_cast<ConstantSDNode>(Val->getOperand(1))) {
+        APInt CVal = C->getAPIntValue();
+        if (CVal.abs().ugt(31) &&
+            AArch64_AM::isLegalArithImmed(CVal.abs().getZExtValue()))
+          PreferFirst = true;
+      }
+    }
     return true;
   }
   // Protect against exponential runtime and stack overflow.
@@ -21996,6 +22007,28 @@ static SDValue performANDORCSELCombine(SDNode *N, SelectionDAG &DAG) {
     return SDValue();
 
   SDLoc DL(N);
+  // If the opcode is SUBS and the comparison value is negative, transform
+  // it to ADDS (CMN) when abs(C) - 1 is a legal arithmetic immediate,
+  // avoiding materialization of the constant into a register.
+  if (Cmp0.getOpcode() == AArch64ISD::SUBS) {
+    if (auto *C0 = dyn_cast<ConstantSDNode>(Cmp0.getOperand(1))) {
+      APInt C0Val = C0->getAPIntValue();
+      if (C0Val.isNegative() && C0Val.abs().ugt(31) &&
+          (CC0 == AArch64CC::LE || CC0 == AArch64CC::GT)) {
+        APInt AbsC0Minus1 = C0Val.abs() - 1;
+        if (AArch64_AM::isLegalArithImmed(AbsC0Minus1.getZExtValue())) {
+          SDValue NewImm =
+              DAG.getConstant(AbsC0Minus1, DL, C0->getValueType(0));
+          Cmp0 = DAG.getNode(AArch64ISD::ADDS, DL,
+                             DAG.getVTList(Cmp0.getOperand(0).getValueType(),
+                                           FlagsVT),
+                             Cmp0.getOperand(0), NewImm)
+                     .getValue(1);
+          CC0 = (CC0 == AArch64CC::LE) ? AArch64CC::LT : AArch64CC::GE;
+        }
+      }
+    }
+  }
   SDValue CCmp, Condition;
   unsigned NZCV;
 

>From e46ed993e81ca33b0c44c08f2e991e035c551ee3 Mon Sep 17 00:00:00 2001
From: Mugundan S <smugundan12a at gmail.com>
Date: Sat, 5 Sep 2026 11:12:51 +0530
Subject: [PATCH 2/5] Add llvm test case for testing CMN optimization

---
 .../CodeGen/AArch64/aarch64-test-cmn-opt.ll   | 104 ++++++++++++++++++
 1 file changed, 104 insertions(+)
 create mode 100644 llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll

diff --git a/llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll b/llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll
new file mode 100644
index 0000000000000..66b2ef86e5f5f
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll
@@ -0,0 +1,104 @@
+; RUN: llc -mtriple=aarch64-linux-gnu -verify-machineinstrs < %s | FileCheck %s
+
+; This test file validates the CMN optimization for negative SUBS immediates. 
+; Example: if (b > -34 && ...) should emit CMN w1, #34.
+
+; Test: negative constant -34
+define i32 @test_neg34(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: test_neg34:
+; CHECK:       cmn w1, #34
+; CHECK:       ccmp w0, w2, #0, gt
+; CHECK:       csel w0, w1, w0, lt
+; CHECK:       ret
+  %cmp1 = icmp sgt i32 %b, -34
+  %cmp2 = icmp slt i32 %a, %c
+  %and = and i1 %cmp1, %cmp2
+  %res = select i1 %and, i32 %b, i32 %a
+  ret i32 %res
+}
+
+
+; Test: boundary around CCMN immediate range
+; -31 should not require the special transformation.
+define i32 @test_neg31(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: test_neg31:
+; CHECK:       ccmp
+; CHECK:       ret
+  %cmp1 = icmp sgt i32 %b, -31
+  %cmp2 = icmp slt i32 %a, %c
+  %and = and i1 %cmp1, %cmp2
+  %res = select i1 %and, i32 %b, i32 %a
+  ret i32 %res
+}
+
+
+; Test: -32
+define i32 @test_neg32(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: test_neg32:
+; CHECK:       ccmp
+; CHECK:       ret
+  %cmp1 = icmp sgt i32 %b, -32
+  %cmp2 = icmp slt i32 %a, %c
+  %and = and i1 %cmp1, %cmp2
+  %res = select i1 %and, i32 %b, i32 %a
+  ret i32 %res
+}
+
+
+; Test: -33
+; This is the first value outside the CCMN immediate range.
+define i32 @test_neg33(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: test_neg33:
+; CHECK:       cmn w1, #33
+; CHECK:       ccmp
+; CHECK:       ret
+  %cmp1 = icmp sgt i32 %b, -33
+  %cmp2 = icmp slt i32 %a, %c
+  %and = and i1 %cmp1, %cmp2
+  %res = select i1 %and, i32 %b, i32 %a
+  ret i32 %res
+}
+
+
+; Test: -4095
+; Maximum value representable by a 12-bit arithmetic immediate.
+define i32 @test_neg4095(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: test_neg4095:
+; CHECK:       cmn w1, #4095
+; CHECK:       ccmp
+; CHECK:       ret
+  %cmp1 = icmp sgt i32 %b, -4095
+  %cmp2 = icmp slt i32 %a, %c
+  %and = and i1 %cmp1, %cmp2
+  %res = select i1 %and, i32 %b, i32 %a
+  ret i32 %res
+}
+
+
+; Test: -4096
+; Should not use plain CMN #4096 because it is outside
+; the 12-bit immediate range.
+define i32 @test_neg4096(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: test_neg4096:
+; CHECK-NOT:   cmn w1, #4096
+; CHECK:       ret
+  %cmp1 = icmp sgt i32 %b, -4096
+  %cmp2 = icmp slt i32 %a, %c
+  %and = and i1 %cmp1, %cmp2
+  %res = select i1 %and, i32 %b, i32 %a
+  ret i32 %res
+}
+
+
+; Test: positive constant.
+; The CMN transformation should not apply.
+define i32 @test_positive(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: test_positive:
+; CHECK-NOT:   cmn
+; CHECK:       ret
+  %cmp1 = icmp sgt i32 %b, 34
+  %cmp2 = icmp slt i32 %a, %c
+  %and = and i1 %cmp1, %cmp2
+  %res = select i1 %and, i32 %b, i32 %a
+  ret i32 %res
+}

>From 4835408499007394c89b86c9ca7bc228bac794a0 Mon Sep 17 00:00:00 2001
From: Mugundan S <smugundan12a at gmail.com>
Date: Sat, 5 Sep 2026 11:50:06 +0530
Subject: [PATCH 3/5] Fix clang formatting

---
 llvm/lib/Target/AArch64/AArch64ISelLowering.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index b553e31580f28..4e333c7bd6b16 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -4219,8 +4219,9 @@ static bool canEmitConjunction(SelectionDAG &DAG, const SDValue Val,
     PreferFirst = DAG.doesNodeExist(ISD::SUB, DAG.getVTList(VT),
                                     {Val->getOperand(0), Val->getOperand(1)});
     // The first comparison supports the full 12-bit CMP/CMN immediate range,
-    // while subsequent CCMP/CCMN comparisons only support a 5-bit immediate [0, 31].
-    // Prefer a wider-immediate comparison first to avoid materializing constants.
+    // while subsequent CCMP/CCMN comparisons only support a 5-bit immediate [0,
+    // 31]. Prefer a wider-immediate comparison first to avoid materializing
+    // constants.
     if (!PreferFirst && VT.isInteger()) {
       if (auto *C = dyn_cast<ConstantSDNode>(Val->getOperand(1))) {
         APInt CVal = C->getAPIntValue();

>From c1d9662480cde6e583c11974566bc8555927b01e Mon Sep 17 00:00:00 2001
From: Mugundan S <smugundan12a at gmail.com>
Date: Sat, 5 Sep 2026 12:33:02 +0530
Subject: [PATCH 4/5] Fix test case

---
 .../CodeGen/AArch64/aarch64-test-cmn-opt.ll   | 58 ++++++++++++-------
 1 file changed, 37 insertions(+), 21 deletions(-)

diff --git a/llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll b/llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll
index 66b2ef86e5f5f..e093faf23aead 100644
--- a/llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll
+++ b/llvm/test/CodeGen/AArch64/aarch64-test-cmn-opt.ll
@@ -1,15 +1,17 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
 ; RUN: llc -mtriple=aarch64-linux-gnu -verify-machineinstrs < %s | FileCheck %s
 
-; This test file validates the CMN optimization for negative SUBS immediates. 
+; This test file validates the CMN optimization for negative SUBS immediates.
 ; Example: if (b > -34 && ...) should emit CMN w1, #34.
 
 ; Test: negative constant -34
 define i32 @test_neg34(i32 %a, i32 %b, i32 %c) {
 ; CHECK-LABEL: test_neg34:
-; CHECK:       cmn w1, #34
-; CHECK:       ccmp w0, w2, #0, gt
-; CHECK:       csel w0, w1, w0, lt
-; CHECK:       ret
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn w1, #34
+; CHECK-NEXT:    ccmp w0, w2, #0, gt
+; CHECK-NEXT:    csel w0, w1, w0, lt
+; CHECK-NEXT:    ret
   %cmp1 = icmp sgt i32 %b, -34
   %cmp2 = icmp slt i32 %a, %c
   %and = and i1 %cmp1, %cmp2
@@ -22,8 +24,11 @@ define i32 @test_neg34(i32 %a, i32 %b, i32 %c) {
 ; -31 should not require the special transformation.
 define i32 @test_neg31(i32 %a, i32 %b, i32 %c) {
 ; CHECK-LABEL: test_neg31:
-; CHECK:       ccmp
-; CHECK:       ret
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmp w0, w2
+; CHECK-NEXT:    ccmn w1, #31, #4, lt
+; CHECK-NEXT:    csel w0, w1, w0, gt
+; CHECK-NEXT:    ret
   %cmp1 = icmp sgt i32 %b, -31
   %cmp2 = icmp slt i32 %a, %c
   %and = and i1 %cmp1, %cmp2
@@ -35,8 +40,11 @@ define i32 @test_neg31(i32 %a, i32 %b, i32 %c) {
 ; Test: -32
 define i32 @test_neg32(i32 %a, i32 %b, i32 %c) {
 ; CHECK-LABEL: test_neg32:
-; CHECK:       ccmp
-; CHECK:       ret
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn w1, #32
+; CHECK-NEXT:    ccmp w0, w2, #0, gt
+; CHECK-NEXT:    csel w0, w1, w0, lt
+; CHECK-NEXT:    ret
   %cmp1 = icmp sgt i32 %b, -32
   %cmp2 = icmp slt i32 %a, %c
   %and = and i1 %cmp1, %cmp2
@@ -49,9 +57,11 @@ define i32 @test_neg32(i32 %a, i32 %b, i32 %c) {
 ; This is the first value outside the CCMN immediate range.
 define i32 @test_neg33(i32 %a, i32 %b, i32 %c) {
 ; CHECK-LABEL: test_neg33:
-; CHECK:       cmn w1, #33
-; CHECK:       ccmp
-; CHECK:       ret
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn w1, #33
+; CHECK-NEXT:    ccmp w0, w2, #0, gt
+; CHECK-NEXT:    csel w0, w1, w0, lt
+; CHECK-NEXT:    ret
   %cmp1 = icmp sgt i32 %b, -33
   %cmp2 = icmp slt i32 %a, %c
   %and = and i1 %cmp1, %cmp2
@@ -64,9 +74,11 @@ define i32 @test_neg33(i32 %a, i32 %b, i32 %c) {
 ; Maximum value representable by a 12-bit arithmetic immediate.
 define i32 @test_neg4095(i32 %a, i32 %b, i32 %c) {
 ; CHECK-LABEL: test_neg4095:
-; CHECK:       cmn w1, #4095
-; CHECK:       ccmp
-; CHECK:       ret
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn w1, #4095
+; CHECK-NEXT:    ccmp w0, w2, #0, gt
+; CHECK-NEXT:    csel w0, w1, w0, lt
+; CHECK-NEXT:    ret
   %cmp1 = icmp sgt i32 %b, -4095
   %cmp2 = icmp slt i32 %a, %c
   %and = and i1 %cmp1, %cmp2
@@ -76,12 +88,13 @@ define i32 @test_neg4095(i32 %a, i32 %b, i32 %c) {
 
 
 ; Test: -4096
-; Should not use plain CMN #4096 because it is outside
-; the 12-bit immediate range.
 define i32 @test_neg4096(i32 %a, i32 %b, i32 %c) {
 ; CHECK-LABEL: test_neg4096:
-; CHECK-NOT:   cmn w1, #4096
-; CHECK:       ret
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmn w1, #1, lsl #12 // =4096
+; CHECK-NEXT:    ccmp w0, w2, #0, gt
+; CHECK-NEXT:    csel w0, w1, w0, lt
+; CHECK-NEXT:    ret
   %cmp1 = icmp sgt i32 %b, -4096
   %cmp2 = icmp slt i32 %a, %c
   %and = and i1 %cmp1, %cmp2
@@ -94,8 +107,11 @@ define i32 @test_neg4096(i32 %a, i32 %b, i32 %c) {
 ; The CMN transformation should not apply.
 define i32 @test_positive(i32 %a, i32 %b, i32 %c) {
 ; CHECK-LABEL: test_positive:
-; CHECK-NOT:   cmn
-; CHECK:       ret
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmp w1, #34
+; CHECK-NEXT:    ccmp w0, w2, #0, gt
+; CHECK-NEXT:    csel w0, w1, w0, lt
+; CHECK-NEXT:    ret
   %cmp1 = icmp sgt i32 %b, 34
   %cmp2 = icmp slt i32 %a, %c
   %and = and i1 %cmp1, %cmp2

>From a7280e2930cf24f4de90853ae53cffe2fc91f1dd Mon Sep 17 00:00:00 2001
From: Mugundan S <smugundan12a at gmail.com>
Date: Sat, 5 Sep 2026 12:35:31 +0530
Subject: [PATCH 5/5] Fix the test case for cmn

---
 llvm/test/CodeGen/AArch64/arm64-ccmp.ll | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/test/CodeGen/AArch64/arm64-ccmp.ll b/llvm/test/CodeGen/AArch64/arm64-ccmp.ll
index 54d05c581bf2c..67610a58697b9 100644
--- a/llvm/test/CodeGen/AArch64/arm64-ccmp.ll
+++ b/llvm/test/CodeGen/AArch64/arm64-ccmp.ll
@@ -625,10 +625,9 @@ define i32 @select_andor(i32 %v1, i32 %v2, i32 %v3) {
 define i32 @select_andor32(i32 %v1, i32 %v2, i32 %v3) {
 ; CHECK-SD-LABEL: select_andor32:
 ; CHECK-SD:       ; %bb.0:
-; CHECK-SD-NEXT:    cmp w1, w2
-; CHECK-SD-NEXT:    mov w8, #32 ; =0x20
-; CHECK-SD-NEXT:    ccmp w0, w8, #4, lt
-; CHECK-SD-NEXT:    ccmp w0, w1, #0, eq
+; CHECK-SD-NEXT:    cmp w0, #32
+; CHECK-SD-NEXT:    ccmp w1, w2, #0, ne
+; CHECK-SD-NEXT:    ccmp w0, w1, #0, ge
 ; CHECK-SD-NEXT:    csel w0, w0, w1, eq
 ; CHECK-SD-NEXT:    ret
 ;
@@ -1258,3 +1257,4 @@ define i1 @cmp_or_negative_const(i32 %a, i32 %b) {
   ret i1 %or.cond
 }
 attributes #0 = { nounwind }
+



More information about the llvm-commits mailing list