Skip to content

Commit 18c29b0

Browse files
committed
DI: New analysis to replace the 'self consumed' analysis
In a throwing or failable initializer for a class, the typical pattern is that an apply or try_apply consumes the self value, and returns success or failure. On success, a new self value is produced. On failure, there is no new self value. In both cases, the original self value no longer exists. We used to model this by attempting to look at the apply or try_apply instruction, and figure out from subsequent control flow which successor block was the success case and which was the error case. The error blocks were marked as such, and a dataflow analysis was used to compute whether 'self' had been consumed in each block reachable from the entry block. This analysis was used to prevent invalid use of 'self' in catch blocks when the initializer delegation was wrapped in do/catch; more importantly, it was also used to know when to release 'self' on exit from the initializer. For example, when we 'throw e' here, 'self' was already consumed and does not need to be released -- doing so would cause a crash: do { try self.init(...) } catch let e { // do some other cleanup throw e } On the other hand, here we do have to release 'self', otherwise we will exit leaking memory: do { try someOtherThing() self.init(...) } catch let e { // do some other cleanup throw e } The problem with the old analysis is that it was too brittle and did not recognize certain patterns generated by SILGen. For example, it did not correctly detect the failure block of a delegation to a foreign throwing initializer, because those are not modeled as a try_apply; instead, they return an Optional value. For similar reasons, we did not correctly failure blocks emitted after calls to initializers which are both throwing and failable. The new analysis is simpler and more robust. The idea is that in the success block, SILGen emits a store of the new 'self' value into the self box. So all we need to do is seed the dataflow analysis with the set of blocks where the 'self' box is stored to, excluding the initial entry block. The new analysis is called 'self initialized' rather than 'self consumed'. In blocks dominated by the self.init() delegation, the result is the logical not of the old analysis: - If the old analysis said self was consumed, the new one says self is not initialized. - If the old analysis said self was not consumed, the new analysis says that self *is* initialized. - If the old analysis returned a partial result, the new analysis will also; it means the block in question can be reached from blocks where the 'self' box is both initialized and not. Note that any blocks that precede the self.init() delegation now report self as uninitialized, because they are not dominated by a store into the box. So any clients of the old analysis must first check if self is "live", meaning we're past the point of the self.init() call. Only if self is live do we then go on to check the 'self initialized' analysis.
1 parent 09b59c5 commit 18c29b0

File tree

3 files changed

+160
-20
lines changed

3 files changed

+160
-20
lines changed

lib/SILOptimizer/Mandatory/DIMemoryUseCollectorOwnership.cpp

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,10 @@ bool DIMemoryUse::onlyTouchesTrivialElements(
426426
// DIElementUseInfo Implementation
427427
//===----------------------------------------------------------------------===//
428428

429+
void DIElementUseInfo::trackStoreToSelf(SILInstruction *I) {
430+
StoresToSelf.push_back(I);
431+
}
432+
429433
void DIElementUseInfo::trackFailableInitCall(
430434
const DIMemoryObjectInfo &MemoryInfo, SILInstruction *I) {
431435
// If we have a store to self inside the normal BB, we have a 'real'
@@ -1106,7 +1110,7 @@ void ElementUseCollector::collectClassSelfUses() {
11061110
// Okay, given that we have a proper setup, we walk the use chains of the self
11071111
// box to find any accesses to it. The possible uses are one of:
11081112
//
1109-
// 1) The initialization store (TheStore).
1113+
// 1) The initialization store.
11101114
// 2) Loads of the box, which have uses of self hanging off of them.
11111115
// 3) An assign to the box, which happens at super.init.
11121116
// 4) Potential escapes after super.init, if self is closed over.
@@ -1115,10 +1119,26 @@ void ElementUseCollector::collectClassSelfUses() {
11151119
for (auto *Op : MUI->getUses()) {
11161120
SILInstruction *User = Op->getUser();
11171121

1118-
// Stores to self are initializations store or the rebind of self as
1119-
// part of the super.init call. Ignore both of these.
1120-
if (isa<StoreInst>(User) && Op->getOperandNumber() == 1)
1121-
continue;
1122+
// Stores to self.
1123+
if (auto *SI = dyn_cast<StoreInst>(User)) {
1124+
if (Op->getOperandNumber() == 1) {
1125+
// The initial store of 'self' into the box at the start of the
1126+
// function. Ignore it.
1127+
if (auto *Arg = dyn_cast<SILArgument>(SI->getSrc()))
1128+
if (Arg->getParent() == MUI->getParent())
1129+
continue;
1130+
1131+
// A store of a load from the box is ignored.
1132+
// FIXME: SILGen should not emit these.
1133+
if (auto *LI = dyn_cast<LoadInst>(SI->getSrc()))
1134+
if (LI->getOperand() == MUI)
1135+
continue;
1136+
1137+
// Any other store needs to be recorded.
1138+
UseInfo.trackStoreToSelf(SI);
1139+
continue;
1140+
}
1141+
}
11221142

11231143
// Ignore end_borrows. These can only come from us being the source of a
11241144
// load_borrow.
@@ -1475,10 +1495,26 @@ void DelegatingInitElementUseCollector::collectClassInitSelfUses() {
14751495
if (isa<EndBorrowInst>(User))
14761496
continue;
14771497

1478-
// Stores to self are initializations store or the rebind of self as
1479-
// part of the super.init call. Ignore both of these.
1480-
if (isa<StoreInst>(User) && Op->getOperandNumber() == 1)
1481-
continue;
1498+
// Stores to self.
1499+
if (auto *SI = dyn_cast<StoreInst>(User)) {
1500+
if (Op->getOperandNumber() == 1) {
1501+
// The initial store of 'self' into the box at the start of the
1502+
// function. Ignore it.
1503+
if (auto *Arg = dyn_cast<SILArgument>(SI->getSrc()))
1504+
if (Arg->getParent() == MUI->getParent())
1505+
continue;
1506+
1507+
// A store of a load from the box is ignored.
1508+
// FIXME: SILGen should not emit these.
1509+
if (auto *LI = dyn_cast<LoadInst>(SI->getSrc()))
1510+
if (LI->getOperand() == MUI)
1511+
continue;
1512+
1513+
// Any other store needs to be recorded.
1514+
UseInfo.trackStoreToSelf(SI);
1515+
continue;
1516+
}
1517+
}
14821518

14831519
// For class initializers, the assign into the self box may be
14841520
// captured as SelfInit or SuperInit elsewhere.
@@ -1491,6 +1527,7 @@ void DelegatingInitElementUseCollector::collectClassInitSelfUses() {
14911527
if (auto *Fn = AI->getCalleeFunction()) {
14921528
if (Fn->getRepresentation() ==
14931529
SILFunctionTypeRepresentation::CFunctionPointer) {
1530+
UseInfo.trackStoreToSelf(User);
14941531
UseInfo.trackUse(DIMemoryUse(User, DIUseKind::SelfInit, 0, 1));
14951532
continue;
14961533
}
@@ -1503,13 +1540,15 @@ void DelegatingInitElementUseCollector::collectClassInitSelfUses() {
15031540
if (auto *AI = dyn_cast<AssignInst>(User)) {
15041541
if (auto *AssignSource = AI->getOperand(0)->getDefiningInstruction()) {
15051542
if (isSelfInitUse(AssignSource) || isSuperInitUse(AssignSource)) {
1543+
UseInfo.trackStoreToSelf(User);
15061544
UseInfo.trackUse(DIMemoryUse(User, DIUseKind::SelfInit, 0, 1));
15071545
continue;
15081546
}
15091547
}
15101548
if (auto *AssignSource = dyn_cast<SILArgument>(AI->getOperand(0))) {
15111549
if (AssignSource->getParent() == AI->getParent() &&
15121550
(isSelfInitUse(AssignSource) || isSuperInitUse(AssignSource))) {
1551+
UseInfo.trackStoreToSelf(User);
15131552
UseInfo.trackUse(DIMemoryUse(User, DIUseKind::SelfInit, 0, 1));
15141553
continue;
15151554
}
@@ -1573,13 +1612,17 @@ void DelegatingInitElementUseCollector::collectValueTypeInitSelfUses(
15731612
// Stores *to* the allocation are writes. If the value being stored is a
15741613
// call to self.init()... then we have a self.init call.
15751614
if (auto *AI = dyn_cast<AssignInst>(User)) {
1576-
if (AI->getDest() == I)
1615+
if (AI->getDest() == I) {
1616+
UseInfo.trackStoreToSelf(AI);
15771617
Kind = DIUseKind::InitOrAssign;
1618+
}
15781619
}
15791620

15801621
if (auto *CAI = dyn_cast<CopyAddrInst>(User)) {
1581-
if (CAI->getDest() == I)
1622+
if (CAI->getDest() == I) {
1623+
UseInfo.trackStoreToSelf(CAI);
15821624
Kind = DIUseKind::InitOrAssign;
1625+
}
15831626
}
15841627

15851628
// Look through begin_access

lib/SILOptimizer/Mandatory/DIMemoryUseCollectorOwnership.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ class DIMemoryObjectInfo {
150150
return false;
151151
}
152152

153+
/// True if this memory object is the 'self' of a non-root class init method.
154+
bool isNonRootClassSelf() const {
155+
if (isClassInitSelf())
156+
if (auto MUI = dyn_cast<MarkUninitializedInst>(MemoryInst))
157+
return !MUI->isRootSelf();
158+
159+
return false;
160+
}
161+
153162
/// isDelegatingInit - True if this is a delegating initializer, one that
154163
/// calls 'self.init'.
155164
bool isDelegatingInit() const {
@@ -270,11 +279,14 @@ struct DIElementUseInfo {
270279
SmallVector<DIMemoryUse, 16> Uses;
271280
SmallVector<SILInstruction *, 4> Releases;
272281
TinyPtrVector<TermInst *> FailableInits;
282+
TinyPtrVector<SILInstruction *> StoresToSelf;
273283

274284
void trackUse(DIMemoryUse Use) { Uses.push_back(Use); }
275285

276286
void trackDestroy(SILInstruction *Destroy) { Releases.push_back(Destroy); }
277287

288+
void trackStoreToSelf(SILInstruction *I);
289+
278290
void trackFailableInitCall(const DIMemoryObjectInfo &TheMemory,
279291
SILInstruction *I);
280292

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 94 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -353,25 +353,33 @@ namespace {
353353

354354
/// Helper flag used during building the worklist for the dataflow analysis.
355355
bool isInWorkList : 1;
356-
356+
357357
/// Availability of elements within the block.
358358
/// Not "empty" for all blocks which have non-load uses or contain the
359359
/// definition of the memory object.
360360
AvailabilitySet LocalAvailability;
361-
361+
362362
/// The live out information of the block. This is the LocalAvailability
363363
/// plus the information merged-in from the predecessor blocks.
364364
AvailabilitySet OutAvailability;
365-
365+
366366
/// Keep track of blocks where the contents of the self box are not valid
367367
/// because we're in an error path dominated by a self.init or super.init
368368
/// delegation.
369369
Optional<DIKind> LocalSelfConsumed;
370-
370+
371371
/// The live out information of the block. This is the LocalSelfConsumed
372372
/// plus the information merged-in from the predecessor blocks.
373373
Optional<DIKind> OutSelfConsumed;
374374

375+
/// Keep track of blocks where the contents of the self box are stored to
376+
/// as a result of a successful self.init or super.init call.
377+
Optional<DIKind> LocalSelfInitialized;
378+
379+
/// The live out information of the block. This is the LocalSelfInitialized
380+
/// plus the information merged-in from the predecessor blocks.
381+
Optional<DIKind> OutSelfInitialized;
382+
375383
LiveOutBlockState(unsigned NumElements)
376384
: HasNonLoadUse(false),
377385
isInWorkList(false),
@@ -387,6 +395,10 @@ namespace {
387395
LocalSelfConsumed = DIKind::No;
388396
if (!OutSelfConsumed.hasValue())
389397
OutSelfConsumed = DIKind::No;
398+
if (!LocalSelfInitialized.hasValue())
399+
LocalSelfInitialized = DIKind::No;
400+
if (!OutSelfInitialized.hasValue())
401+
OutSelfInitialized = DIKind::No;
390402
}
391403

392404
/// Transfer function for dataflow analysis.
@@ -429,13 +441,22 @@ namespace {
429441
}
430442
}
431443

432-
Optional<DIKind> result;
444+
Optional<DIKind> temp;
433445
if (transferAvailability(Pred.OutSelfConsumed,
434446
OutSelfConsumed,
435447
LocalSelfConsumed,
448+
temp)) {
449+
changed = true;
450+
OutSelfConsumed = temp;
451+
}
452+
453+
Optional<DIKind> result;
454+
if (transferAvailability(Pred.OutSelfInitialized,
455+
OutSelfInitialized,
456+
LocalSelfInitialized,
436457
result)) {
437458
changed = true;
438-
OutSelfConsumed = result;
459+
OutSelfInitialized = result;
439460
}
440461

441462
return changed;
@@ -460,9 +481,17 @@ namespace {
460481
OutSelfConsumed = LocalSelfConsumed;
461482
}
462483

484+
/// Mark the block as storing to self, indicating the self box has been
485+
/// initialized.
486+
void markStoreToSelf() {
487+
LocalSelfInitialized = DIKind::Yes;
488+
OutSelfInitialized = DIKind::Yes;
489+
}
490+
463491
/// If true, we're not done with our dataflow analysis yet.
464492
bool containsUndefinedValues() {
465493
return (!OutSelfConsumed.hasValue() ||
494+
!OutSelfInitialized.hasValue() ||
466495
OutAvailability.containsUnknownElements());
467496
}
468497
};
@@ -487,6 +516,7 @@ namespace {
487516

488517
SmallVectorImpl<DIMemoryUse> &Uses;
489518
TinyPtrVector<TermInst *> &FailableInits;
519+
TinyPtrVector<SILInstruction *> &StoresToSelf;
490520
SmallVectorImpl<SILInstruction *> &Destroys;
491521
std::vector<ConditionalDestroy> ConditionalDestroys;
492522

@@ -537,6 +567,7 @@ namespace {
537567
unsigned NumElts);
538568

539569
DIKind getSelfConsumedAtInst(SILInstruction *Inst);
570+
DIKind getSelfInitializedAtInst(SILInstruction *Inst);
540571

541572
bool isInitializedAtUse(const DIMemoryUse &Use,
542573
bool *SuperInitDone = nullptr,
@@ -571,6 +602,7 @@ namespace {
571602
void computePredsLiveOut(SILBasicBlock *BB);
572603
void getOutAvailability(SILBasicBlock *BB, AvailabilitySet &Result);
573604
void getOutSelfConsumed(SILBasicBlock *BB, Optional<DIKind> &Result);
605+
void getOutSelfInitialized(SILBasicBlock *BB, Optional<DIKind> &Result);
574606

575607
bool shouldEmitError(SILInstruction *Inst);
576608
std::string getUninitElementName(const DIMemoryUse &Use);
@@ -589,7 +621,7 @@ LifetimeChecker::LifetimeChecker(const DIMemoryObjectInfo &TheMemory,
589621
DIElementUseInfo &UseInfo)
590622
: Module(TheMemory.MemoryInst->getModule()), TheMemory(TheMemory),
591623
Uses(UseInfo.Uses), FailableInits(UseInfo.FailableInits),
592-
Destroys(UseInfo.Releases) {
624+
StoresToSelf(UseInfo.StoresToSelf), Destroys(UseInfo.Releases) {
593625

594626
// The first step of processing an element is to collect information about the
595627
// element into data structures we use later.
@@ -614,6 +646,13 @@ LifetimeChecker::LifetimeChecker(const DIMemoryObjectInfo &TheMemory,
614646
BBInfo.markAvailable(Use);
615647
}
616648

649+
// Mark blocks where the self box is initialized.
650+
for (auto *I : StoresToSelf) {
651+
// FIXME: critical edges?
652+
auto *bb = I->getParent();
653+
getBlockInfo(bb).markStoreToSelf();
654+
}
655+
617656
// Mark blocks where the self value has been consumed.
618657
for (auto *I : FailableInits) {
619658
auto *bb = I->getSuccessors()[1].getBB();
@@ -2460,6 +2499,14 @@ getOutSelfConsumed(SILBasicBlock *BB, Optional<DIKind> &Result) {
24602499
Result = mergeKinds(Result, getBlockInfo(Pred).OutSelfConsumed);
24612500
}
24622501

2502+
void LifetimeChecker::
2503+
getOutSelfInitialized(SILBasicBlock *BB, Optional<DIKind> &Result) {
2504+
computePredsLiveOut(BB);
2505+
2506+
for (auto *Pred : BB->getPredecessorBlocks())
2507+
Result = mergeKinds(Result, getBlockInfo(Pred).OutSelfInitialized);
2508+
}
2509+
24632510
AvailabilitySet
24642511
LifetimeChecker::getLivenessAtNonTupleInst(swift::SILInstruction *Inst,
24652512
swift::SILBasicBlock *InstBB,
@@ -2602,14 +2649,13 @@ DIKind LifetimeChecker::
26022649
getSelfConsumedAtInst(SILInstruction *Inst) {
26032650
DEBUG(llvm::dbgs() << "Get self consumed at " << *Inst);
26042651

2605-
Optional<DIKind> Result;
2606-
26072652
SILBasicBlock *InstBB = Inst->getParent();
26082653
auto &BlockInfo = getBlockInfo(InstBB);
26092654

26102655
if (BlockInfo.LocalSelfConsumed.hasValue())
26112656
return *BlockInfo.LocalSelfConsumed;
26122657

2658+
Optional<DIKind> Result;
26132659
getOutSelfConsumed(InstBB, Result);
26142660

26152661
// If the result wasn't computed, we must be analyzing code within
@@ -2621,6 +2667,45 @@ getSelfConsumedAtInst(SILInstruction *Inst) {
26212667
return *Result;
26222668
}
26232669

2670+
/// getSelfInitializedAtInst - Check if the self box in an initializer has
2671+
/// a fully initialized value at the specified instruction.
2672+
///
2673+
/// Possible outcomes:
2674+
/// - 'Yes' -- 'self' is fully initialized, and should be destroyed in the
2675+
/// usual manner in an error path
2676+
///
2677+
/// - 'No', and instruction is dominated by a SelfInit use -- this means
2678+
/// 'self' was consumed by a self.init or super.init call, and we're in
2679+
/// an error path; there's nothing to clean up
2680+
///
2681+
/// - 'No', and instruction is not dominated by a SelfInit use -- this means
2682+
/// we have to do a partial cleanup, for example deallocating a class
2683+
/// instance without destroying its members
2684+
///
2685+
/// Also, the full range of conditional outcomes is possible above, if the
2686+
/// result is 'Partial'.
2687+
DIKind LifetimeChecker::
2688+
getSelfInitializedAtInst(SILInstruction *Inst) {
2689+
DEBUG(llvm::dbgs() << "Get self initialized at " << *Inst);
2690+
2691+
SILBasicBlock *InstBB = Inst->getParent();
2692+
auto &BlockInfo = getBlockInfo(InstBB);
2693+
2694+
if (BlockInfo.LocalSelfInitialized.hasValue())
2695+
return *BlockInfo.LocalSelfInitialized;
2696+
2697+
Optional<DIKind> Result;
2698+
getOutSelfInitialized(InstBB, Result);
2699+
2700+
// If the result wasn't computed, we must be analyzing code within
2701+
// an unreachable cycle that is not dominated by "TheMemory". Just force
2702+
// the result to initialized so that clients don't have to handle this.
2703+
if (!Result.hasValue())
2704+
Result = DIKind::Yes;
2705+
2706+
return *Result;
2707+
}
2708+
26242709
/// The specified instruction is a use of some number of elements. Determine
26252710
/// whether all of the elements touched by the instruction are definitely
26262711
/// initialized at this point or not.

0 commit comments

Comments
 (0)