Tail-call optimization in C is relatively recent (2025)
摘要
LWN 评论区的一篇讨论,作者 anton 回顾了 C 语言尾调用优化的历史:由于 C 调用约定要求调用者负责清理栈上参数,早期编译器(如 1994 年的产品)无法做尾调用优化;2001 年 Mark Probst 在 GCC 中通过独立调用约定实现了尾调用优化,但当时有限制(如无法处理间接调用)。作者去年读到《Copy-and-Patch Compilation》论文后测试发现,现代 GCC 和 Clang 已能对文章所示的尾调用做优化。他还提到 Gforth 目前受限于不到 2000 个代码片段,而论文中使用了 10 万个,若能实现尾调用优化将解锁更多技术。另一条评论(Robbepop)补充说,Wasmi 等现代 WebAssembly 解释器重度依赖尾调用(直接线程化代码)做指令分发,基准测试显示其性能远优于循环加 switch 的分发方式。
荐读理由
现代 GCC 与 Clang 已能对 C 尾调用做优化,这直接改变你给解释器或 VM 选调度方案的判断——尾调用派发不再受编译器能力限制,可放心评估迁移
原文
Tail-call optimization in C is relatively recent
Posted Aug 21, 2025 22:11 UTC (Thu) by anton (subscriber, #25547) Parent article: Python, tail calls, and performance
Actually tail calls in C have not been around forever. The C calling convention has been that the callee does not remove any stuff the caller has put on the stack. The caller could see the declaration int f();, the actual call could have n>0 arguments, and the actual function could have m≤n parameters. That would not always work if the callee removed the arguments.
So the caller had to remove the arguments between the call and the following return, turning the call into a non-tail call.
When I looked in 1994 at the C compilers of the day, they did not perform tail-call optimization for the kind of usage shown in the article. In 2001 Mark Probst implemented tail-call optimization in GCC with a separate calling convention; he lists the limitations of the then-existing tail-call optimization in GCC in section 6.4, among them: "It cannot handle indirect calls" (which would have been used in tail calls for interpreter dispatch).
I have not looked at the issue since then (GCC's goto * was good enough (well, mostly)), and I had not much reason for assuming that something had changed wrt to GCC support for tail-calls (although one release note mentioned sibcalls, and I remember thinking that I should be checking that out.
Anyway, last year I read the paper on "Copy-and-Patch Compilation" by Xu and Kjolstad, and they use tail-call optimization. In any case, after reading that paper, I made some tests if gcc and clang can do tail-call optimization for the kind of tail calls shown in the article. And it works. And Xu and Kjolstad report that they use 100,000 code snippets, whereas we limit ourselves in Gforth to <2000 (for VM instructions, stack caching variations thereof, static superinstructions etc.). Being able to do 100,000 would allow us to use techniques that need too many different code snippets to be usable in a goto *-based system.
We have not gotten around to putting this into Gforth yet, so congratulations to the Python community for being there first.
Tail-call optimization in C is relatively recent
Posted Aug 10, 2026 17:43 UTC (Mon) by Robbepop (guest, #185603) [Link]
Just today I re-benchmarked the upcoming Wasmi (WebAssembly Interpreter) version.
It makes heavy use of direct-threaded code (tail-calls) for its instruction dispatch but can be configured to use indirect-threading or even an old-school loop-switch dispatch. Benchmarks show an enormous performance difference between switch-loop and threaded-code techniques and that's why all modern and fast Wasm interpreters such as Wasmi, Wasm3 and Stitch are using tail-calls.
To me native tail-calls is one of the fundamentals for any true system programming language.
Further information & links:
这条对你有帮助吗?