Skip to content

Inlining

markw65 edited this page Apr 10, 2024 · 3 revisions

Summary

The optimizer can perform simple inlining. Since it can be hard to determine the benefits of inlining programmatically, it's mostly left up to the user to annotate suitable functions, although a few, very simple patterns, where inlining always results in smaller, faster code are automatically inlined.

The optimizer uses MonkeyC's exclude-annotations to determine which functions to inline.

The (:inline) Annotation

Annotating a function with (:inline) specifies that the optimizer should attempt to inline the function.

The (:inline_foo) Annotation

Annotating a function with (:inline_foo) specifies that the optimizer should inline the function unless the jungle file specifies foo as an exclude annotation.

This can be useful if a helper function is only called from one location when compiled for certain devices, but is called from several places on other devices, since inlining a function thats only called once (and deleting the body out-of-line version) will always result in smaller code.

Limitations on where inlining can be done

Because the optimizer is a source-to-source optimizer, and because of limitations in MonkeyC's expressiveness, it can be hard to inline complex function bodies in certain situations.

Simple functions, whose body consists of a single return statement, and nothing else, can generally be inlined anywhere, since they can generally be replaced by the return statement's expression.

For example:

(:inline)
function foo(x) {
  return x * 2;
}

This can always be inlined by replacing foo(E) by ((E) * 2).

For more complex functions, the optimizer may have to insert a block of code.

For example:

var g = 0;
(:inline)
function foo(x) {
  var y = x * 2;
  if (x < 10) {
    g++;
  }
  return y;
}

In order to inline foo(p), the optimizer has to produce something like:

    var result;
    {
      var x = p;
      var y = x * 2;
      if (x < 10) {
        g++;
      }
      result = y;
    }

and then replace the call to foo(p) with result. But the problem is where to put that code.

For example, suppose the calling code was:

    var t = g + foo(p);

You can't insert the inline body before the var t, because it modifies g. And what about

    switch (x) {
      case foo(p): // yes, its legal!
        // ...
    }

There's literally nowhere to put the inline body that would correctly capture the semantics.

So inlining is restricted to a few fairly common places, or contexts:

  • A top level function call
  • The argument to a return statement
  • The left-most component of the right hand side of an assignment
  • The left-most component of a variable initializer
  • The left-most component of the argument to an if-statement

"The left-most component" means that the call can be part of a larger expression, as long as it's the first thing to be executed as part of that expression. So eg:

  1. var x = foo(p);
  2. var x = foo(p) + 1;
  3. var x = !(foo(p) > 42);
  4. var x = foo(p) > 42 ? bar() : baz();

all work, while

  • var x = a + foo(p);

does not.

Note that optimizers prior to @markw65/[email protected] don't inline an if-statement's argument, and only work if the call is the full expression (ie only case 1. above works).

Limitations on the inline function body

Generally, the optimizer can't inline a function unless it has a single return statement, and the return statement must be last, both in lexical order and in control flow order. This is because MonkeyC doesn't support goto, making it very hard to mimic the control flow produced by multiple returns.

The one exception is if the function being inlined is the argument to a return statement - in which case the body of the inlined function can simply replace the original return statement. Even in this case, every path must have a return statement.

Diagnostics

If a function is marked for inlining, and the optimizer is unable to inline it, the optimizer will issue INFO level diagnostics that explain what went wrong.

Clone this wiki locally