From: Kyrylo Tkachov <[email protected]>
A conditional comparison does not compare its operands when the preceding
condition is false, so it does not raise the exception that the comparison
it replaces would raise. A bitwise operation evaluates both of its
operands, so that exception has to be preserved. For example, at -O2:
int
f (double a, double b, double c, double d)
{
return (a < b) & (c < d);
}
fcmpe d0, d1
fccmpe d2, d3, 0, mi
cset w0, mi
A quiet NaN in c or d does not raise Invalid when a < b is false. After
this patch:
fcmpe d0, d1
cset w1, mi
fcmpe d2, d3
cset w0, mi
and w0, w1, w0
Reject such a comparison in aarch64_gen_ccmp_next. CCFPE marks a
comparison that raises Invalid for a quiet NaN, so it identifies the
comparisons that have to stay unconditional, and a signalling NaN raises
Invalid in every comparison. Both a short-circuit source and a chain
under -ffinite-math-only or -fno-trapping-math keep their conditional
comparisons: ifcombine only merges conditions that cannot trap, and
neither option makes the exception observable.
The first comparison of a sequence always executes, so a trapping
comparison can still lead one. The expander already tries both orders of
a comparison pair, so a mixed sequence keeps the floating-point comparison
in front and folds the integer one.
Document the rule in the gen_ccmp_next hook. The i386 implementation
already refuses every floating-point comparison in this position.
Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill
gcc/
* config/aarch64/aarch64.cc (aarch64_gen_ccmp_next): Reject a
comparison that would lose an observable exception.
* target.def (gen_ccmp_next): Document the rule.
* doc/tm.texi: Regenerate.
gcc/testsuite/
* gcc.target/aarch64/fccmp-trap-1.c: New test.
* gcc.target/aarch64/fccmp-trap-2.c: Likewise.
* gcc.target/aarch64/fccmp-trap-3.c: Likewise.
Signed-off-by: Kyrylo Tkachov <[email protected]>
---
gcc/config/aarch64/aarch64.cc | 10 ++++
gcc/doc/tm.texi | 3 ++
gcc/target.def | 3 ++
.../gcc.target/aarch64/fccmp-trap-1.c | 25 +++++++++
.../gcc.target/aarch64/fccmp-trap-2.c | 54 +++++++++++++++++++
.../gcc.target/aarch64/fccmp-trap-3.c | 14 +++++
6 files changed, 109 insertions(+)
create mode 100644 gcc/testsuite/gcc.target/aarch64/fccmp-trap-1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/fccmp-trap-2.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/fccmp-trap-3.c
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index e94b583d1bd..68e5aca9018 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -29720,6 +29720,16 @@ aarch64_gen_ccmp_next (rtx_insn **prep_seq, rtx_insn
**gen_seq, rtx prev,
case E_DFmode:
cmp_mode = op_mode;
cc_mode = aarch64_select_cc_mode (cmp_code, op0, op1);
+ /* A conditional comparison does not compare its operands when the
+ preceding condition is false, so it cannot raise the exception that
+ the comparison it replaces would raise. CCFPE marks a comparison
+ that raises Invalid for a quiet NaN, and every comparison raises it
+ for a signalling NaN. */
+ if (cc_mode == CCFPEmode || HONOR_SNANS (op_mode))
+ {
+ end_sequence ();
+ return NULL_RTX;
+ }
break;
default:
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 85e17525683..6d5a77f2ea4 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -12615,6 +12615,9 @@ This function prepares to emit a conditional comparison
within a sequence
or @code{gen_ccmp_next}. It may return @code{NULL} if the combination of
@var{prev} and this comparison is not supported, otherwise the result must
be appropriate for passing to @code{gen_ccmp_next} or @code{cbranch_optab}.
+ The comparison is not performed when @var{prev} is false, so this function
+ has to return @code{NULL} for a comparison that raises an exception which
+ has to be preserved.
@var{code} is the @code{rtx_code} of the compare for @var{op0} and @var{op1}.
@var{bit_code} is @code{AND} or @code{IOR}, which is the op on the compares.
@end deftypefn
diff --git a/gcc/target.def b/gcc/target.def
index 884fe1bd57e..995b0699307 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -2843,6 +2843,9 @@ DEFHOOK
or @code{gen_ccmp_next}. It may return @code{NULL} if the combination of\n\
@var{prev} and this comparison is not supported, otherwise the result must\n\
be appropriate for passing to @code{gen_ccmp_next} or @code{cbranch_optab}.\n\
+ The comparison is not performed when @var{prev} is false, so this function\n\
+ has to return @code{NULL} for a comparison that raises an exception which\n\
+ has to be preserved.\n\
@var{code} is the @code{rtx_code} of the compare for @var{op0} and
@var{op1}.\n\
@var{bit_code} is @code{AND} or @code{IOR}, which is the op on the compares.",
rtx, (rtx_insn **prep_seq, rtx_insn **gen_seq, rtx prev, rtx_code cmp_code,
tree op0, tree op1, rtx_code bit_code),
diff --git a/gcc/testsuite/gcc.target/aarch64/fccmp-trap-1.c
b/gcc/testsuite/gcc.target/aarch64/fccmp-trap-1.c
new file mode 100644
index 00000000000..1d151f2f056
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fccmp-trap-1.c
@@ -0,0 +1,25 @@
+/* { dg-do run } */
+/* { dg-options "-O2" } */
+
+enum { FPSR_IOC = 1 << 0 };
+
+__attribute__ ((noipa))
+static int
+and_lt (double a, double b, double c, double d)
+{
+ return (a < b) & (c < d);
+}
+
+int
+main (void)
+{
+ double qnan = __builtin_nan ("");
+
+ __builtin_aarch64_set_fpsr (0);
+ if (and_lt (1.0, 0.0, qnan, 0.0) != 0)
+ __builtin_abort ();
+ if ((__builtin_aarch64_get_fpsr () & FPSR_IOC) == 0)
+ __builtin_abort ();
+
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/fccmp-trap-2.c
b/gcc/testsuite/gcc.target/aarch64/fccmp-trap-2.c
new file mode 100644
index 00000000000..0f5fb75b922
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fccmp-trap-2.c
@@ -0,0 +1,54 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/* A bitwise operation evaluates both comparisons, so a conditional
+ comparison would hide the Invalid exception that the second ordered
+ comparison raises for a quiet NaN. */
+
+/*
+** and_lt:
+** fcmpe d0, d1
+** cset w[0-9]+, mi
+** fcmpe d2, d3
+** cset w[0-9]+, mi
+** and w0, w[0-9]+, w[0-9]+
+** ret
+*/
+int
+and_lt (double a, double b, double c, double d)
+{
+ return (a < b) & (c < d);
+}
+
+/* An equality comparison only raises Invalid for a signalling NaN, which
+ this test does not honour, so it can still be made conditional. */
+
+/*
+** and_eq:
+** fcmp d0, d1
+** fccmp d2, d3, 0, eq
+** cset w0, eq
+** ret
+*/
+int
+and_eq (double a, double b, double c, double d)
+{
+ return (a == b) & (c == d);
+}
+
+/* Only the floating-point comparison has to stay unconditional. The target
+ rejects it in the conditional position, and the expander leads with it. */
+
+/*
+** and_int:
+** fcmpe d0, d1
+** ccmp w0, w1, 0, mi
+** cset w0, lt
+** ret
+*/
+int
+and_int (double a, double b, int i, int j)
+{
+ return (a < b) & (i < j);
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/fccmp-trap-3.c
b/gcc/testsuite/gcc.target/aarch64/fccmp-trap-3.c
new file mode 100644
index 00000000000..2004e435eb1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fccmp-trap-3.c
@@ -0,0 +1,14 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fsignaling-nans" } */
+
+/* Every comparison raises Invalid for a signalling NaN, so none of them can
+ become conditional. */
+
+int
+and_eq (double a, double b, double c, double d)
+{
+ return (a == b) & (c == d);
+}
+
+/* { dg-final { scan-assembler-times {\tfcmp\td[0-9]+, d[0-9]+} 2 } } */
+/* { dg-final { scan-assembler-not {\tfccmpe?\t} } } */
--
2.50.1 (Apple Git-155)