Skip to content

Commit a310f23

Browse files
committed
[ownership] Add support for load_borrow in predictable mem opt.
This reduces the diff in between -Onone output when stripping before/after serialization. We support load_borrow by translating it to the load [copy] case. Specifically, for +1, we normally perform the following transform. store %1 to [init] %0 ... %2 = load [copy] %0 ... use(%2) ... destroy_value %2 => %1a = copy_value %1 store %1 to [init] %0 ... use(%1a) ... destroy_value %1a We analogously can optimize load_borrow by replacing the load with a begin_borrow: store %1 to [init] %0 ... %2 = load_borrow %0 ... use(%2) ... end_borrow %2 => %1a = copy_value %1 store %1 to [init] %0 ... %2 = begin_borrow %1a ... use(%2) ... end_borrow %2 destroy_value %1a The store from outside a loop being used by a load_borrow inside a loop is a similar transformation as the +0 version except that we use a begin_borrow inside the loop instead of a copy_value (making it even more efficient).
1 parent fa45f94 commit a310f23

File tree

4 files changed

+450
-199
lines changed

4 files changed

+450
-199
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ bool isOwnershipForwardingInst(SILInstruction *i);
162162

163163
bool isGuaranteedForwardingInst(SILInstruction *i);
164164

165+
/// Look up through the def-use chain of \p inputValue, recording any "borrow"
166+
/// introducers that we find into \p out.
165167
bool getUnderlyingBorrowIntroducers(SILValue inputValue,
166168
SmallVectorImpl<SILValue> &out);
167169

lib/SILOptimizer/Mandatory/PMOMemoryUseCollector.cpp

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,51 @@ static SILValue scalarizeLoad(LoadInst *LI,
7878
return B.createStruct(LI->getLoc(), LI->getType(), ElementTmps);
7979
}
8080

81+
/// Scalarize a load_borrow down to its subelements. It will scalarize each of
82+
/// the end_borrows of the load_borrow as well.
83+
static void scalarizeLoadBorrow(LoadBorrowInst *lbi,
84+
SmallVectorImpl<SILValue> &elementAddrs) {
85+
// First gather all of our end_borrows. We are going to scalarize them as
86+
// well.
87+
SmallVector<EndBorrowInst *, 8> endBorrows;
88+
for (auto *op : lbi->getUses()) {
89+
if (auto *ebi = dyn_cast<EndBorrowInst>(op->getUser())) {
90+
endBorrows.push_back(ebi);
91+
}
92+
}
93+
94+
SILBuilderWithScope b(lbi);
95+
SmallVector<SILValue, 4> elementTmps;
96+
97+
for (unsigned i : indices(elementAddrs)) {
98+
if (elementAddrs[i]->getType().isTrivial(lbi->getModule())) {
99+
elementTmps.push_back(b.createLoad(lbi->getLoc(), elementAddrs[i],
100+
LoadOwnershipQualifier::Trivial));
101+
continue;
102+
}
103+
104+
SILValue v = b.createLoadBorrow(lbi->getLoc(), elementAddrs[i]);
105+
for (auto *ebi : endBorrows) {
106+
SILBuilderWithScope(ebi).createEndBorrow(lbi->getLoc(), v);
107+
}
108+
elementTmps.push_back(v);
109+
}
110+
111+
// Inline constructor.
112+
auto result = ([&]() -> SILValue {
113+
if (lbi->getType().is<TupleType>())
114+
return b.createTuple(lbi->getLoc(), lbi->getType(), elementTmps);
115+
return b.createStruct(lbi->getLoc(), lbi->getType(), elementTmps);
116+
})();
117+
118+
// Delete all of the end borrows, rauw, and we are done!
119+
for (auto *ebi : endBorrows) {
120+
ebi->eraseFromParent();
121+
}
122+
lbi->replaceAllUsesWith(result);
123+
lbi->eraseFromParent();
124+
}
125+
81126
//===----------------------------------------------------------------------===//
82127
// ElementUseCollector Implementation
83128
//===----------------------------------------------------------------------===//
@@ -230,7 +275,7 @@ bool ElementUseCollector::collectUses(SILValue Pointer) {
230275
}
231276

232277
// Loads are a use of the value.
233-
if (isa<LoadInst>(User)) {
278+
if (isa<LoadInst>(User) || isa<LoadBorrowInst>(User)) {
234279
if (PointeeType.is<TupleType>())
235280
UsesToScalarize.push_back(User);
236281
else
@@ -418,6 +463,12 @@ bool ElementUseCollector::collectUses(SILValue Pointer) {
418463
continue;
419464
}
420465

466+
// Scalarize LoadBorrowInst
467+
if (auto *LBI = dyn_cast<LoadBorrowInst>(User)) {
468+
scalarizeLoadBorrow(LBI, ElementAddrs);
469+
continue;
470+
}
471+
421472
// Scalarize StoreInst
422473
if (auto *SI = dyn_cast<StoreInst>(User)) {
423474
SILBuilderWithScope B(User, SI);

lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,9 @@ class AvailableValueAggregator {
425425
/// If as a result of us copying values, we may have unconsumed destroys, find
426426
/// the appropriate location and place the values there. Only used when
427427
/// ownership is enabled.
428-
LoadInst *addMissingDestroysForCopiedValues(LoadInst *li, SILValue newVal);
428+
SingleValueInstruction *
429+
addMissingDestroysForCopiedValues(SingleValueInstruction *li,
430+
SILValue newVal);
429431

430432
void print(llvm::raw_ostream &os) const;
431433
void dump() const LLVM_ATTRIBUTE_USED;
@@ -746,14 +748,14 @@ SILValue AvailableValueAggregator::handlePrimitiveValue(SILType loadTy,
746748
return eltVal;
747749
}
748750

749-
LoadInst *
750-
AvailableValueAggregator::addMissingDestroysForCopiedValues(LoadInst *li,
751-
SILValue newVal) {
751+
SingleValueInstruction *
752+
AvailableValueAggregator::addMissingDestroysForCopiedValues(
753+
SingleValueInstruction *svi, SILValue newVal) {
752754
// If ownership is not enabled... bail. We do not need to do this since we do
753755
// not need to insert an extra copy unless we have ownership since without
754756
// ownership stores do not consume.
755757
if (!B.hasOwnership())
756-
return li;
758+
return svi;
757759

758760
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
759761
SmallVector<SILBasicBlock *, 8> leakingBlocks;
@@ -776,8 +778,9 @@ AvailableValueAggregator::addMissingDestroysForCopiedValues(LoadInst *li,
776778
// Then perform the linear lifetime check. If we succeed, continue. We have
777779
// no further work to do.
778780
auto errorKind = ownership::ErrorBehaviorKind::ReturnFalse;
779-
auto error = valueHasLinearLifetime(
780-
cvi, {li}, {}, visitedBlocks, deadEndBlocks, errorKind, &leakingBlocks);
781+
auto error =
782+
valueHasLinearLifetime(cvi, {svi}, {}, visitedBlocks, deadEndBlocks,
783+
errorKind, &leakingBlocks);
781784
if (!error.getFoundError())
782785
continue;
783786

@@ -795,17 +798,40 @@ AvailableValueAggregator::addMissingDestroysForCopiedValues(LoadInst *li,
795798
}
796799
}
797800

798-
// If we didn't find a loop, we are done, just return li to get RAUWed.
799-
if (!foundLoop)
800-
return li;
801+
// If we didn't find a loop, we are done, just return svi to get RAUWed.
802+
if (!foundLoop) {
803+
// If we had a load_borrow, we have created an extra copy that we are going
804+
// to borrow at the load point. This means we need to handle the destroying
805+
// of the value along paths reachable from the load_borrow. Luckily that
806+
// will exactly be after the end_borrows of the load_borrow.
807+
if (isa<LoadBorrowInst>(svi)) {
808+
for (auto *use : svi->getUses()) {
809+
if (auto *ebi = dyn_cast<EndBorrowInst>(use->getUser())) {
810+
auto next = std::next(ebi->getIterator());
811+
SILBuilderWithScope(next).emitDestroyValueOperation(ebi->getLoc(),
812+
newVal);
813+
}
814+
}
815+
}
816+
return svi;
817+
}
801818

802819
// If we found a loop, then we know that our leaking blocks are the exiting
803-
// blocks of the loop. Thus we need to change the load inst to a copy_value
804-
// instead of deleting it.
805-
newVal = SILBuilderWithScope(li).emitCopyValueOperation(loc, newVal);
806-
li->replaceAllUsesWith(newVal);
807-
SILValue addr = li->getOperand();
808-
li->eraseFromParent();
820+
// blocks of the loop and the value has been lifetime extended over the loop.
821+
if (isa<LoadInst>(svi)) {
822+
// If we have a load, we need to put in a copy so that the destroys within
823+
// the loop are properly balanced.
824+
newVal = SILBuilderWithScope(svi).emitCopyValueOperation(loc, newVal);
825+
} else {
826+
// If we have a load_borrow, we create a begin_borrow for the end_borrows in
827+
// the loop.
828+
assert(isa<LoadBorrowInst>(svi));
829+
newVal = SILBuilderWithScope(svi).createBeginBorrow(svi->getLoc(), newVal);
830+
}
831+
832+
svi->replaceAllUsesWith(newVal);
833+
SILValue addr = svi->getOperand(0);
834+
svi->eraseFromParent();
809835
if (auto *addrI = addr->getDefiningInstruction())
810836
recursivelyDeleteTriviallyDeadInstructions(addrI);
811837
return nullptr;
@@ -1308,19 +1334,23 @@ class AllocOptimize {
13081334
/// If we are able to optimize \p Inst, return the source address that
13091335
/// instruction is loading from. If we can not optimize \p Inst, then just
13101336
/// return an empty SILValue.
1311-
static SILValue tryFindSrcAddrForLoad(SILInstruction *Inst) {
1337+
static SILValue tryFindSrcAddrForLoad(SILInstruction *i) {
1338+
// We can always promote a load_borrow.
1339+
if (auto *lbi = dyn_cast<LoadBorrowInst>(i))
1340+
return lbi->getOperand();
1341+
13121342
// We only handle load [copy], load [trivial], load and copy_addr right
13131343
// now. Notably we do not support load [take] when promoting loads.
1314-
if (auto *LI = dyn_cast<LoadInst>(Inst))
1315-
if (LI->getOwnershipQualifier() != LoadOwnershipQualifier::Take)
1316-
return LI->getOperand();
1344+
if (auto *li = dyn_cast<LoadInst>(i))
1345+
if (li->getOwnershipQualifier() != LoadOwnershipQualifier::Take)
1346+
return li->getOperand();
13171347

13181348
// If this is a CopyAddr, verify that the element type is loadable. If not,
13191349
// we can't explode to a load.
1320-
auto *CAI = dyn_cast<CopyAddrInst>(Inst);
1321-
if (!CAI || !CAI->getSrc()->getType().isLoadable(CAI->getModule()))
1350+
auto *cai = dyn_cast<CopyAddrInst>(i);
1351+
if (!cai || !cai->getSrc()->getType().isLoadable(cai->getModule()))
13221352
return SILValue();
1323-
return CAI->getSrc();
1353+
return cai->getSrc();
13241354
}
13251355

13261356
/// At this point, we know that this element satisfies the definitive init
@@ -1385,25 +1415,30 @@ bool AllocOptimize::promoteLoad(SILInstruction *Inst) {
13851415
// removing the instruction from Uses for us, so we return false.
13861416
return false;
13871417
}
1388-
1418+
1419+
assert((isa<LoadBorrowInst>(Inst) || isa<LoadInst>(Inst)) &&
1420+
"Unhandled instruction for this code path!");
1421+
13891422
// Aggregate together all of the subelements into something that has the same
13901423
// type as the load did, and emit smaller loads for any subelements that were
1391-
// not available.
1392-
auto *Load = cast<LoadInst>(Inst);
1393-
AvailableValueAggregator Agg(Load, AvailableValues, Uses, deadEndBlocks,
1424+
// not available. We are "propagating" a +1 available value from the store
1425+
// points.
1426+
auto *load = dyn_cast<SingleValueInstruction>(Inst);
1427+
AvailableValueAggregator agg(load, AvailableValues, Uses, deadEndBlocks,
13941428
false /*isTake*/);
1395-
SILValue newVal = Agg.aggregateValues(LoadTy, Load->getOperand(), FirstElt);
1429+
SILValue newVal = agg.aggregateValues(LoadTy, load->getOperand(0), FirstElt);
13961430

1397-
LLVM_DEBUG(llvm::dbgs() << " *** Promoting load: " << *Load << "\n");
1431+
LLVM_DEBUG(llvm::dbgs() << " *** Promoting load: " << *load << "\n");
13981432
LLVM_DEBUG(llvm::dbgs() << " To value: " << *newVal << "\n");
13991433

14001434
// If we inserted any copies, we created the copies at our stores. We know
14011435
// that in our load block, we will reform the aggregate as appropriate at the
1402-
// load implying that the value /must/ be fully consumed. Thus any leaking
1436+
// load implying that the value /must/ be fully consumed. If we promoted a +0
1437+
// value, we created dominating destroys along those paths. Thus any leaking
14031438
// blocks that we may have can be found by performing a linear lifetime check
14041439
// over all copies that we found using the load as the "consuming uses" (just
14051440
// for the purposes of identifying the consuming block).
1406-
auto *oldLoad = Agg.addMissingDestroysForCopiedValues(Load, newVal);
1441+
auto *oldLoad = agg.addMissingDestroysForCopiedValues(load, newVal);
14071442

14081443
++NumLoadPromoted;
14091444

@@ -1412,8 +1447,14 @@ bool AllocOptimize::promoteLoad(SILInstruction *Inst) {
14121447
if (!oldLoad)
14131448
return true;
14141449

1450+
// If our load was a +0 value, borrow the value and the RAUW. We reuse the
1451+
// end_borrows of our load_borrow.
1452+
if (isa<LoadBorrowInst>(oldLoad)) {
1453+
newVal = SILBuilderWithScope(oldLoad).createBeginBorrow(oldLoad->getLoc(),
1454+
newVal);
1455+
}
14151456
oldLoad->replaceAllUsesWith(newVal);
1416-
SILValue addr = oldLoad->getOperand();
1457+
SILValue addr = oldLoad->getOperand(0);
14171458
oldLoad->eraseFromParent();
14181459
if (auto *addrI = addr->getDefiningInstruction())
14191460
recursivelyDeleteTriviallyDeadInstructions(addrI);

0 commit comments

Comments
 (0)