> The key to this optimisation idea is the exponent gets truncated back to 8 bits when being written back to memory.
That's incorrect - the clamping of the exponent only occurs if you were to use FST/m32 or FST/m64, but if you're using x87 you're presumably doing FSTP/m80fp so there is no truncation or rounding on store, regardless of the prevision flag in the control word.
It sounds like what you're trying to arrange is an optimization such that a rosetta like translator/emulator can optimize this highly awesome function to be performed entirely using hardware fp32 or fp64 support:
fp32 f(fp32 *fs, size_t count) {
// pseudo code obviously :D
ControlWord cw = fstcw();
cw.precision = Precision32;
fldcw(cw);
fp80 result = 0;
for (unsigned j = 0; j < count; j++) {
result += fs[j];
}
return (fp32)result;
// pretend we restored state before returning :)
}
The problem you run into though, is that an optimization pass can make decisions based on anything other than the code it is presented with. So your optimizer can't assume sign or magnitude here, so that += has to be able to under or overflow the range that fp32 offers.
Things get really miserable once you go beyond +/-, because you can end up in a position where an optimization to do everything in the 32/64 bit units means that you won't get observable double rounding.
This is kind of moot in the rosetta case as I don't believe we ever implemented support for the precision control bits
More fun are the transcendentals - x87 specifies them as using range reduction and so they are incredibly inaccurate (in the context of maths functions), especially around multiples of pi/4, and if you go test it you'll find rosetta will produce the same degree of terrible output :D
I was thinking more about functions along the lines of this vertex transform function that you might theoretically find as hot code in a late 90s or early 2000s windows game (before hardware transform and lighting).
void transform_verts(fp32 *m, fp32 *verts, size_t vert_count) {
// it's a game, decent chance it applies percision32 across the whole process
// Especially since directx < 10 automatically sets it when a 3d context is created
while (vert_count--) {
verts[0] = verts[0] * m[0] + verts[0] * m[1] + verts[0] * m[2] + m[3];
verts[1] = verts[1] * m[4] + verts[1] * m[5] + verts[1] * m[6] + m[7];
verts[2] = verts[2] * m[8] + verts[2] * m[9] + verts[2] * m[10] + m[11];
verts += 3;
}
}
Would be nice if we could optimise it all to pure hardware fp32 without any issues. But not really possible with those six operation long chains. And you are right, we can't really assume anything about the data.
But we can go for guards and fallbacks instead. Implement that loop body as something like
loop:
// Attempt calculation with hardware fp32
$1 = hwmul(verts[0], m[0])
$2 = hwmul(verts[0], m[1])
$3 = hwmul(verts[0], m[2])
$4 = hwadd($1, $2)
$5 = hwadd($3, $4)
$6 = hwadd($5, m[3]) // any infs from above sub-equations will saturate though to here
if any(is_subnormal_or_zero([$1, $2, $3, $4, $5]) || $6 is inf: // guard
// one of the above subcalulcations became either inf or subnormal, so our
// hwresult might not be accurate. recalculate with safe softfloat
$8 = swadd(swmul(verts[0], m[0]), swmul(verts[0], m[1]))
$6 = swadd(swadd($8, swmul(vert[0], m[2])), m[3])
verts[0] = $6
// repeat above pattern for verts[1] and verts[2]
goto loop
I think that produces bit-accurate results?
Sure, it might seem complicated to calculate twice. But the resulting code might end up faster than just pure softfloat code across average data. Maybe this is the type of optimisation that you only attempt at the highest level on a multi-tier JIT for really hot code. You could perhaps even instrument the function first to get an idea what the common shape of the data is.
> This is kind of moot in the rosetta case as I don't believe we ever implemented support for the precision control bits
So it's already producing inaccurate results for code that sets precision control? Might as well just switch over to hardware fp32 and fp64 /s
I guess for the rosetta usecase, Intel macs didn't until 2006 and so most of the install base of x86 programs will be compiled with SSE2 support, and commonly 64bit.
Probably the most common usecase for x87 support in rosetta will be 64bit code used long doubles and compilers/ABIs annoying implemented them as x87.
> So it's already producing inaccurate results for code that sets precision control? Might as well just switch over to hardware fp32 and fp64 /s
:D
But in practice the only reason for changing the x87 precision is performance, which was then simply retained in hardware for backwards compatibility. Modern code (as in >= SSE era) simply uses fp32 or fp64 which is faster, more memory compact, has vector units, has a much more sane ISA, etc. Anyone who does try to toggle x87 mode in general is in for a world of hurt because the system libraries all assume the unit is operating in default state.
You are correct that the only reason x86_64 needs x87 is that the unix x86_64 ABI decided to specify the already clearly deprecated format the implementation of long double. I often looked wistfully at win64 where long double == double.
That's incorrect - the clamping of the exponent only occurs if you were to use FST/m32 or FST/m64, but if you're using x87 you're presumably doing FSTP/m80fp so there is no truncation or rounding on store, regardless of the prevision flag in the control word.
It sounds like what you're trying to arrange is an optimization such that a rosetta like translator/emulator can optimize this highly awesome function to be performed entirely using hardware fp32 or fp64 support:
The problem you run into though, is that an optimization pass can make decisions based on anything other than the code it is presented with. So your optimizer can't assume sign or magnitude here, so that += has to be able to under or overflow the range that fp32 offers.Things get really miserable once you go beyond +/-, because you can end up in a position where an optimization to do everything in the 32/64 bit units means that you won't get observable double rounding.
This is kind of moot in the rosetta case as I don't believe we ever implemented support for the precision control bits
More fun are the transcendentals - x87 specifies them as using range reduction and so they are incredibly inaccurate (in the context of maths functions), especially around multiples of pi/4, and if you go test it you'll find rosetta will produce the same degree of terrible output :D