Skip to content

Commit 4b592c0

Browse files
committed
Add OSSA utilities for extending lifetimes and borrow scopes
Without introducing any new borrow scopes or owned lifetimes. Top-level APIs: - extendOwnedLifetime() - extendLocalBorrow() New utilitiy: GuaranteedOwnershipExtension. This is a simple utility to determine whether new uses can be added to an existing guaranteed value. It reports the kind of transformation needed and performs the transformation if requested. If transformation is needed, it simply calls one of the two top-level APIs.
1 parent a2e0964 commit 4b592c0

File tree

2 files changed

+310
-0
lines changed

2 files changed

+310
-0
lines changed

include/swift/SILOptimizer/Utils/OwnershipOptUtils.h

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/Basic/Defer.h"
2323
#include "swift/SIL/BasicBlockUtils.h"
2424
#include "swift/SIL/OwnershipUtils.h"
25+
#include "swift/SIL/PrunedLiveness.h"
2526
#include "swift/SIL/SILModule.h"
2627
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
2728

@@ -34,6 +35,31 @@ inline bool requiresOSSACleanup(SILValue v) {
3435
&& v.getOwnershipKind() != OwnershipKind::Unowned;
3536
}
3637

38+
/// Rewrite the lifetime of \p ownedValue to match \p lifetimeBoundary. This may
39+
/// insert copies at forwarding consumes, including phis.
40+
///
41+
/// Precondition: lifetimeBoundary is dominated by ownedValue.
42+
///
43+
/// Precondition: lifetimeBoundary is a superset of ownedValue's current
44+
/// lifetime (therefore, none of the safety checks done during
45+
/// CanonicalizeOSSALifetime are needed here).
46+
void extendOwnedLifetime(SILValue ownedValue,
47+
PrunedLivenessBoundary &lifetimeBoundary,
48+
InstructionDeleter &deleter);
49+
50+
/// Rewrite the local borrow scope introduced by \p beginBorrow to match \p
51+
/// guaranteedBoundary.
52+
///
53+
/// Precondition: guaranteedBoundary is dominated by beginBorrow which has no
54+
/// reborrows.
55+
///
56+
/// Precondition: guaranteedBoundary is a superset of beginBorrow's current
57+
/// scope (therefore, none of the safety checks done during
58+
/// CanonicalizeBorrowScope are needed here).
59+
void extendLocalBorrow(BeginBorrowInst *beginBorrow,
60+
PrunedLivenessBoundary &guaranteedBoundary,
61+
InstructionDeleter &deleter);
62+
3763
/// Given a new phi that may use a guaranteed value, create nested borrow scopes
3864
/// for its incoming operands and end_borrows that cover the phi's extended
3965
/// borrow scope, which transitively includes any phis that use this phi.
@@ -47,6 +73,73 @@ inline bool requiresOSSACleanup(SILValue v) {
4773
/// newly created phis do not yet have a borrow scope.
4874
bool createBorrowScopeForPhiOperands(SILPhiArgument *newPhi);
4975

76+
//===----------------------------------------------------------------------===//
77+
// GuaranteedOwnershipExtension
78+
//===----------------------------------------------------------------------===//
79+
80+
/// Extend existing guaranteed ownership to cover new guaranteeed uses that are
81+
/// dominated by the borrow introducer.
82+
class GuaranteedOwnershipExtension {
83+
// --- context
84+
InstructionDeleter &deleter;
85+
DeadEndBlocks &deBlocks;
86+
87+
// --- analysis state
88+
PrunedLiveness guaranteedLiveness;
89+
PrunedLiveness ownedLifetime;
90+
SmallVector<SILBasicBlock *, 4> ownedConsumeBlocks;
91+
BeginBorrowInst *beginBorrow = nullptr;
92+
93+
public:
94+
GuaranteedOwnershipExtension(InstructionDeleter &deleter,
95+
DeadEndBlocks &deBlocks)
96+
: deleter(deleter), deBlocks(deBlocks) {}
97+
98+
void clear() {
99+
guaranteedLiveness.clear();
100+
ownedLifetime.clear();
101+
ownedConsumeBlocks.clear();
102+
beginBorrow = nullptr;
103+
}
104+
105+
/// Invalid indicates that the current guaranteed scope is insufficient, and
106+
/// it does not meet the precondition for scope extension.
107+
///
108+
/// Valid indicates that the current guaranteed scope is sufficient with no
109+
/// transformation required.
110+
///
111+
/// ExtendBorrow indicates that the local borrow scope can be extended without
112+
/// affecting the owned lifetime or introducing copies.
113+
///
114+
/// ExtendLifetime indicates that the owned lifetime can be extended possibly
115+
/// requiring additional copies.
116+
enum Status { Invalid, Valid, ExtendBorrow, ExtendLifetime };
117+
118+
/// Can the OSSA ownership of the \p parentAddress cover all uses of the \p
119+
/// childAddress?
120+
///
121+
/// Precondition: \p parentAddress dominates \p childAddress
122+
Status checkAddressOwnership(SILValue parentAddress, SILValue childAddress);
123+
124+
/// Can the OSSA scope of \p borrow cover all \p newUses?
125+
///
126+
/// Precondition: \p borrow dominates \p newUses
127+
Status checkBorrowExtension(BorrowedValue borrow,
128+
ArrayRef<Operand *> newUses);
129+
130+
/// Can the OSSA scope of \p ownedValue cover all the guaranteed \p newUses?
131+
///
132+
/// Precondition: \p ownedValue dominates \p newUses
133+
Status checkLifetimeExtension(SILValue ownedValue,
134+
ArrayRef<Operand *> newUses);
135+
136+
void transform(Status status);
137+
};
138+
139+
//===----------------------------------------------------------------------===//
140+
// RAUW - Replace All Uses With...
141+
//===----------------------------------------------------------------------===//
142+
50143
/// A struct that contains context shared in between different operation +
51144
/// "ownership fixup" utilities. Please do not put actual methods on this, it is
52145
/// meant to be composed with.

lib/SILOptimizer/Utils/OwnershipOptUtils.cpp

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,223 @@
3434

3535
using namespace swift;
3636

37+
//===----------------------------------------------------------------------===//
38+
// Basic scope and lifetime extension API
39+
//===----------------------------------------------------------------------===//
40+
41+
void swift::extendOwnedLifetime(SILValue ownedValue,
42+
PrunedLivenessBoundary &lifetimeBoundary,
43+
InstructionDeleter &deleter) {
44+
// Gather the current set of destroy_values, which may die.
45+
SmallSetVector<Operand *, 4> extraConsumes;
46+
SmallPtrSet<SILInstruction *, 4> extraConsumers;
47+
for (Operand *use : ownedValue->getUses()) {
48+
if (use->isConsuming()) {
49+
extraConsumes.insert(use);
50+
extraConsumers.insert(use->getUser());
51+
}
52+
}
53+
// Insert or reuse a destroy_value at all last users.
54+
auto createDestroy = [&](SILBuilder &builder) {
55+
auto loc = RegularLocation::getAutoGeneratedLocation(
56+
builder.getInsertionPointLoc());
57+
auto *destroy = builder.createDestroyValue(loc, ownedValue);
58+
deleter.getCallbacks().createdNewInst(destroy);
59+
};
60+
for (SILInstruction *lastUser : lifetimeBoundary.lastUsers) {
61+
if (extraConsumers.erase(lastUser))
62+
continue;
63+
64+
SILBuilderWithScope::insertAfter(lastUser, createDestroy);
65+
}
66+
// Insert a destroy_value at all boundary edges.
67+
for (SILBasicBlock *edge : lifetimeBoundary.boundaryEdges) {
68+
SILBuilderWithScope builder(edge->begin());
69+
createDestroy(builder);
70+
}
71+
// Delete or copy extra consumes.
72+
for (auto *consume : extraConsumes) {
73+
auto *consumer = consume->getUser();
74+
if (!extraConsumers.count(consumer))
75+
continue;
76+
77+
if (isa<DestroyValueInst>(consumer)) {
78+
deleter.forceDelete(consumer);
79+
continue;
80+
}
81+
auto loc = RegularLocation::getAutoGeneratedLocation(consumer->getLoc());
82+
auto *copy = SILBuilderWithScope(consumer).createCopyValue(loc, ownedValue);
83+
consume->set(copy);
84+
deleter.getCallbacks().createdNewInst(copy);
85+
}
86+
}
87+
88+
void swift::extendLocalBorrow(BeginBorrowInst *beginBorrow,
89+
PrunedLivenessBoundary &guaranteedBoundary,
90+
InstructionDeleter &deleter) {
91+
// Gather the current set of end_borrows, which may die.
92+
SmallVector<EndBorrowInst *, 4> endBorrows;
93+
SmallPtrSet<EndBorrowInst *, 4> deadEndBorrows;
94+
for (Operand *use : beginBorrow->getUses()) {
95+
if (auto *endBorrow = dyn_cast<EndBorrowInst>(use->getUser())) {
96+
endBorrows.push_back(endBorrow);
97+
deadEndBorrows.insert(endBorrow);
98+
continue;
99+
}
100+
assert(use->getOperandOwnership() != OperandOwnership::EndBorrow
101+
&& use->getOperandOwnership() != OperandOwnership::Reborrow
102+
&& "expecting a purely local borrow scope");
103+
}
104+
// Insert or reuse an end_borrow at all last users.
105+
auto createEndBorrow = [&](SILBuilder &builder) {
106+
auto loc = RegularLocation::getAutoGeneratedLocation(
107+
builder.getInsertionPointLoc());
108+
auto *endBorrow = builder.createEndBorrow(loc, beginBorrow);
109+
deleter.getCallbacks().createdNewInst(endBorrow);
110+
};
111+
for (SILInstruction *lastUser : guaranteedBoundary.lastUsers) {
112+
if (auto *endBorrow = dyn_cast<EndBorrowInst>(lastUser)) {
113+
if (deadEndBorrows.erase(endBorrow))
114+
continue;
115+
}
116+
SILBuilderWithScope::insertAfter(lastUser, createEndBorrow);
117+
}
118+
// Insert an end_borrow at all boundary edges.
119+
for (SILBasicBlock *edge : guaranteedBoundary.boundaryEdges) {
120+
SILBuilderWithScope builder(edge->begin());
121+
createEndBorrow(builder);
122+
}
123+
// Delete dead end_borrows.
124+
for (auto *endBorrow : endBorrows) {
125+
if (deadEndBorrows.count(endBorrow))
126+
deleter.forceDelete(endBorrow);
127+
}
128+
}
129+
130+
//===----------------------------------------------------------------------===//
131+
// GuaranteedOwnershipExtension
132+
//===----------------------------------------------------------------------===//
133+
134+
// Can the OSSA ownership of the \p parentAddress cover all uses of the \p
135+
// childAddress?
136+
GuaranteedOwnershipExtension::Status
137+
GuaranteedOwnershipExtension::checkAddressOwnership(SILValue parentAddress,
138+
SILValue childAddress) {
139+
AddressOwnership addressOwnership(parentAddress);
140+
if (!addressOwnership.hasLocalOwnershipLifetime()) {
141+
// Indirect Arg, Stack, Global, Unidentified, Yield
142+
// (these have no reference lifetime to extend).
143+
return Valid;
144+
}
145+
SmallVector<Operand *, 8> childUses;
146+
if (findTransitiveUsesForAddress(childAddress, &childUses)
147+
!= AddressUseKind::NonEscaping) {
148+
return Invalid; // pointer escape, so we don't know required lifetime
149+
}
150+
SILValue referenceRoot = addressOwnership.getOwnershipReferenceRoot();
151+
assert(referenceRoot && "expect to find a reference to Box/Class/Tail");
152+
153+
if (referenceRoot.getOwnershipKind() != OwnershipKind::Guaranteed) {
154+
// Note: Addresses are normally guarded by a borrow scope. But eventually,
155+
// an address base can be considered an implicit borrow. This current
156+
// handles project_box, which is not in a borrow scope (it is sadly modeled
157+
// as a PointerEscape). But we can treat project_box like an implicit borrow
158+
// in this context.
159+
return checkLifetimeExtension(referenceRoot, childUses);
160+
}
161+
BorrowedValue parentBorrow(referenceRoot);
162+
if (!parentBorrow)
163+
return Invalid; // unexpected borrow introducer
164+
165+
return checkBorrowExtension(parentBorrow, childUses);
166+
}
167+
168+
// Can the OSSA scope of \p borrow cover all \p newUses?
169+
GuaranteedOwnershipExtension::Status
170+
GuaranteedOwnershipExtension::checkBorrowExtension(
171+
BorrowedValue borrow, ArrayRef<Operand *> newUses) {
172+
173+
if (!borrow.isLocalScope())
174+
return Valid; // arguments have whole-function ownership
175+
176+
assert(guaranteedLiveness.empty());
177+
borrow.computeLiveness(guaranteedLiveness);
178+
179+
if (guaranteedLiveness.areUsesWithinBoundary(newUses, deBlocks))
180+
return Valid; // reuse the borrow scope as-is
181+
182+
beginBorrow = dyn_cast<BeginBorrowInst>(borrow.value);
183+
if (!beginBorrow)
184+
return Invalid; // cannot extend load_borrow without memory lifetime
185+
186+
// Extend liveness to the new uses before returning any status that leads to
187+
// transformation.
188+
for (Operand *use : newUses) {
189+
guaranteedLiveness.updateForUse(use->getUser(), /*lifetimeEnding*/ false);
190+
}
191+
// It is unusual to have a borrow scope that (a) dominates the new uses, (b)
192+
// does not already cover the new uses, but (c) already has a reborrow for
193+
// some other reason.
194+
if (borrow.hasReborrow())
195+
return Invalid; // Can only extend a local scope up to dominated uses
196+
197+
auto status = checkLifetimeExtension(beginBorrow->getOperand(), newUses);
198+
if (status == Valid) {
199+
// The owned lifetime is adequate, but the borrow scope must be extended.
200+
return ExtendBorrow;
201+
}
202+
return status;
203+
}
204+
205+
GuaranteedOwnershipExtension::Status
206+
GuaranteedOwnershipExtension::checkLifetimeExtension(
207+
SILValue ownedValue, ArrayRef<Operand *> newUses) {
208+
assert(ownedLifetime.empty());
209+
210+
auto ownershipKind = ownedValue.getOwnershipKind();
211+
if (ownershipKind == OwnershipKind::None)
212+
return Valid;
213+
214+
// If the ownedValue is not owned, give up for simplicity. We expect nested
215+
// borrows to be removed.
216+
if (ownershipKind != OwnershipKind::Owned)
217+
return Invalid;
218+
219+
ownedLifetime.initializeDefBlock(ownedValue->getParentBlock());
220+
for (Operand *use : ownedValue->getUses()) {
221+
auto *user = use->getUser();
222+
if (use->isConsuming()) {
223+
ownedLifetime.updateForUse(user, true);
224+
ownedConsumeBlocks.push_back(user->getParent());
225+
}
226+
}
227+
if (ownedLifetime.areUsesWithinBoundary(newUses, deBlocks))
228+
return Valid;
229+
230+
return ExtendLifetime; // Can't cover newUses without destroy sinking.
231+
}
232+
233+
void GuaranteedOwnershipExtension::transform(Status status) {
234+
switch (status) {
235+
case Invalid:
236+
case Valid:
237+
return;
238+
case ExtendBorrow: {
239+
PrunedLivenessBoundary guaranteedBoundary;
240+
guaranteedBoundary.compute(guaranteedLiveness, ownedConsumeBlocks);
241+
extendLocalBorrow(beginBorrow, guaranteedBoundary, deleter);
242+
break;
243+
}
244+
case ExtendLifetime: {
245+
ownedLifetime.extendAcrossLiveness(guaranteedLiveness);
246+
PrunedLivenessBoundary ownedBoundary;
247+
ownedBoundary.compute(ownedLifetime, ownedConsumeBlocks);
248+
extendOwnedLifetime(beginBorrow->getOperand(), ownedBoundary, deleter);
249+
break;
250+
}
251+
}
252+
}
253+
37254
//===----------------------------------------------------------------------===//
38255
// Utility Helper Functions
39256
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)