Skip to content

Commit 6f5114e

Browse files
committed
[OwnershipUtils] Classify more moves as redundant.
Moves from limited use values are redundant. When a move separates a non-escaping lifetime from an escaping lifetime, it is still redundant if the original lifetime couldn't be optimized because it's already as small as possible.
1 parent 1f3623d commit 6f5114e

File tree

3 files changed

+185
-54
lines changed

3 files changed

+185
-54
lines changed

include/swift/SIL/OwnershipUtils.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -1349,10 +1349,17 @@ bool isNestedLexicalBeginBorrow(BeginBorrowInst *bbi);
13491349

13501350
/// Whether specified move_value is redundant.
13511351
///
1352-
/// A move_value is redundant if the lifetimes that it separates both have the
1352+
/// A move_value is redundant if it doesn't
1353+
/// - alter constraints (lexicality, ownership)
1354+
/// - enable optimizations (e.g, separate smaller scopes within which a value
1355+
/// escapes)
1356+
///
1357+
/// For example, if the lifetimes that a move_value separates both have the
13531358
/// same characteristics with respect to
1359+
/// - ownership
13541360
/// - lexicality
13551361
/// - escaping
1362+
/// then the move_value is redundant.
13561363
bool isRedundantMoveValue(MoveValueInst *mvi);
13571364

13581365
} // namespace swift

lib/SIL/Utils/OwnershipUtils.cpp

+46-5
Original file line numberDiff line numberDiff line change
@@ -2383,21 +2383,62 @@ bool swift::isNestedLexicalBeginBorrow(BeginBorrowInst *bbi) {
23832383
}
23842384

23852385
bool swift::isRedundantMoveValue(MoveValueInst *mvi) {
2386+
// Check whether the moved-from value's lifetime and the moved-to value's
2387+
// lifetime have the same (1) ownership, (2) lexicality, and (3) escaping.
2388+
//
2389+
// Along the way, also check for cases where they have different values for
2390+
// those characteristics but it doesn't matter because of how limited the uses
2391+
// of the original value are (for now, whether the move is the only use).
2392+
23862393
auto original = mvi->getOperand();
23872394

2388-
// If the moved-from value has none ownership, hasPointerEscape can't handle
2389-
// it, so it can't be used to determine whether escaping matches.
2395+
// (1) Ownership matches?
2396+
// (The new value always has owned ownership.)
23902397
if (original->getOwnershipKind() != OwnershipKind::Owned) {
23912398
return false;
23922399
}
23932400

2394-
// First, check whether lexicality matches, the cheaper check.
2401+
// (2) Lexicality matches?
23952402
if (mvi->isLexical() != original->isLexical()) {
23962403
return false;
23972404
}
23982405

2399-
// Then, check whether escaping matches, the more expensive check.
2400-
if (hasPointerEscape(mvi) != hasPointerEscape(original)) {
2406+
// The move doesn't alter constraints: ownership and lexicality match.
2407+
2408+
auto *singleUser =
2409+
original->getSingleUse() ? original->getSingleUse()->getUser() : nullptr;
2410+
if (mvi == singleUser && !SILArgument::asPhi(original)) {
2411+
assert(!hasPointerEscape(original));
2412+
// The moved-from value's only use is the move_value, and the moved-from
2413+
// value isn't a phi. So, it doesn't escape. (A phi's incoming values
2414+
// might escape.)
2415+
//
2416+
// Still, no optimization is enabled by separating the two lifetimes.
2417+
// The moved-from value's lifetime could not be shrunk regardless of whether
2418+
// the moved-to value escapes.
2419+
return true;
2420+
}
2421+
2422+
auto moveHasEscape = hasPointerEscape(mvi);
2423+
auto originalHasEscape = hasPointerEscape(original);
2424+
2425+
// (3) Escaping matches? (Expensive check, saved for last.)
2426+
if (moveHasEscape != originalHasEscape) {
2427+
if (!originalHasEscape) {
2428+
auto *singleConsumingUser =
2429+
original->getSingleConsumingUse()
2430+
? original->getSingleConsumingUse()->getUser()
2431+
: nullptr;
2432+
if (mvi == singleConsumingUser) {
2433+
// The moved-from value's only consuming use is the move_value and it
2434+
// doesn't escape.
2435+
//
2436+
// Although the moved-to value escapes, no optimization is enabled by
2437+
// separating the two lifetimes. The moved-from value's lifetime
2438+
// already couldn't be shrunk.
2439+
return true;
2440+
}
2441+
}
24012442
return false;
24022443
}
24032444

test/SILOptimizer/semantic-arc-opts-redundant-move-elimination.sil

+131-48
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,34 @@ sil @borrow : $@convention(thin) (@guaranteed C) -> ()
1818
sil @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
1919

2020
// Test that move_value instructions are removed when they are "redundant". A
21-
// move_value instruction is redundant when the lifetime it introduces has the
22-
// same characteristics as the lifetime that it ends along in the following two
23-
// aspects:
24-
// - lexicaliity
25-
// - escapingness
26-
21+
// move_value instruction is redundant when the lifetime it neither
22+
// - alters semantics (ownership, lexicality)
23+
// - enables optimization (e.g. separating the lifetimes limits the scope
24+
// within which values can escape and one of lifetimes can be shrunk as a
25+
// result)
26+
//
27+
// For example, when the lifetime of the moved-from and the moved-to values have
28+
// the same characteristics
29+
// - ownership
30+
// - lexicality
31+
// - escaping
32+
// then the move_value is redundant.
33+
//
2734
// The tests are named as follows:
2835
//
29-
// @test_{old_characteristics}_{new_characteristics}
36+
// @test_{old_non_move_uses}_{old_characteristics}_{new_characteristics}
37+
// where old_non_move_uses is
38+
// - 0: none
39+
// - 1: non-consuming
40+
// - 2: consuming
3041
// where both old_characteristics and new_characteristics are of the form
3142
//
32-
// {is_lexical}{has_escaping_use}
43+
// {is_owned}{is_lexical}{has_escaping_use}
3344
//
3445
// and both is_lexical and has_escaping_use are 1 or 0 depending on whether each
3546
// is true.
3647
//
37-
// So for example, in @test_00_10, there is a move_value instruction which ends
48+
// So for example, in @test_1_100_110, there is a move_value instruction which ends
3849
// a lifetime that is both neither lexical nor escaping and begins a lifetime
3950
// which is lexical but not escaping. Since the characteristics of the old and
4051
// new lifetimes differ, the move_value should be preserved.
@@ -43,16 +54,17 @@ sil @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
4354
// easier to specify the characteristics of the first lifetime. The move_value
4455
// of real interest for the tests is the second.
4556

46-
// Old: lexical , non-escaping
47-
// New: lexical , non-escaping
57+
// Non-move use: non-consuming
58+
// Old: owned , lexical , non-escaping
59+
// New: owned , lexical , non-escaping
4860
// Same. Redundant. Remove move_value.
4961
//
50-
// CHECK-LABEL: sil [ossa] @test_10_10 : {{.*}} {
62+
// CHECK-LABEL: sil [ossa] @test_1_110_110 : {{.*}} {
5163
// CHECK: [[INSTANCE:%[^,]+]] = apply
5264
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
5365
// CHECK-NOT: move_value
54-
// CHECK-LABEL: } // end sil function 'test_10_10'
55-
sil [ossa] @test_10_10 : $@convention(thin) () -> () {
66+
// CHECK-LABEL: } // end sil function 'test_1_110_110'
67+
sil [ossa] @test_1_110_110 : $@convention(thin) () -> () {
5668
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
5769
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
5870
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
@@ -65,16 +77,17 @@ sil [ossa] @test_10_10 : $@convention(thin) () -> () {
6577
return %retval : $()
6678
}
6779

68-
// Old: lexical , non-escaping
69-
// New: non-lexical, non-escaping
80+
// Non-move use: non-consuming
81+
// Old: owned , lexical , non-escaping
82+
// New: owned , non-lexical, non-escaping
7083
// Different. Non-redundant. Keep move_value.
7184
//
72-
// CHECK-LABEL: sil [ossa] @test_10_00 : {{.*}} {
85+
// CHECK-LABEL: sil [ossa] @test_1_110_100 : {{.*}} {
7386
// CHECK: [[INSTANCE:%[^,]+]] = apply
7487
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
7588
// CHECK: move_value [[LIFETIME]]
76-
// CHECK-LABEL: } // end sil function 'test_10_00'
77-
sil [ossa] @test_10_00 : $@convention(thin) () -> () {
89+
// CHECK-LABEL: } // end sil function 'test_1_110_100'
90+
sil [ossa] @test_1_110_100 : $@convention(thin) () -> () {
7891
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
7992
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
8093
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
@@ -87,15 +100,16 @@ sil [ossa] @test_10_00 : $@convention(thin) () -> () {
87100
return %retval : $()
88101
}
89102

90-
// Old: non-lexical, non-escaping
91-
// New: lexical , non-escaping
103+
// Non-move use: non-consuming
104+
// Old: non-owned , lexical, non-escaping
105+
// New: lexiowned , cal , non-escaping
92106
// Different. Non-redundant. Keep move_value.
93107
//
94-
// CHECK-LABEL: sil [ossa] @test_00_10 : {{.*}} {
108+
// CHECK-LABEL: sil [ossa] @test_1_100_110 : {{.*}} {
95109
// CHECK: [[INSTANCE:%[^,]+]] = apply
96110
// CHECK: move_value [lexical] [[INSTANCE]]
97-
// CHECK-LABEL: } // end sil function 'test_00_10'
98-
sil [ossa] @test_00_10 : $@convention(thin) () -> () {
111+
// CHECK-LABEL: } // end sil function 'test_1_100_110'
112+
sil [ossa] @test_1_100_110 : $@convention(thin) () -> () {
99113
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
100114
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
101115
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
@@ -107,15 +121,16 @@ sil [ossa] @test_00_10 : $@convention(thin) () -> () {
107121
return %retval : $()
108122
}
109123

110-
// Old: non-lexical, non-escaping
111-
// New: non-lexical, non-escaping
124+
// Non-move use: non-consuming
125+
// Old: non-owned , lexical, non-escaping
126+
// New: non-owned , lexical, non-escaping
112127
// Same. Redundant. Remove move_value.
113128
//
114-
// CHECK-LABEL: sil [ossa] @test_00_00 : {{.*}} {
129+
// CHECK-LABEL: sil [ossa] @test_1_100_100 : {{.*}} {
115130
// CHECK: [[INSTANCE:%[^,]+]] = apply
116131
// CHECK-NOT: move_value
117-
// CHECK-LABEL: } // end sil function 'test_00_00'
118-
sil [ossa] @test_00_00 : $@convention(thin) () -> () {
132+
// CHECK-LABEL: } // end sil function 'test_1_100_100'
133+
sil [ossa] @test_1_100_100 : $@convention(thin) () -> () {
119134
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
120135
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
121136
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
@@ -127,16 +142,17 @@ sil [ossa] @test_00_00 : $@convention(thin) () -> () {
127142
return %retval : $()
128143
}
129144

130-
// Old: lexical , escaping
131-
// New: lexical , escaping
145+
// Non-move use: non-consuming
146+
// Old: owned , lexical , escaping
147+
// New: owned , lexical , escaping
132148
// Same. Redundant. Remove move_value.
133149
//
134-
// CHECK-LABEL: sil [ossa] @test_11_11 : {{.*}} {
150+
// CHECK-LABEL: sil [ossa] @test_1_111_111 : {{.*}} {
135151
// CHECK: [[INSTANCE:%[^,]+]] = apply
136152
// CHECK: move_value [lexical] [[INSTANCE]]
137153
// CHECK-NOT: move_value
138-
// CHECK-LABEL: } // end sil function 'test_11_11'
139-
sil [ossa] @test_11_11 : $@convention(thin) () -> () {
154+
// CHECK-LABEL: } // end sil function 'test_1_111_111'
155+
sil [ossa] @test_1_111_111 : $@convention(thin) () -> () {
140156
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
141157
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
142158
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
@@ -154,16 +170,17 @@ sil [ossa] @test_11_11 : $@convention(thin) () -> () {
154170
return %retval : $()
155171
}
156172

157-
// Old: lexical , escaping
158-
// New: lexical , non-escaping
173+
// Non-move use: non-consuming
174+
// Old: owned , lexical , escaping
175+
// New: owned , lexical , non-escaping
159176
// Different. Non-redundant. Keep move_value.
160177
//
161-
// CHECK-LABEL: sil [ossa] @test_11_10 : {{.*}} {
178+
// CHECK-LABEL: sil [ossa] @test_1_111_110 : {{.*}} {
162179
// CHECK: [[INSTANCE:%[^,]+]] = apply
163180
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
164181
// CHECK: move_value [lexical] [[LIFETIME]]
165-
// CHECK-LABEL: } // end sil function 'test_11_10'
166-
sil [ossa] @test_11_10 : $@convention(thin) () -> () {
182+
// CHECK-LABEL: } // end sil function 'test_1_111_110'
183+
sil [ossa] @test_1_111_110 : $@convention(thin) () -> () {
167184
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
168185
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
169186
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
@@ -179,22 +196,83 @@ sil [ossa] @test_11_10 : $@convention(thin) () -> () {
179196
return %retval : $()
180197
}
181198

182-
// Old: lexical , non-escaping
183-
// New: lexical , escaping
184-
// Different. Non-redundant. Keep move_value.
199+
// Non-move use: non-consuming
200+
// Old: owned , lexical , non-escaping
201+
// New: owned , lexical , escaping
202+
// Different, but only consuming use of original is move_value. Redundant.
203+
// Remove move_value.
185204
//
186-
// CHECK-LABEL: sil [ossa] @test_10_11 : {{.*}} {
205+
// CHECK-LABEL: sil [ossa] @test_1_110_111 : {{.*}} {
187206
// CHECK: [[INSTANCE:%[^,]+]] = apply
188207
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
208+
// CHECK-NOT: move_value
209+
// CHECK-LABEL: } // end sil function 'test_1_110_111'
210+
sil [ossa] @test_1_110_111 : $@convention(thin) () -> () {
211+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
212+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
213+
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
214+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
215+
%lifetime = move_value [lexical] %instance : $C
216+
apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> ()
217+
%lifetime2 = move_value [lexical] %lifetime : $C
218+
%escape = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C
219+
apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> ()
220+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
221+
destroy_value %lifetime2 : $C
222+
%retval = tuple ()
223+
return %retval : $()
224+
}
225+
226+
// Non-move use: consuming
227+
// Old: owned , lexical , non-escaping
228+
// New: owned , lexical , escaping
229+
// Different, and non-move_value consuming use. Non-redundant. Keep move_value.
230+
//
231+
// CHECK-LABEL: sil [ossa] @test_2_110_111 : {{.*}} {
232+
// CHECK: [[INSTANCE:%[^,]+]] = apply
233+
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
234+
// CHECK: destroy_value [[LIFETIME]]
189235
// CHECK: move_value [lexical] [[LIFETIME]]
190-
// CHECK-LABEL: } // end sil function 'test_10_11'
191-
sil [ossa] @test_10_11 : $@convention(thin) () -> () {
236+
// CHECK-LABEL: } // end sil function 'test_2_110_111'
237+
sil [ossa] @test_2_110_111 : $@convention(thin) () -> () {
192238
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
193239
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
194240
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
195241
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
196242
%lifetime = move_value [lexical] %instance : $C
197243
apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> ()
244+
cond_br undef, left, right
245+
left:
246+
destroy_value %lifetime : $C
247+
br bottom
248+
right:
249+
%lifetime2 = move_value [lexical] %lifetime : $C
250+
%escape = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C
251+
apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> ()
252+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
253+
destroy_value %lifetime2 : $C
254+
br bottom
255+
bottom:
256+
%retval = tuple ()
257+
return %retval : $()
258+
}
259+
260+
// Non-move use: no
261+
// Old: owned , lexical , non-escaping
262+
// New: owned , lexical , escaping
263+
// Different. Redundant. Remove move_value.
264+
//
265+
// CHECK-LABEL: sil [ossa] @test_0_110_111 : {{.*}} {
266+
// CHECK: [[INSTANCE:%[^,]+]] = apply
267+
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
268+
// CHECK-NOT: move_value
269+
// CHECK-LABEL: } // end sil function 'test_0_110_111'
270+
sil [ossa] @test_0_110_111 : $@convention(thin) () -> () {
271+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
272+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
273+
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
274+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
275+
%lifetime = move_value [lexical] %instance : $C
198276
%lifetime2 = move_value [lexical] %lifetime : $C
199277
%escape = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C
200278
apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> ()
@@ -208,11 +286,16 @@ sil [ossa] @test_10_11 : $@convention(thin) () -> () {
208286
// ownership, we can't determine whether the moved-from value has a pointer
209287
// escape.
210288
//
211-
// CHECK-LABEL: sil [ossa] @f_none_optional : {{.*}} {
289+
// Non-move use: no
290+
// Old: none , non-lexical, non-escaping
291+
// New: owned , non-lexical, escaping
292+
// Different. Non-redundant. Keep move_value.
293+
//
294+
// CHECK-LABEL: sil [ossa] @test_0_000_100 : {{.*}} {
212295
// CHECK: [[INSTANCE:%[^,]+]] = enum $FakeOptional<C>, #FakeOptional.none!enumelt
213296
// CHECK: [[LIFETIME:%[^,]+]] = move_value [[INSTANCE]]
214-
// CHECK-LABEL: } // end sil function 'f_none_optional'
215-
sil [ossa] @f_none_optional : $@convention(thin) () -> () {
297+
// CHECK-LABEL: } // end sil function 'test_0_000_100'
298+
sil [ossa] @test_0_000_100 : $@convention(thin) () -> () {
216299
%none = enum $FakeOptional<C>, #FakeOptional.none!enumelt
217300
%lifetime = move_value %none : $FakeOptional<C>
218301
destroy_value %lifetime : $FakeOptional<C>

0 commit comments

Comments
 (0)