Skip to content

Commit 7fbd1bf

Browse files
authored
Merge pull request #22040 from gottesmm/pr-ce2c3b7aa7de382e779651e06185310aaad6deee
[pmo] Use the linear lifetime checker to insert compensating destroys…
2 parents 57773f7 + 04b60bb commit 7fbd1bf

File tree

2 files changed

+177
-56
lines changed

2 files changed

+177
-56
lines changed

lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp

Lines changed: 117 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#define DEBUG_TYPE "predictable-memopt"
1414

1515
#include "PMOMemoryUseCollector.h"
16+
#include "swift/SIL/BasicBlockUtils.h"
17+
#include "swift/SIL/BranchPropagatedUser.h"
18+
#include "swift/SIL/OwnershipUtils.h"
1619
#include "swift/SIL/SILBuilder.h"
1720
#include "swift/SILOptimizer/PassManager/Passes.h"
1821
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -390,13 +393,22 @@ class AvailableValueAggregator {
390393
SILLocation Loc;
391394
MutableArrayRef<AvailableValue> AvailableValueList;
392395
SmallVectorImpl<PMOMemoryUse> &Uses;
396+
DeadEndBlocks &deadEndBlocks;
397+
398+
/// Keep track of all instructions that we have added. Once we are done
399+
/// promoting a value, we need to make sure that if we need to balance any
400+
/// copies (to avoid leaks), we do so. This is not used if we are performing a
401+
/// take.
402+
SmallVector<SILInstruction *, 16> insertedInsts;
393403

394404
public:
395405
AvailableValueAggregator(SILInstruction *Inst,
396406
MutableArrayRef<AvailableValue> AvailableValueList,
397-
SmallVectorImpl<PMOMemoryUse> &Uses)
407+
SmallVectorImpl<PMOMemoryUse> &Uses,
408+
DeadEndBlocks &deadEndBlocks)
398409
: M(Inst->getModule()), B(Inst), Loc(Inst->getLoc()),
399-
AvailableValueList(AvailableValueList), Uses(Uses) {}
410+
AvailableValueList(AvailableValueList), Uses(Uses),
411+
deadEndBlocks(deadEndBlocks) {}
400412

401413
// This is intended to be passed by reference only once constructed.
402414
AvailableValueAggregator(const AvailableValueAggregator &) = delete;
@@ -406,10 +418,10 @@ class AvailableValueAggregator {
406418
AvailableValueAggregator &operator=(AvailableValueAggregator &&) = delete;
407419

408420
SILValue aggregateValues(SILType LoadTy, SILValue Address, unsigned FirstElt);
421+
void addMissingDestroysForCopiedValues(LoadInst *li);
409422

410423
void print(llvm::raw_ostream &os) const;
411424
void dump() const LLVM_ATTRIBUTE_USED;
412-
413425
private:
414426
SILValue aggregateFullyAvailableValue(SILType LoadTy, unsigned FirstElt);
415427
SILValue aggregateTupleSubElts(TupleType *TT, SILType LoadTy,
@@ -493,7 +505,7 @@ AvailableValueAggregator::aggregateFullyAvailableValue(SILType loadTy,
493505
ArrayRef<StoreInst *> insertPts = firstVal.getInsertionPoints();
494506
if (insertPts.size() == 1) {
495507
// Use the scope and location of the store at the insertion point.
496-
SILBuilderWithScope builder(insertPts[0]);
508+
SILBuilderWithScope builder(insertPts[0], &insertedInsts);
497509
SILLocation loc = insertPts[0]->getLoc();
498510
return builder.emitCopyValueOperation(loc, firstVal.getValue());
499511
}
@@ -506,7 +518,7 @@ AvailableValueAggregator::aggregateFullyAvailableValue(SILType loadTy,
506518
updater.Initialize(loadTy);
507519
for (auto *insertPt : firstVal.getInsertionPoints()) {
508520
// Use the scope and location of the store at the insertion point.
509-
SILBuilderWithScope builder(insertPt);
521+
SILBuilderWithScope builder(insertPt, &insertedInsts);
510522
SILLocation loc = insertPt->getLoc();
511523
SILValue eltVal = builder.emitCopyValueOperation(loc, firstVal.getValue());
512524
updater.AddAvailableValue(insertPt->getParent(), eltVal);
@@ -568,62 +580,109 @@ SILValue AvailableValueAggregator::aggregateStructSubElts(StructDecl *SD,
568580
// We have looked through all of the aggregate values and finally found a
569581
// "primitive value". If the value is available, use it (extracting if we need
570582
// to), otherwise emit a load of the value with the appropriate qualifier.
571-
SILValue AvailableValueAggregator::handlePrimitiveValue(SILType LoadTy,
572-
SILValue Address,
573-
unsigned FirstElt) {
574-
auto &Val = AvailableValueList[FirstElt];
583+
SILValue AvailableValueAggregator::handlePrimitiveValue(SILType loadTy,
584+
SILValue address,
585+
unsigned firstElt) {
586+
auto &val = AvailableValueList[firstElt];
575587

576588
// If the value is not available, load the value and update our use list.
577-
if (!Val) {
578-
LoadInst *Load = ([&]() {
589+
if (!val) {
590+
LoadInst *load = ([&]() {
579591
if (B.hasOwnership()) {
580-
return B.createTrivialLoadOr(Loc, Address,
592+
return B.createTrivialLoadOr(Loc, address,
581593
LoadOwnershipQualifier::Copy);
582594
}
583-
return B.createLoad(Loc, Address, LoadOwnershipQualifier::Unqualified);
595+
return B.createLoad(Loc, address, LoadOwnershipQualifier::Unqualified);
584596
}());
585-
Uses.emplace_back(Load, PMOUseKind::Load);
586-
return Load;
597+
Uses.emplace_back(load, PMOUseKind::Load);
598+
return load;
587599
}
588600

589601
// If we have 1 insertion point, just extract the value and return.
590602
//
591603
// This saves us from having to spend compile time in the SSA updater in this
592604
// case.
593-
ArrayRef<StoreInst *> InsertPts = Val.getInsertionPoints();
594-
if (InsertPts.size() == 1) {
605+
ArrayRef<StoreInst *> insertPts = val.getInsertionPoints();
606+
if (insertPts.size() == 1) {
595607
// Use the scope and location of the store at the insertion point.
596-
SILBuilderWithScope Builder(InsertPts[0]);
597-
SILLocation Loc = InsertPts[0]->getLoc();
598-
SILValue EltVal = nonDestructivelyExtractSubElement(Val, Builder, Loc);
608+
SILBuilderWithScope builder(insertPts[0], &insertedInsts);
609+
SILLocation loc = insertPts[0]->getLoc();
610+
SILValue eltVal = nonDestructivelyExtractSubElement(val, builder, loc);
599611
assert(
600-
!Builder.hasOwnership() ||
601-
EltVal.getOwnershipKind().isCompatibleWith(ValueOwnershipKind::Owned));
602-
assert(EltVal->getType() == LoadTy && "Subelement types mismatch");
603-
return EltVal;
612+
!builder.hasOwnership() ||
613+
eltVal.getOwnershipKind().isCompatibleWith(ValueOwnershipKind::Owned));
614+
assert(eltVal->getType() == loadTy && "Subelement types mismatch");
615+
return eltVal;
604616
}
605617

606618
// If we have an available value, then we want to extract the subelement from
607619
// the borrowed aggregate before each insertion point.
608-
SILSSAUpdater Updater(B.getModule());
609-
Updater.Initialize(LoadTy);
610-
for (auto *I : Val.getInsertionPoints()) {
620+
SILSSAUpdater updater(B.getModule());
621+
updater.Initialize(loadTy);
622+
623+
for (auto *i : insertPts) {
611624
// Use the scope and location of the store at the insertion point.
612-
SILBuilderWithScope Builder(I);
613-
SILLocation Loc = I->getLoc();
614-
SILValue EltVal = nonDestructivelyExtractSubElement(Val, Builder, Loc);
625+
SILBuilderWithScope builder(i, &insertedInsts);
626+
SILLocation loc = i->getLoc();
627+
SILValue eltVal = nonDestructivelyExtractSubElement(val, builder, loc);
615628
assert(
616-
!Builder.hasOwnership() ||
617-
EltVal.getOwnershipKind().isCompatibleWith(ValueOwnershipKind::Owned));
618-
Updater.AddAvailableValue(I->getParent(), EltVal);
629+
!builder.hasOwnership() ||
630+
eltVal.getOwnershipKind().isCompatibleWith(ValueOwnershipKind::Owned));
631+
632+
updater.AddAvailableValue(i->getParent(), eltVal);
619633
}
620634

621635
// Finally, grab the value from the SSA updater.
622-
SILValue EltVal = Updater.GetValueInMiddleOfBlock(B.getInsertionBB());
636+
SILValue eltVal = updater.GetValueInMiddleOfBlock(B.getInsertionBB());
623637
assert(!B.hasOwnership() ||
624-
EltVal.getOwnershipKind().isCompatibleWith(ValueOwnershipKind::Owned));
625-
assert(EltVal->getType() == LoadTy && "Subelement types mismatch");
626-
return EltVal;
638+
eltVal.getOwnershipKind().isCompatibleWith(ValueOwnershipKind::Owned));
639+
assert(eltVal->getType() == loadTy && "Subelement types mismatch");
640+
return eltVal;
641+
}
642+
643+
void AvailableValueAggregator::addMissingDestroysForCopiedValues(LoadInst *li) {
644+
// If ownership is not enabled... bail. We do not need to do this since we do
645+
// not need to insert an extra copy unless we have ownership since without
646+
// ownership stores do not consume.
647+
if (!B.hasOwnership())
648+
return;
649+
650+
SmallVector<BranchPropagatedUser, 1> consumingUses;
651+
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
652+
SmallVector<SILBasicBlock *, 8> leakingBlocks;
653+
while (!insertedInsts.empty()) {
654+
auto *cvi = dyn_cast<CopyValueInst>(insertedInsts.pop_back_val());
655+
if (!cvi)
656+
continue;
657+
658+
// Clear our worklist.
659+
consumingUses.clear();
660+
visitedBlocks.clear();
661+
leakingBlocks.clear();
662+
663+
// The linear lifetime checker doesn't care if the passed in load is
664+
// actually a user of our copy_value. What we care about is that the load is
665+
// guaranteed to be in the block where we have reformed the tuple in a
666+
// consuming manner. This means if we add it as the consuming use of the
667+
// copy, we can find the leaking places if any exist.
668+
consumingUses.push_back(li);
669+
670+
// Then perform the linear lifetime check. If we succeed, continue. We have
671+
// no further work to do.
672+
auto errorKind =
673+
ownership::ErrorBehaviorKind::ReturnFalseOnLeakAssertOtherwise;
674+
if (valueHasLinearLifetime(cvi, consumingUses, {}, visitedBlocks,
675+
deadEndBlocks, errorKind, &leakingBlocks))
676+
continue;
677+
678+
// Ok, we found some leaking blocks. Insert destroys at the
679+
// beginning of these blocks for our copy_value.
680+
auto loc = RegularLocation::getAutoGeneratedLocation();
681+
for (auto *bb : leakingBlocks) {
682+
SILBuilderWithScope b(bb->begin());
683+
b.emitDestroyValueOperation(loc, cvi);
684+
}
685+
}
627686
}
628687

629688
//===----------------------------------------------------------------------===//
@@ -1091,16 +1150,19 @@ class AllocOptimize {
10911150
SmallVectorImpl<PMOMemoryUse> &Uses;
10921151
SmallVectorImpl<SILInstruction *> &Releases;
10931152

1153+
DeadEndBlocks &deadEndBlocks;
1154+
10941155
/// A structure that we use to compute our available values.
10951156
AvailableValueDataflowContext DataflowContext;
10961157

10971158
public:
10981159
AllocOptimize(AllocationInst *memory, SmallVectorImpl<PMOMemoryUse> &uses,
1099-
SmallVectorImpl<SILInstruction *> &releases)
1160+
SmallVectorImpl<SILInstruction *> &releases,
1161+
DeadEndBlocks &deadEndBlocks)
11001162
: Module(memory->getModule()), TheMemory(memory),
11011163
MemoryType(getMemoryType(memory)),
11021164
NumMemorySubElements(getNumSubElements(MemoryType, Module)), Uses(uses),
1103-
Releases(releases),
1165+
Releases(releases), deadEndBlocks(deadEndBlocks),
11041166
DataflowContext(TheMemory, NumMemorySubElements, uses) {}
11051167

11061168
bool optimizeMemoryAccesses();
@@ -1202,9 +1264,17 @@ bool AllocOptimize::promoteLoad(SILInstruction *Inst) {
12021264
// type as the load did, and emit smaller loads for any subelements that were
12031265
// not available.
12041266
auto *Load = cast<LoadInst>(Inst);
1205-
AvailableValueAggregator Agg(Load, AvailableValues, Uses);
1267+
AvailableValueAggregator Agg(Load, AvailableValues, Uses, deadEndBlocks);
12061268
SILValue NewVal = Agg.aggregateValues(LoadTy, Load->getOperand(), FirstElt);
12071269

1270+
// If we inserted any copies, we created the copies at our stores. We know
1271+
// that in our load block, we will reform the aggregate as appropriate at the
1272+
// load implying that the value /must/ be fully consumed. Thus any leaking
1273+
// blocks that we may have can be found by performing a linear lifetime check
1274+
// over all copies that we found using the load as the "consuming uses" (just
1275+
// for the purposes of identifying the consuming block).
1276+
Agg.addMissingDestroysForCopiedValues(Load);
1277+
12081278
++NumLoadPromoted;
12091279

12101280
// Simply replace the load.
@@ -1283,7 +1353,7 @@ void AllocOptimize::promoteDestroyAddr(
12831353
// Aggregate together all of the subelements into something that has the same
12841354
// type as the load did, and emit smaller) loads for any subelements that were
12851355
// not available.
1286-
AvailableValueAggregator Agg(DAI, AvailableValues, Uses);
1356+
AvailableValueAggregator Agg(DAI, AvailableValues, Uses, deadEndBlocks);
12871357
SILValue NewVal = Agg.aggregateValues(LoadTy, Address, FirstElt);
12881358

12891359
++NumDestroyAddrPromoted;
@@ -1493,6 +1563,8 @@ static AllocationInst *getOptimizableAllocation(SILInstruction *i) {
14931563

14941564
static bool optimizeMemoryAccesses(SILFunction &fn) {
14951565
bool changed = false;
1566+
DeadEndBlocks deadEndBlocks(&fn);
1567+
14961568
for (auto &bb : fn) {
14971569
auto i = bb.begin(), e = bb.end();
14981570
while (i != e) {
@@ -1519,7 +1591,7 @@ static bool optimizeMemoryAccesses(SILFunction &fn) {
15191591
continue;
15201592
}
15211593

1522-
AllocOptimize allocOptimize(alloc, uses, destroys);
1594+
AllocOptimize allocOptimize(alloc, uses, destroys, deadEndBlocks);
15231595
changed |= allocOptimize.optimizeMemoryAccesses();
15241596

15251597
// Move onto the next instruction. We know this is safe since we do not
@@ -1533,6 +1605,8 @@ static bool optimizeMemoryAccesses(SILFunction &fn) {
15331605

15341606
static bool eliminateDeadAllocations(SILFunction &fn) {
15351607
bool changed = false;
1608+
DeadEndBlocks deadEndBlocks(&fn);
1609+
15361610
for (auto &bb : fn) {
15371611
auto i = bb.begin(), e = bb.end();
15381612
while (i != e) {
@@ -1560,7 +1634,7 @@ static bool eliminateDeadAllocations(SILFunction &fn) {
15601634
continue;
15611635
}
15621636

1563-
AllocOptimize allocOptimize(alloc, uses, destroys);
1637+
AllocOptimize allocOptimize(alloc, uses, destroys, deadEndBlocks);
15641638
changed |= allocOptimize.tryToRemoveDeadAllocation();
15651639

15661640
// Move onto the next instruction. We know this is safe since we do not

0 commit comments

Comments
 (0)