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 nearly all 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.
The one parameter that is not a scalar is an aggregate the classification put in the argument
area whole, which is rucc_ir::Abi::ByVal. The IR calls it a pointer, because a pointer is
what an instruction reading it has to have, and the convention says the bytes travel and the
pointer does not. So this is the one place that reads what the classification said rather than
only the type, on both sides of the call, and the two sides are the two halves of one copy.
§What it writes
One x64.arg_val_* per parameter that arrived in a register, 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 parameter whose bytes travelled is the exception to all of that. Its bytes are already in
this function, at a place in the caller’s argument area the same walk gives, so nothing is
brought in at all: what the parameter is is where they are, and that is one lea. It waits on
the frame the way the loads below it do, and for the same reason.
A parameter past the last register arrived in the caller’s memory rather than in a register, so
it is a load and not a pseudo, and it is a real instruction that encodes to real bytes. How far
up the caller’s argument area it is is a number rucc_target::Places answers here, but where
that area is from inside this function is a distance into a frame, and no frame exists until
after allocation. So the load is written with nothing in its displacement, which of the two
registers it reads through is left to be settled too, and both are filled in by crate::finish
out of crate::frame::Frame::incoming. That is the same bargain an alloca already makes,
for the same reason and in the same two places.
§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.
An argument past the last register the convention has for it is a store into the outgoing area
rather than an operand of the call, written in front of the call in the same block. Where that
area is does not have to wait for the frame the way the incoming one does, because the outgoing
area is at the bottom of the frame and the bottom of the frame is where the stack pointer is:
that is the whole reason the frame puts it there, since it is where the callee will look. So the
offset rucc_target::Places gives back is the offset the store is written with.
An object passed by value in memory is the same thing again and a copy rather than a store. The
caller owes the callee a copy it is free to write to, which is what makes a C call by value
different from passing a pointer the callee must not keep, and the argument area is where the
convention says that copy goes. So the bytes are read out of the object and written into the
area a word at a time, in front of the call, with the words chosen by the same function that
chooses them for a memcpy. An object with more words than that unrolls to is turned down,
because the copy it wants is a call to the runtime and one call cannot be built inside another.
The call still reports how many bytes it needed, because the frame reserves as many as the widest call in the function asked for and cannot know that until every call has been seen.
Structs§
- Arrived
- What a function’s parameters came to.
- Calling
- One call, as everything about it that is not the function it is being built into.
- Made
- What one call came to.
- Passing
- One value a call passes.
- 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.
Functions§
- call
- Builds one call: what it passes, what comes back, and what it destroys.
- entry
- Binds a function’s parameters to where the convention says they arrive.
- head_of
- What the pseudo for an argument of that type is called.
- load_of
- What the instruction that reads an argument of that type out of memory is called.
- refuses
- Why a value of that type cannot travel at all, or nothing if it can.
- ret_of
- What the instruction that leaves a returned value in its register is called, for the value at that place in its own register file.
- store_
of - What the instruction that writes an argument of that type into memory is called.