Skip to main content

Module half

Module half 

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