Skip to content

Commit 0183c0d

Browse files
authored
Merge pull request swiftlang#35671 from atrick/ossa-borrow-utils
Centralize and document low-level OSSA utilities
2 parents d1c7a00 + 2095c0c commit 0183c0d

File tree

7 files changed

+398
-261
lines changed

7 files changed

+398
-261
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 111 additions & 48 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,21 +460,24 @@ 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;
406475

407-
/// If value is a borrow introducer return it after doing some checks.
408-
///
409-
/// This is the only way to construct a BorrowScopeIntroducingValue. We make
410-
/// 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};
476+
/// If value is a borrow introducer, create a valid BorrowedValue.
477+
explicit BorrowedValue(SILValue value) {
478+
kind = BorrowedValueKind::get(value);
479+
if (kind)
480+
this->value = value;
416481
}
417482

418483
operator bool() const { return kind != BorrowedValueKind::Invalid && value; }
@@ -430,14 +495,16 @@ struct BorrowedValue {
430495
/// instructions and pass them individually to visitor. Asserts if this is
431496
/// called with a scope that is not local.
432497
///
498+
/// Returns false and early exist if \p visitor returns false.
499+
///
433500
/// The intention is that this method can be used instead of
434501
/// BorrowScopeIntroducingValue::getLocalScopeEndingUses() to avoid
435502
/// introducing an intermediate array when one needs to transform the
436503
/// instructions before storing them.
437504
///
438505
/// NOTE: To determine if a scope is a local scope, call
439506
/// BorrowScopeIntoducingValue::isLocalScope().
440-
void visitLocalScopeEndingUses(function_ref<void(Operand *)> visitor) const;
507+
bool visitLocalScopeEndingUses(function_ref<bool(Operand *)> visitor) const;
441508

442509
bool isLocalScope() const { return kind.isLocalScope(); }
443510

@@ -451,9 +518,10 @@ struct BorrowedValue {
451518
DeadEndBlocks &deadEndBlocks) const;
452519

453520
/// 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;
521+
/// users. This means that this looks through guaranteed block arguments. \p
522+
/// visitor is *not* called on Reborrows, only on final scope ending uses.
523+
bool visitExtendedLocalScopeEndingUses(
524+
function_ref<bool(Operand *)> visitor) const;
457525

458526
void print(llvm::raw_ostream &os) const;
459527
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
@@ -492,12 +560,6 @@ struct BorrowedValue {
492560
SILValue operator->() const { return value; }
493561
SILValue operator*() { return value; }
494562
SILValue operator*() const { return value; }
495-
496-
private:
497-
/// Internal constructor for failable static constructor. Please do not expand
498-
/// its usage since it assumes the code passed in is well formed.
499-
BorrowedValue(SILValue value, BorrowedValueKind kind)
500-
: value(value), kind(kind) {}
501563
};
502564

503565
llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
@@ -632,14 +694,15 @@ struct InteriorPointerOperand {
632694
/// Return the end scope of all borrow introducers of the parent value of this
633695
/// projection. Returns true if we were able to find all borrow introducing
634696
/// values.
635-
bool visitBaseValueScopeEndingUses(function_ref<void(Operand *)> func) const {
697+
bool visitBaseValueScopeEndingUses(function_ref<bool(Operand *)> func) const {
636698
SmallVector<BorrowedValue, 4> introducers;
637699
if (!getAllBorrowIntroducingValues(operand->get(), introducers))
638700
return false;
639701
for (const auto &introducer : introducers) {
640702
if (!introducer.isLocalScope())
641703
continue;
642-
introducer.visitLocalScopeEndingUses(func);
704+
if (!introducer.visitLocalScopeEndingUses(func))
705+
return false;
643706
}
644707
return true;
645708
}

0 commit comments

Comments
 (0)