[llvm] [InstCombine] Generalize bools mul to and fold using KnownBits (PR #219355)

via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 27 20:42:12 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Isaac Chan (i-nahc)

<details>
<summary>Changes</summary>

use computeKnownBits to fold mul to and when both operands are known to be in the range {0,1}

Alive2: https://alive2.llvm.org/ce/z/uYFqiZ

Fixes: #<!-- -->214333 

Note: Chris Lattner actually had an old comment about this generalization/change in the visitMul function 

---
Full diff: https://github.com/llvm/llvm-project/pull/219355.diff


3 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp (+3-3) 
- (modified) llvm/test/Transforms/InstCombine/mul-inseltpoison.ll (+1-1) 
- (modified) llvm/test/Transforms/InstCombine/mul.ll (+82-3) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index 128e3e3dcdfe8..aae1e1e589f87 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -498,11 +498,11 @@ Instruction *InstCombinerImpl::visitMul(BinaryOperator &I) {
   // Fold the following two scenarios:
   //   1) i1 mul -> i1 and.
   //   2) X * Y --> X & Y, iff X, Y can be only {0,1}.
-  // Note: We could use known bits to generalize this and related patterns with
-  // shifts/truncs
   if (Ty->isIntOrIntVectorTy(1) ||
       (match(Op0, m_And(m_Value(), m_One())) &&
-       match(Op1, m_And(m_Value(), m_One()))))
+       match(Op1, m_And(m_Value(), m_One()))) ||
+      (computeKnownBits(Op0, &I).countMaxActiveBits() <= 1 &&
+       computeKnownBits(Op1, &I).countMaxActiveBits() <= 1))
     return BinaryOperator::CreateAnd(Op0, Op1);
 
   if (Value *R = foldMulShl1(I, /* CommuteOperands */ false, Builder))
diff --git a/llvm/test/Transforms/InstCombine/mul-inseltpoison.ll b/llvm/test/Transforms/InstCombine/mul-inseltpoison.ll
index 8baf6a70fdd5d..8a22e17292458 100644
--- a/llvm/test/Transforms/InstCombine/mul-inseltpoison.ll
+++ b/llvm/test/Transforms/InstCombine/mul-inseltpoison.ll
@@ -177,7 +177,7 @@ define i32 @mul_bools_use3(i1 %x, i1 %y) {
 ; CHECK-NEXT:    call void @use32(i32 [[ZX]])
 ; CHECK-NEXT:    [[ZY:%.*]] = zext i1 [[Y:%.*]] to i32
 ; CHECK-NEXT:    call void @use32(i32 [[ZY]])
-; CHECK-NEXT:    [[R:%.*]] = select i1 [[X]], i32 [[ZY]], i32 0
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[ZX]], [[ZY]]
 ; CHECK-NEXT:    ret i32 [[R]]
 ;
   %zx = zext i1 %x to i32
diff --git a/llvm/test/Transforms/InstCombine/mul.ll b/llvm/test/Transforms/InstCombine/mul.ll
index f0bb1e90ec069..0e2ee99269f1d 100644
--- a/llvm/test/Transforms/InstCombine/mul.ll
+++ b/llvm/test/Transforms/InstCombine/mul.ll
@@ -436,7 +436,7 @@ define i32 @mul_bools_use3(i1 %x, i1 %y) !prof !0 {
 ; CHECK-NEXT:    call void @use32(i32 [[ZX]])
 ; CHECK-NEXT:    [[ZY:%.*]] = zext i1 [[Y:%.*]] to i32
 ; CHECK-NEXT:    call void @use32(i32 [[ZY]])
-; CHECK-NEXT:    [[R:%.*]] = select i1 [[X]], i32 [[ZY]], i32 0, !prof [[PROF1]]
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[ZX]], [[ZY]]
 ; CHECK-NEXT:    ret i32 [[R]]
 ;
   %zx = zext i1 %x to i32
@@ -567,8 +567,7 @@ define i32 @mul_bool_zext_one_extra_user(i1 %x) {
 ; CHECK-LABEL: @mul_bool_zext_one_extra_user(
 ; CHECK-NEXT:    [[SX:%.*]] = zext i1 [[X:%.*]] to i32
 ; CHECK-NEXT:    call void @use32(i32 [[SX]])
-; CHECK-NEXT:    [[R:%.*]] = zext i1 [[X]] to i32
-; CHECK-NEXT:    ret i32 [[R]]
+; CHECK-NEXT:    ret i32 [[SX]]
 ;
   %sx = zext i1 %x to i32
   call void @use32(i32 %sx)
@@ -2519,7 +2518,84 @@ define i1 @neg_mul_add_one_i1(i1 %x, i1 %y) {
   ret i1 %ret
 }
 
+; mul of operands known to be {0,1} via !range metadata folds to and.
+; Alive2: https://alive2.llvm.org/ce/z/uYFqiZ
+define i8 @mul_known_bool_range_noundef(ptr %p, ptr %q) {
+; CHECK-LABEL: @mul_known_bool_range_noundef(
+; CHECK-NEXT:    [[X:%.*]] = load i8, ptr [[P:%.*]], align 1, !range [[RNG2:![0-9]+]], !noundef [[META3:![0-9]+]]
+; CHECK-NEXT:    [[Y:%.*]] = load i8, ptr [[Q:%.*]], align 1, !range [[RNG2]], !noundef [[META3]]
+; CHECK-NEXT:    [[M:%.*]] = and i8 [[Y]], [[X]]
+; CHECK-NEXT:    ret i8 [[M]]
+;
+  %x = load i8, ptr %p, align 1, !range !10, !noundef !11
+  %y = load i8, ptr %q, align 1, !range !10, !noundef !11
+  %m = mul i8 %y, %x
+  ret i8 %m
+}
+
+; mul->and is a poison-safe refinement, so !noundef is not required.
+define i8 @mul_known_bool_range_no_noundef(ptr %p, ptr %q) {
+; CHECK-LABEL: @mul_known_bool_range_no_noundef(
+; CHECK-NEXT:    [[X:%.*]] = load i8, ptr [[P:%.*]], align 1, !range [[RNG2]]
+; CHECK-NEXT:    [[Y:%.*]] = load i8, ptr [[Q:%.*]], align 1, !range [[RNG2]]
+; CHECK-NEXT:    [[M:%.*]] = and i8 [[Y]], [[X]]
+; CHECK-NEXT:    ret i8 [[M]]
+;
+  %x = load i8, ptr %p, align 1, !range !10
+  %y = load i8, ptr %q, align 1, !range !10
+  %m = mul i8 %y, %x
+  ret i8 %m
+}
+
+; Vector variant
+
+define <2 x i8> @mul_known_bool_range_vec(ptr %p, ptr %q) {
+; CHECK-LABEL: @mul_known_bool_range_vec(
+; CHECK-NEXT:    [[X:%.*]] = load <2 x i8>, ptr [[P:%.*]], align 2, !range [[RNG2]], !noundef [[META3]]
+; CHECK-NEXT:    [[Y:%.*]] = load <2 x i8>, ptr [[Q:%.*]], align 2, !range [[RNG2]], !noundef [[META3]]
+; CHECK-NEXT:    [[M:%.*]] = and <2 x i8> [[Y]], [[X]]
+; CHECK-NEXT:    ret <2 x i8> [[M]]
+;
+  %x = load <2 x i8>, ptr %p, align 2, !range !10, !noundef !11
+  %y = load <2 x i8>, ptr %q, align 2, !range !10, !noundef !11
+  %m = mul <2 x i8> %y, %x
+  ret <2 x i8> %m
+}
+
+; Negative test: operands not known to be {0,1}.
+
+define i8 @mul_not_bool_range(ptr %p, ptr %q) {
+; CHECK-LABEL: @mul_not_bool_range(
+; CHECK-NEXT:    [[X:%.*]] = load i8, ptr [[P:%.*]], align 1, !range [[RNG4:![0-9]+]], !noundef [[META3]]
+; CHECK-NEXT:    [[Y:%.*]] = load i8, ptr [[Q:%.*]], align 1, !range [[RNG4]], !noundef [[META3]]
+; CHECK-NEXT:    [[M:%.*]] = mul nuw nsw i8 [[Y]], [[X]]
+; CHECK-NEXT:    ret i8 [[M]]
+;
+  %x = load i8, ptr %p, align 1, !range !12, !noundef !11
+  %y = load i8, ptr %q, align 1, !range !12, !noundef !11
+  %m = mul i8 %y, %x
+  ret i8 %m
+}
+
+; Negative test: only one operand is known to be {0,1}.
+
+define i8 @mul_one_bool_one_not(ptr %p, ptr %q) {
+; CHECK-LABEL: @mul_one_bool_one_not(
+; CHECK-NEXT:    [[X:%.*]] = load i8, ptr [[P:%.*]], align 1, !range [[RNG2]], !noundef [[META3]]
+; CHECK-NEXT:    [[Y:%.*]] = load i8, ptr [[Q:%.*]], align 1, !range [[RNG4]], !noundef [[META3]]
+; CHECK-NEXT:    [[M:%.*]] = mul nuw nsw i8 [[Y]], [[X]]
+; CHECK-NEXT:    ret i8 [[M]]
+;
+  %x = load i8, ptr %p, align 1, !range !10, !noundef !11
+  %y = load i8, ptr %q, align 1, !range !12, !noundef !11
+  %m = mul i8 %y, %x
+  ret i8 %m
+}
+
 !0 = !{!"function_entry_count", i64 1000}
+!10 = !{i8 0, i8 2}
+!11 = !{}
+!12 = !{i8 0, i8 4}
 ;.
 ; 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) }
@@ -2527,4 +2603,7 @@ define i1 @neg_mul_add_one_i1(i1 %x, i1 %y) {
 ;.
 ; CHECK: [[META0:![0-9]+]] = !{!"function_entry_count", i64 1000}
 ; CHECK: [[PROF1]] = !{!"unknown", !"instcombine"}
+; CHECK: [[RNG2]] = !{i8 0, i8 2}
+; CHECK: [[META3]] = !{}
+; CHECK: [[RNG4]] = !{i8 0, i8 4}
 ;.

``````````

</details>


https://github.com/llvm/llvm-project/pull/219355


More information about the llvm-commits mailing list