[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