[llvm] [DirectX] Expand {u,s}mul.with.overflow in DXILIntrinsicExpansion (PR #207297)

Damyan Pepper via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 3 11:22:57 PDT 2026


https://github.com/damyanp updated https://github.com/llvm/llvm-project/pull/207297

>From a61446c78a490e889938e4e0bb2b56efb947de03 Mon Sep 17 00:00:00 2001
From: Damyan Pepper <damyanp at microsoft.com>
Date: Thu, 2 Jul 2026 17:20:36 -0700
Subject: [PATCH 1/2] [DirectX] Expand {u,s}mul.with.overflow in
 DXILIntrinsicExpansion

DXIL has no op for the llvm.{u,s}mul.with.overflow intrinsics. These can be
emulated by performing the full multiply using double-width values and then
checking the high-part of the result. However, this should be avoided for 32-bit
values since we don't want to make the shader start using 64-bit values if it
wasn't before. In this case we can use the UMul and IMul DXIL operations that
return the result as separate low & high values.

Wider (64-bit) multiplies, which can't widen further, compute the high half with
same-width arithmetic instead.

Fixes #207090

Co-authored-by: Copilot <223556219+Copilot at users.noreply.github.com>
---
 llvm/include/llvm/IR/IntrinsicsDirectX.td     |   4 +
 llvm/lib/Target/DirectX/DXIL.td               |  23 ++++
 .../Target/DirectX/DXILIntrinsicExpansion.cpp | 101 ++++++++++++++++++
 llvm/lib/Target/DirectX/DXILOpBuilder.cpp     |   9 ++
 llvm/test/CodeGen/DirectX/imul_umul.ll        |  57 ++++++++++
 .../CodeGen/DirectX/overflow_intrinsics.ll    |  87 +++++++++++++++
 6 files changed, 281 insertions(+)
 create mode 100644 llvm/test/CodeGen/DirectX/imul_umul.ll
 create mode 100644 llvm/test/CodeGen/DirectX/overflow_intrinsics.ll

diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index 63186ab4d7191..4dd86270f0d01 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -288,6 +288,10 @@ def int_dx_sign : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32
 def int_dx_step : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty, LLVMMatchType<0>], [IntrNoMem]>;
 def int_dx_splitdouble : DefaultAttrsIntrinsic<[llvm_anyint_ty, LLVMMatchType<0>],
     [LLVMScalarOrSameVectorWidth<0, llvm_double_ty>], [IntrNoMem, IntrTriviallyScalarizable]>;
+def int_dx_imul : DefaultAttrsIntrinsic<[llvm_anyint_ty, LLVMMatchType<0>],
+    [LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem, IntrTriviallyScalarizable]>;
+def int_dx_umul : DefaultAttrsIntrinsic<[llvm_anyint_ty, LLVMMatchType<0>],
+    [LLVMMatchType<0>, LLVMMatchType<0>], [IntrNoMem, IntrTriviallyScalarizable]>;
 def int_dx_radians : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>], [IntrNoMem]>;
 def int_dx_discard : DefaultAttrsIntrinsic<[], [llvm_i1_ty], []>;
 def int_dx_ddx_coarse : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>], [IntrNoMem, IntrTriviallyScalarizable]>;
diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td
index 299d2d113b6bf..a268276b07655 100644
--- a/llvm/lib/Target/DirectX/DXIL.td
+++ b/llvm/lib/Target/DirectX/DXIL.td
@@ -59,6 +59,7 @@ def SplitDoubleTy : DXILOpParamType;
 def BinaryWithCarryTy : DXILOpParamType;
 def DimensionsTy : DXILOpParamType;
 def Fouri32s : DXILOpParamType;
+def TwoI32Ty : DXILOpParamType;
 
 class DXILOpClass;
 
@@ -800,6 +801,28 @@ def Fma : DXILOp<47, tertiary> {
   let attributes = [Attributes<DXIL1_0, [ReadNone]>];
 }
 
+def IMul : DXILOp<41, binaryWithTwoOuts> {
+  let Doc = "Signed integer multiply returning the high and low halves of the "
+            "full-width product.";
+  let intrinsics = [IntrinSelect<int_dx_imul>];
+  let arguments = [OverloadTy, OverloadTy];
+  let result = TwoI32Ty;
+  let overloads = [Overloads<DXIL1_0, [Int32Ty]>];
+  let stages = [Stages<DXIL1_0, [all_stages]>];
+  let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
+def UMul : DXILOp<42, binaryWithTwoOuts> {
+  let Doc = "Unsigned integer multiply returning the high and low halves of the "
+            "full-width product.";
+  let intrinsics = [IntrinSelect<int_dx_umul>];
+  let arguments = [OverloadTy, OverloadTy];
+  let result = TwoI32Ty;
+  let overloads = [Overloads<DXIL1_0, [Int32Ty]>];
+  let stages = [Stages<DXIL1_0, [all_stages]>];
+  let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
 def IMad : DXILOp<48, tertiary> {
   let Doc = "Signed integer arithmetic multiply/add operation. imad(m,a,b) = m "
             "* a + b.";
diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index 2a91ea223ed2d..aa2a3515f9260 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -12,6 +12,7 @@
 
 #include "DXILIntrinsicExpansion.h"
 #include "DirectX.h"
+#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/CodeGen/Passes.h"
@@ -234,6 +235,8 @@ static bool isIntrinsicExpansion(Function &F) {
   case Intrinsic::vector_reduce_fadd:
   case Intrinsic::matrix_multiply:
   case Intrinsic::matrix_transpose:
+  case Intrinsic::umul_with_overflow:
+  case Intrinsic::smul_with_overflow:
     return true;
   case Intrinsic::dx_resource_load_rawbuffer:
     return resourceAccessNeeds64BitExpansion(
@@ -266,6 +269,99 @@ static Value *expandUsubSat(CallInst *Orig) {
   return Builder.CreateSelect(Cmp, Zero, Sub, "usub.sat");
 }
 
+// Compute the high N bits of the 2N-bit unsigned product of two N-bit values
+// using only N-bit arithmetic, so we don't introduce a wider integer type that
+// may be unsupported in DXIL. This is the same half-width split used by
+// TargetLowering::expandMUL_LOHI (SelectionDAG) and
+// LegalizerHelper::narrowScalarMul (GlobalISel).
+static Value *createMulHighUnsigned(IRBuilder<> &Builder, Value *A, Value *B,
+                                    Type *Ty, unsigned BW) {
+  assert(BW % 2 == 0 && "high-half split needs symmetric halves");
+  unsigned Half = BW / 2;
+  Value *HalfShift = ConstantInt::get(Ty, Half);
+  Value *LoMask = ConstantInt::get(Ty, APInt::getLowBitsSet(BW, Half));
+
+  Value *U0 = Builder.CreateAnd(A, LoMask);
+  Value *U1 = Builder.CreateLShr(A, HalfShift);
+  Value *V0 = Builder.CreateAnd(B, LoMask);
+  Value *V1 = Builder.CreateLShr(B, HalfShift);
+
+  Value *W0 = Builder.CreateMul(U0, V0);
+  Value *T = Builder.CreateAdd(Builder.CreateMul(U1, V0),
+                               Builder.CreateLShr(W0, HalfShift));
+  Value *W1 = Builder.CreateAnd(T, LoMask);
+  Value *W2 = Builder.CreateLShr(T, HalfShift);
+  W1 = Builder.CreateAdd(Builder.CreateMul(U0, V1), W1);
+  return Builder.CreateAdd(Builder.CreateAdd(Builder.CreateMul(U1, V1), W2),
+                           Builder.CreateLShr(W1, HalfShift));
+}
+
+// Expand a {u,s}mul.with.overflow intrinsic. The low half of the result is a
+// plain multiply; overflow is derived from the high half of the double-width
+// product.
+static Value *expandMulWithOverflow(CallInst *Orig, bool Signed) {
+  IRBuilder<> Builder(Orig);
+  Value *A = Orig->getArgOperand(0);
+  Value *B = Orig->getArgOperand(1);
+  Type *Ty = A->getType();
+  unsigned BW = Ty->getScalarSizeInBits();
+
+  Value *Lo;
+  Value *Ov;
+
+  // A plain double-width multiply is simplest, but we avoid it once it would
+  // introduce a 64-bit (or wider) integer, which DXIL does not always support.
+  // For i32 we use the native DXIL IMul/UMul ops, which return the full product
+  // as two i32s; wider types fall back to a same-width high-half computation.
+  if (2 * BW <= 32) {
+    Lo = Builder.CreateMul(A, B);
+    Type *WideTy = Ty->getWithNewBitWidth(2 * BW);
+    Value *WideA =
+        Signed ? Builder.CreateSExt(A, WideTy) : Builder.CreateZExt(A, WideTy);
+    Value *WideB =
+        Signed ? Builder.CreateSExt(B, WideTy) : Builder.CreateZExt(B, WideTy);
+    Value *Wide = Builder.CreateMul(WideA, WideB);
+    if (Signed) {
+      // Overflow when the full product doesn't fit back into BW signed bits.
+      Ov = Builder.CreateICmpNE(Wide, Builder.CreateSExt(Lo, WideTy));
+    } else {
+      Value *Hi = Builder.CreateLShr(Wide, ConstantInt::get(WideTy, BW));
+      Ov = Builder.CreateICmpNE(Hi, ConstantInt::get(WideTy, 0));
+    }
+  } else if (BW == 32) {
+    // IMul/UMul return {high, low}; index 0 is the high 32 bits.
+    Type *ResTy = StructType::get(Ty, Ty);
+    Intrinsic::ID ID = Signed ? Intrinsic::dx_imul : Intrinsic::dx_umul;
+    Value *Mul = Builder.CreateIntrinsic(ResTy, ID, {A, B});
+    Value *Hi = Builder.CreateExtractValue(Mul, 0);
+    Lo = Builder.CreateExtractValue(Mul, 1);
+    if (Signed)
+      Ov = Builder.CreateICmpNE(
+          Hi, Builder.CreateAShr(Lo, ConstantInt::get(Ty, BW - 1)));
+    else
+      Ov = Builder.CreateICmpNE(Hi, ConstantInt::get(Ty, 0));
+  } else {
+    Lo = Builder.CreateMul(A, B);
+    Value *Hi = createMulHighUnsigned(Builder, A, B, Ty, BW);
+    if (Signed) {
+      // Turn the unsigned high half into the signed one, then overflow means it
+      // isn't the sign extension of the low half.
+      Value *SignShift = ConstantInt::get(Ty, BW - 1);
+      Value *ASign = Builder.CreateAShr(A, SignShift);
+      Value *BSign = Builder.CreateAShr(B, SignShift);
+      Hi = Builder.CreateSub(Hi, Builder.CreateAnd(ASign, B));
+      Hi = Builder.CreateSub(Hi, Builder.CreateAnd(BSign, A));
+      Ov = Builder.CreateICmpNE(Hi, Builder.CreateAShr(Lo, SignShift));
+    } else {
+      Ov = Builder.CreateICmpNE(Hi, ConstantInt::get(Ty, 0));
+    }
+  }
+
+  Value *Agg = PoisonValue::get(Orig->getType());
+  Agg = Builder.CreateInsertValue(Agg, Lo, 0);
+  return Builder.CreateInsertValue(Agg, Ov, 1);
+}
+
 static Value *expandVecReduceAdd(CallInst *Orig, Intrinsic::ID IntrinsicId) {
   assert(IntrinsicId == Intrinsic::vector_reduce_add ||
          IntrinsicId == Intrinsic::vector_reduce_fadd);
@@ -1271,6 +1367,11 @@ static bool expandIntrinsic(Function &F, CallInst *Orig) {
   case Intrinsic::usub_sat:
     Result = expandUsubSat(Orig);
     break;
+  case Intrinsic::umul_with_overflow:
+  case Intrinsic::smul_with_overflow:
+    Result = expandMulWithOverflow(
+        Orig, /*Signed=*/IntrinsicId == Intrinsic::smul_with_overflow);
+    break;
   case Intrinsic::vector_reduce_add:
   case Intrinsic::vector_reduce_fadd:
     Result = expandVecReduceAdd(Orig, IntrinsicId);
diff --git a/llvm/lib/Target/DirectX/DXILOpBuilder.cpp b/llvm/lib/Target/DirectX/DXILOpBuilder.cpp
index 1ee381d9e4e24..997b0cef3fdf5 100644
--- a/llvm/lib/Target/DirectX/DXILOpBuilder.cpp
+++ b/llvm/lib/Target/DirectX/DXILOpBuilder.cpp
@@ -275,6 +275,13 @@ static StructType *getFouri32sType(LLVMContext &Context) {
                                {Int32Ty, Int32Ty, Int32Ty, Int32Ty}, Context);
 }
 
+static StructType *getTwoI32Type(LLVMContext &Context) {
+  if (auto *ST = StructType::getTypeByName(Context, "dx.types.twoi32"))
+    return ST;
+  Type *Int32Ty = Type::getInt32Ty(Context);
+  return StructType::create({Int32Ty, Int32Ty}, "dx.types.twoi32");
+}
+
 static Type *getTypeFromOpParamType(OpParamType Kind, LLVMContext &Ctx,
                                     Type *OverloadTy) {
   switch (Kind) {
@@ -336,6 +343,8 @@ static Type *getTypeFromOpParamType(OpParamType Kind, LLVMContext &Ctx,
     return getDimensionsType(Ctx);
   case OpParamType::Fouri32s:
     return getFouri32sType(Ctx);
+  case OpParamType::TwoI32Ty:
+    return getTwoI32Type(Ctx);
   }
 
   llvm_unreachable("Invalid parameter kind");
diff --git a/llvm/test/CodeGen/DirectX/imul_umul.ll b/llvm/test/CodeGen/DirectX/imul_umul.ll
new file mode 100644
index 0000000000000..87c533305c414
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/imul_umul.ll
@@ -0,0 +1,57 @@
+; RUN: opt -passes='function(scalarizer),module(dxil-op-lower)' -S -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+
+; Lower the dx.imul/dx.umul intrinsics to the DXIL IMul(41)/UMul(42) ops, which
+; return the high and low halves of the full-width product as two i32s.
+
+define i32 @umul_scalar(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @umul_scalar(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[M:%.*]] = call [[DX_TYPES_TWOI32:%.*]] @dx.op.binaryWithTwoOuts.i32(i32 42, i32 [[A]], i32 [[B]])
+; CHECK-NEXT:    [[HI:%.*]] = extractvalue [[DX_TYPES_TWOI32]] [[M]], 0
+; CHECK-NEXT:    [[LO:%.*]] = extractvalue [[DX_TYPES_TWOI32]] [[M]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[HI]], [[LO]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %m = call { i32, i32 } @llvm.dx.umul.i32(i32 %a, i32 %b)
+  %hi = extractvalue { i32, i32 } %m, 0
+  %lo = extractvalue { i32, i32 } %m, 1
+  %r = add i32 %hi, %lo
+  ret i32 %r
+}
+
+define i32 @imul_scalar(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @imul_scalar(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[M:%.*]] = call [[DX_TYPES_TWOI32:%.*]] @dx.op.binaryWithTwoOuts.i32(i32 41, i32 [[A]], i32 [[B]])
+; CHECK-NEXT:    [[HI:%.*]] = extractvalue [[DX_TYPES_TWOI32]] [[M]], 0
+; CHECK-NEXT:    [[LO:%.*]] = extractvalue [[DX_TYPES_TWOI32]] [[M]], 1
+; CHECK-NEXT:    [[R:%.*]] = add i32 [[HI]], [[LO]]
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %m = call { i32, i32 } @llvm.dx.imul.i32(i32 %a, i32 %b)
+  %hi = extractvalue { i32, i32 } %m, 0
+  %lo = extractvalue { i32, i32 } %m, 1
+  %r = add i32 %hi, %lo
+  ret i32 %r
+}
+
+; Vector calls scalarize into per-lane ops before lowering.
+define <2 x i32> @umul_vector(<2 x i32> %a, <2 x i32> %b) {
+; CHECK-LABEL: define <2 x i32> @umul_vector(
+; CHECK-SAME: <2 x i32> [[A:%.*]], <2 x i32> [[B:%.*]]) {
+; CHECK-NEXT:    [[A0:%.*]] = extractelement <2 x i32> [[A]], i64 0
+; CHECK-NEXT:    [[B0:%.*]] = extractelement <2 x i32> [[B]], i64 0
+; CHECK-NEXT:    [[M0:%.*]] = call [[DX_TYPES_TWOI32:%.*]] @dx.op.binaryWithTwoOuts.i32(i32 42, i32 [[A0]], i32 [[B0]])
+; CHECK-NEXT:    [[A1:%.*]] = extractelement <2 x i32> [[A]], i64 1
+; CHECK-NEXT:    [[B1:%.*]] = extractelement <2 x i32> [[B]], i64 1
+; CHECK-NEXT:    [[M1:%.*]] = call [[DX_TYPES_TWOI32]] @dx.op.binaryWithTwoOuts.i32(i32 42, i32 [[A1]], i32 [[B1]])
+; CHECK-NEXT:    [[LO0:%.*]] = extractvalue [[DX_TYPES_TWOI32]] [[M0]], 1
+; CHECK-NEXT:    [[LO1:%.*]] = extractvalue [[DX_TYPES_TWOI32]] [[M1]], 1
+; CHECK-NEXT:    [[R0:%.*]] = insertelement <2 x i32> poison, i32 [[LO0]], i64 0
+; CHECK-NEXT:    [[R:%.*]] = insertelement <2 x i32> [[R0]], i32 [[LO1]], i64 1
+; CHECK-NEXT:    ret <2 x i32> [[R]]
+;
+  %m = call { <2 x i32>, <2 x i32> } @llvm.dx.umul.v2i32(<2 x i32> %a, <2 x i32> %b)
+  %lo = extractvalue { <2 x i32>, <2 x i32> } %m, 1
+  ret <2 x i32> %lo
+}
diff --git a/llvm/test/CodeGen/DirectX/overflow_intrinsics.ll b/llvm/test/CodeGen/DirectX/overflow_intrinsics.ll
new file mode 100644
index 0000000000000..f83b7b46e5859
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/overflow_intrinsics.ll
@@ -0,0 +1,87 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -dxil-intrinsic-expansion -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+
+; Verify the {u,s}mul.with.overflow intrinsics are expanded away, since DXIL has
+; no llvm.*.with.overflow op. The overflow check is computed without a wider
+; integer type so that no 64-bit ops are introduced.
+
+define { i32, i1 } @umul_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: define { i32, i1 } @umul_i32(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call { i32, i32 } @llvm.dx.umul.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT:    [[TMP17:%.*]] = extractvalue { i32, i32 } [[TMP2]], 0
+; CHECK-NEXT:    [[TMP1:%.*]] = extractvalue { i32, i32 } [[TMP2]], 1
+; CHECK-NEXT:    [[TMP18:%.*]] = icmp ne i32 [[TMP17]], 0
+; CHECK-NEXT:    [[TMP19:%.*]] = insertvalue { i32, i1 } poison, i32 [[TMP1]], 0
+; CHECK-NEXT:    [[TMP20:%.*]] = insertvalue { i32, i1 } [[TMP19]], i1 [[TMP18]], 1
+; CHECK-NEXT:    ret { i32, i1 } [[TMP20]]
+;
+  %r = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %a, i32 %b)
+  ret { i32, i1 } %r
+}
+
+define { <4 x i32>, <4 x i1> } @umul_v4i32(<4 x i32> %a, <4 x i32> %b) {
+; CHECK-LABEL: define { <4 x i32>, <4 x i1> } @umul_v4i32(
+; CHECK-SAME: <4 x i32> [[A:%.*]], <4 x i32> [[B:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call { <4 x i32>, <4 x i32> } @llvm.dx.umul.v4i32(<4 x i32> [[A]], <4 x i32> [[B]])
+; CHECK-NEXT:    [[TMP17:%.*]] = extractvalue { <4 x i32>, <4 x i32> } [[TMP2]], 0
+; CHECK-NEXT:    [[TMP1:%.*]] = extractvalue { <4 x i32>, <4 x i32> } [[TMP2]], 1
+; CHECK-NEXT:    [[TMP18:%.*]] = icmp ne <4 x i32> [[TMP17]], zeroinitializer
+; CHECK-NEXT:    [[TMP19:%.*]] = insertvalue { <4 x i32>, <4 x i1> } poison, <4 x i32> [[TMP1]], 0
+; CHECK-NEXT:    [[TMP20:%.*]] = insertvalue { <4 x i32>, <4 x i1> } [[TMP19]], <4 x i1> [[TMP18]], 1
+; CHECK-NEXT:    ret { <4 x i32>, <4 x i1> } [[TMP20]]
+;
+  %r = call { <4 x i32>, <4 x i1> } @llvm.umul.with.overflow.v4i32(<4 x i32> %a, <4 x i32> %b)
+  ret { <4 x i32>, <4 x i1> } %r
+}
+
+define { i32, i1 } @smul_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: define { i32, i1 } @smul_i32(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[TMP2:%.*]] = call { i32, i32 } @llvm.dx.imul.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT:    [[TMP23:%.*]] = extractvalue { i32, i32 } [[TMP2]], 0
+; CHECK-NEXT:    [[TMP1:%.*]] = extractvalue { i32, i32 } [[TMP2]], 1
+; CHECK-NEXT:    [[TMP24:%.*]] = ashr i32 [[TMP1]], 31
+; CHECK-NEXT:    [[TMP25:%.*]] = icmp ne i32 [[TMP23]], [[TMP24]]
+; CHECK-NEXT:    [[TMP26:%.*]] = insertvalue { i32, i1 } poison, i32 [[TMP1]], 0
+; CHECK-NEXT:    [[TMP27:%.*]] = insertvalue { i32, i1 } [[TMP26]], i1 [[TMP25]], 1
+; CHECK-NEXT:    ret { i32, i1 } [[TMP27]]
+;
+  %r = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 %a, i32 %b)
+  ret { i32, i1 } %r
+}
+
+; Narrow overloads widen to a 2*BW multiply, since that stays within 32 bits.
+define { i16, i1 } @umul_i16(i16 %a, i16 %b) {
+; CHECK-LABEL: define { i16, i1 } @umul_i16(
+; CHECK-SAME: i16 [[A:%.*]], i16 [[B:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = mul i16 [[A]], [[B]]
+; CHECK-NEXT:    [[TMP2:%.*]] = zext i16 [[A]] to i32
+; CHECK-NEXT:    [[TMP3:%.*]] = zext i16 [[B]] to i32
+; CHECK-NEXT:    [[TMP4:%.*]] = mul i32 [[TMP2]], [[TMP3]]
+; CHECK-NEXT:    [[TMP5:%.*]] = lshr i32 [[TMP4]], 16
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp ne i32 [[TMP5]], 0
+; CHECK-NEXT:    [[TMP7:%.*]] = insertvalue { i16, i1 } poison, i16 [[TMP1]], 0
+; CHECK-NEXT:    [[TMP8:%.*]] = insertvalue { i16, i1 } [[TMP7]], i1 [[TMP6]], 1
+; CHECK-NEXT:    ret { i16, i1 } [[TMP8]]
+;
+  %r = call { i16, i1 } @llvm.umul.with.overflow.i16(i16 %a, i16 %b)
+  ret { i16, i1 } %r
+}
+
+define { i16, i1 } @smul_i16(i16 %a, i16 %b) {
+; CHECK-LABEL: define { i16, i1 } @smul_i16(
+; CHECK-SAME: i16 [[A:%.*]], i16 [[B:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = mul i16 [[A]], [[B]]
+; CHECK-NEXT:    [[TMP2:%.*]] = sext i16 [[A]] to i32
+; CHECK-NEXT:    [[TMP3:%.*]] = sext i16 [[B]] to i32
+; CHECK-NEXT:    [[TMP4:%.*]] = mul i32 [[TMP2]], [[TMP3]]
+; CHECK-NEXT:    [[TMP5:%.*]] = sext i16 [[TMP1]] to i32
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp ne i32 [[TMP4]], [[TMP5]]
+; CHECK-NEXT:    [[TMP7:%.*]] = insertvalue { i16, i1 } poison, i16 [[TMP1]], 0
+; CHECK-NEXT:    [[TMP8:%.*]] = insertvalue { i16, i1 } [[TMP7]], i1 [[TMP6]], 1
+; CHECK-NEXT:    ret { i16, i1 } [[TMP8]]
+;
+  %r = call { i16, i1 } @llvm.smul.with.overflow.i16(i16 %a, i16 %b)
+  ret { i16, i1 } %r
+}

>From f553d1ccf9afba05d4dcffb03e4a5f953017d380 Mon Sep 17 00:00:00 2001
From: Damyan Pepper <damyanp at microsoft.com>
Date: Fri, 3 Jul 2026 11:18:29 -0700
Subject: [PATCH 2/2] [DirectX] Fix clang-format in DXILIntrinsicExpansion

Co-authored-by: Copilot <223556219+Copilot at users.noreply.github.com>
---
 llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index aa2a3515f9260..c4c74295839d1 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -1369,8 +1369,8 @@ static bool expandIntrinsic(Function &F, CallInst *Orig) {
     break;
   case Intrinsic::umul_with_overflow:
   case Intrinsic::smul_with_overflow:
-    Result = expandMulWithOverflow(
-        Orig, /*Signed=*/IntrinsicId == Intrinsic::smul_with_overflow);
+    Result = expandMulWithOverflow(Orig, /*Signed=*/IntrinsicId ==
+                                             Intrinsic::smul_with_overflow);
     break;
   case Intrinsic::vector_reduce_add:
   case Intrinsic::vector_reduce_fadd:



More information about the llvm-commits mailing list