Why C Compilers Took Decades to Get Tail-Call Optimization
Tail-call optimization is often assumed to be a long-standing C compiler feature, but it arrived surprisingly late—and the delay was baked into the language’s calling convention. Because C historically let callers invoke functions with more arguments than the callee declared (thanks to prototype-less declarations like int f()), responsibility for cleaning arguments off the stack fell to the caller, not the callee. That mandatory cleanup code sits between the call and the following return, which mechanically converts what looks like a tail call into an ordinary one. As a result, compilers surveyed in 1994 simply didn’t optimize these calls.
Progress was slow and partial. Mark Probst added tail-call support to GCC in 2001, but only via a separate calling convention, and it couldn’t handle the indirect calls needed for interpreter dispatch—the exact case that matters for language runtimes. Developers like the Gforth author instead leaned on GCC’s computed-goto (goto *) as a good-enough workaround, and had little reason to revisit compiler support for years.
The situation has quietly changed. Prompted by Xu and Kjolstad’s “Copy-and-Patch Compilation” paper, which relies on tail calls, the author tested current GCC and Clang and found both now perform the optimization for these patterns. The payoff is scale: copy-and-patch uses roughly 100,000 code snippets, versus fewer than 2,000 in a goto-based Gforth, opening the door to techniques that were previously impractical. Gforth hasn’t adopted the approach yet—leaving the Python community as an early mover in putting modern C tail calls to work.
Read the full article
Continue reading at Hacker News →This is an AI-generated summary. Read the original for the full story.