Expand description
The float that is narrower than any instruction, as the work at a wider one.
_Float16 is the other arithmetic type a C program writes that this machine does not compute
with. It is the mirror of crate::quad: sixteen bits fit in a vector register, so moving one,
passing one and returning one are things this back end has, and everything else has no
instruction behind it, because half precision arithmetic on x86-64 arrived with AVX512FP16 and
that is far above this target’s baseline. gcc 16 is in exactly the same position at the same
baseline and does exactly this, so what this pass writes is what gcc writes.
The difference from the quad is that the half has somewhere to go. Every value of this format
is a value of float exactly, since eleven bits of significand and five of exponent fit inside
twenty four and eight with room to spare, so the operation the program wrote is the float
operation with a widening in front of it and a narrowing behind it. Only the widening and the
narrowing are calls.
§Why float in the middle is the same answer and not merely a close one
Because twenty four is at least two times eleven plus two. That is the double rounding bound:
an addition, a subtraction, a multiplication or a division of two values of a format with p
significant bits, computed in a format with at least 2p + 2 bits and then rounded to the first
one, is the same value as the operation rounded once. A half has eleven bits and a float has
twenty four, which clears the bound by one, so a + b at this format really is
__truncsfhf2(extend(a) + extend(b)) and not an approximation of it. The exponent range is not
in the way either: float holds every product and every quotient of two halves, subnormal ones
included, without overflowing or losing a bit to its own subnormal range.
This is also why the comparisons are not calls the way the quad’s are. Widening is exact and
exactness is all a comparison needs, so two halves compare as the two floats they widen to,
and every predicate including the unordered ones comes out the same. libgcc does define
__eqhf2 and __nehf2, and gcc calls neither of them on this target for the same reason.
§The narrowing is where the care goes
Rounding twice is not rounding once. A double that sits a hair above the midpoint between two
halves rounds down to exactly that midpoint in a float, and then to even from the midpoint,
which is the other neighbour from the one a single rounding gives. So there is a routine per
source width, __truncsfhf2, __truncdfhf2 and __trunctfhf2, and this pass picks the one
that matches what the program actually had rather than going through float every time.
An integer becoming a half goes through double for the same reason and is exact all the same.
Every integer a half can represent is below 65536 and is therefore exact in a double, and
every integer at or above 65520 is an infinity at this format whatever happened on the way, so
there is no value of any integer width where the intermediate rounding to double can change
the answer. That is why this needs no __floatsihf family and libgcc has none.
§What is left alone
A conversion against the eighty bit format, which is the one thing here with a libgcc routine
that this pass does not call. __truncxfhf2 exists and would be the right answer, and what
stands in the way is that an eighty bit value travels in the argument area as bytes rather than
in a register, so a call taking one is a different shape from every other call written here. It
is refused by name instead, which is the position crate::quad takes on the same pair, and it
is tamnd/rucc#1064’s row rather than this pass’s work today.
A constant of the format and a negation of one are left alone as well, and those two are not
refusals. crate::expand::floats already writes a float constant as the integer spelling its
bits and a reinterpretation, and a negation as an exclusive or with the sign bit in a general
purpose register, and both of those are written for every float of sixty four bits or narrower
rather than for the two the machine computes in. So a half reaches them and comes out as the
bits and a bitcast, which is what the rule set now has an instruction for.
A load is left alone too, because the machine has one. pinsrw reads sixteen bits straight into
the low lane of a vector register and a rule writes it. A store is not the mirror of that: the
form of pextrw that writes memory is SSE4.1, so a store is the bits out through a general
purpose register and an ordinary sixteen bit store after them, which is two instructions and so
this pass’s work rather than a rule’s.
Functions§
- calls
- Rewrites every operation at this format into the work at a wider one.