Bug type: crash
Component: GVN
Loads
Bug location: llvm/lib/Analysis/Loads.cpp: llvm::canReplacePointersInUseIfEqual
llvm/lib/Transforms/Scalar/GVN.cpp: GVNPass::replaceOperandsForInBlockEquality
Issue title: Non alloca pointer in llvm.lifetime.end appeared after GVN
Labels: regression
crash-on-valid
llvm:GVN
Reported by: e-kud

https://godbolt.org/z/sK5aYrfhs

```llvm
define ptr @foobar(ptr %this, ptr %RHS) {
entry:
  store volatile i32 0, ptr %this, align 4
  %call2 = call ptr @bar(ptr %this, ptr %RHS)
  ret ptr %call2
}

define ptr @bar(ptr %this, ptr %RHS) {
entry:
  %cmp = icmp eq ptr %this, %RHS
  call void @llvm.assume(i1 %cmp)
  ret ptr null
}

define i1 @foo(ptr %Builder) {
entry:
  %Result = alloca { [24 x i8] }, align 8
  %0 = call ptr @foobar(ptr %Result, ptr %Builder)
  call void @llvm.lifetime.end.p0(i64 0, ptr %Result)
  ret i1 false
}
```
`opt -O2` produces
```
llvm.lifetime.start/end can only be used on alloca
  call void @llvm.lifetime.end.p0(i64 0, ptr nonnull %Builder)
```
It is a result of GVN
```llvm
; *** IR Dump After MergedLoadStoreMotionPass on foo ***
define i1 @foo(ptr readnone captures(address) %Builder) local_unnamed_addr {
entry:
  %Result = alloca { [24 x i8] }, align 8
  store volatile i32 0, ptr %Result, align 4
  %cmp.i.i = icmp eq ptr %Result, %Builder
  call void @llvm.assume(i1 %cmp.i.i)
  call void @llvm.lifetime.end.p0(i64 0, ptr nonnull %Result)
  ret i1 false
}
; *** IR Dump After GVNPass on foo ***
define i1 @foo(ptr readnone captures(address) %Builder) local_unnamed_addr {
entry:
  %Result = alloca { [24 x i8] }, align 8
  store volatile i32 0, ptr %Result, align 4
  %cmp.i.i = icmp eq ptr %Result, %Builder
  call void @llvm.assume(i1 %cmp.i.i)
  call void @llvm.lifetime.end.p0(i64 0, ptr nonnull %Builder)
  ret i1 false
}
```

Relates to #149310 and https://github.com/llvm/llvm-project/pull/149838 cc @nikic 

Comments: author: e-kud
body: A shorter reproducer
```llvm
define i1 @foo(ptr %Builder) {
entry:
  %Result = alloca { [24 x i8] }, align 8
  store volatile i32 0, ptr %Result, align 4
  %cmp = icmp eq ptr %Result, %Builder
  call void @llvm.assume(i1 %cmp)
  call void @llvm.lifetime.end.p0(i64 0, ptr %Result)
  ret i1 false
}
```

Fix patch:
commit 4b5b36e5c4b887a86fc7164d6899caaa2e2f5aed
Author: Nikita Popov <npopov@redhat.com>
Date:   Mon Aug 4 12:05:37 2025 +0200

    [GVN] Avoid creating lifetime of non-alloca
    
    There is a larger problem here in that we should not be performing
    arbitrary pointer replacements for assumes. This is handled for
    branches, but assume goes through a different code path.
    
    Fixes https://github.com/llvm/llvm-project/issues/151785.

diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index 6fc81d787c22..da76f5b3d498 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -833,6 +833,10 @@ bool llvm::canReplacePointersInUseIfEqual(const Use &U, const Value *To,
   if (!To->getType()->isPointerTy())
     return true;
 
+  // Do not perform replacements in lifetime intrinsic arguments.
+  if (isa<LifetimeIntrinsic>(U.getUser()))
+    return false;
+
   if (isPointerAlwaysReplaceable(&*U, To, DL))
     return true;
   return isPointerUseReplacable(U);
diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index f6bf09d09433..fa6ee95d33d1 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -2502,6 +2502,10 @@ bool GVNPass::replaceOperandsForInBlockEquality(Instruction *Instr) const {
     Value *Operand = Instr->getOperand(OpNum);
     auto It = ReplaceOperandsWithMap.find(Operand);
     if (It != ReplaceOperandsWithMap.end()) {
+      // Do not replace lifetime alloca argument with something else.
+      if (Instr->isLifetimeStartOrEnd())
+        continue;
+
       LLVM_DEBUG(dbgs() << "GVN replacing: " << *Operand << " with "
                         << *It->second << " in instruction " << *Instr << '\n');
       Instr->setOperand(OpNum, It->second);


Regression tests:
file: llvm/test/Transforms/GVN/lifetime-simple.ll
commands: opt < %s -passes=gvn -S
tests: test_name: assume_eq_null
test_body: define void @assume_eq_null() {
  %alloca = alloca i32, align 4, addrspace(1)
  %cmp = icmp eq ptr addrspace(1) %alloca, null
  call void @llvm.assume(i1 %cmp)
  call void @llvm.lifetime.start.p1(i64 4, ptr addrspace(1) %alloca)
  store volatile i32 0, ptr addrspace(1) %alloca, align 4
  call void @llvm.lifetime.end.p1(i64 4, ptr addrspace(1) %alloca)
  ret void
}

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write)
declare void @llvm.assume(i1 noundef) #0

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p1(i64 immarg, ptr addrspace(1) captures(none)) #1

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p1(i64 immarg, ptr addrspace(1) captures(none)) #1

attributes #0 = { nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write) }
attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }

test_name: dom_eq_null
test_body: define void @dom_eq_null() {
  %alloca = alloca i32, align 4, addrspace(1)
  %cmp = icmp eq ptr addrspace(1) %alloca, null
  br i1 %cmp, label %if, label %else

if:                                               ; preds = %0
  call void @llvm.lifetime.start.p1(i64 4, ptr addrspace(1) %alloca)
  store volatile i32 0, ptr addrspace(1) %alloca, align 4
  call void @llvm.lifetime.end.p1(i64 4, ptr addrspace(1) %alloca)
  ret void

else:                                             ; preds = %0
  ret void
}

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p1(i64 immarg, ptr addrspace(1) captures(none)) #0

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p1(i64 immarg, ptr addrspace(1) captures(none)) #0

attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }

test_name: assume_eq_arg
test_body: define void @assume_eq_arg(ptr %arg) {
  %alloca = alloca i32, align 4
  %cmp = icmp eq ptr %alloca, %arg
  call void @llvm.assume(i1 %cmp)
  call void @llvm.lifetime.start.p0(i64 4, ptr %alloca)
  store volatile i32 0, ptr %alloca, align 4
  call void @llvm.lifetime.end.p0(i64 4, ptr %alloca)
  ret void
}

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr captures(none)) #0

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write)
declare void @llvm.assume(i1 noundef) #1

attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
attributes #1 = { nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write) }