← Back to context

Comment by bla3

5 years ago

It does make code harder to debug when something goes wrong. Since tail calls create no stack frames, a call sequence f->g->h where g tail calls h will show up as a stack of (f, h) in the debugger. The fix is easy, just make MUSTTAIL expand to nothing in debug builds. But it's something to keep in mind, and it means your debug mode code will have different memory use characteristics.

That is true, but there is always https://rr-project.org for easy reverse debugging if you're having trouble figuring out where you came from.

If the alternative is to drop to assembly, a C-based approach seems quite easy to debug. You can just add printf() statements! Previously when I had been using assembly language or a JIT, I had to resort to techniques like this: https://blog.reverberate.org/2013/06/printf-debugging-in-ass...

  • Of course RR is too expensive to be deployed in production builds. So if you are getting core dumps from "production" you won't have this information. So while RR helps it doesn't completely mitigate the tradeoff.

    • You may be interested in Intel PT snapshots in Linux core dumps, that gdb knows how to interpret and will give you a detailed branch trace. Less cool than rr but still very interesting!

  • Perhaps a "ring logger" approach could be useful. Append the more useful bits of what would normally go into the stack frame but without the allocation overhead. Seems like a few memcpy's and modulo operations wouldn't hurt the code gen too much.

  • > but there is always https://rr-project.org for easy reverse debugging if you're having trouble figuring out where you came from

    How did I not know about this? Thanks!

    • rr is really a hidden treasure. C/C++ wasn't the same for me after discovering it.

      It's sort of like the first time you learn about interactive debugging. Everything you did before that suddenly feels like "caveman coding" and you never want to go back.

Only if the alternative is non-tail function calls. Often the alternative to tail calls is a while loop, which also doesn't leave a stack trace.

  • As so often, the moral is that pushing your problems in-band doesn't make them go away.

Programmer's have traditionally relied on the stack as an (expensive yet incomplete) execution logging system, but the east fix for that is to use an actually log structure instead of the abusing the stack.

Seems like not much of a fix if your code depends on it. In Scheme such behavior would break standard compliance (and lots of programs). And since presumably you'd use this attribute precisely when your code depended on it, disabling it may not be feasible.

Fortunately the patterns generated by such code are no more difficult to debug than simple loops in other languages, so the lack of "history" of your computation seems no more concerning with tail calls than it is with loops (or at least that's how I always looked at it). And people don't seem to be avoiding loops in programming merely for the reason of loops not keeping track of their history for debugging purposes.

> The fix is easy, just make MUSTTAIL expand to nothing in debug builds.

That wouldn’t work. The final call is a recursive call to dispatch, if that is not a tail call it will blow the stack instantly.