Live data from Hacker News

Tail Call Improvements in .NET Framework 4

extended64.com

1–10 of 18 posts

Re: Tail Call Improvements in .NET Framework 4

#2
However the stack space is ‘recycled’ such that if you have a tail recursive algorithm, the first tail call that isn’t ‘easy’ will erect the TailCallHelper stack frame, and subsequent non-easy tail calls may need to grow that frame to accommodate all the arguments. Hopefully once the algorithm has gone through a full cycle of recursion the TailCallHelper stack frame has grown to the maximum sized needed by all the non-easy calls involved in the recursion, and then never grows, and thus prevents a stack overflow.

- does this mean that it's possible to use TailCallHelper but still not be able to guarantee no stack overflow?

Re: Tail Call Improvements in .NET Framework 4

#3

However the stack space is ‘recycled’ such that if you have a tail recursive algorithm, the first tail call that isn’t ‘easy’ will erect the TailCallHelper stack frame, and subsequent non-easy tail calls may need to grow that frame to accommodate all the arguments. Hopefully once the algorithm has gone through a full cycle of recursion the TailCallHelper stack frame has grown to the maximum sized needed by all the no…

Yes, but I don't think that's more true of TailCallHelper than of any function calls using a stack. This sounds like it's saying that even with tail call optimization it's possible for the arguments to a function to be too big for the stack. For example, if the arguments you pass to the recursive function call are stack allocated and grow each time then each call will increase stack usage, even though it's reusing the space from the previous call.

Re: Tail Call Improvements in .NET Framework 4

#4

However the stack space is ‘recycled’ such that if you have a tail recursive algorithm, the first tail call that isn’t ‘easy’ will erect the TailCallHelper stack frame, and subsequent non-easy tail calls may need to grow that frame to accommodate all the arguments. Hopefully once the algorithm has gone through a full cycle of recursion the TailCallHelper stack frame has grown to the maximum sized needed by all the no…

It sounds to me like the stack space required by TailCallHelper is proportional to the largest amount of stack space ever consumed by a single call of the tail recursive function(s) it's assisting, whereas without it, the stack space required would be proportional to the depth of recursive nesting. So using TailCallHelper you should be able to guarantee arbitrarily deep tail recursion.

Re: Tail Call Improvements in .NET Framework 4

#5
on x64 you should see shorter call stacks in optimized code because the JIT generated more tail calls (don’t worry this optimization is turned off for debug code)

I think it used to be that there was little perf difference between DEBUG and RELEASE builds, so for simplicity, with internal apps we always just build to DEBUG.

The above quote suggests that these days, they really are doing worthwhile optimizations in RELEASE builds. Does anybody know more about that?

Re: Tail Call Improvements in .NET Framework 4

#6

on x64 you should see shorter call stacks in optimized code because the JIT generated more tail calls (don’t worry this optimization is turned off for debug code) I think it used to be that there was little perf difference between DEBUG and RELEASE builds, so for simplicity, with internal apps we always just build to DEBUG. The above quote suggests that these days, they really are doing worthwhile optimizations in RE…

This sounds like a bad idea.

TCO is not just a perf difference. Once you add it to a language, you've changed the semantics. The same piece of code will throw a StackOverflowException in DEBUG mode, yet functional perfectly in RELEASE. Scheme's spec requires TCO for this very reason.

Re: Tail Call Improvements in .NET Framework 4

#8

on x64 you should see shorter call stacks in optimized code because the JIT generated more tail calls (don’t worry this optimization is turned off for debug code) I think it used to be that there was little perf difference between DEBUG and RELEASE builds, so for simplicity, with internal apps we always just build to DEBUG. The above quote suggests that these days, they really are doing worthwhile optimizations in RE…

This sounds like a bad idea. TCO is not just a perf difference. Once you add it to a language, you've changed the semantics. The same piece of code will throw a StackOverflowException in DEBUG mode, yet functional perfectly in RELEASE. Scheme's spec requires TCO for this very reason.

Sounds like a bad idea to me too ... but apparently X64 TCO on CLR 2 was considered an optimization, not something that changes the semantics ... so the DEBUG/RELEASE split has always been in there, and now it's a matter of backwards compatibility ... since it can change the stack-trace and in DEBUG-mode you expect the full trace.

Re: Tail Call Improvements in .NET Framework 4

#9

on x64 you should see shorter call stacks in optimized code because the JIT generated more tail calls (don’t worry this optimization is turned off for debug code) I think it used to be that there was little perf difference between DEBUG and RELEASE builds, so for simplicity, with internal apps we always just build to DEBUG. The above quote suggests that these days, they really are doing worthwhile optimizations in RE…

This sounds like a bad idea. TCO is not just a perf difference. Once you add it to a language, you've changed the semantics. The same piece of code will throw a StackOverflowException in DEBUG mode, yet functional perfectly in RELEASE. Scheme's spec requires TCO for this very reason.

You and I have a different definition of "semantics" when it comes to programming languages. Your code still achieves the same result, just in a different way. That requirement - equivalent outcome - is how compilers decide what optimizations it can and can't perform. This is often stated as "optimizations can't change program semantics."

Re: Tail Call Improvements in .NET Framework 4

#10

However the stack space is ‘recycled’ such that if you have a tail recursive algorithm, the first tail call that isn’t ‘easy’ will erect the TailCallHelper stack frame, and subsequent non-easy tail calls may need to grow that frame to accommodate all the arguments. Hopefully once the algorithm has gone through a full cycle of recursion the TailCallHelper stack frame has grown to the maximum sized needed by all the no…

It seems to me that the TailCallHelper is just a trampoline ... the method wanting to do a tail call places on the stack a reference to the next method to be called + it's arguments, returns, and then TailCallHelper calls it.

So yeah ... you have a guarantee, but it's also using stack space, so you have to watch-out for big arguments lists, because that may overflow the stack.

Post reply on HN