Skip to content

Commit 03c63e4

Browse files
authored
Generalize "trivial call" flag to other simple instructions (#7670)
A "trivial call" function is a function that just calls another with small arguments. Currently, these functions are always inlined when they arguments are just local.gets with the right order, and inlined with -O3 when the arguments have size 1. This PR generalizes "trivial calls" to "trivial instructions", to cover instructions other than just calls. All instructions other than control flow instructions are now covered. With this PR wasm-opt now inlines trivial functions in dart2wasm outputs like: (func $IntWasmInstructions|ltU (;40;) (param $var0 i64) (param $var1 i64) (result i32) local.get $var0 local.get $var1 i64.lt_u ) (func $... implicit setter (param $var0 (ref exact $...)) (param $var1 (ref exact $WasmListBase)) local.get $var0 local.get $var1 struct.set $... $field6 ) Note: dart2wasm doesn't directly generate these functions, these become "trivial" after wasm-opt passes. Impact of this change in a Wasm module optimized with flags: --closed-world --traps-never-happen --type-unfinalizing -Os \ --type-ssa --gufa -Os --type-merging -Os --type-finalizing --minimize-rec-groups Before optimizations: 38,035,036 bytes Optimized with main branch: 21,147,177 bytes Optimized with this branch: 20,991,223 bytes Diff: -155,954 bytes, -0.73%
1 parent 8844b1f commit 03c63e4

File tree

2 files changed

+444
-28
lines changed

2 files changed

+444
-28
lines changed

src/passes/Inlining.cpp

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "ir/localize.h"
4141
#include "ir/module-utils.h"
4242
#include "ir/names.h"
43+
#include "ir/properties.h"
4344
#include "ir/type-updating.h"
4445
#include "ir/utils.h"
4546
#include "parsing.h"
@@ -67,22 +68,25 @@ enum class InliningMode {
6768
SplitPatternB
6869
};
6970

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.
7574
NotTrivial,
7675

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
7978
// argument list.
8079
//
8180
// 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.)
8387
Shrinks,
8488

85-
// Function just calls another function, but maybe with constant arguments, or
89+
// Function is a single instruction, but maybe with constant arguments, or
8690
// maybe some locals are used more than once. In these cases code size does
8791
// not always shrink: at the call sites, omitted locals can create `drop`
8892
// instructions, a local used multiple times can create new locals, and
@@ -102,7 +106,7 @@ struct FunctionInfo {
102106
// Something is used globally if there is a reference to it in a table or
103107
// export etc.
104108
bool usedGlobally;
105-
TrivialCall trivialCall;
109+
TrivialInstruction trivialInstruction;
106110
InliningMode inliningMode;
107111

108112
FunctionInfo() { clear(); }
@@ -114,7 +118,7 @@ struct FunctionInfo {
114118
hasLoops = false;
115119
hasTryDelegate = false;
116120
usedGlobally = false;
117-
trivialCall = TrivialCall::NotTrivial;
121+
trivialInstruction = TrivialInstruction::NotTrivial;
118122
inliningMode = InliningMode::Unknown;
119123
}
120124

@@ -126,7 +130,7 @@ struct FunctionInfo {
126130
hasLoops = other.hasLoops;
127131
hasTryDelegate = other.hasTryDelegate;
128132
usedGlobally = other.usedGlobally;
129-
trivialCall = other.trivialCall;
133+
trivialInstruction = other.trivialInstruction;
130134
inliningMode = other.inliningMode;
131135
return *this;
132136
}
@@ -150,7 +154,7 @@ struct FunctionInfo {
150154
}
151155
// If the function calls another one in a way that always shrinks when
152156
// inlined, inline it in all optimization and shrink modes.
153-
if (trivialCall == TrivialCall::Shrinks) {
157+
if (trivialInstruction == TrivialInstruction::Shrinks) {
154158
return true;
155159
}
156160
// If it's so big that we have no flexible options that could allow it,
@@ -164,12 +168,12 @@ struct FunctionInfo {
164168
if (options.shrinkLevel > 0 || options.optimizeLevel < 3) {
165169
return false;
166170
}
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) {
170174
return true;
171175
}
172-
// Trivial calls are already handled. Inline if
176+
// Trivial instructions are already handled. Inline if
173177
// 1. The function doesn't have calls, and
174178
// 2. The function doesn't have loops, or we allow inlining with loops.
175179
return !hasCalls && (!hasLoops || options.inlining.allowFunctionsWithLoops);
@@ -240,14 +244,23 @@ struct FunctionInfoScanner
240244

241245
info.size = Measurer::measure(curr->body);
242246

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)) {
248261
bool shrinks = true;
249262
Index nextLocalGetIndex = 0;
250-
for (auto* operand : call->operands) {
263+
for (auto* operand : ChildIterator(body)) {
251264
if (auto* localGet = operand->dynCast<LocalGet>()) {
252265
if (localGet->index == nextLocalGetIndex) {
253266
nextLocalGetIndex++;
@@ -262,14 +275,16 @@ struct FunctionInfoScanner
262275
}
263276

264277
if (shrinks) {
265-
info.trivialCall = TrivialCall::Shrinks;
278+
info.trivialInstruction = TrivialInstruction::Shrinks;
266279
return;
267280
}
268281

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;
273288
}
274289
}
275290
}

0 commit comments

Comments
 (0)