Skip to main content

Module abi

Module abi 

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

Functions§

call
Builds one call: what it passes, what comes back, and what it destroys.
entry
Binds a function’s parameters to the registers the convention says they arrive in.
head_of
What the pseudo for an argument of that type is called.