Skip to content

Commit b750c37

Browse files
authored
Merge pull request swiftlang#39357 from atrick/add-borrow-extender
Add a BorrowedLifetimeExtender utility.
2 parents d7281a1 + ffb7ecc commit b750c37

File tree

3 files changed

+238
-0
lines changed

3 files changed

+238
-0
lines changed

include/swift/SILOptimizer/Utils/OwnershipOptUtils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ struct OwnershipFixupContext {
5858
InstModCallbacks &callbacks;
5959
DeadEndBlocks &deBlocks;
6060

61+
62+
// FIXME: remove these two vectors once BorrowedLifetimeExtender is used
63+
// everywhere.
6164
SmallVector<Operand *, 8> transitiveBorrowedUses;
6265
SmallVector<PhiOperand, 8> recursiveReborrows;
6366

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,9 @@ void BorrowedValue::getLocalScopeEndingInstructions(
512512
llvm_unreachable("Covered switch isn't covered?!");
513513
}
514514

515+
// Note: BorrowedLifetimeExtender assumes no intermediate values between a
516+
// borrow introducer and its reborrow. The borrowed value must be an operand of
517+
// the reborrow.
515518
bool BorrowedValue::visitLocalScopeEndingUses(
516519
function_ref<bool(Operand *)> visitor) const {
517520
assert(isLocalScope() && "Should only call this given a local scope");

lib/SILOptimizer/Utils/OwnershipOptUtils.cpp

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,238 @@ insertOwnedBaseValueAlongBranchEdge(BranchInst *bi, SILValue innerCopy,
100100
return phiArg;
101101
}
102102

103+
//===----------------------------------------------------------------------===//
104+
// BorrowedLifetimeExtender
105+
//===----------------------------------------------------------------------===//
106+
107+
/// Model an extended borrow scope, including transitive reborrows. This applies
108+
/// to "local" borrow scopes (begin_borrow, load_borrow, & phi).
109+
///
110+
/// Allow extending the lifetime of an owned value that dominates this borrowed
111+
/// value across that extended borrow scope. This handles uses of reborrows that
112+
/// are not dominated by the owned value by generating phis and copying the
113+
/// borrowed values the reach this borrow scope from non-dominated paths.
114+
///
115+
/// This produces somewhat canonical owned phis, although that isn't a
116+
/// requirement for valid SIL. Given an owned value, a dominated borrowed value,
117+
/// and a reborrow:
118+
///
119+
/// %ownedValue = ...
120+
/// %borrowedValue = ...
121+
/// %reborrow = phi(%borrowedValue, %otherBorrowedValue)
122+
///
123+
/// %otherBorrowedValue will always be copied even if %ownedValue also dominates
124+
/// %otherBorrowedValue, as such:
125+
///
126+
/// %otherCopy = copy_value %borrowedValue
127+
/// %newPhi = phi(%ownedValue, %otherCopy)
128+
///
129+
/// The immediate effect is to produce an unnecesssary copy, but it avoids
130+
/// extending %ownedValue's liveness to new paths and hopefully simplifies
131+
/// downstream optimization and debugging. Unnecessary copies could be
132+
/// avoided with simple dominance check if it becomes desirable to do so.
133+
struct BorrowedLifetimeExtender {
134+
BorrowedValue borrowedValue;
135+
136+
// Owned value currently being extended over borrowedValue.
137+
SILValue currentOwnedValue;
138+
139+
InstModCallbacks &callbacks;
140+
141+
llvm::SmallVector<PhiValue, 4> reborrowedPhis;
142+
llvm::SmallDenseMap<PhiValue, PhiValue, 4> reborrowedToOwnedPhis;
143+
144+
/// Check that all reaching operands are handled. This can be removed once the
145+
/// utility and OSSA representation are stable.
146+
SWIFT_ASSERT_ONLY_DECL(llvm::SmallDenseSet<PhiOperand, 4> reborrowedOperands);
147+
148+
/// Initially map the reborrowed phi to an invalid value prior to creating the
149+
/// owned phi.
150+
void discoverReborrow(PhiValue reborrowedPhi) {
151+
if (reborrowedToOwnedPhis.try_emplace(reborrowedPhi, PhiValue()).second) {
152+
reborrowedPhis.push_back(reborrowedPhi);
153+
}
154+
}
155+
156+
/// Remap the reborrowed phi to an valid owned phi after creating it.
157+
void mapOwnedPhi(PhiValue reborrowedPhi, PhiValue ownedPhi) {
158+
reborrowedToOwnedPhis[reborrowedPhi] = ownedPhi;
159+
}
160+
161+
/// Get the owned value associated with this reborrowed operand, or return an
162+
/// invalid SILValue indicating that the borrowed lifetime does not reach this
163+
/// operand.
164+
SILValue getExtendedOwnedValue(PhiOperand reborrowedOper) {
165+
// If this operand reborrows the original borrow, then the currentOwned phi
166+
// reaches it directly.
167+
SILValue borrowSource = reborrowedOper.getSource();
168+
if (borrowSource == borrowedValue.value)
169+
return currentOwnedValue;
170+
171+
// Check if the borrowed operand's source is already mapped to an owned phi.
172+
auto reborrowedAndOwnedPhi = reborrowedToOwnedPhis.find(borrowSource);
173+
if (reborrowedAndOwnedPhi != reborrowedToOwnedPhis.end()) {
174+
// Return the already-mapped owned phi.
175+
assert(reborrowedOperands.erase(reborrowedOper));
176+
return reborrowedAndOwnedPhi->second;
177+
}
178+
// The owned value does not reach this reborrowed operand.
179+
assert(
180+
!reborrowedOperands.count(reborrowedOper)
181+
&& "reachable borrowed phi operand must be mapped to an owned value");
182+
return SILValue();
183+
}
184+
185+
public:
186+
/// Precondition: \p borrowedValue must introduce a local borrow scope
187+
/// (begin_borrow, load_borrow, & phi).
188+
BorrowedLifetimeExtender(BorrowedValue borrowedValue,
189+
InstModCallbacks &callbacks)
190+
: borrowedValue(borrowedValue), callbacks(callbacks) {
191+
assert(borrowedValue.isLocalScope() && "expect a valid borrowed value");
192+
}
193+
194+
/// Extend \p ownedValue over this extended borrow scope.
195+
///
196+
/// Precondition: \p ownedValue dominates this borrowed value.
197+
void extendOverBorrowScopeAndConsume(SILValue ownedValue);
198+
199+
protected:
200+
void analyzeExtendedScope();
201+
202+
SILValue createCopyAtEdge(PhiOperand reborrowOper);
203+
204+
void destroyAtScopeEnd(SILValue ownedValue, BorrowedValue pairedBorrow);
205+
};
206+
207+
// Gather all transitive phi-reborrows and check that all the borrowed uses can
208+
// be found with no escapes.
209+
//
210+
// Calls discoverReborrow to populate reborrowedPhis.
211+
void BorrowedLifetimeExtender::analyzeExtendedScope() {
212+
auto visitReborrow = [&](Operand *endScope) {
213+
if (auto borrowingOper = BorrowingOperand(endScope)) {
214+
assert(borrowingOper.isReborrow());
215+
216+
SWIFT_ASSERT_ONLY(reborrowedOperands.insert(endScope));
217+
218+
// TODO: if non-phi reborrows are added, handle multiple results.
219+
discoverReborrow(borrowingOper.getBorrowIntroducingUserResult().value);
220+
}
221+
return true;
222+
};
223+
224+
bool result = borrowedValue.visitLocalScopeEndingUses(visitReborrow);
225+
assert(result && "visitReborrow always succeeds, escapes are irrelevant");
226+
227+
// Note: Iterate in the same manner as findExtendedTransitiveGuaranteedUses(),
228+
// but using BorrowedLifetimeExtender's own reborrowedPhis.
229+
for (unsigned idx = 0; idx < reborrowedPhis.size(); ++idx) {
230+
auto borrowedValue = BorrowedValue(reborrowedPhis[idx]);
231+
result = borrowedValue.visitLocalScopeEndingUses(visitReborrow);
232+
assert(result && "visitReborrow always succeeds, escapes are irrelevant");
233+
}
234+
}
235+
236+
// Insert a copy on this edge. This might not be necessary if the owned
237+
// value dominates this path, but this avoids forcing the owned value to be
238+
// live across new paths.
239+
//
240+
// TODO: consider copying the base of the borrowed value instead of the
241+
// borrowed value directly. It's likely that the copy is used outside of the
242+
// borrow scope, in which case, canonicalizeOSSA will create a copy outside
243+
// the borrow scope anyway. However, we can't be sure that the base is the
244+
// same type.
245+
//
246+
// TODO: consider reusing copies that dominate multiple reborrowed
247+
// operands. Howeer, this requires copying in an earlier block and inserting
248+
// post-dominating destroys, which may be better handled in an ownership phi
249+
// canonicalization pass.
250+
SILValue BorrowedLifetimeExtender::createCopyAtEdge(PhiOperand reborrowOper) {
251+
auto *branch = reborrowOper.getBranch();
252+
auto loc = RegularLocation::getAutoGeneratedLocation(branch->getLoc());
253+
auto *copy = SILBuilderWithScope(branch).createCopyValue(
254+
loc, reborrowOper.getSource());
255+
callbacks.createdNewInst(copy);
256+
return copy;
257+
}
258+
259+
// Destroy \p ownedValue at \p pairedBorrow's scope-ending uses, excluding
260+
// reborrows.
261+
//
262+
// Precondition: ownedValue takes ownership of its value at the same point as
263+
// pairedBorrow. e.g. an owned and guaranteed pair of phis.
264+
void BorrowedLifetimeExtender::destroyAtScopeEnd(SILValue ownedValue,
265+
BorrowedValue pairedBorrow) {
266+
pairedBorrow.visitLocalScopeEndingUses([&](Operand *scopeEnd) {
267+
if (scopeEnd->getOperandOwnership() == OperandOwnership::Reborrow)
268+
return true;
269+
270+
auto *endInst = scopeEnd->getUser();
271+
assert(!isa<TermInst>(endInst) && "branch must be a reborrow");
272+
auto *destroyPt = &*std::next(endInst->getIterator());
273+
auto *destroy = SILBuilderWithScope(destroyPt).createDestroyValue(
274+
destroyPt->getLoc(), ownedValue);
275+
callbacks.createdNewInst(destroy);
276+
return true;
277+
});
278+
}
279+
280+
// Insert and map an owned phi for each reborrowed phi.
281+
//
282+
// For each reborrowed phi, insert a copy on each edge that does not originate
283+
// from the extended borrowedValue.
284+
//
285+
// TODO: If non-phi reborrows are added, they would also need to be
286+
// mapped to their owned counterpart. This means generating new owned
287+
// struct/destructure instructions.
288+
void BorrowedLifetimeExtender::
289+
extendOverBorrowScopeAndConsume(SILValue ownedValue) {
290+
currentOwnedValue = ownedValue;
291+
292+
// Populate the reborrowedPhis vector.
293+
analyzeExtendedScope();
294+
295+
InstructionDeleter deleter(callbacks);
296+
297+
// Generate and map the phis with undef operands first, in case of recursion.
298+
auto undef = SILUndef::get(ownedValue->getType(), *ownedValue->getFunction());
299+
for (PhiValue reborrowedPhi : reborrowedPhis) {
300+
auto *phiBlock = reborrowedPhi.phiBlock;
301+
auto *ownedPhi = phiBlock->createPhiArgument(ownedValue->getType(),
302+
OwnershipKind::Owned);
303+
for (auto *predBlock : phiBlock->getPredecessorBlocks()) {
304+
TermInst *ti = predBlock->getTerminator();
305+
addNewEdgeValueToBranch(ti, phiBlock, undef, deleter);
306+
}
307+
mapOwnedPhi(reborrowedPhi, PhiValue(ownedPhi));
308+
}
309+
// Generate copies and set the phi operands.
310+
for (PhiValue reborrowedPhi : reborrowedPhis) {
311+
PhiValue ownedPhi = reborrowedToOwnedPhis[reborrowedPhi];
312+
reborrowedPhi.getValue()->visitIncomingPhiOperands(
313+
// For each reborrowed operand, get the owned value for that edge,
314+
// and set the owned phi's operand.
315+
[&](Operand *reborrowedOper) {
316+
SILValue ownedVal = getExtendedOwnedValue(reborrowedOper);
317+
if (!ownedVal) {
318+
ownedVal = createCopyAtEdge(reborrowedOper);
319+
}
320+
BranchInst *branch = PhiOperand(reborrowedOper).getBranch();
321+
branch->getOperandRef(ownedPhi.argIndex).set(ownedVal);
322+
return true;
323+
});
324+
}
325+
assert(reborrowedOperands.empty() && "not all phi operands are handled");
326+
327+
// Create destroys at the last uses.
328+
destroyAtScopeEnd(ownedValue, borrowedValue);
329+
for (PhiValue reborrowedPhi : reborrowedPhis) {
330+
PhiValue ownedPhi = reborrowedToOwnedPhis[reborrowedPhi];
331+
destroyAtScopeEnd(ownedPhi, BorrowedValue(reborrowedPhi));
332+
}
333+
}
334+
103335
//===----------------------------------------------------------------------===//
104336
// Ownership RAUW Helper Functions
105337
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)