@@ -67,7 +67,32 @@ enum class InliningMode {
6767 SplitPatternB
6868};
6969
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.
7196struct FunctionInfo {
7297 std::atomic<Index> refs;
7398 Index size;
@@ -77,16 +102,7 @@ struct FunctionInfo {
77102 // Something is used globally if there is a reference to it in a table or
78103 // export etc.
79104 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;
90106 InliningMode inliningMode;
91107
92108 FunctionInfo () { clear (); }
@@ -98,7 +114,7 @@ struct FunctionInfo {
98114 hasLoops = false ;
99115 hasTryDelegate = false ;
100116 usedGlobally = false ;
101- isTrivialCall = false ;
117+ trivialCall = TrivialCall::NotTrivial ;
102118 inliningMode = InliningMode::Unknown;
103119 }
104120
@@ -110,7 +126,7 @@ struct FunctionInfo {
110126 hasLoops = other.hasLoops ;
111127 hasTryDelegate = other.hasTryDelegate ;
112128 usedGlobally = other.usedGlobally ;
113- isTrivialCall = other.isTrivialCall ;
129+ trivialCall = other.trivialCall ;
114130 inliningMode = other.inliningMode ;
115131 return *this ;
116132 }
@@ -132,6 +148,11 @@ struct FunctionInfo {
132148 size <= options.inlining .oneCallerInlineMaxSize ) {
133149 return true ;
134150 }
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+ }
135156 // If it's so big that we have no flexible options that could allow it,
136157 // do not inline.
137158 if (size > options.inlining .flexibleInlineMaxSize ) {
@@ -143,22 +164,15 @@ struct FunctionInfo {
143164 if (options.shrinkLevel > 0 || options.optimizeLevel < 3 ) {
144165 return false ;
145166 }
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 );
162176 }
163177};
164178
@@ -227,10 +241,35 @@ struct FunctionInfoScanner
227241 info.size = Measurer::measure (curr->body );
228242
229243 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+
230269 if (info.size == call->operands .size () + 1 ) {
231270 // This function body is a call with some trivial (size 1) operands like
232271 // LocalGet or Const, so it is a trivial call.
233- info.isTrivialCall = true ;
272+ info.trivialCall = TrivialCall::MayNotShrink ;
234273 }
235274 }
236275 }
0 commit comments