On Tue, Apr 28, 2020 at 10:15 AM Matthias Kretz <m.kr...@gsi.de> wrote: > > On Dienstag, 28. April 2020 09:21:38 CEST Richard Biener wrote: > > On Mon, Apr 27, 2020 at 11:26 PM Matthias Kretz <m.kr...@gsi.de> wrote: > > > On Montag, 27. April 2020 21:39:17 CEST Richard Sandiford wrote: > > > > "Dr. Matthias Kretz" <m.kr...@gsi.de> writes: > > > > > On Montag, 27. April 2020 18:59:08 CEST Richard Sandiford wrote: > > > > >> Richard Biener via Gcc-patches <gcc-patches@gcc.gnu.org> writes: > > > > >> > On Mon, Apr 27, 2020 at 6:09 PM Matthias Kretz <m.kr...@gsi.de> > wrote: > > > > >> >> Hi, > > > > >> >> > > > > >> >> This documentation change clarifies the effect of > > > > >> >> -ffinite-math-only. > > > > >> >> With the current documentation, it is unclear what the presence of > > > > >> >> NaN > > > > >> >> and Inf representations means if (arithmetic) operations on such > > > > >> >> values > > > > >> >> are unspecified and even classification functions like isnan are > > > > >> >> unreliable. If the hardware thinks a certain bit pattern is a NaN, > > > > >> >> but > > > > >> >> the software assumes a NaN value cannot ever exist, it is > > > > >> >> questionable > > > > >> >> whether, from a language viewpoint, a representation for NaNs > > > > >> >> really > > > > >> >> exists. Because, a NaN is defined by its behavior. This change > > > > >> >> also > > > > >> >> clarifies that isnan(nan) returning false is fine. > > > > >> >> > > > > >> >> This relates to PR84949. > > > > >> >> > > > > >> >> * doc/invoke.texi: Clarify the effects of > > > > >> >> -ffinite-math-only. > > > > >> >> > > > > >> >> --- > > > > >> >> > > > > >> >> gcc/doc/invoke.texi | 6 ++++-- > > > > >> >> 1 file changed, 4 insertions(+), 2 deletions(-) > > > > >> >> > > > > >> >> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi > > > > >> >> index a37a2ee9c19..9e76ab057a9 100644 > > > > >> >> --- a/gcc/doc/invoke.texi > > > > >> >> +++ b/gcc/doc/invoke.texi > > > > >> >> @@ -11619,8 +11619,10 @@ The default is > > > > >> >> @option{-fno-reciprocal-math}. > > > > >> >> > > > > >> >> @item -ffinite-math-only > > > > >> >> @opindex ffinite-math-only > > > > >> >> > > > > >> >> -Allow optimizations for floating-point arithmetic that assume > > > > >> >> -that arguments and results are not NaNs or +-Infs. > > > > >> >> +Assume that floating-point types in the language do not have > > > > >> >> representations for > > > > >> >> +NaNs and +-Inf. Whether floating-point hardware supports and acts > > > > >> >> on > > > > >> >> NaNs and ++-Inf is not affected. The behavior of a program that > > > > >> >> uses a > > > > >> >> NaN or +-Inf value > > > > >> >> +as function argument, macro argument, or operand is undefined. > > > > >> > > > > > >> > Minor nit here - I'd avoid the 'undefined' word which has bad > > > > >> > connotation > > > > >> > and use 'unspecified'. Maybe we can even use ISO C language > > > > >> > specification > > > > >> > terms but I'm not sure which one is most appropriate here. > > > > > > > > > > I'm an ISO C++ person, and unspecified sounds too reliable to me: > > > > > https://wg21.link/intro.defs#defns.unspecified. > > > > > > > > > >> > Curiously __builtin_nan ("nan") still gets you a NaN representation > > > > >> > but isnan(__builtin_nan("nan")) is resolved to false. > > > > > > > > > > Right, that's because only the hardware thinks __builtin_nan ("nan") > > > > > is a > > > > > NaN representation. With -ffinite-math-only, the double data type in > > > > > C/C++ can either hold a finite real value, or an invalid value (i.e. a > > > > > value that the optimizer unconditionally excludes as a possible value > > > > > for > > > > > any object of floating-point type). FWIW, with -ffinite-math-only, > > > > > ubsan > > > > > should flag isnan(__builtin_nan("nan")) or any f(constexpr nan). > > > > > > > > > > With the above documentation change, it is clear that with > > > > > https://wg21.link/ P1841 std::numbers::quiet_NaN<float> would be > > > > > ill-formed under -ffinite-math- only. Without the documentation > > > > > change, > > > > > it can be argued either way. > > > > > > > > > > There's another interesting observation resulting from the above: > > > > > double > > > > > and double under -ffinite-math-only are different types. Any function > > > > > call from one world to the other is dangerous. Inline functions > > > > > translated in different TUs compiled with different math flags violate > > > > > the ODR. But that's all the more reason to have a very precise > > > > > documentation/understanding of what -ffinite-math-only does. Because > > > > > this > > > > > gotcha is already the status quo.> > > > > > > > > > >> Yeah, for that and other reasons, I think it would be good to avoid > > > > >> giving the impression that -ffinite-math-only can be relied on to > > > > >> make > > > > >> the assumption above. Wouldn't it be more accurate to say that the > > > > >> compiler is allowed to make the assumption, at any point that it > > > > >> seems > > > > >> convenient? > > > > > > > > > > I think undefined behavior does what you're asking for while > > > > > unspecified > > > > > behavior does what you want to avoid. I.e. its an undocumented > > > > > behavior, > > > > > but it can be relied on with a given implementation (compiler). > > > > > > > > Although it wasn't obvious from my quoting, I was worried more about > > > > the wording of the first "Assume X" sentence than the unspecified vs. > > > > undefined thing. I think saying "Assume X" loses an important but > > > > correct nuance of the original wording: that completely ignoring the > > > > option would be a valid (if not very useful) implementation. "Assume X" > > > > instead makes it sound like the compiler is required to do something > > > > (at least to me). > > > > > > Fair point. However, I want to make it clear what value to expect from > > > std::numeric_limits<fp-type>::has_quiet_NaN. Currently that's up for > > > interpretation. Reading C17 5.2.4.2.2/4, which defines quiet NaNs to have > > > a > > > certain behavior, I believe has_quiet_NaN has always had to be false with > > > -ffinite-math-only. Anyway, only talking about more freedom for the > > > optimizer is not enough to make that clarification. > > > > > > I believe my suggested wording is not incorrect even in the case where the > > > compiler acts like -ffinite-math-only was not used. Because interpreting > > > the "invalid values" as IEC559 NaNs and Infs is one possible behavior > > > that can result from undefined behavior. > > > > > > > E.g., does making it undefined behaviour mean that: > > > > constexpr bool e = __builtin_isnan(__builtin_nan("nan")); > > > > > > > > must be rejected for -ffinite-math-only, since AIUI constexprs aren't > > > > allowed to invoke UB? (Sorry if this has already been covered in the > > > > earlier conversation.) > > > > > > Nit: __builtin_nan("nan") isn't a constant expression. __builtin_nan("") > > > is, though. numeric_limits<fp-type>::quiet_NaN() is implemented via > > > __builtin_nan(""). > > > https://wg21.link/numeric.limits.members#lib:numeric_limits,quiet_NaN says > > > quiet_NaN is not "meaningful" if has_quiet_NaN == false. That's no > > > guidance. > > > > > > If my documentation change means to say C++ UB (which I intended), then > > > yes, both > > > > > > constexpr bool e = __builtin_isnan(__builtin_nan("")); > > > constexpr auto x = __builtin_inf() + 1; > > > > > > have to be ill-formed. That sounds like a reasonable feature: Asking for a > > > NaN/Inf value while saying the program (well, TU) only does finite math is > > > an inconsistency that should at least be warned about, if not be > > > ill-formed. > > > > > > I believe I'm not changing the status quo (of the documentation), only > > > clarifying the consequences. However, since GCC does not implement all > > > that > > > freedom yet, we might want to call out more details in the documentation. > > > > > > Some more random thoughts: > > > > > > * The builtins don't have to return NaN and Inf (i.e. invalid values that > > > invoke UB on use). The current documentation gives the freedom under > > > -ffinite- math-only to assume any return value is not NaN/Inf. > > > Consequently translating __builtin_nan and __builtin_inf right now as 0 > > > seems plausible. > > > > > > * The documentation could expand on return values and say that *if the > > > value of an expression is not a finite value it is an unspecified value*. > > > At least for the non-constexpr case this means: "we don't know whether > > > `__builtin_inf() + 1` is UB; it could be 1; it could be 2; let's see what > > > the FPU makes of it". This important because UB allows to imply > > > `__builtin_unreachable()`. > > > > > > * Why not disable NaN and Inf independently? Inf is just a reciprocal 0. > > > Inf is as far away from numeric_limits::max as 0 is from > > > numeric_limits::min (infinitely many steps on a floating-point "type" > > > with infinite exponent). > > > > Just a comment on the last point - internally it's already split into > > HONOR_NANS and HONOR_INFINITIES that there's just > > a single user-facing option -ffinite-math-only is historical (IMHO more > > options are more opportunities for users to get confused ;)). > > > > Maybe also interesting to the discussion the internal comments say > > > > /* True if the given mode has a NaN representation and the treatment of > > NaN operands is important. Certain optimizations, such as folding > > x * 0 into 0, are not correct for NaN operands, and are normally > > disabled for modes with NaNs. The user can ask for them to be > > done anyway using the -funsafe-math-optimizations switch. */ > > extern bool HONOR_NANS (machine_mode); > > [...] > > note how the comment says "treatment of NaN operands is important" > > ... if HONOR_NANS(mode) == true. So with -ffinite-math-only that implies > "treatment of NaN operands is not important", right? > > > and the example of simplifying x * 0 to 0 is about preserving NaNs > > during expression simplification when the FPU would. > > Yes, the `nan * 0 -> 0` simplification with -ffinite-math-only is why I'm > saying the status quo already has no NaN representation *in C/C++* anymore. > Because a float with NaN bits doesn't behave like C17 5.2.4.2.2/4 requires. > And thus it's an invalid/unspecified/undefined value, not a NaN. If there are > no NaNs (as defined by C), then there is no NaN representation and > __FLT_HAS_QUIET_NAN__ must be 0. > > > I think these > > kind of optimizations are what originally was intended to be allowed > > with -ffinite-math-only - that we started to simplify isnan(x) to false > > was extending the scope and that change wasn't uncontested since > > it makes -ffinite-math-only less useful to some people. > > I understand and agree with this point. However I think the problem with the > current -ffinite-math-only is that it doesn't do enough. I.e. it's a dangerous > optimization for people who rely on nan+inf in some way. But in many cases > people don't realize they do, and it may be a latent issue that turns into a > bug on unrelated code changes (or compiler changes). By going all the way, > aggressively optimizing assuming there can't ever be nan/inf in the program, > making the use of inf/nan ill-formed NDR, and integrating with ubsan, I think > the option becomes less dangerous and more useful. That would be a bold move, > certainly. > > One should be able to debug NaNs via `-ffast-math -fno-finite-math-only` or > via FP traps and code compiled with -ffinite-math-only. I.e. whenever an > instruction produces a NaN the CPU would still trap, independent of the > optimization. > > > So there's > > the choice to go back, possibly by doing a comprehensive audit > > of flag_finite_math_only usage (mainly uses of HONOR_*). > > Changing (clarifying) the documentation and changing the libstdc++ > > implementatin would make this harder. > > Yes. And having two options -ffinite-math-only and -freally-only-finite-math > is out of the question. ;-) > But you already have my opinion on which way is more useful and safer. > > > In inlining we for example allow inlining a function compiled with > > -ffinite-math-only into a function compiled without (but not the > > other way around). > > The problem is if an inline function is not inlined: > > a.h: > inline float f(float x) { > return x * 0; > } > > b.cc (-fno-finite-math-only): > void f0() { > assert(isnan(f(__builtin_nan("")))); > } > > c.cc (-ffinite-math-only): > float f1() { > return f(1.f); > } > > b.o now contains a function f(float) that returns after fp mul. c.o contains a > function f(float) that unconditionally returns 0. On linking b.o and c.o one > of them is discarded. By realizing that -ffinite-math-only modifies the float > type, we see that f was translated with different types and thus this is an > ODR violation. > > Tangent: It'd be interesting if compiler flags that effectively modify types > and those that change available ISA extensions were part of the mangling. In > general, right now, programs consisting of code compiled with different > compiler flags is full of ODR violations.
Yep, this most visibly hits people compiling some CUs with -march=XYZ with functions whose calls are properly guarded by cpu capability checks and unknowingly end up with template instantiations from the STL in that ... I think we don't have some -fno-comdats or so forcing all instantiated templates to be really private (hidden visibility doesn't help here). But that's a really separate issue... even enumerating all options that possibly affect things this way is hard. Richard. > > -- > ────────────────────────────────────────────────────────────────────────── > Dr. Matthias Kretz https://mattkretz.github.io > GSI Helmholtz Centre for Heavy Ion Research https://gsi.de > std::experimental::simd https://github.com/VcDevel/std-simd > ────────────────────────────────────────────────────────────────────────── > > >