Skip to content

Commit 63161e6

Browse files
Merge pull request #41228 from nate-chandler/lexical_lifetimes/owned_function_args_are_lexical
[SIL] Make owned function arguments lexical.
2 parents 9b661aa + 2da6ac1 commit 63161e6

15 files changed

+477
-248
lines changed

docs/SIL.rst

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,6 +2364,69 @@ The current list of interior pointer SIL instructions are:
23642364
(*) We still need to finish adding support for project_box, but all other
23652365
interior pointers are guarded already.
23662366

2367+
Variable Lifetimes
2368+
~~~~~~~~~~~~~~~~~~
2369+
2370+
In order for programmer intended lifetimes to be maintained under optimization,
2371+
the lifetimes of SIL values which correspond to named source-level values can
2372+
only be modified in limited ways. Generally, the behavior is that the lifetime
2373+
of a named source-level value cannot _observably_ end before the end of the
2374+
lexical scope in which that value is defined. Specifically, code motion may
2375+
not move the ends of these lifetimes across a **deinit barrier**.
2376+
2377+
A few sorts of SIL value have lifetimes that are constrained that way:
2378+
2379+
1: `begin_borrow [lexical]`
2380+
2: `move_value [lexical]`
2381+
3: @owned function arguments
2382+
4: `alloc_stack [lexical]`
2383+
2384+
That these three have constrained lifetimes is encoded in ValueBase::isLexical,
2385+
which should be checked before changing the lifetime of a value.
2386+
2387+
The reason that only @owned function arguments are constrained is that a
2388+
@guaranteed function argument is guaranteed by the function's caller to live for
2389+
the full duration of the function already. Optimization of the function alone
2390+
can't shorten it. When such a function is inlined into its caller, though, a
2391+
lexical borrow scope is added for each of its @guaranteed arguments, ensuring
2392+
that the lifetime of the corresponding source-level value is not shortened in a
2393+
way that doesn't respect deinit barriers.
2394+
2395+
Unlike the other sorts, `alloc_stack [lexical]` isn't a SILValue. Instead, it
2396+
constrains the lifetime of an addressable variable. Since the constraint is
2397+
applied to the in-memory representation, no additional lexical SILValue is
2398+
required.
2399+
2400+
Deinit Barriers
2401+
```````````````
2402+
2403+
Deinit barriers (see swift::isDeinitBarrier) are instructions which would be
2404+
affected by the side effects of deinitializers. To maintain the order of
2405+
effects that is visible to the programmer, destroys of lexical values cannot be
2406+
reordered with respect to them. There are three kinds:
2407+
2408+
1. synchronization points (locks, memory barriers, syscalls, etc.)
2409+
2. loads of weak or unowned values
2410+
3. accesses of pointers
2411+
2412+
Examples:
2413+
2414+
1. Given an instance of a class which owns a file handle and closes the file
2415+
handle on deinit, writing to the file handle and then deallocating the
2416+
instance would result in changes being written. If the destroy of the
2417+
instance were hoisted above the call to write to the file handle, an error
2418+
would be raised instead.
2419+
2. Given an instance `c` of a class `C` which weakly references an instance `d`
2420+
of a second class `D`, if `d` is referenced via a local variable `v`, then
2421+
loading that weak reference from `c` within the variable scope should return
2422+
a non-nil reference to `d`. Hoisting the destroy of `v` above the weak load
2423+
from `c`, however, would result in the destruction of `d` before that load
2424+
and a nil weak reference to `D`.
2425+
3. Given an instance of a class which owns a buffer and deallocates it on
2426+
deinitialization, accessing the pointer and then deallocating the instance
2427+
is defined behavior. Hoisting the destroy of the instance above the access
2428+
to the memory would result in accessing a freed pointer.
2429+
23672430
Memory Lifetime
23682431
~~~~~~~~~~~~~~~
23692432

include/swift/SIL/MemAccessUtils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ inline bool accessKindMayConflict(SILAccessKind a, SILAccessKind b) {
234234

235235
/// Return true if \p instruction is a deinitialization barrier.
236236
///
237-
/// Deinitialization barriers constrain variable lifetimes. Lexical end_borrow
238-
/// and destroy_addr cannot be hoisted above them.
237+
/// Deinitialization barriers constrain variable lifetimes. Lexical end_borrow,
238+
/// destroy_value, and destroy_addr cannot be hoisted above them.
239239
bool isDeinitBarrier(SILInstruction *instruction);
240240

241241
} // end namespace swift

include/swift/SIL/OwnershipUtils.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ bool findExtendedTransitiveGuaranteedUses(
169169
SILValue guaranteedValue,
170170
SmallVectorImpl<Operand *> &usePoints);
171171

172+
/// Find non-transitive uses of a simple (i.e. without looking through
173+
/// reborrows) value.
174+
///
175+
/// The scope-ending use of borrows of the value are included. If a borrow of
176+
/// the value is reborrowed, returns false.
177+
bool findUsesOfSimpleValue(SILValue value,
178+
SmallVectorImpl<Operand *> *usePoints = nullptr);
179+
172180
/// An operand that forwards ownership to one or more results.
173181
class ForwardingOperand {
174182
Operand *use = nullptr;

include/swift/SILOptimizer/Utils/OwnershipOptUtils.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ class OwnershipRAUWHelper {
212212
/// their value ownership. This ignores any current uses of \p oldValue. To
213213
/// determine whether \p oldValue can be replaced as-is with it's existing
214214
/// uses, create an instance of OwnershipRAUWHelper and check its validity.
215-
static bool hasValidRAUWOwnership(SILValue oldValue, SILValue newValue);
215+
static bool hasValidRAUWOwnership(SILValue oldValue, SILValue newValue,
216+
ArrayRef<Operand *> oldUses);
216217

217218
private:
218219
OwnershipFixupContext *ctx;
@@ -283,6 +284,10 @@ class OwnershipRAUWHelper {
283284
SILValue getReplacementAddress();
284285
};
285286

287+
/// Whether the provided uses lie within the current liveness of the
288+
/// specified lexical value.
289+
bool areUsesWithinLexicalValueLifetime(SILValue, ArrayRef<Operand *>);
290+
286291
/// A utility composed ontop of OwnershipFixupContext that knows how to replace
287292
/// a single use of a value with another value with a different ownership. We
288293
/// allow for the values to have different types.

lib/SIL/IR/SILValue.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,8 @@ ValueBase::getDefiningInstructionResult() {
9999
}
100100

101101
bool ValueBase::isLexical() const {
102-
// TODO: Eventually, rather than SILGen'ing a borrow scope for owned
103-
// arguments, we will just have this check here.
104-
// if (auto *argument = dyn_cast<SILArgument>(this))
105-
// return argument->getOwnershipKind() == OwnershipKind::Owned;
102+
if (auto *argument = dyn_cast<SILFunctionArgument>(this))
103+
return argument->getOwnershipKind() == OwnershipKind::Owned;
106104
if (auto *bbi = dyn_cast<BeginBorrowInst>(this))
107105
return bbi->isLexical();
108106
if (auto *mvi = dyn_cast<MoveValueInst>(this))

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,25 @@ bool swift::findExtendedUsesOfSimpleBorrowedValue(
305305
return true;
306306
}
307307

308+
bool swift::findUsesOfSimpleValue(SILValue value,
309+
SmallVectorImpl<Operand *> *usePoints) {
310+
for (auto *use : value->getUses()) {
311+
if (use->getOperandOwnership() == OperandOwnership::Borrow) {
312+
if (!BorrowingOperand(use).visitScopeEndingUses([&](Operand *end) {
313+
if (end->getOperandOwnership() == OperandOwnership::Reborrow) {
314+
return false;
315+
}
316+
usePoints->push_back(use);
317+
return true;
318+
})) {
319+
return false;
320+
}
321+
}
322+
usePoints->push_back(use);
323+
}
324+
return true;
325+
}
326+
308327
// Find all use points of \p guaranteedValue within its borrow scope. All use
309328
// points will be dominated by \p guaranteedValue.
310329
//

lib/SILOptimizer/Utils/LexicalDestroyFolding.cpp

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ struct BorroweeUsage final {
290290
/// %lifetime. In detail, PrunedLiveness::isWithinBoundary relies on
291291
/// clients to know that instructions are after the start of liveness. We
292292
/// determine this via the dominance tree.
293-
bool findBorroweeUsage(Context &, BorroweeUsage &);
293+
bool findBorroweeUsage(Context const &, BorroweeUsage &);
294294

295295
/// Sift scope ends of %lifetime for those that CAN be folded.
296296
class FilterCandidates final {
@@ -308,9 +308,6 @@ class FilterCandidates final {
308308

309309
/// Determines whether each candidate is viable for folding.
310310
///
311-
/// Requiring findUsers to have been called, this uses the more expensive
312-
/// isViableMatch.
313-
///
314311
/// returns true if any candidates were viable
315312
/// false otherwise
316313
bool run(Candidates &candidates);
@@ -334,8 +331,7 @@ class FilterCandidates final {
334331
///
335332
/// If there are, we can't fold the apply. Specifically, we can't introduce
336333
/// a move_value [lexical] %borrowee because that value still needs to be used
337-
/// in those locations. While updateSSA would happily rewrite those uses to
338-
/// be uses of the move, that is not a semantically valid transformation.
334+
/// in those locations.
339335
///
340336
/// For example, given the following SIL
341337
/// %borrowee : @owned
@@ -410,11 +406,7 @@ class Rewriter final {
410406
/// possible)
411407
/// (2) hoist the end_borrow
412408
/// (3) delete the destroy_value
413-
void fold(Match, SmallVectorImpl<int> const &rewritableArgumentIndices);
414-
415-
// Update SSA to reflect that fact that %move and %borrowee are two
416-
// defs for the "same value".
417-
void updateSSA();
409+
void fold(Match, ArrayRef<int> rewritableArgumentIndices);
418410

419411
// The move_value [lexical] instruction that was added during the run.
420412
//
@@ -470,25 +462,31 @@ bool run(Context &context) {
470462
void Rewriter::run() {
471463
bool foldedAny = false;
472464
(void)foldedAny;
473-
for (unsigned index = 0, count = candidates.vector.size(); index < count;
474-
++index) {
465+
auto size = candidates.vector.size();
466+
for (unsigned index = 0; index < size; ++index) {
475467
auto candidate = candidates.vector[index];
476-
if (!candidate.viable)
477-
continue;
478468
createMove();
469+
if (!candidate.viable) {
470+
// Nonviable candidates still end with the pattern
471+
//
472+
// end_borrow %lifetime
473+
// ...
474+
// destroy_value %borrowee
475+
//
476+
// Now that the new move_value [lexical] dominates all candidates, the
477+
// every candidate's destroy_value %borrowee is dominated by it, so every
478+
// one is dominated by another consuming use which is illegal. Rewrite
479+
// each such destroy_value to be a destroy_value of the move.
480+
candidate.match.dvi->setOperand(mvi);
481+
continue;
482+
}
479483

480484
fold(candidate.match, candidate.argumentIndices);
481485
#ifndef NDEBUG
482486
foldedAny = true;
483487
#endif
484488
}
485489
assert(foldedAny && "rewriting without anything to rewrite!?");
486-
487-
// There are now "two defs for %borrowee":
488-
// - %borrowee
489-
// - %move
490-
// We need to update SSA.
491-
updateSSA();
492490
}
493491

494492
bool Rewriter::createMove() {
@@ -505,8 +503,7 @@ bool Rewriter::createMove() {
505503
return true;
506504
}
507505

508-
void Rewriter::fold(Match candidate,
509-
SmallVectorImpl<int> const &rewritableArgumentIndices) {
506+
void Rewriter::fold(Match candidate, ArrayRef<int> rewritableArgumentIndices) {
510507
// First, rewrite the apply in terms of the move_value.
511508
unsigned argumentNumber = 0;
512509
for (auto index : rewritableArgumentIndices) {
@@ -551,26 +548,6 @@ void Rewriter::fold(Match candidate,
551548
context.deleter.forceDelete(candidate.dvi);
552549
}
553550

554-
void Rewriter::updateSSA() {
555-
SILSSAUpdater updater;
556-
updater.initialize(context.borrowee->getType(),
557-
context.borrowee.getOwnershipKind());
558-
updater.addAvailableValue(context.borrowee->getParentBlock(),
559-
context.borrowee);
560-
updater.addAvailableValue(mvi->getParentBlock(), mvi);
561-
562-
SmallVector<Operand *, 16> uses;
563-
for (auto use : context.borrowee->getUses()) {
564-
if (use->getUser() == mvi)
565-
continue;
566-
uses.push_back(use);
567-
}
568-
569-
for (auto use : uses) {
570-
updater.rewriteUse(*use);
571-
}
572-
}
573-
574551
//===----------------------------------------------------------------------===//
575552
// MARK: Lookups
576553
//===----------------------------------------------------------------------===//
@@ -638,8 +615,10 @@ bool FilterCandidates::run(Candidates &candidates) {
638615
return anyViable;
639616
}
640617

641-
bool findBorroweeUsage(Context &context, BorroweeUsage &usage) {
618+
bool findBorroweeUsage(Context const &context, BorroweeUsage &usage) {
642619
auto recordUse = [&](Operand *use) {
620+
// Ignore uses that aren't dominated by the introducer. PrunedLiveness
621+
// relies on us doing this check.
643622
if (!context.dominanceTree.dominates(context.introducer, use->getUser()))
644623
return;
645624
usage.uses.push_back(use);

0 commit comments

Comments
 (0)