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)