Skip to content

Commit bfd140f

Browse files
authored
Merge pull request swiftlang#62937 from jckarter/nonescaping-closure-move-only
[WIP] Treat nonescaping closure types as owned types in SIL; use borrowing to model capture lifetimes
2 parents 6b5f449 + 8753d48 commit bfd140f

File tree

105 files changed

+1452
-465
lines changed

Some content is hidden

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

105 files changed

+1452
-465
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ class BorrowingOperandKind {
285285
Apply,
286286
TryApply,
287287
Yield,
288+
PartialApplyStack,
289+
BeginAsyncLet,
288290
};
289291

290292
private:
@@ -295,8 +297,8 @@ class BorrowingOperandKind {
295297

296298
operator Kind() const { return value; }
297299

298-
static BorrowingOperandKind get(SILInstructionKind kind) {
299-
switch (kind) {
300+
static BorrowingOperandKind get(SILInstruction *i) {
301+
switch (i->getKind()) {
300302
default:
301303
return Kind::Invalid;
302304
case SILInstructionKind::BeginBorrowInst:
@@ -311,6 +313,15 @@ class BorrowingOperandKind {
311313
return Kind::TryApply;
312314
case SILInstructionKind::YieldInst:
313315
return Kind::Yield;
316+
case SILInstructionKind::PartialApplyInst:
317+
return Kind::PartialApplyStack;
318+
case SILInstructionKind::BuiltinInst: {
319+
auto bi = cast<BuiltinInst>(i);
320+
if (bi->getBuiltinKind() == BuiltinValueKind::StartAsyncLetWithLocalBuffer) {
321+
return Kind::BeginAsyncLet;
322+
}
323+
return Kind::Invalid;
324+
}
314325
}
315326
}
316327

@@ -338,7 +349,7 @@ struct BorrowingOperand {
338349
BorrowingOperandKind kind;
339350

340351
BorrowingOperand(Operand *op)
341-
: op(op), kind(BorrowingOperandKind::get(op->getUser()->getKind())) {
352+
: op(op), kind(BorrowingOperandKind::get(op->getUser())) {
342353
auto ownership = op->getOperandOwnership();
343354
if (ownership != OperandOwnership::Borrow
344355
&& ownership != OperandOwnership::Reborrow) {
@@ -405,6 +416,8 @@ struct BorrowingOperand {
405416
case BorrowingOperandKind::Apply:
406417
case BorrowingOperandKind::TryApply:
407418
case BorrowingOperandKind::Yield:
419+
case BorrowingOperandKind::PartialApplyStack:
420+
case BorrowingOperandKind::BeginAsyncLet:
408421
return false;
409422
case BorrowingOperandKind::Branch:
410423
return true;
@@ -435,6 +448,8 @@ struct BorrowingOperand {
435448
case BorrowingOperandKind::Apply:
436449
case BorrowingOperandKind::TryApply:
437450
case BorrowingOperandKind::Yield:
451+
case BorrowingOperandKind::PartialApplyStack:
452+
case BorrowingOperandKind::BeginAsyncLet:
438453
return false;
439454
}
440455
llvm_unreachable("Covered switch isn't covered?!");

include/swift/SIL/SILBuilder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2760,7 +2760,7 @@ class SILBuilder {
27602760
SILLocation Loc, NormalDifferentiableFunctionTypeComponent Extractee,
27612761
SILValue Function, Optional<SILType> ExtracteeType = None) {
27622762
return createDifferentiableFunctionExtract(
2763-
Loc, Extractee, Function, Function->getOwnershipKind(), ExtracteeType);
2763+
Loc, Extractee, Function, OwnershipKind::Guaranteed, ExtracteeType);
27642764
}
27652765

27662766
DifferentiableFunctionExtractInst *createDifferentiableFunctionExtract(

include/swift/SIL/SILCloner.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1835,6 +1835,13 @@ template <typename ImplClass>
18351835
void SILCloner<ImplClass>::visitCopyValueInst(CopyValueInst *Inst) {
18361836
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
18371837
if (!getBuilder().hasOwnership()) {
1838+
// Noescape closures become trivial after OSSA.
1839+
if (auto fnTy = Inst->getType().getAs<SILFunctionType>()) {
1840+
if (fnTy->isTrivialNoEscape()) {
1841+
return recordFoldedValue(Inst, getOpValue(Inst->getOperand()));
1842+
}
1843+
}
1844+
18381845
SILValue newValue = getBuilder().emitCopyValueOperation(
18391846
getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()));
18401847
return recordFoldedValue(Inst, newValue);
@@ -1950,6 +1957,34 @@ template <typename ImplClass>
19501957
void SILCloner<ImplClass>::visitDestroyValueInst(DestroyValueInst *Inst) {
19511958
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
19521959
if (!getBuilder().hasOwnership()) {
1960+
// Noescape closures become trivial after OSSA.
1961+
if (auto fnTy = Inst->getOperand()->getType().getAs<SILFunctionType>()) {
1962+
if (fnTy->isTrivialNoEscape()) {
1963+
// Destroying the partial_apply [stack] becomes the stack deallocation
1964+
// of the context.
1965+
// Look through mark_dependence and other wrapper instructions.
1966+
SILValue deallocOperand = Inst->getOperand();
1967+
while (true) {
1968+
if (auto mdi = dyn_cast<MarkDependenceInst>(deallocOperand)) {
1969+
deallocOperand = mdi->getValue();
1970+
} else if (isa<ConvertEscapeToNoEscapeInst>(deallocOperand)) {
1971+
break;
1972+
} else if (auto conv = dyn_cast<ConversionInst>(deallocOperand)) {
1973+
deallocOperand = conv->getConverted();
1974+
} else {
1975+
break;
1976+
}
1977+
}
1978+
if (isa<PartialApplyInst>(deallocOperand)) {
1979+
recordClonedInstruction(Inst,
1980+
getBuilder().createDeallocStack(getOpLocation(Inst->getLoc()),
1981+
getOpValue(deallocOperand)));
1982+
}
1983+
1984+
return;
1985+
}
1986+
}
1987+
19531988
return recordClonedInstruction(
19541989
Inst, getBuilder().createReleaseValue(
19551990
getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()),

include/swift/SIL/SILInstruction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3019,6 +3019,9 @@ class PartialApplyInst final
30193019
OnStackKind isOnStack() const {
30203020
return getFunctionType()->isNoEscape() ? OnStack : NotOnStack;
30213021
}
3022+
3023+
/// Visit the instructions that end the lifetime of an OSSA on-stack closure.
3024+
bool visitOnStackLifetimeEnds(llvm::function_ref<bool (Operand*)> func) const;
30223025
};
30233026

30243027
class EndApplyInst;

include/swift/SIL/SILType.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ class SILType {
631631

632632
/// Returns true if and only if this type is a first class move only
633633
/// type. NOTE: Returns false if the type is a move only wrapped type.
634-
bool isMoveOnlyType() const;
634+
bool isMoveOnlyNominalType() const;
635635

636636
/// Returns true if this SILType is a move only wrapper type.
637637
///

include/swift/SILOptimizer/Analysis/AccessSummaryAnalysis.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class AccessSummaryAnalysis : public BottomUpIPAnalysis {
122122
unsigned getArgumentCount() const { return ArgAccesses.size(); }
123123

124124
void print(raw_ostream &os, SILFunction *fn) const;
125+
void dump(SILFunction *fn) const;
125126
};
126127

127128
class FunctionInfo;

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ void releasePartialApplyCapturedArg(
275275
void insertDestroyOfCapturedArguments(
276276
PartialApplyInst *pai, SILBuilder &builder,
277277
llvm::function_ref<bool(SILValue)> shouldInsertDestroy =
278-
[](SILValue arg) -> bool { return true; });
278+
[](SILValue arg) -> bool { return true; },
279+
SILLocation loc = RegularLocation::getAutoGeneratedLocation());
279280

280281
void insertDeallocOfCapturedArguments(PartialApplyInst *pai,
281282
DominanceInfo *domInfo);

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -549,9 +549,21 @@ OperandOwnershipClassifier::visitTryApplyInst(TryApplyInst *i) {
549549

550550
OperandOwnership
551551
OperandOwnershipClassifier::visitPartialApplyInst(PartialApplyInst *i) {
552-
// partial_apply [stack] does not take ownership of its operands.
552+
// partial_apply [stack] borrows its operands.
553553
if (i->isOnStack()) {
554-
return OperandOwnership::InstantaneousUse;
554+
auto operandTy = getValue()->getType();
555+
// Trivial values we can treat as trivial uses.
556+
if (operandTy.isTrivial(*i->getFunction())) {
557+
return OperandOwnership::TrivialUse;
558+
}
559+
560+
// Borrowing of address operands is ultimately handled by the move-only
561+
// address checker and/or exclusivity checker rather than by value ownership.
562+
if (operandTy.isAddress()) {
563+
return OperandOwnership::TrivialUse;
564+
}
565+
566+
return OperandOwnership::Borrow;
555567
}
556568
// All non-trivial types should be captured.
557569
return OperandOwnership::ForwardingConsume;
@@ -833,15 +845,26 @@ OperandOwnership OperandOwnershipBuiltinClassifier::visitCopy(BuiltinInst *bi,
833845
}
834846
}
835847
BUILTIN_OPERAND_OWNERSHIP(DestroyingConsume, StartAsyncLet)
836-
BUILTIN_OPERAND_OWNERSHIP(DestroyingConsume, EndAsyncLet)
837-
BUILTIN_OPERAND_OWNERSHIP(DestroyingConsume, StartAsyncLetWithLocalBuffer)
838-
BUILTIN_OPERAND_OWNERSHIP(DestroyingConsume, EndAsyncLetLifetime)
848+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, EndAsyncLet)
849+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, EndAsyncLetLifetime)
839850
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, CreateTaskGroup)
840851
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, CreateTaskGroupWithFlags)
841852
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, DestroyTaskGroup)
842853

843854
BUILTIN_OPERAND_OWNERSHIP(ForwardingConsume, COWBufferForReading)
844855

856+
OperandOwnership
857+
OperandOwnershipBuiltinClassifier
858+
::visitStartAsyncLetWithLocalBuffer(BuiltinInst *bi, StringRef attr) {
859+
if (&op == &bi->getOperandRef(0)) {
860+
// The result buffer pointer is a trivial use.
861+
return OperandOwnership::TrivialUse;
862+
}
863+
864+
// The closure is borrowed while the async let task is executing.
865+
return OperandOwnership::Borrow;
866+
}
867+
845868
const int PARAMETER_INDEX_CREATE_ASYNC_TASK_FUTURE_FUNCTION = 2;
846869
const int PARAMETER_INDEX_CREATE_ASYNC_TASK_GROUP_FUTURE_FUNCTION = 3;
847870

@@ -900,7 +923,7 @@ visitResumeThrowingContinuationThrowing(BuiltinInst *bi, StringRef attr) {
900923
return OperandOwnership::TrivialUse;
901924
}
902925

903-
BUILTIN_OPERAND_OWNERSHIP(TrivialUse, TaskRunInline)
926+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, TaskRunInline)
904927

905928
BUILTIN_OPERAND_OWNERSHIP(InteriorPointer, CancelAsyncTask)
906929
BUILTIN_OPERAND_OWNERSHIP(InteriorPointer, InitializeDefaultActor)

lib/SIL/IR/SILFunctionType.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,10 +1212,20 @@ class Conventions {
12121212
unsigned index, const AbstractionPattern &type,
12131213
const TypeLowering &substTL) const {
12141214
switch (ownership) {
1215-
case ValueOwnership::Default:
1215+
case ValueOwnership::Default: {
12161216
if (forSelf)
12171217
return getDirectSelfParameter(type);
1218-
return getDirectParameter(index, type, substTL);
1218+
auto convention = getDirectParameter(index, type, substTL);
1219+
// Nonescaping closures can only be borrowed across calls currently.
1220+
if (convention == ParameterConvention::Direct_Owned) {
1221+
if (auto fnTy = substTL.getLoweredType().getAs<SILFunctionType>()) {
1222+
if (fnTy->isTrivialNoEscape()) {
1223+
return ParameterConvention::Direct_Guaranteed;
1224+
}
1225+
}
1226+
}
1227+
return convention;
1228+
}
12191229
case ValueOwnership::InOut:
12201230
return ParameterConvention::Indirect_Inout;
12211231
case ValueOwnership::Shared:

lib/SIL/IR/SILInstruction.cpp

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,8 +1239,15 @@ bool SILInstruction::isAllocatingStack() const {
12391239
return true;
12401240
}
12411241

1242-
if (auto *PA = dyn_cast<PartialApplyInst>(this))
1243-
return PA->isOnStack();
1242+
// In OSSA, PartialApply is modeled as a value which borrows its operands
1243+
// and whose lifetime is ended by a `destroy_value`.
1244+
//
1245+
// After OSSA, we make the memory allocation and dependencies explicit again,
1246+
// with a `dealloc_stack` ending the closure's lifetime.
1247+
if (auto *PA = dyn_cast<PartialApplyInst>(this)) {
1248+
return PA->isOnStack()
1249+
&& !PA->getFunction()->hasOwnership();
1250+
}
12441251

12451252
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
12461253
if (BI->getBuiltinKind() == BuiltinValueKind::StackAlloc ||
@@ -1658,6 +1665,46 @@ bool SILInstruction::maySuspend() const {
16581665
return false;
16591666
}
16601667

1668+
static bool visitRecursivelyLifetimeEndingUses(
1669+
SILValue i,
1670+
bool &noUsers,
1671+
llvm::function_ref<bool(Operand *)> func)
1672+
{
1673+
for (Operand *use : i->getConsumingUses()) {
1674+
noUsers = false;
1675+
if (isa<DestroyValueInst>(use->getUser())) {
1676+
if (!func(use)) {
1677+
return false;
1678+
}
1679+
continue;
1680+
}
1681+
1682+
// There shouldn't be any dead-end consumptions of a nonescaping
1683+
// partial_apply that don't forward it along, aside from destroy_value.
1684+
assert(use->getUser()->hasResults());
1685+
for (auto result : use->getUser()->getResults()) {
1686+
if (!visitRecursivelyLifetimeEndingUses(result, noUsers, func)) {
1687+
return false;
1688+
}
1689+
}
1690+
}
1691+
return true;
1692+
}
1693+
1694+
bool
1695+
PartialApplyInst::visitOnStackLifetimeEnds(
1696+
llvm::function_ref<bool (Operand *)> func) const {
1697+
assert(getFunction()->hasOwnership()
1698+
&& isOnStack()
1699+
&& "only meaningful for OSSA stack closures");
1700+
bool noUsers = true;
1701+
1702+
if (!visitRecursivelyLifetimeEndingUses(this, noUsers, func)) {
1703+
return false;
1704+
}
1705+
return !noUsers;
1706+
}
1707+
16611708
#ifndef NDEBUG
16621709

16631710
//---

0 commit comments

Comments
 (0)