Expand description
What a function does to read the arguments its own signature does not name.
Design: spec/12-abi-and-runtime.md, which is where the layout below comes from.
A variadic callee has a problem an ordinary one does not. Six of its arguments arrived in
general purpose registers and eight more in vector ones, and it cannot know which of those hold
anything, because what it was passed is a thing only the caller knew. Registers are also not
addressable, and va_arg walks arguments one after another at run time, which is walking
addresses. So the convention says the callee spills all fourteen of them into a block of its own
frame on the way in, and from then on every argument it was passed is somewhere in memory: the
ones that came in registers are in that block, and the ones that did not are in the caller’s
argument area where they were left.
That block is the register save area, and a va_list is four fields saying how far into the
arguments the walk has got:
offset 0 gp_offset bytes into the save area of the next argument from a gpr
offset 4 fp_offset bytes into the save area of the next argument from an xmm
offset 8 overflow_arg_area the next argument that came in the caller's memory
offset 16 reg_save_area the bottom of the save areava_start fills all four in. The two offsets do not start at zero: the arguments the signature
does name took registers too, and they took the first ones, so each offset starts past them.
va_arg is then one question asked at run time, which is whether the offset for its file has run
off the end of the save area. If it has not, the argument is in the save area and the offset
steps on by a slot. If it has, the argument is in the caller’s memory and the overflow pointer
steps on by a word instead.
§Why the layout is exactly the psABI’s and not a convenient one
Nothing outside the function can see the save area, so its shape looks like a private decision.
It is not one, because a va_list is a thing a program hands to another function, and the
function it usually hands it to is vfprintf in the C library, which somebody else compiled and
which walks the list by the rules in the psABI document. So the offsets are the document’s
offsets, the area is the document’s one hundred and seventy six bytes, and the eight bytes
between two general purpose slots and the sixteen between two vector ones are the document’s too.
What is not the document’s is what goes in the upper half of a vector slot, and the answer here
is nothing at all. A slot is sixteen bytes wide because the register is, and the low eight are
the whole of what any reader of a list looks at, since the widest thing va_arg names in this
compiler is a double. So the spill writes eight bytes per vector register rather than sixteen
and leaves the eight above them holding whatever the frame held. A reader that wanted all sixteen
would be reading a vector type, which is issue #200 and is not a thing yet.
§What is here and what is next door
va_arg becomes a compare and a branch, and this is where, because a rewrite that needs new
blocks has to happen before selection for the reason crate::expand gives. Everything it needs
is in the list it was handed, so it needs nothing from the frame and can run here.
va_start is the other way round. Three of the four fields it writes are distances into a frame
that does not exist yet, so it stays an instruction as far as crate::lower, which builds it
out of the frame the way it builds an alloca. The spill that fills the save area is written
there for the same reason.
Structs§
- Area
- How big a callee’s register save area is and where its two halves are.
Constants§
- FP_
OFFSET - Where the count of vector register bytes already walked is.
- GP_
OFFSET - Where the count of general purpose register bytes already walked is.
- OVERFLOW
- Where the pointer to the next argument in the caller’s memory is.
- SAVE_
AREA - Where the pointer to the bottom of the register save area is.
- SIZE
- How many bytes one
va_listis, which is what ava_copymoves. - VECTOR_
SLOT - How wide the slot one vector register is saved in is, which is how wide the register is whatever this actually writes into it.
Functions§
- lists
- Rewrites every
va_arg,va_copyandva_endin the function, and leavesva_startalone.