svn commit: r279981 - in head: contrib/compiler-rt/lib/builtins lib/libcompiler_rt

Bruce Evans brde at optusnet.com.au
Sat Mar 14 18:49:14 UTC 2015


On Sat, 14 Mar 2015, Dimitry Andric wrote:

> Log:
>  Pull in r231965 from upstream compiler-rt trunk (by Jörg Sonnenberger):
>
>    Refactor float to integer conversion to share the same code.
>    80bit Intel/PPC long double is excluded due to lacking support
>    for the abstraction. Consistently provide saturation logic.
>    Extend to long double on 128bit IEEE extended platforms.

I hoped that this would fix a longstanding conversion bug, but that bug
is actually for integer to float conversion, and the conversion is inline.

clang can't even convert integer 0 to floating point correctly (when the
integer has type uintmax_t and is variable with value 0, and the rounding
mode is downwards):

X #include <fenv.h>
X #include <stdint.h>
X #include <stdio.h>
X 
X int
X main(void)
X {
X 	volatile uintmax_t er = 0;
X 
X 	fesetround(FE_DOWNWARD);
X 	printf("%.4f\n", (double)er);
X 	return (0);
X }

clang generates broken inline code giving a result of -0.0000.  It does
a magic conversion involving loading the variable as an integer, shuffling
bits, subtracting a double and adding a double.  The subtraction gives
-0.0 when the rounding mode is downwards.

gcc48 generates apparently-correct inline code.  It does a less magic but
slower conversion involving:
- for values not exceeding INT64_MAX, just cvtsi2sdq
- for values exceeding INT64_MAX, modify er to ((er >> 1) | (er & 1)),
   convert that using cvtsi2sdq, then double the result.

Does this commit fix the differences between the runtime calculations
and compile-time calculations for overflowing cases?  Saturation logic
should do this.  My old test programs (previously last tested in 2004)
show the differences.  Compilers produced much the same garbage in
1994, 2004 and 2015.  Before this commit, they do the following:
- gcc48 saturates at compile time.  Its runtime results are inconsistent
   except for some cases of converting negative values to unsigned:
   - generally, the result is 0x8000000000000000 in bits for 64-bit
     values and 0x80000000 for 32-bit values.  This is the corect x86
     value since it is what is generated on overflow by the hardware.
     Call it IOV.  gcc corrupts even this value:
   - overflowing u_long or long -> (u_long)IOV or (long)IOV, OK
   - overflowing u_long, positive value -> 0
   - overflowing u_long, negative value -> (u_long)IOV, OK, but it is weird
       that negative values overflow to a larger value than positive values.
   - this is with 64-bit longs on amd64.  For conversions to u_int and int,
     the results are the same (with the the 32-bit IOV), except for the
     weird last result.  Now:
   - overflowing u_int, negative value -> 0.  This is the one case for
     unsigned types where the runtime result is consistent with the compile
     time result.
- clang gives identical results.
gcc was much more inconsistent in 1994.  Its typical behaviour was to
handle u_int by storing an int64_t and discarding the top bits.  uint64_t
is harder to handle and was more broken.

The behaviour is undefined on overflow, so these bugs are less serious
than for converting 0.  I prefer traps on overflow.  Everyone is used
to integer division trapping when the result would be infinite.  x86
hardware makes the trap for this impossible to avoid (but the kernel
could handle the trap and produce an in-band error result like IOV).
Converting FP infinity to integer should do the same.  Unfortunately,
x86 hardware make this trap hard to get efficiently (traps for it
would have to be enabled; then the kernel could produce a signal for
the integer case and emulate the hardware trap handling for the FP
case).

Bruce


More information about the svn-src-head mailing list