4

JavaScript will only optimize into a non-recursive loop a recursive step if it is the last expression in a block (IIUC). Does that mean that the right-hand recursive call will be optimised and the left-hand recursive call will NOT in the following?

function fibonacci(n) {
  if(n < 2) return n;
  return fibonacci(n-1) + fibonacci(n-2);
}
6
  • Define "optimise". Commented Jun 26, 2017 at 11:11
  • Transformed into a loop rather than leveraging the call stack. Commented Jun 26, 2017 at 11:11
  • @NiettheDarkAbsol Meaning the call will use constant stack space only. Commented Jun 26, 2017 at 11:13
  • 1
    Fair enough. I honestly don't know, but I just wanted to clarify because "optimise" is such a generic term. For instance, you could easily optimise by realising that fibonacci(n-1) is almost always going to call fibonacci(n-2) as part of its own recursion, so you could optimise that. Or you could have a known_fib list that is checked first, and only if a number is not in the list does fibonacci() calculate it. (the latter will reduce fibonacci(n-2) to a single lookup) Commented Jun 26, 2017 at 11:13
  • I dont think so. The upper code cant be written in a loop without taking another approach ( which the parser cant do) Commented Jun 26, 2017 at 11:18

1 Answer 1

3

Does that mean that the right-hand recursive call will be optimised and the left-hand recursive call will NOT in the following?

I don't think so. TCO is only possible when you directly return what other function returns. Since your function processes both results before returning them, neither of the calls can be tail-optimized.

Low-level explanation

In terms of a stack-based machine, a code like this:

function fun1()
   return fun2(42)

function fun2(arg)
    return arg + 1

is translated to this

fun1:
    push 42
    call fun2
    result = pop
    push result
    exit

fun2:
    arg = pop
    arg = arg + 1
    push arg
    exit

TCO can eliminate call-pop-push and instead jump to fun2 directly:

fun1:
    push 42
    goto fun2
    exit

However, a snippet like yours would be this:

fun1:
    push n - 1
    call fun2
    result1 = pop

    push n - 2
    call fun2
    result2 = pop

    result3 = result1 + result2
    push result3
    exit

Here, it's not possible to replace calls with jumps, because we need to return back to fun1 to perform the addition.

Disclaimer: this is a rather theoretical explanation, I have no idea how modern JS compilers actually implement TCO. They are quite smart, so perhaps there's a way to optimize stuff like this as well.

Sign up to request clarification or add additional context in comments.

3 Comments

Is the meaning of your first bit of "translated" code for fun1: "create a stack frame for the call to fun2 with 42 as an argument, capture the result in the calling stack frame as the return value for the frame and pop the frame"? Any exposition on why the jump optimisation is valid would be very helpful too.
TCO is only possible when you directly return what other function returns. This is just wrong. For instance, return 1 + myself(); would be "optimized" just fine.
@torazaburo: could you elaborate? maybe edit my answer or post a new one?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.