40
40
#include " ir/localize.h"
41
41
#include " ir/module-utils.h"
42
42
#include " ir/names.h"
43
+ #include " ir/properties.h"
43
44
#include " ir/type-updating.h"
44
45
#include " ir/utils.h"
45
46
#include " parsing.h"
@@ -67,22 +68,25 @@ enum class InliningMode {
67
68
SplitPatternB
68
69
};
69
70
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.
71
+ // Whether a function is just one instruction that always shrinks when inlined.
72
+ enum class TrivialInstruction {
73
+ // Function is not a single instruction, or it may not shrink when inlined.
75
74
NotTrivial,
76
75
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
76
+ // Function is just one instruction , with `local.get`s as arguments, and with
77
+ // each `local` is used exactly once, and in the order they appear in the
79
78
// argument list.
80
79
//
81
80
// In this case, inlining the function generates smaller code, and it is also
82
- // good for runtime.
81
+ // good for runtime. (Note that in theory inlining an instruction might grow
82
+ // the size, if before we had a call - one byte+LEB - and after we have
83
+ // something like a prefixed instruction - two bytes - with some other LEB
84
+ // like a type index. Figuring out when the LEBs will cause growth here is
85
+ // hard, and probably not worth it, since doing a call to run a single
86
+ // instruction is almost always going to be larger and slower.)
83
87
Shrinks,
84
88
85
- // Function just calls another function , but maybe with constant arguments, or
89
+ // Function is a single instruction , but maybe with constant arguments, or
86
90
// maybe some locals are used more than once. In these cases code size does
87
91
// not always shrink: at the call sites, omitted locals can create `drop`
88
92
// instructions, a local used multiple times can create new locals, and
@@ -102,7 +106,7 @@ struct FunctionInfo {
102
106
// Something is used globally if there is a reference to it in a table or
103
107
// export etc.
104
108
bool usedGlobally;
105
- TrivialCall trivialCall ;
109
+ TrivialInstruction trivialInstruction ;
106
110
InliningMode inliningMode;
107
111
108
112
FunctionInfo () { clear (); }
@@ -114,7 +118,7 @@ struct FunctionInfo {
114
118
hasLoops = false ;
115
119
hasTryDelegate = false ;
116
120
usedGlobally = false ;
117
- trivialCall = TrivialCall ::NotTrivial;
121
+ trivialInstruction = TrivialInstruction ::NotTrivial;
118
122
inliningMode = InliningMode::Unknown;
119
123
}
120
124
@@ -126,7 +130,7 @@ struct FunctionInfo {
126
130
hasLoops = other.hasLoops ;
127
131
hasTryDelegate = other.hasTryDelegate ;
128
132
usedGlobally = other.usedGlobally ;
129
- trivialCall = other.trivialCall ;
133
+ trivialInstruction = other.trivialInstruction ;
130
134
inliningMode = other.inliningMode ;
131
135
return *this ;
132
136
}
@@ -150,7 +154,7 @@ struct FunctionInfo {
150
154
}
151
155
// If the function calls another one in a way that always shrinks when
152
156
// inlined, inline it in all optimization and shrink modes.
153
- if (trivialCall == TrivialCall ::Shrinks) {
157
+ if (trivialInstruction == TrivialInstruction ::Shrinks) {
154
158
return true ;
155
159
}
156
160
// If it's so big that we have no flexible options that could allow it,
@@ -164,12 +168,12 @@ struct FunctionInfo {
164
168
if (options.shrinkLevel > 0 || options.optimizeLevel < 3 ) {
165
169
return false ;
166
170
}
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) {
171
+ // The function is just one instruction , but the code size may increase when
172
+ // inlined. We only inline it fully with `-O3`.
173
+ if (trivialInstruction == TrivialInstruction ::MayNotShrink) {
170
174
return true ;
171
175
}
172
- // Trivial calls are already handled. Inline if
176
+ // Trivial instructions are already handled. Inline if
173
177
// 1. The function doesn't have calls, and
174
178
// 2. The function doesn't have loops, or we allow inlining with loops.
175
179
return !hasCalls && (!hasLoops || options.inlining .allowFunctionsWithLoops );
@@ -240,14 +244,23 @@ struct FunctionInfoScanner
240
244
241
245
info.size = Measurer::measure (curr->body );
242
246
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.
247
+ // If the body is a simple instruction with roughly the same encoded size as
248
+ // a `call` instruction, and arguments are function locals read in order,
249
+ // then the code size always shrinks when the call is inlined.
250
+ //
251
+ // Note that skipping arguments can create `drop` instructions, and using
252
+ // arguments multiple times can create new locals, at the call sites. So we
253
+ // don't consider the function as "always shrinks" in these cases.
254
+ // TODO: Consider allowing drops, as at least in traps-never-happen mode
255
+ // they can usually be removed.
256
+ auto * body = curr->body ;
257
+ // Skip control flow as those can be substantially larger (middle and end
258
+ // bytes in an If), or no situation exists where we can optimize them (a
259
+ // Block with only LocalGets would have been removed by other passes).
260
+ if (!Properties::isControlFlowStructure (body)) {
248
261
bool shrinks = true ;
249
262
Index nextLocalGetIndex = 0 ;
250
- for (auto * operand : call-> operands ) {
263
+ for (auto * operand : ChildIterator (body) ) {
251
264
if (auto * localGet = operand->dynCast <LocalGet>()) {
252
265
if (localGet->index == nextLocalGetIndex) {
253
266
nextLocalGetIndex++;
@@ -262,14 +275,16 @@ struct FunctionInfoScanner
262
275
}
263
276
264
277
if (shrinks) {
265
- info.trivialCall = TrivialCall ::Shrinks;
278
+ info.trivialInstruction = TrivialInstruction ::Shrinks;
266
279
return ;
267
280
}
268
281
269
- if (info.size == call->operands .size () + 1 ) {
270
- // This function body is a call with some trivial (size 1) operands like
271
- // LocalGet or Const, so it is a trivial call.
272
- info.trivialCall = TrivialCall::MayNotShrink;
282
+ // If the operands are trivial (size 1) like LocalGet or Const, we still
283
+ // consider this as trivial instruction, but the size may not shrink when
284
+ // inlined.
285
+ uint32_t numOperands = ChildIterator (body).children .size ();
286
+ if (info.size == numOperands + 1 ) {
287
+ info.trivialInstruction = TrivialInstruction::MayNotShrink;
273
288
}
274
289
}
275
290
}
0 commit comments