Skip to content

Commit e6604bd

Browse files
committed
[SAO] Add RedundantMoveValueElimination.
Adds to SemanticARCOpts a new step of removing move_value instructions if they are redundant. A move_value is redundant if it adds no new information or optimization opportunities. An example of adding information: a lifetime becoming lexical. The new lifetime's destroys cannot be hoisted over deinit barriers. An example of adding an optimization opportunity: the original value escapes but the value produced by the move_value does not escape. So destroys of the new value can be hoisted more aggressively.
1 parent d16c745 commit e6604bd

File tree

6 files changed

+301
-2
lines changed

6 files changed

+301
-2
lines changed

lib/SILOptimizer/SemanticARC/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ target_sources(swiftSILOptimizer PRIVATE
88
Context.cpp
99
SemanticARCOptVisitor.cpp
1010
OwnershipConversionElimination.cpp
11+
RedundantMoveValueElimination.cpp
1112
)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===--- RedundantMoveValueElimination.cpp - Delete spurious move_values --===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
// A move_value ends an owned lifetime and begins an owned lifetime.
13+
//
14+
// The new lifetime may have the same characteristics as the original lifetime
15+
// with regards to
16+
// - lexicality
17+
// - escaping
18+
//
19+
// If it has the same characteristics, there is no reason to have two separate
20+
// lifetimes--they are redundant. This optimization deletes such redundant
21+
// move_values.
22+
//===----------------------------------------------------------------------===//
23+
24+
#include "SemanticARC/SemanticARCOpts.h"
25+
#include "SemanticARCOptVisitor.h"
26+
#include "swift/SIL/LinearLifetimeChecker.h"
27+
28+
using namespace swift;
29+
using namespace semanticarc;
30+
31+
//===----------------------------------------------------------------------===//
32+
// Top Level Entrypoint
33+
//===----------------------------------------------------------------------===//
34+
35+
bool SemanticARCOptVisitor::visitMoveValueInst(MoveValueInst *mvi) {
36+
if (ctx.onlyMandatoryOpts)
37+
return false;
38+
39+
if (!ctx.shouldPerform(ARCTransformKind::RedundantMoveValueElim))
40+
return false;
41+
42+
auto original = mvi->getOperand();
43+
44+
// If the moved-from value has none ownership, hasPointerEscape can't handle
45+
// it, so it can't be used to determine whether escaping matches.
46+
if (original->getOwnershipKind() != OwnershipKind::Owned) {
47+
return false;
48+
}
49+
50+
// If the moved-from value is a phi, hasPointerEscape can't handle it. Every
51+
// transitive incoming value escaping would have to match.
52+
if (SILArgument::asPhi(original)) {
53+
return false;
54+
}
55+
56+
// First, check whether lexicality matches, the cheaper check.
57+
if (mvi->isLexical() != original->isLexical()) {
58+
return false;
59+
}
60+
61+
// Then, check whether escaping matches, the more expensive check.
62+
if (hasPointerEscape(mvi) != hasPointerEscape(original)) {
63+
return false;
64+
}
65+
66+
// Both characteristics match.
67+
eraseAndRAUWSingleValueInstruction(mvi, original);
68+
return true;
69+
}

lib/SILOptimizer/SemanticARC/SemanticARCOptVisitor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ struct LLVM_LIBRARY_VISIBILITY SemanticARCOptVisitor
143143
bool visitCopyValueInst(CopyValueInst *cvi);
144144
bool visitBeginBorrowInst(BeginBorrowInst *bbi);
145145
bool visitLoadInst(LoadInst *li);
146+
bool visitMoveValueInst(MoveValueInst *mvi);
146147
bool
147148
visitUncheckedOwnershipConversionInst(UncheckedOwnershipConversionInst *uoci);
148149

@@ -153,6 +154,7 @@ struct LLVM_LIBRARY_VISIBILITY SemanticARCOptVisitor
153154
case SILInstructionKind::CopyValueInst:
154155
case SILInstructionKind::BeginBorrowInst:
155156
case SILInstructionKind::LoadInst:
157+
case SILInstructionKind::MoveValueInst:
156158
case SILInstructionKind::UncheckedOwnershipConversionInst:
157159
return true;
158160
}

lib/SILOptimizer/SemanticARC/SemanticARCOpts.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ static llvm::cl::list<ARCTransformKind> TransformsToPerform(
5151
"sil-semantic-arc-owned-to-guaranteed-phi",
5252
"Perform Owned To Guaranteed Phi. NOTE: Seeded by peephole "
5353
"optimizer for compile time saving purposes, so run this "
54-
"after running peepholes)")),
54+
"after running peepholes)"),
55+
clEnumValN(ARCTransformKind::RedundantMoveValueElim,
56+
"sil-semantic-arc-redundant-move-value-elim",
57+
"Eliminate move_value which don't change owned lifetime "
58+
"characteristics. (Escaping, Lexical).")),
5559
llvm::cl::desc(
5660
"For testing purposes only run the specified list of semantic arc "
5761
"optimization. If the list is empty, we run all transforms"));
@@ -86,6 +90,7 @@ struct SemanticARCOpts : SILFunctionTransform {
8690
case ARCTransformKind::LoadCopyToLoadBorrowPeephole:
8791
case ARCTransformKind::AllPeepholes:
8892
case ARCTransformKind::OwnershipConversionElimPeephole:
93+
case ARCTransformKind::RedundantMoveValueElim:
8994
// We never assume we are at fixed point when running these transforms.
9095
if (performPeepholesWithoutFixedPoint(visitor)) {
9196
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);

lib/SILOptimizer/SemanticARC/SemanticARCOpts.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ enum class ARCTransformKind : uint64_t {
3131
RedundantCopyValueElimPeephole = 0x8,
3232
LifetimeJoiningPeephole = 0x10,
3333
OwnershipConversionElimPeephole = 0x20,
34+
RedundantMoveValueElim = 0x40,
3435

3536
AllPeepholes = LoadCopyToLoadBorrowPeephole |
3637
RedundantBorrowScopeElimPeephole |
3738
RedundantCopyValueElimPeephole | LifetimeJoiningPeephole |
3839
OwnershipConversionElimPeephole,
39-
All = AllPeepholes | OwnedToGuaranteedPhi,
40+
All = AllPeepholes | OwnedToGuaranteedPhi | RedundantMoveValueElim,
4041
};
4142

4243
inline ARCTransformKind operator&(ARCTransformKind lhs, ARCTransformKind rhs) {
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// RUN: %target-sil-opt -module-name Swift -enable-sil-verify-all -semantic-arc-opts -sil-semantic-arc-redundant-move-value-elim %s | %FileCheck %s
2+
3+
sil_stage canonical
4+
5+
import Builtin
6+
7+
class C {}
8+
9+
enum FakeOptional<T> {
10+
case none
11+
case some(T)
12+
}
13+
14+
sil @getOwned : $@convention(thin) () -> (@owned C)
15+
16+
sil @borrow : $@convention(thin) (@guaranteed C) -> ()
17+
18+
sil @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
19+
20+
// 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+
27+
// The tests are named as follows:
28+
//
29+
// @test_{old_characteristics}_{new_characteristics}
30+
// where both old_characteristics and new_characteristics are of the form
31+
//
32+
// {is_lexical}{has_escaping_use}
33+
//
34+
// and both is_lexical and has_escaping_use are 1 or 0 depending on whether each
35+
// is true.
36+
//
37+
// So for example, in @test_00_10, there is a move_value instruction which ends
38+
// a lifetime that is both neither lexical nor escaping and begins a lifetime
39+
// which is lexical but not escaping. Since the characteristics of the old and
40+
// new lifetimes differ, the move_value should be preserved.
41+
42+
// Note that these tests all have two move_values. That's just to make it a bit
43+
// easier to specify the characteristics of the first lifetime. The move_value
44+
// of real interest for the tests is the second.
45+
46+
// Old: lexical , non-escaping
47+
// New: lexical , non-escaping
48+
// Same. Redundant. Remove move_value.
49+
//
50+
// CHECK-LABEL: sil [ossa] @test_10_10 : {{.*}} {
51+
// CHECK: [[INSTANCE:%[^,]+]] = apply
52+
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
53+
// CHECK-NOT: move_value
54+
// CHECK-LABEL: } // end sil function 'test_10_10'
55+
sil [ossa] @test_10_10 : $@convention(thin) () -> () {
56+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
57+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
58+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
59+
%lifetime = move_value [lexical] %instance : $C
60+
apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> ()
61+
%lifetime2 = move_value [lexical] %lifetime : $C
62+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
63+
destroy_value %lifetime2 : $C
64+
%retval = tuple ()
65+
return %retval : $()
66+
}
67+
68+
// Old: lexical , non-escaping
69+
// New: non-lexical, non-escaping
70+
// Different. Non-redundant. Keep move_value.
71+
//
72+
// CHECK-LABEL: sil [ossa] @test_10_00 : {{.*}} {
73+
// CHECK: [[INSTANCE:%[^,]+]] = apply
74+
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
75+
// CHECK: move_value [[LIFETIME]]
76+
// CHECK-LABEL: } // end sil function 'test_10_00'
77+
sil [ossa] @test_10_00 : $@convention(thin) () -> () {
78+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
79+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
80+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
81+
%lifetime = move_value [lexical] %instance : $C
82+
apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> ()
83+
%lifetime2 = move_value %lifetime : $C
84+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
85+
destroy_value %lifetime2 : $C
86+
%retval = tuple ()
87+
return %retval : $()
88+
}
89+
90+
// Old: non-lexical, non-escaping
91+
// New: lexical , non-escaping
92+
// Different. Non-redundant. Keep move_value.
93+
//
94+
// CHECK-LABEL: sil [ossa] @test_00_10 : {{.*}} {
95+
// CHECK: [[INSTANCE:%[^,]+]] = apply
96+
// CHECK: move_value [lexical] [[INSTANCE]]
97+
// CHECK-LABEL: } // end sil function 'test_00_10'
98+
sil [ossa] @test_00_10 : $@convention(thin) () -> () {
99+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
100+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
101+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
102+
apply %borrow(%instance) : $@convention(thin) (@guaranteed C) -> ()
103+
%lifetime2 = move_value [lexical] %instance : $C
104+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
105+
destroy_value %lifetime2 : $C
106+
%retval = tuple ()
107+
return %retval : $()
108+
}
109+
110+
// Old: non-lexical, non-escaping
111+
// New: non-lexical, non-escaping
112+
// Same. Redundant. Remove move_value.
113+
//
114+
// CHECK-LABEL: sil [ossa] @test_00_00 : {{.*}} {
115+
// CHECK: [[INSTANCE:%[^,]+]] = apply
116+
// CHECK-NOT: move_value
117+
// CHECK-LABEL: } // end sil function 'test_00_00'
118+
sil [ossa] @test_00_00 : $@convention(thin) () -> () {
119+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
120+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
121+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
122+
apply %borrow(%instance) : $@convention(thin) (@guaranteed C) -> ()
123+
%lifetime2 = move_value %instance : $C
124+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
125+
destroy_value %lifetime2 : $C
126+
%retval = tuple ()
127+
return %retval : $()
128+
}
129+
130+
// Old: lexical , escaping
131+
// New: lexical , escaping
132+
// Same. Redundant. Remove move_value.
133+
//
134+
// CHECK-LABEL: sil [ossa] @test_11_11 : {{.*}} {
135+
// CHECK: [[INSTANCE:%[^,]+]] = apply
136+
// CHECK: move_value [lexical] [[INSTANCE]]
137+
// CHECK-NOT: move_value
138+
// CHECK-LABEL: } // end sil function 'test_11_11'
139+
sil [ossa] @test_11_11 : $@convention(thin) () -> () {
140+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
141+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
142+
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
143+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
144+
%lifetime = move_value [lexical] %instance : $C
145+
%escape = ref_to_unmanaged %lifetime : $C to $@sil_unmanaged C
146+
apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> ()
147+
apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> ()
148+
%lifetime2 = move_value [lexical] %lifetime : $C
149+
%escape2 = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C
150+
apply %useUnmanaged(%escape2) : $@convention(thin) (@sil_unmanaged C) -> ()
151+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
152+
destroy_value %lifetime2 : $C
153+
%retval = tuple ()
154+
return %retval : $()
155+
}
156+
157+
// Old: lexical , escaping
158+
// New: lexical , non-escaping
159+
// Different. Non-redundant. Keep move_value.
160+
//
161+
// CHECK-LABEL: sil [ossa] @test_11_10 : {{.*}} {
162+
// CHECK: [[INSTANCE:%[^,]+]] = apply
163+
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
164+
// CHECK: move_value [lexical] [[LIFETIME]]
165+
// CHECK-LABEL: } // end sil function 'test_11_10'
166+
sil [ossa] @test_11_10 : $@convention(thin) () -> () {
167+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
168+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
169+
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
170+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
171+
%lifetime = move_value [lexical] %instance : $C
172+
%escape = ref_to_unmanaged %lifetime : $C to $@sil_unmanaged C
173+
apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> ()
174+
apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> ()
175+
%lifetime2 = move_value [lexical] %lifetime : $C
176+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
177+
destroy_value %lifetime2 : $C
178+
%retval = tuple ()
179+
return %retval : $()
180+
}
181+
182+
// Old: lexical , non-escaping
183+
// New: lexical , escaping
184+
// Different. Non-redundant. Keep move_value.
185+
//
186+
// CHECK-LABEL: sil [ossa] @test_10_11 : {{.*}} {
187+
// CHECK: [[INSTANCE:%[^,]+]] = apply
188+
// CHECK: [[LIFETIME:%[^,]+]] = move_value [lexical] [[INSTANCE]]
189+
// CHECK: move_value [lexical] [[LIFETIME]]
190+
// CHECK-LABEL: } // end sil function 'test_10_11'
191+
sil [ossa] @test_10_11 : $@convention(thin) () -> () {
192+
%getOwned = function_ref @getOwned : $@convention(thin) () -> (@owned C)
193+
%borrow = function_ref @borrow : $@convention(thin) (@guaranteed C) -> ()
194+
%useUnmanaged = function_ref @useUnmanaged : $@convention(thin) (@sil_unmanaged C) -> ()
195+
%instance = apply %getOwned() : $@convention(thin) () -> (@owned C)
196+
%lifetime = move_value [lexical] %instance : $C
197+
apply %borrow(%lifetime) : $@convention(thin) (@guaranteed C) -> ()
198+
%lifetime2 = move_value [lexical] %lifetime : $C
199+
%escape = ref_to_unmanaged %lifetime2 : $C to $@sil_unmanaged C
200+
apply %useUnmanaged(%escape) : $@convention(thin) (@sil_unmanaged C) -> ()
201+
apply %borrow(%lifetime2) : $@convention(thin) (@guaranteed C) -> ()
202+
destroy_value %lifetime2 : $C
203+
%retval = tuple ()
204+
return %retval : $()
205+
}
206+
207+
// Moves from values with non-owned ownership cannot be removed. Without owned
208+
// ownership, we can't determine whether the moved-from value has a pointer
209+
// escape.
210+
//
211+
// CHECK-LABEL: sil [ossa] @f_none_optional : {{.*}} {
212+
// CHECK: [[INSTANCE:%[^,]+]] = enum $FakeOptional<C>, #FakeOptional.none!enumelt
213+
// CHECK: [[LIFETIME:%[^,]+]] = move_value [[INSTANCE]]
214+
// CHECK-LABEL: } // end sil function 'f_none_optional'
215+
sil [ossa] @f_none_optional : $@convention(thin) () -> () {
216+
%none = enum $FakeOptional<C>, #FakeOptional.none!enumelt
217+
%lifetime = move_value %none : $FakeOptional<C>
218+
destroy_value %lifetime : $FakeOptional<C>
219+
%retval = tuple ()
220+
return %retval : $()
221+
}

0 commit comments

Comments
 (0)