converting 1.475740e+19 to uint64_t: well-defined or undefined behaviour?

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I'm trying to debug a portability issue in the Erlang VM, which uses
FP exception status flags to detect various invalid FP operations.

In one specific case it converts a double containing 1.475740e+19 to
uint64_t through an assignment.  This works when compiled by gcc-4.8,
but gives an unexpected FE_INVALID exception when compiled by clang-3.6.0.
My host is Fedora 20 Linux x86_64, but the issue also occurs on FreeBSD
and OSX.

I'm trying to determine if the source code invokes undefined behaviour,
or if clang miscompiles it.

The following standalone testcase illustrates the issue:

==snip==
#include <fenv.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
double dblval = 1.475740e+19;

__attribute__((__noinline__))
uint64_t mytrunc(double d)
{
    uint64_t x;

    x = d; /* trunc */
    return x;
}

int main(void)
{
    uint64_t result;
    int excepts;

    printf("dblval:     %f\n", dblval);
    printf("INT64_MAX:   %" PRId64 "\n", INT64_MAX);
    printf("UINT64_MAX: %" PRIu64 "\n", UINT64_MAX);

    if (feclearexcept(FE_ALL_EXCEPT) == -1) {
	perror("feclearexcept");
	exit(1);
    }
    result = mytrunc(dblval);
    excepts = fetestexcept(FE_ALL_EXCEPT);
    printf("mytrunc:    %" PRIu64 "\n", result);
    printf("excepts:    %x\n", excepts);

    if (excepts)
	abort();

    return 0;
}
==snip==

Compiling with gcc-4.8 and then running it I see:

dblval:     14757400000000000000.000000
INT64_MAX:   9223372036854775807
UINT64_MAX: 18446744073709551615
mytrunc:    14757400000000000000
excepts:    0

but with clang-3.6.0 excepts is 1 (FE_INVALID) and it aborts.

Reading the C standard (e.g. C1x n1494 section 6.3.1.4), it seems to me that
the real value is trivially representible in uint64_t (it's non-negative and
in range), so this should not invoke undefined behaviour, and in that case
clang is miscompiling the code.

gcc-4.8 -O2 generates the following code for mytrunc:

0000000000400840 <mytrunc>:
  400840:       f2 0f 10 0d 10 01 00    movsd  0x110(%rip),%xmm1        # 400958 <_IO_stdin_used+0x68>
  400847:       00 
  400848:       66 0f 2e c1             ucomisd %xmm1,%xmm0
  40084c:       73 0a                   jae    400858 <mytrunc+0x18>
  40084e:       f2 48 0f 2c c0          cvttsd2si %xmm0,%rax
  400853:       c3                      retq   
  400854:       0f 1f 40 00             nopl   0x0(%rax)
  400858:       f2 0f 5c c1             subsd  %xmm1,%xmm0
  40085c:       48 ba 00 00 00 00 00    movabs $0x8000000000000000,%rdx
  400863:       00 00 80 
  400866:       f2 48 0f 2c c0          cvttsd2si %xmm0,%rax
  40086b:       48 31 d0                xor    %rdx,%rax
  40086e:       c3                      retq   
  40086f:       90                      nop

which seems to perform range reduction before the cvttsd2si, while clang-3.6.0 -O2
generates:

0000000000400770 <mytrunc>:
  400770:       f2 0f 10 0d 68 01 00    movsd  0x168(%rip),%xmm1        # 4008e0 <__dso_handle+0x8>
  400777:       00 
  400778:       0f 28 d0                movaps %xmm0,%xmm2
  40077b:       f2 0f 5c d1             subsd  %xmm1,%xmm2
  40077f:       f2 48 0f 2c c2          cvttsd2si %xmm2,%rax
  400784:       48 b9 00 00 00 00 00    movabs $0x8000000000000000,%rcx
  40078b:       00 00 80 
  40078e:       48 31 c1                xor    %rax,%rcx
  400791:       f2 48 0f 2c c0          cvttsd2si %xmm0,%rax
  400796:       66 0f 2e c1             ucomisd %xmm1,%xmm0
  40079a:       48 0f 43 c1             cmovae %rcx,%rax
  40079e:       c3                      retq   
  40079f:       90                      nop

which seems to perform two conversions, one on the original parameter and one on
a range-reduced version, and then selects the correct integer to return afterwards,
but then the FP exception will already have been signalled (by the cvttsd2si at
0x400791).

So, is the source code broken or did clang miscompile it?




[Index of Archives]     [Linux C Programming]     [Linux Kernel]     [eCos]     [Fedora Development]     [Fedora Announce]     [Autoconf]     [The DWARVES Debugging Tools]     [Yosemite Campsites]     [Yosemite News]     [Linux GCC]

  Powered by Linux