You can still do that with a caller-cleanup convention. Suppose you have a convention like
* Set up stack
* Call
* Clean up stack
and you have functions f(), g(), and h(), where g() and h() use this convention and f() calls into g(), and g() into h(). The sequence of instructions from f() to h() without TCO would be
* f: Set up stack for g()
* f: Call g()
* g: Do work
* g: Set up stack for h()
* g: Call h()
* h: Do work
* h: Return
* g: Clean up stack
* g: Return
* f: Clean up stack
And with TCO:
* f: Set up stack for g()
* f: Call g()
* g: Do work
* g: Move things around on the stack so that h()'s arguments are written where g()'s were. This may require a temporary stack allocation that's released before the next step.
* g: Jump to h()
(At this point it looks as if f() called h() directly.)
* h: Do work
* h: Return
* f: Clean up stack
This is always possible as long as h()'s caller-managed stack allocation is no bigger than g()'s.
The whole thread is about how the traditional calling convention makes it difficult to implement TCO in C. Functions with different arity having different stack layout is indeed one of the roadblocks, so I think we agree here, no?
> I can't see why the calling convention could matter.
The "could matter" was understood as "would influence TCO" and so the rest of the thread was devoted to explain how the two are connected, while you meant "should be fixed and not be changed at the compiler's whims".
And you are right, of course: if a function is static and its address is never taken, the compiler can choose whatever calling strategy it wants, possibly one that facilitates TCO.