Bug type: crash
Component: Instrumentation
Bug location: llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp: CHR::transformScopes
insertTrivialPHIs
Issue title: [ControlHeightReductionPass] Creates invalid IR in ThinLTO Backend on AArch64 when building clang with ThinLTO+IRPGO
Labels: crash
LTO
llvm:transforms
Reported by: boomanaiden154

https://gist.github.com/boomanaiden154/7f1400804c879e25a012d7907f3922c9 is the IR.

Running `opt -mtriple=aarch64-unknown-linux-gnu -passes="default<O3>" <file path> -disable-output` will reproduce the issue. The file comes from compiling clang on Aarch64 when using the BOLT caches. More details on the failure are in https://github.com/llvm/llvm-project/pull/159367.

Comments: author: boomanaiden154
body: Working on minimizing this currently.
author: boomanaiden154
body: https://gist.github.com/boomanaiden154/3ae4fd0efc2cefa4d006fe1a4caec509 is the crash log.
author: boomanaiden154
body: Reduced IR:
```llvm
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux-gnu"

%"class.llvm::SmallVector.136" = type { %"class.llvm::SmallVectorImpl.76", %"struct.llvm::SmallVectorStorage.137" }
%"class.llvm::SmallVectorImpl.76" = type { %"class.llvm::SmallVectorTemplateBase.77" }
%"class.llvm::SmallVectorTemplateBase.77" = type { %"class.llvm::SmallVectorTemplateCommon.78" }
%"class.llvm::SmallVectorTemplateCommon.78" = type { %"class.llvm::SmallVectorBase" }
%"class.llvm::SmallVectorBase" = type { ptr, i32, i32 }
%"struct.llvm::SmallVectorStorage.137" = type { [256 x i8] }

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

define i1 @_ZN4llvm23MemoryDependenceResults27getNonLocalPointerDepFromBBEPNS_11InstructionERKNS_12PHITransAddrERKNS_14MemoryLocationEbPNS_10BasicBlockERNS_15SmallVectorImplINS_17NonLocalDepResultEEERNS_13SmallDenseMapISA_PNS_5ValueELj16ENS_12DenseMapInfoISA_vEENS_6detail12DenseMapPairISA_SH_EEEEbb(ptr %this, ptr %QueryInst, i1 %isLoad, i1 %cmp.i.i.not.i.i) !prof !29 {
entry:
  %Worklist1 = alloca %"class.llvm::SmallVector.136", align 8
  %shl.i.i.i = select i1 %isLoad, i64 4, i64 0, !prof !30
  br i1 %cmp.i.i.not.i.i, label %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit, label %if.end.i.i, !prof !31

if.end.i.i:                                       ; preds = %entry
  call void @llvm.assume(i1 %isLoad)
  br label %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit

_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit: ; preds = %if.end.i.i, %entry
  store i64 %shl.i.i.i, ptr %QueryInst, align 8
  call void @llvm.lifetime.start.p0(ptr %Worklist1)
  store ptr %Worklist1, ptr %this, align 8
  br label %for.body.i.i.i.i1401

for.cond.preheader.i.i.i:                         ; preds = %for.body.i.i.i.i1401
  ret i1 false

for.body.i.i.i.i1401:                             ; preds = %for.body.i.i.i.i1401, %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit
  %__begin0.0.i34.i.i.i = phi ptr [ %incdec.ptr.i.i.i.i1402, %for.body.i.i.i.i1401 ], [ null, %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit ]
  %incdec.ptr.i.i.i.i1402 = getelementptr i8, ptr %__begin0.0.i34.i.i.i, i64 24
  %cmp.not.i.i.i.i1403 = icmp eq ptr %__begin0.0.i34.i.i.i, %this
  br i1 %cmp.not.i.i.i.i1403, label %for.cond.preheader.i.i.i, label %for.body.i.i.i.i1401
}

; 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) }

!llvm.module.flags = !{!0}

!0 = !{i32 1, !"ProfileSummary", !1}
!1 = !{!2, !3, !4, !5, !6, !7, !8, !9, !10, !11}
!2 = !{!"ProfileFormat", !"InstrProf"}
!3 = !{!"TotalCount", i64 51888332104}
!4 = !{!"MaxCount", i64 367187222}
!5 = !{!"MaxInternalCount", i64 367187222}
!6 = !{!"MaxFunctionCount", i64 281208867}
!7 = !{!"NumCounts", i64 1698950}
!8 = !{!"NumFunctions", i64 164155}
!9 = !{!"IsPartialProfile", i64 0}
!10 = !{!"PartialProfileRatio", double 0.000000e+00}
!11 = !{!"DetailedSummary", !12}
!12 = !{!13, !14, !15, !16, !17, !18, !19, !20, !21, !22, !23, !24, !25, !26, !27, !28}
!13 = !{i32 10000, i64 281208867, i32 2}
!14 = !{i32 100000, i64 88528372, i32 35}
!15 = !{i32 200000, i64 38263822, i32 128}
!16 = !{i32 300000, i64 20879095, i32 314}
!17 = !{i32 400000, i64 12121080, i32 643}
!18 = !{i32 500000, i64 6627144, i32 1239}
!19 = !{i32 600000, i64 3697961, i32 2312}
!20 = !{i32 700000, i64 2031812, i32 4214}
!21 = !{i32 800000, i64 988368, i32 7927}
!22 = !{i32 900000, i64 365146, i32 16522}
!23 = !{i32 950000, i64 149448, i32 27512}
!24 = !{i32 990000, i64 25291, i32 58500}
!25 = !{i32 999000, i64 2484, i32 108076}
!26 = !{i32 999900, i64 238, i32 156548}
!27 = !{i32 999990, i64 44, i32 191314}
!28 = !{i32 999999, i64 6, i32 216289}
!29 = !{!"function_entry_count", i64 193405}
!30 = !{!"branch_weights", i32 193405, i32 0}
!31 = !{!"branch_weights", i32 0, i32 193405}
```

It seems like this might be an issue with an earlier pass. It's just a verify call in LoopVectorizer that's tripping, and at the crash site we have the following IR:
```llvm
_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit: ; preds = %if.end.i.i.nonchr, %entry.split.nonchr, %if.end.i.i, %entry.split
  %5 = phi i64 [ %shl.i.i.i, %if.end.i.i ], [ %shl.i.i.i, %entry.split ], [ %shl.i.i.i.nonchr, %if.end.i.i.nonchr ], [ %shl.i.i.i.nonchr, %entry.split.nonchr ]
  %6 = phi ptr [ %Worklist1, %if.end.i.i ], [ %Worklist1, %entry.split ], [ %Worklist1, %if.end.i.i.nonchr ], [ %Worklist1, %entry.split.nonchr ]
  store i64 %5, ptr %QueryInst, align 8
  call void @llvm.lifetime.start.p0(ptr nonnull %6)
  store ptr %6, ptr %this, align 8
  br label %for.body.i.i.i.i1401
```
The lifetime start annotation pointing at a phi node is not legal.
author: fhahn
body: The input to LV is already invalid, `-verify-each` points to ControlHeightReductionPass or earlier

https://llvm.godbolt.org/z/bdMMq6q5E
author: fhahn
body: Just extracting the IR before CHR doesn't seem to reproduce it, so possibly it gets broken earlier
author: boomanaiden154
body: Yeah, just saw that `ControlHeightReduction` is what introduces the invalid construct (from `-print-after-all`). Sorry for the noise about the loop vectorizer.

> Just extracting the IR before CHR doesn't seem to reproduce it, so possibly it gets broken earlier

Just ran into that too.
author: boomanaiden154
body: Running just `chr` in opt doesn't work, because `chr` expects a cached profile summary analysis to be available. Taking the following IR from directly before CHR in the pipeline:
```llvm
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux-gnu"

%"class.llvm::SmallVector.136" = type { %"class.llvm::SmallVectorImpl.76", %"struct.llvm::SmallVectorStorage.137" }
%"class.llvm::SmallVectorImpl.76" = type { %"class.llvm::SmallVectorTemplateBase.77" }
%"class.llvm::SmallVectorTemplateBase.77" = type { %"class.llvm::SmallVectorTemplateCommon.78" }
%"class.llvm::SmallVectorTemplateCommon.78" = type { %"class.llvm::SmallVectorBase" }
%"class.llvm::SmallVectorBase" = type { ptr, i32, i32 }
%"struct.llvm::SmallVectorStorage.137" = type { [256 x i8] }

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

; Function Attrs: nofree norecurse nosync nounwind memory(argmem: write, inaccessiblemem: write)
define noundef i1 @_ZN4llvm23MemoryDependenceResults27getNonLocalPointerDepFromBBEPNS_11InstructionERKNS_12PHITransAddrERKNS_14MemoryLocationEbPNS_10BasicBlockERNS_15SmallVectorImplINS_17NonLocalDepResultEEERNS_13SmallDenseMapISA_PNS_5ValueELj16ENS_12DenseMapInfoISA_vEENS_6detail12DenseMapPairISA_SH_EEEEbb(ptr writeonly captures(address) initializes((0, 8)) %this, ptr writeonly captures(none) initializes((0, 8)) %QueryInst, i1 %isLoad, i1 %cmp.i.i.not.i.i) local_unnamed_addr #1 !prof !29 {
entry:
  %Worklist1 = alloca %"class.llvm::SmallVector.136", align 8
  %shl.i.i.i = select i1 %isLoad, i64 4, i64 0, !prof !30
  br i1 %cmp.i.i.not.i.i, label %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit, label %if.end.i.i, !prof !31

if.end.i.i:                                       ; preds = %entry
  tail call void @llvm.assume(i1 %isLoad)
  br label %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit

_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit: ; preds = %if.end.i.i, %entry
  store i64 %shl.i.i.i, ptr %QueryInst, align 8
  call void @llvm.lifetime.start.p0(ptr nonnull %Worklist1)
  store ptr %Worklist1, ptr %this, align 8
  br label %for.body.i.i.i.i1401

for.cond.preheader.i.i.i:                         ; preds = %for.body.i.i.i.i1401
  ret i1 false

for.body.i.i.i.i1401:                             ; preds = %for.body.i.i.i.i1401, %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit
  %__begin0.0.i34.i.i.i = phi ptr [ %incdec.ptr.i.i.i.i1402, %for.body.i.i.i.i1401 ], [ null, %_ZNSt6vectorIN4llvm16NonLocalDepEntryESaIS1_EEC2ERKS3_.exit ]
  %incdec.ptr.i.i.i.i1402 = getelementptr i8, ptr %__begin0.0.i34.i.i.i, i64 24
  %cmp.not.i.i.i.i1403 = icmp eq ptr %__begin0.0.i34.i.i.i, %this
  br i1 %cmp.not.i.i.i.i1403, label %for.cond.preheader.i.i.i, label %for.body.i.i.i.i1401
}

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

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

!llvm.module.flags = !{!0}

!0 = !{i32 1, !"ProfileSummary", !1}
!1 = !{!2, !3, !4, !5, !6, !7, !8, !9, !10, !11}
!2 = !{!"ProfileFormat", !"InstrProf"}
!3 = !{!"TotalCount", i64 51888332104}
!4 = !{!"MaxCount", i64 367187222}
!5 = !{!"MaxInternalCount", i64 367187222}
!6 = !{!"MaxFunctionCount", i64 281208867}
!7 = !{!"NumCounts", i64 1698950}
!8 = !{!"NumFunctions", i64 164155}
!9 = !{!"IsPartialProfile", i64 0}
!10 = !{!"PartialProfileRatio", double 0.000000e+00}
!11 = !{!"DetailedSummary", !12}
!12 = !{!13, !14, !15, !16, !17, !18, !19, !20, !21, !22, !23, !24, !25, !26, !27, !28}
!13 = !{i32 10000, i64 281208867, i32 2}
!14 = !{i32 100000, i64 88528372, i32 35}
!15 = !{i32 200000, i64 38263822, i32 128}
!16 = !{i32 300000, i64 20879095, i32 314}
!17 = !{i32 400000, i64 12121080, i32 643}
!18 = !{i32 500000, i64 6627144, i32 1239}
!19 = !{i32 600000, i64 3697961, i32 2312}
!20 = !{i32 700000, i64 2031812, i32 4214}
!21 = !{i32 800000, i64 988368, i32 7927}
!22 = !{i32 900000, i64 365146, i32 16522}
!23 = !{i32 950000, i64 149448, i32 27512}
!24 = !{i32 990000, i64 25291, i32 58500}
!25 = !{i32 999000, i64 2484, i32 108076}
!26 = !{i32 999900, i64 238, i32 156548}
!27 = !{i32 999990, i64 44, i32 191314}
!28 = !{i32 999999, i64 6, i32 216289}
!29 = !{!"function_entry_count", i64 193405}
!30 = !{!"branch_weights", i32 193405, i32 0}
!31 = !{!"branch_weights", i32 0, i32 193405}
```

And then running the following opt invocation:
```
opt - -passes="require<profile-summary>,chr" -disable-output
```
successfully reproduces the crash. Running that on the original IR rather than the IR right before CHR also reproduces the crash, so I don't think it's something earlier producing invalid IR.

It looks like this was caught by 92c55a315eab455d5fed2625fe0f61f88cb25499.



Fix patch:
commit a00450944d2a91aba302954556c1c23ae049dfc7
Author: Aiden Grossman <aidengrossman@google.com>
Date:   Mon Sep 22 13:57:59 2025 -0700

    [ControlHeightReduction] Drop lifetime annotations where necessary (#159686)
    
    ControlHeightReduction will duplicate some blocks and insert phi nodes
    in exit blocks of regions that it operates on for any live values. This
    includes allocas. Having a lifetime annotation refer to a phi node was
    made illegal in 92c55a315eab455d5fed2625fe0f61f88cb25499, which causes
    the verifier to fail after CHR.
    
    There are some cases where we might not need to drop lifetime
    annotations (usually because we do not need the phi to begin with), but
    drop all annotations for now to be conservative.
    
    Fixes #159621.

diff --git a/llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp b/llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp
index c14bbecf0d4e..6f6245020ae5 100644
--- a/llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp
+++ b/llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp
@@ -1591,7 +1591,16 @@ static void insertTrivialPHIs(CHRScope *Scope,
         }
         TrivialPHIs.insert(PN);
         CHR_DEBUG(dbgs() << "Insert phi " << *PN << "\n");
+        bool FoundLifetimeAnnotation = false;
         for (Instruction *UI : Users) {
+          // If we found a lifetime annotation, remove it, but set a flag
+          // to ensure that we remove all other lifetime annotations attached
+          // to the alloca.
+          if (UI->isLifetimeStartOrEnd()) {
+            UI->eraseFromParent();
+            FoundLifetimeAnnotation = true;
+            continue;
+          }
           for (unsigned J = 0, NumOps = UI->getNumOperands(); J < NumOps; ++J) {
             if (UI->getOperand(J) == &I) {
               UI->setOperand(J, PN);
@@ -1599,6 +1608,14 @@ static void insertTrivialPHIs(CHRScope *Scope,
           }
           CHR_DEBUG(dbgs() << "Updated user " << *UI << "\n");
         }
+        // Erase any leftover lifetime annotations for a dynamic alloca.
+        if (FoundLifetimeAnnotation) {
+          for (User *U : make_early_inc_range(I.users())) {
+            if (auto *UI = dyn_cast<Instruction>(U))
+              if (UI->isLifetimeStartOrEnd())
+                UI->eraseFromParent();
+          }
+        }
       }
     }
   }
@@ -1693,14 +1710,12 @@ void CHR::transformScopes(CHRScope *Scope, DenseSet<PHINode *> &TrivialPHIs) {
   BasicBlock *ExitBlock = LastRegion->getExit();
   std::optional<uint64_t> ProfileCount = BFI.getBlockProfileCount(EntryBlock);
 
-  if (ExitBlock) {
-    // Insert a trivial phi at the exit block (where the CHR hot path and the
-    // cold path merges) for a value that's defined in the scope but used
-    // outside it (meaning it's alive at the exit block). We will add the
-    // incoming values for the CHR cold paths to it below. Without this, we'd
-    // miss updating phi's for such values unless there happens to already be a
-    // phi for that value there.
-    insertTrivialPHIs(Scope, EntryBlock, ExitBlock, TrivialPHIs);
+  SmallVector<AllocaInst *> StaticAllocas;
+  for (Instruction &I : *EntryBlock) {
+    if (auto *AI = dyn_cast<AllocaInst>(&I)) {
+      if (AI->isStaticAlloca())
+        StaticAllocas.push_back(AI);
+    }
   }
 
   // Split the entry block of the first region. The new block becomes the new
@@ -1719,6 +1734,20 @@ void CHR::transformScopes(CHRScope *Scope, DenseSet<PHINode *> &TrivialPHIs) {
   FirstRegion->replaceEntryRecursive(NewEntryBlock);
   BasicBlock *PreEntryBlock = EntryBlock;
 
+  // Move static allocas into the pre-entry block so they stay static.
+  for (AllocaInst *AI : StaticAllocas)
+    AI->moveBefore(EntryBlock->getTerminator()->getIterator());
+
+  if (ExitBlock) {
+    // Insert a trivial phi at the exit block (where the CHR hot path and the
+    // cold path merges) for a value that's defined in the scope but used
+    // outside it (meaning it's alive at the exit block). We will add the
+    // incoming values for the CHR cold paths to it below. Without this, we'd
+    // miss updating phi's for such values unless there happens to already be a
+    // phi for that value there.
+    insertTrivialPHIs(Scope, EntryBlock, ExitBlock, TrivialPHIs);
+  }
+
   ValueToValueMapTy VMap;
   // Clone the blocks in the scope (excluding the PreEntryBlock) to split into a
   // hot path (originals) and a cold path (clones) and update the PHIs at the


Regression tests:
file: llvm/test/Transforms/PGOProfile/chr-lifetimes.ll
commands: opt < %s -passes='require<profile-summary>,chr' -S
tests: test_name: <module>
test_body: 
declare void @foo()
declare void @bar()
declare void @baz(i64)

; Test that when we have a static alloca in an entry block that will get split,
; the alloca remains static and we preserve its lifetime annotations.
define void @test_chr_with_lifetimes(ptr %i) !prof !14 {
;
entry:
  %1 = load i32, ptr %i
  %2 = icmp eq i32 %1, 0
  %3 = select i1 %2, i64 4, i64 0, !prof !15
  %test = alloca i32, align 8
  call void @baz(i64 %3)
  br i1 %2, label %bb1, label %bb0, !prof !15

bb0:
  call void @foo()
  br label %bb1

bb1:
  call void @llvm.lifetime.start.p0(ptr %test)
  store ptr %test, ptr %i, align 8
  br label %bb2

bb2:
  %4 = phi ptr [ %5, %bb2 ], [ null, %bb1 ]
  %5 = getelementptr i8, ptr %4, i64 24
  %6 = icmp eq ptr %4, %i
  br i1 %6, label %bb3, label %bb2

bb3:
  ret void
}

; Test that we remove lifetime markers that would otherwise refer to phi
; nodes given the dynamic allocas they referred to have been duplicated.
define void @test_chr_dynamic_alloca(ptr %i) !prof !14 {
;
entry:
  %test1 = load i32, ptr %i
  %test2 = icmp eq i32 %test1, 5
  br i1 %test2, label %bb4, label %bb3

bb4:
  %1 = load i32, ptr %i
  %2 = icmp eq i32 %1, 0
  %3 = select i1 %2, i64 4, i64 0, !prof !15
  %test = alloca i32, align 8
  call void @baz(i64 %3)
  br i1 %2, label %bb1, label %bb0, !prof !15

bb0:
  call void @foo()
  call void @llvm.lifetime.start.p0(ptr %test)
  store ptr %test, ptr %i, align 8
  br label %bb1

bb1:
  call void @bar()
  call void @llvm.lifetime.start.p0(ptr %test)
  store ptr %test, ptr %i, align 8
  br label %bb2

bb2:
  %4 = phi ptr [ %5, %bb2 ], [ null, %bb1 ]
  %5 = getelementptr i8, ptr %4, i64 24
  %test5 = load ptr, ptr %test
  call void @llvm.lifetime.end.p0(ptr %test)
  %6 = icmp eq ptr %4, %test5
  br i1 %6, label %bb3, label %bb2

bb3:
  ret void
}


!llvm.module.flags = !{!0}
!0 = !{i32 1, !"ProfileSummary", !1}
!1 = !{!2, !3, !4, !5, !6, !7, !8, !9}
!2 = !{!"ProfileFormat", !"InstrProf"}
!3 = !{!"TotalCount", i64 10000}
!4 = !{!"MaxCount", i64 10}
!5 = !{!"MaxInternalCount", i64 1}
!6 = !{!"MaxFunctionCount", i64 1000}
!7 = !{!"NumCounts", i64 3}
!8 = !{!"NumFunctions", i64 3}
!9 = !{!"DetailedSummary", !10}
!10 = !{!11, !12, !13}
!11 = !{i32 10000, i64 100, i32 1}
!12 = !{i32 999000, i64 100, i32 1}
!13 = !{i32 999999, i64 1, i32 2}

!14 = !{!"function_entry_count", i64 100}
!15 = !{!"branch_weights", i32 0, i32 1}