Message ID | 005b01d7aedd$91545eb0$b3fd1c10$@nextmovesoftware.com |
---|---|

State | Changes Requested |

Headers | show |

Series | [RFC/PATCH] C++ constexpr vs. floating point exceptions. | expand |

On Tue, 2021-09-21 at 12:41 +0100, Roger Sayle wrote: > > I was wondering if I may ask the C++ language experts for their > opinion > on whether (potential) floating point exceptions/traps can be ignored > in constant expressions; this is related to PR c++/96862. I think my > question boils down to whether (or not) the following is valid C++: > > constexpr float my_inf = 7.0 / 0.0; It's not. C++ disallows constexpr from invoking undefined behaviors in Clauses 4 through 19, while division by zero is an undefined behavior in Clause 8. > [This is currently an error with "-O2", but OK with "-O2 -ffast- > math"!] > There's a long history of g++'s semantics being accidentally tied to > the middle-end's constant folding, such that the current status quo > is that some middle-end bugs can't be fixed without breaking C++, > and vice versa. I'm hoping that the patch below (following Jakub's > lead with rounding math) might be a next step to improving things, > provided that my understanding of the desired/correct behaviour of > the C++ front-end is correct. > > This patch has been tested on x86_64-pc-linux-gnu with a "make > bootstrap" > and "make -k check" with no new failures after tweaking two checks in > g++.dg/ubsan/pr63956.C. > With this change the middle-end can become more > strict about respecting flag_trapping_math without affecting C++'s > behavior. Ideally, what the front-end considers valid should be > independent of whether the user specified -fno-trapping-math (or > -ffast-math) to the middle-end. I think we can allow a constexpr to contain floating div-by-zero with - fno-trapping-math, if we consider -fno-trapping-math make the behavior "no longer undefined" but I'm not sure. However -ffast-math also enables -ffinite-math-only, which makes div-by- zero absolutely undefined. So to me the expected behavior is: g++ t.cc -> Compile error g++ t.cc -ffast-math -> Compile error g++ t.cc -fno-trapping-math -> Ok g++ t.cc -fno-trapping-math -ffinite-math-only -> Compile Error > Thoughts? Ok for mainline? Based on the reasoning above I think it's not OK. But anyway I'm not a maintainer. > 2021-09-21 Roger Sayle <roger@nextmovesoftware.com> > > gcc/cp/ChangeLog > * constexpr.c (cxx_eval_outermost_const_expr): Temporarily > disable > the middle-end from honoring floating point exceptions/traps > while > folding "manifestly constant" expressions. > > gcc/testsuite/ChangeLog > * g++.dg/ubsan/pr63956.C: Update to (always) allow floating > point > division in constexpr (if both operands are constexpr). > > Roger > -- >

Can you double check? Integer division by zero is undefined, but isn't floating point division by zero defined by the appropriate IEEE standards? Roger -- -----Original Message----- From: Xi Ruoyao <xry111@mengyan1223.wang> Sent: 21 September 2021 14:07 To: Roger Sayle <roger@nextmovesoftware.com>; 'GCC Patches' <gcc-patches@gcc.gnu.org> Subject: Re: [RFC/PATCH] C++ constexpr vs. floating point exceptions. On Tue, 2021-09-21 at 12:41 +0100, Roger Sayle wrote: > > I was wondering if I may ask the C++ language experts for their > opinion on whether (potential) floating point exceptions/traps can be > ignored in constant expressions; this is related to PR c++/96862. I > think my question boils down to whether (or not) the following is > valid C++: > > constexpr float my_inf = 7.0 / 0.0; It's not. C++ disallows constexpr from invoking undefined behaviors in Clauses 4 through 19, while division by zero is an undefined behavior in Clause 8. > [This is currently an error with "-O2", but OK with "-O2 -ffast- > math"!] > There's a long history of g++'s semantics being accidentally tied to > the middle-end's constant folding, such that the current status quo is > that some middle-end bugs can't be fixed without breaking C++, and > vice versa. I'm hoping that the patch below (following Jakub's lead > with rounding math) might be a next step to improving things, provided > that my understanding of the desired/correct behaviour of the C++ > front-end is correct. > > This patch has been tested on x86_64-pc-linux-gnu with a "make > bootstrap" > and "make -k check" with no new failures after tweaking two checks in > g++.dg/ubsan/pr63956.C. > With this change the middle-end can become more strict about > respecting flag_trapping_math without affecting C++'s behavior. > Ideally, what the front-end considers valid should be independent of > whether the user specified -fno-trapping-math (or > -ffast-math) to the middle-end. I think we can allow a constexpr to contain floating div-by-zero with - fno-trapping-math, if we consider -fno-trapping-math make the behavior "no longer undefined" but I'm not sure. However -ffast-math also enables -ffinite-math-only, which makes div-by- zero absolutely undefined. So to me the expected behavior is: g++ t.cc -> Compile error g++ t.cc -ffast-math -> Compile error g++ t.cc -fno-trapping-math -> Ok g++ t.cc -fno-trapping-math -ffinite-math-only -> Compile Error > Thoughts? Ok for mainline? Based on the reasoning above I think it's not OK. But anyway I'm not a maintainer. > 2021-09-21 Roger Sayle <roger@nextmovesoftware.com> > > gcc/cp/ChangeLog > * constexpr.c (cxx_eval_outermost_const_expr): Temporarily > disable > the middle-end from honoring floating point exceptions/traps > while > folding "manifestly constant" expressions. > > gcc/testsuite/ChangeLog > * g++.dg/ubsan/pr63956.C: Update to (always) allow floating > point > division in constexpr (if both operands are constexpr). > > Roger > -- > -- Xi Ruoyao <xry111@mengyan1223.wang> School of Aerospace Science and Technology, Xidian University

On Tue, Sep 21, 2021 at 02:15:59PM +0100, Roger Sayle wrote: > Can you double check? Integer division by zero is undefined, but isn't floating point > division by zero defined by the appropriate IEEE standards? https://eel.is/c++draft/expr.mul#4 doesn't make the division by zero behavior conditional on integral types. C has similar wording. Jakub

On Tue, Sep 21, 2021 at 3:07 PM Xi Ruoyao via Gcc-patches <gcc-patches@gcc.gnu.org> wrote: > > On Tue, 2021-09-21 at 12:41 +0100, Roger Sayle wrote: > > > > I was wondering if I may ask the C++ language experts for their > > opinion > > on whether (potential) floating point exceptions/traps can be ignored > > in constant expressions; this is related to PR c++/96862. I think my > > question boils down to whether (or not) the following is valid C++: > > > > constexpr float my_inf = 7.0 / 0.0; > > It's not. C++ disallows constexpr from invoking undefined behaviors in > Clauses 4 through 19, while division by zero is an undefined behavior in > Clause 8. > > > [This is currently an error with "-O2", but OK with "-O2 -ffast- > > math"!] > > > There's a long history of g++'s semantics being accidentally tied to > > the middle-end's constant folding, such that the current status quo > > is that some middle-end bugs can't be fixed without breaking C++, > > and vice versa. I'm hoping that the patch below (following Jakub's > > lead with rounding math) might be a next step to improving things, > > provided that my understanding of the desired/correct behaviour of > > the C++ front-end is correct. > > > > This patch has been tested on x86_64-pc-linux-gnu with a "make > > bootstrap" > > and "make -k check" with no new failures after tweaking two checks in > > g++.dg/ubsan/pr63956.C. > > > With this change the middle-end can become more > > strict about respecting flag_trapping_math without affecting C++'s > > behavior. Ideally, what the front-end considers valid should be > > independent of whether the user specified -fno-trapping-math (or > > -ffast-math) to the middle-end. > > I think we can allow a constexpr to contain floating div-by-zero with - > fno-trapping-math, if we consider -fno-trapping-math make the behavior > "no longer undefined" but I'm not sure. Btw, -ftrapping-math also makes inexact operations possibly trapping so I'm not sure if any combination of -ftrapping-math and -ffinite-math-only can capture what C++ requires here. > However -ffast-math also enables -ffinite-math-only, which makes div-by- > zero absolutely undefined. So to me the expected behavior is: > > g++ t.cc -> Compile error > g++ t.cc -ffast-math -> Compile error > g++ t.cc -fno-trapping-math -> Ok > g++ t.cc -fno-trapping-math -ffinite-math-only -> Compile Error > > > Thoughts? Ok for mainline? > > Based on the reasoning above I think it's not OK. But anyway I'm not a > maintainer. > > > 2021-09-21 Roger Sayle <roger@nextmovesoftware.com> > > > > gcc/cp/ChangeLog > > * constexpr.c (cxx_eval_outermost_const_expr): Temporarily > > disable > > the middle-end from honoring floating point exceptions/traps > > while > > folding "manifestly constant" expressions. > > > > gcc/testsuite/ChangeLog > > * g++.dg/ubsan/pr63956.C: Update to (always) allow floating > > point > > division in constexpr (if both operands are constexpr). > > > > Roger > > -- > > > > -- > Xi Ruoyao <xry111@mengyan1223.wang> > School of Aerospace Science and Technology, Xidian University

On Tue, 2021-09-21 at 14:15 +0100, Roger Sayle wrote: > > Can you double check? Integer division by zero is undefined, but isn't > floating point > division by zero defined by the appropriate IEEE standards? It's an undefined behavior in C++ standard. Even if we assume C++ *was* IEEE-754 conforming (C++ standard is not saying "C++ is IEEE-754 conforming"!), it would be still not "something can be evaluated in compile time". IEEE 754 says the default behavior (it can be altered with some way, for example using feenableexcept() on systems with Glibc) of floating div-by-zero with both operands finite is "The divideByZero exception shall be signaled", and: "The operations of this standard, when exceptional, can as a side effect raise some of the following status flags: inexact, underflow, overflow, divideByZero, and invalid operation." And this is a side effect in real-life: those status flags are normally implemented as some bits in the floating point status register of the CPU and can be queried, for example ISO C defined fegetexceptflag() to do this. So it's a side effect anyway. You can't evaluate a side-effect during compilation. BTW the "correct" way to get a NaN in C++ seems: #include <limits> constexpr double my_nan = std::numeric_limits<double>::quiet_NaN(); If the C++ impl supports ISO/IEC 60559 (an alias of IEEE 754), it will produce a quiet NaN which can be used in constexpr (you can use signal_NaN() instead of quiet_NaN() if you want a signaling NaN). Otherwise the compilation will fail.

On Tue, 2021-09-21 at 21:38 +0800, Xi Ruoyao via Gcc-patches wrote: > BTW the "correct" way to get a NaN in C++ seems: > > #include <limits> > constexpr double my_nan = std::numeric_limits<double>::quiet_NaN(); Sorry, we were discussing inf, not NaN... Then constexpr double my_inf = std::numeric_limits<double>::infinity(); If the C++ impl supports ISO/IEC 60559 (an alias of IEEE 754), it will produce a positive infinity. Otherwise the compilation will fail.

Thanks. It's a minefield. I'm regretting using a division by zero example (as it unintentionally drags in the whole undefined behaviour thing). How about my recently added g++.dg/pr88173-1.C, is the following valid, i.e. const, if ordered comparisons against NaNs should raise an exception with (the default) -ftrapping-math? Or only with -fno-trapping-math? constexpr bool my_false1 = __builtin_nan("") < 1.0; constexpr bool my_false2 = __builtin_nans("") < 1.0; I think Richard Beiner was right; some trapping math is acceptable and other trapping math is unacceptable, with (currently) no distinction (in the middle-end) between the two. Perhaps the g++ front-end relies on/is abusing flag_trapping_math as a proxy to ensure that division by zero is undefined (is unfolded). Cheers, Roger -- -----Original Message----- From: Jakub Jelinek <jakub@redhat.com> Sent: 21 September 2021 14:22 To: Roger Sayle <roger@nextmovesoftware.com>; Jason Merrill <jason@redhat.com>; Jonathan Wakely <jwakely@redhat.com> Cc: 'Xi Ruoyao' <xry111@mengyan1223.wang>; 'GCC Patches' <gcc-patches@gcc.gnu.org> Subject: Re: [RFC/PATCH] C++ constexpr vs. floating point exceptions. On Tue, Sep 21, 2021 at 02:15:59PM +0100, Roger Sayle wrote: > Can you double check? Integer division by zero is undefined, but > isn't floating point division by zero defined by the appropriate IEEE standards? https://eel.is/c++draft/expr.mul#4 doesn't make the division by zero behavior conditional on integral types. C has similar wording. Jakub

On Tue, 21 Sep 2021, Jakub Jelinek via Gcc-patches wrote: > On Tue, Sep 21, 2021 at 02:15:59PM +0100, Roger Sayle wrote: > > Can you double check? Integer division by zero is undefined, but isn't floating point > > division by zero defined by the appropriate IEEE standards? > > https://eel.is/c++draft/expr.mul#4 doesn't make the division by zero > behavior conditional on integral types. > C has similar wording. The position for C is that Annex F semantics take precedence over all the ways in which floating-point arithmetic has undefined behavior in the main body of the standard. So floating-point overflow and division by zero are fully defined in the presence of Annex F support, while out-of-range conversions from floating point to integer types produce an unspecified value (not necessarily the same unspecified value for different executions of the conversion in the abstract machine - as discussed in bug 93806, GCC can get that wrong and act as if a single execution of such a conversion in the abstract machine produces more than one result). In C, as specified in Annex F, initializers for floating-point objects with static or thread storage duration are evaluated with exceptions discarded and the default rounding mode in effect; 7.0 / 0.0 is a fully valid initializer for such an object to initialize it to positive infinity. As I understand it, the question for this thread is whether C++ constexpr should have a similar rule to C static initializers (which ought to apply to 1.0 / 3.0, raising inexact, just as much as to 7.0 / 0.0).

On Tue, Sep 21, 2021 at 4:30 PM Joseph Myers <joseph@codesourcery.com> wrote: > On Tue, 21 Sep 2021, Jakub Jelinek via Gcc-patches wrote: > > > On Tue, Sep 21, 2021 at 02:15:59PM +0100, Roger Sayle wrote: > > > Can you double check? Integer division by zero is undefined, but > isn't floating point > > > division by zero defined by the appropriate IEEE standards? > > > > https://eel.is/c++draft/expr.mul#4 doesn't make the division by zero > > behavior conditional on integral types. > > C has similar wording. > > The position for C is that Annex F semantics take precedence over all the > ways in which floating-point arithmetic has undefined behavior in the main > body of the standard. So floating-point overflow and division by zero are > fully defined in the presence of Annex F support, while out-of-range > conversions from floating point to integer types produce an unspecified > value (not necessarily the same unspecified value for different executions > of the conversion in the abstract machine - as discussed in bug 93806, GCC > can get that wrong and act as if a single execution of such a conversion > in the abstract machine produces more than one result). > > In C, as specified in Annex F, initializers for floating-point objects > with static or thread storage duration are evaluated with exceptions > discarded and the default rounding mode in effect; 7.0 / 0.0 is a fully > valid initializer for such an object to initialize it to positive > infinity. As I understand it, the question for this thread is whether C++ > constexpr should have a similar rule to C static initializers (which ought > to apply to 1.0 / 3.0, raising inexact, just as much as to 7.0 / 0.0). > The C rules seem to be F.8.2 Translation During translation the IEC 60559 default modes are in effect: — The rounding direction mode is rounding to nearest. — The rounding precision mode (if supported) is set so that results are not shortened. — Trapping or stopping (if supported) is disabled on all floating-point exceptions. Recommended practice: The implementation should produce a diagnostic message for each translation-time floating-point exception, other than “inexact”; the implementation should then proceed with the translation of the program. I think following the same rules for C++ would be appropriate in a diagnosing context: warn and continue. In a template argument deduction (SFINAE) context, where errors become silent substitution failures, it's probably better to treat them as non-constant. Jason

On Tue, 21 Sep 2021, Jason Merrill via Gcc-patches wrote: > On Tue, Sep 21, 2021 at 4:30 PM Joseph Myers <joseph@codesourcery.com> > wrote: > >> On Tue, 21 Sep 2021, Jakub Jelinek via Gcc-patches wrote: >> >>> On Tue, Sep 21, 2021 at 02:15:59PM +0100, Roger Sayle wrote: >>>> Can you double check? Integer division by zero is undefined, but >> isn't floating point >>>> division by zero defined by the appropriate IEEE standards? >>> >>> https://eel.is/c++draft/expr.mul#4 doesn't make the division by zero >>> behavior conditional on integral types. >>> C has similar wording. >> >> The position for C is that Annex F semantics take precedence over all the >> ways in which floating-point arithmetic has undefined behavior in the main >> body of the standard. So floating-point overflow and division by zero are >> fully defined in the presence of Annex F support, while out-of-range >> conversions from floating point to integer types produce an unspecified >> value (not necessarily the same unspecified value for different executions >> of the conversion in the abstract machine - as discussed in bug 93806, GCC >> can get that wrong and act as if a single execution of such a conversion >> in the abstract machine produces more than one result). >> >> In C, as specified in Annex F, initializers for floating-point objects >> with static or thread storage duration are evaluated with exceptions >> discarded and the default rounding mode in effect; 7.0 / 0.0 is a fully >> valid initializer for such an object to initialize it to positive >> infinity. As I understand it, the question for this thread is whether C++ >> constexpr should have a similar rule to C static initializers (which ought >> to apply to 1.0 / 3.0, raising inexact, just as much as to 7.0 / 0.0). >> > > The C rules seem to be > > F.8.2 Translation > During translation the IEC 60559 default modes are in effect: > — The rounding direction mode is rounding to nearest. > — The rounding precision mode (if supported) is set so that results are > not shortened. > — Trapping or stopping (if supported) is disabled on all floating-point > exceptions. > Recommended practice: > The implementation should produce a diagnostic message for each > translation-time floating-point exception, other than “inexact”; the > implementation should then proceed with the translation of the program. > > I think following the same rules for C++ would be appropriate in a I agree that looking at the C standard is more interesting, C++ is very bad at specifying anything float related. > diagnosing context: warn and continue. In a template argument deduction > (SFINAE) context, where errors become silent substitution failures, it's > probably better to treat them as non-constant. I am trying to imagine a sfinae example affected by whether 1./0. is constant. Does that mean A<0.,__builtin_inf()> would fail to use the specialization in template<double a,double d>struct A{}; template<double a>struct A<a,1/a>{}; ? I don't like that, I believe it should use the specialization. With ieee754, 1./0. is perfectly well defined as +inf, the only question is whether it should also set a flag at runtime, which is not relevant in a manifestly consteval context (fetestexcept, etc are not constexpr, that should be enough to catch mistakes). If some user wants to forbid FE_DIVBYZERO, FE_INEXACT, FE_INVALID, FE_OVERFLOW or FE_UNDERFLOW in compile-time operations, that looks like it could be part of a separate compiler flag or pragma, like C's FENV_ROUND can affect the rounding mode in static initializers (of course C++ templates make pragmas less convenient).

diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 8a5dd06..ddea132 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -7276,6 +7276,13 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant, /* Turn off -frounding-math for manifestly constant evaluation. */ warning_sentinel rm (flag_rounding_math, ctx.manifestly_const_eval); + /* For manifestly constant evaluation, trapping (floating point) + exceptions don't prevent evaluation at compile-time, so temporarily + turn off -fsignaling-nans, -ftrapping-math and -ftrapv. */ + warning_sentinel sn (flag_signaling_nans, ctx.manifestly_const_eval); + warning_sentinel tm (flag_trapping_math, ctx.manifestly_const_eval); + warning_sentinel tv (flag_trapv, ctx.manifestly_const_eval); + tree type = initialized_type (t); tree r = t; bool is_consteval = false; diff --git a/gcc/testsuite/g++.dg/ubsan/pr63956.C b/gcc/testsuite/g++.dg/ubsan/pr63956.C index 3a1596e..126ed1d 100644 --- a/gcc/testsuite/g++.dg/ubsan/pr63956.C +++ b/gcc/testsuite/g++.dg/ubsan/pr63956.C @@ -69,12 +69,12 @@ constexpr float fn4 (float a, float b) { if (b != 2.0) - a = a / b; // { dg-error "is not a constant expression" } + a = a / b; return a; } constexpr float l1 = fn4 (5.0, 3.0); -constexpr float l2 = fn4 (7.0, 0.0); // { dg-message "in .constexpr. expansion" } +constexpr float l2 = fn4 (7.0, 0.0); constexpr int fn5 (const int *a, int b)