Skip to main content

Module tail

Module tail 

Source
Expand description

A call in tail position, as a jump once the frame is given back.

Design: spec/optimizer/25-tail-calls.md section 25.2, the sibling call.

return f(x) needs nothing of the caller once f is running, so the caller can give its frame back first and jump to f, and f returns straight to whoever called the caller. That saves a call and a return, and it makes a chain of calls like that run in the same stack however long it is, which is what a state machine written as functions calling each other relies on. gcc does it at -O2 and -Os, and so does this.

§Three places

mark works on the IR, before selection. It turns a direct call whose results are exactly what the block returns next into a tail_call, which ends the block the way a return did, and it turns down the whole function when the callee could see something of the caller’s frame. Its answer is what refusal says, a reason rather than a no.

crate::lower builds a tail_call as the call and the return it stands for, and writes the call down as a Tail when the convention put every argument in a register. A call that needs the argument area is left as the call it was: the area is the bottom of this function’s frame, and the frame is gone by the time the callee would read it.

jumps runs last, on machine code with every register handed out and the epilogue written. Each Tail whose block goes straight from the call to the epilogue, with nothing in between touching a register the call reads or writes, loses its call, and the ret at the end becomes a jmp to the callee. Anything else stays a call and a ret, which is right, just not as short.

§Why the frame check is this strict

The one thing that makes a tail call wrong is a pointer into the frame that is given back, and the section above asks for “provably not reachable” rather than “not known to be”. The only addresses into a frame the IR has are the ones an alloca makes, so a function with no alloca has nothing a pointer could point at, and that is the rule. It turns down functions an escape analysis would let through, which costs a few calls and nothing else.

Structs§

Tail
One call crate::lower built for a tail_call, and the pseudos that leave its answer where the caller’s answer goes.

Functions§

jumps
Turns each Tail that can be into the epilogue and a jump, and says how many it turned.
mark
Turns every call in tail position into a tail_call, unless refusal has a reason not to, and says how many it turned.
refusal
Why no call in this function can be made in tail position, or None when one can.