Skip to content

[InstCombine] Convert logical and/or with trunc nuw to i1 into bitwise ops#198178

Merged
andjo403 merged 3 commits into
llvm:mainfrom
andjo403:logicalAndOrToBinaryForTruncNuw
May 17, 2026
Merged

[InstCombine] Convert logical and/or with trunc nuw to i1 into bitwise ops#198178
andjo403 merged 3 commits into
llvm:mainfrom
andjo403:logicalAndOrToBinaryForTruncNuw

Conversation

@andjo403
Copy link
Copy Markdown
Contributor

if it is know that trunc nuw to i1 can not be poison logical and/or can be folded to bitwise ops.

regression seen in dtcxzyw/llvm-opt-benchmark#3528 (comment) from #184182

proof https://alive2.llvm.org/ce/z/xQ2Sj-

@andjo403 andjo403 requested review from dtcxzyw and nikic May 17, 2026 13:34
@llvmorg-github-actions llvmorg-github-actions Bot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels May 17, 2026
@llvmorg-github-actions
Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

Author: Andreas Jonson (andjo403)

Changes

if it is know that trunc nuw to i1 can not be poison logical and/or can be folded to bitwise ops.

regression seen in dtcxzyw/llvm-opt-benchmark#3528 (comment) from #184182

proof https://alive2.llvm.org/ce/z/xQ2Sj-


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp (+15-5)
  • (modified) llvm/test/Transforms/InstCombine/logical-select.ll (+55)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index a064aa1016399..4a47a2a06809b 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -3653,7 +3653,7 @@ static Instruction *foldNestedSelects(SelectInst &OuterSelVal,
 /// already poison. For example, if ValAssumedPoison is `icmp samesign X, 10`
 /// and V is `icmp ne X, 5`, impliesPoisonOrCond returns true.
 static bool impliesPoisonOrCond(const Value *ValAssumedPoison, const Value *V,
-                                bool Expected) {
+                                bool Expected, const SimplifyQuery &SQ) {
   if (impliesPoison(ValAssumedPoison, V))
     return true;
 
@@ -3678,6 +3678,16 @@ static bool impliesPoisonOrCond(const Value *ValAssumedPoison, const Value *V,
                       *RHSC2);
     }
   }
+  Value *A;
+  if (match(ValAssumedPoison, m_NUWTrunc(m_Value(A))) &&
+      isGuaranteedNotToBePoison(A)) {
+    assert(ValAssumedPoison->getType()->isIntOrIntVectorTy(1));
+    unsigned BitWidth = A->getType()->getScalarSizeInBits();
+    return ConstantRange(APInt::getZero(BitWidth), APInt(BitWidth, 2))
+        .contains(computeConstantRangeIncludingKnownBits(
+            A, false,
+            SQ.getWithInstruction(cast<Instruction>(ValAssumedPoison))));
+  }
 
   return false;
 }
@@ -3703,13 +3713,13 @@ Instruction *InstCombinerImpl::foldSelectOfBools(SelectInst &SI) {
   // checks whether folding it does not convert a well-defined value into
   // poison.
   if (match(TrueVal, m_One())) {
-    if (impliesPoisonOrCond(FalseVal, CondVal, /*Expected=*/false)) {
+    if (impliesPoisonOrCond(FalseVal, CondVal, /*Expected=*/false, SQ)) {
       // Change: A = select B, true, C --> A = or B, C
       return BinaryOperator::CreateOr(CondVal, FalseVal);
     }
 
     if (match(CondVal, m_OneUse(m_Select(m_Value(A), m_One(), m_Value(B)))) &&
-        impliesPoisonOrCond(FalseVal, B, /*Expected=*/false)) {
+        impliesPoisonOrCond(FalseVal, B, /*Expected=*/false, SQ)) {
       // (A || B) || C --> A || (B | C)
       Value *LOr = Builder.CreateLogicalOr(A, Builder.CreateOr(B, FalseVal));
       if (auto *I = dyn_cast<Instruction>(LOr)) {
@@ -3749,13 +3759,13 @@ Instruction *InstCombinerImpl::foldSelectOfBools(SelectInst &SI) {
   }
 
   if (match(FalseVal, m_Zero())) {
-    if (impliesPoisonOrCond(TrueVal, CondVal, /*Expected=*/true)) {
+    if (impliesPoisonOrCond(TrueVal, CondVal, /*Expected=*/true, SQ)) {
       // Change: A = select B, C, false --> A = and B, C
       return BinaryOperator::CreateAnd(CondVal, TrueVal);
     }
 
     if (match(CondVal, m_OneUse(m_Select(m_Value(A), m_Value(B), m_Zero()))) &&
-        impliesPoisonOrCond(TrueVal, B, /*Expected=*/true)) {
+        impliesPoisonOrCond(TrueVal, B, /*Expected=*/true, SQ)) {
       // (A && B) && C --> A && (B & C)
       Value *LAnd = Builder.CreateLogicalAnd(A, Builder.CreateAnd(B, TrueVal));
       if (auto *I = dyn_cast<Instruction>(LAnd)) {
diff --git a/llvm/test/Transforms/InstCombine/logical-select.ll b/llvm/test/Transforms/InstCombine/logical-select.ll
index e6de063969a6a..85e8c98455c91 100644
--- a/llvm/test/Transforms/InstCombine/logical-select.ll
+++ b/llvm/test/Transforms/InstCombine/logical-select.ll
@@ -1637,3 +1637,58 @@ define <2 x i1> @test_logical_and_icmp_samesign_vec_with_poison_tv(<2 x i8> %x)
   %and = select <2 x i1> %cmp1, <2 x i1> %cmp2, <2 x i1> zeroinitializer
   ret <2 x i1> %and
 }
+
+define i1 @test_logical_and_trunc_nuw(i1 %c, i8 noundef range(i8 0,2) %x) {
+; CHECK-LABEL: @test_logical_and_trunc_nuw(
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[AND:%.*]] = and i1 [[C:%.*]], [[TRUNC]]
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %trunc = trunc nuw i8 %x to i1
+  %and = select i1 %c, i1 %trunc, i1 false
+  ret i1 %and
+}
+
+define i1 @test_logical_or_trunc_nuw(i1 %c, i8 noundef range(i8 0,2) %x) {
+; CHECK-LABEL: @test_logical_or_trunc_nuw(
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[OR:%.*]] = or i1 [[C:%.*]], [[TRUNC]]
+; CHECK-NEXT:    ret i1 [[OR]]
+;
+  %trunc = trunc nuw i8 %x to i1
+  %or = select i1 %c, i1 true, i1 %trunc
+  ret i1 %or
+}
+
+define <2 x i1> @test_logical_and_trunc_nuw_vec(<2 x i1> %c, <2 x i8> noundef range(i8 0,2) %x) {
+; CHECK-LABEL: @test_logical_and_trunc_nuw_vec(
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw <2 x i8> [[X:%.*]] to <2 x i1>
+; CHECK-NEXT:    [[AND:%.*]] = and <2 x i1> [[C:%.*]], [[TRUNC]]
+; CHECK-NEXT:    ret <2 x i1> [[AND]]
+;
+  %trunc = trunc nuw <2 x i8> %x to <2 x i1>
+  %and = select <2 x i1> %c, <2 x i1> %trunc, <2 x i1> zeroinitializer
+  ret <2 x i1> %and
+}
+
+define i1 @neg_test_logical_and_trunc_nuw_no_range(i1 %c, i8 noundef %x) {
+; CHECK-LABEL: @neg_test_logical_and_trunc_nuw_no_range(
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C:%.*]], i1 [[TRUNC]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %trunc = trunc nuw i8 %x to i1
+  %and = select i1 %c, i1 %trunc, i1 false
+  ret i1 %and
+}
+
+define i1 @neg_test_logical_and_trunc_nuw_no_noundef(i1 %c, i8 range(i8 0,2) %x) {
+; CHECK-LABEL: @neg_test_logical_and_trunc_nuw_no_noundef(
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C:%.*]], i1 [[TRUNC]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %trunc = trunc nuw i8 %x to i1
+  %and = select i1 %c, i1 %trunc, i1 false
+  ret i1 %and
+}

assert(ValAssumedPoison->getType()->isIntOrIntVectorTy(1));
unsigned BitWidth = A->getType()->getScalarSizeInBits();
return ConstantRange(APInt::getZero(BitWidth), APInt(BitWidth, 2))
.contains(computeConstantRangeIncludingKnownBits(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use just computeKnownBits here? I don't think computeConstantRange() adds any precision here, as it's a power of 2 range.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no changes in local run of llvm-opt-benchmark with the change so fix pushed

Comment thread llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
Copy link
Copy Markdown
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@andjo403 andjo403 merged commit c497efb into llvm:main May 17, 2026
10 checks passed
@andjo403 andjo403 deleted the logicalAndOrToBinaryForTruncNuw branch May 17, 2026 19:35
pedroMVicente pushed a commit to pedroMVicente/llvm-project that referenced this pull request May 19, 2026
…e ops (llvm#198178)

if it is know that `trunc nuw to i1 ` can not be poison logical and/or
can be folded to bitwise ops.

proof https://alive2.llvm.org/ce/z/xQ2Sj-
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants