Skip to content

Commit 511971f

Browse files
authored
Merge pull request swiftlang#83959 from kavon/rdar159079818-subscript-read-nc
silgen: allow borrow of subscript for noncopyables
2 parents 6d20c74 + 16c5c36 commit 511971f

File tree

10 files changed

+270
-21
lines changed

10 files changed

+270
-21
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/SILGenDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,6 +1770,7 @@ void SILGenFunction::emitStmtCondition(StmtCondition Cond, JumpDest FalseDest,
17701770
auto *expr = elt.getBoolean();
17711771
// Evaluate the condition as an i1 value (guaranteed by Sema).
17721772
FullExpr Scope(Cleanups, CleanupLocation(expr));
1773+
FormalEvaluationScope EvalScope(*this);
17731774
booleanTestValue = emitRValue(expr).forwardAsSingleValue(*this, expr);
17741775
booleanTestValue = emitUnwrapIntegerResult(expr, booleanTestValue);
17751776
booleanTestLoc = expr;

lib/SILGen/SILGenExpr.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ void SILGenFunction::emitExprInto(Expr *E, Initialization *I,
412412
return;
413413
}
414414

415+
FormalEvaluationScope writeback(*this);
415416
RValue result = emitRValue(E, SGFContext(I));
416417
if (result.isInContext())
417418
return;
@@ -2792,6 +2793,20 @@ RValue RValueEmitter::visitSubscriptExpr(SubscriptExpr *E, SGFContext C) {
27922793
// Any writebacks for this access are tightly scoped.
27932794
FormalEvaluationScope scope(SGF);
27942795

2796+
// For a noncopyable resulting expression, try to borrow instead.
2797+
if (E->getType()->isNoncopyable()) {
2798+
LValue lv = SGF.emitLValue(E, SGFAccessKind::BorrowedObjectRead);
2799+
ManagedValue mv = SGF.emitRawProjectedLValue(E, std::move(lv));
2800+
2801+
// If we got back a borrow (aka +0) because of a coroutine read
2802+
// accessor, defer the end_apply to the outer evaluation scope.
2803+
// Otherwise, we can end any other formal accesses now.
2804+
if (mv.isPlusZero())
2805+
std::move(scope).deferPop();
2806+
2807+
return RValue(SGF, E, mv);
2808+
}
2809+
27952810
LValue lv = SGF.emitLValue(E, SGFAccessKind::OwnedObjectRead);
27962811
// We can't load at +0 without further analysis, since the formal access into
27972812
// the lvalue will end immediately.
@@ -6143,6 +6158,8 @@ RValue RValueEmitter::visitOptionalEvaluationExpr(OptionalEvaluationExpr *E,
61436158
SmallVector<ManagedValue, 1> results;
61446159
SGF.emitOptionalEvaluation(E, E->getType(), results, C,
61456160
[&](SmallVectorImpl<ManagedValue> &results, SGFContext primaryC) {
6161+
// Generate each result in its own evaluation scope.
6162+
FormalEvaluationScope evalScope(SGF);
61466163
ManagedValue result;
61476164
if (!emitOptimizedOptionalEvaluation(SGF, E, result, primaryC)) {
61486165
result = SGF.emitRValueAsSingleValue(E->getSubExpr(), primaryC);
@@ -6476,7 +6493,7 @@ RValue RValueEmitter::emitForceValue(ForceValueExpr *loc, Expr *E,
64766493
void SILGenFunction::emitOpenExistentialExprImpl(
64776494
OpenExistentialExpr *E,
64786495
llvm::function_ref<void(Expr *)> emitSubExpr) {
6479-
assert(isInFormalEvaluationScope());
6496+
ASSERT(isInFormalEvaluationScope());
64806497

64816498
// Emit the existential value.
64826499
if (E->getExistentialValue()->getType()->is<LValueType>()) {
@@ -6511,7 +6528,14 @@ RValue RValueEmitter::visitOpenExistentialExpr(OpenExistentialExpr *E,
65116528
return RValue(SGF, E, *result);
65126529
}
65136530

6514-
FormalEvaluationScope writebackScope(SGF);
6531+
// Introduce a fresh, nested evaluation scope for Copyable types.
6532+
// This is a bit of a hack, as it should always be up to our caller
6533+
// to establish the right level of formal access scope, in case we
6534+
// formally borrow the opened existential instead of copying it.
6535+
std::optional<FormalEvaluationScope> scope;
6536+
if (!E->getType()->isNoncopyable())
6537+
scope.emplace(SGF);
6538+
65156539
return SGF.emitOpenExistentialExpr<RValue>(E,
65166540
[&](Expr *subExpr) -> RValue {
65176541
return visit(subExpr, C);
@@ -7496,17 +7520,17 @@ void SILGenFunction::emitIgnoredExpr(Expr *E) {
74967520
// arguments.
74977521

74987522
FullExpr scope(Cleanups, CleanupLocation(E));
7523+
FormalEvaluationScope evalScope(*this);
7524+
74997525
if (E->getType()->hasLValueType()) {
75007526
// Emit the l-value, but don't perform an access.
7501-
FormalEvaluationScope scope(*this);
75027527
emitLValue(E, SGFAccessKind::IgnoredRead);
75037528
return;
75047529
}
75057530

75067531
// If this is a load expression, we try hard not to actually do the load
75077532
// (which could materialize a potentially expensive value with cleanups).
75087533
if (auto *LE = dyn_cast<LoadExpr>(E)) {
7509-
FormalEvaluationScope scope(*this);
75107534
LValue lv = emitLValue(LE->getSubExpr(), SGFAccessKind::IgnoredRead);
75117535

75127536
// If loading from the lvalue is guaranteed to have no side effects, we
@@ -7541,7 +7565,6 @@ void SILGenFunction::emitIgnoredExpr(Expr *E) {
75417565
// emit the precondition(s) without having to load the value.
75427566
SmallVector<ForceValueExpr *, 4> forceValueExprs;
75437567
if (auto *LE = findLoadThroughForceValueExprs(E, forceValueExprs)) {
7544-
FormalEvaluationScope scope(*this);
75457568
LValue lv = emitLValue(LE->getSubExpr(), SGFAccessKind::IgnoredRead);
75467569

75477570
ManagedValue value;

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

lib/SILGen/SILGenStmt.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc,
729729
} else {
730730
// SILValue return.
731731
FullExpr scope(Cleanups, CleanupLocation(ret));
732+
FormalEvaluationScope writeback(*this);
732733

733734
// Does the return context require reabstraction?
734735
RValue RV;
@@ -774,7 +775,11 @@ void StmtEmitter::visitThrowStmt(ThrowStmt *S) {
774775
return;
775776
}
776777

777-
ManagedValue exn = SGF.emitRValueAsSingleValue(S->getSubExpr());
778+
ManagedValue exn;
779+
{
780+
FormalEvaluationScope scope(SGF);
781+
exn = SGF.emitRValueAsSingleValue(S->getSubExpr());
782+
}
778783
SGF.emitThrow(S, exn, /* emit a call to willThrow */ true);
779784
}
780785

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]]
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// RUN: %target-swift-emit-silgen -DTRIVIAL %s | %FileCheck --check-prefix TRIVIAL %s
2+
// RUN: %target-swift-emit-sil -DTRIVIAL -sil-verify-all %s > /dev/null
3+
4+
// RUN: %target-swift-emit-silgen -DLOADABLE %s | %FileCheck --check-prefix LOADABLE %s
5+
// RUN: %target-swift-emit-sil -DLOADABLE -sil-verify-all %s > /dev/null
6+
7+
// RUN: %target-swift-emit-silgen -DADDRESS_ONLY %s | %FileCheck --check-prefix ADDRESS_ONLY %s
8+
// RUN: %target-swift-emit-sil -DADDRESS_ONLY -sil-verify-all %s > /dev/null
9+
10+
class X {
11+
let snc = SNC()
12+
}
13+
14+
struct NC: ~Copyable {
15+
#if TRIVIAL
16+
typealias T = Int
17+
var x: T = 0
18+
#elseif LOADABLE
19+
typealias T = X
20+
var x: T = X()
21+
#elseif ADDRESS_ONLY
22+
typealias T = Any
23+
var x: T = X()
24+
#else
25+
#error("pick a mode")
26+
#endif
27+
28+
var consumingGetter: T {
29+
consuming get { fatalError() }
30+
}
31+
var readCoroutine: T {
32+
_read { yield x }
33+
}
34+
consuming func consumingFunc() -> T { fatalError() }
35+
borrowing func borrowingFunc() -> T { fatalError() }
36+
37+
deinit { print("destroy") }
38+
}
39+
40+
struct SNC: ~Copyable {
41+
private var _data: NC = NC()
42+
43+
subscript(index: Int) -> NC {
44+
_read { yield _data }
45+
_modify { yield &_data }
46+
}
47+
}
48+
49+
func use(_ x: NC.T) {}
50+
51+
52+
// TRIVIAL-LABEL: sil hidden [ossa] @${{.*}}test_subscript_read3sncyAA3SNCV_tF : $@convention(thin) (@guaranteed SNC) -> () {
53+
// TRIVIAL: ([[YIELDED:%.*]], [[HANDLE:%.*]]) = begin_apply
54+
// TRIVIAL: [[COPY:%.*]] = copy_value [[YIELDED]]
55+
// TRIVIAL: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[COPY]]
56+
// TRIVIAL: [[BORROW_MARK:%.*]] = begin_borrow [[MARK]]
57+
// TRIVIAL: [[X:%.*]] = struct_extract [[BORROW_MARK]], #NC.x
58+
// TRIVIAL: end_borrow [[BORROW_MARK]]
59+
// TRIVIAL: destroy_value [[MARK]]
60+
// TRIVIAL: = end_apply [[HANDLE]] as $()
61+
// TRIVIAL: = apply {{%.*}}([[X]])
62+
63+
64+
// LOADABLE-LABEL: sil hidden [ossa] @${{.*}}test_subscript_read3sncyAA3SNCV_tF : $@convention(thin) (@guaranteed SNC) -> () {
65+
// LOADABLE: ([[YIELDED:%.*]], [[HANDLE:%.*]]) = begin_apply
66+
// LOADABLE: [[COPY:%.*]] = copy_value [[YIELDED]]
67+
// LOADABLE: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[COPY]]
68+
// LOADABLE: [[BORROW_MARK:%.*]] = begin_borrow [[MARK]]
69+
// LOADABLE: [[X:%.*]] = struct_extract [[BORROW_MARK]], #NC.x
70+
71+
// LOADABLE: [[X_COPY:%.*]] = copy_value [[X]]
72+
73+
// LOADABLE: end_borrow [[BORROW_MARK]]
74+
// LOADABLE: destroy_value [[MARK]]
75+
// LOADABLE: = end_apply [[HANDLE]] as $()
76+
77+
// LOADABLE: = apply {{%.*}}([[X_COPY]])
78+
79+
80+
81+
// ADDRESS_ONLY-LABEL: sil hidden [ossa] @${{.*}}test_subscript_read3sncyAA3SNCV_tF : $@convention(thin) (@in_guaranteed SNC) -> () {
82+
// ADDRESS_ONLY: ([[YIELDED:%.*]], [[HANDLE:%.*]]) = begin_apply
83+
84+
// ADDRESS_ONLY: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[YIELDED]]
85+
// ADDRESS_ONLY: [[X_FIELD_ADDR:%.*]] = struct_element_addr [[MARK]], #NC.x
86+
// ADDRESS_ONLY: [[X_LOCAL_ADDR:%.*]] = alloc_stack $Any
87+
// ADDRESS_ONLY: copy_addr [[X_FIELD_ADDR]] to [init] [[X_LOCAL_ADDR]]
88+
// ADDRESS_ONLY: = end_apply [[HANDLE]] as $()
89+
// ADDRESS_ONLY: = apply {{%.*}}([[X_LOCAL_ADDR]])
90+
// ADDRESS_ONLY: destroy_addr [[X_LOCAL_ADDR]]
91+
// ADDRESS_ONLY: dealloc_stack [[X_LOCAL_ADDR]]
92+
93+
func test_subscript_read(snc: borrowing SNC) {
94+
use(snc[0].x)
95+
}
96+
97+
98+
struct SNC_OPT: ~Copyable {
99+
private var _data: NC? = NC()
100+
101+
subscript(index: Int) -> NC? {
102+
_read { yield _data }
103+
_modify { yield &_data }
104+
}
105+
}
106+
107+
// LOADABLE-LABEL: sil hidden [ossa] @${{.*}}test_subscript_write3sncyAA7SNC_OPTVz_tF
108+
109+
// For the snc[0].take(), ensure the end_apply happens after the call to take.
110+
// LOADABLE: begin_access [modify]
111+
// LOADABLE: begin_apply
112+
// LOADABLE: apply
113+
// LOADABLE: end_apply
114+
// LOADABLE: end_access
115+
116+
// LOADABLE: begin_access [modify]
117+
// LOADABLE: begin_apply
118+
// LOADABLE: assign
119+
// LOADABLE: end_apply
120+
// LOADABLE: end_access
121+
func test_subscript_write(snc: inout SNC_OPT) {
122+
let x = snc[0].take()
123+
snc[0] = x
124+
}

0 commit comments

Comments
 (0)