That's one reason - there are also a few more minor technical places in the spec where the depth of the call stack is observable in semantics such as I think the security manager.
But I don't think it's unusual for a language to expose call stacks? Ruby, Python, C#, etc all do it.
I think a Java implementation could do tail calls, but it would have to create a fake stack trace when needed.
(I haven’t looked, but I would expect modern Java implementations already make up quite a bit of the stack trace only when it is needed, for example when inlining calls)
And, traditionally, it _is_ unusual to expose call stacks to calling programs for Algol-style languages (implementations typically dumped a call stack and might add a way for a program to walk its stack, but that would be implementation-dependent)
Without exceptions or a repl, where would you use them, other than in a crash dump?
> (I haven’t looked, but I would expect modern Java implementations already make up quite a bit of the stack trace only when it is needed, for example when inlining calls)
Yes they do. But they can't make up the stack for tail calls because the information is simply gone. If you've got a set of mutually functions that are tail calling, and you suddenly need to generate a stack trace, how do you know how many times the functions have been called and which order they were called? That info was never recorded - it's just done. Inlining is static info and much simpler - it is recorded and isn't gone.
You’re not going to handle the Ackermann function that way, but for both simple recursive and mutually recursive calls, I would think you can often infer up front how many loops there will be, or just maintain a single counter counting the number of recursive calls, instead of counting using a linked list of stack frames.
Now, whether that’s worth the implementation effort, of course, would depend on whether the construct is used a lot, and that’s a catch-22.
For functions jumping into other functions, I guess that, when the program counter says you’re in baz while the first stack frame says foo got called, you could infer that foo called bar and bar called baz from metadata that the compiler would have to add.
Now, if foo can get into baz along multiple routes, you would have to record something, but I would guess there are plenty of those simpler cases. For those, the main challenge would be to infer recursion count for a recursive call.
If you only need to know how many times things were called, you could probably record that in much less space. Something like O(log n) of the depth of the stack, instead of O(n) that you need for the actual stack frames.
But I assume that kind of information is probably not enough for Java.
I thought C#/.Net was much more permissive than Java/JVM regarding 'lying' about the call stack, in optimised release environments. [0] From a quick search it looks like tail-call optimisation is supported in the CLR but only when the IR is tagged appropriately, which the F# compiler takes advantage of, but the C# compiler does not. [1]
Is this because Java exposes stack traces in a way most languages don't?