[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