Skip to content

Commit f3ef432

Browse files
committed
SILGen: Avoid redundant materialization when reabstracting a property base from loadable to in-memory.
The code here was materializing the value in this case, but then re-loading it, which is unnecessary since call emission will materialize the value to match the callee's calling convention already. It does look like a copy into formal evaluation scope is necessary to get the correct lifetimes in some circumstances. The move-only checker doesn't like any additional copies at all because it thinks they're consumes, so only borrow in the case where the value is move-only.
1 parent 4bd466a commit f3ef432

File tree

3 files changed

+67
-13
lines changed

3 files changed

+67
-13
lines changed

lib/SILGen/SILGenApply.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6524,9 +6524,9 @@ ArgumentSource AccessorBaseArgPreparer::prepareAccessorObjectBaseArg() {
65246524
if (selfParam.isConsumed() && !base.hasCleanup()) {
65256525
base = base.copyUnmanaged(SGF, loc);
65266526
}
6527-
6528-
// If the parameter is indirect, we need to drop the value into
6529-
// temporary memory.
6527+
// If the parameter is indirect, we'll need to drop the value into
6528+
// temporary memory. Make a copy scoped to the current formal access that
6529+
// we can materialize later.
65306530
if (SGF.silConv.isSILIndirect(selfParam)) {
65316531
// It's a really bad idea to materialize when we're about to
65326532
// pass a value to an inout argument, because it's a really easy
@@ -6536,14 +6536,17 @@ ArgumentSource AccessorBaseArgPreparer::prepareAccessorObjectBaseArg() {
65366536
// itself).
65376537
assert(!selfParam.isIndirectMutating() &&
65386538
"passing unmaterialized r-value as inout argument");
6539-
6540-
base = base.formallyMaterialize(SGF, loc);
6541-
auto shouldTake = IsTake_t(base.hasCleanup());
6542-
base = SGF.emitFormalAccessLoad(loc, base.forward(SGF),
6543-
SGF.getTypeLowering(baseLoweredType),
6544-
SGFContext(), shouldTake);
6539+
6540+
// Avoid copying the base if it's move-only. It should be OK to do in this
6541+
// case since the move-only base will be accessed in a formal access scope
6542+
// as well. This isn't always true for copyable bases so be less aggressive
6543+
// in that case.
6544+
if (base.getType().isMoveOnly()) {
6545+
base = base.formalAccessBorrow(SGF, loc);
6546+
} else {
6547+
base = base.formalAccessCopy(SGF, loc);
6548+
}
65456549
}
6546-
65476550
return ArgumentSource(loc, RValue(SGF, loc, baseFormalType, base));
65486551
}
65496552

test/SILGen/cf_members.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,8 @@ public func foo(_ x: Double) {
124124
// CHECK: [[READ:%.*]] = begin_access [read] [unknown] [[Z]] : $*Struct1
125125
// CHECK: [[ZVAL:%.*]] = load [trivial] [[READ]]
126126
// CHECK: store [[ZVAL]] to [trivial] [[ZTMP:%.*]] :
127-
// CHECK: [[ZVAL_2:%.*]] = load [trivial] [[ZTMP]]
128-
// CHECK: store [[ZVAL_2]] to [trivial] [[ZTMP_2:%.*]] :
129127
// CHECK: [[GET:%.*]] = function_ref @IAMStruct1GetRadius : $@convention(c) (@in Struct1) -> Double
130-
// CHECK: apply [[GET]]([[ZTMP_2]])
128+
// CHECK: apply [[GET]]([[ZTMP]])
131129
_ = z.radius
132130
// CHECK: [[READ:%.*]] = begin_access [read] [unknown] [[Z]] : $*Struct1
133131
// CHECK: [[ZVAL:%.*]] = load [trivial] [[READ]]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// RUN: %target-swift-frontend -emit-sil -verify -enable-library-evolution -enable-experimental-feature MoveOnlyResilientTypes %s
2+
3+
// Verify that call sequences that require reabstracting a noncopyable value
4+
// from a loadable representation to an in-memory one are properly allowed by
5+
// the move-only checker.
6+
7+
public struct Foo: ~Copyable {
8+
private var x: String = ""
9+
10+
public var isEmpty: Bool {
11+
@_silgen_name("get_isEmpty") get
12+
}
13+
}
14+
15+
public struct Bar: ~Copyable {
16+
public var x = Foo()
17+
18+
public consuming func foo() {
19+
// `Foo` is internally a loadable type, but it's public with library
20+
// evolution enabled, so public members like `isEmpty` take their
21+
// argument indirectly.
22+
if x.isEmpty {}
23+
//else { bar(x) }
24+
}
25+
}
26+
27+
@_silgen_name("bar")
28+
func bar(_: consuming Foo)
29+
30+
func foo(_ x: consuming Foo) {
31+
// `[String]` is a loadable copyable type, but we're using the
32+
// `consuming` modifier on `x` to suppress implicit copyability.
33+
// `isEmpty` is a method from the Collection protocol, so it takes `self`
34+
// generically and therefore indirectly.
35+
if x.isEmpty {}
36+
else { bar(x) }
37+
}
38+
39+
func copyableBar(_: consuming [String]) {}
40+
41+
func copyableFoo(prefix: consuming [String]) {
42+
if prefix.isEmpty { }
43+
else { copyableBar(prefix) }
44+
}
45+
46+
struct CopyableFoo {
47+
var prefix: [String]
48+
49+
consuming func foo() {
50+
if prefix.isEmpty { }
51+
else { copyableBar(prefix) }
52+
}
53+
}

0 commit comments

Comments
 (0)