Bug type: miscompilation
Component: InstCombine
Bug location: llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp: InstCombinerImpl::foldAndOrOfICmps
foldIsPowerOf2
Issue title: [InstCombine] poison-generating attributes are not dropped when folding logical and/or of icmps
Labels: miscompilation
llvm:instcombine
Reported by: dtcxzyw

Reproducer: https://alive2.llvm.org/ce/z/rSUfvo
```
define i1 @src(i32 noundef %Value) {
entry:
  %cmp.not = icmp eq i32 %Value, 0
  %0 = call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %Value)
  %cmp1 = icmp ult i32 %0, 2
  %1 = select i1 %cmp.not, i1 false, i1 %cmp1
  ret i1 %1
}

define i1 @tgt(i32 noundef %Value) {
entry:
  %0 = call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %Value)
  %1 = icmp eq i32 %0, 1
  ret i1 %1
}
```
When `%Value == 0`, `@src` returns `false` but `@tgt` returns `poison`.
This miscompilation was triggered after https://github.com/llvm/llvm-project/pull/111284 was landed.



Fix patch:
commit 6a65e98fa7901dc1de91172d065fafb16ce89d77
Author: Yingwei Zheng <dtcxzyw2333@gmail.com>
Date:   Fri Oct 11 18:19:21 2024 +0800

    [InstCombine] Drop range attributes in `foldIsPowerOf2` (#111946)
    
    Fixes https://github.com/llvm/llvm-project/issues/111934.

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 688601a8ffa5..964616a4eb35 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -955,9 +955,11 @@ static Value *foldIsPowerOf2OrZero(ICmpInst *Cmp0, ICmpInst *Cmp1, bool IsAnd,
 }
 
 /// Reduce a pair of compares that check if a value has exactly 1 bit set.
-/// Also used for logical and/or, must be poison safe.
+/// Also used for logical and/or, must be poison safe if range attributes are
+/// dropped.
 static Value *foldIsPowerOf2(ICmpInst *Cmp0, ICmpInst *Cmp1, bool JoinedByAnd,
-                             InstCombiner::BuilderTy &Builder) {
+                             InstCombiner::BuilderTy &Builder,
+                             InstCombinerImpl &IC) {
   // Handle 'and' / 'or' commutation: make the equality check the first operand.
   if (JoinedByAnd && Cmp1->getPredicate() == ICmpInst::ICMP_NE)
     std::swap(Cmp0, Cmp1);
@@ -971,7 +973,10 @@ static Value *foldIsPowerOf2(ICmpInst *Cmp0, ICmpInst *Cmp1, bool JoinedByAnd,
       match(Cmp1, m_SpecificICmp(ICmpInst::ICMP_ULT,
                                  m_Intrinsic<Intrinsic::ctpop>(m_Specific(X)),
                                  m_SpecificInt(2)))) {
-    Value *CtPop = Cmp1->getOperand(0);
+    auto *CtPop = cast<Instruction>(Cmp1->getOperand(0));
+    // Drop range attributes and re-infer them in the next iteration.
+    CtPop->dropPoisonGeneratingAnnotations();
+    IC.addToWorklist(CtPop);
     return Builder.CreateICmpEQ(CtPop, ConstantInt::get(CtPop->getType(), 1));
   }
   // (X == 0) || (ctpop(X) u> 1) --> ctpop(X) != 1
@@ -980,7 +985,10 @@ static Value *foldIsPowerOf2(ICmpInst *Cmp0, ICmpInst *Cmp1, bool JoinedByAnd,
       match(Cmp1, m_SpecificICmp(ICmpInst::ICMP_UGT,
                                  m_Intrinsic<Intrinsic::ctpop>(m_Specific(X)),
                                  m_SpecificInt(1)))) {
-    Value *CtPop = Cmp1->getOperand(0);
+    auto *CtPop = cast<Instruction>(Cmp1->getOperand(0));
+    // Drop range attributes and re-infer them in the next iteration.
+    CtPop->dropPoisonGeneratingAnnotations();
+    IC.addToWorklist(CtPop);
     return Builder.CreateICmpNE(CtPop, ConstantInt::get(CtPop->getType(), 1));
   }
   return nullptr;
@@ -3375,7 +3383,7 @@ Value *InstCombinerImpl::foldAndOrOfICmps(ICmpInst *LHS, ICmpInst *RHS,
     if (Value *V = foldSignedTruncationCheck(LHS, RHS, I, Builder))
       return V;
 
-  if (Value *V = foldIsPowerOf2(LHS, RHS, IsAnd, Builder))
+  if (Value *V = foldIsPowerOf2(LHS, RHS, IsAnd, Builder, *this))
     return V;
 
   if (Value *V = foldPowerOf2AndShiftedMask(LHS, RHS, IsAnd, Builder))


Regression tests:
file: llvm/test/Transforms/InstCombine/ispow2.ll
commands: opt < %s -passes=instcombine -S
tests: test_name: has_single_bit
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.ctpop.i32(i32) #0

define i1 @has_single_bit(i32 %x) {
entry:
  %cmp1 = icmp ne i32 %x, 0
  %popcnt = call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %x)
  %cmp2 = icmp ult i32 %popcnt, 2
  %sel = select i1 %cmp1, i1 %cmp2, i1 false
  ret i1 %sel
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: has_single_bit_inv
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.ctpop.i32(i32) #0

define i1 @has_single_bit_inv(i32 %x) {
entry:
  %cmp1 = icmp eq i32 %x, 0
  %popcnt = call range(i32 1, 33) i32 @llvm.ctpop.i32(i32 %x)
  %cmp2 = icmp ugt i32 %popcnt, 1
  %sel = select i1 %cmp1, i1 true, i1 %cmp2
  ret i1 %sel
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: not_pow2_or_z_known_bits_fail_wrong_cmp
test_body: define <2 x i1> @not_pow2_or_z_known_bits_fail_wrong_cmp(<2 x i32> %xin) {
  %x = or <2 x i32> %xin, splat (i32 64)
  %cnt = call <2 x i32> @llvm.ctpop.v2i32(<2 x i32> %x)
  %r = icmp ugt <2 x i32> %cnt, splat (i32 2)
  ret <2 x i1> %r
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare <2 x i32> @llvm.ctpop.v2i32(<2 x i32>) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }