Skip to content

Commit 9567bd4

Browse files
committed
[SILOptimizer] Alter FSO arg explosion heuristic.
The new rule is that an argument will be exploded if one of the following sets of conditions hold: (1) (a) Specializing the function will result in a thunk. That is, the thunk that is generated cannot be inlined everywhere. (b) The argument has dead non-trivial leaves. (c) The argument has fewer than three live leaves. (2) (a) Specializing the function will not result in a thunk. That is, the thunk that is generated will be inlined everywhere and eliminated as dead code. (b) The argument has dead potentially trivial leaves. (c) The argument has fewer than six live leaves. This change is based heavily on @GottesM's #16756 . rdar://problem/39957093
1 parent 23a0422 commit 9567bd4

11 files changed

+3062
-326
lines changed

lib/SILOptimizer/FunctionSignatureTransforms/ArgumentExplosionTransform.cpp

Lines changed: 271 additions & 23 deletions
Large diffs are not rendered by default.

lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ static bool isSpecializableRepresentation(SILFunctionTypeRepresentation Rep,
9595
llvm_unreachable("Unhandled SILFunctionTypeRepresentation in switch.");
9696
}
9797

98-
/// Returns true if F is a function which the pass know show to specialize
98+
/// Returns true if F is a function which the pass knows how to specialize
9999
/// function signatures for.
100100
static bool canSpecializeFunction(SILFunction *F,
101101
const CallerAnalysis::FunctionInfo *FuncInfo,
@@ -622,7 +622,11 @@ void FunctionSignatureTransform::createFunctionSignatureOptimizedFunction() {
622622

623623
// Run the optimization.
624624
bool FunctionSignatureTransform::run(bool hasCaller) {
625-
bool Changed = false;
625+
// We use a reference here on purpose so our transformations can know if we
626+
// are going to make a thunk and thus should just optimize.
627+
bool &Changed = TransformDescriptor.Changed;
628+
bool hasOnlyDirectInModuleCallers =
629+
TransformDescriptor.hasOnlyDirectInModuleCallers;
626630
SILFunction *F = TransformDescriptor.OriginalFunction;
627631

628632
// Never repeat the same function signature optimization on the same function.
@@ -657,7 +661,8 @@ bool FunctionSignatureTransform::run(bool hasCaller) {
657661
// Run DeadArgument elimination transformation. We only specialize
658662
// if this function has a caller inside the current module or we have
659663
// already created a thunk.
660-
if ((hasCaller || Changed) && DeadArgumentAnalyzeParameters()) {
664+
if ((hasCaller || Changed || hasOnlyDirectInModuleCallers) &&
665+
DeadArgumentAnalyzeParameters()) {
661666
Changed = true;
662667
LLVM_DEBUG(llvm::dbgs() << " remove dead arguments\n");
663668
DeadArgumentTransformFunction();
@@ -675,7 +680,8 @@ bool FunctionSignatureTransform::run(bool hasCaller) {
675680
// In order to not miss any opportunity, we send the optimized function
676681
// to the passmanager to optimize any opportunities exposed by argument
677682
// explosion.
678-
if ((hasCaller || Changed) && ArgumentExplosionAnalyzeParameters()) {
683+
if ((hasCaller || Changed || hasOnlyDirectInModuleCallers) &&
684+
ArgumentExplosionAnalyzeParameters()) {
679685
Changed = true;
680686
}
681687

@@ -830,7 +836,8 @@ class FunctionSignatureOpts : public SILFunctionTransform {
830836
SILOptFunctionBuilder FuncBuilder(*this);
831837
// Owned to guaranteed optimization.
832838
FunctionSignatureTransform FST(FuncBuilder, F, RCIA, EA, Mangler, AIM,
833-
ArgumentDescList, ResultDescList);
839+
ArgumentDescList, ResultDescList,
840+
FuncInfo.foundAllCallers());
834841

835842
bool Changed = false;
836843
if (OptForPartialApply) {

lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.h

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ struct FunctionSignatureTransformDescriptor {
198198
/// will use during our optimization.
199199
MutableArrayRef<ResultDescriptor> ResultDescList;
200200

201+
/// Are we going to make a change to this function?
202+
bool Changed;
203+
204+
/// Does this function only have direct callers. In such a case we know that
205+
/// all thunks we create will be eliminated so we can be more aggressive.
206+
bool hasOnlyDirectInModuleCallers;
207+
201208
/// Return a function name based on the current state of ArgumentDescList and
202209
/// ResultDescList.
203210
///
@@ -218,6 +225,19 @@ struct FunctionSignatureTransformDescriptor {
218225
/// simply passes it through.
219226
void addThunkArgument(ArgumentDescriptor &AD, SILBuilder &Builder,
220227
SILBasicBlock *BB, SmallVectorImpl<SILValue> &NewArgs);
228+
229+
/// Whether specializing the function will result in a thunk with the same
230+
/// signature as the original function that calls through to the specialized
231+
/// function.
232+
///
233+
/// Such a thunk is necessary if there is (or could be) code that calls the
234+
/// function which we are unable to specialize to match the function's
235+
/// specialization.
236+
bool willSpecializationIntroduceThunk() {
237+
return !hasOnlyDirectInModuleCallers ||
238+
OriginalFunction->isPossiblyUsedExternally() ||
239+
OriginalFunction->isAvailableExternally();
240+
}
221241
};
222242

223243
class FunctionSignatureTransform {
@@ -291,15 +311,17 @@ class FunctionSignatureTransform {
291311
public:
292312
/// Constructor.
293313
FunctionSignatureTransform(
294-
SILOptFunctionBuilder &FunctionBuilder,
295-
SILFunction *F, RCIdentityAnalysis *RCIA, EpilogueARCAnalysis *EA,
314+
SILOptFunctionBuilder &FunctionBuilder, SILFunction *F,
315+
RCIdentityAnalysis *RCIA, EpilogueARCAnalysis *EA,
296316
Mangle::FunctionSignatureSpecializationMangler &Mangler,
297317
llvm::SmallDenseMap<int, int> &AIM,
298318
llvm::SmallVector<ArgumentDescriptor, 4> &ADL,
299-
llvm::SmallVector<ResultDescriptor, 4> &RDL)
319+
llvm::SmallVector<ResultDescriptor, 4> &RDL,
320+
bool hasOnlyDirectInModuleCallers)
300321
: FunctionBuilder(FunctionBuilder),
301-
TransformDescriptor{F, nullptr, AIM, false, ADL, RDL}, RCIA(RCIA),
302-
EA(EA) {}
322+
TransformDescriptor{F, nullptr, AIM, false,
323+
ADL, RDL, false, hasOnlyDirectInModuleCallers},
324+
RCIA(RCIA), EA(EA) {}
303325

304326
/// Return the optimized function.
305327
SILFunction *getOptimizedFunction() {
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// RUN: %target-sil-opt -enable-objc-interop -enable-sil-verify-all -function-signature-opts -sil-fso-disable-dead-argument -sil-fso-disable-owned-to-guaranteed -enable-expand-all -sil-fso-optimize-if-not-called %s | %FileCheck %s
2+
3+
// *NOTE* We turn off all other fso optimizations including dead arg so we can
4+
// make sure that we are not exploding those.
5+
6+
sil_stage canonical
7+
8+
import Builtin
9+
10+
//////////////////
11+
// Declarations //
12+
//////////////////
13+
14+
struct BigTrivial {
15+
var x1: Builtin.Int32
16+
var x2: Builtin.Int32
17+
var x3: Builtin.Int32
18+
var x4: Builtin.Int32
19+
var x5: Builtin.Int32
20+
var x6: Builtin.Int32
21+
}
22+
23+
class Klass {}
24+
25+
struct LargeNonTrivialStructOneNonTrivialField {
26+
var k1: Klass
27+
var k2: Klass
28+
var x1: Builtin.Int32
29+
var x2: Builtin.Int32
30+
var x3: Builtin.Int32
31+
var x4: Builtin.Int32
32+
}
33+
34+
sil @int_user : $@convention(thin) (Builtin.Int32) -> ()
35+
sil @consuming_user : $@convention(thin) (@owned Klass) -> ()
36+
sil @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
37+
38+
///////////
39+
// Tests //
40+
///////////
41+
42+
// We should never optimize this. If we did this would become a thunk, so we
43+
// know that just be checking NFC we have proven no optimization has occured.
44+
//
45+
// CHECK-LABEL: sil @never_explode_trivial : $@convention(thin) (BigTrivial) -> () {
46+
// CHECK: } // end sil function 'never_explode_trivial'
47+
sil @never_explode_trivial : $@convention(thin) (BigTrivial) -> () {
48+
bb0(%0 : $BigTrivial):
49+
%1 = struct_extract %0 : $BigTrivial, #BigTrivial.x1
50+
%intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> ()
51+
apply %intfunc(%1) : $@convention(thin) (Builtin.Int32) -> ()
52+
%9999 = tuple()
53+
return %9999 : $()
54+
}
55+
56+
// If a value is never used, do not touch it. We leave it for dead argument
57+
// elimination. We have delibrately turned this off to test that behavior.
58+
//
59+
// CHECK-LABEL: sil @big_arg_with_no_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
60+
// CHECK-NOT: apply
61+
// CHECK: } // end sil function 'big_arg_with_no_uses'
62+
sil @big_arg_with_no_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
63+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
64+
%9999 = tuple()
65+
return %9999 : $()
66+
}
67+
68+
// We are using a single non-trivial field of the struct. We should explode this
69+
// so we eliminate the second non-trivial leaf.
70+
//
71+
// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @big_arg_with_one_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
72+
// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField):
73+
// CHECK: [[FUNC:%.*]] = function_ref @$s31big_arg_with_one_nontrivial_useTf4x_n
74+
// CHECK: [[FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
75+
// CHECK: apply [[FUNC]]([[FIELD]])
76+
// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use'
77+
sil @big_arg_with_one_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
78+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
79+
%1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
80+
%2 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
81+
apply %2(%1) : $@convention(thin) (@guaranteed Klass) -> ()
82+
%9999 = tuple()
83+
return %9999 : $()
84+
}
85+
86+
// We are using a single non-trivial field and a single trivial field. We are
87+
// willing to blow this up.
88+
//
89+
// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @big_arg_with_one_nontrivial_use_one_trivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
90+
// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField):
91+
// CHECK: [[FUNC:%.*]] = function_ref @$s032big_arg_with_one_nontrivial_use_d9_trivial_F0Tf4x_n : $@convention(thin) (@guaranteed Klass, Builtin.Int32) -> ()
92+
// CHECK: [[TRIVIAL_FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1
93+
// CHECK: [[NON_TRIVIAL_FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
94+
// CHECK: apply [[FUNC]]([[NON_TRIVIAL_FIELD]], [[TRIVIAL_FIELD]])
95+
// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use_one_trivial_use'
96+
sil @big_arg_with_one_nontrivial_use_one_trivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
97+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
98+
%1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
99+
%2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1
100+
%3 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
101+
apply %3(%1) : $@convention(thin) (@guaranteed Klass) -> ()
102+
%intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> ()
103+
apply %intfunc(%2) : $@convention(thin) (Builtin.Int32) -> ()
104+
%9999 = tuple()
105+
return %9999 : $()
106+
}
107+
108+
// We can still explode this, since our limit is 3 values.
109+
//
110+
// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @big_arg_with_one_nontrivial_use_two_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
111+
// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField):
112+
// CHECK: [[FUNC:%.*]] = function_ref @$s48big_arg_with_one_nontrivial_use_two_trivial_usesTf4x_n : $@convention(thin)
113+
// CHECK: [[TRIVIAL_FIELD1:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x2
114+
// CHECK: [[TRIVIAL_FIELD2:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1
115+
// CHECK: [[NON_TRIVIAL_FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
116+
// CHECK: apply [[FUNC]]([[NON_TRIVIAL_FIELD]], [[TRIVIAL_FIELD2]], [[TRIVIAL_FIELD1]])
117+
sil @big_arg_with_one_nontrivial_use_two_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
118+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
119+
%1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
120+
%2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1
121+
%3 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x2
122+
%4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
123+
apply %4(%1) : $@convention(thin) (@guaranteed Klass) -> ()
124+
%intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> ()
125+
apply %intfunc(%2) : $@convention(thin) (Builtin.Int32) -> ()
126+
apply %intfunc(%3) : $@convention(thin) (Builtin.Int32) -> ()
127+
%9999 = tuple()
128+
return %9999 : $()
129+
}
130+
131+
// We do not blow up the struct here since we have 4 uses, not 3.
132+
//
133+
// CHECK-LABEL: sil @big_arg_with_one_nontrivial_use_three_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
134+
sil @big_arg_with_one_nontrivial_use_three_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
135+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
136+
%1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
137+
%2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1
138+
%3 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x2
139+
%3a = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x3
140+
%4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
141+
apply %4(%1) : $@convention(thin) (@guaranteed Klass) -> ()
142+
%intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> ()
143+
apply %intfunc(%2) : $@convention(thin) (Builtin.Int32) -> ()
144+
apply %intfunc(%3) : $@convention(thin) (Builtin.Int32) -> ()
145+
apply %intfunc(%3a) : $@convention(thin) (Builtin.Int32) -> ()
146+
%9999 = tuple()
147+
return %9999 : $()
148+
}
149+
150+
// In this case, we shouldn't blow up the struct since we have not reduced the
151+
// number of non-trivial leaf nodes used.
152+
//
153+
// CHECK-LABEL: sil @big_arg_with_two_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
154+
sil @big_arg_with_two_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () {
155+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
156+
%1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
157+
%2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2
158+
%3 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> ()
159+
apply %3(%1) : $@convention(thin) (@guaranteed Klass) -> ()
160+
apply %3(%2) : $@convention(thin) (@guaranteed Klass) -> ()
161+
%9999 = tuple()
162+
return %9999 : $()
163+
}
164+
165+
// If we have one non-trivial value that is live and only live because of a
166+
// destroy, we can delete the argument after performing o2g.
167+
//
168+
// We are using a single non-trivial field of the struct. We should explode this
169+
// so we eliminate the second non-trivial leaf.
170+
//
171+
// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @big_arg_with_one_nontrivial_use_o2g_other_dead : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () {
172+
// CHECK-NOT: release_value
173+
// CHECK: apply
174+
// CHECK-NOT: release_value
175+
// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use_o2g_other_dead'
176+
sil @big_arg_with_one_nontrivial_use_o2g_other_dead : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () {
177+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
178+
%1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
179+
release_value %1 : $Klass
180+
%9999 = tuple()
181+
return %9999 : $()
182+
}
183+
184+
// If we have two non-trivial values that are live and one is always dead and
185+
// the other is kept alive due to a release, we can get rid of both since FSO
186+
// reruns with o2g. Test here that we explode it appropriatel even though we
187+
// aren't reducing the number of non-trivial uses. The
188+
// funcsig_explode_heuristic_inline.sil test makes sure we in combination
189+
// produce the appropriate SIL.
190+
//
191+
// We check that we can inline this correctly in the inline test.
192+
//
193+
// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @big_arg_with_one_nontrivial_use_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () {
194+
// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField):
195+
// CHECK: [[FUNC:%.*]] = function_ref @$s35big_arg_with_one_nontrivial_use_o2gTf4x_n : $@convention(thin) (@owned Klass, @owned Klass) -> ()
196+
// CHECK: apply [[FUNC]](
197+
// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use_o2g'
198+
sil @big_arg_with_one_nontrivial_use_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () {
199+
bb0(%0 : $LargeNonTrivialStructOneNonTrivialField):
200+
%1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1
201+
%2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2
202+
%3 = function_ref @consuming_user : $@convention(thin) (@owned Klass) -> ()
203+
apply %3(%2) : $@convention(thin) (@owned Klass) -> ()
204+
release_value %1 : $Klass
205+
%9999 = tuple()
206+
return %9999 : $()
207+
}

0 commit comments

Comments
 (0)