6 ms·
The Ffast and the Furious
- wffurr 4y ago"fun and safe" math. Never-ending source of weird graphics bugs.
- amluto 4y agoI filed this bug almost 10 years ago. Sigh. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55522 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55522
- jcranmer 4y agoAnnoyingly, Clang seems unwilling to make any changes until gcc changes its mind.
- duped 4y agoAnother victim of shared libraries.
- 323 4y agoVictim 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 4y agoBut 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?
- tlb 4y agoThat’s true about many cpu registers, but compilers take care of it correctly for everything except floating point flags.
- 323 4y agoIf you're using fibers it's assumed you know what your doing.
- duped 4y agoDepending on the implementation some green threads will save the mxcsr register during context switches (iirc ucontext does this on Linux) but some "fast" context switching libraries will intentionally avoid it to save some cycles since it requires a syscall.
- duped 4y agoThat's just wrong. The problem is a constructor in the .so being called as it's loaded. There is no way to guarantee state is safe between calls to functions loaded from shared libraries or when they're linked without expensive checks. The point is that there is state being mutated due to the fact we rely on shared libraries and allow dynamic libraries to mutate that state when they're loaded. This is not a problem with statically linked libraries. And also note, I made a point of talking about shared libraries - not dynamic linking. They're not equivalent. The issue here is that the libraries are shared and do bad things, not that they're loaded dynamically. It would be fine if the library wasn't shared!
- planede 4y agoStatically linked libraries can have global constructors all the same, what are you talking about?
- jwilk 4y agoRelated: https://news.ycombinator.com/item?id=32738206 https://news.ycombinator.com/item?id=32738206 ("Someone’s Been Messing with My Subnormals", 2 days ago, over 120 comments)
- StefanKarpinski 4y agoThe Julia ecosystem (BinaryBuilder specifically) forbids libraries built with -ffast-math for exactly this kind of reason.
- warmwaffles 4y agoReminds 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 https://hovav.net/ucsd/dist/geometry.pdf
- an1sotropy 4y agoThe 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 4y agoUpdates welcome to make it less contrived! :) It's clearly very confused code, but it does work under normal FP behavior.
- an1sotropy 4y agoThank you for creating and sharing this example. I think the fact of merely loading a shared library as modifying the FP behavior is the real shocker, more than any particular FP computation that hinges on denormals. Still, I think one of the great wins with denormals is that they make "x == y iff x-y == 0" true; absent denormals you can have two normalized x and y, x != y, such y-x evaluates to 0. So for an example, maybe something that involves y > x and yet ceil(y-x) == 0, which is surprising. The only setting I can think of is something like making a histogram (with flipped indices, like your pixel row) of differences of values, so that ceil(y-x) == 0 leads to an invalid histogram index. Some values are x=1e-32 and y=1.0000011e-32, which parse (via sscanf) to: x = 0:00010100:10011111011000100011111 = (-1)^0 * 2^( 20-127) * (1+5222687/2^23) y = 0:00010100:10011111011000100101110 = (-1)^0 * 2^( 20-127) * (1+5222702/2^23) and then the denormalized difference d = x-y should be: d = 0:00000000:11110000000000000000000 = (-1)^0 * 2^(1-127) * (0+7864320/2^23) Having some specific values sidesteps concerns about nextafterf(). In this case I think y is many ulps above x, not just 1.
- moyix 4y agoThanks for this excellent idea! I implemented it in this histogram plotting demo: https://github.com/moyix/2_ffast_2_furious https://github.com/moyix/2_ffast_2_furious
- MauranKilom 4y agoIf 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 4y agoNot 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 4y ago`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/bfa7ed6b1d76c114e19b379e1b9be16f https://gist.github.com/ridiculousfish/bfa7ed6b1d76c114e19b3...
- moyix 4y agoI don't think this is correct. The return value of nextafterf is a float, and the ABI requires those to be returned in xmm0. So although nextafterf is using integers internally, the flush to zero happens inside of nextafterf when it moves eax into xmm0; even though eax is 1, xmm0 becomes 0.0. You can see that by stepping through it in gdb: https://gist.github.com/moyix/846afb7fcec7c618a6ef52d93b4ca7b9 https://gist.github.com/moyix/846afb7fcec7c618a6ef52d93b4ca7...
- zokier 4y agoI do find it bit odd that all the floating point environment control flags are usually pretty well hidden away in most programming languages; I feel those are something that could be more clearly exposed, maybe even as a first-class language feature. But even just having some raii-style scoped environments could be neat
- kccqzy 4y agoIt's that way because processors make it a special register (MXCSR) rather than something that you can specify on a per-operation basis.
- planede 4y agoIMO if it was properly exposed in C, or even as a GNU extension, then "fast math" would be part of the function type. 1. A non-fast-math function calling a fast-math function (or the other way around) should result in the floating point environment to be changed on entering the function, and reset on leaving the function. 2. A function calling an other function of the same kind shouldn't touch the floating point environment. 3. Function pointers to different kind of functions shouldn't be convertible to each other (that is `void ()() __attribute__((fast_math))` and `void ()()` are unrelated types). As for a compiler flag altering the default "fast_math" or "no_fast_math" attribute, I'm not sure about. It's probably a bad idea. Libraries that want to make this configurable can always do their own macro. Altering the floating point environment for everybody in a global constructor within the library is just horrible.
- Storeblog 4y ago
- IncRnd 4y agoEric Holder ruined the phrase, "Fast and Furious," for me. Now, whenever I see that phrase, I am reminded of gun-running US attorneys held in Contempt of Congress.