On Fri, 9 Oct 2026, Igor Shevlyakov wrote:

> and_comparisons_1 and or_comparisons_1 pass TRUTH_ANDIF_EXPR and
> TRUTH_ORIF_EXPR to combine_comparisons.  Under -ftrapping-math those
> codes tell it that the second comparison is only evaluated when the
> first does not decide the result, so it may drop the exception the
> second one raises.  That is wrong for reassoc, which combines the
> operands of a BIT_AND_EXPR or BIT_IOR_EXPR, both of them evaluated:
> 
> - !__builtin_isgreaterequal (a, b) | (a > b) became a quiet a != b,
>   dropping the FE_INVALID that a > b raises for a NaN;
> - (a <= b) & (a == b) became a quiet a == b;
> - a < b || __builtin_isunordered (a, b), once phiopt has made both
>   comparisons unconditional, became a quiet a u< b.
> 
> if-conversion's fold_or_predicates likewise combines block predicates
> that the if-converted code computes unconditionally.
> 
> Pass TRUTH_AND_EXPR and TRUTH_OR_EXPR instead, as
> fold_truth_andor_for_ifcombine already does.  combine_comparisons then
> requires the combined comparison to trap exactly when one of the two
> does.  ifcombine, the one caller whose conditions are short-circuited,
> loses nothing: since r17-4921 it passes them in evaluation order, it
> combines an inner condition that can trap itself, with TRUTH_ANDIF_EXPR,
> and it only calls maybe_fold_and_comparisons for one that cannot.  For a
> second comparison that cannot trap the short-circuit rules of
> combine_comparisons make no difference.  fp-trapping-cmp-8.c checks that
> ifcombine still merges if (a == b) if (a <= b) into a == b.
> 
> A combination that folds to a constant, e.g. (a < b) & (a > b) -> 0,
> can still lose the exception, as x < x -> 0 does.

I still believe it's dangerous to simply change {and,or}_comparisons_1
since at least the maybe_fold_and_comparisons caller calls it from
a context where TRUTH_ANDIF_EXPR is correct which is why I suggested
(ISTR I did ...) to pass down the kind from callers and investigate/adjust
all ultimate callers.

I'm also not sure either TRUTH_ANDIF_EXPR or TRUTH_AND_EXPR is
conservatively correct?

>       PR middle-end/127786
> 
> gcc/ChangeLog:
> 
>       * gimple-fold.cc (and_comparisons_1): Pass TRUTH_AND_EXPR to
>       combine_comparisons.
>       (or_comparisons_1): Pass TRUTH_OR_EXPR to combine_comparisons.
> 
> gcc/testsuite/ChangeLog:
> 
>       * gcc.dg/torture/cmp-of-cmp-fenv.c (unlt_or_gt, le_and_eq)
>       (le_then_eq, lt_orif_uno): New functions.
>       (tests): Add them.
>       * gcc.dg/tree-ssa/fp-trapping-cmp-8.c: New test.
> 
> Signed-off-by: Igor Shevlyakov <[email protected]>
> ---
>  gcc/gimple-fold.cc                            | 21 +++++++++++++------
>  .../gcc.dg/torture/cmp-of-cmp-fenv.c          | 20 ++++++++++++++++++
>  .../gcc.dg/tree-ssa/fp-trapping-cmp-8.c       | 16 ++++++++++++++
>  3 files changed, 51 insertions(+), 6 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/fp-trapping-cmp-8.c
> 
> diff --git a/gcc/gimple-fold.cc b/gcc/gimple-fold.cc
> index 63e289c0d44..c735b881f94 100644
> --- a/gcc/gimple-fold.cc
> +++ b/gcc/gimple-fold.cc
> @@ -7586,7 +7586,15 @@ and_var_with_comparison_1 (tree type, gimple *stmt,
>     If this can be done without constructing an intermediate value,
>     return the resulting tree; otherwise NULL_TREE is returned.
>     This function is deliberately asymmetric as it recurses on SSA_DEFs
> -   in the first comparison but not the second.  */
> +   in the first comparison but not the second.
> +
> +   Both comparisons are assumed to be evaluated, so combine_comparisons
> +   gets TRUTH_AND_EXPR rather than TRUTH_ANDIF_EXPR: its short-circuit
> +   rules would let the combined comparison drop an exception that the
> +   second comparison raises, which is wrong for the BIT_AND_EXPRs that
> +   reassoc combines.  ifcombine loses nothing: it combines an inner
> +   condition that can trap itself, and calls this only for one that
> +   cannot, where the short-circuit rules make no difference.  */
>  
>  static tree
>  and_comparisons_1 (tree type, enum tree_code code1, tree op1a, tree op1b,
> @@ -7601,7 +7609,7 @@ and_comparisons_1 (tree type, enum tree_code code1, 
> tree op1a, tree op1b,
>      {
>        /* Result will be either NULL_TREE, or a combined comparison.  */
>        tree t = combine_comparisons (UNKNOWN_LOCATION,
> -                                 TRUTH_ANDIF_EXPR, code1, code2,
> +                                 TRUTH_AND_EXPR, code1, code2,
>                                   truth_type, op1a, op1b);
>        if (t)
>       return t;
> @@ -7613,7 +7621,7 @@ and_comparisons_1 (tree type, enum tree_code code1, 
> tree op1a, tree op1b,
>      {
>        /* Result will be either NULL_TREE, or a combined comparison.  */
>        tree t = combine_comparisons (UNKNOWN_LOCATION,
> -                                 TRUTH_ANDIF_EXPR, code1,
> +                                 TRUTH_AND_EXPR, code1,
>                                   swap_tree_comparison (code2),
>                                   truth_type, op1a, op1b);
>        if (t)
> @@ -9527,7 +9535,8 @@ or_var_with_comparison_1 (tree type, gimple *stmt,
>     If this can be done without constructing an intermediate value,
>     return the resulting tree; otherwise NULL_TREE is returned.
>     This function is deliberately asymmetric as it recurses on SSA_DEFs
> -   in the first comparison but not the second.  */
> +   in the first comparison but not the second.  As in and_comparisons_1,
> +   both comparisons are assumed to be evaluated (TRUTH_OR_EXPR).  */
>  
>  static tree
>  or_comparisons_1 (tree type, enum tree_code code1, tree op1a, tree op1b,
> @@ -9542,7 +9551,7 @@ or_comparisons_1 (tree type, enum tree_code code1, tree 
> op1a, tree op1b,
>      {
>        /* Result will be either NULL_TREE, or a combined comparison.  */
>        tree t = combine_comparisons (UNKNOWN_LOCATION,
> -                                 TRUTH_ORIF_EXPR, code1, code2,
> +                                 TRUTH_OR_EXPR, code1, code2,
>                                   truth_type, op1a, op1b);
>        if (t)
>       return t;
> @@ -9554,7 +9563,7 @@ or_comparisons_1 (tree type, enum tree_code code1, tree 
> op1a, tree op1b,
>      {
>        /* Result will be either NULL_TREE, or a combined comparison.  */
>        tree t = combine_comparisons (UNKNOWN_LOCATION,
> -                                 TRUTH_ORIF_EXPR, code1,
> +                                 TRUTH_OR_EXPR, code1,
>                                   swap_tree_comparison (code2),
>                                   truth_type, op1a, op1b);
>        if (t)
> diff --git a/gcc/testsuite/gcc.dg/torture/cmp-of-cmp-fenv.c 
> b/gcc/testsuite/gcc.dg/torture/cmp-of-cmp-fenv.c
> index 157a3d1af64..45715831ba1 100644
> --- a/gcc/testsuite/gcc.dg/torture/cmp-of-cmp-fenv.c
> +++ b/gcc/testsuite/gcc.dg/torture/cmp-of-cmp-fenv.c
> @@ -32,6 +32,22 @@ NOIPA int unlt_orif_gt (double a, double b)
>  /* Quiet & quiet: LTGT would signal.  */
>  NOIPA int ord_and_ne (double a, double b)
>  { return !__builtin_isunordered (a, b) & (a != b); }
> +/* Quiet | signaling: > is evaluated, so NE would not signal.  */
> +NOIPA int unlt_or_gt (double a, double b)
> +{ return !__builtin_isgreaterequal (a, b) | (a > b); }
> +/* Signaling & quiet: EQ would not signal.  */
> +NOIPA int le_and_eq (double a, double b) { return (a <= b) & (a == b); }
> +/* Signaling, then quiet in an inner if: EQ would not signal.  */
> +NOIPA int le_then_eq (double a, double b)
> +{
> +  if (a <= b)
> +    if (a == b)
> +      return 1;
> +  return 0;
> +}
> +/* Signaling || quiet: < is always evaluated, UNLT would not signal.  */
> +NOIPA int lt_orif_uno (double a, double b)
> +{ return a < b || __builtin_isunordered (a, b); }
>  
>  static const struct
>  {
> @@ -49,6 +65,10 @@ static const struct
>    { uno_or_eq,      0, { 1, 0, 1, 0 } },
>    { unlt_orif_gt,   0, { 1, 1, 0, 1 } },
>    { ord_and_ne,     0, { 0, 1, 0, 1 } },
> +  { unlt_or_gt,     1, { 1, 1, 0, 1 } },
> +  { le_and_eq,      1, { 0, 0, 1, 0 } },
> +  { le_then_eq,     1, { 0, 0, 1, 0 } },
> +  { lt_orif_uno,    1, { 1, 1, 0, 0 } },
>  };
>  
>  int
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/fp-trapping-cmp-8.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/fp-trapping-cmp-8.c
> new file mode 100644
> index 00000000000..2a13f3b85a2
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/fp-trapping-cmp-8.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O1 -ftrapping-math -fdump-tree-ifcombine-details" } */
> +
> +/* a <= b is only evaluated when a == b is true, so the operands are
> +   ordered and it cannot trap: ifcombine may still merge the two.  */
> +
> +int
> +f (double a, double b)
> +{
> +  if (a == b)
> +    if (a <= b)
> +      return 1;
> +  return 0;
> +}
> +
> +/* { dg-final { scan-tree-dump "optimizing trapping cond to 
> a_\[0-9\]+\\(D\\) == b_\[0-9\]+\\(D\\)" "ifcombine" } } */
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri; (HRB 36809, AG Nuernberg)

Reply via email to