Skip to content

Commit 0d67d60

Browse files
committed
[SIL] Verify extend_lifetime instruction.
1 parent 12773bf commit 0d67d60

File tree

4 files changed

+457
-25
lines changed

4 files changed

+457
-25
lines changed

lib/SIL/Verifier/LinearLifetimeChecker.cpp

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ struct State {
9090
/// The list of passed in consuming uses.
9191
ArrayRef<Operand *> consumingUses;
9292

93+
/// extend_lifetime uses.
94+
ArrayRef<Operand *> extendLifetimeUses;
95+
9396
/// The list of passed in non consuming uses.
9497
ArrayRef<Operand *> nonConsumingUses;
9598

@@ -117,32 +120,43 @@ struct State {
117120
std::optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
118121
std::optional<function_ref<void(Operand *)>>
119122
nonConsumingUseOutsideLifetimeCallback,
120-
ArrayRef<Operand *> consumingUses, ArrayRef<Operand *> nonConsumingUses)
123+
ArrayRef<Operand *> consumingUses,
124+
ArrayRef<Operand *> extendLifetimeUses,
125+
ArrayRef<Operand *> nonConsumingUses)
121126
: value(value), beginInst(value->getDefiningInsertionPoint()),
122127
errorBuilder(errorBuilder), visitedBlocks(value->getFunction()),
123128
leakingBlockCallback(leakingBlockCallback),
124129
nonConsumingUseOutsideLifetimeCallback(
125130
nonConsumingUseOutsideLifetimeCallback),
126-
consumingUses(consumingUses), nonConsumingUses(nonConsumingUses),
131+
consumingUses(consumingUses), extendLifetimeUses(extendLifetimeUses),
132+
nonConsumingUses(nonConsumingUses),
127133
blocksWithConsumingUses(value->getFunction()) {}
128134

129135
State(SILBasicBlock *beginBlock,
130136
LinearLifetimeChecker::ErrorBuilder &errorBuilder,
131137
std::optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
132138
std::optional<function_ref<void(Operand *)>>
133139
nonConsumingUseOutsideLifetimeCallback,
134-
ArrayRef<Operand *> consumingUses, ArrayRef<Operand *> nonConsumingUses)
140+
ArrayRef<Operand *> consumingUses,
141+
ArrayRef<Operand *> linearLifetimeEndingUses,
142+
ArrayRef<Operand *> nonConsumingUses)
135143
: value(), beginInst(&*beginBlock->begin()), errorBuilder(errorBuilder),
136144
visitedBlocks(beginBlock->getParent()),
137145
leakingBlockCallback(leakingBlockCallback),
138146
nonConsumingUseOutsideLifetimeCallback(
139147
nonConsumingUseOutsideLifetimeCallback),
140-
consumingUses(consumingUses), nonConsumingUses(nonConsumingUses),
148+
consumingUses(consumingUses),
149+
extendLifetimeUses(linearLifetimeEndingUses),
150+
nonConsumingUses(nonConsumingUses),
141151
blocksWithConsumingUses(beginBlock->getParent()) {}
142152

143153
SILBasicBlock *getBeginBlock() const { return beginInst->getParent(); }
144154

145155
void initializeAllNonConsumingUses(ArrayRef<Operand *> nonConsumingUsers);
156+
void initializeAllExtendLifetimeUses(
157+
ArrayRef<Operand *> linearLifetimeEndingUses,
158+
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
159+
&predsToAddToWorklist);
146160
void initializeAllConsumingUses(
147161
ArrayRef<Operand *> consumingUsers,
148162
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
@@ -245,6 +259,53 @@ void State::initializeAllNonConsumingUses(
245259
// Consuming Use Initialization
246260
//===----------------------------------------------------------------------===//
247261

262+
void State::initializeAllExtendLifetimeUses(
263+
ArrayRef<Operand *> linearLifetimeEndingUses,
264+
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
265+
&predsToAddToWorklist) {
266+
// TODO: Efficiency.
267+
for (auto *use : linearLifetimeEndingUses) {
268+
auto *user = use->getUser();
269+
auto *block = user->getParent();
270+
271+
auto iter = blocksWithNonConsumingUses.find(block);
272+
if (!iter.has_value()) {
273+
continue;
274+
}
275+
276+
// Remove the block from `blocksWithNonConsumingUses` if all non-consuming
277+
// uses within the block occur before `user`.
278+
bool allBefore = true;
279+
for (auto *nonConsumingUse : *iter) {
280+
auto *nonConsumingUser = nonConsumingUse->getUser();
281+
assert(nonConsumingUser != user);
282+
283+
bool sawNonConsumingUser = false;
284+
for (auto *inst = user->getNextInstruction(); inst;
285+
inst = inst->getNextInstruction()) {
286+
if (inst == nonConsumingUser) {
287+
sawNonConsumingUser = true;
288+
break;
289+
}
290+
}
291+
if (sawNonConsumingUser) {
292+
allBefore = false;
293+
break;
294+
}
295+
}
296+
if (allBefore) {
297+
blocksWithNonConsumingUses.erase(block);
298+
}
299+
300+
// Add relevant predecessors to the worklist.
301+
if (block == getBeginBlock())
302+
continue;
303+
for (auto *predecessor : block->getPredecessorBlocks()) {
304+
predsToAddToWorklist.push_back({use, predecessor});
305+
}
306+
}
307+
}
308+
248309
void State::initializeAllConsumingUses(
249310
ArrayRef<Operand *> consumingUses,
250311
SmallVectorImpl<std::pair<Operand *, SILBasicBlock *>>
@@ -555,7 +616,7 @@ void State::checkDataflowEndState(DeadEndBlocks *deBlocks) {
555616

556617
LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
557618
SILValue value, ArrayRef<Operand *> consumingUses,
558-
ArrayRef<Operand *> nonConsumingUses, ErrorBuilder &errorBuilder,
619+
ArrayRef<Operand *> regularUses, ErrorBuilder &errorBuilder,
559620
std::optional<function_ref<void(SILBasicBlock *)>> leakingBlockCallback,
560621
std::optional<function_ref<void(Operand *)>>
561622
nonConsumingUseOutsideLifetimeCallback) {
@@ -571,9 +632,19 @@ LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
571632
// || deadEndBlocks.isDeadEnd(value->getParentBlock())) &&
572633
// "Must have at least one consuming user?!");
573634

635+
SmallVector<Operand *, 32> nonConsumingUses;
636+
SmallVector<Operand *, 32> extendLifetimeUses;
637+
for (auto *use : regularUses) {
638+
if (isa<ExtendLifetimeInst>(use->getUser())) {
639+
extendLifetimeUses.push_back(use);
640+
} else {
641+
nonConsumingUses.push_back(use);
642+
}
643+
}
644+
574645
State state(value, errorBuilder, leakingBlockCallback,
575646
nonConsumingUseOutsideLifetimeCallback, consumingUses,
576-
nonConsumingUses);
647+
extendLifetimeUses, nonConsumingUses);
577648

578649
// First add our non-consuming uses and their blocks to the
579650
// blocksWithNonConsumingUses map. While we do this, if we have multiple uses
@@ -595,6 +666,9 @@ LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
595666
SmallVector<std::pair<Operand *, SILBasicBlock *>, 32> predsToAddToWorklist;
596667
state.initializeAllConsumingUses(consumingUses, predsToAddToWorklist);
597668

669+
state.initializeAllExtendLifetimeUses(extendLifetimeUses,
670+
predsToAddToWorklist);
671+
598672
// If we have a singular consuming use and it is in the same block as value's
599673
// def, we bail early. Any use-after-frees due to non-consuming uses would
600674
// have been detected by initializing our consuming uses. So we are done.
@@ -633,6 +707,10 @@ LinearLifetimeChecker::Error LinearLifetimeChecker::checkValueImpl(
633707
state.visitedBlocks.insert(i->getUser()->getParent());
634708
}
635709

710+
for (const auto &i : extendLifetimeUses) {
711+
state.visitedBlocks.insert(i->getUser()->getParent());
712+
}
713+
636714
// Now that we have marked all of our producing blocks, we go through our
637715
// predsToAddToWorklist list and add our preds, making sure that none of these
638716
// preds are in blocksWithConsumingUses. This is important so that we do not

lib/SIL/Verifier/SILOwnershipVerifier.cpp

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ class SILValueOwnershipChecker {
9999
/// successful.
100100
SmallVector<Operand *, 16> lifetimeEndingUsers;
101101

102+
/// extend_lifetime users of `value`.
103+
///
104+
/// Collected separately because such users do "end the lifetime" but are not
105+
/// consuming users.
106+
SmallVector<Operand *, 16> extendLifetimeUses;
107+
102108
/// The list of non lifetime ending users that we found. Only valid if check
103109
/// is successful.
104110
SmallVector<Operand *, 16> regularUsers;
@@ -129,16 +135,26 @@ class SILValueOwnershipChecker {
129135
bool isCompatibleDefUse(Operand *op, ValueOwnershipKind ownershipKind);
130136

131137
bool gatherUsers(SmallVectorImpl<Operand *> &lifetimeEndingUsers,
138+
SmallVectorImpl<Operand *> &extendLifetimeUses,
132139
SmallVectorImpl<Operand *> &regularUsers);
133140

134141
bool gatherNonGuaranteedUsers(SmallVectorImpl<Operand *> &lifetimeEndingUsers,
142+
SmallVectorImpl<Operand *> &extendLifetimeUses,
135143
SmallVectorImpl<Operand *> &regularUsers);
136144

137-
bool checkValueWithoutLifetimeEndingUses(ArrayRef<Operand *> regularUsers);
145+
bool
146+
checkValueWithoutLifetimeEndingUses(ArrayRef<Operand *> regularUsers,
147+
ArrayRef<Operand *> extendLifetimeUses);
138148

139-
bool checkFunctionArgWithoutLifetimeEndingUses(SILFunctionArgument *arg);
140-
bool checkYieldWithoutLifetimeEndingUses(MultipleValueInstructionResult *yield,
141-
ArrayRef<Operand *> regularUsers);
149+
bool checkFunctionArgWithoutLifetimeEndingUses(
150+
SILFunctionArgument *arg, ArrayRef<Operand *> regularUsers,
151+
ArrayRef<Operand *> extendLifetimeUses);
152+
bool
153+
checkYieldWithoutLifetimeEndingUses(MultipleValueInstructionResult *yield,
154+
ArrayRef<Operand *> regularUsers,
155+
ArrayRef<Operand *> extendLifetimeUses);
156+
bool checkDeadEnds(SILBasicBlock *block, ArrayRef<Operand *> regularUsers,
157+
ArrayRef<Operand *> extendLifetimeUses);
142158

143159
bool isGuaranteedFunctionArgWithLifetimeEndingUses(
144160
SILFunctionArgument *arg,
@@ -166,6 +182,7 @@ bool SILValueOwnershipChecker::check() {
166182
llvm::copy(lifetimeEndingUsers, std::back_inserter(allLifetimeEndingUsers));
167183
SmallVector<Operand *, 32> allRegularUsers;
168184
llvm::copy(regularUsers, std::back_inserter(allRegularUsers));
185+
llvm::copy(extendLifetimeUses, std::back_inserter(allRegularUsers));
169186

170187
LinearLifetimeChecker checker(deadEndBlocks);
171188
auto linearLifetimeResult = checker.checkValue(value, allLifetimeEndingUsers,
@@ -199,6 +216,7 @@ bool SILValueOwnershipChecker::isCompatibleDefUse(
199216

200217
bool SILValueOwnershipChecker::gatherNonGuaranteedUsers(
201218
SmallVectorImpl<Operand *> &lifetimeEndingUsers,
219+
SmallVectorImpl<Operand *> &extendLifetimeUses,
202220
SmallVectorImpl<Operand *> &nonLifetimeEndingUsers) {
203221
bool foundError = false;
204222

@@ -209,6 +227,11 @@ bool SILValueOwnershipChecker::gatherNonGuaranteedUsers(
209227
for (auto *op : value->getUses()) {
210228
auto *user = op->getUser();
211229

230+
if (isa<ExtendLifetimeInst>(user)) {
231+
extendLifetimeUses.push_back(op);
232+
continue;
233+
}
234+
212235
// For example, type dependent operands are non-use. It is not interesting
213236
// from an ownership perspective.
214237
if (op->getOperandOwnership() == OperandOwnership::NonUse)
@@ -268,14 +291,15 @@ bool SILValueOwnershipChecker::gatherNonGuaranteedUsers(
268291

269292
bool SILValueOwnershipChecker::gatherUsers(
270293
SmallVectorImpl<Operand *> &lifetimeEndingUsers,
294+
SmallVectorImpl<Operand *> &extendLifetimeUses,
271295
SmallVectorImpl<Operand *> &nonLifetimeEndingUsers) {
272296

273297
// See if Value is guaranteed. If we are guaranteed and not forwarding, then
274298
// we need to look through subobject uses for more uses. Otherwise, if we are
275299
// forwarding, we do not create any lifetime ending users/non lifetime ending
276300
// users since we verify against our base.
277301
if (value->getOwnershipKind() != OwnershipKind::Guaranteed) {
278-
return !gatherNonGuaranteedUsers(lifetimeEndingUsers,
302+
return !gatherNonGuaranteedUsers(lifetimeEndingUsers, extendLifetimeUses,
279303
nonLifetimeEndingUsers);
280304
}
281305

@@ -474,8 +498,39 @@ bool SILValueOwnershipChecker::gatherUsers(
474498
return !foundError;
475499
}
476500

501+
/// Precondition: value has no lifetimeEndingUsers
502+
bool SILValueOwnershipChecker::checkDeadEnds(
503+
SILBasicBlock *block, ArrayRef<Operand *> regularUses,
504+
ArrayRef<Operand *> extendedLifetimeUses) {
505+
if (deadEndBlocks && deadEndBlocks->isDeadEnd(block))
506+
return true;
507+
508+
if (extendLifetimeUses.size() == 0)
509+
return false;
510+
511+
SSAPrunedLiveness liveness(value->getFunction());
512+
liveness.initializeDef(value);
513+
for (auto *use : extendedLifetimeUses) {
514+
liveness.updateForUse(use->getUser(), /*lifetimeEnding=*/true);
515+
}
516+
auto allWithinBoundary = true;
517+
for (auto *use : regularUses) {
518+
if (!liveness.isWithinBoundary(use->getUser())) {
519+
allWithinBoundary |= errorBuilder.handleMalformedSIL([&] {
520+
llvm::errs()
521+
<< "Owned value without lifetime ending uses whose regular use "
522+
"isn't enclosed within extend_lifetime instructions:\n"
523+
<< "Value: " << value << '\n'
524+
<< "User: " << use->getUser() << '\n';
525+
});
526+
}
527+
}
528+
return allWithinBoundary;
529+
}
530+
477531
bool SILValueOwnershipChecker::checkFunctionArgWithoutLifetimeEndingUses(
478-
SILFunctionArgument *arg) {
532+
SILFunctionArgument *arg, ArrayRef<Operand *> regularUses,
533+
ArrayRef<Operand *> extendLifetimeUses) {
479534
switch (arg->getOwnershipKind()) {
480535
case OwnershipKind::Any:
481536
llvm_unreachable("Value can not have any ownership kind?!");
@@ -487,7 +542,7 @@ bool SILValueOwnershipChecker::checkFunctionArgWithoutLifetimeEndingUses(
487542
break;
488543
}
489544

490-
if (deadEndBlocks && deadEndBlocks->isDeadEnd(arg->getParent()))
545+
if (checkDeadEnds(arg->getParent(), regularUses, extendLifetimeUses))
491546
return true;
492547

493548
return !errorBuilder.handleMalformedSIL([&] {
@@ -497,16 +552,17 @@ bool SILValueOwnershipChecker::checkFunctionArgWithoutLifetimeEndingUses(
497552
}
498553

499554
bool SILValueOwnershipChecker::checkYieldWithoutLifetimeEndingUses(
500-
MultipleValueInstructionResult *yield, ArrayRef<Operand *> regularUses) {
555+
MultipleValueInstructionResult *yield, ArrayRef<Operand *> regularUses,
556+
ArrayRef<Operand *> extendLifetimeUses) {
501557
switch (yield->getOwnershipKind()) {
502558
case OwnershipKind::Any:
503559
llvm_unreachable("value with any ownership kind?!");
504560
case OwnershipKind::Unowned:
505561
case OwnershipKind::None:
506562
return true;
507563
case OwnershipKind::Owned:
508-
if (deadEndBlocks
509-
&& deadEndBlocks->isDeadEnd(yield->getParent()->getParent())) {
564+
if (checkDeadEnds(yield->getParent()->getParent(), regularUses,
565+
extendLifetimeUses)) {
510566
return true;
511567
}
512568
return !errorBuilder.handleMalformedSIL([&] {
@@ -549,16 +605,21 @@ bool SILValueOwnershipChecker::checkYieldWithoutLifetimeEndingUses(
549605
}
550606

551607
bool SILValueOwnershipChecker::checkValueWithoutLifetimeEndingUses(
552-
ArrayRef<Operand *> regularUses) {
608+
ArrayRef<Operand *> regularUses, ArrayRef<Operand *> extendLifetimeUses) {
609+
if (extendLifetimeUses.size()) {
610+
}
611+
553612
LLVM_DEBUG(llvm::dbgs() << "No lifetime ending users?! Bailing early.\n");
554613
if (auto *arg = dyn_cast<SILFunctionArgument>(value)) {
555-
if (checkFunctionArgWithoutLifetimeEndingUses(arg)) {
614+
if (checkFunctionArgWithoutLifetimeEndingUses(arg, regularUses,
615+
extendLifetimeUses)) {
556616
return true;
557617
}
558618
}
559619

560620
if (auto *yield = isaResultOf<BeginApplyInst>(value)) {
561-
return checkYieldWithoutLifetimeEndingUses(yield, regularUses);
621+
return checkYieldWithoutLifetimeEndingUses(yield, regularUses,
622+
extendLifetimeUses);
562623
}
563624

564625
// Check if we are a guaranteed subobject. In such a case, we should never
@@ -575,7 +636,7 @@ bool SILValueOwnershipChecker::checkValueWithoutLifetimeEndingUses(
575636
return true;
576637

577638
if (auto *parentBlock = value->getParentBlock()) {
578-
if (deadEndBlocks && deadEndBlocks->isDeadEnd(parentBlock)) {
639+
if (checkDeadEnds(parentBlock, regularUses, extendLifetimeUses)) {
579640
LLVM_DEBUG(llvm::dbgs() << "Ignoring transitively unreachable value "
580641
<< "without users!\n"
581642
<< " Value: " << *value << '\n');
@@ -673,7 +734,7 @@ bool SILValueOwnershipChecker::checkUses() {
673734
// 1. Verify that none of the uses are in the same block. This would be an
674735
// overconsume so in this case we assert.
675736
// 2. Verify that the uses are compatible with our ownership convention.
676-
if (!gatherUsers(lifetimeEndingUsers, regularUsers)) {
737+
if (!gatherUsers(lifetimeEndingUsers, extendLifetimeUses, regularUsers)) {
677738
// Silently return false if this fails.
678739
//
679740
// If the user pass in a ErrorBehaviorKind that will assert, we
@@ -701,7 +762,7 @@ bool SILValueOwnershipChecker::checkUses() {
701762
// In the case of a yielded guaranteed value, we need to validate that all
702763
// regular uses of the value are within the coroutine.
703764
if (lifetimeEndingUsers.empty()) {
704-
if (checkValueWithoutLifetimeEndingUses(regularUsers))
765+
if (checkValueWithoutLifetimeEndingUses(regularUsers, extendLifetimeUses))
705766
return false;
706767
return true;
707768
}

0 commit comments

Comments
 (0)