[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