diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp --- a/llvm/lib/Transforms/Utils/Local.cpp +++ b/llvm/lib/Transforms/Utils/Local.cpp @@ -403,9 +403,22 @@ II->getIntrinsicID() == Intrinsic::launder_invariant_group) return true; - // Lifetime intrinsics are dead when their right-hand is undef. - if (II->isLifetimeStartOrEnd()) - return isa(II->getArgOperand(1)); + if (II->isLifetimeStartOrEnd()) { + auto *Arg = II->getArgOperand(1); + // Lifetime intrinsics are dead when their right-hand is undef. + if (isa(Arg)) + return true; + // If the right-hand is an alloc, global, or argument and the only uses + // are lifetime intrinsics then the intrinsics are dead. + if (isa(Arg) || isa(Arg) || isa(Arg)) + return llvm::all_of(Arg->uses(), [](Use &Use) { + if (IntrinsicInst *IntrinsicUse = + dyn_cast(Use.getUser())) + return IntrinsicUse->isLifetimeStartOrEnd(); + return false; + }); + return false; + } // Assumptions are dead if their condition is trivially true. Guards on // true are operationally no-ops. In the future we can consider more diff --git a/llvm/test/Transforms/Attributor/memory_locations.ll b/llvm/test/Transforms/Attributor/memory_locations.ll --- a/llvm/test/Transforms/Attributor/memory_locations.ll +++ b/llvm/test/Transforms/Attributor/memory_locations.ll @@ -386,11 +386,11 @@ store i8 0, i8* %unknown ret void } -; CHECK: Function Attrs: argmemonly nounwind willreturn + +; CHECK: Function Attrs: nofree {{(norecurse )?}}nosync nounwind readnone willreturn define void @callerE(i8* %arg) { ; CHECK-LABEL: define {{[^@]+}}@callerE -; CHECK-SAME: (i8* nocapture [[ARG:%.*]]) -; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 4, i8* nocapture [[ARG]]) +; CHECK-SAME: (i8* nocapture nofree readnone [[ARG:%.*]]) ; CHECK-NEXT: ret void ; call void @llvm.lifetime.start.p0i8(i64 4, i8* %arg) diff --git a/llvm/test/Transforms/DCE/basic.ll b/llvm/test/Transforms/DCE/basic.ll --- a/llvm/test/Transforms/DCE/basic.ll +++ b/llvm/test/Transforms/DCE/basic.ll @@ -11,5 +11,63 @@ ret void } +declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) nounwind +declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) nounwind + +; CHECK-LABEL: @test_lifetime_alloca +define i32 @test_lifetime_alloca() { +; Check that lifetime intrinsics are removed along with the pointer. +; CHECK-NEXT: @llvm.dbg.value +; CHECK-NEXT: ret i32 0 +; CHECK-NOT: llvm.lifetime.start +; CHECK-NOT: llvm.lifetime.end + %i = alloca i8, align 4 + call void @llvm.lifetime.start.p0i8(i64 -1, i8* %i) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* %i) + ret i32 0 +} + +; CHECK-LABEL: @test_lifetime_arg +define i32 @test_lifetime_arg(i8*) { +; Check that lifetime intrinsics are removed along with the pointer. +; CHECK-NEXT: llvm.dbg.value +; CHECK-NEXT: ret i32 0 +; CHECK-NOT: llvm.lifetime.start +; CHECK-NOT: llvm.lifetime.end + call void @llvm.lifetime.start.p0i8(i64 -1, i8* %0) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* %0) + ret i32 0 +} + +@glob = global i8 1 + +; CHECK-LABEL: @test_lifetime_global +define i32 @test_lifetime_global() { +; Check that lifetime intrinsics are removed along with the pointer. +; CHECK-NEXT: llvm.dbg.value +; CHECK-NEXT: ret i32 0 +; CHECK-NOT: llvm.lifetime.start +; CHECK-NOT: llvm.lifetime.end + call void @llvm.lifetime.start.p0i8(i64 -1, i8* @glob) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* @glob) + ret i32 0 +} + +; CHECK-LABEL: @test_lifetime_bitcast +define i32 @test_lifetime_bitcast(i32*) { +; Check that lifetime intrinsics are NOT removed when the pointer is a bitcast. +; It's not uncommon for two bitcasts to be made: one for lifetime, one for use. +; TODO: Support the above case. +; CHECK-NEXT: bitcast +; CHECK-NEXT: llvm.dbg.value +; CHECK-NEXT: llvm.lifetime.start +; CHECK-NEXT: llvm.lifetime.end +; CHECK-NEXT: ret i32 0 + %2 = bitcast i32* %0 to i8* + call void @llvm.lifetime.start.p0i8(i64 -1, i8* %2) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* %2) + ret i32 0 +} + ; CHECK: [[add]] = !DILocalVariable ; CHECK: [[sub]] = !DILocalVariable diff --git a/llvm/test/Transforms/DeadStoreElimination/lifetime.ll b/llvm/test/Transforms/DeadStoreElimination/lifetime.ll --- a/llvm/test/Transforms/DeadStoreElimination/lifetime.ll +++ b/llvm/test/Transforms/DeadStoreElimination/lifetime.ll @@ -12,7 +12,7 @@ store i8 0, i8* %A ;; Written to by memset call void @llvm.lifetime.end.p0i8(i64 1, i8* %A) -; CHECK: lifetime.end +; CHECK-NOT: lifetime.end call void @llvm.memset.p0i8.i8(i8* %A, i8 0, i8 -1, i1 false) ; CHECK-NOT: memset @@ -26,11 +26,9 @@ %Q = getelementptr i32, i32* %P, i32 1 %R = bitcast i32* %Q to i8* call void @llvm.lifetime.start.p0i8(i64 4, i8* %R) -; CHECK: lifetime.start store i32 0, i32* %Q ;; This store is dead. ; CHECK-NOT: store call void @llvm.lifetime.end.p0i8(i64 4, i8* %R) -; CHECK: lifetime.end ret void } diff --git a/llvm/test/Transforms/InstCombine/vararg.ll b/llvm/test/Transforms/InstCombine/vararg.ll --- a/llvm/test/Transforms/InstCombine/vararg.ll +++ b/llvm/test/Transforms/InstCombine/vararg.ll @@ -1,4 +1,4 @@ -; RUN: opt < %s -instcombine -instcombine-infinite-loop-threshold=2 -S | FileCheck %s +; RUN: opt < %s -instcombine -instcombine-infinite-loop-threshold=3 -S | FileCheck %s %struct.__va_list = type { i8*, i8*, i8*, i32, i32 }