https://gcc.gnu.org/g:8158ffe53bc70511246588be6efe5e98431e55d9
commit r17-5585-g8158ffe53bc70511246588be6efe5e98431e55d9 Author: Igor Shevlyakov <[email protected]> Date: Tue Oct 6 17:22:20 2026 -0700 middle-end: Don't reverse LTGT/UNEQ when traps are honored [PR122307] LTGT is a signaling comparison and UNEQ is a quiet one, so reversing one into the other changes whether a quiet NaN operand raises FE_INVALID. reversed_comparison_code_parts nevertheless reverses them unconditionally. Return UNKNOWN for them when flag_trapping_math is set and the operands may be NaNs (or the comparison is in a MODE_CC mode, which does not tell), mirroring what invert_tree_comparison does at the GIMPLE level; keep the reversal otherwise. That change alone exposes a latent problem in emit_store_flag: it splits x != y into x unord y || x ltgt y, emitting a store-flag for UNORDERED and a conditional move on LTGT. The conditional move evaluates its comparison unconditionally, so for a quiet != the LTGT raises FE_INVALID on a NaN operand. Today this is hidden only because emit_conditional_move happens to reverse LTGT into the quiet UNEQ when canonicalizing its arms; with LTGT no longer reversible, x86 emits a signaling comisd for a quiet !=. Use the equivalent x unord y || !(x uneq y) directly, as do_compare_rtx_and_jump already does for jumps, and no longer treat LTGT as a non-trapping remainder. The NE split in do_compare_rtx_and_jump itself is safe: the UNORDERED jump comes first, so LTGT only ever sees ordered operands. PR middle-end/122307 gcc/ChangeLog: * jump.cc (reversed_comparison_code_parts): Do not reverse LTGT and UNEQ if traps are honored and NaNs are possible. * expmed.cc (emit_store_flag): Split NE as UNORDERED || !UNEQ instead of UNORDERED || LTGT; do not treat LTGT as non-trapping. gcc/testsuite/ChangeLog: * gcc.c-torture/execute/ieee/pr122307.c: New test. Signed-off-by: Igor Shevlyakov <[email protected]> Diff: --- gcc/expmed.cc | 18 ++++++- gcc/jump.cc | 12 ++++- .../gcc.c-torture/execute/ieee/pr122307.c | 56 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/gcc/expmed.cc b/gcc/expmed.cc index dab1f5ea27da..367e8af46e5a 100644 --- a/gcc/expmed.cc +++ b/gcc/expmed.cc @@ -6430,8 +6430,19 @@ emit_store_flag (rtx target, enum rtx_code code, rtx op0, rtx op1, if (!HAVE_conditional_move) return 0; - /* Do not turn a trapping comparison into a non-trapping one. */ - if ((code != EQ && code != NE && code != UNEQ && code != LTGT) + /* x != y is split into x unord y || x ltgt y, but the conditional move + below evaluates its comparison unconditionally and LTGT is signaling. + Use the quiet x unord y || !(x uneq y) instead. */ + bool invert = false; + if (code == LTGT && !and_them) + { + code = UNEQ; + invert = true; + } + + /* Do not change whether the comparison traps: only the quiet + EQ, NE and UNEQ remainders come from quiet comparisons. */ + if ((code != EQ && code != NE && code != UNEQ) && flag_trapping_math) return 0; @@ -6445,6 +6456,9 @@ emit_store_flag (rtx target, enum rtx_code code, rtx op0, rtx op1, if (and_them) tem = emit_conditional_move (target, { code, op0, op1, mode }, tem, const0_rtx, GET_MODE (tem), 0); + else if (invert) + tem = emit_conditional_move (target, { code, op0, op1, mode }, + tem, trueval, GET_MODE (tem), 0); else tem = emit_conditional_move (target, { code, op0, op1, mode }, trueval, tem, GET_MODE (tem), 0); diff --git a/gcc/jump.cc b/gcc/jump.cc index 100a0b136117..1327e7fcf972 100644 --- a/gcc/jump.cc +++ b/gcc/jump.cc @@ -388,11 +388,19 @@ reversed_comparison_code_parts (enum rtx_code code, const_rtx arg0, return reverse_condition (code); case ORDERED: case UNORDERED: - case LTGT: - case UNEQ: /* In case we already see unordered comparison, we can be sure to be dealing with floating point so we don't need any more tests. */ return reverse_condition_maybe_unordered (code); + case LTGT: + case UNEQ: + /* LTGT is signaling and UNEQ is quiet, so reversing one into the + other changes whether a quiet NaN operand raises an exception. + That only matters if FP traps are honored and the operands may + be NaNs; a MODE_CC comparison does not tell us the latter. */ + if (flag_trapping_math + && (GET_MODE_CLASS (mode) == MODE_CC || HONOR_NANS (mode))) + return UNKNOWN; + return reverse_condition_maybe_unordered (code); case UNLT: case UNLE: case UNGT: diff --git a/gcc/testsuite/gcc.c-torture/execute/ieee/pr122307.c b/gcc/testsuite/gcc.c-torture/execute/ieee/pr122307.c new file mode 100644 index 000000000000..71481a091176 --- /dev/null +++ b/gcc/testsuite/gcc.c-torture/execute/ieee/pr122307.c @@ -0,0 +1,56 @@ +/* PR middle-end/122307: a quiet != (and ==) must not raise FE_INVALID + for a quiet NaN operand, in any expansion form. */ +/* { dg-do run } */ +/* { dg-require-effective-target fenv_exceptions } */ +/* { dg-additional-options "-ftrapping-math" } */ +/* { dg-add-options ieee } */ + +#include <fenv.h> + +#define NI __attribute__ ((noipa)) + +#define GEN(T, S) \ + NI int ne_##S (T a, T b) { return a != b; } \ + NI int eq_##S (T a, T b) { return a == b; } \ + NI int ne_sel_##S (T a, T b, int x, int y) { return a != b ? x : y; } \ + NI int eq_sel_##S (T a, T b, int x, int y) { return a == b ? x : y; } \ + NI int lg_##S (T a, T b) { return __builtin_islessgreater (a, b); } \ + NI int ueq_##S (T a, T b) { return !__builtin_islessgreater (a, b); } + +GEN (float, f) +GEN (double, d) + +#define CHECK(E, V) \ + do { \ + feclearexcept (FE_INVALID); \ + if ((E) != (V) || fetestexcept (FE_INVALID)) \ + __builtin_abort (); \ + } while (0) + +int +main (void) +{ + volatile float nf = __builtin_nanf (""), of = 1.0f; + volatile double nd = __builtin_nan (""), od = 1.0; + + CHECK (ne_f (nf, of), 1); + CHECK (ne_f (of, nf), 1); + CHECK (eq_f (nf, of), 0); + CHECK (ne_sel_f (nf, of, 5, 7), 5); + CHECK (eq_sel_f (nf, of, 5, 7), 7); + CHECK (lg_f (nf, of), 0); + CHECK (ueq_f (nf, of), 1); + + CHECK (ne_d (nd, od), 1); + CHECK (ne_d (od, nd), 1); + CHECK (eq_d (nd, od), 0); + CHECK (ne_sel_d (nd, od, 5, 7), 5); + CHECK (eq_sel_d (nd, od, 5, 7), 7); + CHECK (lg_d (nd, od), 0); + CHECK (ueq_d (nd, od), 1); + + /* Ordered operands still compare correctly. */ + CHECK (ne_d (od, 2.0), 1); + CHECK (ne_d (od, od), 0); + return 0; +}
