Skip to content

Commit 92c55a3

Browse files
authored
[IR] Only allow lifetime.start/end on allocas (#149310)
lifetime.start and lifetime.end are primarily intended for use on allocas, to enable stack coloring and other liveness optimizations. This is necessary because all (static) allocas are hoisted into the entry block, so lifetime markers are the only way to convey the actual lifetimes. However, lifetime.start and lifetime.end are currently *allowed* to be used on non-alloca pointers. We don't actually do this in practice, but just the mere fact that this is possible breaks the core purpose of the lifetime markers, which is stack coloring of allocas. Stack coloring can only work correctly if all lifetime markers for an alloca are analyzable. * If a lifetime marker may operate on multiple allocas via a select/phi, we don't know which lifetime actually starts/ends and handle it incorrectly (#104776). * Stack coloring operates on the assumption that all lifetime markers are visible, and not, for example, hidden behind a function call or escaped pointer. It's not possible to change this, as part of the purpose of lifetime markers is that they work even in the presence of escaped pointers, where simple use analysis is insufficient. I don't think there is any way to have coherent semantics for lifetime markers on allocas, while also permitting them on arbitrary pointer values. This PR restricts lifetimes to operate on allocas only. As a followup, I will also drop the size argument, which is superfluous if we always operate on an alloca. (This change also renders various code handling lifetime markers on non-alloca dead. I plan to clean up that kind of code after dropping the size argument as well.) In practice, I've only found a few places that currently produce lifetimes on non-allocas: * CoroEarly replaces the promise alloca with the result of an intrinsic, which will later be replaced back with an alloca. I think this is the only place where there is some legitimate loss of functionality, but I don't think this is particularly important (I don't think we'd expect the promise in a coroutine to admit useful lifetime optimization.) * SafeStack moves unsafe allocas onto a separate frame. We can safely drop lifetimes here, as SafeStack performs its own stack coloring. * Similar for AddressSanitizer, it also moves allocas into separate memory. * LSR sometimes replaces the lifetime argument with a GEP chain of the alloca (where the offsets ultimately cancel out). This is just unnecessary. (Fixed separately in #149492.) * InferAddrSpaces sometimes makes lifetimes operate on an addrspacecast of an alloca. I don't think this is necessary.
1 parent b78b16b commit 92c55a3

File tree

63 files changed

+376
-1085
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+376
-1085
lines changed

llvm/docs/LangRef.rst

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26639,19 +26639,14 @@ Arguments:
2663926639

2664026640
The first argument is a constant integer representing the size of the
2664126641
object, or -1 if it is variable sized. The second argument is a pointer
26642-
to the object.
26642+
to an ``alloca`` instruction.
2664326643

2664426644
Semantics:
2664526645
""""""""""
2664626646

26647-
If ``ptr`` is a stack-allocated object and it points to the first byte of
26648-
the object, the object is initially marked as dead.
26649-
``ptr`` is conservatively considered as a non-stack-allocated object if
26650-
the stack coloring algorithm that is used in the optimization pipeline cannot
26651-
conclude that ``ptr`` is a stack-allocated object.
26652-
26653-
After '``llvm.lifetime.start``', the stack object that ``ptr`` points is marked
26654-
as alive and has an uninitialized value.
26647+
The stack-allocated object that ``ptr`` points to is initially marked as dead.
26648+
After '``llvm.lifetime.start``', the stack object is marked as alive and has an
26649+
uninitialized value.
2665526650
The stack object is marked as dead when either
2665626651
:ref:`llvm.lifetime.end <int_lifeend>` to the alloca is executed or the
2665726652
function returns.
@@ -26661,11 +26656,6 @@ After :ref:`llvm.lifetime.end <int_lifeend>` is called,
2666126656
The second '``llvm.lifetime.start``' call marks the object as alive, but it
2666226657
does not change the address of the object.
2666326658

26664-
If ``ptr`` is a non-stack-allocated object, it does not point to the first
26665-
byte of the object or it is a stack object that is already alive, it simply
26666-
fills all bytes of the object with ``poison``.
26667-
26668-
2666926659
.. _int_lifeend:
2667026660

2667126661
'``llvm.lifetime.end``' Intrinsic
@@ -26689,24 +26679,16 @@ Arguments:
2668926679

2669026680
The first argument is a constant integer representing the size of the
2669126681
object, or -1 if it is variable sized. The second argument is a pointer
26692-
to the object.
26682+
to an ``alloca`` instruction.
2669326683

2669426684
Semantics:
2669526685
""""""""""
2669626686

26697-
If ``ptr`` is a stack-allocated object and it points to the first byte of the
26698-
object, the object is dead.
26699-
``ptr`` is conservatively considered as a non-stack-allocated object if
26700-
the stack coloring algorithm that is used in the optimization pipeline cannot
26701-
conclude that ``ptr`` is a stack-allocated object.
26687+
The stack-allocated object that ``ptr`` points to becomes dead after the call
26688+
to this intrinsic.
2670226689

2670326690
Calling ``llvm.lifetime.end`` on an already dead alloca is no-op.
2670426691

26705-
If ``ptr`` is a non-stack-allocated object or it does not point to the first
26706-
byte of the object, it is equivalent to simply filling all bytes of the object
26707-
with ``poison``.
26708-
26709-
2671026692
'``llvm.invariant.start``' Intrinsic
2671126693
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2671226694

llvm/lib/Bitcode/Reader/BitcodeReader.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7124,9 +7124,11 @@ Error BitcodeReader::materializeModule() {
71247124
if (CallInst *CI = dyn_cast<CallInst>(U))
71257125
UpgradeIntrinsicCall(CI, I.second);
71267126
}
7127-
if (!I.first->use_empty())
7128-
I.first->replaceAllUsesWith(I.second);
7129-
I.first->eraseFromParent();
7127+
if (I.first != I.second) {
7128+
if (!I.first->use_empty())
7129+
I.first->replaceAllUsesWith(I.second);
7130+
I.first->eraseFromParent();
7131+
}
71307132
}
71317133
UpgradedIntrinsics.clear();
71327134

llvm/lib/CodeGen/SafeStack.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,13 @@ Value *SafeStack::moveStaticAllocasToUnsafeStack(
614614
Use &U = *AI->use_begin();
615615
Instruction *User = cast<Instruction>(U.getUser());
616616

617+
// Drop lifetime markers now that this is no longer an alloca.
618+
// SafeStack has already performed its own stack coloring.
619+
if (User->isLifetimeStartOrEnd()) {
620+
User->eraseFromParent();
621+
continue;
622+
}
623+
617624
Instruction *InsertBefore;
618625
if (auto *PHI = dyn_cast<PHINode>(User))
619626
InsertBefore = PHI->getIncomingBlock(U)->getTerminator();

llvm/lib/IR/AutoUpgrade.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,18 @@ static bool upgradeIntrinsicFunction1(Function *F, Function *&NewFn,
13101310
return true;
13111311
}
13121312
break;
1313+
case 'l':
1314+
if (Name.starts_with("lifetime.start") ||
1315+
Name.starts_with("lifetime.end")) {
1316+
// Unless remangling is required, do not upgrade the function declaration,
1317+
// but do upgrade the calls.
1318+
if (auto Result = llvm::Intrinsic::remangleIntrinsicFunction(F))
1319+
NewFn = *Result;
1320+
else
1321+
NewFn = F;
1322+
return true;
1323+
}
1324+
break;
13131325
case 'm': {
13141326
// Updating the memory intrinsics (memcpy/memmove/memset) that have an
13151327
// alignment parameter to embedding the alignment as an attribute of
@@ -1629,7 +1641,6 @@ bool llvm::UpgradeIntrinsicFunction(Function *F, Function *&NewFn,
16291641
NewFn = nullptr;
16301642
bool Upgraded =
16311643
upgradeIntrinsicFunction1(F, NewFn, CanUpgradeDebugIntrinsicsToRecords);
1632-
assert(F != NewFn && "Intrinsic function upgraded to the same function");
16331644

16341645
// Upgrade intrinsic attributes. This does not change the function.
16351646
if (NewFn)
@@ -4570,6 +4581,9 @@ void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
45704581
}
45714582

45724583
const auto &DefaultCase = [&]() -> void {
4584+
if (F == NewFn)
4585+
return;
4586+
45734587
if (CI->getFunctionType() == NewFn->getFunctionType()) {
45744588
// Handle generic mangling change.
45754589
assert(
@@ -5109,6 +5123,31 @@ void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
51095123
MTI->setSourceAlignment(Align->getMaybeAlignValue());
51105124
break;
51115125
}
5126+
5127+
case Intrinsic::lifetime_start:
5128+
case Intrinsic::lifetime_end: {
5129+
Value *Size = CI->getArgOperand(0);
5130+
Value *Ptr = CI->getArgOperand(1);
5131+
if (isa<AllocaInst>(Ptr)) {
5132+
DefaultCase();
5133+
return;
5134+
}
5135+
5136+
// Try to strip pointer casts, such that the lifetime works on an alloca.
5137+
Ptr = Ptr->stripPointerCasts();
5138+
if (isa<AllocaInst>(Ptr)) {
5139+
// Don't use NewFn, as we might have looked through an addrspacecast.
5140+
if (NewFn->getIntrinsicID() == Intrinsic::lifetime_start)
5141+
NewCall = Builder.CreateLifetimeStart(Ptr, cast<ConstantInt>(Size));
5142+
else
5143+
NewCall = Builder.CreateLifetimeEnd(Ptr, cast<ConstantInt>(Size));
5144+
break;
5145+
}
5146+
5147+
// Otherwise remove the lifetime marker.
5148+
CI->eraseFromParent();
5149+
return;
5150+
}
51125151
}
51135152
assert(NewCall && "Should have either set this variable or returned through "
51145153
"the default case");
@@ -5131,7 +5170,8 @@ void llvm::UpgradeCallsToIntrinsic(Function *F) {
51315170
UpgradeIntrinsicCall(CB, NewFn);
51325171

51335172
// Remove old function, no longer used, from the module.
5134-
F->eraseFromParent();
5173+
if (F != NewFn)
5174+
F->eraseFromParent();
51355175
}
51365176
}
51375177

llvm/lib/IR/Verifier.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6720,6 +6720,11 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
67206720
"llvm.threadlocal.address operand isThreadLocal() must be true");
67216721
break;
67226722
}
6723+
case Intrinsic::lifetime_start:
6724+
case Intrinsic::lifetime_end:
6725+
Check(isa<AllocaInst>(Call.getArgOperand(1)),
6726+
"llvm.lifetime.start/end can only be used on alloca", &Call);
6727+
break;
67236728
};
67246729

67256730
// Verify that there aren't any unmediated control transfers between funclets.

llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
380380
bool Changed = false;
381381
const SPIRVSubtarget &STI = TM.getSubtarget<SPIRVSubtarget>(*F);
382382
for (BasicBlock &BB : *F) {
383-
for (Instruction &I : BB) {
383+
for (Instruction &I : make_early_inc_range(BB)) {
384384
auto Call = dyn_cast<CallInst>(&I);
385385
if (!Call)
386386
continue;
@@ -408,12 +408,16 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
408408
if (!STI.isShader()) {
409409
Changed |= toSpvOverloadedIntrinsic(
410410
II, Intrinsic::SPVIntrinsics::spv_lifetime_start, {1});
411+
} else {
412+
II->eraseFromParent();
411413
}
412414
break;
413415
case Intrinsic::lifetime_end:
414416
if (!STI.isShader()) {
415417
Changed |= toSpvOverloadedIntrinsic(
416418
II, Intrinsic::SPVIntrinsics::spv_lifetime_end, {1});
419+
} else {
420+
II->eraseFromParent();
417421
}
418422
break;
419423
case Intrinsic::ptr_annotation:

llvm/lib/Transforms/Coroutines/CoroEarly.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ void Lowerer::hidePromiseAlloca(CoroIdInst *CoroId, CoroBeginInst *CoroBegin) {
170170
auto *PI = Builder.CreateIntrinsic(
171171
Builder.getPtrTy(), Intrinsic::coro_promise, Arg, {}, "promise.addr");
172172
PI->setCannotDuplicate();
173+
// Remove lifetime markers, as these are only allowed on allocas.
174+
for (User *U : make_early_inc_range(PA->users())) {
175+
auto *I = cast<Instruction>(U);
176+
if (I->isLifetimeStartOrEnd())
177+
I->eraseFromParent();
178+
}
173179
PA->replaceUsesWithIf(PI, [CoroId](Use &U) {
174180
bool IsBitcast = U == U.getUser()->stripPointerCasts();
175181
bool IsCoroId = U.getUser() == CoroId;

llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3637,6 +3637,7 @@ void FunctionStackPoisoner::processStaticAllocas() {
36373637
"Variable descriptions relative to ASan stack base will be dropped");
36383638

36393639
// Replace Alloca instructions with base+offset.
3640+
SmallVector<Value *> NewAllocaPtrs;
36403641
for (const auto &Desc : SVD) {
36413642
AllocaInst *AI = Desc.AI;
36423643
replaceDbgDeclare(AI, LocalStackBaseAllocaPtr, DIB, DIExprFlags,
@@ -3645,6 +3646,7 @@ void FunctionStackPoisoner::processStaticAllocas() {
36453646
IRB.CreateAdd(LocalStackBase, ConstantInt::get(IntptrTy, Desc.Offset)),
36463647
AI->getType());
36473648
AI->replaceAllUsesWith(NewAllocaPtr);
3649+
NewAllocaPtrs.push_back(NewAllocaPtr);
36483650
}
36493651

36503652
// The left-most redzone has enough space for at least 4 pointers.
@@ -3694,6 +3696,15 @@ void FunctionStackPoisoner::processStaticAllocas() {
36943696
}
36953697
}
36963698

3699+
// Remove lifetime markers now that these are no longer allocas.
3700+
for (Value *NewAllocaPtr : NewAllocaPtrs) {
3701+
for (User *U : make_early_inc_range(NewAllocaPtr->users())) {
3702+
auto *I = cast<Instruction>(U);
3703+
if (I->isLifetimeStartOrEnd())
3704+
I->eraseFromParent();
3705+
}
3706+
}
3707+
36973708
SmallVector<uint8_t, 64> ShadowClean(ShadowAfterScope.size(), 0);
36983709
SmallVector<uint8_t, 64> ShadowAfterReturn;
36993710

@@ -3829,6 +3840,13 @@ void FunctionStackPoisoner::handleDynamicAllocaCall(AllocaInst *AI) {
38293840

38303841
Value *NewAddressPtr = IRB.CreateIntToPtr(NewAddress, AI->getType());
38313842

3843+
// Remove lifetime markers now that this is no longer an alloca.
3844+
for (User *U : make_early_inc_range(AI->users())) {
3845+
auto *I = cast<Instruction>(U);
3846+
if (I->isLifetimeStartOrEnd())
3847+
I->eraseFromParent();
3848+
}
3849+
38323850
// Replace all uses of AddessReturnedByAlloca with NewAddressPtr.
38333851
AI->replaceAllUsesWith(NewAddressPtr);
38343852

llvm/lib/Transforms/Scalar/InferAddressSpaces.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,8 @@ bool InferAddressSpacesImpl::rewriteIntrinsicOperands(IntrinsicInst *II,
430430
}
431431
case Intrinsic::lifetime_start:
432432
case Intrinsic::lifetime_end: {
433+
// Always force lifetime markers to work directly on the alloca.
434+
NewV = NewV->stripPointerCasts();
433435
Function *NewDecl = Intrinsic::getOrInsertDeclaration(
434436
M, II->getIntrinsicID(), {NewV->getType()});
435437
II->setArgOperand(1, NewV);

llvm/test/Analysis/BasicAA/modref.ll

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,27 +67,33 @@ define i8 @test2a(ptr %P) {
6767
ret i8 %A
6868
}
6969

70-
define void @test3(ptr %P, i8 %X) {
70+
define void @test3(i8 %X) {
7171
; CHECK-LABEL: @test3(
72-
; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P:%.*]], i32 2
72+
; CHECK-NEXT: [[P:%.*]] = alloca i64, align 8
73+
; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P]], i32 2
7374
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 1, ptr [[P]])
7475
; CHECK-NEXT: store i8 2, ptr [[P2]], align 1
76+
; CHECK-NEXT: call void @external(ptr [[P]])
7577
; CHECK-NEXT: ret void
7678
;
79+
%P = alloca i64
7780
%Y = add i8 %X, 1 ;; Dead, because the only use (the store) is dead.
7881

7982
%P2 = getelementptr i8, ptr %P, i32 2
8083
store i8 %Y, ptr %P2 ;; Not read by lifetime.end, should be removed.
8184
call void @llvm.lifetime.end.p0(i64 1, ptr %P)
8285
store i8 2, ptr %P2
86+
call void @external(ptr %P)
8387
ret void
8488
}
8589

86-
define void @test3a(ptr %P, i8 %X) {
90+
define void @test3a(i8 %X) {
8791
; CHECK-LABEL: @test3a(
88-
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 10, ptr [[P:%.*]])
92+
; CHECK-NEXT: [[P:%.*]] = alloca i64, align 8
93+
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 10, ptr [[P]])
8994
; CHECK-NEXT: ret void
9095
;
96+
%P = alloca i64
9197
%Y = add i8 %X, 1 ;; Dead, because the only use (the store) is dead.
9298

9399
%P2 = getelementptr i8, ptr %P, i32 2

0 commit comments

Comments
 (0)