[llvm-branch-commits] [llvm] 557e1d6 - [DAGCombiner] Require a 0/1 carry-in when rebuilding a borrow chain (#223396)

Tobias Hieta via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Sep 23 04:54:30 PDT 2026


Author: Paweł Bylica
Date: 2026-09-23T13:54:12+02:00
New Revision: 557e1d658863199dfa784f63c9454eaab9bd8aaf

URL: https://github.com/llvm/llvm-project/commit/557e1d658863199dfa784f63c9454eaab9bd8aaf
DIFF: https://github.com/llvm/llvm-project/commit/557e1d658863199dfa784f63c9454eaab9bd8aaf.diff

LOG: [DAGCombiner] Require a 0/1 carry-in when rebuilding a borrow chain (#223396)

combineOrOfSetCCToUSUBOCarry (aae871ba1224) rewrites

  carry_out = (A <u B) | ((A == B) & carry_in)

to USUBO_CARRY(A, B, carry_in), accepting any value as carry_in.

USUBO_CARRY's third operand is specified as "1 if and only if there is
an incoming carry/borrow", so 0 and 1 are its only meaningful values.
Unlike SADDO_CARRY/SSUBO_CARRY it does not fall back to
getBooleanContents for wider types. The matched `and` is bitwise against
a 0/1 setcc, so only bit 0 of carry_in reaches carry_out and nothing
else constrains it; a carry_in with any other bit set is outside the
operand's contract and miscompiles.

Require carry_in to be known 0/1. Check known bits rather than look for
an `and carry_in, 1`: only bit 0 is demanded inside the `and`, so such a
mask is usually folded away before this combine runs. An i1 carry_in,
which is what a real borrow chain produces, passes trivially.

Fixes #222839.

Assisted-by: Claude Code
(cherry picked from commit 4eba5bce282ded21bb912b22aae4aa91e27818da)

Added: 
    

Modified: 
    llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
    llvm/test/CodeGen/AArch64/cgp-usubo.ll
    llvm/test/CodeGen/X86/subcarry.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 67e16dcb41c7fc..8f07e311f11ea9 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -3968,6 +3968,13 @@ static SDValue combineOrOfSetCCToUSUBOCarry(SDNode *N, SelectionDAG &DAG,
                                                     *DAG.getContext(), IntVT)))
     return SDValue();
 
+  // USUBO_CARRY's carry-in must be 0 or 1, which the matched pattern does not
+  // guarantee.
+  if (!DAG.MaskedValueIsZero(
+          CarryIn,
+          APInt::getBitsSetFrom(CarryIn.getScalarValueSizeInBits(), 1)))
+    return SDValue();
+
   SDLoc DL(N);
   SDVTList VTs = DAG.getVTList(IntVT, N->getValueType(0));
   return DAG.getNode(ISD::USUBO_CARRY, DL, VTs, A, B, CarryIn).getValue(1);

diff  --git a/llvm/test/CodeGen/AArch64/cgp-usubo.ll b/llvm/test/CodeGen/AArch64/cgp-usubo.ll
index f990920e2793a4..450965455f8845 100644
--- a/llvm/test/CodeGen/AArch64/cgp-usubo.ll
+++ b/llvm/test/CodeGen/AArch64/cgp-usubo.ll
@@ -321,3 +321,22 @@ end:
   ret i1 %ov
 }
 
+
+; Negative test: %c is not known to be 0/1 and cannot be a borrow-in.
+; See https://github.com/llvm/llvm-project/issues/222839.
+define i8 @no_subcarry_carry_in_not_bool(i64 %a, i64 %b, i8 %c) nounwind {
+; CHECK-LABEL: no_subcarry_carry_in_not_bool:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    cmp x0, x1
+; CHECK-NEXT:    cset w8, eq
+; CHECK-NEXT:    and w8, w8, w2
+; CHECK-NEXT:    csinc w0, w8, wzr, hs
+; CHECK-NEXT:    ret
+  %ult = icmp ult i64 %a, %b
+  %ultz = zext i1 %ult to i8
+  %eq = icmp eq i64 %a, %b
+  %eqz = zext i1 %eq to i8
+  %and = and i8 %eqz, %c
+  %or = or i8 %ultz, %and
+  ret i8 %or
+}

diff  --git a/llvm/test/CodeGen/X86/subcarry.ll b/llvm/test/CodeGen/X86/subcarry.ll
index cd361babf53fb5..b53a206b14181b 100644
--- a/llvm/test/CodeGen/X86/subcarry.ll
+++ b/llvm/test/CodeGen/X86/subcarry.ll
@@ -1607,3 +1607,93 @@ define <4 x i1> @no_subcarry_vector(<4 x i32> %x0, <4 x i32> %x1, <4 x i32> %y0,
   ret <4 x i1> %br
 }
 
+; Negative test: %c is not known to be 0/1 and cannot be a borrow-in.
+; See https://github.com/llvm/llvm-project/issues/222839.
+define i8 @no_subcarry_carry_in_not_bool(i64 %a, i64 %b, i8 %c) nounwind {
+; X64-LABEL: no_subcarry_carry_in_not_bool:
+; X64:       # %bb.0:
+; X64-NEXT:    cmpq %rsi, %rdi
+; X64-NEXT:    setb %cl
+; X64-NEXT:    sete %al
+; X64-NEXT:    andb %dl, %al
+; X64-NEXT:    orb %cl, %al
+; X64-NEXT:    retq
+;
+; X86-LABEL: no_subcarry_carry_in_not_bool:
+; X86:       # %bb.0:
+; X86-NEXT:    pushl %ebx
+; X86-NEXT:    pushl %edi
+; X86-NEXT:    pushl %esi
+; X86-NEXT:    movl 24(%esp), %eax
+; X86-NEXT:    movl 28(%esp), %ecx
+; X86-NEXT:    movl 16(%esp), %edx
+; X86-NEXT:    movl 20(%esp), %esi
+; X86-NEXT:    cmpl %eax, %edx
+; X86-NEXT:    movl %esi, %edi
+; X86-NEXT:    sbbl %ecx, %edi
+; X86-NEXT:    setb %bl
+; X86-NEXT:    xorl %ecx, %esi
+; X86-NEXT:    xorl %eax, %edx
+; X86-NEXT:    orl %esi, %edx
+; X86-NEXT:    sete %al
+; X86-NEXT:    andb 32(%esp), %al
+; X86-NEXT:    orb %bl, %al
+; X86-NEXT:    popl %esi
+; X86-NEXT:    popl %edi
+; X86-NEXT:    popl %ebx
+; X86-NEXT:    retl
+  %ult = icmp ult i64 %a, %b
+  %ultz = zext i1 %ult to i8
+  %eq = icmp eq i64 %a, %b
+  %eqz = zext i1 %eq to i8
+  %and = and i8 %eqz, %c
+  %or = or i8 %ultz, %and
+  ret i8 %or
+}
+
+; %c is known to be 0/1 here, so the borrow chain is still rebuilt. Deliberately
+; not an i1 carry: that would pass the check trivially and would not catch a
+; regression to a structural test for the carry-in.
+define i8 @subcarry_carry_in_known_bool(i64 %a, i64 %b, i8 %c) nounwind {
+; X64-LABEL: subcarry_carry_in_known_bool:
+; X64:       # %bb.0:
+; X64-NEXT:    shrb $7, %dl
+; X64-NEXT:    addb $-1, %dl
+; X64-NEXT:    sbbq %rsi, %rdi
+; X64-NEXT:    setb %al
+; X64-NEXT:    retq
+;
+; X86-LABEL: subcarry_carry_in_known_bool:
+; X86:       # %bb.0:
+; X86-NEXT:    pushl %ebx
+; X86-NEXT:    pushl %edi
+; X86-NEXT:    pushl %esi
+; X86-NEXT:    movl 24(%esp), %eax
+; X86-NEXT:    movl 28(%esp), %ecx
+; X86-NEXT:    movl 16(%esp), %edx
+; X86-NEXT:    movl 20(%esp), %esi
+; X86-NEXT:    movzbl 32(%esp), %ebx
+; X86-NEXT:    shrb $7, %bl
+; X86-NEXT:    cmpl %eax, %edx
+; X86-NEXT:    movl %esi, %edi
+; X86-NEXT:    sbbl %ecx, %edi
+; X86-NEXT:    setb %bh
+; X86-NEXT:    xorl %ecx, %esi
+; X86-NEXT:    xorl %eax, %edx
+; X86-NEXT:    orl %esi, %edx
+; X86-NEXT:    sete %al
+; X86-NEXT:    andb %bl, %al
+; X86-NEXT:    orb %bh, %al
+; X86-NEXT:    popl %esi
+; X86-NEXT:    popl %edi
+; X86-NEXT:    popl %ebx
+; X86-NEXT:    retl
+  %cin = lshr i8 %c, 7
+  %ult = icmp ult i64 %a, %b
+  %ultz = zext i1 %ult to i8
+  %eq = icmp eq i64 %a, %b
+  %eqz = zext i1 %eq to i8
+  %and = and i8 %eqz, %cin
+  %or = or i8 %ultz, %and
+  ret i8 %or
+}


        


More information about the llvm-branch-commits mailing list