aboutsummaryrefslogtreecommitdiff
path: root/gcc/gimple-ssa-split-paths.cc
diff options
context:
space:
mode:
authorThomas Schwinge <thomas@codesourcery.com>2022-02-03 21:12:21 +0100
committerThomas Schwinge <thomas@codesourcery.com>2022-02-03 21:14:10 +0100
commit7eef766dc5a8abda2ca2cf8d535cdf160f40b50c (patch)
treef85ed9010c56dc8f250d7cba5761b4eae58f2a42 /gcc/gimple-ssa-split-paths.cc
parent5199ecb8519c4c5f92160365cefe8e0aa1ca3873 (diff)
parentff7aeceb6b3a476c3bac66a7f39a5ef4240206fc (diff)
downloadgcc-7eef766dc5a8abda2ca2cf8d535cdf160f40b50c.zip
gcc-7eef766dc5a8abda2ca2cf8d535cdf160f40b50c.tar.gz
gcc-7eef766dc5a8abda2ca2cf8d535cdf160f40b50c.tar.bz2
Merge commit 'ff7aeceb6b3a476c3bac66a7f39a5ef4240206fc' [#247, #906]
Diffstat (limited to 'gcc/gimple-ssa-split-paths.cc')
-rw-r--r--gcc/gimple-ssa-split-paths.cc594
1 files changed, 594 insertions, 0 deletions
diff --git a/gcc/gimple-ssa-split-paths.cc b/gcc/gimple-ssa-split-paths.cc
new file mode 100644
index 0000000..6abe1d5
--- /dev/null
+++ b/gcc/gimple-ssa-split-paths.cc
@@ -0,0 +1,594 @@
+/* Support routines for Splitting Paths to loop backedges
+ Copyright (C) 2015-2022 Free Software Foundation, Inc.
+ Contributed by Ajit Kumar Agarwal <ajitkum@xilinx.com>.
+
+ This file is part of GCC.
+
+ GCC is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "tree-cfg.h"
+#include "cfganal.h"
+#include "cfgloop.h"
+#include "gimple-iterator.h"
+#include "tracer.h"
+#include "predict.h"
+#include "gimple-ssa.h"
+#include "tree-phinodes.h"
+#include "ssa-iterators.h"
+#include "fold-const.h"
+
+/* Given LATCH, the latch block in a loop, see if the shape of the
+ path reaching LATCH is suitable for being split by duplication.
+ If so, return the block that will be duplicated into its predecessor
+ paths. Else return NULL. */
+
+static basic_block
+find_block_to_duplicate_for_splitting_paths (basic_block latch)
+{
+ /* We should have simple latches at this point. So the latch should
+ have a single successor. This implies the predecessor of the latch
+ likely has the loop exit. And it's that predecessor we're most
+ interested in. To keep things simple, we're going to require that
+ the latch have a single predecessor too. */
+ if (single_succ_p (latch) && single_pred_p (latch))
+ {
+ basic_block bb = get_immediate_dominator (CDI_DOMINATORS, latch);
+ gcc_assert (single_pred_edge (latch)->src == bb);
+
+ /* If BB has been marked as not to be duplicated, then honor that
+ request. */
+ if (ignore_bb_p (bb))
+ return NULL;
+
+ gimple *last = gsi_stmt (gsi_last_nondebug_bb (bb));
+ /* The immediate dominator of the latch must end in a conditional. */
+ if (!last || gimple_code (last) != GIMPLE_COND)
+ return NULL;
+
+ /* We're hoping that BB is a join point for an IF-THEN-ELSE diamond
+ region. Verify that it is.
+
+ First, verify that BB has two predecessors (each arm of the
+ IF-THEN-ELSE) and two successors (the latch and exit) and that
+ all edges are normal. */
+ if (EDGE_COUNT (bb->preds) == 2
+ && !(EDGE_PRED (bb, 0)->flags & EDGE_COMPLEX)
+ && !(EDGE_PRED (bb, 1)->flags & EDGE_COMPLEX)
+ && EDGE_COUNT (bb->succs) == 2
+ && !(EDGE_SUCC (bb, 0)->flags & EDGE_COMPLEX)
+ && !(EDGE_SUCC (bb, 1)->flags & EDGE_COMPLEX))
+ {
+ /* Now verify that BB's immediate dominator ends in a
+ conditional as well. */
+ basic_block bb_idom = get_immediate_dominator (CDI_DOMINATORS, bb);
+ gimple *last = gsi_stmt (gsi_last_nondebug_bb (bb_idom));
+ if (!last || gimple_code (last) != GIMPLE_COND)
+ return NULL;
+
+ /* And that BB's immediate dominator's successors are the
+ predecessors of BB or BB itself. */
+ if (!(EDGE_PRED (bb, 0)->src == bb_idom
+ || find_edge (bb_idom, EDGE_PRED (bb, 0)->src))
+ || !(EDGE_PRED (bb, 1)->src == bb_idom
+ || find_edge (bb_idom, EDGE_PRED (bb, 1)->src)))
+ return NULL;
+
+ /* And that the predecessors of BB each have a single successor
+ or are BB's immediate domiator itself. */
+ if (!(EDGE_PRED (bb, 0)->src == bb_idom
+ || single_succ_p (EDGE_PRED (bb, 0)->src))
+ || !(EDGE_PRED (bb, 1)->src == bb_idom
+ || single_succ_p (EDGE_PRED (bb, 1)->src)))
+ return NULL;
+
+ /* So at this point we have a simple diamond for an IF-THEN-ELSE
+ construct starting at BB_IDOM, with a join point at BB. BB
+ pass control outside the loop or to the loop latch.
+
+ We're going to want to create two duplicates of BB, one for
+ each successor of BB_IDOM. */
+ return bb;
+ }
+ }
+ return NULL;
+}
+
+/* Return the number of non-debug statements in a block. */
+static unsigned int
+count_stmts_in_block (basic_block bb)
+{
+ gimple_stmt_iterator gsi;
+ unsigned int num_stmts = 0;
+
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (!is_gimple_debug (stmt))
+ num_stmts++;
+ }
+ return num_stmts;
+}
+
+/* Return TRUE if CODE represents a tree code that is not likely to
+ be easily if-convertable because it likely expands into multiple
+ insns, FALSE otherwise. */
+static bool
+poor_ifcvt_candidate_code (enum tree_code code)
+{
+ return (code == MIN_EXPR
+ || code == MAX_EXPR
+ || code == ABS_EXPR
+ || code == COND_EXPR
+ || code == CALL_EXPR);
+}
+
+/* Return TRUE if BB is a reasonable block to duplicate by examining
+ its size, false otherwise. BB will always be a loop latch block.
+
+ Things to consider:
+
+ We do not want to spoil if-conversion if at all possible.
+
+ Most of the benefit seems to be from eliminating the unconditional
+ jump rather than CSE/DCE opportunities. So favor duplicating
+ small latches. A latch with just a conditional branch is ideal.
+
+ CSE/DCE opportunties crop up when statements from the predecessors
+ feed statements in the latch and allow statements in the latch to
+ simplify. */
+
+static bool
+is_feasible_trace (basic_block bb)
+{
+ basic_block pred1 = EDGE_PRED (bb, 0)->src;
+ basic_block pred2 = EDGE_PRED (bb, 1)->src;
+ int num_stmts_in_join = count_stmts_in_block (bb);
+ int num_stmts_in_pred1
+ = EDGE_COUNT (pred1->succs) == 1 ? count_stmts_in_block (pred1) : 0;
+ int num_stmts_in_pred2
+ = EDGE_COUNT (pred2->succs) == 1 ? count_stmts_in_block (pred2) : 0;
+
+ /* This is meant to catch cases that are likely opportunities for
+ if-conversion. Essentially we look for the case where
+ BB's predecessors are both single statement blocks where
+ the output of that statement feed the same PHI in BB. */
+ if (num_stmts_in_pred1 == 1 && num_stmts_in_pred2 == 1)
+ {
+ gimple *stmt1 = last_and_only_stmt (pred1);
+ gimple *stmt2 = last_and_only_stmt (pred2);
+
+ if (stmt1 && stmt2
+ && gimple_code (stmt1) == GIMPLE_ASSIGN
+ && gimple_code (stmt2) == GIMPLE_ASSIGN)
+ {
+ enum tree_code code1 = gimple_assign_rhs_code (stmt1);
+ enum tree_code code2 = gimple_assign_rhs_code (stmt2);
+
+ if (!poor_ifcvt_candidate_code (code1)
+ && !poor_ifcvt_candidate_code (code2))
+ {
+ tree lhs1 = gimple_assign_lhs (stmt1);
+ tree lhs2 = gimple_assign_lhs (stmt2);
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *phi = gsi_stmt (gsi);
+ if ((gimple_phi_arg_def (phi, 0) == lhs1
+ && gimple_phi_arg_def (phi, 1) == lhs2)
+ || (gimple_phi_arg_def (phi, 1) == lhs1
+ && gimple_phi_arg_def (phi, 0) == lhs2))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Block %d appears to be a join point for "
+ "if-convertable diamond.\n",
+ bb->index);
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ /* Canonicalize the form. */
+ if (num_stmts_in_pred1 == 0 && num_stmts_in_pred2 == 1)
+ {
+ std::swap (pred1, pred2);
+ std::swap (num_stmts_in_pred1, num_stmts_in_pred2);
+ }
+
+ /* Another variant. This one is half-diamond. */
+ if (num_stmts_in_pred1 == 1 && num_stmts_in_pred2 == 0
+ && dominated_by_p (CDI_DOMINATORS, pred1, pred2))
+ {
+ gimple *stmt1 = last_and_only_stmt (pred1);
+
+ /* The only statement in PRED1 must be an assignment that is
+ not a good candidate for if-conversion. This may need some
+ generalization. */
+ if (stmt1 && gimple_code (stmt1) == GIMPLE_ASSIGN)
+ {
+ enum tree_code code1 = gimple_assign_rhs_code (stmt1);
+
+ if (!poor_ifcvt_candidate_code (code1))
+ {
+ tree lhs1 = gimple_assign_lhs (stmt1);
+ tree rhs1 = gimple_assign_rhs1 (stmt1);
+
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *phi = gsi_stmt (gsi);
+ if ((gimple_phi_arg_def (phi, 0) == lhs1
+ && gimple_phi_arg_def (phi, 1) == rhs1)
+ || (gimple_phi_arg_def (phi, 1) == lhs1
+ && gimple_phi_arg_def (phi, 0) == rhs1))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Block %d appears to be a join point for "
+ "if-convertable half-diamond.\n",
+ bb->index);
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ /* Canonicalize the form. */
+ if (single_pred_p (pred1) && single_pred (pred1) == pred2
+ && num_stmts_in_pred1 == 0)
+ std::swap (pred1, pred2);
+
+ /* This is meant to catch another kind of cases that are likely opportunities
+ for if-conversion. After canonicalizing, PRED2 must be an empty block and
+ PRED1 must be the only predecessor of PRED2. Moreover, PRED1 is supposed
+ to end with a cond_stmt which has the same args with the PHI in BB. */
+ if (single_pred_p (pred2) && single_pred (pred2) == pred1
+ && num_stmts_in_pred2 == 0)
+ {
+ gimple *cond_stmt = last_stmt (pred1);
+ if (cond_stmt && gimple_code (cond_stmt) == GIMPLE_COND)
+ {
+ tree lhs = gimple_cond_lhs (cond_stmt);
+ tree rhs = gimple_cond_rhs (cond_stmt);
+
+ gimple_stmt_iterator gsi;
+ for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *phi = gsi_stmt (gsi);
+ if ((operand_equal_p (gimple_phi_arg_def (phi, 0), lhs)
+ && operand_equal_p (gimple_phi_arg_def (phi, 1), rhs))
+ || (operand_equal_p (gimple_phi_arg_def (phi, 0), rhs)
+ && (operand_equal_p (gimple_phi_arg_def (phi, 1), lhs))))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Block %d appears to be optimized to a join "
+ "point for if-convertable half-diamond.\n",
+ bb->index);
+ return false;
+ }
+ }
+ }
+ }
+
+ /* If the joiner has no PHIs with useful uses there is zero chance
+ of CSE/DCE/jump-threading possibilities exposed by duplicating it. */
+ bool found_useful_phi = false;
+ for (gphi_iterator si = gsi_start_phis (bb); ! gsi_end_p (si);
+ gsi_next (&si))
+ {
+ gphi *phi = si.phi ();
+ use_operand_p use_p;
+ imm_use_iterator iter;
+ FOR_EACH_IMM_USE_FAST (use_p, iter, gimple_phi_result (phi))
+ {
+ gimple *stmt = USE_STMT (use_p);
+ if (is_gimple_debug (stmt))
+ continue;
+ /* If there's a use in the joiner this might be a CSE/DCE
+ opportunity, but not if the use is in a conditional
+ which makes this a likely if-conversion candidate. */
+ if (gimple_bb (stmt) == bb
+ && (!is_gimple_assign (stmt)
+ || (TREE_CODE_CLASS (gimple_assign_rhs_code (stmt))
+ != tcc_comparison)))
+ {
+ found_useful_phi = true;
+ break;
+ }
+ /* If the use is on a loop header PHI and on one path the
+ value is unchanged this might expose a jump threading
+ opportunity. */
+ if (gimple_code (stmt) == GIMPLE_PHI
+ && gimple_bb (stmt) == bb->loop_father->header
+ /* But for memory the PHI alone isn't good enough. */
+ && ! virtual_operand_p (gimple_phi_result (stmt)))
+ {
+ bool found_unchanged_path = false;
+ for (unsigned i = 0; i < gimple_phi_num_args (phi); ++i)
+ if (gimple_phi_arg_def (phi, i) == gimple_phi_result (stmt))
+ {
+ found_unchanged_path = true;
+ break;
+ }
+ /* If we found an unchanged path this can only be a threading
+ opportunity if we have uses of the loop header PHI result
+ in a stmt dominating the merge block. Otherwise the
+ splitting may prevent if-conversion. */
+ if (found_unchanged_path)
+ {
+ use_operand_p use2_p;
+ imm_use_iterator iter2;
+ FOR_EACH_IMM_USE_FAST (use2_p, iter2, gimple_phi_result (stmt))
+ {
+ gimple *use_stmt = USE_STMT (use2_p);
+ if (is_gimple_debug (use_stmt))
+ continue;
+ basic_block use_bb = gimple_bb (use_stmt);
+ if (use_bb != bb
+ && dominated_by_p (CDI_DOMINATORS, bb, use_bb))
+ {
+ if (gcond *cond = dyn_cast <gcond *> (use_stmt))
+ if (gimple_cond_code (cond) == EQ_EXPR
+ || gimple_cond_code (cond) == NE_EXPR)
+ found_useful_phi = true;
+ break;
+ }
+ }
+ }
+ if (found_useful_phi)
+ break;
+ }
+ }
+ if (found_useful_phi)
+ break;
+ }
+ /* There is one exception namely a controlling condition we can propagate
+ an equivalence from to the joiner. */
+ bool found_cprop_opportunity = false;
+ basic_block dom = get_immediate_dominator (CDI_DOMINATORS, bb);
+ gcond *cond = as_a <gcond *> (last_stmt (dom));
+ if (gimple_cond_code (cond) == EQ_EXPR
+ || gimple_cond_code (cond) == NE_EXPR)
+ for (unsigned i = 0; i < 2; ++i)
+ {
+ tree op = gimple_op (cond, i);
+ if (TREE_CODE (op) == SSA_NAME)
+ {
+ use_operand_p use_p;
+ imm_use_iterator iter;
+ FOR_EACH_IMM_USE_FAST (use_p, iter, op)
+ {
+ if (is_gimple_debug (USE_STMT (use_p)))
+ continue;
+ if (gimple_bb (USE_STMT (use_p)) == bb)
+ {
+ found_cprop_opportunity = true;
+ break;
+ }
+ }
+ }
+ if (found_cprop_opportunity)
+ break;
+ }
+
+ if (! found_useful_phi && ! found_cprop_opportunity)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Block %d is a join that does not expose CSE/DCE/jump-thread "
+ "opportunities when duplicated.\n",
+ bb->index);
+ return false;
+ }
+
+ /* We may want something here which looks at dataflow and tries
+ to guess if duplication of BB is likely to result in simplification
+ of instructions in BB in either the original or the duplicate. */
+
+ /* Upper Hard limit on the number statements to copy. */
+ if (num_stmts_in_join
+ >= param_max_jump_thread_duplication_stmts)
+ return false;
+
+ return true;
+}
+
+/* If the immediate dominator of the latch of the loop is
+ block with conditional branch, then the loop latch is
+ duplicated to its predecessors path preserving the SSA
+ semantics.
+
+ CFG before transformation.
+
+ 2
+ |
+ |
+ +---->3
+ | / \
+ | / \
+ | 4 5
+ | \ /
+ | \ /
+ | 6
+ | / \
+ | / \
+ | 8 7
+ | | |
+ ---+ E
+
+
+
+ Block 8 is the latch. We're going to make copies of block 6 (9 & 10)
+ and wire things up so they look like this:
+
+ 2
+ |
+ |
+ +---->3
+ | / \
+ | / \
+ | 4 5
+ | | |
+ | | |
+ | 9 10
+ | |\ /|
+ | | \ / |
+ | | 7 |
+ | | | |
+ | | E |
+ | | |
+ | \ /
+ | \ /
+ +-----8
+
+
+ Blocks 9 and 10 will get merged into blocks 4 & 5 respectively which
+ enables CSE, DCE and other optimizations to occur on a larger block
+ of code. */
+
+static bool
+split_paths ()
+{
+ bool changed = false;
+
+ loop_optimizer_init (LOOPS_NORMAL | LOOPS_HAVE_RECORDED_EXITS);
+ initialize_original_copy_tables ();
+ calculate_dominance_info (CDI_DOMINATORS);
+
+ for (auto loop : loops_list (cfun, LI_FROM_INNERMOST))
+ {
+ /* Only split paths if we are optimizing this loop for speed. */
+ if (!optimize_loop_for_speed_p (loop))
+ continue;
+
+ /* See if there is a block that we can duplicate to split the
+ path to the loop latch. */
+ basic_block bb
+ = find_block_to_duplicate_for_splitting_paths (loop->latch);
+
+ /* BB is the merge point for an IF-THEN-ELSE we want to transform.
+
+ Essentially we want to create a duplicate of bb and redirect the
+ first predecessor of BB to the duplicate (leaving the second
+ predecessor as is. This will split the path leading to the latch
+ re-using BB to avoid useless copying. */
+ if (bb && is_feasible_trace (bb))
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Duplicating join block %d into predecessor paths\n",
+ bb->index);
+ basic_block pred0 = EDGE_PRED (bb, 0)->src;
+ if (EDGE_COUNT (pred0->succs) != 1)
+ pred0 = EDGE_PRED (bb, 1)->src;
+ transform_duplicate (pred0, bb);
+ changed = true;
+
+ /* If BB has an outgoing edge marked as IRREDUCIBLE, then
+ duplicating BB may result in an irreducible region turning
+ into a natural loop.
+
+ Long term we might want to hook this into the block
+ duplication code, but as we've seen with similar changes
+ for edge removal, that can be somewhat risky. */
+ if (EDGE_SUCC (bb, 0)->flags & EDGE_IRREDUCIBLE_LOOP
+ || EDGE_SUCC (bb, 1)->flags & EDGE_IRREDUCIBLE_LOOP)
+ {
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ fprintf (dump_file,
+ "Join block %d has EDGE_IRREDUCIBLE_LOOP set. "
+ "Scheduling loop fixups.\n",
+ bb->index);
+ loops_state_set (LOOPS_NEED_FIXUP);
+ }
+ }
+ }
+
+ loop_optimizer_finalize ();
+ free_original_copy_tables ();
+ return changed;
+}
+
+/* Main entry point for splitting paths. Returns TODO_cleanup_cfg if any
+ paths where split, otherwise return zero. */
+
+static unsigned int
+execute_split_paths ()
+{
+ /* If we don't have at least 2 real blocks and backedges in the
+ CFG, then there's no point in trying to perform path splitting. */
+ if (n_basic_blocks_for_fn (cfun) <= NUM_FIXED_BLOCKS + 1
+ || !mark_dfs_back_edges ())
+ return 0;
+
+ bool changed = split_paths();
+ if (changed)
+ free_dominance_info (CDI_DOMINATORS);
+
+ return changed ? TODO_cleanup_cfg : 0;
+}
+
+static bool
+gate_split_paths ()
+{
+ return flag_split_paths;
+}
+
+namespace {
+
+const pass_data pass_data_split_paths =
+{
+ GIMPLE_PASS, /* type */
+ "split-paths", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_SPLIT_PATHS, /* tv_id */
+ PROP_ssa, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ TODO_update_ssa, /* todo_flags_finish */
+};
+
+class pass_split_paths : public gimple_opt_pass
+{
+ public:
+ pass_split_paths (gcc::context *ctxt)
+ : gimple_opt_pass (pass_data_split_paths, ctxt)
+ {}
+ /* opt_pass methods: */
+ opt_pass * clone () { return new pass_split_paths (m_ctxt); }
+ virtual bool gate (function *) { return gate_split_paths (); }
+ virtual unsigned int execute (function *) { return execute_split_paths (); }
+
+}; // class pass_split_paths
+
+} // anon namespace
+
+gimple_opt_pass *
+make_pass_split_paths (gcc::context *ctxt)
+{
+ return new pass_split_paths (ctxt);
+}