[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