[llvm] [InstCombine] Generalize bools mul to and fold using KnownBits (PR #219355)
Isaac Chan via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 27 20:41:23 PDT 2026
https://github.com/i-nahc updated https://github.com/llvm/llvm-project/pull/219355
>From e85f3cdea62b799fc33d0a1b45aaabcf22598061 Mon Sep 17 00:00:00 2001
From: i-nahc <i7chan at uwaterloo.ca>
Date: Tue, 25 Aug 2026 22:28:15 -0400
Subject: [PATCH 1/5] First attempt at fixing bool mul opt
---
.../InstCombine/InstCombineMulDivRem.cpp | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index 128e3e3dcdfe8..ca6197418c418 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -495,14 +495,21 @@ 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
+ // 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
+
+ // Link to issue for posterity: https://github.com/llvm/llvm-project/issues/214333
+ // first attempt: make it use the noted change (I guess this is probably
+ // a todo or something from whoever wrote this previously)
+ // TODO: Update reg tests and also later see about the Alive2 proof saying they weren't identical
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))
>From 1e1d5d14081b8ee596b960f8f51fd0d61927c17f Mon Sep 17 00:00:00 2001
From: i-nahc <i7chan at uwaterloo.ca>
Date: Thu, 27 Aug 2026 22:53:12 -0400
Subject: [PATCH 2/5] Update InstCombine mul pass
---
.../InstCombine/InstCombineMulDivRem.cpp | 21 +++++++------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index ca6197418c418..e7502132649a1 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -495,21 +495,14 @@ 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
-
- // Link to issue for posterity: https://github.com/llvm/llvm-project/issues/214333
- // first attempt: make it use the noted change (I guess this is probably
- // a todo or something from whoever wrote this previously)
- // TODO: Update reg tests and also later see about the Alive2 proof saying they weren't identical
+ // Fold the following two scenarios:
+ // 1) i1 mul -> i1 and
+ // 2) X * Y -> X & Y, iff X, Y can be only {0,1}
if (Ty->isIntOrIntVectorTy(1) ||
- (match(Op0, m_And(m_Value(), m_One())) &&
- match(Op1, m_And(m_Value(), m_One()))) ||
- (computeKnownBits(Op0, &I).countMaxActiveBits() <= 1 &&
- computeKnownBits(Op1, &I).countMaxActiveBits() <= 1))
+ (match(Op0, 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))
>From 9afb2f2879de0cecc8bf48ca48f4bb45a052923b Mon Sep 17 00:00:00 2001
From: i-nahc <i7chan at uwaterloo.ca>
Date: Thu, 27 Aug 2026 22:55:26 -0400
Subject: [PATCH 3/5] Update mul_bools_use3 test to accept and
---
llvm/test/Transforms/InstCombine/mul-inseltpoison.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
>From 433af6986142990668de8163eb167f41a8b2e70a Mon Sep 17 00:00:00 2001
From: i-nahc <i7chan at uwaterloo.ca>
Date: Thu, 27 Aug 2026 23:19:47 -0400
Subject: [PATCH 4/5] Fix swhitespace
---
.../Transforms/InstCombine/InstCombineMulDivRem.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index e7502132649a1..aae1e1e589f87 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -496,13 +496,13 @@ 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}
+ // 1) i1 mul -> i1 and.
+ // 2) X * Y --> X & Y, iff X, Y can be only {0,1}.
if (Ty->isIntOrIntVectorTy(1) ||
- (match(Op0, m_And(m_Value(), m_One())) &&
- match(Op1, m_And(m_Value(), m_One()))) ||
- (computeKnownBits(Op0, &I).countMaxActiveBits() <= 1 &&
- computeKnownBits(Op1, &I).countMaxActiveBits() <= 1))
+ (match(Op0, 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))
>From f90dcb1a5583c78b6e13f9ee6355bb2a7b40d354 Mon Sep 17 00:00:00 2001
From: i-nahc <i7chan at uwaterloo.ca>
Date: Thu, 27 Aug 2026 23:33:49 -0400
Subject: [PATCH 5/5] update mul test
---
llvm/test/Transforms/InstCombine/mul.ll | 85 ++++++++++++++++++++++++-
1 file changed, 82 insertions(+), 3 deletions(-)
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}
;.
More information about the llvm-commits
mailing list