Bug type: miscompilation
Component: GVN
NewGVN
Local
Bug location: llvm/lib/Transforms/Scalar/GVN.cpp: GVNPass::performScalarPRE
GVNPass::processAssumeIntrinsic
GVNPass::processInstruction
patchAndReplaceAllUsesWith
llvm/lib/Transforms/Scalar/NewGVN.cpp: NewGVN::eliminateInstructions
NewGVN::findPHIOfOpsLeader
llvm/lib/Transforms/Utils/Local.cpp: llvm::patchReplacementInstruction
Issue title: [GVNPass] Range attribute should be handled after CSE
Labels: miscompilation
llvm:GVN
Reported by: dtcxzyw

Reproducer: https://alive2.llvm.org/ce/z/HAnoKn
```
define i1 @src(i32 noundef %x) {
  %cmp1 = icmp sgt i32 %x, 0
  %ctpop1 = tail call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 %x)
  %cmp2 = icmp samesign ult i32 %ctpop1, 2
  %cond = select i1 %cmp1, i1 %cmp2, i1 false
  br i1 %cond, label %if.then, label %if.else

if.else:
  %ctpop2 = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 %x)
  %res = icmp eq i32 %ctpop2, 1
  ret i1 %res

if.then:
  ret i1 false
}

define i1 @tgt(i32 noundef %x) {
  %cmp1 = icmp sgt i32 %x, 0
  %ctpop1 = tail call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 %x)
  %cmp2 = icmp samesign ult i32 %ctpop1, 2
  %cond = select i1 %cmp1, i1 %cmp2, i1 false
  br i1 %cond, label %if.then, label %if.else

if.else:
  %res = icmp eq i32 %ctpop1, 1
  ret i1 %res

if.then:
  ret i1 false
}
```
```

----------------------------------------
define i1 @src(i32 noundef %x) {
#0:
  %cmp1 = icmp sgt i32 noundef %x, 0
  %ctpop1 = ctpop i32 noundef %x
  %#range_0_%ctpop1 = !range i32 %ctpop1, i32 1, i32 32
  %cmp2 = icmp samesign ult i32 %#range_0_%ctpop1, 2
  %cond = select i1 %cmp1, i1 %cmp2, i1 0
  br i1 %cond, label %if.then, label %if.else

if.else:
  %ctpop2 = ctpop i32 noundef %x
  %#range_1_%ctpop2 = !range i32 %ctpop2, i32 0, i32 33
  %res = icmp eq i32 %#range_1_%ctpop2, 1
  ret i1 %res

if.then:
  ret i1 0
}
=>
define i1 @tgt(i32 noundef %x) {
#0:
  %cmp1 = icmp sgt i32 noundef %x, 0
  %ctpop1 = ctpop i32 noundef %x
  %#range_0_%ctpop1 = !range i32 %ctpop1, i32 1, i32 32
  %cmp2 = icmp samesign ult i32 %#range_0_%ctpop1, 2
  %cond = select i1 %cmp1, i1 %cmp2, i1 0
  br i1 %cond, label %if.then, label %if.else

if.else:
  %res = icmp eq i32 %#range_0_%ctpop1, 1
  ret i1 %res

if.then:
  ret i1 0
}
Transformation doesn't verify!

ERROR: Target is more poisonous than source

Example:
i32 noundef %x = #x00000000 (0)

Source:
i1 %cmp1 = #x0 (0)
i32 %ctpop1 = #x00000000 (0)
i32 %#range_0_%ctpop1 = poison
i1 %cmp2 = poison
i1 %cond = #x0 (0)
  >> Jump to %if.else
i32 %ctpop2 = #x00000000 (0)
i32 %#range_1_%ctpop2 = #x00000000 (0)
i1 %res = #x0 (0)

Target:
i1 %cmp1 = #x0 (0)
i32 %ctpop1 = #x00000000 (0)
i32 %#range_0_%ctpop1 = poison
i1 %cmp2 = poison
i1 %cond = #x0 (0)
  >> Jump to %if.else
i1 %res = poison
Source value: #x0 (0)
Target value: poison
```

Reported by @pranavk. See https://github.com/llvm/llvm-project/pull/100899#issuecomment-2442780517.



Fix patch:
commit f16bff1261a92169992c6edf6bc6b38d1c815c8d
Author: Yingwei Zheng <dtcxzyw2333@gmail.com>
Date:   Fri Nov 1 12:44:33 2024 +0800

    [GVN][NewGVN][Local] Handle attributes for function calls after CSE (#114011)
    
    This patch intersects attributes of two calls to avoid introducing UB.
    It also skips incompatible call pairs in GVN/NewGVN. However, I cannot
    provide negative tests for these changes.
    
    Fixes https://github.com/llvm/llvm-project/issues/113997.

diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index 2ba600497e00..ad9b1217089d 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -2189,6 +2189,16 @@ bool GVNPass::processAssumeIntrinsic(AssumeInst *IntrinsicI) {
   return Changed;
 }
 
+// Return true iff V1 can be replaced with V2.
+static bool canBeReplacedBy(Value *V1, Value *V2) {
+  if (auto *CB1 = dyn_cast<CallBase>(V1))
+    if (auto *CB2 = dyn_cast<CallBase>(V2))
+      return CB1->getAttributes()
+          .intersectWith(CB2->getContext(), CB2->getAttributes())
+          .has_value();
+  return true;
+}
+
 static void patchAndReplaceAllUsesWith(Instruction *I, Value *Repl) {
   patchReplacementInstruction(I, Repl);
   I->replaceAllUsesWith(Repl);
@@ -2734,7 +2744,7 @@ bool GVNPass::processInstruction(Instruction *I) {
   // Perform fast-path value-number based elimination of values inherited from
   // dominators.
   Value *Repl = findLeader(I->getParent(), Num);
-  if (!Repl) {
+  if (!Repl || !canBeReplacedBy(I, Repl)) {
     // Failure, just remember this instance for future use.
     LeaderTable.insert(Num, I, I->getParent());
     return false;
@@ -3000,7 +3010,7 @@ bool GVNPass::performScalarPRE(Instruction *CurInst) {
 
     uint32_t TValNo = VN.phiTranslate(P, CurrentBlock, ValNo, *this);
     Value *predV = findLeader(P, TValNo);
-    if (!predV) {
+    if (!predV || !canBeReplacedBy(CurInst, predV)) {
       predMap.push_back(std::make_pair(static_cast<Value *>(nullptr), P));
       PREPred = P;
       ++NumWithout;
diff --git a/llvm/lib/Transforms/Scalar/NewGVN.cpp b/llvm/lib/Transforms/Scalar/NewGVN.cpp
index 13d9e8f186b4..6800ad51cc0a 100644
--- a/llvm/lib/Transforms/Scalar/NewGVN.cpp
+++ b/llvm/lib/Transforms/Scalar/NewGVN.cpp
@@ -3854,6 +3854,16 @@ Value *NewGVN::findPHIOfOpsLeader(const Expression *E,
   return nullptr;
 }
 
+// Return true iff V1 can be replaced with V2.
+static bool canBeReplacedBy(Value *V1, Value *V2) {
+  if (auto *CB1 = dyn_cast<CallBase>(V1))
+    if (auto *CB2 = dyn_cast<CallBase>(V2))
+      return CB1->getAttributes()
+          .intersectWith(CB2->getContext(), CB2->getAttributes())
+          .has_value();
+  return true;
+}
+
 bool NewGVN::eliminateInstructions(Function &F) {
   // This is a non-standard eliminator. The normal way to eliminate is
   // to walk the dominator tree in order, keeping track of available
@@ -3963,6 +3973,9 @@ bool NewGVN::eliminateInstructions(Function &F) {
           MembersLeft.insert(Member);
           continue;
         }
+        if (!canBeReplacedBy(Member, Leader))
+          continue;
+
         LLVM_DEBUG(dbgs() << "Found replacement " << *(Leader) << " for "
                           << *Member << "\n");
         auto *I = cast<Instruction>(Member);
@@ -4069,8 +4082,11 @@ bool NewGVN::eliminateInstructions(Function &F) {
               if (DominatingLeader != Def) {
                 // Even if the instruction is removed, we still need to update
                 // flags/metadata due to downstreams users of the leader.
-                if (!match(DefI, m_Intrinsic<Intrinsic::ssa_copy>()))
+                if (!match(DefI, m_Intrinsic<Intrinsic::ssa_copy>())) {
+                  if (!canBeReplacedBy(DefI, DominatingLeader))
+                    continue;
                   patchReplacementInstruction(DefI, DominatingLeader);
+                }
 
                 markInstructionForDeletion(DefI);
               }
@@ -4112,17 +4128,21 @@ bool NewGVN::eliminateInstructions(Function &F) {
           // Don't replace our existing users with ourselves.
           if (U->get() == DominatingLeader)
             continue;
-          LLVM_DEBUG(dbgs()
-                     << "Found replacement " << *DominatingLeader << " for "
-                     << *U->get() << " in " << *(U->getUser()) << "\n");
 
           // If we replaced something in an instruction, handle the patching of
           // metadata.  Skip this if we are replacing predicateinfo with its
           // original operand, as we already know we can just drop it.
           auto *ReplacedInst = cast<Instruction>(U->get());
           auto *PI = PredInfo->getPredicateInfoFor(ReplacedInst);
-          if (!PI || DominatingLeader != PI->OriginalOp)
+          if (!PI || DominatingLeader != PI->OriginalOp) {
+            if (!canBeReplacedBy(ReplacedInst, DominatingLeader))
+              continue;
             patchReplacementInstruction(ReplacedInst, DominatingLeader);
+          }
+
+          LLVM_DEBUG(dbgs()
+                     << "Found replacement " << *DominatingLeader << " for "
+                     << *U->get() << " in " << *(U->getUser()) << "\n");
           U->set(DominatingLeader);
           // This is now a use of the dominating leader, which means if the
           // dominating leader was dead, it's now live!
diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp
index 65c1669f92b4..47a704925596 100644
--- a/llvm/lib/Transforms/Utils/Local.cpp
+++ b/llvm/lib/Transforms/Utils/Local.cpp
@@ -3508,6 +3508,17 @@ void llvm::patchReplacementInstruction(Instruction *I, Value *Repl) {
   else if (!isa<LoadInst>(I))
     ReplInst->andIRFlags(I);
 
+  // Handle attributes.
+  if (auto *CB1 = dyn_cast<CallBase>(ReplInst)) {
+    if (auto *CB2 = dyn_cast<CallBase>(I)) {
+      bool Success = CB1->tryIntersectAttributes(CB2);
+      assert(Success && "We should not be trying to sink callbases "
+                        "with non-intersectable attributes");
+      // For NDEBUG Compile.
+      (void)Success;
+    }
+  }
+
   // FIXME: If both the original and replacement value are part of the
   // same control-flow region (meaning that the execution of one
   // guarantees the execution of the other), then we can combine the


Regression tests:
file: llvm/test/Transforms/GVN/pr113997.ll
commands: opt -S -passes=gvn < %s
tests: test_name: bucket
test_body: define i1 @bucket(i32 noundef %x) {
  %cmp1 = icmp sgt i32 %x, 0
  %ctpop1 = tail call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 %x)
  %cmp2 = icmp samesign ult i32 %ctpop1, 2
  %cond = select i1 %cmp1, i1 %cmp2, i1 false
  br i1 %cond, label %if.then, label %if.else

if.else:                                          ; preds = %0
  %ctpop2 = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 %x)
  %res = icmp eq i32 %ctpop2, 1
  ret i1 %res

if.then:                                          ; preds = %0
  ret i1 false
}

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

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

file: llvm/test/Transforms/NewGVN/pr113997.ll
commands: opt -S -passes=newgvn < %s
tests: test_name: bucket
test_body: define i1 @bucket(i32 noundef %x) {
  %cmp1 = icmp sgt i32 %x, 0
  %ctpop1 = tail call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 %x)
  %cmp2 = icmp samesign ult i32 %ctpop1, 2
  %cond = select i1 %cmp1, i1 %cmp2, i1 false
  br i1 %cond, label %if.then, label %if.else

if.else:                                          ; preds = %0
  %ctpop2 = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 %x)
  %res = icmp eq i32 %ctpop2, 1
  ret i1 %res

if.then:                                          ; preds = %0
  ret i1 false
}

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

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