Skip to content

Commit eda3824

Browse files
Merge pull request swiftlang#40194 from nate-chandler/lexical_lifetimes/shrink-borrow-scope/deinit-barrier
[CopyPropagation] Add ShrinkBorrowScope.
2 parents fecc887 + 14c2caa commit eda3824

File tree

13 files changed

+1031
-57
lines changed

13 files changed

+1031
-57
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: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,10 +1102,7 @@ bool SILInstruction::mayHaveSideEffects() const {
11021102
if (mayTrap())
11031103
return true;
11041104

1105-
MemoryBehavior B = getMemoryBehavior();
1106-
return B == MemoryBehavior::MayWrite ||
1107-
B == MemoryBehavior::MayReadWrite ||
1108-
B == MemoryBehavior::MayHaveSideEffects;
1105+
return mayWriteToMemory();
11091106
}
11101107

11111108
bool SILInstruction::mayRelease() const {
@@ -1373,6 +1370,12 @@ bool SILInstruction::mayTrap() const {
13731370
}
13741371
}
13751372

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+
13761379
bool SILInstruction::isMetaInstruction() const {
13771380
// Every instruction that implements getVarInfo() should be in this list.
13781381
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/Analysis/EscapeAnalysis.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2851,7 +2851,7 @@ bool EscapeAnalysis::canPointToSameMemory(SILValue V1, SILValue V2) {
28512851
// graph path exists from the referenced object to a global-escaping or
28522852
// argument-escaping node.
28532853
//
2854-
// TODO: This API is inneffective for release hoisting, because the release
2854+
// TODO: This API is ineffective for release hoisting, because the release
28552855
// itself is often the only place that an object's contents may escape. We can't
28562856
// currently determine that since the contents cannot escape prior to \p
28572857
// releasePtr, then livePtr cannot possible point to the same memory!

lib/SILOptimizer/SemanticARC/BorrowScopeOpts.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,35 @@
2424
using namespace swift;
2525
using namespace swift::semanticarc;
2626

27+
/// Whether the provided lexical begin_borrow instruction is redundant.
28+
///
29+
/// A begin_borrow [lexical] is redundant if the borrowed value's lifetime is
30+
/// otherwise guaranteed. That happens if:
31+
/// - the value is a guaranteed argument to the function
32+
/// - the value is itself a begin_borrow [lexical]
33+
static bool isRedundantLexicalBeginBorrow(BeginBorrowInst *bbi) {
34+
assert(bbi->isLexical());
35+
auto value = bbi->getOperand();
36+
if (auto *outerBBI = dyn_cast<BeginBorrowInst>(value)) {
37+
return outerBBI->isLexical();
38+
}
39+
if (auto *arg = dyn_cast<SILFunctionArgument>(value)) {
40+
return arg->getOwnershipKind() == OwnershipKind::Guaranteed;
41+
}
42+
return false;
43+
}
44+
2745
bool SemanticARCOptVisitor::visitBeginBorrowInst(BeginBorrowInst *bbi) {
2846
// Quickly check if we are supposed to perform this transformation.
2947
if (!ctx.shouldPerform(ARCTransformKind::RedundantBorrowScopeElimPeephole))
3048
return false;
3149

3250
// Lexical borrow scopes must remain in order to ensure that value lifetimes
3351
// are not observably shortened.
34-
if (bbi->isLexical())
35-
return false;
52+
if (bbi->isLexical()) {
53+
if (!isRedundantLexicalBeginBorrow(bbi))
54+
return false;
55+
}
3656

3757
auto kind = bbi->getOperand().getOwnershipKind();
3858
SmallVector<EndBorrowInst *, 16> endBorrows;

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)