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

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.
Refused
Which of a call’s values could not be passed, and why.

Enums§

Callee
What a call goes to.
Missing
Why a parameter could not be brought in.

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.
store_of
What the instruction that writes an argument of that type into memory is called.