Skip to content

[IR] Allow poison argument to lifetime markers #151148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26656,14 +26656,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.
Copy link
Contributor

@arsenm arsenm Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we relax this to any constant? As it is this will require special casing in llvm-reduce at a minimum, but other places also could replace with null

Copy link
Contributor Author

@nikic nikic Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not broaden this if we can be avoided at all. Allowing any constant would also allow globals, where people might reasonable expect it to do something.

Adding some special cases to llvm-reduce should be fine to avoid invalid reductions, e.g. #93713 is now more relevant. We can also skip the zero replacement for lifetimes, which just is not a useful thing to do (it should be either dropped entirely or left alone -- also the fact that llvm-reduce replaces the size argument with zero is really annoying, though that will go away soon anyway).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#151694 and #151697 should avoid invalid reductions.


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 <int_lifeend>` to the alloca is executed or the
function returns.
Expand Down Expand Up @@ -26697,13 +26700,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.

Expand Down
5 changes: 4 additions & 1 deletion llvm/lib/Analysis/StackLifetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<AllocaInst>(II.getArgOperand(1));
const AllocaInst *AI = dyn_cast<AllocaInst>(II.getArgOperand(1));
if (!AI)
return nullptr;

auto AllocaSize = AI->getAllocationSize(DL);
if (!AllocaSize)
return nullptr;
Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<AllocaInst>(CI.getArgOperand(1));
if (!AI->isStaticAlloca())
const AllocaInst *AI = dyn_cast<AllocaInst>(CI.getArgOperand(1));
if (!AI || !AI->isStaticAlloca())
return true;

MIRBuilder.buildInstr(Op).addFrameIndex(getOrCreateFrameIndex(*AI));
Expand Down
4 changes: 3 additions & 1 deletion llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7598,7 +7598,9 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
if (TM.getOptLevel() == CodeGenOptLevel::None)
return;

const AllocaInst *LifetimeObject = cast<AllocaInst>(I.getArgOperand(1));
const AllocaInst *LifetimeObject = dyn_cast<AllocaInst>(I.getArgOperand(1));
if (!LifetimeObject)
return;
Comment on lines +7601 to +7603
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing tests?


// First check that the Alloca is static, otherwise it won't have a
// valid frame index.
Expand Down
9 changes: 6 additions & 3 deletions llvm/lib/IR/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6769,10 +6769,13 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
break;
}
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
Check(isa<AllocaInst>(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<AllocaInst>(Ptr) || isa<PoisonValue>(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.
Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1222,9 +1222,9 @@ struct FunctionStackPoisoner : public InstVisitor<FunctionStackPoisoner> {
!ConstantInt::isValueValidForType(IntptrTy, SizeValue))
return;
// Find alloca instruction that corresponds to llvm.lifetime argument.
AllocaInst *AI = cast<AllocaInst>(II.getArgOperand(1));
AllocaInst *AI = dyn_cast<AllocaInst>(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};
Expand Down
5 changes: 3 additions & 2 deletions llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3301,8 +3301,9 @@ struct MemorySanitizerVisitor : public InstVisitor<MemorySanitizerVisitor> {
void handleLifetimeStart(IntrinsicInst &I) {
if (!PoisonStack)
return;
AllocaInst *AI = cast<AllocaInst>(I.getArgOperand(1));
LifetimeStartList.push_back(std::make_pair(&I, AI));
AllocaInst *AI = dyn_cast<AllocaInst>(I.getArgOperand(1));
if (AI)
LifetimeStartList.push_back(std::make_pair(&I, AI));
}

void handleBswap(IntrinsicInst &I) {
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Transforms/Utils/Local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,9 @@ bool llvm::wouldInstructionBeTriviallyDead(const Instruction *I,

if (II->isLifetimeStartOrEnd()) {
auto *Arg = II->getArgOperand(1);
if (isa<PoisonValue>(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) {
Expand Down
5 changes: 3 additions & 2 deletions llvm/lib/Transforms/Utils/MemoryTaggingSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ void StackInfoBuilder::visit(OptimizationRemarkEmitter &ORE,
return;
}
if (auto *II = dyn_cast<LifetimeIntrinsic>(&Inst)) {
AllocaInst *AI = cast<AllocaInst>(II->getArgOperand(1));
if (getAllocaInterestingness(*AI) != AllocaInterestingness::kInteresting)
AllocaInst *AI = dyn_cast<AllocaInst>(II->getArgOperand(1));
if (!AI ||
getAllocaInterestingness(*AI) != AllocaInterestingness::kInteresting)
return;
if (II->getIntrinsicID() == Intrinsic::lifetime_start)
Info.AllocasToInstrument[AI].LifetimeStart.push_back(II);
Expand Down
16 changes: 0 additions & 16 deletions llvm/test/Transforms/InstCombine/pr150338.ll

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
}