logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·31.10.26
← Back to news

Tail-call optimization in C is relatively recent (2025)

Original article
lwn.net|89 points|62 comments|by prakashqwerty|Aug 10, 2026

The Evolution of Tail-Call Optimization (TCO) in C

LWN.net Logo LWN


📜 Perspective 1: The Technical History of C's TCO

Posted by anton (subscriber, #25547) on Aug 21, 2025

Contrary to popular belief, tail-call optimization in the C language is ancient a relatively recent development. The primary obstacle was the traditional C calling convention, which dictates that the callee is not responsible for cleaning up the stack space allocated by the caller.

The Stack Dilemma

In older C standards, a function could be declared as int f();. This created a scenario where:

  • The actual call might provide nnn arguments.
  • The function definition might expect mmm parameters.

Mathematically, if n≠mn \neq mn=m, the stack would be left in an inconsistent state if the callee tried to clean it. Therefore, the caller had to perform the cleanup after the function returned. This requirement effectively turned every potential tail call into a standard 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."

The Road to Modern Support

The timeline of GCC's evolution regarding TCO looks like this:

  1. 2001: Mark Probst introduced TCO to GCC, but it required a separate calling convention.
  2. Limitations: As noted in section 6.4 of his documentation, it could not handle indirect calls, which are essential for interpreter dispatch.
  3. Workarounds: For a long time, goto * (computed gotos) provided a sufficient alternative.

Recently, the paper "Copy-and-Patch Compilation" by Xu and Kjolstad highlighted the power of TCO. Testing confirms that modern gcc and clang now support TCO for the patterns discussed in the Python performance context.

Impact on Gforth

The ability to utilize TCO opens new doors for Gforth. Currently, Gforth is limited to roughly 2,000 code snippets (for VM instructions and stack caching) because it relies on goto *.

Featuregoto * SystemTCO-based System
Snippet Capacity≈2,000\approx 2,000≈2,000≈100,000\approx 100,000≈100,000
FlexibilityLimitedHigh

While the Python community has already leveraged this, Gforth has yet to implement it.


🎨 Perspective 2: Practical Application in Forth

Posted by lafp (subscriber, #89554) on Aug 23, 2025

I have recently developed a toy interpreter for a Forth variant that utilizes tail calls for instruction dispatch. This approach provided a significant architectural advantage: built-in functions now behave identically to instructions, eliminating the need for a specific "call built-in" opcode.

Project Goals & Status

The project is a variant of the Forth Haiku language, designed for creating art (similar to how ShaderToy works for GLSL).

  • Implement TCO-based dispatch
  • Support super-instructions
  • Optimize for small-scale hardware
  • Expose interpreter via web browser
  • Control an LED matrix (inspired by Flaschen Taschen)

The source code is available here: https://github.com/lpereira/lwan/blob/master/src/samples/...


⚡ Perspective 3: High-Performance Wasm Interpreters

Posted by Robbepop (guest, #185603) on Aug 10, 2026

Recent benchmarks for the upcoming Wasmi (WebAssembly Interpreter) emphasize the critical nature of TCO. Wasmi utilizes direct-threaded code (via tail-calls) for dispatch, though it can be toggled to other modes.

Dispatch Method Comparison

The performance delta is staggering:

  • Loop-Switch Dispatch: Slowest (Old-school approach).
  • Indirect-Threading: Moderate.
  • Direct-Threaded (Tail-Calls): Fastest.

Because of this, all top-tier Wasm interpreters—including Wasmi, Wasm3, and Stitch—rely on tail-calls. In my view, native TCO is not just a feature, but a fundamental requirement for any language claiming to be a true system programming language.

// Conceptual representation of a tail-call dispatch
void execute_instruction(Instruction *ins) {
    switch (ins->opcode) {
        case OP_ADD: return handle_add(ins->next); // Tail call
        case OP_SUB: return handle_sub(ins->next); // Tail call
        // ...
    }
}

Copyright 2026, Eklektix, Inc. Comments and public postings are copyrighted by their creators.