Make flag_trapping_math a non-binary Boolean.

Message ID 01b201d7b1df$8eadd7e0$ac0987a0$@nextmovesoftware.com
State New
Headers
Series Make flag_trapping_math a non-binary Boolean. |

Commit Message

Roger Sayle Sept. 25, 2021, 7:32 a.m. UTC
  Normally Boolean options/flags in GCC take the values zero or one.
This patch tweaks flag_trapping_math to take the values 0 or 65535.
More accurately it introduces a new trapping_math_model enumeration in
flag-types.h, and uses this to allow front-ends to (potentially) control
which expressions may be constant folded at compile-time by the middle-end.
Floating point/language experts may recognize these flags (bits) as being
modelled upon (extended) FENV_ACCESS.

This instalment simply introduces the necessary infrastructure without
yet changing any functionality.  The test "if (flag_trapping_math)" will
remain perfectly valid (but pessimistic).  The goal is to allow time
for out-of-tree front-ends (modula-2, rust, etc.) to update themselves,
if required, and to confirm that this change doesn't introduce problems
for LTO, or elsewhere.

This patch has been tested on x86_64-pc-linux-gnu with "make bootstrap"
and "make -k check", all languages including Ada, with no new failures.
Ok for mainline?


2021-09-25  Roger Sayle  <roger@nextmovesoftware.com>

gcc/ChangeLog
	* flag-types.h (trapping_math_model): New enumeration (of bits)
	specifying possible floating-point (and integer) exceptions/traps.
	* common.opt (ftrapping-math): Specify UInteger and initialize to
	flag_trapping_math to TRAPPING_MATH_DEFAULT.
	* toplev.c (process_options): The option -fsignaling-nans should
	set flag_trapping_math to TRAPPING_MATH_DEFAULT.

gcc/ada/ChangeLog
	* gcc-interface/misc.c (gnat_init_gcc_fp): Set flag_trapping_math
	to TRAPPING_MATH_DEFAULT (instead of 1) if S'Machine_Overflow.

Roger
--
  

Comments

Joseph Myers Sept. 27, 2021, 8:04 p.m. UTC | #1
On Sat, 25 Sep 2021, Roger Sayle wrote:

> Normally Boolean options/flags in GCC take the values zero or one.
> This patch tweaks flag_trapping_math to take the values 0 or 65535.
> More accurately it introduces a new trapping_math_model enumeration in
> flag-types.h, and uses this to allow front-ends to (potentially) control
> which expressions may be constant folded at compile-time by the middle-end.
> Floating point/language experts may recognize these flags (bits) as being
> modelled upon (extended) FENV_ACCESS.

I'm not sure exactly what those bits are modelled on (they are similar to, 
but not exactly the same as, IEEE 754 sub-exceptions), but a lot more 
explanation is needed - explanation of the rationale for the particular 
model chosen, explanation for what is or is not considered a default 
there, comments on each bit documenting its semantics in detail, and 
explanation of how this relates to other relevant discussions and patch 
proposals.

I think the following are the three key things this should be related to, 
none of which are mentioned in this patch submission:


(a) The various possible effects -ftrapping-math might have on allowed 
transformations, as discussed in bug 54192, where in comment #8 I 
identified five different kinds of restriction that -ftrapping-math might 
imply (only the first of which, and maybe to some extent the second, is 
handled much in GCC at present - and even there, there are various open 
bugs about cases where e.g. the expanders generate code not raising the 
right exceptions, or libgcc functions don't raise the right exceptions, 
especially when conversions between floating-point and integer are 
involved).

I actually think this sort of classification of effects of -ftrapping-math 
is probably more useful to provide control over (both internally in GCC 
and externally via more fine-grained command-line options) than the 
details of which individual exceptions are involved as suggested in your 
flags values.  However, the two are largely orthogonal (other than the 
point about exact underflows only relating to one of the exceptions).

I don't make any assertion here of which of these effects (if any) ought 
to be the default - making -ftrapping-math actually implement all five 
restrictions fully while keeping it the default might significantly impair 
optimization.  Also as mentioned in bug 54192, even if some such 
restrictions are applied by default, for soft-float systems with no 
support for exceptions it would make sense to apply more transformations 
unconditionally.


(b) Marc Glisse's -ffenv-access patches from August 2020 (and the 
discussion of them from that time).  Those don't claim to be complete, but 
they are the nearest we have to an attempt at implementing the sort of 
thing that would actually be needed to avoid code movement or removal that 
is invalid in the presence of code using floating-point flags (which 
overlaps a lot with what's needed to get -frounding-math correct under 
similar circumstances - except that a full -ftrapping-math might well 
involve stricter optimization restrictions than full -frounding-math, even 
in the absence of supporting non-local control float for trap handlers, 
because floating-point operations only read the rounding mode, but both 
read and write the exception state).


(c) The alternate exception handling bindings (FENV_EXCEPT pragma) in TS 
18661-5.  I'm not aware of any implementations of those bindings, it's far 
from clear whether they will turn out in the end to be a good way of 
providing C bindings to IEEE 754 alternate exception handling or not, and 
(given those issues) they aren't going to be integrated into C23.  But 
it's at least possible that the OPTIONAL_FLAG action (allowing 
transformations that cause certain exceptions or sub-exceptions not to 
raise the corresponding flag) could sometimes be useful in practice - and 
it's what seems to relate most closely to the sort of classification of 
exceptions in your patch (to implement it, you'd need that classification 
- though you'd also need to fix the other issues under (a) above).


> +  TRAPPING_MATH_QNANOP = 1UL << 0,
> +  TRAPPING_MATH_SNANOP = 1UL << 1,
> +  TRAPPING_MATH_QNANCMP = 1UL << 2,
> +  TRAPPING_MATH_SNANCMP = 1UL << 3,
> +  TRAPPING_MATH_INTCONV = 1UL << 4,
> +  TRAPPING_MATH_SQRTNEG = 1UL << 5,
> +  TRAPPING_MATH_LIBMFUN = 1UL << 6,
> +  TRAPPING_MATH_FDIVZERO = 1UL << 7,
> +  TRAPPING_MATH_IDIVZERO = 1UL << 8,
> +  TRAPPING_MATH_FPDENORM = 1UL << 9,
> +  TRAPPING_MATH_OVERFLOW = 1UL << 10,
> +  TRAPPING_MATH_UNDERFLOW = 1UL << 11,
> +  TRAPPING_MATH_INFDIVINF = 1UL << 12,
> +  TRAPPING_MATH_INFSUBINF = 1UL << 13,
> +  TRAPPING_MATH_INFMULZERO = 1UL << 14,
> +  TRAPPING_MATH_ZERODIVZERO = 1UL << 15,

Many of these are similar to, but not the same as, the sub-exceptions in 
IEEE 754 (enumerated as a more explicit list with names in TS 18661-5).

I think that if you want to handle sub-exceptions at all, it would be much 
better to follow the list in TS 18661-5 exactly (including using the names 
after the FE_ that appear in TS 18661-5, rather than inventing a different 
name for the same thing).  Maybe you then need an additional name to cover 
sub-exception cases not mentioned there (for lots of math.h functions, 
it's not exactly clear which sub-exception an invalid input corresponds 
to), but the TS 18661-5 list would be a good starting point.  (The only 
case I know of hardware that tracks sub-exception information, powerpc, 
has a more limited set of flag bits for sub-exceptions of "invalid".)

> +  TRAPPING_MATH_DEFAULT = (1UL << 16) - 1,
> +
> +  TRAPPING_MATH_INEXACT = 1UL << 16,

I think the existing semantics of -ftrapping-math apply to "inexact" just 
as they do to other exceptions (there may well be bugs there, of course), 
and it should be considered to be included in the default.  Any change to 
the default should be proposed separately from adding more fine-grained 
tracking of parts of -ftrapping-math.

> +  TRAPPING_MATH_TRAPV = 1UL << 17

I'm not sure what the semantics of this one are meant to be.  But since 
-ftrapv is more or less obsolescent, superseded by sanitizers, I don't 
think a new flag should really be named after it.  (And not named after 
the sanitizers either, unless you arrange for the sanitizers to generate 
IR using the new flag in some way.)
  
Roger Sayle Sept. 28, 2021, 11:34 a.m. UTC | #2
Hi Joseph,
Firstly very many thanks for taking the time to respond, and especially for
mentioning
the discussion in PR 54192 (and Marc Glisse's -ffenv-access patches, but
they are a
little less relevant).  Indeed the starting point for this patch is Richard
Beiner's proposal
in comment #9 for that PR.  That you've partially misunderstood the goal of
this patch is
encouraging (if it was simple to understand/fix, there wouldn't be so many
open PRs).
Hopefully, I'm bringing some fresh thinking on how to solve/tackle these
long standing
issues.

Next, I'd like to state that your "five restrictions" ontology is an
excellent starting point,
but I'd like to argue that your proposed list of 5 is the wrong shape
(insufficiently refined).
Instead, I'd like to counter-propose that an improvement/refinement of the
Myers model,
is actually "3 primitive restrictions * N trapping conditions * 2 flow
control sensitivity".

For reference, here's your original list:
> [1] Disallowing code transformations that cause some code to raise more 
> exception flags than it would have before.
> [2] Disallowing code transformations that cause some code to raise fewer 
> exception flags than it would have before.
> [3] Ensuring the code generated allows for possible non-local control flow

> from exception traps raised by floating-point operations (this is the part

> where -fnon-call-exceptions might be relevant).
> [4] Disallowing code transformations that might affect whether an exact 
> underflow exception occurs in some code (not observable through exception 
> flags, is observable through trap handlers).
> [5] Ensuring floating-point operations that might raise exception flags
are 
> not removed, or moved past code (asms or function calls) that might read 
> or modify the exception flag state

Firstly your item [3], concerns the relationship between traps and flow
control, such as C++ exception handling, which is as you correctly point out
the role of "-fnon-call-exceptions", which Richard B has recently confirmed
only applies to targets/languages supporting C++ style exceptions, i.e. this
is controlled by -fexceptions.  On targets such as nvptx-none, that don't
support non-local control flow, stack unwinding nor setjmp/longjmp, i.e.
don't support exceptions, this is completely orthogonal to the others.

Next your item [4] highlights what I consider the underlying problem that
until now has been overlooked, that there are different kinds of traps are
observationally/behaviourally different.  Above you describe, "underflow",
but likewise there are traps for inexact result, "2.0 / 3.0", traps for
division
by 0.0, that invokes undefined behaviour in C++ (but sometimes not in C),
and distinctions between quiet and signaling NaNs.  Your primitivie
restrictions,
[1], [2] and [5] may apply differently to these different kinds of
exceptions.

More relevant than Marc Glisse's -fenv-access is actually my
-fpreserve-traps
patch from July:
https://gcc.gnu.org/pipermail/gcc-patches/2021-July/574885.html
which tackles restriction [5] (and perhaps [2]).

Working towards the Myers restriction model, I believe we'd be a significant
step
closer with three (command line) flags (or families of flags):

-ftrapping-math		related to Myers restrictons [1],[2],[5]
-fpreserve-traps	related to Myers restriction [5]
-fcounted-traps		related to Myers restriction [2]

The insight that untangles the Gordian knot, is that these three options are

not simple true/false Binary flags, but actually (bit) sets of exception
types
(hopefully all actually using the same TRAPPING_MATH enumeration).

Consider the following four lines of C++:
constexpr t1 = 2.0 / 3.0;
constexpr t2 = std::numeric_limits<double>::quiet_NaN() == 0.0;
constexpr t2 = std::numeric_limits<double>::quiet_NaN() < 0.0;
constexpr t3 = 1.0 / 0.0;
which by IEEE generate four different types of exception, but as you've
expertly
confirmed have (sometimes) different behaviours under the C++ standard.
Treating all trapping conditions identically is clearly insufficient.

Hopefully, the argument/proposal above is sufficient to convince the list
that
we need some form of enumeration (following Richard Beiner's proposal).
Perhaps the devil is in the details, as to what the final form of this
enumeration
should look like [even though at this stage there are no functional changes
yet].

Two very useful references I've been following are:
https://docs.oracle.com/cd/E19957-01/806-3568/ncg_handle.html
https://docs.oracle.com/cd/E88353_01/html/E37846/fex-getexcepthandler-3m.htm
l

Ultimately, the fields and naming of this enumeration are a middle-end
detail,
and reflect constant folding transformations that the middle-end may or may
not perform on either trees or RTL.  In theory, the could be named after
line
numbers in match.pd, fold-const.c and simplify-rtx.c.  For example, what
IEEE calls
"FPE_INTOVF" is more commonly known as TRAPV inside GCC.  Likewise, IEEE
concepts such as FE_INVALID are really just groups of bits in our
enumeration,
but we allow much finer control, for example, whether sqrt of a negative
number
(other than -0.0) is considered a trap.

I'd prefer that GCC maintainers retain control/definition over the semantics
of
this enumeration rather the a standards committee.  It is for front-ends and
libraries to map from their semantics, to the handles provided by the
middle-end.

Finally, I'll correct you that the reason why TRAPPING_MATH_INEXACT is not
included in TRAPPING_MATH_DEFAULT is precisely to preserve the current 
behaviour.  The expressions 2.0/3.0 and (float)1.12345678 are both folded by
the middle-end and considered constexpr by g++; changing this would inhibit
optimization and affect what C++ considers valid code.  However, with full
-ffenv-access we may wish (elect) to perform these operations at run-time.

And with -fcounted-traps, i.e. (flag_counted_traps & TRAPPING_MATH_INEXACT)
	double x = 2.0/3.0;
	double y = 2.0/3.0;
we may even wish to perfom the division twice (c.f. your restriction [2]).


I hope this is useful/insightful.  Please let me know if you strongly feel
all FP traps
must be treated the same by the middle-end.  Indeed, if flag_trapping_math
is
restricted to only be FLAG_TRAPPING_DEFAULT in a front-end(s), they will be.

Best regards,
Roger
--

-----Original Message-----
From: Joseph Myers <joseph@codesourcery.com> 
Sent: 27 September 2021 21:05
To: Roger Sayle <roger@nextmovesoftware.com>
Cc: 'GCC Patches' <gcc-patches@gcc.gnu.org>; 'Eric Botcazou'
<botcazou@adacore.com>
Subject: Re: [PATCH] Make flag_trapping_math a non-binary Boolean.

On Sat, 25 Sep 2021, Roger Sayle wrote:

> Normally Boolean options/flags in GCC take the values zero or one.
> This patch tweaks flag_trapping_math to take the values 0 or 65535.
> More accurately it introduces a new trapping_math_model enumeration in 
> flag-types.h, and uses this to allow front-ends to (potentially) 
> control which expressions may be constant folded at compile-time by the
middle-end.
> Floating point/language experts may recognize these flags (bits) as 
> being modelled upon (extended) FENV_ACCESS.

I'm not sure exactly what those bits are modelled on (they are similar to,
but not exactly the same as, IEEE 754 sub-exceptions), but a lot more
explanation is needed - explanation of the rationale for the particular
model chosen, explanation for what is or is not considered a default there,
comments on each bit documenting its semantics in detail, and explanation of
how this relates to other relevant discussions and patch proposals.

I think the following are the three key things this should be related to,
none of which are mentioned in this patch submission:


(a) The various possible effects -ftrapping-math might have on allowed 
transformations, as discussed in bug 54192, where in comment #8 I 
identified five different kinds of restriction that -ftrapping-math might 
imply (only the first of which, and maybe to some extent the second, is 
handled much in GCC at present - and even there, there are various open 
bugs about cases where e.g. the expanders generate code not raising the 
right exceptions, or libgcc functions don't raise the right exceptions, 
especially when conversions between floating-point and integer are 
involved).

I actually think this sort of classification of effects of -ftrapping-math 
is probably more useful to provide control over (both internally in GCC 
and externally via more fine-grained command-line options) than the 
details of which individual exceptions are involved as suggested in your 
flags values.  However, the two are largely orthogonal (other than the 
point about exact underflows only relating to one of the exceptions).

I don't make any assertion here of which of these effects (if any) ought 
to be the default - making -ftrapping-math actually implement all five 
restrictions fully while keeping it the default might significantly impair 
optimization.  Also as mentioned in bug 54192, even if some such 
restrictions are applied by default, for soft-float systems with no 
support for exceptions it would make sense to apply more transformations 
unconditionally.


(b) Marc Glisse's -ffenv-access patches from August 2020 (and the 
discussion of them from that time).  Those don't claim to be complete, but 
they are the nearest we have to an attempt at implementing the sort of 
thing that would actually be needed to avoid code movement or removal that 
is invalid in the presence of code using floating-point flags (which 
overlaps a lot with what's needed to get -frounding-math correct under 
similar circumstances - except that a full -ftrapping-math might well 
involve stricter optimization restrictions than full -frounding-math, even 
in the absence of supporting non-local control float for trap handlers, 
because floating-point operations only read the rounding mode, but both 
read and write the exception state).


(c) The alternate exception handling bindings (FENV_EXCEPT pragma) in TS 
18661-5.  I'm not aware of any implementations of those bindings, it's far 
from clear whether they will turn out in the end to be a good way of 
providing C bindings to IEEE 754 alternate exception handling or not, and 
(given those issues) they aren't going to be integrated into C23.  But 
it's at least possible that the OPTIONAL_FLAG action (allowing 
transformations that cause certain exceptions or sub-exceptions not to 
raise the corresponding flag) could sometimes be useful in practice - and 
it's what seems to relate most closely to the sort of classification of 
exceptions in your patch (to implement it, you'd need that classification 
- though you'd also need to fix the other issues under (a) above).


> +  TRAPPING_MATH_QNANOP = 1UL << 0,
> +  TRAPPING_MATH_SNANOP = 1UL << 1,
> +  TRAPPING_MATH_QNANCMP = 1UL << 2,
> +  TRAPPING_MATH_SNANCMP = 1UL << 3,
> +  TRAPPING_MATH_INTCONV = 1UL << 4,
> +  TRAPPING_MATH_SQRTNEG = 1UL << 5,
> +  TRAPPING_MATH_LIBMFUN = 1UL << 6,
> +  TRAPPING_MATH_FDIVZERO = 1UL << 7,
> +  TRAPPING_MATH_IDIVZERO = 1UL << 8,
> +  TRAPPING_MATH_FPDENORM = 1UL << 9,
> +  TRAPPING_MATH_OVERFLOW = 1UL << 10,
> +  TRAPPING_MATH_UNDERFLOW = 1UL << 11,
> +  TRAPPING_MATH_INFDIVINF = 1UL << 12,
> +  TRAPPING_MATH_INFSUBINF = 1UL << 13,
> +  TRAPPING_MATH_INFMULZERO = 1UL << 14,
> +  TRAPPING_MATH_ZERODIVZERO = 1UL << 15,

Many of these are similar to, but not the same as, the sub-exceptions in 
IEEE 754 (enumerated as a more explicit list with names in TS 18661-5).

I think that if you want to handle sub-exceptions at all, it would be much 
better to follow the list in TS 18661-5 exactly (including using the names 
after the FE_ that appear in TS 18661-5, rather than inventing a different 
name for the same thing).  Maybe you then need an additional name to cover 
sub-exception cases not mentioned there (for lots of math.h functions, 
it's not exactly clear which sub-exception an invalid input corresponds 
to), but the TS 18661-5 list would be a good starting point.  (The only 
case I know of hardware that tracks sub-exception information, powerpc, 
has a more limited set of flag bits for sub-exceptions of "invalid".)

> +  TRAPPING_MATH_DEFAULT = (1UL << 16) - 1,
> +
> +  TRAPPING_MATH_INEXACT = 1UL << 16,

I think the existing semantics of -ftrapping-math apply to "inexact" just 
as they do to other exceptions (there may well be bugs there, of course), 
and it should be considered to be included in the default.  Any change to 
the default should be proposed separately from adding more fine-grained 
tracking of parts of -ftrapping-math.

> +  TRAPPING_MATH_TRAPV = 1UL << 17

I'm not sure what the semantics of this one are meant to be.  But since 
-ftrapv is more or less obsolescent, superseded by sanitizers, I don't 
think a new flag should really be named after it.  (And not named after 
the sanitizers either, unless you arrange for the sanitizers to generate 
IR using the new flag in some way.)
  
Richard Biener Sept. 28, 2021, 12:15 p.m. UTC | #3
On Tue, Sep 28, 2021 at 1:34 PM Roger Sayle <roger@nextmovesoftware.com> wrote:
>
>
> Hi Joseph,
> Firstly very many thanks for taking the time to respond, and especially for
> mentioning
> the discussion in PR 54192 (and Marc Glisse's -ffenv-access patches, but
> they are a
> little less relevant).  Indeed the starting point for this patch is Richard
> Beiner's proposal
> in comment #9 for that PR.  That you've partially misunderstood the goal of
> this patch is
> encouraging (if it was simple to understand/fix, there wouldn't be so many
> open PRs).
> Hopefully, I'm bringing some fresh thinking on how to solve/tackle these
> long standing
> issues.
>
> Next, I'd like to state that your "five restrictions" ontology is an
> excellent starting point,
> but I'd like to argue that your proposed list of 5 is the wrong shape
> (insufficiently refined).
> Instead, I'd like to counter-propose that an improvement/refinement of the
> Myers model,
> is actually "3 primitive restrictions * N trapping conditions * 2 flow
> control sensitivity".
>
> For reference, here's your original list:
> > [1] Disallowing code transformations that cause some code to raise more
> > exception flags than it would have before.
> > [2] Disallowing code transformations that cause some code to raise fewer
> > exception flags than it would have before.
> > [3] Ensuring the code generated allows for possible non-local control flow
>
> > from exception traps raised by floating-point operations (this is the part
>
> > where -fnon-call-exceptions might be relevant).
> > [4] Disallowing code transformations that might affect whether an exact
> > underflow exception occurs in some code (not observable through exception
> > flags, is observable through trap handlers).
> > [5] Ensuring floating-point operations that might raise exception flags
> are
> > not removed, or moved past code (asms or function calls) that might read
> > or modify the exception flag state
>
> Firstly your item [3], concerns the relationship between traps and flow
> control, such as C++ exception handling, which is as you correctly point out
> the role of "-fnon-call-exceptions", which Richard B has recently confirmed
> only applies to targets/languages supporting C++ style exceptions, i.e. this
> is controlled by -fexceptions.  On targets such as nvptx-none, that don't
> support non-local control flow, stack unwinding nor setjmp/longjmp, i.e.
> don't support exceptions, this is completely orthogonal to the others.
>
> Next your item [4] highlights what I consider the underlying problem that
> until now has been overlooked, that there are different kinds of traps are
> observationally/behaviourally different.  Above you describe, "underflow",
> but likewise there are traps for inexact result, "2.0 / 3.0", traps for
> division
> by 0.0, that invokes undefined behaviour in C++ (but sometimes not in C),
> and distinctions between quiet and signaling NaNs.  Your primitivie
> restrictions,
> [1], [2] and [5] may apply differently to these different kinds of
> exceptions.
>
> More relevant than Marc Glisse's -fenv-access is actually my
> -fpreserve-traps
> patch from July:
> https://gcc.gnu.org/pipermail/gcc-patches/2021-July/574885.html
> which tackles restriction [5] (and perhaps [2]).
>
> Working towards the Myers restriction model, I believe we'd be a significant
> step
> closer with three (command line) flags (or families of flags):
>
> -ftrapping-math         related to Myers restrictons [1],[2],[5]
> -fpreserve-traps        related to Myers restriction [5]
> -fcounted-traps         related to Myers restriction [2]

Just to throw in a comment without intending to interrupt the fruitful
argument...

I'd like to keep changes refined to the frontends / middle-ends until we
sort out the bigger picture and have an approach that is usable in the
actual implementation and also extensible, that is, it doesn't fall apart
when considering the related problems Joseph mentioned (-frounding-math,
FENV access).

And only _then_ think of how to expose this best to the user with new
user-visible options and tunables.  Because those tend to stick around
forever and so mistakes there are much more costly (and it's not that
we don't have too many entangled knobs in the area of math semantics...)

> The insight that untangles the Gordian knot, is that these three options are
>
> not simple true/false Binary flags, but actually (bit) sets of exception
> types
> (hopefully all actually using the same TRAPPING_MATH enumeration).
>
> Consider the following four lines of C++:
> constexpr t1 = 2.0 / 3.0;
> constexpr t2 = std::numeric_limits<double>::quiet_NaN() == 0.0;
> constexpr t2 = std::numeric_limits<double>::quiet_NaN() < 0.0;
> constexpr t3 = 1.0 / 0.0;
> which by IEEE generate four different types of exception, but as you've
> expertly
> confirmed have (sometimes) different behaviours under the C++ standard.
> Treating all trapping conditions identically is clearly insufficient.
>
> Hopefully, the argument/proposal above is sufficient to convince the list
> that
> we need some form of enumeration (following Richard Beiner's proposal).
> Perhaps the devil is in the details, as to what the final form of this
> enumeration
> should look like [even though at this stage there are no functional changes
> yet].
>
> Two very useful references I've been following are:
> https://docs.oracle.com/cd/E19957-01/806-3568/ncg_handle.html
> https://docs.oracle.com/cd/E88353_01/html/E37846/fex-getexcepthandler-3m.htm
> l
>
> Ultimately, the fields and naming of this enumeration are a middle-end
> detail,
> and reflect constant folding transformations that the middle-end may or may
> not perform on either trees or RTL.  In theory, the could be named after
> line
> numbers in match.pd, fold-const.c and simplify-rtx.c.  For example, what
> IEEE calls
> "FPE_INTOVF" is more commonly known as TRAPV inside GCC.  Likewise, IEEE
> concepts such as FE_INVALID are really just groups of bits in our
> enumeration,
> but we allow much finer control, for example, whether sqrt of a negative
> number
> (other than -0.0) is considered a trap.
>
> I'd prefer that GCC maintainers retain control/definition over the semantics
> of
> this enumeration rather the a standards committee.  It is for front-ends and
> libraries to map from their semantics, to the handles provided by the
> middle-end.
>
> Finally, I'll correct you that the reason why TRAPPING_MATH_INEXACT is not
> included in TRAPPING_MATH_DEFAULT is precisely to preserve the current
> behaviour.  The expressions 2.0/3.0 and (float)1.12345678 are both folded by
> the middle-end and considered constexpr by g++; changing this would inhibit
> optimization and affect what C++ considers valid code.  However, with full
> -ffenv-access we may wish (elect) to perform these operations at run-time.
>
> And with -fcounted-traps, i.e. (flag_counted_traps & TRAPPING_MATH_INEXACT)
>         double x = 2.0/3.0;
>         double y = 2.0/3.0;
> we may even wish to perfom the division twice (c.f. your restriction [2]).
>
>
> I hope this is useful/insightful.  Please let me know if you strongly feel
> all FP traps
> must be treated the same by the middle-end.  Indeed, if flag_trapping_math
> is
> restricted to only be FLAG_TRAPPING_DEFAULT in a front-end(s), they will be.
>
> Best regards,
> Roger
> --
>
> -----Original Message-----
> From: Joseph Myers <joseph@codesourcery.com>
> Sent: 27 September 2021 21:05
> To: Roger Sayle <roger@nextmovesoftware.com>
> Cc: 'GCC Patches' <gcc-patches@gcc.gnu.org>; 'Eric Botcazou'
> <botcazou@adacore.com>
> Subject: Re: [PATCH] Make flag_trapping_math a non-binary Boolean.
>
> On Sat, 25 Sep 2021, Roger Sayle wrote:
>
> > Normally Boolean options/flags in GCC take the values zero or one.
> > This patch tweaks flag_trapping_math to take the values 0 or 65535.
> > More accurately it introduces a new trapping_math_model enumeration in
> > flag-types.h, and uses this to allow front-ends to (potentially)
> > control which expressions may be constant folded at compile-time by the
> middle-end.
> > Floating point/language experts may recognize these flags (bits) as
> > being modelled upon (extended) FENV_ACCESS.
>
> I'm not sure exactly what those bits are modelled on (they are similar to,
> but not exactly the same as, IEEE 754 sub-exceptions), but a lot more
> explanation is needed - explanation of the rationale for the particular
> model chosen, explanation for what is or is not considered a default there,
> comments on each bit documenting its semantics in detail, and explanation of
> how this relates to other relevant discussions and patch proposals.
>
> I think the following are the three key things this should be related to,
> none of which are mentioned in this patch submission:
>
>
> (a) The various possible effects -ftrapping-math might have on allowed
> transformations, as discussed in bug 54192, where in comment #8 I
> identified five different kinds of restriction that -ftrapping-math might
> imply (only the first of which, and maybe to some extent the second, is
> handled much in GCC at present - and even there, there are various open
> bugs about cases where e.g. the expanders generate code not raising the
> right exceptions, or libgcc functions don't raise the right exceptions,
> especially when conversions between floating-point and integer are
> involved).
>
> I actually think this sort of classification of effects of -ftrapping-math
> is probably more useful to provide control over (both internally in GCC
> and externally via more fine-grained command-line options) than the
> details of which individual exceptions are involved as suggested in your
> flags values.  However, the two are largely orthogonal (other than the
> point about exact underflows only relating to one of the exceptions).
>
> I don't make any assertion here of which of these effects (if any) ought
> to be the default - making -ftrapping-math actually implement all five
> restrictions fully while keeping it the default might significantly impair
> optimization.  Also as mentioned in bug 54192, even if some such
> restrictions are applied by default, for soft-float systems with no
> support for exceptions it would make sense to apply more transformations
> unconditionally.
>
>
> (b) Marc Glisse's -ffenv-access patches from August 2020 (and the
> discussion of them from that time).  Those don't claim to be complete, but
> they are the nearest we have to an attempt at implementing the sort of
> thing that would actually be needed to avoid code movement or removal that
> is invalid in the presence of code using floating-point flags (which
> overlaps a lot with what's needed to get -frounding-math correct under
> similar circumstances - except that a full -ftrapping-math might well
> involve stricter optimization restrictions than full -frounding-math, even
> in the absence of supporting non-local control float for trap handlers,
> because floating-point operations only read the rounding mode, but both
> read and write the exception state).
>
>
> (c) The alternate exception handling bindings (FENV_EXCEPT pragma) in TS
> 18661-5.  I'm not aware of any implementations of those bindings, it's far
> from clear whether they will turn out in the end to be a good way of
> providing C bindings to IEEE 754 alternate exception handling or not, and
> (given those issues) they aren't going to be integrated into C23.  But
> it's at least possible that the OPTIONAL_FLAG action (allowing
> transformations that cause certain exceptions or sub-exceptions not to
> raise the corresponding flag) could sometimes be useful in practice - and
> it's what seems to relate most closely to the sort of classification of
> exceptions in your patch (to implement it, you'd need that classification
> - though you'd also need to fix the other issues under (a) above).
>
>
> > +  TRAPPING_MATH_QNANOP = 1UL << 0,
> > +  TRAPPING_MATH_SNANOP = 1UL << 1,
> > +  TRAPPING_MATH_QNANCMP = 1UL << 2,
> > +  TRAPPING_MATH_SNANCMP = 1UL << 3,
> > +  TRAPPING_MATH_INTCONV = 1UL << 4,
> > +  TRAPPING_MATH_SQRTNEG = 1UL << 5,
> > +  TRAPPING_MATH_LIBMFUN = 1UL << 6,
> > +  TRAPPING_MATH_FDIVZERO = 1UL << 7,
> > +  TRAPPING_MATH_IDIVZERO = 1UL << 8,
> > +  TRAPPING_MATH_FPDENORM = 1UL << 9,
> > +  TRAPPING_MATH_OVERFLOW = 1UL << 10,
> > +  TRAPPING_MATH_UNDERFLOW = 1UL << 11,
> > +  TRAPPING_MATH_INFDIVINF = 1UL << 12,
> > +  TRAPPING_MATH_INFSUBINF = 1UL << 13,
> > +  TRAPPING_MATH_INFMULZERO = 1UL << 14,
> > +  TRAPPING_MATH_ZERODIVZERO = 1UL << 15,
>
> Many of these are similar to, but not the same as, the sub-exceptions in
> IEEE 754 (enumerated as a more explicit list with names in TS 18661-5).
>
> I think that if you want to handle sub-exceptions at all, it would be much
> better to follow the list in TS 18661-5 exactly (including using the names
> after the FE_ that appear in TS 18661-5, rather than inventing a different
> name for the same thing).  Maybe you then need an additional name to cover
> sub-exception cases not mentioned there (for lots of math.h functions,
> it's not exactly clear which sub-exception an invalid input corresponds
> to), but the TS 18661-5 list would be a good starting point.  (The only
> case I know of hardware that tracks sub-exception information, powerpc,
> has a more limited set of flag bits for sub-exceptions of "invalid".)
>
> > +  TRAPPING_MATH_DEFAULT = (1UL << 16) - 1,
> > +
> > +  TRAPPING_MATH_INEXACT = 1UL << 16,
>
> I think the existing semantics of -ftrapping-math apply to "inexact" just
> as they do to other exceptions (there may well be bugs there, of course),
> and it should be considered to be included in the default.  Any change to
> the default should be proposed separately from adding more fine-grained
> tracking of parts of -ftrapping-math.
>
> > +  TRAPPING_MATH_TRAPV = 1UL << 17
>
> I'm not sure what the semantics of this one are meant to be.  But since
> -ftrapv is more or less obsolescent, superseded by sanitizers, I don't
> think a new flag should really be named after it.  (And not named after
> the sanitizers either, unless you arrange for the sanitizers to generate
> IR using the new flag in some way.)
>
> --
> Joseph S. Myers
> joseph@codesourcery.com
>
  
Joseph Myers Sept. 28, 2021, 9:27 p.m. UTC | #4
On Tue, 28 Sep 2021, Roger Sayle wrote:

> Next, I'd like to state that your "five restrictions" ontology is an 
> excellent starting point, but I'd like to argue that your proposed list 
> of 5 is the wrong shape (insufficiently refined). Instead, I'd like to 
> counter-propose that an improvement/refinement of the Myers model, is 
> actually "3 primitive restrictions * N trapping conditions * 2 flow 
> control sensitivity".

It's true you can treat the rules on what code transformations are 
permitted as orthogonal to which exceptions or sub-exceptions those are 
applied to.  (I'm not sure exact what you are including under "flow 
control sensitivity".)  And also that IEEE 754-2019 subclause 8.1 says 
that language standards should allow for alternate exception handling 
attributes to be associated with sets of exceptions or sub-exceptions (as 
well as being associated to particular blocks in the source code, as 
represented for C by the pragmas defined in TS 18661-5), which does tend 
to suggest such a model listing (sub-)exceptions separately for each rule 
on alternate exception handling.

Note that "trapping conditions" is not a good way of expressing things; 
the right way is much closer to "alternate exception handling" as defined 
in IEEE 754-2019 (or -2008), even if some of the more permissive modes 
(e.g. allowing spurious exceptions to be raised) don't actually correspond 
to any kind of alternate exception handling described in IEEE 754.  
Trapping, in the sense of transferring control to a trap handler 
(typically a SIGFPE signal handler) is, at least at the level of APIs for 
user code, an obsolescent form of exception handling: it was described in 
IEEE 754-1985 but removed in IEEE 754-2008, replaced by alternate 
exception handling.  Trapping and trap handlers are too machine-specific 
to form a good API for normal user code.  Some architectures may invoke a 
trap handler some time later than the instruction that signaled the 
exception.  Some may not support trapping on floating-point exceptions at 
all; support is optional on Arm and many processors don't implement it, 
trapping on floating-point exceptions isn't supported by RISC-V at all, 
for example.

So I think we should avoid reference to traps, when talking about 
floating-point exceptions, as much as possible, in the GCC documentation, 
command-line option names, source code and development discussion, except 
in limited cases where the specific legacy mechanism described in IEEE 
754-1985 is meant.  That doesn't make much difference to permitted 
optimizations; some forms of alternate exception handling would place 
similar restrictions on permitted code transformations to those 
restrictions coming from 1985-style trapping.

> Next your item [4] highlights what I consider the underlying problem that
> until now has been overlooked, that there are different kinds of traps are
> observationally/behaviourally different.  Above you describe, "underflow",
> but likewise there are traps for inexact result, "2.0 / 3.0", traps for
> division
> by 0.0, that invokes undefined behaviour in C++ (but sometimes not in C),
> and distinctions between quiet and signaling NaNs.  Your primitivie
> restrictions,
> [1], [2] and [5] may apply differently to these different kinds of
> exceptions.

As per the above, these aren't kinds of traps, but exceptions or 
sub-exceptions.

> Consider the following four lines of C++:
> constexpr t1 = 2.0 / 3.0;
> constexpr t2 = std::numeric_limits<double>::quiet_NaN() == 0.0;
> constexpr t2 = std::numeric_limits<double>::quiet_NaN() < 0.0;
> constexpr t3 = 1.0 / 0.0;
> which by IEEE generate four different types of exception, but as you've

t2 does not generate an exception; == is compareQuietEqual not 
compareSignalingEqual.

> Two very useful references I've been following are:
> https://docs.oracle.com/cd/E19957-01/806-3568/ncg_handle.html
> https://docs.oracle.com/cd/E88353_01/html/E37846/fex-getexcepthandler-3m.html

I don't think these are a good starting point; the TS 18661-5 APIs are a 
more appropriate basis for possible C bindings to alternate exception 
handling as described in IEEE 754-2008 or -2019, as opposed to 1985-style 
trapping or anything not based on 754-2008 or newer.

> numbers in match.pd, fold-const.c and simplify-rtx.c.  For example, what
> IEEE calls
> "FPE_INTOVF" is more commonly known as TRAPV inside GCC.  Likewise, IEEE

IEEE has no such name as FPE_INTOVF.

-ftrapv is itself an obsolescent feature.  Not because of any problems 
with its notion of trap, which is disjoint from that of floating-point 
exceptions (it's a synchronous call to abort or something equivalent), but 
because the implementation is problematic and an alias for certain 
sanitizer options is more maintainable.  We should be moving to that alias 
rather than adding any more internal representation related to -ftrapv.

> concepts such as FE_INVALID are really just groups of bits in our 
> enumeration, but we allow much finer control, for example, whether sqrt 
> of a negative number (other than -0.0) is considered a trap.

That's item (g), "squareRoot if the operand is less than zero", in 
subclause 7.2 of IEEE 754-2019.  TS 18661-5 gives it the name 
FE_INVALID_SQRT.  So we don't need to invent our own name for it.

> I'd prefer that GCC maintainers retain control/definition over the 
> semantics of this enumeration rather the a standards committee.  It is 
> for front-ends and libraries to map from their semantics, to the handles 
> provided by the middle-end.

Where languages want to use this granularity at all, I expect it will be 
for language bindings doing what IEEE 754-2019 specifies regarding 
alternate exception handling - including supporting lists of 
sub-exceptions as specified in IEEE 754-2019.  That means the 
classification in IEEE 754-2019 (given names in TS 18661-5) is the 
appropriate starting point.  That enumeration is sufficiently fine-grained 
it seems very unlikely it will be useful to subdivide it further.

It might be relevant to add *one* further enumeration element for those 
"invalid" exceptions that do not correspond to any of the listed cases 
(for example, sin(Inf) doesn't seem to fall into any of them, and, as per 
TS 18661-5, "Sub-exceptions corresponding to defined macros occur as 
specified below, and not in other cases." so it shouldn't be added to one 
of them by the implementation).

I don't think any enumeration element will need adding to distinguish 
exact and inexact underflow, simply because that's covered by one of the 
orthogonal pieces of data (what kind of alternate exception handling 
applies to the underflow exception - if we only care about flags, exact 
underflow is OK to ignore and OK to signal spuriously because it's not 
observable through flags; if we care about anything changing flow of 
control, or some other kinds of alternate exception handling such as 
abruptUnderflow, exact underflow does become of significance).

> Finally, I'll correct you that the reason why TRAPPING_MATH_INEXACT is not
> included in TRAPPING_MATH_DEFAULT is precisely to preserve the current 
> behaviour.  The expressions 2.0/3.0 and (float)1.12345678 are both folded by
> the middle-end and considered constexpr by g++; changing this would inhibit

Losing the inexact exception there is a bug, according to documented 
semantics of -ftrapping-math.  It's a specific case of failing to disallow 
local transformations that might cause code to signal fewer exceptions 
than it would have before.

Documented semantics apply equally to all exceptions.  We might choose to 
change some defaults (possibly just for inexact, possibly more generally 
allowing by default transformations that lose exceptions, possibly 
disabling -ftrapping-math by default), or to change the details of what 
-ftrapping-math does.  And we might choose, when fixing a bug about 
inexact, to decide that we need to change the defaults at that time.  But 
any such change should be separate from internal classification of 
(existing) transformations in GCC regarding what restrictions they obey 
for what exceptions or sub-exceptions.
  

Patch

diff --git a/gcc/ada/gcc-interface/misc.c b/gcc/ada/gcc-interface/misc.c
index 96199bd..93cbc71 100644
--- a/gcc/ada/gcc-interface/misc.c
+++ b/gcc/ada/gcc-interface/misc.c
@@ -451,7 +451,7 @@  gnat_init_gcc_fp (void)
   /* Assume that FP operations can trap if S'Machine_Overflow is true,
      but don't override the user if not.  */
   if (Machine_Overflows_On_Target)
-    flag_trapping_math = 1;
+    flag_trapping_math = TRAPPING_MATH_DEFAULT;
   else if (!global_options_set.x_flag_trapping_math)
     flag_trapping_math = 0;
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index b921f5e..314f9a7 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2750,7 +2750,7 @@  generate them instead of using descriptors.
 ; (user-visible) trap.  This is the case, for example, in nonstop
 ; IEEE 754 arithmetic.
 ftrapping-math
-Common Var(flag_trapping_math) Init(1) Optimization SetByCombined
+Common Var(flag_trapping_math) Init(TRAPPING_MATH_DEFAULT) Optimization SetByCombined UInteger
 Assume floating-point operations can trap.
 
 ftrapv
diff --git a/gcc/flag-types.h b/gcc/flag-types.h
index 5bd1f77..98b9ff0 100644
--- a/gcc/flag-types.h
+++ b/gcc/flag-types.h
@@ -481,6 +481,33 @@  enum openacc_privatization
   OPENACC_PRIVATIZATION_NOISY
 };
 
+/* Trapping math exception classes.  */
+enum trapping_math_model
+{
+  TRAPPING_MATH_NONE = 0,
+  TRAPPING_MATH_QNANOP = 1UL << 0,
+  TRAPPING_MATH_SNANOP = 1UL << 1,
+  TRAPPING_MATH_QNANCMP = 1UL << 2,
+  TRAPPING_MATH_SNANCMP = 1UL << 3,
+  TRAPPING_MATH_INTCONV = 1UL << 4,
+  TRAPPING_MATH_SQRTNEG = 1UL << 5,
+  TRAPPING_MATH_LIBMFUN = 1UL << 6,
+  TRAPPING_MATH_FDIVZERO = 1UL << 7,
+  TRAPPING_MATH_IDIVZERO = 1UL << 8,
+  TRAPPING_MATH_FPDENORM = 1UL << 9,
+  TRAPPING_MATH_OVERFLOW = 1UL << 10,
+  TRAPPING_MATH_UNDERFLOW = 1UL << 11,
+  TRAPPING_MATH_INFDIVINF = 1UL << 12,
+  TRAPPING_MATH_INFSUBINF = 1UL << 13,
+  TRAPPING_MATH_INFMULZERO = 1UL << 14,
+  TRAPPING_MATH_ZERODIVZERO = 1UL << 15,
+
+  TRAPPING_MATH_DEFAULT = (1UL << 16) - 1,
+
+  TRAPPING_MATH_INEXACT = 1UL << 16,
+  TRAPPING_MATH_TRAPV = 1UL << 17
+};
+
 #endif
 
 #endif /* ! GCC_FLAG_TYPES_H */
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 14d1335..6cd71cc 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1664,7 +1664,7 @@  process_options (void)
 
   /* The presence of IEEE signaling NaNs, implies all math can trap.  */
   if (flag_signaling_nans)
-    flag_trapping_math = 1;
+    flag_trapping_math = TRAPPING_MATH_DEFAULT;
 
   /* We cannot reassociate if we want traps or signed zeros.  */
   if (flag_associative_math && (flag_trapping_math || flag_signed_zeros))