| /* An SH specific RTL pass that tries to combine comparisons and redundant |
| condition code register stores across multiple basic blocks. |
| Copyright (C) 2013-2020 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/>. */ |
| |
| #define IN_TARGET_CODE 1 |
| |
| #include "config.h" |
| #define INCLUDE_ALGORITHM |
| #define INCLUDE_LIST |
| #define INCLUDE_VECTOR |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "memmodel.h" |
| #include "optabs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "cfgrtl.h" |
| #include "tree-pass.h" |
| #include "expr.h" |
| |
| /* |
| This pass tries to optimize for example this: |
| mov.l @(4,r4),r1 |
| tst r1,r1 |
| movt r1 |
| tst r1,r1 |
| bt/s .L5 |
| |
| into something simpler: |
| mov.l @(4,r4),r1 |
| tst r1,r1 |
| bf/s .L5 |
| |
| Such sequences can be identified by looking for conditional branches and |
| checking whether the ccreg is set before the conditional branch |
| by testing another register for != 0, which was set by a ccreg store. |
| This can be optimized by eliminating the redundant comparison and |
| inverting the branch condition. There can be multiple comparisons in |
| different basic blocks that all end up in the redunant test insn before the |
| conditional branch. Some example RTL ... |
| |
| Example 1) |
| ---------- |
| |
| [bb 3] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 173) (const_int 0))) |
| (set (reg:SI 167) (xor:SI (reg:SI 147 t) (const_int 1))) |
| -> bb 5 |
| |
| [bb 4] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 177) (const_int 0))) |
| (set (reg:SI 167) (reg:SI 147 t)) |
| -> bb 5 |
| |
| [bb 5] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 167) (const_int 0))) |
| (set (pc) (if_then_else (ne (reg:SI 147 t) (const_int 0)) |
| (label_ref:SI 50) (pc))) |
| |
| In [bb 4] elimination of the comparison would require inversion of the branch |
| condition and compensation of other BBs. |
| Instead the comparison in [bb 3] can be replaced with the comparison in [bb 5] |
| by using a reg-reg move. In [bb 4] a logical not is used to compensate the |
| inverted condition. |
| |
| [bb 3] |
| (set (reg:SI 167) (reg:SI 173)) |
| -> bb 5 |
| |
| [BB 4] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 177) (const_int 0))) |
| (set (reg:SI 167) (reg:SI 147 t)) |
| -> bb 5 |
| |
| [bb 5] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 167) (const_int 0))) |
| (set (pc) (if_then_else (ne (reg:SI 147 t) (const_int 0))) |
| (label_ref:SI 50) (pc))) |
| |
| |
| Example 2) |
| ---------- |
| |
| [bb 3] |
| (set (reg:SI 147 t) (gt:SI (reg:SI 173) (reg:SI 175))) |
| (set (reg:SI 167) (reg:SI 147 t)) |
| -> bb 5 |
| |
| [bb 4] |
| (set (reg:SI 147 t) (gt:SI (reg:SI 177) (reg:SI 179))) |
| (set (reg:SI 167) (reg:SI 147 t)) |
| -> bb 5 |
| |
| [bb 5] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 167) (const_int 0))) |
| (set (pc) (if_then_else (ne (reg:SI 147 t) (const_int 0)) |
| (label_ref:SI 51) (pc))) |
| |
| The common comparison is factored out and the branch condition is inverted: |
| |
| [bb 3] |
| (set (reg:SI 167) (reg:SI 173)) |
| (set (reg:SI 200) (reg:SI 175)) |
| -> bb 5 |
| |
| [bb 4] |
| (set (reg:SI 167) (reg:SI 177)) |
| (set (reg:SI 200) (reg:SI 179)) |
| -> bb 5 |
| |
| [bb 5] |
| (set (reg:SI 147 t) (gt:SI (reg:SI 167) (reg:SI 200))) |
| (set (pc) (if_then_else (eq (reg:SI 147 t) (const_int 0)) |
| (label_ref:SI 51) (pc))) |
| |
| |
| Example 3) |
| ---------- |
| |
| [bb 3] |
| (set (reg:SI 147 t) (gt:SI (reg:SI 173) (reg:SI 175))) |
| (set (reg:SI 167) (reg:SI 147 t)) |
| -> bb 5 |
| |
| [bb 4] |
| (set (reg:SI 147 t) (ge:SI (reg:SI 179) (reg:SI 177))) |
| (set (reg:SI 167) (reg:SI 147 t)) |
| -> bb 5 |
| |
| [bb 5] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 167) (const_int 0))) |
| (set (pc) (if_then_else (ne (reg:SI 147 t) (const_int 0)) |
| (label_ref:SI 51) (pc))) |
| |
| The T bit lifetime is extended and the branch condition is inverted: |
| |
| [bb 3] |
| (set (reg:SI 147 t) (gt:SI (reg:SI 173) (reg:SI 175))) |
| -> bb 5 |
| |
| [bb 4] |
| (set (reg:SI 147 t) (ge:SI (reg:SI 179) (reg:SI 177))) |
| -> bb 5 |
| |
| [bb 5] |
| (set (pc) (if_then_else (eq (reg:SI 147 t) (const_int 0)) |
| (label_ref:SI 51) (pc))) |
| |
| |
| Example 4) |
| ---------- |
| |
| [bb 3] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 173) (const_int 5))) |
| (set (reg:SI 167) (reg:SI 147 t)) |
| -> bb 5 |
| |
| [bb 4] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 176) (const_int 5))) |
| (set (reg:SI 167) (xor:SI (reg:SI 147 t) (const_int 1))) |
| -> bb 5 |
| |
| [bb 5] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 167) (const_int 0))) |
| (set (pc) (if_then_else (ne (reg:SI 147 t) (const_int 0)) |
| (label_ref:SI 50) (pc))) |
| |
| In this case the comparisons are the same and could be combined, but the |
| branch condition is different for [bb 3] and [bb 5]. Since the comparison |
| is not a zero comparison, we can't negate one of the operands. The best thing |
| we can do here is to eliminate the comparison before the cbranch and invert |
| the ccreg in one of the BBs. On SH2A this will utilize the 'nott' instruction. |
| |
| [bb 3] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 173) (const_int 5))) |
| -> bb 5 |
| |
| [bb 4] |
| (set (reg:SI 147 t) (eq:SI (reg:SI 176) (const_int 5))) |
| (set (reg:SI 147 t) (xor:SI (reg:SI 147 t) (const_int 1))) |
| -> bb 5 |
| |
| [bb 5] |
| (set (pc) (if_then_else (eq (reg:SI 147 t) (const_int 0)) // inverted |
| (label_ref:SI 50) (pc))) |
| |
| |
| In order to handle cases such as above the RTL pass does the following: |
| |
| - Find the ccreg sets (comparisons) and ccreg stores |
| (inverting and non-inverting) in all related BBs. |
| |
| - If the comparison types in the BBs are all the same, try to combine the |
| comparisons in the BBs and replace the zero comparison before the cbranch |
| with the common comparison. |
| |
| - If the cstores are the same, move the comparison before the cbranch |
| and replace the comparisons in the BBs with reg-reg copies to get the |
| operands in place (create new pseudo regs). |
| |
| - If the cstores differ and the comparison is a test against zero, |
| use reg-reg copies for the dominating cstores and logical not cstores |
| for the subordinate cstores. |
| |
| - If the comparison types in the BBs are not the same, or the first approach |
| doesn't work out for some reason, try to eliminate the comparison before the |
| cbranch by extending the lifetime of the ccreg by leaving the individual |
| comparisons but eliminating the cstores. |
| If the cstores are all the same this is straight forward. |
| If they're not, try to reverse the ccreg for the subordinate cstore type |
| and eliminate the dominating one. |
| */ |
| |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| // Helper functions |
| |
| #define log_msg(...)\ |
| do { if (dump_file != NULL) fprintf (dump_file, __VA_ARGS__); } while (0) |
| |
| #define log_insn(i)\ |
| do { if (dump_file != NULL) print_rtl_single (dump_file, \ |
| (const_rtx)i); } while (0) |
| |
| #define log_rtx(r)\ |
| do { if (dump_file != NULL) print_rtl (dump_file, (const_rtx)r); } while (0) |
| |
| #define log_return(retval, ...)\ |
| do { if (dump_file != NULL) fprintf (dump_file, __VA_ARGS__); \ |
| return retval; } while (0) |
| |
| #define log_return_void(...)\ |
| do { if (dump_file != NULL) fprintf (dump_file, __VA_ARGS__); \ |
| return; } while (0) |
| |
| struct set_of_reg |
| { |
| // The insn where the search stopped or NULL. |
| rtx_insn *insn; |
| |
| // The set rtx of the specified reg if found, NULL_RTX otherwise. |
| // Notice that the set rtx can also be in a parallel. |
| const_rtx set_rtx; |
| |
| // The set source operand rtx if found, NULL_RTX otherwise. |
| rtx |
| set_src (void) const |
| { |
| return set_rtx == NULL_RTX ? NULL_RTX : XEXP (set_rtx, 1); |
| } |
| |
| // The set destination operand rtx if found, NULL_RTX otherwise. |
| rtx |
| set_dst (void) const |
| { |
| return set_rtx == NULL_RTX ? NULL_RTX : XEXP (set_rtx, 0); |
| } |
| |
| bool |
| empty (void) const |
| { |
| return insn == NULL_RTX || set_rtx == NULL_RTX; |
| } |
| }; |
| |
| // Given a reg rtx and a start insn find the insn (in the same basic block) |
| // that sets the reg. |
| static set_of_reg |
| find_set_of_reg_bb (rtx reg, rtx_insn *insn) |
| { |
| set_of_reg result = { insn, NULL_RTX }; |
| |
| if (!REG_P (reg) || insn == NULL) |
| return result; |
| |
| for (result.insn = insn; result.insn != NULL; |
| result.insn = prev_nonnote_nondebug_insn_bb (result.insn)) |
| { |
| if (BARRIER_P (result.insn)) |
| return result; |
| if (!NONJUMP_INSN_P (result.insn)) |
| continue; |
| if (reg_set_p (reg, result.insn)) |
| { |
| result.set_rtx = set_of (reg, result.insn); |
| if (result.set_rtx == NULL_RTX || GET_CODE (result.set_rtx) != SET) |
| result.set_rtx = NULL_RTX; |
| return result; |
| } |
| } |
| |
| return result; |
| } |
| |
| static bool |
| reg_dead_after_insn (const_rtx reg, const_rtx insn) |
| { |
| return find_regno_note (insn, REG_DEAD, REGNO (reg)) != NULL_RTX; |
| } |
| |
| static bool |
| reg_unused_after_insn (const_rtx reg, const_rtx insn) |
| { |
| return find_regno_note (insn, REG_UNUSED, REGNO (reg)) != NULL_RTX; |
| } |
| |
| // Check whether the two specified basic blocks are adjacent, i.e. there's no |
| // other basic block in between them. |
| static bool |
| is_adjacent_bb (basic_block a, basic_block b) |
| { |
| basic_block bb0[] = { a, b }; |
| basic_block bb1[] = { b, a }; |
| |
| for (int i = 0; i < 2; ++i) |
| for (edge_iterator ei = ei_start (bb0[i]->succs); |
| !ei_end_p (ei); ei_next (&ei)) |
| if (ei_edge (ei)->dest == bb1[i]) |
| return true; |
| |
| return false; |
| } |
| |
| // Internal function of trace_reg_uses. |
| static void |
| trace_reg_uses_1 (rtx reg, rtx_insn *start_insn, basic_block bb, int& count, |
| std::vector<basic_block>& visited_bb, rtx abort_at_insn) |
| { |
| if (bb == NULL) |
| return; |
| |
| if (std::find (visited_bb.begin (), visited_bb.end (), bb) |
| != visited_bb.end ()) |
| log_return_void ("[bb %d] already visited\n", bb->index); |
| |
| visited_bb.push_back (bb); |
| |
| if (BB_END (bb) == NULL_RTX) |
| log_return_void ("[bb %d] BB_END is null\n", bb->index); |
| |
| if (start_insn == NULL_RTX) |
| log_return_void ("[bb %d] start_insn is null\n", bb->index); |
| |
| rtx end_insn = NEXT_INSN (BB_END (bb)); |
| if (end_insn == NULL_RTX) |
| log_return_void ("[bb %d] end_insn is null\n", bb->index); |
| |
| for (rtx_insn *i = NEXT_INSN (start_insn); i != end_insn; i = NEXT_INSN (i)) |
| { |
| if (INSN_P (i)) |
| { |
| if (NONDEBUG_INSN_P (i) |
| && (reg_overlap_mentioned_p (reg, PATTERN (i)) |
| || (CALL_P (i) && find_reg_fusage (i, USE, reg)))) |
| { |
| log_msg ("found use in [bb %d] at insn:\n", bb->index); |
| log_insn (i); |
| log_msg ("\n"); |
| count += 1; |
| } |
| |
| // Stop following this BB if the reg is set or dies along the way. |
| if (reg_set_p (reg, i) || reg_dead_after_insn (reg, i)) |
| return; |
| } |
| |
| if (abort_at_insn != NULL_RTX && abort_at_insn == i) |
| return; |
| } |
| |
| for (edge_iterator ei = ei_start (bb->succs); !ei_end_p (ei); ei_next (&ei)) |
| { |
| basic_block succ_bb = ei_edge (ei)->dest; |
| trace_reg_uses_1 (reg, BB_HEAD (succ_bb), succ_bb, count, visited_bb, |
| abort_at_insn); |
| } |
| } |
| |
| // Trace uses of the specified reg in all basic blocks that are reachable from |
| // the specified insn. If 'abort_at_insn' is not null, abort the trace at |
| // that insn. If the insn 'abort_at_insn' uses the specified reg, it is also |
| // counted. |
| static int |
| trace_reg_uses (rtx reg, rtx_insn *start_insn, rtx abort_at_insn) |
| { |
| log_msg ("\ntrace_reg_uses\nreg = "); |
| log_rtx (reg); |
| log_msg ("\nstart_insn = "); |
| log_insn (start_insn); |
| |
| int count = 0; |
| std::vector<basic_block> visited_bb; |
| visited_bb.reserve (32); |
| |
| trace_reg_uses_1 (reg, start_insn, BLOCK_FOR_INSN (start_insn), |
| count, visited_bb, abort_at_insn); |
| return count; |
| } |
| |
| static bool |
| is_conditional_insn (rtx_insn* i) |
| { |
| if (! (INSN_P (i) && NONDEBUG_INSN_P (i))) |
| return false; |
| |
| rtx p = PATTERN (i); |
| return GET_CODE (p) == SET && GET_CODE (XEXP (p, 1)) == IF_THEN_ELSE; |
| } |
| |
| // FIXME: Remove dependency on SH predicate function somehow. |
| extern int t_reg_operand (rtx, machine_mode); |
| extern int negt_reg_operand (rtx, machine_mode); |
| |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| // RTL pass class |
| |
| class sh_treg_combine : public rtl_opt_pass |
| { |
| public: |
| sh_treg_combine (gcc::context* ctx, bool split_insns, const char* name); |
| virtual ~sh_treg_combine (void); |
| virtual bool gate (function *); |
| virtual unsigned int execute (function *); |
| |
| private: |
| // Type of ccreg store that is supported. |
| enum cstore_type_t |
| { |
| cstore_normal = 0, |
| cstore_inverted = 1, |
| cstore_unknown = -1 |
| }; |
| |
| // Type of branch condition that is supported. |
| enum branch_condition_type_t |
| { |
| branch_if_true = 1, |
| branch_if_false = 0, |
| unknown_branch_condition = -1 |
| }; |
| |
| // For each basic block there can be a trace entry which consists of an |
| // insn that sets the ccreg (usually a comparison) and a ccreg store. |
| struct bb_entry |
| { |
| basic_block bb; |
| set_of_reg setcc; |
| set_of_reg cstore; |
| cstore_type_t cstore_type; |
| std::vector<set_of_reg> cstore_reg_reg_copies; |
| |
| bb_entry (basic_block b) |
| : bb (b), setcc (), cstore (), cstore_type (cstore_unknown) { } |
| |
| rtx comparison_rtx (void) const { return setcc.set_src (); } |
| }; |
| |
| // A ccreg trace for a conditional branch. |
| struct cbranch_trace |
| { |
| rtx_insn *cbranch_insn; |
| rtx* condition_rtx_in_insn; |
| branch_condition_type_t cbranch_type; |
| |
| // The comparison against zero right before the conditional branch. |
| set_of_reg setcc; |
| |
| // All BBs that are related to the cbranch. The last BB in the list is |
| // the BB of the cbranch itself and might be empty. |
| std::list<bb_entry> bb_entries; |
| |
| cbranch_trace (rtx_insn *insn) |
| : cbranch_insn (insn), |
| condition_rtx_in_insn (NULL), |
| cbranch_type (unknown_branch_condition), |
| setcc () |
| { |
| if (is_conditional_insn (cbranch_insn)) |
| condition_rtx_in_insn = &XEXP (XEXP (PATTERN (cbranch_insn), 1), 0); |
| else if (rtx x = pc_set (cbranch_insn)) |
| condition_rtx_in_insn = &XEXP (XEXP (x, 1), 0); |
| } |
| |
| basic_block bb (void) const { return BLOCK_FOR_INSN (cbranch_insn); } |
| |
| rtx |
| branch_condition_rtx (void) const |
| { |
| return condition_rtx_in_insn != NULL ? *condition_rtx_in_insn : NULL; |
| } |
| rtx& |
| branch_condition_rtx_ref (void) const |
| { |
| // Before anything gets to invoke this function, there are other checks |
| // in place to make sure that we have a known branch condition and thus |
| // the ref to the rtx in the insn. |
| gcc_assert (condition_rtx_in_insn != NULL); |
| return *condition_rtx_in_insn; |
| } |
| |
| bool |
| can_invert_condition (void) const |
| { |
| // The branch condition can be inverted safely only if the condition |
| // reg is dead after the cbranch. |
| return reg_dead_after_insn (XEXP (branch_condition_rtx (), 0), |
| cbranch_insn); |
| } |
| }; |
| |
| static const pass_data default_pass_data; |
| |
| // Tells whether modified or newly added insns are to be split at the end |
| // of the pass. |
| const bool m_split_insns; |
| |
| // rtx of the ccreg that is obtained from the target. |
| rtx m_ccreg; |
| |
| // Newly added or modified insns. |
| std::vector<rtx> m_touched_insns; |
| |
| // Given an rtx determine whether it's a comparison with a constant zero. |
| static bool is_cmp_eq_zero (const_rtx i); |
| |
| // Update the stored mode of the ccreg from the given branch condition rtx. |
| void update_ccreg_mode (const_rtx cond); |
| |
| // Given an rtx, figure out the branch condition, assuming that it is |
| // in canonical form: |
| // (ne (reg) (const_int 0)) |
| // (eq (reg) (const_int 0)) |
| branch_condition_type_t branch_condition_type (const_rtx cond) const; |
| |
| // Return true if the specified rtx is either a normal ccreg or |
| // a negated form of the ccreg. |
| bool is_normal_ccreg (const_rtx x) const; |
| bool is_inverted_ccreg (const_rtx x) const; |
| |
| // Given a reg rtx and a start insn rtx, try to find the insn in the same |
| // basic block that sets the specified reg. |
| // Return how the search ended and the insn where it stopped or NULL_RTX. |
| enum record_return_t |
| { |
| set_found, |
| set_not_found, |
| other_set_found |
| }; |
| record_return_t record_set_of_reg (rtx reg, rtx_insn *start_insn, |
| bb_entry& e); |
| |
| // Tells whether the cbranch insn of the specified bb_entry can be removed |
| // safely without triggering any side effects. |
| bool can_remove_cstore (const bb_entry& e, |
| const cbranch_trace& trace) const; |
| |
| // Tells whether the setcc insn of the specified bb_entry can be removed |
| // safely without triggering any side effects. |
| bool can_remove_comparison (const bb_entry& e, |
| const cbranch_trace& trace) const; |
| |
| // Tells whether the two specified comparison rtx can be combined into a |
| // single comparison. |
| bool can_combine_comparisons (const_rtx x, const_rtx y) const; |
| |
| // Tells whether the ccreg usage can be extended from the bb_entry on until |
| // the final cbranch of the trace. |
| bool can_extend_ccreg_usage (const bb_entry& e, |
| const cbranch_trace& trace) const; |
| |
| // Create an insn rtx that performs a logical not (test != 0) on the src_reg |
| // and stores the result in dst_reg. |
| rtx make_not_reg_insn (rtx dst_reg, rtx src_reg) const; |
| |
| // Create an insn rtx that inverts the ccreg. |
| rtx_insn *make_inv_ccreg_insn (void) const; |
| |
| // Adds the specified insn to the set of modified or newly added insns that |
| // might need splitting at the end of the pass. |
| rtx touched_insn (rtx i); |
| |
| // Try to invert the branch condition of the specified trace. |
| bool try_invert_branch_condition (cbranch_trace& trace); |
| |
| // Try to optimize a cbranch trace by combining comparisons in BBs and |
| // eliminate the cstores. |
| bool try_combine_comparisons (cbranch_trace& trace, |
| int cstore_count, int inv_cstore_count, |
| cstore_type_t dominating_cstore); |
| |
| // Try to optimize a cbranch trace by eliminating the cstores in BBs only. |
| bool try_eliminate_cstores (cbranch_trace& trace, |
| int cstore_count, int inv_cstore_count, |
| cstore_type_t dominating_cstore); |
| |
| // Given a branch insn, try to optimize its branch condition. |
| // If any insns are modified or added they are added to 'm_touched_insns'. |
| void try_optimize_cbranch (rtx_insn *i); |
| }; |
| |
| |
| const pass_data sh_treg_combine::default_pass_data = |
| { |
| RTL_PASS, // type |
| "", // name (overwritten by the constructor) |
| OPTGROUP_NONE, // optinfo_flags |
| TV_OPTIMIZE, // tv_id |
| 0, // properties_required |
| 0, // properties_provided |
| 0, // properties_destroyed |
| 0, // todo_flags_start |
| TODO_df_finish | TODO_df_verify // todo_flags_finish |
| }; |
| |
| sh_treg_combine::sh_treg_combine (gcc::context* ctx, bool split_insns, |
| const char* name) |
| : rtl_opt_pass (default_pass_data, ctx), |
| m_split_insns (split_insns), |
| m_ccreg (NULL_RTX) |
| { |
| // Overwrite default name in pass_data base class. |
| this->name = name; |
| } |
| |
| sh_treg_combine::~sh_treg_combine (void) |
| { |
| } |
| |
| void sh_treg_combine::update_ccreg_mode (const_rtx cond) |
| { |
| if (REG_P (XEXP (cond, 0)) && REGNO (XEXP (cond, 0)) != REGNO (m_ccreg)) |
| return; |
| |
| machine_mode m = GET_MODE (XEXP (cond, 0)); |
| if (m == GET_MODE (m_ccreg)) |
| return; |
| |
| PUT_MODE (m_ccreg, m); |
| log_msg ("updated ccreg mode: "); |
| log_rtx (m_ccreg); |
| log_msg ("\n"); |
| } |
| |
| bool |
| sh_treg_combine::is_cmp_eq_zero (const_rtx i) |
| { |
| return i != NULL_RTX && GET_CODE (i) == EQ |
| && REG_P (XEXP (i, 0)) && XEXP (i, 1) == const0_rtx; |
| } |
| |
| sh_treg_combine::branch_condition_type_t |
| sh_treg_combine::branch_condition_type (const_rtx cond) const |
| { |
| if (cond == NULL_RTX) |
| return unknown_branch_condition; |
| |
| if (GET_CODE (cond) == NE |
| && REG_P (XEXP (cond, 0)) && REGNO (XEXP (cond, 0)) == REGNO (m_ccreg) |
| && XEXP (cond, 1) == const0_rtx) |
| return branch_if_true; |
| |
| else if (GET_CODE (cond) == EQ |
| && REG_P (XEXP (cond, 0)) && REGNO (XEXP (cond, 0)) == REGNO (m_ccreg) |
| && XEXP (cond, 1) == const0_rtx) |
| return branch_if_false; |
| |
| else |
| return unknown_branch_condition; |
| } |
| |
| bool |
| sh_treg_combine::is_normal_ccreg (const_rtx x) const |
| { |
| return t_reg_operand (const_cast<rtx> (x), VOIDmode); |
| } |
| |
| bool |
| sh_treg_combine::is_inverted_ccreg (const_rtx x) const |
| { |
| return negt_reg_operand (const_cast<rtx> (x), VOIDmode); |
| } |
| |
| sh_treg_combine::record_return_t |
| sh_treg_combine::record_set_of_reg (rtx reg, rtx_insn *start_insn, |
| bb_entry& new_entry) |
| { |
| log_msg ("\n[bb %d]\n", new_entry.bb->index); |
| |
| if (start_insn == NULL_RTX) |
| log_return (set_not_found, "set of reg not found. empty BB?\n"); |
| |
| new_entry.cstore_type = cstore_unknown; |
| |
| for (rtx_insn *i = start_insn; i != NULL; ) |
| { |
| new_entry.cstore = find_set_of_reg_bb (reg, i); |
| |
| if (new_entry.cstore.set_src () == NULL_RTX) |
| log_return (set_not_found, "set of reg not found (cstore)\n"); |
| |
| log_insn (new_entry.cstore.insn); |
| log_msg ("\n"); |
| |
| if (is_normal_ccreg (new_entry.cstore.set_src ())) |
| { |
| log_msg ("normal condition store\n"); |
| new_entry.cstore_type = cstore_normal; |
| } |
| else if (is_inverted_ccreg (new_entry.cstore.set_src ())) |
| { |
| log_msg ("inverted condition store\n"); |
| new_entry.cstore_type = cstore_inverted; |
| } |
| else if (REG_P (new_entry.cstore.set_src ())) |
| { |
| // If it's a reg-reg copy follow the copied reg. |
| new_entry.cstore_reg_reg_copies.push_back (new_entry.cstore); |
| reg = new_entry.cstore.set_src (); |
| i = new_entry.cstore.insn; |
| |
| log_msg ("reg-reg copy. tracing "); |
| log_rtx (reg); |
| log_msg ("\n"); |
| continue; |
| } |
| else |
| log_return (other_set_found, "not a condition store\n"); |
| |
| gcc_assert (new_entry.cstore_type != cstore_unknown); |
| |
| // Now see how the ccreg was set. |
| // For now it must be in the same BB. |
| log_msg ("tracing ccreg\n"); |
| new_entry.setcc = find_set_of_reg_bb |
| (m_ccreg, prev_nonnote_nondebug_insn_bb (new_entry.cstore.insn)); |
| |
| // If cstore was found but setcc was not found continue anyway, as |
| // for some of the optimization types the setcc is irrelevant. |
| if (new_entry.setcc.set_src () == NULL_RTX) |
| log_return (set_found, "set of ccreg not found\n"); |
| |
| else if (GET_CODE (new_entry.setcc.set_rtx) == SET) |
| { |
| // Also allow insns that set the ccreg, but are not true comparison |
| // insns, as long as they are sets and not e.g. clobbers. |
| log_insn (new_entry.setcc.insn); |
| log_msg ("\n"); |
| return set_found; |
| } |
| else |
| // If cstore was found but setcc was not found continue anyway, as |
| // for some of the optimization types the setcc is irrelevant. |
| log_return (set_found, "unknown set of ccreg\n"); |
| } |
| |
| log_return (set_not_found, "set of reg not found\n"); |
| } |
| |
| bool |
| sh_treg_combine::can_remove_cstore (const bb_entry& e, |
| const cbranch_trace& trace) const |
| { |
| if (volatile_insn_p (PATTERN (e.cstore.insn))) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.cstore.insn); |
| log_return (false, "\nbecause it's volatile\n"); |
| } |
| |
| // On SH there are parallel patterns which store the ccreg multiple times. |
| // In this case it's not safe. |
| rtx cstore_pat = PATTERN (e.cstore.insn); |
| if (GET_CODE (cstore_pat) == PARALLEL) |
| for (int i = 0; i < XVECLEN (cstore_pat, 0); ++i) |
| { |
| rtx x = XVECEXP (cstore_pat, 0, i); |
| |
| // It's the cstore set that we're referring to, ignore that one. |
| if (x != e.cstore.set_rtx |
| && GET_CODE (x) == SET && reg_referenced_p (m_ccreg, x)) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.cstore.insn); |
| log_return (false, "\nbecause it's a multiple ccreg store\n"); |
| } |
| } |
| |
| // If the cstore sets the ccreg (e.g. negc) and the ccreg is used afterwards |
| // it's not safe. |
| if (modified_in_p (m_ccreg, e.cstore.insn) |
| && !(reg_dead_after_insn (m_ccreg, e.cstore.insn) |
| || reg_unused_after_insn (m_ccreg, e.cstore.insn))) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.cstore.insn); |
| log_return (false, "\nbecause it sets the ccreg\n"); |
| } |
| |
| // If the cstore destination reg is copied around check the reg-reg |
| // copies. At every reg-reg copy the copied reg must be dead and there |
| // must not be a usage of the copied regs between the reg-reg copies. |
| // Otherwise we assume that the result of the cstore is used in some |
| // other way. |
| rtx_insn *prev_insn = e.cstore.insn; |
| for (std::vector<set_of_reg>::const_reverse_iterator i = |
| e.cstore_reg_reg_copies.rbegin (); |
| i != e.cstore_reg_reg_copies.rend (); ++i) |
| { |
| if (!reg_dead_after_insn (i->set_src (), i->insn)) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (i->insn); |
| log_return (false, "\nbecause source of reg-reg copy doesn't die\n"); |
| } |
| |
| if (reg_used_between_p (i->set_src (), prev_insn, i->insn)) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (i->insn); |
| log_return (false, "\nbecause reg %d is otherwise used\n", |
| REGNO (i->set_src ())); |
| } |
| |
| prev_insn = i->insn; |
| } |
| |
| // The cstore_dst reg must die after the test before the cbranch, otherwise |
| // it's not safe to remove the cstore. |
| // If the cstore destination reg is copied around check the effective |
| // destination reg of the cstore. The reg-reg copies are recorded in |
| // reverse order, i.e. the most recent reg-reg copy in the insn list |
| // comes first. |
| rtx cstore_dst = e.cstore_reg_reg_copies.empty () |
| ? e.cstore.set_dst () |
| : e.cstore_reg_reg_copies.front ().set_dst (); |
| |
| if (!reg_dead_after_insn (cstore_dst, trace.setcc.insn)) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.cstore.insn); |
| log_return (false, "\nbecause its effective target reg %d doesn't die " |
| "after trace.setcc.insn\n", REGNO (cstore_dst)); |
| } |
| |
| // Also check that the cstore_dst reg is not used in other reachable code |
| // paths before it dies. |
| // Count the uses of the effective cstore_dst reg (i.e. the last known reg |
| // that holds the cstore value after reg-reg copies) in all BBs that can be |
| // reached from bb_entry's BB including the BB of the cstore insn. |
| // If we get more than 1 uses we assume that it's used somewhere else and is |
| // not safe to be removed. |
| int cstore_dst_use_count = trace_reg_uses (cstore_dst, e.cstore.insn, |
| trace.setcc.insn); |
| if (cstore_dst_use_count > 1) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.cstore.insn); |
| log_return (false, "\nbecause its effective target reg %d is used " |
| "in %d other places\n", REGNO (cstore_dst), |
| cstore_dst_use_count - 1); |
| } |
| |
| return true; |
| } |
| |
| bool |
| sh_treg_combine::can_remove_comparison (const bb_entry& e, |
| const cbranch_trace&/* trace*/) const |
| { |
| // If the ccreg is used otherwise between the comparison and the cstore, |
| // it's not safe. |
| if (reg_used_between_p (m_ccreg, e.setcc.insn, e.cstore.insn)) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.setcc.insn); |
| log_return (false, "\nbecause the ccreg is used otherwise\n"); |
| } |
| |
| if (!reg_dead_after_insn (m_ccreg, e.cstore.insn) |
| && !reg_unused_after_insn (m_ccreg, e.cstore.insn)) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.cstore.insn); |
| log_return (false, "\nbecause ccreg is not dead or unused afterwards\n"); |
| } |
| |
| // On SH there are also multiple set patterns that can be used for |
| // comparisons, such as "shll". It's not safe to remove those. |
| if (multiple_sets (e.setcc.insn)) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (e.cstore.insn); |
| log_return (false, "\nbecause it's a multiple set\n"); |
| } |
| |
| return true; |
| } |
| |
| rtx |
| sh_treg_combine::make_not_reg_insn (rtx dst_reg, rtx src_reg) const |
| { |
| // On SH we can do only SImode and DImode comparisons. |
| if (! (GET_MODE (src_reg) == SImode || GET_MODE (src_reg) == DImode)) |
| return NULL; |
| |
| // On SH we can store the ccreg into an SImode or DImode reg only. |
| if (! (GET_MODE (dst_reg) == SImode || GET_MODE (dst_reg) == DImode)) |
| return NULL; |
| |
| start_sequence (); |
| |
| emit_insn (gen_rtx_SET (m_ccreg, gen_rtx_fmt_ee (EQ, SImode, |
| src_reg, const0_rtx))); |
| |
| if (GET_MODE (dst_reg) == SImode) |
| emit_move_insn (dst_reg, m_ccreg); |
| else if (GET_MODE (dst_reg) == DImode) |
| { |
| emit_move_insn (gen_lowpart (SImode, dst_reg), m_ccreg); |
| emit_move_insn (gen_highpart (SImode, dst_reg), const0_rtx); |
| } |
| else |
| gcc_unreachable (); |
| |
| rtx i = get_insns (); |
| end_sequence (); |
| |
| return i; |
| } |
| |
| rtx_insn * |
| sh_treg_combine::make_inv_ccreg_insn (void) const |
| { |
| start_sequence (); |
| rtx_insn *i = emit_insn (gen_rtx_SET (m_ccreg, |
| gen_rtx_fmt_ee (XOR, GET_MODE (m_ccreg), |
| m_ccreg, const1_rtx))); |
| end_sequence (); |
| return i; |
| } |
| |
| rtx |
| sh_treg_combine::touched_insn (rtx i) |
| { |
| m_touched_insns.push_back (i); |
| return i; |
| } |
| |
| bool |
| sh_treg_combine::can_combine_comparisons (const_rtx x, const_rtx y) const |
| { |
| if (GET_CODE (x) != GET_CODE (y)) |
| return false; |
| |
| rtx x_op0 = XEXP (x, 0); |
| rtx x_op1 = XEXP (x, 1); |
| |
| rtx y_op0 = XEXP (y, 0); |
| rtx y_op1 = XEXP (y, 1); |
| |
| if (!REG_P (x_op0) || !REG_P (y_op0)) |
| return false; |
| |
| if (GET_MODE (x_op0) != GET_MODE (y_op0)) |
| return false; |
| |
| // rtx_equal_p also compares the reg numbers which we do not care about |
| // here, as long as both are regs and the modes are the same. |
| if (REG_P (x_op1)) |
| return REG_P (y_op1) && GET_MODE (x_op1) == GET_MODE (y_op1); |
| |
| return rtx_equal_p (x_op1, y_op1); |
| } |
| |
| bool |
| sh_treg_combine::can_extend_ccreg_usage (const bb_entry& e, |
| const cbranch_trace& trace) const |
| { |
| // Check if the ccreg is not modified by other insins in the BB path until |
| // the final cbranch of the trace. |
| // Start checking after the cstore that follows the setcc, assuming that |
| // the cstore will be removed. |
| |
| // The assumption here is that the specified bb_entry's BB is a direct |
| // predecessor of the trace.cbranch_insn's BB. |
| if (e.bb != trace.bb () && !is_adjacent_bb (e.bb, trace.bb ())) |
| log_return (false, |
| "can't extend ccreg usage -- [bb %d] and [bb %d] are not adjacent\n", |
| e.bb->index, trace.bb ()->index); |
| |
| if (e.cstore.empty ()) |
| log_return (false, "can't extend ccreg usage -- no cstore\n"); |
| |
| // The entry's cstore is in the same BB as the final cbranch. |
| if (e.bb == trace.bb ()) |
| { |
| if (reg_set_between_p (m_ccreg, e.cstore.insn, trace.setcc.insn)) |
| log_return (false, |
| "can't extend ccreg usage -- it's modified between e.cstore.insn " |
| "and trace.setcc.insn"); |
| else |
| return true; |
| } |
| |
| // The entry's cstore and the final cbranch are in different BBs. |
| if (reg_set_between_p (m_ccreg, e.cstore.insn, NEXT_INSN (BB_END (e.bb)))) |
| log_return (false, |
| "can't extend ccreg usage -- it's modified in [bb %d]", e.bb->index); |
| |
| if (reg_set_between_p (m_ccreg, PREV_INSN (BB_HEAD (trace.bb ())), |
| trace.setcc.insn)) |
| log_return (false, |
| "can't extend ccreg usage -- it's modified in [bb %d]", |
| trace.bb ()->index); |
| |
| return true; |
| } |
| |
| bool |
| sh_treg_combine::try_invert_branch_condition (cbranch_trace& trace) |
| { |
| log_msg ("inverting branch condition\n"); |
| |
| rtx& comp = trace.branch_condition_rtx_ref (); |
| |
| rtx_code rev_cmp_code = reversed_comparison_code (comp, trace.cbranch_insn); |
| |
| if (rev_cmp_code == UNKNOWN) |
| log_return (false, "reversed_comparison_code = UNKNOWN\n"); |
| |
| validate_change (trace.cbranch_insn, &comp, |
| gen_rtx_fmt_ee (rev_cmp_code, |
| GET_MODE (comp), XEXP (comp, 0), |
| XEXP (comp, 1)), |
| 1); |
| |
| if (verify_changes (num_validated_changes ())) |
| confirm_change_group (); |
| else |
| log_return (false, "verify_changed failed\n"); |
| |
| touched_insn (trace.cbranch_insn); |
| return true; |
| } |
| |
| bool |
| sh_treg_combine::try_combine_comparisons (cbranch_trace& trace, |
| int cstore_count, |
| int inv_cstore_count, |
| cstore_type_t dominating_cstore) |
| { |
| log_msg ("\ntry_combine_comparisons\n"); |
| |
| // This function will always try to create new pseudos. |
| if (!can_create_pseudo_p ()) |
| log_return (false, "can't create pseudos\n"); |
| |
| // Check that all ccset insns are comparisons and all comparison types in |
| // all BBs are the same and could be combined into one single comparison. |
| rtx comp = NULL_RTX; |
| rtx comp_insn = NULL_RTX; |
| |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| { |
| int i_empty_count = i->setcc.empty () + i->cstore.empty (); |
| |
| // A completly empty entry is OK (could be the BB of the cbranch). |
| if (i_empty_count == 2) |
| continue; |
| |
| // Otherwise we need both, the setcc and the cstore. |
| if (i_empty_count != 0) |
| log_return (false, "bb entry is not a setcc cstore pair\n"); |
| |
| rtx other_comp = i->comparison_rtx (); |
| |
| if (!COMPARISON_P (other_comp)) |
| { |
| log_msg ("setcc is not a comparison:\n"); |
| log_rtx (other_comp); |
| log_return (false, "\n"); |
| } |
| |
| if (comp_insn == NULL_RTX) |
| { |
| comp = other_comp; |
| comp_insn = i->setcc.insn; |
| } |
| else if (!can_combine_comparisons (comp, other_comp)) |
| return false; |
| |
| // The goal here is to eliminate all cstores and comparisons in the BBs. |
| // Thus check if every cstore can actually be removed safely. |
| if (!can_remove_cstore (*i, trace) || !can_remove_comparison (*i, trace)) |
| return false; |
| } |
| |
| // FIXME: The first operand of the comparison must be a simple reg. |
| // This effectively prohibits combining div0s comparisons such as |
| // (lt:SI (xor:SI (reg:SI) (reg:SI))) |
| if (!REG_P (XEXP (comp, 0))) |
| { |
| log_msg ("comparison operand 0\n"); |
| log_rtx (XEXP (comp, 0)); |
| log_return (false, "\nis not a reg\n"); |
| } |
| |
| rtx comp_op0 = gen_reg_rtx (GET_MODE (XEXP (comp, 0))); |
| rtx comp_op1 = REG_P (XEXP (comp, 1)) |
| ? gen_reg_rtx (GET_MODE (XEXP (comp, 1))) |
| : XEXP (comp, 1); |
| |
| // If there are both, inverting and non-inverting cstores, they can only |
| // be eliminated if the comparison can be inverted. We assume that the |
| // comparison insns that we find are already minimal and canonicalized. |
| // There is one special case though, where an integer comparison |
| // (eq (reg) (const_int 0)) |
| // can be inverted with a sequence |
| // (set (t) (eq (reg) (const_int 0)) |
| // (set (reg) (t)) |
| // (eq (reg) (const_int 0)) |
| // |
| // FIXME: On SH2A it might be better to use the nott insn in this case, |
| // i.e. do the try_eliminate_cstores approach instead. |
| if (inv_cstore_count != 0 && cstore_count != 0) |
| { |
| if (make_not_reg_insn (comp_op0, comp_op0) == NULL_RTX) |
| log_return (false, "make_not_reg_insn failed.\n"); |
| |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| { |
| if (i->setcc.empty () || i->cstore.empty ()) |
| continue; |
| |
| if (i->cstore_type != dominating_cstore |
| && !is_cmp_eq_zero (i->comparison_rtx ())) |
| { |
| log_msg ("can't invert comparison in insn\n"); |
| log_insn (i->setcc.insn); |
| log_return (false, |
| "\nbecause it's not a (eq (reg) (const_int 0))\n"); |
| } |
| } |
| } |
| |
| if (dominating_cstore == cstore_normal |
| && !try_invert_branch_condition (trace)) |
| return false; |
| |
| // Replace the test insn before the cbranch with the common comparison. |
| // Instead of creating a new insn from scratch we copy the common comparison |
| // pattern. This simplifies handling parallel comparison patterns, such as |
| // FP comparisons on SH, which have an extra use on FPSCR. |
| log_msg ("installing common comparison in [bb %d]\n", trace.bb ()->index); |
| |
| rtx common_comp_pat = copy_rtx (PATTERN (comp_insn)); |
| rtx common_comp = const_cast<rtx> (set_of (m_ccreg, common_comp_pat)); |
| |
| gcc_assert (common_comp != NULL_RTX); |
| |
| XEXP (XEXP (common_comp, 1), 0) = comp_op0; |
| XEXP (XEXP (common_comp, 1), 1) = comp_op1; |
| |
| log_rtx (common_comp_pat); |
| log_msg ("\n"); |
| |
| rtx common_comp_insn = touched_insn (emit_insn_after (common_comp_pat, |
| trace.setcc.insn)); |
| |
| if (REG_P (comp_op0)) |
| add_reg_note (common_comp_insn, REG_DEAD, copy_rtx (comp_op0)); |
| if (REG_P (comp_op1)) |
| add_reg_note (common_comp_insn, REG_DEAD, copy_rtx (comp_op1)); |
| |
| delete_insn (trace.setcc.insn); |
| |
| // Replace comparison and cstore insns with reg-reg moves in all BBs. |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| { |
| if (i->setcc.empty () || i->cstore.empty ()) |
| continue; |
| |
| rtx i_comp_op0 = XEXP (i->comparison_rtx (), 0); |
| rtx i_comp_op1 = XEXP (i->comparison_rtx (), 1); |
| |
| if (i->cstore_type == dominating_cstore) |
| { |
| log_msg ("replacing comparison and cstore with reg move " |
| "in [bb %d]\n", i->bb->index); |
| |
| rtx new_i = touched_insn ( |
| emit_insn_after (gen_move_insn (comp_op0, i_comp_op0), |
| i->setcc.insn)); |
| |
| if (REG_P (i_comp_op0) |
| && reg_dead_after_insn (i_comp_op0, i->setcc.insn)) |
| add_reg_note (new_i, REG_DEAD, copy_rtx (i_comp_op0)); |
| |
| // If the second operand is a reg, have to emit a move insn. |
| // Otherwise assume it's a const_int and just reference it. |
| if (REG_P (comp_op1)) |
| { |
| new_i = touched_insn ( |
| emit_insn_after (gen_move_insn (comp_op1, i_comp_op1), |
| i->setcc.insn)); |
| |
| if (reg_dead_after_insn (i_comp_op1, i->setcc.insn)) |
| add_reg_note (new_i, REG_DEAD, copy_rtx (i_comp_op1)); |
| } |
| } |
| else |
| { |
| log_msg ("replacing comparison and cstore with inverting reg move " |
| "in [bb %d]\n", i->bb->index); |
| |
| rtx new_i = make_not_reg_insn (comp_op0, i_comp_op0); |
| if (REG_P (i_comp_op0) |
| && reg_dead_after_insn (i_comp_op0, i->setcc.insn)) |
| add_reg_note (new_i, REG_DEAD, copy_rtx (i_comp_op0)); |
| |
| touched_insn (emit_insn_after (new_i, i->setcc.insn)); |
| } |
| |
| delete_insn (i->cstore.insn); |
| delete_insn (i->setcc.insn); |
| } |
| |
| return true; |
| } |
| |
| bool |
| sh_treg_combine::try_eliminate_cstores (cbranch_trace& trace, |
| int cstore_count, int inv_cstore_count, |
| cstore_type_t dominating_cstore) |
| { |
| log_msg ("\ntry_eliminate_cstores\n"); |
| |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| { |
| // A completly empty entry is OK (could be the BB of the cbranch). |
| if (i->setcc.empty () && i->cstore.empty ()) |
| continue; |
| |
| // We're going to eliminate cstores, but for that they have to be |
| // there. We don't care about the setcc in this case. |
| if (i->cstore.empty ()) |
| log_return (false, "bb entry cstore empty -- aborting\n"); |
| |
| // The goal here is to eliminate all cstores in the BBs and extend the |
| // ccreg usage. |
| if (!can_extend_ccreg_usage (*i, trace)) |
| return false; |
| |
| // If the cstore can't be removed we can keep it around as long as |
| // it doesn't modify the ccreg. |
| if (!can_remove_cstore (*i, trace) |
| && modified_in_p (m_ccreg, i->cstore.insn)) |
| log_return (false, "cstore sets ccreg -- aborting\n"); |
| } |
| |
| // If there are both, inverting and non-inverting cstores, we'll have to |
| // invert the ccreg as a replacement for one of them. |
| if (cstore_count != 0 && inv_cstore_count != 0) |
| { |
| rtx_insn *i = make_inv_ccreg_insn (); |
| if (recog_memoized (i) < 0) |
| { |
| log_msg ("failed to match ccreg inversion insn:\n"); |
| log_rtx (PATTERN (i)); |
| log_return (false, "\naborting\n"); |
| } |
| } |
| |
| if (dominating_cstore == cstore_normal |
| && !try_invert_branch_condition (trace)) |
| return false; |
| |
| // Eliminate cstores in all BBs. |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| { |
| if (i->cstore.empty ()) |
| continue; |
| |
| if (i->cstore_type == dominating_cstore) |
| log_msg ("removing cstore in [bb %d]\n", i->bb->index); |
| else |
| { |
| log_msg ("replacing cstore with ccreg inversion in [bb %d]\n", |
| i->bb->index); |
| |
| touched_insn ( |
| emit_insn_after (make_inv_ccreg_insn (), i->cstore.insn)); |
| } |
| |
| if (can_remove_cstore (*i, trace)) |
| delete_insn (i->cstore.insn); |
| } |
| |
| log_msg ("removing test insn before cbranch\n"); |
| delete_insn (trace.setcc.insn); |
| return true; |
| } |
| |
| void |
| sh_treg_combine::try_optimize_cbranch (rtx_insn *insn) |
| { |
| cbranch_trace trace (insn); |
| |
| log_msg ("\n\n--------------------------------------\n"); |
| log_msg ("found cbranch insn in [bb %d]:\n", trace.bb ()->index); |
| log_insn (insn); |
| |
| trace.cbranch_type = branch_condition_type (trace.branch_condition_rtx ()); |
| |
| if (trace.cbranch_type == branch_if_true) |
| log_msg ("condition: branch if true\n"); |
| else if (trace.cbranch_type == branch_if_false) |
| log_msg ("condition: branch if false\n"); |
| else |
| { |
| log_msg ("unknown branch condition\n"); |
| log_rtx (trace.branch_condition_rtx ()); |
| log_return_void ("\n"); |
| } |
| |
| update_ccreg_mode (trace.branch_condition_rtx ()); |
| |
| // Scan the insns backwards for an insn that sets the ccreg by testing a |
| // reg against zero like |
| // (set (reg ccreg) (eq (reg) (const_int 0))) |
| // The testing insn could also be outside of the current basic block, but |
| // for now we limit the search to the current basic block. |
| trace.setcc = find_set_of_reg_bb |
| (m_ccreg, prev_nonnote_nondebug_insn_bb (insn)); |
| |
| if (trace.setcc.set_src () == NULL_RTX) |
| log_return_void ("could not find set of ccreg in current BB\n"); |
| |
| if (!is_cmp_eq_zero (trace.setcc.set_src ()) |
| && !is_inverted_ccreg (trace.setcc.set_src ())) |
| { |
| log_msg ("unsupported set of ccreg in current BB: "); |
| log_rtx (trace.setcc.set_src ()); |
| log_return_void ("\n"); |
| } |
| |
| rtx trace_reg = XEXP (trace.setcc.set_src (), 0); |
| |
| log_msg ("set of ccreg:\n"); |
| log_insn (trace.setcc.insn); |
| |
| // See if we can remove the trace.setcc insn safely. |
| if (reg_used_between_p (m_ccreg, trace.setcc.insn, trace.cbranch_insn)) |
| log_return_void ("ccreg used between testing insn and branch insn\n"); |
| |
| if (volatile_insn_p (PATTERN (trace.setcc.insn))) |
| { |
| log_msg ("can't remove insn\n"); |
| log_insn (trace.setcc.insn); |
| log_return_void ("\nbecause it's volatile\n"); |
| } |
| |
| // If the ccreg is inverted before cbranch try inverting the branch |
| // condition. |
| if (is_inverted_ccreg (trace.setcc.set_src ())) |
| { |
| if (!trace.can_invert_condition ()) |
| log_return_void ("branch condition can't be inverted - aborting\n"); |
| |
| if (try_invert_branch_condition (trace)) |
| delete_insn (trace.setcc.insn); |
| |
| return; |
| } |
| |
| // Now that we have an insn which tests some reg and sets the condition |
| // reg before the conditional branch, try to figure out how that tested |
| // reg was formed, i.e. find all the insns that set the tested reg in |
| // some way. |
| // The tested reg might be set in multiple basic blocks so we need to |
| // check all basic blocks which can reach this current basic block. |
| // If the set of reg is an inverting or non-inverting store of the condition |
| // register, check how the ccreg value was obtained. |
| log_msg ("\ntracing "); |
| log_rtx (trace_reg); |
| log_msg ("\n"); |
| |
| |
| // First check the basic block where the conditional branch is in. |
| // If we find it here there's no point in checking other BBs. |
| trace.bb_entries.push_front (bb_entry (trace.bb ())); |
| |
| record_return_t res = record_set_of_reg |
| (trace_reg, prev_nonnote_nondebug_insn_bb (trace.setcc.insn), |
| trace.bb_entries.front ()); |
| |
| if (res == other_set_found) |
| log_return_void ("other set found - aborting trace\n"); |
| else if (res == set_not_found) |
| { |
| // It seems the initial search in the BB of the conditional branch |
| // didn't find anything. Now look in all predecessor BBs. |
| for (edge_iterator ei = ei_start (trace.bb ()->preds); |
| !ei_end_p (ei); ei_next (&ei)) |
| { |
| edge e = ei_edge (ei); |
| trace.bb_entries.push_front (bb_entry (e->src)); |
| |
| res = record_set_of_reg (trace_reg, BB_END (e->src), |
| trace.bb_entries.front ()); |
| if (res != set_found) |
| log_return_void ("set not found - aborting trace\n"); |
| } |
| } |
| |
| if (dump_file != NULL) |
| { |
| log_msg ("\ncbranch trace summary:\n"); |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| { |
| log_msg ("\n[bb %d]\n", i->bb->index); |
| if (!i->setcc.empty ()) |
| { |
| log_rtx (i->setcc.set_rtx); |
| log_msg ("\n"); |
| } |
| if (!i->cstore.empty ()) |
| { |
| log_rtx (i->cstore.set_rtx); |
| log_msg ("\n"); |
| } |
| |
| for (std::vector<set_of_reg>::const_reverse_iterator j = |
| i->cstore_reg_reg_copies.rbegin (); |
| j != i->cstore_reg_reg_copies.rend (); ++j) |
| { |
| log_rtx (j->set_rtx); |
| log_msg ("\n"); |
| } |
| } |
| |
| log_rtx (trace.setcc.set_rtx); |
| log_msg ("\n"); |
| log_rtx (PATTERN (trace.cbranch_insn)); |
| log_msg ("\n"); |
| } |
| |
| // Check that we don't have any empty BBs. |
| // Only the BB with the cbranch may be empty. |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| if (i->setcc.empty () && i->cstore.empty () && i->bb != trace.bb ()) |
| log_return_void ("\n[bb %d] is empty - aborting.\n", i->bb->index); |
| |
| // Determine the dominating cstore type |
| // FIXME: Try to take the probabilities of the BBs into account somehow. |
| int cstore_count = 0; |
| int inv_cstore_count = 0; |
| |
| for (std::list<bb_entry>::const_iterator i = trace.bb_entries.begin (); |
| i != trace.bb_entries.end (); ++i) |
| { |
| if (i->cstore_type == cstore_normal) |
| cstore_count += 1; |
| else if (i->cstore_type == cstore_inverted) |
| inv_cstore_count += 1; |
| } |
| |
| log_msg ("cstore count = %d inverted cstore count = %d\n", |
| cstore_count, inv_cstore_count); |
| |
| // This puts a priority on inverting cstores. |
| cstore_type_t dominating_cstore = inv_cstore_count >= cstore_count |
| ? cstore_inverted |
| : cstore_normal; |
| |
| if (dominating_cstore == cstore_inverted) |
| log_msg ("will try to eliminate inverted cstore\n"); |
| else if (dominating_cstore == cstore_normal) |
| { |
| log_msg ("will try to eliminate normal cstore\n"); |
| if (!trace.can_invert_condition ()) |
| log_return_void ("branch condition can't be inverted - aborting\n"); |
| } |
| else |
| gcc_unreachable (); |
| |
| if (try_combine_comparisons (trace, cstore_count, inv_cstore_count, |
| dominating_cstore)) |
| return; |
| |
| try_eliminate_cstores (trace, cstore_count, inv_cstore_count, |
| dominating_cstore); |
| } |
| |
| bool |
| sh_treg_combine::gate (function *) |
| { |
| return optimize > 0; |
| } |
| |
| unsigned int |
| sh_treg_combine::execute (function *fun) |
| { |
| unsigned int ccr0 = INVALID_REGNUM; |
| unsigned int ccr1 = INVALID_REGNUM; |
| |
| if (targetm.fixed_condition_code_regs (&ccr0, &ccr1) |
| && ccr0 != INVALID_REGNUM) |
| { |
| // Initially create a reg rtx with VOIDmode. |
| // When the first conditional branch is discovered, the mode is changed |
| // to the mode that is actually used by the target. |
| m_ccreg = gen_rtx_REG (VOIDmode, ccr0); |
| } |
| |
| if (m_ccreg == NULL_RTX) |
| log_return (0, "no ccreg.\n\n"); |
| |
| if (STORE_FLAG_VALUE != 1) |
| log_return (0, "unsupported STORE_FLAG_VALUE %d", STORE_FLAG_VALUE); |
| |
| log_msg ("ccreg: "); |
| log_rtx (m_ccreg); |
| log_msg (" STORE_FLAG_VALUE = %d\n", STORE_FLAG_VALUE); |
| |
| // Look for basic blocks that end with a conditional branch or for |
| // conditional insns and try to optimize them. |
| basic_block bb; |
| FOR_EACH_BB_FN (bb, fun) |
| { |
| rtx_insn* i = BB_END (bb); |
| if (i == NULL || i == PREV_INSN (BB_HEAD (bb))) |
| continue; |
| |
| // A conditional branch is always the last insn of a basic block. |
| if (any_condjump_p (i) && onlyjump_p (i)) |
| { |
| try_optimize_cbranch (i); |
| i = PREV_INSN (i); |
| } |
| |
| // Check all insns in block for conditional insns. |
| for (; i != NULL && i != PREV_INSN (BB_HEAD (bb)); i = PREV_INSN (i)) |
| if (is_conditional_insn (i)) |
| try_optimize_cbranch (i); |
| } |
| |
| log_msg ("\n\n"); |
| |
| // If new insns are created and this pass is executed after all insns |
| // have been split already, we must split the insns we've changed or added |
| // ourselves here. |
| // FIXME: Multi-word operations (which emit multiple insns) are not handled |
| // properly here, since only one insn will end up in 'm_touched_insns'. |
| // On SH this is not a problem though. |
| if (m_split_insns) |
| for (std::vector<rtx>::const_iterator i = m_touched_insns.begin (); |
| i != m_touched_insns.end (); ++i) |
| { |
| log_msg ("trying to split insn:\n"); |
| log_insn (*i); |
| log_msg ("\n"); |
| try_split (PATTERN (*i), safe_as_a <rtx_insn *> (*i), 0); |
| } |
| |
| m_touched_insns.clear (); |
| log_return (0, "\n\n"); |
| } |
| |
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| // This allows instantiating the pass somewhere else without having to pull |
| // in a header file. |
| opt_pass* |
| make_pass_sh_treg_combine (gcc::context* ctx, bool split_insns, |
| const char* name) |
| { |
| return new sh_treg_combine (ctx, split_insns, name); |
| } |