[HN Gopher] The Ffast and the Furious
       ___________________________________________________________________
        
       The Ffast and the Furious
        
       Author : mmastrac
       Score  : 64 points
       Date   : 2022-09-07 22:43 UTC (1 days ago)
        
 (HTM) web link (gist.github.com)
 (TXT) w3m dump (gist.github.com)
        
       | jwilk wrote:
       | Related:
       | 
       | https://news.ycombinator.com/item?id=32738206 ("Someone's Been
       | Messing with My Subnormals", 2 days ago, over 120 comments)
        
       | warmwaffles wrote:
       | Reminds me of "The Geometry of Innocent Flesh on the Bone:
       | Return-into-libc without Function Calls (on the x86)" [1]
       | 
       | [1]: https://hovav.net/ucsd/dist/geometry.pdf
        
       | StefanKarpinski wrote:
       | The Julia ecosystem (BinaryBuilder specifically) forbids
       | libraries built with -ffast-math for exactly this kind of reason.
        
       | an1sotropy wrote:
       | The author admits this ia a contrived demo, but I'm still trying
       | to understand its ostensible logic. The FTZ/DAZ problem is on
       | line 20:                   if (x >= nextafterf(0.0, INFINITY)) {
       | 
       | I think the idea here is: test if x is large enough so that the
       | subsequent ceilf() will give you an integer i_x >= 1 (because the
       | actual array index will be PIXBUF_WIDTH - i_x, and the max array
       | index is PIXBUF_WIDTH-1). With FTZ/DAZ, nextafterf(0.0, INFINITY)
       | == 0, which breaks the test.
       | 
       | But in this scenario, wouldn't you just test "if (x > 0)", since
       | that implies that ceilf(x) must be >= 1?
        
         | moyix wrote:
         | Updates welcome to make it less contrived! :) It's clearly very
         | confused code, but it does work under normal FP behavior.
        
       | MauranKilom wrote:
       | If I understand correctly, the nextafterf implementation in
       | crtfastmath.o linked in by the empty file compiled with -ffast-
       | math does not handle infinities, so nextafterf(0.0, INFINITY)
       | returns 0.0 (?) and thus i_x = 0 (whereas it should be 1 at
       | minimum since we access PIXBUF_WIDTH - i_x).
       | 
       | Did I get that right?
        
         | moyix wrote:
         | Not quite. nextafterf() is trying to return the smallest float
         | after 0.0 in the direction of infinity (infinity is still valid
         | even when the FPU is configured to flush denormal numbers to 0;
         | infinity isn't a denormal number). But since the smallest float
         | after zero is denormal, when a shared library built with
         | -ffast-math is loaded (which sets the FPU's FTZ and DAZ bit)
         | denormal numbers get forced to zero, and so nextafterf returns
         | 0.0 and the comparison becomes (x >= 0.0), which means that
         | x=0.0 passes the check and ceilf returns 0.0 rather than 1.0.
        
           | ridiculous_fish wrote:
           | `nextafterf` does not in fact return 0. FTZ flushes denormals
           | to zero when they are the result of an FP calculation, but
           | `nextafter` is implemented using _integer_ arithmetic,
           | because it is much easier: simply add 1 as an integer, with
           | special handling for Inf /NaN/-0.
           | 
           | It's only later that it gets treated as zero by DAZ: in the
           | FP comparison, and then the call to ceil(), both of which use
           | the FPU.
           | 
           | edit: confirmed, you can see it in action on this gist. When
           | compiled with -ffast-math then the denormal does not compare
           | greater than zero (it is flushed to zero by the comparison),
           | but its bit pattern is integer value 1, which is
           | nextafter(0).
           | 
           | https://gist.github.com/ridiculousfish/bfa7ed6b1d76c114e19b3.
           | ..
        
       | duped wrote:
       | Another victim of shared libraries.
        
         | 323 wrote:
         | Victim of shared state, not shared libraries.
         | 
         | You can change floating point modes dynamically, and it's
         | commonly done, and then it doesn't matter if the library was
         | dynamically or statically linked.
        
           | vitiral wrote:
           | But if you forget to change it back you may break other
           | invariants, right?
           | 
           | Also, what about threads or fibers? I would guess it is
           | thread-local but not fiber-local?
        
             | 323 wrote:
             | If you're using fibers it's assumed you know what your
             | doing.
        
             | tlb wrote:
             | That's true about many cpu registers, but compilers take
             | care of it correctly for everything except floating point
             | flags.
        
       | amluto wrote:
       | I filed this bug almost 10 years ago. Sigh.
       | 
       | https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55522
        
         | jcranmer wrote:
         | Annoyingly, Clang seems unwilling to make any changes until gcc
         | changes its mind.
        
       | wffurr wrote:
       | "fun and safe" math. Never-ending source of weird graphics bugs.
        
       ___________________________________________________________________
       (page generated 2022-09-08 23:01 UTC)