From: Kyrylo Tkachov <[email protected]>
Reassociation can turn a chain of four comparisons into a balanced tree:
return ((a < b) & (c < d)) & ((e < f) & (g < h));
The conditional-compare expander rejects this when both operands are
subtrees. AArch64 then emits two separate chains:
cmp w0, w1
ccmp w2, w3, 0, lt
cset w1, lt
cmp w4, w5
ccmp w6, w7, 0, lt
cset w0, lt
and w0, w1, w0
A subtree can be appended when all its operators match the parent. Recognise
these uniform subtrees, expand the other operand first, then append each
comparison. AArch64 then emits one chain:
cmp w0, w1
ccmp w2, w3, 0, lt
ccmp w4, w5, 0, lt
ccmp w6, w7, 0, lt
cset w0, lt
Expand each recursive operand only once. Cache uniform-tree results so the
analysis remains linear for nested mixed trees. Mixed trees that cannot use
one condition-code value remain rejected.
Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
Thanks,
Kyrill
gcc/
* ccmp.cc (ccmp_uniform_cache): New typedef.
(ccmp_uniform_chain_p): New function.
(ccmp_candidate_p): Accept a node with a uniform operand.
(expand_ccmp_chain): New function.
(expand_ccmp_expr_1): Append a uniform operand.
(expand_ccmp_expr): Create the uniform-tree cache.
gcc/testsuite/
* gcc.c-torture/execute/ccmp-tree-1.c: New test.
* gcc.target/aarch64/ccmp_6.c: New test.
Signed-off-by: Kyrylo Tkachov <[email protected]>
---
gcc/ccmp.cc | 94 ++++++++++++++++---
.../gcc.c-torture/execute/ccmp-tree-1.c | 90 ++++++++++++++++++
gcc/testsuite/gcc.target/aarch64/ccmp_6.c | 89 ++++++++++++++++++
3 files changed, 260 insertions(+), 13 deletions(-)
create mode 100644 gcc/testsuite/gcc.c-torture/execute/ccmp-tree-1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/ccmp_6.c
diff --git a/gcc/ccmp.cc b/gcc/ccmp.cc
index f63e44dcc84..1c540f641b2 100644
--- a/gcc/ccmp.cc
+++ b/gcc/ccmp.cc
@@ -37,6 +37,7 @@ along with GCC; see the file COPYING3. If not see
#include "cfgexpand.h"
#include "ccmp.h"
#include "predict.h"
+#include "hash-map.h"
/* Check whether T is a simple boolean variable or a SSA name
set by a comparison operator in the same basic block. */
@@ -90,10 +91,40 @@ ccmp_tree_comparison_p (tree t, basic_block bb)
If all checks OK in expand_ccmp_expr, it emits insns in prep_seq, then
insns in gen_seq. */
+typedef hash_map<tree, bool> ccmp_uniform_cache;
+
+/* Return true if T is a CODE tree in BB whose leaves are comparisons.
+ CACHE records results for logical SSA definitions. */
+
+static bool
+ccmp_uniform_chain_p (tree t, tree_code code, basic_block bb,
+ ccmp_uniform_cache &cache)
+{
+ if (ccmp_tree_comparison_p (t, bb))
+ return true;
+
+ gimple *g = get_gimple_for_ssa_name (t);
+ if (!g || !is_gimple_assign (g) || gimple_assign_rhs_code (g) != code)
+ return false;
+
+ if (bool *value = cache.get (t))
+ return *value;
+
+ tree op0 = gimple_assign_rhs1 (g);
+ tree op1 = gimple_assign_rhs2 (g);
+ bool value = (TREE_CODE (op0) == SSA_NAME
+ && TREE_CODE (op1) == SSA_NAME
+ && ccmp_uniform_chain_p (op0, code, bb, cache)
+ && ccmp_uniform_chain_p (op1, code, bb, cache));
+ cache.put (t, value);
+ return value;
+}
+
/* Check whether G is a potential conditional compare candidate; OUTER is true
if
G is the outer most AND/IOR. */
static bool
-ccmp_candidate_p (gimple *g, bool outer = false)
+ccmp_candidate_p (gimple *g, ccmp_uniform_cache &cache,
+ bool outer = false)
{
tree lhs, op0, op1;
gimple *gs0, *gs1;
@@ -121,13 +152,16 @@ ccmp_candidate_p (gimple *g, bool outer = false)
if (ccmp_tree_comparison_p (op0, bb) && ccmp_tree_comparison_p (op1, bb))
return true;
- if (ccmp_tree_comparison_p (op0, bb) && ccmp_candidate_p (gs1))
+ if (ccmp_tree_comparison_p (op0, bb) && ccmp_candidate_p (gs1, cache))
return true;
- if (ccmp_tree_comparison_p (op1, bb) && ccmp_candidate_p (gs0))
+ if (ccmp_tree_comparison_p (op1, bb) && ccmp_candidate_p (gs0, cache))
return true;
- /* We skip ccmp_candidate_p (gs1) && ccmp_candidate_p (gs0) since
- there is no way to set and maintain the CC flag on both sides of
- the logical operator at the same time. */
+ /* A uniform chain can be appended to the other operand one comparison at
+ a time. */
+ if (ccmp_uniform_chain_p (op1, tcode, bb, cache))
+ return ccmp_candidate_p (gs0, cache);
+ if (ccmp_uniform_chain_p (op0, tcode, bb, cache))
+ return ccmp_candidate_p (gs1, cache);
return false;
}
@@ -179,6 +213,26 @@ expand_ccmp_next (tree op, tree_code code, rtx prev,
rhs1, rhs2, get_rtx_code (code, 0));
}
+/* Append the leaves of uniform CODE tree OP to conditional comparison PREV.
+ Return the extended comparison, or NULL_RTX if expansion fails. */
+
+static rtx
+expand_ccmp_chain (tree op, tree_code code, rtx prev,
+ rtx_insn **prep_seq, rtx_insn **gen_seq)
+{
+ gimple *g = get_gimple_for_ssa_name (op);
+ if (!g || gimple_assign_rhs_code (g) != code)
+ return expand_ccmp_next (op, code, prev, prep_seq, gen_seq);
+
+ prev = expand_ccmp_chain (gimple_assign_rhs1 (g), code, prev,
+ prep_seq, gen_seq);
+ if (!prev)
+ return NULL_RTX;
+
+ return expand_ccmp_chain (gimple_assign_rhs2 (g), code, prev,
+ prep_seq, gen_seq);
+}
+
/* Expand conditional compare gimple G. A typical CCMP sequence is like:
CC0 = CMP (a, b);
@@ -191,7 +245,8 @@ expand_ccmp_next (tree op, tree_code code, rtx prev,
PREP_SEQ returns all insns to prepare operand.
GEN_SEQ returns all compare insns. */
static rtx
-expand_ccmp_expr_1 (gimple *g, rtx_insn **prep_seq, rtx_insn **gen_seq)
+expand_ccmp_expr_1 (gimple *g, rtx_insn **prep_seq, rtx_insn **gen_seq,
+ ccmp_uniform_cache &cache)
{
tree_code code = gimple_assign_rhs_code (g);
basic_block bb = gimple_bb (g);
@@ -270,22 +325,34 @@ expand_ccmp_expr_1 (gimple *g, rtx_insn **prep_seq,
rtx_insn **gen_seq)
}
else
{
- tmp = expand_ccmp_expr_1 (gs1, prep_seq, gen_seq);
+ tmp = expand_ccmp_expr_1 (gs1, prep_seq, gen_seq, cache);
if (!tmp)
return NULL_RTX;
return expand_ccmp_next (op0, code, tmp, prep_seq, gen_seq);
}
}
- else
+ else if (ccmp_tree_comparison_p (op1, bb))
{
gcc_assert (gimple_assign_rhs_code (gs0) == BIT_AND_EXPR
|| gimple_assign_rhs_code (gs0) == BIT_IOR_EXPR);
- gcc_assert (ccmp_tree_comparison_p (op1, bb));
- tmp = expand_ccmp_expr_1 (gs0, prep_seq, gen_seq);
+ tmp = expand_ccmp_expr_1 (gs0, prep_seq, gen_seq, cache);
if (!tmp)
return NULL_RTX;
return expand_ccmp_next (op1, code, tmp, prep_seq, gen_seq);
}
+ else
+ {
+ /* Start with one operand and append the uniform operand. */
+ tree first = op0, rest = op1;
+ if (!ccmp_uniform_chain_p (op1, code, bb, cache))
+ std::swap (first, rest);
+
+ tmp = expand_ccmp_expr_1 (get_gimple_for_ssa_name (first),
+ prep_seq, gen_seq, cache);
+ if (!tmp)
+ return NULL_RTX;
+ return expand_ccmp_chain (rest, code, tmp, prep_seq, gen_seq);
+ }
}
/* Main entry to expand conditional compare statement G.
@@ -296,14 +363,15 @@ expand_ccmp_expr (gimple *g, machine_mode mode)
{
rtx_insn *last;
rtx tmp;
+ ccmp_uniform_cache cache;
- if (!ccmp_candidate_p (g, true))
+ if (!ccmp_candidate_p (g, cache, true))
return NULL_RTX;
last = get_last_insn ();
rtx_insn *prep_seq = NULL, *gen_seq = NULL;
- tmp = expand_ccmp_expr_1 (g, &prep_seq, &gen_seq);
+ tmp = expand_ccmp_expr_1 (g, &prep_seq, &gen_seq, cache);
if (tmp)
{
diff --git a/gcc/testsuite/gcc.c-torture/execute/ccmp-tree-1.c
b/gcc/testsuite/gcc.c-torture/execute/ccmp-tree-1.c
new file mode 100644
index 00000000000..9d7f5241f19
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/execute/ccmp-tree-1.c
@@ -0,0 +1,90 @@
+/* Execution test for conditional-compare chains built from AND/IOR trees of
+ comparisons. Each function is compared against a reference computed with
+ volatile operands so that it cannot be folded into the same code. */
+
+extern void abort (void);
+
+#define OPS int a, int b, int c, int d, int e, int f, int g, int h
+#define ARGS a, b, c, d, e, f, g, h
+
+/* Balanced AND tree. */
+static int __attribute__((noipa)) t1 (OPS)
+{ return ((a < b) & (c < d)) & ((e < f) & (g < h)); }
+
+/* Balanced OR tree. */
+static int __attribute__((noipa)) t2 (OPS)
+{ return ((a < b) | (c < d)) | ((e < f) | (g < h)); }
+
+/* AND of an OR chain and an AND chain. */
+static int __attribute__((noipa)) t3 (OPS)
+{ return ((a < b) | (c < d)) & ((e < f) & (g < h)); }
+
+/* OR of an AND chain and an OR chain. */
+static int __attribute__((noipa)) t4 (OPS)
+{ return ((a < b) & (c < d)) | ((e < f) | (g < h)); }
+
+/* Deeper: eight leaves. */
+static int __attribute__((noipa)) t5 (OPS)
+{
+ return (((a < b) & (c < d)) & ((e < f) & (g < h)))
+ & (((a < c) & (b < d)) & ((e < g) & (f < h)));
+}
+
+/* Mixed signed and unsigned leaves. */
+static int __attribute__((noipa)) t6 (OPS)
+{
+ return (((unsigned) a < (unsigned) b) & (c < d))
+ & ((e < f) & ((unsigned) g < (unsigned) h));
+}
+
+/* Immediates, some inside and some outside the CCMP 5-bit range. */
+static int __attribute__((noipa)) t7 (OPS)
+{ return ((a == 3) & (b == 31)) & ((c == 32) & (d == -4)); }
+
+/* Reference versions. V forces separate evaluation of every leaf. */
+#define V(x) ({ volatile int v_ = (x); v_; })
+
+static int r1 (OPS)
+{ return (V(a < b) & V(c < d)) & (V(e < f) & V(g < h)); }
+static int r2 (OPS)
+{ return (V(a < b) | V(c < d)) | (V(e < f) | V(g < h)); }
+static int r3 (OPS)
+{ return (V(a < b) | V(c < d)) & (V(e < f) & V(g < h)); }
+static int r4 (OPS)
+{ return (V(a < b) & V(c < d)) | (V(e < f) | V(g < h)); }
+static int r5 (OPS)
+{
+ return ((V(a < b) & V(c < d)) & (V(e < f) & V(g < h)))
+ & ((V(a < c) & V(b < d)) & (V(e < g) & V(f < h)));
+}
+static int r6 (OPS)
+{
+ return (V((unsigned) a < (unsigned) b) & V(c < d))
+ & (V(e < f) & V((unsigned) g < (unsigned) h));
+}
+static int r7 (OPS)
+{ return (V(a == 3) & V(b == 31)) & (V(c == 32) & V(d == -4)); }
+static const int vals[] = { -4, 0, 3, 31, 32, 33 };
+#define NV ((int) (sizeof (vals) / sizeof (vals[0])))
+
+int
+main (void)
+{
+ for (int i0 = 0; i0 < NV; i0++)
+ for (int i1 = 0; i1 < NV; i1++)
+ for (int i2 = 0; i2 < NV; i2++)
+ for (int i3 = 0; i3 < NV; i3++)
+ {
+ int a = vals[i0], b = vals[i1], c = vals[i2], d = vals[i3];
+ int e = vals[i3], f = vals[i0], g = vals[i2], h = vals[i1];
+
+ if (t1 (ARGS) != r1 (ARGS)) abort ();
+ if (t2 (ARGS) != r2 (ARGS)) abort ();
+ if (t3 (ARGS) != r3 (ARGS)) abort ();
+ if (t4 (ARGS) != r4 (ARGS)) abort ();
+ if (t5 (ARGS) != r5 (ARGS)) abort ();
+ if (t6 (ARGS) != r6 (ARGS)) abort ();
+ if (t7 (ARGS) != r7 (ARGS)) abort ();
+ }
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/ccmp_6.c
b/gcc/testsuite/gcc.target/aarch64/ccmp_6.c
new file mode 100644
index 00000000000..aa2710775e5
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/ccmp_6.c
@@ -0,0 +1,89 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/* Reassociation rebalances a chain of four comparisons into a tree, so the
+ conditional compare expander has to linearise it again. */
+
+/*
+** and_tree:
+** cmp w0, w1
+** ccmp w2, w3, 0, lt
+** ccmp w4, w5, 0, lt
+** ccmp w6, w7, 0, lt
+** cset w0, lt
+** ret
+*/
+int
+and_tree (int a, int b, int c, int d, int e, int f, int g, int h)
+{
+ return ((a < b) & (c < d)) & ((e < f) & (g < h));
+}
+
+/*
+** ior_tree:
+** cmp w0, w1
+** ccmp w2, w3, 1, ge
+** ccmp w4, w5, 1, ge
+** ccmp w6, w7, 1, ge
+** cset w0, lt
+** ret
+*/
+int
+ior_tree (int a, int b, int c, int d, int e, int f, int g, int h)
+{
+ return ((a < b) | (c < d)) | ((e < f) | (g < h));
+}
+
+/* An AND chain that has to come first and a uniform OR chain. */
+
+/*
+** ior_of_and:
+** cmp w4, w5
+** ccmp w6, w7, 0, lt
+** ccmp w2, w3, 1, ge
+** ccmp w0, w1, 1, ge
+** cset w0, lt
+** ret
+*/
+int
+ior_of_and (int a, int b, int c, int d, int e, int f, int g, int h)
+{
+ return ((a < b) | (c < d)) | ((e < f) & (g < h));
+}
+
+/* An OR chain that has to come first and a uniform AND chain. */
+
+/*
+** and_of_ior_chain:
+** cmp w4, w5
+** ccmp w6, w7, 1, ge
+** ccmp w2, w3, 0, lt
+** ccmp w0, w1, 0, lt
+** cset w0, lt
+** ret
+*/
+int
+and_of_ior_chain (int a, int b, int c, int d, int e, int f, int g, int h)
+{
+ return ((a < b) & (c < d)) & ((e < f) | (g < h));
+}
+
+/* The chain is linear, so neither of these can be expressed as one sequence
+ of conditional compares. Both operands would have to keep a value in the
+ flags at the same time. */
+
+int
+and_of_ior (int a, int b, int c, int d, int e, int f, int g, int h)
+{
+ return ((a < b) | (c < d)) & ((e < f) | (g < h));
+}
+
+int
+ior_of_and_2 (int a, int b, int c, int d, int e, int f, int g, int h)
+{
+ return ((a < b) & (c < d)) | ((e < f) & (g < h));
+}
+
+/* { dg-final { scan-assembler-times {\tand\tw[0-9]+, w[0-9]+, w[0-9]+} 1 } }
*/
+/* { dg-final { scan-assembler-times {\torr\tw[0-9]+, w[0-9]+, w[0-9]+} 1 } }
*/
--
2.50.1 (Apple Git-155)