Skip to content

Commit 906a4cb

Browse files
committed
silgen: allow borrow of subscript for noncopyables
If a subscript uses a read accessor to yield a noncopyable value, we'd emit an `end_apply` that's too tightly scoped to allow for a subsequent borrowing access on the yielded value. resolves rdar://159079818
1 parent b444a0b commit 906a4cb

File tree

7 files changed

+125
-15
lines changed

7 files changed

+125
-15
lines changed

lib/SILGen/FormalEvaluation.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,20 @@ void FormalEvaluationScope::verify() const {
195195
}
196196
}
197197

198+
void FormalEvaluationScope::deferPop() && {
199+
ASSERT(previous && "no previous scope to defer the pop for!");
200+
auto &context = SGF.FormalEvalContext;
201+
202+
// Remove ourselves as the innermost scope of the chain.
203+
ASSERT(context.innermostScope == this &&
204+
"popping formal-evaluation scopes out of order");
205+
context.innermostScope = previous;
206+
207+
// Clear-out our saved depth to deactivate our pop-on-deinit.
208+
savedDepth.reset();
209+
}
210+
211+
198212
//===----------------------------------------------------------------------===//
199213
// Formal Evaluation Context
200214
//===----------------------------------------------------------------------===//

lib/SILGen/FormalEvaluation.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,14 @@ class FormalEvaluationScope {
224224
savedDepth.reset();
225225
}
226226

227+
/// Defers the emission of clean-ups in this scope until the
228+
/// immediate outer evaluation scope is popped.
229+
///
230+
/// This is useful for getting-around tightly-nested formal
231+
/// access scopes, when a borrow of a value needs to be
232+
/// returned and copying the value is not an option.
233+
void deferPop() &&;
234+
227235
FormalEvaluationScope(const FormalEvaluationScope &) = delete;
228236
FormalEvaluationScope &operator=(const FormalEvaluationScope &) = delete;
229237

lib/SILGen/SILGenExpr.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2792,6 +2792,20 @@ RValue RValueEmitter::visitSubscriptExpr(SubscriptExpr *E, SGFContext C) {
27922792
// Any writebacks for this access are tightly scoped.
27932793
FormalEvaluationScope scope(SGF);
27942794

2795+
// For a noncopyable resulting expression, try to borrow instead.
2796+
if (E->getType()->isNoncopyable()) {
2797+
LValue lv = SGF.emitLValue(E, SGFAccessKind::BorrowedObjectRead);
2798+
ManagedValue mv = SGF.emitRawProjectedLValue(E, std::move(lv));
2799+
2800+
// If we got back a borrow (aka +0) because of a coroutine read
2801+
// accessor, defer the end_apply to the outer evaluation scope.
2802+
// Otherwise, we can end any other formal accesses now.
2803+
if (mv.isPlusZero())
2804+
std::move(scope).deferPop();
2805+
2806+
return RValue(SGF, E, mv);
2807+
}
2808+
27952809
LValue lv = SGF.emitLValue(E, SGFAccessKind::OwnedObjectRead);
27962810
// We can't load at +0 without further analysis, since the formal access into
27972811
// the lvalue will end immediately.

lib/SILGen/SILGenFunction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,6 +2207,12 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
22072207
TSanKind tsanKind = TSanKind::None);
22082208
ManagedValue emitConsumedLValue(SILLocation loc, LValue &&src,
22092209
TSanKind tsanKind = TSanKind::None);
2210+
/// Simply projects the LValue as a ManagedValue, allowing the caller
2211+
/// to check invariants and make adjustments manually.
2212+
///
2213+
/// When in doubt, reach for emit[Borrowed|Consumed|etc]LValue.
2214+
ManagedValue emitRawProjectedLValue(SILLocation loc, LValue &&src,
2215+
TSanKind tsanKind = TSanKind::None);
22102216
LValue emitOpenExistentialLValue(SILLocation loc,
22112217
LValue &&existentialLV,
22122218
CanArchetypeType openedArchetype,

lib/SILGen/SILGenLValue.cpp

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5587,11 +5587,8 @@ ManagedValue SILGenFunction::emitAddressOfLValue(SILLocation loc,
55875587
src.getAccessKind() == SGFAccessKind::OwnedAddressConsume ||
55885588
src.getAccessKind() == SGFAccessKind::ReadWrite);
55895589

5590-
ManagedValue addr;
5591-
PathComponent &&component =
5592-
drillToLastComponent(loc, std::move(src), addr, tsanKind);
5590+
ManagedValue addr = emitRawProjectedLValue(loc, std::move(src), tsanKind);
55935591

5594-
addr = drillIntoComponent(*this, loc, std::move(component), addr, tsanKind);
55955592
assert(addr.getType().isAddress() &&
55965593
"resolving lvalue did not give an address");
55975594
return ManagedValue::forLValue(addr.getValue());
@@ -5605,12 +5602,7 @@ ManagedValue SILGenFunction::emitBorrowedLValue(SILLocation loc,
56055602
src.getAccessKind() == SGFAccessKind::BorrowedObjectRead);
56065603
bool isIgnored = src.getAccessKind() == SGFAccessKind::IgnoredRead;
56075604

5608-
ManagedValue base;
5609-
PathComponent &&component =
5610-
drillToLastComponent(loc, std::move(src), base, tsanKind);
5611-
5612-
auto value =
5613-
drillIntoComponent(*this, loc, std::move(component), base, tsanKind);
5605+
auto value = emitRawProjectedLValue(loc, std::move(src), tsanKind);
56145606

56155607
// If project() returned an owned value, and the caller cares, borrow it.
56165608
if (value.hasCleanup() && !isIgnored)
@@ -5621,15 +5613,20 @@ ManagedValue SILGenFunction::emitBorrowedLValue(SILLocation loc,
56215613
ManagedValue SILGenFunction::emitConsumedLValue(SILLocation loc, LValue &&src,
56225614
TSanKind tsanKind) {
56235615
assert(isConsumeAccess(src.getAccessKind()));
5616+
return emitRawProjectedLValue(loc, std::move(src), tsanKind);
5617+
}
56245618

5619+
ManagedValue SILGenFunction::emitRawProjectedLValue(SILLocation loc,
5620+
LValue &&src,
5621+
TSanKind tsanKind) {
56255622
ManagedValue base;
56265623
PathComponent &&component =
5627-
drillToLastComponent(loc, std::move(src), base, tsanKind);
5624+
drillToLastComponent(loc, std::move(src), base, tsanKind);
56285625

5629-
auto value =
5630-
drillIntoComponent(*this, loc, std::move(component), base, tsanKind);
5626+
ManagedValue result =
5627+
drillIntoComponent(*this, loc, std::move(component), base, tsanKind);
56315628

5632-
return value;
5629+
return result;
56335630
}
56345631

56355632
LValue

test/SILGen/moveonly.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,9 +982,13 @@ public struct LoadableSubscriptGetOnlyTester : ~Copyable {
982982
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]]
983983
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[ACCESS]]
984984
// CHECK: [[LOAD:%.*]] = load_borrow [unchecked] [[MARK]]
985-
// CHECK: apply {{%.*}}([[M2_PROJECT]], {{%.*}}, [[LOAD]])
985+
// CHECK: [[STACK_ALLOC:%.*]] = alloc_stack $AddressOnlyProtocol
986+
// CHECK: [[M_STACK_ALLOC:%.*]] = mark_unresolved_non_copyable_value [consumable_and_assignable] [[STACK_ALLOC]]
987+
// CHECK: apply {{%.*}}([[M_STACK_ALLOC]], {{%.*}}, [[LOAD]])
986988
// CHECK: end_borrow [[LOAD]]
987989
// CHECK: end_access [[ACCESS]]
990+
// CHECK: copy_addr [take] [[M_STACK_ALLOC]] to [init] [[M2_PROJECT]]
991+
// CHECK: dealloc_stack [[STACK_ALLOC]]
988992
// CHECK: [[M2_MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[M2_PROJECT]]
989993
// CHECK: end_borrow [[M2_BORROW]]
990994
// CHECK: destroy_value [[M2_BOX]]

test/SILOptimizer/moveonly_read_coroutine.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66
class X {}
77

88
struct NC: ~Copyable {
9+
var field: Int = 0
10+
var consumingGetter: Int {
11+
consuming get { return 0 }
12+
}
13+
var readCoroutine: Int {
14+
_read { yield field }
15+
}
16+
consuming func consumingFunc() -> Int { return 0 }
17+
borrowing func borrowingFunc() -> Int { return 0 }
18+
919
#if EMPTY
1020
#elseif TRIVIAL
1121
var x: Int = 0
@@ -23,6 +33,10 @@ struct S {
2333
var data: NC {
2434
_read { yield NC() }
2535
}
36+
37+
subscript(index: Int) -> NC {
38+
_read { yield NC() }
39+
}
2640
}
2741

2842
struct SNC: ~Copyable {
@@ -31,6 +45,10 @@ struct SNC: ~Copyable {
3145
var data: NC {
3246
_read { yield _data }
3347
}
48+
49+
subscript(index: Int) -> NC {
50+
_read { yield _data }
51+
}
3452
}
3553

3654
class C {
@@ -39,35 +57,84 @@ class C {
3957
var data: NC {
4058
_read { yield _data }
4159
}
60+
61+
subscript(index: Int) -> NC {
62+
_read { yield _data }
63+
}
4264
}
4365

4466
protocol P {
4567
@_borrowed
4668
var data: NC { get }
69+
70+
@_borrowed
71+
subscript(index: Int) -> NC { get }
4772
}
4873

4974
func borrow(_ nc: borrowing NC) {}
5075
func take(_ nc: consuming NC) {}
5176

77+
func assume(_ b: Bool) { precondition(b, "uh oh") }
78+
5279
func test(c: C) {
5380
borrow(c.data)
5481
take(c.data) // expected-error{{'c.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
82+
83+
borrow(c[0])
84+
assume(c[0].field == 0)
85+
assume(c[0].borrowingFunc() == 0)
86+
assume(c[0].readCoroutine == 0)
87+
// take(c[0]) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
88+
assume(c[0].consumingGetter == 0) // expected-error {{'c.subscript' is borrowed and cannot be consumed}} expected-note {{consumed here}}
89+
// assume(c[0].consumingFunc() == 0) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
5590
}
5691
func test(s: S) {
5792
borrow(s.data)
5893
take(s.data) // expected-error{{'s.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
94+
95+
borrow(s[0])
96+
assume(s[0].field == 0)
97+
assume(s[0].borrowingFunc() == 0)
98+
assume(s[0].readCoroutine == 0)
99+
// take(s[0]) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
100+
assume(s[0].consumingGetter == 0) // expected-error {{'s.subscript' is borrowed and cannot be consumed}} expected-note {{consumed here}}
101+
// assume(s[0].consumingFunc() == 0) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
59102
}
60103
func test(snc: borrowing SNC) {
61104
borrow(snc.data)
62105
take(snc.data) // expected-error{{'snc.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
106+
107+
borrow(snc[0])
108+
assume(snc[0].field == 0)
109+
assume(snc[0].borrowingFunc() == 0)
110+
assume(snc[0].readCoroutine == 0)
111+
// take(snc[0]) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
112+
assume(snc[0].consumingGetter == 0) // expected-error {{'snc.subscript' is borrowed and cannot be consumed}} expected-note {{consumed here}}
113+
// assume(snc[0].consumingFunc() == 0) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
63114
}
64115

65116
func test<T: P>(t: T) {
66117
borrow(t.data)
67118
take(t.data) // expected-error{{'t.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
119+
120+
borrow(t[0])
121+
assume(t[0].field == 0)
122+
assume(t[0].borrowingFunc() == 0)
123+
assume(t[0].readCoroutine == 0)
124+
// take(t[0]) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
125+
assume(t[0].consumingGetter == 0) // expected-error {{'t.subscript' is borrowed and cannot be consumed}} expected-note {{consumed here}}
126+
// assume(t[0].consumingFunc() == 0) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
68127
}
69128
func test(p: P) {
70129
borrow(p.data)
71130
take(p.data) // expected-error{{'p.data' is borrowed and cannot be consumed}} expected-note{{consumed here}}
131+
132+
borrow(p[0])
133+
assume(p[0].field == 0)
134+
assume(p[0].borrowingFunc() == 0)
135+
assume(p[0].readCoroutine == 0)
136+
// take(p[0]) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
137+
assume(p[0].consumingGetter == 0) // expected-error {{'p.subscript' is borrowed and cannot be consumed}} expected-note {{consumed here}}
138+
// assume(p[0].consumingFunc() == 0) // FIXME: the move-checker's diagnostics produced for this code are non-deterministic?
72139
}
73140

0 commit comments

Comments
 (0)