[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