Skip to content

Commit caefb9a

Browse files
committed
Centralize and document low-level OSSA utilities
...for handling borrow scopes: - find[Extended]TransitiveGuaranteedUses - BorrowingOperand::visit[Extended]ScopeEndingUses - BorrowedValue BorrowingOperand::getBorrowIntroducingUserResult() And document the logic. Mostly NFC in this commit, but more RAUW cases should be correctly handled now. Particularly, ensure that we can cleanly handle all manner of reborrows. This is necessary to ensure both CanonicalizeOSSA and replace-all-uses higher-level utilities handle all cases. This generalizes some of the logic in CanonicalizeOSSA so it can be shared by other high-level OSSA utilities. These utilities extend the fundamental BorrowingOperand and BorrowedValue functionality that has been designed recently. It builds on and replaces a mix of piece-meal functionality that was needed during bootstrapping. These APIs are now consistent with the more recently designed code. It should be obvious what they mean and how to use them, should be very hard to use them incorrectly, and should be as efficient as possible, since they're fundamental to other higher-level utilities. Most importantly, there were several very subtle correctness issues that were not handled cleanly in a centralized way. There are now a mix of higher-level utilities trying to use this underlying functionality, but it was hard to tell if all those higher-level utilities were covering all the subtle cases: - checking for PointerEscapes everywhere we need to - the differences between uses of borrow introducers and other guaranteed values - handling the uses of nested borrow scopes - transitively handling reborrows - the difference between nested and top-level reborrows - forwarding destructures (which can cause exponential explosion) In short, I was fundamentally confused and getting things wrong before designing these utilities.
1 parent 6780f3d commit caefb9a

File tree

7 files changed

+394
-243
lines changed

7 files changed

+394
-243
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 115 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,66 @@ inline bool isForwardingConsume(SILValue value) {
7575
return canOpcodeForwardOwnedValues(value);
7676
}
7777

78+
/// Find all "use points" of a guaranteed value within its enclosing borrow
79+
/// scope (without looking through reborrows). To find the use points of the
80+
/// extended borrow scope, after looking through reborrows, use
81+
/// findExtendedTransitiveGuaranteedUses() instead.
82+
///
83+
/// Accumulate results in \p usePoints. This avoids the need for separate
84+
/// worklist and result vectors. Existing vector elements are ignored.
85+
///
86+
/// "Use points" are the relevant points for determining lifetime. They are
87+
/// determined differently depending on each of these two cases:
88+
///
89+
/// 1. If \p guaranteedValue introduces a borrow scope (begin_borrow,
90+
/// load_borrow, or phi), then its only use points are the scope-ending uses,
91+
/// and this function returns true. This is, in fact, equivalent to calling
92+
/// BorrowedValue::visitLocalScopeEndingUses(). Any scope-ending uses that are
93+
/// reborrows are recorded as use points without following the reborrowed
94+
/// uses. The \p visitReborrow callback can be used to transitively process
95+
/// reborrows to discover the extended lifetime. Reborrows may be recursive, so
96+
/// this will require checking membership in a working set. Nested borrow scope
97+
/// are irrelevant to the parent scope's lifetime. They are not considered use
98+
/// points, and reborrows within those nested scope are not visited by \p
99+
/// visitReborrow.
100+
///
101+
/// 2. If \p guaranteedValue does not introduce a borrow scope (it is not a
102+
/// valid BorrowedValue), then its uses are discovered transitively by looking
103+
/// through forwarding operations. If any use is a PointerEscape, then this
104+
/// returns false without adding more uses--the guaranteed values lifetime is
105+
/// indeterminite. If a use introduces a nested borrow scope, it creates use
106+
/// points where the "extended" borrow scope ends. An extended borrow
107+
/// scope is found by looking through any reborrows that end the nested
108+
/// scope. Other uses within nested borrow scopes are ignored.
109+
bool findTransitiveGuaranteedUses(SILValue guaranteedValue,
110+
SmallVectorImpl<Operand *> &usePoints,
111+
function_ref<void(Operand *)> visitReborrow);
112+
113+
/// Find all "use points" of guaranteed value across its extended borrow scope
114+
/// (looking through reborrows). The "use points" are the relevant points for
115+
/// determining lifetime.
116+
///
117+
/// Accumulate results in \p usePoints. This avoids the need for separate
118+
/// worklist and result vectors. Existing vector elements are ignored.
119+
///
120+
/// "Use points" are the relevant points for determining lifetime. They are
121+
/// determined differently depending on each of these two cases:
122+
///
123+
/// 1. If \p guaranteedValue introduces a borrow scope (begin_borrow,
124+
/// load_borrow, or phi), then its only use points are the extended scope-ending
125+
/// uses, and this function returns true. This is, in fact, equivalent to
126+
/// calling BorrowedValue::visitExtendedLocalScopeEndingUses().
127+
///
128+
/// 2. If \p guaranteedValue does not introduce a borrow scope (it is not a
129+
/// valid BorrowedValue), then its uses are discovered transitively by looking
130+
/// through forwarding operations. Only a BorrowedValue can have its lifetime
131+
/// extended by a reborrow; therefore, in this case, the algorithm is equivalent
132+
/// to findTransitiveGuaranteedUses(). See those comments for more detail.
133+
bool findExtendedTransitiveGuaranteedUses(
134+
SILValue guaranteedValue,
135+
SmallVectorImpl<Operand *> &usePoints);
136+
137+
/// An operand that forwards ownership to one or more results.
78138
class ForwardingOperand {
79139
Operand *use = nullptr;
80140

@@ -216,16 +276,21 @@ struct BorrowingOperand {
216276
/// over a region of code instead of just for a single instruction, visit
217277
/// those uses.
218278
///
219-
/// Returns true if all visitor invocations returns true. Exits early if a
220-
/// visitor returns false.
279+
/// Returns false and early exits if the visitor \p func returns false.
280+
///
281+
/// For an instantaneous borrow, such as apply, this visits no uses. For
282+
/// begin_apply it visits the end_apply uses. For borrow introducers, it
283+
/// visits the end of the introduced borrow scope.
284+
bool visitScopeEndingUses(function_ref<bool(Operand *)> func) const;
285+
286+
/// Visit the scope ending operands of the extended scope, after transitively
287+
/// searching through reborrows. These uses might not be dominated by this
288+
/// BorrowingOperand.
221289
///
222-
/// Example: An apply performs an instantaneous recursive borrow of a
223-
/// guaranteed value but a begin_apply borrows the value over the entire
224-
/// region of code corresponding to the coroutine.
290+
/// Returns false and early exits if the visitor \p func returns false.
225291
///
226-
/// NOTE: Return false from func to stop iterating. Returns false if the
227-
/// closure requested to stop early.
228-
bool visitLocalEndScopeUses(function_ref<bool(Operand *)> func) const;
292+
/// Note: this does not visit the intermediate reborrows.
293+
bool visitExtendedScopeEndingUses(function_ref<bool(Operand *)> func) const;
229294

230295
/// Returns true if this borrow scope operand consumes guaranteed
231296
/// values and produces a new scope afterwards.
@@ -247,10 +312,12 @@ struct BorrowingOperand {
247312
llvm_unreachable("Covered switch isn't covered?!");
248313
}
249314

250-
/// Is the result of this instruction also a borrow introducer?
315+
/// Return true if the user instruction introduces a borrow scope? This is
316+
/// true for both reborrows and nested borrows.
251317
///
252-
/// TODO: This needs a better name.
253-
bool areAnyUserResultsBorrowIntroducers() const {
318+
/// If true, the visitBorrowIntroducingUserResults() can be called to acquire
319+
/// each BorrowedValue that introduces a new borrow scopes.
320+
bool hasBorrowIntroducingUser() const {
254321
// TODO: Can we derive this by running a borrow introducer check ourselves?
255322
switch (kind) {
256323
case BorrowingOperandKind::Invalid:
@@ -267,24 +334,19 @@ struct BorrowingOperand {
267334
llvm_unreachable("Covered switch isn't covered?!");
268335
}
269336

270-
/// Visit all of the results of the operand's user instruction that are
271-
/// consuming uses.
272-
void visitUserResultConsumingUses(function_ref<void(Operand *)> visitor) const;
273-
274337
/// Visit all of the "results" of the user of this operand that are borrow
275338
/// scope introducers for the specific scope that this borrow scope operand
276339
/// summarizes.
277-
void
278-
visitBorrowIntroducingUserResults(function_ref<void(BorrowedValue)> visitor) const;
279-
280-
/// Passes to visitor all of the consuming uses of this use's using
281-
/// instruction.
282340
///
283-
/// This enables one to walk the def-use chain of guaranteed phis for a single
284-
/// guaranteed scope by using a worklist and checking if any of the operands
285-
/// are BorrowScopeOperands.
286-
void visitConsumingUsesOfBorrowIntroducingUserResults(
287-
function_ref<void(Operand *)> visitor) const;
341+
/// Precondition: hasBorrowIntroducingUser() is true
342+
///
343+
/// Returns false and early exits if \p visitor returns false.
344+
bool visitBorrowIntroducingUserResults(
345+
function_ref<bool(BorrowedValue)> visitor) const;
346+
347+
/// If this operand's user has a single borrowed value result return a
348+
/// valid BorrowedValue instance.
349+
BorrowedValue getBorrowIntroducingUserResult();
288350

289351
/// Compute the implicit uses that this borrowing operand "injects" into the
290352
/// set of its operands uses.
@@ -398,22 +460,32 @@ struct InteriorPointerOperand;
398460
/// guaranteed results are borrow introducers. In practice this means that
399461
/// borrow introducers can not have guaranteed results that are not creating a
400462
/// new borrow scope. No such instructions exist today.
463+
///
464+
/// This provides utilities for visiting the end of the borrow scope introduced
465+
/// by this value. The scope ending uses are always dominated by this value and
466+
/// jointly post-dominate this value (see visitLocalScopeEndingUses()). The
467+
/// extended scope, including reborrows has end points that are not dominated by
468+
/// this value but still jointly post-dominate (see
469+
/// visitExtendedLocalScopeEndingUses()).
401470
struct BorrowedValue {
402471
SILValue value;
403-
BorrowedValueKind kind;
472+
BorrowedValueKind kind = BorrowedValueKind::Invalid;
404473

405-
BorrowedValue() : value(), kind(BorrowedValueKind::Invalid) {}
474+
BorrowedValue() = default;
475+
476+
/// If \p value is a borrow introducer construct a valid BorrowedValue.
477+
BorrowedValue(SILValue value) {
478+
kind = BorrowedValueKind::get(value);
479+
if (!kind)
480+
return;
481+
this->value = value;
482+
}
406483

407484
/// If value is a borrow introducer return it after doing some checks.
408485
///
409486
/// This is the only way to construct a BorrowScopeIntroducingValue. We make
410487
/// the primary constructor private for this reason.
411-
static BorrowedValue get(SILValue value) {
412-
auto kind = BorrowedValueKind::get(value);
413-
if (!kind)
414-
return {nullptr, kind};
415-
return {value, kind};
416-
}
488+
static BorrowedValue get(SILValue value) { return BorrowedValue(value); }
417489

418490
operator bool() const { return kind != BorrowedValueKind::Invalid && value; }
419491

@@ -430,14 +502,16 @@ struct BorrowedValue {
430502
/// instructions and pass them individually to visitor. Asserts if this is
431503
/// called with a scope that is not local.
432504
///
505+
/// Returns false and early exist if \p visitor returns false.
506+
///
433507
/// The intention is that this method can be used instead of
434508
/// BorrowScopeIntroducingValue::getLocalScopeEndingUses() to avoid
435509
/// introducing an intermediate array when one needs to transform the
436510
/// instructions before storing them.
437511
///
438512
/// NOTE: To determine if a scope is a local scope, call
439513
/// BorrowScopeIntoducingValue::isLocalScope().
440-
void visitLocalScopeEndingUses(function_ref<void(Operand *)> visitor) const;
514+
bool visitLocalScopeEndingUses(function_ref<bool(Operand *)> visitor) const;
441515

442516
bool isLocalScope() const { return kind.isLocalScope(); }
443517

@@ -451,9 +525,10 @@ struct BorrowedValue {
451525
DeadEndBlocks &deadEndBlocks) const;
452526

453527
/// Given a local borrow scope introducer, visit all non-forwarding consuming
454-
/// users. This means that this looks through guaranteed block arguments.
455-
bool visitLocalScopeTransitiveEndingUses(
456-
function_ref<void(Operand *)> visitor) const;
528+
/// users. This means that this looks through guaranteed block arguments. \p
529+
/// visitor is *not* called on Reborrows, only on final scope ending uses.
530+
bool visitExtendedLocalScopeEndingUses(
531+
function_ref<bool(Operand *)> visitor) const;
457532

458533
void print(llvm::raw_ostream &os) const;
459534
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
@@ -632,14 +707,15 @@ struct InteriorPointerOperand {
632707
/// Return the end scope of all borrow introducers of the parent value of this
633708
/// projection. Returns true if we were able to find all borrow introducing
634709
/// values.
635-
bool visitBaseValueScopeEndingUses(function_ref<void(Operand *)> func) const {
710+
bool visitBaseValueScopeEndingUses(function_ref<bool(Operand *)> func) const {
636711
SmallVector<BorrowedValue, 4> introducers;
637712
if (!getAllBorrowIntroducingValues(operand->get(), introducers))
638713
return false;
639714
for (const auto &introducer : introducers) {
640715
if (!introducer.isLocalScope())
641716
continue;
642-
introducer.visitLocalScopeEndingUses(func);
717+
if (!introducer.visitLocalScopeEndingUses(func))
718+
return false;
643719
}
644720
return true;
645721
}

0 commit comments

Comments
 (0)