Expand description
Where a function’s arguments already are when it starts running, and where a call puts its own.
Design: spec/12-abi-and-runtime.md.
This is the one part of the calling convention that is not a lowering rule, and it is worth
saying why, because everything else in this crate is. A rule matches a term and rewrites it,
and which register the third argument arrives in is not a fact about any term: it depends on
the argument’s position and on the classification of every argument before it. A pattern has
nowhere to put that. So the arguments are built here, by hand, out of what the convention
says, the same way crate::finish builds a prologue.
The classification itself is not here either. rucc-lower has already run it by the time a
function reaches this crate, which is why the parameters read here are plain scalars: an
aggregate has been split into the pieces it travels in, and a return through memory is an
ordinary pointer parameter in front of the rest. What is left for this is the step after
classification, from how a value travels to which register it is actually in, which is
rucc_target::Places.
§What it writes
One x64.arg_val_* per parameter, at the top of the entry block, each defining a fresh
register constrained to the one the argument arrived in. They encode to nothing. The point of
them is that a parameter has to be defined somewhere for the allocator to have anything to
move, and the entry block cannot define it as a block parameter: there is no edge into the
entry block for the move to go on, which is what rucc_regalloc::rewrite asserts.
What the allocator does with them is the whole of the argument sequence. A parameter that is read where it arrived costs nothing, and one that is not gets a copy, which is the same bargain the return already makes and is decided by the same code.
§A call
The same reasoning the other way round, and one instruction rather than several. x64.call is
the only opcode in the description whose operand vector is empty there, because nothing about
a call’s operands is the same from one call to the next, so they are built here: one read per
argument constrained to the register the convention passes it in, one definition for the value
that comes back constrained to the register it comes back in, and one definition per register
the convention does not preserve.
Those last ones are the clobbers, and they are the whole of what the allocator has to know about a call besides where the values go. Each is a definition of the physical register itself rather than of a value, since there is no value: it says the register is written here, which is exactly what stops the allocator from leaving something in one across the call. A register an argument or the result already names is not repeated, because naming it once already blocks it for the length of the instruction, which is all a clobber does.
What is not here is the bytes an argument past the last register goes in. That is a place in the frame and no frame exists yet, the same reason a parameter arriving there is reported rather than read, so a call is asked how many bytes it would need and reports it, and a call that would need any is turned down for now.
Structs§
- Calling
- One call, as everything about it that is not the function it is being built into.
- Made
- What one call came to.
- Refused
- Which of a call’s values could not be passed, and why.
Enums§
- Missing
- Why a parameter could not be brought in.
Constants§
- CALL
- What the instruction that calls a name is called.