Skip to content

Commit 2fc7659

Browse files
Merge pull request #60670 from nate-chandler/lexical_lifetimes/owned_arguments
[SIL] Maintain owned argument lifetimes at inlining.
2 parents d896877 + 7789b72 commit 2fc7659

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+469
-243
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/WalkUtils.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ extension ValueDefUseWalker {
341341
return unmatchedPath(value: operand, path: path)
342342
}
343343
case is InitExistentialRefInst, is OpenExistentialRefInst,
344-
is BeginBorrowInst, is CopyValueInst,
344+
is BeginBorrowInst, is CopyValueInst, is MoveValueInst,
345345
is UpcastInst, is UncheckedRefCastInst, is EndCOWMutationInst,
346346
is RefToBridgeObjectInst, is BridgeObjectToRefInst, is MarkMustCheckInst:
347347
return walkDownUses(ofValue: (instruction as! SingleValueInstruction), path: path)
@@ -618,7 +618,7 @@ extension ValueUseDefWalker {
618618
return rootDef(value: mvr, path: path)
619619
}
620620
case is InitExistentialRefInst, is OpenExistentialRefInst,
621-
is BeginBorrowInst, is CopyValueInst,
621+
is BeginBorrowInst, is CopyValueInst, is MoveValueInst,
622622
is UpcastInst, is UncheckedRefCastInst, is EndCOWMutationInst,
623623
is RefToBridgeObjectInst, is BridgeObjectToRefInst, is MarkMustCheckInst:
624624
return walkUp(value: (def as! Instruction).operands[0].value, path: path)

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,8 @@ final public class ProjectBoxInst : SingleValueInstruction, UnaryInstruction {
609609

610610
final public class CopyValueInst : SingleValueInstruction, UnaryInstruction {}
611611

612+
final public class MoveValueInst : SingleValueInstruction, UnaryInstruction {}
613+
612614
final public class StrongCopyUnownedValueInst : SingleValueInstruction, UnaryInstruction {}
613615

614616
final public class StrongCopyUnmanagedValueInst : SingleValueInstruction, UnaryInstruction {}

SwiftCompilerSources/Sources/SIL/Registration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public func registerSILClasses() {
123123
register(BeginBorrowInst.self)
124124
register(ProjectBoxInst.self)
125125
register(CopyValueInst.self)
126+
register(MoveValueInst.self)
126127
register(EndCOWMutationInst.self)
127128
register(ClassifyBridgeObjectInst.self)
128129
register(PartialApplyInst.self)

docs/SIL.rst

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,27 +2414,55 @@ Variable Lifetimes
24142414
In order for programmer intended lifetimes to be maintained under optimization,
24152415
the lifetimes of SIL values which correspond to named source-level values can
24162416
only be modified in limited ways. Generally, the behavior is that the lifetime
2417-
of a named source-level value cannot _observably_ end before the end of the
2418-
lexical scope in which that value is defined. Specifically, code motion may
2419-
not move the ends of these lifetimes across a **deinit barrier**.
2420-
2421-
A few sorts of SIL value have lifetimes that are constrained that way:
2417+
of a named source-level value is anchored to the variable's lexical scope and
2418+
confined by **deinit barriers**. Specifically, code motion may not move the
2419+
ends of these lifetimes across a deinit barrier.
2420+
2421+
Source level variables (lets, vars, ...) and function arguments will result in
2422+
SIL-level lexical lifetimes if either of the two sets of circumstances apply:
2423+
(1) Inferred lexicality.
2424+
- the type is non-trivial
2425+
- the type is not eager-move
2426+
- the variable or argument is not annotated to be eager-move
2427+
OR
2428+
(2) Explicit lexicality.
2429+
- the type, variable, or argument is annotated `@_lexical`
2430+
2431+
A type is eager-move by satisfying one of two conditions:
2432+
(1) Inferred: An aggregate is inferred to be eager-move if all of its fields are
2433+
eager-move.
2434+
(2) Annotated: Any type can be eager-move if it is annotated with an attribute
2435+
that explicitly specifies it to be: `@_eagerMove`, `@_noImplicitCopy`.
2436+
2437+
A variable or argument is eager-move by satisfying one of two conditions:
2438+
(1) Inferred: Its type is eager-move.
2439+
(2) Annotated: The variable or argument is annotated with an attribute that
2440+
specifies it to be: `@_eagerMove`, `@_noImplicitCopy`.
2441+
2442+
These source-level rules result in a few sorts of SIL value whose destroys must
2443+
not be moved across deinit barriers:
24222444

24232445
1: `begin_borrow [lexical]`
24242446
2: `move_value [lexical]`
2425-
3: @owned function arguments
2447+
3: function arguments
24262448
4: `alloc_stack [lexical]`
24272449

2428-
That these three have constrained lifetimes is encoded in ValueBase::isLexical,
2429-
which should be checked before changing the lifetime of a value.
2450+
To translate from the source-level representation of lexicality to the
2451+
SIL-level representation, for source-level variables (vars, lets, ...) SILGen
2452+
generates `begin_borrow [lexical]`, `move_value [lexical]`, `alloc_stack
2453+
[lexical]` . For function arguments, there is no work to do:
2454+
a `SILFunctionArgument` itself can be lexical
2455+
(`SILFunctionArgument::isLexical`).
2456+
2457+
That the first three have constrained lifetimes is encoded in
2458+
ValueBase::isLexical, which should be checked before changing the lifetime of a
2459+
value.
24302460

2431-
The reason that only @owned function arguments are constrained is that a
2432-
@guaranteed function argument is guaranteed by the function's caller to live for
2433-
the full duration of the function already. Optimization of the function alone
2434-
can't shorten it. When such a function is inlined into its caller, though, a
2435-
lexical borrow scope is added for each of its @guaranteed arguments, ensuring
2436-
that the lifetime of the corresponding source-level value is not shortened in a
2437-
way that doesn't respect deinit barriers.
2461+
When a function is inlined into its caller, a lexical borrow scope is added for
2462+
each of its @guaranteed arguments, and a lexical move is added for each of its
2463+
@owned arguments, (unless the values being passed are already lexical
2464+
themselves) ensuring that the lifetimes of the corresponding source-level
2465+
values are not shortened in a way that doesn't respect deinit barriers.
24382466

24392467
Unlike the other sorts, `alloc_stack [lexical]` isn't a SILValue. Instead, it
24402468
constrains the lifetime of an addressable variable. Since the constraint is

include/swift/SIL/InstructionUtils.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ SILValue stripBorrow(SILValue V);
8888
//===----------------------------------------------------------------------===//
8989

9090
/// Return a non-null SingleValueInstruction if the given instruction merely
91-
/// copies the value of its first operand, possibly changing its type or
92-
/// ownership state, but otherwise having no effect.
91+
/// copies or moves the value of its first operand, possibly changing its type
92+
/// or ownership state, but otherwise having no effect.
9393
///
9494
/// The returned instruction may have additional "incidental" operands;
9595
/// mark_dependence for example.

include/swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109

110110
namespace swift {
111111

112-
extern llvm::Statistic NumCopiesEliminated;
112+
extern llvm::Statistic NumCopiesAndMovesEliminated;
113113
extern llvm::Statistic NumCopiesGenerated;
114114

115115
/// Insert a copy on this operand. Trace and update stats.

lib/SIL/IR/SILValue.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,8 @@ ValueBase::getDefiningInstructionResult() {
116116
}
117117

118118
bool ValueBase::isLexical() const {
119-
if (auto *argument = dyn_cast<SILFunctionArgument>(this)) {
120-
// TODO: Recognize guaranteed arguments as lexical too.
121-
return argument->getOwnershipKind() == OwnershipKind::Owned &&
122-
argument->getLifetime().isLexical();
123-
}
119+
if (auto *argument = dyn_cast<SILFunctionArgument>(this))
120+
return argument->getLifetime().isLexical();
124121
if (auto *bbi = dyn_cast<BeginBorrowInst>(this))
125122
return bbi->isLexical();
126123
if (auto *mvi = dyn_cast<MoveValueInst>(this))

lib/SIL/Utils/InstructionUtils.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ SILValue swift::lookThroughOwnershipInsts(SILValue v) {
3131
switch (v->getKind()) {
3232
default:
3333
return v;
34+
case ValueKind::MoveValueInst:
3435
case ValueKind::CopyValueInst:
3536
case ValueKind::BeginBorrowInst:
3637
v = cast<SingleValueInstruction>(v)->getOperand(0);
@@ -255,6 +256,7 @@ SingleValueInstruction *swift::getSingleValueCopyOrCast(SILInstruction *I) {
255256
case SILInstructionKind::BeginBorrowInst:
256257
case SILInstructionKind::BeginAccessInst:
257258
case SILInstructionKind::MarkDependenceInst:
259+
case SILInstructionKind::MoveValueInst:
258260
return cast<SingleValueInstruction>(I);
259261
}
260262
}

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,16 +2414,25 @@ void swift::visitTransitiveEndBorrows(
24142414
///
24152415
/// A begin_borrow [lexical] is nested if the borrowed value's lifetime is
24162416
/// guaranteed by another lexical scope. That happens if:
2417-
/// - the value is a guaranteed argument to the function
2418-
/// - the value is itself a begin_borrow [lexical]
2417+
/// - the non-guaranteed borrowee's value is lexical
2418+
/// - the guaranteed borrowee's value's reference roots are lexical
2419+
/// - for example, the borrowee is itself a begin_borrow [lexical]
24192420
bool swift::isNestedLexicalBeginBorrow(BeginBorrowInst *bbi) {
24202421
assert(bbi->isLexical());
24212422
auto value = bbi->getOperand();
2422-
if (auto *outerBBI = dyn_cast<BeginBorrowInst>(value)) {
2423-
return outerBBI->isLexical();
2423+
if (value->getOwnershipKind() != OwnershipKind::Guaranteed) {
2424+
return value->isLexical();
24242425
}
2425-
if (auto *arg = dyn_cast<SILFunctionArgument>(value)) {
2426-
return arg->getOwnershipKind() == OwnershipKind::Guaranteed;
2427-
}
2428-
return false;
2426+
SmallVector<SILValue, 8> roots;
2427+
findGuaranteedReferenceRoots(value, /*lookThroughNestedBorrows=*/false,
2428+
roots);
2429+
return llvm::all_of(roots, [](auto root) {
2430+
if (auto *outerBBI = dyn_cast<BeginBorrowInst>(root)) {
2431+
return outerBBI->isLexical();
2432+
}
2433+
if (auto *arg = dyn_cast<SILFunctionArgument>(root)) {
2434+
return arg->getOwnershipKind() == OwnershipKind::Guaranteed;
2435+
}
2436+
return false;
2437+
});
24292438
}

lib/SIL/Utils/Projection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ Optional<ProjectionPath> ProjectionPath::getProjectionPath(SILValue Start,
387387
// TODO: migrate users to getProjectionPath to the AccessPath utility to
388388
// avoid this hack.
389389
if (!isa<EndCOWMutationInst>(Iter) && !isa<BeginAccessInst>(Iter) &&
390-
!isa<BeginBorrowInst>(Iter)) {
390+
!isa<BeginBorrowInst>(Iter) && !isa<MoveValueInst>(Iter)) {
391391
Projection AP(Iter);
392392
if (!AP.isValid())
393393
break;

lib/SILGen/SILGenProlog.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,15 +303,8 @@ struct ArgumentInitHelper {
303303
// form.
304304
if (!isNoImplicitCopy) {
305305
if (!value->getType().isMoveOnly()) {
306-
// Follow the "normal path": perform a lexical borrow if the lifetime is
307-
// lexical.
308-
if (value->getOwnershipKind() == OwnershipKind::Owned) {
309-
if (lifetime.isLexical()) {
310-
value = SILValue(
311-
SGF.B.createBeginBorrow(loc, value, /*isLexical*/ true));
312-
SGF.Cleanups.pushCleanup<EndBorrowCleanup>(value);
313-
}
314-
}
306+
// Follow the normal path. The value's lifetime will be enforced based
307+
// on its ownership.
315308
return value;
316309
}
317310

lib/SILOptimizer/Mandatory/MandatoryInlining.cpp

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,6 @@ static void diagnose(ASTContext &Context, SourceLoc loc, Diag<T...> diag,
6767
Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
6868
}
6969

70-
static SILValue stripCopiesAndBorrows(SILValue v) {
71-
while (isa<CopyValueInst>(v) || isa<BeginBorrowInst>(v)) {
72-
v = cast<SingleValueInstruction>(v)->getOperand(0);
73-
}
74-
return v;
75-
}
76-
7770
/// Fixup reference counts after inlining a function call (which is a no-op
7871
/// unless the function is a thick function).
7972
///
@@ -388,7 +381,7 @@ static void cleanupCalleeValue(SILValue calleeValue,
388381
if (auto loadedValue = cleanupLoadedCalleeValue(calleeValue))
389382
calleeValue = loadedValue;
390383

391-
calleeValue = stripCopiesAndBorrows(calleeValue);
384+
calleeValue = lookThroughOwnershipInsts(calleeValue);
392385

393386
// Inline constructor
394387
auto calleeSource = ([&]() -> SILValue {
@@ -398,12 +391,12 @@ static void cleanupCalleeValue(SILValue calleeValue,
398391
// will delete any uses of the closure, including a
399392
// convert_escape_to_noescape conversion.
400393
if (auto *cfi = dyn_cast<ConvertFunctionInst>(calleeValue))
401-
return stripCopiesAndBorrows(cfi->getOperand());
394+
return lookThroughOwnershipInsts(cfi->getOperand());
402395

403396
if (auto *cvt = dyn_cast<ConvertEscapeToNoEscapeInst>(calleeValue))
404-
return stripCopiesAndBorrows(cvt->getOperand());
397+
return lookThroughOwnershipInsts(cvt->getOperand());
405398

406-
return stripCopiesAndBorrows(calleeValue);
399+
return lookThroughOwnershipInsts(calleeValue);
407400
})();
408401

409402
if (auto *pai = dyn_cast<PartialApplyInst>(calleeSource)) {
@@ -419,7 +412,7 @@ static void cleanupCalleeValue(SILValue calleeValue,
419412
}
420413
invalidatedStackNesting = true;
421414

422-
calleeValue = stripCopiesAndBorrows(calleeValue);
415+
calleeValue = lookThroughOwnershipInsts(calleeValue);
423416

424417
// Handle function_ref -> convert_function -> partial_apply/thin_to_thick.
425418
if (auto *cfi = dyn_cast<ConvertFunctionInst>(calleeValue)) {
@@ -599,7 +592,7 @@ static SILValue getLoadedCalleeValue(LoadInst *li) {
599592
// a cast.
600593
static SILValue stripFunctionConversions(SILValue CalleeValue) {
601594
// Skip any copies that we see.
602-
CalleeValue = stripCopiesAndBorrows(CalleeValue);
595+
CalleeValue = lookThroughOwnershipInsts(CalleeValue);
603596

604597
// We can also allow a thin @escape to noescape conversion as such:
605598
// %1 = function_ref @thin_closure_impl : $@convention(thin) () -> ()
@@ -626,7 +619,7 @@ static SILValue stripFunctionConversions(SILValue CalleeValue) {
626619
if (FromCalleeTy != EscapingCalleeTy)
627620
return CalleeValue;
628621

629-
return stripCopiesAndBorrows(ConvertFn->getOperand());
622+
return lookThroughOwnershipInsts(ConvertFn->getOperand());
630623
}
631624

632625
// Ignore mark_dependence users. A partial_apply [stack] uses them to mark
@@ -642,7 +635,7 @@ static SILValue stripFunctionConversions(SILValue CalleeValue) {
642635

643636
auto *CFI = dyn_cast<ConvertEscapeToNoEscapeInst>(CalleeValue);
644637
if (!CFI)
645-
return stripCopiesAndBorrows(CalleeValue);
638+
return lookThroughOwnershipInsts(CalleeValue);
646639

647640
// TODO: Handle argument conversion. All the code in this file needs to be
648641
// cleaned up and generalized. The argument conversion handling in
@@ -658,9 +651,9 @@ static SILValue stripFunctionConversions(SILValue CalleeValue) {
658651
auto EscapingCalleeTy =
659652
ToCalleeTy->getWithExtInfo(ToCalleeTy->getExtInfo().withNoEscape(false));
660653
if (FromCalleeTy != EscapingCalleeTy)
661-
return stripCopiesAndBorrows(CalleeValue);
654+
return lookThroughOwnershipInsts(CalleeValue);
662655

663-
return stripCopiesAndBorrows(CFI->getOperand());
656+
return lookThroughOwnershipInsts(CFI->getOperand());
664657
}
665658

666659
/// Returns the callee SILFunction called at a call site, in the case
@@ -687,7 +680,7 @@ getCalleeFunction(SILFunction *F, FullApplySite AI, bool &IsThick,
687680

688681
// Then grab a first approximation of our apply by stripping off all copy
689682
// operations.
690-
SILValue CalleeValue = stripCopiesAndBorrows(AI.getCallee());
683+
SILValue CalleeValue = lookThroughOwnershipInsts(AI.getCallee());
691684

692685
// If after stripping off copy_values, we have a load then see if we the
693686
// function we want to inline has a simple available value through a simple
@@ -696,7 +689,7 @@ getCalleeFunction(SILFunction *F, FullApplySite AI, bool &IsThick,
696689
CalleeValue = getLoadedCalleeValue(li);
697690
if (!CalleeValue)
698691
return nullptr;
699-
CalleeValue = stripCopiesAndBorrows(CalleeValue);
692+
CalleeValue = lookThroughOwnershipInsts(CalleeValue);
700693
}
701694

702695
// Look through a escape to @noescape conversion.
@@ -709,11 +702,11 @@ getCalleeFunction(SILFunction *F, FullApplySite AI, bool &IsThick,
709702
// Collect the applied arguments and their convention.
710703
collectPartiallyAppliedArguments(PAI, CapturedArgConventions, FullArgs);
711704

712-
CalleeValue = stripCopiesAndBorrows(PAI->getCallee());
705+
CalleeValue = lookThroughOwnershipInsts(PAI->getCallee());
713706
IsThick = true;
714707
PartialApply = PAI;
715708
} else if (auto *TTTFI = dyn_cast<ThinToThickFunctionInst>(CalleeValue)) {
716-
CalleeValue = stripCopiesAndBorrows(TTTFI->getOperand());
709+
CalleeValue = lookThroughOwnershipInsts(TTTFI->getOperand());
717710
IsThick = true;
718711
}
719712

lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ struct OwnershipModelEliminatorVisitor
135135
bool visitExplicitCopyValueInst(ExplicitCopyValueInst *cvi);
136136
bool visitDestroyValueInst(DestroyValueInst *dvi);
137137
bool visitLoadBorrowInst(LoadBorrowInst *lbi);
138+
bool visitMoveValueInst(MoveValueInst *mvi) {
139+
eraseInstructionAndRAUW(mvi, mvi->getOperand());
140+
return true;
141+
}
138142
bool visitBeginBorrowInst(BeginBorrowInst *bbi) {
139143
eraseInstructionAndRAUW(bbi, bbi->getOperand());
140144
return true;

lib/SILOptimizer/Transforms/ObjectOutliner.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@ bool ObjectOutliner::getObjectInitVals(SILValue Val,
252252
if (!getObjectInitVals(UC, MemberStores, TailStores, NumTailTupleElements,
253253
toIgnore))
254254
return false;
255+
} else if (auto *mvi = dyn_cast<MoveValueInst>(User)) {
256+
// move_value is transparent.
257+
if (!getObjectInitVals(mvi, MemberStores, TailStores,
258+
NumTailTupleElements, toIgnore))
259+
return false;
255260
} else if (auto *REA = dyn_cast<RefElementAddrInst>(User)) {
256261
// The address of a stored property.
257262
for (Operand *ElemAddrUse : REA->getUses()) {
@@ -316,6 +321,9 @@ static EndCOWMutationInst *getEndCOWMutation(SILValue object) {
316321
// Look through upcast instructions.
317322
if (EndCOWMutationInst *ecm = getEndCOWMutation(upCast))
318323
return ecm;
324+
} else if (auto *mvi = dyn_cast<MoveValueInst>(user)) {
325+
if (auto *ecm = getEndCOWMutation(mvi))
326+
return ecm;
319327
} else if (auto *ecm = dyn_cast<EndCOWMutationInst>(use->getUser())) {
320328
return ecm;
321329
}

0 commit comments

Comments
 (0)