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
and x64.call_reg are the only opcodes 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.
A call through an address has one operand more, which is the address, and it is the one operand of a call that is a fact about the instruction rather than about the signature. It goes in front of the arguments, because the assembler has to find it and an index into a vector whose length depends on the convention is not a way of finding anything.
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§
Constants§
- CALL
- What the instruction that calls a name is called.
- CALL_
REG - What the instruction that calls an address in a register is called.