[llvm] [DAG] FoldConstantArithmetic - add support for ISD::MULO/ADDO/SUBO constant folding (PR #228877)

Prajwal KP via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 4 23:27:35 PDT 2026


https://github.com/Prajwal-kp-18 updated https://github.com/llvm/llvm-project/pull/228877

>From cd103d590147cf214e2b39daba2f5af0202cdab7 Mon Sep 17 00:00:00 2001
From: Prajwal <prajwal.kp.1817 at gmail.com>
Date: Sat, 3 Oct 2026 10:43:24 +0530
Subject: [PATCH] [DAG] FoldConstantArithmetic - add support for
 ISD::MULO/ADDO/SUBO constant folding

---
 llvm/include/llvm/CodeGen/SelectionDAG.h      |   7 ++
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp |  28 +++--
 .../lib/CodeGen/SelectionDAG/SelectionDAG.cpp |  59 +++++++++
 llvm/test/CodeGen/AArch64/pr88966.ll          |   6 +-
 .../CodeGen/AMDGPU/i128-add-carry-chain.ll    |  27 +---
 llvm/test/CodeGen/X86/addcarry.ll             |  20 +--
 llvm/test/CodeGen/X86/combine-addo.ll         |  99 +++++++++++++++
 llvm/test/CodeGen/X86/combine-mulo.ll         |  92 ++++++++++++++
 llvm/test/CodeGen/X86/combine-subo.ll         | 115 +++++++++++++++++-
 .../CodeGen/X86/smul_fix_sat_constants.ll     |   9 +-
 10 files changed, 400 insertions(+), 62 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/SelectionDAG.h b/llvm/include/llvm/CodeGen/SelectionDAG.h
index a345c90ae9c0a..3b6763ce88366 100644
--- a/llvm/include/llvm/CodeGen/SelectionDAG.h
+++ b/llvm/include/llvm/CodeGen/SelectionDAG.h
@@ -2081,6 +2081,13 @@ class SelectionDAG {
                                           EVT VT, ArrayRef<SDValue> Ops,
                                           SDNodeFlags Flags = SDNodeFlags());
 
+  /// Constant fold a node with multiple results, returning a MERGE_VALUES of
+  /// the folded results on success.
+  LLVM_ABI SDValue FoldConstantArithmetic(unsigned Opcode, const SDLoc &DL,
+                                          SDVTList VTList,
+                                          ArrayRef<SDValue> Ops,
+                                          SDNodeFlags Flags = SDNodeFlags());
+
   /// Fold floating-point operations when all operands are constants and/or
   /// undefined.
   LLVM_ABI SDValue foldConstantFPMath(unsigned Opcode, const SDLoc &DL, EVT VT,
diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index c8a984a9dc56e..1ee738dc1a299 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -3632,6 +3632,11 @@ SDValue DAGCombiner::visitADDO(SDNode *N) {
     return CombineTo(N, DAG.getNode(ISD::ADD, DL, VT, N0, N1),
                      DAG.getUNDEF(CarryVT));
 
+  // fold operation with constant operands.
+  if (SDValue C = DAG.FoldConstantArithmetic(N->getOpcode(), DL, N->getVTList(),
+                                             {N0, N1}))
+    return C;
+
   // canonicalize constant to RHS.
   if (DAG.isConstantIntBuildVectorOrConstantInt(N0) &&
       !DAG.isConstantIntBuildVectorOrConstantInt(N1))
@@ -4805,6 +4810,11 @@ SDValue DAGCombiner::visitSUBO(SDNode *N) {
     return CombineTo(N, DAG.getNode(ISD::SUB, DL, VT, N0, N1),
                      DAG.getUNDEF(CarryVT));
 
+  // fold operation with constant operands.
+  if (SDValue C = DAG.FoldConstantArithmetic(N->getOpcode(), DL, N->getVTList(),
+                                             {N0, N1}))
+    return C;
+
   // fold (subo x, x) -> 0 + no borrow
   if (N0 == N1)
     return CombineTo(N, DAG.getConstant(0, DL, VT),
@@ -6169,20 +6179,12 @@ SDValue DAGCombiner::visitMULO(SDNode *N) {
   EVT CarryVT = N->getValueType(1);
   SDLoc DL(N);
 
-  ConstantSDNode *N0C = isConstOrConstSplat(N0);
-  ConstantSDNode *N1C = isConstOrConstSplat(N1);
-
   // fold operation with constant operands.
-  // TODO: Move this to FoldConstantArithmetic when it supports nodes with
-  // multiple results.
-  if (N0C && N1C && !N0C->isOpaque() && !N1C->isOpaque()) {
-    bool Overflow;
-    APInt Result =
-        IsSigned ? N0C->getAPIntValue().smul_ov(N1C->getAPIntValue(), Overflow)
-                 : N0C->getAPIntValue().umul_ov(N1C->getAPIntValue(), Overflow);
-    return CombineTo(N, DAG.getConstant(Result, DL, VT),
-                     DAG.getBoolConstant(Overflow, DL, CarryVT, CarryVT));
-  }
+  if (SDValue C = DAG.FoldConstantArithmetic(N->getOpcode(), DL, N->getVTList(),
+                                             {N0, N1}))
+    return C;
+
+  ConstantSDNode *N1C = isConstOrConstSplat(N1);
 
   // canonicalize constant to RHS.
   if (DAG.isConstantIntBuildVectorOrConstantInt(N0) &&
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
index 77922d8fb1344..0b51b150483bf 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAG.cpp
@@ -8273,6 +8273,62 @@ SDValue SelectionDAG::FoldConstantArithmetic(unsigned Opcode, const SDLoc &DL,
   return V;
 }
 
+SDValue SelectionDAG::FoldConstantArithmetic(unsigned Opcode, const SDLoc &DL,
+                                             SDVTList VTList,
+                                             ArrayRef<SDValue> Ops,
+                                             SDNodeFlags Flags) {
+  if (VTList.NumVTs == 1)
+    return FoldConstantArithmetic(Opcode, DL, VTList.VTs[0], Ops, Flags);
+
+  switch (Opcode) {
+  case ISD::SADDO:
+  case ISD::UADDO:
+  case ISD::SSUBO:
+  case ISD::USUBO:
+  case ISD::SMULO:
+  case ISD::UMULO: {
+    // TODO: Support non-splat vector constants.
+    ConstantSDNode *C1 = isConstOrConstSplat(Ops[0]);
+    ConstantSDNode *C2 = isConstOrConstSplat(Ops[1]);
+    if (!C1 || !C2 || C1->isOpaque() || C2->isOpaque())
+      return SDValue();
+
+    const APInt &V1 = C1->getAPIntValue(), &V2 = C2->getAPIntValue();
+    bool Overflow;
+    APInt Result;
+    switch (Opcode) {
+    default:
+      llvm_unreachable("Unexpected overflow opcode");
+    case ISD::SADDO:
+      Result = V1.sadd_ov(V2, Overflow);
+      break;
+    case ISD::UADDO:
+      Result = V1.uadd_ov(V2, Overflow);
+      break;
+    case ISD::SSUBO:
+      Result = V1.ssub_ov(V2, Overflow);
+      break;
+    case ISD::USUBO:
+      Result = V1.usub_ov(V2, Overflow);
+      break;
+    case ISD::SMULO:
+      Result = V1.smul_ov(V2, Overflow);
+      break;
+    case ISD::UMULO:
+      Result = V1.umul_ov(V2, Overflow);
+      break;
+    }
+
+    EVT VT = VTList.VTs[0];
+    return getNode(ISD::MERGE_VALUES, DL, VTList,
+                   {getConstant(Result, DL, VT),
+                    getBoolConstant(Overflow, DL, VTList.VTs[1], VT)},
+                   Flags);
+  }
+  }
+  return SDValue();
+}
+
 SDValue SelectionDAG::foldConstantFPMath(unsigned Opcode, const SDLoc &DL,
                                          EVT VT, ArrayRef<SDValue> Ops) {
   // TODO: Add support for unary/ternary fp opcodes.
@@ -12044,6 +12100,9 @@ SDValue SelectionDAG::getNode(unsigned Opcode, const SDLoc &DL, SDVTList VTList,
            "Operand is DELETED_NODE!");
 #endif
 
+  if (SDValue V = FoldConstantArithmetic(Opcode, DL, VTList, Ops, Flags))
+    return V;
+
   switch (Opcode) {
   case ISD::SADDO:
   case ISD::UADDO:
diff --git a/llvm/test/CodeGen/AArch64/pr88966.ll b/llvm/test/CodeGen/AArch64/pr88966.ll
index 0743773d171d7..7ade3654a2e93 100644
--- a/llvm/test/CodeGen/AArch64/pr88966.ll
+++ b/llvm/test/CodeGen/AArch64/pr88966.ll
@@ -4,12 +4,10 @@
 define i32 @f(ptr %arg, i41 %arg1, ptr %arg2) {
 ; CHECK-LABEL: f:
 ; CHECK:       // %bb.0: // %bb
-; CHECK-NEXT:    and w9, w1, #0x1
-; CHECK-NEXT:    mov w10, #1 // =0x1
+; CHECK-NEXT:    and x9, x1, #0x1
 ; CHECK-NEXT:    mov x8, x0
-; CHECK-NEXT:    cmp w9, #1
 ; CHECK-NEXT:    mov w0, wzr
-; CHECK-NEXT:    adc x9, xzr, x10
+; CHECK-NEXT:    add x9, x9, #1
 ; CHECK-NEXT:    str x9, [x2]
 ; CHECK-NEXT:    str xzr, [x8]
 ; CHECK-NEXT:    ret
diff --git a/llvm/test/CodeGen/AMDGPU/i128-add-carry-chain.ll b/llvm/test/CodeGen/AMDGPU/i128-add-carry-chain.ll
index 2a8b9e38b9057..ea8477acbd629 100644
--- a/llvm/test/CodeGen/AMDGPU/i128-add-carry-chain.ll
+++ b/llvm/test/CodeGen/AMDGPU/i128-add-carry-chain.ll
@@ -28,20 +28,11 @@ define i128 @i128_add_uniform_carry_to_divergent(i128 %x) #0 {
 ; GFX9-NEXT:    s_waitcnt lgkmcnt(0)
 ; GFX9-NEXT:    s_swappc_b64 s[30:31], s[16:17]
 ; GFX9-NEXT:    s_mov_b64 s[4:5], 1
-; GFX9-NEXT:    s_mov_b64 s[6:7], 0
-; GFX9-NEXT:    s_add_u32 s4, s6, s4
-; GFX9-NEXT:    s_cselect_b64 s[8:9], -1, 0
-; GFX9-NEXT:    s_cmp_lg_u64 s[8:9], 0
-; GFX9-NEXT:    s_addc_u32 s5, s7, s5
-; GFX9-NEXT:    s_cselect_b64 s[8:9], -1, 0
-; GFX9-NEXT:    v_mov_b32_e32 v0, s6
-; GFX9-NEXT:    v_addc_co_u32_e64 v2, s[8:9], v40, v0, s[8:9]
-; GFX9-NEXT:    v_mov_b32_e32 v0, s7
-; GFX9-NEXT:    v_addc_co_u32_e64 v3, s[6:7], v41, v0, s[8:9]
-; GFX9-NEXT:    v_lshrrev_b64 v[4:5], s34, v[2:3]
 ; GFX9-NEXT:    s_lshr_b64 s[6:7], s[4:5], s34
+; GFX9-NEXT:    v_lshrrev_b64 v[4:5], s34, v[40:41]
 ; GFX9-NEXT:    v_mov_b32_e32 v0, s4
 ; GFX9-NEXT:    v_mov_b32_e32 v1, s6
+; GFX9-NEXT:    v_mov_b32_e32 v2, v40
 ; GFX9-NEXT:    v_mov_b32_e32 v3, v4
 ; GFX9-NEXT:    v_readlane_b32 s30, v42, 1
 ; GFX9-NEXT:    v_readlane_b32 s31, v42, 2
@@ -91,23 +82,13 @@ define i128 @i128_add_uniform_carry_to_divergent(i128 %x) #0 {
 ; GFX12-NEXT:    s_wait_kmcnt 0x0
 ; GFX12-NEXT:    s_swappc_b64 s[30:31], s[0:1]
 ; GFX12-NEXT:    s_mov_b64 s[0:1], 1
-; GFX12-NEXT:    s_mov_b64 s[2:3], 0
 ; GFX12-NEXT:    s_wait_alu depctr_sa_sdst(0)
-; GFX12-NEXT:    s_add_co_u32 s0, s2, s0
-; GFX12-NEXT:    s_cselect_b32 s4, -1, 0
-; GFX12-NEXT:    s_wait_alu depctr_sa_sdst(0)
-; GFX12-NEXT:    s_cmp_lg_u32 s4, 0
-; GFX12-NEXT:    s_add_co_ci_u32 s1, s3, s1
-; GFX12-NEXT:    s_cselect_b32 s4, -1, 0
-; GFX12-NEXT:    s_wait_alu depctr_sa_sdst(0)
-; GFX12-NEXT:    v_add_co_ci_u32_e64 v2, s2, v40, s2, s4
-; GFX12-NEXT:    s_wait_alu depctr_va_sdst(0)
-; GFX12-NEXT:    v_add_co_ci_u32_e64 v3, s2, v41, s3, s2
-; GFX12-NEXT:    v_lshrrev_b64 v[3:4], s34, v[2:3]
 ; GFX12-NEXT:    s_lshr_b64 s[2:3], s[0:1], s34
+; GFX12-NEXT:    v_lshrrev_b64 v[3:4], s34, v[40:41]
 ; GFX12-NEXT:    v_mov_b32_e32 v0, s0
 ; GFX12-NEXT:    s_wait_alu depctr_sa_sdst(0)
 ; GFX12-NEXT:    v_mov_b32_e32 v1, s2
+; GFX12-NEXT:    v_mov_b32_e32 v2, v40
 ; GFX12-NEXT:    v_readlane_b32 s30, v42, 1
 ; GFX12-NEXT:    v_readlane_b32 s31, v42, 2
 ; GFX12-NEXT:    v_readlane_b32 s34, v42, 0
diff --git a/llvm/test/CodeGen/X86/addcarry.ll b/llvm/test/CodeGen/X86/addcarry.ll
index 2031a17a23be9..7de8ba36140da 100644
--- a/llvm/test/CodeGen/X86/addcarry.ll
+++ b/llvm/test/CodeGen/X86/addcarry.ll
@@ -3154,20 +3154,10 @@ define { i64, i64 } @addcarry_commutative_2(i64 %x0, i64 %x1, i64 %y0, i64 %y1)
 }
 
 define i1 @pr84831(i64 %arg) {
-; X64-LABEL: pr84831:
-; X64:       # %bb.0:
-; X64-NEXT:    testq %rdi, %rdi
-; X64-NEXT:    setne %al
-; X64-NEXT:    xorl %ecx, %ecx
-; X64-NEXT:    addb $-1, %al
-; X64-NEXT:    adcq $1, %rcx
-; X64-NEXT:    setb %al
-; X64-NEXT:    retq
-;
-; X86-LABEL: pr84831:
-; X86:       # %bb.0:
-; X86-NEXT:    xorl %eax, %eax
-; X86-NEXT:    retl
+; CHECK-LABEL: pr84831:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    xorl %eax, %eax
+; CHECK-NEXT:    ret{{[l|q]}}
   %a = icmp ult i64 0, %arg
   %add1 = add i64 0, 1
   %carryout1 = icmp ult i64 %add1, 0
@@ -3243,5 +3233,3 @@ define void @pr169691(ptr %p0, i64 %implicit, i1 zeroext %carry) nounwind {
   store i64 %uaddo1b.0, ptr %p1, align 8
   ret void
 }
-;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
-; CHECK: {{.*}}
diff --git a/llvm/test/CodeGen/X86/combine-addo.ll b/llvm/test/CodeGen/X86/combine-addo.ll
index ba748b6e653cf..7619a338a6369 100644
--- a/llvm/test/CodeGen/X86/combine-addo.ll
+++ b/llvm/test/CodeGen/X86/combine-addo.ll
@@ -143,3 +143,102 @@ define <4 x i32> @combine_vec_uadd_no_overflow(<4 x i32> %a0, <4 x i32> %a1, <4
   %6 = select <4 x i1> %5, <4 x i32> %a2, <4 x i32> %4
   ret <4 x i32> %4
 }
+
+; fold (sadd c1, c2) -> c3 + overflow
+define { i32, i1 } @combine_sadd_constant_overflow() {
+; CHECK-LABEL: combine_sadd_constant_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-2147483648, %eax # imm = 0x80000000
+; CHECK-NEXT:    movb $1, %dl
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 2147483647, i32 1)
+  ret { i32, i1 } %x
+}
+
+define { i32, i1 } @combine_sadd_constant_no_overflow() {
+; CHECK-LABEL: combine_sadd_constant_no_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $2147483647, %eax # imm = 0x7FFFFFFF
+; CHECK-NEXT:    xorl %edx, %edx
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 2147483646, i32 1)
+  ret { i32, i1 } %x
+}
+
+define { <4 x i32>, <4 x i1> } @combine_vec_sadd_constant_overflow() {
+; SSE-LABEL: combine_vec_sadd_constant_overflow:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm0 = [2147483648,2147483648,2147483648,2147483648]
+; SSE-NEXT:    movaps {{.*#+}} xmm1 = [1,1,1,1]
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: combine_vec_sadd_constant_overflow:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm0 = [2147483648,2147483648,2147483648,2147483648]
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm1 = [1,1,1,1]
+; AVX-NEXT:    retq
+  %x = call { <4 x i32>, <4 x i1> } @llvm.sadd.with.overflow.v4i32(<4 x i32> splat (i32 2147483647), <4 x i32> splat (i32 1))
+  ret { <4 x i32>, <4 x i1> } %x
+}
+
+; fold (uadd c1, c2) -> c3 + overflow
+define { i32, i1 } @combine_uadd_constant_overflow() {
+; CHECK-LABEL: combine_uadd_constant_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $1, %eax
+; CHECK-NEXT:    movb $1, %dl
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 -1, i32 2)
+  ret { i32, i1 } %x
+}
+
+define { i32, i1 } @combine_uadd_constant_no_overflow() {
+; CHECK-LABEL: combine_uadd_constant_no_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-1, %eax
+; CHECK-NEXT:    xorl %edx, %edx
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 -3, i32 2)
+  ret { i32, i1 } %x
+}
+
+define { <4 x i32>, <4 x i1> } @combine_vec_uadd_constant_overflow() {
+; SSE-LABEL: combine_vec_uadd_constant_overflow:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm0 = [1,1,1,1]
+; SSE-NEXT:    movaps %xmm0, %xmm1
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: combine_vec_uadd_constant_overflow:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm0 = [1,1,1,1]
+; AVX-NEXT:    vmovaps %xmm0, %xmm1
+; AVX-NEXT:    retq
+  %x = call { <4 x i32>, <4 x i1> } @llvm.uadd.with.overflow.v4i32(<4 x i32> splat (i32 -1), <4 x i32> splat (i32 2))
+  ret { <4 x i32>, <4 x i1> } %x
+}
+
+; Opaque constants (as created by constant hoisting) should not be folded.
+define { i64, i1 } @combine_sadd_constant_opaque() {
+; CHECK-LABEL: combine_sadd_constant_opaque:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movabsq $9223372036854775792, %rax # imm = 0x7FFFFFFFFFFFFFF0
+; CHECK-NEXT:    addq $100, %rax
+; CHECK-NEXT:    seto %dl
+; CHECK-NEXT:    retq
+  %c = bitcast i64 9223372036854775792 to i64
+  %x = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %c, i64 100)
+  ret { i64, i1 } %x
+}
+
+define { i64, i1 } @combine_uadd_constant_opaque() {
+; CHECK-LABEL: combine_uadd_constant_opaque:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movabsq $-1311768467463790320, %rax # imm = 0xEDCBA98765432110
+; CHECK-NEXT:    addq $-3, %rax
+; CHECK-NEXT:    setb %dl
+; CHECK-NEXT:    retq
+  %c = bitcast i64 -1311768467463790320 to i64
+  %x = call { i64, i1 } @llvm.uadd.with.overflow.i64(i64 %c, i64 -3)
+  ret { i64, i1 } %x
+}
diff --git a/llvm/test/CodeGen/X86/combine-mulo.ll b/llvm/test/CodeGen/X86/combine-mulo.ll
index 4a27c4da6f35c..82b50cca01378 100644
--- a/llvm/test/CodeGen/X86/combine-mulo.ll
+++ b/llvm/test/CodeGen/X86/combine-mulo.ll
@@ -152,3 +152,95 @@ define { i64, i1 } @combine_umul_constant_opaque() {
   %x = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 3, i64 %c)
   ret { i64, i1 } %x
 }
+
+; fold (smul c1, c2) -> c3 + overflow
+define { i32, i1 } @combine_smul_constant_overflow() {
+; CHECK-LABEL: combine_smul_constant_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-2, %eax
+; CHECK-NEXT:    movb $1, %dl
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 2147483647, i32 2)
+  ret { i32, i1 } %x
+}
+
+define { i32, i1 } @combine_smul_constant_no_overflow() {
+; CHECK-LABEL: combine_smul_constant_no_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $2147483646, %eax # imm = 0x7FFFFFFE
+; CHECK-NEXT:    xorl %edx, %edx
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 1073741823, i32 2)
+  ret { i32, i1 } %x
+}
+
+define { <4 x i32>, <4 x i1> } @combine_vec_smul_constant_overflow() {
+; SSE-LABEL: combine_vec_smul_constant_overflow:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967294]
+; SSE-NEXT:    movaps {{.*#+}} xmm1 = [1,1,1,1]
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: combine_vec_smul_constant_overflow:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967294]
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm1 = [1,1,1,1]
+; AVX-NEXT:    retq
+  %x = call { <4 x i32>, <4 x i1> } @llvm.smul.with.overflow.v4i32(<4 x i32> splat (i32 2147483647), <4 x i32> splat (i32 2))
+  ret { <4 x i32>, <4 x i1> } %x
+}
+
+; fold (umul c1, c2) -> c3 + overflow
+define { i32, i1 } @combine_umul_constant_overflow() {
+; CHECK-LABEL: combine_umul_constant_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-2, %eax
+; CHECK-NEXT:    movb $1, %dl
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 -1, i32 2)
+  ret { i32, i1 } %x
+}
+
+define { i32, i1 } @combine_umul_constant_no_overflow() {
+; CHECK-LABEL: combine_umul_constant_no_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-2, %eax
+; CHECK-NEXT:    xorl %edx, %edx
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 2147483647, i32 2)
+  ret { i32, i1 } %x
+}
+
+define { <4 x i32>, <4 x i1> } @combine_vec_umul_constant_overflow() {
+; SSE-LABEL: combine_vec_umul_constant_overflow:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967294]
+; SSE-NEXT:    movaps {{.*#+}} xmm1 = [1,1,1,1]
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: combine_vec_umul_constant_overflow:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967294]
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm1 = [1,1,1,1]
+; AVX-NEXT:    retq
+  %x = call { <4 x i32>, <4 x i1> } @llvm.umul.with.overflow.v4i32(<4 x i32> splat (i32 -1), <4 x i32> splat (i32 2))
+  ret { <4 x i32>, <4 x i1> } %x
+}
+
+; Non-splat constants are not folded by the combine, and are instead folded
+; when the node is expanded during legalization.
+define { <4 x i32>, <4 x i1> } @combine_vec_umul_constant_overflow_nonsplat() {
+; SSE-LABEL: combine_vec_umul_constant_overflow_nonsplat:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967292]
+; SSE-NEXT:    pcmpeqd %xmm1, %xmm1
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: combine_vec_umul_constant_overflow_nonsplat:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovaps {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967292]
+; AVX-NEXT:    vpcmpeqd %xmm1, %xmm1, %xmm1
+; AVX-NEXT:    retq
+  %x = call { <4 x i32>, <4 x i1> } @llvm.umul.with.overflow.v4i32(<4 x i32> <i32 -1, i32 -1, i32 -1, i32 -2>, <4 x i32> splat (i32 2))
+  ret { <4 x i32>, <4 x i1> } %x
+}
diff --git a/llvm/test/CodeGen/X86/combine-subo.ll b/llvm/test/CodeGen/X86/combine-subo.ll
index 5e4bba6e0fd35..e4be84f6ff0d4 100644
--- a/llvm/test/CodeGen/X86/combine-subo.ll
+++ b/llvm/test/CodeGen/X86/combine-subo.ll
@@ -201,14 +201,14 @@ define { i8, i1 } @ssub_always_overflow(i8 %x) nounwind {
 define { <4 x i8>, <4 x i1> } @always_usub_const_vector() nounwind {
 ; SSE-LABEL: always_usub_const_vector:
 ; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm1 = [1,1,1,1]
 ; SSE-NEXT:    pcmpeqd %xmm0, %xmm0
-; SSE-NEXT:    pcmpeqd %xmm1, %xmm1
 ; SSE-NEXT:    retq
 ;
 ; AVX-LABEL: always_usub_const_vector:
 ; AVX:       # %bb.0:
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm1 = [1,1,1,1]
 ; AVX-NEXT:    vpcmpeqd %xmm0, %xmm0, %xmm0
-; AVX-NEXT:    vpcmpeqd %xmm1, %xmm1, %xmm1
 ; AVX-NEXT:    retq
   %x = call { <4 x i8>, <4 x i1> } @llvm.usub.with.overflow.v4i8(<4 x i8> <i8 0, i8 0, i8 0, i8 0>, <4 x i8> <i8 1, i8 1, i8 1, i8 1>)
   ret { <4 x i8>, <4 x i1> } %x
@@ -229,3 +229,114 @@ define { <4 x i8>, <4 x i1> } @never_usub_const_vector() nounwind {
   %x = call { <4 x i8>, <4 x i1> } @llvm.usub.with.overflow.v4i8(<4 x i8> <i8 255, i8 255, i8 255, i8 255>, <4 x i8> <i8 128, i8 0, i8 255, i8 1>)
   ret { <4 x i8>, <4 x i1> } %x
 }
+
+; fold (ssub c1, c2) -> c3 + overflow
+define { i32, i1 } @combine_ssub_constant_overflow() {
+; CHECK-LABEL: combine_ssub_constant_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $2147483647, %eax # imm = 0x7FFFFFFF
+; CHECK-NEXT:    movb $1, %dl
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -2147483648, i32 1)
+  ret { i32, i1 } %x
+}
+
+; INT_MIN on the RHS is not canonicalized to saddo.
+define { i32, i1 } @combine_ssub_constant_overflow_minsigned() {
+; CHECK-LABEL: combine_ssub_constant_overflow_minsigned:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-2147483648, %eax # imm = 0x80000000
+; CHECK-NEXT:    movb $1, %dl
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 0, i32 -2147483648)
+  ret { i32, i1 } %x
+}
+
+define { i32, i1 } @combine_ssub_constant_no_overflow() {
+; CHECK-LABEL: combine_ssub_constant_no_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-2147483648, %eax # imm = 0x80000000
+; CHECK-NEXT:    xorl %edx, %edx
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 1)
+  ret { i32, i1 } %x
+}
+
+define { <4 x i32>, <4 x i1> } @combine_vec_ssub_constant_overflow() {
+; SSE-LABEL: combine_vec_ssub_constant_overflow:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm0 = [2147483647,2147483647,2147483647,2147483647]
+; SSE-NEXT:    movaps {{.*#+}} xmm1 = [1,1,1,1]
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: combine_vec_ssub_constant_overflow:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm0 = [2147483647,2147483647,2147483647,2147483647]
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm1 = [1,1,1,1]
+; AVX-NEXT:    retq
+  %x = call { <4 x i32>, <4 x i1> } @llvm.ssub.with.overflow.v4i32(<4 x i32> splat (i32 -2147483648), <4 x i32> splat (i32 1))
+  ret { <4 x i32>, <4 x i1> } %x
+}
+
+; fold (usub c1, c2) -> c3 + overflow
+define { i32, i1 } @combine_usub_constant_overflow() {
+; CHECK-LABEL: combine_usub_constant_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $-2, %eax
+; CHECK-NEXT:    movb $1, %dl
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 1, i32 3)
+  ret { i32, i1 } %x
+}
+
+define { i32, i1 } @combine_usub_constant_no_overflow() {
+; CHECK-LABEL: combine_usub_constant_no_overflow:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movl $2, %eax
+; CHECK-NEXT:    xorl %edx, %edx
+; CHECK-NEXT:    retq
+  %x = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 3, i32 1)
+  ret { i32, i1 } %x
+}
+
+define { <4 x i32>, <4 x i1> } @combine_vec_usub_constant_overflow() {
+; SSE-LABEL: combine_vec_usub_constant_overflow:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movaps {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967294]
+; SSE-NEXT:    movaps {{.*#+}} xmm1 = [1,1,1,1]
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: combine_vec_usub_constant_overflow:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm0 = [4294967294,4294967294,4294967294,4294967294]
+; AVX-NEXT:    vbroadcastss {{.*#+}} xmm1 = [1,1,1,1]
+; AVX-NEXT:    retq
+  %x = call { <4 x i32>, <4 x i1> } @llvm.usub.with.overflow.v4i32(<4 x i32> splat (i32 1), <4 x i32> splat (i32 3))
+  ret { <4 x i32>, <4 x i1> } %x
+}
+
+; Opaque constants (as created by constant hoisting) should not be folded.
+define { i64, i1 } @combine_ssub_constant_opaque() {
+; CHECK-LABEL: combine_ssub_constant_opaque:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movabsq $-9223372036854775792, %rax # imm = 0x8000000000000010
+; CHECK-NEXT:    addq $-100, %rax
+; CHECK-NEXT:    seto %dl
+; CHECK-NEXT:    retq
+  %c = bitcast i64 -9223372036854775792 to i64
+  %x = call { i64, i1 } @llvm.ssub.with.overflow.i64(i64 %c, i64 100)
+  ret { i64, i1 } %x
+}
+
+define { i64, i1 } @combine_usub_constant_opaque() {
+; CHECK-LABEL: combine_usub_constant_opaque:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movabsq $1311768467463790320, %rcx # imm = 0x123456789ABCDEF0
+; CHECK-NEXT:    movl $3, %eax
+; CHECK-NEXT:    subq %rcx, %rax
+; CHECK-NEXT:    setb %dl
+; CHECK-NEXT:    retq
+  %c = bitcast i64 1311768467463790320 to i64
+  %x = call { i64, i1 } @llvm.usub.with.overflow.i64(i64 3, i64 %c)
+  ret { i64, i1 } %x
+}
diff --git a/llvm/test/CodeGen/X86/smul_fix_sat_constants.ll b/llvm/test/CodeGen/X86/smul_fix_sat_constants.ll
index 5017838a4f265..15756c4fca051 100644
--- a/llvm/test/CodeGen/X86/smul_fix_sat_constants.ll
+++ b/llvm/test/CodeGen/X86/smul_fix_sat_constants.ll
@@ -29,10 +29,11 @@ define i64 @func() nounwind {
 define i64 @func2() nounwind {
 ; X64-LABEL: func2:
 ; X64:       # %bb.0:
-; X64-NEXT:    movl $3, %eax
-; X64-NEXT:    imulq $2, %rax, %rcx
-; X64-NEXT:    movabsq $9223372036854775807, %rax # imm = 0x7FFFFFFFFFFFFFFF
-; X64-NEXT:    cmovaeq %rcx, %rax
+; X64-NEXT:    xorl %eax, %eax
+; X64-NEXT:    testb %al, %al
+; X64-NEXT:    movabsq $9223372036854775807, %rcx # imm = 0x7FFFFFFFFFFFFFFF
+; X64-NEXT:    movl $6, %eax
+; X64-NEXT:    cmovneq %rcx, %rax
 ; X64-NEXT:    retq
   %tmp = call i64 @llvm.smul.fix.sat.i64(i64 3, i64 2, i32 0)
   ret i64 %tmp



More information about the llvm-commits mailing list