Skip to content

[move-only] Implement escaping closure semantics part 1 #63783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 21, 2023
Merged
7 changes: 3 additions & 4 deletions include/swift/AST/DiagnosticsSIL.def
Original file line number Diff line number Diff line change
Expand Up @@ -760,12 +760,11 @@ ERROR(sil_moveonlychecker_notconsumable_but_assignable_was_consumed_global_var,
ERROR(sil_moveonlychecker_notconsumable_but_assignable_was_consumed_global_let, none,
"'%0' was consumed but it is illegal to consume a noncopyable global let. One can only read from it",
(StringRef))
ERROR(sil_moveonlychecker_notconsumable_but_assignable_was_consumed_escaping_let, none,
"'%0' was consumed but it is illegal to consume a noncopyable escaping immutable capture. One can only read from it",
(StringRef))
ERROR(sil_moveonlychecker_notconsumable_but_assignable_was_consumed_escaping_var, none,
"'%0' was consumed but it is illegal to consume a noncopyable escaping mutable capture. One can only read from it or assign over it",
"'%0' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it",
(StringRef))
ERROR(sil_moveonlychecker_let_capture_consumed, none,
"'%0' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it", (StringRef))

NOTE(sil_moveonlychecker_moveonly_field_consumed_here, none,
"move only field consumed here", ())
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/SemanticAttrs.def
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,9 @@ SEMANTICS_ATTR(LIFETIMEMANAGEMENT_COPY, "lifetimemanagement.copy")

SEMANTICS_ATTR(NO_PERFORMANCE_ANALYSIS, "no_performance_analysis")

// A flag used to turn off moveonly diagnostics on functions that allocbox to
// stack specialized.
SEMANTICS_ATTR(NO_MOVEONLY_DIAGNOSTICS, "sil.optimizer.moveonly.diagnostic.ignore")

#undef SEMANTICS_ATTR

23 changes: 17 additions & 6 deletions lib/SIL/IR/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,26 @@ CaptureKind TypeConverter::getDeclCaptureKind(CapturedValue capture,
assert(var->hasStorage() &&
"should not have attempted to directly capture this variable");

auto &lowering = getTypeLowering(
var->getType(), TypeExpansionContext::noOpaqueTypeArchetypesSubstitution(
expansion.getResilienceExpansion()));

// If this is a noncopyable 'let' constant that is not a shared paramdecl or
// used by a noescape capture, then we know it is boxed and want to pass it in
// its boxed form so we can obey Swift's capture reference semantics.
if (!var->supportsMutation() && lowering.getLoweredType().isPureMoveOnly() &&
!capture.isNoEscape()) {
auto *param = dyn_cast<ParamDecl>(var);
if (!param || param->getValueOwnership() != ValueOwnership::Shared) {
return CaptureKind::ImmutableBox;
}
}

// If this is a non-address-only stored 'let' constant, we can capture it
// by value. If it is address-only, then we can't load it, so capture it
// by its address (like a var) instead.
if (!var->supportsMutation() &&
!getTypeLowering(var->getType(),
TypeExpansionContext::noOpaqueTypeArchetypesSubstitution(
expansion.getResilienceExpansion()))
.isAddressOnly())
return CaptureKind::Constant;
if (!var->supportsMutation() && !lowering.isAddressOnly())
return CaptureKind::Constant;

// In-out parameters are captured by address.
if (auto *param = dyn_cast<ParamDecl>(var)) {
Expand Down
34 changes: 24 additions & 10 deletions lib/SILGen/SILGenConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,11 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
// Allocate the local variable for 'self'.
emitLocalVariableWithCleanup(selfDecl, MUIKind)->finishInitialization(*this);

SILValue selfLV = VarLocs[selfDecl].value;
ManagedValue selfLV =
maybeEmitValueOfLocalVarDecl(selfDecl, AccessKind::ReadWrite);
if (!selfLV)
selfLV = maybeEmitAddressForBoxOfLocalVarDecl(selfDecl, selfDecl);
assert(selfLV);

// Emit the prolog.
emitBasicProlog(ctor->getParameters(),
Expand Down Expand Up @@ -491,7 +495,13 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {

if (!F.getConventions().hasIndirectSILResults()) {
// Otherwise, load and return the final 'self' value.
selfValue = lowering.emitLoad(B, cleanupLoc, selfLV,
if (selfLV.getType().isMoveOnly()) {
selfLV = B.createMarkMustCheckInst(
cleanupLoc, selfLV,
MarkMustCheckInst::CheckKind::AssignableButNotConsumable);
}

selfValue = lowering.emitLoad(B, cleanupLoc, selfLV.getValue(),
LoadOwnershipQualifier::Copy);

// Inject the self value into an optional if the constructor is failable.
Expand All @@ -513,17 +523,16 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
returnAddress = completeReturnAddress;
} else {
// If this is a failable initializer, project out the payload.
returnAddress = B.createInitEnumDataAddr(cleanupLoc,
completeReturnAddress,
getASTContext().getOptionalSomeDecl(),
selfLV->getType());
returnAddress = B.createInitEnumDataAddr(
cleanupLoc, completeReturnAddress,
getASTContext().getOptionalSomeDecl(), selfLV.getType());
}

// We have to do a non-take copy because someone else may be using the
// box (e.g. someone could have closed over it).
B.createCopyAddr(cleanupLoc, selfLV, returnAddress,
B.createCopyAddr(cleanupLoc, selfLV.getLValueAddress(), returnAddress,
IsNotTake, IsInitialization);

// Inject the enum tag if the result is optional because of failability.
if (ctor->isFailable()) {
// Inject the 'Some' tag.
Expand Down Expand Up @@ -1022,14 +1031,19 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) {
static ManagedValue emitSelfForMemberInit(SILGenFunction &SGF, SILLocation loc,
VarDecl *selfDecl) {
CanType selfFormalType = selfDecl->getType()->getCanonicalType();
if (selfFormalType->hasReferenceSemantics())
if (selfFormalType->hasReferenceSemantics()) {
return SGF.emitRValueForDecl(loc, selfDecl, selfFormalType,
AccessSemantics::DirectToStorage,
SGFContext::AllowImmediatePlusZero)
.getAsSingleValue(SGF, loc);
else
} else {
// First see if we have a variable that is boxed without a value.
if (auto value = SGF.maybeEmitAddressForBoxOfLocalVarDecl(loc, selfDecl))
return value;
// Otherwise, emit the address directly.
return SGF.emitAddressOfLocalVarDecl(loc, selfDecl, selfFormalType,
SGFAccessKind::Write);
}
}

// FIXME: Can emitMemberInit() share code with InitializationForPattern in
Expand Down
51 changes: 31 additions & 20 deletions lib/SILGen/SILGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,13 +351,13 @@ class LocalVariableInitialization : public SingleBufferInitialization {
"can't emit a local var for a non-local var decl");
assert(decl->hasStorage() && "can't emit storage for a computed variable");
assert(!SGF.VarLocs.count(decl) && "Already have an entry for this decl?");

// The box type's context is lowered in the minimal resilience domain.
auto instanceType = SGF.SGM.Types.getLoweredRValueType(
TypeExpansionContext::minimal(), decl->getType());
auto boxType = SGF.SGM.Types.getContextBoxTypeForCapture(
decl,
SGF.SGM.Types.getLoweredRValueType(TypeExpansionContext::minimal(),
decl->getType()),
SGF.F.getGenericEnvironment(),
/*mutable*/ true);
decl, instanceType, SGF.F.getGenericEnvironment(),
/*mutable*/ !instanceType->isPureMoveOnly() || !decl->isLet());

// The variable may have its lifetime extended by a closure, heap-allocate
// it using a box.
Expand All @@ -376,12 +376,8 @@ class LocalVariableInitialization : public SingleBufferInitialization {
}
}

Addr = SGF.B.createProjectBox(decl, Box, 0);
if (Addr->getType().isMoveOnly()) {
// TODO: Handle no implicit copy here.
Addr = SGF.B.createMarkMustCheckInst(
decl, Addr, MarkMustCheckInst::CheckKind::ConsumableAndAssignable);
}
if (!Box->getType().isBoxedNonCopyableType(Box->getFunction()))
Addr = SGF.B.createProjectBox(decl, Box, 0);

// Push a cleanup to destroy the local variable. This has to be
// inactive until the variable is initialized.
Expand All @@ -401,16 +397,24 @@ class LocalVariableInitialization : public SingleBufferInitialization {
}

SILValue getAddress() const {
assert(Addr);
return Addr;
}

/// If we have an address, returns the address. Otherwise, if we only have a
/// box, lazily projects it out and returns it.
SILValue getAddressForInPlaceInitialization(SILGenFunction &SGF,
SILLocation loc) override {
if (!Addr && Box) {
auto pbi = SGF.B.createProjectBox(loc, Box, 0);
return pbi;
}

return getAddress();
}

bool isInPlaceInitializationOfGlobal() const override {
return isa<GlobalAddrInst>(getAddress());
return dyn_cast_or_null<GlobalAddrInst>(Addr);
}

void finishUninitialized(SILGenFunction &SGF) override {
Expand All @@ -421,7 +425,11 @@ class LocalVariableInitialization : public SingleBufferInitialization {
/// Remember that this is the memory location that we've emitted the
/// decl to.
assert(SGF.VarLocs.count(decl) == 0 && "Already emitted the local?");
SGF.VarLocs[decl] = SILGenFunction::VarLoc::get(Addr, Box);

if (Addr)
SGF.VarLocs[decl] = SILGenFunction::VarLoc::get(Addr, Box);
else
SGF.VarLocs[decl] = SILGenFunction::VarLoc::getForBox(Box);

SingleBufferInitialization::finishInitialization(SGF);
assert(!DidFinish &&
Expand Down Expand Up @@ -485,9 +493,13 @@ class LetValueInitialization : public Initialization {
isUninitialized = true;
} else {
// If this is a let with an initializer or bound value, we only need a
// buffer if the type is address only.
// buffer if the type is address only or is noncopyable.
//
// For noncopyable types, we always need to box them and eagerly
// reproject.
needsTemporaryBuffer =
lowering->isAddressOnly() && SGF.silConv.useLoweredAddresses();
(lowering->isAddressOnly() && SGF.silConv.useLoweredAddresses()) ||
lowering->getLoweredType().isPureMoveOnly();
}

// Make sure that we have a non-address only type when binding a
Expand Down Expand Up @@ -634,8 +646,7 @@ class LetValueInitialization : public Initialization {
// We do this before the begin_borrow "normal" path below since move only
// types do not have no implicit copy attr on them.
if (value->getOwnershipKind() == OwnershipKind::Owned &&
value->getType().isMoveOnly() &&
!value->getType().isMoveOnlyWrapped()) {
value->getType().isPureMoveOnly()) {
value = SGF.B.createMoveValue(PrologueLoc, value, true /*isLexical*/);
return SGF.B.createMarkMustCheckInst(
PrologueLoc, value,
Expand Down Expand Up @@ -1253,10 +1264,10 @@ SILGenFunction::emitInitializationForVarDecl(VarDecl *vd, bool forceImmutable) {

assert(!isa<InOutType>(varType) && "local variables should never be inout");

// If this is a 'let' initialization for a non-global, set up a
// let binding, which stores the initialization value into VarLocs directly.
// If this is a 'let' initialization for a copyable non-global, set up a let
// binding, which stores the initialization value into VarLocs directly.
if (forceImmutable && vd->getDeclContext()->isLocalContext() &&
!isa<ReferenceStorageType>(varType))
!isa<ReferenceStorageType>(varType) && !varType->isPureMoveOnly())
return InitializationPtr(new LetValueInitialization(vd, *this));

// If the variable has no initial value, emit a mark_uninitialized instruction
Expand Down
20 changes: 13 additions & 7 deletions lib/SILGen/SILGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ void SILGenFunction::emitCaptures(SILLocation loc,

// Get an address value for a SILValue if it is address only in an type
// expansion context without opaque archetype substitution.
auto getAddressValue = [&](SILValue entryValue) -> SILValue {
auto getAddressValue = [&](VarLoc entryVarLoc) -> SILValue {
SILValue entryValue = entryVarLoc.getValueOrBoxedValue(*this, vd);
if (SGM.M.useLoweredAddresses()
&& SGM.Types
.getTypeLowering(
Expand All @@ -382,7 +383,7 @@ void SILGenFunction::emitCaptures(SILLocation loc,
case CaptureKind::Constant: {
// let declarations.
auto &tl = getTypeLowering(valueType);
SILValue Val = Entry.value;
SILValue Val = Entry.getValueOrBoxedValue(*this);
bool eliminateMoveOnlyWrapper =
Val->getType().isMoveOnlyWrapped() &&
!vd->getInterfaceType()->is<SILMoveOnlyWrappedType>();
Expand All @@ -409,6 +410,11 @@ void SILGenFunction::emitCaptures(SILLocation loc,
} else {
// If we have a mutable binding for a 'let', such as 'self' in an
// 'init' method, load it.
if (Val->getType().isMoveOnly()) {
Val = B.createMarkMustCheckInst(
loc, Val,
MarkMustCheckInst::CheckKind::AssignableButNotConsumable);
}
Val = emitLoad(loc, Val, tl, SGFContext(), IsNotTake).forward(*this);
}

Expand All @@ -425,14 +431,14 @@ void SILGenFunction::emitCaptures(SILLocation loc,
if (canGuarantee) {
// No-escaping stored declarations are captured as the
// address of the value.
auto entryValue = getAddressValue(Entry.value);
auto entryValue = getAddressValue(Entry);
capturedArgs.push_back(ManagedValue::forBorrowedRValue(entryValue));
}
else if (!silConv.useLoweredAddresses()) {
capturedArgs.push_back(
B.createCopyValue(loc, ManagedValue::forUnmanaged(Entry.value)));
} else {
auto entryValue = getAddressValue(Entry.value);
auto entryValue = getAddressValue(Entry);
// We cannot pass a valid SILDebugVariable while creating the temp here
// See rdar://60425582
auto addr = B.createAllocStack(loc, entryValue->getType().getObjectType());
Expand All @@ -443,7 +449,7 @@ void SILGenFunction::emitCaptures(SILLocation loc,
break;
}
case CaptureKind::StorageAddress: {
auto entryValue = getAddressValue(Entry.value);
auto entryValue = getAddressValue(Entry);
// No-escaping stored declarations are captured as the
// address of the value.
assert(entryValue->getType().isAddress() && "no address for captured var!");
Expand All @@ -452,7 +458,7 @@ void SILGenFunction::emitCaptures(SILLocation loc,
}

case CaptureKind::Box: {
auto entryValue = getAddressValue(Entry.value);
auto entryValue = getAddressValue(Entry);
// LValues are captured as both the box owning the value and the
// address of the value.
assert(entryValue->getType().isAddress() && "no address for captured var!");
Expand Down Expand Up @@ -501,7 +507,7 @@ void SILGenFunction::emitCaptures(SILLocation loc,
break;
}
case CaptureKind::ImmutableBox: {
auto entryValue = getAddressValue(Entry.value);
auto entryValue = getAddressValue(Entry);
// LValues are captured as both the box owning the value and the
// address of the value.
assert(entryValue->getType().isAddress() &&
Expand Down
Loading