| /* Loop header copying on trees. |
| Copyright (C) 2004-2023 Free Software Foundation, Inc. |
| |
| 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 "cfghooks.h" |
| #include "tree-pass.h" |
| #include "gimple-ssa.h" |
| #include "gimple-iterator.h" |
| #include "tree-cfg.h" |
| #include "tree-into-ssa.h" |
| #include "cfgloop.h" |
| #include "tree-inline.h" |
| #include "tree-ssa-threadedge.h" |
| #include "tree-ssa-sccvn.h" |
| #include "tree-phinodes.h" |
| #include "ssa-iterators.h" |
| #include "value-range.h" |
| #include "gimple-range.h" |
| #include "gimple-range-path.h" |
| #include "gimple-pretty-print.h" |
| #include "cfganal.h" |
| #include "tree-ssa-loop-manip.h" |
| |
| /* Return path query insteance for testing ranges of statements |
| in headers of LOOP contained in basic block BB. |
| Use RANGER instance. */ |
| |
| static path_range_query * |
| get_range_query (class loop *loop, |
| basic_block bb, |
| gimple_ranger &ranger) |
| { |
| auto_vec<basic_block, 8> path; |
| for (; bb != loop->header; bb = single_pred_edge (bb)->src) |
| path.safe_push (bb); |
| path.safe_push (loop->header); |
| path.safe_push (loop_preheader_edge (loop)->src); |
| return new path_range_query (ranger, path); |
| } |
| |
| /* Return edge that is true in the first iteration of the loop |
| and NULL otherwise. |
| Formulate corrent ranger query to RANGER. */ |
| |
| static edge |
| static_loop_exit (class loop *l, basic_block bb, gimple_ranger &ranger, |
| path_range_query *&query) |
| { |
| gcond *last = safe_dyn_cast <gcond *> (*gsi_last_bb (bb)); |
| edge ret_e; |
| |
| if (!last) |
| return NULL; |
| |
| edge true_e, false_e; |
| extract_true_false_edges_from_block (bb, &true_e, &false_e); |
| |
| /* If neither edge is the exit edge, this is not a case we'd like to |
| special-case. */ |
| if (!loop_exit_edge_p (l, true_e) && !loop_exit_edge_p (l, false_e)) |
| return NULL; |
| |
| int_range<1> desired_static_range; |
| if (loop_exit_edge_p (l, true_e)) |
| { |
| desired_static_range = range_false (); |
| ret_e = true_e; |
| } |
| else |
| { |
| desired_static_range = range_true (); |
| ret_e = false_e; |
| } |
| |
| if (!query) |
| query = get_range_query (l, gimple_bb (last), ranger); |
| |
| int_range<2> r; |
| if (!query->range_of_stmt (r, last)) |
| return NULL; |
| return r == desired_static_range ? ret_e : NULL; |
| } |
| |
| /* Return true if STMT is static in LOOP. This means that its value |
| is constant in the first iteration. |
| Use RANGER and formulate query cached in QUERY. */ |
| |
| static bool |
| loop_static_stmt_p (class loop *loop, |
| gimple_ranger &ranger, |
| path_range_query *&query, |
| gimple *stmt) |
| { |
| tree type = gimple_range_type (stmt); |
| if (!type || !Value_Range::supports_type_p (type)) |
| return false; |
| |
| if (!query) |
| query = get_range_query (loop, gimple_bb (stmt), ranger); |
| |
| Value_Range r (gimple_range_type (stmt)); |
| if (!query->range_of_stmt (r, stmt)) |
| return false; |
| return r.singleton_p (); |
| } |
| |
| /* Return true if OP is invariant. */ |
| |
| static bool |
| loop_invariant_op_p (class loop *loop, |
| tree op) |
| { |
| if (is_gimple_min_invariant (op)) |
| return true; |
| if (SSA_NAME_IS_DEFAULT_DEF (op) |
| || !flow_bb_inside_loop_p (loop, gimple_bb (SSA_NAME_DEF_STMT (op)))) |
| return true; |
| return gimple_uid (SSA_NAME_DEF_STMT (op)) & 1; |
| } |
| |
| /* Return true if OP combines outcome of static and |
| loop invariant conditional. */ |
| |
| static bool |
| loop_static_op_p (class loop *loop, tree op) |
| { |
| /* Always check for invariant first. */ |
| gcc_checking_assert (!is_gimple_min_invariant (op) |
| && !SSA_NAME_IS_DEFAULT_DEF (op) |
| && flow_bb_inside_loop_p (loop, |
| gimple_bb (SSA_NAME_DEF_STMT (op)))); |
| return gimple_uid (SSA_NAME_DEF_STMT (op)) & 2; |
| } |
| |
| |
| /* Return true if OP combines outcome of static and |
| loop invariant conditional. */ |
| |
| static bool |
| loop_combined_static_and_iv_p (class loop *loop, |
| tree op) |
| { |
| /* Always check for invariant first. */ |
| gcc_checking_assert (!is_gimple_min_invariant (op) |
| && !SSA_NAME_IS_DEFAULT_DEF (op) |
| && flow_bb_inside_loop_p (loop, |
| gimple_bb (SSA_NAME_DEF_STMT (op)))); |
| return gimple_uid (SSA_NAME_DEF_STMT (op)) & 4; |
| } |
| |
| /* Decision about posibility of copying a given header. */ |
| |
| enum ch_decision |
| { |
| /* We do not want to copy this header. */ |
| ch_impossible, |
| /* We can copy it if it enables wins. */ |
| ch_possible, |
| /* We can "copy" it if it enables wins and doing |
| so will introduce no new code. */ |
| ch_possible_zero_cost, |
| /* We want to copy. */ |
| ch_win, |
| /* We want to copy and we will eliminate loop exit. */ |
| ch_win_invariant_exit |
| }; |
| |
| /* Check whether we should duplicate HEADER of LOOP. At most *LIMIT |
| instructions should be duplicated, limit is decreased by the actual |
| amount. */ |
| |
| static ch_decision |
| should_duplicate_loop_header_p (basic_block header, class loop *loop, |
| gimple_ranger *ranger, |
| int *limit, |
| hash_set <edge> *invariant_exits, |
| hash_set <edge> *static_exits) |
| { |
| gimple_stmt_iterator bsi; |
| |
| gcc_assert (!header->aux); |
| |
| gcc_assert (EDGE_COUNT (header->succs) > 0); |
| if (single_succ_p (header)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Not duplicating bb %i: it is single succ.\n", |
| header->index); |
| return ch_impossible; |
| } |
| |
| if (flow_bb_inside_loop_p (loop, EDGE_SUCC (header, 0)->dest) |
| && flow_bb_inside_loop_p (loop, EDGE_SUCC (header, 1)->dest)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Not duplicating bb %i: both successors are in loop.\n", |
| loop->num); |
| return ch_impossible; |
| } |
| |
| /* If this is not the original loop header, we want it to have just |
| one predecessor in order to match the && pattern. */ |
| if (header != loop->header && !single_pred_p (header)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Not duplicating bb %i: it has mutiple predecestors.\n", |
| header->index); |
| return ch_impossible; |
| } |
| |
| gcond *last = safe_dyn_cast <gcond *> (*gsi_last_bb (header)); |
| if (!last) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Not duplicating bb %i: it does not end by conditional.\n", |
| header->index); |
| return ch_impossible; |
| } |
| |
| path_range_query *query = NULL; |
| for (gphi_iterator psi = gsi_start_phis (header); !gsi_end_p (psi); |
| gsi_next (&psi)) |
| if (!virtual_operand_p (gimple_phi_result (psi.phi ()))) |
| { |
| bool static_p = loop_static_stmt_p (loop, *ranger, |
| query, psi.phi ()); |
| gimple_set_uid (psi.phi (), static_p ? 2 : 0); |
| } |
| bool code_size_cost = false; |
| |
| /* Count number of instructions and punt on calls. |
| Populate stmts INV flag to later apply heuristics to the |
| kind of conditions we want to copy. */ |
| for (bsi = gsi_start_bb (header); !gsi_end_p (bsi); gsi_next (&bsi)) |
| { |
| gimple *last = gsi_stmt (bsi); |
| |
| if (gimple_code (last) == GIMPLE_LABEL) |
| continue; |
| |
| if (is_gimple_debug (last)) |
| continue; |
| |
| if (gimple_code (last) == GIMPLE_COND) |
| break; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " Analyzing: "); |
| print_gimple_stmt (dump_file, last, 0, TDF_SLIM); |
| } |
| |
| if (gimple_code (last) == GIMPLE_CALL |
| && (!gimple_inexpensive_call_p (as_a <gcall *> (last)) |
| /* IFN_LOOP_DIST_ALIAS means that inner loop is distributed |
| at current loop's header. Don't copy in this case. */ |
| || gimple_call_internal_p (last, IFN_LOOP_DIST_ALIAS))) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Not duplicating bb %i: it contains call.\n", |
| header->index); |
| if (query) |
| delete query; |
| return ch_impossible; |
| } |
| |
| /* Classify the stmt is invariant in the loop. */ |
| gimple_set_uid (last, 0); |
| if (!gimple_vuse (last) |
| && gimple_code (last) != GIMPLE_ASM |
| && (gimple_code (last) != GIMPLE_CALL |
| || gimple_call_flags (last) & ECF_CONST)) |
| { |
| bool inv = true, static_p = false; |
| ssa_op_iter i; |
| tree op; |
| FOR_EACH_SSA_TREE_OPERAND (op, last, i, SSA_OP_USE) |
| if (!loop_invariant_op_p (loop, op)) |
| inv = false; |
| /* Assume that code is reasonably optimized and invariant |
| constants are already identified. */ |
| if (!inv) |
| static_p = loop_static_stmt_p (loop, *ranger, query, last); |
| gimple_set_uid (last, (inv ? 1 : 0) | (static_p ? 2 : 0)); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| if (inv) |
| fprintf (dump_file, " Stmt is loop invariant\n"); |
| if (static_p) |
| fprintf (dump_file, " Stmt is static " |
| "(constant in the first iteration)\n"); |
| } |
| /* Loop invariants will be optimized out in loop body after |
| duplication; do not account invariant computation in code |
| size costs. |
| |
| Similarly static computations will be optimized out in the |
| duplicatd header. */ |
| if (inv || static_p) |
| continue; |
| |
| /* Match the following: |
| _1 = i_1 < 10 <- static condtion |
| _2 = n != 0 <- loop invariant condition |
| _3 = _1 & _2 <- combined static and iv statement. */ |
| tree_code code; |
| if (gimple_code (last) == GIMPLE_ASSIGN |
| && ((code = gimple_assign_rhs_code (last)) == BIT_AND_EXPR |
| || code == BIT_IOR_EXPR || code == BIT_XOR_EXPR)) |
| { |
| tree op1 = gimple_assign_rhs1 (last); |
| tree op2 = gimple_assign_rhs2 (last); |
| |
| if ((loop_invariant_op_p (loop, op1) |
| || loop_combined_static_and_iv_p (loop, op1) |
| || loop_static_op_p (loop, op1)) |
| && (loop_invariant_op_p (loop, op2) |
| || loop_combined_static_and_iv_p (loop, op2) |
| || loop_static_op_p (loop, op2))) |
| { |
| /* Duplicating loop header with combned conditional will |
| remove this statement in each copy. But we account for |
| that later when seeing that condition. |
| |
| Note that this may be overly optimistic for bit operations |
| where the static parameter may still result in non-trivial |
| bit operation. */ |
| gimple_set_uid (last, 4); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Stmt combines static and invariant op\n"); |
| continue; |
| } |
| } |
| } |
| |
| int insns = estimate_num_insns (last, &eni_size_weights); |
| *limit -= insns; |
| if (insns) |
| code_size_cost = true; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Acconting stmt as %i insns\n", insns); |
| if (*limit < 0) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Not duplicating bb %i contains too many insns.\n", |
| header->index); |
| if (query) |
| delete query; |
| return ch_impossible; |
| } |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " Analyzing: "); |
| print_gimple_stmt (dump_file, last, 0, TDF_SLIM); |
| } |
| |
| /* If the condition tests a non-IV loop variant we do not want to rotate |
| the loop further. Unless this is the original loop header. */ |
| tree lhs = gimple_cond_lhs (last); |
| tree rhs = gimple_cond_rhs (last); |
| bool lhs_invariant = loop_invariant_op_p (loop, lhs); |
| bool rhs_invariant = loop_invariant_op_p (loop, rhs); |
| |
| /* Combined conditional is a result of if combining: |
| |
| _1 = i_1 < 10 <- static condtion |
| _2 = n != 0 <- loop invariant condition |
| _3 = _1 & _2 <- combined static and iv statement |
| if (_3 != 0) <- combined conditional |
| |
| Combined conditionals will not be optimized out in either copy. |
| However duplicaed header simplifies to: |
| |
| if (n < 10) |
| |
| while loop body to |
| |
| if (i_1 < 10) |
| |
| So effectively the resulting code sequence will be of same length as |
| the original code. |
| |
| Combined conditionals are never static or invariant, so save some work |
| below. */ |
| if (lhs_invariant != rhs_invariant |
| && (lhs_invariant |
| || loop_combined_static_and_iv_p (loop, lhs)) |
| && (rhs_invariant |
| || loop_combined_static_and_iv_p (loop, rhs))) |
| { |
| if (query) |
| delete query; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Conditional combines static and invariant op.\n"); |
| return ch_win; |
| } |
| |
| edge static_exit = static_loop_exit (loop, header, *ranger, query); |
| if (query) |
| delete query; |
| |
| if (static_exit && static_exits) |
| { |
| static_exits->add (static_exit); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Will eliminate peeled conditional in bb %d.\n", |
| static_exit->src->index); |
| /* Still look for invariant exits; exit may be both. */ |
| } |
| if (lhs_invariant && rhs_invariant) |
| { |
| if (invariant_exits) |
| { |
| edge e; |
| edge_iterator ei; |
| FOR_EACH_EDGE (e, ei, header->succs) |
| if (loop_exit_edge_p (loop, e)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Will elliminate invariant exit %i->%i\n", |
| e->src->index, e->dest->index); |
| invariant_exits->add (e); |
| } |
| } |
| return ch_win_invariant_exit; |
| } |
| |
| /* If the static exit fully optimize out, it is win to "duplicate" |
| it. |
| |
| TODO: Even if duplication costs some size we may opt to do so in case |
| exit probability is significant enough (do partial peeling). */ |
| if (static_exit) |
| return !code_size_cost ? ch_possible_zero_cost : ch_possible; |
| |
| /* We was not able to prove that conditional will be eliminated. */ |
| int insns = estimate_num_insns (last, &eni_size_weights); |
| *limit -= insns; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Acconting stmt as %i insns\n", insns); |
| if (*limit < 0) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Not duplicating bb %i contains too many insns.\n", |
| header->index); |
| return ch_impossible; |
| } |
| |
| return ch_possible; |
| } |
| |
| /* Checks whether LOOP is a do-while style loop. */ |
| |
| static bool |
| do_while_loop_p (class loop *loop) |
| { |
| gimple *stmt = last_nondebug_stmt (loop->latch); |
| |
| /* If the latch of the loop is not empty, it is not a do-while loop. */ |
| if (stmt |
| && gimple_code (stmt) != GIMPLE_LABEL) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Loop %i is not do-while loop: latch is not empty.\n", |
| loop->num); |
| return false; |
| } |
| |
| /* If the latch does not have a single predecessor, it is not a |
| do-while loop. */ |
| if (!single_pred_p (loop->latch)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Loop %i is not do-while loop: latch has multiple " |
| "predecessors.\n", loop->num); |
| return false; |
| } |
| basic_block pred = single_pred (loop->latch); |
| |
| /* If the latch predecessor doesn't exit the loop, it is not a |
| do-while loop. */ |
| if (!loop_exits_from_bb_p (loop, pred)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Loop %i is not do-while loop: latch predecessor " |
| "does not exit loop.\n", loop->num); |
| return false; |
| } |
| gcond *last = safe_dyn_cast <gcond *> (*gsi_last_bb (pred)); |
| if (last |
| && (gimple_cond_lhs (last) == boolean_false_node |
| || gimple_cond_lhs (last) == boolean_true_node) |
| && gimple_cond_rhs (last) == boolean_false_node) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Loop %i is not do-while loop: latch predecessor " |
| "contains exit we optimized out.\n", loop->num); |
| return false; |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Loop %i is do-while loop\n", loop->num); |
| |
| return true; |
| } |
| |
| /* Update profile after header copying of LOOP. |
| REGION is the original (in loop) sequence, REGION_COPY is the |
| duplicated header (now outside of loop). N_REGION is number of |
| bbs duplicated. |
| ELIMINATED_EDGE is edge to be removed from duplicated sequence. |
| INVARIANT_EXITS are edges in the loop body to be elimianted |
| since they are loop invariants |
| |
| So We expect the following: |
| |
| // region_copy_start entry will be scaled to entry_count |
| if (cond1) <- this condition will become false |
| and we update probabilities |
| goto loop_exit; |
| if (cond2) <- this condition is loop invariant |
| goto loop_exit; |
| goto loop_header <- this will be redirected to loop. |
| // region_copy_end |
| loop: |
| <body> |
| // region start |
| loop_header: |
| if (cond1) <- we need to update probabbility here |
| goto loop_exit; |
| if (cond2) <- and determine scaling factor here. |
| moreover cond2 is now always true |
| goto loop_exit; |
| else |
| goto loop; |
| // region end |
| |
| Adding support for more exits can be done similarly, |
| but only consumer so far is tree-ssa-loop-ch and it uses only this |
| to handle the common case of peeling headers which have |
| conditionals known to be always true upon entry. */ |
| |
| static void |
| update_profile_after_ch (class loop *loop, |
| basic_block *region, basic_block *region_copy, |
| unsigned n_region, |
| hash_set <edge> *invariant_exits, |
| hash_set <edge> *static_exits, |
| profile_count entry_count) |
| { |
| for (unsigned int i = 0; i < n_region; i++) |
| { |
| edge exit_e, exit_e_copy, e, e_copy; |
| if (EDGE_COUNT (region[i]->succs) == 1) |
| { |
| region_copy[i]->count = entry_count; |
| region[i]->count -= entry_count; |
| continue; |
| } |
| |
| gcc_checking_assert (EDGE_COUNT (region[i]->succs) == 2); |
| if (loop_exit_edge_p (loop, |
| EDGE_SUCC (region[i], 0))) |
| { |
| exit_e = EDGE_SUCC (region[i], 0); |
| exit_e_copy = EDGE_SUCC (region_copy[i], 0); |
| e = EDGE_SUCC (region[i], 1); |
| e_copy = EDGE_SUCC (region_copy[i], 1); |
| } |
| else |
| { |
| exit_e = EDGE_SUCC (region[i], 1); |
| exit_e_copy = EDGE_SUCC (region_copy[i], 1); |
| e = EDGE_SUCC (region[i], 0); |
| e_copy = EDGE_SUCC (region_copy[i], 0); |
| } |
| gcc_assert (i == n_region - 1 |
| || (e->dest == region[i + 1] |
| && e_copy->dest == region_copy[i + 1])); |
| region_copy[i]->count = entry_count; |
| profile_count exit_e_count = exit_e->count (); |
| bool was_static = false; |
| if (static_exits->contains (exit_e)) |
| { |
| /* Update profile and the conditional. |
| CFG update is done by caller. */ |
| static_exits->remove (exit_e); |
| was_static = true; |
| e_copy->probability = profile_probability::always (); |
| exit_e_copy->probability = profile_probability::never (); |
| gcond *cond_stmt |
| = as_a <gcond *>(*gsi_last_bb (region_copy[i])); |
| if (e_copy->flags & EDGE_TRUE_VALUE) |
| gimple_cond_make_true (cond_stmt); |
| else |
| gimple_cond_make_false (cond_stmt); |
| update_stmt (cond_stmt); |
| /* Header copying is a special case of jump threading, so use |
| common code to update loop body exit condition. */ |
| update_bb_profile_for_threading (region[i], entry_count, e); |
| } |
| else |
| region[i]->count -= region_copy[i]->count; |
| if (invariant_exits->contains (exit_e)) |
| { |
| invariant_exits->remove (exit_e); |
| /* All exits will happen in exit_e_copy which is out of the |
| loop, so increase probability accordingly. |
| If the edge is eliminated_edge we already corrected |
| profile above. */ |
| if (entry_count.nonzero_p () && !was_static) |
| set_edge_probability_and_rescale_others |
| (exit_e_copy, exit_e_count.probability_in |
| (entry_count)); |
| /* Eliminate in-loop conditional. */ |
| e->probability = profile_probability::always (); |
| exit_e->probability = profile_probability::never (); |
| gcond *cond_stmt = as_a <gcond *>(*gsi_last_bb (region[i])); |
| if (e->flags & EDGE_TRUE_VALUE) |
| gimple_cond_make_true (cond_stmt); |
| else |
| gimple_cond_make_false (cond_stmt); |
| update_stmt (cond_stmt); |
| } |
| entry_count = e_copy->count (); |
| } |
| /* Be sure that we seen all invariant exit edges we are supposed to update. |
| We may have recorded some static exists we decided to not duplicate. */ |
| gcc_checking_assert (invariant_exits->is_empty ()); |
| } |
| |
| namespace { |
| |
| /* Common superclass for both header-copying phases. */ |
| class ch_base : public gimple_opt_pass |
| { |
| protected: |
| ch_base (pass_data data, gcc::context *ctxt) |
| : gimple_opt_pass (data, ctxt) |
| {} |
| |
| /* Copies headers of all loops in FUN for which process_loop_p is true. */ |
| unsigned int copy_headers (function *fun); |
| |
| /* Return true to copy headers of LOOP or false to skip. */ |
| virtual bool process_loop_p (class loop *loop) = 0; |
| }; |
| |
| const pass_data pass_data_ch = |
| { |
| GIMPLE_PASS, /* type */ |
| "ch", /* name */ |
| OPTGROUP_LOOP, /* optinfo_flags */ |
| TV_TREE_CH, /* tv_id */ |
| ( PROP_cfg | PROP_ssa ), /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_ch : public ch_base |
| { |
| public: |
| pass_ch (gcc::context *ctxt) |
| : ch_base (pass_data_ch, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| bool gate (function *) final override { return flag_tree_ch != 0; } |
| |
| /* Initialize and finalize loop structures, copying headers inbetween. */ |
| unsigned int execute (function *) final override; |
| |
| opt_pass * clone () final override { return new pass_ch (m_ctxt); } |
| |
| protected: |
| /* ch_base method: */ |
| bool process_loop_p (class loop *loop) final override; |
| }; // class pass_ch |
| |
| const pass_data pass_data_ch_vect = |
| { |
| GIMPLE_PASS, /* type */ |
| "ch_vect", /* name */ |
| OPTGROUP_LOOP, /* optinfo_flags */ |
| TV_TREE_CH, /* tv_id */ |
| ( PROP_cfg | PROP_ssa ), /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| /* This is a more aggressive version of the same pass, designed to run just |
| before if-conversion and vectorization, to put more loops into the form |
| required for those phases. */ |
| class pass_ch_vect : public ch_base |
| { |
| public: |
| pass_ch_vect (gcc::context *ctxt) |
| : ch_base (pass_data_ch_vect, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| bool gate (function *fun) final override |
| { |
| return flag_tree_ch != 0 |
| && (flag_tree_loop_vectorize != 0 || fun->has_force_vectorize_loops); |
| } |
| |
| /* Just copy headers, no initialization/finalization of loop structures. */ |
| unsigned int execute (function *) final override; |
| |
| protected: |
| /* ch_base method: */ |
| bool process_loop_p (class loop *loop) final override; |
| }; // class pass_ch_vect |
| |
| /* For all loops, copy the condition at the end of the loop body in front |
| of the loop. This is beneficial since it increases efficiency of |
| code motion optimizations. It also saves one jump on entry to the loop. */ |
| |
| unsigned int |
| ch_base::copy_headers (function *fun) |
| { |
| basic_block *bbs, *copied_bbs; |
| unsigned bbs_size; |
| bool changed = false; |
| |
| if (number_of_loops (fun) <= 1) |
| return 0; |
| |
| bbs = XNEWVEC (basic_block, n_basic_blocks_for_fn (fun)); |
| copied_bbs = XNEWVEC (basic_block, n_basic_blocks_for_fn (fun)); |
| bbs_size = n_basic_blocks_for_fn (fun); |
| |
| struct candidate |
| { |
| class loop *loop; |
| unsigned int nheaders; |
| hash_set <edge> *invariant_exits, *static_exits; |
| }; |
| auto_vec<struct candidate> candidates; |
| auto_vec<std::pair<edge, loop_p> > copied; |
| auto_vec<class loop *> loops_to_unloop; |
| auto_vec<int> loops_to_unloop_nunroll; |
| |
| mark_dfs_back_edges (); |
| gimple_ranger *ranger = new gimple_ranger; |
| for (auto loop : loops_list (cfun, 0)) |
| { |
| int initial_limit = optimize_loop_for_speed_p (loop) |
| ? param_max_loop_header_insns : 0; |
| int remaining_limit = initial_limit; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Analyzing loop %i\n", loop->num); |
| |
| basic_block header = loop->header; |
| if (!get_max_loop_iterations_int (loop)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Loop %d never loops.\n", loop->num); |
| scale_loop_profile (loop, profile_probability::always (), 0); |
| loops_to_unloop.safe_push (loop); |
| loops_to_unloop_nunroll.safe_push (0); |
| continue; |
| } |
| |
| /* If the loop is already a do-while style one (either because it was |
| written as such, or because jump threading transformed it into one), |
| we might be in fact peeling the first iteration of the loop. This |
| in general is not a good idea. Also avoid touching infinite loops. */ |
| if (!loop_has_exit_edges (loop) |
| || !process_loop_p (loop)) |
| continue; |
| |
| /* Iterate the header copying up to limit; this takes care of the cases |
| like while (a && b) {...}, where we want to have both of the conditions |
| copied. TODO -- handle while (a || b) - like cases, by not requiring |
| the header to have just a single successor and copying up to |
| postdominator. */ |
| int nheaders = 0; |
| int last_win_nheaders = 0; |
| bool last_win_invariant_exit = false; |
| ch_decision ret; |
| auto_vec <ch_decision, 32> decision; |
| hash_set <edge> *invariant_exits = new hash_set <edge>; |
| hash_set <edge> *static_exits = new hash_set <edge>; |
| while ((ret = should_duplicate_loop_header_p (header, loop, ranger, |
| &remaining_limit, |
| invariant_exits, |
| static_exits)) |
| != ch_impossible) |
| { |
| nheaders++; |
| decision.safe_push (ret); |
| if (ret >= ch_win) |
| { |
| last_win_nheaders = nheaders; |
| last_win_invariant_exit = (ret == ch_win_invariant_exit); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Duplicating bb %i is a win\n", |
| header->index); |
| } |
| else |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " May duplicate bb %i\n", header->index); |
| |
| /* Find a successor of header that is inside a loop; i.e. the new |
| header after the condition is copied. */ |
| if (flow_bb_inside_loop_p (loop, EDGE_SUCC (header, 0)->dest)) |
| header = EDGE_SUCC (header, 0)->dest; |
| else |
| header = EDGE_SUCC (header, 1)->dest; |
| } |
| |
| /* Try to turn loop into do-while. This means ensuring that |
| last duplicated header will not have loop invariant exit. */ |
| if (last_win_nheaders && last_win_invariant_exit |
| && nheaders > last_win_nheaders) |
| { |
| last_win_nheaders++; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Duplicating additional BB to obtain do-while loop\n"); |
| } |
| else if (!last_win_nheaders && nheaders && !do_while_loop_p (loop)) |
| { |
| last_win_nheaders = 1; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Duplicating header BB to obtain do-while loop\n"); |
| } |
| /* "Duplicate" all BBs with zero cost following last basic blocks we |
| decided to copy. */ |
| while (last_win_nheaders < (int)decision.length () |
| && decision[last_win_nheaders] == ch_possible_zero_cost) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| " Duplicating extra bb is a win; it has zero cost\n"); |
| last_win_nheaders++; |
| } |
| |
| if (last_win_nheaders) |
| candidates.safe_push ({loop, last_win_nheaders, |
| invariant_exits, static_exits}); |
| else |
| { |
| delete invariant_exits; |
| delete static_exits; |
| } |
| } |
| /* Do not use ranger after we change the IL and not have updated SSA. */ |
| delete ranger; |
| |
| for (auto candidate : candidates) |
| { |
| class loop *loop = candidate.loop; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Copying headers of loop %i\n", loop->num); |
| |
| basic_block header = loop->header; |
| edge nonexit = NULL; |
| edge exit = NULL; |
| unsigned int n_bbs = 0; |
| int nexits = 0; |
| profile_count exit_count = profile_count::zero (); |
| profile_count entry_count = profile_count::zero (); |
| edge e; |
| edge_iterator ei; |
| |
| FOR_EACH_EDGE (e, ei, loop->header->preds) |
| if (e->src != loop->latch) |
| entry_count += e->count (); |
| while (n_bbs < candidate.nheaders) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Will duplicate bb %i\n", header->index); |
| /* Find a successor of header that is inside a loop; i.e. the new |
| header after the condition is copied. */ |
| if (flow_bb_inside_loop_p (loop, EDGE_SUCC (header, 0)->dest)) |
| { |
| nonexit = EDGE_SUCC (header, 0); |
| exit = EDGE_SUCC (header, 1); |
| } |
| else |
| { |
| nonexit = EDGE_SUCC (header, 1); |
| exit = EDGE_SUCC (header, 0); |
| } |
| exit_count += exit->count (); |
| nexits++; |
| bbs[n_bbs++] = header; |
| gcc_assert (bbs_size > n_bbs); |
| header = nonexit->dest; |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Duplicating header of the loop %d up to edge %d->%d\n", |
| loop->num, exit->src->index, exit->dest->index); |
| |
| /* Ensure that the header will have just the latch as a predecessor |
| inside the loop. */ |
| if (!single_pred_p (nonexit->dest)) |
| { |
| header = split_edge (nonexit); |
| exit = single_pred_edge (header); |
| } |
| |
| edge entry = loop_preheader_edge (loop); |
| |
| propagate_threaded_block_debug_into (exit->dest, entry->dest); |
| if (!gimple_duplicate_seme_region (entry, exit, bbs, n_bbs, copied_bbs, |
| true)) |
| { |
| delete candidate.static_exits; |
| delete candidate.invariant_exits; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Duplication failed.\n"); |
| continue; |
| } |
| if (loop->header->count.initialized_p ()) |
| update_profile_after_ch (loop, bbs, copied_bbs, n_bbs, |
| candidate.invariant_exits, |
| candidate.static_exits, |
| entry_count); |
| delete candidate.static_exits; |
| delete candidate.invariant_exits; |
| copied.safe_push (std::make_pair (entry, loop)); |
| |
| /* If the loop has the form "for (i = j; i < j + 10; i++)" then |
| this copying can introduce a case where we rely on undefined |
| signed overflow to eliminate the preheader condition, because |
| we assume that "j < j + 10" is true. We don't want to warn |
| about that case for -Wstrict-overflow, because in general we |
| don't warn about overflow involving loops. Prevent the |
| warning by setting the no_warning flag in the condition. */ |
| if (warn_strict_overflow > 0) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < n_bbs; ++i) |
| { |
| gimple_stmt_iterator bsi; |
| |
| for (bsi = gsi_start_bb (copied_bbs[i]); |
| !gsi_end_p (bsi); |
| gsi_next (&bsi)) |
| { |
| gimple *stmt = gsi_stmt (bsi); |
| if (gimple_code (stmt) == GIMPLE_COND) |
| { |
| tree lhs = gimple_cond_lhs (stmt); |
| if (gimple_cond_code (stmt) != EQ_EXPR |
| && gimple_cond_code (stmt) != NE_EXPR |
| && INTEGRAL_TYPE_P (TREE_TYPE (lhs)) |
| && TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (lhs))) |
| suppress_warning (stmt, OPT_Wstrict_overflow_); |
| } |
| else if (is_gimple_assign (stmt)) |
| { |
| enum tree_code rhs_code = gimple_assign_rhs_code (stmt); |
| tree rhs1 = gimple_assign_rhs1 (stmt); |
| if (TREE_CODE_CLASS (rhs_code) == tcc_comparison |
| && rhs_code != EQ_EXPR |
| && rhs_code != NE_EXPR |
| && INTEGRAL_TYPE_P (TREE_TYPE (rhs1)) |
| && TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (rhs1))) |
| suppress_warning (stmt, OPT_Wstrict_overflow_); |
| } |
| } |
| } |
| } |
| |
| /* Update header of the loop. */ |
| loop->header = header; |
| /* Find correct latch. We only duplicate chain of conditionals so |
| there should be precisely two edges to the new header. One entry |
| edge and one to latch. */ |
| FOR_EACH_EDGE (e, ei, loop->header->preds) |
| if (header != e->src) |
| { |
| loop->latch = e->src; |
| break; |
| } |
| /* Ensure that the latch is simple. */ |
| if (!single_succ_p (loop_latch_edge (loop)->src)) |
| split_edge (loop_latch_edge (loop)); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| if (do_while_loop_p (loop)) |
| fprintf (dump_file, "Loop %d is now do-while loop.\n", loop->num); |
| else |
| fprintf (dump_file, "Loop %d is still not do-while loop.\n", |
| loop->num); |
| fprintf (dump_file, "Exit count: "); |
| exit_count.dump (dump_file); |
| fprintf (dump_file, "\nEntry count: "); |
| entry_count.dump (dump_file); |
| fprintf (dump_file, "\n"); |
| } |
| |
| /* We possibly decreased number of itrations by 1. */ |
| auto_vec<edge> exits = get_loop_exit_edges (loop); |
| bool precise = (nexits == (int) exits.length ()); |
| /* Check that loop may not terminate in other way than via |
| basic blocks we duplicated. */ |
| if (precise) |
| { |
| basic_block *bbs = get_loop_body (loop); |
| for (unsigned i = 0; i < loop->num_nodes && precise; ++i) |
| { |
| basic_block bb = bbs[i]; |
| bool found_exit = false; |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if (!flow_bb_inside_loop_p (loop, e->dest)) |
| { |
| found_exit = true; |
| break; |
| } |
| /* If BB has exit, it was duplicated. */ |
| if (found_exit) |
| continue; |
| /* Give up on irreducible loops. */ |
| if (bb->flags & BB_IRREDUCIBLE_LOOP) |
| { |
| precise = false; |
| break; |
| } |
| /* Check that inner loops are finite. */ |
| for (class loop *l = bb->loop_father; l != loop && precise; |
| l = loop_outer (l)) |
| if (!l->finite_p) |
| { |
| precise = false; |
| break; |
| } |
| /* Verify that there is no statement that may be terminate |
| execution in a way not visible to CFG. */ |
| for (gimple_stmt_iterator bsi = gsi_start_bb (bb); |
| !gsi_end_p (bsi); gsi_next (&bsi)) |
| if (stmt_can_terminate_bb_p (gsi_stmt (bsi))) |
| precise = false; |
| } |
| free (bbs); |
| } |
| if (precise |
| && get_max_loop_iterations_int (loop) == 1) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Loop %d no longer loops.\n", loop->num); |
| scale_loop_profile (loop, profile_probability::always (), 0); |
| loops_to_unloop.safe_push (loop); |
| loops_to_unloop_nunroll.safe_push (0); |
| } |
| else if (precise) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Peeled all exits:" |
| " decreased number of iterations of loop %d by 1.\n", |
| loop->num); |
| adjust_loop_info_after_peeling (loop, 1, true); |
| } |
| else if (exit_count >= entry_count.apply_scale (9, 10)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Peeled likely exits: likely decreased number " |
| "of iterations of loop %d by 1.\n", loop->num); |
| adjust_loop_info_after_peeling (loop, 1, false); |
| } |
| else if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Not decreased number" |
| " of iterations of loop %d; likely exits remains.\n", |
| loop->num); |
| |
| changed = true; |
| } |
| |
| if (changed) |
| { |
| update_ssa (TODO_update_ssa); |
| /* After updating SSA form perform CSE on the loop header |
| copies. This is esp. required for the pass before |
| vectorization since nothing cleans up copied exit tests |
| that can now be simplified. CSE from the entry of the |
| region we copied till all loop exit blocks but not |
| entering the loop itself. */ |
| for (unsigned i = 0; i < copied.length (); ++i) |
| { |
| edge entry = copied[i].first; |
| loop_p loop = copied[i].second; |
| auto_vec<edge> exit_edges = get_loop_exit_edges (loop); |
| bitmap exit_bbs = BITMAP_ALLOC (NULL); |
| for (unsigned j = 0; j < exit_edges.length (); ++j) |
| bitmap_set_bit (exit_bbs, exit_edges[j]->dest->index); |
| bitmap_set_bit (exit_bbs, loop->header->index); |
| do_rpo_vn (cfun, entry, exit_bbs); |
| BITMAP_FREE (exit_bbs); |
| } |
| } |
| if (!loops_to_unloop.is_empty ()) |
| { |
| bool irred_invalidated; |
| auto_bitmap lc_invalidated; |
| auto_vec<edge> edges_to_remove; |
| unloop_loops (loops_to_unloop, loops_to_unloop_nunroll, edges_to_remove, |
| lc_invalidated, &irred_invalidated); |
| if (loops_state_satisfies_p (fun, LOOP_CLOSED_SSA) |
| && !bitmap_empty_p (lc_invalidated)) |
| rewrite_into_loop_closed_ssa (NULL, 0); |
| changed = true; |
| } |
| free (bbs); |
| free (copied_bbs); |
| |
| return changed ? TODO_cleanup_cfg : 0; |
| } |
| |
| /* Initialize the loop structures we need, and finalize after. */ |
| |
| unsigned int |
| pass_ch::execute (function *fun) |
| { |
| loop_optimizer_init (LOOPS_HAVE_PREHEADERS |
| | LOOPS_HAVE_SIMPLE_LATCHES |
| | LOOPS_HAVE_RECORDED_EXITS); |
| |
| unsigned int res = copy_headers (fun); |
| |
| loop_optimizer_finalize (); |
| return res; |
| } |
| |
| /* Assume an earlier phase has already initialized all the loop structures that |
| we need here (and perhaps others too), and that these will be finalized by |
| a later phase. */ |
| |
| unsigned int |
| pass_ch_vect::execute (function *fun) |
| { |
| return copy_headers (fun); |
| } |
| |
| /* Apply header copying according to a very simple test of do-while shape. */ |
| |
| bool |
| pass_ch::process_loop_p (class loop *) |
| { |
| return true; |
| } |
| |
| /* Apply header-copying to loops where we might enable vectorization. */ |
| |
| bool |
| pass_ch_vect::process_loop_p (class loop *loop) |
| { |
| if (!flag_tree_loop_vectorize && !loop->force_vectorize) |
| return false; |
| |
| if (loop->dont_vectorize) |
| return false; |
| |
| /* The vectorizer won't handle anything with multiple exits, so skip. */ |
| edge exit = single_exit (loop); |
| if (!exit) |
| return false; |
| |
| if (!do_while_loop_p (loop)) |
| return true; |
| |
| return false; |
| } |
| |
| } // anon namespace |
| |
| gimple_opt_pass * |
| make_pass_ch_vect (gcc::context *ctxt) |
| { |
| return new pass_ch_vect (ctxt); |
| } |
| |
| gimple_opt_pass * |
| make_pass_ch (gcc::context *ctxt) |
| { |
| return new pass_ch (ctxt); |
| } |