Skip to content

Commit cde250a

Browse files
committed
[CopyPropagation] Add ShrinkBorrowScope.
During copy propagation (for which -enable-copy-propagation must still be passed), also try to shrink borrow scopes by hoisting end_borrows using the newly added ShrinkBorrowScope utility. Allow end_borrow instructions to be hoisted over instructions that are not deinit barriers for the value which is borrowed. Deinit barriers include uses of the value, loads of memory, loads of weak references that may be zeroed during deinit, and "synchronization points". rdar://79149830
1 parent c8f0dd2 commit cde250a

File tree

11 files changed

+934
-11
lines changed

11 files changed

+934
-11
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class SILModule;
3131
class SILValue;
3232
class DeadEndBlocks;
3333
class PrunedLiveness;
34+
struct BorrowedValue;
3435

3536
/// Returns true if v is an address or trivial.
3637
bool isValueAddressOrTrivial(SILValue v);
@@ -103,6 +104,15 @@ inline bool isForwardingConsume(SILValue value) {
103104
bool findInnerTransitiveGuaranteedUses(
104105
SILValue guaranteedValue, SmallVectorImpl<Operand *> *usePoints = nullptr);
105106

107+
/// Like findInnerTransitiveGuaranteedUses except that rather than it being a
108+
/// precondition that the provided value not be a BorrowedValue, it is a [type-
109+
/// system-enforced] precondition that the provided value be a BorrowedValue.
110+
///
111+
/// TODO: Merge with findInnerTransitiveGuaranteedUses.
112+
bool findInnerTransitiveGuaranteedUsesOfBorrowedValue(
113+
BorrowedValue borrowedValue,
114+
SmallVectorImpl<Operand *> *usePoints = nullptr);
115+
106116
/// Find leaf "use points" of a guaranteed value within its enclosing borrow
107117
/// scope (without looking through reborrows). To find the use points of the
108118
/// extended borrow scope, after looking through reborrows, use

include/swift/SIL/SILInstruction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,12 @@ class SILInstruction : public llvm::ilist_node<SILInstruction> {
630630
/// Can this instruction abort the program in some manner?
631631
bool mayTrap() const;
632632

633+
/// Involves a synchronization point like a memory barrier, lock or syscall.
634+
///
635+
/// TODO: We need side-effect analysis and library annotation for this to be
636+
/// a reasonable API. For now, this is just a placeholder.
637+
bool maySynchronize() const;
638+
633639
/// Returns true if the given instruction is completely identical to RHS.
634640
bool isIdenticalTo(const SILInstruction *RHS) const {
635641
return isIdenticalTo(RHS,

include/swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616
/// deleted, which in turn allows canonicalization of the outer owned values
1717
/// (via CanonicalizeOSSALifetime).
1818
///
19-
/// This does not shrink borrow scopes; it does not rewrite end_borrows.
20-
///
21-
/// TODO: A separate utility to shrink borrow scopes should eventually run
22-
/// before this utility. It should hoist end_borrow up to the latest "destroy
23-
/// barrier" whenever the scope does not contain a PointerEscape.
19+
/// This does not shrink borrow scopes; it does not rewrite end_borrows. For
20+
/// that, see ShrinkBorrowScope.
2421
///
2522
//===----------------------------------------------------------------------===//
2623

@@ -150,6 +147,8 @@ class CanonicalizeBorrowScope {
150147
bool consolidateBorrowScope();
151148
};
152149

150+
bool shrinkBorrowScope(BeginBorrowInst *bbi, InstructionDeleter &deleter);
151+
153152
} // namespace swift
154153

155154
#endif // SWIFT_SILOPTIMIZER_UTILS_CANONICALIZEBORROWSCOPES_H

lib/SIL/IR/SILInstruction.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,12 @@ bool SILInstruction::mayTrap() const {
13701370
}
13711371
}
13721372

1373+
bool SILInstruction::maySynchronize() const {
1374+
// TODO: We need side-effect analysis and library annotation for this to be
1375+
// a reasonable API. For now, this is just a placeholder.
1376+
return isa<FullApplySite>(this);
1377+
}
1378+
13731379
bool SILInstruction::isMetaInstruction() const {
13741380
// Every instruction that implements getVarInfo() should be in this list.
13751381
switch (getKind()) {

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,103 @@ bool swift::findInnerTransitiveGuaranteedUses(
188188
return true;
189189
}
190190

191+
/// Like findInnerTransitiveGuaranteedUses except that rather than it being a
192+
/// precondition that the provided value not be a BorrowedValue, it is a [type-
193+
/// system-enforced] precondition that the provided value be a BorrowedValue.
194+
///
195+
/// TODO: Merge with findInnerTransitiveGuaranteedUses. Note that at the moment
196+
/// the two are _almost_ identical, but not quite because the other has a
197+
/// #if 0 and not just leaf uses but ALL uses are recorded.
198+
bool swift::findInnerTransitiveGuaranteedUsesOfBorrowedValue(
199+
BorrowedValue borrowedValue, SmallVectorImpl<Operand *> *usePoints) {
200+
201+
auto recordUse = [&](Operand *use) {
202+
if (usePoints && use->getOperandOwnership() != OperandOwnership::NonUse) {
203+
usePoints->push_back(use);
204+
}
205+
};
206+
207+
// Push the value's immediate uses.
208+
//
209+
// TODO: The worklist can be a simple vector without any a membership check if
210+
// destructures are changed to be represented as reborrows. Currently a
211+
// destructure forwards multiple results! This means that the worklist could
212+
// grow exponentially without the membership check. It's fine to do this
213+
// membership check locally in this function (within a borrow scope) because
214+
// it isn't needed for the immediate uses, only the transitive uses.
215+
GraphNodeWorklist<Operand *, 8> worklist;
216+
for (Operand *use : borrowedValue.value->getUses()) {
217+
if (use->getOperandOwnership() != OperandOwnership::NonUse)
218+
worklist.insert(use);
219+
}
220+
221+
// --- Transitively follow forwarded uses and look for escapes.
222+
223+
// usePoints grows in this loop.
224+
while (Operand *use = worklist.pop()) {
225+
switch (use->getOperandOwnership()) {
226+
case OperandOwnership::NonUse:
227+
case OperandOwnership::TrivialUse:
228+
case OperandOwnership::ForwardingConsume:
229+
case OperandOwnership::DestroyingConsume:
230+
llvm_unreachable("this operand cannot handle an inner guaranteed use");
231+
232+
case OperandOwnership::ForwardingUnowned:
233+
case OperandOwnership::PointerEscape:
234+
return false;
235+
236+
case OperandOwnership::InstantaneousUse:
237+
case OperandOwnership::UnownedInstantaneousUse:
238+
case OperandOwnership::BitwiseEscape:
239+
// Reborrow only happens when this is called on a value that creates a
240+
// borrow scope.
241+
case OperandOwnership::Reborrow:
242+
// EndBorrow either happens when this is called on a value that creates a
243+
// borrow scope, or when it is pushed as a use when processing a nested
244+
// borrow.
245+
case OperandOwnership::EndBorrow:
246+
recordUse(use);
247+
break;
248+
249+
case OperandOwnership::InteriorPointer:
250+
if (InteriorPointerOperandKind::get(use) ==
251+
InteriorPointerOperandKind::Invalid)
252+
return false;
253+
// If our base guaranteed value does not have any consuming uses (consider
254+
// function arguments), we need to be sure to include interior pointer
255+
// operands since we may not get a use from a end_scope instruction.
256+
if (InteriorPointerOperand(use).findTransitiveUses(usePoints) !=
257+
AddressUseKind::NonEscaping) {
258+
return false;
259+
}
260+
break;
261+
262+
case OperandOwnership::ForwardingBorrow: {
263+
ForwardingOperand(use).visitForwardedValues([&](SILValue result) {
264+
// Do not include transitive uses with 'none' ownership
265+
if (result.getOwnershipKind() == OwnershipKind::None)
266+
return true;
267+
for (auto *resultUse : result->getUses()) {
268+
if (resultUse->getOperandOwnership() != OperandOwnership::NonUse) {
269+
worklist.insert(resultUse);
270+
}
271+
}
272+
return true;
273+
});
274+
recordUse(use);
275+
break;
276+
}
277+
case OperandOwnership::Borrow:
278+
BorrowingOperand(use).visitExtendedScopeEndingUses([&](Operand *endUse) {
279+
recordUse(endUse);
280+
return true;
281+
});
282+
break;
283+
}
284+
}
285+
return true;
286+
}
287+
191288
// Find all use points of \p guaranteedValue within its borrow scope. All use
192289
// points will be dominated by \p guaranteedValue.
193290
//

lib/SILOptimizer/Transforms/CopyPropagation.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ struct CanonicalDefWorklist {
7575
CanonicalDefWorklist(bool canonicalizeBorrows)
7676
: canonicalizeBorrows(canonicalizeBorrows) {}
7777

78+
// Update the worklist for the def corresponding to \p bbi, a BeginBorrow.
79+
void updateForBorrow(BeginBorrowInst *bbi) { borrowedValues.insert(bbi); }
80+
7881
// Update the worklist for the def corresponding to \p copy, which is usually
7982
// a CopyValue, but may be any owned value such as the operand of a
8083
// DestroyValue (to force lifetime shortening).
@@ -414,18 +417,30 @@ void CopyPropagation::run() {
414417
InstructionDeleter deleter(std::move(callbacks));
415418
bool changed = false;
416419

417-
// Driver: Find all copied defs.
420+
GraphNodeWorklist<BeginBorrowInst *, 16> beginBorrowsToShrink;
421+
422+
// Driver: Find all copied or borrowed defs.
418423
for (auto &bb : *f) {
419424
for (auto &i : bb) {
420425
if (auto *copy = dyn_cast<CopyValueInst>(&i)) {
421426
defWorklist.updateForCopy(copy);
427+
} else if (auto *borrow = dyn_cast<BeginBorrowInst>(&i)) {
428+
beginBorrowsToShrink.insert(borrow);
422429
} else if (canonicalizeAll) {
423430
if (auto *destroy = dyn_cast<DestroyValueInst>(&i)) {
424431
defWorklist.updateForCopy(destroy->getOperand());
425432
}
426433
}
427434
}
428435
}
436+
437+
// NOTE: We assume that the function is in reverse post order so visiting the
438+
// blocks and pushing begin_borrows as we see them and then popping them
439+
// off the end will result in shrinking inner borrow scopes first.
440+
while (auto *bbi = beginBorrowsToShrink.pop()) {
441+
shrinkBorrowScope(bbi, deleter);
442+
}
443+
429444
// canonicalizer performs all modifications through deleter's callbacks, so we
430445
// don't need to explicitly check for changes.
431446
CanonicalizeOSSALifetime canonicalizer(pruneDebug, poisonRefs,

lib/SILOptimizer/Utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ target_sources(swiftSILOptimizer PRIVATE
2222
OptimizerStatsUtils.cpp
2323
PartialApplyCombiner.cpp
2424
PerformanceInlinerUtils.cpp
25+
ShrinkBorrowScope.cpp
2526
SILInliner.cpp
2627
SILSSAUpdater.cpp
2728
SpecializationMangler.cpp

0 commit comments

Comments
 (0)