From: Kyrylo Tkachov <[email protected]>
Only the first comparison of a conditional-compare sequence is a plain
comparison. Its operand constraints can differ from those of later
conditional comparisons. Reassociation orders operands by rank and does
not know these target constraints. For
int
f (int a, int b, int c, int d, int e)
{
return (e == 100) & (a < b) & (c < d);
}
AArch64 materialised the constant for a conditional comparison:
cmp w0, w1
mov w0, 100
ccmp w2, w3, 0, lt
ccmp w4, w0, 0, lt
cset w0, eq
Add a target hook that rejects an unsupported comparison or classifies it
as having no head preference, preferring the head, or requiring the head.
Targets that do not implement the hook keep the normal reassociation order.
The generic pass also requires a forwarded trapping comparison to lead.
Reject a chain with two comparisons that require this position.
AArch64 prefers constants that a plain comparison accepts but a conditional
comparison does not. It also prefers a floating-point zero. The example
then emits:
cmp w4, 100
ccmp w2, w3, 0, eq
ccmp w0, w1, 0, lt
cset w0, lt
Tests cover AArch64 immediate and floating-point constraints and a conflict
between a trapping comparison and preferred constants.
Changes since v1:
- Replace generic operand scoring with a target policy hook.
- Separate unsupported, neutral, preferred, and required heads.
- Implement only the AArch64 policy and leave x86 on the safe default.
gcc/
* config/aarch64/aarch64.cc
(aarch64_ccmp_first_preference): New function.
(TARGET_CCMP_FIRST_PREFERENCE): Define.
* coretypes.h (ccmp_first_kind): New enum.
* doc/tm.texi: Regenerate.
* doc/tm.texi.in (TARGET_CCMP_FIRST_PREFERENCE): Add hook.
* target.def (ccmp_first_preference): New hook.
* targhooks.cc (default_ccmp_first_preference): New function.
* targhooks.h (default_ccmp_first_preference): Declare.
* tree-ssa-reassoc.cc (ccmp_first_preference): New function.
(prepare_ccmp_chain): Use it to select one head. Reject unsupported
comparisons and multiple required heads.
gcc/testsuite/
* gcc.target/aarch64/ccmp_8.c: New test.
* gcc.target/aarch64/fccmp_4.c: Likewise.
* gcc.target/aarch64/fccmp_7.c: Likewise.
Signed-off-by: Kyrylo Tkachov <[email protected]>
---
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 5544d5f8c7a..4b93152fec1 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -29745,6 +29745,38 @@ aarch64_asan_shadow_offset (void)
return (HOST_WIDE_INT_1 << 36);
}
+/* Implement TARGET_CCMP_FIRST_PREFERENCE. */
+
+static ccmp_first_kind
+aarch64_ccmp_first_preference (machine_mode mode, tree_code code,
+ const_tree op0, const_tree op1)
+{
+ if (SCALAR_FLOAT_MODE_P (mode))
+ {
+ if (!((mode == HFmode && TARGET_FP_F16INST)
+ || mode == SFmode || mode == DFmode))
+ return CCMP_FIRST_UNSUPPORTED;
+ return real_zerop (op1) ? CCMP_FIRST_PREFERRED : CCMP_FIRST_NONE;
+ }
+
+ if (!(mode == QImode || mode == HImode
+ || mode == SImode || mode == DImode))
+ return CCMP_FIRST_UNSUPPORTED;
+
+ if (TREE_CODE (op1) != INTEGER_CST)
+ return CCMP_FIRST_NONE;
+
+ scalar_int_mode int_mode = as_a <scalar_int_mode> (mode);
+ rtx_code rcode = get_rtx_code (code, TYPE_UNSIGNED (TREE_TYPE (op0)));
+ rtx imm = immed_wide_int_const
+ (wi::to_wide (op1, GET_MODE_PRECISION (int_mode)), int_mode);
+ canonicalize_comparison (int_mode, &rcode, &imm);
+
+ return (aarch64_plus_immediate (imm, int_mode)
+ && !aarch64_ccmp_immediate (imm, int_mode)
+ ? CCMP_FIRST_PREFERRED : CCMP_FIRST_NONE);
+}
+
static rtx
aarch64_gen_ccmp_first (rtx_insn **prep_seq, rtx_insn **gen_seq,
rtx_code code, tree treeop0, tree treeop1)
@@ -29950,6 +29982,9 @@ aarch64_gen_ccmp_next (rtx_insn **prep_seq, rtx_insn
**gen_seq, rtx prev,
#undef TARGET_GEN_CCMP_FIRST
#define TARGET_GEN_CCMP_FIRST aarch64_gen_ccmp_first
+#undef TARGET_CCMP_FIRST_PREFERENCE
+#define TARGET_CCMP_FIRST_PREFERENCE aarch64_ccmp_first_preference
+
#undef TARGET_GEN_CCMP_NEXT
#define TARGET_GEN_CCMP_NEXT aarch64_gen_ccmp_next
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index 5cc602ed7e5..afd228fb12e 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -297,6 +297,14 @@ enum optimization_type {
OPTIMIZE_FOR_SIZE
};
+/* How strongly a comparison should start a conditional-compare sequence. */
+enum ccmp_first_kind {
+ CCMP_FIRST_UNSUPPORTED,
+ CCMP_FIRST_NONE,
+ CCMP_FIRST_PREFERRED,
+ CCMP_FIRST_REQUIRED
+};
+
/* Enumerates a padding direction. */
enum pad_direction {
/* No padding is required. */
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index ca68bdc3e61..2b800e7baae 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -12619,6 +12619,15 @@ This function prepares to emit a comparison insn for
the first compare in a
@var{code} is the @code{rtx_code} of the compare for @var{op0} and @var{op1}.
@end deftypefn
+@deftypefn {Target Hook} ccmp_first_kind TARGET_CCMP_FIRST_PREFERENCE
(machine_mode @var{mode}, tree_code @var{code}, const_tree @var{op0},
const_tree @var{op1})
+Return how strongly the comparison of @var{op0} and @var{op1} with tree
+code @var{code} and mode @var{mode} should start a conditional-compare
+sequence. Return @code{CCMP_FIRST_REQUIRED} if the comparison cannot be
+conditional, @code{CCMP_FIRST_PREFERRED} if starting with it is profitable,
+@code{CCMP_FIRST_NONE} if its position does not matter, or
+@code{CCMP_FIRST_UNSUPPORTED} if it cannot be part of the sequence.
+@end deftypefn
+
@deftypefn {Target Hook} rtx TARGET_GEN_CCMP_NEXT (rtx_insn **@var{prep_seq},
rtx_insn **@var{gen_seq}, rtx @var{prev}, rtx_code @var{cmp_code}, tree
@var{op0}, tree @var{op1}, rtx_code @var{bit_code})
This function prepares to emit a conditional comparison within a sequence
of conditional comparisons. It returns an appropriate comparison with
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 532e95ef216..6ae56d3c563 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -8005,6 +8005,8 @@ lists.
@hook TARGET_GEN_CCMP_FIRST
+@hook TARGET_CCMP_FIRST_PREFERENCE
+
@hook TARGET_GEN_CCMP_NEXT
@hook TARGET_HAVE_CCMP
diff --git a/gcc/target.def b/gcc/target.def
index 66c20a9c9d2..83f66b45742 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -2831,6 +2831,18 @@ DEFHOOK
rtx, (rtx_insn **prep_seq, rtx_insn **gen_seq, rtx_code code, tree op0, tree
op1),
NULL)
+DEFHOOK
+(ccmp_first_preference,
+ "Return how strongly the comparison of @var{op0} and @var{op1} with tree\n\
+code @var{code} and mode @var{mode} should start a conditional-compare\n\
+sequence. Return @code{CCMP_FIRST_REQUIRED} if the comparison cannot be\n\
+conditional, @code{CCMP_FIRST_PREFERRED} if starting with it is profitable,\n\
+@code{CCMP_FIRST_NONE} if its position does not matter, or\n\
+@code{CCMP_FIRST_UNSUPPORTED} if it cannot be part of the sequence.",
+ ccmp_first_kind,
+ (machine_mode mode, tree_code code, const_tree op0, const_tree op1),
+ default_ccmp_first_preference)
+
DEFHOOK
(gen_ccmp_next,
"This function prepares to emit a conditional comparison within a sequence\n\
@@ -7707,4 +7719,3 @@ The default value is NULL.",
/* Close the 'struct gcc_target' definition. */
HOOK_VECTOR_END (C90_EMPTY_HACK)
-
diff --git a/gcc/targhooks.cc b/gcc/targhooks.cc
index 388f696c8aa..2107f32f890 100644
--- a/gcc/targhooks.cc
+++ b/gcc/targhooks.cc
@@ -1960,6 +1960,15 @@ default_have_ccmp (void)
return targetm.gen_ccmp_first != NULL;
}
+/* Default implementation of TARGET_CCMP_FIRST_PREFERENCE. */
+
+ccmp_first_kind
+default_ccmp_first_preference (machine_mode, tree_code, const_tree,
+ const_tree)
+{
+ return CCMP_FIRST_UNSUPPORTED;
+}
+
/* By default we assume that c99 functions are present at the runtime,
but sincos is not. */
bool
diff --git a/gcc/targhooks.h b/gcc/targhooks.h
index 86de6ef8a69..c82f9738ef4 100644
--- a/gcc/targhooks.h
+++ b/gcc/targhooks.h
@@ -222,6 +222,8 @@ extern rtx default_addr_space_convert (rtx, tree, tree);
extern unsigned int default_case_values_threshold (void);
extern bool default_have_conditional_execution (void);
extern bool default_have_ccmp (void);
+extern ccmp_first_kind default_ccmp_first_preference
+ (machine_mode, tree_code, const_tree, const_tree);
extern bool default_libc_has_function (enum function_class, tree);
extern bool default_libc_has_fast_function (int fcode);
diff --git a/gcc/testsuite/gcc.target/aarch64/ccmp_8.c
b/gcc/testsuite/gcc.target/aarch64/ccmp_8.c
new file mode 100644
index 00000000000..8ed28057caf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/ccmp_8.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/* Only the leading comparison of a conditional compare sequence is a plain
+ comparison, which accepts constants that CCMP rejects. */
+
+/*
+** int_head:
+** cmp w4, 100
+** ccmp w0, w1, 0, eq
+** ccmp w2, w3, 0, lt
+** cset w0, lt
+** ret
+*/
+int
+int_head (int a, int b, int c, int d, int e)
+{
+ return (e == 100) & (a < b) & (c < d);
+}
+
+/* Both comparisons against a constant reach the leading pair, so the
+ expander can compare their costs and lead with the constant that CCMP
+ cannot encode. */
+
+/*
+** int_two_constants:
+** cmp w0, 100
+** ccmp w1, w2, 0, eq
+** ccmp w3, 3, 0, lt
+** cset w0, eq
+** ret
+*/
+int
+int_two_constants (int a, int b, int c, int d)
+{
+ return (a == 100) & (b < c) & (d == 3);
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/fccmp_4.c
b/gcc/testsuite/gcc.target/aarch64/fccmp_4.c
new file mode 100644
index 00000000000..978436e8954
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fccmp_4.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -ffinite-math-only" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/* FCMP compares against zero, FCCMP does not. A comparison against a
+ constant therefore has to reach the head of the sequence. */
+
+/*
+** fp_zero_head:
+** fcmp d4, #0\.0
+** fccmp d0, d1, 0, mi
+** fccmp d2, d3, 0, mi
+** cset w0, mi
+** ret
+*/
+int
+fp_zero_head (double a, double b, double c, double d, double e)
+{
+ return (e < 0.0) & (a < b) & (c < d);
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/fccmp_7.c
b/gcc/testsuite/gcc.target/aarch64/fccmp_7.c
new file mode 100644
index 00000000000..23f6f81e103
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/fccmp_7.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+/* A trapping comparison takes precedence over comparisons that are cheaper
+ at the start of the sequence. */
+
+int
+trap_before_zero (double a, double b, double c, double x, double y,
+ int i, int j)
+{
+ return (a == 0.0) & (x < y) & (c == 0.0) & (i < j);
+}
+
+/* { dg-final { scan-assembler-times {\tfcmpe\t} 1 } } */
+/* { dg-final { scan-assembler-times {\tfccmp\t} 2 } } */
+/* { dg-final { scan-assembler-times {\tccmp\t} 1 } } */
+/* { dg-final { scan-assembler-times {\tcset\t} 1 } } */
+/* { dg-final { scan-assembler-not {\tand\t} } } */
diff --git a/gcc/tree-ssa-reassoc.cc b/gcc/tree-ssa-reassoc.cc
index 737d30ba0a4..1f9bfea084c 100644
--- a/gcc/tree-ssa-reassoc.cc
+++ b/gcc/tree-ssa-reassoc.cc
@@ -5182,8 +5182,28 @@ ccmp_comparison_p (tree op, basic_block bb)
/* A conditional compare sets the flags from one scalar comparison. */
machine_mode mode = TYPE_MODE (TREE_TYPE (gimple_assign_rhs1 (def)));
- return ((SCALAR_INT_MODE_P (mode) || SCALAR_FLOAT_MODE_P (mode))
- && known_le (GET_MODE_SIZE (mode), UNITS_PER_WORD));
+ return SCALAR_INT_MODE_P (mode) || SCALAR_FLOAT_MODE_P (mode);
+}
+
+/* Return how strongly OP should start a conditional compare sequence.
+ FOLDABLE is true if OP can be folded into the flags. */
+
+static ccmp_first_kind
+ccmp_first_preference (tree op, bool foldable)
+{
+ if (!foldable)
+ return CCMP_FIRST_NONE;
+
+ gimple *def = SSA_NAME_DEF_STMT (op);
+ gcc_checking_assert (is_gimple_assign (def));
+
+ tree op0 = gimple_assign_rhs1 (def);
+ tree op1 = gimple_assign_rhs2 (def);
+ ccmp_first_kind kind = targetm.ccmp_first_preference
+ (TYPE_MODE (TREE_TYPE (op0)), gimple_assign_rhs_code (def), op0, op1);
+ if (kind == CCMP_FIRST_UNSUPPORTED || !gimple_could_trap_p (def))
+ return kind;
+ return CCMP_FIRST_REQUIRED;
}
/* Return true if all operands in OPS form one conditional compare sequence
@@ -5200,6 +5220,7 @@ prepare_ccmp_chain (vec<operand_entry *> &ops, tree_code
code, basic_block bb)
bool seen_foldable = false;
int head = -1;
+ ccmp_first_kind head_kind = CCMP_FIRST_NONE;
for (unsigned int i = 0; i < ops.length (); ++i)
{
@@ -5208,12 +5229,17 @@ prepare_ccmp_chain (vec<operand_entry *> &ops,
tree_code code, basic_block bb)
return false;
bool foldable = has_single_use (oe->op);
- if (foldable
- && gimple_could_trap_p (SSA_NAME_DEF_STMT (oe->op)))
+
+ ccmp_first_kind kind
+ = ccmp_first_preference (oe->op, foldable);
+ if (kind == CCMP_FIRST_UNSUPPORTED)
+ return false;
+ if (kind == CCMP_FIRST_REQUIRED && head_kind == CCMP_FIRST_REQUIRED)
+ return false;
+ if (kind > head_kind)
{
- if (head >= 0)
- return false;
head = i;
+ head_kind = kind;
}
/* Comparison leaves can have uses outside this chain. */
--
2.50.1 (Apple Git-155)