Skip to content

Commit eca40f3

Browse files
committed
SILGen: Emit the base of a read coroutine as a borrow in a yield/borrow/noncopyable context.
1 parent 88192ff commit eca40f3

File tree

6 files changed

+191
-45
lines changed

6 files changed

+191
-45
lines changed

lib/SILGen/SILGen.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -418,12 +418,6 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
418418
/// Emit a global initialization.
419419
void emitGlobalInitialization(PatternBindingDecl *initializer, unsigned elt);
420420

421-
/// Should the self argument of the given method always be emitted as
422-
/// an r-value (meaning that it can be borrowed only if that is not
423-
/// semantically detectable), or it acceptable to emit it as a borrowed
424-
/// storage reference?
425-
bool shouldEmitSelfAsRValue(FuncDecl *method, CanType selfType);
426-
427421
/// Is the self method of the given nonmutating method passed indirectly?
428422
bool isNonMutatingSelfIndirect(SILDeclRef method);
429423

lib/SILGen/SILGenApply.cpp

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5455,22 +5455,6 @@ CallEmission CallEmission::forApplyExpr(SILGenFunction &SGF, ApplyExpr *e) {
54555455
return emission;
54565456
}
54575457

5458-
bool SILGenModule::shouldEmitSelfAsRValue(FuncDecl *fn, CanType selfType) {
5459-
if (fn->isStatic())
5460-
return true;
5461-
5462-
switch (fn->getSelfAccessKind()) {
5463-
case SelfAccessKind::Mutating:
5464-
return false;
5465-
case SelfAccessKind::NonMutating:
5466-
case SelfAccessKind::LegacyConsuming:
5467-
case SelfAccessKind::Consuming:
5468-
case SelfAccessKind::Borrowing:
5469-
return true;
5470-
}
5471-
llvm_unreachable("bad self-access kind");
5472-
}
5473-
54745458
bool SILGenModule::isNonMutatingSelfIndirect(SILDeclRef methodRef) {
54755459
auto method = methodRef.getFuncDecl();
54765460
if (method->isStatic())

lib/SILGen/SILGenFunction.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ enum class SGFAccessKind : uint8_t {
111111
/// The access is a read that would prefer the address of a borrowed value.
112112
/// This should only be used when it is semantically acceptable to borrow
113113
/// the value, not just because the caller would benefit from a borrowed
114-
/// value. See shouldEmitSelfAsRValue.
114+
/// value. See shouldEmitSelfAsRValue in SILGenLValue.cpp.
115115
///
116116
/// The caller will be calling emitAddressOfLValue or emitLoadOfLValue
117117
/// on the l-value. The latter may be less efficient than an access
@@ -121,7 +121,7 @@ enum class SGFAccessKind : uint8_t {
121121
/// The access is a read that would prefer a loaded borrowed value.
122122
/// This should only be used when it is semantically acceptable to borrow
123123
/// the value, not just because the caller would benefit from a borrowed
124-
/// value. See shouldEmitSelfAsRValue.
124+
/// value. See shouldEmitSelfAsRValue in SILGenLValue.cpp.
125125
///
126126
/// There isn't yet a way to emit the access that takes advantage of this.
127127
BorrowedObjectRead,

lib/SILGen/SILGenLValue.cpp

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,8 @@ static SGFAccessKind getBaseAccessKind(SILGenModule &SGM,
13381338
AbstractStorageDecl *member,
13391339
SGFAccessKind accessKind,
13401340
AccessStrategy strategy,
1341-
CanType baseFormalType);
1341+
CanType baseFormalType,
1342+
bool forBorrowExpr);
13421343

13431344
namespace {
13441345
/// A helper class for implementing components that involve accessing
@@ -1977,7 +1978,8 @@ namespace {
19771978
if (!base) return LValue();
19781979
auto baseAccessKind =
19791980
getBaseAccessKind(SGF.SGM, Storage, accessKind, strategy,
1980-
BaseFormalType);
1981+
BaseFormalType,
1982+
/*for borrow*/ false);
19811983
return LValue::forValue(baseAccessKind, base, BaseFormalType);
19821984
}();
19831985

@@ -3055,7 +3057,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor
30553057
auto baseFormalType = getBaseFormalType(e->getBase());
30563058
LValue lv = visit(
30573059
e->getBase(),
3058-
getBaseAccessKind(SGF.SGM, var, accessKind, strategy, baseFormalType),
3060+
getBaseAccessKind(SGF.SGM, var, accessKind, strategy, baseFormalType,
3061+
/*for borrow*/ true),
30593062
getBaseOptions(options, strategy));
30603063
llvm::Optional<ActorIsolation> actorIso;
30613064
if (e->isImplicitlyAsync())
@@ -3724,14 +3727,50 @@ LValue SILGenLValue::visitDotSyntaxBaseIgnoredExpr(DotSyntaxBaseIgnoredExpr *e,
37243727
return visitRec(e->getRHS(), accessKind, options);
37253728
}
37263729

3730+
/// Should the self argument of the given method always be emitted as
3731+
/// an r-value (meaning that it can be borrowed only if that is not
3732+
/// semantically detectable), or it acceptable to emit it as a borrowed
3733+
/// storage reference?
3734+
static bool shouldEmitSelfAsRValue(AccessorDecl *fn, CanType selfType,
3735+
bool forBorrowExpr) {
3736+
if (fn->isStatic())
3737+
return true;
3738+
3739+
switch (fn->getSelfAccessKind()) {
3740+
case SelfAccessKind::Mutating:
3741+
return false;
3742+
case SelfAccessKind::Borrowing:
3743+
case SelfAccessKind::NonMutating:
3744+
// If the accessor is a coroutine, we may want to access the projected
3745+
// value through a borrow of the base. But if it's a regular get/set then
3746+
// there isn't any real benefit to doing so.
3747+
if (!fn->isCoroutine()) {
3748+
return true;
3749+
}
3750+
// Normally we'll copy the base to minimize accesses. But if the base
3751+
// is noncopyable, or we're accessing it in a `borrow` expression, then
3752+
// we want to keep the access nested on the original base.
3753+
if (forBorrowExpr || selfType->isNoncopyable()) {
3754+
return false;
3755+
}
3756+
return true;
3757+
3758+
case SelfAccessKind::LegacyConsuming:
3759+
case SelfAccessKind::Consuming:
3760+
return true;
3761+
}
3762+
llvm_unreachable("bad self-access kind");
3763+
}
3764+
37273765
static SGFAccessKind getBaseAccessKindForAccessor(SILGenModule &SGM,
37283766
AccessorDecl *accessor,
3729-
CanType baseFormalType) {
3767+
CanType baseFormalType,
3768+
bool forBorrowExpr) {
37303769
if (accessor->isMutating())
37313770
return SGFAccessKind::ReadWrite;
37323771

37333772
auto declRef = SGM.getAccessorDeclRef(accessor, ResilienceExpansion::Minimal);
3734-
if (SGM.shouldEmitSelfAsRValue(accessor, baseFormalType)) {
3773+
if (shouldEmitSelfAsRValue(accessor, baseFormalType, forBorrowExpr)) {
37353774
return SGM.isNonMutatingSelfIndirect(declRef)
37363775
? SGFAccessKind::OwnedAddressRead
37373776
: SGFAccessKind::OwnedObjectRead;
@@ -3755,7 +3794,8 @@ static SGFAccessKind getBaseAccessKind(SILGenModule &SGM,
37553794
AbstractStorageDecl *member,
37563795
SGFAccessKind accessKind,
37573796
AccessStrategy strategy,
3758-
CanType baseFormalType) {
3797+
CanType baseFormalType,
3798+
bool forBorrowExpr) {
37593799
switch (strategy.getKind()) {
37603800
case AccessStrategy::Storage:
37613801
return getBaseAccessKindForStorage(accessKind);
@@ -3764,7 +3804,8 @@ static SGFAccessKind getBaseAccessKind(SILGenModule &SGM,
37643804
assert(accessKind == SGFAccessKind::ReadWrite);
37653805
auto writeBaseKind = getBaseAccessKind(SGM, member, SGFAccessKind::Write,
37663806
strategy.getWriteStrategy(),
3767-
baseFormalType);
3807+
baseFormalType,
3808+
/*for borrow*/ false);
37683809

37693810
// Fast path for the common case that the write will need to mutate
37703811
// the base.
@@ -3774,7 +3815,8 @@ static SGFAccessKind getBaseAccessKind(SILGenModule &SGM,
37743815
auto readBaseKind = getBaseAccessKind(SGM, member,
37753816
SGFAccessKind::OwnedAddressRead,
37763817
strategy.getReadStrategy(),
3777-
baseFormalType);
3818+
baseFormalType,
3819+
/*for borrow*/ false);
37783820

37793821
// If they're the same kind, just use that.
37803822
if (readBaseKind == writeBaseKind)
@@ -3793,7 +3835,8 @@ static SGFAccessKind getBaseAccessKind(SILGenModule &SGM,
37933835
case AccessStrategy::DispatchToAccessor:
37943836
case AccessStrategy::DispatchToDistributedThunk: {
37953837
auto accessor = member->getOpaqueAccessor(strategy.getAccessor());
3796-
return getBaseAccessKindForAccessor(SGM, accessor, baseFormalType);
3838+
return getBaseAccessKindForAccessor(SGM, accessor, baseFormalType,
3839+
forBorrowExpr);
37973840
}
37983841
}
37993842
llvm_unreachable("bad access strategy");
@@ -3870,7 +3913,8 @@ LValue SILGenLValue::visitMemberRefExpr(MemberRefExpr *e,
38703913

38713914
LValue lv = visitRec(e->getBase(),
38723915
getBaseAccessKind(SGF.SGM, var, accessKind, strategy,
3873-
getBaseFormalType(e->getBase())),
3916+
getBaseFormalType(e->getBase()),
3917+
/* for borrow */ false),
38743918
getBaseOptions(options, strategy));
38753919
assert(lv.isValid());
38763920

@@ -4075,7 +4119,8 @@ LValue SILGenLValue::visitSubscriptExpr(SubscriptExpr *e,
40754119

40764120
LValue lv = visitRec(e->getBase(),
40774121
getBaseAccessKind(SGF.SGM, decl, accessKind, strategy,
4078-
getBaseFormalType(e->getBase())),
4122+
getBaseFormalType(e->getBase()),
4123+
/*for borrow*/ false),
40794124
getBaseOptions(options, strategy));
40804125
assert(lv.isValid());
40814126

@@ -4432,7 +4477,8 @@ LValue SILGenFunction::emitPropertyLValue(SILLocation loc, ManagedValue base,
44324477
F.getResilienceExpansion());
44334478

44344479
auto baseAccessKind =
4435-
getBaseAccessKind(SGM, ivar, accessKind, strategy, baseFormalType);
4480+
getBaseAccessKind(SGM, ivar, accessKind, strategy, baseFormalType,
4481+
/*for borrow*/ false);
44364482

44374483
LValueTypeData baseTypeData =
44384484
getValueTypeData(baseAccessKind, baseFormalType, base.getValue());
@@ -5155,7 +5201,8 @@ RValue SILGenFunction::emitRValueForStorageLoad(
51555201
if (!base) return LValue();
51565202

51575203
auto baseAccess = getBaseAccessKind(SGM, storage, accessKind,
5158-
strategy, baseFormalType);
5204+
strategy, baseFormalType,
5205+
/*for borrow*/ false);
51595206
return LValue::forValue(baseAccess, base, baseFormalType);
51605207
}();
51615208

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// RUN: %target-swift-frontend -enable-experimental-feature NoncopyableGenerics -enable-experimental-feature BorrowingSwitch -enable-experimental-feature MoveOnlyPartialConsumption -parse-as-library -O -emit-sil -verify %s
2+
3+
extension List {
4+
var peek: Element {
5+
_read {
6+
// TODO: fix move-only checker induced ownership bug when
7+
// we try to switch over `self.head` here.
8+
yield head.peek
9+
}
10+
}
11+
}
12+
13+
struct MyPointer<Wrapped: ~Copyable>: Copyable {
14+
var v: UnsafeMutablePointer<Int>
15+
16+
static func allocate(capacity: Int) -> Self {
17+
fatalError()
18+
}
19+
20+
func initialize(to: consuming Wrapped) {
21+
}
22+
func deinitialize(count: Int) {
23+
}
24+
func deallocate() {
25+
}
26+
func move() -> Wrapped {
27+
fatalError()
28+
}
29+
30+
var pointee: Wrapped {
31+
_read { fatalError() }
32+
}
33+
}
34+
35+
struct Box<Wrapped: ~Copyable>: ~Copyable {
36+
private let _pointer: MyPointer<Wrapped>
37+
38+
init(_ element: consuming Wrapped) {
39+
_pointer = .allocate(capacity: 1)
40+
print("allocatin", _pointer)
41+
_pointer.initialize(to: element)
42+
}
43+
44+
deinit {
45+
print("not deallocatin", _pointer)
46+
_pointer.deinitialize(count: 1)
47+
_pointer.deallocate()
48+
}
49+
50+
consuming func move() -> Wrapped {
51+
let wrapped = _pointer.move()
52+
print("deallocatin", _pointer)
53+
_pointer.deallocate()
54+
discard self
55+
return wrapped
56+
}
57+
58+
var wrapped: Wrapped {
59+
_read { yield _pointer.pointee }
60+
}
61+
}
62+
63+
struct List<Element>: ~Copyable {
64+
var head: Link<Element> = .empty
65+
var bool = false
66+
}
67+
68+
enum Link<Element>: ~Copyable {
69+
case empty
70+
case more(Box<Node<Element>>)
71+
72+
var peek: Element {
73+
_read {
74+
switch self {
75+
case .empty: fatalError()
76+
case .more(_borrowing box):
77+
yield box.wrapped.element
78+
}
79+
}
80+
}
81+
}
82+
83+
struct Node<Element>: ~Copyable {
84+
let element: Element
85+
let next: Link<Element>
86+
var bool = true
87+
}
88+
89+
extension List {
90+
mutating func append(_ element: consuming Element) {
91+
self = List(head: .more(Box(Node(element: element, next: self.head))))
92+
}
93+
94+
var isEmpty: Bool {
95+
switch self.head {
96+
case .empty: true
97+
default: false
98+
}
99+
}
100+
101+
mutating func pop() -> Element {
102+
let h = self.head
103+
switch h {
104+
case .empty: fatalError()
105+
case .more(let box):
106+
let node = box.move()
107+
self = .init(head: node.next)
108+
return node.element
109+
}
110+
}
111+
112+
}
113+
114+
@main
115+
struct Main {
116+
static func main() {
117+
var l: List<Int> = .init()
118+
l.append(1)
119+
l.append(2)
120+
l.append(3)
121+
print(l.pop())
122+
print(l.pop())
123+
print(l.pop())
124+
print(l.isEmpty)
125+
}
126+
}

test/SILOptimizer/moveonly_consuming_switch.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
// RUN: %target-swift-frontend -emit-sil -enable-experimental-feature NoncopyableGenerics -enable-experimental-feature BorrowingSwitch -enable-experimental-feature MoveOnlyPartialConsumption -verify %s
2+
3+
// TODO: Remove this and just use the real `UnsafeMutablePointer` when
4+
// noncopyable type support has been upstreamed.
25
struct MyPointer<Wrapped: ~Copyable>: Copyable {
36
var v: UnsafeMutablePointer<Int>
47

@@ -41,14 +44,6 @@ struct Box<Wrapped: ~Copyable>: ~Copyable {
4144
}
4245
}
4346

44-
45-
46-
47-
48-
49-
50-
51-
5247
enum List<Element: ~Copyable>: ~Copyable {
5348
case empty
5449
case node(Element, Box<List>)

0 commit comments

Comments
 (0)