Skip to content

Commit 5644d15

Browse files
authored
Inlining: exposed inlining thresholds as command-line parameters. (#2125)
* Inlining: exposed inlining thresholds as command-line parameters. This will allow easier experimentation with optimal settings. Also tweaked the default logic slightly to always inline single caller functions up to a certain size. The command-line arguments were tested to have the desired effect for example by the Makefile change in this commit: aardappel/lobster@39ae393 which in turn relies on: emscripten-core/emscripten#8635 * Grouped inlining options & reverted defaults. Now uses same defaults for inlining as before for the sake of not having to redo a lot of tests. Added FIXME to indicate that the current inlining logic needs fixing. * Fixed default values now pulled from code. * clang-format
1 parent eabf9ed commit 5644d15

3 files changed

Lines changed: 68 additions & 23 deletions

File tree

src/pass.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,29 @@ struct PassRegistry {
5656
std::map<std::string, PassInfo> passInfos;
5757
};
5858

59+
struct InliningOptions {
60+
// Function size at which we always inline.
61+
// Typically a size so small that after optimizations, the inlined code will
62+
// be smaller than the call instruction itself. 2 is a safe number because
63+
// there is no risk of things like
64+
// (func $reverse (param $x i32) (param $y i32)
65+
// (call $something (local.get $y) (local.get $x))
66+
// )
67+
// in which case the reversing of the params means we'll possibly need
68+
// a block and a temp local. But that takes at least 3 nodes, and 2 < 3.
69+
// More generally, with 2 items we may have a local.get, but no way to
70+
// require it to be saved instead of directly consumed.
71+
Index alwaysInlineMaxSize = 2;
72+
// Function size which we inline when functions are lightweight (no loops
73+
// and calls) and we are doing aggressive optimisation for speed (-O3).
74+
// In particular it's nice that with this limit we can inline the clamp
75+
// functions (i32s-div, f64-to-int, etc.), that can affect perf.
76+
Index flexibleInlineMaxSize = 20;
77+
// Function size which we inline when there is only one caller.
78+
// FIXME: this should logically be higher than flexibleInlineMaxSize.
79+
Index oneCallerInlineMaxSize = 15;
80+
};
81+
5982
struct PassOptions {
6083
// Run passes in debug mode, doing extra validation and timing checks.
6184
bool debug = false;
@@ -67,6 +90,8 @@ struct PassOptions {
6790
int optimizeLevel = 0;
6891
// 0, 1, 2 correspond to -O0, -Os, -Oz
6992
int shrinkLevel = 0;
93+
// Tweak thresholds for the Inlining pass.
94+
InliningOptions inlining;
7095
// Optimize assuming things like div by 0, bad load/store, will not trap.
7196
bool ignoreImplicitTraps = false;
7297
// Optimize assuming that the low 1K of memory is not valid memory for the

src/passes/Inlining.cpp

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,6 @@
4343

4444
namespace wasm {
4545

46-
// A limit on how big a function to inline when being careful about size
47-
static const int CAREFUL_SIZE_LIMIT = 15;
48-
49-
// A limit on how big a function to inline when being more flexible. In
50-
// particular it's nice that with this limit we can inline the clamp
51-
// functions (i32s-div, f64-to-int, etc.), that can affect perf.
52-
static const int FLEXIBLE_SIZE_LIMIT = 20;
53-
54-
// A size so small that after optimizations, the inlined code will be
55-
// smaller than the call instruction itself. 2 is a safe number because
56-
// there is no risk of things like
57-
// (func $reverse (param $x i32) (param $y i32)
58-
// (call $something (local.get $y) (local.get $x))
59-
// )
60-
// in which case the reversing of the params means we'll possibly need
61-
// a block and a temp local. But that takes at least 3 nodes, and 2 < 3.
62-
// More generally, with 2 items we may have a local.get, but no way to
63-
// require it to be saved instead of directly consumed.
64-
static const int INLINING_OPTIMIZING_WILL_DECREASE_SIZE_LIMIT = 2;
65-
6646
// Useful into on a function, helping us decide if we can inline it
6747
struct FunctionInfo {
6848
std::atomic<Index> calls;
@@ -77,20 +57,25 @@ struct FunctionInfo {
7757
usedGlobally = false;
7858
}
7959

60+
// See pass.h for how defaults for these options were chosen.
8061
bool worthInlining(PassOptions& options) {
8162
// if it's big, it's just not worth doing (TODO: investigate more)
82-
if (size > FLEXIBLE_SIZE_LIMIT) {
63+
if (size > options.inlining.flexibleInlineMaxSize) {
8364
return false;
8465
}
8566
// if it's so small we have a guarantee that after we optimize the
8667
// size will not increase, inline it
87-
if (size <= INLINING_OPTIMIZING_WILL_DECREASE_SIZE_LIMIT) {
68+
if (size <= options.inlining.alwaysInlineMaxSize) {
8869
return true;
8970
}
9071
// if it has one use, then inlining it would likely reduce code size
9172
// since we are just moving code around, + optimizing, so worth it
9273
// if small enough that we are pretty sure its ok
93-
if (calls == 1 && !usedGlobally && size <= CAREFUL_SIZE_LIMIT) {
74+
// FIXME: move this check to be first in this function, since we should
75+
// return true if oneCallerInlineMaxSize is bigger than
76+
// flexibleInlineMaxSize (which it typically should be).
77+
if (calls == 1 && !usedGlobally &&
78+
size <= options.inlining.oneCallerInlineMaxSize) {
9479
return true;
9580
}
9681
// more than one use, so we can't eliminate it after inlining,

src/tools/optimization-options.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,41 @@ struct OptimizationOptions : public ToolOptions {
123123
[this](Options* o, const std::string& argument) {
124124
passOptions.shrinkLevel = atoi(argument.c_str());
125125
})
126+
.add("--always-inline-max-function-size",
127+
"-aimfs",
128+
"Max size of functions that are always inlined (default " +
129+
std::to_string(InliningOptions().alwaysInlineMaxSize) +
130+
", which "
131+
"is safe for use with -Os builds)",
132+
Options::Arguments::One,
133+
[this](Options* o, const std::string& argument) {
134+
passOptions.inlining.alwaysInlineMaxSize =
135+
static_cast<Index>(atoi(argument.c_str()));
136+
})
137+
.add("--flexible-inline-max-function-size",
138+
"-fimfs",
139+
"Max size of functions that are inlined when lightweight (no loops "
140+
"or function calls) when optimizing aggressively for speed (-O3). "
141+
"Default: " +
142+
std::to_string(InliningOptions().flexibleInlineMaxSize),
143+
Options::Arguments::One,
144+
[this](Options* o, const std::string& argument) {
145+
passOptions.inlining.flexibleInlineMaxSize =
146+
static_cast<Index>(atoi(argument.c_str()));
147+
})
148+
.add("--one-caller-inline-max-function-size",
149+
"-ocimfs",
150+
"Max size of functions that are inlined when there is only one "
151+
"caller (default " +
152+
std::to_string(InliningOptions().oneCallerInlineMaxSize) +
153+
"). Reason this is not unbounded is that some "
154+
"implementations may have a hard time optimizing really large "
155+
"functions",
156+
Options::Arguments::One,
157+
[this](Options* o, const std::string& argument) {
158+
passOptions.inlining.oneCallerInlineMaxSize =
159+
static_cast<Index>(atoi(argument.c_str()));
160+
})
126161
.add("--ignore-implicit-traps",
127162
"-iit",
128163
"Optimize under the helpful assumption that no surprising traps "

0 commit comments

Comments
 (0)