[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