Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make method actually inline

I forge a simple example to check the @inline annotation behavior:

import scala.annotation.tailrec

object InlineTest extends App {
  @inline
  private def corec(x : Int) : Int = rec(x - 1)

  @tailrec
  private def rec(x : Int) : Int =
    if (x < 3) x else {
      if (x % 3 == 0)
        corec(x-1)
      else
        rec(x-4)
    }

  @tailrec
  private def rec1(x : Int) : Int =
    if (x < 3) x else {
      if (x % 3 == 0) {
        val arg = x - 1
        rec1(arg - 1)
      } else
        rec1(x-4)
    }

  Console.println( rec(args(0).toInt) )
}

This example compiled without warnings, both tailrec annotations were in effect as I thought. But when I actually execute the code, it gives me stackoverflow exception. This means that the tail recursion optimization failed due to inline method being not so inlined.

I have control function rec1 that differs from the original only with "inline" transformation performed manually. And as this function works well as expected avoiding stackoverflow exception with tail recursion.

What prevents the annotated method from being inlined?

like image 320
ayvango Avatar asked Aug 30 '14 12:08

ayvango


People also ask

How do you inline a method?

Press ⌥⌘N (macOS), or Ctrl+Alt+N (Windows/Linux), to inline a method.

How do you inline a method in Java?

No, Java does not provide inline functions it is typically done by the JVM at execution time.

How does inline function actually work?

An inline function is one for which the compiler copies the code from the function definition directly into the code of the calling function rather than creating a separate set of instructions in memory. This eliminates call-linkage overhead and can expose significant optimization opportunities.

What is __ inline in C?

The __inline keyword suggests to the compiler that it compiles a C or C++ function inline, if it is sensible to do so. The semantics of __inline are exactly the same as those of the inline keyword. However, inline is not available in C90. __inline is a storage class qualifier. It does not affect the type of a function.


2 Answers

This won't work, indeed. The treatment of @inline is applied way later than the treatment of tail calls, preventing the optimization you're hoping for.

During the so-called "tailcalls" phase of the compiler, the compiler tries to transform methods so that tail-recursive calls are removed and replaced by loops. Note that only calls within the same function can be eliminated in this way. So, here, the compiler will be able to transform the function rec into something more or less equivalent to the following:

  @tailrec
  private def rec(x0: Int) : Int =
    var x: Int = x0
    while (true) {
      if (x < 3) x else {
        if (x % 3 == 0)
          return corec(x-1)
        else {
          x = x-4
          continue
        }
      }
    }

Note that the call to corec is not eliminated, because you're calling another method. At that time in the compiler, @inline annotations are not even looked at.

But why doesn't the compiler warn you that it cannot eliminate the call, since you've put @tailrec? Because it only checks that it is actually able to replace all calls to the current method. It doesn't care whether another method in the program calls rec (even if that method, in this case corec, is itself called by rec).

So you get no warning, but still the called to corec is not tail-call eliminated.

When you get to the treatment of @inline later in the compiler pipeline, and assuming it does choose to follow your advice to inline corec, it will modify again rec to inline the body of corec, which gives the following:

  @tailrec
  private def rec(x0: Int) : Int =
    var x: Int = x0
    while (true) {
      if (x < 3) x else {
        if (x % 3 == 0)
          return rec((x-1) - 1)
        else {
          x = x-4
          continue
        }
      }
    }

Never mind then that this creates a new tail-recursive call. It's too late. It won't be eliminated anymore. Hence, the stack overflow.

You can use the TailCalls object and its methods to turn mutually tail-recursive methods into loops. See details in the API.

like image 144
sjrd Avatar answered Oct 12 '22 01:10

sjrd


Note that there is a proposal in progress (scala PR 567) to introduce an actual inline keyword. (Sept. 2016)

See "SIP NN: Inline Definitions and Meta Expressions (Public Draft)"

It will work for:

concrete value definitions, e.g.

inline val x = 4

concrete methods, e.g.

inline def square(x: Double) = x * x

parameters of inline methods, e.g.

inline def pow(b: Double, inline n: Int): Double = {
  if (n == 0) 1
  else pow(b, n - 1)
}

Value and method definitions labeled inline are effectively final; they cannot be overridden.
Inline members also never override other members. Instead, every inline member becomes an overloaded alternative of all other members with the same name.

Inline definitions only exist at compile time; no storage is allocated for them in object layout and no code is generated for them in object method tables.
That means that it is OK to have an inline member that has the same type erasure as some other member with the same name.

like image 3
VonC Avatar answered Oct 12 '22 00:10

VonC