| /* Back-propagation of usage information to definitions. |
| Copyright (C) 2015-2021 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/>. */ |
| |
| /* This pass propagates information that is common to all uses of an SSA |
| name back up through the sequence of statements that generate it, |
| simplifying the statements where possible. Sometimes this can expose |
| fully or partially dead code, but the main focus is simplifying |
| computations. |
| |
| At the moment the pass only handles one piece of information: whether the |
| sign of a value matters, and therefore whether sign-changing operations |
| can be skipped. The pass could be extended to more interesting |
| information in future, such as which bits of an integer are significant. |
| |
| For example, take the function: |
| |
| double |
| f (double *a, int n, double start) |
| { |
| double x = fabs (start); |
| for (int i = 0; i < n; ++i) |
| x *= a[i]; |
| return __builtin_cos (x); |
| } |
| |
| cos(x) == cos(-x), so the sign of the final x doesn't matter. |
| That x is the result of a series of multiplications, and if |
| the sign of the result of a multiplication doesn't matter, |
| the signs of the inputs don't matter either. |
| |
| The pass would replace the incoming value of x (i.e. fabs(start)) |
| with start. Since there are no other uses of the fabs result, |
| the call would get deleted as dead. |
| |
| The algorithm is: |
| |
| (1) Do a post-order traversal of the blocks in the function, walking |
| each block backwards. For each potentially-simplifiable statement |
| that defines an SSA name X, examine all uses of X to see what |
| information is actually significant. Record this as INFO_MAP[X]. |
| Optimistically ignore for now any back-edge references to |
| unprocessed phis. |
| |
| (An alternative would be to record each use when we visit its |
| statement and take the intersection as we go along. However, |
| this would lead to more SSA names being entered into INFO_MAP |
| unnecessarily, only to be taken out again later. At the moment |
| very few SSA names end up with useful information.) |
| |
| (2) Iteratively reduce the optimistic result of (1) until we reach |
| a maximal fixed point (which at the moment would mean revisiting |
| statements at most once). First push all SSA names that used an |
| optimistic assumption about a backedge phi onto a worklist. |
| While the worklist is nonempty, pick off an SSA name X and recompute |
| INFO_MAP[X]. If the value changes, push all SSA names used in the |
| definition of X onto the worklist. |
| |
| (3) Iterate over each SSA name X with info in INFO_MAP, in the |
| opposite order to (1), i.e. a forward reverse-post-order walk. |
| Try to optimize the definition of X using INFO_MAP[X] and fold |
| the result. (This ensures that we fold definitions before uses.) |
| |
| (4) Iterate over each SSA name X with info in INFO_MAP, in the same |
| order as (1), and delete any statements that are now dead. |
| (This ensures that if a sequence of statements is dead, |
| we delete the last statement first.) |
| |
| Note that this pass does not deal with direct redundancies, |
| such as cos(-x)->cos(x). match.pd handles those cases instead. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "gimple-iterator.h" |
| #include "ssa.h" |
| #include "fold-const.h" |
| #include "tree-pass.h" |
| #include "cfganal.h" |
| #include "gimple-pretty-print.h" |
| #include "tree-cfg.h" |
| #include "tree-ssa.h" |
| #include "tree-ssa-propagate.h" |
| #include "gimple-fold.h" |
| #include "alloc-pool.h" |
| #include "tree-hash-traits.h" |
| #include "case-cfn-macros.h" |
| |
| namespace { |
| |
| /* Information about a group of uses of an SSA name. */ |
| class usage_info |
| { |
| public: |
| usage_info () : flag_word (0) {} |
| usage_info &operator &= (const usage_info &); |
| usage_info operator & (const usage_info &) const; |
| bool operator == (const usage_info &) const; |
| bool operator != (const usage_info &) const; |
| bool is_useful () const; |
| |
| static usage_info intersection_identity (); |
| |
| union |
| { |
| struct |
| { |
| /* True if the uses treat x and -x in the same way. */ |
| unsigned int ignore_sign : 1; |
| } flags; |
| /* All the flag bits as a single int. */ |
| unsigned int flag_word; |
| }; |
| }; |
| |
| /* Return an X such that X & Y == Y for all Y. This is the most |
| optimistic assumption possible. */ |
| |
| usage_info |
| usage_info::intersection_identity () |
| { |
| usage_info ret; |
| ret.flag_word = -1; |
| return ret; |
| } |
| |
| /* Intersect *THIS with OTHER, so that *THIS describes all uses covered |
| by the original *THIS and OTHER. */ |
| |
| usage_info & |
| usage_info::operator &= (const usage_info &other) |
| { |
| flag_word &= other.flag_word; |
| return *this; |
| } |
| |
| /* Return the intersection of *THIS and OTHER, i.e. a structure that |
| describes all uses covered by *THIS and OTHER. */ |
| |
| usage_info |
| usage_info::operator & (const usage_info &other) const |
| { |
| usage_info info (*this); |
| info &= other; |
| return info; |
| } |
| |
| bool |
| usage_info::operator == (const usage_info &other) const |
| { |
| return flag_word == other.flag_word; |
| } |
| |
| bool |
| usage_info::operator != (const usage_info &other) const |
| { |
| return !operator == (other); |
| } |
| |
| /* Return true if *THIS is not simply the default, safe assumption. */ |
| |
| bool |
| usage_info::is_useful () const |
| { |
| return flag_word != 0; |
| } |
| |
| /* Start a dump line about SSA name VAR. */ |
| |
| static void |
| dump_usage_prefix (FILE *file, tree var) |
| { |
| fprintf (file, " "); |
| print_generic_expr (file, var); |
| fprintf (file, ": "); |
| } |
| |
| /* Print INFO to FILE. */ |
| |
| static void |
| dump_usage_info (FILE *file, tree var, usage_info *info) |
| { |
| if (info->flags.ignore_sign) |
| { |
| dump_usage_prefix (file, var); |
| fprintf (file, "sign bit not important\n"); |
| } |
| } |
| |
| /* Represents one execution of the pass. */ |
| class backprop |
| { |
| public: |
| backprop (function *); |
| ~backprop (); |
| |
| void execute (); |
| |
| private: |
| const usage_info *lookup_operand (tree); |
| |
| void push_to_worklist (tree); |
| tree pop_from_worklist (); |
| |
| void process_builtin_call_use (gcall *, tree, usage_info *); |
| void process_assign_use (gassign *, tree, usage_info *); |
| void process_phi_use (gphi *, usage_info *); |
| void process_use (gimple *, tree, usage_info *); |
| bool intersect_uses (tree, usage_info *); |
| void reprocess_inputs (gimple *); |
| void process_var (tree); |
| void process_block (basic_block); |
| |
| void prepare_change (tree); |
| void complete_change (gimple *); |
| void optimize_builtin_call (gcall *, tree, const usage_info *); |
| void replace_assign_rhs (gassign *, tree, tree, tree, tree); |
| void optimize_assign (gassign *, tree, const usage_info *); |
| void optimize_phi (gphi *, tree, const usage_info *); |
| |
| typedef hash_map <tree_ssa_name_hash, usage_info *> info_map_type; |
| typedef std::pair <tree, usage_info *> var_info_pair; |
| |
| /* The function we're optimizing. */ |
| function *m_fn; |
| |
| /* Pool for allocating usage_info structures. */ |
| object_allocator <usage_info> m_info_pool; |
| |
| /* Maps an SSA name to a description of all uses of that SSA name. |
| All the usage_infos satisfy is_useful. |
| |
| We use a hash_map because the map is expected to be sparse |
| (i.e. most SSA names won't have useful information attached to them). |
| We could move to a directly-indexed array if that situation changes. */ |
| info_map_type m_info_map; |
| |
| /* Post-ordered list of all potentially-interesting SSA names, |
| along with information that describes all uses. */ |
| auto_vec <var_info_pair, 128> m_vars; |
| |
| /* A bitmap of blocks that we have finished processing in the initial |
| post-order walk. */ |
| auto_sbitmap m_visited_blocks; |
| |
| /* A bitmap of phis that we have finished processing in the initial |
| post-order walk, excluding those from blocks mentioned in |
| M_VISITED_BLOCKS. */ |
| auto_bitmap m_visited_phis; |
| |
| /* A worklist of SSA names whose definitions need to be reconsidered. */ |
| auto_vec <tree, 64> m_worklist; |
| |
| /* The SSA names in M_WORKLIST, identified by their SSA_NAME_VERSION. |
| We use a bitmap rather than an sbitmap because most SSA names are |
| never added to the worklist. */ |
| bitmap m_worklist_names; |
| }; |
| |
| backprop::backprop (function *fn) |
| : m_fn (fn), |
| m_info_pool ("usage_info"), |
| m_visited_blocks (last_basic_block_for_fn (m_fn)), |
| m_worklist_names (BITMAP_ALLOC (NULL)) |
| { |
| bitmap_clear (m_visited_blocks); |
| } |
| |
| backprop::~backprop () |
| { |
| BITMAP_FREE (m_worklist_names); |
| m_info_pool.release (); |
| } |
| |
| /* Return usage information for general operand OP, or null if none. */ |
| |
| const usage_info * |
| backprop::lookup_operand (tree op) |
| { |
| if (op && TREE_CODE (op) == SSA_NAME) |
| { |
| usage_info **slot = m_info_map.get (op); |
| if (slot) |
| return *slot; |
| } |
| return NULL; |
| } |
| |
| /* Add SSA name VAR to the worklist, if it isn't on the worklist already. */ |
| |
| void |
| backprop::push_to_worklist (tree var) |
| { |
| if (!bitmap_set_bit (m_worklist_names, SSA_NAME_VERSION (var))) |
| return; |
| m_worklist.safe_push (var); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "[WORKLIST] Pushing "); |
| print_generic_expr (dump_file, var); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| /* Remove and return the next SSA name from the worklist. The worklist |
| is known to be nonempty. */ |
| |
| tree |
| backprop::pop_from_worklist () |
| { |
| tree var = m_worklist.pop (); |
| bitmap_clear_bit (m_worklist_names, SSA_NAME_VERSION (var)); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "[WORKLIST] Popping "); |
| print_generic_expr (dump_file, var); |
| fprintf (dump_file, "\n"); |
| } |
| return var; |
| } |
| |
| /* Make INFO describe all uses of RHS in CALL, which is a call to a |
| built-in function. */ |
| |
| void |
| backprop::process_builtin_call_use (gcall *call, tree rhs, usage_info *info) |
| { |
| combined_fn fn = gimple_call_combined_fn (call); |
| tree lhs = gimple_call_lhs (call); |
| switch (fn) |
| { |
| case CFN_LAST: |
| break; |
| |
| CASE_CFN_COS: |
| CASE_CFN_COSH: |
| CASE_CFN_CCOS: |
| CASE_CFN_CCOSH: |
| CASE_CFN_HYPOT: |
| /* The signs of all inputs are ignored. */ |
| info->flags.ignore_sign = true; |
| break; |
| |
| CASE_CFN_COPYSIGN: |
| CASE_CFN_COPYSIGN_FN: |
| /* The sign of the first input is ignored. */ |
| if (rhs != gimple_call_arg (call, 1)) |
| info->flags.ignore_sign = true; |
| break; |
| |
| CASE_CFN_POW: |
| { |
| /* The sign of the first input is ignored as long as the second |
| input is an even real. */ |
| tree power = gimple_call_arg (call, 1); |
| HOST_WIDE_INT n; |
| if (TREE_CODE (power) == REAL_CST |
| && real_isinteger (&TREE_REAL_CST (power), &n) |
| && (n & 1) == 0) |
| info->flags.ignore_sign = true; |
| break; |
| } |
| |
| CASE_CFN_FMA: |
| CASE_CFN_FMA_FN: |
| case CFN_FMS: |
| case CFN_FNMA: |
| case CFN_FNMS: |
| /* In X * X + Y, where Y is distinct from X, the sign of X doesn't |
| matter. */ |
| if (gimple_call_arg (call, 0) == rhs |
| && gimple_call_arg (call, 1) == rhs |
| && gimple_call_arg (call, 2) != rhs) |
| info->flags.ignore_sign = true; |
| break; |
| |
| default: |
| if (negate_mathfn_p (fn)) |
| { |
| /* The sign of the (single) input doesn't matter provided |
| that the sign of the output doesn't matter. */ |
| const usage_info *lhs_info = lookup_operand (lhs); |
| if (lhs_info) |
| info->flags.ignore_sign = lhs_info->flags.ignore_sign; |
| } |
| break; |
| } |
| } |
| |
| /* Make INFO describe all uses of RHS in ASSIGN. */ |
| |
| void |
| backprop::process_assign_use (gassign *assign, tree rhs, usage_info *info) |
| { |
| tree lhs = gimple_assign_lhs (assign); |
| switch (gimple_assign_rhs_code (assign)) |
| { |
| case ABS_EXPR: |
| case ABSU_EXPR: |
| /* The sign of the input doesn't matter. */ |
| info->flags.ignore_sign = true; |
| break; |
| |
| case COND_EXPR: |
| /* For A = B ? C : D, propagate information about all uses of A |
| to C and D. */ |
| if (rhs != gimple_assign_rhs1 (assign)) |
| { |
| const usage_info *lhs_info = lookup_operand (lhs); |
| if (lhs_info) |
| *info = *lhs_info; |
| } |
| break; |
| |
| case MULT_EXPR: |
| /* In X * X, the sign of X doesn't matter. */ |
| if (gimple_assign_rhs1 (assign) == rhs |
| && gimple_assign_rhs2 (assign) == rhs) |
| info->flags.ignore_sign = true; |
| /* Fall through. */ |
| |
| case NEGATE_EXPR: |
| case RDIV_EXPR: |
| /* If the sign of the result doesn't matter, the sign of the inputs |
| doesn't matter either. */ |
| if (FLOAT_TYPE_P (TREE_TYPE (rhs))) |
| { |
| const usage_info *lhs_info = lookup_operand (lhs); |
| if (lhs_info) |
| info->flags.ignore_sign = lhs_info->flags.ignore_sign; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* Make INFO describe the uses of PHI's result. */ |
| |
| void |
| backprop::process_phi_use (gphi *phi, usage_info *info) |
| { |
| tree result = gimple_phi_result (phi); |
| if (const usage_info *result_info = lookup_operand (result)) |
| *info = *result_info; |
| } |
| |
| /* Make INFO describe all uses of RHS in STMT. */ |
| |
| void |
| backprop::process_use (gimple *stmt, tree rhs, usage_info *info) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "[USE] "); |
| print_generic_expr (dump_file, rhs); |
| fprintf (dump_file, " in "); |
| print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); |
| } |
| |
| if (gcall *call = dyn_cast <gcall *> (stmt)) |
| process_builtin_call_use (call, rhs, info); |
| else if (gassign *assign = dyn_cast <gassign *> (stmt)) |
| process_assign_use (assign, rhs, info); |
| else if (gphi *phi = dyn_cast <gphi *> (stmt)) |
| process_phi_use (phi, info); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| dump_usage_info (dump_file, rhs, info); |
| } |
| |
| /* Make INFO describe all uses of VAR, returning true if the result |
| is useful. If the uses include phis that haven't been processed yet, |
| make the most optimistic assumption possible, so that we aim for |
| a maximum rather than a minimum fixed point. */ |
| |
| bool |
| backprop::intersect_uses (tree var, usage_info *info) |
| { |
| imm_use_iterator iter; |
| use_operand_p use_p; |
| *info = usage_info::intersection_identity (); |
| FOR_EACH_IMM_USE_FAST (use_p, iter, var) |
| { |
| gimple *stmt = USE_STMT (use_p); |
| if (is_gimple_debug (stmt)) |
| continue; |
| gphi *phi = dyn_cast <gphi *> (stmt); |
| if (phi |
| && !bitmap_bit_p (m_visited_blocks, gimple_bb (phi)->index) |
| && !bitmap_bit_p (m_visited_phis, |
| SSA_NAME_VERSION (gimple_phi_result (phi)))) |
| { |
| /* Skip unprocessed phis. */ |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "[BACKEDGE] "); |
| print_generic_expr (dump_file, var); |
| fprintf (dump_file, " in "); |
| print_gimple_stmt (dump_file, phi, 0, TDF_SLIM); |
| } |
| } |
| else |
| { |
| usage_info subinfo; |
| process_use (stmt, var, &subinfo); |
| *info &= subinfo; |
| if (!info->is_useful ()) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /* Queue for reconsideration any input of STMT that has information |
| associated with it. This is used if that information might be |
| too optimistic. */ |
| |
| void |
| backprop::reprocess_inputs (gimple *stmt) |
| { |
| use_operand_p use_p; |
| ssa_op_iter oi; |
| FOR_EACH_PHI_OR_STMT_USE (use_p, stmt, oi, SSA_OP_USE) |
| { |
| tree var = get_use_from_ptr (use_p); |
| if (lookup_operand (var)) |
| push_to_worklist (var); |
| } |
| } |
| |
| /* Say that we're recording INFO for SSA name VAR, or that we're deleting |
| existing information if INFO is null. INTRO describes the change. */ |
| |
| static void |
| dump_var_info (tree var, usage_info *info, const char *intro) |
| { |
| fprintf (dump_file, "[DEF] %s for ", intro); |
| print_gimple_stmt (dump_file, SSA_NAME_DEF_STMT (var), 0, TDF_SLIM); |
| if (info) |
| dump_usage_info (dump_file, var, info); |
| } |
| |
| /* Process all uses of VAR and record or update the result in |
| M_INFO_MAP and M_VARS. */ |
| |
| void |
| backprop::process_var (tree var) |
| { |
| if (has_zero_uses (var)) |
| return; |
| |
| usage_info info; |
| intersect_uses (var, &info); |
| |
| gimple *stmt = SSA_NAME_DEF_STMT (var); |
| if (info.is_useful ()) |
| { |
| bool existed; |
| usage_info *&map_info = m_info_map.get_or_insert (var, &existed); |
| if (!existed) |
| { |
| /* Recording information about VAR for the first time. */ |
| map_info = m_info_pool.allocate (); |
| *map_info = info; |
| m_vars.safe_push (var_info_pair (var, map_info)); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| dump_var_info (var, map_info, "Recording new information"); |
| |
| /* If STMT is a phi, reprocess any backedge uses. This is a |
| no-op for other uses, which won't have any information |
| associated with them. */ |
| if (is_a <gphi *> (stmt)) |
| reprocess_inputs (stmt); |
| } |
| else if (info != *map_info) |
| { |
| /* Recording information that is less optimistic than before. */ |
| gcc_checking_assert ((info & *map_info) == info); |
| *map_info = info; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| dump_var_info (var, map_info, "Updating information"); |
| reprocess_inputs (stmt); |
| } |
| } |
| else |
| { |
| if (usage_info **slot = m_info_map.get (var)) |
| { |
| /* Removing previously-recorded information. */ |
| **slot = info; |
| m_info_map.remove (var); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| dump_var_info (var, NULL, "Deleting information"); |
| reprocess_inputs (stmt); |
| } |
| else |
| { |
| /* If STMT is a phi, remove any information recorded for |
| its arguments. */ |
| if (is_a <gphi *> (stmt)) |
| reprocess_inputs (stmt); |
| } |
| } |
| } |
| |
| /* Process all statements and phis in BB, during the first post-order walk. */ |
| |
| void |
| backprop::process_block (basic_block bb) |
| { |
| for (gimple_stmt_iterator gsi = gsi_last_bb (bb); !gsi_end_p (gsi); |
| gsi_prev (&gsi)) |
| { |
| tree lhs = gimple_get_lhs (gsi_stmt (gsi)); |
| if (lhs && TREE_CODE (lhs) == SSA_NAME) |
| process_var (lhs); |
| } |
| for (gphi_iterator gpi = gsi_start_phis (bb); !gsi_end_p (gpi); |
| gsi_next (&gpi)) |
| { |
| tree result = gimple_phi_result (gpi.phi ()); |
| process_var (result); |
| bitmap_set_bit (m_visited_phis, SSA_NAME_VERSION (result)); |
| } |
| bitmap_clear (m_visited_phis); |
| } |
| |
| /* Delete the definition of VAR, which has no uses. */ |
| |
| static void |
| remove_unused_var (tree var) |
| { |
| gimple *stmt = SSA_NAME_DEF_STMT (var); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Deleting "); |
| print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); |
| } |
| gimple_stmt_iterator gsi = gsi_for_stmt (stmt); |
| gsi_remove (&gsi, true); |
| release_defs (stmt); |
| } |
| |
| /* Note that we're replacing OLD_RHS with NEW_RHS in STMT. */ |
| |
| static void |
| note_replacement (gimple *stmt, tree old_rhs, tree new_rhs) |
| { |
| fprintf (dump_file, "Replacing use of "); |
| print_generic_expr (dump_file, old_rhs); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, new_rhs); |
| fprintf (dump_file, " in "); |
| print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); |
| } |
| |
| /* If RHS is an SSA name whose definition just changes the sign of a value, |
| return that other value, otherwise return null. */ |
| |
| static tree |
| strip_sign_op_1 (tree rhs) |
| { |
| if (TREE_CODE (rhs) != SSA_NAME) |
| return NULL_TREE; |
| |
| gimple *def_stmt = SSA_NAME_DEF_STMT (rhs); |
| if (gassign *assign = dyn_cast <gassign *> (def_stmt)) |
| switch (gimple_assign_rhs_code (assign)) |
| { |
| case ABS_EXPR: |
| case ABSU_EXPR: |
| case NEGATE_EXPR: |
| return gimple_assign_rhs1 (assign); |
| |
| default: |
| break; |
| } |
| else if (gcall *call = dyn_cast <gcall *> (def_stmt)) |
| switch (gimple_call_combined_fn (call)) |
| { |
| CASE_CFN_COPYSIGN: |
| CASE_CFN_COPYSIGN_FN: |
| return gimple_call_arg (call, 0); |
| |
| default: |
| break; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* If RHS is an SSA name whose definition just changes the sign of a value, |
| strip all such operations and return the ultimate input to them. |
| Return null otherwise. |
| |
| Although this could in principle lead to quadratic searching, |
| in practice a long sequence of sign manipulations should already |
| have been folded down. E.g. --x -> x, abs(-x) -> abs(x). We search |
| for more than one operation in order to catch cases like -abs(x). */ |
| |
| static tree |
| strip_sign_op (tree rhs) |
| { |
| tree new_rhs = strip_sign_op_1 (rhs); |
| if (!new_rhs) |
| return NULL_TREE; |
| while (tree next = strip_sign_op_1 (new_rhs)) |
| new_rhs = next; |
| return new_rhs; |
| } |
| |
| /* Start a change in the value of VAR that is suitable for all non-debug |
| uses of VAR. We need to make sure that debug statements continue to |
| use the original definition of VAR where possible, or are nullified |
| otherwise. */ |
| |
| void |
| backprop::prepare_change (tree var) |
| { |
| if (MAY_HAVE_DEBUG_BIND_STMTS) |
| insert_debug_temp_for_var_def (NULL, var); |
| reset_flow_sensitive_info (var); |
| } |
| |
| /* STMT has been changed. Give the fold machinery a chance to simplify |
| and canonicalize it (e.g. by ensuring that commutative operands have |
| the right order), then record the updates. */ |
| |
| void |
| backprop::complete_change (gimple *stmt) |
| { |
| gimple_stmt_iterator gsi = gsi_for_stmt (stmt); |
| if (fold_stmt (&gsi)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, " which folds to: "); |
| print_gimple_stmt (dump_file, gsi_stmt (gsi), 0, TDF_SLIM); |
| } |
| } |
| update_stmt (gsi_stmt (gsi)); |
| } |
| |
| /* Optimize CALL, a call to a built-in function with lhs LHS, on the |
| basis that INFO describes all uses of LHS. */ |
| |
| void |
| backprop::optimize_builtin_call (gcall *call, tree lhs, const usage_info *info) |
| { |
| /* If we have an f such that -f(x) = f(-x), and if the sign of the result |
| doesn't matter, strip any sign operations from the input. */ |
| if (info->flags.ignore_sign |
| && negate_mathfn_p (gimple_call_combined_fn (call))) |
| { |
| tree new_arg = strip_sign_op (gimple_call_arg (call, 0)); |
| if (new_arg) |
| { |
| prepare_change (lhs); |
| gimple_call_set_arg (call, 0, new_arg); |
| complete_change (call); |
| } |
| } |
| } |
| |
| /* Optimize ASSIGN, an assignment to LHS, by replacing rhs operand N |
| with RHS<N>, if RHS<N> is nonnull. This may change the value of LHS. */ |
| |
| void |
| backprop::replace_assign_rhs (gassign *assign, tree lhs, tree rhs1, |
| tree rhs2, tree rhs3) |
| { |
| if (!rhs1 && !rhs2 && !rhs3) |
| return; |
| |
| prepare_change (lhs); |
| if (rhs1) |
| gimple_assign_set_rhs1 (assign, rhs1); |
| if (rhs2) |
| gimple_assign_set_rhs2 (assign, rhs2); |
| if (rhs3) |
| gimple_assign_set_rhs3 (assign, rhs3); |
| complete_change (assign); |
| } |
| |
| /* Optimize ASSIGN, an assignment to LHS, on the basis that INFO |
| describes all uses of LHS. */ |
| |
| void |
| backprop::optimize_assign (gassign *assign, tree lhs, const usage_info *info) |
| { |
| switch (gimple_assign_rhs_code (assign)) |
| { |
| case MULT_EXPR: |
| case RDIV_EXPR: |
| /* If the sign of the result doesn't matter, strip sign operations |
| from both inputs. */ |
| if (info->flags.ignore_sign) |
| replace_assign_rhs (assign, lhs, |
| strip_sign_op (gimple_assign_rhs1 (assign)), |
| strip_sign_op (gimple_assign_rhs2 (assign)), |
| NULL_TREE); |
| break; |
| |
| case COND_EXPR: |
| /* If the sign of A ? B : C doesn't matter, strip sign operations |
| from both B and C. */ |
| if (info->flags.ignore_sign) |
| replace_assign_rhs (assign, lhs, |
| NULL_TREE, |
| strip_sign_op (gimple_assign_rhs2 (assign)), |
| strip_sign_op (gimple_assign_rhs3 (assign))); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* Optimize PHI, which defines VAR, on the basis that INFO describes all |
| uses of the result. */ |
| |
| void |
| backprop::optimize_phi (gphi *phi, tree var, const usage_info *info) |
| { |
| /* If the sign of the result doesn't matter, try to strip sign operations |
| from arguments. */ |
| if (info->flags.ignore_sign) |
| { |
| basic_block bb = gimple_bb (phi); |
| use_operand_p use; |
| ssa_op_iter oi; |
| bool replaced = false; |
| FOR_EACH_PHI_ARG (use, phi, oi, SSA_OP_USE) |
| { |
| /* Propagating along abnormal edges is delicate, punt for now. */ |
| const int index = PHI_ARG_INDEX_FROM_USE (use); |
| if (EDGE_PRED (bb, index)->flags & EDGE_ABNORMAL) |
| continue; |
| |
| tree new_arg = strip_sign_op (USE_FROM_PTR (use)); |
| if (new_arg) |
| { |
| if (!replaced) |
| prepare_change (var); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| note_replacement (phi, USE_FROM_PTR (use), new_arg); |
| replace_exp (use, new_arg); |
| replaced = true; |
| } |
| } |
| } |
| } |
| |
| void |
| backprop::execute () |
| { |
| /* Phase 1: Traverse the function, making optimistic assumptions |
| about any phi whose definition we haven't seen. */ |
| int *postorder = XNEWVEC (int, n_basic_blocks_for_fn (m_fn)); |
| unsigned int postorder_num = post_order_compute (postorder, false, false); |
| for (unsigned int i = 0; i < postorder_num; ++i) |
| { |
| process_block (BASIC_BLOCK_FOR_FN (m_fn, postorder[i])); |
| bitmap_set_bit (m_visited_blocks, postorder[i]); |
| } |
| XDELETEVEC (postorder); |
| |
| /* Phase 2: Use the initial (perhaps overly optimistic) information |
| to create a maximal fixed point solution. */ |
| while (!m_worklist.is_empty ()) |
| process_var (pop_from_worklist ()); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "\n"); |
| |
| /* Phase 3: Do a reverse post-order walk, using information about |
| the uses of SSA names to optimize their definitions. */ |
| for (unsigned int i = m_vars.length (); i-- > 0;) |
| { |
| usage_info *info = m_vars[i].second; |
| if (info->is_useful ()) |
| { |
| tree var = m_vars[i].first; |
| gimple *stmt = SSA_NAME_DEF_STMT (var); |
| if (gcall *call = dyn_cast <gcall *> (stmt)) |
| optimize_builtin_call (call, var, info); |
| else if (gassign *assign = dyn_cast <gassign *> (stmt)) |
| optimize_assign (assign, var, info); |
| else if (gphi *phi = dyn_cast <gphi *> (stmt)) |
| optimize_phi (phi, var, info); |
| } |
| } |
| |
| /* Phase 4: Do a post-order walk, deleting statements that are no |
| longer needed. */ |
| for (unsigned int i = 0; i < m_vars.length (); ++i) |
| { |
| tree var = m_vars[i].first; |
| if (has_zero_uses (var)) |
| remove_unused_var (var); |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "\n"); |
| } |
| |
| const pass_data pass_data_backprop = |
| { |
| GIMPLE_PASS, /* type */ |
| "backprop", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_TREE_BACKPROP, /* tv_id */ |
| ( PROP_cfg | PROP_ssa ), /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_backprop : public gimple_opt_pass |
| { |
| public: |
| pass_backprop (gcc::context *ctxt) |
| : gimple_opt_pass (pass_data_backprop, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| opt_pass * clone () { return new pass_backprop (m_ctxt); } |
| virtual bool gate (function *) { return flag_ssa_backprop; } |
| virtual unsigned int execute (function *); |
| |
| }; // class pass_backprop |
| |
| unsigned int |
| pass_backprop::execute (function *fn) |
| { |
| backprop (fn).execute (); |
| return 0; |
| } |
| |
| } // anon namespace |
| |
| gimple_opt_pass * |
| make_pass_backprop (gcc::context *ctxt) |
| { |
| return new pass_backprop (ctxt); |
| } |