[llvm] [AArch64] fuse constant addition after sbb (PR #185117)
Takashi Idobe via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 9 18:51:15 PDT 2026
https://github.com/Takashiidobe updated https://github.com/llvm/llvm-project/pull/185117
>From cab53e68763f99b7bc1619904a89076cd42a84bb Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Fri, 6 Mar 2026 16:48:02 -0500
Subject: [PATCH 1/6] fuse constant addition after sbc for arm only
---
.../Target/AArch64/AArch64ISelLowering.cpp | 33 ++++++++++++++++++-
1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index cd9de6c729649..5ed109b298e08 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -23346,6 +23346,36 @@ static SDValue performAddTruncShiftCombine(SDNode *N, SelectionDAG &DAG) {
return DAG.getNode(ISD::ADD, DL, VT, Trunc, Shift);
}
+// Fold ADD(SBC(Y, 0, W), C) -> SBC(Y, -C, W)
+// SBC(Y, 0, W) = Y - 0 - ~carry = Y + carry - 1
+// Adding C: Y + carry - 1 + C = Y - (-C) - ~carry = SBC(Y, -C, W)
+static SDValue performAddWithSBCCombine(SDNode *N, SelectionDAG &DAG) {
+ if (N->getOpcode() != ISD::ADD)
+ return SDValue();
+ EVT VT = N->getValueType(0);
+ if (VT != MVT::i32 && VT != MVT::i64)
+ return SDValue();
+
+ SDValue SBC = N->getOperand(0);
+ SDValue C = N->getOperand(1);
+ // ADD is commutative; constant may be on either side.
+ if (SBC.getOpcode() != AArch64ISD::SBC)
+ std::swap(SBC, C);
+ if (SBC.getOpcode() != AArch64ISD::SBC || !SBC.hasOneUse())
+ return SDValue();
+ if (!isNullConstant(SBC.getOperand(1)))
+ return SDValue();
+ // AArch64 SBC (non-flag-setting) has only one output; no flags guard needed.
+ auto *CC = dyn_cast<ConstantSDNode>(C);
+ if (!CC)
+ return SDValue();
+
+ SDLoc DL(N);
+ return DAG.getNode(AArch64ISD::SBC, DL, VT, SBC.getOperand(0),
+ DAG.getConstant(-CC->getAPIntValue(), DL, VT),
+ SBC.getOperand(2));
+}
+
static SDValue performAddSubCombine(SDNode *N,
TargetLowering::DAGCombinerInfo &DCI) {
// Try to change sum of two reductions.
@@ -23371,7 +23401,8 @@ static SDValue performAddSubCombine(SDNode *N,
return Val;
if (SDValue Val = performAddTruncShiftCombine(N, DCI.DAG))
return Val;
-
+ if (SDValue Val = performAddWithSBCCombine(N, DCI.DAG))
+ return Val;
if (SDValue Val = performExtBinopLoadFold(N, DCI.DAG))
return Val;
>From a85776c33b655e645077bb84476510a11b6fcc1a Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Sat, 7 Mar 2026 10:08:13 -0500
Subject: [PATCH 2/6] add positive and negative tests for SBC fold on arm64
---
llvm/test/CodeGen/AArch64/sbc-add-constant.ll | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 llvm/test/CodeGen/AArch64/sbc-add-constant.ll
diff --git a/llvm/test/CodeGen/AArch64/sbc-add-constant.ll b/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
new file mode 100644
index 0000000000000..016091bbf28e7
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
@@ -0,0 +1,60 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu < %s | FileCheck %s
+;
+; Verify that ADD(SBB(Y,0,flags),C) folds to SBB(Y,-C,flags).
+; SBB(Y,0) = Y - CF; adding C gives Y - CF + C = Y - (-C) - CF = SBB(Y,-C).
+
+declare {i64, i1} @llvm.usub.with.overflow.i64(i64, i64)
+
+; Fold should fire, adding with constant
+define i64 @g_i64(i64 %a, i64 %b) nounwind {
+; CHECK-LABEL: g_i64:
+; CHECK: // %bb.0:
+; CHECK-NEXT: mov x8, #-10 // =0xfffffffffffffff6
+; CHECK-NEXT: subs x9, x0, x1
+; CHECK-NEXT: sbc x0, x9, x8
+; CHECK-NEXT: ret
+ %ov = call {i64, i1} @llvm.usub.with.overflow.i64(i64 %a, i64 %b)
+ %val = extractvalue { i64, i1 } %ov, 0
+ %bit = extractvalue { i64, i1 } %ov, 1
+ %ext = sext i1 %bit to i64
+ %r = add i64 %val, %ext
+ %r2 = add i64 %r, 10
+ ret i64 %r2
+}
+
+; Non-constant addend should not generate the fold
+define i64 @g_nonconstant(i64 %a, i64 %b, i64 %c) nounwind {
+; CHECK-LABEL: g_nonconstant:
+; CHECK: // %bb.0:
+; CHECK-NEXT: subs x8, x0, x1
+; CHECK-NEXT: sbc x8, x8, xzr
+; CHECK-NEXT: add x0, x8, x2
+; CHECK-NEXT: ret
+ %ov = call {i64, i1} @llvm.usub.with.overflow.i64(i64 %a, i64 %b)
+ %val = extractvalue { i64, i1 } %ov, 0
+ %bit = extractvalue { i64, i1 } %ov, 1
+ %ext = sext i1 %bit to i64
+ %r = add i64 %val, %ext
+ %r2 = add i64 %r, %c
+ ret i64 %r2
+}
+
+; Multiple uses of SBC result should not generate the fold
+define i64 @g_multi_use(i64 %a, i64 %b, ptr %out) nounwind {
+; CHECK-LABEL: g_multi_use:
+; CHECK: // %bb.0:
+; CHECK-NEXT: subs x8, x0, x1
+; CHECK-NEXT: sbc x8, x8, xzr
+; CHECK-NEXT: add x0, x8, #10
+; CHECK-NEXT: str x8, [x2]
+; CHECK-NEXT: ret
+ %ov = call {i64, i1} @llvm.usub.with.overflow.i64(i64 %a, i64 %b)
+ %val = extractvalue { i64, i1 } %ov, 0
+ %bit = extractvalue { i64, i1 } %ov, 1
+ %ext = sext i1 %bit to i64
+ %sbc = add i64 %val, %ext
+ store i64 %sbc, ptr %out
+ %r = add i64 %sbc, 10
+ ret i64 %r
+}
>From b8d3c941777198e9a10228bec91292b332ff47a4 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Mon, 9 Mar 2026 09:09:31 -0400
Subject: [PATCH 3/6] code review feedback: relax constant in fold to be
non-const to allow for earlier optimization of C in fold
---
llvm/lib/Target/AArch64/AArch64ISelLowering.cpp | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index 5ed109b298e08..e4a7e48e3f9ab 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -23358,7 +23358,7 @@ static SDValue performAddWithSBCCombine(SDNode *N, SelectionDAG &DAG) {
SDValue SBC = N->getOperand(0);
SDValue C = N->getOperand(1);
- // ADD is commutative; constant may be on either side.
+ // ADD is commutative; operands may be on either side.
if (SBC.getOpcode() != AArch64ISD::SBC)
std::swap(SBC, C);
if (SBC.getOpcode() != AArch64ISD::SBC || !SBC.hasOneUse())
@@ -23366,13 +23366,9 @@ static SDValue performAddWithSBCCombine(SDNode *N, SelectionDAG &DAG) {
if (!isNullConstant(SBC.getOperand(1)))
return SDValue();
// AArch64 SBC (non-flag-setting) has only one output; no flags guard needed.
- auto *CC = dyn_cast<ConstantSDNode>(C);
- if (!CC)
- return SDValue();
-
SDLoc DL(N);
return DAG.getNode(AArch64ISD::SBC, DL, VT, SBC.getOperand(0),
- DAG.getConstant(-CC->getAPIntValue(), DL, VT),
+ DAG.getNegative(C, DL, VT),
SBC.getOperand(2));
}
>From 98875f1e5d62d17a68ac515a891acf480cfa55a5 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Mon, 9 Mar 2026 09:13:02 -0400
Subject: [PATCH 4/6] code review feedback: relax testing when operand is
non-constant and also add exercise swap check in impl
---
llvm/test/CodeGen/AArch64/sbc-add-constant.ll | 96 ++++++++++++++++++-
1 file changed, 92 insertions(+), 4 deletions(-)
diff --git a/llvm/test/CodeGen/AArch64/sbc-add-constant.ll b/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
index 016091bbf28e7..799e78ce51dfc 100644
--- a/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
+++ b/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
@@ -5,6 +5,7 @@
; SBB(Y,0) = Y - CF; adding C gives Y - CF + C = Y - (-C) - CF = SBB(Y,-C).
declare {i64, i1} @llvm.usub.with.overflow.i64(i64, i64)
+declare {i32, i1} @llvm.usub.with.overflow.i32(i32, i32)
; Fold should fire, adding with constant
define i64 @g_i64(i64 %a, i64 %b) nounwind {
@@ -23,13 +24,30 @@ define i64 @g_i64(i64 %a, i64 %b) nounwind {
ret i64 %r2
}
-; Non-constant addend should not generate the fold
+; Fold should fire, adding with constant
+define i32 @g_i32(i32 %a, i32 %b) nounwind {
+; CHECK-LABEL: g_i32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: mov w8, #-10 // =0xfffffff6
+; CHECK-NEXT: subs w9, w0, w1
+; CHECK-NEXT: sbc w0, w9, w8
+; CHECK-NEXT: ret
+ %ov = call {i32, i1} @llvm.usub.with.overflow.i32(i32 %a, i32 %b)
+ %val = extractvalue { i32, i1 } %ov, 0
+ %bit = extractvalue { i32, i1 } %ov, 1
+ %ext = sext i1 %bit to i32
+ %r = add i32 %val, %ext
+ %r2 = add i32 %r, 10
+ ret i32 %r2
+}
+
+; Fold should fire for non-constant addend too
define i64 @g_nonconstant(i64 %a, i64 %b, i64 %c) nounwind {
; CHECK-LABEL: g_nonconstant:
; CHECK: // %bb.0:
-; CHECK-NEXT: subs x8, x0, x1
-; CHECK-NEXT: sbc x8, x8, xzr
-; CHECK-NEXT: add x0, x8, x2
+; CHECK-NEXT: neg x8, x2
+; CHECK-NEXT: subs x9, x0, x1
+; CHECK-NEXT: sbc x0, x9, x8
; CHECK-NEXT: ret
%ov = call {i64, i1} @llvm.usub.with.overflow.i64(i64 %a, i64 %b)
%val = extractvalue { i64, i1 } %ov, 0
@@ -40,6 +58,57 @@ define i64 @g_nonconstant(i64 %a, i64 %b, i64 %c) nounwind {
ret i64 %r2
}
+; Fold should fire for non-constant addend too
+define i32 @g_nonconstant_i32(i32 %a, i32 %b, i32 %c) nounwind {
+; CHECK-LABEL: g_nonconstant_i32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: neg w8, w2
+; CHECK-NEXT: subs w9, w0, w1
+; CHECK-NEXT: sbc w0, w9, w8
+; CHECK-NEXT: ret
+ %ov = call {i32, i1} @llvm.usub.with.overflow.i32(i32 %a, i32 %b)
+ %val = extractvalue { i32, i1 } %ov, 0
+ %bit = extractvalue { i32, i1 } %ov, 1
+ %ext = sext i1 %bit to i32
+ %r = add i32 %val, %ext
+ %r2 = add i32 %r, %c
+ ret i32 %r2
+}
+
+; Fold should fire for non-constant addend in commuted form too
+define i64 @g_nonconstant_commuted(i64 %a, i64 %b, i64 %c) nounwind {
+; CHECK-LABEL: g_nonconstant_commuted:
+; CHECK: // %bb.0:
+; CHECK-NEXT: neg x8, x2
+; CHECK-NEXT: subs x9, x0, x1
+; CHECK-NEXT: sbc x0, x9, x8
+; CHECK-NEXT: ret
+ %ov = call {i64, i1} @llvm.usub.with.overflow.i64(i64 %a, i64 %b)
+ %val = extractvalue { i64, i1 } %ov, 0
+ %bit = extractvalue { i64, i1 } %ov, 1
+ %ext = sext i1 %bit to i64
+ %r = add i64 %val, %ext
+ %r2 = add i64 %c, %r
+ ret i64 %r2
+}
+
+; Fold should fire for non-constant addend in commuted form too
+define i32 @g_nonconstant_commuted_i32(i32 %a, i32 %b, i32 %c) nounwind {
+; CHECK-LABEL: g_nonconstant_commuted_i32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: neg w8, w2
+; CHECK-NEXT: subs w9, w0, w1
+; CHECK-NEXT: sbc w0, w9, w8
+; CHECK-NEXT: ret
+ %ov = call {i32, i1} @llvm.usub.with.overflow.i32(i32 %a, i32 %b)
+ %val = extractvalue { i32, i1 } %ov, 0
+ %bit = extractvalue { i32, i1 } %ov, 1
+ %ext = sext i1 %bit to i32
+ %r = add i32 %val, %ext
+ %r2 = add i32 %c, %r
+ ret i32 %r2
+}
+
; Multiple uses of SBC result should not generate the fold
define i64 @g_multi_use(i64 %a, i64 %b, ptr %out) nounwind {
; CHECK-LABEL: g_multi_use:
@@ -58,3 +127,22 @@ define i64 @g_multi_use(i64 %a, i64 %b, ptr %out) nounwind {
%r = add i64 %sbc, 10
ret i64 %r
}
+
+; Multiple uses of SBC result should not generate the fold
+define i32 @g_multi_use_i32(i32 %a, i32 %b, ptr %out) nounwind {
+; CHECK-LABEL: g_multi_use_i32:
+; CHECK: // %bb.0:
+; CHECK-NEXT: subs w8, w0, w1
+; CHECK-NEXT: sbc w8, w8, wzr
+; CHECK-NEXT: add w0, w8, #10
+; CHECK-NEXT: str w8, [x2]
+; CHECK-NEXT: ret
+ %ov = call {i32, i1} @llvm.usub.with.overflow.i32(i32 %a, i32 %b)
+ %val = extractvalue { i32, i1 } %ov, 0
+ %bit = extractvalue { i32, i1 } %ov, 1
+ %ext = sext i1 %bit to i32
+ %sbc = add i32 %val, %ext
+ store i32 %sbc, ptr %out
+ %r = add i32 %sbc, 10
+ ret i32 %r
+}
>From fa6d3db6e8a1b53be42f21e03aede7ca1ec2c3b0 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Mon, 9 Mar 2026 09:14:05 -0400
Subject: [PATCH 5/6] clang format diff
---
llvm/lib/Target/AArch64/AArch64ISelLowering.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
index e4a7e48e3f9ab..3bc518810af5c 100644
--- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp
@@ -23368,8 +23368,7 @@ static SDValue performAddWithSBCCombine(SDNode *N, SelectionDAG &DAG) {
// AArch64 SBC (non-flag-setting) has only one output; no flags guard needed.
SDLoc DL(N);
return DAG.getNode(AArch64ISD::SBC, DL, VT, SBC.getOperand(0),
- DAG.getNegative(C, DL, VT),
- SBC.getOperand(2));
+ DAG.getNegative(C, DL, VT), SBC.getOperand(2));
}
static SDValue performAddSubCombine(SDNode *N,
>From e6481d4fcca81327425c6cf55a1cd1c2fdafd26f Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Mon, 9 Mar 2026 21:51:02 -0400
Subject: [PATCH 6/6] remove llvm.usub.with.overflow in tests which are
unnecessary
---
llvm/test/CodeGen/AArch64/sbc-add-constant.ll | 3 ---
1 file changed, 3 deletions(-)
diff --git a/llvm/test/CodeGen/AArch64/sbc-add-constant.ll b/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
index 799e78ce51dfc..22e715ece89e8 100644
--- a/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
+++ b/llvm/test/CodeGen/AArch64/sbc-add-constant.ll
@@ -4,9 +4,6 @@
; Verify that ADD(SBB(Y,0,flags),C) folds to SBB(Y,-C,flags).
; SBB(Y,0) = Y - CF; adding C gives Y - CF + C = Y - (-C) - CF = SBB(Y,-C).
-declare {i64, i1} @llvm.usub.with.overflow.i64(i64, i64)
-declare {i32, i1} @llvm.usub.with.overflow.i32(i32, i32)
-
; Fold should fire, adding with constant
define i64 @g_i64(i64 %a, i64 %b) nounwind {
; CHECK-LABEL: g_i64:
More information about the llvm-commits
mailing list