https://gcc.gnu.org/g:cddcc2cb25fffc4a119614c522bd6a21b436c97e
commit cddcc2cb25fffc4a119614c522bd6a21b436c97e Author: Alexandre Oliva <ol...@gnu.org> Date: Thu Sep 19 06:43:22 2024 -0300 adjust probs after modified ifcombine Diff: --- gcc/tree-ssa-ifcombine.cc | 355 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 289 insertions(+), 66 deletions(-) diff --git a/gcc/tree-ssa-ifcombine.cc b/gcc/tree-ssa-ifcombine.cc index 79ccc70b2678..d08bd942643d 100644 --- a/gcc/tree-ssa-ifcombine.cc +++ b/gcc/tree-ssa-ifcombine.cc @@ -42,6 +42,7 @@ along with GCC; see the file COPYING3. If not see #include "tree-ssa.h" #include "attribs.h" #include "asan.h" +#include "bitmap.h" #ifndef LOGICAL_OP_NON_SHORT_CIRCUIT #define LOGICAL_OP_NON_SHORT_CIRCUIT \ @@ -49,6 +50,28 @@ along with GCC; see the file COPYING3. If not see false) >= 2) #endif +/* Return TRUE iff COND is NULL, or the condition in it is constant. */ + +static bool +constant_condition_p (gcond *cond) +{ + if (!cond) + return true; + + return (CONSTANT_CLASS_P (gimple_cond_lhs (cond)) + && CONSTANT_CLASS_P (gimple_cond_rhs (cond))); +} + +/* Return FALSE iff the condition in the COND stmt that ends COND_BB is not + constant. */ + +static bool +constant_condition_p (basic_block cond_bb) +{ + gcond *cond = safe_dyn_cast <gcond *> (*gsi_last_bb (cond_bb)); + return constant_condition_p (cond); +} + /* This pass combines COND_EXPRs to simplify control flow. It currently recognizes bit tests and comparisons in chains that represent logical and or logical or of two COND_EXPRs. @@ -107,12 +130,7 @@ recognize_if_then_else (basic_block cond_bb, if (!*else_bb) *else_bb = e->dest; - gcond *cond = safe_dyn_cast <gcond *> (*gsi_last_bb (cond_bb)); - if (!cond) - return false; - - if (CONSTANT_CLASS_P (gimple_cond_lhs (cond)) - && CONSTANT_CLASS_P (gimple_cond_rhs (cond))) + if (constant_condition_p (cond_bb)) return false; return true; @@ -364,14 +382,27 @@ recognize_bits_test (gcond *cond, tree *name, tree *bits, bool inv) } -/* Update profile after code in outer_cond_bb was adjusted so - outer_cond_bb has no condition. */ +/* Update profile after code in either outer_cond_bb or inner_cond_bb was + adjusted so that it has no condition. */ static void update_profile_after_ifcombine (basic_block inner_cond_bb, basic_block outer_cond_bb) { - edge outer_to_inner = find_edge (outer_cond_bb, inner_cond_bb); + /* In the following we assume that inner_cond_bb has single predecessor. */ + gcc_assert (single_pred_p (inner_cond_bb)); + + basic_block outer_to_inner_bb = inner_cond_bb; + profile_probability prob = profile_probability::always (); + for (basic_block parent = single_pred (outer_to_inner_bb); + parent != outer_cond_bb; + parent = single_pred (outer_to_inner_bb)) + { + prob *= find_edge (parent, outer_to_inner_bb)->probability; + outer_to_inner_bb = parent; + } + + edge outer_to_inner = find_edge (outer_cond_bb, outer_to_inner_bb); edge outer2 = (EDGE_SUCC (outer_cond_bb, 0) == outer_to_inner ? EDGE_SUCC (outer_cond_bb, 1) : EDGE_SUCC (outer_cond_bb, 0)); @@ -382,29 +413,62 @@ update_profile_after_ifcombine (basic_block inner_cond_bb, std::swap (inner_taken, inner_not_taken); gcc_assert (inner_taken->dest == outer2->dest); - /* In the following we assume that inner_cond_bb has single predecessor. */ - gcc_assert (single_pred_p (inner_cond_bb)); + if (constant_condition_p (outer_cond_bb)) + { + gcc_checking_assert (outer_to_inner_bb == inner_cond_bb); - /* Path outer_cond_bb->(outer2) needs to be merged into path - outer_cond_bb->(outer_to_inner)->inner_cond_bb->(inner_taken) - and probability of inner_not_taken updated. */ + /* Path outer_cond_bb->(outer2) needs to be merged into path + outer_cond_bb->(outer_to_inner)->inner_cond_bb->(inner_taken) + and probability of inner_not_taken updated. */ - inner_cond_bb->count = outer_cond_bb->count; + inner_cond_bb->count = outer_cond_bb->count; - /* Handle special case where inner_taken probability is always. In this case - we know that the overall outcome will be always as well, but combining - probabilities will be conservative because it does not know that - outer2->probability is inverse of outer_to_inner->probability. */ - if (inner_taken->probability == profile_probability::always ()) - ; - else - inner_taken->probability = outer2->probability + outer_to_inner->probability - * inner_taken->probability; - inner_not_taken->probability = profile_probability::always () - - inner_taken->probability; + /* Handle special case where inner_taken probability is always. In this + case we know that the overall outcome will be always as well, but + combining probabilities will be conservative because it does not know + that outer2->probability is inverse of + outer_to_inner->probability. */ + if (inner_taken->probability == profile_probability::always ()) + ; + else + inner_taken->probability = outer2->probability + + outer_to_inner->probability * inner_taken->probability; + inner_not_taken->probability = profile_probability::always () + - inner_taken->probability; - outer_to_inner->probability = profile_probability::always (); - outer2->probability = profile_probability::never (); + outer_to_inner->probability = profile_probability::always (); + outer2->probability = profile_probability::never (); + } + else if (constant_condition_p (inner_cond_bb)) + { + /* Path inner_cond_bb->(inner_taken) needs to be merged into path + outer_cond_bb->(outer2). We've accumulated the probabilities from + outer_cond_bb->(outer)->...->inner_cond_bb in prob, so we have to + adjust that by inner_taken, and make inner unconditional. */ + + prob *= inner_taken->probability; + outer2->probability += prob; + outer_to_inner->probability = profile_probability::always () + - outer2->probability; + + inner_taken->probability = profile_probability::never (); + inner_not_taken->probability = profile_probability::always (); + } + else + { + /* We've moved part of the inner cond to outer, but we don't know the + probabilities for each part, so estimate the effects by moving half of + the odds of inner_taken to outer. */ + + inner_taken->probability *= profile_probability::even (); + inner_not_taken->probability = profile_probability::always () + - inner_taken->probability; + + prob *= inner_taken->probability; + outer2->probability += prob; + outer_to_inner->probability = profile_probability::always () + - outer2->probability; + } } /* FIXME: move to a header file. */ @@ -415,6 +479,21 @@ fold_truth_andor_maybe_separate (location_t loc, enum tree_code rcode, tree rl_arg, tree rr_arg, tree *separatep); +/* Mark in DATA (a bitmap) any SSA_NAMEs used in *t. */ + +static tree +ifcombine_mark_ssa_name (tree *t, int *, void *data) +{ + bitmap used = (bitmap) data; + + if (*t && TREE_CODE (*t) == SSA_NAME) + /* ??? Check where *T is defined, whether it is already in outer or in a + dominator thereof? We wouldn't need to mark it if so. */ + bitmap_set_bit (used, SSA_NAME_VERSION (*t)); + + return NULL; +} + /* Replace the conditions in INNER_COND and OUTER_COND with COND and COND2. COND and COND2 are computed for insertion at INNER_COND, with OUTER_COND replaced with a constant, but if there are intervening blocks, it's best to @@ -426,41 +505,181 @@ ifcombine_replace_cond (gcond *inner_cond, bool inner_inv, tree cond, bool must_canon, tree cond2) { - tree t = cond; - bool result_inv = inner_inv; + tree ret = cond; - /* ??? Support intervening blocks. */ - if (single_pred (gimple_bb (inner_cond)) != gimple_bb (outer_cond)) - return NULL_TREE; + /* Split cond into cond2 if they're contiguous. */ + if (!cond + && TREE_CODE (cond) == TRUTH_ANDIF_EXPR + && single_pred (gimple_bb (inner_cond)) == gimple_bb (outer_cond)) + { + cond2 = TREE_OPERAND (cond, 1); + cond = TREE_OPERAND (cond, 0); + } + if (!cond + && TREE_CODE (cond) == TRUTH_ORIF_EXPR + && single_pred (gimple_bb (inner_cond)) == gimple_bb (outer_cond)) + { + gcc_unreachable (); /* ??? Do we ever hit this? */ + cond2 = TREE_OPERAND (cond, 1); + cond = TREE_OPERAND (cond, 0); + inner_inv = !inner_inv; + outer_inv = !outer_inv; + } - /* ??? Use both conditions. */ - if (cond2) - t = fold_build2 (TRUTH_AND_EXPR, TREE_TYPE (t), cond, cond2); + bool outer_p = true /* for experimentation only. */ + || cond2 || (single_pred (gimple_bb (inner_cond)) + != gimple_bb (outer_cond)); + bool result_inv = outer_p ? outer_inv : inner_inv; - /* ??? Insert at outer_cond. */ if (result_inv) - t = fold_build1 (TRUTH_NOT_EXPR, TREE_TYPE (t), t); - tree ret = t; + cond = fold_build1 (TRUTH_NOT_EXPR, TREE_TYPE (cond), cond); - if (tree tcanon = canonicalize_cond_expr_cond (t)) - ret = t = tcanon; + if (tree tcanon = canonicalize_cond_expr_cond (cond)) + cond = tcanon; else if (must_canon) return NULL_TREE; - if (!is_gimple_condexpr_for_cond (t)) + + if (outer_p) { - gimple_stmt_iterator gsi = gsi_for_stmt (inner_cond); - t = force_gimple_operand_gsi_1 (&gsi, t, is_gimple_condexpr_for_cond, - NULL, true, GSI_SAME_STMT); - } - gimple_cond_set_condition_from_tree (inner_cond, t); - update_stmt (inner_cond); + { + auto_bitmap used; - /* Leave CFG optimization to cfg_cleanup. */ - gimple_cond_set_condition_from_tree (outer_cond, - outer_inv - ? boolean_false_node - : boolean_true_node); - update_stmt (outer_cond); + /* Mark SSA DEFs that are referenced by cond and may thus need to be + moved to outer. */ + walk_tree (&cond, ifcombine_mark_ssa_name, bitmap (used), NULL); + + if (!bitmap_empty_p (used)) + { + /* Iterate up from inner_cond, moving DEFs identified as used by + cond, and marking USEs in the DEFs for moving as well. */ + gimple_stmt_iterator gsins = gsi_for_stmt (outer_cond); + for (basic_block bb = gimple_bb (inner_cond); + bb != gimple_bb (outer_cond); + bb = single_pred (bb)) + { + for (gimple_stmt_iterator gsitr = gsi_last_bb (bb); + !gsi_end_p (gsitr); gsi_prev (&gsitr)) + { + gimple *stmt = gsi_stmt (gsitr); + bool move = false; + tree t; + ssa_op_iter it; + + FOR_EACH_SSA_TREE_OPERAND (t, stmt, it, SSA_OP_DEF) + if (bitmap_bit_p (used, SSA_NAME_VERSION (t))) + { + move = true; + break; + } + + if (!move) + continue; + + /* Mark uses in STMT before moving it. */ + FOR_EACH_SSA_TREE_OPERAND (t, stmt, it, SSA_OP_USE) + bitmap_set_bit (used, SSA_NAME_VERSION (t)); + + gsi_move_before (&gsitr, &gsins, GSI_NEW_STMT); + } + + /* Surprisingly, there may be PHI nodes in single-predecessor + bocks, as in pr50682.C. Fortunately, since they can't + involve back edges, there won't be references to parallel + nodes that we'd have to pay special attention to to keep + them parallel. We can't move the PHI nodes, but we can turn + them into assignments. */ + for (gphi_iterator gsi = gsi_start_phis (bb); + !gsi_end_p (gsi);) + { + gphi *phi = gsi.phi (); + + gcc_assert (gimple_phi_num_args (phi) == 1); + tree def = gimple_phi_result (phi); + + if (!bitmap_bit_p (used, SSA_NAME_VERSION (def))) + { + gsi_next (&gsi); + continue; + } + + /* Mark uses in STMT before moving it. */ + use_operand_p use_p; + ssa_op_iter it; + FOR_EACH_PHI_ARG (use_p, phi, it, SSA_OP_USE) + bitmap_set_bit (used, + SSA_NAME_VERSION (USE_FROM_PTR (use_p))); + + tree use = gimple_phi_arg_def (phi, 0); + location_t loc = gimple_phi_arg_location (phi, 0); + + remove_phi_node (&gsi, false); + + gassign *a = gimple_build_assign (def, use); + gimple_set_location (a, loc); + gsi_insert_before (&gsins, a, GSI_NEW_STMT); + } + } + } + } + + if (!is_gimple_condexpr_for_cond (cond)) + { + gimple_stmt_iterator gsi = gsi_for_stmt (outer_cond); + cond = force_gimple_operand_gsi_1 (&gsi, cond, + is_gimple_condexpr_for_cond, + NULL, true, GSI_SAME_STMT); + } + gimple_cond_set_condition_from_tree (outer_cond, cond); + update_stmt (outer_cond); + + /* Leave CFG optimization to cfg_cleanup. */ + gimple_cond_set_condition_from_tree (outer_cond, cond); + update_stmt (outer_cond); + + if (cond2) + { + ret = fold_build2 (TRUTH_AND_EXPR, TREE_TYPE (ret), ret, cond2); + + if (inner_inv) + cond2 = fold_build1 (TRUTH_NOT_EXPR, TREE_TYPE (cond2), cond2); + + if (tree tcanon = canonicalize_cond_expr_cond (cond2)) + cond2 = tcanon; + if (!is_gimple_condexpr_for_cond (cond2)) + { + gimple_stmt_iterator gsi = gsi_for_stmt (inner_cond); + cond2 = force_gimple_operand_gsi_1 (&gsi, cond2, + is_gimple_condexpr_for_cond, + NULL, true, GSI_SAME_STMT); + } + gimple_cond_set_condition_from_tree (inner_cond, cond2); + } + else + gimple_cond_set_condition_from_tree (inner_cond, + inner_inv + ? boolean_false_node + : boolean_true_node); + update_stmt (inner_cond); + } + else + { + if (!is_gimple_condexpr_for_cond (cond)) + { + gimple_stmt_iterator gsi = gsi_for_stmt (inner_cond); + cond = force_gimple_operand_gsi_1 (&gsi, cond, + is_gimple_condexpr_for_cond, + NULL, true, GSI_SAME_STMT); + } + gimple_cond_set_condition_from_tree (inner_cond, cond); + update_stmt (inner_cond); + + /* Leave CFG optimization to cfg_cleanup. */ + gimple_cond_set_condition_from_tree (outer_cond, + outer_inv + ? boolean_false_node + : boolean_true_node); + update_stmt (outer_cond); + } update_profile_after_ifcombine (gimple_bb (inner_cond), gimple_bb (outer_cond)); @@ -611,7 +830,7 @@ ifcombine_ifandif (basic_block inner_cond_bb, bool inner_inv, else if (TREE_CODE_CLASS (gimple_cond_code (inner_cond)) == tcc_comparison && TREE_CODE_CLASS (gimple_cond_code (outer_cond)) == tcc_comparison) { - tree t, ts = NULL_TREE, ts2 = NULL_TREE; + tree t, ts = NULL_TREE; enum tree_code inner_cond_code = gimple_cond_code (inner_cond); enum tree_code outer_cond_code = gimple_cond_code (outer_cond); @@ -635,16 +854,16 @@ ifcombine_ifandif (basic_block inner_cond_bb, bool inner_inv, gimple_cond_lhs (outer_cond), gimple_cond_rhs (outer_cond), gimple_bb (outer_cond))) - && !(t = ts = (fold_truth_andor_maybe_separate - (UNKNOWN_LOCATION, TRUTH_ANDIF_EXPR, - boolean_type_node, - outer_cond_code, - gimple_cond_lhs (outer_cond), - gimple_cond_rhs (outer_cond), - inner_cond_code, - gimple_cond_lhs (inner_cond), - gimple_cond_rhs (inner_cond), - &ts2)))) + && !(t = (fold_truth_andor_maybe_separate + (UNKNOWN_LOCATION, TRUTH_ANDIF_EXPR, + boolean_type_node, + outer_cond_code, + gimple_cond_lhs (outer_cond), + gimple_cond_rhs (outer_cond), + inner_cond_code, + gimple_cond_lhs (inner_cond), + gimple_cond_rhs (inner_cond), + &ts)))) { { tree t1, t2; @@ -674,7 +893,7 @@ ifcombine_ifandif (basic_block inner_cond_bb, bool inner_inv, if (!(t = ifcombine_replace_cond (inner_cond, inner_inv, outer_cond, outer_inv, - t, false, ts2))) + t, false, ts))) return false; if (dump_file) @@ -700,6 +919,8 @@ tree_ssa_ifcombine_bb_1 (basic_block inner_cond_bb, basic_block outer_cond_bb, basic_block then_bb, basic_block else_bb, basic_block phi_pred_bb) { + /* ??? Check the blocks for non-continuous configurations. */ + /* The && form is characterized by a common else_bb with the two edges leading to it mergable. The latter is guaranteed by matching PHI arguments in the else_bb and @@ -800,6 +1021,8 @@ tree_ssa_ifcombine_bb (basic_block inner_cond_bb) { basic_block outer_cond_bb = bb = single_pred (bb); + /* ??? Pass bb down? */ + if (tree_ssa_ifcombine_bb_1 (inner_cond_bb, outer_cond_bb, then_bb, else_bb, inner_cond_bb)) return bb;