Skip to main content

Module wide

Module wide 

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