Skip to content

Commit a72417d

Browse files
committed
Move-only-check the yielded result from read coroutines when they're noncopyable.
Mark the result of starting a read coroutine to be checked by the move-only checker, and then update the pattern matching in the move checker itself so that it recognizes code patterns involving yielding from and receiving yields from read coroutines. Teach move only diagnostics to get the property name for an access through a read coroutine from the referenced declaration.
1 parent 1cb92c0 commit a72417d

File tree

5 files changed

+193
-5
lines changed

5 files changed

+193
-5
lines changed

lib/SILGen/SILGenApply.cpp

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4822,17 +4822,28 @@ namespace {
48224822
/// Cleanup to end a coroutine application.
48234823
class EndCoroutineApply : public Cleanup {
48244824
SILValue ApplyToken;
4825+
std::vector<BeginBorrowInst *> BorrowedMoveOnlyValues;
48254826
public:
48264827
EndCoroutineApply(SILValue applyToken) : ApplyToken(applyToken) {}
48274828

4829+
void setBorrowedMoveOnlyValues(ArrayRef<BeginBorrowInst *> values) {
4830+
BorrowedMoveOnlyValues.insert(BorrowedMoveOnlyValues.end(),
4831+
values.begin(), values.end());
4832+
}
4833+
48284834
void emit(SILGenFunction &SGF, CleanupLocation l, ForUnwind_t forUnwind) override {
4835+
for (auto i = BorrowedMoveOnlyValues.rbegin(), e = BorrowedMoveOnlyValues.rend();
4836+
i != e; ++i) {
4837+
SGF.B.createEndBorrow(l, *i);
4838+
SGF.B.createDestroyValue(l, (*i)->getOperand());
4839+
}
48294840
if (forUnwind) {
48304841
SGF.B.createAbortApply(l, ApplyToken);
48314842
} else {
48324843
SGF.B.createEndApply(l, ApplyToken);
48334844
}
48344845
}
4835-
4846+
48364847
void dump(SILGenFunction &SGF) const override {
48374848
#ifndef NDEBUG
48384849
llvm::errs() << "EndCoroutineApply "
@@ -4897,23 +4908,56 @@ SILGenFunction::emitBeginApply(SILLocation loc, ManagedValue fn,
48974908
auto yieldInfos = substFnType->getYields();
48984909
assert(yieldValues.size() == yieldInfos.size());
48994910
bool useLoweredAddresses = silConv.useLoweredAddresses();
4911+
SmallVector<BeginBorrowInst *, 2> borrowedMoveOnlyValues;
49004912
for (auto i : indices(yieldValues)) {
49014913
auto value = yieldValues[i];
49024914
auto info = yieldInfos[i];
49034915
if (info.isIndirectInOut()) {
4916+
if (value->getType().isMoveOnly()) {
4917+
value = B.createMarkUnresolvedNonCopyableValueInst(loc, value,
4918+
MarkUnresolvedNonCopyableValueInst::CheckKind::AssignableButNotConsumable);
4919+
}
49044920
yields.push_back(ManagedValue::forLValue(value));
49054921
} else if (info.isConsumed()) {
4922+
if (value->getType().isMoveOnly()) {
4923+
value = B.createMarkUnresolvedNonCopyableValueInst(loc, value,
4924+
MarkUnresolvedNonCopyableValueInst::CheckKind::ConsumableAndAssignable);
4925+
}
49064926
!useLoweredAddresses && value->getType().isTrivial(getFunction())
49074927
? yields.push_back(ManagedValue::forRValueWithoutOwnership(value))
49084928
: yields.push_back(emitManagedRValueWithCleanup(value));
49094929
} else if (info.isGuaranteed()) {
4910-
!useLoweredAddresses && value->getType().isTrivial(getFunction())
4911-
? yields.push_back(ManagedValue::forRValueWithoutOwnership(value))
4912-
: yields.push_back(ManagedValue::forBorrowedRValue(value));
4930+
if (value->getType().isMoveOnly()) {
4931+
if (!useLoweredAddresses
4932+
|| !value->getType().isAddressOnly(F)) {
4933+
// The move checker uses the lifetime of the "copy" for borrow checking.
4934+
value = B.createCopyValue(loc, value);
4935+
value = B.createMarkUnresolvedNonCopyableValueInst(loc, value,
4936+
MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign);
4937+
auto borrow = B.createBeginBorrow(loc, value);
4938+
yields.push_back(ManagedValue::forBorrowedRValue(borrow));
4939+
borrowedMoveOnlyValues.push_back(borrow);
4940+
} else {
4941+
value = B.createMarkUnresolvedNonCopyableValueInst(loc, value,
4942+
MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign);
4943+
yields.push_back(ManagedValue::forRValueWithoutOwnership(value));
4944+
}
4945+
} else {
4946+
!useLoweredAddresses && value->getType().isTrivial(getFunction())
4947+
? yields.push_back(ManagedValue::forRValueWithoutOwnership(value))
4948+
: yields.push_back(ManagedValue::forBorrowedRValue(value));
4949+
}
49134950
} else {
4951+
assert(!value->getType().isMoveOnly()
4952+
&& "move-only types shouldn't be trivial");
49144953
yields.push_back(ManagedValue::forRValueWithoutOwnership(value));
49154954
}
49164955
}
4956+
4957+
if (!borrowedMoveOnlyValues.empty()) {
4958+
auto &endApply = static_cast<EndCoroutineApply &>(Cleanups.getCleanup(endApplyHandle));
4959+
endApply.setBorrowedMoveOnlyValues(borrowedMoveOnlyValues);
4960+
}
49174961

49184962
return endApplyHandle;
49194963
}

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,9 @@ void UseState::initializeLiveness(
950950
reinitInstAndValue.second);
951951
}
952952
}
953+
954+
// FIXME: Whether the initial use is an initialization ought to be entirely
955+
// derivable from the CheckKind of the mark instruction.
953956

954957
// Then check if our markedValue is from an argument that is in,
955958
// in_guaranteed, inout, or inout_aliasable, consider the marked address to be
@@ -1048,6 +1051,12 @@ void UseState::initializeLiveness(
10481051
recordInitUse(address, address, liveness.getTopLevelSpan());
10491052
liveness.initializeDef(address, liveness.getTopLevelSpan());
10501053
}
1054+
1055+
if (auto *bai = dyn_cast_or_null<BeginApplyInst>(
1056+
stripAccessMarkers(address->getOperand())->getDefiningInstruction())) {
1057+
recordInitUse(address, address, liveness.getTopLevelSpan());
1058+
liveness.initializeDef(address, liveness.getTopLevelSpan());
1059+
}
10511060

10521061
// Now that we have finished initialization of defs, change our multi-maps
10531062
// from their array form to their map form.

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ static void getVariableNameForValue(SILValue value2,
113113
break;
114114
}
115115

116+
if (auto bai = dyn_cast_or_null<BeginApplyInst>(searchValue->getDefiningInstruction())) {
117+
// Use the name of the property being accessed if we can get to it.
118+
if (isa<FunctionRefBaseInst>(bai->getCallee())
119+
|| isa<MethodInst>(bai->getCallee())) {
120+
variableNamePath.push_back(bai->getCallee()->getDefiningInstruction());
121+
// Try to name the base of the property if this is a method.
122+
if (bai->getSubstCalleeType()->hasSelfParam()) {
123+
searchValue = bai->getSelfArgument();
124+
continue;
125+
} else {
126+
break;
127+
}
128+
}
129+
}
130+
116131
// If we do not do an exact match, see if we can find a debug_var inst. If
117132
// we do, we always break since we have a root value.
118133
if (auto *use = getAnyDebugUse(searchValue)) {
@@ -132,12 +147,25 @@ static void getVariableNameForValue(SILValue value2,
132147
searchValue = cast<SingleValueInstruction>(searchValue)->getOperand(0);
133148
continue;
134149
}
135-
150+
136151
// If we do not pattern match successfully, just set resulting string to
137152
// unknown and return early.
138153
resultingString += "unknown";
139154
return;
140155
}
156+
157+
auto nameFromDecl = [&](Decl *d) -> StringRef {
158+
if (d) {
159+
if (auto accessor = dyn_cast<AccessorDecl>(d)) {
160+
return accessor->getStorage()->getBaseName().userFacingName();
161+
}
162+
if (auto vd = dyn_cast<ValueDecl>(d)) {
163+
return vd->getBaseName().userFacingName();
164+
}
165+
}
166+
167+
return "<unknown decl>";
168+
};
141169

142170
// Walk backwards, constructing our string.
143171
while (true) {
@@ -148,6 +176,16 @@ static void getVariableNameForValue(SILValue value2,
148176
resultingString += i.getName();
149177
} else if (auto i = VarDeclCarryingInst(inst)) {
150178
resultingString += i.getName();
179+
} else if (auto f = dyn_cast<FunctionRefBaseInst>(inst)) {
180+
if (auto dc = f->getInitiallyReferencedFunction()->getDeclContext()) {
181+
resultingString += nameFromDecl(dc->getAsDecl());
182+
} else {
183+
resultingString += "<unknown decl>";
184+
}
185+
} else if (auto m = dyn_cast<MethodInst>(inst)) {
186+
resultingString += nameFromDecl(m->getMember().getDecl());
187+
} else {
188+
resultingString += "<unknown decl>";
151189
}
152190
} else {
153191
auto value = next.get<SILValue>();

lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,11 @@ void MoveOnlyObjectCheckerPImpl::check(DominanceInfo *domTree,
481481
i = copyToMoveOnly;
482482
}
483483

484+
// TODO: Instead of pattern matching specific code generation patterns,
485+
// we should be able to eliminate any `copy_value` whose canonical
486+
// lifetime fits within the borrow scope of the borrowed value being
487+
// copied from.
488+
484489
// Handle:
485490
//
486491
// bb0(%0 : @guaranteed $Type):
@@ -521,6 +526,24 @@ void MoveOnlyObjectCheckerPImpl::check(DominanceInfo *domTree,
521526
}
522527
}
523528
}
529+
530+
// Handle:
531+
// (%yield, ..., %handle) = begin_apply
532+
// %copy = copy_value %yield
533+
// %mark = mark_unresolved_noncopyable_value [no_consume_or_assign] %copy
534+
if (auto bai = dyn_cast_or_null<BeginApplyInst>(i->getOperand(0)->getDefiningInstruction())) {
535+
if (i->getOperand(0)->getOwnershipKind() == OwnershipKind::Guaranteed) {
536+
for (auto *use : markedInst->getConsumingUses()) {
537+
destroys.push_back(cast<DestroyValueInst>(use->getUser()));
538+
}
539+
while (!destroys.empty())
540+
destroys.pop_back_val()->eraseFromParent();
541+
markedInst->replaceAllUsesWith(i->getOperand(0));
542+
markedInst->eraseFromParent();
543+
cvi->eraseFromParent();
544+
continue;
545+
}
546+
}
524547
}
525548
}
526549
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// RUN: %target-swift-frontend -DADDRESS_ONLY -emit-sil -verify %s
2+
// RUN: %target-swift-frontend -DLOADABLE -emit-sil -verify %s
3+
// RUN: %target-swift-frontend -DTRIVIAL -emit-sil -verify %s
4+
// RUN: %target-swift-frontend -DEMPTY -emit-sil -verify %s
5+
6+
class X {}
7+
8+
struct NC: ~Copyable {
9+
#if EMPTY
10+
#elseif TRIVIAL
11+
var x: Int = 0
12+
#elseif LOADABLE
13+
var x: X = X()
14+
#elseif ADDRESS_ONLY
15+
var x: Any = X()
16+
#else
17+
#error("pick a mode")
18+
#endif
19+
deinit { print("destroy") }
20+
}
21+
22+
struct S {
23+
var data: NC {
24+
_read { yield NC() }
25+
}
26+
}
27+
28+
struct SNC: ~Copyable {
29+
private var _data: NC = NC()
30+
31+
var data: NC {
32+
_read { yield _data }
33+
}
34+
}
35+
36+
class C {
37+
private var _data: NC = NC()
38+
39+
var data: NC {
40+
_read { yield _data }
41+
}
42+
}
43+
44+
protocol P {
45+
@_borrowed
46+
var data: NC { get }
47+
}
48+
49+
func borrow(_ nc: borrowing NC) {}
50+
func take(_ nc: consuming NC) {}
51+
52+
func test(c: C) {
53+
borrow(c.data)
54+
take(c.data) // expected-error{{'c.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
55+
}
56+
func test(s: S) {
57+
borrow(s.data)
58+
take(s.data) // expected-error{{'s.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
59+
}
60+
func test(snc: borrowing SNC) {
61+
borrow(snc.data)
62+
take(snc.data) // expected-error{{'snc.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
63+
}
64+
65+
// FIXME: get the actual variable name instead of 'unknown' in these cases.
66+
func test<T: P>(t: T) {
67+
borrow(t.data)
68+
take(t.data) // expected-error{{'unknown.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
69+
}
70+
func test(p: P) {
71+
borrow(p.data)
72+
take(p.data) // expected-error{{'unknown.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
73+
}
74+

0 commit comments

Comments
 (0)