| /* SCC value numbering for trees |
| Copyright (C) 2006-2022 Free Software Foundation, Inc. |
| Contributed by Daniel Berlin <dan@dberlin.org> |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "splay-tree.h" |
| #include "backend.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "ssa.h" |
| #include "expmed.h" |
| #include "insn-config.h" |
| #include "memmodel.h" |
| #include "emit-rtl.h" |
| #include "cgraph.h" |
| #include "gimple-pretty-print.h" |
| #include "alias.h" |
| #include "fold-const.h" |
| #include "stor-layout.h" |
| #include "cfganal.h" |
| #include "tree-inline.h" |
| #include "internal-fn.h" |
| #include "gimple-fold.h" |
| #include "tree-eh.h" |
| #include "gimplify.h" |
| #include "flags.h" |
| #include "dojump.h" |
| #include "explow.h" |
| #include "calls.h" |
| #include "varasm.h" |
| #include "stmt.h" |
| #include "expr.h" |
| #include "tree-dfa.h" |
| #include "tree-ssa.h" |
| #include "dumpfile.h" |
| #include "cfgloop.h" |
| #include "tree-ssa-propagate.h" |
| #include "tree-cfg.h" |
| #include "domwalk.h" |
| #include "gimple-iterator.h" |
| #include "gimple-match.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "tree-pass.h" |
| #include "statistics.h" |
| #include "langhooks.h" |
| #include "ipa-utils.h" |
| #include "dbgcnt.h" |
| #include "tree-cfgcleanup.h" |
| #include "tree-ssa-loop.h" |
| #include "tree-scalar-evolution.h" |
| #include "tree-ssa-loop-niter.h" |
| #include "builtins.h" |
| #include "fold-const-call.h" |
| #include "ipa-modref-tree.h" |
| #include "ipa-modref.h" |
| #include "tree-ssa-sccvn.h" |
| |
| /* This algorithm is based on the SCC algorithm presented by Keith |
| Cooper and L. Taylor Simpson in "SCC-Based Value numbering" |
| (http://citeseer.ist.psu.edu/41805.html). In |
| straight line code, it is equivalent to a regular hash based value |
| numbering that is performed in reverse postorder. |
| |
| For code with cycles, there are two alternatives, both of which |
| require keeping the hashtables separate from the actual list of |
| value numbers for SSA names. |
| |
| 1. Iterate value numbering in an RPO walk of the blocks, removing |
| all the entries from the hashtable after each iteration (but |
| keeping the SSA name->value number mapping between iterations). |
| Iterate until it does not change. |
| |
| 2. Perform value numbering as part of an SCC walk on the SSA graph, |
| iterating only the cycles in the SSA graph until they do not change |
| (using a separate, optimistic hashtable for value numbering the SCC |
| operands). |
| |
| The second is not just faster in practice (because most SSA graph |
| cycles do not involve all the variables in the graph), it also has |
| some nice properties. |
| |
| One of these nice properties is that when we pop an SCC off the |
| stack, we are guaranteed to have processed all the operands coming from |
| *outside of that SCC*, so we do not need to do anything special to |
| ensure they have value numbers. |
| |
| Another nice property is that the SCC walk is done as part of a DFS |
| of the SSA graph, which makes it easy to perform combining and |
| simplifying operations at the same time. |
| |
| The code below is deliberately written in a way that makes it easy |
| to separate the SCC walk from the other work it does. |
| |
| In order to propagate constants through the code, we track which |
| expressions contain constants, and use those while folding. In |
| theory, we could also track expressions whose value numbers are |
| replaced, in case we end up folding based on expression |
| identities. |
| |
| In order to value number memory, we assign value numbers to vuses. |
| This enables us to note that, for example, stores to the same |
| address of the same value from the same starting memory states are |
| equivalent. |
| TODO: |
| |
| 1. We can iterate only the changing portions of the SCC's, but |
| I have not seen an SCC big enough for this to be a win. |
| 2. If you differentiate between phi nodes for loops and phi nodes |
| for if-then-else, you can properly consider phi nodes in different |
| blocks for equivalence. |
| 3. We could value number vuses in more cases, particularly, whole |
| structure copies. |
| */ |
| |
| /* There's no BB_EXECUTABLE but we can use BB_VISITED. */ |
| #define BB_EXECUTABLE BB_VISITED |
| |
| static vn_lookup_kind default_vn_walk_kind; |
| |
| /* vn_nary_op hashtable helpers. */ |
| |
| struct vn_nary_op_hasher : nofree_ptr_hash <vn_nary_op_s> |
| { |
| typedef vn_nary_op_s *compare_type; |
| static inline hashval_t hash (const vn_nary_op_s *); |
| static inline bool equal (const vn_nary_op_s *, const vn_nary_op_s *); |
| }; |
| |
| /* Return the computed hashcode for nary operation P1. */ |
| |
| inline hashval_t |
| vn_nary_op_hasher::hash (const vn_nary_op_s *vno1) |
| { |
| return vno1->hashcode; |
| } |
| |
| /* Compare nary operations P1 and P2 and return true if they are |
| equivalent. */ |
| |
| inline bool |
| vn_nary_op_hasher::equal (const vn_nary_op_s *vno1, const vn_nary_op_s *vno2) |
| { |
| return vno1 == vno2 || vn_nary_op_eq (vno1, vno2); |
| } |
| |
| typedef hash_table<vn_nary_op_hasher> vn_nary_op_table_type; |
| typedef vn_nary_op_table_type::iterator vn_nary_op_iterator_type; |
| |
| |
| /* vn_phi hashtable helpers. */ |
| |
| static int |
| vn_phi_eq (const_vn_phi_t const vp1, const_vn_phi_t const vp2); |
| |
| struct vn_phi_hasher : nofree_ptr_hash <vn_phi_s> |
| { |
| static inline hashval_t hash (const vn_phi_s *); |
| static inline bool equal (const vn_phi_s *, const vn_phi_s *); |
| }; |
| |
| /* Return the computed hashcode for phi operation P1. */ |
| |
| inline hashval_t |
| vn_phi_hasher::hash (const vn_phi_s *vp1) |
| { |
| return vp1->hashcode; |
| } |
| |
| /* Compare two phi entries for equality, ignoring VN_TOP arguments. */ |
| |
| inline bool |
| vn_phi_hasher::equal (const vn_phi_s *vp1, const vn_phi_s *vp2) |
| { |
| return vp1 == vp2 || vn_phi_eq (vp1, vp2); |
| } |
| |
| typedef hash_table<vn_phi_hasher> vn_phi_table_type; |
| typedef vn_phi_table_type::iterator vn_phi_iterator_type; |
| |
| |
| /* Compare two reference operands P1 and P2 for equality. Return true if |
| they are equal, and false otherwise. */ |
| |
| static int |
| vn_reference_op_eq (const void *p1, const void *p2) |
| { |
| const_vn_reference_op_t const vro1 = (const_vn_reference_op_t) p1; |
| const_vn_reference_op_t const vro2 = (const_vn_reference_op_t) p2; |
| |
| return (vro1->opcode == vro2->opcode |
| /* We do not care for differences in type qualification. */ |
| && (vro1->type == vro2->type |
| || (vro1->type && vro2->type |
| && types_compatible_p (TYPE_MAIN_VARIANT (vro1->type), |
| TYPE_MAIN_VARIANT (vro2->type)))) |
| && expressions_equal_p (vro1->op0, vro2->op0) |
| && expressions_equal_p (vro1->op1, vro2->op1) |
| && expressions_equal_p (vro1->op2, vro2->op2) |
| && (vro1->opcode != CALL_EXPR || vro1->clique == vro2->clique)); |
| } |
| |
| /* Free a reference operation structure VP. */ |
| |
| static inline void |
| free_reference (vn_reference_s *vr) |
| { |
| vr->operands.release (); |
| } |
| |
| |
| /* vn_reference hashtable helpers. */ |
| |
| struct vn_reference_hasher : nofree_ptr_hash <vn_reference_s> |
| { |
| static inline hashval_t hash (const vn_reference_s *); |
| static inline bool equal (const vn_reference_s *, const vn_reference_s *); |
| }; |
| |
| /* Return the hashcode for a given reference operation P1. */ |
| |
| inline hashval_t |
| vn_reference_hasher::hash (const vn_reference_s *vr1) |
| { |
| return vr1->hashcode; |
| } |
| |
| inline bool |
| vn_reference_hasher::equal (const vn_reference_s *v, const vn_reference_s *c) |
| { |
| return v == c || vn_reference_eq (v, c); |
| } |
| |
| typedef hash_table<vn_reference_hasher> vn_reference_table_type; |
| typedef vn_reference_table_type::iterator vn_reference_iterator_type; |
| |
| /* Pretty-print OPS to OUTFILE. */ |
| |
| void |
| print_vn_reference_ops (FILE *outfile, const vec<vn_reference_op_s> ops) |
| { |
| vn_reference_op_t vro; |
| unsigned int i; |
| fprintf (outfile, "{"); |
| for (i = 0; ops.iterate (i, &vro); i++) |
| { |
| bool closebrace = false; |
| if (vro->opcode != SSA_NAME |
| && TREE_CODE_CLASS (vro->opcode) != tcc_declaration) |
| { |
| fprintf (outfile, "%s", get_tree_code_name (vro->opcode)); |
| if (vro->op0 || vro->opcode == CALL_EXPR) |
| { |
| fprintf (outfile, "<"); |
| closebrace = true; |
| } |
| } |
| if (vro->op0 || vro->opcode == CALL_EXPR) |
| { |
| if (!vro->op0) |
| fprintf (outfile, internal_fn_name ((internal_fn)vro->clique)); |
| else |
| print_generic_expr (outfile, vro->op0); |
| if (vro->op1) |
| { |
| fprintf (outfile, ","); |
| print_generic_expr (outfile, vro->op1); |
| } |
| if (vro->op2) |
| { |
| fprintf (outfile, ","); |
| print_generic_expr (outfile, vro->op2); |
| } |
| } |
| if (closebrace) |
| fprintf (outfile, ">"); |
| if (i != ops.length () - 1) |
| fprintf (outfile, ","); |
| } |
| fprintf (outfile, "}"); |
| } |
| |
| DEBUG_FUNCTION void |
| debug_vn_reference_ops (const vec<vn_reference_op_s> ops) |
| { |
| print_vn_reference_ops (stderr, ops); |
| fputc ('\n', stderr); |
| } |
| |
| /* The set of VN hashtables. */ |
| |
| typedef struct vn_tables_s |
| { |
| vn_nary_op_table_type *nary; |
| vn_phi_table_type *phis; |
| vn_reference_table_type *references; |
| } *vn_tables_t; |
| |
| |
| /* vn_constant hashtable helpers. */ |
| |
| struct vn_constant_hasher : free_ptr_hash <vn_constant_s> |
| { |
| static inline hashval_t hash (const vn_constant_s *); |
| static inline bool equal (const vn_constant_s *, const vn_constant_s *); |
| }; |
| |
| /* Hash table hash function for vn_constant_t. */ |
| |
| inline hashval_t |
| vn_constant_hasher::hash (const vn_constant_s *vc1) |
| { |
| return vc1->hashcode; |
| } |
| |
| /* Hash table equality function for vn_constant_t. */ |
| |
| inline bool |
| vn_constant_hasher::equal (const vn_constant_s *vc1, const vn_constant_s *vc2) |
| { |
| if (vc1->hashcode != vc2->hashcode) |
| return false; |
| |
| return vn_constant_eq_with_type (vc1->constant, vc2->constant); |
| } |
| |
| static hash_table<vn_constant_hasher> *constant_to_value_id; |
| |
| |
| /* Obstack we allocate the vn-tables elements from. */ |
| static obstack vn_tables_obstack; |
| /* Special obstack we never unwind. */ |
| static obstack vn_tables_insert_obstack; |
| |
| static vn_reference_t last_inserted_ref; |
| static vn_phi_t last_inserted_phi; |
| static vn_nary_op_t last_inserted_nary; |
| static vn_ssa_aux_t last_pushed_avail; |
| |
| /* Valid hashtables storing information we have proven to be |
| correct. */ |
| static vn_tables_t valid_info; |
| |
| |
| /* Valueization hook for simplify_replace_tree. Valueize NAME if it is |
| an SSA name, otherwise just return it. */ |
| tree (*vn_valueize) (tree); |
| static tree |
| vn_valueize_for_srt (tree t, void* context ATTRIBUTE_UNUSED) |
| { |
| basic_block saved_vn_context_bb = vn_context_bb; |
| /* Look for sth available at the definition block of the argument. |
| This avoids inconsistencies between availability there which |
| decides if the stmt can be removed and availability at the |
| use site. The SSA property ensures that things available |
| at the definition are also available at uses. */ |
| if (!SSA_NAME_IS_DEFAULT_DEF (t)) |
| vn_context_bb = gimple_bb (SSA_NAME_DEF_STMT (t)); |
| tree res = vn_valueize (t); |
| vn_context_bb = saved_vn_context_bb; |
| return res; |
| } |
| |
| |
| /* This represents the top of the VN lattice, which is the universal |
| value. */ |
| |
| tree VN_TOP; |
| |
| /* Unique counter for our value ids. */ |
| |
| static unsigned int next_value_id; |
| static int next_constant_value_id; |
| |
| |
| /* Table of vn_ssa_aux_t's, one per ssa_name. The vn_ssa_aux_t objects |
| are allocated on an obstack for locality reasons, and to free them |
| without looping over the vec. */ |
| |
| struct vn_ssa_aux_hasher : typed_noop_remove <vn_ssa_aux_t> |
| { |
| typedef vn_ssa_aux_t value_type; |
| typedef tree compare_type; |
| static inline hashval_t hash (const value_type &); |
| static inline bool equal (const value_type &, const compare_type &); |
| static inline void mark_deleted (value_type &) {} |
| static const bool empty_zero_p = true; |
| static inline void mark_empty (value_type &e) { e = NULL; } |
| static inline bool is_deleted (value_type &) { return false; } |
| static inline bool is_empty (value_type &e) { return e == NULL; } |
| }; |
| |
| hashval_t |
| vn_ssa_aux_hasher::hash (const value_type &entry) |
| { |
| return SSA_NAME_VERSION (entry->name); |
| } |
| |
| bool |
| vn_ssa_aux_hasher::equal (const value_type &entry, const compare_type &name) |
| { |
| return name == entry->name; |
| } |
| |
| static hash_table<vn_ssa_aux_hasher> *vn_ssa_aux_hash; |
| typedef hash_table<vn_ssa_aux_hasher>::iterator vn_ssa_aux_iterator_type; |
| static struct obstack vn_ssa_aux_obstack; |
| |
| static vn_nary_op_t vn_nary_op_insert_stmt (gimple *, tree); |
| static vn_nary_op_t vn_nary_op_insert_into (vn_nary_op_t, |
| vn_nary_op_table_type *); |
| static void init_vn_nary_op_from_pieces (vn_nary_op_t, unsigned int, |
| enum tree_code, tree, tree *); |
| static tree vn_lookup_simplify_result (gimple_match_op *); |
| static vn_reference_t vn_reference_lookup_or_insert_for_pieces |
| (tree, alias_set_type, alias_set_type, tree, |
| vec<vn_reference_op_s, va_heap>, tree); |
| |
| /* Return whether there is value numbering information for a given SSA name. */ |
| |
| bool |
| has_VN_INFO (tree name) |
| { |
| return vn_ssa_aux_hash->find_with_hash (name, SSA_NAME_VERSION (name)); |
| } |
| |
| vn_ssa_aux_t |
| VN_INFO (tree name) |
| { |
| vn_ssa_aux_t *res |
| = vn_ssa_aux_hash->find_slot_with_hash (name, SSA_NAME_VERSION (name), |
| INSERT); |
| if (*res != NULL) |
| return *res; |
| |
| vn_ssa_aux_t newinfo = *res = XOBNEW (&vn_ssa_aux_obstack, struct vn_ssa_aux); |
| memset (newinfo, 0, sizeof (struct vn_ssa_aux)); |
| newinfo->name = name; |
| newinfo->valnum = VN_TOP; |
| /* We are using the visited flag to handle uses with defs not within the |
| region being value-numbered. */ |
| newinfo->visited = false; |
| |
| /* Given we create the VN_INFOs on-demand now we have to do initialization |
| different than VN_TOP here. */ |
| if (SSA_NAME_IS_DEFAULT_DEF (name)) |
| switch (TREE_CODE (SSA_NAME_VAR (name))) |
| { |
| case VAR_DECL: |
| /* All undefined vars are VARYING. */ |
| newinfo->valnum = name; |
| newinfo->visited = true; |
| break; |
| |
| case PARM_DECL: |
| /* Parameters are VARYING but we can record a condition |
| if we know it is a non-NULL pointer. */ |
| newinfo->visited = true; |
| newinfo->valnum = name; |
| if (POINTER_TYPE_P (TREE_TYPE (name)) |
| && nonnull_arg_p (SSA_NAME_VAR (name))) |
| { |
| tree ops[2]; |
| ops[0] = name; |
| ops[1] = build_int_cst (TREE_TYPE (name), 0); |
| vn_nary_op_t nary; |
| /* Allocate from non-unwinding stack. */ |
| nary = alloc_vn_nary_op_noinit (2, &vn_tables_insert_obstack); |
| init_vn_nary_op_from_pieces (nary, 2, NE_EXPR, |
| boolean_type_node, ops); |
| nary->predicated_values = 0; |
| nary->u.result = boolean_true_node; |
| vn_nary_op_insert_into (nary, valid_info->nary); |
| gcc_assert (nary->unwind_to == NULL); |
| /* Also do not link it into the undo chain. */ |
| last_inserted_nary = nary->next; |
| nary->next = (vn_nary_op_t)(void *)-1; |
| nary = alloc_vn_nary_op_noinit (2, &vn_tables_insert_obstack); |
| init_vn_nary_op_from_pieces (nary, 2, EQ_EXPR, |
| boolean_type_node, ops); |
| nary->predicated_values = 0; |
| nary->u.result = boolean_false_node; |
| vn_nary_op_insert_into (nary, valid_info->nary); |
| gcc_assert (nary->unwind_to == NULL); |
| last_inserted_nary = nary->next; |
| nary->next = (vn_nary_op_t)(void *)-1; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Recording "); |
| print_generic_expr (dump_file, name, TDF_SLIM); |
| fprintf (dump_file, " != 0\n"); |
| } |
| } |
| break; |
| |
| case RESULT_DECL: |
| /* If the result is passed by invisible reference the default |
| def is initialized, otherwise it's uninitialized. Still |
| undefined is varying. */ |
| newinfo->visited = true; |
| newinfo->valnum = name; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| return newinfo; |
| } |
| |
| /* Return the SSA value of X. */ |
| |
| inline tree |
| SSA_VAL (tree x, bool *visited = NULL) |
| { |
| vn_ssa_aux_t tem = vn_ssa_aux_hash->find_with_hash (x, SSA_NAME_VERSION (x)); |
| if (visited) |
| *visited = tem && tem->visited; |
| return tem && tem->visited ? tem->valnum : x; |
| } |
| |
| /* Return the SSA value of the VUSE x, supporting released VDEFs |
| during elimination which will value-number the VDEF to the |
| associated VUSE (but not substitute in the whole lattice). */ |
| |
| static inline tree |
| vuse_ssa_val (tree x) |
| { |
| if (!x) |
| return NULL_TREE; |
| |
| do |
| { |
| x = SSA_VAL (x); |
| gcc_assert (x != VN_TOP); |
| } |
| while (SSA_NAME_IN_FREE_LIST (x)); |
| |
| return x; |
| } |
| |
| /* Similar to the above but used as callback for walk_non_aliased_vuses |
| and thus should stop at unvisited VUSE to not walk across region |
| boundaries. */ |
| |
| static tree |
| vuse_valueize (tree vuse) |
| { |
| do |
| { |
| bool visited; |
| vuse = SSA_VAL (vuse, &visited); |
| if (!visited) |
| return NULL_TREE; |
| gcc_assert (vuse != VN_TOP); |
| } |
| while (SSA_NAME_IN_FREE_LIST (vuse)); |
| return vuse; |
| } |
| |
| |
| /* Return the vn_kind the expression computed by the stmt should be |
| associated with. */ |
| |
| enum vn_kind |
| vn_get_stmt_kind (gimple *stmt) |
| { |
| switch (gimple_code (stmt)) |
| { |
| case GIMPLE_CALL: |
| return VN_REFERENCE; |
| case GIMPLE_PHI: |
| return VN_PHI; |
| case GIMPLE_ASSIGN: |
| { |
| enum tree_code code = gimple_assign_rhs_code (stmt); |
| tree rhs1 = gimple_assign_rhs1 (stmt); |
| switch (get_gimple_rhs_class (code)) |
| { |
| case GIMPLE_UNARY_RHS: |
| case GIMPLE_BINARY_RHS: |
| case GIMPLE_TERNARY_RHS: |
| return VN_NARY; |
| case GIMPLE_SINGLE_RHS: |
| switch (TREE_CODE_CLASS (code)) |
| { |
| case tcc_reference: |
| /* VOP-less references can go through unary case. */ |
| if ((code == REALPART_EXPR |
| || code == IMAGPART_EXPR |
| || code == VIEW_CONVERT_EXPR |
| || code == BIT_FIELD_REF) |
| && (TREE_CODE (TREE_OPERAND (rhs1, 0)) == SSA_NAME |
| || is_gimple_min_invariant (TREE_OPERAND (rhs1, 0)))) |
| return VN_NARY; |
| |
| /* Fallthrough. */ |
| case tcc_declaration: |
| return VN_REFERENCE; |
| |
| case tcc_constant: |
| return VN_CONSTANT; |
| |
| default: |
| if (code == ADDR_EXPR) |
| return (is_gimple_min_invariant (rhs1) |
| ? VN_CONSTANT : VN_REFERENCE); |
| else if (code == CONSTRUCTOR) |
| return VN_NARY; |
| return VN_NONE; |
| } |
| default: |
| return VN_NONE; |
| } |
| } |
| default: |
| return VN_NONE; |
| } |
| } |
| |
| /* Lookup a value id for CONSTANT and return it. If it does not |
| exist returns 0. */ |
| |
| unsigned int |
| get_constant_value_id (tree constant) |
| { |
| vn_constant_s **slot; |
| struct vn_constant_s vc; |
| |
| vc.hashcode = vn_hash_constant_with_type (constant); |
| vc.constant = constant; |
| slot = constant_to_value_id->find_slot (&vc, NO_INSERT); |
| if (slot) |
| return (*slot)->value_id; |
| return 0; |
| } |
| |
| /* Lookup a value id for CONSTANT, and if it does not exist, create a |
| new one and return it. If it does exist, return it. */ |
| |
| unsigned int |
| get_or_alloc_constant_value_id (tree constant) |
| { |
| vn_constant_s **slot; |
| struct vn_constant_s vc; |
| vn_constant_t vcp; |
| |
| /* If the hashtable isn't initialized we're not running from PRE and thus |
| do not need value-ids. */ |
| if (!constant_to_value_id) |
| return 0; |
| |
| vc.hashcode = vn_hash_constant_with_type (constant); |
| vc.constant = constant; |
| slot = constant_to_value_id->find_slot (&vc, INSERT); |
| if (*slot) |
| return (*slot)->value_id; |
| |
| vcp = XNEW (struct vn_constant_s); |
| vcp->hashcode = vc.hashcode; |
| vcp->constant = constant; |
| vcp->value_id = get_next_constant_value_id (); |
| *slot = vcp; |
| return vcp->value_id; |
| } |
| |
| /* Compute the hash for a reference operand VRO1. */ |
| |
| static void |
| vn_reference_op_compute_hash (const vn_reference_op_t vro1, inchash::hash &hstate) |
| { |
| hstate.add_int (vro1->opcode); |
| if (vro1->opcode == CALL_EXPR && !vro1->op0) |
| hstate.add_int (vro1->clique); |
| if (vro1->op0) |
| inchash::add_expr (vro1->op0, hstate); |
| if (vro1->op1) |
| inchash::add_expr (vro1->op1, hstate); |
| if (vro1->op2) |
| inchash::add_expr (vro1->op2, hstate); |
| } |
| |
| /* Compute a hash for the reference operation VR1 and return it. */ |
| |
| static hashval_t |
| vn_reference_compute_hash (const vn_reference_t vr1) |
| { |
| inchash::hash hstate; |
| hashval_t result; |
| int i; |
| vn_reference_op_t vro; |
| poly_int64 off = -1; |
| bool deref = false; |
| |
| FOR_EACH_VEC_ELT (vr1->operands, i, vro) |
| { |
| if (vro->opcode == MEM_REF) |
| deref = true; |
| else if (vro->opcode != ADDR_EXPR) |
| deref = false; |
| if (maybe_ne (vro->off, -1)) |
| { |
| if (known_eq (off, -1)) |
| off = 0; |
| off += vro->off; |
| } |
| else |
| { |
| if (maybe_ne (off, -1) |
| && maybe_ne (off, 0)) |
| hstate.add_poly_int (off); |
| off = -1; |
| if (deref |
| && vro->opcode == ADDR_EXPR) |
| { |
| if (vro->op0) |
| { |
| tree op = TREE_OPERAND (vro->op0, 0); |
| hstate.add_int (TREE_CODE (op)); |
| inchash::add_expr (op, hstate); |
| } |
| } |
| else |
| vn_reference_op_compute_hash (vro, hstate); |
| } |
| } |
| result = hstate.end (); |
| /* ??? We would ICE later if we hash instead of adding that in. */ |
| if (vr1->vuse) |
| result += SSA_NAME_VERSION (vr1->vuse); |
| |
| return result; |
| } |
| |
| /* Return true if reference operations VR1 and VR2 are equivalent. This |
| means they have the same set of operands and vuses. */ |
| |
| bool |
| vn_reference_eq (const_vn_reference_t const vr1, const_vn_reference_t const vr2) |
| { |
| unsigned i, j; |
| |
| /* Early out if this is not a hash collision. */ |
| if (vr1->hashcode != vr2->hashcode) |
| return false; |
| |
| /* The VOP needs to be the same. */ |
| if (vr1->vuse != vr2->vuse) |
| return false; |
| |
| /* If the operands are the same we are done. */ |
| if (vr1->operands == vr2->operands) |
| return true; |
| |
| if (!vr1->type || !vr2->type) |
| { |
| if (vr1->type != vr2->type) |
| return false; |
| } |
| else if (vr1->type == vr2->type) |
| ; |
| else if (COMPLETE_TYPE_P (vr1->type) != COMPLETE_TYPE_P (vr2->type) |
| || (COMPLETE_TYPE_P (vr1->type) |
| && !expressions_equal_p (TYPE_SIZE (vr1->type), |
| TYPE_SIZE (vr2->type)))) |
| return false; |
| else if (vr1->operands[0].opcode == CALL_EXPR |
| && !types_compatible_p (vr1->type, vr2->type)) |
| return false; |
| else if (INTEGRAL_TYPE_P (vr1->type) |
| && INTEGRAL_TYPE_P (vr2->type)) |
| { |
| if (TYPE_PRECISION (vr1->type) != TYPE_PRECISION (vr2->type)) |
| return false; |
| } |
| else if (INTEGRAL_TYPE_P (vr1->type) |
| && (TYPE_PRECISION (vr1->type) |
| != TREE_INT_CST_LOW (TYPE_SIZE (vr1->type)))) |
| return false; |
| else if (INTEGRAL_TYPE_P (vr2->type) |
| && (TYPE_PRECISION (vr2->type) |
| != TREE_INT_CST_LOW (TYPE_SIZE (vr2->type)))) |
| return false; |
| |
| i = 0; |
| j = 0; |
| do |
| { |
| poly_int64 off1 = 0, off2 = 0; |
| vn_reference_op_t vro1, vro2; |
| vn_reference_op_s tem1, tem2; |
| bool deref1 = false, deref2 = false; |
| bool reverse1 = false, reverse2 = false; |
| for (; vr1->operands.iterate (i, &vro1); i++) |
| { |
| if (vro1->opcode == MEM_REF) |
| deref1 = true; |
| /* Do not look through a storage order barrier. */ |
| else if (vro1->opcode == VIEW_CONVERT_EXPR && vro1->reverse) |
| return false; |
| reverse1 |= vro1->reverse; |
| if (known_eq (vro1->off, -1)) |
| break; |
| off1 += vro1->off; |
| } |
| for (; vr2->operands.iterate (j, &vro2); j++) |
| { |
| if (vro2->opcode == MEM_REF) |
| deref2 = true; |
| /* Do not look through a storage order barrier. */ |
| else if (vro2->opcode == VIEW_CONVERT_EXPR && vro2->reverse) |
| return false; |
| reverse2 |= vro2->reverse; |
| if (known_eq (vro2->off, -1)) |
| break; |
| off2 += vro2->off; |
| } |
| if (maybe_ne (off1, off2) || reverse1 != reverse2) |
| return false; |
| if (deref1 && vro1->opcode == ADDR_EXPR) |
| { |
| memset (&tem1, 0, sizeof (tem1)); |
| tem1.op0 = TREE_OPERAND (vro1->op0, 0); |
| tem1.type = TREE_TYPE (tem1.op0); |
| tem1.opcode = TREE_CODE (tem1.op0); |
| vro1 = &tem1; |
| deref1 = false; |
| } |
| if (deref2 && vro2->opcode == ADDR_EXPR) |
| { |
| memset (&tem2, 0, sizeof (tem2)); |
| tem2.op0 = TREE_OPERAND (vro2->op0, 0); |
| tem2.type = TREE_TYPE (tem2.op0); |
| tem2.opcode = TREE_CODE (tem2.op0); |
| vro2 = &tem2; |
| deref2 = false; |
| } |
| if (deref1 != deref2) |
| return false; |
| if (!vn_reference_op_eq (vro1, vro2)) |
| return false; |
| ++j; |
| ++i; |
| } |
| while (vr1->operands.length () != i |
| || vr2->operands.length () != j); |
| |
| return true; |
| } |
| |
| /* Copy the operations present in load/store REF into RESULT, a vector of |
| vn_reference_op_s's. */ |
| |
| static void |
| copy_reference_ops_from_ref (tree ref, vec<vn_reference_op_s> *result) |
| { |
| /* For non-calls, store the information that makes up the address. */ |
| tree orig = ref; |
| while (ref) |
| { |
| vn_reference_op_s temp; |
| |
| memset (&temp, 0, sizeof (temp)); |
| temp.type = TREE_TYPE (ref); |
| temp.opcode = TREE_CODE (ref); |
| temp.off = -1; |
| |
| switch (temp.opcode) |
| { |
| case MODIFY_EXPR: |
| temp.op0 = TREE_OPERAND (ref, 1); |
| break; |
| case WITH_SIZE_EXPR: |
| temp.op0 = TREE_OPERAND (ref, 1); |
| temp.off = 0; |
| break; |
| case MEM_REF: |
| /* The base address gets its own vn_reference_op_s structure. */ |
| temp.op0 = TREE_OPERAND (ref, 1); |
| if (!mem_ref_offset (ref).to_shwi (&temp.off)) |
| temp.off = -1; |
| temp.clique = MR_DEPENDENCE_CLIQUE (ref); |
| temp.base = MR_DEPENDENCE_BASE (ref); |
| temp.reverse = REF_REVERSE_STORAGE_ORDER (ref); |
| break; |
| case TARGET_MEM_REF: |
| /* The base address gets its own vn_reference_op_s structure. */ |
| temp.op0 = TMR_INDEX (ref); |
| temp.op1 = TMR_STEP (ref); |
| temp.op2 = TMR_OFFSET (ref); |
| temp.clique = MR_DEPENDENCE_CLIQUE (ref); |
| temp.base = MR_DEPENDENCE_BASE (ref); |
| result->safe_push (temp); |
| memset (&temp, 0, sizeof (temp)); |
| temp.type = NULL_TREE; |
| temp.opcode = ERROR_MARK; |
| temp.op0 = TMR_INDEX2 (ref); |
| temp.off = -1; |
| break; |
| case BIT_FIELD_REF: |
| /* Record bits, position and storage order. */ |
| temp.op0 = TREE_OPERAND (ref, 1); |
| temp.op1 = TREE_OPERAND (ref, 2); |
| if (!multiple_p (bit_field_offset (ref), BITS_PER_UNIT, &temp.off)) |
| temp.off = -1; |
| temp.reverse = REF_REVERSE_STORAGE_ORDER (ref); |
| break; |
| case COMPONENT_REF: |
| /* The field decl is enough to unambiguously specify the field, |
| so use its type here. */ |
| temp.type = TREE_TYPE (TREE_OPERAND (ref, 1)); |
| temp.op0 = TREE_OPERAND (ref, 1); |
| temp.op1 = TREE_OPERAND (ref, 2); |
| temp.reverse = (AGGREGATE_TYPE_P (TREE_TYPE (TREE_OPERAND (ref, 0))) |
| && TYPE_REVERSE_STORAGE_ORDER |
| (TREE_TYPE (TREE_OPERAND (ref, 0)))); |
| { |
| tree this_offset = component_ref_field_offset (ref); |
| if (this_offset |
| && poly_int_tree_p (this_offset)) |
| { |
| tree bit_offset = DECL_FIELD_BIT_OFFSET (TREE_OPERAND (ref, 1)); |
| if (TREE_INT_CST_LOW (bit_offset) % BITS_PER_UNIT == 0) |
| { |
| poly_offset_int off |
| = (wi::to_poly_offset (this_offset) |
| + (wi::to_offset (bit_offset) >> LOG2_BITS_PER_UNIT)); |
| /* Probibit value-numbering zero offset components |
| of addresses the same before the pass folding |
| __builtin_object_size had a chance to run. */ |
| if (TREE_CODE (orig) != ADDR_EXPR |
| || maybe_ne (off, 0) |
| || (cfun->curr_properties & PROP_objsz)) |
| off.to_shwi (&temp.off); |
| } |
| } |
| } |
| break; |
| case ARRAY_RANGE_REF: |
| case ARRAY_REF: |
| { |
| tree eltype = TREE_TYPE (TREE_TYPE (TREE_OPERAND (ref, 0))); |
| /* Record index as operand. */ |
| temp.op0 = TREE_OPERAND (ref, 1); |
| /* Always record lower bounds and element size. */ |
| temp.op1 = array_ref_low_bound (ref); |
| /* But record element size in units of the type alignment. */ |
| temp.op2 = TREE_OPERAND (ref, 3); |
| temp.align = eltype->type_common.align; |
| if (! temp.op2) |
| temp.op2 = size_binop (EXACT_DIV_EXPR, TYPE_SIZE_UNIT (eltype), |
| size_int (TYPE_ALIGN_UNIT (eltype))); |
| if (poly_int_tree_p (temp.op0) |
| && poly_int_tree_p (temp.op1) |
| && TREE_CODE (temp.op2) == INTEGER_CST) |
| { |
| poly_offset_int off = ((wi::to_poly_offset (temp.op0) |
| - wi::to_poly_offset (temp.op1)) |
| * wi::to_offset (temp.op2) |
| * vn_ref_op_align_unit (&temp)); |
| off.to_shwi (&temp.off); |
| } |
| temp.reverse = (AGGREGATE_TYPE_P (TREE_TYPE (TREE_OPERAND (ref, 0))) |
| && TYPE_REVERSE_STORAGE_ORDER |
| (TREE_TYPE (TREE_OPERAND (ref, 0)))); |
| } |
| break; |
| case VAR_DECL: |
| if (DECL_HARD_REGISTER (ref)) |
| { |
| temp.op0 = ref; |
| break; |
| } |
| /* Fallthru. */ |
| case PARM_DECL: |
| case CONST_DECL: |
| case RESULT_DECL: |
| /* Canonicalize decls to MEM[&decl] which is what we end up with |
| when valueizing MEM[ptr] with ptr = &decl. */ |
| temp.opcode = MEM_REF; |
| temp.op0 = build_int_cst (build_pointer_type (TREE_TYPE (ref)), 0); |
| temp.off = 0; |
| result->safe_push (temp); |
| temp.opcode = ADDR_EXPR; |
| temp.op0 = build1 (ADDR_EXPR, TREE_TYPE (temp.op0), ref); |
| temp.type = TREE_TYPE (temp.op0); |
| temp.off = -1; |
| break; |
| case STRING_CST: |
| case INTEGER_CST: |
| case POLY_INT_CST: |
| case COMPLEX_CST: |
| case VECTOR_CST: |
| case REAL_CST: |
| case FIXED_CST: |
| case CONSTRUCTOR: |
| case SSA_NAME: |
| temp.op0 = ref; |
| break; |
| case ADDR_EXPR: |
| if (is_gimple_min_invariant (ref)) |
| { |
| temp.op0 = ref; |
| break; |
| } |
| break; |
| /* These are only interesting for their operands, their |
| existence, and their type. They will never be the last |
| ref in the chain of references (IE they require an |
| operand), so we don't have to put anything |
| for op* as it will be handled by the iteration */ |
| case REALPART_EXPR: |
| temp.off = 0; |
| break; |
| case VIEW_CONVERT_EXPR: |
| temp.off = 0; |
| temp.reverse = storage_order_barrier_p (ref); |
| break; |
| case IMAGPART_EXPR: |
| /* This is only interesting for its constant offset. */ |
| temp.off = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (TREE_TYPE (ref))); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| result->safe_push (temp); |
| |
| if (REFERENCE_CLASS_P (ref) |
| || TREE_CODE (ref) == MODIFY_EXPR |
| || TREE_CODE (ref) == WITH_SIZE_EXPR |
| || (TREE_CODE (ref) == ADDR_EXPR |
| && !is_gimple_min_invariant (ref))) |
| ref = TREE_OPERAND (ref, 0); |
| else |
| ref = NULL_TREE; |
| } |
| } |
| |
| /* Build a alias-oracle reference abstraction in *REF from the vn_reference |
| operands in *OPS, the reference alias set SET and the reference type TYPE. |
| Return true if something useful was produced. */ |
| |
| bool |
| ao_ref_init_from_vn_reference (ao_ref *ref, |
| alias_set_type set, alias_set_type base_set, |
| tree type, const vec<vn_reference_op_s> &ops) |
| { |
| unsigned i; |
| tree base = NULL_TREE; |
| tree *op0_p = &base; |
| poly_offset_int offset = 0; |
| poly_offset_int max_size; |
| poly_offset_int size = -1; |
| tree size_tree = NULL_TREE; |
| |
| /* We don't handle calls. */ |
| if (!type) |
| return false; |
| |
| machine_mode mode = TYPE_MODE (type); |
| if (mode == BLKmode) |
| size_tree = TYPE_SIZE (type); |
| else |
| size = GET_MODE_BITSIZE (mode); |
| if (size_tree != NULL_TREE |
| && poly_int_tree_p (size_tree)) |
| size = wi::to_poly_offset (size_tree); |
| |
| /* Lower the final access size from the outermost expression. */ |
| const_vn_reference_op_t cst_op = &ops[0]; |
| /* Cast away constness for the sake of the const-unsafe |
| FOR_EACH_VEC_ELT(). */ |
| vn_reference_op_t op = const_cast<vn_reference_op_t>(cst_op); |
| size_tree = NULL_TREE; |
| if (op->opcode == COMPONENT_REF) |
| size_tree = DECL_SIZE (op->op0); |
| else if (op->opcode == BIT_FIELD_REF) |
| size_tree = op->op0; |
| if (size_tree != NULL_TREE |
| && poly_int_tree_p (size_tree) |
| && (!known_size_p (size) |
| || known_lt (wi::to_poly_offset (size_tree), size))) |
| size = wi::to_poly_offset (size_tree); |
| |
| /* Initially, maxsize is the same as the accessed element size. |
| In the following it will only grow (or become -1). */ |
| max_size = size; |
| |
| /* Compute cumulative bit-offset for nested component-refs and array-refs, |
| and find the ultimate containing object. */ |
| FOR_EACH_VEC_ELT (ops, i, op) |
| { |
| switch (op->opcode) |
| { |
| /* These may be in the reference ops, but we cannot do anything |
| sensible with them here. */ |
| case ADDR_EXPR: |
| /* Apart from ADDR_EXPR arguments to MEM_REF. */ |
| if (base != NULL_TREE |
| && TREE_CODE (base) == MEM_REF |
| && op->op0 |
| && DECL_P (TREE_OPERAND (op->op0, 0))) |
| { |
| const_vn_reference_op_t pop = &ops[i-1]; |
| base = TREE_OPERAND (op->op0, 0); |
| if (known_eq (pop->off, -1)) |
| { |
| max_size = -1; |
| offset = 0; |
| } |
| else |
| offset += pop->off * BITS_PER_UNIT; |
| op0_p = NULL; |
| break; |
| } |
| /* Fallthru. */ |
| case CALL_EXPR: |
| return false; |
| |
| /* Record the base objects. */ |
| case MEM_REF: |
| *op0_p = build2 (MEM_REF, op->type, |
| NULL_TREE, op->op0); |
| MR_DEPENDENCE_CLIQUE (*op0_p) = op->clique; |
| MR_DEPENDENCE_BASE (*op0_p) = op->base; |
| op0_p = &TREE_OPERAND (*op0_p, 0); |
| break; |
| |
| case VAR_DECL: |
| case PARM_DECL: |
| case RESULT_DECL: |
| case SSA_NAME: |
| *op0_p = op->op0; |
| op0_p = NULL; |
| break; |
| |
| /* And now the usual component-reference style ops. */ |
| case BIT_FIELD_REF: |
| offset += wi::to_poly_offset (op->op1); |
| break; |
| |
| case COMPONENT_REF: |
| { |
| tree field = op->op0; |
| /* We do not have a complete COMPONENT_REF tree here so we |
| cannot use component_ref_field_offset. Do the interesting |
| parts manually. */ |
| tree this_offset = DECL_FIELD_OFFSET (field); |
| |
| if (op->op1 || !poly_int_tree_p (this_offset)) |
| max_size = -1; |
| else |
| { |
| poly_offset_int woffset = (wi::to_poly_offset (this_offset) |
| << LOG2_BITS_PER_UNIT); |
| woffset += wi::to_offset (DECL_FIELD_BIT_OFFSET (field)); |
| offset += woffset; |
| } |
| break; |
| } |
| |
| case ARRAY_RANGE_REF: |
| case ARRAY_REF: |
| /* We recorded the lower bound and the element size. */ |
| if (!poly_int_tree_p (op->op0) |
| || !poly_int_tree_p (op->op1) |
| || TREE_CODE (op->op2) != INTEGER_CST) |
| max_size = -1; |
| else |
| { |
| poly_offset_int woffset |
| = wi::sext (wi::to_poly_offset (op->op0) |
| - wi::to_poly_offset (op->op1), |
| TYPE_PRECISION (sizetype)); |
| woffset *= wi::to_offset (op->op2) * vn_ref_op_align_unit (op); |
| woffset <<= LOG2_BITS_PER_UNIT; |
| offset += woffset; |
| } |
| break; |
| |
| case REALPART_EXPR: |
| break; |
| |
| case IMAGPART_EXPR: |
| offset += size; |
| break; |
| |
| case VIEW_CONVERT_EXPR: |
| break; |
| |
| case STRING_CST: |
| case INTEGER_CST: |
| case COMPLEX_CST: |
| case VECTOR_CST: |
| case REAL_CST: |
| case CONSTRUCTOR: |
| case CONST_DECL: |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| if (base == NULL_TREE) |
| return false; |
| |
| ref->ref = NULL_TREE; |
| ref->base = base; |
| ref->ref_alias_set = set; |
| ref->base_alias_set = base_set; |
| /* We discount volatiles from value-numbering elsewhere. */ |
| ref->volatile_p = false; |
| |
| if (!size.to_shwi (&ref->size) || maybe_lt (ref->size, 0)) |
| { |
| ref->offset = 0; |
| ref->size = -1; |
| ref->max_size = -1; |
| return true; |
| } |
| |
| if (!offset.to_shwi (&ref->offset)) |
| { |
| ref->offset = 0; |
| ref->max_size = -1; |
| return true; |
| } |
| |
| if (!max_size.to_shwi (&ref->max_size) || maybe_lt (ref->max_size, 0)) |
| ref->max_size = -1; |
| |
| return true; |
| } |
| |
| /* Copy the operations present in load/store/call REF into RESULT, a vector of |
| vn_reference_op_s's. */ |
| |
| static void |
| copy_reference_ops_from_call (gcall *call, |
| vec<vn_reference_op_s> *result) |
| { |
| vn_reference_op_s temp; |
| unsigned i; |
| tree lhs = gimple_call_lhs (call); |
| int lr; |
| |
| /* If 2 calls have a different non-ssa lhs, vdef value numbers should be |
| different. By adding the lhs here in the vector, we ensure that the |
| hashcode is different, guaranteeing a different value number. */ |
| if (lhs && TREE_CODE (lhs) != SSA_NAME) |
| { |
| memset (&temp, 0, sizeof (temp)); |
| temp.opcode = MODIFY_EXPR; |
| temp.type = TREE_TYPE (lhs); |
| temp.op0 = lhs; |
| temp.off = -1; |
| result->safe_push (temp); |
| } |
| |
| /* Copy the type, opcode, function, static chain and EH region, if any. */ |
| memset (&temp, 0, sizeof (temp)); |
| temp.type = gimple_call_fntype (call); |
| temp.opcode = CALL_EXPR; |
| temp.op0 = gimple_call_fn (call); |
| if (gimple_call_internal_p (call)) |
| temp.clique = gimple_call_internal_fn (call); |
| temp.op1 = gimple_call_chain (call); |
| if (stmt_could_throw_p (cfun, call) && (lr = lookup_stmt_eh_lp (call)) > 0) |
| temp.op2 = size_int (lr); |
| temp.off = -1; |
| result->safe_push (temp); |
| |
| /* Copy the call arguments. As they can be references as well, |
| just chain them together. */ |
| for (i = 0; i < gimple_call_num_args (call); ++i) |
| { |
| tree callarg = gimple_call_arg (call, i); |
| copy_reference_ops_from_ref (callarg, result); |
| } |
| } |
| |
| /* Fold *& at position *I_P in a vn_reference_op_s vector *OPS. Updates |
| *I_P to point to the last element of the replacement. */ |
| static bool |
| vn_reference_fold_indirect (vec<vn_reference_op_s> *ops, |
| unsigned int *i_p) |
| { |
| unsigned int i = *i_p; |
| vn_reference_op_t op = &(*ops)[i]; |
| vn_reference_op_t mem_op = &(*ops)[i - 1]; |
| tree addr_base; |
| poly_int64 addr_offset = 0; |
| |
| /* The only thing we have to do is from &OBJ.foo.bar add the offset |
| from .foo.bar to the preceding MEM_REF offset and replace the |
| address with &OBJ. */ |
| addr_base = get_addr_base_and_unit_offset_1 (TREE_OPERAND (op->op0, 0), |
| &addr_offset, vn_valueize); |
| gcc_checking_assert (addr_base && TREE_CODE (addr_base) != MEM_REF); |
| if (addr_base != TREE_OPERAND (op->op0, 0)) |
| { |
| poly_offset_int off |
| = (poly_offset_int::from (wi::to_poly_wide (mem_op->op0), |
| SIGNED) |
| + addr_offset); |
| mem_op->op0 = wide_int_to_tree (TREE_TYPE (mem_op->op0), off); |
| op->op0 = build_fold_addr_expr (addr_base); |
| if (tree_fits_shwi_p (mem_op->op0)) |
| mem_op->off = tree_to_shwi (mem_op->op0); |
| else |
| mem_op->off = -1; |
| return true; |
| } |
| return false; |
| } |
| |
| /* Fold *& at position *I_P in a vn_reference_op_s vector *OPS. Updates |
| *I_P to point to the last element of the replacement. */ |
| static bool |
| vn_reference_maybe_forwprop_address (vec<vn_reference_op_s> *ops, |
| unsigned int *i_p) |
| { |
| bool changed = false; |
| vn_reference_op_t op; |
| |
| do |
| { |
| unsigned int i = *i_p; |
| op = &(*ops)[i]; |
| vn_reference_op_t mem_op = &(*ops)[i - 1]; |
| gimple *def_stmt; |
| enum tree_code code; |
| poly_offset_int off; |
| |
| def_stmt = SSA_NAME_DEF_STMT (op->op0); |
| if (!is_gimple_assign (def_stmt)) |
| return changed; |
| |
| code = gimple_assign_rhs_code (def_stmt); |
| if (code != ADDR_EXPR |
| && code != POINTER_PLUS_EXPR) |
| return changed; |
| |
| off = poly_offset_int::from (wi::to_poly_wide (mem_op->op0), SIGNED); |
| |
| /* The only thing we have to do is from &OBJ.foo.bar add the offset |
| from .foo.bar to the preceding MEM_REF offset and replace the |
| address with &OBJ. */ |
| if (code == ADDR_EXPR) |
| { |
| tree addr, addr_base; |
| poly_int64 addr_offset; |
| |
| addr = gimple_assign_rhs1 (def_stmt); |
| addr_base = get_addr_base_and_unit_offset_1 (TREE_OPERAND (addr, 0), |
| &addr_offset, |
| vn_valueize); |
| /* If that didn't work because the address isn't invariant propagate |
| the reference tree from the address operation in case the current |
| dereference isn't offsetted. */ |
| if (!addr_base |
| && *i_p == ops->length () - 1 |
| && known_eq (off, 0) |
| /* This makes us disable this transform for PRE where the |
| reference ops might be also used for code insertion which |
| is invalid. */ |
| && default_vn_walk_kind == VN_WALKREWRITE) |
| { |
| auto_vec<vn_reference_op_s, 32> tem; |
| copy_reference_ops_from_ref (TREE_OPERAND (addr, 0), &tem); |
| /* Make sure to preserve TBAA info. The only objects not |
| wrapped in MEM_REFs that can have their address taken are |
| STRING_CSTs. */ |
| if (tem.length () >= 2 |
| && tem[tem.length () - 2].opcode == MEM_REF) |
| { |
| vn_reference_op_t new_mem_op = &tem[tem.length () - 2]; |
| new_mem_op->op0 |
| = wide_int_to_tree (TREE_TYPE (mem_op->op0), |
| wi::to_poly_wide (new_mem_op->op0)); |
| } |
| else |
| gcc_assert (tem.last ().opcode == STRING_CST); |
| ops->pop (); |
| ops->pop (); |
| ops->safe_splice (tem); |
| --*i_p; |
| return true; |
| } |
| if (!addr_base |
| || TREE_CODE (addr_base) != MEM_REF |
| || (TREE_CODE (TREE_OPERAND (addr_base, 0)) == SSA_NAME |
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (TREE_OPERAND (addr_base, |
| 0)))) |
| return changed; |
| |
| off += addr_offset; |
| off += mem_ref_offset (addr_base); |
| op->op0 = TREE_OPERAND (addr_base, 0); |
| } |
| else |
| { |
| tree ptr, ptroff; |
| ptr = gimple_assign_rhs1 (def_stmt); |
| ptroff = gimple_assign_rhs2 (def_stmt); |
| if (TREE_CODE (ptr) != SSA_NAME |
| || SSA_NAME_OCCURS_IN_ABNORMAL_PHI (ptr) |
| /* Make sure to not endlessly recurse. |
| See gcc.dg/tree-ssa/20040408-1.c for an example. Can easily |
| happen when we value-number a PHI to its backedge value. */ |
| || SSA_VAL (ptr) == op->op0 |
| || !poly_int_tree_p (ptroff)) |
| return changed; |
| |
| off += wi::to_poly_offset (ptroff); |
| op->op0 = ptr; |
| } |
| |
| mem_op->op0 = wide_int_to_tree (TREE_TYPE (mem_op->op0), off); |
| if (tree_fits_shwi_p (mem_op->op0)) |
| mem_op->off = tree_to_shwi (mem_op->op0); |
| else |
| mem_op->off = -1; |
| /* ??? Can end up with endless recursion here!? |
| gcc.c-torture/execute/strcmp-1.c */ |
| if (TREE_CODE (op->op0) == SSA_NAME) |
| op->op0 = SSA_VAL (op->op0); |
| if (TREE_CODE (op->op0) != SSA_NAME) |
| op->opcode = TREE_CODE (op->op0); |
| |
| changed = true; |
| } |
| /* Tail-recurse. */ |
| while (TREE_CODE (op->op0) == SSA_NAME); |
| |
| /* Fold a remaining *&. */ |
| if (TREE_CODE (op->op0) == ADDR_EXPR) |
| vn_reference_fold_indirect (ops, i_p); |
| |
| return changed; |
| } |
| |
| /* Optimize the reference REF to a constant if possible or return |
| NULL_TREE if not. */ |
| |
| tree |
| fully_constant_vn_reference_p (vn_reference_t ref) |
| { |
| vec<vn_reference_op_s> operands = ref->operands; |
| vn_reference_op_t op; |
| |
| /* Try to simplify the translated expression if it is |
| a call to a builtin function with at most two arguments. */ |
| op = &operands[0]; |
| if (op->opcode == CALL_EXPR |
| && (!op->op0 |
| || (TREE_CODE (op->op0) == ADDR_EXPR |
| && TREE_CODE (TREE_OPERAND (op->op0, 0)) == FUNCTION_DECL |
| && fndecl_built_in_p (TREE_OPERAND (op->op0, 0), |
| BUILT_IN_NORMAL))) |
| && operands.length () >= 2 |
| && operands.length () <= 3) |
| { |
| vn_reference_op_t arg0, arg1 = NULL; |
| bool anyconst = false; |
| arg0 = &operands[1]; |
| if (operands.length () > 2) |
| arg1 = &operands[2]; |
| if (TREE_CODE_CLASS (arg0->opcode) == tcc_constant |
| || (arg0->opcode == ADDR_EXPR |
| && is_gimple_min_invariant (arg0->op0))) |
| anyconst = true; |
| if (arg1 |
| && (TREE_CODE_CLASS (arg1->opcode) == tcc_constant |
| || (arg1->opcode == ADDR_EXPR |
| && is_gimple_min_invariant (arg1->op0)))) |
| anyconst = true; |
| if (anyconst) |
| { |
| combined_fn fn; |
| if (op->op0) |
| fn = as_combined_fn (DECL_FUNCTION_CODE |
| (TREE_OPERAND (op->op0, 0))); |
| else |
| fn = as_combined_fn ((internal_fn) op->clique); |
| tree folded; |
| if (arg1) |
| folded = fold_const_call (fn, ref->type, arg0->op0, arg1->op0); |
| else |
| folded = fold_const_call (fn, ref->type, arg0->op0); |
| if (folded |
| && is_gimple_min_invariant (folded)) |
| return folded; |
| } |
| } |
| |
| /* Simplify reads from constants or constant initializers. */ |
| else if (BITS_PER_UNIT == 8 |
| && ref->type |
| && COMPLETE_TYPE_P (ref->type) |
| && is_gimple_reg_type (ref->type)) |
| { |
| poly_int64 off = 0; |
| HOST_WIDE_INT size; |
| if (INTEGRAL_TYPE_P (ref->type)) |
| size = TYPE_PRECISION (ref->type); |
| else if (tree_fits_shwi_p (TYPE_SIZE (ref->type))) |
| size = tree_to_shwi (TYPE_SIZE (ref->type)); |
| else |
| return NULL_TREE; |
| if (size % BITS_PER_UNIT != 0 |
| || size > MAX_BITSIZE_MODE_ANY_MODE) |
| return NULL_TREE; |
| size /= BITS_PER_UNIT; |
| unsigned i; |
| for (i = 0; i < operands.length (); ++i) |
| { |
| if (TREE_CODE_CLASS (operands[i].opcode) == tcc_constant) |
| { |
| ++i; |
| break; |
| } |
| if (known_eq (operands[i].off, -1)) |
| return NULL_TREE; |
| off += operands[i].off; |
| if (operands[i].opcode == MEM_REF) |
| { |
| ++i; |
| break; |
| } |
| } |
| vn_reference_op_t base = &operands[--i]; |
| tree ctor = error_mark_node; |
| tree decl = NULL_TREE; |
| if (TREE_CODE_CLASS (base->opcode) == tcc_constant) |
| ctor = base->op0; |
| else if (base->opcode == MEM_REF |
| && base[1].opcode == ADDR_EXPR |
| && (TREE_CODE (TREE_OPERAND (base[1].op0, 0)) == VAR_DECL |
| || TREE_CODE (TREE_OPERAND (base[1].op0, 0)) == CONST_DECL |
| || TREE_CODE (TREE_OPERAND (base[1].op0, 0)) == STRING_CST)) |
| { |
| decl = TREE_OPERAND (base[1].op0, 0); |
| if (TREE_CODE (decl) == STRING_CST) |
| ctor = decl; |
| else |
| ctor = ctor_for_folding (decl); |
| } |
| if (ctor == NULL_TREE) |
| return build_zero_cst (ref->type); |
| else if (ctor != error_mark_node) |
| { |
| HOST_WIDE_INT const_off; |
| if (decl) |
| { |
| tree res = fold_ctor_reference (ref->type, ctor, |
| off * BITS_PER_UNIT, |
| size * BITS_PER_UNIT, decl); |
| if (res) |
| { |
| STRIP_USELESS_TYPE_CONVERSION (res); |
| if (is_gimple_min_invariant (res)) |
| return res; |
| } |
| } |
| else if (off.is_constant (&const_off)) |
| { |
| unsigned char buf[MAX_BITSIZE_MODE_ANY_MODE / BITS_PER_UNIT]; |
| int len = native_encode_expr (ctor, buf, size, const_off); |
| if (len > 0) |
| return native_interpret_expr (ref->type, buf, len); |
| } |
| } |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Return true if OPS contain a storage order barrier. */ |
| |
| static bool |
| contains_storage_order_barrier_p (vec<vn_reference_op_s> ops) |
| { |
| vn_reference_op_t op; |
| unsigned i; |
| |
| FOR_EACH_VEC_ELT (ops, i, op) |
| if (op->opcode == VIEW_CONVERT_EXPR && op->reverse) |
| return true; |
| |
| return false; |
| } |
| |
| /* Return true if OPS represent an access with reverse storage order. */ |
| |
| static bool |
| reverse_storage_order_for_component_p (vec<vn_reference_op_s> ops) |
| { |
| unsigned i = 0; |
| if (ops[i].opcode == REALPART_EXPR || ops[i].opcode == IMAGPART_EXPR) |
| ++i; |
| switch (ops[i].opcode) |
| { |
| case ARRAY_REF: |
| case COMPONENT_REF: |
| case BIT_FIELD_REF: |
| case MEM_REF: |
| return ops[i].reverse; |
| default: |
| return false; |
| } |
| } |
| |
| /* Transform any SSA_NAME's in a vector of vn_reference_op_s |
| structures into their value numbers. This is done in-place, and |
| the vector passed in is returned. *VALUEIZED_ANYTHING will specify |
| whether any operands were valueized. */ |
| |
| static void |
| valueize_refs_1 (vec<vn_reference_op_s> *orig, bool *valueized_anything, |
| bool with_avail = false) |
| { |
| *valueized_anything = false; |
| |
| for (unsigned i = 0; i < orig->length (); ++i) |
| { |
| re_valueize: |
| vn_reference_op_t vro = &(*orig)[i]; |
| if (vro->opcode == SSA_NAME |
| || (vro->op0 && TREE_CODE (vro->op0) == SSA_NAME)) |
| { |
| tree tem = with_avail ? vn_valueize (vro->op0) : SSA_VAL (vro->op0); |
| if (tem != vro->op0) |
| { |
| *valueized_anything = true; |
| vro->op0 = tem; |
| } |
| /* If it transforms from an SSA_NAME to a constant, update |
| the opcode. */ |
| if (TREE_CODE (vro->op0) != SSA_NAME && vro->opcode == SSA_NAME) |
| vro->opcode = TREE_CODE (vro->op0); |
| } |
| if (vro->op1 && TREE_CODE (vro->op1) == SSA_NAME) |
| { |
| tree tem = with_avail ? vn_valueize (vro->op1) : SSA_VAL (vro->op1); |
| if (tem != vro->op1) |
| { |
| *valueized_anything = true; |
| vro->op1 = tem; |
| } |
| } |
| if (vro->op2 && TREE_CODE (vro->op2) == SSA_NAME) |
| { |
| tree tem = with_avail ? vn_valueize (vro->op2) : SSA_VAL (vro->op2); |
| if (tem != vro->op2) |
| { |
| *valueized_anything = true; |
| vro->op2 = tem; |
| } |
| } |
| /* If it transforms from an SSA_NAME to an address, fold with |
| a preceding indirect reference. */ |
| if (i > 0 |
| && vro->op0 |
| && TREE_CODE (vro->op0) == ADDR_EXPR |
| && (*orig)[i - 1].opcode == MEM_REF) |
| { |
| if (vn_reference_fold_indirect (orig, &i)) |
| *valueized_anything = true; |
| } |
| else if (i > 0 |
| && vro->opcode == SSA_NAME |
| && (*orig)[i - 1].opcode == MEM_REF) |
| { |
| if (vn_reference_maybe_forwprop_address (orig, &i)) |
| { |
| *valueized_anything = true; |
| /* Re-valueize the current operand. */ |
| goto re_valueize; |
| } |
| } |
| /* If it transforms a non-constant ARRAY_REF into a constant |
| one, adjust the constant offset. */ |
| else if (vro->opcode == ARRAY_REF |
| && known_eq (vro->off, -1) |
| && poly_int_tree_p (vro->op0) |
| && poly_int_tree_p (vro->op1) |
| && TREE_CODE (vro->op2) == INTEGER_CST) |
| { |
| poly_offset_int off = ((wi::to_poly_offset (vro->op0) |
| - wi::to_poly_offset (vro->op1)) |
| * wi::to_offset (vro->op2) |
| * vn_ref_op_align_unit (vro)); |
| off.to_shwi (&vro->off); |
| } |
| } |
| } |
| |
| static void |
| valueize_refs (vec<vn_reference_op_s> *orig) |
| { |
| bool tem; |
| valueize_refs_1 (orig, &tem); |
| } |
| |
| static vec<vn_reference_op_s> shared_lookup_references; |
| |
| /* Create a vector of vn_reference_op_s structures from REF, a |
| REFERENCE_CLASS_P tree. The vector is shared among all callers of |
| this function. *VALUEIZED_ANYTHING will specify whether any |
| operands were valueized. */ |
| |
| static vec<vn_reference_op_s> |
| valueize_shared_reference_ops_from_ref (tree ref, bool *valueized_anything) |
| { |
| if (!ref) |
| return vNULL; |
| shared_lookup_references.truncate (0); |
| copy_reference_ops_from_ref (ref, &shared_lookup_references); |
| valueize_refs_1 (&shared_lookup_references, valueized_anything); |
| return shared_lookup_references; |
| } |
| |
| /* Create a vector of vn_reference_op_s structures from CALL, a |
| call statement. The vector is shared among all callers of |
| this function. */ |
| |
| static vec<vn_reference_op_s> |
| valueize_shared_reference_ops_from_call (gcall *call) |
| { |
| if (!call) |
| return vNULL; |
| shared_lookup_references.truncate (0); |
| copy_reference_ops_from_call (call, &shared_lookup_references); |
| valueize_refs (&shared_lookup_references); |
| return shared_lookup_references; |
| } |
| |
| /* Lookup a SCCVN reference operation VR in the current hash table. |
| Returns the resulting value number if it exists in the hash table, |
| NULL_TREE otherwise. VNRESULT will be filled in with the actual |
| vn_reference_t stored in the hashtable if something is found. */ |
| |
| static tree |
| vn_reference_lookup_1 (vn_reference_t vr, vn_reference_t *vnresult) |
| { |
| vn_reference_s **slot; |
| hashval_t hash; |
| |
| hash = vr->hashcode; |
| slot = valid_info->references->find_slot_with_hash (vr, hash, NO_INSERT); |
| if (slot) |
| { |
| if (vnresult) |
| *vnresult = (vn_reference_t)*slot; |
| return ((vn_reference_t)*slot)->result; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| |
| /* Partial definition tracking support. */ |
| |
| struct pd_range |
| { |
| HOST_WIDE_INT offset; |
| HOST_WIDE_INT size; |
| }; |
| |
| struct pd_data |
| { |
| tree rhs; |
| HOST_WIDE_INT offset; |
| HOST_WIDE_INT size; |
| }; |
| |
| /* Context for alias walking. */ |
| |
| struct vn_walk_cb_data |
| { |
| vn_walk_cb_data (vn_reference_t vr_, tree orig_ref_, tree *last_vuse_ptr_, |
| vn_lookup_kind vn_walk_kind_, bool tbaa_p_, tree mask_) |
| : vr (vr_), last_vuse_ptr (last_vuse_ptr_), last_vuse (NULL_TREE), |
| mask (mask_), masked_result (NULL_TREE), vn_walk_kind (vn_walk_kind_), |
| tbaa_p (tbaa_p_), saved_operands (vNULL), first_set (-2), |
| first_base_set (-2), known_ranges (NULL) |
| { |
| if (!last_vuse_ptr) |
| last_vuse_ptr = &last_vuse; |
| ao_ref_init (&orig_ref, orig_ref_); |
| if (mask) |
| { |
| wide_int w = wi::to_wide (mask); |
| unsigned int pos = 0, prec = w.get_precision (); |
| pd_data pd; |
| pd.rhs = build_constructor (NULL_TREE, NULL); |
| /* When bitwise and with a constant is done on a memory load, |
| we don't really need all the bits to be defined or defined |
| to constants, we don't really care what is in the position |
| corresponding to 0 bits in the mask. |
| So, push the ranges of those 0 bits in the mask as artificial |
| zero stores and let the partial def handling code do the |
| rest. */ |
| while (pos < prec) |
| { |
| int tz = wi::ctz (w); |
| if (pos + tz > prec) |
| tz = prec - pos; |
| if (tz) |
| { |
| if (BYTES_BIG_ENDIAN) |
| pd.offset = prec - pos - tz; |
| else |
| pd.offset = pos; |
| pd.size = tz; |
| void *r = push_partial_def (pd, 0, 0, 0, prec); |
| gcc_assert (r == NULL_TREE); |
| } |
| pos += tz; |
| if (pos == prec) |
| break; |
| w = wi::lrshift (w, tz); |
| tz = wi::ctz (wi::bit_not (w)); |
| if (pos + tz > prec) |
| tz = prec - pos; |
| pos += tz; |
| w = wi::lrshift (w, tz); |
| } |
| } |
| } |
| ~vn_walk_cb_data (); |
| void *finish (alias_set_type, alias_set_type, tree); |
| void *push_partial_def (pd_data pd, |
| alias_set_type, alias_set_type, HOST_WIDE_INT, |
| HOST_WIDE_INT); |
| |
| vn_reference_t vr; |
| ao_ref orig_ref; |
| tree *last_vuse_ptr; |
| tree last_vuse; |
| tree mask; |
| tree masked_result; |
| vn_lookup_kind vn_walk_kind; |
| bool tbaa_p; |
| vec<vn_reference_op_s> saved_operands; |
| |
| /* The VDEFs of partial defs we come along. */ |
| auto_vec<pd_data, 2> partial_defs; |
| /* The first defs range to avoid splay tree setup in most cases. */ |
| pd_range first_range; |
| alias_set_type first_set; |
| alias_set_type first_base_set; |
| splay_tree known_ranges; |
| obstack ranges_obstack; |
| }; |
| |
| vn_walk_cb_data::~vn_walk_cb_data () |
| { |
| if (known_ranges) |
| { |
| splay_tree_delete (known_ranges); |
| obstack_free (&ranges_obstack, NULL); |
| } |
| saved_operands.release (); |
| } |
| |
| void * |
| vn_walk_cb_data::finish (alias_set_type set, alias_set_type base_set, tree val) |
| { |
| if (first_set != -2) |
| { |
| set = first_set; |
| base_set = first_base_set; |
| } |
| if (mask) |
| { |
| masked_result = val; |
| return (void *) -1; |
| } |
| vec<vn_reference_op_s> &operands |
| = saved_operands.exists () ? saved_operands : vr->operands; |
| return vn_reference_lookup_or_insert_for_pieces (last_vuse, set, base_set, |
| vr->type, operands, val); |
| } |
| |
| /* pd_range splay-tree helpers. */ |
| |
| static int |
| pd_range_compare (splay_tree_key offset1p, splay_tree_key offset2p) |
| { |
| HOST_WIDE_INT offset1 = *(HOST_WIDE_INT *)offset1p; |
| HOST_WIDE_INT offset2 = *(HOST_WIDE_INT *)offset2p; |
| if (offset1 < offset2) |
| return -1; |
| else if (offset1 > offset2) |
| return 1; |
| return 0; |
| } |
| |
| static void * |
| pd_tree_alloc (int size, void *data_) |
| { |
| vn_walk_cb_data *data = (vn_walk_cb_data *)data_; |
| return obstack_alloc (&data->ranges_obstack, size); |
| } |
| |
| static void |
| pd_tree_dealloc (void *, void *) |
| { |
| } |
| |
| /* Push PD to the vector of partial definitions returning a |
| value when we are ready to combine things with VUSE, SET and MAXSIZEI, |
| NULL when we want to continue looking for partial defs or -1 |
| on failure. */ |
| |
| void * |
| vn_walk_cb_data::push_partial_def (pd_data pd, |
| alias_set_type set, alias_set_type base_set, |
| HOST_WIDE_INT offseti, |
| HOST_WIDE_INT maxsizei) |
| { |
| const HOST_WIDE_INT bufsize = 64; |
| /* We're using a fixed buffer for encoding so fail early if the object |
| we want to interpret is bigger. */ |
| if (maxsizei > bufsize * BITS_PER_UNIT |
| || CHAR_BIT != 8 |
| || BITS_PER_UNIT != 8 |
| /* Not prepared to handle PDP endian. */ |
| || BYTES_BIG_ENDIAN != WORDS_BIG_ENDIAN) |
| return (void *)-1; |
| |
| /* Turn too large constant stores into non-constant stores. */ |
| if (CONSTANT_CLASS_P (pd.rhs) && pd.size > bufsize * BITS_PER_UNIT) |
| pd.rhs = error_mark_node; |
| |
| /* And for non-constant or CONSTRUCTOR stores shrink them to only keep at |
| most a partial byte before and/or after the region. */ |
| if (!CONSTANT_CLASS_P (pd.rhs)) |
| { |
| if (pd.offset < offseti) |
| { |
| HOST_WIDE_INT o = ROUND_DOWN (offseti - pd.offset, BITS_PER_UNIT); |
| gcc_assert (pd.size > o); |
| pd.size -= o; |
| pd.offset += o; |
| } |
| if (pd.size > maxsizei) |
| pd.size = maxsizei + ((pd.size - maxsizei) % BITS_PER_UNIT); |
| } |
| |
| pd.offset -= offseti; |
| |
| bool pd_constant_p = (TREE_CODE (pd.rhs) == CONSTRUCTOR |
| || CONSTANT_CLASS_P (pd.rhs)); |
| if (partial_defs.is_empty ()) |
| { |
| /* If we get a clobber upfront, fail. */ |
| if (TREE_CLOBBER_P (pd.rhs)) |
| return (void *)-1; |
| if (!pd_constant_p) |
| return (void *)-1; |
| partial_defs.safe_push (pd); |
| first_range.offset = pd.offset; |
| first_range.size = pd.size; |
| first_set = set; |
| first_base_set = base_set; |
| last_vuse_ptr = NULL; |
| /* Continue looking for partial defs. */ |
| return NULL; |
| } |
| |
| if (!known_ranges) |
| { |
| /* ??? Optimize the case where the 2nd partial def completes things. */ |
| gcc_obstack_init (&ranges_obstack); |
| known_ranges = splay_tree_new_with_allocator (pd_range_compare, 0, 0, |
| pd_tree_alloc, |
| pd_tree_dealloc, this); |
| splay_tree_insert (known_ranges, |
| (splay_tree_key)&first_range.offset, |
| (splay_tree_value)&first_range); |
| } |
| |
| pd_range newr = { pd.offset, pd.size }; |
| splay_tree_node n; |
| pd_range *r; |
| /* Lookup the predecessor of offset + 1 and see if we need to merge. */ |
| HOST_WIDE_INT loffset = newr.offset + 1; |
| if ((n = splay_tree_predecessor (known_ranges, (splay_tree_key)&loffset)) |
| && ((r = (pd_range *)n->value), true) |
| && ranges_known_overlap_p (r->offset, r->size + 1, |
| newr.offset, newr.size)) |
| { |
| /* Ignore partial defs already covered. Here we also drop shadowed |
| clobbers arriving here at the floor. */ |
| if (known_subrange_p (newr.offset, newr.size, r->offset, r->size)) |
| return NULL; |
| r->size = MAX (r->offset + r->size, newr.offset + newr.size) - r->offset; |
| } |
| else |
| { |
| /* newr.offset wasn't covered yet, insert the range. */ |
| r = XOBNEW (&ranges_obstack, pd_range); |
| *r = newr; |
| splay_tree_insert (known_ranges, (splay_tree_key)&r->offset, |
| (splay_tree_value)r); |
| } |
| /* Merge r which now contains newr and is a member of the splay tree with |
| adjacent overlapping ranges. */ |
| pd_range *rafter; |
| while ((n = splay_tree_successor (known_ranges, (splay_tree_key)&r->offset)) |
| && ((rafter = (pd_range *)n->value), true) |
| && ranges_known_overlap_p (r->offset, r->size + 1, |
| rafter->offset, rafter->size)) |
| { |
| r->size = MAX (r->offset + r->size, |
| rafter->offset + rafter->size) - r->offset; |
| splay_tree_remove (known_ranges, (splay_tree_key)&rafter->offset); |
| } |
| /* If we get a clobber, fail. */ |
| if (TREE_CLOBBER_P (pd.rhs)) |
| return (void *)-1; |
| /* Non-constants are OK as long as they are shadowed by a constant. */ |
| if (!pd_constant_p) |
| return (void *)-1; |
| partial_defs.safe_push (pd); |
| |
| /* Now we have merged newr into the range tree. When we have covered |
| [offseti, sizei] then the tree will contain exactly one node which has |
| the desired properties and it will be 'r'. */ |
| if (!known_subrange_p (0, maxsizei, r->offset, r->size)) |
| /* Continue looking for partial defs. */ |
| return NULL; |
| |
| /* Now simply native encode all partial defs in reverse order. */ |
| unsigned ndefs = partial_defs.length (); |
| /* We support up to 512-bit values (for V8DFmode). */ |
| unsigned char buffer[bufsize + 1]; |
| unsigned char this_buffer[bufsize + 1]; |
| int len; |
| |
| memset (buffer, 0, bufsize + 1); |
| unsigned needed_len = ROUND_UP (maxsizei, BITS_PER_UNIT) / BITS_PER_UNIT; |
| while (!partial_defs.is_empty ()) |
| { |
| pd_data pd = partial_defs.pop (); |
| unsigned int amnt; |
| if (TREE_CODE (pd.rhs) == CONSTRUCTOR) |
| { |
| /* Empty CONSTRUCTOR. */ |
| if (pd.size >= needed_len * BITS_PER_UNIT) |
| len = needed_len; |
| else |
| len = ROUND_UP (pd.size, BITS_PER_UNIT) / BITS_PER_UNIT; |
| memset (this_buffer, 0, len); |
| } |
| else |
| { |
| len = native_encode_expr (pd.rhs, this_buffer, bufsize, |
| MAX (0, -pd.offset) / BITS_PER_UNIT); |
| if (len <= 0 |
| || len < (ROUND_UP (pd.size, BITS_PER_UNIT) / BITS_PER_UNIT |
| - MAX (0, -pd.offset) / BITS_PER_UNIT)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Failed to encode %u " |
| "partial definitions\n", ndefs); |
| return (void *)-1; |
| } |
| } |
| |
| unsigned char *p = buffer; |
| HOST_WIDE_INT size = pd.size; |
| if (pd.offset < 0) |
| size -= ROUND_DOWN (-pd.offset, BITS_PER_UNIT); |
| this_buffer[len] = 0; |
| if (BYTES_BIG_ENDIAN) |
| { |
| /* LSB of this_buffer[len - 1] byte should be at |
| pd.offset + pd.size - 1 bits in buffer. */ |
| amnt = ((unsigned HOST_WIDE_INT) pd.offset |
| + pd.size) % BITS_PER_UNIT; |
| if (amnt) |
| shift_bytes_in_array_right (this_buffer, len + 1, amnt); |
| unsigned char *q = this_buffer; |
| unsigned int off = 0; |
| if (pd.offset >= 0) |
| { |
| unsigned int msk; |
| off = pd.offset / BITS_PER_UNIT; |
| gcc_assert (off < needed_len); |
| p = buffer + off; |
| if (size <= amnt) |
| { |
| msk = ((1 << size) - 1) << (BITS_PER_UNIT - amnt); |
| *p = (*p & ~msk) | (this_buffer[len] & msk); |
| size = 0; |
| } |
| else |
| { |
| if (TREE_CODE (pd.rhs) != CONSTRUCTOR) |
| q = (this_buffer + len |
| - (ROUND_UP (size - amnt, BITS_PER_UNIT) |
| / BITS_PER_UNIT)); |
| if (pd.offset % BITS_PER_UNIT) |
| { |
| msk = -1U << (BITS_PER_UNIT |
| - (pd.offset % BITS_PER_UNIT)); |
| *p = (*p & msk) | (*q & ~msk); |
| p++; |
| q++; |
| off++; |
| size -= BITS_PER_UNIT - (pd.offset % BITS_PER_UNIT); |
| gcc_assert (size >= 0); |
| } |
| } |
| } |
| else if (TREE_CODE (pd.rhs) != CONSTRUCTOR) |
| { |
| q = (this_buffer + len |
| - (ROUND_UP (size - amnt, BITS_PER_UNIT) |
| / BITS_PER_UNIT)); |
| if (pd.offset % BITS_PER_UNIT) |
| { |
| q++; |
| size -= BITS_PER_UNIT - ((unsigned HOST_WIDE_INT) pd.offset |
| % BITS_PER_UNIT); |
| gcc_assert (size >= 0); |
| } |
| } |
| if ((unsigned HOST_WIDE_INT) size / BITS_PER_UNIT + off |
| > needed_len) |
| size = (needed_len - off) * BITS_PER_UNIT; |
| memcpy (p, q, size / BITS_PER_UNIT); |
| if (size % BITS_PER_UNIT) |
| { |
| unsigned int msk |
| = -1U << (BITS_PER_UNIT - (size % BITS_PER_UNIT)); |
| p += size / BITS_PER_UNIT; |
| q += size / BITS_PER_UNIT; |
| *p = (*q & msk) | (*p & ~msk); |
| } |
| } |
| else |
| { |
| if (pd.offset >= 0) |
| { |
| /* LSB of this_buffer[0] byte should be at pd.offset bits |
| in buffer. */ |
| unsigned int msk; |
| size = MIN (size, (HOST_WIDE_INT) needed_len * BITS_PER_UNIT); |
| amnt = pd.offset % BITS_PER_UNIT; |
| if (amnt) |
| shift_bytes_in_array_left (this_buffer, len + 1, amnt); |
| unsigned int off = pd.offset / BITS_PER_UNIT; |
| gcc_assert (off < needed_len); |
| size = MIN (size, |
| (HOST_WIDE_INT) (needed_len - off) * BITS_PER_UNIT); |
| p = buffer + off; |
| if (amnt + size < BITS_PER_UNIT) |
| { |
| /* Low amnt bits come from *p, then size bits |
| from this_buffer[0] and the remaining again from |
| *p. */ |
| msk = ((1 << size) - 1) << amnt; |
| *p = (*p & ~msk) | (this_buffer[0] & msk); |
| size = 0; |
| } |
| else if (amnt) |
| { |
| msk = -1U << amnt; |
| *p = (*p & ~msk) | (this_buffer[0] & msk); |
| p++; |
| size -= (BITS_PER_UNIT - amnt); |
| } |
| } |
| else |
| { |
| amnt = (unsigned HOST_WIDE_INT) pd.offset % BITS_PER_UNIT; |
| if (amnt) |
| size -= BITS_PER_UNIT - amnt; |
| size = MIN (size, (HOST_WIDE_INT) needed_len * BITS_PER_UNIT); |
| if (amnt) |
| shift_bytes_in_array_left (this_buffer, len + 1, amnt); |
| } |
| memcpy (p, this_buffer + (amnt != 0), size / BITS_PER_UNIT); |
| p += size / BITS_PER_UNIT; |
| if (size % BITS_PER_UNIT) |
| { |
| unsigned int msk = -1U << (size % BITS_PER_UNIT); |
| *p = (this_buffer[(amnt != 0) + size / BITS_PER_UNIT] |
| & ~msk) | (*p & msk); |
| } |
| } |
| } |
| |
| tree type = vr->type; |
| /* Make sure to interpret in a type that has a range covering the whole |
| access size. */ |
| if (INTEGRAL_TYPE_P (vr->type) && maxsizei != TYPE_PRECISION (vr->type)) |
| type = build_nonstandard_integer_type (maxsizei, TYPE_UNSIGNED (type)); |
| tree val; |
| if (BYTES_BIG_ENDIAN) |
| { |
| unsigned sz = needed_len; |
| if (maxsizei % BITS_PER_UNIT) |
| shift_bytes_in_array_right (buffer, needed_len, |
| BITS_PER_UNIT |
| - (maxsizei % BITS_PER_UNIT)); |
| if (INTEGRAL_TYPE_P (type)) |
| sz = GET_MODE_SIZE (SCALAR_INT_TYPE_MODE (type)); |
| if (sz > needed_len) |
| { |
| memcpy (this_buffer + (sz - needed_len), buffer, needed_len); |
| val = native_interpret_expr (type, this_buffer, sz); |
| } |
| else |
| val = native_interpret_expr (type, buffer, needed_len); |
| } |
| else |
| val = native_interpret_expr (type, buffer, bufsize); |
| /* If we chop off bits because the types precision doesn't match the memory |
| access size this is ok when optimizing reads but not when called from |
| the DSE code during elimination. */ |
| if (val && type != vr->type) |
| { |
| if (! int_fits_type_p (val, vr->type)) |
| val = NULL_TREE; |
| else |
| val = fold_convert (vr->type, val); |
| } |
| |
| if (val) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Successfully combined %u partial definitions\n", ndefs); |
| /* We are using the alias-set of the first store we encounter which |
| should be appropriate here. */ |
| return finish (first_set, first_base_set, val); |
| } |
| else |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "Failed to interpret %u encoded partial definitions\n", ndefs); |
| return (void *)-1; |
| } |
| } |
| |
| /* Callback for walk_non_aliased_vuses. Adjusts the vn_reference_t VR_ |
| with the current VUSE and performs the expression lookup. */ |
| |
| static void * |
| vn_reference_lookup_2 (ao_ref *op ATTRIBUTE_UNUSED, tree vuse, void *data_) |
| { |
| vn_walk_cb_data *data = (vn_walk_cb_data *)data_; |
| vn_reference_t vr = data->vr; |
| vn_reference_s **slot; |
| hashval_t hash; |
| |
| /* If we have partial definitions recorded we have to go through |
| vn_reference_lookup_3. */ |
| if (!data->partial_defs.is_empty ()) |
| return NULL; |
| |
| if (data->last_vuse_ptr) |
| { |
| *data->last_vuse_ptr = vuse; |
| data->last_vuse = vuse; |
| } |
| |
| /* Fixup vuse and hash. */ |
| if (vr->vuse) |
| vr->hashcode = vr->hashcode - SSA_NAME_VERSION (vr->vuse); |
| vr->vuse = vuse_ssa_val (vuse); |
| if (vr->vuse) |
| vr->hashcode = vr->hashcode + SSA_NAME_VERSION (vr->vuse); |
| |
| hash = vr->hashcode; |
| slot = valid_info->references->find_slot_with_hash (vr, hash, NO_INSERT); |
| if (slot) |
| { |
| if ((*slot)->result && data->saved_operands.exists ()) |
| return data->finish (vr->set, vr->base_set, (*slot)->result); |
| return *slot; |
| } |
| |
| return NULL; |
| } |
| |
| /* Lookup an existing or insert a new vn_reference entry into the |
| value table for the VUSE, SET, TYPE, OPERANDS reference which |
| has the value VALUE which is either a constant or an SSA name. */ |
| |
| static vn_reference_t |
| vn_reference_lookup_or_insert_for_pieces (tree vuse, |
| alias_set_type set, |
| alias_set_type base_set, |
| tree type, |
| vec<vn_reference_op_s, |
| va_heap> operands, |
| tree value) |
| { |
| vn_reference_s vr1; |
| vn_reference_t result; |
| unsigned value_id; |
| vr1.vuse = vuse ? SSA_VAL (vuse) : NULL_TREE; |
| vr1.operands = operands; |
| vr1.type = type; |
| vr1.set = set; |
| vr1.base_set = base_set; |
| vr1.hashcode = vn_reference_compute_hash (&vr1); |
| if (vn_reference_lookup_1 (&vr1, &result)) |
| return result; |
| if (TREE_CODE (value) == SSA_NAME) |
| value_id = VN_INFO (value)->value_id; |
| else |
| value_id = get_or_alloc_constant_value_id (value); |
| return vn_reference_insert_pieces (vuse, set, base_set, type, |
| operands.copy (), value, value_id); |
| } |
| |
| /* Return a value-number for RCODE OPS... either by looking up an existing |
| value-number for the possibly simplified result or by inserting the |
| operation if INSERT is true. If SIMPLIFY is false, return a value |
| number for the unsimplified expression. */ |
| |
| static tree |
| vn_nary_build_or_lookup_1 (gimple_match_op *res_op, bool insert, |
| bool simplify) |
| { |
| tree result = NULL_TREE; |
| /* We will be creating a value number for |
| RCODE (OPS...). |
| So first simplify and lookup this expression to see if it |
| is already available. */ |
| /* For simplification valueize. */ |
| unsigned i = 0; |
| if (simplify) |
| for (i = 0; i < res_op->num_ops; ++i) |
| if (TREE_CODE (res_op->ops[i]) == SSA_NAME) |
| { |
| tree tem = vn_valueize (res_op->ops[i]); |
| if (!tem) |
| break; |
| res_op->ops[i] = tem; |
| } |
| /* If valueization of an operand fails (it is not available), skip |
| simplification. */ |
| bool res = false; |
| if (i == res_op->num_ops) |
| { |
| mprts_hook = vn_lookup_simplify_result; |
| res = res_op->resimplify (NULL, vn_valueize); |
| mprts_hook = NULL; |
| } |
| gimple *new_stmt = NULL; |
| if (res |
| && gimple_simplified_result_is_gimple_val (res_op)) |
| { |
| /* The expression is already available. */ |
| result = res_op->ops[0]; |
| /* Valueize it, simplification returns sth in AVAIL only. */ |
| if (TREE_CODE (result) == SSA_NAME) |
| result = SSA_VAL (result); |
| } |
| else |
| { |
| tree val = vn_lookup_simplify_result (res_op); |
| if (!val && insert) |
| { |
| gimple_seq stmts = NULL; |
| result = maybe_push_res_to_seq (res_op, &stmts); |
| if (result) |
| { |
| gcc_assert (gimple_seq_singleton_p (stmts)); |
| new_stmt = gimple_seq_first_stmt (stmts); |
| } |
| } |
| else |
| /* The expression is already available. */ |
| result = val; |
| } |
| if (new_stmt) |
| { |
| /* The expression is not yet available, value-number lhs to |
| the new SSA_NAME we created. */ |
| /* Initialize value-number information properly. */ |
| vn_ssa_aux_t result_info = VN_INFO (result); |
| result_info->valnum = result; |
| result_info->value_id = get_next_value_id (); |
| result_info->visited = 1; |
| gimple_seq_add_stmt_without_update (&VN_INFO (result)->expr, |
| new_stmt); |
| result_info->needs_insertion = true; |
| /* ??? PRE phi-translation inserts NARYs without corresponding |
| SSA name result. Re-use those but set their result according |
| to the stmt we just built. */ |
| vn_nary_op_t nary = NULL; |
| vn_nary_op_lookup_stmt (new_stmt, &nary); |
| if (nary) |
| { |
| gcc_assert (! nary->predicated_values && nary->u.result == NULL_TREE); |
| nary->u.result = gimple_assign_lhs (new_stmt); |
| } |
| /* As all "inserted" statements are singleton SCCs, insert |
| to the valid table. This is strictly needed to |
| avoid re-generating new value SSA_NAMEs for the same |
| expression during SCC iteration over and over (the |
| optimistic table gets cleared after each iteration). |
| We do not need to insert into the optimistic table, as |
| lookups there will fall back to the valid table. */ |
| else |
| { |
| unsigned int length = vn_nary_length_from_stmt (new_stmt); |
| vn_nary_op_t vno1 |
| = alloc_vn_nary_op_noinit (length, &vn_tables_insert_obstack); |
| vno1->value_id = result_info->value_id; |
| vno1->length = length; |
| vno1->predicated_values = 0; |
| vno1->u.result = result; |
| init_vn_nary_op_from_stmt (vno1, as_a <gassign *> (new_stmt)); |
| vn_nary_op_insert_into (vno1, valid_info->nary); |
| /* Also do not link it into the undo chain. */ |
| last_inserted_nary = vno1->next; |
| vno1->next = (vn_nary_op_t)(void *)-1; |
| } |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Inserting name "); |
| print_generic_expr (dump_file, result); |
| fprintf (dump_file, " for expression "); |
| print_gimple_expr (dump_file, new_stmt, 0, TDF_SLIM); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| return result; |
| } |
| |
| /* Return a value-number for RCODE OPS... either by looking up an existing |
| value-number for the simplified result or by inserting the operation. */ |
| |
| static tree |
| vn_nary_build_or_lookup (gimple_match_op *res_op) |
| { |
| return vn_nary_build_or_lookup_1 (res_op, true, true); |
| } |
| |
| /* Try to simplify the expression RCODE OPS... of type TYPE and return |
| its value if present. */ |
| |
| tree |
| vn_nary_simplify (vn_nary_op_t nary) |
| { |
| if (nary->length > gimple_match_op::MAX_NUM_OPS) |
| return NULL_TREE; |
| gimple_match_op op (gimple_match_cond::UNCOND, nary->opcode, |
| nary->type, nary->length); |
| memcpy (op.ops, nary->op, sizeof (tree) * nary->length); |
| return vn_nary_build_or_lookup_1 (&op, false, true); |
| } |
| |
| /* Elimination engine. */ |
| |
| class eliminate_dom_walker : public dom_walker |
| { |
| public: |
| eliminate_dom_walker (cdi_direction, bitmap); |
| ~eliminate_dom_walker (); |
| |
| virtual edge before_dom_children (basic_block); |
| virtual void after_dom_children (basic_block); |
| |
| virtual tree eliminate_avail (basic_block, tree op); |
| virtual void eliminate_push_avail (basic_block, tree op); |
| tree eliminate_insert (basic_block, gimple_stmt_iterator *gsi, tree val); |
| |
| void eliminate_stmt (basic_block, gimple_stmt_iterator *); |
| |
| unsigned eliminate_cleanup (bool region_p = false); |
| |
| bool do_pre; |
| unsigned int el_todo; |
| unsigned int eliminations; |
| unsigned int insertions; |
| |
| /* SSA names that had their defs inserted by PRE if do_pre. */ |
| bitmap inserted_exprs; |
| |
| /* Blocks with statements that have had their EH properties changed. */ |
| bitmap need_eh_cleanup; |
| |
| /* Blocks with statements that have had their AB properties changed. */ |
| bitmap need_ab_cleanup; |
| |
| /* Local state for the eliminate domwalk. */ |
| auto_vec<gimple *> to_remove; |
| auto_vec<gimple *> to_fixup; |
| auto_vec<tree> avail; |
| auto_vec<tree> avail_stack; |
| }; |
| |
| /* Adaptor to the elimination engine using RPO availability. */ |
| |
| class rpo_elim : public eliminate_dom_walker |
| { |
| public: |
| rpo_elim(basic_block entry_) |
| : eliminate_dom_walker (CDI_DOMINATORS, NULL), entry (entry_), |
| m_avail_freelist (NULL) {} |
| |
| virtual tree eliminate_avail (basic_block, tree op); |
| |
| virtual void eliminate_push_avail (basic_block, tree); |
| |
| basic_block entry; |
| /* Freelist of avail entries which are allocated from the vn_ssa_aux |
| obstack. */ |
| vn_avail *m_avail_freelist; |
| }; |
| |
| /* Global RPO state for access from hooks. */ |
| static eliminate_dom_walker *rpo_avail; |
| basic_block vn_context_bb; |
| |
| /* Return true if BASE1 and BASE2 can be adjusted so they have the |
| same address and adjust *OFFSET1 and *OFFSET2 accordingly. |
| Otherwise return false. */ |
| |
| static bool |
| adjust_offsets_for_equal_base_address (tree base1, poly_int64 *offset1, |
| tree base2, poly_int64 *offset2) |
| { |
| poly_int64 soff; |
| if (TREE_CODE (base1) == MEM_REF |
| && TREE_CODE (base2) == MEM_REF) |
| { |
| if (mem_ref_offset (base1).to_shwi (&soff)) |
| { |
| base1 = TREE_OPERAND (base1, 0); |
| *offset1 += soff * BITS_PER_UNIT; |
| } |
| if (mem_ref_offset (base2).to_shwi (&soff)) |
| { |
| base2 = TREE_OPERAND (base2, 0); |
| *offset2 += soff * BITS_PER_UNIT; |
| } |
| return operand_equal_p (base1, base2, 0); |
| } |
| return operand_equal_p (base1, base2, OEP_ADDRESS_OF); |
| } |
| |
| /* Callback for walk_non_aliased_vuses. Tries to perform a lookup |
| from the statement defining VUSE and if not successful tries to |
| translate *REFP and VR_ through an aggregate copy at the definition |
| of VUSE. If *DISAMBIGUATE_ONLY is true then do not perform translation |
| of *REF and *VR. If only disambiguation was performed then |
| *DISAMBIGUATE_ONLY is set to true. */ |
| |
| static void * |
| vn_reference_lookup_3 (ao_ref *ref, tree vuse, void *data_, |
| translate_flags *disambiguate_only) |
| { |
| vn_walk_cb_data *data = (vn_walk_cb_data *)data_; |
| vn_reference_t vr = data->vr; |
| gimple *def_stmt = SSA_NAME_DEF_STMT (vuse); |
| tree base = ao_ref_base (ref); |
| HOST_WIDE_INT offseti = 0, maxsizei, sizei = 0; |
| static vec<vn_reference_op_s> lhs_ops; |
| ao_ref lhs_ref; |
| bool lhs_ref_ok = false; |
| poly_int64 copy_size; |
| |
| /* First try to disambiguate after value-replacing in the definitions LHS. */ |
| if (is_gimple_assign (def_stmt)) |
| { |
| tree lhs = gimple_assign_lhs (def_stmt); |
| bool valueized_anything = false; |
| /* Avoid re-allocation overhead. */ |
| lhs_ops.truncate (0); |
| basic_block saved_rpo_bb = vn_context_bb; |
| vn_context_bb = gimple_bb (def_stmt); |
| if (*disambiguate_only <= TR_VALUEIZE_AND_DISAMBIGUATE) |
| { |
| copy_reference_ops_from_ref (lhs, &lhs_ops); |
| valueize_refs_1 (&lhs_ops, &valueized_anything, true); |
| } |
| vn_context_bb = saved_rpo_bb; |
| ao_ref_init (&lhs_ref, lhs); |
| lhs_ref_ok = true; |
| if (valueized_anything |
| && ao_ref_init_from_vn_reference |
| (&lhs_ref, ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), TREE_TYPE (lhs), lhs_ops) |
| && !refs_may_alias_p_1 (ref, &lhs_ref, data->tbaa_p)) |
| { |
| *disambiguate_only = TR_VALUEIZE_AND_DISAMBIGUATE; |
| return NULL; |
| } |
| |
| /* Besides valueizing the LHS we can also use access-path based |
| disambiguation on the original non-valueized ref. */ |
| if (!ref->ref |
| && lhs_ref_ok |
| && data->orig_ref.ref) |
| { |
| /* We want to use the non-valueized LHS for this, but avoid redundant |
| work. */ |
| ao_ref *lref = &lhs_ref; |
| ao_ref lref_alt; |
| if (valueized_anything) |
| { |
| ao_ref_init (&lref_alt, lhs); |
| lref = &lref_alt; |
| } |
| if (!refs_may_alias_p_1 (&data->orig_ref, lref, data->tbaa_p)) |
| { |
| *disambiguate_only = (valueized_anything |
| ? TR_VALUEIZE_AND_DISAMBIGUATE |
| : TR_DISAMBIGUATE); |
| return NULL; |
| } |
| } |
| |
| /* If we reach a clobbering statement try to skip it and see if |
| we find a VN result with exactly the same value as the |
| possible clobber. In this case we can ignore the clobber |
| and return the found value. */ |
| if (is_gimple_reg_type (TREE_TYPE (lhs)) |
| && types_compatible_p (TREE_TYPE (lhs), vr->type) |
| && (ref->ref || data->orig_ref.ref)) |
| { |
| tree *saved_last_vuse_ptr = data->last_vuse_ptr; |
| /* Do not update last_vuse_ptr in vn_reference_lookup_2. */ |
| data->last_vuse_ptr = NULL; |
| tree saved_vuse = vr->vuse; |
| hashval_t saved_hashcode = vr->hashcode; |
| void *res = vn_reference_lookup_2 (ref, gimple_vuse (def_stmt), data); |
| /* Need to restore vr->vuse and vr->hashcode. */ |
| vr->vuse = saved_vuse; |
| vr->hashcode = saved_hashcode; |
| data->last_vuse_ptr = saved_last_vuse_ptr; |
| if (res && res != (void *)-1) |
| { |
| vn_reference_t vnresult = (vn_reference_t) res; |
| tree rhs = gimple_assign_rhs1 (def_stmt); |
| if (TREE_CODE (rhs) == SSA_NAME) |
| rhs = SSA_VAL (rhs); |
| if (vnresult->result |
| && operand_equal_p (vnresult->result, rhs, 0) |
| /* We have to honor our promise about union type punning |
| and also support arbitrary overlaps with |
| -fno-strict-aliasing. So simply resort to alignment to |
| rule out overlaps. Do this check last because it is |
| quite expensive compared to the hash-lookup above. */ |
| && multiple_p (get_object_alignment |
| (ref->ref ? ref->ref : data->orig_ref.ref), |
| ref->size) |
| && multiple_p (get_object_alignment (lhs), ref->size)) |
| return res; |
| } |
| } |
| } |
| else if (*disambiguate_only <= TR_VALUEIZE_AND_DISAMBIGUATE |
| && gimple_call_builtin_p (def_stmt, BUILT_IN_NORMAL) |
| && gimple_call_num_args (def_stmt) <= 4) |
| { |
| /* For builtin calls valueize its arguments and call the |
| alias oracle again. Valueization may improve points-to |
| info of pointers and constify size and position arguments. |
| Originally this was motivated by PR61034 which has |
| conditional calls to free falsely clobbering ref because |
| of imprecise points-to info of the argument. */ |
| tree oldargs[4]; |
| bool valueized_anything = false; |
| for (unsigned i = 0; i < gimple_call_num_args (def_stmt); ++i) |
| { |
| oldargs[i] = gimple_call_arg (def_stmt, i); |
| tree val = vn_valueize (oldargs[i]); |
| if (val != oldargs[i]) |
| { |
| gimple_call_set_arg (def_stmt, i, val); |
| valueized_anything = true; |
| } |
| } |
| if (valueized_anything) |
| { |
| bool res = call_may_clobber_ref_p_1 (as_a <gcall *> (def_stmt), |
| ref, data->tbaa_p); |
| for (unsigned i = 0; i < gimple_call_num_args (def_stmt); ++i) |
| gimple_call_set_arg (def_stmt, i, oldargs[i]); |
| if (!res) |
| { |
| *disambiguate_only = TR_VALUEIZE_AND_DISAMBIGUATE; |
| return NULL; |
| } |
| } |
| } |
| |
| if (*disambiguate_only > TR_TRANSLATE) |
| return (void *)-1; |
| |
| /* If we cannot constrain the size of the reference we cannot |
| test if anything kills it. */ |
| if (!ref->max_size_known_p ()) |
| return (void *)-1; |
| |
| poly_int64 offset = ref->offset; |
| poly_int64 maxsize = ref->max_size; |
| |
| /* def_stmt may-defs *ref. See if we can derive a value for *ref |
| from that definition. |
| 1) Memset. */ |
| if (is_gimple_reg_type (vr->type) |
| && (gimple_call_builtin_p (def_stmt, BUILT_IN_MEMSET) |
| || gimple_call_builtin_p (def_stmt, BUILT_IN_MEMSET_CHK)) |
| && (integer_zerop (gimple_call_arg (def_stmt, 1)) |
| || ((TREE_CODE (gimple_call_arg (def_stmt, 1)) == INTEGER_CST |
| || (INTEGRAL_TYPE_P (vr->type) && known_eq (ref->size, 8))) |
| && CHAR_BIT == 8 |
| && BITS_PER_UNIT == 8 |
| && BYTES_BIG_ENDIAN == WORDS_BIG_ENDIAN |
| && offset.is_constant (&offseti) |
| && ref->size.is_constant (&sizei) |
| && (offseti % BITS_PER_UNIT == 0 |
| || TREE_CODE (gimple_call_arg (def_stmt, 1)) == INTEGER_CST))) |
| && (poly_int_tree_p (gimple_call_arg (def_stmt, 2)) |
| || (TREE_CODE (gimple_call_arg (def_stmt, 2)) == SSA_NAME |
| && poly_int_tree_p (SSA_VAL (gimple_call_arg (def_stmt, 2))))) |
| && (TREE_CODE (gimple_call_arg (def_stmt, 0)) == ADDR_EXPR |
| || TREE_CODE (gimple_call_arg (def_stmt, 0)) == SSA_NAME)) |
| { |
| tree base2; |
| poly_int64 offset2, size2, maxsize2; |
| bool reverse; |
| tree ref2 = gimple_call_arg (def_stmt, 0); |
| if (TREE_CODE (ref2) == SSA_NAME) |
| { |
| ref2 = SSA_VAL (ref2); |
| if (TREE_CODE (ref2) == SSA_NAME |
| && (TREE_CODE (base) != MEM_REF |
| || TREE_OPERAND (base, 0) != ref2)) |
| { |
| gimple *def_stmt = SSA_NAME_DEF_STMT (ref2); |
| if (gimple_assign_single_p (def_stmt) |
| && gimple_assign_rhs_code (def_stmt) == ADDR_EXPR) |
| ref2 = gimple_assign_rhs1 (def_stmt); |
| } |
| } |
| if (TREE_CODE (ref2) == ADDR_EXPR) |
| { |
| ref2 = TREE_OPERAND (ref2, 0); |
| base2 = get_ref_base_and_extent (ref2, &offset2, &size2, &maxsize2, |
| &reverse); |
| if (!known_size_p (maxsize2) |
| || !known_eq (maxsize2, size2) |
| || !operand_equal_p (base, base2, OEP_ADDRESS_OF)) |
| return (void *)-1; |
| } |
| else if (TREE_CODE (ref2) == SSA_NAME) |
| { |
| poly_int64 soff; |
| if (TREE_CODE (base) != MEM_REF |
| || !(mem_ref_offset (base) |
| << LOG2_BITS_PER_UNIT).to_shwi (&soff)) |
| return (void *)-1; |
| offset += soff; |
| offset2 = 0; |
| if (TREE_OPERAND (base, 0) != ref2) |
| { |
| gimple *def = SSA_NAME_DEF_STMT (ref2); |
| if (is_gimple_assign (def) |
| && gimple_assign_rhs_code (def) == POINTER_PLUS_EXPR |
| && gimple_assign_rhs1 (def) == TREE_OPERAND (base, 0) |
| && poly_int_tree_p (gimple_assign_rhs2 (def))) |
| { |
| tree rhs2 = gimple_assign_rhs2 (def); |
| if (!(poly_offset_int::from (wi::to_poly_wide (rhs2), |
| SIGNED) |
| << LOG2_BITS_PER_UNIT).to_shwi (&offset2)) |
| return (void *)-1; |
| ref2 = gimple_assign_rhs1 (def); |
| if (TREE_CODE (ref2) == SSA_NAME) |
| ref2 = SSA_VAL (ref2); |
| } |
| else |
| return (void *)-1; |
| } |
| } |
| else |
| return (void *)-1; |
| tree len = gimple_call_arg (def_stmt, 2); |
| HOST_WIDE_INT leni, offset2i; |
| if (TREE_CODE (len) == SSA_NAME) |
| len = SSA_VAL (len); |
| /* Sometimes the above trickery is smarter than alias analysis. Take |
| advantage of that. */ |
| if (!ranges_maybe_overlap_p (offset, maxsize, offset2, |
| (wi::to_poly_offset (len) |
| << LOG2_BITS_PER_UNIT))) |
| return NULL; |
| if (data->partial_defs.is_empty () |
| && known_subrange_p (offset, maxsize, offset2, |
| wi::to_poly_offset (len) << LOG2_BITS_PER_UNIT)) |
| { |
| tree val; |
| if (integer_zerop (gimple_call_arg (def_stmt, 1))) |
| val = build_zero_cst (vr->type); |
| else if (INTEGRAL_TYPE_P (vr->type) |
| && known_eq (ref->size, 8) |
| && offseti % BITS_PER_UNIT == 0) |
| { |
| gimple_match_op res_op (gimple_match_cond::UNCOND, NOP_EXPR, |
| vr->type, gimple_call_arg (def_stmt, 1)); |
| val = vn_nary_build_or_lookup (&res_op); |
| if (!val |
| || (TREE_CODE (val) == SSA_NAME |
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (val))) |
| return (void *)-1; |
| } |
| else |
| { |
| unsigned buflen = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (vr->type)) + 1; |
| if (INTEGRAL_TYPE_P (vr->type)) |
| buflen = GET_MODE_SIZE (SCALAR_INT_TYPE_MODE (vr->type)) + 1; |
| unsigned char *buf = XALLOCAVEC (unsigned char, buflen); |
| memset (buf, TREE_INT_CST_LOW (gimple_call_arg (def_stmt, 1)), |
| buflen); |
| if (BYTES_BIG_ENDIAN) |
| { |
| unsigned int amnt |
| = (((unsigned HOST_WIDE_INT) offseti + sizei) |
| % BITS_PER_UNIT); |
| if (amnt) |
| { |
| shift_bytes_in_array_right (buf, buflen, |
| BITS_PER_UNIT - amnt); |
| buf++; |
| buflen--; |
| } |
| } |
| else if (offseti % BITS_PER_UNIT != 0) |
| { |
| unsigned int amnt |
| = BITS_PER_UNIT - ((unsigned HOST_WIDE_INT) offseti |
| % BITS_PER_UNIT); |
| shift_bytes_in_array_left (buf, buflen, amnt); |
| buf++; |
| buflen--; |
| } |
| val = native_interpret_expr (vr->type, buf, buflen); |
| if (!val) |
| return (void *)-1; |
| } |
| return data->finish (0, 0, val); |
| } |
| /* For now handle clearing memory with partial defs. */ |
| else if (known_eq (ref->size, maxsize) |
| && integer_zerop (gimple_call_arg (def_stmt, 1)) |
| && tree_fits_poly_int64_p (len) |
| && tree_to_poly_int64 (len).is_constant (&leni) |
| && leni <= INTTYPE_MAXIMUM (HOST_WIDE_INT) / BITS_PER_UNIT |
| && offset.is_constant (&offseti) |
| && offset2.is_constant (&offset2i) |
| && maxsize.is_constant (&maxsizei) |
| && ranges_known_overlap_p (offseti, maxsizei, offset2i, |
| leni << LOG2_BITS_PER_UNIT)) |
| { |
| pd_data pd; |
| pd.rhs = build_constructor (NULL_TREE, NULL); |
| pd.offset = offset2i; |
| pd.size = leni << LOG2_BITS_PER_UNIT; |
| return data->push_partial_def (pd, 0, 0, offseti, maxsizei); |
| } |
| } |
| |
| /* 2) Assignment from an empty CONSTRUCTOR. */ |
| else if (is_gimple_reg_type (vr->type) |
| && gimple_assign_single_p (def_stmt) |
| && gimple_assign_rhs_code (def_stmt) == CONSTRUCTOR |
| && CONSTRUCTOR_NELTS (gimple_assign_rhs1 (def_stmt)) == 0) |
| { |
| tree base2; |
| poly_int64 offset2, size2, maxsize2; |
| HOST_WIDE_INT offset2i, size2i; |
| gcc_assert (lhs_ref_ok); |
| base2 = ao_ref_base (&lhs_ref); |
| offset2 = lhs_ref.offset; |
| size2 = lhs_ref.size; |
| maxsize2 = lhs_ref.max_size; |
| if (known_size_p (maxsize2) |
| && known_eq (maxsize2, size2) |
| && adjust_offsets_for_equal_base_address (base, &offset, |
| base2, &offset2)) |
| { |
| if (data->partial_defs.is_empty () |
| && known_subrange_p (offset, maxsize, offset2, size2)) |
| { |
| /* While technically undefined behavior do not optimize |
| a full read from a clobber. */ |
| if (gimple_clobber_p (def_stmt)) |
| return (void *)-1; |
| tree val = build_zero_cst (vr->type); |
| return data->finish (ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), val); |
| } |
| else if (known_eq (ref->size, maxsize) |
| && maxsize.is_constant (&maxsizei) |
| && offset.is_constant (&offseti) |
| && offset2.is_constant (&offset2i) |
| && size2.is_constant (&size2i) |
| && ranges_known_overlap_p (offseti, maxsizei, |
| offset2i, size2i)) |
| { |
| /* Let clobbers be consumed by the partial-def tracker |
| which can choose to ignore them if they are shadowed |
| by a later def. */ |
| pd_data pd; |
| pd.rhs = gimple_assign_rhs1 (def_stmt); |
| pd.offset = offset2i; |
| pd.size = size2i; |
| return data->push_partial_def (pd, ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), |
| offseti, maxsizei); |
| } |
| } |
| } |
| |
| /* 3) Assignment from a constant. We can use folds native encode/interpret |
| routines to extract the assigned bits. */ |
| else if (known_eq (ref->size, maxsize) |
| && is_gimple_reg_type (vr->type) |
| && !reverse_storage_order_for_component_p (vr->operands) |
| && !contains_storage_order_barrier_p (vr->operands) |
| && gimple_assign_single_p (def_stmt) |
| && CHAR_BIT == 8 |
| && BITS_PER_UNIT == 8 |
| && BYTES_BIG_ENDIAN == WORDS_BIG_ENDIAN |
| /* native_encode and native_decode operate on arrays of bytes |
| and so fundamentally need a compile-time size and offset. */ |
| && maxsize.is_constant (&maxsizei) |
| && offset.is_constant (&offseti) |
| && (is_gimple_min_invariant (gimple_assign_rhs1 (def_stmt)) |
| || (TREE_CODE (gimple_assign_rhs1 (def_stmt)) == SSA_NAME |
| && is_gimple_min_invariant (SSA_VAL (gimple_assign_rhs1 (def_stmt)))))) |
| { |
| tree lhs = gimple_assign_lhs (def_stmt); |
| tree base2; |
| poly_int64 offset2, size2, maxsize2; |
| HOST_WIDE_INT offset2i, size2i; |
| bool reverse; |
| gcc_assert (lhs_ref_ok); |
| base2 = ao_ref_base (&lhs_ref); |
| offset2 = lhs_ref.offset; |
| size2 = lhs_ref.size; |
| maxsize2 = lhs_ref.max_size; |
| reverse = reverse_storage_order_for_component_p (lhs); |
| if (base2 |
| && !reverse |
| && !storage_order_barrier_p (lhs) |
| && known_eq (maxsize2, size2) |
| && adjust_offsets_for_equal_base_address (base, &offset, |
| base2, &offset2) |
| && offset.is_constant (&offseti) |
| && offset2.is_constant (&offset2i) |
| && size2.is_constant (&size2i)) |
| { |
| if (data->partial_defs.is_empty () |
| && known_subrange_p (offseti, maxsizei, offset2, size2)) |
| { |
| /* We support up to 512-bit values (for V8DFmode). */ |
| unsigned char buffer[65]; |
| int len; |
| |
| tree rhs = gimple_assign_rhs1 (def_stmt); |
| if (TREE_CODE (rhs) == SSA_NAME) |
| rhs = SSA_VAL (rhs); |
| len = native_encode_expr (rhs, |
| buffer, sizeof (buffer) - 1, |
| (offseti - offset2i) / BITS_PER_UNIT); |
| if (len > 0 && len * BITS_PER_UNIT >= maxsizei) |
| { |
| tree type = vr->type; |
| unsigned char *buf = buffer; |
| unsigned int amnt = 0; |
| /* Make sure to interpret in a type that has a range |
| covering the whole access size. */ |
| if (INTEGRAL_TYPE_P (vr->type) |
| && maxsizei != TYPE_PRECISION (vr->type)) |
| type = build_nonstandard_integer_type (maxsizei, |
| TYPE_UNSIGNED (type)); |
| if (BYTES_BIG_ENDIAN) |
| { |
| /* For big-endian native_encode_expr stored the rhs |
| such that the LSB of it is the LSB of buffer[len - 1]. |
| That bit is stored into memory at position |
| offset2 + size2 - 1, i.e. in byte |
| base + (offset2 + size2 - 1) / BITS_PER_UNIT. |
| E.g. for offset2 1 and size2 14, rhs -1 and memory |
| previously cleared that is: |
| 0 1 |
| 01111111|11111110 |
| Now, if we want to extract offset 2 and size 12 from |
| it using native_interpret_expr (which actually works |
| for integral bitfield types in terms of byte size of |
| the mode), the native_encode_expr stored the value |
| into buffer as |
| XX111111|11111111 |
| and returned len 2 (the X bits are outside of |
| precision). |
| Let sz be maxsize / BITS_PER_UNIT if not extracting |
| a bitfield, and GET_MODE_SIZE otherwise. |
| We need to align the LSB of the value we want to |
| extract as the LSB of buf[sz - 1]. |
| The LSB from memory we need to read is at position |
| offset + maxsize - 1. */ |
| HOST_WIDE_INT sz = maxsizei / BITS_PER_UNIT; |
| if (INTEGRAL_TYPE_P (type)) |
| sz = GET_MODE_SIZE (SCALAR_INT_TYPE_MODE (type)); |
| amnt = ((unsigned HOST_WIDE_INT) offset2i + size2i |
| - offseti - maxsizei) % BITS_PER_UNIT; |
| if (amnt) |
| shift_bytes_in_array_right (buffer, len, amnt); |
| amnt = ((unsigned HOST_WIDE_INT) offset2i + size2i |
| - offseti - maxsizei - amnt) / BITS_PER_UNIT; |
| if ((unsigned HOST_WIDE_INT) sz + amnt > (unsigned) len) |
| len = 0; |
| else |
| { |
| buf = buffer + len - sz - amnt; |
| len -= (buf - buffer); |
| } |
| } |
| else |
| { |
| amnt = ((unsigned HOST_WIDE_INT) offset2i |
| - offseti) % BITS_PER_UNIT; |
| if (amnt) |
| { |
| buffer[len] = 0; |
| shift_bytes_in_array_left (buffer, len + 1, amnt); |
| buf = buffer + 1; |
| } |
| } |
| tree val = native_interpret_expr (type, buf, len); |
| /* If we chop off bits because the types precision doesn't |
| match the memory access size this is ok when optimizing |
| reads but not when called from the DSE code during |
| elimination. */ |
| if (val |
| && type != vr->type) |
| { |
| if (! int_fits_type_p (val, vr->type)) |
| val = NULL_TREE; |
| else |
| val = fold_convert (vr->type, val); |
| } |
| |
| if (val) |
| return data->finish (ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), val); |
| } |
| } |
| else if (ranges_known_overlap_p (offseti, maxsizei, offset2i, |
| size2i)) |
| { |
| pd_data pd; |
| tree rhs = gimple_assign_rhs1 (def_stmt); |
| if (TREE_CODE (rhs) == SSA_NAME) |
| rhs = SSA_VAL (rhs); |
| pd.rhs = rhs; |
| pd.offset = offset2i; |
| pd.size = size2i; |
| return data->push_partial_def (pd, ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), |
| offseti, maxsizei); |
| } |
| } |
| } |
| |
| /* 4) Assignment from an SSA name which definition we may be able |
| to access pieces from or we can combine to a larger entity. */ |
| else if (known_eq (ref->size, maxsize) |
| && is_gimple_reg_type (vr->type) |
| && !reverse_storage_order_for_component_p (vr->operands) |
| && !contains_storage_order_barrier_p (vr->operands) |
| && gimple_assign_single_p (def_stmt) |
| && TREE_CODE (gimple_assign_rhs1 (def_stmt)) == SSA_NAME) |
| { |
| tree lhs = gimple_assign_lhs (def_stmt); |
| tree base2; |
| poly_int64 offset2, size2, maxsize2; |
| HOST_WIDE_INT offset2i, size2i, offseti; |
| bool reverse; |
| gcc_assert (lhs_ref_ok); |
| base2 = ao_ref_base (&lhs_ref); |
| offset2 = lhs_ref.offset; |
| size2 = lhs_ref.size; |
| maxsize2 = lhs_ref.max_size; |
| reverse = reverse_storage_order_for_component_p (lhs); |
| tree def_rhs = gimple_assign_rhs1 (def_stmt); |
| if (!reverse |
| && !storage_order_barrier_p (lhs) |
| && known_size_p (maxsize2) |
| && known_eq (maxsize2, size2) |
| && adjust_offsets_for_equal_base_address (base, &offset, |
| base2, &offset2)) |
| { |
| if (data->partial_defs.is_empty () |
| && known_subrange_p (offset, maxsize, offset2, size2) |
| /* ??? We can't handle bitfield precision extracts without |
| either using an alternate type for the BIT_FIELD_REF and |
| then doing a conversion or possibly adjusting the offset |
| according to endianness. */ |
| && (! INTEGRAL_TYPE_P (vr->type) |
| || known_eq (ref->size, TYPE_PRECISION (vr->type))) |
| && multiple_p (ref->size, BITS_PER_UNIT)) |
| { |
| tree val = NULL_TREE; |
| if (! INTEGRAL_TYPE_P (TREE_TYPE (def_rhs)) |
| || type_has_mode_precision_p (TREE_TYPE (def_rhs))) |
| { |
| gimple_match_op op (gimple_match_cond::UNCOND, |
| BIT_FIELD_REF, vr->type, |
| SSA_VAL (def_rhs), |
| bitsize_int (ref->size), |
| bitsize_int (offset - offset2)); |
| val = vn_nary_build_or_lookup (&op); |
| } |
| else if (known_eq (ref->size, size2)) |
| { |
| gimple_match_op op (gimple_match_cond::UNCOND, |
| VIEW_CONVERT_EXPR, vr->type, |
| SSA_VAL (def_rhs)); |
| val = vn_nary_build_or_lookup (&op); |
| } |
| if (val |
| && (TREE_CODE (val) != SSA_NAME |
| || ! SSA_NAME_OCCURS_IN_ABNORMAL_PHI (val))) |
| return data->finish (ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), val); |
| } |
| else if (maxsize.is_constant (&maxsizei) |
| && offset.is_constant (&offseti) |
| && offset2.is_constant (&offset2i) |
| && size2.is_constant (&size2i) |
| && ranges_known_overlap_p (offset, maxsize, offset2, size2)) |
| { |
| pd_data pd; |
| pd.rhs = SSA_VAL (def_rhs); |
| pd.offset = offset2i; |
| pd.size = size2i; |
| return data->push_partial_def (pd, ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), |
| offseti, maxsizei); |
| } |
| } |
| } |
| |
| /* 5) For aggregate copies translate the reference through them if |
| the copy kills ref. */ |
| else if (data->vn_walk_kind == VN_WALKREWRITE |
| && gimple_assign_single_p (def_stmt) |
| && (DECL_P (gimple_assign_rhs1 (def_stmt)) |
| || TREE_CODE (gimple_assign_rhs1 (def_stmt)) == MEM_REF |
| || handled_component_p (gimple_assign_rhs1 (def_stmt)))) |
| { |
| tree base2; |
| int i, j, k; |
| auto_vec<vn_reference_op_s> rhs; |
| vn_reference_op_t vro; |
| ao_ref r; |
| |
| gcc_assert (lhs_ref_ok); |
| |
| /* See if the assignment kills REF. */ |
| base2 = ao_ref_base (&lhs_ref); |
| if (!lhs_ref.max_size_known_p () |
| || (base != base2 |
| && (TREE_CODE (base) != MEM_REF |
| || TREE_CODE (base2) != MEM_REF |
| || TREE_OPERAND (base, 0) != TREE_OPERAND (base2, 0) |
| || !tree_int_cst_equal (TREE_OPERAND (base, 1), |
| TREE_OPERAND (base2, 1)))) |
| || !stmt_kills_ref_p (def_stmt, ref)) |
| return (void *)-1; |
| |
| /* Find the common base of ref and the lhs. lhs_ops already |
| contains valueized operands for the lhs. */ |
| i = vr->operands.length () - 1; |
| j = lhs_ops.length () - 1; |
| while (j >= 0 && i >= 0 |
| && vn_reference_op_eq (&vr->operands[i], &lhs_ops[j])) |
| { |
| i--; |
| j--; |
| } |
| |
| /* ??? The innermost op should always be a MEM_REF and we already |
| checked that the assignment to the lhs kills vr. Thus for |
| aggregate copies using char[] types the vn_reference_op_eq |
| may fail when comparing types for compatibility. But we really |
| don't care here - further lookups with the rewritten operands |
| will simply fail if we messed up types too badly. */ |
| poly_int64 extra_off = 0; |
| if (j == 0 && i >= 0 |
| && lhs_ops[0].opcode == MEM_REF |
| && maybe_ne (lhs_ops[0].off, -1)) |
| { |
| if (known_eq (lhs_ops[0].off, vr->operands[i].off)) |
| i--, j--; |
| else if (vr->operands[i].opcode == MEM_REF |
| && maybe_ne (vr->operands[i].off, -1)) |
| { |
| extra_off = vr->operands[i].off - lhs_ops[0].off; |
| i--, j--; |
| } |
| } |
| |
| /* i now points to the first additional op. |
| ??? LHS may not be completely contained in VR, one or more |
| VIEW_CONVERT_EXPRs could be in its way. We could at least |
| try handling outermost VIEW_CONVERT_EXPRs. */ |
| if (j != -1) |
| return (void *)-1; |
| |
| /* Punt if the additional ops contain a storage order barrier. */ |
| for (k = i; k >= 0; k--) |
| { |
| vro = &vr->operands[k]; |
| if (vro->opcode == VIEW_CONVERT_EXPR && vro->reverse) |
| return (void *)-1; |
| } |
| |
| /* Now re-write REF to be based on the rhs of the assignment. */ |
| tree rhs1 = gimple_assign_rhs1 (def_stmt); |
| copy_reference_ops_from_ref (rhs1, &rhs); |
| |
| /* Apply an extra offset to the inner MEM_REF of the RHS. */ |
| if (maybe_ne (extra_off, 0)) |
| { |
| if (rhs.length () < 2) |
| return (void *)-1; |
| int ix = rhs.length () - 2; |
| if (rhs[ix].opcode != MEM_REF |
| || known_eq (rhs[ix].off, -1)) |
| return (void *)-1; |
| rhs[ix].off += extra_off; |
| rhs[ix].op0 = int_const_binop (PLUS_EXPR, rhs[ix].op0, |
| build_int_cst (TREE_TYPE (rhs[ix].op0), |
| extra_off)); |
| } |
| |
| /* Save the operands since we need to use the original ones for |
| the hash entry we use. */ |
| if (!data->saved_operands.exists ()) |
| data->saved_operands = vr->operands.copy (); |
| |
| /* We need to pre-pend vr->operands[0..i] to rhs. */ |
| vec<vn_reference_op_s> old = vr->operands; |
| if (i + 1 + rhs.length () > vr->operands.length ()) |
| vr->operands.safe_grow (i + 1 + rhs.length (), true); |
| else |
| vr->operands.truncate (i + 1 + rhs.length ()); |
| FOR_EACH_VEC_ELT (rhs, j, vro) |
| vr->operands[i + 1 + j] = *vro; |
| valueize_refs (&vr->operands); |
| if (old == shared_lookup_references) |
| shared_lookup_references = vr->operands; |
| vr->hashcode = vn_reference_compute_hash (vr); |
| |
| /* Try folding the new reference to a constant. */ |
| tree val = fully_constant_vn_reference_p (vr); |
| if (val) |
| { |
| if (data->partial_defs.is_empty ()) |
| return data->finish (ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), val); |
| /* This is the only interesting case for partial-def handling |
| coming from targets that like to gimplify init-ctors as |
| aggregate copies from constant data like aarch64 for |
| PR83518. */ |
| if (maxsize.is_constant (&maxsizei) && known_eq (ref->size, maxsize)) |
| { |
| pd_data pd; |
| pd.rhs = val; |
| pd.offset = 0; |
| pd.size = maxsizei; |
| return data->push_partial_def (pd, ao_ref_alias_set (&lhs_ref), |
| ao_ref_base_alias_set (&lhs_ref), |
| 0, maxsizei); |
| } |
| } |
| |
| /* Continuing with partial defs isn't easily possible here, we |
| have to find a full def from further lookups from here. Probably |
| not worth the special-casing everywhere. */ |
| if (!data->partial_defs.is_empty ()) |
| return (void *)-1; |
| |
| /* Adjust *ref from the new operands. */ |
| ao_ref rhs1_ref; |
| ao_ref_init (&rhs1_ref, rhs1); |
| if (!ao_ref_init_from_vn_reference (&r, ao_ref_alias_set (&rhs1_ref), |
| ao_ref_base_alias_set (&rhs1_ref), |
| vr->type, vr->operands)) |
| return (void *)-1; |
| /* This can happen with bitfields. */ |
| if (maybe_ne (ref->size, r.size)) |
| { |
| /* If the access lacks some subsetting simply apply that by |
| shortening it. That in the end can only be successful |
| if we can pun the lookup result which in turn requires |
| exact offsets. */ |
| if (known_eq (r.size, r.max_size) |
| && known_lt (ref->size, r.size)) |
| r.size = r.max_size = ref->size; |
| else |
| return (void *)-1; |
| } |
| *ref = r; |
| |
| /* Do not update last seen VUSE after translating. */ |
| data->last_vuse_ptr = NULL; |
| /* Invalidate the original access path since it now contains |
| the wrong base. */ |
| data->orig_ref.ref = NULL_TREE; |
| /* Use the alias-set of this LHS for recording an eventual result. */ |
| if (data->first_set == -2) |
| { |
| data->first_set = ao_ref_alias_set (&lhs_ref); |
| data->first_base_set = ao_ref_base_alias_set (&lhs_ref); |
| } |
| |
| /* Keep looking for the adjusted *REF / VR pair. */ |
| return NULL; |
| } |
| |
| /* 6) For memcpy copies translate the reference through them if the copy |
| kills ref. But we cannot (easily) do this translation if the memcpy is |
| a storage order barrier, i.e. is equivalent to a VIEW_CONVERT_EXPR that |
| can modify the storage order of objects (see storage_order_barrier_p). */ |
| else if (data->vn_walk_kind == VN_WALKREWRITE |
| && is_gimple_reg_type (vr->type) |
| /* ??? Handle BCOPY as well. */ |
| && (gimple_call_builtin_p (def_stmt, BUILT_IN_MEMCPY) |
| || gimple_call_builtin_p (def_stmt, BUILT_IN_MEMCPY_CHK) |
| || gimple_call_builtin_p (def_stmt, BUILT_IN_MEMPCPY) |
| || gimple_call_builtin_p (def_stmt, BUILT_IN_MEMPCPY_CHK) |
| || gimple_call_builtin_p (def_stmt, BUILT_IN_MEMMOVE) |
| || gimple_call_builtin_p (def_stmt, BUILT_IN_MEMMOVE_CHK)) |
| && (TREE_CODE (gimple_call_arg (def_stmt, 0)) == ADDR_EXPR |
| || TREE_CODE (gimple_call_arg (def_stmt, 0)) == SSA_NAME) |
| && (TREE_CODE (gimple_call_arg (def_stmt, 1)) == ADDR_EXPR |
| || TREE_CODE (gimple_call_arg (def_stmt, 1)) == SSA_NAME) |
| && (poly_int_tree_p (gimple_call_arg (def_stmt, 2), ©_size) |
| || (TREE_CODE (gimple_call_arg (def_stmt, 2)) == SSA_NAME |
| && poly_int_tree_p (SSA_VAL (gimple_call_arg (def_stmt, 2)), |
| ©_size))) |
| /* Handling this is more complicated, give up for now. */ |
| && data->partial_defs.is_empty ()) |
| { |
| tree lhs, rhs; |
| ao_ref r; |
| poly_int64 rhs_offset, lhs_offset; |
| vn_reference_op_s op; |
| poly_uint64 mem_offset; |
| poly_int64 at, byte_maxsize; |
| |
| /* Only handle non-variable, addressable refs. */ |
| if (maybe_ne (ref->size, maxsize) |
| || !multiple_p (offset, BITS_PER_UNIT, &at) |
| || !multiple_p (maxsize, BITS_PER_UNIT, &byte_maxsize)) |
| return (void *)-1; |
| |
| /* Extract a pointer base and an offset for the destination. */ |
| lhs = gimple_call_arg (def_stmt, 0); |
| lhs_offset = 0; |
| if (TREE_CODE (lhs) == SSA_NAME) |
| { |
| lhs = vn_valueize (lhs); |
| if (TREE_CODE (lhs) == SSA_NAME) |
| { |
| gimple *def_stmt = SSA_NAME_DEF_STMT (lhs); |
| if (gimple_assign_single_p (def_stmt) |
| && gimple_assign_rhs_code (def_stmt) == ADDR_EXPR) |
| lhs = gimple_assign_rhs1 (def_stmt); |
| } |
| } |
| if (TREE_CODE (lhs) == ADDR_EXPR) |
| { |
| if (AGGREGATE_TYPE_P (TREE_TYPE (TREE_TYPE (lhs))) |
| && TYPE_REVERSE_STORAGE_ORDER (TREE_TYPE (TREE_TYPE (lhs)))) |
| return (void *)-1; |
| tree tem = get_addr_base_and_unit_offset (TREE_OPERAND (lhs, 0), |
| &lhs_offset); |
| if (!tem) |
| return (void *)-1; |
| if (TREE_CODE (tem) == MEM_REF |
| && poly_int_tree_p (TREE_OPERAND (tem, 1), &mem_offset)) |
| { |
| lhs = TREE_OPERAND (tem, 0); |
| if (TREE_CODE (lhs) == SSA_NAME) |
| lhs = vn_valueize (lhs); |
| lhs_offset += mem_offset; |
| } |
| else if (DECL_P (tem)) |
| lhs = build_fold_addr_expr (tem); |
| else |
| return (void *)-1; |
| } |
| if (TREE_CODE (lhs) != SSA_NAME |
| && TREE_CODE (lhs) != ADDR_EXPR) |
| return (void *)-1; |
| |
| /* Extract a pointer base and an offset for the source. */ |
| rhs = gimple_call_arg (def_stmt, 1); |
| rhs_offset = 0; |
| if (TREE_CODE (rhs) == SSA_NAME) |
| rhs = vn_valueize (rhs); |
| if (TREE_CODE (rhs) == ADDR_EXPR) |
| { |
| if (AGGREGATE_TYPE_P (TREE_TYPE (TREE_TYPE (rhs))) |
| && TYPE_REVERSE_STORAGE_ORDER (TREE_TYPE (TREE_TYPE (rhs)))) |
| return (void *)-1; |
| tree tem = get_addr_base_and_unit_offset (TREE_OPERAND (rhs, 0), |
| &rhs_offset); |
| if (!tem) |
| return (void *)-1; |
| if (TREE_CODE (tem) == MEM_REF |
| && poly_int_tree_p (TREE_OPERAND (tem, 1), &mem_offset)) |
| { |
| rhs = TREE_OPERAND (tem, 0); |
| rhs_offset += mem_offset; |
| } |
| else if (DECL_P (tem) |
| || TREE_CODE (tem) == STRING_CST) |
| rhs = build_fold_addr_expr (tem); |
| else |
| return (void *)-1; |
| } |
| if (TREE_CODE (rhs) == SSA_NAME) |
| rhs = SSA_VAL (rhs); |
| else if (TREE_CODE (rhs) != ADDR_EXPR) |
| return (void *)-1; |
| |
| /* The bases of the destination and the references have to agree. */ |
| if (TREE_CODE (base) == MEM_REF) |
| { |
| if (TREE_OPERAND (base, 0) != lhs |
| || !poly_int_tree_p (TREE_OPERAND (base, 1), &mem_offset)) |
| return (void *) -1; |
| at += mem_offset; |
| } |
| else if (!DECL_P (base) |
| || TREE_CODE (lhs) != ADDR_EXPR |
| || TREE_OPERAND (lhs, 0) != base) |
| return (void *)-1; |
| |
| /* If the access is completely outside of the memcpy destination |
| area there is no aliasing. */ |
| if (!ranges_maybe_overlap_p (lhs_offset, copy_size, at, byte_maxsize)) |
| return NULL; |
| /* And the access has to be contained within the memcpy destination. */ |
| if (!known_subrange_p (at, byte_maxsize, lhs_offset, copy_size)) |
| return (void *)-1; |
| |
| /* Save the operands since we need to use the original ones for |
| the hash entry we use. */ |
| if (!data->saved_operands.exists ()) |
| data->saved_operands = vr->operands.copy (); |
| |
| /* Make room for 2 operands in the new reference. */ |
| if (vr->operands.length () < 2) |
| { |
| vec<vn_reference_op_s> old = vr->operands; |
| vr->operands.safe_grow_cleared (2, true); |
| if (old == shared_lookup_references) |
| shared_lookup_references = vr->operands; |
| } |
| else |
| vr->operands.truncate (2); |
| |
| /* The looked-through reference is a simple MEM_REF. */ |
| memset (&op, 0, sizeof (op)); |
| op.type = vr->type; |
| op.opcode = MEM_REF; |
| op.op0 = build_int_cst (ptr_type_node, at - lhs_offset + rhs_offset); |
| op.off = at - lhs_offset + rhs_offset; |
| vr->operands[0] = op; |
| op.type = TREE_TYPE (rhs); |
| op.opcode = TREE_CODE (rhs); |
| op.op0 = rhs; |
| op.off = -1; |
| vr->operands[1] = op; |
| vr->hashcode = vn_reference_compute_hash (vr); |
| |
| /* Try folding the new reference to a constant. */ |
| tree val = fully_constant_vn_reference_p (vr); |
| if (val) |
| return data->finish (0, 0, val); |
| |
| /* Adjust *ref from the new operands. */ |
| if (!ao_ref_init_from_vn_reference (&r, 0, 0, vr->type, vr->operands)) |
| return (void *)-1; |
| /* This can happen with bitfields. */ |
| if (maybe_ne (ref->size, r.size)) |
| return (void *)-1; |
| *ref = r; |
| |
| /* Do not update last seen VUSE after translating. */ |
| data->last_vuse_ptr = NULL; |
| /* Invalidate the original access path since it now contains |
| the wrong base. */ |
| data->orig_ref.ref = NULL_TREE; |
| /* Use the alias-set of this stmt for recording an eventual result. */ |
| if (data->first_set == -2) |
| { |
| data->first_set = 0; |
| data->first_base_set = 0; |
| } |
| |
| /* Keep looking for the adjusted *REF / VR pair. */ |
| return NULL; |
| } |
| |
| /* Bail out and stop walking. */ |
| return (void *)-1; |
| } |
| |
| /* Return a reference op vector from OP that can be used for |
| vn_reference_lookup_pieces. The caller is responsible for releasing |
| the vector. */ |
| |
| vec<vn_reference_op_s> |
| vn_reference_operands_for_lookup (tree op) |
| { |
| bool valueized; |
| return valueize_shared_reference_ops_from_ref (op, &valueized).copy (); |
| } |
| |
| /* Lookup a reference operation by it's parts, in the current hash table. |
| Returns the resulting value number if it exists in the hash table, |
| NULL_TREE otherwise. VNRESULT will be filled in with the actual |
| vn_reference_t stored in the hashtable if something is found. */ |
| |
| tree |
| vn_reference_lookup_pieces (tree vuse, alias_set_type set, |
| alias_set_type base_set, tree type, |
| vec<vn_reference_op_s> operands, |
| vn_reference_t *vnresult, vn_lookup_kind kind) |
| { |
| struct vn_reference_s vr1; |
| vn_reference_t tmp; |
| tree cst; |
| |
| if (!vnresult) |
| vnresult = &tmp; |
| *vnresult = NULL; |
| |
| vr1.vuse = vuse_ssa_val (vuse); |
| shared_lookup_references.truncate (0); |
| shared_lookup_references.safe_grow (operands.length (), true); |
| memcpy (shared_lookup_references.address (), |
| operands.address (), |
| sizeof (vn_reference_op_s) |
| * operands.length ()); |
| bool valueized_p; |
| valueize_refs_1 (&shared_lookup_references, &valueized_p); |
| vr1.operands = shared_lookup_references; |
| vr1.type = type; |
| vr1.set = set; |
| vr1.base_set = base_set; |
| vr1.hashcode = vn_reference_compute_hash (&vr1); |
| if ((cst = fully_constant_vn_reference_p (&vr1))) |
| return cst; |
| |
| vn_reference_lookup_1 (&vr1, vnresult); |
| if (!*vnresult |
| && kind != VN_NOWALK |
| && vr1.vuse) |
| { |
| ao_ref r; |
| unsigned limit = param_sccvn_max_alias_queries_per_access; |
| vn_walk_cb_data data (&vr1, NULL_TREE, NULL, kind, true, NULL_TREE); |
| vec<vn_reference_op_s> ops_for_ref; |
| if (!valueized_p) |
| ops_for_ref = vr1.operands; |
| else |
| { |
| /* For ao_ref_from_mem we have to ensure only available SSA names |
| end up in base and the only convenient way to make this work |
| for PRE is to re-valueize with that in mind. */ |
| ops_for_ref.create (operands.length ()); |
| ops_for_ref.quick_grow (operands.length ()); |
| memcpy (ops_for_ref.address (), |
| operands.address (), |
| sizeof (vn_reference_op_s) |
| * operands.length ()); |
| valueize_refs_1 (&ops_for_ref, &valueized_p, true); |
| } |
| if (ao_ref_init_from_vn_reference (&r, set, base_set, type, |
| ops_for_ref)) |
| *vnresult |
| = ((vn_reference_t) |
| walk_non_aliased_vuses (&r, vr1.vuse, true, vn_reference_lookup_2, |
| vn_reference_lookup_3, vuse_valueize, |
| limit, &data)); |
| if (ops_for_ref != shared_lookup_references) |
| ops_for_ref.release (); |
| gcc_checking_assert (vr1.operands == shared_lookup_references); |
| } |
| |
| if (*vnresult) |
| return (*vnresult)->result; |
| |
| return NULL_TREE; |
| } |
| |
| /* Lookup OP in the current hash table, and return the resulting value |
| number if it exists in the hash table. Return NULL_TREE if it does |
| not exist in the hash table or if the result field of the structure |
| was NULL.. VNRESULT will be filled in with the vn_reference_t |
| stored in the hashtable if one exists. When TBAA_P is false assume |
| we are looking up a store and treat it as having alias-set zero. |
| *LAST_VUSE_PTR will be updated with the VUSE the value lookup succeeded. |
| MASK is either NULL_TREE, or can be an INTEGER_CST if the result of the |
| load is bitwise anded with MASK and so we are only interested in a subset |
| of the bits and can ignore if the other bits are uninitialized or |
| not initialized with constants. */ |
| |
| tree |
| vn_reference_lookup (tree op, tree vuse, vn_lookup_kind kind, |
| vn_reference_t *vnresult, bool tbaa_p, |
| tree *last_vuse_ptr, tree mask) |
| { |
| vec<vn_reference_op_s> operands; |
| struct vn_reference_s vr1; |
| bool valueized_anything; |
| |
| if (vnresult) |
| *vnresult = NULL; |
| |
| vr1.vuse = vuse_ssa_val (vuse); |
| vr1.operands = operands |
| = valueize_shared_reference_ops_from_ref (op, &valueized_anything); |
| vr1.type = TREE_TYPE (op); |
| ao_ref op_ref; |
| ao_ref_init (&op_ref, op); |
| vr1.set = ao_ref_alias_set (&op_ref); |
| vr1.base_set = ao_ref_base_alias_set (&op_ref); |
| vr1.hashcode = vn_reference_compute_hash (&vr1); |
| if (mask == NULL_TREE) |
| if (tree cst = fully_constant_vn_reference_p (&vr1)) |
| return cst; |
| |
| if (kind != VN_NOWALK && vr1.vuse) |
| { |
| vn_reference_t wvnresult; |
| ao_ref r; |
| unsigned limit = param_sccvn_max_alias_queries_per_access; |
| auto_vec<vn_reference_op_s> ops_for_ref; |
| if (valueized_anything) |
| { |
| copy_reference_ops_from_ref (op, &ops_for_ref); |
| bool tem; |
| valueize_refs_1 (&ops_for_ref, &tem, true); |
| } |
| /* Make sure to use a valueized reference if we valueized anything. |
| Otherwise preserve the full reference for advanced TBAA. */ |
| if (!valueized_anything |
| || !ao_ref_init_from_vn_reference (&r, vr1.set, vr1.base_set, |
| vr1.type, ops_for_ref)) |
| ao_ref_init (&r, op); |
| vn_walk_cb_data data (&vr1, r.ref ? NULL_TREE : op, |
| last_vuse_ptr, kind, tbaa_p, mask); |
| |
| wvnresult |
| = ((vn_reference_t) |
| walk_non_aliased_vuses (&r, vr1.vuse, tbaa_p, vn_reference_lookup_2, |
| vn_reference_lookup_3, vuse_valueize, limit, |
| &data)); |
| gcc_checking_assert (vr1.operands == shared_lookup_references); |
| if (wvnresult) |
| { |
| gcc_assert (mask == NULL_TREE); |
| if (vnresult) |
| *vnresult = wvnresult; |
| return wvnresult->result; |
| } |
| else if (mask) |
| return data.masked_result; |
| |
| return NULL_TREE; |
| } |
| |
| if (last_vuse_ptr) |
| *last_vuse_ptr = vr1.vuse; |
| if (mask) |
| return NULL_TREE; |
| return vn_reference_lookup_1 (&vr1, vnresult); |
| } |
| |
| /* Lookup CALL in the current hash table and return the entry in |
| *VNRESULT if found. Populates *VR for the hashtable lookup. */ |
| |
| void |
| vn_reference_lookup_call (gcall *call, vn_reference_t *vnresult, |
| vn_reference_t vr) |
| { |
| if (vnresult) |
| *vnresult = NULL; |
| |
| tree vuse = gimple_vuse (call); |
| |
| vr->vuse = vuse ? SSA_VAL (vuse) : NULL_TREE; |
| vr->operands = valueize_shared_reference_ops_from_call (call); |
| tree lhs = gimple_call_lhs (call); |
| /* For non-SSA return values the referece ops contain the LHS. */ |
| vr->type = ((lhs && TREE_CODE (lhs) == SSA_NAME) |
| ? TREE_TYPE (lhs) : NULL_TREE); |
| vr->punned = false; |
| vr->set = 0; |
| vr->base_set = 0; |
| vr->hashcode = vn_reference_compute_hash (vr); |
| vn_reference_lookup_1 (vr, vnresult); |
| } |
| |
| /* Insert OP into the current hash table with a value number of RESULT. */ |
| |
| static void |
| vn_reference_insert (tree op, tree result, tree vuse, tree vdef) |
| { |
| vn_reference_s **slot; |
| vn_reference_t vr1; |
| bool tem; |
| |
| vr1 = XOBNEW (&vn_tables_obstack, vn_reference_s); |
| if (TREE_CODE (result) == SSA_NAME) |
| vr1->value_id = VN_INFO (result)->value_id; |
| else |
| vr1->value_id = get_or_alloc_constant_value_id (result); |
| vr1->vuse = vuse_ssa_val (vuse); |
| vr1->operands = valueize_shared_reference_ops_from_ref (op, &tem).copy (); |
| vr1->type = TREE_TYPE (op); |
| vr1->punned = false; |
| ao_ref op_ref; |
| ao_ref_init (&op_ref, op); |
| vr1->set = ao_ref_alias_set (&op_ref); |
| vr1->base_set = ao_ref_base_alias_set (&op_ref); |
| vr1->hashcode = vn_reference_compute_hash (vr1); |
| vr1->result = TREE_CODE (result) == SSA_NAME ? SSA_VAL (result) : result; |
| vr1->result_vdef = vdef; |
| |
| slot = valid_info->references->find_slot_with_hash (vr1, vr1->hashcode, |
| INSERT); |
| |
| /* Because IL walking on reference lookup can end up visiting |
| a def that is only to be visited later in iteration order |
| when we are about to make an irreducible region reducible |
| the def can be effectively processed and its ref being inserted |
| by vn_reference_lookup_3 already. So we cannot assert (!*slot) |
| but save a lookup if we deal with already inserted refs here. */ |
| if (*slot) |
| { |
| /* We cannot assert that we have the same value either because |
| when disentangling an irreducible region we may end up visiting |
| a use before the corresponding def. That's a missed optimization |
| only though. See gcc.dg/tree-ssa/pr87126.c for example. */ |
| if (dump_file && (dump_flags & TDF_DETAILS) |
| && !operand_equal_p ((*slot)->result, vr1->result, 0)) |
| { |
| fprintf (dump_file, "Keeping old value "); |
| print_generic_expr (dump_file, (*slot)->result); |
| fprintf (dump_file, " because of collision\n"); |
| } |
| free_reference (vr1); |
| obstack_free (&vn_tables_obstack, vr1); |
| return; |
| } |
| |
| *slot = vr1; |
| vr1->next = last_inserted_ref; |
| last_inserted_ref = vr1; |
| } |
| |
| /* Insert a reference by it's pieces into the current hash table with |
| a value number of RESULT. Return the resulting reference |
| structure we created. */ |
| |
| vn_reference_t |
| vn_reference_insert_pieces (tree vuse, alias_set_type set, |
| alias_set_type base_set, tree type, |
| vec<vn_reference_op_s> operands, |
| tree result, unsigned int value_id) |
| |
| { |
| vn_reference_s **slot; |
| vn_reference_t vr1; |
| |
| vr1 = XOBNEW (&vn_tables_obstack, vn_reference_s); |
| vr1->value_id = value_id; |
| vr1->vuse = vuse_ssa_val (vuse); |
| vr1->operands = operands; |
| valueize_refs (&vr1->operands); |
| vr1->type = type; |
| vr1->punned = false; |
| vr1->set = set; |
| vr1->base_set = base_set; |
| vr1->hashcode = vn_reference_compute_hash (vr1); |
| if (result && TREE_CODE (result) == SSA_NAME) |
| result = SSA_VAL (result); |
| vr1->result = result; |
| vr1->result_vdef = NULL_TREE; |
| |
| slot = valid_info->references->find_slot_with_hash (vr1, vr1->hashcode, |
| INSERT); |
| |
| /* At this point we should have all the things inserted that we have |
| seen before, and we should never try inserting something that |
| already exists. */ |
| gcc_assert (!*slot); |
| |
| *slot = vr1; |
| vr1->next = last_inserted_ref; |
| last_inserted_ref = vr1; |
| return vr1; |
| } |
| |
| /* Compute and return the hash value for nary operation VBO1. */ |
| |
| hashval_t |
| vn_nary_op_compute_hash (const vn_nary_op_t vno1) |
| { |
| inchash::hash hstate; |
| unsigned i; |
| |
| if (((vno1->length == 2 |
| && commutative_tree_code (vno1->opcode)) |
| || (vno1->length == 3 |
| && commutative_ternary_tree_code (vno1->opcode))) |
| && tree_swap_operands_p (vno1->op[0], vno1->op[1])) |
| std::swap (vno1->op[0], vno1->op[1]); |
| else if (TREE_CODE_CLASS (vno1->opcode) == tcc_comparison |
| && tree_swap_operands_p (vno1->op[0], vno1->op[1])) |
| { |
| std::swap (vno1->op[0], vno1->op[1]); |
| vno1->opcode = swap_tree_comparison (vno1->opcode); |
| } |
| |
| hstate.add_int (vno1->opcode); |
| for (i = 0; i < vno1->length; ++i) |
| inchash::add_expr (vno1->op[i], hstate); |
| |
| return hstate.end (); |
| } |
| |
| /* Compare nary operations VNO1 and VNO2 and return true if they are |
| equivalent. */ |
| |
| bool |
| vn_nary_op_eq (const_vn_nary_op_t const vno1, const_vn_nary_op_t const vno2) |
| { |
| unsigned i; |
| |
| if (vno1->hashcode != vno2->hashcode) |
| return false; |
| |
| if (vno1->length != vno2->length) |
| return false; |
| |
| if (vno1->opcode != vno2->opcode |
| || !types_compatible_p (vno1->type, vno2->type)) |
| return false; |
| |
| for (i = 0; i < vno1->length; ++i) |
| if (!expressions_equal_p (vno1->op[i], vno2->op[i])) |
| return false; |
| |
| /* BIT_INSERT_EXPR has an implict operand as the type precision |
| of op1. Need to check to make sure they are the same. */ |
| if (vno1->opcode == BIT_INSERT_EXPR |
| && TREE_CODE (vno1->op[1]) == INTEGER_CST |
| && TYPE_PRECISION (TREE_TYPE (vno1->op[1])) |
| != TYPE_PRECISION (TREE_TYPE (vno2->op[1]))) |
| return false; |
| |
| return true; |
| } |
| |
| /* Initialize VNO from the pieces provided. */ |
| |
| static void |
| init_vn_nary_op_from_pieces (vn_nary_op_t vno, unsigned int length, |
| enum tree_code code, tree type, tree *ops) |
| { |
| vno->opcode = code; |
| vno->length = length; |
| vno->type = type; |
| memcpy (&vno->op[0], ops, sizeof (tree) * length); |
| } |
| |
| /* Return the number of operands for a vn_nary ops structure from STMT. */ |
| |
| unsigned int |
| vn_nary_length_from_stmt (gimple *stmt) |
| { |
| switch (gimple_assign_rhs_code (stmt)) |
| { |
| case REALPART_EXPR: |
| case IMAGPART_EXPR: |
| case VIEW_CONVERT_EXPR: |
| return 1; |
| |
| case BIT_FIELD_REF: |
| return 3; |
| |
| case CONSTRUCTOR: |
| return CONSTRUCTOR_NELTS (gimple_assign_rhs1 (stmt)); |
| |
| default: |
| return gimple_num_ops (stmt) - 1; |
| } |
| } |
| |
| /* Initialize VNO from STMT. */ |
| |
| void |
| init_vn_nary_op_from_stmt (vn_nary_op_t vno, gassign *stmt) |
| { |
| unsigned i; |
| |
| vno->opcode = gimple_assign_rhs_code (stmt); |
| vno->type = TREE_TYPE (gimple_assign_lhs (stmt)); |
| switch (vno->opcode) |
| { |
| case REALPART_EXPR: |
| case IMAGPART_EXPR: |
| case VIEW_CONVERT_EXPR: |
| vno->length = 1; |
| vno->op[0] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 0); |
| break; |
| |
| case BIT_FIELD_REF: |
| vno->length = 3; |
| vno->op[0] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 0); |
| vno->op[1] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 1); |
| vno->op[2] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 2); |
| break; |
| |
| case CONSTRUCTOR: |
| vno->length = CONSTRUCTOR_NELTS (gimple_assign_rhs1 (stmt)); |
| for (i = 0; i < vno->length; ++i) |
| vno->op[i] = CONSTRUCTOR_ELT (gimple_assign_rhs1 (stmt), i)->value; |
| break; |
| |
| default: |
| gcc_checking_assert (!gimple_assign_single_p (stmt)); |
| vno->length = gimple_num_ops (stmt) - 1; |
| for (i = 0; i < vno->length; ++i) |
| vno->op[i] = gimple_op (stmt, i + 1); |
| } |
| } |
| |
| /* Compute the hashcode for VNO and look for it in the hash table; |
| return the resulting value number if it exists in the hash table. |
| Return NULL_TREE if it does not exist in the hash table or if the |
| result field of the operation is NULL. VNRESULT will contain the |
| vn_nary_op_t from the hashtable if it exists. */ |
| |
| static tree |
| vn_nary_op_lookup_1 (vn_nary_op_t vno, vn_nary_op_t *vnresult) |
| { |
| vn_nary_op_s **slot; |
| |
| if (vnresult) |
| *vnresult = NULL; |
| |
| for (unsigned i = 0; i < vno->length; ++i) |
| if (TREE_CODE (vno->op[i]) == SSA_NAME) |
| vno->op[i] = SSA_VAL (vno->op[i]); |
| |
| vno->hashcode = vn_nary_op_compute_hash (vno); |
| slot = valid_info->nary->find_slot_with_hash (vno, vno->hashcode, NO_INSERT); |
| if (!slot) |
| return NULL_TREE; |
| if (vnresult) |
| *vnresult = *slot; |
| return (*slot)->predicated_values ? NULL_TREE : (*slot)->u.result; |
| } |
| |
| /* Lookup a n-ary operation by its pieces and return the resulting value |
| number if it exists in the hash table. Return NULL_TREE if it does |
| not exist in the hash table or if the result field of the operation |
| is NULL. VNRESULT will contain the vn_nary_op_t from the hashtable |
| if it exists. */ |
| |
| tree |
| vn_nary_op_lookup_pieces (unsigned int length, enum tree_code code, |
| tree type, tree *ops, vn_nary_op_t *vnresult) |
| { |
| vn_nary_op_t vno1 = XALLOCAVAR (struct vn_nary_op_s, |
| sizeof_vn_nary_op (length)); |
| init_vn_nary_op_from_pieces (vno1, length, code, type, ops); |
| return vn_nary_op_lookup_1 (vno1, vnresult); |
| } |
| |
| /* Lookup the rhs of STMT in the current hash table, and return the resulting |
| value number if it exists in the hash table. Return NULL_TREE if |
| it does not exist in the hash table. VNRESULT will contain the |
| vn_nary_op_t from the hashtable if it exists. */ |
| |
| tree |
| vn_nary_op_lookup_stmt (gimple *stmt, vn_nary_op_t *vnresult) |
| { |
| vn_nary_op_t vno1 |
| = XALLOCAVAR (struct vn_nary_op_s, |
| sizeof_vn_nary_op (vn_nary_length_from_stmt (stmt))); |
| init_vn_nary_op_from_stmt (vno1, as_a <gassign *> (stmt)); |
| return vn_nary_op_lookup_1 (vno1, vnresult); |
| } |
| |
| /* Allocate a vn_nary_op_t with LENGTH operands on STACK. */ |
| |
| vn_nary_op_t |
| alloc_vn_nary_op_noinit (unsigned int length, struct obstack *stack) |
| { |
| return (vn_nary_op_t) obstack_alloc (stack, sizeof_vn_nary_op (length)); |
| } |
| |
| /* Allocate and initialize a vn_nary_op_t on CURRENT_INFO's |
| obstack. */ |
| |
| static vn_nary_op_t |
| alloc_vn_nary_op (unsigned int length, tree result, unsigned int value_id) |
| { |
| vn_nary_op_t vno1 = alloc_vn_nary_op_noinit (length, &vn_tables_obstack); |
| |
| vno1->value_id = value_id; |
| vno1->length = length; |
| vno1->predicated_values = 0; |
| vno1->u.result = result; |
| |
| return vno1; |
| } |
| |
| /* Insert VNO into TABLE. */ |
| |
| static vn_nary_op_t |
| vn_nary_op_insert_into (vn_nary_op_t vno, vn_nary_op_table_type *table) |
| { |
| vn_nary_op_s **slot; |
| |
| gcc_assert (! vno->predicated_values |
| || (! vno->u.values->next |
| && vno->u.values->n == 1)); |
| |
| for (unsigned i = 0; i < vno->length; ++i) |
| if (TREE_CODE (vno->op[i]) == SSA_NAME) |
| vno->op[i] = SSA_VAL (vno->op[i]); |
| |
| vno->hashcode = vn_nary_op_compute_hash (vno); |
| slot = table->find_slot_with_hash (vno, vno->hashcode, INSERT); |
| vno->unwind_to = *slot; |
| if (*slot) |
| { |
| /* Prefer non-predicated values. |
| ??? Only if those are constant, otherwise, with constant predicated |
| value, turn them into predicated values with entry-block validity |
| (??? but we always find the first valid result currently). */ |
| if ((*slot)->predicated_values |
| && ! vno->predicated_values) |
| { |
| /* ??? We cannot remove *slot from the unwind stack list. |
| For the moment we deal with this by skipping not found |
| entries but this isn't ideal ... */ |
| *slot = vno; |
| /* ??? Maintain a stack of states we can unwind in |
| vn_nary_op_s? But how far do we unwind? In reality |
| we need to push change records somewhere... Or not |
| unwind vn_nary_op_s and linking them but instead |
| unwind the results "list", linking that, which also |
| doesn't move on hashtable resize. */ |
| /* We can also have a ->unwind_to recording *slot there. |
| That way we can make u.values a fixed size array with |
| recording the number of entries but of course we then |
| have always N copies for each unwind_to-state. Or we |
| make sure to only ever append and each unwinding will |
| pop off one entry (but how to deal with predicated |
| replaced with non-predicated here?) */ |
| vno->next = last_inserted_nary; |
| last_inserted_nary = vno; |
| return vno; |
| } |
| else if (vno->predicated_values |
| && ! (*slot)->predicated_values) |
| return *slot; |
| else if (vno->predicated_values |
| && (*slot)->predicated_values) |
| { |
| /* ??? Factor this all into a insert_single_predicated_value |
| routine. */ |
| gcc_assert (!vno->u.values->next && vno->u.values->n == 1); |
| basic_block vno_bb |
| = BASIC_BLOCK_FOR_FN (cfun, vno->u.values->valid_dominated_by_p[0]); |
| vn_pval *nval = vno->u.values; |
| vn_pval **next = &vno->u.values; |
| bool found = false; |
| for (vn_pval *val = (*slot)->u.values; val; val = val->next) |
| { |
| if (expressions_equal_p (val->result, nval->result)) |
| { |
| found = true; |
| for (unsigned i = 0; i < val->n; ++i) |
| { |
| basic_block val_bb |
| = BASIC_BLOCK_FOR_FN (cfun, |
| val->valid_dominated_by_p[i]); |
| if (dominated_by_p (CDI_DOMINATORS, vno_bb, val_bb)) |
| /* Value registered with more generic predicate. */ |
| return *slot; |
| else if (dominated_by_p (CDI_DOMINATORS, val_bb, vno_bb)) |
| /* Shouldn't happen, we insert in RPO order. */ |
| gcc_unreachable (); |
| } |
| /* Append value. */ |
| *next = (vn_pval *) obstack_alloc (&vn_tables_obstack, |
| sizeof (vn_pval) |
| + val->n * sizeof (int)); |
| (*next)->next = NULL; |
| (*next)->result = val->result; |
| (*next)->n = val->n + 1; |
| memcpy ((*next)->valid_dominated_by_p, |
| val->valid_dominated_by_p, |
| val->n * sizeof (int)); |
| (*next)->valid_dominated_by_p[val->n] = vno_bb->index; |
| next = &(*next)->next; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Appending predicate to value.\n"); |
| continue; |
| } |
| /* Copy other predicated values. */ |
| *next = (vn_pval *) obstack_alloc (&vn_tables_obstack, |
| sizeof (vn_pval) |
| + (val->n-1) * sizeof (int)); |
| memcpy (*next, val, sizeof (vn_pval) + (val->n-1) * sizeof (int)); |
| (*next)->next = NULL; |
| next = &(*next)->next; |
| } |
| if (!found) |
| *next = nval; |
| |
| *slot = vno; |
| vno->next = last_inserted_nary; |
| last_inserted_nary = vno; |
| return vno; |
| } |
| |
| /* While we do not want to insert things twice it's awkward to |
| avoid it in the case where visit_nary_op pattern-matches stuff |
| and ends up simplifying the replacement to itself. We then |
| get two inserts, one from visit_nary_op and one from |
| vn_nary_build_or_lookup. |
| So allow inserts with the same value number. */ |
| if ((*slot)->u.result == vno->u.result) |
| return *slot; |
| } |
| |
| /* ??? There's also optimistic vs. previous commited state merging |
| that is problematic for the case of unwinding. */ |
| |
| /* ??? We should return NULL if we do not use 'vno' and have the |
| caller release it. */ |
| gcc_assert (!*slot); |
| |
| *slot = vno; |
| vno->next = last_inserted_nary; |
| last_inserted_nary = vno; |
| return vno; |
| } |
| |
| /* Insert a n-ary operation into the current hash table using it's |
| pieces. Return the vn_nary_op_t structure we created and put in |
| the hashtable. */ |
| |
| vn_nary_op_t |
| vn_nary_op_insert_pieces (unsigned int length, enum tree_code code, |
| tree type, tree *ops, |
| tree result, unsigned int value_id) |
| { |
| vn_nary_op_t vno1 = alloc_vn_nary_op (length, result, value_id); |
| init_vn_nary_op_from_pieces (vno1, length, code, type, ops); |
| return vn_nary_op_insert_into (vno1, valid_info->nary); |
| } |
| |
| static vn_nary_op_t |
| vn_nary_op_insert_pieces_predicated (unsigned int length, enum tree_code code, |
| tree type, tree *ops, |
| tree result, unsigned int value_id, |
| edge pred_e) |
| { |
| /* ??? Currently tracking BBs. */ |
| if (! single_pred_p (pred_e->dest)) |
| { |
| /* Never record for backedges. */ |
| if (pred_e->flags & EDGE_DFS_BACK) |
| return NULL; |
| edge_iterator ei; |
| edge e; |
| int cnt = 0; |
| /* Ignore backedges. */ |
| FOR_EACH_EDGE (e, ei, pred_e->dest->preds) |
| if (! dominated_by_p (CDI_DOMINATORS, e->src, e->dest)) |
| cnt++; |
| if (cnt != 1) |
| return NULL; |
| } |
| if (dump_file && (dump_flags & TDF_DETAILS) |
| /* ??? Fix dumping, but currently we only get comparisons. */ |
| && TREE_CODE_CLASS (code) == tcc_comparison) |
| { |
| fprintf (dump_file, "Recording on edge %d->%d ", pred_e->src->index, |
| pred_e->dest->index); |
| print_generic_expr (dump_file, ops[0], TDF_SLIM); |
| fprintf (dump_file, " %s ", get_tree_code_name (code)); |
| print_generic_expr (dump_file, ops[1], TDF_SLIM); |
| fprintf (dump_file, " == %s\n", |
| integer_zerop (result) ? "false" : "true"); |
| } |
| vn_nary_op_t vno1 = alloc_vn_nary_op (length, NULL_TREE, value_id); |
| init_vn_nary_op_from_pieces (vno1, length, code, type, ops); |
| vno1->predicated_values = 1; |
| vno1->u.values = (vn_pval *) obstack_alloc (&vn_tables_obstack, |
| sizeof (vn_pval)); |
| vno1->u.values->next = NULL; |
| vno1->u.values->result = result; |
| vno1->u.values->n = 1; |
| vno1->u.values->valid_dominated_by_p[0] = pred_e->dest->index; |
| return vn_nary_op_insert_into (vno1, valid_info->nary); |
| } |
| |
| static bool |
| dominated_by_p_w_unex (basic_block bb1, basic_block bb2, bool); |
| |
| static tree |
| vn_nary_op_get_predicated_value (vn_nary_op_t vno, basic_block bb) |
| { |
| if (! vno->predicated_values) |
| return vno->u.result; |
| for (vn_pval *val = vno->u.values; val; val = val->next) |
| for (unsigned i = 0; i < val->n; ++i) |
| /* Do not handle backedge executability optimistically since |
| when figuring out whether to iterate we do not consider |
| changed predication. */ |
| if (dominated_by_p_w_unex |
| (bb, BASIC_BLOCK_FOR_FN (cfun, val->valid_dominated_by_p[i]), |
| false)) |
| return val->result; |
| return NULL_TREE; |
| } |
| |
| /* Insert the rhs of STMT into the current hash table with a value number of |
| RESULT. */ |
| |
| static vn_nary_op_t |
| vn_nary_op_insert_stmt (gimple *stmt, tree result) |
| { |
| vn_nary_op_t vno1 |
| = alloc_vn_nary_op (vn_nary_length_from_stmt (stmt), |
| result, VN_INFO (result)->value_id); |
| init_vn_nary_op_from_stmt (vno1, as_a <gassign *> (stmt)); |
| return vn_nary_op_insert_into (vno1, valid_info->nary); |
| } |
| |
| /* Compute a hashcode for PHI operation VP1 and return it. */ |
| |
| static inline hashval_t |
| vn_phi_compute_hash (vn_phi_t vp1) |
| { |
| inchash::hash hstate; |
| tree phi1op; |
| tree type; |
| edge e; |
| edge_iterator ei; |
| |
| hstate.add_int (EDGE_COUNT (vp1->block->preds)); |
| switch (EDGE_COUNT (vp1->block->preds)) |
| { |
| case 1: |
| break; |
| case 2: |
| if (vp1->block->loop_father->header == vp1->block) |
| ; |
| else |
| break; |
| /* Fallthru. */ |
| default: |
| hstate.add_int (vp1->block->index); |
| } |
| |
| /* If all PHI arguments are constants we need to distinguish |
| the PHI node via its type. */ |
| type = vp1->type; |
| hstate.merge_hash (vn_hash_type (type)); |
| |
| FOR_EACH_EDGE (e, ei, vp1->block->preds) |
| { |
| /* Don't hash backedge values they need to be handled as VN_TOP |
| for optimistic value-numbering. */ |
| if (e->flags & EDGE_DFS_BACK) |
| continue; |
| |
| phi1op = vp1->phiargs[e->dest_idx]; |
| if (phi1op == VN_TOP) |
| continue; |
| inchash::add_expr (phi1op, hstate); |
| } |
| |
| return hstate.end (); |
| } |
| |
| |
| /* Return true if COND1 and COND2 represent the same condition, set |
| *INVERTED_P if one needs to be inverted to make it the same as |
| the other. */ |
| |
| static bool |
| cond_stmts_equal_p (gcond *cond1, tree lhs1, tree rhs1, |
| gcond *cond2, tree lhs2, tree rhs2, bool *inverted_p) |
| { |
| enum tree_code code1 = gimple_cond_code (cond1); |
| enum tree_code code2 = gimple_cond_code (cond2); |
| |
| *inverted_p = false; |
| if (code1 == code2) |
| ; |
| else if (code1 == swap_tree_comparison (code2)) |
| std::swap (lhs2, rhs2); |
| else if (code1 == invert_tree_comparison (code2, HONOR_NANS (lhs2))) |
| *inverted_p = true; |
| else if (code1 == invert_tree_comparison |
| (swap_tree_comparison (code2), HONOR_NANS (lhs2))) |
| { |
| std::swap (lhs2, rhs2); |
| *inverted_p = true; |
| } |
| else |
| return false; |
| |
| return ((expressions_equal_p (lhs1, lhs2) |
| && expressions_equal_p (rhs1, rhs2)) |
| || (commutative_tree_code (code1) |
| && expressions_equal_p (lhs1, rhs2) |
| && expressions_equal_p (rhs1, lhs2))); |
| } |
| |
| /* Compare two phi entries for equality, ignoring VN_TOP arguments. */ |
| |
| static int |
| vn_phi_eq (const_vn_phi_t const vp1, const_vn_phi_t const vp2) |
| { |
| if (vp1->hashcode != vp2->hashcode) |
| return false; |
| |
| if (vp1->block != vp2->block) |
| { |
| if (EDGE_COUNT (vp1->block->preds) != EDGE_COUNT (vp2->block->preds)) |
| return false; |
| |
| switch (EDGE_COUNT (vp1->block->preds)) |
| { |
| case 1: |
| /* Single-arg PHIs are just copies. */ |
| break; |
| |
| case 2: |
| { |
| /* Rule out backedges into the PHI. */ |
| if (vp1->block->loop_father->header == vp1->block |
| || vp2->block->loop_father->header == vp2->block) |
| return false; |
| |
| /* If the PHI nodes do not have compatible types |
| they are not the same. */ |
| if (!types_compatible_p (vp1->type, vp2->type)) |
| return false; |
| |
| basic_block idom1 |
| = get_immediate_dominator (CDI_DOMINATORS, vp1->block); |
| basic_block idom2 |
| = get_immediate_dominator (CDI_DOMINATORS, vp2->block); |
| /* If the immediate dominator end in switch stmts multiple |
| values may end up in the same PHI arg via intermediate |
| CFG merges. */ |
| if (EDGE_COUNT (idom1->succs) != 2 |
| || EDGE_COUNT (idom2->succs) != 2) |
| return false; |
| |
| /* Verify the controlling stmt is the same. */ |
| gcond *last1 = safe_dyn_cast <gcond *> (last_stmt (idom1)); |
| gcond *last2 = safe_dyn_cast <gcond *> (last_stmt (idom2)); |
| if (! last1 || ! last2) |
| return false; |
| bool inverted_p; |
| if (! cond_stmts_equal_p (last1, vp1->cclhs, vp1->ccrhs, |
| last2, vp2->cclhs, vp2->ccrhs, |
| &inverted_p)) |
| return false; |
| |
| /* Get at true/false controlled edges into the PHI. */ |
| edge te1, te2, fe1, fe2; |
| if (! extract_true_false_controlled_edges (idom1, vp1->block, |
| &te1, &fe1) |
| || ! extract_true_false_controlled_edges (idom2, vp2->block, |
| &te2, &fe2)) |
| return false; |
| |
| /* Swap edges if the second condition is the inverted of the |
| first. */ |
| if (inverted_p) |
| std::swap (te2, fe2); |
| |
| /* Since we do not know which edge will be executed we have |
| to be careful when matching VN_TOP. Be conservative and |
| only match VN_TOP == VN_TOP for now, we could allow |
| VN_TOP on the not prevailing PHI though. See for example |
| PR102920. */ |
| if (! expressions_equal_p (vp1->phiargs[te1->dest_idx], |
| vp2->phiargs[te2->dest_idx], false) |
| || ! expressions_equal_p (vp1->phiargs[fe1->dest_idx], |
| vp2->phiargs[fe2->dest_idx], false)) |
| return false; |
| |
| return true; |
| } |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* If the PHI nodes do not have compatible types |
| they are not the same. */ |
| if (!types_compatible_p (vp1->type, vp2->type)) |
| return false; |
| |
| /* Any phi in the same block will have it's arguments in the |
| same edge order, because of how we store phi nodes. */ |
| unsigned nargs = EDGE_COUNT (vp1->block->preds); |
| for (unsigned i = 0; i < nargs; ++i) |
| { |
| tree phi1op = vp1->phiargs[i]; |
| tree phi2op = vp2->phiargs[i]; |
| if (phi1op == phi2op) |
| continue; |
| if (!expressions_equal_p (phi1op, phi2op, false)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Lookup PHI in the current hash table, and return the resulting |
| value number if it exists in the hash table. Return NULL_TREE if |
| it does not exist in the hash table. */ |
| |
| static tree |
| vn_phi_lookup (gimple *phi, bool backedges_varying_p) |
| { |
| vn_phi_s **slot; |
| struct vn_phi_s *vp1; |
| edge e; |
| edge_iterator ei; |
| |
| vp1 = XALLOCAVAR (struct vn_phi_s, |
| sizeof (struct vn_phi_s) |
| + (gimple_phi_num_args (phi) - 1) * sizeof (tree)); |
| |
| /* Canonicalize the SSA_NAME's to their value number. */ |
| FOR_EACH_EDGE (e, ei, gimple_bb (phi)->preds) |
| { |
| tree def = PHI_ARG_DEF_FROM_EDGE (phi, e); |
| if (TREE_CODE (def) == SSA_NAME |
| && (!backedges_varying_p || !(e->flags & EDGE_DFS_BACK))) |
| { |
| if (ssa_undefined_value_p (def, false)) |
| def = VN_TOP; |
| else |
| def = SSA_VAL (def); |
| } |
| vp1->phiargs[e->dest_idx] = def; |
| } |
| vp1->type = TREE_TYPE (gimple_phi_result (phi)); |
| vp1->block = gimple_bb (phi); |
| /* Extract values of the controlling condition. */ |
| vp1->cclhs = NULL_TREE; |
| vp1->ccrhs = NULL_TREE; |
| basic_block idom1 = get_immediate_dominator (CDI_DOMINATORS, vp1->block); |
| if (EDGE_COUNT (idom1->succs) == 2) |
| if (gcond *last1 = safe_dyn_cast <gcond *> (last_stmt (idom1))) |
| { |
| /* ??? We want to use SSA_VAL here. But possibly not |
| allow VN_TOP. */ |
| vp1->cclhs = vn_valueize (gimple_cond_lhs (last1)); |
| vp1->ccrhs = vn_valueize (gimple_cond_rhs (last1)); |
| } |
| vp1->hashcode = vn_phi_compute_hash (vp1); |
| slot = valid_info->phis->find_slot_with_hash (vp1, vp1->hashcode, NO_INSERT); |
| if (!slot) |
| return NULL_TREE; |
| return (*slot)->result; |
| } |
| |
| /* Insert PHI into the current hash table with a value number of |
| RESULT. */ |
| |
| static vn_phi_t |
| vn_phi_insert (gimple *phi, tree result, bool backedges_varying_p) |
| { |
| vn_phi_s **slot; |
| vn_phi_t vp1 = (vn_phi_t) obstack_alloc (&vn_tables_obstack, |
| sizeof (vn_phi_s) |
| + ((gimple_phi_num_args (phi) - 1) |
| * sizeof (tree))); |
| edge e; |
| edge_iterator ei; |
| |
| /* Canonicalize the SSA_NAME's to their value number. */ |
| FOR_EACH_EDGE (e, ei, gimple_bb (phi)->preds) |
| { |
| tree def = PHI_ARG_DEF_FROM_EDGE (phi, e); |
| if (TREE_CODE (def) == SSA_NAME |
| && (!backedges_varying_p || !(e->flags & EDGE_DFS_BACK))) |
| { |
| if (ssa_undefined_value_p (def, false)) |
| def = VN_TOP; |
| else |
| def = SSA_VAL (def); |
| } |
| vp1->phiargs[e->dest_idx] = def; |
| } |
| vp1->value_id = VN_INFO (result)->value_id; |
| vp1->type = TREE_TYPE (gimple_phi_result (phi)); |
| vp1->block = gimple_bb (phi); |
| /* Extract values of the controlling condition. */ |
| vp1->cclhs = NULL_TREE; |
| vp1->ccrhs = NULL_TREE; |
| basic_block idom1 = get_immediate_dominator (CDI_DOMINATORS, vp1->block); |
| if (EDGE_COUNT (idom1->succs) == 2) |
| if (gcond *last1 = safe_dyn_cast <gcond *> (last_stmt (idom1))) |
| { |
| /* ??? We want to use SSA_VAL here. But possibly not |
| allow VN_TOP. */ |
| vp1->cclhs = vn_valueize (gimple_cond_lhs (last1)); |
| vp1->ccrhs = vn_valueize (gimple_cond_rhs (last1)); |
| } |
| vp1->result = result; |
| vp1->hashcode = vn_phi_compute_hash (vp1); |
| |
| slot = valid_info->phis->find_slot_with_hash (vp1, vp1->hashcode, INSERT); |
| gcc_assert (!*slot); |
| |
| *slot = vp1; |
| vp1->next = last_inserted_phi; |
| last_inserted_phi = vp1; |
| return vp1; |
| } |
| |
| |
| /* Return true if BB1 is dominated by BB2 taking into account edges |
| that are not executable. When ALLOW_BACK is false consider not |
| executable backedges as executable. */ |
| |
| static bool |
| dominated_by_p_w_unex (basic_block bb1, basic_block bb2, bool allow_back) |
| { |
| edge_iterator ei; |
| edge e; |
| |
| if (dominated_by_p (CDI_DOMINATORS, bb1, bb2)) |
| return true; |
| |
| /* Before iterating we'd like to know if there exists a |
| (executable) path from bb2 to bb1 at all, if not we can |
| directly return false. For now simply iterate once. */ |
| |
| /* Iterate to the single executable bb1 predecessor. */ |
| if (EDGE_COUNT (bb1->preds) > 1) |
| { |
| edge prede = NULL; |
| FOR_EACH_EDGE (e, ei, bb1->preds) |
| if ((e->flags & EDGE_EXECUTABLE) |
| || (!allow_back && (e->flags & EDGE_DFS_BACK))) |
| { |
| if (prede) |
| { |
| prede = NULL; |
| break; |
| } |
| prede = e; |
| } |
| if (prede) |
| { |
| bb1 = prede->src; |
| |
| /* Re-do the dominance check with changed bb1. */ |
| if (dominated_by_p (CDI_DOMINATORS, bb1, bb2)) |
| return true; |
| } |
| } |
| |
| /* Iterate to the single executable bb2 successor. */ |
| edge succe = NULL; |
| FOR_EACH_EDGE (e, ei, bb2->succs) |
| if ((e->flags & EDGE_EXECUTABLE) |
| || (!allow_back && (e->flags & EDGE_DFS_BACK))) |
| { |
| if (succe) |
| { |
| succe = NULL; |
| break; |
| } |
| succe = e; |
| } |
| if (succe) |
| { |
| /* Verify the reached block is only reached through succe. |
| If there is only one edge we can spare us the dominator |
| check and iterate directly. */ |
| if (EDGE_COUNT (succe->dest->preds) > 1) |
| { |
| FOR_EACH_EDGE (e, ei, succe->dest->preds) |
| if (e != succe |
| && ((e->flags & EDGE_EXECUTABLE) |
| || (!allow_back && (e->flags & EDGE_DFS_BACK)))) |
| { |
| succe = NULL; |
| break; |
| } |
| } |
| if (succe) |
| { |
| bb2 = succe->dest; |
| |
| /* Re-do the dominance check with changed bb2. */ |
| if (dominated_by_p (CDI_DOMINATORS, bb1, bb2)) |
| return true; |
| } |
| } |
| |
| /* We could now iterate updating bb1 / bb2. */ |
| return false; |
| } |
| |
| /* Set the value number of FROM to TO, return true if it has changed |
| as a result. */ |
| |
| static inline bool |
| set_ssa_val_to (tree from, tree to) |
| { |
| vn_ssa_aux_t from_info = VN_INFO (from); |
| tree currval = from_info->valnum; // SSA_VAL (from) |
| poly_int64 toff, coff; |
| bool curr_undefined = false; |
| bool curr_invariant = false; |
| |
| /* The only thing we allow as value numbers are ssa_names |
| and invariants. So assert that here. We don't allow VN_TOP |
| as visiting a stmt should produce a value-number other than |
| that. |
| ??? Still VN_TOP can happen for unreachable code, so force |
| it to varying in that case. Not all code is prepared to |
| get VN_TOP on valueization. */ |
| if (to == VN_TOP) |
| { |
| /* ??? When iterating and visiting PHI <undef, backedge-value> |
| for the first time we rightfully get VN_TOP and we need to |
| preserve that to optimize for example gcc.dg/tree-ssa/ssa-sccvn-2.c. |
| With SCCVN we were simply lucky we iterated the other PHI |
| cycles first and thus visited the backedge-value DEF. */ |
| if (currval == VN_TOP) |
| goto set_and_exit; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Forcing value number to varying on " |
| "receiving VN_TOP\n"); |
| to = from; |
| } |
| |
| gcc_checking_assert (to != NULL_TREE |
| && ((TREE_CODE (to) == SSA_NAME |
| && (to == from || SSA_VAL (to) == to)) |
| || is_gimple_min_invariant (to))); |
| |
| if (from != to) |
| { |
| if (currval == from) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Not changing value number of "); |
| print_generic_expr (dump_file, from); |
| fprintf (dump_file, " from VARYING to "); |
| print_generic_expr (dump_file, to); |
| fprintf (dump_file, "\n"); |
| } |
| return false; |
| } |
| curr_invariant = is_gimple_min_invariant (currval); |
| curr_undefined = (TREE_CODE (currval) == SSA_NAME |
| && ssa_undefined_value_p (currval, false)); |
| if (currval != VN_TOP |
| && !curr_invariant |
| && !curr_undefined |
| && is_gimple_min_invariant (to)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Forcing VARYING instead of changing " |
| "value number of "); |
| print_generic_expr (dump_file, from); |
| fprintf (dump_file, " from "); |
| print_generic_expr (dump_file, currval); |
| fprintf (dump_file, " (non-constant) to "); |
| print_generic_expr (dump_file, to); |
| fprintf (dump_file, " (constant)\n"); |
| } |
| to = from; |
| } |
| else if (currval != VN_TOP |
| && !curr_undefined |
| && TREE_CODE (to) == SSA_NAME |
| && ssa_undefined_value_p (to, false)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Forcing VARYING instead of changing " |
| "value number of "); |
| print_generic_expr (dump_file, from); |
| fprintf (dump_file, " from "); |
| print_generic_expr (dump_file, currval); |
| fprintf (dump_file, " (non-undefined) to "); |
| print_generic_expr (dump_file, to); |
| fprintf (dump_file, " (undefined)\n"); |
| } |
| to = from; |
| } |
| else if (TREE_CODE (to) == SSA_NAME |
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (to)) |
| to = from; |
| } |
| |
| set_and_exit: |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Setting value number of "); |
| print_generic_expr (dump_file, from); |
| fprintf (dump_file, " to "); |
| print_generic_expr (dump_file, to); |
| } |
| |
| if (currval != to |
| && !operand_equal_p (currval, to, 0) |
| /* Different undefined SSA names are not actually different. See |
| PR82320 for a testcase were we'd otherwise not terminate iteration. */ |
| && !(curr_undefined |
| && TREE_CODE (to) == SSA_NAME |
| && ssa_undefined_value_p (to, false)) |
| /* ??? For addresses involving volatile objects or types operand_equal_p |
| does not reliably detect ADDR_EXPRs as equal. We know we are only |
| getting invariant gimple addresses here, so can use |
| get_addr_base_and_unit_offset to do this comparison. */ |
| && !(TREE_CODE (currval) == ADDR_EXPR |
| && TREE_CODE (to) == ADDR_EXPR |
| && (get_addr_base_and_unit_offset (TREE_OPERAND (currval, 0), &coff) |
| == get_addr_base_and_unit_offset (TREE_OPERAND (to, 0), &toff)) |
| && known_eq (coff, toff))) |
| { |
| if (to != from |
| && currval != VN_TOP |
| && !curr_undefined |
| /* We do not want to allow lattice transitions from one value |
| to another since that may lead to not terminating iteration |
| (see PR95049). Since there's no convenient way to check |
| for the allowed transition of VAL -> PHI (loop entry value, |
| same on two PHIs, to same PHI result) we restrict the check |
| to invariants. */ |
| && curr_invariant |
| && is_gimple_min_invariant (to)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " forced VARYING"); |
| to = from; |
| } |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " (changed)\n"); |
| from_info->valnum = to; |
| return true; |
| } |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "\n"); |
| return false; |
| } |
| |
| /* Set all definitions in STMT to value number to themselves. |
| Return true if a value number changed. */ |
| |
| static bool |
| defs_to_varying (gimple *stmt) |
| { |
| bool changed = false; |
| ssa_op_iter iter; |
| def_operand_p defp; |
| |
| FOR_EACH_SSA_DEF_OPERAND (defp, stmt, iter, SSA_OP_ALL_DEFS) |
| { |
| tree def = DEF_FROM_PTR (defp); |
| changed |= set_ssa_val_to (def, def); |
| } |
| return changed; |
| } |
| |
| /* Visit a copy between LHS and RHS, return true if the value number |
| changed. */ |
| |
| static bool |
| visit_copy (tree lhs, tree rhs) |
| { |
| /* Valueize. */ |
| rhs = SSA_VAL (rhs); |
| |
| return set_ssa_val_to (lhs, rhs); |
| } |
| |
| /* Lookup a value for OP in type WIDE_TYPE where the value in type of OP |
| is the same. */ |
| |
| static tree |
| valueized_wider_op (tree wide_type, tree op, bool allow_truncate) |
| { |
| if (TREE_CODE (op) == SSA_NAME) |
| op = vn_valueize (op); |
| |
| /* Either we have the op widened available. */ |
| tree ops[3] = {}; |
| ops[0] = op; |
| tree tem = vn_nary_op_lookup_pieces (1, NOP_EXPR, |
| wide_type, ops, NULL); |
| if (tem) |
| return tem; |
| |
| /* Or the op is truncated from some existing value. */ |
| if (allow_truncate && TREE_CODE (op) == SSA_NAME) |
| { |
| gimple *def = SSA_NAME_DEF_STMT (op); |
| if (is_gimple_assign (def) |
| && CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (def))) |
| { |
| tem = gimple_assign_rhs1 (def); |
| if (useless_type_conversion_p (wide_type, TREE_TYPE (tem))) |
| { |
| if (TREE_CODE (tem) == SSA_NAME) |
| tem = vn_valueize (tem); |
| return tem; |
| } |
| } |
| } |
| |
| /* For constants simply extend it. */ |
| if (TREE_CODE (op) == INTEGER_CST) |
| return wide_int_to_tree (wide_type, wi::to_wide (op)); |
| |
| return NULL_TREE; |
| } |
| |
| /* Visit a nary operator RHS, value number it, and return true if the |
| value number of LHS has changed as a result. */ |
| |
| static bool |
| visit_nary_op (tree lhs, gassign *stmt) |
| { |
| vn_nary_op_t vnresult; |
| tree result = vn_nary_op_lookup_stmt (stmt, &vnresult); |
| if (! result && vnresult) |
| result = vn_nary_op_get_predicated_value (vnresult, gimple_bb (stmt)); |
| if (result) |
| return set_ssa_val_to (lhs, result); |
| |
| /* Do some special pattern matching for redundancies of operations |
| in different types. */ |
| enum tree_code code = gimple_assign_rhs_code (stmt); |
| tree type = TREE_TYPE (lhs); |
| tree rhs1 = gimple_assign_rhs1 (stmt); |
| switch (code) |
| { |
| CASE_CONVERT: |
| /* Match arithmetic done in a different type where we can easily |
| substitute the result from some earlier sign-changed or widened |
| operation. */ |
| if (INTEGRAL_TYPE_P (type) |
| && TREE_CODE (rhs1) == SSA_NAME |
| /* We only handle sign-changes, zero-extension -> & mask or |
| sign-extension if we know the inner operation doesn't |
| overflow. */ |
| && (((TYPE_UNSIGNED (TREE_TYPE (rhs1)) |
| || (INTEGRAL_TYPE_P (TREE_TYPE (rhs1)) |
| && TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (rhs1)))) |
| && TYPE_PRECISION (type) > TYPE_PRECISION (TREE_TYPE (rhs1))) |
| || TYPE_PRECISION (type) == TYPE_PRECISION (TREE_TYPE (rhs1)))) |
| { |
| gassign *def = dyn_cast <gassign *> (SSA_NAME_DEF_STMT (rhs1)); |
| if (def |
| && (gimple_assign_rhs_code (def) == PLUS_EXPR |
| || gimple_assign_rhs_code (def) == MINUS_EXPR |
| || gimple_assign_rhs_code (def) == MULT_EXPR)) |
| { |
| tree ops[3] = {}; |
| /* When requiring a sign-extension we cannot model a |
| previous truncation with a single op so don't bother. */ |
| bool allow_truncate = TYPE_UNSIGNED (TREE_TYPE (rhs1)); |
| /* Either we have the op widened available. */ |
| ops[0] = valueized_wider_op (type, gimple_assign_rhs1 (def), |
| allow_truncate); |
| if (ops[0]) |
| ops[1] = valueized_wider_op (type, gimple_assign_rhs2 (def), |
| allow_truncate); |
| if (ops[0] && ops[1]) |
| { |
| ops[0] = vn_nary_op_lookup_pieces |
| (2, gimple_assign_rhs_code (def), type, ops, NULL); |
| /* We have wider operation available. */ |
| if (ops[0] |
| /* If the leader is a wrapping operation we can |
| insert it for code hoisting w/o introducing |
| undefined overflow. If it is not it has to |
| be available. See PR86554. */ |
| && (TYPE_OVERFLOW_WRAPS (TREE_TYPE (ops[0])) |
| || (rpo_avail && vn_context_bb |
| && rpo_avail->eliminate_avail (vn_context_bb, |
| ops[0])))) |
| { |
| unsigned lhs_prec = TYPE_PRECISION (type); |
| unsigned rhs_prec = TYPE_PRECISION (TREE_TYPE (rhs1)); |
| if (lhs_prec == rhs_prec |
| || (INTEGRAL_TYPE_P (TREE_TYPE (rhs1)) |
| && TYPE_OVERFLOW_UNDEFINED (TREE_TYPE (rhs1)))) |
| { |
| gimple_match_op match_op (gimple_match_cond::UNCOND, |
| NOP_EXPR, type, ops[0]); |
| result = vn_nary_build_or_lookup (&match_op); |
| if (result) |
| { |
| bool changed = set_ssa_val_to (lhs, result); |
| vn_nary_op_insert_stmt (stmt, result); |
| return changed; |
| } |
| } |
| else |
| { |
| tree mask = wide_int_to_tree |
| (type, wi::mask (rhs_prec, false, lhs_prec)); |
| gimple_match_op match_op (gimple_match_cond::UNCOND, |
| BIT_AND_EXPR, |
| TREE_TYPE (lhs), |
| ops[0], mask); |
| result = vn_nary_build_or_lookup (&match_op); |
| if (result) |
| { |
| bool changed = set_ssa_val_to (lhs, result); |
| vn_nary_op_insert_stmt (stmt, result); |
| return changed; |
| } |
| } |
| } |
| } |
| } |
| } |
| break; |
| case BIT_AND_EXPR: |
| if (INTEGRAL_TYPE_P (type) |
| && TREE_CODE (rhs1) == SSA_NAME |
| && TREE_CODE (gimple_assign_rhs2 (stmt)) == INTEGER_CST |
| && !SSA_NAME_OCCURS_IN_ABNORMAL_PHI (rhs1) |
| && default_vn_walk_kind != VN_NOWALK |
| && CHAR_BIT == 8 |
| && BITS_PER_UNIT == 8 |
| && BYTES_BIG_ENDIAN == WORDS_BIG_ENDIAN |
| && !integer_all_onesp (gimple_assign_rhs2 (stmt)) |
| && !integer_zerop (gimple_assign_rhs2 (stmt))) |
| { |
| gassign *ass = dyn_cast <gassign *> (SSA_NAME_DEF_STMT (rhs1)); |
| if (ass |
| && !gimple_has_volatile_ops (ass) |
| && vn_get_stmt_kind (ass) == VN_REFERENCE) |
| { |
| tree last_vuse = gimple_vuse (ass); |
| tree op = gimple_assign_rhs1 (ass); |
| tree result = vn_reference_lookup (op, gimple_vuse (ass), |
| default_vn_walk_kind, |
| NULL, true, &last_vuse, |
| gimple_assign_rhs2 (stmt)); |
| if (result |
| && useless_type_conversion_p (TREE_TYPE (result), |
| TREE_TYPE (op))) |
| return set_ssa_val_to (lhs, result); |
| } |
| } |
| break; |
| case TRUNC_DIV_EXPR: |
| if (TYPE_UNSIGNED (type)) |
| break; |
| /* Fallthru. */ |
| case RDIV_EXPR: |
| case MULT_EXPR: |
| /* Match up ([-]a){/,*}([-])b with v=a{/,*}b, replacing it with -v. */ |
| if (! HONOR_SIGN_DEPENDENT_ROUNDING (type)) |
| { |
| tree rhs[2]; |
| rhs[0] = rhs1; |
| rhs[1] = gimple_assign_rhs2 (stmt); |
| for (unsigned i = 0; i <= 1; ++i) |
| { |
| unsigned j = i == 0 ? 1 : 0; |
| tree ops[2]; |
| gimple_match_op match_op (gimple_match_cond::UNCOND, |
| NEGATE_EXPR, type, rhs[i]); |
| ops[i] = vn_nary_build_or_lookup_1 (&match_op, false, true); |
| ops[j] = rhs[j]; |
| if (ops[i] |
| && (ops[0] = vn_nary_op_lookup_pieces (2, code, |
| type, ops, NULL))) |
| { |
| gimple_match_op match_op (gimple_match_cond::UNCOND, |
| NEGATE_EXPR, type, ops[0]); |
| result = vn_nary_build_or_lookup_1 (&match_op, true, false); |
| if (result) |
| { |
| bool changed = set_ssa_val_to (lhs, result); |
| vn_nary_op_insert_stmt (stmt, result); |
| return changed; |
| } |
| } |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| bool changed = set_ssa_val_to (lhs, lhs); |
| vn_nary_op_insert_stmt (stmt, lhs); |
| return changed; |
| } |
| |
| /* Visit a call STMT storing into LHS. Return true if the value number |
| of the LHS has changed as a result. */ |
| |
| static bool |
| visit_reference_op_call (tree lhs, gcall *stmt) |
| { |
| bool changed = false; |
| struct vn_reference_s vr1; |
| vn_reference_t vnresult = NULL; |
| tree vdef = gimple_vdef (stmt); |
| modref_summary *summary; |
| |
| /* Non-ssa lhs is handled in copy_reference_ops_from_call. */ |
| if (lhs && TREE_CODE (lhs) != SSA_NAME) |
| lhs = NULL_TREE; |
| |
| vn_reference_lookup_call (stmt, &vnresult, &vr1); |
| |
| /* If the lookup did not succeed for pure functions try to use |
| modref info to find a candidate to CSE to. */ |
| const unsigned accesses_limit = 8; |
| if (!vnresult |
| && !vdef |
| && lhs |
| && gimple_vuse (stmt) |
| && (((summary = get_modref_function_summary (stmt, NULL)) |
| && !summary->global_memory_read |
| && summary->load_accesses < accesses_limit) |
| || gimple_call_flags (stmt) & ECF_CONST)) |
| { |
| /* First search if we can do someting useful and build a |
| vector of all loads we have to check. */ |
| bool unknown_memory_access = false; |
| auto_vec<ao_ref, accesses_limit> accesses; |
| unsigned load_accesses = summary ? summary->load_accesses : 0; |
| if (!unknown_memory_access) |
| /* Add loads done as part of setting up the call arguments. |
| That's also necessary for CONST functions which will |
| not have a modref summary. */ |
| for (unsigned i = 0; i < gimple_call_num_args (stmt); ++i) |
| { |
| tree arg = gimple_call_arg (stmt, i); |
| if (TREE_CODE (arg) != SSA_NAME |
| && !is_gimple_min_invariant (arg)) |
| { |
| if (accesses.length () >= accesses_limit - load_accesses) |
| { |
| unknown_memory_access = true; |
| break; |
| } |
| accesses.quick_grow (accesses.length () + 1); |
| ao_ref_init (&accesses.last (), arg); |
| } |
| } |
| if (summary && !unknown_memory_access) |
| { |
| /* Add loads as analyzed by IPA modref. */ |
| for (auto base_node : summary->loads->bases) |
| if (unknown_memory_access) |
| break; |
| else for (auto ref_node : base_node->refs) |
| if (unknown_memory_access) |
| break; |
| else for (auto access_node : ref_node->accesses) |
| { |
| accesses.quick_grow (accesses.length () + 1); |
| ao_ref *r = &accesses.last (); |
| if (!access_node.get_ao_ref (stmt, r)) |
| { |
| /* Initialize a ref based on the argument and |
| unknown offset if possible. */ |
| tree arg = access_node.get_call_arg (stmt); |
| if (arg && TREE_CODE (arg) == SSA_NAME) |
| arg = SSA_VAL (arg); |
| if (arg |
| && TREE_CODE (arg) == ADDR_EXPR |
| && (arg = get_base_address (arg)) |
| && DECL_P (arg)) |
| { |
| ao_ref_init (r, arg); |
| r->ref = NULL_TREE; |
| r->base = arg; |
| } |
| else |
| { |
| unknown_memory_access = true; |
| break; |
| } |
| } |
| r->base_alias_set = base_node->base; |
| r->ref_alias_set = ref_node->ref; |
| } |
| } |
| |
| /* Walk the VUSE->VDEF chain optimistically trying to find an entry |
| for the call in the hashtable. */ |
| unsigned limit = (unknown_memory_access |
| ? 0 |
| : (param_sccvn_max_alias_queries_per_access |
| / (accesses.length () + 1))); |
| tree saved_vuse = vr1.vuse; |
| hashval_t saved_hashcode = vr1.hashcode; |
| while (limit > 0 && !vnresult && !SSA_NAME_IS_DEFAULT_DEF (vr1.vuse)) |
| { |
| vr1.hashcode = vr1.hashcode - SSA_NAME_VERSION (vr1.vuse); |
| gimple *def = SSA_NAME_DEF_STMT (vr1.vuse); |
| /* ??? We could use fancy stuff like in walk_non_aliased_vuses, but |
| do not bother for now. */ |
| if (is_a <gphi *> (def)) |
| break; |
| vr1.vuse = vuse_ssa_val (gimple_vuse (def)); |
| vr1.hashcode = vr1.hashcode + SSA_NAME_VERSION (vr1.vuse); |
| vn_reference_lookup_1 (&vr1, &vnresult); |
| limit--; |
| } |
| |
| /* If we found a candidate to CSE to verify it is valid. */ |
| if (vnresult && !accesses.is_empty ()) |
| { |
| tree vuse = vuse_ssa_val (gimple_vuse (stmt)); |
| while (vnresult && vuse != vr1.vuse) |
| { |
| gimple *def = SSA_NAME_DEF_STMT (vuse); |
| for (auto &ref : accesses) |
| { |
| /* ??? stmt_may_clobber_ref_p_1 does per stmt constant |
| analysis overhead that we might be able to cache. */ |
| if (stmt_may_clobber_ref_p_1 (def, &ref, true)) |
| { |
| vnresult = NULL; |
| break; |
| } |
| } |
| vuse = vuse_ssa_val (gimple_vuse (def)); |
| } |
| } |
| vr1.vuse = saved_vuse; |
| vr1.hashcode = saved_hashcode; |
| } |
| |
| if (vnresult) |
| { |
| if (vdef) |
| { |
| if (vnresult->result_vdef) |
| changed |= set_ssa_val_to (vdef, vnresult->result_vdef); |
| else if (!lhs && gimple_call_lhs (stmt)) |
| /* If stmt has non-SSA_NAME lhs, value number the vdef to itself, |
| as the call still acts as a lhs store. */ |
| changed |= set_ssa_val_to (vdef, vdef); |
| else |
| /* If the call was discovered to be pure or const reflect |
| that as far as possible. */ |
| changed |= set_ssa_val_to (vdef, |
| vuse_ssa_val (gimple_vuse (stmt))); |
| } |
| |
| if (!vnresult->result && lhs) |
| vnresult->result = lhs; |
| |
| if (vnresult->result && lhs) |
| changed |= set_ssa_val_to (lhs, vnresult->result); |
| } |
| else |
| { |
| vn_reference_t vr2; |
| vn_reference_s **slot; |
| tree vdef_val = vdef; |
| if (vdef) |
| { |
| /* If we value numbered an indirect functions function to |
| one not clobbering memory value number its VDEF to its |
| VUSE. */ |
| tree fn = gimple_call_fn (stmt); |
| if (fn && TREE_CODE (fn) == SSA_NAME) |
| { |
| fn = SSA_VAL (fn); |
| if (TREE_CODE (fn) == ADDR_EXPR |
| && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL |
| && (flags_from_decl_or_type (TREE_OPERAND (fn, 0)) |
| & (ECF_CONST | ECF_PURE)) |
| /* If stmt has non-SSA_NAME lhs, value number the |
| vdef to itself, as the call still acts as a lhs |
| store. */ |
| && (lhs || gimple_call_lhs (stmt) == NULL_TREE)) |
| vdef_val = vuse_ssa_val (gimple_vuse (stmt)); |
| } |
| changed |= set_ssa_val_to (vdef, vdef_val); |
| } |
| if (lhs) |
| changed |= set_ssa_val_to (lhs, lhs); |
| vr2 = XOBNEW (&vn_tables_obstack, vn_reference_s); |
| vr2->vuse = vr1.vuse; |
| /* As we are not walking the virtual operand chain we know the |
| shared_lookup_references are still original so we can re-use |
| them here. */ |
| vr2->operands = vr1.operands.copy (); |
| vr2->type = vr1.type; |
| vr2->punned = vr1.punned; |
| vr2->set = vr1.set; |
| vr2->base_set = vr1.base_set; |
| vr2->hashcode = vr1.hashcode; |
| vr2->result = lhs; |
| vr2->result_vdef = vdef_val; |
| vr2->value_id = 0; |
| slot = valid_info->references->find_slot_with_hash (vr2, vr2->hashcode, |
| INSERT); |
| gcc_assert (!*slot); |
| *slot = vr2; |
| vr2->next = last_inserted_ref; |
| last_inserted_ref = vr2; |
| } |
| |
| return changed; |
| } |
| |
| /* Visit a load from a reference operator RHS, part of STMT, value number it, |
| and return true if the value number of the LHS has changed as a result. */ |
| |
| static bool |
| visit_reference_op_load (tree lhs, tree op, gimple *stmt) |
| { |
| bool changed = false; |
| tree result; |
| vn_reference_t res; |
| |
| tree vuse = gimple_vuse (stmt); |
| tree last_vuse = vuse; |
| result = vn_reference_lookup (op, vuse, default_vn_walk_kind, &res, true, &last_vuse); |
| |
| /* We handle type-punning through unions by value-numbering based |
| on offset and size of the access. Be prepared to handle a |
| type-mismatch here via creating a VIEW_CONVERT_EXPR. */ |
| if (result |
| && !useless_type_conversion_p (TREE_TYPE (result), TREE_TYPE (op))) |
| { |
| /* Avoid the type punning in case the result mode has padding where |
| the op we lookup has not. */ |
| if (maybe_lt (GET_MODE_PRECISION (TYPE_MODE (TREE_TYPE (result))), |
| GET_MODE_PRECISION (TYPE_MODE (TREE_TYPE (op))))) |
| result = NULL_TREE; |
| else |
| { |
| /* We will be setting the value number of lhs to the value number |
| of VIEW_CONVERT_EXPR <TREE_TYPE (result)> (result). |
| So first simplify and lookup this expression to see if it |
| is already available. */ |
| gimple_match_op res_op (gimple_match_cond::UNCOND, |
| VIEW_CONVERT_EXPR, TREE_TYPE (op), result); |
| result = vn_nary_build_or_lookup (&res_op); |
| if (result |
| && TREE_CODE (result) == SSA_NAME |
| && VN_INFO (result)->needs_insertion) |
| /* Track whether this is the canonical expression for different |
| typed loads. We use that as a stopgap measure for code |
| hoisting when dealing with floating point loads. */ |
| res->punned = true; |
| } |
| |
| /* When building the conversion fails avoid inserting the reference |
| again. */ |
| if (!result) |
| return set_ssa_val_to (lhs, lhs); |
| } |
| |
| if (result) |
| changed = set_ssa_val_to (lhs, result); |
| else |
| { |
| changed = set_ssa_val_to (lhs, lhs); |
| vn_reference_insert (op, lhs, last_vuse, NULL_TREE); |
| if (vuse && SSA_VAL (last_vuse) != SSA_VAL (vuse)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Using extra use virtual operand "); |
| print_generic_expr (dump_file, last_vuse); |
| fprintf (dump_file, "\n"); |
| } |
| vn_reference_insert (op, lhs, vuse, NULL_TREE); |
| } |
| } |
| |
| return changed; |
| } |
| |
| |
| /* Visit a store to a reference operator LHS, part of STMT, value number it, |
| and return true if the value number of the LHS has changed as a result. */ |
| |
| static bool |
| visit_reference_op_store (tree lhs, tree op, gimple *stmt) |
| { |
| bool changed = false; |
| vn_reference_t vnresult = NULL; |
| tree assign; |
| bool resultsame = false; |
| tree vuse = gimple_vuse (stmt); |
| tree vdef = gimple_vdef (stmt); |
| |
| if (TREE_CODE (op) == SSA_NAME) |
| op = SSA_VAL (op); |
| |
| /* First we want to lookup using the *vuses* from the store and see |
| if there the last store to this location with the same address |
| had the same value. |
| |
| The vuses represent the memory state before the store. If the |
| memory state, address, and value of the store is the same as the |
| last store to this location, then this store will produce the |
| same memory state as that store. |
| |
| In this case the vdef versions for this store are value numbered to those |
| vuse versions, since they represent the same memory state after |
| this store. |
| |
| Otherwise, the vdefs for the store are used when inserting into |
| the table, since the store generates a new memory state. */ |
| |
| vn_reference_lookup (lhs, vuse, VN_NOWALK, &vnresult, false); |
| if (vnresult |
| && vnresult->result) |
| { |
| tree result = vnresult->result; |
| gcc_checking_assert (TREE_CODE (result) != SSA_NAME |
| || result == SSA_VAL (result)); |
| resultsame = expressions_equal_p (result, op); |
| if (resultsame) |
| { |
| /* If the TBAA state isn't compatible for downstream reads |
| we cannot value-number the VDEFs the same. */ |
| ao_ref lhs_ref; |
| ao_ref_init (&lhs_ref, lhs); |
| alias_set_type set = ao_ref_alias_set (&lhs_ref); |
| alias_set_type base_set = ao_ref_base_alias_set (&lhs_ref); |
| if ((vnresult->set != set |
| && ! alias_set_subset_of (set, vnresult->set)) |
| || (vnresult->base_set != base_set |
| && ! alias_set_subset_of (base_set, vnresult->base_set))) |
| resultsame = false; |
| } |
| } |
| |
| if (!resultsame) |
| { |
| /* Only perform the following when being called from PRE |
| which embeds tail merging. */ |
| if (default_vn_walk_kind == VN_WALK) |
| { |
| assign = build2 (MODIFY_EXPR, TREE_TYPE (lhs), lhs, op); |
| vn_reference_lookup (assign, vuse, VN_NOWALK, &vnresult, false); |
| if (vnresult) |
| { |
| VN_INFO (vdef)->visited = true; |
| return set_ssa_val_to (vdef, vnresult->result_vdef); |
| } |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "No store match\n"); |
| fprintf (dump_file, "Value numbering store "); |
| print_generic_expr (dump_file, lhs); |
| fprintf (dump_file, " to "); |
| print_generic_expr (dump_file, op); |
| fprintf (dump_file, "\n"); |
| } |
| /* Have to set value numbers before insert, since insert is |
| going to valueize the references in-place. */ |
| if (vdef) |
| changed |= set_ssa_val_to (vdef, vdef); |
| |
| /* Do not insert structure copies into the tables. */ |
| if (is_gimple_min_invariant (op) |
| || is_gimple_reg (op)) |
| vn_reference_insert (lhs, op, vdef, NULL); |
| |
| /* Only perform the following when being called from PRE |
| which embeds tail merging. */ |
| if (default_vn_walk_kind == VN_WALK) |
| { |
| assign = build2 (MODIFY_EXPR, TREE_TYPE (lhs), lhs, op); |
| vn_reference_insert (assign, lhs, vuse, vdef); |
| } |
| } |
| else |
| { |
| /* We had a match, so value number the vdef to have the value |
| number of the vuse it came from. */ |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Store matched earlier value, " |
| "value numbering store vdefs to matching vuses.\n"); |
| |
| changed |= set_ssa_val_to (vdef, SSA_VAL (vuse)); |
| } |
| |
| return changed; |
| } |
| |
| /* Visit and value number PHI, return true if the value number |
| changed. When BACKEDGES_VARYING_P is true then assume all |
| backedge values are varying. When INSERTED is not NULL then |
| this is just a ahead query for a possible iteration, set INSERTED |
| to true if we'd insert into the hashtable. */ |
| |
| static bool |
| visit_phi (gimple *phi, bool *inserted, bool backedges_varying_p) |
| { |
| tree result, sameval = VN_TOP, seen_undef = NULL_TREE; |
| tree backedge_val = NULL_TREE; |
| bool seen_non_backedge = false; |
| tree sameval_base = NULL_TREE; |
| poly_int64 soff, doff; |
| unsigned n_executable = 0; |
| edge_iterator ei; |
| edge e; |
| |
| /* TODO: We could check for this in initialization, and replace this |
| with a gcc_assert. */ |
| if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (PHI_RESULT (phi))) |
| return set_ssa_val_to (PHI_RESULT (phi), PHI_RESULT (phi)); |
| |
| /* We track whether a PHI was CSEd to to avoid excessive iterations |
| that would be necessary only because the PHI changed arguments |
| but not value. */ |
| if (!inserted) |
| gimple_set_plf (phi, GF_PLF_1, false); |
| |
| /* See if all non-TOP arguments have the same value. TOP is |
| equivalent to everything, so we can ignore it. */ |
| FOR_EACH_EDGE (e, ei, gimple_bb (phi)->preds) |
| if (e->flags & EDGE_EXECUTABLE) |
| { |
| tree def = PHI_ARG_DEF_FROM_EDGE (phi, e); |
| |
| if (def == PHI_RESULT (phi)) |
| continue; |
| ++n_executable; |
| if (TREE_CODE (def) == SSA_NAME) |
| { |
| if (!backedges_varying_p || !(e->flags & EDGE_DFS_BACK)) |
| def = SSA_VAL (def); |
| if (e->flags & EDGE_DFS_BACK) |
| backedge_val = def; |
| } |
| if (!(e->flags & EDGE_DFS_BACK)) |
| seen_non_backedge = true; |
| if (def == VN_TOP) |
| ; |
| /* Ignore undefined defs for sameval but record one. */ |
| else if (TREE_CODE (def) == SSA_NAME |
| && ! virtual_operand_p (def) |
| && ssa_undefined_value_p (def, false)) |
| seen_undef = def; |
| else if (sameval == VN_TOP) |
| sameval = def; |
| else if (!expressions_equal_p (def, sameval)) |
| { |
| /* We know we're arriving only with invariant addresses here, |
| try harder comparing them. We can do some caching here |
| which we cannot do in expressions_equal_p. */ |
| if (TREE_CODE (def) == ADDR_EXPR |
| && TREE_CODE (sameval) == ADDR_EXPR |
| && sameval_base != (void *)-1) |
| { |
| if (!sameval_base) |
| sameval_base = get_addr_base_and_unit_offset |
| (TREE_OPERAND (sameval, 0), &soff); |
| if (!sameval_base) |
| sameval_base = (tree)(void *)-1; |
| else if ((get_addr_base_and_unit_offset |
| (TREE_OPERAND (def, 0), &doff) == sameval_base) |
| && known_eq (soff, doff)) |
| continue; |
| } |
| sameval = NULL_TREE; |
| break; |
| } |
| } |
| |
| /* If the value we want to use is flowing over the backedge and we |
| should take it as VARYING but it has a non-VARYING value drop to |
| VARYING. |
| If we value-number a virtual operand never value-number to the |
| value from the backedge as that confuses the alias-walking code. |
| See gcc.dg/torture/pr87176.c. If the value is the same on a |
| non-backedge everything is OK though. */ |
| bool visited_p; |
| if ((backedge_val |
| && !seen_non_backedge |
| && TREE_CODE (backedge_val) == SSA_NAME |
| && sameval == backedge_val |
| && (SSA_NAME_IS_VIRTUAL_OPERAND (backedge_val) |
| || SSA_VAL (backedge_val) != backedge_val)) |
| /* Do not value-number a virtual operand to sth not visited though |
| given that allows us to escape a region in alias walking. */ |
| || (sameval |
| && TREE_CODE (sameval) == SSA_NAME |
| && !SSA_NAME_IS_DEFAULT_DEF (sameval) |
| && SSA_NAME_IS_VIRTUAL_OPERAND (sameval) |
| && (SSA_VAL (sameval, &visited_p), !visited_p))) |
| /* Note this just drops to VARYING without inserting the PHI into |
| the hashes. */ |
| result = PHI_RESULT (phi); |
| /* If none of the edges was executable keep the value-number at VN_TOP, |
| if only a single edge is exectuable use its value. */ |
| else if (n_executable <= 1) |
| result = seen_undef ? seen_undef : sameval; |
| /* If we saw only undefined values and VN_TOP use one of the |
| undefined values. */ |
| else if (sameval == VN_TOP) |
| result = seen_undef ? seen_undef : sameval; |
| /* First see if it is equivalent to a phi node in this block. We prefer |
| this as it allows IV elimination - see PRs 66502 and 67167. */ |
| else if ((result = vn_phi_lookup (phi, backedges_varying_p))) |
| { |
| if (!inserted |
| && TREE_CODE (result) == SSA_NAME |
| && gimple_code (SSA_NAME_DEF_STMT (result)) == GIMPLE_PHI) |
| { |
| gimple_set_plf (SSA_NAME_DEF_STMT (result), GF_PLF_1, true); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Marking CSEd to PHI node "); |
| print_gimple_expr (dump_file, SSA_NAME_DEF_STMT (result), |
| 0, TDF_SLIM); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| } |
| /* If all values are the same use that, unless we've seen undefined |
| values as well and the value isn't constant. |
| CCP/copyprop have the same restriction to not remove uninit warnings. */ |
| else if (sameval |
| && (! seen_undef || is_gimple_min_invariant (sameval))) |
| result = sameval; |
| else |
| { |
| result = PHI_RESULT (phi); |
| /* Only insert PHIs that are varying, for constant value numbers |
| we mess up equivalences otherwise as we are only comparing |
| the immediate controlling predicates. */ |
| vn_phi_insert (phi, result, backedges_varying_p); |
| if (inserted) |
| *inserted = true; |
| } |
| |
| return set_ssa_val_to (PHI_RESULT (phi), result); |
| } |
| |
| /* Try to simplify RHS using equivalences and constant folding. */ |
| |
| static tree |
| try_to_simplify (gassign *stmt) |
| { |
| enum tree_code code = gimple_assign_rhs_code (stmt); |
| tree tem; |
| |
| /* For stores we can end up simplifying a SSA_NAME rhs. Just return |
| in this case, there is no point in doing extra work. */ |
| if (code == SSA_NAME) |
| return NULL_TREE; |
| |
| /* First try constant folding based on our current lattice. */ |
| mprts_hook = vn_lookup_simplify_result; |
| tem = gimple_fold_stmt_to_constant_1 (stmt, vn_valueize, vn_valueize); |
| mprts_hook = NULL; |
| if (tem |
| && (TREE_CODE (tem) == SSA_NAME |
| || is_gimple_min_invariant (tem))) |
| return tem; |
| |
| return NULL_TREE; |
| } |
| |
| /* Visit and value number STMT, return true if the value number |
| changed. */ |
| |
| static bool |
| visit_stmt (gimple *stmt, bool backedges_varying_p = false) |
| { |
| bool changed = false; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Value numbering stmt = "); |
| print_gimple_stmt (dump_file, stmt, 0); |
| } |
| |
| if (gimple_code (stmt) == GIMPLE_PHI) |
| changed = visit_phi (stmt, NULL, backedges_varying_p); |
| else if (gimple_has_volatile_ops (stmt)) |
| changed = defs_to_varying (stmt); |
| else if (gassign *ass = dyn_cast <gassign *> (stmt)) |
| { |
| enum tree_code code = gimple_assign_rhs_code (ass); |
| tree lhs = gimple_assign_lhs (ass); |
| tree rhs1 = gimple_assign_rhs1 (ass); |
| tree simplified; |
| |
| /* Shortcut for copies. Simplifying copies is pointless, |
| since we copy the expression and value they represent. */ |
| if (code == SSA_NAME |
| && TREE_CODE (lhs) == SSA_NAME) |
| { |
| changed = visit_copy (lhs, rhs1); |
| goto done; |
| } |
| simplified = try_to_simplify (ass); |
| if (simplified) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "RHS "); |
| print_gimple_expr (dump_file, ass, 0); |
| fprintf (dump_file, " simplified to "); |
| print_generic_expr (dump_file, simplified); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| /* Setting value numbers to constants will occasionally |
| screw up phi congruence because constants are not |
| uniquely associated with a single ssa name that can be |
| looked up. */ |
| if (simplified |
| && is_gimple_min_invariant (simplified) |
| && TREE_CODE (lhs) == SSA_NAME) |
| { |
| changed = set_ssa_val_to (lhs, simplified); |
| goto done; |
| } |
| else if (simplified |
| && TREE_CODE (simplified) == SSA_NAME |
| && TREE_CODE (lhs) == SSA_NAME) |
| { |
| changed = visit_copy (lhs, simplified); |
| goto done; |
| } |
| |
| if ((TREE_CODE (lhs) == SSA_NAME |
| /* We can substitute SSA_NAMEs that are live over |
| abnormal edges with their constant value. */ |
| && !(gimple_assign_copy_p (ass) |
| && is_gimple_min_invariant (rhs1)) |
| && !(simplified |
| && is_gimple_min_invariant (simplified)) |
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs)) |
| /* Stores or copies from SSA_NAMEs that are live over |
| abnormal edges are a problem. */ |
| || (code == SSA_NAME |
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (rhs1))) |
| changed = defs_to_varying (ass); |
| else if (REFERENCE_CLASS_P (lhs) |
| || DECL_P (lhs)) |
| changed = visit_reference_op_store (lhs, rhs1, ass); |
| else if (TREE_CODE (lhs) == SSA_NAME) |
| { |
| if ((gimple_assign_copy_p (ass) |
| && is_gimple_min_invariant (rhs1)) |
| || (simplified |
| && is_gimple_min_invariant (simplified))) |
| { |
| if (simplified) |
| changed = set_ssa_val_to (lhs, simplified); |
| else |
| changed = set_ssa_val_to (lhs, rhs1); |
| } |
| else |
| { |
| /* Visit the original statement. */ |
| switch (vn_get_stmt_kind (ass)) |
| { |
| case VN_NARY: |
| changed = visit_nary_op (lhs, ass); |
| break; |
| case VN_REFERENCE: |
| changed = visit_reference_op_load (lhs, rhs1, ass); |
| break; |
| default: |
| changed = defs_to_varying (ass); |
| break; |
| } |
| } |
| } |
| else |
| changed = defs_to_varying (ass); |
| } |
| else if (gcall *call_stmt = dyn_cast <gcall *> (stmt)) |
| { |
| tree lhs = gimple_call_lhs (call_stmt); |
| if (lhs && TREE_CODE (lhs) == SSA_NAME) |
| { |
| /* Try constant folding based on our current lattice. */ |
| tree simplified = gimple_fold_stmt_to_constant_1 (call_stmt, |
| vn_valueize); |
| if (simplified) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "call "); |
| print_gimple_expr (dump_file, call_stmt, 0); |
| fprintf (dump_file, " simplified to "); |
| print_generic_expr (dump_file, simplified); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| /* Setting value numbers to constants will occasionally |
| screw up phi congruence because constants are not |
| uniquely associated with a single ssa name that can be |
| looked up. */ |
| if (simplified |
| && is_gimple_min_invariant (simplified)) |
| { |
| changed = set_ssa_val_to (lhs, simplified); |
| if (gimple_vdef (call_stmt)) |
| changed |= set_ssa_val_to (gimple_vdef (call_stmt), |
| SSA_VAL (gimple_vuse (call_stmt))); |
| goto done; |
| } |
| else if (simplified |
| && TREE_CODE (simplified) == SSA_NAME) |
| { |
| changed = visit_copy (lhs, simplified); |
| if (gimple_vdef (call_stmt)) |
| changed |= set_ssa_val_to (gimple_vdef (call_stmt), |
| SSA_VAL (gimple_vuse (call_stmt))); |
| goto done; |
| } |
| else if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs)) |
| { |
| changed = defs_to_varying (call_stmt); |
| goto done; |
| } |
| } |
| |
| /* Pick up flags from a devirtualization target. */ |
| tree fn = gimple_call_fn (stmt); |
| int extra_fnflags = 0; |
| if (fn && TREE_CODE (fn) == SSA_NAME) |
| { |
| fn = SSA_VAL (fn); |
| if (TREE_CODE (fn) == ADDR_EXPR |
| && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL) |
| extra_fnflags = flags_from_decl_or_type (TREE_OPERAND (fn, 0)); |
| } |
| if ((/* Calls to the same function with the same vuse |
| and the same operands do not necessarily return the same |
| value, unless they're pure or const. */ |
| ((gimple_call_flags (call_stmt) | extra_fnflags) |
| & (ECF_PURE | ECF_CONST)) |
| /* If calls have a vdef, subsequent calls won't have |
| the same incoming vuse. So, if 2 calls with vdef have the |
| same vuse, we know they're not subsequent. |
| We can value number 2 calls to the same function with the |
| same vuse and the same operands which are not subsequent |
| the same, because there is no code in the program that can |
| compare the 2 values... */ |
| || (gimple_vdef (call_stmt) |
| /* ... unless the call returns a pointer which does |
| not alias with anything else. In which case the |
| information that the values are distinct are encoded |
| in the IL. */ |
| && !(gimple_call_return_flags (call_stmt) & ERF_NOALIAS) |
| /* Only perform the following when being called from PRE |
| which embeds tail merging. */ |
| && default_vn_walk_kind == VN_WALK)) |
| /* Do not process .DEFERRED_INIT since that confuses uninit |
| analysis. */ |
| && !gimple_call_internal_p (call_stmt, IFN_DEFERRED_INIT)) |
| changed = visit_reference_op_call (lhs, call_stmt); |
| else |
| changed = defs_to_varying (call_stmt); |
| } |
| else |
| changed = defs_to_varying (stmt); |
| done: |
| return changed; |
| } |
| |
| |
| /* Allocate a value number table. */ |
| |
| static void |
| allocate_vn_table (vn_tables_t table, unsigned size) |
| { |
| table->phis = new vn_phi_table_type (size); |
| table->nary = new vn_nary_op_table_type (size); |
| table->references = new vn_reference_table_type (size); |
| } |
| |
| /* Free a value number table. */ |
| |
| static void |
| free_vn_table (vn_tables_t table) |
| { |
| /* Walk over elements and release vectors. */ |
| vn_reference_iterator_type hir; |
| vn_reference_t vr; |
| FOR_EACH_HASH_TABLE_ELEMENT (*table->references, vr, vn_reference_t, hir) |
| vr->operands.release (); |
| delete table->phis; |
| table->phis = NULL; |
| delete table->nary; |
| table->nary = NULL; |
| delete table->references; |
| table->references = NULL; |
| } |
| |
| /* Set *ID according to RESULT. */ |
| |
| static void |
| set_value_id_for_result (tree result, unsigned int *id) |
| { |
| if (result && TREE_CODE (result) == SSA_NAME) |
| *id = VN_INFO (result)->value_id; |
| else if (result && is_gimple_min_invariant (result)) |
| *id = get_or_alloc_constant_value_id (result); |
| else |
| *id = get_next_value_id (); |
| } |
| |
| /* Set the value ids in the valid hash tables. */ |
| |
| static void |
| set_hashtable_value_ids (void) |
| { |
| vn_nary_op_iterator_type hin; |
| vn_phi_iterator_type hip; |
| vn_reference_iterator_type hir; |
| vn_nary_op_t vno; |
| vn_reference_t vr; |
| vn_phi_t vp; |
| |
| /* Now set the value ids of the things we had put in the hash |
| table. */ |
| |
| FOR_EACH_HASH_TABLE_ELEMENT (*valid_info->nary, vno, vn_nary_op_t, hin) |
| if (! vno->predicated_values) |
| set_value_id_for_result (vno->u.result, &vno->value_id); |
| |
| FOR_EACH_HASH_TABLE_ELEMENT (*valid_info->phis, vp, vn_phi_t, hip) |
| set_value_id_for_result (vp->result, &vp->value_id); |
| |
| FOR_EACH_HASH_TABLE_ELEMENT (*valid_info->references, vr, vn_reference_t, |
| hir) |
| set_value_id_for_result (vr->result, &vr->value_id); |
| } |
| |
| /* Return the maximum value id we have ever seen. */ |
| |
| unsigned int |
| get_max_value_id (void) |
| { |
| return next_value_id; |
| } |
| |
| /* Return the maximum constant value id we have ever seen. */ |
| |
| unsigned int |
| get_max_constant_value_id (void) |
| { |
| return -next_constant_value_id; |
| } |
| |
| /* Return the next unique value id. */ |
| |
| unsigned int |
| get_next_value_id (void) |
| { |
| gcc_checking_assert ((int)next_value_id > 0); |
| return next_value_id++; |
| } |
| |
| /* Return the next unique value id for constants. */ |
| |
| unsigned int |
| get_next_constant_value_id (void) |
| { |
| gcc_checking_assert (next_constant_value_id < 0); |
| return next_constant_value_id--; |
| } |
| |
| |
| /* Compare two expressions E1 and E2 and return true if they are equal. |
| If match_vn_top_optimistically is true then VN_TOP is equal to anything, |
| otherwise VN_TOP only matches VN_TOP. */ |
| |
| bool |
| expressions_equal_p (tree e1, tree e2, bool match_vn_top_optimistically) |
| { |
| /* The obvious case. */ |
| if (e1 == e2) |
| return true; |
| |
| /* If either one is VN_TOP consider them equal. */ |
| if (match_vn_top_optimistically |
| && (e1 == VN_TOP || e2 == VN_TOP)) |
| return true; |
| |
| /* SSA_NAME compare pointer equal. */ |
| if (TREE_CODE (e1) == SSA_NAME || TREE_CODE (e2) == SSA_NAME) |
| return false; |
| |
| /* Now perform the actual comparison. */ |
| if (TREE_CODE (e1) == TREE_CODE (e2) |
| && operand_equal_p (e1, e2, OEP_PURE_SAME)) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Return true if the nary operation NARY may trap. This is a copy |
| of stmt_could_throw_1_p adjusted to the SCCVN IL. */ |
| |
| bool |
| vn_nary_may_trap (vn_nary_op_t nary) |
| { |
| tree type; |
| tree rhs2 = NULL_TREE; |
| bool honor_nans = false; |
| bool honor_snans = false; |
| bool fp_operation = false; |
| bool honor_trapv = false; |
| bool handled, ret; |
| unsigned i; |
| |
| if (TREE_CODE_CLASS (nary->opcode) == tcc_comparison |
| || TREE_CODE_CLASS (nary->opcode) == tcc_unary |
| || TREE_CODE_CLASS (nary->opcode) == tcc_binary) |
| { |
| type = nary->type; |
| fp_operation = FLOAT_TYPE_P (type); |
| if (fp_operation) |
| { |
| honor_nans = flag_trapping_math && !flag_finite_math_only; |
| honor_snans = flag_signaling_nans != 0; |
| } |
| else if (INTEGRAL_TYPE_P (type) && TYPE_OVERFLOW_TRAPS (type)) |
| honor_trapv = true; |
| } |
| if (nary->length >= 2) |
| rhs2 = nary->op[1]; |
| ret = operation_could_trap_helper_p (nary->opcode, fp_operation, |
| honor_trapv, honor_nans, honor_snans, |
| rhs2, &handled); |
| if (handled && ret) |
| return true; |
| |
| for (i = 0; i < nary->length; ++i) |
| if (tree_could_trap_p (nary->op[i])) |
| return true; |
| |
| return false; |
| } |
| |
| /* Return true if the reference operation REF may trap. */ |
| |
| bool |
| vn_reference_may_trap (vn_reference_t ref) |
| { |
| switch (ref->operands[0].opcode) |
| { |
| case MODIFY_EXPR: |
| case CALL_EXPR: |
| /* We do not handle calls. */ |
| return true; |
| case ADDR_EXPR: |
| /* And toplevel address computations never trap. */ |
| return false; |
| default:; |
| } |
| |
| vn_reference_op_t op; |
| unsigned i; |
| FOR_EACH_VEC_ELT (ref->operands, i, op) |
| { |
| switch (op->opcode) |
| { |
| case WITH_SIZE_EXPR: |
| case TARGET_MEM_REF: |
| /* Always variable. */ |
| return true; |
| case COMPONENT_REF: |
| if (op->op1 && TREE_CODE (op->op1) == SSA_NAME) |
| return true; |
| break; |
| case ARRAY_RANGE_REF: |
| if (TREE_CODE (op->op0) == SSA_NAME) |
| return true; |
| break; |
| case ARRAY_REF: |
| { |
| if (TREE_CODE (op->op0) != INTEGER_CST) |
| return true; |
| |
| /* !in_array_bounds */ |
| tree domain_type = TYPE_DOMAIN (ref->operands[i+1].type); |
| if (!domain_type) |
| return true; |
| |
| tree min = op->op1; |
| tree max = TYPE_MAX_VALUE (domain_type); |
| if (!min |
| || !max |
| || TREE_CODE (min) != INTEGER_CST |
| || TREE_CODE (max) != INTEGER_CST) |
| return true; |
| |
| if (tree_int_cst_lt (op->op0, min) |
| || tree_int_cst_lt (max, op->op0)) |
| return true; |
| |
| break; |
| } |
| case MEM_REF: |
| /* Nothing interesting in itself, the base is separate. */ |
| break; |
| /* The following are the address bases. */ |
| case SSA_NAME: |
| return true; |
| case ADDR_EXPR: |
| if (op->op0) |
| return tree_could_trap_p (TREE_OPERAND (op->op0, 0)); |
| return false; |
| default:; |
| } |
| } |
| return false; |
| } |
| |
| eliminate_dom_walker::eliminate_dom_walker (cdi_direction direction, |
| bitmap inserted_exprs_) |
| : dom_walker (direction), do_pre (inserted_exprs_ != NULL), |
| el_todo (0), eliminations (0), insertions (0), |
| inserted_exprs (inserted_exprs_) |
| { |
| need_eh_cleanup = BITMAP_ALLOC (NULL); |
| need_ab_cleanup = BITMAP_ALLOC (NULL); |
| } |
| |
| eliminate_dom_walker::~eliminate_dom_walker () |
| { |
| BITMAP_FREE (need_eh_cleanup); |
| BITMAP_FREE (need_ab_cleanup); |
| } |
| |
| /* Return a leader for OP that is available at the current point of the |
| eliminate domwalk. */ |
| |
| tree |
| eliminate_dom_walker::eliminate_avail (basic_block, tree op) |
| { |
| tree valnum = VN_INFO (op)->valnum; |
| if (TREE_CODE (valnum) == SSA_NAME) |
| { |
| if (SSA_NAME_IS_DEFAULT_DEF (valnum)) |
| return valnum; |
| if (avail.length () > SSA_NAME_VERSION (valnum)) |
| return avail[SSA_NAME_VERSION (valnum)]; |
| } |
| else if (is_gimple_min_invariant (valnum)) |
| return valnum; |
| return NULL_TREE; |
| } |
| |
| /* At the current point of the eliminate domwalk make OP available. */ |
| |
| void |
| eliminate_dom_walker::eliminate_push_avail (basic_block, tree op) |
| { |
| tree valnum = VN_INFO (op)->valnum; |
| if (TREE_CODE (valnum) == SSA_NAME) |
| { |
| if (avail.length () <= SSA_NAME_VERSION (valnum)) |
| avail.safe_grow_cleared (SSA_NAME_VERSION (valnum) + 1, true); |
| tree pushop = op; |
| if (avail[SSA_NAME_VERSION (valnum)]) |
| pushop = avail[SSA_NAME_VERSION (valnum)]; |
| avail_stack.safe_push (pushop); |
| avail[SSA_NAME_VERSION (valnum)] = op; |
| } |
| } |
| |
| /* Insert the expression recorded by SCCVN for VAL at *GSI. Returns |
| the leader for the expression if insertion was successful. */ |
| |
| tree |
| eliminate_dom_walker::eliminate_insert (basic_block bb, |
| gimple_stmt_iterator *gsi, tree val) |
| { |
| /* We can insert a sequence with a single assignment only. */ |
| gimple_seq stmts = VN_INFO (val)->expr; |
| if (!gimple_seq_singleton_p (stmts)) |
| return NULL_TREE; |
| gassign *stmt = dyn_cast <gassign *> (gimple_seq_first_stmt (stmts)); |
| if (!stmt |
| || (!CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (stmt)) |
| && gimple_assign_rhs_code (stmt) != VIEW_CONVERT_EXPR |
| && gimple_assign_rhs_code (stmt) != NEGATE_EXPR |
| && gimple_assign_rhs_code (stmt) != BIT_FIELD_REF |
| && (gimple_assign_rhs_code (stmt) != BIT_AND_EXPR |
| || TREE_CODE (gimple_assign_rhs2 (stmt)) != INTEGER_CST))) |
| return NULL_TREE; |
| |
| tree op = gimple_assign_rhs1 (stmt); |
| if (gimple_assign_rhs_code (stmt) == VIEW_CONVERT_EXPR |
| || gimple_assign_rhs_code (stmt) == BIT_FIELD_REF) |
| op = TREE_OPERAND (op, 0); |
| tree leader = TREE_CODE (op) == SSA_NAME ? eliminate_avail (bb, op) : op; |
| if (!leader) |
| return NULL_TREE; |
| |
| tree res; |
| stmts = NULL; |
| if (gimple_assign_rhs_code (stmt) == BIT_FIELD_REF) |
| res = gimple_build (&stmts, BIT_FIELD_REF, |
| TREE_TYPE (val), leader, |
| TREE_OPERAND (gimple_assign_rhs1 (stmt), 1), |
| TREE_OPERAND (gimple_assign_rhs1 (stmt), 2)); |
| else if (gimple_assign_rhs_code (stmt) == BIT_AND_EXPR) |
| res = gimple_build (&stmts, BIT_AND_EXPR, |
| TREE_TYPE (val), leader, gimple_assign_rhs2 (stmt)); |
| else |
| res = gimple_build (&stmts, gimple_assign_rhs_code (stmt), |
| TREE_TYPE (val), leader); |
| if (TREE_CODE (res) != SSA_NAME |
| || SSA_NAME_IS_DEFAULT_DEF (res) |
| || gimple_bb (SSA_NAME_DEF_STMT (res))) |
| { |
| gimple_seq_discard (stmts); |
| |
| /* During propagation we have to treat SSA info conservatively |
| and thus we can end up simplifying the inserted expression |
| at elimination time to sth not defined in stmts. */ |
| /* But then this is a redundancy we failed to detect. Which means |
| res now has two values. That doesn't play well with how |
| we track availability here, so give up. */ |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| if (TREE_CODE (res) == SSA_NAME) |
| res = eliminate_avail (bb, res); |
| if (res) |
| { |
| fprintf (dump_file, "Failed to insert expression for value "); |
| print_generic_expr (dump_file, val); |
| fprintf (dump_file, " which is really fully redundant to "); |
| print_generic_expr (dump_file, res); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| return NULL_TREE; |
| } |
| else |
| { |
| gsi_insert_seq_before (gsi, stmts, GSI_SAME_STMT); |
| vn_ssa_aux_t vn_info = VN_INFO (res); |
| vn_info->valnum = val; |
| vn_info->visited = true; |
| } |
| |
| insertions++; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Inserted "); |
| print_gimple_stmt (dump_file, SSA_NAME_DEF_STMT (res), 0); |
| } |
| |
| return res; |
| } |
| |
| void |
| eliminate_dom_walker::eliminate_stmt (basic_block b, gimple_stmt_iterator *gsi) |
| { |
| tree sprime = NULL_TREE; |
| gimple *stmt = gsi_stmt (*gsi); |
| tree lhs = gimple_get_lhs (stmt); |
| if (lhs && TREE_CODE (lhs) == SSA_NAME |
| && !gimple_has_volatile_ops (stmt) |
| /* See PR43491. Do not replace a global register variable when |
| it is a the RHS of an assignment. Do replace local register |
| variables since gcc does not guarantee a local variable will |
| be allocated in register. |
| ??? The fix isn't effective here. This should instead |
| be ensured by not value-numbering them the same but treating |
| them like volatiles? */ |
| && !(gimple_assign_single_p (stmt) |
| && (TREE_CODE (gimple_assign_rhs1 (stmt)) == VAR_DECL |
| && DECL_HARD_REGISTER (gimple_assign_rhs1 (stmt)) |
| && is_global_var (gimple_assign_rhs1 (stmt))))) |
| { |
| sprime = eliminate_avail (b, lhs); |
| if (!sprime) |
| { |
| /* If there is no existing usable leader but SCCVN thinks |
| it has an expression it wants to use as replacement, |
| insert that. */ |
| tree val = VN_INFO (lhs)->valnum; |
| vn_ssa_aux_t vn_info; |
| if (val != VN_TOP |
| && TREE_CODE (val) == SSA_NAME |
| && (vn_info = VN_INFO (val), true) |
| && vn_info->needs_insertion |
| && vn_info->expr != NULL |
| && (sprime = eliminate_insert (b, gsi, val)) != NULL_TREE) |
| eliminate_push_avail (b, sprime); |
| } |
| |
| /* If this now constitutes a copy duplicate points-to |
| and range info appropriately. This is especially |
| important for inserted code. See tree-ssa-copy.cc |
| for similar code. */ |
| if (sprime |
| && TREE_CODE (sprime) == SSA_NAME) |
| { |
| basic_block sprime_b = gimple_bb (SSA_NAME_DEF_STMT (sprime)); |
| if (POINTER_TYPE_P (TREE_TYPE (lhs)) |
| && SSA_NAME_PTR_INFO (lhs) |
| && ! SSA_NAME_PTR_INFO (sprime)) |
| { |
| duplicate_ssa_name_ptr_info (sprime, |
| SSA_NAME_PTR_INFO (lhs)); |
| if (b != sprime_b) |
| reset_flow_sensitive_info (sprime); |
| } |
| else if (INTEGRAL_TYPE_P (TREE_TYPE (lhs)) |
| && SSA_NAME_RANGE_INFO (lhs) |
| && ! SSA_NAME_RANGE_INFO (sprime) |
| && b == sprime_b) |
| duplicate_ssa_name_range_info (sprime, |
| SSA_NAME_RANGE_TYPE (lhs), |
| SSA_NAME_RANGE_INFO (lhs)); |
| } |
| |
| /* Inhibit the use of an inserted PHI on a loop header when |
| the address of the memory reference is a simple induction |
| variable. In other cases the vectorizer won't do anything |
| anyway (either it's loop invariant or a complicated |
| expression). */ |
| if (sprime |
| && TREE_CODE (sprime) == SSA_NAME |
| && do_pre |
| && (flag_tree_loop_vectorize || flag_tree_parallelize_loops > 1) |
| && loop_outer (b->loop_father) |
| && has_zero_uses (sprime) |
| && bitmap_bit_p (inserted_exprs, SSA_NAME_VERSION (sprime)) |
| && gimple_assign_load_p (stmt)) |
| { |
| gimple *def_stmt = SSA_NAME_DEF_STMT (sprime); |
| basic_block def_bb = gimple_bb (def_stmt); |
| if (gimple_code (def_stmt) == GIMPLE_PHI |
| && def_bb->loop_father->header == def_bb) |
| { |
| loop_p loop = def_bb->loop_father; |
| ssa_op_iter iter; |
| tree op; |
| bool found = false; |
| FOR_EACH_SSA_TREE_OPERAND (op, stmt, iter, SSA_OP_USE) |
| { |
| affine_iv iv; |
| def_bb = gimple_bb (SSA_NAME_DEF_STMT (op)); |
| if (def_bb |
| && flow_bb_inside_loop_p (loop, def_bb) |
| && simple_iv (loop, loop, op, &iv, true)) |
| { |
| found = true; |
| break; |
| } |
| } |
| if (found) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Not replacing "); |
| print_gimple_expr (dump_file, stmt, 0); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, sprime); |
| fprintf (dump_file, " which would add a loop" |
| " carried dependence to loop %d\n", |
| loop->num); |
| } |
| /* Don't keep sprime available. */ |
| sprime = NULL_TREE; |
| } |
| } |
| } |
| |
| if (sprime) |
| { |
| /* If we can propagate the value computed for LHS into |
| all uses don't bother doing anything with this stmt. */ |
| if (may_propagate_copy (lhs, sprime)) |
| { |
| /* Mark it for removal. */ |
| to_remove.safe_push (stmt); |
| |
| /* ??? Don't count copy/constant propagations. */ |
| if (gimple_assign_single_p (stmt) |
| && (TREE_CODE (gimple_assign_rhs1 (stmt)) == SSA_NAME |
| || gimple_assign_rhs1 (stmt) == sprime)) |
| return; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Replaced "); |
| print_gimple_expr (dump_file, stmt, 0); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, sprime); |
| fprintf (dump_file, " in all uses of "); |
| print_gimple_stmt (dump_file, stmt, 0); |
| } |
| |
| eliminations++; |
| return; |
| } |
| |
| /* If this is an assignment from our leader (which |
| happens in the case the value-number is a constant) |
| then there is nothing to do. Likewise if we run into |
| inserted code that needed a conversion because of |
| our type-agnostic value-numbering of loads. */ |
| if ((gimple_assign_single_p (stmt) |
| || (is_gimple_assign (stmt) |
| && (CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (stmt)) |
| || gimple_assign_rhs_code (stmt) == VIEW_CONVERT_EXPR))) |
| && sprime == gimple_assign_rhs1 (stmt)) |
| return; |
| |
| /* Else replace its RHS. */ |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Replaced "); |
| print_gimple_expr (dump_file, stmt, 0); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, sprime); |
| fprintf (dump_file, " in "); |
| print_gimple_stmt (dump_file, stmt, 0); |
| } |
| eliminations++; |
| |
| bool can_make_abnormal_goto = (is_gimple_call (stmt) |
| && stmt_can_make_abnormal_goto (stmt)); |
| gimple *orig_stmt = stmt; |
| if (!useless_type_conversion_p (TREE_TYPE (lhs), |
| TREE_TYPE (sprime))) |
| { |
| /* We preserve conversions to but not from function or method |
| types. This asymmetry makes it necessary to re-instantiate |
| conversions here. */ |
| if (POINTER_TYPE_P (TREE_TYPE (lhs)) |
| && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (TREE_TYPE (lhs)))) |
| sprime = fold_convert (TREE_TYPE (lhs), sprime); |
| else |
| gcc_unreachable (); |
| } |
| tree vdef = gimple_vdef (stmt); |
| tree vuse = gimple_vuse (stmt); |
| propagate_tree_value_into_stmt (gsi, sprime); |
| stmt = gsi_stmt (*gsi); |
| update_stmt (stmt); |
| /* In case the VDEF on the original stmt was released, value-number |
| it to the VUSE. This is to make vuse_ssa_val able to skip |
| released virtual operands. */ |
| if (vdef != gimple_vdef (stmt)) |
| { |
| gcc_assert (SSA_NAME_IN_FREE_LIST (vdef)); |
| VN_INFO (vdef)->valnum = vuse; |
| } |
| |
| /* If we removed EH side-effects from the statement, clean |
| its EH information. */ |
| if (maybe_clean_or_replace_eh_stmt (orig_stmt, stmt)) |
| { |
| bitmap_set_bit (need_eh_cleanup, |
| gimple_bb (stmt)->index); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Removed EH side-effects.\n"); |
| } |
| |
| /* Likewise for AB side-effects. */ |
| if (can_make_abnormal_goto |
| && !stmt_can_make_abnormal_goto (stmt)) |
| { |
| bitmap_set_bit (need_ab_cleanup, |
| gimple_bb (stmt)->index); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Removed AB side-effects.\n"); |
| } |
| |
| return; |
| } |
| } |
| |
| /* If the statement is a scalar store, see if the expression |
| has the same value number as its rhs. If so, the store is |
| dead. */ |
| if (gimple_assign_single_p (stmt) |
| && !gimple_has_volatile_ops (stmt) |
| && !is_gimple_reg (gimple_assign_lhs (stmt)) |
| && (TREE_CODE (gimple_assign_rhs1 (stmt)) == SSA_NAME |
| || is_gimple_min_invariant (gimple_assign_rhs1 (stmt)))) |
| { |
| tree rhs = gimple_assign_rhs1 (stmt); |
| vn_reference_t vnresult; |
| /* ??? gcc.dg/torture/pr91445.c shows that we lookup a boolean |
| typed load of a byte known to be 0x11 as 1 so a store of |
| a boolean 1 is detected as redundant. Because of this we |
| have to make sure to lookup with a ref where its size |
| matches the precision. */ |
| tree lookup_lhs = lhs; |
| if (INTEGRAL_TYPE_P (TREE_TYPE (lhs)) |
| && (TREE_CODE (lhs) != COMPONENT_REF |
| || !DECL_BIT_FIELD_TYPE (TREE_OPERAND (lhs, 1))) |
| && !type_has_mode_precision_p (TREE_TYPE (lhs))) |
| { |
| if (TREE_CODE (lhs) == COMPONENT_REF |
| || TREE_CODE (lhs) == MEM_REF) |
| { |
| tree ltype = build_nonstandard_integer_type |
| (TREE_INT_CST_LOW (TYPE_SIZE (TREE_TYPE (lhs))), |
| TYPE_UNSIGNED (TREE_TYPE (lhs))); |
| if (TREE_CODE (lhs) == COMPONENT_REF) |
| { |
| tree foff = component_ref_field_offset (lhs); |
| tree f = TREE_OPERAND (lhs, 1); |
| if (!poly_int_tree_p (foff)) |
| lookup_lhs = NULL_TREE; |
| else |
| lookup_lhs = build3 (BIT_FIELD_REF, ltype, |
| TREE_OPERAND (lhs, 0), |
| TYPE_SIZE (TREE_TYPE (lhs)), |
| bit_from_pos |
| (foff, DECL_FIELD_BIT_OFFSET (f))); |
| } |
| else |
| lookup_lhs = build2 (MEM_REF, ltype, |
| TREE_OPERAND (lhs, 0), |
| TREE_OPERAND (lhs, 1)); |
| } |
| else |
| lookup_lhs = NULL_TREE; |
| } |
| tree val = NULL_TREE; |
| if (lookup_lhs) |
| val = vn_reference_lookup (lookup_lhs, gimple_vuse (stmt), |
| VN_WALKREWRITE, &vnresult, false); |
| if (TREE_CODE (rhs) == SSA_NAME) |
| rhs = VN_INFO (rhs)->valnum; |
| if (val |
| && (operand_equal_p (val, rhs, 0) |
| /* Due to the bitfield lookups above we can get bit |
| interpretations of the same RHS as values here. Those |
| are redundant as well. */ |
| || (TREE_CODE (val) == SSA_NAME |
| && gimple_assign_single_p (SSA_NAME_DEF_STMT (val)) |
| && (val = gimple_assign_rhs1 (SSA_NAME_DEF_STMT (val))) |
| && TREE_CODE (val) == VIEW_CONVERT_EXPR |
| && TREE_OPERAND (val, 0) == rhs))) |
| { |
| /* We can only remove the later store if the former aliases |
| at least all accesses the later one does or if the store |
| was to readonly memory storing the same value. */ |
| ao_ref lhs_ref; |
| ao_ref_init (&lhs_ref, lhs); |
| alias_set_type set = ao_ref_alias_set (&lhs_ref); |
| alias_set_type base_set = ao_ref_base_alias_set (&lhs_ref); |
| if (! vnresult |
| || ((vnresult->set == set |
| || alias_set_subset_of (set, vnresult->set)) |
| && (vnresult->base_set == base_set |
| || alias_set_subset_of (base_set, vnresult->base_set)))) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Deleted redundant store "); |
| print_gimple_stmt (dump_file, stmt, 0); |
| } |
| |
| /* Queue stmt for removal. */ |
| to_remove.safe_push (stmt); |
| return; |
| } |
| } |
| } |
| |
| /* If this is a control statement value numbering left edges |
| unexecuted on force the condition in a way consistent with |
| that. */ |
| if (gcond *cond = dyn_cast <gcond *> (stmt)) |
| { |
| if ((EDGE_SUCC (b, 0)->flags & EDGE_EXECUTABLE) |
| ^ (EDGE_SUCC (b, 1)->flags & EDGE_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Removing unexecutable edge from "); |
| print_gimple_stmt (dump_file, stmt, 0); |
| } |
| if (((EDGE_SUCC (b, 0)->flags & EDGE_TRUE_VALUE) != 0) |
| == ((EDGE_SUCC (b, 0)->flags & EDGE_EXECUTABLE) != 0)) |
| gimple_cond_make_true (cond); |
| else |
| gimple_cond_make_false (cond); |
| update_stmt (cond); |
| el_todo |= TODO_cleanup_cfg; |
| return; |
| } |
| } |
| |
| bool can_make_abnormal_goto = stmt_can_make_abnormal_goto (stmt); |
| bool was_noreturn = (is_gimple_call (stmt) |
| && gimple_call_noreturn_p (stmt)); |
| tree vdef = gimple_vdef (stmt); |
| tree vuse = gimple_vuse (stmt); |
| |
| /* If we didn't replace the whole stmt (or propagate the result |
| into all uses), replace all uses on this stmt with their |
| leaders. */ |
| bool modified = false; |
| use_operand_p use_p; |
| ssa_op_iter iter; |
| FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, SSA_OP_USE) |
| { |
| tree use = USE_FROM_PTR (use_p); |
| /* ??? The call code above leaves stmt operands un-updated. */ |
| if (TREE_CODE (use) != SSA_NAME) |
| continue; |
| tree sprime; |
| if (SSA_NAME_IS_DEFAULT_DEF (use)) |
| /* ??? For default defs BB shouldn't matter, but we have to |
| solve the inconsistency between rpo eliminate and |
| dom eliminate avail valueization first. */ |
| sprime = eliminate_avail (b, use); |
| else |
| /* Look for sth available at the definition block of the argument. |
| This avoids inconsistencies between availability there which |
| decides if the stmt can be removed and availability at the |
| use site. The SSA property ensures that things available |
| at the definition are also available at uses. */ |
| sprime = eliminate_avail (gimple_bb (SSA_NAME_DEF_STMT (use)), use); |
| if (sprime && sprime != use |
| && may_propagate_copy (use, sprime, true) |
| /* We substitute into debug stmts to avoid excessive |
| debug temporaries created by removed stmts, but we need |
| to avoid doing so for inserted sprimes as we never want |
| to create debug temporaries for them. */ |
| && (!inserted_exprs |
| || TREE_CODE (sprime) != SSA_NAME |
| || !is_gimple_debug (stmt) |
| || !bitmap_bit_p (inserted_exprs, SSA_NAME_VERSION (sprime)))) |
| { |
| propagate_value (use_p, sprime); |
| modified = true; |
| } |
| } |
| |
| /* Fold the stmt if modified, this canonicalizes MEM_REFs we propagated |
| into which is a requirement for the IPA devirt machinery. */ |
| gimple *old_stmt = stmt; |
| if (modified) |
| { |
| /* If a formerly non-invariant ADDR_EXPR is turned into an |
| invariant one it was on a separate stmt. */ |
| if (gimple_assign_single_p (stmt) |
| && TREE_CODE (gimple_assign_rhs1 (stmt)) == ADDR_EXPR) |
| recompute_tree_invariant_for_addr_expr (gimple_assign_rhs1 (stmt)); |
| gimple_stmt_iterator prev = *gsi; |
| gsi_prev (&prev); |
| if (fold_stmt (gsi, follow_all_ssa_edges)) |
| { |
| /* fold_stmt may have created new stmts inbetween |
| the previous stmt and the folded stmt. Mark |
| all defs created there as varying to not confuse |
| the SCCVN machinery as we're using that even during |
| elimination. */ |
| if (gsi_end_p (prev)) |
| prev = gsi_start_bb (b); |
| else |
| gsi_next (&prev); |
| if (gsi_stmt (prev) != gsi_stmt (*gsi)) |
| do |
| { |
| tree def; |
| ssa_op_iter dit; |
| FOR_EACH_SSA_TREE_OPERAND (def, gsi_stmt (prev), |
| dit, SSA_OP_ALL_DEFS) |
| /* As existing DEFs may move between stmts |
| only process new ones. */ |
| if (! has_VN_INFO (def)) |
| { |
| vn_ssa_aux_t vn_info = VN_INFO (def); |
| vn_info->valnum = def; |
| vn_info->visited = true; |
| } |
| if (gsi_stmt (prev) == gsi_stmt (*gsi)) |
| break; |
| gsi_next (&prev); |
| } |
| while (1); |
| } |
| stmt = gsi_stmt (*gsi); |
| /* In case we folded the stmt away schedule the NOP for removal. */ |
| if (gimple_nop_p (stmt)) |
| to_remove.safe_push (stmt); |
| } |
| |
| /* Visit indirect calls and turn them into direct calls if |
| possible using the devirtualization machinery. Do this before |
| checking for required EH/abnormal/noreturn cleanup as devird |
| may expose more of those. */ |
| if (gcall *call_stmt = dyn_cast <gcall *> (stmt)) |
| { |
| tree fn = gimple_call_fn (call_stmt); |
| if (fn |
| && flag_devirtualize |
| && virtual_method_call_p (fn)) |
| { |
| tree otr_type = obj_type_ref_class (fn); |
| unsigned HOST_WIDE_INT otr_tok |
| = tree_to_uhwi (OBJ_TYPE_REF_TOKEN (fn)); |
| tree instance; |
| ipa_polymorphic_call_context context (current_function_decl, |
| fn, stmt, &instance); |
| context.get_dynamic_type (instance, OBJ_TYPE_REF_OBJECT (fn), |
| otr_type, stmt, NULL); |
| bool final; |
| vec <cgraph_node *> targets |
| = possible_polymorphic_call_targets (obj_type_ref_class (fn), |
| otr_tok, context, &final); |
| if (dump_file) |
| dump_possible_polymorphic_call_targets (dump_file, |
| obj_type_ref_class (fn), |
| otr_tok, context); |
| if (final && targets.length () <= 1 && dbg_cnt (devirt)) |
| { |
| tree fn; |
| if (targets.length () == 1) |
| fn = targets[0]->decl; |
| else |
| fn = builtin_decl_implicit (BUILT_IN_UNREACHABLE); |
| if (dump_enabled_p ()) |
| { |
| dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, stmt, |
| "converting indirect call to " |
| "function %s\n", |
| lang_hooks.decl_printable_name (fn, 2)); |
| } |
| gimple_call_set_fndecl (call_stmt, fn); |
| /* If changing the call to __builtin_unreachable |
| or similar noreturn function, adjust gimple_call_fntype |
| too. */ |
| if (gimple_call_noreturn_p (call_stmt) |
| && VOID_TYPE_P (TREE_TYPE (TREE_TYPE (fn))) |
| && TYPE_ARG_TYPES (TREE_TYPE (fn)) |
| && (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (fn))) |
| == void_type_node)) |
| gimple_call_set_fntype (call_stmt, TREE_TYPE (fn)); |
| maybe_remove_unused_call_args (cfun, call_stmt); |
| modified = true; |
| } |
| } |
| } |
| |
| if (modified) |
| { |
| /* When changing a call into a noreturn call, cfg cleanup |
| is needed to fix up the noreturn call. */ |
| if (!was_noreturn |
| && is_gimple_call (stmt) && gimple_call_noreturn_p (stmt)) |
| to_fixup.safe_push (stmt); |
| /* When changing a condition or switch into one we know what |
| edge will be executed, schedule a cfg cleanup. */ |
| if ((gimple_code (stmt) == GIMPLE_COND |
| && (gimple_cond_true_p (as_a <gcond *> (stmt)) |
| || gimple_cond_false_p (as_a <gcond *> (stmt)))) |
| || (gimple_code (stmt) == GIMPLE_SWITCH |
| && TREE_CODE (gimple_switch_index |
| (as_a <gswitch *> (stmt))) == INTEGER_CST)) |
| el_todo |= TODO_cleanup_cfg; |
| /* If we removed EH side-effects from the statement, clean |
| its EH information. */ |
| if (maybe_clean_or_replace_eh_stmt (old_stmt, stmt)) |
| { |
| bitmap_set_bit (need_eh_cleanup, |
| gimple_bb (stmt)->index); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Removed EH side-effects.\n"); |
| } |
| /* Likewise for AB side-effects. */ |
| if (can_make_abnormal_goto |
| && !stmt_can_make_abnormal_goto (stmt)) |
| { |
| bitmap_set_bit (need_ab_cleanup, |
| gimple_bb (stmt)->index); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, " Removed AB side-effects.\n"); |
| } |
| update_stmt (stmt); |
| /* In case the VDEF on the original stmt was released, value-number |
| it to the VUSE. This is to make vuse_ssa_val able to skip |
| released virtual operands. */ |
| if (vdef && SSA_NAME_IN_FREE_LIST (vdef)) |
| VN_INFO (vdef)->valnum = vuse; |
| } |
| |
| /* Make new values available - for fully redundant LHS we |
| continue with the next stmt above and skip this. */ |
| def_operand_p defp; |
| FOR_EACH_SSA_DEF_OPERAND (defp, stmt, iter, SSA_OP_DEF) |
| eliminate_push_avail (b, DEF_FROM_PTR (defp)); |
| } |
| |
| /* Perform elimination for the basic-block B during the domwalk. */ |
| |
| edge |
| eliminate_dom_walker::before_dom_children (basic_block b) |
| { |
| /* Mark new bb. */ |
| avail_stack.safe_push (NULL_TREE); |
| |
| /* Skip unreachable blocks marked unreachable during the SCCVN domwalk. */ |
| if (!(b->flags & BB_EXECUTABLE)) |
| return NULL; |
| |
| vn_context_bb = b; |
| |
| for (gphi_iterator gsi = gsi_start_phis (b); !gsi_end_p (gsi);) |
| { |
| gphi *phi = gsi.phi (); |
| tree res = PHI_RESULT (phi); |
| |
| if (virtual_operand_p (res)) |
| { |
| gsi_next (&gsi); |
| continue; |
| } |
| |
| tree sprime = eliminate_avail (b, res); |
| if (sprime |
| && sprime != res) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Replaced redundant PHI node defining "); |
| print_generic_expr (dump_file, res); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, sprime); |
| fprintf (dump_file, "\n"); |
| } |
| |
| /* If we inserted this PHI node ourself, it's not an elimination. */ |
| if (! inserted_exprs |
| || ! bitmap_bit_p (inserted_exprs, SSA_NAME_VERSION (res))) |
| eliminations++; |
| |
| /* If we will propagate into all uses don't bother to do |
| anything. */ |
| if (may_propagate_copy (res, sprime)) |
| { |
| /* Mark the PHI for removal. */ |
| to_remove.safe_push (phi); |
| gsi_next (&gsi); |
| continue; |
| } |
| |
| remove_phi_node (&gsi, false); |
| |
| if (!useless_type_conversion_p (TREE_TYPE (res), TREE_TYPE (sprime))) |
| sprime = fold_convert (TREE_TYPE (res), sprime); |
| gimple *stmt = gimple_build_assign (res, sprime); |
| gimple_stmt_iterator gsi2 = gsi_after_labels (b); |
| gsi_insert_before (&gsi2, stmt, GSI_NEW_STMT); |
| continue; |
| } |
| |
| eliminate_push_avail (b, res); |
| gsi_next (&gsi); |
| } |
| |
| for (gimple_stmt_iterator gsi = gsi_start_bb (b); |
| !gsi_end_p (gsi); |
| gsi_next (&gsi)) |
| eliminate_stmt (b, &gsi); |
| |
| /* Replace destination PHI arguments. */ |
| edge_iterator ei; |
| edge e; |
| FOR_EACH_EDGE (e, ei, b->succs) |
| if (e->flags & EDGE_EXECUTABLE) |
| for (gphi_iterator gsi = gsi_start_phis (e->dest); |
| !gsi_end_p (gsi); |
| gsi_next (&gsi)) |
| { |
| gphi *phi = gsi.phi (); |
| use_operand_p use_p = PHI_ARG_DEF_PTR_FROM_EDGE (phi, e); |
| tree arg = USE_FROM_PTR (use_p); |
| if (TREE_CODE (arg) != SSA_NAME |
| || virtual_operand_p (arg)) |
| continue; |
| tree sprime = eliminate_avail (b, arg); |
| if (sprime && may_propagate_copy (arg, sprime)) |
| propagate_value (use_p, sprime); |
| } |
| |
| vn_context_bb = NULL; |
| |
| return NULL; |
| } |
| |
| /* Make no longer available leaders no longer available. */ |
| |
| void |
| eliminate_dom_walker::after_dom_children (basic_block) |
| { |
| tree entry; |
| while ((entry = avail_stack.pop ()) != NULL_TREE) |
| { |
| tree valnum = VN_INFO (entry)->valnum; |
| tree old = avail[SSA_NAME_VERSION (valnum)]; |
| if (old == entry) |
| avail[SSA_NAME_VERSION (valnum)] = NULL_TREE; |
| else |
| avail[SSA_NAME_VERSION (valnum)] = entry; |
| } |
| } |
| |
| /* Remove queued stmts and perform delayed cleanups. */ |
| |
| unsigned |
| eliminate_dom_walker::eliminate_cleanup (bool region_p) |
| { |
| statistics_counter_event (cfun, "Eliminated", eliminations); |
| statistics_counter_event (cfun, "Insertions", insertions); |
| |
| /* We cannot remove stmts during BB walk, especially not release SSA |
| names there as this confuses the VN machinery. The stmts ending |
| up in to_remove are either stores or simple copies. |
| Remove stmts in reverse order to make debug stmt creation possible. */ |
| while (!to_remove.is_empty ()) |
| { |
| bool do_release_defs = true; |
| gimple *stmt = to_remove.pop (); |
| |
| /* When we are value-numbering a region we do not require exit PHIs to |
| be present so we have to make sure to deal with uses outside of the |
| region of stmts that we thought are eliminated. |
| ??? Note we may be confused by uses in dead regions we didn't run |
| elimination on. Rather than checking individual uses we accept |
| dead copies to be generated here (gcc.c-torture/execute/20060905-1.c |
| contains such example). */ |
| if (region_p) |
| { |
| if (gphi *phi = dyn_cast <gphi *> (stmt)) |
| { |
| tree lhs = gimple_phi_result (phi); |
| if (!has_zero_uses (lhs)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Keeping eliminated stmt live " |
| "as copy because of out-of-region uses\n"); |
| tree sprime = eliminate_avail (gimple_bb (stmt), lhs); |
| gimple *copy = gimple_build_assign (lhs, sprime); |
| gimple_stmt_iterator gsi |
| = gsi_after_labels (gimple_bb (stmt)); |
| gsi_insert_before (&gsi, copy, GSI_SAME_STMT); |
| do_release_defs = false; |
| } |
| } |
| else if (tree lhs = gimple_get_lhs (stmt)) |
| if (TREE_CODE (lhs) == SSA_NAME |
| && !has_zero_uses (lhs)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Keeping eliminated stmt live " |
| "as copy because of out-of-region uses\n"); |
| tree sprime = eliminate_avail (gimple_bb (stmt), lhs); |
| gimple_stmt_iterator gsi = gsi_for_stmt (stmt); |
| if (is_gimple_assign (stmt)) |
| { |
| gimple_assign_set_rhs_from_tree (&gsi, sprime); |
| stmt = gsi_stmt (gsi); |
| update_stmt (stmt); |
| if (maybe_clean_or_replace_eh_stmt (stmt, stmt)) |
| bitmap_set_bit (need_eh_cleanup, gimple_bb (stmt)->index); |
| continue; |
| } |
| else |
| { |
| gimple *copy = gimple_build_assign (lhs, sprime); |
| gsi_insert_before (&gsi, copy, GSI_SAME_STMT); |
| do_release_defs = false; |
| } |
| } |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Removing dead stmt "); |
| print_gimple_stmt (dump_file, stmt, 0, TDF_NONE); |
| } |
| |
| gimple_stmt_iterator gsi = gsi_for_stmt (stmt); |
| if (gimple_code (stmt) == GIMPLE_PHI) |
| remove_phi_node (&gsi, do_release_defs); |
| else |
| { |
| basic_block bb = gimple_bb (stmt); |
| unlink_stmt_vdef (stmt); |
| if (gsi_remove (&gsi, true)) |
| bitmap_set_bit (need_eh_cleanup, bb->index); |
| if (is_gimple_call (stmt) && stmt_can_make_abnormal_goto (stmt)) |
| bitmap_set_bit (need_ab_cleanup, bb->index); |
| if (do_release_defs) |
| release_defs (stmt); |
| } |
| |
| /* Removing a stmt may expose a forwarder block. */ |
| el_todo |= TODO_cleanup_cfg; |
| } |
| |
| /* Fixup stmts that became noreturn calls. This may require splitting |
| blocks and thus isn't possible during the dominator walk. Do this |
| in reverse order so we don't inadvertedly remove a stmt we want to |
| fixup by visiting a dominating now noreturn call first. */ |
| while (!to_fixup.is_empty ()) |
| { |
| gimple *stmt = to_fixup.pop (); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Fixing up noreturn call "); |
| print_gimple_stmt (dump_file, stmt, 0); |
| } |
| |
| if (fixup_noreturn_call (stmt)) |
| el_todo |= TODO_cleanup_cfg; |
| } |
| |
| bool do_eh_cleanup = !bitmap_empty_p (need_eh_cleanup); |
| bool do_ab_cleanup = !bitmap_empty_p (need_ab_cleanup); |
| |
| if (do_eh_cleanup) |
| gimple_purge_all_dead_eh_edges (need_eh_cleanup); |
| |
| if (do_ab_cleanup) |
| gimple_purge_all_dead_abnormal_call_edges (need_ab_cleanup); |
| |
| if (do_eh_cleanup || do_ab_cleanup) |
| el_todo |= TODO_cleanup_cfg; |
| |
| return el_todo; |
| } |
| |
| /* Eliminate fully redundant computations. */ |
| |
| unsigned |
| eliminate_with_rpo_vn (bitmap inserted_exprs) |
| { |
| eliminate_dom_walker walker (CDI_DOMINATORS, inserted_exprs); |
| |
| eliminate_dom_walker *saved_rpo_avail = rpo_avail; |
| rpo_avail = &walker; |
| walker.walk (cfun->cfg->x_entry_block_ptr); |
| rpo_avail = saved_rpo_avail; |
| |
| return walker.eliminate_cleanup (); |
| } |
| |
| unsigned |
| do_rpo_vn (function *fn, edge entry, bitmap exit_bbs, |
| bool iterate, bool eliminate, vn_lookup_kind kind); |
| |
| void |
| run_rpo_vn (vn_lookup_kind kind) |
| { |
| do_rpo_vn (cfun, NULL, NULL, true, false, kind); |
| |
| /* ??? Prune requirement of these. */ |
| constant_to_value_id = new hash_table<vn_constant_hasher> (23); |
| |
| /* Initialize the value ids and prune out remaining VN_TOPs |
| from dead code. */ |
| tree name; |
| unsigned i; |
| FOR_EACH_SSA_NAME (i, name, cfun) |
| { |
| vn_ssa_aux_t info = VN_INFO (name); |
| if (!info->visited |
| || info->valnum == VN_TOP) |
| info->valnum = name; |
| if (info->valnum == name) |
| info->value_id = get_next_value_id (); |
| else if (is_gimple_min_invariant (info->valnum)) |
| info->value_id = get_or_alloc_constant_value_id (info->valnum); |
| } |
| |
| /* Propagate. */ |
| FOR_EACH_SSA_NAME (i, name, cfun) |
| { |
| vn_ssa_aux_t info = VN_INFO (name); |
| if (TREE_CODE (info->valnum) == SSA_NAME |
| && info->valnum != name |
| && info->value_id != VN_INFO (info->valnum)->value_id) |
| info->value_id = VN_INFO (info->valnum)->value_id; |
| } |
| |
| set_hashtable_value_ids (); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Value numbers:\n"); |
| FOR_EACH_SSA_NAME (i, name, cfun) |
| { |
| if (VN_INFO (name)->visited |
| && SSA_VAL (name) != name) |
| { |
| print_generic_expr (dump_file, name); |
| fprintf (dump_file, " = "); |
| print_generic_expr (dump_file, SSA_VAL (name)); |
| fprintf (dump_file, " (%04d)\n", VN_INFO (name)->value_id); |
| } |
| } |
| } |
| } |
| |
| /* Free VN associated data structures. */ |
| |
| void |
| free_rpo_vn (void) |
| { |
| free_vn_table (valid_info); |
| XDELETE (valid_info); |
| obstack_free (&vn_tables_obstack, NULL); |
| obstack_free (&vn_tables_insert_obstack, NULL); |
| |
| vn_ssa_aux_iterator_type it; |
| vn_ssa_aux_t info; |
| FOR_EACH_HASH_TABLE_ELEMENT (*vn_ssa_aux_hash, info, vn_ssa_aux_t, it) |
| if (info->needs_insertion) |
| release_ssa_name (info->name); |
| obstack_free (&vn_ssa_aux_obstack, NULL); |
| delete vn_ssa_aux_hash; |
| |
| delete constant_to_value_id; |
| constant_to_value_id = NULL; |
| } |
| |
| /* Hook for maybe_push_res_to_seq, lookup the expression in the VN tables. */ |
| |
| static tree |
| vn_lookup_simplify_result (gimple_match_op *res_op) |
| { |
| if (!res_op->code.is_tree_code ()) |
| return NULL_TREE; |
| tree *ops = res_op->ops; |
| unsigned int length = res_op->num_ops; |
| if (res_op->code == CONSTRUCTOR |
| /* ??? We're arriving here with SCCVNs view, decomposed CONSTRUCTOR |
| and GIMPLEs / match-and-simplifies, CONSTRUCTOR as GENERIC tree. */ |
| && TREE_CODE (res_op->ops[0]) == CONSTRUCTOR) |
| { |
| length = CONSTRUCTOR_NELTS (res_op->ops[0]); |
| ops = XALLOCAVEC (tree, length); |
| for (unsigned i = 0; i < length; ++i) |
| ops[i] = CONSTRUCTOR_ELT (res_op->ops[0], i)->value; |
| } |
| vn_nary_op_t vnresult = NULL; |
| tree res = vn_nary_op_lookup_pieces (length, (tree_code) res_op->code, |
| res_op->type, ops, &vnresult); |
| /* If this is used from expression simplification make sure to |
| return an available expression. */ |
| if (res && TREE_CODE (res) == SSA_NAME && mprts_hook && rpo_avail) |
| res = rpo_avail->eliminate_avail (vn_context_bb, res); |
| return res; |
| } |
| |
| /* Return a leader for OPs value that is valid at BB. */ |
| |
| tree |
| rpo_elim::eliminate_avail (basic_block bb, tree op) |
| { |
| bool visited; |
| tree valnum = SSA_VAL (op, &visited); |
| /* If we didn't visit OP then it must be defined outside of the |
| region we process and also dominate it. So it is available. */ |
| if (!visited) |
| return op; |
| if (TREE_CODE (valnum) == SSA_NAME) |
| { |
| if (SSA_NAME_IS_DEFAULT_DEF (valnum)) |
| return valnum; |
| vn_avail *av = VN_INFO (valnum)->avail; |
| if (!av) |
| return NULL_TREE; |
| if (av->location == bb->index) |
| /* On tramp3d 90% of the cases are here. */ |
| return ssa_name (av->leader); |
| do |
| { |
| basic_block abb = BASIC_BLOCK_FOR_FN (cfun, av->location); |
| /* ??? During elimination we have to use availability at the |
| definition site of a use we try to replace. This |
| is required to not run into inconsistencies because |
| of dominated_by_p_w_unex behavior and removing a definition |
| while not replacing all uses. |
| ??? We could try to consistently walk dominators |
| ignoring non-executable regions. The nearest common |
| dominator of bb and abb is where we can stop walking. We |
| may also be able to "pre-compute" (bits of) the next immediate |
| (non-)dominator during the RPO walk when marking edges as |
| executable. */ |
| if (dominated_by_p_w_unex (bb, abb, true)) |
| { |
| tree leader = ssa_name (av->leader); |
| /* Prevent eliminations that break loop-closed SSA. */ |
| if (loops_state_satisfies_p (LOOP_CLOSED_SSA) |
| && ! SSA_NAME_IS_DEFAULT_DEF (leader) |
| && ! flow_bb_inside_loop_p (gimple_bb (SSA_NAME_DEF_STMT |
| (leader))->loop_father, |
| bb)) |
| return NULL_TREE; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| print_generic_expr (dump_file, leader); |
| fprintf (dump_file, " is available for "); |
| print_generic_expr (dump_file, valnum); |
| fprintf (dump_file, "\n"); |
| } |
| /* On tramp3d 99% of the _remaining_ cases succeed at |
| the first enty. */ |
| return leader; |
| } |
| /* ??? Can we somehow skip to the immediate dominator |
| RPO index (bb_to_rpo)? Again, maybe not worth, on |
| tramp3d the worst number of elements in the vector is 9. */ |
| av = av->next; |
| } |
| while (av); |
| } |
| else if (valnum != VN_TOP) |
| /* valnum is is_gimple_min_invariant. */ |
| return valnum; |
| return NULL_TREE; |
| } |
| |
| /* Make LEADER a leader for its value at BB. */ |
| |
| void |
| rpo_elim::eliminate_push_avail (basic_block bb, tree leader) |
| { |
| tree valnum = VN_INFO (leader)->valnum; |
| if (valnum == VN_TOP |
| || is_gimple_min_invariant (valnum)) |
| return; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Making available beyond BB%d ", bb->index); |
| print_generic_expr (dump_file, leader); |
| fprintf (dump_file, " for value "); |
| print_generic_expr (dump_file, valnum); |
| fprintf (dump_file, "\n"); |
| } |
| vn_ssa_aux_t value = VN_INFO (valnum); |
| vn_avail *av; |
| if (m_avail_freelist) |
| { |
| av = m_avail_freelist; |
| m_avail_freelist = m_avail_freelist->next; |
| } |
| else |
| av = XOBNEW (&vn_ssa_aux_obstack, vn_avail); |
| av->location = bb->index; |
| av->leader = SSA_NAME_VERSION (leader); |
| av->next = value->avail; |
| av->next_undo = last_pushed_avail; |
| last_pushed_avail = value; |
| value->avail = av; |
| } |
| |
| /* Valueization hook for RPO VN plus required state. */ |
| |
| tree |
| rpo_vn_valueize (tree name) |
| { |
| if (TREE_CODE (name) == SSA_NAME) |
| { |
| vn_ssa_aux_t val = VN_INFO (name); |
| if (val) |
| { |
| tree tem = val->valnum; |
| if (tem != VN_TOP && tem != name) |
| { |
| if (TREE_CODE (tem) != SSA_NAME) |
| return tem; |
| /* For all values we only valueize to an available leader |
| which means we can use SSA name info without restriction. */ |
| tem = rpo_avail->eliminate_avail (vn_context_bb, tem); |
| if (tem) |
| return tem; |
| } |
| } |
| } |
| return name; |
| } |
| |
| /* Insert on PRED_E predicates derived from CODE OPS being true besides the |
| inverted condition. */ |
| |
| static void |
| insert_related_predicates_on_edge (enum tree_code code, tree *ops, edge pred_e) |
| { |
| switch (code) |
| { |
| case LT_EXPR: |
| /* a < b -> a {!,<}= b */ |
| vn_nary_op_insert_pieces_predicated (2, NE_EXPR, boolean_type_node, |
| ops, boolean_true_node, 0, pred_e); |
| vn_nary_op_insert_pieces_predicated (2, LE_EXPR, boolean_type_node, |
| ops, boolean_true_node, 0, pred_e); |
| /* a < b -> ! a {>,=} b */ |
| vn_nary_op_insert_pieces_predicated (2, GT_EXPR, boolean_type_node, |
| ops, boolean_false_node, 0, pred_e); |
| vn_nary_op_insert_pieces_predicated (2, EQ_EXPR, boolean_type_node, |
| ops, boolean_false_node, 0, pred_e); |
| break; |
| case GT_EXPR: |
| /* a > b -> a {!,>}= b */ |
| vn_nary_op_insert_pieces_predicated (2, NE_EXPR, boolean_type_node, |
| ops, boolean_true_node, 0, pred_e); |
| vn_nary_op_insert_pieces_predicated (2, GE_EXPR, boolean_type_node, |
| ops, boolean_true_node, 0, pred_e); |
| /* a > b -> ! a {<,=} b */ |
| vn_nary_op_insert_pieces_predicated (2, LT_EXPR, boolean_type_node, |
| ops, boolean_false_node, 0, pred_e); |
| vn_nary_op_insert_pieces_predicated (2, EQ_EXPR, boolean_type_node, |
| ops, boolean_false_node, 0, pred_e); |
| break; |
| case EQ_EXPR: |
| /* a == b -> ! a {<,>} b */ |
| vn_nary_op_insert_pieces_predicated (2, LT_EXPR, boolean_type_node, |
| ops, boolean_false_node, 0, pred_e); |
| vn_nary_op_insert_pieces_predicated (2, GT_EXPR, boolean_type_node, |
| ops, boolean_false_node, 0, pred_e); |
| break; |
| case LE_EXPR: |
| case GE_EXPR: |
| case NE_EXPR: |
| /* Nothing besides inverted condition. */ |
| break; |
| default:; |
| } |
| } |
| |
| /* Main stmt worker for RPO VN, process BB. */ |
| |
| static unsigned |
| process_bb (rpo_elim &avail, basic_block bb, |
| bool bb_visited, bool iterate_phis, bool iterate, bool eliminate, |
| bool do_region, bitmap exit_bbs, bool skip_phis) |
| { |
| unsigned todo = 0; |
| edge_iterator ei; |
| edge e; |
| |
| vn_context_bb = bb; |
| |
| /* If we are in loop-closed SSA preserve this state. This is |
| relevant when called on regions from outside of FRE/PRE. */ |
| bool lc_phi_nodes = false; |
| if (!skip_phis |
| && loops_state_satisfies_p (LOOP_CLOSED_SSA)) |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| if (e->src->loop_father != e->dest->loop_father |
| && flow_loop_nested_p (e->dest->loop_father, |
| e->src->loop_father)) |
| { |
| lc_phi_nodes = true; |
| break; |
| } |
| |
| /* When we visit a loop header substitute into loop info. */ |
| if (!iterate && eliminate && bb->loop_father->header == bb) |
| { |
| /* Keep fields in sync with substitute_in_loop_info. */ |
| if (bb->loop_father->nb_iterations) |
| bb->loop_father->nb_iterations |
| = simplify_replace_tree (bb->loop_father->nb_iterations, |
| NULL_TREE, NULL_TREE, &vn_valueize_for_srt); |
| } |
| |
| /* Value-number all defs in the basic-block. */ |
| if (!skip_phis) |
| for (gphi_iterator gsi = gsi_start_phis (bb); !gsi_end_p (gsi); |
| gsi_next (&gsi)) |
| { |
| gphi *phi = gsi.phi (); |
| tree res = PHI_RESULT (phi); |
| vn_ssa_aux_t res_info = VN_INFO (res); |
| if (!bb_visited) |
| { |
| gcc_assert (!res_info->visited); |
| res_info->valnum = VN_TOP; |
| res_info->visited = true; |
| } |
| |
| /* When not iterating force backedge values to varying. */ |
| visit_stmt (phi, !iterate_phis); |
| if (virtual_operand_p (res)) |
| continue; |
| |
| /* Eliminate */ |
| /* The interesting case is gcc.dg/tree-ssa/pr22230.c for correctness |
| how we handle backedges and availability. |
| And gcc.dg/tree-ssa/ssa-sccvn-2.c for optimization. */ |
| tree val = res_info->valnum; |
| if (res != val && !iterate && eliminate) |
| { |
| if (tree leader = avail.eliminate_avail (bb, res)) |
| { |
| if (leader != res |
| /* Preserve loop-closed SSA form. */ |
| && (! lc_phi_nodes |
| || is_gimple_min_invariant (leader))) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Replaced redundant PHI node " |
| "defining "); |
| print_generic_expr (dump_file, res); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, leader); |
| fprintf (dump_file, "\n"); |
| } |
| avail.eliminations++; |
| |
| if (may_propagate_copy (res, leader)) |
| { |
| /* Schedule for removal. */ |
| avail.to_remove.safe_push (phi); |
| continue; |
| } |
| /* ??? Else generate a copy stmt. */ |
| } |
| } |
| } |
| /* Only make defs available that not already are. But make |
| sure loop-closed SSA PHI node defs are picked up for |
| downstream uses. */ |
| if (lc_phi_nodes |
| || res == val |
| || ! avail.eliminate_avail (bb, res)) |
| avail.eliminate_push_avail (bb, res); |
| } |
| |
| /* For empty BBs mark outgoing edges executable. For non-empty BBs |
| we do this when processing the last stmt as we have to do this |
| before elimination which otherwise forces GIMPLE_CONDs to |
| if (1 != 0) style when seeing non-executable edges. */ |
| if (gsi_end_p (gsi_start_bb (bb))) |
| { |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| if (!(e->flags & EDGE_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "marking outgoing edge %d -> %d executable\n", |
| e->src->index, e->dest->index); |
| e->flags |= EDGE_EXECUTABLE; |
| e->dest->flags |= BB_EXECUTABLE; |
| } |
| else if (!(e->dest->flags & BB_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "marking destination block %d reachable\n", |
| e->dest->index); |
| e->dest->flags |= BB_EXECUTABLE; |
| } |
| } |
| } |
| for (gimple_stmt_iterator gsi = gsi_start_bb (bb); |
| !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| ssa_op_iter i; |
| tree op; |
| if (!bb_visited) |
| { |
| FOR_EACH_SSA_TREE_OPERAND (op, gsi_stmt (gsi), i, SSA_OP_ALL_DEFS) |
| { |
| vn_ssa_aux_t op_info = VN_INFO (op); |
| gcc_assert (!op_info->visited); |
| op_info->valnum = VN_TOP; |
| op_info->visited = true; |
| } |
| |
| /* We somehow have to deal with uses that are not defined |
| in the processed region. Forcing unvisited uses to |
| varying here doesn't play well with def-use following during |
| expression simplification, so we deal with this by checking |
| the visited flag in SSA_VAL. */ |
| } |
| |
| visit_stmt (gsi_stmt (gsi)); |
| |
| gimple *last = gsi_stmt (gsi); |
| e = NULL; |
| switch (gimple_code (last)) |
| { |
| case GIMPLE_SWITCH: |
| e = find_taken_edge (bb, vn_valueize (gimple_switch_index |
| (as_a <gswitch *> (last)))); |
| break; |
| case GIMPLE_COND: |
| { |
| tree lhs = vn_valueize (gimple_cond_lhs (last)); |
| tree rhs = vn_valueize (gimple_cond_rhs (last)); |
| tree val = gimple_simplify (gimple_cond_code (last), |
| boolean_type_node, lhs, rhs, |
| NULL, vn_valueize); |
| /* If the condition didn't simplfy see if we have recorded |
| an expression from sofar taken edges. */ |
| if (! val || TREE_CODE (val) != INTEGER_CST) |
| { |
| vn_nary_op_t vnresult; |
| tree ops[2]; |
| ops[0] = lhs; |
| ops[1] = rhs; |
| val = vn_nary_op_lookup_pieces (2, gimple_cond_code (last), |
| boolean_type_node, ops, |
| &vnresult); |
| /* Did we get a predicated value? */ |
| if (! val && vnresult && vnresult->predicated_values) |
| { |
| val = vn_nary_op_get_predicated_value (vnresult, bb); |
| if (val && dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Got predicated value "); |
| print_generic_expr (dump_file, val, TDF_NONE); |
| fprintf (dump_file, " for "); |
| print_gimple_stmt (dump_file, last, TDF_SLIM); |
| } |
| } |
| } |
| if (val) |
| e = find_taken_edge (bb, val); |
| if (! e) |
| { |
| /* If we didn't manage to compute the taken edge then |
| push predicated expressions for the condition itself |
| and related conditions to the hashtables. This allows |
| simplification of redundant conditions which is |
| important as early cleanup. */ |
| edge true_e, false_e; |
| extract_true_false_edges_from_block (bb, &true_e, &false_e); |
| enum tree_code code = gimple_cond_code (last); |
| enum tree_code icode |
| = invert_tree_comparison (code, HONOR_NANS (lhs)); |
| tree ops[2]; |
| ops[0] = lhs; |
| ops[1] = rhs; |
| if (do_region |
| && bitmap_bit_p (exit_bbs, true_e->dest->index)) |
| true_e = NULL; |
| if (do_region |
| && bitmap_bit_p (exit_bbs, false_e->dest->index)) |
| false_e = NULL; |
| if (true_e) |
| vn_nary_op_insert_pieces_predicated |
| (2, code, boolean_type_node, ops, |
| boolean_true_node, 0, true_e); |
| if (false_e) |
| vn_nary_op_insert_pieces_predicated |
| (2, code, boolean_type_node, ops, |
| boolean_false_node, 0, false_e); |
| if (icode != ERROR_MARK) |
| { |
| if (true_e) |
| vn_nary_op_insert_pieces_predicated |
| (2, icode, boolean_type_node, ops, |
| boolean_false_node, 0, true_e); |
| if (false_e) |
| vn_nary_op_insert_pieces_predicated |
| (2, icode, boolean_type_node, ops, |
| boolean_true_node, 0, false_e); |
| } |
| /* Relax for non-integers, inverted condition handled |
| above. */ |
| if (INTEGRAL_TYPE_P (TREE_TYPE (lhs))) |
| { |
| if (true_e) |
| insert_related_predicates_on_edge (code, ops, true_e); |
| if (false_e) |
| insert_related_predicates_on_edge (icode, ops, false_e); |
| } |
| } |
| break; |
| } |
| case GIMPLE_GOTO: |
| e = find_taken_edge (bb, vn_valueize (gimple_goto_dest (last))); |
| break; |
| default: |
| e = NULL; |
| } |
| if (e) |
| { |
| todo = TODO_cleanup_cfg; |
| if (!(e->flags & EDGE_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "marking known outgoing %sedge %d -> %d executable\n", |
| e->flags & EDGE_DFS_BACK ? "back-" : "", |
| e->src->index, e->dest->index); |
| e->flags |= EDGE_EXECUTABLE; |
| e->dest->flags |= BB_EXECUTABLE; |
| } |
| else if (!(e->dest->flags & BB_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "marking destination block %d reachable\n", |
| e->dest->index); |
| e->dest->flags |= BB_EXECUTABLE; |
| } |
| } |
| else if (gsi_one_before_end_p (gsi)) |
| { |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| if (!(e->flags & EDGE_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "marking outgoing edge %d -> %d executable\n", |
| e->src->index, e->dest->index); |
| e->flags |= EDGE_EXECUTABLE; |
| e->dest->flags |= BB_EXECUTABLE; |
| } |
| else if (!(e->dest->flags & BB_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, |
| "marking destination block %d reachable\n", |
| e->dest->index); |
| e->dest->flags |= BB_EXECUTABLE; |
| } |
| } |
| } |
| |
| /* Eliminate. That also pushes to avail. */ |
| if (eliminate && ! iterate) |
| avail.eliminate_stmt (bb, &gsi); |
| else |
| /* If not eliminating, make all not already available defs |
| available. */ |
| FOR_EACH_SSA_TREE_OPERAND (op, gsi_stmt (gsi), i, SSA_OP_DEF) |
| if (! avail.eliminate_avail (bb, op)) |
| avail.eliminate_push_avail (bb, op); |
| } |
| |
| /* Eliminate in destination PHI arguments. Always substitute in dest |
| PHIs, even for non-executable edges. This handles region |
| exits PHIs. */ |
| if (!iterate && eliminate) |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| for (gphi_iterator gsi = gsi_start_phis (e->dest); |
| !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| gphi *phi = gsi.phi (); |
| use_operand_p use_p = PHI_ARG_DEF_PTR_FROM_EDGE (phi, e); |
| tree arg = USE_FROM_PTR (use_p); |
| if (TREE_CODE (arg) != SSA_NAME |
| || virtual_operand_p (arg)) |
| continue; |
| tree sprime; |
| if (SSA_NAME_IS_DEFAULT_DEF (arg)) |
| { |
| sprime = SSA_VAL (arg); |
| gcc_assert (TREE_CODE (sprime) != SSA_NAME |
| || SSA_NAME_IS_DEFAULT_DEF (sprime)); |
| } |
| else |
| /* Look for sth available at the definition block of the argument. |
| This avoids inconsistencies between availability there which |
| decides if the stmt can be removed and availability at the |
| use site. The SSA property ensures that things available |
| at the definition are also available at uses. */ |
| sprime = avail.eliminate_avail (gimple_bb (SSA_NAME_DEF_STMT (arg)), |
| arg); |
| if (sprime |
| && sprime != arg |
| && may_propagate_copy (arg, sprime)) |
| propagate_value (use_p, sprime); |
| } |
| |
| vn_context_bb = NULL; |
| return todo; |
| } |
| |
| /* Unwind state per basic-block. */ |
| |
| struct unwind_state |
| { |
| /* Times this block has been visited. */ |
| unsigned visited; |
| /* Whether to handle this as iteration point or whether to treat |
| incoming backedge PHI values as varying. */ |
| bool iterate; |
| /* Maximum RPO index this block is reachable from. */ |
| int max_rpo; |
| /* Unwind state. */ |
| void *ob_top; |
| vn_reference_t ref_top; |
| vn_phi_t phi_top; |
| vn_nary_op_t nary_top; |
| vn_avail *avail_top; |
| }; |
| |
| /* Unwind the RPO VN state for iteration. */ |
| |
| static void |
| do_unwind (unwind_state *to, rpo_elim &avail) |
| { |
| gcc_assert (to->iterate); |
| for (; last_inserted_nary != to->nary_top; |
| last_inserted_nary = last_inserted_nary->next) |
| { |
| vn_nary_op_t *slot; |
| slot = valid_info->nary->find_slot_with_hash |
| (last_inserted_nary, last_inserted_nary->hashcode, NO_INSERT); |
| /* Predication causes the need to restore previous state. */ |
| if ((*slot)->unwind_to) |
| *slot = (*slot)->unwind_to; |
| else |
| valid_info->nary->clear_slot (slot); |
| } |
| for (; last_inserted_phi != to->phi_top; |
| last_inserted_phi = last_inserted_phi->next) |
| { |
| vn_phi_t *slot; |
| slot = valid_info->phis->find_slot_with_hash |
| (last_inserted_phi, last_inserted_phi->hashcode, NO_INSERT); |
| valid_info->phis->clear_slot (slot); |
| } |
| for (; last_inserted_ref != to->ref_top; |
| last_inserted_ref = last_inserted_ref->next) |
| { |
| vn_reference_t *slot; |
| slot = valid_info->references->find_slot_with_hash |
| (last_inserted_ref, last_inserted_ref->hashcode, NO_INSERT); |
| (*slot)->operands.release (); |
| valid_info->references->clear_slot (slot); |
| } |
| obstack_free (&vn_tables_obstack, to->ob_top); |
| |
| /* Prune [rpo_idx, ] from avail. */ |
| for (; last_pushed_avail && last_pushed_avail->avail != to->avail_top;) |
| { |
| vn_ssa_aux_t val = last_pushed_avail; |
| vn_avail *av = val->avail; |
| val->avail = av->next; |
| last_pushed_avail = av->next_undo; |
| av->next = avail.m_avail_freelist; |
| avail.m_avail_freelist = av; |
| } |
| } |
| |
| /* Do VN on a SEME region specified by ENTRY and EXIT_BBS in FN. |
| If ITERATE is true then treat backedges optimistically as not |
| executed and iterate. If ELIMINATE is true then perform |
| elimination, otherwise leave that to the caller. */ |
| |
| unsigned |
| do_rpo_vn (function *fn, edge entry, bitmap exit_bbs, |
| bool iterate, bool eliminate, vn_lookup_kind kind) |
| { |
| unsigned todo = 0; |
| default_vn_walk_kind = kind; |
| |
| /* We currently do not support region-based iteration when |
| elimination is requested. */ |
| gcc_assert (!entry || !iterate || !eliminate); |
| /* When iterating we need loop info up-to-date. */ |
| gcc_assert (!iterate || !loops_state_satisfies_p (LOOPS_NEED_FIXUP)); |
| |
| bool do_region = entry != NULL; |
| if (!do_region) |
| { |
| entry = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (fn)); |
| exit_bbs = BITMAP_ALLOC (NULL); |
| bitmap_set_bit (exit_bbs, EXIT_BLOCK); |
| } |
| |
| /* Clear EDGE_DFS_BACK on "all" entry edges, RPO order compute will |
| re-mark those that are contained in the region. */ |
| edge_iterator ei; |
| edge e; |
| FOR_EACH_EDGE (e, ei, entry->dest->preds) |
| e->flags &= ~EDGE_DFS_BACK; |
| |
| int *rpo = XNEWVEC (int, n_basic_blocks_for_fn (fn) - NUM_FIXED_BLOCKS); |
| auto_vec<std::pair<int, int> > toplevel_scc_extents; |
| int n = rev_post_order_and_mark_dfs_back_seme |
| (fn, entry, exit_bbs, true, rpo, !iterate ? &toplevel_scc_extents : NULL); |
| |
| if (!do_region) |
| BITMAP_FREE (exit_bbs); |
| |
| /* If there are any non-DFS_BACK edges into entry->dest skip |
| processing PHI nodes for that block. This supports |
| value-numbering loop bodies w/o the actual loop. */ |
| FOR_EACH_EDGE (e, ei, entry->dest->preds) |
| if (e != entry |
| && !(e->flags & EDGE_DFS_BACK)) |
| break; |
| bool skip_entry_phis = e != NULL; |
| if (skip_entry_phis && dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Region does not contain all edges into " |
| "the entry block, skipping its PHIs.\n"); |
| |
| int *bb_to_rpo = XNEWVEC (int, last_basic_block_for_fn (fn)); |
| for (int i = 0; i < n; ++i) |
| bb_to_rpo[rpo[i]] = i; |
| |
| unwind_state *rpo_state = XNEWVEC (unwind_state, n); |
| |
| rpo_elim avail (entry->dest); |
| rpo_avail = &avail; |
| |
| /* Verify we have no extra entries into the region. */ |
| if (flag_checking && do_region) |
| { |
| auto_bb_flag bb_in_region (fn); |
| for (int i = 0; i < n; ++i) |
| { |
| basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[i]); |
| bb->flags |= bb_in_region; |
| } |
| /* We can't merge the first two loops because we cannot rely |
| on EDGE_DFS_BACK for edges not within the region. But if |
| we decide to always have the bb_in_region flag we can |
| do the checking during the RPO walk itself (but then it's |
| also easy to handle MEME conservatively). */ |
| for (int i = 0; i < n; ++i) |
| { |
| basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[i]); |
| edge e; |
| edge_iterator ei; |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| gcc_assert (e == entry |
| || (skip_entry_phis && bb == entry->dest) |
| || (e->src->flags & bb_in_region)); |
| } |
| for (int i = 0; i < n; ++i) |
| { |
| basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[i]); |
| bb->flags &= ~bb_in_region; |
| } |
| } |
| |
| /* Create the VN state. For the initial size of the various hashtables |
| use a heuristic based on region size and number of SSA names. */ |
| unsigned region_size = (((unsigned HOST_WIDE_INT)n * num_ssa_names) |
| / (n_basic_blocks_for_fn (fn) - NUM_FIXED_BLOCKS)); |
| VN_TOP = create_tmp_var_raw (void_type_node, "vn_top"); |
| next_value_id = 1; |
| next_constant_value_id = -1; |
| |
| vn_ssa_aux_hash = new hash_table <vn_ssa_aux_hasher> (region_size * 2); |
| gcc_obstack_init (&vn_ssa_aux_obstack); |
| |
| gcc_obstack_init (&vn_tables_obstack); |
| gcc_obstack_init (&vn_tables_insert_obstack); |
| valid_info = XCNEW (struct vn_tables_s); |
| allocate_vn_table (valid_info, region_size); |
| last_inserted_ref = NULL; |
| last_inserted_phi = NULL; |
| last_inserted_nary = NULL; |
| last_pushed_avail = NULL; |
| |
| vn_valueize = rpo_vn_valueize; |
| |
| /* Initialize the unwind state and edge/BB executable state. */ |
| unsigned curr_scc = 0; |
| for (int i = 0; i < n; ++i) |
| { |
| basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[i]); |
| rpo_state[i].visited = 0; |
| rpo_state[i].max_rpo = i; |
| if (!iterate && curr_scc < toplevel_scc_extents.length ()) |
| { |
| if (i >= toplevel_scc_extents[curr_scc].first |
| && i <= toplevel_scc_extents[curr_scc].second) |
| rpo_state[i].max_rpo = toplevel_scc_extents[curr_scc].second; |
| if (i == toplevel_scc_extents[curr_scc].second) |
| curr_scc++; |
| } |
| bb->flags &= ~BB_EXECUTABLE; |
| bool has_backedges = false; |
| edge e; |
| edge_iterator ei; |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| { |
| if (e->flags & EDGE_DFS_BACK) |
| has_backedges = true; |
| e->flags &= ~EDGE_EXECUTABLE; |
| if (iterate || e == entry || (skip_entry_phis && bb == entry->dest)) |
| continue; |
| } |
| rpo_state[i].iterate = iterate && has_backedges; |
| } |
| entry->flags |= EDGE_EXECUTABLE; |
| entry->dest->flags |= BB_EXECUTABLE; |
| |
| /* As heuristic to improve compile-time we handle only the N innermost |
| loops and the outermost one optimistically. */ |
| if (iterate) |
| { |
| unsigned max_depth = param_rpo_vn_max_loop_depth; |
| for (auto loop : loops_list (cfun, LI_ONLY_INNERMOST)) |
| if (loop_depth (loop) > max_depth) |
| for (unsigned i = 2; |
| i < loop_depth (loop) - max_depth; ++i) |
| { |
| basic_block header = superloop_at_depth (loop, i)->header; |
| bool non_latch_backedge = false; |
| edge e; |
| edge_iterator ei; |
| FOR_EACH_EDGE (e, ei, header->preds) |
| if (e->flags & EDGE_DFS_BACK) |
| { |
| /* There can be a non-latch backedge into the header |
| which is part of an outer irreducible region. We |
| cannot avoid iterating this block then. */ |
| if (!dominated_by_p (CDI_DOMINATORS, |
| e->src, e->dest)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "non-latch backedge %d -> %d " |
| "forces iteration of loop %d\n", |
| e->src->index, e->dest->index, loop->num); |
| non_latch_backedge = true; |
| } |
| else |
| e->flags |= EDGE_EXECUTABLE; |
| } |
| rpo_state[bb_to_rpo[header->index]].iterate = non_latch_backedge; |
| } |
| } |
| |
| uint64_t nblk = 0; |
| int idx = 0; |
| if (iterate) |
| /* Go and process all blocks, iterating as necessary. */ |
| do |
| { |
| basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[idx]); |
| |
| /* If the block has incoming backedges remember unwind state. This |
| is required even for non-executable blocks since in irreducible |
| regions we might reach them via the backedge and re-start iterating |
| from there. |
| Note we can individually mark blocks with incoming backedges to |
| not iterate where we then handle PHIs conservatively. We do that |
| heuristically to reduce compile-time for degenerate cases. */ |
| if (rpo_state[idx].iterate) |
| { |
| rpo_state[idx].ob_top = obstack_alloc (&vn_tables_obstack, 0); |
| rpo_state[idx].ref_top = last_inserted_ref; |
| rpo_state[idx].phi_top = last_inserted_phi; |
| rpo_state[idx].nary_top = last_inserted_nary; |
| rpo_state[idx].avail_top |
| = last_pushed_avail ? last_pushed_avail->avail : NULL; |
| } |
| |
| if (!(bb->flags & BB_EXECUTABLE)) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Block %d: BB%d found not executable\n", |
| idx, bb->index); |
| idx++; |
| continue; |
| } |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Processing block %d: BB%d\n", idx, bb->index); |
| nblk++; |
| todo |= process_bb (avail, bb, |
| rpo_state[idx].visited != 0, |
| rpo_state[idx].iterate, |
| iterate, eliminate, do_region, exit_bbs, false); |
| rpo_state[idx].visited++; |
| |
| /* Verify if changed values flow over executable outgoing backedges |
| and those change destination PHI values (that's the thing we |
| can easily verify). Reduce over all such edges to the farthest |
| away PHI. */ |
| int iterate_to = -1; |
| edge_iterator ei; |
| edge e; |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if ((e->flags & (EDGE_DFS_BACK|EDGE_EXECUTABLE)) |
| == (EDGE_DFS_BACK|EDGE_EXECUTABLE) |
| && rpo_state[bb_to_rpo[e->dest->index]].iterate) |
| { |
| int destidx = bb_to_rpo[e->dest->index]; |
| if (!rpo_state[destidx].visited) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Unvisited destination %d\n", |
| e->dest->index); |
| if (iterate_to == -1 || destidx < iterate_to) |
| iterate_to = destidx; |
| continue; |
| } |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Looking for changed values of backedge" |
| " %d->%d destination PHIs\n", |
| e->src->index, e->dest->index); |
| vn_context_bb = e->dest; |
| gphi_iterator gsi; |
| for (gsi = gsi_start_phis (e->dest); |
| !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| bool inserted = false; |
| /* While we'd ideally just iterate on value changes |
| we CSE PHIs and do that even across basic-block |
| boundaries. So even hashtable state changes can |
| be important (which is roughly equivalent to |
| PHI argument value changes). To not excessively |
| iterate because of that we track whether a PHI |
| was CSEd to with GF_PLF_1. */ |
| bool phival_changed; |
| if ((phival_changed = visit_phi (gsi.phi (), |
| &inserted, false)) |
| || (inserted && gimple_plf (gsi.phi (), GF_PLF_1))) |
| { |
| if (!phival_changed |
| && dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "PHI was CSEd and hashtable " |
| "state (changed)\n"); |
| if (iterate_to == -1 || destidx < iterate_to) |
| iterate_to = destidx; |
| break; |
| } |
| } |
| vn_context_bb = NULL; |
| } |
| if (iterate_to != -1) |
| { |
| do_unwind (&rpo_state[iterate_to], avail); |
| idx = iterate_to; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Iterating to %d BB%d\n", |
| iterate_to, rpo[iterate_to]); |
| continue; |
| } |
| |
| idx++; |
| } |
| while (idx < n); |
| |
| else /* !iterate */ |
| { |
| /* Process all blocks greedily with a worklist that enforces RPO |
| processing of reachable blocks. */ |
| auto_bitmap worklist; |
| bitmap_set_bit (worklist, 0); |
| while (!bitmap_empty_p (worklist)) |
| { |
| int idx = bitmap_first_set_bit (worklist); |
| bitmap_clear_bit (worklist, idx); |
| basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[idx]); |
| gcc_assert ((bb->flags & BB_EXECUTABLE) |
| && !rpo_state[idx].visited); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Processing block %d: BB%d\n", idx, bb->index); |
| |
| /* When we run into predecessor edges where we cannot trust its |
| executable state mark them executable so PHI processing will |
| be conservative. |
| ??? Do we need to force arguments flowing over that edge |
| to be varying or will they even always be? */ |
| edge_iterator ei; |
| edge e; |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| if (!(e->flags & EDGE_EXECUTABLE) |
| && (bb == entry->dest |
| || (!rpo_state[bb_to_rpo[e->src->index]].visited |
| && (rpo_state[bb_to_rpo[e->src->index]].max_rpo |
| >= (int)idx)))) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Cannot trust state of predecessor " |
| "edge %d -> %d, marking executable\n", |
| e->src->index, e->dest->index); |
| e->flags |= EDGE_EXECUTABLE; |
| } |
| |
| nblk++; |
| todo |= process_bb (avail, bb, false, false, false, eliminate, |
| do_region, exit_bbs, |
| skip_entry_phis && bb == entry->dest); |
| rpo_state[idx].visited++; |
| |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if ((e->flags & EDGE_EXECUTABLE) |
| && e->dest->index != EXIT_BLOCK |
| && (!do_region || !bitmap_bit_p (exit_bbs, e->dest->index)) |
| && !rpo_state[bb_to_rpo[e->dest->index]].visited) |
| bitmap_set_bit (worklist, bb_to_rpo[e->dest->index]); |
| } |
| } |
| |
| /* If statistics or dump file active. */ |
| int nex = 0; |
| unsigned max_visited = 1; |
| for (int i = 0; i < n; ++i) |
| { |
| basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[i]); |
| if (bb->flags & BB_EXECUTABLE) |
| nex++; |
| statistics_histogram_event (cfun, "RPO block visited times", |
| rpo_state[i].visited); |
| if (rpo_state[i].visited > max_visited) |
| max_visited = rpo_state[i].visited; |
| } |
| unsigned nvalues = 0, navail = 0; |
| for (hash_table<vn_ssa_aux_hasher>::iterator i = vn_ssa_aux_hash->begin (); |
| i != vn_ssa_aux_hash->end (); ++i) |
| { |
| nvalues++; |
| vn_avail *av = (*i)->avail; |
| while (av) |
| { |
| navail++; |
| av = av->next; |
| } |
| } |
| statistics_counter_event (cfun, "RPO blocks", n); |
| statistics_counter_event (cfun, "RPO blocks visited", nblk); |
| statistics_counter_event (cfun, "RPO blocks executable", nex); |
| statistics_histogram_event (cfun, "RPO iterations", 10*nblk / nex); |
| statistics_histogram_event (cfun, "RPO num values", nvalues); |
| statistics_histogram_event (cfun, "RPO num avail", navail); |
| statistics_histogram_event (cfun, "RPO num lattice", |
| vn_ssa_aux_hash->elements ()); |
| if (dump_file && (dump_flags & (TDF_DETAILS|TDF_STATS))) |
| { |
| fprintf (dump_file, "RPO iteration over %d blocks visited %" PRIu64 |
| " blocks in total discovering %d executable blocks iterating " |
| "%d.%d times, a block was visited max. %u times\n", |
| n, nblk, nex, |
| (int)((10*nblk / nex)/10), (int)((10*nblk / nex)%10), |
| max_visited); |
| fprintf (dump_file, "RPO tracked %d values available at %d locations " |
| "and %" PRIu64 " lattice elements\n", |
| nvalues, navail, (uint64_t) vn_ssa_aux_hash->elements ()); |
| } |
| |
| if (eliminate) |
| { |
| /* When !iterate we already performed elimination during the RPO |
| walk. */ |
| if (iterate) |
| { |
| /* Elimination for region-based VN needs to be done within the |
| RPO walk. */ |
| gcc_assert (! do_region); |
| /* Note we can't use avail.walk here because that gets confused |
| by the existing availability and it will be less efficient |
| as well. */ |
| todo |= eliminate_with_rpo_vn (NULL); |
| } |
| else |
| todo |= avail.eliminate_cleanup (do_region); |
| } |
| |
| vn_valueize = NULL; |
| rpo_avail = NULL; |
| |
| XDELETEVEC (bb_to_rpo); |
| XDELETEVEC (rpo); |
| XDELETEVEC (rpo_state); |
| |
| return todo; |
| } |
| |
| /* Region-based entry for RPO VN. Performs value-numbering and elimination |
| on the SEME region specified by ENTRY and EXIT_BBS. If ENTRY is not |
| the only edge into the region at ENTRY->dest PHI nodes in ENTRY->dest |
| are not considered. */ |
| |
| unsigned |
| do_rpo_vn (function *fn, edge entry, bitmap exit_bbs) |
| { |
| unsigned todo = do_rpo_vn (fn, entry, exit_bbs, false, true, VN_WALKREWRITE); |
| free_rpo_vn (); |
| return todo; |
| } |
| |
| |
| namespace { |
| |
| const pass_data pass_data_fre = |
| { |
| GIMPLE_PASS, /* type */ |
| "fre", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_TREE_FRE, /* tv_id */ |
| ( PROP_cfg | PROP_ssa ), /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_fre : public gimple_opt_pass |
| { |
| public: |
| pass_fre (gcc::context *ctxt) |
| : gimple_opt_pass (pass_data_fre, ctxt), may_iterate (true) |
| {} |
| |
| /* opt_pass methods: */ |
| opt_pass * clone () { return new pass_fre (m_ctxt); } |
| void set_pass_param (unsigned int n, bool param) |
| { |
| gcc_assert (n == 0); |
| may_iterate = param; |
| } |
| virtual bool gate (function *) |
| { |
| return flag_tree_fre != 0 && (may_iterate || optimize > 1); |
| } |
| virtual unsigned int execute (function *); |
| |
| private: |
| bool may_iterate; |
| }; // class pass_fre |
| |
| unsigned int |
| pass_fre::execute (function *fun) |
| { |
| unsigned todo = 0; |
| |
| /* At -O[1g] use the cheap non-iterating mode. */ |
| bool iterate_p = may_iterate && (optimize > 1); |
| calculate_dominance_info (CDI_DOMINATORS); |
| if (iterate_p) |
| loop_optimizer_init (AVOID_CFG_MODIFICATIONS); |
| |
| todo = do_rpo_vn (fun, NULL, NULL, iterate_p, true, VN_WALKREWRITE); |
| free_rpo_vn (); |
| |
| if (iterate_p) |
| loop_optimizer_finalize (); |
| |
| if (scev_initialized_p ()) |
| scev_reset_htab (); |
| |
| /* For late FRE after IVOPTs and unrolling, see if we can |
| remove some TREE_ADDRESSABLE and rewrite stuff into SSA. */ |
| if (!may_iterate) |
| todo |= TODO_update_address_taken; |
| |
| return todo; |
| } |
| |
| } // anon namespace |
| |
| gimple_opt_pass * |
| make_pass_fre (gcc::context *ctxt) |
| { |
| return new pass_fre (ctxt); |
| } |
| |
| #undef BB_EXECUTABLE |