From 6ce279ac3173494917d036e4268a0deb4dd15d3a Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Tue, 29 Jul 2025 15:11:54 +0200 Subject: [PATCH 1/2] [IR] Allow poison argument to lifetime markers This slightly relaxes the invariant established in #149310, by also allowing the lifetime argument to be poison. This is to support the typical pattern of RAUWing with poison when removing an instruction. It's worth noting that this does not require any conservative assumptions, lifetimes with poison arguments can simply be skipped. Fixes https://github.com/llvm/llvm-project/issues/151119. --- llvm/docs/LangRef.rst | 20 +++++---- llvm/lib/Analysis/StackLifetime.cpp | 5 ++- llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp | 4 +- .../SelectionDAG/SelectionDAGBuilder.cpp | 4 +- llvm/lib/IR/Verifier.cpp | 9 ++-- .../Instrumentation/AddressSanitizer.cpp | 4 +- .../Instrumentation/MemorySanitizer.cpp | 5 ++- llvm/lib/Transforms/Utils/Local.cpp | 3 ++ .../Transforms/Utils/MemoryTaggingSupport.cpp | 5 ++- llvm/test/Transforms/InstCombine/pr150338.ll | 16 ------- .../unreachable-alloca-lifetime-markers.ll | 42 +++++++++++++++++++ 11 files changed, 81 insertions(+), 36 deletions(-) delete mode 100644 llvm/test/Transforms/InstCombine/pr150338.ll create mode 100644 llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 28746bf9d05aa..5946a1814faf1 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -26658,14 +26658,17 @@ Arguments: The first argument is a constant integer, which is ignored and will be removed in the future. -The second argument is a pointer to an ``alloca`` instruction. +The second argument is either a pointer to an ``alloca`` instruction or +a ``poison`` value. Semantics: """""""""" -The stack-allocated object that ``ptr`` points to is initially marked as dead. -After '``llvm.lifetime.start``', the stack object is marked as alive and has an -uninitialized value. +If ``ptr`` is a ``poison`` value, the intrinsic has no effect. + +Otherwise, the stack-allocated object that ``ptr`` points to is initially +marked as dead. After '``llvm.lifetime.start``', the stack object is marked as +alive and has an uninitialized value. The stack object is marked as dead when either :ref:`llvm.lifetime.end ` to the alloca is executed or the function returns. @@ -26699,13 +26702,16 @@ Arguments: The first argument is a constant integer, which is ignored and will be removed in the future. -The second argument is a pointer to an ``alloca`` instruction. +The second argument is either a pointer to an ``alloca`` instruction or +a ``poison`` value. Semantics: """""""""" -The stack-allocated object that ``ptr`` points to becomes dead after the call -to this intrinsic. +If ``ptr`` is a ``poison`` value, the intrinsic has no effect. + +Otherwise, the stack-allocated object that ``ptr`` points to becomes dead after +the call to this intrinsic. Calling ``llvm.lifetime.end`` on an already dead alloca is no-op. diff --git a/llvm/lib/Analysis/StackLifetime.cpp b/llvm/lib/Analysis/StackLifetime.cpp index 34a7a0416d290..b3f999400f154 100644 --- a/llvm/lib/Analysis/StackLifetime.cpp +++ b/llvm/lib/Analysis/StackLifetime.cpp @@ -63,7 +63,10 @@ bool StackLifetime::isAliveAfter(const AllocaInst *AI, // markers has the same size and points to the alloca start. static const AllocaInst *findMatchingAlloca(const IntrinsicInst &II, const DataLayout &DL) { - const AllocaInst *AI = cast(II.getArgOperand(1)); + const AllocaInst *AI = dyn_cast(II.getArgOperand(1)); + if (!AI) + return nullptr; + auto AllocaSize = AI->getAllocationSize(DL); if (!AllocaSize) return nullptr; diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp index fd38c308003b7..ab6fb3082ab7d 100644 --- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp +++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp @@ -2189,8 +2189,8 @@ bool IRTranslator::translateKnownIntrinsic(const CallInst &CI, Intrinsic::ID ID, unsigned Op = ID == Intrinsic::lifetime_start ? TargetOpcode::LIFETIME_START : TargetOpcode::LIFETIME_END; - const AllocaInst *AI = cast(CI.getArgOperand(1)); - if (!AI->isStaticAlloca()) + const AllocaInst *AI = dyn_cast(CI.getArgOperand(1)); + if (!AI || !AI->isStaticAlloca()) return true; MIRBuilder.buildInstr(Op).addFrameIndex(getOrCreateFrameIndex(*AI)); diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index 306e068f1c1da..ac0440fef5f60 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -7598,7 +7598,9 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, if (TM.getOptLevel() == CodeGenOptLevel::None) return; - const AllocaInst *LifetimeObject = cast(I.getArgOperand(1)); + const AllocaInst *LifetimeObject = dyn_cast(I.getArgOperand(1)); + if (!LifetimeObject) + return; // First check that the Alloca is static, otherwise it won't have a // valid frame index. diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index 3ff9895e161c4..ca3f148f881a4 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -6769,10 +6769,13 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) { break; } case Intrinsic::lifetime_start: - case Intrinsic::lifetime_end: - Check(isa(Call.getArgOperand(1)), - "llvm.lifetime.start/end can only be used on alloca", &Call); + case Intrinsic::lifetime_end: { + Value *Ptr = Call.getArgOperand(1); + Check(isa(Ptr) || isa(Ptr), + "llvm.lifetime.start/end can only be used on alloca or poison", + &Call); break; + } }; // Verify that there aren't any unmediated control transfers between funclets. diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp index e87bee79a6a69..8da65c597116f 100644 --- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -1222,9 +1222,9 @@ struct FunctionStackPoisoner : public InstVisitor { !ConstantInt::isValueValidForType(IntptrTy, SizeValue)) return; // Find alloca instruction that corresponds to llvm.lifetime argument. - AllocaInst *AI = cast(II.getArgOperand(1)); + AllocaInst *AI = dyn_cast(II.getArgOperand(1)); // We're interested only in allocas we can handle. - if (!ASan.isInterestingAlloca(*AI)) + if (!AI || !ASan.isInterestingAlloca(*AI)) return; bool DoPoison = (ID == Intrinsic::lifetime_end); AllocaPoisonCall APC = {&II, AI, SizeValue, DoPoison}; diff --git a/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp b/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp index 54d9a832d0f66..7d3c940c0065f 100644 --- a/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp @@ -3301,8 +3301,9 @@ struct MemorySanitizerVisitor : public InstVisitor { void handleLifetimeStart(IntrinsicInst &I) { if (!PoisonStack) return; - AllocaInst *AI = cast(I.getArgOperand(1)); - LifetimeStartList.push_back(std::make_pair(&I, AI)); + AllocaInst *AI = dyn_cast(I.getArgOperand(1)); + if (AI) + LifetimeStartList.push_back(std::make_pair(&I, AI)); } void handleBswap(IntrinsicInst &I) { diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp index babd7f6b3a058..3852f1aa40ac5 100644 --- a/llvm/lib/Transforms/Utils/Local.cpp +++ b/llvm/lib/Transforms/Utils/Local.cpp @@ -482,6 +482,9 @@ bool llvm::wouldInstructionBeTriviallyDead(const Instruction *I, if (II->isLifetimeStartOrEnd()) { auto *Arg = II->getArgOperand(1); + if (isa(Arg)) + return true; + // If the only uses of the alloca are lifetime intrinsics, then the // intrinsics are dead. return llvm::all_of(Arg->uses(), [](Use &Use) { diff --git a/llvm/lib/Transforms/Utils/MemoryTaggingSupport.cpp b/llvm/lib/Transforms/Utils/MemoryTaggingSupport.cpp index bea76d39bb216..472c03f7fc6ca 100644 --- a/llvm/lib/Transforms/Utils/MemoryTaggingSupport.cpp +++ b/llvm/lib/Transforms/Utils/MemoryTaggingSupport.cpp @@ -155,8 +155,9 @@ void StackInfoBuilder::visit(OptimizationRemarkEmitter &ORE, return; } if (auto *II = dyn_cast(&Inst)) { - AllocaInst *AI = cast(II->getArgOperand(1)); - if (getAllocaInterestingness(*AI) != AllocaInterestingness::kInteresting) + AllocaInst *AI = dyn_cast(II->getArgOperand(1)); + if (!AI || + getAllocaInterestingness(*AI) != AllocaInterestingness::kInteresting) return; if (II->getIntrinsicID() == Intrinsic::lifetime_start) Info.AllocasToInstrument[AI].LifetimeStart.push_back(II); diff --git a/llvm/test/Transforms/InstCombine/pr150338.ll b/llvm/test/Transforms/InstCombine/pr150338.ll deleted file mode 100644 index 2ad454ec60f13..0000000000000 --- a/llvm/test/Transforms/InstCombine/pr150338.ll +++ /dev/null @@ -1,16 +0,0 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 -; RUN: opt -S -passes=instcombine < %s | FileCheck %s - -; Make sure this does not crash. -define void @test(ptr %arg) { -; CHECK-LABEL: define void @test( -; CHECK-SAME: ptr [[ARG:%.*]]) { -; CHECK-NEXT: store i1 true, ptr poison, align 1 -; CHECK-NEXT: ret void -; - %a = alloca i32 - store ptr %a, ptr %arg - store i1 true, ptr poison - call void @llvm.lifetime.end.p0(i64 4, ptr %a) - ret void -} diff --git a/llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll b/llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll new file mode 100644 index 0000000000000..340f3862870ac --- /dev/null +++ b/llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll @@ -0,0 +1,42 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 +; RUN: opt -S -passes=instcombine < %s | FileCheck %s + +; Make sure this does not crash. + +define void @pr150338(ptr %arg) { +; CHECK-LABEL: define void @pr150338( +; CHECK-SAME: ptr [[ARG:%.*]]) { +; CHECK-NEXT: store i1 true, ptr poison, align 1 +; CHECK-NEXT: ret void +; + %a = alloca i32 + store ptr %a, ptr %arg + store i1 true, ptr poison + call void @llvm.lifetime.end.p0(i64 4, ptr %a) + ret void +} + +define ptr @pr151119() { +; CHECK-LABEL: define ptr @pr151119() { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: store i1 false, ptr poison, align 1 +; CHECK-NEXT: br i1 false, label %[[BB1:.*]], label %[[BB2:.*]] +; CHECK: [[BB1]]: +; CHECK-NEXT: br label %[[BB2]] +; CHECK: [[BB2]]: +; CHECK-NEXT: br label %[[BB1]] +; +entry: + %a = alloca i32, align 4 + store i1 false, ptr poison + br i1 false, label %bb1, label %bb2 + +bb1: + %phi1 = phi ptr [ null, %entry ], [ %phi2, %bb2 ] + call void @llvm.lifetime.start.p0(i64 4, ptr %a) + br label %bb2 + +bb2: + %phi2 = phi ptr [ null, %entry ], [ %a, %bb1 ] + br label %bb1 +} From a962fc897d813a1b9e15ec99555ff793e3751a7e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 4 Aug 2025 09:19:32 +0200 Subject: [PATCH 2/2] add additional tests --- llvm/test/CodeGen/AArch64/lifetime-poison.ll | 14 ++++++++++++++ .../AddressSanitizer/lifetime.ll | 15 +++++++++++++++ .../stack-safety-analysis.ll | 19 +++++++++++++++++++ .../unreachable-alloca-lifetime-markers.ll | 9 +++++++++ 4 files changed, 57 insertions(+) create mode 100644 llvm/test/CodeGen/AArch64/lifetime-poison.ll diff --git a/llvm/test/CodeGen/AArch64/lifetime-poison.ll b/llvm/test/CodeGen/AArch64/lifetime-poison.ll new file mode 100644 index 0000000000000..e04530de528b5 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/lifetime-poison.ll @@ -0,0 +1,14 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5 +; RUN: llc -mtriple=aarch64 -global-isel=0 < %s | FileCheck %s +; RUN: llc -mtriple=aarch64 -global-isel=1 < %s | FileCheck %s + +; Check that lifetime.start/end with poison argument are ignored. + +define void @test() { +; CHECK-LABEL: test: +; CHECK: // %bb.0: +; CHECK-NEXT: ret + call void @llvm.lifetime.start.p0(i64 4, ptr poison) + call void @llvm.lifetime.end.p0(i64 4, ptr poison) + ret void +} diff --git a/llvm/test/Instrumentation/AddressSanitizer/lifetime.ll b/llvm/test/Instrumentation/AddressSanitizer/lifetime.ll index bbfe00bf9a0eb..959437001a039 100644 --- a/llvm/test/Instrumentation/AddressSanitizer/lifetime.ll +++ b/llvm/test/Instrumentation/AddressSanitizer/lifetime.ll @@ -334,6 +334,21 @@ entry: ret void } + +; Lifetimes on poison should be ignored. +define void @lifetime_poison(i64 %a) #0 { +; CHECK-LABEL: define void @lifetime_poison( +; CHECK-SAME: i64 [[A:%.*]]) { +; CHECK-NEXT: [[A_ADDR:%.*]] = alloca i64, align 8 +; CHECK-NEXT: store i64 [[A]], ptr [[A_ADDR]], align 8 +; CHECK-NEXT: ret void +; + %a.addr = alloca i64, align 8 + call void @llvm.lifetime.start.p0(i64 8, ptr poison) + store i64 %a, ptr %a.addr, align 8 + call void @llvm.lifetime.end.p0(i64 8, ptr poison) + ret void +} ;. ; CHECK-DEFAULT: [[PROF1]] = !{!"branch_weights", i32 1, i32 1048575} ;. diff --git a/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll b/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll index e96ca91d139dd..60af551eb458b 100644 --- a/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll +++ b/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll @@ -395,6 +395,25 @@ entry: ret i32 0 } +; Check that lifetimes on poison are ignored. +define i32 @test_lifetime_poison(ptr %a) sanitize_hwaddress { +entry: + ; CHECK-LABEL: @test_lifetime_poison + ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store + ; SAFETY-NOT: call {{.*}}__hwasan_generate_tag + ; SAFETY-NOT: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store + ; SAFETY-REMARKS: --- !Passed{{[[:space:]]}}Pass: hwasan{{[[:space:]]}}Name: safeAlloca{{[[:space:]]}}Function: test_lifetime_poison + ; SAFETY-REMARKS: --- !Passed{{[[:space:]]}}Pass: hwasan{{[[:space:]]}}Name: ignoreAccess{{[[:space:]]}}Function: test_lifetime_poison + %buf.sroa.0 = alloca i8, align 4 + call void @llvm.lifetime.start.p0(i64 1, ptr poison) + store volatile i8 0, ptr %buf.sroa.0, align 4, !tbaa !8 + call void @llvm.lifetime.end.p0(i64 1, ptr poison) + ret i32 0 +} + ; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) diff --git a/llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll b/llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll index 340f3862870ac..ab744c6213e4b 100644 --- a/llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll +++ b/llvm/test/Transforms/InstCombine/unreachable-alloca-lifetime-markers.ll @@ -40,3 +40,12 @@ bb2: %phi2 = phi ptr [ null, %entry ], [ %a, %bb1 ] br label %bb1 } + +define void @lifetime_poison() { +; CHECK-LABEL: define void @lifetime_poison() { +; CHECK-NEXT: ret void +; + call void @llvm.lifetime.start.p0(i64 4, ptr poison) + call void @llvm.lifetime.end.p0(i64 4, ptr poison) + ret void +}