@@ -67,7 +67,32 @@ enum class InliningMode {
67
67
SplitPatternB
68
68
};
69
69
70
- // Useful into on a function, helping us decide if we can inline it
70
+ // Whether a function just calls another function in a way that always shrinks
71
+ // when the calling function is inlined.
72
+ enum class TrivialCall {
73
+ // Function does not just call another function, or it may not shrink when
74
+ // inlined.
75
+ NotTrivial,
76
+
77
+ // Function just calls another function, with `local.get`s as arguments, and
78
+ // with each `local` is used exactly once, and in the order they appear in the
79
+ // argument list.
80
+ //
81
+ // In this case, inlining the function generates smaller code, and it is also
82
+ // good for runtime.
83
+ Shrinks,
84
+
85
+ // Function just calls another function, but maybe with constant arguments, or
86
+ // maybe some locals are used more than once. In these cases code size does
87
+ // not always shrink: at the call sites, omitted locals can create `drop`
88
+ // instructions, a local used multiple times can create new locals, and
89
+ // encoding of constants may be larger than just a `local.get` with a small
90
+ // index. In these cases we still want to inline with `-O3`, but the code size
91
+ // may increase when inlined.
92
+ MayNotShrink,
93
+ };
94
+
95
+ // Useful info on a function, helping us decide if we can inline it.
71
96
struct FunctionInfo {
72
97
std::atomic<Index> refs;
73
98
Index size;
@@ -77,16 +102,7 @@ struct FunctionInfo {
77
102
// Something is used globally if there is a reference to it in a table or
78
103
// export etc.
79
104
bool usedGlobally;
80
- // We consider a function to be a trivial call if the body is just a call with
81
- // trivial arguments, like this:
82
- //
83
- // (func $forward (param $x) (param $y)
84
- // (call $target (local.get $x) (local.get $y))
85
- // )
86
- //
87
- // Specifically the body must be a call, and the operands to the call must be
88
- // of size 1 (generally, LocalGet or Const).
89
- bool isTrivialCall;
105
+ TrivialCall trivialCall;
90
106
InliningMode inliningMode;
91
107
92
108
FunctionInfo () { clear (); }
@@ -98,7 +114,7 @@ struct FunctionInfo {
98
114
hasLoops = false ;
99
115
hasTryDelegate = false ;
100
116
usedGlobally = false ;
101
- isTrivialCall = false ;
117
+ trivialCall = TrivialCall::NotTrivial ;
102
118
inliningMode = InliningMode::Unknown;
103
119
}
104
120
@@ -110,7 +126,7 @@ struct FunctionInfo {
110
126
hasLoops = other.hasLoops ;
111
127
hasTryDelegate = other.hasTryDelegate ;
112
128
usedGlobally = other.usedGlobally ;
113
- isTrivialCall = other.isTrivialCall ;
129
+ trivialCall = other.trivialCall ;
114
130
inliningMode = other.inliningMode ;
115
131
return *this ;
116
132
}
@@ -132,6 +148,11 @@ struct FunctionInfo {
132
148
size <= options.inlining .oneCallerInlineMaxSize ) {
133
149
return true ;
134
150
}
151
+ // If the function calls another one in a way that always shrinks when
152
+ // inlined, inline it in all optimization and shrink modes.
153
+ if (trivialCall == TrivialCall::Shrinks) {
154
+ return true ;
155
+ }
135
156
// If it's so big that we have no flexible options that could allow it,
136
157
// do not inline.
137
158
if (size > options.inlining .flexibleInlineMaxSize ) {
@@ -143,22 +164,15 @@ struct FunctionInfo {
143
164
if (options.shrinkLevel > 0 || options.optimizeLevel < 3 ) {
144
165
return false ;
145
166
}
146
- if (hasCalls) {
147
- // This has calls. If it is just a trivial call itself then inline, as we
148
- // will save a call that way - basically we skip a trampoline in the
149
- // middle - but if it is something more complex, leave it alone, as we may
150
- // not help much (and with recursion we may end up with a wasteful
151
- // increase in code size).
152
- //
153
- // Note that inlining trivial calls may increase code size, e.g. if they
154
- // use a parameter more than once (forcing us after inlining to save that
155
- // value to a local, etc.), but here we are optimizing for speed and not
156
- // size, so we risk it.
157
- return isTrivialCall;
158
- }
159
- // This doesn't have calls. Inline if loops do not prevent us (normally, a
160
- // loop suggests a lot of work and so inlining is less useful).
161
- return !hasLoops || options.inlining .allowFunctionsWithLoops ;
167
+ // The function just calls another function, but the code size may increase
168
+ // when inlined. We only inline it fully with `-O3`.
169
+ if (trivialCall == TrivialCall::MayNotShrink) {
170
+ return true ;
171
+ }
172
+ // Trivial calls are already handled. Inline if
173
+ // 1. The function doesn't have calls, and
174
+ // 2. The function doesn't have loops, or we allow inlining with loops.
175
+ return !hasCalls && (!hasLoops || options.inlining .allowFunctionsWithLoops );
162
176
}
163
177
};
164
178
@@ -227,10 +241,35 @@ struct FunctionInfoScanner
227
241
info.size = Measurer::measure (curr->body );
228
242
229
243
if (auto * call = curr->body ->dynCast <Call>()) {
244
+ // If call arguments are function locals read in order, then the code size
245
+ // always shrinks when the call is inlined. Note that we don't allow
246
+ // skipping function arguments here, as that can create `drop`
247
+ // instructions at the call sites, increasing code size.
248
+ bool shrinks = true ;
249
+ Index nextLocalGetIndex = 0 ;
250
+ for (auto * operand : call->operands ) {
251
+ if (auto * localGet = operand->dynCast <LocalGet>()) {
252
+ if (localGet->index == nextLocalGetIndex) {
253
+ nextLocalGetIndex++;
254
+ } else {
255
+ shrinks = false ;
256
+ break ;
257
+ }
258
+ } else {
259
+ shrinks = false ;
260
+ break ;
261
+ }
262
+ }
263
+
264
+ if (shrinks) {
265
+ info.trivialCall = TrivialCall::Shrinks;
266
+ return ;
267
+ }
268
+
230
269
if (info.size == call->operands .size () + 1 ) {
231
270
// This function body is a call with some trivial (size 1) operands like
232
271
// LocalGet or Const, so it is a trivial call.
233
- info.isTrivialCall = true ;
272
+ info.trivialCall = TrivialCall::MayNotShrink ;
234
273
}
235
274
}
236
275
}
0 commit comments