fenv.h fixes for softfloat

Ian Lepore freebsd at damnhippie.dyndns.org
Tue Jan 17 19:10:41 UTC 2012


On Mon, 2012-01-16 at 14:51 -0500, David Schultz wrote:
> On Mon, Jan 16, 2012, Ian Lepore wrote:
> The problem is that double->long conversions use the __fixdfsi
> function in softfloat, but there's no equivalent double->longlong.
> Instead, a semi-bogus __fixdfdi function is provided in libc/quad.
> It'll take a little while to come up with a good fix.

Okay, so cast to 64 bits is a known problem.  To see if any other
problems are lurking in this area I redefined assert to be non-fatal in
test-lrint and ran it so we could see the results of the later tests.

It looks like there are two failures (at lines 104 and 108) that aren't
related (at least obviously/directly) to the llrint failures.  I'll
paste the whole log in case my sense of what's "obvious" is wrong... 

        1..1
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0) file test-lrint.c line 96
            while testing llrint: x=1 result=1 excepts=0x10
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0) file test-lrint.c line 96
            while testing llrintf: x=1 result=1 excepts=0x10
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0) file test-lrint.c line 96
            while testing llrintl: x=1 result=1 excepts=0x10
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0) file test-lrint.c line 97
            while testing llrint: x=3.05418e+08 result=305418240 excepts=0x10
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0) file test-lrint.c line 97
            while testing llrintf: x=3.05418e+08 result=305418240 excepts=0x10
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0) file test-lrint.c line 97
            while testing llrintl: x=3.05418e+08 result=305418240 excepts=0x10
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 104
            while testing lrint: x=2.14749e+09 result=0 excepts=0x11
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 108
            while testing lrint: x=-2.14749e+09 result=0 excepts=0x11
        
        nfassert: (llrint)(_d) == (0) || fetestexcept(FE_INVALID) file test-lrint.c line 136
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 136
            while testing llrint: x=9.22337e+18 result=0 excepts=0
        
        nfassert: (llrintf)(_d) == (0) || fetestexcept(FE_INVALID) file test-lrint.c line 137
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 137
            while testing llrintf: x=9.22337e+18 result=0 excepts=0
        
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0) file test-lrint.c line 138
            while testing llrint: x=9.22337e+18 result=9223372036854774784 excepts=0x10
        
        nfassert: (llrint)(_d) == (0) || fetestexcept(FE_INVALID) file test-lrint.c line 140
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 140
            while testing llrint: x=-9.22337e+18 result=0 excepts=0x10
        
        nfassert: (llrintf)(_d) == (0) || fetestexcept(FE_INVALID) file test-lrint.c line 141
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 141
            while testing llrintf: x=-9.22337e+18 result=0 excepts=0
        
        ok 1 - lrint
        
        
Zooming in on the two lrint failures, it looks like the rint() routine
raises INEXACT and the cast to long raises INVALID.  I came up with that
by using instrumented code to print the state of the exception flags at
various points within s_rint.c and s_lrint.c

The s_lrint.c instrumentation was a problem, and I figured I'd better
detail it in case it indicates some wider subtle problem.  My goal was
to capture and display the flags after rint(), then clean them out so I
could separately display the flags raised by the cast, then blend the
flags back together so that the flags seen by the caller would be the
same as they would have been without my instrumentation changes:
        
        dtype
        fn(type x)
        {
            fenv_t env;
            volatile dtype d;
            volatile type rx;
        
            feholdexcept(&env);
            rx = roundit(x);
            printf("after rint %#x\n", fetestexcept(FE_ALL_EXCEPT));
            if (fetestexcept(FE_INVALID))
                feclearexcept(FE_INEXACT);
            feupdateenv(&env);
        
            feholdexcept(&env);
            printf("before cast %#x\n", fetestexcept(FE_ALL_EXCEPT));
            d = (dtype)rx;
            printf("after cast  %#x\n", fetestexcept(FE_ALL_EXCEPT));
            feupdateenv(&env);
            printf("after update %#x\n", fetestexcept(FE_ALL_EXCEPT));
            return (d);
        }

Here are the run results (testing just lines 104 and 108, by moving them
into main() making them lines 158 & 160 respectively):

        tflex# ./test-lrint
        1..1
        rint 0: except 0
        rint 1: except 0 i0=0x41dfffff i1=0xffe00000 sx=0 j0=30
        rint 6: except 0 i=0x3fffff j0=0x1e
        rint 8: except 0x10 i0=0x41dfffff i1=0xffe00000 x=2.14749e+09 w=4.50361e+15 result=2.14749e+09
        after rint   0x10
        before cast  0
        after cast   0x1
        after update 0x11
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 158
            while testing lrint: x=2.14749e+09 result=0 excepts=0x11
        
        rint 0: except 0
        rint 1: except 0 i0=0xc1e00000 i1=0x100000 sx=1 j0=31
        rint 6: except 0 i=0x1fffff j0=0x1f
        rint 8: except 0x10 i0=0xc1e00000 i1=0x100000 x=-2.14749e+09 w=-4.50361e+15 result=-2.14749e+09
        after rint   0x10
        before cast  0
        after cast   0x1
        after update 0x11
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 160
            while testing lrint: x=-2.14749e+09 result=0 excepts=0x11
        
Okay, so all of that is what I summarized above.  Here's the problem: 

To get that output, I had to change __softfloat_float_exception_flags to
be declared as volatile in all 3 places it's mentioned (two extern decls
and the definition).  I tried just casting to volatile in
fetestexception() and that alone wasn't enough to printf everything
correctly.

Before changing that var to volatile, I got this output from the
instrumented code:

        tflex# ./test-lrint
        1..1
        rint 0: except 0
        rint 1: except 0 i0=0x41dfffff i1=0xffe00000 sx=0 j0=30
        rint 6: except 0 i=0x3fffff j0=0x1e
        rint 8: except 0x10 i0=0x41dfffff i1=0xffe00000 x=2.14748e+09 w=4.5036e+15 result=2.14748e+09
        after rint   0
        before cast  0
        after cast   0x1
        after update 0x11
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 158
            while testing lrint: x=2.14748e+09 result=0 excepts=0x11
        
You can see the insanity there that drove me to try using volatile:
rint() says it has raised INEXACT just before returning, but printing
the flags immediately upon return from rint() showed 0.  But, note that
the flag variable was correct because the final feupdateenv() raised it
again, so the second feholdenv() must have seen it set, right?  

But if you can't print the right values along the way, then the compiler
is somehow feeling free to re-order the code in such a way that the
printfs don't show the right values.  If it could do that in such a way
as to mess up the printfs, could it do so in a way that had more serious
side effects?

Notice also that I declared the intermidate vars 'd' and 'rx' as
volatile.  This was also necessary to prevent re-ordering the code in a
way that ruined more than just printfs.  It was deferring the cast until
the return statement, making all the code trying to print flags and
manipulate the fenv between the cast and the return moot.  Even though
the cast is accomplished with a function call, it's almost as if the
optimizer isn't treating it as a normal function call that can have the
side effect of modifying a global variable.

Note that this last one seems like a real problem, not just "my printfs
aren't right".  If I add feclearexcept(FE_INVALID) right before the last
feupdateenv(), then when the cast raises FE_INVALID it should get
cleared and not be seen by the caller of lrint().  But because the cast
gets deferred until the return, the caller does see FE_INVALID:

        tflex# ./test-lrint
        1..1
        rint 0: except 0
        rint 1: except 0 i0=0x41dfffff i1=0xffe00000 sx=0 j0=30
        rint 6: except 0 i=0x3fffff j0=0x1e
        rint 8: except 0x10 i0=0x41dfffff i1=0xffe00000 x=2.14748e+09 w=4.5036e+15 result=2.14748e+09
        after rint   0x10
        before cast  0
        after cast   0
        after update 0x10
        nfassert: fetestexcept(FE_ALL_EXCEPT) == (0x0001) file test-lrint.c line 158
            while testing lrint: x=2.14748e+09 result=0 excepts=0x11

Note that without 'd' being declared volatile the exception flags are
0x11 after the call even though the 0x01 flag was cleared before
returning from lrint() (the global flags var was volatile for this run;
from the printfs it looks like it was never set, but remember that's
because the optimizer relocated the cast to the point of the return).

This may all come down to a big steaming pile of compiler buggage that
you can't do anything about, but I figured I'd dump out everything I've
seen so that if you get future reports of odd behavior you're at least
forearmed with some knowledge of the ways wonky things can happen.

-- Ian





More information about the freebsd-arm mailing list