Skip to content

Commit d0f8843

Browse files
authored
Merge pull request #63822 from kavon/resilient-noncopyable
Support resilient properties of move-only type
2 parents e1e4012 + 410673c commit d0f8843

15 files changed

+484
-149
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6927,8 +6927,6 @@ NOTE(moveonly_copyable_type_that_contains_moveonly_type_location, none,
69276927
ERROR(moveonly_cannot_conform_to_type, none,
69286928
"move-only %0 %1 cannot conform to %2",
69296929
(DescriptiveDeclKind, DeclName, Type))
6930-
ERROR(moveonly_non_final_class_cannot_contain_moveonly_field, none,
6931-
"non-final classes containing move only fields is not yet supported", ())
69326930
ERROR(moveonly_parameter_missing_ownership, none,
69336931
"noncopyable parameter must specify its ownership", ())
69346932
NOTE(moveonly_parameter_ownership_suggestion, none,

lib/SILGen/ArgumentSource.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,15 @@ class ArgumentSource {
222222
return Storage.get<LValueStorage>(StoredKind).Loc;
223223
}
224224

225+
/// The kind of operation under which we are querying a storage reference.
226+
enum class StorageReferenceOperationKind {
227+
Borrow,
228+
Consume
229+
};
230+
225231
Expr *findStorageReferenceExprForBorrow() &&;
226-
Expr *findStorageReferenceExprForMoveOnlyBorrow(SILGenFunction &SGF) &&;
232+
Expr *findStorageReferenceExprForMoveOnly(SILGenFunction &SGF,
233+
StorageReferenceOperationKind refKind) &&;
227234
Expr *findStorageReferenceExprForBorrowExpr(SILGenFunction &SGF) &&;
228235

229236
/// Given that this source is an expression, extract and clear

lib/SILGen/SILGenApply.cpp

Lines changed: 124 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,7 +2939,48 @@ static void emitDelayedArguments(SILGenFunction &SGF,
29392939
}
29402940
}
29412941

2942-
static Expr *findStorageReferenceExprForBorrow(Expr *e) {
2942+
namespace {
2943+
/// Container to hold the result of a search for the storage reference
2944+
/// when determining to emit a borrow.
2945+
struct StorageRefResult {
2946+
private:
2947+
Expr *storageRef;
2948+
Expr *transitiveRoot;
2949+
2950+
public:
2951+
// Represents an empty result
2952+
StorageRefResult() : storageRef(nullptr), transitiveRoot(nullptr) {}
2953+
bool isEmpty() const { return transitiveRoot == nullptr; }
2954+
operator bool() const { return !isEmpty(); }
2955+
2956+
/// The root of the expression that accesses the storage in \c storageRef.
2957+
/// When in doubt, this is probably what you want, as it includes the
2958+
/// entire expression tree involving the reference.
2959+
Expr *getTransitiveRoot() const { return transitiveRoot; }
2960+
2961+
/// The direct storage reference that was discovered.
2962+
Expr *getStorageRef() const { return storageRef; }
2963+
2964+
StorageRefResult(Expr *storageRef, Expr *transitiveRoot)
2965+
: storageRef(storageRef), transitiveRoot(transitiveRoot) {
2966+
assert(storageRef && transitiveRoot && "use the zero-arg init for empty");
2967+
}
2968+
2969+
// Initializes a storage reference where the base matches the ref.
2970+
StorageRefResult(Expr *storageRef)
2971+
: StorageRefResult(storageRef, storageRef) {}
2972+
2973+
StorageRefResult withTransitiveRoot(StorageRefResult refResult) const {
2974+
return withTransitiveRoot(refResult.transitiveRoot);
2975+
}
2976+
2977+
StorageRefResult withTransitiveRoot(Expr *newRoot) const {
2978+
return StorageRefResult(storageRef, newRoot);
2979+
}
2980+
};
2981+
} // namespace
2982+
2983+
static StorageRefResult findStorageReferenceExprForBorrow(Expr *e) {
29432984
e = e->getSemanticsProvidingExpr();
29442985

29452986
// These are basically defined as the cases implemented by SILGenLValue.
@@ -2962,71 +3003,100 @@ static Expr *findStorageReferenceExprForBorrow(Expr *e) {
29623003
// sub-expression is a storage reference, but don't return the
29633004
// sub-expression.
29643005
} else if (auto tue = dyn_cast<TupleElementExpr>(e)) {
2965-
if (findStorageReferenceExprForBorrow(tue->getBase()))
2966-
return tue;
3006+
if (auto result = findStorageReferenceExprForBorrow(tue->getBase()))
3007+
return result.withTransitiveRoot(tue);
3008+
29673009
} else if (auto fve = dyn_cast<ForceValueExpr>(e)) {
2968-
if (findStorageReferenceExprForBorrow(fve->getSubExpr()))
2969-
return fve;
3010+
if (auto result = findStorageReferenceExprForBorrow(fve->getSubExpr()))
3011+
return result.withTransitiveRoot(fve);
3012+
29703013
} else if (auto boe = dyn_cast<BindOptionalExpr>(e)) {
2971-
if (findStorageReferenceExprForBorrow(boe->getSubExpr()))
2972-
return boe;
3014+
if (auto result = findStorageReferenceExprForBorrow(boe->getSubExpr()))
3015+
return result.withTransitiveRoot(boe);
3016+
29733017
} else if (auto oe = dyn_cast<OpenExistentialExpr>(e)) {
2974-
if (findStorageReferenceExprForBorrow(oe->getExistentialValue()) &&
2975-
findStorageReferenceExprForBorrow(oe->getSubExpr()))
2976-
return oe;
3018+
if (findStorageReferenceExprForBorrow(oe->getExistentialValue()))
3019+
if (auto result = findStorageReferenceExprForBorrow(oe->getSubExpr()))
3020+
return result.withTransitiveRoot(oe);
3021+
29773022
} else if (auto bie = dyn_cast<DotSyntaxBaseIgnoredExpr>(e)) {
2978-
if (findStorageReferenceExprForBorrow(bie->getRHS()))
2979-
return bie;
3023+
if (auto result = findStorageReferenceExprForBorrow(bie->getRHS()))
3024+
return result.withTransitiveRoot(bie);
3025+
29803026
} else if (auto te = dyn_cast<AnyTryExpr>(e)) {
2981-
if (findStorageReferenceExprForBorrow(te->getSubExpr()))
2982-
return te;
3027+
if (auto result = findStorageReferenceExprForBorrow(te->getSubExpr()))
3028+
return result.withTransitiveRoot(te);
3029+
29833030
} else if (auto ioe = dyn_cast<InOutExpr>(e)) {
29843031
return ioe;
29853032
}
29863033

2987-
return nullptr;
3034+
return StorageRefResult();
29883035
}
29893036

2990-
Expr *ArgumentSource::findStorageReferenceExprForMoveOnlyBorrow(
2991-
SILGenFunction &SGF) && {
3037+
Expr *ArgumentSource::findStorageReferenceExprForMoveOnly(
3038+
SILGenFunction &SGF, StorageReferenceOperationKind kind) && {
29923039
if (!isExpr())
29933040
return nullptr;
29943041

29953042
auto argExpr = asKnownExpr();
2996-
auto *li = dyn_cast<LoadExpr>(argExpr);
2997-
if (!li)
3043+
3044+
// If there's a load around the outer part of this arg expr, look past it.
3045+
bool sawLoad = false;
3046+
if (auto *li = dyn_cast<LoadExpr>(argExpr)) {
3047+
argExpr = li->getSubExpr();
3048+
sawLoad = true;
3049+
}
3050+
3051+
// If we're consuming instead, then the load _must_ have been there.
3052+
if (kind == StorageReferenceOperationKind::Consume && !sawLoad)
29983053
return nullptr;
29993054

3000-
auto *lvExpr = ::findStorageReferenceExprForBorrow(li->getSubExpr());
3055+
auto result = ::findStorageReferenceExprForBorrow(argExpr);
30013056

3002-
// Claim the value of this argument if we found a storage reference that has a
3003-
// move only base.
3004-
if (lvExpr) {
3005-
// We want to perform a borrow if our initial type is a pure move only /or/
3006-
// if after looking through multiple copyable member ref expr, we get to a
3007-
// move only member ref expr or a move only decl ref expr.
3008-
//
3009-
// NOTE: We purposely do not look through load implying that if we have
3010-
// copyable types extracted from a move only class, we will not borrow.
3011-
auto *iterExpr = lvExpr;
3012-
while (true) {
3013-
SILType ty = SGF.getLoweredType(
3014-
iterExpr->getType()->getWithoutSpecifierType()->getCanonicalType());
3015-
if (ty.isPureMoveOnly())
3016-
break;
3057+
if (!result)
3058+
return nullptr;
30173059

3018-
if (auto *mre = dyn_cast<MemberRefExpr>(iterExpr)) {
3019-
iterExpr = mre->getBase();
3020-
continue;
3021-
}
3060+
// We want to perform a borrow/consume if the first piece of storage being
3061+
// referenced is a move-only type.
3062+
3063+
VarDecl *storage = nullptr;
3064+
Type type;
3065+
if (auto dre = dyn_cast<DeclRefExpr>(result.getStorageRef())) {
3066+
storage = dyn_cast<VarDecl>(dre->getDecl());
3067+
type = dre->getType();
3068+
} else if (auto mre = dyn_cast<MemberRefExpr>(result.getStorageRef())) {
3069+
storage = dyn_cast<VarDecl>(mre->getDecl().getDecl());
3070+
type = mre->getType();
3071+
}
30223072

3073+
if (!storage)
30233074
return nullptr;
3024-
}
3075+
assert(type);
30253076

3026-
(void)std::move(*this).asKnownExpr();
3027-
}
3077+
SILType ty =
3078+
SGF.getLoweredType(type->getWithoutSpecifierType()->getCanonicalType());
3079+
if (!ty.isPureMoveOnly())
3080+
return nullptr;
30283081

3029-
return lvExpr;
3082+
// It makes sense to borrow any kind of storage we refer to at this stage,
3083+
// but SILGenLValue does not currently handle some kinds of references well.
3084+
//
3085+
// When rejecting to do the LValue-style borrow here, it'll end up going thru
3086+
// the RValue-style emission, after which the extra copy will get eliminated.
3087+
//
3088+
// If we did not see a LoadExpr around the argument expression, then only
3089+
// do the borrow if the storage is non-local.
3090+
// FIXME: I don't have a principled reason for why this matters and hope that
3091+
// we can fix the AST we're working with.
3092+
if (!sawLoad && storage->getDeclContext()->isLocalContext())
3093+
return nullptr;
3094+
3095+
// Claim the value of this argument since we found a storage reference that
3096+
// has a move only base.
3097+
(void)std::move(*this).asKnownExpr();
3098+
3099+
return result.getTransitiveRoot();
30303100
}
30313101

30323102
Expr *
@@ -3054,7 +3124,8 @@ ArgumentSource::findStorageReferenceExprForBorrowExpr(SILGenFunction &SGF) && {
30543124
if (!borrowExpr)
30553125
return nullptr;
30563126

3057-
auto *lvExpr = ::findStorageReferenceExprForBorrow(borrowExpr->getSubExpr());
3127+
Expr *lvExpr = ::findStorageReferenceExprForBorrow(borrowExpr->getSubExpr())
3128+
.getTransitiveRoot();
30583129

30593130
// Claim the value of this argument.
30603131
if (lvExpr) {
@@ -3068,7 +3139,8 @@ Expr *ArgumentSource::findStorageReferenceExprForBorrow() && {
30683139
if (!isExpr()) return nullptr;
30693140

30703141
auto argExpr = asKnownExpr();
3071-
auto lvExpr = ::findStorageReferenceExprForBorrow(argExpr);
3142+
auto *lvExpr =
3143+
::findStorageReferenceExprForBorrow(argExpr).getTransitiveRoot();
30723144

30733145
// Claim the value of this argument if we found a storage reference.
30743146
if (lvExpr) {
@@ -3318,8 +3390,8 @@ class ArgEmitter {
33183390
}
33193391

33203392
if (IsYield) {
3321-
if (auto lvExpr = findStorageReferenceExprForBorrow(e)) {
3322-
emitExpandedBorrowed(lvExpr, origParamType);
3393+
if (auto result = findStorageReferenceExprForBorrow(e)) {
3394+
emitExpandedBorrowed(result.getTransitiveRoot(), origParamType);
33233395
return;
33243396
}
33253397
}
@@ -3431,7 +3503,8 @@ class ArgEmitter {
34313503
assert(paramsSlice.size() == 1);
34323504

34333505
// Try to find an expression we can emit as a borrowed l-value.
3434-
auto lvExpr = std::move(arg).findStorageReferenceExprForMoveOnlyBorrow(SGF);
3506+
auto lvExpr = std::move(arg).findStorageReferenceExprForMoveOnly(
3507+
SGF, ArgumentSource::StorageReferenceOperationKind::Borrow);
34353508
if (!lvExpr)
34363509
return false;
34373510

@@ -3503,8 +3576,9 @@ class ArgEmitter {
35033576
ClaimedParamsRef paramsSlice) {
35043577
assert(paramsSlice.size() == 1);
35053578

3506-
// Try to find an expression we can emit as a borrowed l-value.
3507-
auto lvExpr = std::move(arg).findStorageReferenceExprForMoveOnlyBorrow(SGF);
3579+
// Try to find an expression we can emit as a consumed l-value.
3580+
auto lvExpr = std::move(arg).findStorageReferenceExprForMoveOnly(
3581+
SGF, ArgumentSource::StorageReferenceOperationKind::Consume);
35083582
if (!lvExpr)
35093583
return false;
35103584

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,8 @@ struct MoveOnlyAddressCheckerPImpl {
959959

960960
bool performSingleCheck(MarkMustCheckInst *markedValue);
961961

962-
void insertDestroysOnBoundary(FieldSensitiveMultiDefPrunedLiveRange &liveness,
962+
void insertDestroysOnBoundary(MarkMustCheckInst *markedValue,
963+
FieldSensitiveMultiDefPrunedLiveRange &liveness,
963964
FieldSensitivePrunedLivenessBoundary &boundary);
964965

965966
void rewriteUses(FieldSensitiveMultiDefPrunedLiveRange &liveness,
@@ -1845,10 +1846,23 @@ static void insertDestroyBeforeInstruction(UseState &addressUseState,
18451846
}
18461847

18471848
void MoveOnlyAddressCheckerPImpl::insertDestroysOnBoundary(
1849+
MarkMustCheckInst *markedValue,
18481850
FieldSensitiveMultiDefPrunedLiveRange &liveness,
18491851
FieldSensitivePrunedLivenessBoundary &boundary) {
18501852
using IsInterestingUser = FieldSensitivePrunedLiveness::IsInterestingUser;
18511853
LLVM_DEBUG(llvm::dbgs() << "Inserting destroys on boundary!\n");
1854+
1855+
// If we're in no_consume_or_assign mode, we don't insert destroys, as we've
1856+
// already checked that there are no consumes. There can only be borrow uses,
1857+
// which means no destruction is needed at all.
1858+
if (markedValue->getCheckKind() ==
1859+
MarkMustCheckInst::CheckKind::NoConsumeOrAssign) {
1860+
LLVM_DEBUG(llvm::dbgs()
1861+
<< " Skipping destroy insertion b/c no_consume_or_assign\n");
1862+
consumes.finishRecordingFinalConsumes();
1863+
return;
1864+
}
1865+
18521866
LLVM_DEBUG(llvm::dbgs() << " Visiting users!\n");
18531867

18541868
for (auto &pair : boundary.getLastUsers()) {
@@ -2113,7 +2127,7 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
21132127
SWIFT_DEFER { consumes.clear(); };
21142128
FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements());
21152129
liveness.computeBoundary(boundary);
2116-
insertDestroysOnBoundary(liveness, boundary);
2130+
insertDestroysOnBoundary(markedAddress, liveness, boundary);
21172131
rewriteUses(liveness, boundary);
21182132

21192133
return true;

lib/SILOptimizer/Utils/Devirtualize.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ replaceBeginApplyInst(SILBuilder &builder, SILLocation loc,
559559
auto newYield = newYields[i];
560560
// Insert any end_borrow if the yielded value before the token's uses.
561561
SmallVector<SILInstruction *, 4> users(
562-
makeUserIteratorRange(oldBAI->getTokenResult()->getUses()));
562+
makeUserIteratorRange(oldYield->getUses()));
563563
auto yieldCastRes = castValueToABICompatibleType(
564564
&builder, loc, newYield, newYield->getType(), oldYield->getType(),
565565
users);

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2974,15 +2974,6 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
29742974

29752975
diagnoseIncompatibleProtocolsForMoveOnlyType(CD);
29762976

2977-
// Ban non-final classes from having move only fields.
2978-
if (!CD->isFinal()) {
2979-
for (auto *field : CD->getStoredProperties()) {
2980-
if (field->getType()->isPureMoveOnly()) {
2981-
field->diagnose(
2982-
diag::moveonly_non_final_class_cannot_contain_moveonly_field);
2983-
}
2984-
}
2985-
}
29862977
}
29872978

29882979
void visitProtocolDecl(ProtocolDecl *PD) {

lib/Sema/TypeCheckStorage.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,20 @@ StoredPropertiesAndMissingMembersRequest::evaluate(Evaluator &evaluator,
282282
/// This query is careful not to trigger accessor macro expansion, which
283283
/// creates a cycle. It conservatively assumes that all accessor macros
284284
/// produce computed properties, which is... incorrect.
285+
///
286+
/// The query also avoids triggering a `StorageImplInfoRequest` for patterns
287+
/// involved in a ProtocolDecl, because we know they can never contain storage.
288+
/// For background, vars of noncopyable type have their OpaqueReadOwnership
289+
/// determined by the type of the var decl, but that type hasn't always been
290+
/// determined when this query is made.
285291
static bool mayHaveStorage(Pattern *pattern) {
286-
// Check whether there are any accessor macros.
292+
// Check whether there are any accessor macros, or it's a protocol member.
287293
bool hasAccessorMacros = false;
294+
bool inProtocolDecl = false;
288295
pattern->forEachVariable([&](VarDecl *VD) {
296+
if (isa<ProtocolDecl>(VD->getDeclContext()))
297+
inProtocolDecl = true;
298+
289299
VD->forEachAttachedMacro(MacroRole::Accessor,
290300
[&](CustomAttr *customAttr, MacroDecl *macro) {
291301
hasAccessorMacros = true;
@@ -295,6 +305,10 @@ static bool mayHaveStorage(Pattern *pattern) {
295305
if (hasAccessorMacros)
296306
return false;
297307

308+
// protocol members can never contain storage; avoid triggering request.
309+
if (inProtocolDecl)
310+
return false;
311+
298312
return pattern->hasStorage();
299313
}
300314

@@ -599,9 +613,13 @@ IsSetterMutatingRequest::evaluate(Evaluator &evaluator,
599613
OpaqueReadOwnership
600614
OpaqueReadOwnershipRequest::evaluate(Evaluator &evaluator,
601615
AbstractStorageDecl *storage) const {
602-
return (storage->getAttrs().hasAttribute<BorrowedAttr>()
603-
? OpaqueReadOwnership::Borrowed
604-
: OpaqueReadOwnership::Owned);
616+
if (storage->getAttrs().hasAttribute<BorrowedAttr>())
617+
return OpaqueReadOwnership::Borrowed;
618+
619+
if (storage->getValueInterfaceType()->isPureMoveOnly())
620+
return OpaqueReadOwnership::Borrowed;
621+
622+
return OpaqueReadOwnership::Owned;
605623
}
606624

607625
/// Insert the specified decl into the DeclContext's member list. If the hint

0 commit comments

Comments
 (0)