Expand description
The integer that is wider than a register, as the two registers it is held in.
__int128 is the one integer a C program on this machine writes that no register holds.
Everything else the front end produces is a width the machine has, or is a width
crate::widths rounds up into one, and neither of those is true here: there is nothing to
round up into above sixty four bits. What there is, is two registers, and the convention already
says so. System V classifies a __int128 as two eightbytes of class INTEGER, so it travels in a
pair of general purpose registers, comes back in the pair a return comes back in, and sits in
memory as two words with the low one first. That is what this pass writes down.
Every value a hundred and twenty eight bits wide becomes two values of sixty four, a low half and a high half, and every instruction over such a value becomes instructions over the halves. After it there is no value of that width left anywhere in the function, which is what lets the rest of the back end stay written about widths the machine has. Nothing below this knows the type existed.
§Why a pass and not a rule
A rule matches a term and rewrites it into instructions of the machine, and the selector works a
value at a time. There is no register a value this wide can be selected into, so there is
nothing for a rule to produce, and a rule that produced a pair would have to say which register
each half landed in, which is the allocator’s answer and not a rule’s. So the splitting happens
before selection, in the IR, where a value is still something a pass may make two of. That is
the same reasoning crate::widths follows from the other end, and the two are the two halves
of one sentence: nothing reaching the selector is at a width the machine has no register for.
§What crosses the boundary
A parameter and a return value are agreed with something this compilation is not looking at, so
splitting one is a claim about where the two halves are. The claim is true when both halves land
in registers, because the convention hands out argument registers in order and two halves in a
row take the two registers the whole value would have taken. It is not true when they do not: a
value the convention could not fit in registers travels in the argument area as sixteen bytes
aligned to sixteen, and two independent words travel as two words each aligned to eight, which
is a different place as soon as an odd number of words went before them. So a function whose
wide parameter would run out of registers is left exactly as it was and refused by name, the
same as a function this pass does not understand. tamnd/rucc#351 carries what passing one in
memory would take, which is a form of parameter the IR has no way to spell today.
§Dividing and converting are calls into the runtime
Every other operation at this width is the same operation over the halves with whatever crossed
between them put back. A quotient is not. The halves of a quotient are not a function of the
halves of its operands taken apart, at this width or at any other, which is why every compiler’s
runtime has a division routine in it and none of them has an addition one. So a divide and a
remainder become a call to the routines runtime/builtins/div.c defines, which are libgcc’s four
names and libgcc’s signatures, and spec/12-abi-and-runtime.md section 12.8 is what they are.
A conversion to or from a floating point value is the other one, for a plainer reason: the
machine’s own conversion reaches sixty four bits and no further, so there is no instruction to
split into. Those are the eight names runtime/builtins/convert.c defines, one for each of a
signed and an unsigned integer against a float and a double in each direction, and the four
runtime/builtins/quad.c defines for a _Float128, which has no instruction of its own at any
width and so is a call here for both reasons at once. An eighty bit float is not among them,
because this machine has no register that holds one and the back end says so, which is
tamnd/rucc#326, so a function converting at that width is left alone here and refused below the
way every function of this width used to be.
The call is built with the halves already in it, four parameters of sixty four bits for the two
operands of a divide and two results for the answer, or two parameters and a float, or a float
and two results, which is the shape this pass gives a call it found in the program anyway. Both
ends agree because the convention puts a __int128 argument in two registers in a row and hands
out argument registers in order, which is the same sentence the section below about crossing the
boundary is.
Functions§
- halves
- Splits every integer the machine holds in two registers into the two halves it holds it in.