Hi Konstantinos,
just a few things/nits I noticed but I didn't go through everything.
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/fold-xor-and-or-2.c
> b/gcc/testsuite/gcc.dg/tree-ssa/fold-xor-and-or-2.c
> new file mode 100644
> index 000000000000..593cbaebcc5b
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/fold-xor-and-or-2.c
> @@ -0,0 +1,59 @@
> +/* This test is not working across all targets (e.g. it fails on PowerPC,
> + because each condition of the AND/OR expression is placed into
> + a different basic block). Therefore, it is gated for x86-64 and AArch64,
> + where we know that it has to pass. */
Nit: GNU coding style (two spaces after full stop, no space at the end of a
line. Seems a bit inconsistent throughout the patch, but easily fixed.
> +/* { dg-do compile { target { aarch64-*-* x86_64-*-* } } } */
> +/* { dg-options "-O3 -fdump-tree-optimized" } */
> +
> +typedef unsigned long int uint64_t;
> +
> +int cmp1_or_inter(int d1, int d2, int d3) {
Here and for the following cases: Missing space after opening parenthesis.
Given the same oversight occurs several times, are the tests auto-generated or
copied from the same single one?
> /* The if should be removed, so the condition should not exist */
> -/* { dg-final { scan-tree-dump-not "d1_\[0-9\]+.D. \\^ d2_\[0-9\]+.D."
> "optimized" } } */
> +/* { dg-final { scan-tree-dump-not "(d1_\[0-9\]+.D. \\^
> d2_\[0-9\]+.D.|d2_\[0-9\]+.D. \\^ d1_\[0-9\]+.D.)" "optimized" } } */
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/fold-xor-and.c
> b/gcc/testsuite/gcc.dg/tree-ssa/fold-xor-and.c
> new file mode 100644
> index 000000000000..dde8952c84e6
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/fold-xor-and.c
> @@ -0,0 +1,55 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O3 -fdump-tree-optimized" } */
> +
> +typedef unsigned long int uint64_t;
> +
> +int cmp1(int d1, int d2) {
Same here.
> +/* Helper function for optimize_cmp_xor_exprs. Visit EXPR operands
> + recursively and try to find comparison or XOR expressions that can be
> + solved using the expressions in CALC_STMTS. Expressions that can be
> folded
> + to 0 are stored in STMTS_TO_FOLD. IS_OR_EXPR is true for OR expressions
> + and false for AND expressions. */
> +
> +static tree
> +solve_expr (tree expr, auto_vec<gimple *> *calc_stmts,
> + hash_set<gimple *> *stmts_to_fold, hash_set<tree> *visited,
> + bool is_or_expr)
Just clarifying: It looks like stmts_to_fold can change even if the function
returns NULL due to its recursion? Maybe add a note regarding the return value
in the function-level comment?
> +/* Helper function for optimize_cmp_xor_exprs. Unfold EXPR and get the
> + terminal nodes in which it is analyzed. */
> +
> +static void
> +find_terminal_nodes (tree expr, hash_set<tree> *terminal_nodes,
> + hash_set<tree> *visited)
> +{
> + if (visited->add (expr))
> + return;
> +
> + if (TREE_CODE (expr) != SSA_NAME)
> + {
> + terminal_nodes->add (expr);
> + return;
> + }
> +
> + gimple *def_stmt = SSA_NAME_DEF_STMT (expr);
> +
> + if (is_gimple_debug (def_stmt))
> + return;
> +
> + if (!def_stmt || !is_gimple_assign (def_stmt))
I guess !def_stmt will be optimized away here because we called is_gimple_debug
on it already?
> + {
> + terminal_nodes->add (expr);
> + return;
> + }
> +
> + /* Visit the expression operands recursively. */
> + unsigned int op_num = gimple_num_ops (def_stmt);
> + for (unsigned i = 1; i < op_num; ++i)
> + {
> + tree op = gimple_op (def_stmt, i);
> + if (!op)
> + continue;
> + find_terminal_nodes (op, terminal_nodes, visited);
> + }
> +}
> +
> +/* Functions to sort two TREE nodes or GIMPLE statements. */
> +
> +template<typename T>
> +static int sort_elements (const void *p1, const void *p2);
> +
> +template<>
> +int sort_elements<tree> (const void *p1, const void *p2)
> +{
> + const tree t1 = *(const tree *)p1;
> + const tree t2 = *(const tree *)p2;
> +
> + gcc_checking_assert (TREE_CODE (t1) == SSA_NAME
> + && TREE_CODE (t2) == SSA_NAME);
> +
> + return SSA_NAME_VERSION (t1) - SSA_NAME_VERSION (t2);
Isn't this a potential overflow when b > a? Does that work with qsort?
> +template<>
> +int sort_elements<gimple *> (const void *p1, const void *p2)
> +{
> + const gimple *s1 = *(const gimple* const*)p1;
> + const gimple *s2 = *(const gimple* const*)p2;
> +
> + return gimple_uid (s1) - gimple_uid (s2);
> +}
Same here.
> + for (const tree &term : terms_in_preds)
> + expr_terms.add (term);
> +
> + /* Copy the hash_set into a vector in order to traverse it in a specific
> + order. */
> + auto_vec<tree> expr_terms_vec;
> + copy_hashset_to_vec_and_sort (expr_terms, &expr_terms_vec);
> +
> + /* Pre-compute the terminal nodes for each entry of expr_terms_vec.
> + Reserving up front keeps the storage stable so references taken
> + below remain valid. */
> + auto_vec<hash_set<tree>> terminal_nodes;
This doesn't look cheap. I didn't go through all code but would a bitmap work
here?
In general the patch seems to prefer hash sets and vectors over bitmaps :)
> + hash_set<tree> already_folded;
> + for (const vec<tree> &expr_set : op_subexprsets)
> + {
> + if (expr_set.length () < 2)
> + continue;
> +
> + auto_vec<gimple *> calc_stmts;
> + hash_set<gimple *> stmts_to_fold;
> + bool any_change;
> +
> + do
> + {
> + any_change = false;
> + for (tree subexpr : expr_set)
> + {
> + if (already_folded.contains (subexpr))
> + continue;
> + gimple *def_stmt = SSA_NAME_DEF_STMT (subexpr);
> + if (!is_gimple_assign (def_stmt))
> + continue;
> +
> + /* If the expression's def is an EQ or NE expression, store it
> + in calc_stmts in order to use it to solve more complex
> + expressions. */
> + tree_code def_stmt_code = gimple_assign_rhs_code (def_stmt);
> + if ((def_stmt_code == EQ_EXPR || def_stmt_code == NE_EXPR)
> + && !calc_stmts.contains (def_stmt)
That looks like a linear search. I guess we don't usually add many statements
to the vector?
> + && !stmts_to_fold.contains (def_stmt))
> + {
> + calc_stmts.safe_push (def_stmt);
> + any_change = true;
> + }
> + else
> + {
> + hash_set<tree> visited;
> + solve_expr (subexpr, &calc_stmts, &stmts_to_fold,
> + &visited, is_or_expr);
> + }
> + }
> + }
> + while (any_change);
> +
> + auto_vec<gimple *> stmts_to_fold_vec;
> + copy_hashset_to_vec_and_sort (stmts_to_fold, &stmts_to_fold_vec);
> +
> + unsigned int i;
> + gimple *stmt;
> + FOR_EACH_VEC_ELT (stmts_to_fold_vec, i, stmt)
> + {
> + tree stmt_lhs = gimple_assign_lhs (stmt);
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + {
> + fprintf (dump_file, "Folding ");
> + print_generic_expr (dump_file, stmt_lhs);
> + fprintf (dump_file, " to 0\n");
> + }
> +
> + operand_entry *oe;
> + unsigned int i;
No big deal but i seems unused in the outer loop and overridden here?
> + tree zero = build_zero_cst (TREE_TYPE (stmt_lhs));
> + FOR_EACH_VEC_ELT (*ops, i, oe)
> +
> + replace_uses_by (stmt_lhs, zero);
Out of curiosity and I might be late to the party, but why does the regular
reassoc way of handling ops not apply here? (modifying an operands list)
--
Regards
Robin