[llvm] [InstCombine] Fold constant shift/mul into select arms for mul instruction (PR #196872)
via llvm-commits
llvm-commits at lists.llvm.org
Sun May 10 21:41:34 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: FathimaHaris
<details>
<summary>Changes</summary>
Fixes llvm#<!-- -->190907
Extends the optimization reported to cover four symmetric
patterns where one operand of a multiplication is a constant shift or
multiply, and the other is a select with constant arms.
Instead of keeping the outer shl/mul, the constant is pushed into the
select arms:
(shl X, C1) * (select cond, C2, C3) --> X * (select cond, C2<<C1, C3<<C1)
(mul X, C1) * (select cond, C2, C3) --> X * (select cond, C2*C1, C3*C1)
(Also handles their commuted forms )
Alive2 Proof :https://alive2.llvm.org/ce/z/dwTXJw
---
Full diff: https://github.com/llvm/llvm-project/pull/196872.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp (+35)
- (modified) llvm/test/Transforms/InstCombine/mul.ll (+217-6)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index 384f38b2c5362..9d9192d49d5ce 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -331,6 +331,41 @@ Instruction *InstCombinerImpl::visitMul(BinaryOperator &I) {
if (Value *FoldedMul = foldMulSelectToNegate(I, Builder))
return replaceInstUsesWith(I, FoldedMul);
+ // (shl X, C1)*(select cond, C2, C3)--> X * (select cond, C2<<C1, C3<<C1)
+ // (mul X, C1)*(select cond, C2, C3)--> X * (select cond, C2*C1, C3*C1)
+ // (Includes commuted forms)
+
+ {
+ Value *NewOp, *Cond, *OtherValue;
+ Constant *C1, *C2, *C3;
+
+ if (match(&I, m_c_Mul(m_OneUse(m_Value(OtherValue)),
+ m_OneUse(m_Select(m_Value(Cond), m_Constant(C2),
+ m_Constant(C3))))) &&
+ (match(OtherValue, m_c_Mul(m_Value(NewOp), m_Constant(C1))) ||
+ match(OtherValue, m_Shl(m_Value(NewOp), m_Constant(C1))))) {
+
+ auto *OtherInst = cast<OverflowingBinaryOperator>(OtherValue);
+ auto Opc = OtherInst->getOpcode();
+
+ Constant *NewTV = ConstantFoldBinaryOpOperands(Opc, C2, C1, DL);
+ Constant *NewFV = ConstantFoldBinaryOpOperands(Opc, C3, C1, DL);
+
+ if (NewTV && NewFV) {
+ Value *NewSel = Builder.CreateSelect(Cond, NewTV, NewFV);
+ BinaryOperator *BO = BinaryOperator::CreateMul(NewOp, NewSel);
+
+ if (HasNUW && OtherInst->hasNoUnsignedWrap())
+ BO->setHasNoUnsignedWrap();
+ if (HasNSW && OtherInst->hasNoSignedWrap() &&
+ NewTV->isNotMinSignedValue() && NewFV->isNotMinSignedValue())
+ BO->setHasNoSignedWrap();
+
+ return BO;
+ }
+ }
+ }
+
// Simplify mul instructions with a constant RHS.
Constant *MulC;
if (match(Op1, m_ImmConstant(MulC))) {
diff --git a/llvm/test/Transforms/InstCombine/mul.ll b/llvm/test/Transforms/InstCombine/mul.ll
index 696490190511d..8d11c74fd637c 100644
--- a/llvm/test/Transforms/InstCombine/mul.ll
+++ b/llvm/test/Transforms/InstCombine/mul.ll
@@ -1,14 +1,13 @@
+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals
; RUN: opt < %s -passes=instcombine -S | FileCheck %s
; RUN: opt < %s -passes=instcombine -use-constant-int-for-fixed-length-splat -S | FileCheck %s
declare i32 @llvm.abs.i32(i32, i1)
-;.
; CHECK: @g = internal global i32 0, align 4
; CHECK: @PR22087 = external global i32
; CHECK: @X = global i32 5
-;.
define i32 @pow2_multiplier(i32 %A) {
; CHECK-LABEL: @pow2_multiplier(
; CHECK-NEXT: [[B:%.*]] = shl i32 [[A:%.*]], 1
@@ -359,7 +358,7 @@ define i32 @mul_sext_bool(i1 %x) !prof !0 {
; CHECK-LABEL: @mul_sext_bool(
; CHECK-NEXT: [[M:%.*]] = select i1 [[X:%.*]], i32 -42, i32 0
; CHECK-NEXT: ret i32 [[M]]
-;
+
%s = sext i1 %x to i32
%m = mul i32 %s, 42
ret i32 %m
@@ -2235,12 +2234,224 @@ define i16 @mul_udiv_zext_uneq(i8 %x) {
ret i16 %mul
}
+; (shl X, C1) * (select cond, C2, C3) --> X * (select cond, C2<<C1, C3<<C1)
+define i16 @shl_select_mul(i16 %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_mul(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], i16 20, i16 12
+; CHECK-NEXT: [[MUL:%.*]] = mul i16 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %shl = shl i16 %x, 2
+ %sel = select i1 %cond, i16 5, i16 3
+ %mul = mul i16 %shl, %sel
+ ret i16 %mul
+}
+
+; (select cond, C2, C3) * (shl X, C1) --> commuted outer mul
+define i16 @shl_select_commuted(i16 %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_commuted(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], i16 20, i16 12
+; CHECK-NEXT: [[MUL:%.*]] = mul i16 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %shl = shl i16 %x, 2
+ %sel = select i1 %cond, i16 5, i16 3
+ %mul = mul i16 %sel, %shl
+ ret i16 %mul
+}
+
+
+; One select arm is zero (reproduced from issue report)
+define noundef i64 @shl_select_zero_arm(i64 %0, i1 %1) {
+; CHECK-LABEL: @shl_select_zero_arm(
+; CHECK-NEXT: [[TMP3:%.*]] = select i1 [[TMP1:%.*]], i64 4, i64 0
+; CHECK-NEXT: [[TMP4:%.*]] = mul i64 [[TMP0:%.*]], [[TMP3]]
+; CHECK-NEXT: ret i64 [[TMP4]]
+;
+ %3 = select i1 %1, i64 2, i64 0
+ %4 = shl i64 %0, 1
+ %5 = mul i64 %4, %3
+ ret i64 %5
+}
+
+; (mul X, C1) * (select cond, C2, C3) --> X * (select cond, C2*C1, C3*C1)
+define i16 @mul_select_mul(i16 %x, i1 %cond) {
+; CHECK-LABEL: @mul_select_mul(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], i16 12, i16 6
+; CHECK-NEXT: [[MUL:%.*]] = mul i16 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %mul1 = mul i16 %x, 3
+ %sel = select i1 %cond, i16 4, i16 2
+ %mul = mul i16 %mul1, %sel
+ ret i16 %mul
+}
+
+
+; Flag tests
+
+; No flags on outer mul — no flags on result
+define i16 @shl_select_no_flags(i16 %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_no_flags(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], i16 20, i16 12
+; CHECK-NEXT: [[MUL:%.*]] = mul i16 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %shl = shl nsw i16 %x, 2
+ %sel = select i1 %cond, i16 5, i16 3
+ %mul = mul i16 %shl, %sel
+ ret i16 %mul
+}
+
+; Only nuw on outer mul - no flag on result
+define i16 @shl_select_nuw_only(i16 %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_nuw_only(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], i16 20, i16 12
+; CHECK-NEXT: [[MUL:%.*]] = mul i16 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %shl = shl i16 %x, 2
+ %sel = select i1 %cond, i16 5, i16 3
+ %mul = mul nuw i16 %shl, %sel
+ ret i16 %mul
+}
+
+
+
+; Both inner shl and outer mul carry nuw nsw — flags on result
+define i16 @shl_select_both_nuw_nsw(i16 %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_both_nuw_nsw(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], i16 20, i16 12
+; CHECK-NEXT: [[MUL:%.*]] = mul nuw nsw i16 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %shl = shl nuw nsw i16 %x, 2
+ %sel = select i1 %cond, i16 5, i16 3
+ %mul = mul nuw nsw i16 %shl, %sel
+ ret i16 %mul
+}
+
+
+; Vector tests — splat
+
+; shl splat vector
+define <4 x i16> @shl_select_vec_splat(<4 x i16> %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_vec_splat(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], <4 x i16> splat (i16 20), <4 x i16> splat (i16 12)
+; CHECK-NEXT: [[MUL:%.*]] = mul <4 x i16> [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret <4 x i16> [[MUL]]
+;
+ %shl = shl <4 x i16> %x, <i16 2, i16 2, i16 2, i16 2>
+ %sel = select i1 %cond,
+ <4 x i16> <i16 5, i16 5, i16 5, i16 5>,
+ <4 x i16> <i16 3, i16 3, i16 3, i16 3>
+ %mul = mul <4 x i16> %shl, %sel
+ ret <4 x i16> %mul
+}
+
+; mul splat vector
+define <4 x i16> @mul_select_vec_splat(<4 x i16> %x, i1 %cond) {
+; CHECK-LABEL: @mul_select_vec_splat(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], <4 x i16> splat (i16 12), <4 x i16> splat (i16 6)
+; CHECK-NEXT: [[MUL:%.*]] = mul <4 x i16> [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret <4 x i16> [[MUL]]
+;
+ %mul1 = mul <4 x i16> %x, <i16 3, i16 3, i16 3, i16 3>
+ %sel = select i1 %cond,
+ <4 x i16> <i16 4, i16 4, i16 4, i16 4>,
+ <4 x i16> <i16 2, i16 2, i16 2, i16 2>
+ %mul = mul <4 x i16> %mul1, %sel
+ ret <4 x i16> %mul
+}
+
+; splat with poison element
+define <4 x i16> @shl_select_vector_with_poison(<4 x i16> %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_vector_with_poison(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], <4 x i16> <i16 20, i16 poison, i16 poison, i16 20>, <4 x i16> <i16 12, i16 poison, i16 poison, i16 12>
+; CHECK-NEXT: [[MUL:%.*]] = mul <4 x i16> [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret <4 x i16> [[MUL]]
+;
+ %shl = shl <4 x i16> %x, <i16 2, i16 poison, i16 poison, i16 2>
+ %sel = select i1 %cond,
+ <4 x i16> <i16 5, i16 poison, i16 5, i16 5>,
+ <4 x i16> <i16 3, i16 poison, i16 3, i16 3>
+ %mul = mul <4 x i16> %shl, %sel
+ ret <4 x i16> %mul
+}
+
+; non-splat vector
+
+define <4 x i16> @shl_select_vec_non_splat(<4 x i16> %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_vec_non_splat(
+; CHECK-NEXT: [[TMP1:%.*]] = select i1 [[COND:%.*]], <4 x i16> <i16 10, i16 12, i16 56, i16 16>, <4 x i16> <i16 4, i16 16, i16 48, i16 128>
+; CHECK-NEXT: [[MUL:%.*]] = mul <4 x i16> [[X:%.*]], [[TMP1]]
+; CHECK-NEXT: ret <4 x i16> [[MUL]]
+;
+ %shl = shl <4 x i16> %x, <i16 1, i16 2, i16 3, i16 4>
+ %sel = select i1 %cond,
+ <4 x i16> <i16 5, i16 3, i16 7, i16 1>,
+ <4 x i16> <i16 2, i16 4, i16 6, i16 8>
+ %mul = mul <4 x i16> %shl, %sel
+ ret <4 x i16> %mul
+}
+
+; Negative tests — transform should NOT fire
+
+declare void @use16(i16)
+; shl has extra use
+define i16 @shl_select_extra_use_shl(i16 %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_extra_use_shl(
+; CHECK-NEXT: [[SHL:%.*]] = shl nuw nsw i16 [[X:%.*]], 2
+; CHECK-NEXT: call void @use16(i16 [[SHL]])
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND:%.*]], i16 5, i16 3
+; CHECK-NEXT: [[MUL:%.*]] = mul nuw nsw i16 [[SHL]], [[SEL]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %shl = shl nuw nsw i16 %x, 2
+ call void @use16(i16 %shl)
+ %sel = select i1 %cond, i16 5, i16 3
+ %mul = mul nuw nsw i16 %shl, %sel
+ ret i16 %mul
+}
+
+; select has extra use
+define i16 @shl_select_extra_use_sel(i16 %x, i1 %cond) {
+; CHECK-LABEL: @shl_select_extra_use_sel(
+; CHECK-NEXT: [[SHL:%.*]] = shl nuw nsw i16 [[X:%.*]], 2
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND:%.*]], i16 5, i16 3
+; CHECK-NEXT: call void @use16(i16 [[SEL]])
+; CHECK-NEXT: [[MUL:%.*]] = mul nuw nsw i16 [[SHL]], [[SEL]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %shl = shl nuw nsw i16 %x, 2
+ %sel = select i1 %cond, i16 5, i16 3
+ call void @use16(i16 %sel)
+ %mul = mul nuw nsw i16 %shl, %sel
+ ret i16 %mul
+}
+
+; inner mul has extra use
+define i16 @mul_select_extra_use_mul(i16 %x, i1 %cond) {
+; CHECK-LABEL: @mul_select_extra_use_mul(
+; CHECK-NEXT: [[MUL1:%.*]] = mul nuw nsw i16 [[X:%.*]], 3
+; CHECK-NEXT: call void @use16(i16 [[MUL1]])
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND:%.*]], i16 9, i16 5
+; CHECK-NEXT: [[MUL:%.*]] = mul nuw nsw i16 [[MUL1]], [[SEL]]
+; CHECK-NEXT: ret i16 [[MUL]]
+;
+ %mul1 = mul nuw nsw i16 %x, 3
+ call void @use16(i16 %mul1)
+ %sel = select i1 %cond, i16 9, i16 5
+ %mul = mul nuw nsw i16 %mul1, %sel
+ ret i16 %mul
+}
+
+
+
!0 = !{!"function_entry_count", i64 1000}
-;.
+
; CHECK: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
; CHECK: attributes #[[ATTR1:[0-9]+]] = { nocallback nocreateundeforpoison nofree nosync nounwind speculatable willreturn memory(none) }
; CHECK: attributes #[[ATTR2:[0-9]+]] = { nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write) }
-;.
; CHECK: [[META0:![0-9]+]] = !{!"function_entry_count", i64 1000}
; CHECK: [[PROF1]] = !{!"unknown", !"instcombine"}
-;.
``````````
</details>
https://github.com/llvm/llvm-project/pull/196872
More information about the llvm-commits
mailing list