On 9/2/20 6:43 PM, Marc Glisse wrote:
On Wed, 2 Sep 2020, Jason Merrill via Gcc-patches wrote:

On 9/1/20 6:13 AM, Marc Glisse wrote:
On Tue, 1 Sep 2020, Jakub Jelinek via Gcc-patches wrote:

As discussed in the PR, fold-const.c punts on floating point constant
evaluation if the result is inexact and -frounding-math is turned on.
     /* Don't constant fold this floating point operation if the         result may dependent upon the run-time rounding mode and         flag_rounding_math is set, or if GCC's software emulation         is unable to accurately represent the result.  */
     if ((flag_rounding_math
          || (MODE_COMPOSITE_P (mode) && !flag_unsafe_math_optimizations))          && (inexact || !real_identical (&result, &value)))
       return NULL_TREE;
Jonathan said that we should be evaluating them anyway, e.g. conceptually as if they are done with the default rounding mode before user had a chance
to change that, and e.g. in C in initializers it is also ignored.
In fact, fold-const.c for C initializers turns off various other options:

/* Perform constant folding and related simplification of initializer
  expression EXPR.  These behave identically to "fold_buildN" but ignore   potential run-time traps and exceptions that fold must preserve.  */

#define START_FOLD_INIT \
 int saved_signaling_nans = flag_signaling_nans;\
 int saved_trapping_math = flag_trapping_math;\
 int saved_rounding_math = flag_rounding_math;\
 int saved_trapv = flag_trapv;\
 int saved_folding_initializer = folding_initializer;\
 flag_signaling_nans = 0;\
 flag_trapping_math = 0;\
 flag_rounding_math = 0;\
 flag_trapv = 0;\
 folding_initializer = 1;

#define END_FOLD_INIT \
 flag_signaling_nans = saved_signaling_nans;\
 flag_trapping_math = saved_trapping_math;\
 flag_rounding_math = saved_rounding_math;\
 flag_trapv = saved_trapv;\
 folding_initializer = saved_folding_initializer;

So, shall cxx_eval_outermost_constant_expr instead turn off all those
options (then warning_sentinel wouldn't be the right thing to use, but given the 8 or how many return stmts in cxx_eval_outermost_constant_expr, we'd need a RAII class for this.  Not sure about the folding_initializer, that one is affecting complex multiplication and division constant evaluation
somehow.

I don't think we need to turn off flag_signaling_nans or flag_trapv. I think we want to turn off flag_trapping_math so we can fold 1./0 to inf (still in a context where folding is mandatory). Setting folding_initializer seems consistent with that, enabling infinite results in complex folding (it also forces folding of __builtin_constant_p, which may be redundant with force_folding_builtin_constant_p).

C++ says that division by zero has undefined behavior, and that an expression with undefined behavior is not constant, so we shouldn't fold 1./0 to inf anyway.  The same is true of other trapping operations.  So clearing flag_signaling_nans, flag_trapping_math, and flag_trapv seems wrong for C++. And folding_initializer seems to be used for the same sort of thing.

So we should actually force flag_trapping_math to true during constexpr
evaluation? And folding_initializer to false, and never mind trapv but
maybe disable wrapv?

#include <limits>
constexpr double a = std::numeric_limits<double>::infinity();
constexpr double b = a + a;
constexpr double c = a - a;
constexpr double d = 1. / a;
constexpr double e = 1. / d;

clang rejects c and e. MSVC rejects e. Intel warns on c.

Gcc rejects only e, and accepts the whole thing if I pass
-fno-trapping-math.

At the November 2016 C++ meeting the numerics study group (SG6) said about core issue 2168, "arithmetic operations (not conversions) that produce infinity are not allowed in a constant expression. Just using std::numeric_limits<T>::infinity() is ok, but you can't add 0 to it." But the issue is still open.

Some later discussion disputes that this is the right choice, but there were voices on both sides and the discussion didn't come to a conclusion.
https://lists.isocpp.org/core/2018/02/3974.php

It seems that clang used to reject expressions that produced infinities (as per SG6 above), but that was changed last year to treat the range of floating point types as including infinity, so that a floating point operation is never considered overflowing.
https://reviews.llvm.org/D63793

C11 says, "The minimum range of representable values for a floating type is the most negative finite floating-point number representable in that type through the most positive finite floating-point number representable in that type. In addition, if negative infinity is representable in a type, the range of that type is extended to all negative real numbers; likewise, if positive infinity is representable in a type, the range of that type is extended to all positive real numbers."

The clang change seems consistent with C11. And given that C++ inherits <float.h> from C11, it would make sense to adopt the associated floating point model as well.

Almost any FP operation is possibly trapping, 1./3. sets FE_INEXACT just as 1./0. sets FE_DIVBYZERO. But the standard says

char array[1 + int(1 + 0.2 - 0.1 - 0.1)]; // Must be evaluated during translation

So it doesn't seem like it cares about that? Division by zero is the only one that gets weirdly special-cased...

Yes, division by zero is still explicitly undefined behavior ("If the second operand of / or % is zero the behavior is undefined."). So I guess cxx_eval_binary_expression should check this before calling fold_binary_loc.

I suspect that the unfortunate thing here is that constexpr generally declared undefined behavior to mean non-constant, and we didn't notice that this use of "undefined behavior" needed to be changed to accommodate implementations that gave it well-defined behavior.

Hopefully CWG/SG6 discussion now will be more fruitful.

Jason

Reply via email to