
Show HN: Tail Recursion Optimization for the JVM - sipkab
https://github.com/Sipkab/jvm-tail-recursion
======
salmonellaeater
I've been hoping for tail call elimination[1] on the JVM for a long time, and
historically it has been blocked by a security requirement[2]. Changing the
number of stack frames between two calls would cause an error. Apparently that
check is no longer there so TCE is possible!

[1] Using the word 'optimization' suggests that eliminating tail calls is
optional, but for it to be really useful it has to be guaranteed. If you're
writing in recursive style, your program becomes instantly incorrect if TCE
goes away because many acceptable inputs will start causing stack overflows.

[2] See this answer:
[https://softwareengineering.stackexchange.com/a/272086](https://softwareengineering.stackexchange.com/a/272086)

~~~
cryptonector
> in jdk classes [...] there are a number of security sensitive methods that
> rely on counting stack frames between jdk library code and calling code to
> figure out who's calling them.

That's... lame. BTW, tail code elimination could keep a count of tail calls
for this. It wouldn't work so well for mutual recursion, but maybe that would
suffice. In any case, nothing should be so dependent on stack traces!

~~~
saagarjha
Java's security manager model is fairly lame in general.

~~~
cryptonector
Yes. And JAAS won't die even though applets did.

------
waynesonfire
i recently learned about project loom that's addressing tail call elimination
among other things, exciting stuff

[https://wiki.openjdk.java.net/display/loom/Main](https://wiki.openjdk.java.net/display/loom/Main)

~~~
vips7L
Now only if pron would tell us when its ready!

~~~
pjmlp
There is an updated EA build available.

------
dahfizz
Doesn't Scala already do this?

~~~
smabie
Yeah, but you need to explicitly annotate your functions with @rec.

~~~
freekh
Not sure if this is accurate. You can enforce @tailrec if you want, but I
believe it will try to in any case, even if it is not annotated.

~~~
yashap
This is correct. @tailrec will simply cause a compile failure if the compiler
DOESN’T optimize the function, but the compiler does still optimize tail
recursion without the annotation.

It’s a weird but helpful annotation. For example, Scala won’t optimize methods
that can be overridden (non-final and public/protected), which is easy to
forget. So the annotation is a nice check/confirmation that the compiler is
doing what you expect.

------
Gollapalli
This is exciting. I'm guessing since it applies to bytecode it works for
clojure as well? That would mean we'll be able to use (func (when cond
function)) instead of (loop [] (recur)).

~~~
hencq
The use of recur in Clojure is by design. It would have been quite trivial to
optimize this type of self recursion, but since it doesn't do general tail
call optimization, they introduced the recur keyword. See also [1]. By the
way, you don't need to use loop in Clojure either, it also works with
functions.

[1].
[https://clojure.org/about/functional_programming#_recursive_...](https://clojure.org/about/functional_programming#_recursive_looping)

------
exabrial
Could/Should these be added to the Graal project as compiler optimizations?

~~~
sipkab
Definitely not by default.

This optimization messes with the stack trace, so if you throw an exception in
one of the recursively called methods, it will only show up only once in the
stack trace. This may very well cause confusion in cases where the developer
is not aware that such optimizations have been performed.

However, you can still perform the optimization on your Java bytecode classes,
and then pass them to Graal. (If I understand Graal correctly.)

~~~
igravious
Code gets optimized out of existence all the time and isn't a problem for
exception handling code and debugging tools, I don't see why this would be any
different.

[http://cesquivias.github.io/blog/2015/01/15/writing-a-
langua...](http://cesquivias.github.io/blog/2015/01/15/writing-a-language-in-
truffle-part-4-adding-features-the-truffle-way/#a-lisp-birthright-tail-call-
optimization)

~~~
sipkab
This optimization is simply a goto to the start of the function, and
reassignment of the arguments. It doesn't prevent exception handling in any
way. It doesn't interfere with debugging tools.

The point is that when your program throws an exception, and prints the
stacktrace, you will only see a single stack frame for your method. Even if it
recursively descended into itself multiple times. This is counter-intuitive as
you don't see the evidence of the recursive calls in the stack trace. If
you're unaware that tail recursion optimization was performed, you won't know
why it seems like your method was only called once.

The program flow stays the same, only the diagnostic information of the
program changes.

~~~
lalaithion
Sure, but it wouldn't be that hard to do something like <677 tail calls
elided> from the stack trace.

~~~
jmaa
Well, yes, it would be that hard.

The whole idea with tail call elimination is that a stack trace shouldn't be
able to see the frames for the tail calls, because the tail call reuses the
frame. To add support for tracking the tail call stack frames, you would need
to modify the compiler to output code that specifically keeps track of "elided
tail call frames", and you would need to update the stack trace traversal code
to be able to recognize the extra debug information.

Sure, it's something you could build into a new toolchain, but adding
something like this to the JVM would be harder due to the constraints already
placed on the JVM. Furthermore I don't know of such support in any toolchains,
not even for Lua or Scheme, both of which guarantee tail call optimization.
(If anybody has an example, please share!)

~~~
saagarjha
JavaScriptCore keeps track of frames, even for JavaScript proper tail calls:
[https://webkit.org/blog/6240/ecmascript-6-proper-tail-
calls-...](https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-in-
webkit/)

~~~
chrisseaton
The essential words in that article are...

> There are some non-standard ECMAScript features in JavaScript that work
> differently in the presence of PTC.

Java has a strict spec, and the relevant methods which would break aren’t non-
standard like they are in JavaScript.

If you’re willing to change the spec (I think Loom does) then yeah, but you
can’t implement it as an ‘optimisation’ until then, because it’s not an
optimisation if it changes behaviour.

~~~
saagarjha
What would Java's be? Is there an interface besides Thread.getStackTrace()?

~~~
chrisseaton
It’s also the format of the backtrack - I think that’s covered by the TCK.

------
buremba
It sounds to be a great idea for an IDE plugin. Looking at you, Intellij IDEA.

