/* An SH specific RTL pass that tries to optimize clrt and sett insns. 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 . */ #define IN_TARGET_CODE 1 #include "config.h" #define INCLUDE_ALGORITHM #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" #include "backend.h" #include "target.h" #include "rtl.h" #include "df.h" #include "cfgrtl.h" #include "tree-pass.h" /* This pass tries to eliminate unnecessary sett or clrt instructions in cases where the ccreg value is already known to be the same as the constant set would set it to. This is done as follows: Check every BB's insn and see if it's a sett or clrt. Once a sett or clrt insn is hit, walk insns and predecessor basic blocks backwards from that insn and determine all possible ccreg values from all basic block paths. Insns that set the ccreg value in some way (simple set, clobber etc) are recorded. Conditional branches where one edge leads to the sett / clrt insn are also recorded, since for each edge in the conditional branch the ccreg value is known constant. After collecting all possible ccreg values at the sett / clrt insn, check that all the values are the same. If that value is the same as the sett / clrt insn would set the ccreg to, the sett / clrt insn can be eliminated. */ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // 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) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // RTL pass class class sh_optimize_sett_clrt : public rtl_opt_pass { public: sh_optimize_sett_clrt (gcc::context* ctx, const char* name); virtual ~sh_optimize_sett_clrt (void); virtual bool gate (function*); virtual unsigned int execute (function* fun); private: static const pass_data default_pass_data; struct ccreg_value { // The insn at which the ccreg value was determined. // Might be NULL if e.g. an unknown value is recorded for an // empty basic block. rtx_insn *insn; // The basic block where the insn was discovered. basic_block bb; // The value of ccreg. If NULL_RTX, the exact value is not known, but // the ccreg is changed in some way (e.g. clobbered). rtx value; }; // Update the mode of the captured m_ccreg with the specified mode. void update_ccreg_mode (machine_mode m); // Given an insn pattern, check if it sets the ccreg to a constant value // of either zero or STORE_FLAG_VALUE. If so, return the value rtx, // NULL_RTX otherwise. rtx const_setcc_value (rtx pat) const; // Given a start insn and its basic block, recursively determine all // possible ccreg values in all basic block paths that can lead to the // start insn. bool find_last_ccreg_values (rtx_insn *start_insn, basic_block bb, std::vector& values_out, std::vector& prev_visited_bb) const; // Given a cbranch insn, its basic block and another basic block, determine // the value to which the ccreg will be set after jumping/falling through to // the specified target basic block. bool sh_cbranch_ccreg_value (rtx_insn *cbranch_insn, basic_block cbranch_insn_bb, basic_block branch_target_bb) const; // Check whether all of the ccreg values are the same. static bool all_ccreg_values_equal (const std::vector& values); // Remove REG_DEAD and REG_UNUSED notes from insns of the specified // ccreg_value entries. void remove_ccreg_dead_unused_notes (std::vector& values) const; // rtx of the ccreg that is obtained from the target. rtx m_ccreg; }; const pass_data sh_optimize_sett_clrt::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 0 // todo_flags_finish }; sh_optimize_sett_clrt::sh_optimize_sett_clrt (gcc::context* ctx, const char* name) : rtl_opt_pass (default_pass_data, ctx), m_ccreg (NULL_RTX) { // Overwrite default name in pass_data base class. this->name = name; } sh_optimize_sett_clrt::~sh_optimize_sett_clrt (void) { } bool sh_optimize_sett_clrt::gate (function*) { return optimize > 0; } unsigned int sh_optimize_sett_clrt::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 constant setcc 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); if (!df_regs_ever_live_p (ccr0)) log_return (0, "ccreg never live\n\n"); // Output vector for find_known_ccreg_values. std::vector ccreg_values; ccreg_values.reserve (32); // Something for recording visited basic blocks to avoid infinite recursion. std::vector visited_bbs; visited_bbs.reserve (32); // Look for insns that set the ccreg to a constant value and see if it can // be optimized. basic_block bb; FOR_EACH_BB_REVERSE_FN (bb, fun) for (rtx_insn *next_i, *i = NEXT_INSN (BB_HEAD (bb)); i != NULL_RTX && i != BB_END (bb); i = next_i) { next_i = NEXT_INSN (i); if (!INSN_P (i) || !NONDEBUG_INSN_P (i)) continue; rtx setcc_val = const_setcc_value (PATTERN (i)); if (setcc_val != NULL_RTX) { update_ccreg_mode (GET_MODE (XEXP (PATTERN (i), 0))); log_msg ("\n\nfound const setcc insn in [bb %d]: \n", bb->index); log_insn (i); log_msg ("\n"); ccreg_values.clear (); visited_bbs.clear (); bool ok = find_last_ccreg_values (PREV_INSN (i), bb, ccreg_values, visited_bbs); log_msg ("number of ccreg values collected: %u\n", (unsigned int)ccreg_values.size ()); // If all the collected values are equal and are equal to the // constant value of the setcc insn, the setcc insn can be // removed. if (ok && all_ccreg_values_equal (ccreg_values) && rtx_equal_p (ccreg_values.front ().value, setcc_val)) { log_msg ("all values are "); log_rtx (setcc_val); log_msg ("\n"); delete_insn (i); remove_ccreg_dead_unused_notes (ccreg_values); } } } log_return (0, "\n\n"); } void sh_optimize_sett_clrt::update_ccreg_mode (machine_mode m) { if (GET_MODE (m_ccreg) == m) return; PUT_MODE (m_ccreg, m); log_msg ("updated ccreg mode: "); log_rtx (m_ccreg); log_msg ("\n\n"); } rtx sh_optimize_sett_clrt::const_setcc_value (rtx pat) const { if (GET_CODE (pat) == SET && REG_P (XEXP (pat, 0)) && REGNO (XEXP (pat, 0)) == REGNO (m_ccreg) && CONST_INT_P (XEXP (pat, 1)) && (INTVAL (XEXP (pat, 1)) == 0 || INTVAL (XEXP (pat, 1)) == STORE_FLAG_VALUE)) return XEXP (pat, 1); else return NULL_RTX; } bool sh_optimize_sett_clrt ::sh_cbranch_ccreg_value (rtx_insn *cbranch_insn, basic_block cbranch_insn_bb, basic_block branch_target_bb) const { rtx pc_set_rtx = pc_set (cbranch_insn); gcc_assert (pc_set_rtx != NULL_RTX); gcc_assert (branch_target_bb != NULL); rtx cond = XEXP (XEXP (pc_set_rtx, 1), 0); bool branch_if; if (GET_CODE (cond) == NE && REG_P (XEXP (cond, 0)) && REGNO (XEXP (cond, 0)) == REGNO (m_ccreg) && XEXP (cond, 1) == const0_rtx) 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) branch_if = false; else gcc_unreachable (); if (branch_target_bb == BRANCH_EDGE (cbranch_insn_bb)->dest) return branch_if; else if (branch_target_bb == FALLTHRU_EDGE (cbranch_insn_bb)->dest) return !branch_if; else gcc_unreachable (); } bool sh_optimize_sett_clrt ::find_last_ccreg_values (rtx_insn *start_insn, basic_block bb, std::vector& values_out, std::vector& prev_visited_bb) const { // FIXME: For larger CFGs this will unnecessarily re-visit basic blocks. // Once a basic block has been visited, the result should be stored in // some container so that it can be looked up quickly eliminating the // re-visits. log_msg ("looking for ccreg values in [bb %d] ", bb->index); if (!prev_visited_bb.empty ()) log_msg ("(prev visited [bb %d])", prev_visited_bb.back ()->index); log_msg ("\n"); for (rtx_insn *i = start_insn; i != NULL && i != PREV_INSN (BB_HEAD (bb)); i = PREV_INSN (i)) { if (!INSN_P (i)) continue; if (reg_set_p (m_ccreg, i)) { const_rtx set_rtx = set_of (m_ccreg, i); ccreg_value v; v.insn = i; v.bb = bb; v.value = set_rtx != NULL_RTX && GET_CODE (set_rtx) == SET ? XEXP (set_rtx, 1) : NULL_RTX; log_msg ("found setcc in [bb %d] in insn:\n", bb->index); log_insn (i); log_msg ("\nccreg value: "); log_rtx (v.value); log_msg ("\n"); values_out.push_back (v); return true; } if (any_condjump_p (i) && onlyjump_p (i) && !prev_visited_bb.empty ()) { // For a conditional branch the ccreg value will be a known constant // of either 0 or STORE_FLAG_VALUE after branching/falling through // to one of the two successor BBs. Record the value for the BB // where we came from. log_msg ("found cbranch in [bb %d]:\n", bb->index); log_insn (i); ccreg_value v; v.insn = i; v.bb = bb; v.value = GEN_INT (sh_cbranch_ccreg_value (i, bb, prev_visited_bb.back ())); log_msg (" branches to [bb %d] with ccreg value ", prev_visited_bb.back ()->index); log_rtx (v.value); log_msg ("\n"); values_out.push_back (v); return true; } } // If here, we've walked up all the insns of the current basic block // and none of them seems to modify the ccreg. // In this case, check the predecessor basic blocks. unsigned int pred_bb_count = 0; // If the current basic block is not in the stack of previously visited // basic blocks yet, we can recursively check the predecessor basic blocks. // Otherwise we have a loop in the CFG and recursing again will result in // an infinite loop. if (std::find (prev_visited_bb.rbegin (), prev_visited_bb.rend (), bb) == prev_visited_bb.rend ()) { prev_visited_bb.push_back (bb); for (edge_iterator ei = ei_start (bb->preds); !ei_end_p (ei); ei_next (&ei)) { if (ei_edge (ei)->flags & EDGE_COMPLEX) log_return (false, "aborting due to complex edge\n"); basic_block pred_bb = ei_edge (ei)->src; pred_bb_count += 1; if (!find_last_ccreg_values (BB_END (pred_bb), pred_bb, values_out, prev_visited_bb)) return false; } prev_visited_bb.pop_back (); } else log_msg ("loop detected for [bb %d]\n", bb->index); log_msg ("[bb %d] pred_bb_count = %u\n", bb->index, pred_bb_count); if (pred_bb_count == 0) { // If we haven't checked a single predecessor basic block, the current // basic block is probably a leaf block and we don't know the ccreg value. log_msg ("unknown ccreg value for [bb %d]\n", bb->index); ccreg_value v; v.insn = BB_END (bb); v.bb = bb; v.value = NULL_RTX; values_out.push_back (v); } return true; } bool sh_optimize_sett_clrt ::all_ccreg_values_equal (const std::vector& values) { if (values.empty ()) return false; rtx last_value = values.front ().value; // If the ccreg is modified in the insn but the exact value is not known // the value rtx might be null. if (last_value == NULL_RTX) return false; for (std::vector::const_iterator i = values.begin (); i != values.end (); ++i) if (i->value == NULL_RTX || !rtx_equal_p (last_value, i->value)) return false; return true; } void sh_optimize_sett_clrt ::remove_ccreg_dead_unused_notes (std::vector& values) const { for (std::vector::iterator i = values.begin (); i != values.end (); ++i) { if (i->insn == NULL_RTX) continue; rtx n = find_regno_note (i->insn, REG_DEAD, REGNO (m_ccreg)); if (n != NULL_RTX) remove_note (i->insn, n); n = find_regno_note (i->insn, REG_UNUSED, REGNO (m_ccreg)); if (n != NULL_RTX) remove_note (i->insn, n); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // This allows instantiating the pass somewhere else without having to pull // in a header file. opt_pass* make_pass_sh_optimize_sett_clrt (gcc::context* ctx, const char* name) { return new sh_optimize_sett_clrt (ctx, name); }