Skip to main content

Module varargs

Module varargs 

Source
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 area

va_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_list is, which is what a va_copy moves.
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_copy and va_end in the function, and leaves va_start alone.