| /* Scalar Replacement of Aggregates (SRA) converts some structure |
| references into scalar references, exposing them to the scalar |
| optimizers. |
| Copyright (C) 2008-2013 Free Software Foundation, Inc. |
| Contributed by Martin Jambor <mjambor@suse.cz> |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 3, or (at your option) any later |
| version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* This file implements Scalar Reduction of Aggregates (SRA). SRA is run |
| twice, once in the early stages of compilation (early SRA) and once in the |
| late stages (late SRA). The aim of both is to turn references to scalar |
| parts of aggregates into uses of independent scalar variables. |
| |
| The two passes are nearly identical, the only difference is that early SRA |
| does not scalarize unions which are used as the result in a GIMPLE_RETURN |
| statement because together with inlining this can lead to weird type |
| conversions. |
| |
| Both passes operate in four stages: |
| |
| 1. The declarations that have properties which make them candidates for |
| scalarization are identified in function find_var_candidates(). The |
| candidates are stored in candidate_bitmap. |
| |
| 2. The function body is scanned. In the process, declarations which are |
| used in a manner that prevent their scalarization are removed from the |
| candidate bitmap. More importantly, for every access into an aggregate, |
| an access structure (struct access) is created by create_access() and |
| stored in a vector associated with the aggregate. Among other |
| information, the aggregate declaration, the offset and size of the access |
| and its type are stored in the structure. |
| |
| On a related note, assign_link structures are created for every assign |
| statement between candidate aggregates and attached to the related |
| accesses. |
| |
| 3. The vectors of accesses are analyzed. They are first sorted according to |
| their offset and size and then scanned for partially overlapping accesses |
| (i.e. those which overlap but one is not entirely within another). Such |
| an access disqualifies the whole aggregate from being scalarized. |
| |
| If there is no such inhibiting overlap, a representative access structure |
| is chosen for every unique combination of offset and size. Afterwards, |
| the pass builds a set of trees from these structures, in which children |
| of an access are within their parent (in terms of offset and size). |
| |
| Then accesses are propagated whenever possible (i.e. in cases when it |
| does not create a partially overlapping access) across assign_links from |
| the right hand side to the left hand side. |
| |
| Then the set of trees for each declaration is traversed again and those |
| accesses which should be replaced by a scalar are identified. |
| |
| 4. The function is traversed again, and for every reference into an |
| aggregate that has some component which is about to be scalarized, |
| statements are amended and new statements are created as necessary. |
| Finally, if a parameter got scalarized, the scalar replacements are |
| initialized with values from respective parameter aggregates. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "alloc-pool.h" |
| #include "tm.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "cgraph.h" |
| #include "tree-flow.h" |
| #include "tree-pass.h" |
| #include "ipa-prop.h" |
| #include "statistics.h" |
| #include "params.h" |
| #include "toplev.h" |
| #include "target.h" |
| #include "flags.h" |
| #include "dbgcnt.h" |
| #include "tree-inline.h" |
| #include "gimple-pretty-print.h" |
| #include "l-ipo.h" |
| #include "ipa-inline.h" |
| |
| /* Enumeration of all aggregate reductions we can do. */ |
| enum sra_mode { SRA_MODE_EARLY_IPA, /* early call regularization */ |
| SRA_MODE_EARLY_INTRA, /* early intraprocedural SRA */ |
| SRA_MODE_INTRA }; /* late intraprocedural SRA */ |
| |
| /* Global variable describing which aggregate reduction we are performing at |
| the moment. */ |
| static enum sra_mode sra_mode; |
| |
| struct assign_link; |
| |
| /* ACCESS represents each access to an aggregate variable (as a whole or a |
| part). It can also represent a group of accesses that refer to exactly the |
| same fragment of an aggregate (i.e. those that have exactly the same offset |
| and size). Such representatives for a single aggregate, once determined, |
| are linked in a linked list and have the group fields set. |
| |
| Moreover, when doing intraprocedural SRA, a tree is built from those |
| representatives (by the means of first_child and next_sibling pointers), in |
| which all items in a subtree are "within" the root, i.e. their offset is |
| greater or equal to offset of the root and offset+size is smaller or equal |
| to offset+size of the root. Children of an access are sorted by offset. |
| |
| Note that accesses to parts of vector and complex number types always |
| represented by an access to the whole complex number or a vector. It is a |
| duty of the modifying functions to replace them appropriately. */ |
| |
| struct access |
| { |
| /* Values returned by `get_ref_base_and_extent' for each component reference |
| If EXPR isn't a component reference just set `BASE = EXPR', `OFFSET = 0', |
| `SIZE = TREE_SIZE (TREE_TYPE (expr))'. */ |
| HOST_WIDE_INT offset; |
| HOST_WIDE_INT size; |
| tree base; |
| |
| /* Expression. It is context dependent so do not use it to create new |
| expressions to access the original aggregate. See PR 42154 for a |
| testcase. */ |
| tree expr; |
| /* Type. */ |
| tree type; |
| |
| /* The statement this access belongs to. */ |
| gimple stmt; |
| |
| /* Next group representative for this aggregate. */ |
| struct access *next_grp; |
| |
| /* Pointer to the group representative. Pointer to itself if the struct is |
| the representative. */ |
| struct access *group_representative; |
| |
| /* If this access has any children (in terms of the definition above), this |
| points to the first one. */ |
| struct access *first_child; |
| |
| /* In intraprocedural SRA, pointer to the next sibling in the access tree as |
| described above. In IPA-SRA this is a pointer to the next access |
| belonging to the same group (having the same representative). */ |
| struct access *next_sibling; |
| |
| /* Pointers to the first and last element in the linked list of assign |
| links. */ |
| struct assign_link *first_link, *last_link; |
| |
| /* Pointer to the next access in the work queue. */ |
| struct access *next_queued; |
| |
| /* Replacement variable for this access "region." Never to be accessed |
| directly, always only by the means of get_access_replacement() and only |
| when grp_to_be_replaced flag is set. */ |
| tree replacement_decl; |
| |
| /* Is this particular access write access? */ |
| unsigned write : 1; |
| |
| /* Is this access an access to a non-addressable field? */ |
| unsigned non_addressable : 1; |
| |
| /* Is this access currently in the work queue? */ |
| unsigned grp_queued : 1; |
| |
| /* Does this group contain a write access? This flag is propagated down the |
| access tree. */ |
| unsigned grp_write : 1; |
| |
| /* Does this group contain a read access? This flag is propagated down the |
| access tree. */ |
| unsigned grp_read : 1; |
| |
| /* Does this group contain a read access that comes from an assignment |
| statement? This flag is propagated down the access tree. */ |
| unsigned grp_assignment_read : 1; |
| |
| /* Does this group contain a write access that comes from an assignment |
| statement? This flag is propagated down the access tree. */ |
| unsigned grp_assignment_write : 1; |
| |
| /* Does this group contain a read access through a scalar type? This flag is |
| not propagated in the access tree in any direction. */ |
| unsigned grp_scalar_read : 1; |
| |
| /* Does this group contain a write access through a scalar type? This flag |
| is not propagated in the access tree in any direction. */ |
| unsigned grp_scalar_write : 1; |
| |
| /* Is this access an artificial one created to scalarize some record |
| entirely? */ |
| unsigned grp_total_scalarization : 1; |
| |
| /* Other passes of the analysis use this bit to make function |
| analyze_access_subtree create scalar replacements for this group if |
| possible. */ |
| unsigned grp_hint : 1; |
| |
| /* Is the subtree rooted in this access fully covered by scalar |
| replacements? */ |
| unsigned grp_covered : 1; |
| |
| /* If set to true, this access and all below it in an access tree must not be |
| scalarized. */ |
| unsigned grp_unscalarizable_region : 1; |
| |
| /* Whether data have been written to parts of the aggregate covered by this |
| access which is not to be scalarized. This flag is propagated up in the |
| access tree. */ |
| unsigned grp_unscalarized_data : 1; |
| |
| /* Does this access and/or group contain a write access through a |
| BIT_FIELD_REF? */ |
| unsigned grp_partial_lhs : 1; |
| |
| /* Set when a scalar replacement should be created for this variable. */ |
| unsigned grp_to_be_replaced : 1; |
| |
| /* Set when we want a replacement for the sole purpose of having it in |
| generated debug statements. */ |
| unsigned grp_to_be_debug_replaced : 1; |
| |
| /* Should TREE_NO_WARNING of a replacement be set? */ |
| unsigned grp_no_warning : 1; |
| |
| /* Is it possible that the group refers to data which might be (directly or |
| otherwise) modified? */ |
| unsigned grp_maybe_modified : 1; |
| |
| /* Set when this is a representative of a pointer to scalar (i.e. by |
| reference) parameter which we consider for turning into a plain scalar |
| (i.e. a by value parameter). */ |
| unsigned grp_scalar_ptr : 1; |
| |
| /* Set when we discover that this pointer is not safe to dereference in the |
| caller. */ |
| unsigned grp_not_necessarilly_dereferenced : 1; |
| }; |
| |
| typedef struct access *access_p; |
| |
| |
| /* Alloc pool for allocating access structures. */ |
| static alloc_pool access_pool; |
| |
| /* A structure linking lhs and rhs accesses from an aggregate assignment. They |
| are used to propagate subaccesses from rhs to lhs as long as they don't |
| conflict with what is already there. */ |
| struct assign_link |
| { |
| struct access *lacc, *racc; |
| struct assign_link *next; |
| }; |
| |
| /* Alloc pool for allocating assign link structures. */ |
| static alloc_pool link_pool; |
| |
| /* Base (tree) -> Vector (vec<access_p> *) map. */ |
| static struct pointer_map_t *base_access_vec; |
| |
| /* Set of candidates. */ |
| static bitmap candidate_bitmap; |
| static htab_t candidates; |
| |
| /* For a candidate UID return the candidates decl. */ |
| |
| static inline tree |
| candidate (unsigned uid) |
| { |
| struct tree_decl_minimal t; |
| t.uid = uid; |
| return (tree) htab_find_with_hash (candidates, &t, uid); |
| } |
| |
| /* Bitmap of candidates which we should try to entirely scalarize away and |
| those which cannot be (because they are and need be used as a whole). */ |
| static bitmap should_scalarize_away_bitmap, cannot_scalarize_away_bitmap; |
| |
| /* Obstack for creation of fancy names. */ |
| static struct obstack name_obstack; |
| |
| /* Head of a linked list of accesses that need to have its subaccesses |
| propagated to their assignment counterparts. */ |
| static struct access *work_queue_head; |
| |
| /* Number of parameters of the analyzed function when doing early ipa SRA. */ |
| static int func_param_count; |
| |
| /* scan_function sets the following to true if it encounters a call to |
| __builtin_apply_args. */ |
| static bool encountered_apply_args; |
| |
| /* Set by scan_function when it finds a recursive call. */ |
| static bool encountered_recursive_call; |
| |
| /* Set by scan_function when it finds a recursive call with less actual |
| arguments than formal parameters.. */ |
| static bool encountered_unchangable_recursive_call; |
| |
| /* This is a table in which for each basic block and parameter there is a |
| distance (offset + size) in that parameter which is dereferenced and |
| accessed in that BB. */ |
| static HOST_WIDE_INT *bb_dereferences; |
| /* Bitmap of BBs that can cause the function to "stop" progressing by |
| returning, throwing externally, looping infinitely or calling a function |
| which might abort etc.. */ |
| static bitmap final_bbs; |
| |
| /* Representative of no accesses at all. */ |
| static struct access no_accesses_representant; |
| |
| /* Predicate to test the special value. */ |
| |
| static inline bool |
| no_accesses_p (struct access *access) |
| { |
| return access == &no_accesses_representant; |
| } |
| |
| /* Dump contents of ACCESS to file F in a human friendly way. If GRP is true, |
| representative fields are dumped, otherwise those which only describe the |
| individual access are. */ |
| |
| static struct |
| { |
| /* Number of processed aggregates is readily available in |
| analyze_all_variable_accesses and so is not stored here. */ |
| |
| /* Number of created scalar replacements. */ |
| int replacements; |
| |
| /* Number of times sra_modify_expr or sra_modify_assign themselves changed an |
| expression. */ |
| int exprs; |
| |
| /* Number of statements created by generate_subtree_copies. */ |
| int subtree_copies; |
| |
| /* Number of statements created by load_assign_lhs_subreplacements. */ |
| int subreplacements; |
| |
| /* Number of times sra_modify_assign has deleted a statement. */ |
| int deleted; |
| |
| /* Number of times sra_modify_assign has to deal with subaccesses of LHS and |
| RHS reparately due to type conversions or nonexistent matching |
| references. */ |
| int separate_lhs_rhs_handling; |
| |
| /* Number of parameters that were removed because they were unused. */ |
| int deleted_unused_parameters; |
| |
| /* Number of scalars passed as parameters by reference that have been |
| converted to be passed by value. */ |
| int scalar_by_ref_to_by_val; |
| |
| /* Number of aggregate parameters that were replaced by one or more of their |
| components. */ |
| int aggregate_params_reduced; |
| |
| /* Numbber of components created when splitting aggregate parameters. */ |
| int param_reductions_created; |
| } sra_stats; |
| |
| static void |
| dump_access (FILE *f, struct access *access, bool grp) |
| { |
| fprintf (f, "access { "); |
| fprintf (f, "base = (%d)'", DECL_UID (access->base)); |
| print_generic_expr (f, access->base, 0); |
| fprintf (f, "', offset = " HOST_WIDE_INT_PRINT_DEC, access->offset); |
| fprintf (f, ", size = " HOST_WIDE_INT_PRINT_DEC, access->size); |
| fprintf (f, ", expr = "); |
| print_generic_expr (f, access->expr, 0); |
| fprintf (f, ", type = "); |
| print_generic_expr (f, access->type, 0); |
| if (grp) |
| fprintf (f, ", grp_read = %d, grp_write = %d, grp_assignment_read = %d, " |
| "grp_assignment_write = %d, grp_scalar_read = %d, " |
| "grp_scalar_write = %d, grp_total_scalarization = %d, " |
| "grp_hint = %d, grp_covered = %d, " |
| "grp_unscalarizable_region = %d, grp_unscalarized_data = %d, " |
| "grp_partial_lhs = %d, grp_to_be_replaced = %d, " |
| "grp_to_be_debug_replaced = %d, grp_maybe_modified = %d, " |
| "grp_not_necessarilly_dereferenced = %d\n", |
| access->grp_read, access->grp_write, access->grp_assignment_read, |
| access->grp_assignment_write, access->grp_scalar_read, |
| access->grp_scalar_write, access->grp_total_scalarization, |
| access->grp_hint, access->grp_covered, |
| access->grp_unscalarizable_region, access->grp_unscalarized_data, |
| access->grp_partial_lhs, access->grp_to_be_replaced, |
| access->grp_to_be_debug_replaced, access->grp_maybe_modified, |
| access->grp_not_necessarilly_dereferenced); |
| else |
| fprintf (f, ", write = %d, grp_total_scalarization = %d, " |
| "grp_partial_lhs = %d\n", |
| access->write, access->grp_total_scalarization, |
| access->grp_partial_lhs); |
| } |
| |
| /* Dump a subtree rooted in ACCESS to file F, indent by LEVEL. */ |
| |
| static void |
| dump_access_tree_1 (FILE *f, struct access *access, int level) |
| { |
| do |
| { |
| int i; |
| |
| for (i = 0; i < level; i++) |
| fputs ("* ", dump_file); |
| |
| dump_access (f, access, true); |
| |
| if (access->first_child) |
| dump_access_tree_1 (f, access->first_child, level + 1); |
| |
| access = access->next_sibling; |
| } |
| while (access); |
| } |
| |
| /* Dump all access trees for a variable, given the pointer to the first root in |
| ACCESS. */ |
| |
| static void |
| dump_access_tree (FILE *f, struct access *access) |
| { |
| for (; access; access = access->next_grp) |
| dump_access_tree_1 (f, access, 0); |
| } |
| |
| /* Return true iff ACC is non-NULL and has subaccesses. */ |
| |
| static inline bool |
| access_has_children_p (struct access *acc) |
| { |
| return acc && acc->first_child; |
| } |
| |
| /* Return true iff ACC is (partly) covered by at least one replacement. */ |
| |
| static bool |
| access_has_replacements_p (struct access *acc) |
| { |
| struct access *child; |
| if (acc->grp_to_be_replaced) |
| return true; |
| for (child = acc->first_child; child; child = child->next_sibling) |
| if (access_has_replacements_p (child)) |
| return true; |
| return false; |
| } |
| |
| /* Return a vector of pointers to accesses for the variable given in BASE or |
| NULL if there is none. */ |
| |
| static vec<access_p> * |
| get_base_access_vector (tree base) |
| { |
| void **slot; |
| |
| slot = pointer_map_contains (base_access_vec, base); |
| if (!slot) |
| return NULL; |
| else |
| return *(vec<access_p> **) slot; |
| } |
| |
| /* Find an access with required OFFSET and SIZE in a subtree of accesses rooted |
| in ACCESS. Return NULL if it cannot be found. */ |
| |
| static struct access * |
| find_access_in_subtree (struct access *access, HOST_WIDE_INT offset, |
| HOST_WIDE_INT size) |
| { |
| while (access && (access->offset != offset || access->size != size)) |
| { |
| struct access *child = access->first_child; |
| |
| while (child && (child->offset + child->size <= offset)) |
| child = child->next_sibling; |
| access = child; |
| } |
| |
| return access; |
| } |
| |
| /* Return the first group representative for DECL or NULL if none exists. */ |
| |
| static struct access * |
| get_first_repr_for_decl (tree base) |
| { |
| vec<access_p> *access_vec; |
| |
| access_vec = get_base_access_vector (base); |
| if (!access_vec) |
| return NULL; |
| |
| return (*access_vec)[0]; |
| } |
| |
| /* Find an access representative for the variable BASE and given OFFSET and |
| SIZE. Requires that access trees have already been built. Return NULL if |
| it cannot be found. */ |
| |
| static struct access * |
| get_var_base_offset_size_access (tree base, HOST_WIDE_INT offset, |
| HOST_WIDE_INT size) |
| { |
| struct access *access; |
| |
| access = get_first_repr_for_decl (base); |
| while (access && (access->offset + access->size <= offset)) |
| access = access->next_grp; |
| if (!access) |
| return NULL; |
| |
| return find_access_in_subtree (access, offset, size); |
| } |
| |
| /* Add LINK to the linked list of assign links of RACC. */ |
| static void |
| add_link_to_rhs (struct access *racc, struct assign_link *link) |
| { |
| gcc_assert (link->racc == racc); |
| |
| if (!racc->first_link) |
| { |
| gcc_assert (!racc->last_link); |
| racc->first_link = link; |
| } |
| else |
| racc->last_link->next = link; |
| |
| racc->last_link = link; |
| link->next = NULL; |
| } |
| |
| /* Move all link structures in their linked list in OLD_RACC to the linked list |
| in NEW_RACC. */ |
| static void |
| relink_to_new_repr (struct access *new_racc, struct access *old_racc) |
| { |
| if (!old_racc->first_link) |
| { |
| gcc_assert (!old_racc->last_link); |
| return; |
| } |
| |
| if (new_racc->first_link) |
| { |
| gcc_assert (!new_racc->last_link->next); |
| gcc_assert (!old_racc->last_link || !old_racc->last_link->next); |
| |
| new_racc->last_link->next = old_racc->first_link; |
| new_racc->last_link = old_racc->last_link; |
| } |
| else |
| { |
| gcc_assert (!new_racc->last_link); |
| |
| new_racc->first_link = old_racc->first_link; |
| new_racc->last_link = old_racc->last_link; |
| } |
| old_racc->first_link = old_racc->last_link = NULL; |
| } |
| |
| /* Add ACCESS to the work queue (which is actually a stack). */ |
| |
| static void |
| add_access_to_work_queue (struct access *access) |
| { |
| if (!access->grp_queued) |
| { |
| gcc_assert (!access->next_queued); |
| access->next_queued = work_queue_head; |
| access->grp_queued = 1; |
| work_queue_head = access; |
| } |
| } |
| |
| /* Pop an access from the work queue, and return it, assuming there is one. */ |
| |
| static struct access * |
| pop_access_from_work_queue (void) |
| { |
| struct access *access = work_queue_head; |
| |
| work_queue_head = access->next_queued; |
| access->next_queued = NULL; |
| access->grp_queued = 0; |
| return access; |
| } |
| |
| |
| /* Allocate necessary structures. */ |
| |
| static void |
| sra_initialize (void) |
| { |
| candidate_bitmap = BITMAP_ALLOC (NULL); |
| candidates = htab_create (vec_safe_length (cfun->local_decls) / 2, |
| uid_decl_map_hash, uid_decl_map_eq, NULL); |
| should_scalarize_away_bitmap = BITMAP_ALLOC (NULL); |
| cannot_scalarize_away_bitmap = BITMAP_ALLOC (NULL); |
| gcc_obstack_init (&name_obstack); |
| access_pool = create_alloc_pool ("SRA accesses", sizeof (struct access), 16); |
| link_pool = create_alloc_pool ("SRA links", sizeof (struct assign_link), 16); |
| base_access_vec = pointer_map_create (); |
| memset (&sra_stats, 0, sizeof (sra_stats)); |
| encountered_apply_args = false; |
| encountered_recursive_call = false; |
| encountered_unchangable_recursive_call = false; |
| } |
| |
| /* Hook fed to pointer_map_traverse, deallocate stored vectors. */ |
| |
| static bool |
| delete_base_accesses (const void *key ATTRIBUTE_UNUSED, void **value, |
| void *data ATTRIBUTE_UNUSED) |
| { |
| vec<access_p> *access_vec = (vec<access_p> *) *value; |
| vec_free (access_vec); |
| return true; |
| } |
| |
| /* Deallocate all general structures. */ |
| |
| static void |
| sra_deinitialize (void) |
| { |
| BITMAP_FREE (candidate_bitmap); |
| htab_delete (candidates); |
| BITMAP_FREE (should_scalarize_away_bitmap); |
| BITMAP_FREE (cannot_scalarize_away_bitmap); |
| free_alloc_pool (access_pool); |
| free_alloc_pool (link_pool); |
| obstack_free (&name_obstack, NULL); |
| |
| pointer_map_traverse (base_access_vec, delete_base_accesses, NULL); |
| pointer_map_destroy (base_access_vec); |
| } |
| |
| /* Remove DECL from candidates for SRA and write REASON to the dump file if |
| there is one. */ |
| static void |
| disqualify_candidate (tree decl, const char *reason) |
| { |
| if (bitmap_clear_bit (candidate_bitmap, DECL_UID (decl))) |
| htab_clear_slot (candidates, |
| htab_find_slot_with_hash (candidates, decl, |
| DECL_UID (decl), NO_INSERT)); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "! Disqualifying "); |
| print_generic_expr (dump_file, decl, 0); |
| fprintf (dump_file, " - %s\n", reason); |
| } |
| } |
| |
| /* Return true iff the type contains a field or an element which does not allow |
| scalarization. */ |
| |
| static bool |
| type_internals_preclude_sra_p (tree type, const char **msg) |
| { |
| tree fld; |
| tree et; |
| |
| switch (TREE_CODE (type)) |
| { |
| case RECORD_TYPE: |
| case UNION_TYPE: |
| case QUAL_UNION_TYPE: |
| for (fld = TYPE_FIELDS (type); fld; fld = DECL_CHAIN (fld)) |
| if (TREE_CODE (fld) == FIELD_DECL) |
| { |
| tree ft = TREE_TYPE (fld); |
| |
| if (TREE_THIS_VOLATILE (fld)) |
| { |
| *msg = "volatile structure field"; |
| return true; |
| } |
| if (!DECL_FIELD_OFFSET (fld)) |
| { |
| *msg = "no structure field offset"; |
| return true; |
| } |
| if (!DECL_SIZE (fld)) |
| { |
| *msg = "zero structure field size"; |
| return true; |
| } |
| if (!host_integerp (DECL_FIELD_OFFSET (fld), 1)) |
| { |
| *msg = "structure field offset not fixed"; |
| return true; |
| } |
| if (!host_integerp (DECL_SIZE (fld), 1)) |
| { |
| *msg = "structure field size not fixed"; |
| return true; |
| } |
| if (!host_integerp (bit_position (fld), 0)) |
| { |
| *msg = "structure field size too big"; |
| return true; |
| } |
| if (AGGREGATE_TYPE_P (ft) |
| && int_bit_position (fld) % BITS_PER_UNIT != 0) |
| { |
| *msg = "structure field is bit field"; |
| return true; |
| } |
| |
| if (AGGREGATE_TYPE_P (ft) && type_internals_preclude_sra_p (ft, msg)) |
| return true; |
| } |
| |
| return false; |
| |
| case ARRAY_TYPE: |
| et = TREE_TYPE (type); |
| |
| if (TYPE_VOLATILE (et)) |
| { |
| *msg = "element type is volatile"; |
| return true; |
| } |
| |
| if (AGGREGATE_TYPE_P (et) && type_internals_preclude_sra_p (et, msg)) |
| return true; |
| |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* If T is an SSA_NAME, return NULL if it is not a default def or return its |
| base variable if it is. Return T if it is not an SSA_NAME. */ |
| |
| static tree |
| get_ssa_base_param (tree t) |
| { |
| if (TREE_CODE (t) == SSA_NAME) |
| { |
| if (SSA_NAME_IS_DEFAULT_DEF (t)) |
| return SSA_NAME_VAR (t); |
| else |
| return NULL_TREE; |
| } |
| return t; |
| } |
| |
| /* Mark a dereference of BASE of distance DIST in a basic block tht STMT |
| belongs to, unless the BB has already been marked as a potentially |
| final. */ |
| |
| static void |
| mark_parm_dereference (tree base, HOST_WIDE_INT dist, gimple stmt) |
| { |
| basic_block bb = gimple_bb (stmt); |
| int idx, parm_index = 0; |
| tree parm; |
| |
| if (bitmap_bit_p (final_bbs, bb->index)) |
| return; |
| |
| for (parm = DECL_ARGUMENTS (current_function_decl); |
| parm && parm != base; |
| parm = DECL_CHAIN (parm)) |
| parm_index++; |
| |
| gcc_assert (parm_index < func_param_count); |
| |
| idx = bb->index * func_param_count + parm_index; |
| if (bb_dereferences[idx] < dist) |
| bb_dereferences[idx] = dist; |
| } |
| |
| /* Allocate an access structure for BASE, OFFSET and SIZE, clear it, fill in |
| the three fields. Also add it to the vector of accesses corresponding to |
| the base. Finally, return the new access. */ |
| |
| static struct access * |
| create_access_1 (tree base, HOST_WIDE_INT offset, HOST_WIDE_INT size) |
| { |
| vec<access_p> *v; |
| struct access *access; |
| void **slot; |
| |
| access = (struct access *) pool_alloc (access_pool); |
| memset (access, 0, sizeof (struct access)); |
| access->base = base; |
| access->offset = offset; |
| access->size = size; |
| |
| slot = pointer_map_contains (base_access_vec, base); |
| if (slot) |
| v = (vec<access_p> *) *slot; |
| else |
| vec_alloc (v, 32); |
| |
| v->safe_push (access); |
| |
| *((vec<access_p> **) |
| pointer_map_insert (base_access_vec, base)) = v; |
| |
| return access; |
| } |
| |
| /* Create and insert access for EXPR. Return created access, or NULL if it is |
| not possible. */ |
| |
| static struct access * |
| create_access (tree expr, gimple stmt, bool write) |
| { |
| struct access *access; |
| HOST_WIDE_INT offset, size, max_size; |
| tree base = expr; |
| bool ptr, unscalarizable_region = false; |
| |
| base = get_ref_base_and_extent (expr, &offset, &size, &max_size); |
| |
| if (sra_mode == SRA_MODE_EARLY_IPA |
| && TREE_CODE (base) == MEM_REF) |
| { |
| base = get_ssa_base_param (TREE_OPERAND (base, 0)); |
| if (!base) |
| return NULL; |
| ptr = true; |
| } |
| else |
| ptr = false; |
| |
| if (!DECL_P (base) || !bitmap_bit_p (candidate_bitmap, DECL_UID (base))) |
| return NULL; |
| |
| if (sra_mode == SRA_MODE_EARLY_IPA) |
| { |
| if (size < 0 || size != max_size) |
| { |
| disqualify_candidate (base, "Encountered a variable sized access."); |
| return NULL; |
| } |
| if (TREE_CODE (expr) == COMPONENT_REF |
| && DECL_BIT_FIELD (TREE_OPERAND (expr, 1))) |
| { |
| disqualify_candidate (base, "Encountered a bit-field access."); |
| return NULL; |
| } |
| gcc_checking_assert ((offset % BITS_PER_UNIT) == 0); |
| |
| if (ptr) |
| mark_parm_dereference (base, offset + size, stmt); |
| } |
| else |
| { |
| if (size != max_size) |
| { |
| size = max_size; |
| unscalarizable_region = true; |
| } |
| if (size < 0) |
| { |
| disqualify_candidate (base, "Encountered an unconstrained access."); |
| return NULL; |
| } |
| } |
| |
| access = create_access_1 (base, offset, size); |
| access->expr = expr; |
| access->type = TREE_TYPE (expr); |
| access->write = write; |
| access->grp_unscalarizable_region = unscalarizable_region; |
| access->stmt = stmt; |
| |
| if (TREE_CODE (expr) == COMPONENT_REF |
| && DECL_NONADDRESSABLE_P (TREE_OPERAND (expr, 1))) |
| access->non_addressable = 1; |
| |
| return access; |
| } |
| |
| |
| /* Return true iff TYPE is a RECORD_TYPE with fields that are either of gimple |
| register types or (recursively) records with only these two kinds of fields. |
| It also returns false if any of these records contains a bit-field. */ |
| |
| static bool |
| type_consists_of_records_p (tree type) |
| { |
| tree fld; |
| |
| if (TREE_CODE (type) != RECORD_TYPE) |
| return false; |
| |
| for (fld = TYPE_FIELDS (type); fld; fld = DECL_CHAIN (fld)) |
| if (TREE_CODE (fld) == FIELD_DECL) |
| { |
| tree ft = TREE_TYPE (fld); |
| |
| if (DECL_BIT_FIELD (fld)) |
| return false; |
| |
| if (!is_gimple_reg_type (ft) |
| && !type_consists_of_records_p (ft)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Create total_scalarization accesses for all scalar type fields in DECL that |
| must be of a RECORD_TYPE conforming to type_consists_of_records_p. BASE |
| must be the top-most VAR_DECL representing the variable, OFFSET must be the |
| offset of DECL within BASE. REF must be the memory reference expression for |
| the given decl. */ |
| |
| static void |
| completely_scalarize_record (tree base, tree decl, HOST_WIDE_INT offset, |
| tree ref) |
| { |
| tree fld, decl_type = TREE_TYPE (decl); |
| |
| for (fld = TYPE_FIELDS (decl_type); fld; fld = DECL_CHAIN (fld)) |
| if (TREE_CODE (fld) == FIELD_DECL) |
| { |
| HOST_WIDE_INT pos = offset + int_bit_position (fld); |
| tree ft = TREE_TYPE (fld); |
| tree nref = build3 (COMPONENT_REF, TREE_TYPE (fld), ref, fld, |
| NULL_TREE); |
| |
| if (is_gimple_reg_type (ft)) |
| { |
| struct access *access; |
| HOST_WIDE_INT size; |
| |
| size = tree_low_cst (DECL_SIZE (fld), 1); |
| access = create_access_1 (base, pos, size); |
| access->expr = nref; |
| access->type = ft; |
| access->grp_total_scalarization = 1; |
| /* Accesses for intraprocedural SRA can have their stmt NULL. */ |
| } |
| else |
| completely_scalarize_record (base, fld, pos, nref); |
| } |
| } |
| |
| /* Create total_scalarization accesses for all scalar type fields in VAR and |
| for VAR a a whole. VAR must be of a RECORD_TYPE conforming to |
| type_consists_of_records_p. */ |
| |
| static void |
| completely_scalarize_var (tree var) |
| { |
| HOST_WIDE_INT size = tree_low_cst (DECL_SIZE (var), 1); |
| struct access *access; |
| |
| access = create_access_1 (var, 0, size); |
| access->expr = var; |
| access->type = TREE_TYPE (var); |
| access->grp_total_scalarization = 1; |
| |
| completely_scalarize_record (var, var, 0, var); |
| } |
| |
| /* Search the given tree for a declaration by skipping handled components and |
| exclude it from the candidates. */ |
| |
| static void |
| disqualify_base_of_expr (tree t, const char *reason) |
| { |
| t = get_base_address (t); |
| if (sra_mode == SRA_MODE_EARLY_IPA |
| && TREE_CODE (t) == MEM_REF) |
| t = get_ssa_base_param (TREE_OPERAND (t, 0)); |
| |
| if (t && DECL_P (t)) |
| disqualify_candidate (t, reason); |
| } |
| |
| /* Scan expression EXPR and create access structures for all accesses to |
| candidates for scalarization. Return the created access or NULL if none is |
| created. */ |
| |
| static struct access * |
| build_access_from_expr_1 (tree expr, gimple stmt, bool write) |
| { |
| struct access *ret = NULL; |
| bool partial_ref; |
| |
| if (TREE_CODE (expr) == BIT_FIELD_REF |
| || TREE_CODE (expr) == IMAGPART_EXPR |
| || TREE_CODE (expr) == REALPART_EXPR) |
| { |
| expr = TREE_OPERAND (expr, 0); |
| partial_ref = true; |
| } |
| else |
| partial_ref = false; |
| |
| /* We need to dive through V_C_Es in order to get the size of its parameter |
| and not the result type. Ada produces such statements. We are also |
| capable of handling the topmost V_C_E but not any of those buried in other |
| handled components. */ |
| if (TREE_CODE (expr) == VIEW_CONVERT_EXPR) |
| expr = TREE_OPERAND (expr, 0); |
| |
| if (contains_view_convert_expr_p (expr)) |
| { |
| disqualify_base_of_expr (expr, "V_C_E under a different handled " |
| "component."); |
| return NULL; |
| } |
| |
| switch (TREE_CODE (expr)) |
| { |
| case MEM_REF: |
| if (TREE_CODE (TREE_OPERAND (expr, 0)) != ADDR_EXPR |
| && sra_mode != SRA_MODE_EARLY_IPA) |
| return NULL; |
| /* fall through */ |
| case VAR_DECL: |
| case PARM_DECL: |
| case RESULT_DECL: |
| case COMPONENT_REF: |
| case ARRAY_REF: |
| case ARRAY_RANGE_REF: |
| ret = create_access (expr, stmt, write); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (write && partial_ref && ret) |
| ret->grp_partial_lhs = 1; |
| |
| return ret; |
| } |
| |
| /* Scan expression EXPR and create access structures for all accesses to |
| candidates for scalarization. Return true if any access has been inserted. |
| STMT must be the statement from which the expression is taken, WRITE must be |
| true if the expression is a store and false otherwise. */ |
| |
| static bool |
| build_access_from_expr (tree expr, gimple stmt, bool write) |
| { |
| struct access *access; |
| |
| access = build_access_from_expr_1 (expr, stmt, write); |
| if (access) |
| { |
| /* This means the aggregate is accesses as a whole in a way other than an |
| assign statement and thus cannot be removed even if we had a scalar |
| replacement for everything. */ |
| if (cannot_scalarize_away_bitmap) |
| bitmap_set_bit (cannot_scalarize_away_bitmap, DECL_UID (access->base)); |
| return true; |
| } |
| return false; |
| } |
| |
| /* Disqualify LHS and RHS for scalarization if STMT must end its basic block in |
| modes in which it matters, return true iff they have been disqualified. RHS |
| may be NULL, in that case ignore it. If we scalarize an aggregate in |
| intra-SRA we may need to add statements after each statement. This is not |
| possible if a statement unconditionally has to end the basic block. */ |
| static bool |
| disqualify_ops_if_throwing_stmt (gimple stmt, tree lhs, tree rhs) |
| { |
| if ((sra_mode == SRA_MODE_EARLY_INTRA || sra_mode == SRA_MODE_INTRA) |
| && (stmt_can_throw_internal (stmt) || stmt_ends_bb_p (stmt))) |
| { |
| disqualify_base_of_expr (lhs, "LHS of a throwing stmt."); |
| if (rhs) |
| disqualify_base_of_expr (rhs, "RHS of a throwing stmt."); |
| return true; |
| } |
| return false; |
| } |
| |
| /* Scan expressions occurring in STMT, create access structures for all accesses |
| to candidates for scalarization and remove those candidates which occur in |
| statements or expressions that prevent them from being split apart. Return |
| true if any access has been inserted. */ |
| |
| static bool |
| build_accesses_from_assign (gimple stmt) |
| { |
| tree lhs, rhs; |
| struct access *lacc, *racc; |
| |
| if (!gimple_assign_single_p (stmt) |
| /* Scope clobbers don't influence scalarization. */ |
| || gimple_clobber_p (stmt)) |
| return false; |
| |
| lhs = gimple_assign_lhs (stmt); |
| rhs = gimple_assign_rhs1 (stmt); |
| |
| if (disqualify_ops_if_throwing_stmt (stmt, lhs, rhs)) |
| return false; |
| |
| racc = build_access_from_expr_1 (rhs, stmt, false); |
| lacc = build_access_from_expr_1 (lhs, stmt, true); |
| |
| if (lacc) |
| lacc->grp_assignment_write = 1; |
| |
| if (racc) |
| { |
| racc->grp_assignment_read = 1; |
| if (should_scalarize_away_bitmap && !gimple_has_volatile_ops (stmt) |
| && !is_gimple_reg_type (racc->type)) |
| bitmap_set_bit (should_scalarize_away_bitmap, DECL_UID (racc->base)); |
| } |
| |
| if (lacc && racc |
| && (sra_mode == SRA_MODE_EARLY_INTRA || sra_mode == SRA_MODE_INTRA) |
| && !lacc->grp_unscalarizable_region |
| && !racc->grp_unscalarizable_region |
| && AGGREGATE_TYPE_P (TREE_TYPE (lhs)) |
| && lacc->size == racc->size |
| && useless_type_conversion_p (lacc->type, racc->type)) |
| { |
| struct assign_link *link; |
| |
| link = (struct assign_link *) pool_alloc (link_pool); |
| memset (link, 0, sizeof (struct assign_link)); |
| |
| link->lacc = lacc; |
| link->racc = racc; |
| |
| add_link_to_rhs (racc, link); |
| } |
| |
| return lacc || racc; |
| } |
| |
| /* Callback of walk_stmt_load_store_addr_ops visit_addr used to determine |
| GIMPLE_ASM operands with memory constrains which cannot be scalarized. */ |
| |
| static bool |
| asm_visit_addr (gimple stmt ATTRIBUTE_UNUSED, tree op, |
| void *data ATTRIBUTE_UNUSED) |
| { |
| op = get_base_address (op); |
| if (op |
| && DECL_P (op)) |
| disqualify_candidate (op, "Non-scalarizable GIMPLE_ASM operand."); |
| |
| return false; |
| } |
| |
| /* Return true iff callsite CALL has at least as many actual arguments as there |
| are formal parameters of the function currently processed by IPA-SRA. */ |
| |
| static inline bool |
| callsite_has_enough_arguments_p (gimple call) |
| { |
| return gimple_call_num_args (call) >= (unsigned) func_param_count; |
| } |
| |
| /* Scan function and look for interesting expressions and create access |
| structures for them. Return true iff any access is created. */ |
| |
| static bool |
| scan_function (void) |
| { |
| basic_block bb; |
| bool ret = false; |
| |
| FOR_EACH_BB (bb) |
| { |
| gimple_stmt_iterator gsi; |
| for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| gimple stmt = gsi_stmt (gsi); |
| tree t; |
| unsigned i; |
| |
| if (final_bbs && stmt_can_throw_external (stmt)) |
| bitmap_set_bit (final_bbs, bb->index); |
| switch (gimple_code (stmt)) |
| { |
| case GIMPLE_RETURN: |
| t = gimple_return_retval (stmt); |
| if (t != NULL_TREE) |
| ret |= build_access_from_expr (t, stmt, false); |
| if (final_bbs) |
| bitmap_set_bit (final_bbs, bb->index); |
| break; |
| |
| case GIMPLE_ASSIGN: |
| ret |= build_accesses_from_assign (stmt); |
| break; |
| |
| case GIMPLE_CALL: |
| for (i = 0; i < gimple_call_num_args (stmt); i++) |
| ret |= build_access_from_expr (gimple_call_arg (stmt, i), |
| stmt, false); |
| |
| if (sra_mode == SRA_MODE_EARLY_IPA) |
| { |
| tree dest = gimple_call_fndecl (stmt); |
| int flags = gimple_call_flags (stmt); |
| |
| if (dest) |
| { |
| if (DECL_BUILT_IN_CLASS (dest) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (dest) == BUILT_IN_APPLY_ARGS) |
| encountered_apply_args = true; |
| if (cgraph_get_node (dest) |
| == cgraph_get_node (current_function_decl)) |
| { |
| encountered_recursive_call = true; |
| if (!callsite_has_enough_arguments_p (stmt)) |
| encountered_unchangable_recursive_call = true; |
| } |
| } |
| |
| if (final_bbs |
| && (flags & (ECF_CONST | ECF_PURE)) == 0) |
| bitmap_set_bit (final_bbs, bb->index); |
| } |
| |
| t = gimple_call_lhs (stmt); |
| if (t && !disqualify_ops_if_throwing_stmt (stmt, t, NULL)) |
| ret |= build_access_from_expr (t, stmt, true); |
| break; |
| |
| case GIMPLE_ASM: |
| walk_stmt_load_store_addr_ops (stmt, NULL, NULL, NULL, |
| asm_visit_addr); |
| if (final_bbs) |
| bitmap_set_bit (final_bbs, bb->index); |
| |
| for (i = 0; i < gimple_asm_ninputs (stmt); i++) |
| { |
| t = TREE_VALUE (gimple_asm_input_op (stmt, i)); |
| ret |= build_access_from_expr (t, stmt, false); |
| } |
| for (i = 0; i < gimple_asm_noutputs (stmt); i++) |
| { |
| t = TREE_VALUE (gimple_asm_output_op (stmt, i)); |
| ret |= build_access_from_expr (t, stmt, true); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* Helper of QSORT function. There are pointers to accesses in the array. An |
| access is considered smaller than another if it has smaller offset or if the |
| offsets are the same but is size is bigger. */ |
| |
| static int |
| compare_access_positions (const void *a, const void *b) |
| { |
| const access_p *fp1 = (const access_p *) a; |
| const access_p *fp2 = (const access_p *) b; |
| const access_p f1 = *fp1; |
| const access_p f2 = *fp2; |
| |
| if (f1->offset != f2->offset) |
| return f1->offset < f2->offset ? -1 : 1; |
| |
| if (f1->size == f2->size) |
| { |
| if (f1->type == f2->type) |
| return 0; |
| /* Put any non-aggregate type before any aggregate type. */ |
| else if (!is_gimple_reg_type (f1->type) |
| && is_gimple_reg_type (f2->type)) |
| return 1; |
| else if (is_gimple_reg_type (f1->type) |
| && !is_gimple_reg_type (f2->type)) |
| return -1; |
| /* Put any complex or vector type before any other scalar type. */ |
| else if (TREE_CODE (f1->type) != COMPLEX_TYPE |
| && TREE_CODE (f1->type) != VECTOR_TYPE |
| && (TREE_CODE (f2->type) == COMPLEX_TYPE |
| || TREE_CODE (f2->type) == VECTOR_TYPE)) |
| return 1; |
| else if ((TREE_CODE (f1->type) == COMPLEX_TYPE |
| || TREE_CODE (f1->type) == VECTOR_TYPE) |
| && TREE_CODE (f2->type) != COMPLEX_TYPE |
| && TREE_CODE (f2->type) != VECTOR_TYPE) |
| return -1; |
| /* Put the integral type with the bigger precision first. */ |
| else if (INTEGRAL_TYPE_P (f1->type) |
| && INTEGRAL_TYPE_P (f2->type)) |
| return TYPE_PRECISION (f2->type) - TYPE_PRECISION (f1->type); |
| /* Put any integral type with non-full precision last. */ |
| else if (INTEGRAL_TYPE_P (f1->type) |
| && (TREE_INT_CST_LOW (TYPE_SIZE (f1->type)) |
| != TYPE_PRECISION (f1->type))) |
| return 1; |
| else if (INTEGRAL_TYPE_P (f2->type) |
| && (TREE_INT_CST_LOW (TYPE_SIZE (f2->type)) |
| != TYPE_PRECISION (f2->type))) |
| return -1; |
| /* Stabilize the sort. */ |
| return TYPE_UID (f1->type) - TYPE_UID (f2->type); |
| } |
| |
| /* We want the bigger accesses first, thus the opposite operator in the next |
| line: */ |
| return f1->size > f2->size ? -1 : 1; |
| } |
| |
| |
| /* Append a name of the declaration to the name obstack. A helper function for |
| make_fancy_name. */ |
| |
| static void |
| make_fancy_decl_name (tree decl) |
| { |
| char buffer[32]; |
| |
| tree name = DECL_NAME (decl); |
| if (name) |
| obstack_grow (&name_obstack, IDENTIFIER_POINTER (name), |
| IDENTIFIER_LENGTH (name)); |
| else |
| { |
| sprintf (buffer, "D%u", DECL_UID (decl)); |
| obstack_grow (&name_obstack, buffer, strlen (buffer)); |
| } |
| } |
| |
| /* Helper for make_fancy_name. */ |
| |
| static void |
| make_fancy_name_1 (tree expr) |
| { |
| char buffer[32]; |
| tree index; |
| |
| if (DECL_P (expr)) |
| { |
| make_fancy_decl_name (expr); |
| return; |
| } |
| |
| switch (TREE_CODE (expr)) |
| { |
| case COMPONENT_REF: |
| make_fancy_name_1 (TREE_OPERAND (expr, 0)); |
| obstack_1grow (&name_obstack, '$'); |
| make_fancy_decl_name (TREE_OPERAND (expr, 1)); |
| break; |
| |
| case ARRAY_REF: |
| make_fancy_name_1 (TREE_OPERAND (expr, 0)); |
| obstack_1grow (&name_obstack, '$'); |
| /* Arrays with only one element may not have a constant as their |
| index. */ |
| index = TREE_OPERAND (expr, 1); |
| if (TREE_CODE (index) != INTEGER_CST) |
| break; |
| sprintf (buffer, HOST_WIDE_INT_PRINT_DEC, TREE_INT_CST_LOW (index)); |
| obstack_grow (&name_obstack, buffer, strlen (buffer)); |
| break; |
| |
| case ADDR_EXPR: |
| make_fancy_name_1 (TREE_OPERAND (expr, 0)); |
| break; |
| |
| case MEM_REF: |
| make_fancy_name_1 (TREE_OPERAND (expr, 0)); |
| if (!integer_zerop (TREE_OPERAND (expr, 1))) |
| { |
| obstack_1grow (&name_obstack, '$'); |
| sprintf (buffer, HOST_WIDE_INT_PRINT_DEC, |
| TREE_INT_CST_LOW (TREE_OPERAND (expr, 1))); |
| obstack_grow (&name_obstack, buffer, strlen (buffer)); |
| } |
| break; |
| |
| case BIT_FIELD_REF: |
| case REALPART_EXPR: |
| case IMAGPART_EXPR: |
| gcc_unreachable (); /* we treat these as scalars. */ |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* Create a human readable name for replacement variable of ACCESS. */ |
| |
| static char * |
| make_fancy_name (tree expr) |
| { |
| make_fancy_name_1 (expr); |
| obstack_1grow (&name_obstack, '\0'); |
| return XOBFINISH (&name_obstack, char *); |
| } |
| |
| /* Construct a MEM_REF that would reference a part of aggregate BASE of type |
| EXP_TYPE at the given OFFSET. If BASE is something for which |
| get_addr_base_and_unit_offset returns NULL, gsi must be non-NULL and is used |
| to insert new statements either before or below the current one as specified |
| by INSERT_AFTER. This function is not capable of handling bitfields. |
| |
| BASE must be either a declaration or a memory reference that has correct |
| alignment ifformation embeded in it (e.g. a pre-existing one in SRA). */ |
| |
| tree |
| build_ref_for_offset (location_t loc, tree base, HOST_WIDE_INT offset, |
| tree exp_type, gimple_stmt_iterator *gsi, |
| bool insert_after) |
| { |
| tree prev_base = base; |
| tree off; |
| tree mem_ref; |
| HOST_WIDE_INT base_offset; |
| unsigned HOST_WIDE_INT misalign; |
| unsigned int align; |
| |
| gcc_checking_assert (offset % BITS_PER_UNIT == 0); |
| get_object_alignment_1 (base, &align, &misalign); |
| base = get_addr_base_and_unit_offset (base, &base_offset); |
| |
| /* get_addr_base_and_unit_offset returns NULL for references with a variable |
| offset such as array[var_index]. */ |
| if (!base) |
| { |
| gimple stmt; |
| tree tmp, addr; |
| |
| gcc_checking_assert (gsi); |
| tmp = make_ssa_name (build_pointer_type (TREE_TYPE (prev_base)), NULL); |
| addr = build_fold_addr_expr (unshare_expr (prev_base)); |
| STRIP_USELESS_TYPE_CONVERSION (addr); |
| stmt = gimple_build_assign (tmp, addr); |
| gimple_set_location (stmt, loc); |
| if (insert_after) |
| gsi_insert_after (gsi, stmt, GSI_NEW_STMT); |
| else |
| gsi_insert_before (gsi, stmt, GSI_SAME_STMT); |
| |
| off = build_int_cst (reference_alias_ptr_type (prev_base), |
| offset / BITS_PER_UNIT); |
| base = tmp; |
| } |
| else if (TREE_CODE (base) == MEM_REF) |
| { |
| off = build_int_cst (TREE_TYPE (TREE_OPERAND (base, 1)), |
| base_offset + offset / BITS_PER_UNIT); |
| off = int_const_binop (PLUS_EXPR, TREE_OPERAND (base, 1), off); |
| base = unshare_expr (TREE_OPERAND (base, 0)); |
| } |
| else |
| { |
| off = build_int_cst (reference_alias_ptr_type (base), |
| base_offset + offset / BITS_PER_UNIT); |
| base = build_fold_addr_expr (unshare_expr (base)); |
| } |
| |
| misalign = (misalign + offset) & (align - 1); |
| if (misalign != 0) |
| align = (misalign & -misalign); |
| if (align < TYPE_ALIGN (exp_type)) |
| exp_type = build_aligned_type (exp_type, align); |
| |
| mem_ref = fold_build2_loc (loc, MEM_REF, exp_type, base, off); |
| if (TREE_THIS_VOLATILE (prev_base)) |
| TREE_THIS_VOLATILE (mem_ref) = 1; |
| if (TREE_SIDE_EFFECTS (prev_base)) |
| TREE_SIDE_EFFECTS (mem_ref) = 1; |
| return mem_ref; |
| } |
| |
| /* Construct a memory reference to a part of an aggregate BASE at the given |
| OFFSET and of the same type as MODEL. In case this is a reference to a |
| bit-field, the function will replicate the last component_ref of model's |
| expr to access it. GSI and INSERT_AFTER have the same meaning as in |
| build_ref_for_offset. */ |
| |
| static tree |
| build_ref_for_model (location_t loc, tree base, HOST_WIDE_INT offset, |
| struct access *model, gimple_stmt_iterator *gsi, |
| bool insert_after) |
| { |
| if (TREE_CODE (model->expr) == COMPONENT_REF |
| && DECL_BIT_FIELD (TREE_OPERAND (model->expr, 1))) |
| { |
| /* This access represents a bit-field. */ |
| tree t, exp_type, fld = TREE_OPERAND (model->expr, 1); |
| |
| offset -= int_bit_position (fld); |
| exp_type = TREE_TYPE (TREE_OPERAND (model->expr, 0)); |
| t = build_ref_for_offset (loc, base, offset, exp_type, gsi, insert_after); |
| return fold_build3_loc (loc, COMPONENT_REF, TREE_TYPE (fld), t, fld, |
| NULL_TREE); |
| } |
| else |
| return build_ref_for_offset (loc, base, offset, model->type, |
| gsi, insert_after); |
| } |
| |
| /* Attempt to build a memory reference that we could but into a gimple |
| debug_bind statement. Similar to build_ref_for_model but punts if it has to |
| create statements and return s NULL instead. This function also ignores |
| alignment issues and so its results should never end up in non-debug |
| statements. */ |
| |
| static tree |
| build_debug_ref_for_model (location_t loc, tree base, HOST_WIDE_INT offset, |
| struct access *model) |
| { |
| HOST_WIDE_INT base_offset; |
| tree off; |
| |
| if (TREE_CODE (model->expr) == COMPONENT_REF |
| && DECL_BIT_FIELD (TREE_OPERAND (model->expr, 1))) |
| return NULL_TREE; |
| |
| base = get_addr_base_and_unit_offset (base, &base_offset); |
| if (!base) |
| return NULL_TREE; |
| if (TREE_CODE (base) == MEM_REF) |
| { |
| off = build_int_cst (TREE_TYPE (TREE_OPERAND (base, 1)), |
| base_offset + offset / BITS_PER_UNIT); |
| off = int_const_binop (PLUS_EXPR, TREE_OPERAND (base, 1), off); |
| base = unshare_expr (TREE_OPERAND (base, 0)); |
| } |
| else |
| { |
| off = build_int_cst (reference_alias_ptr_type (base), |
| base_offset + offset / BITS_PER_UNIT); |
| base = build_fold_addr_expr (unshare_expr (base)); |
| } |
| |
| return fold_build2_loc (loc, MEM_REF, model->type, base, off); |
| } |
| |
| /* Construct a memory reference consisting of component_refs and array_refs to |
| a part of an aggregate *RES (which is of type TYPE). The requested part |
| should have type EXP_TYPE at be the given OFFSET. This function might not |
| succeed, it returns true when it does and only then *RES points to something |
| meaningful. This function should be used only to build expressions that we |
| might need to present to user (e.g. in warnings). In all other situations, |
| build_ref_for_model or build_ref_for_offset should be used instead. */ |
| |
| static bool |
| build_user_friendly_ref_for_offset (tree *res, tree type, HOST_WIDE_INT offset, |
| tree exp_type) |
| { |
| while (1) |
| { |
| tree fld; |
| tree tr_size, index, minidx; |
| HOST_WIDE_INT el_size; |
| |
| if (offset == 0 && exp_type |
| && types_compatible_p (exp_type, type)) |
| return true; |
| |
| switch (TREE_CODE (type)) |
| { |
| case UNION_TYPE: |
| case QUAL_UNION_TYPE: |
| case RECORD_TYPE: |
| for (fld = TYPE_FIELDS (type); fld; fld = DECL_CHAIN (fld)) |
| { |
| HOST_WIDE_INT pos, size; |
| tree tr_pos, expr, *expr_ptr; |
| |
| if (TREE_CODE (fld) != FIELD_DECL) |
| continue; |
| |
| tr_pos = bit_position (fld); |
| if (!tr_pos || !host_integerp (tr_pos, 1)) |
| continue; |
| pos = TREE_INT_CST_LOW (tr_pos); |
| gcc_assert (TREE_CODE (type) == RECORD_TYPE || pos == 0); |
| tr_size = DECL_SIZE (fld); |
| if (!tr_size || !host_integerp (tr_size, 1)) |
| continue; |
| size = TREE_INT_CST_LOW (tr_size); |
| if (size == 0) |
| { |
| if (pos != offset) |
| continue; |
| } |
| else if (pos > offset || (pos + size) <= offset) |
| continue; |
| |
| expr = build3 (COMPONENT_REF, TREE_TYPE (fld), *res, fld, |
| NULL_TREE); |
| expr_ptr = &expr; |
| if (build_user_friendly_ref_for_offset (expr_ptr, TREE_TYPE (fld), |
| offset - pos, exp_type)) |
| { |
| *res = expr; |
| return true; |
| } |
| } |
| return false; |
| |
| case ARRAY_TYPE: |
| tr_size = TYPE_SIZE (TREE_TYPE (type)); |
| if (!tr_size || !host_integerp (tr_size, 1)) |
| return false; |
| el_size = tree_low_cst (tr_size, 1); |
| |
| minidx = TYPE_MIN_VALUE (TYPE_DOMAIN (type)); |
| if (TREE_CODE (minidx) != INTEGER_CST || el_size == 0) |
| return false; |
| index = build_int_cst (TYPE_DOMAIN (type), offset / el_size); |
| if (!integer_zerop (minidx)) |
| index = int_const_binop (PLUS_EXPR, index, minidx); |
| *res = build4 (ARRAY_REF, TREE_TYPE (type), *res, index, |
| NULL_TREE, NULL_TREE); |
| offset = offset % el_size; |
| type = TREE_TYPE (type); |
| break; |
| |
| default: |
| if (offset != 0) |
| return false; |
| |
| if (exp_type) |
| return false; |
| else |
| return true; |
| } |
| } |
| } |
| |
| /* Return true iff TYPE is stdarg va_list type. */ |
| |
| static inline bool |
| is_va_list_type (tree type) |
| { |
| return TYPE_MAIN_VARIANT (type) == TYPE_MAIN_VARIANT (va_list_type_node); |
| } |
| |
| /* Print message to dump file why a variable was rejected. */ |
| |
| static void |
| reject (tree var, const char *msg) |
| { |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Rejected (%d): %s: ", DECL_UID (var), msg); |
| print_generic_expr (dump_file, var, 0); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| /* Return true if VAR is a candidate for SRA. */ |
| |
| static bool |
| maybe_add_sra_candidate (tree var) |
| { |
| tree type = TREE_TYPE (var); |
| const char *msg; |
| void **slot; |
| |
| if (!AGGREGATE_TYPE_P (type)) |
| { |
| reject (var, "not aggregate"); |
| return false; |
| } |
| if (needs_to_live_in_memory (var)) |
| { |
| reject (var, "needs to live in memory"); |
| return false; |
| } |
| if (TREE_THIS_VOLATILE (var)) |
| { |
| reject (var, "is volatile"); |
| return false; |
| } |
| if (!COMPLETE_TYPE_P (type)) |
| { |
| reject (var, "has incomplete type"); |
| return false; |
| } |
| if (!host_integerp (TYPE_SIZE (type), 1)) |
| { |
| reject (var, "type size not fixed"); |
| return false; |
| } |
| if (tree_low_cst (TYPE_SIZE (type), 1) == 0) |
| { |
| reject (var, "type size is zero"); |
| return false; |
| } |
| if (type_internals_preclude_sra_p (type, &msg)) |
| { |
| reject (var, msg); |
| return false; |
| } |
| if (/* Fix for PR 41089. tree-stdarg.c needs to have va_lists intact but |
| we also want to schedule it rather late. Thus we ignore it in |
| the early pass. */ |
| (sra_mode == SRA_MODE_EARLY_INTRA |
| && is_va_list_type (type))) |
| { |
| reject (var, "is va_list"); |
| return false; |
| } |
| |
| bitmap_set_bit (candidate_bitmap, DECL_UID (var)); |
| slot = htab_find_slot_with_hash (candidates, var, DECL_UID (var), INSERT); |
| *slot = (void *) var; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Candidate (%d): ", DECL_UID (var)); |
| print_generic_expr (dump_file, var, 0); |
| fprintf (dump_file, "\n"); |
| } |
| |
| return true; |
| } |
| |
| /* The very first phase of intraprocedural SRA. It marks in candidate_bitmap |
| those with type which is suitable for scalarization. */ |
| |
| static bool |
| find_var_candidates (void) |
| { |
| tree var, parm; |
| unsigned int i; |
| bool ret = false; |
| |
| for (parm = DECL_ARGUMENTS (current_function_decl); |
| parm; |
| parm = DECL_CHAIN (parm)) |
| ret |= maybe_add_sra_candidate (parm); |
| |
| FOR_EACH_LOCAL_DECL (cfun, i, var) |
| { |
| if (TREE_CODE (var) != VAR_DECL) |
| continue; |
| |
| ret |= maybe_add_sra_candidate (var); |
| } |
| |
| return ret; |
| } |
| |
| /* Sort all accesses for the given variable, check for partial overlaps and |
| return NULL if there are any. If there are none, pick a representative for |
| each combination of offset and size and create a linked list out of them. |
| Return the pointer to the first representative and make sure it is the first |
| one in the vector of accesses. */ |
| |
| static struct access * |
| sort_and_splice_var_accesses (tree var) |
| { |
| int i, j, access_count; |
| struct access *res, **prev_acc_ptr = &res; |
| vec<access_p> *access_vec; |
| bool first = true; |
| HOST_WIDE_INT low = -1, high = 0; |
| |
| access_vec = get_base_access_vector (var); |
| if (!access_vec) |
| return NULL; |
| access_count = access_vec->length (); |
| |
| /* Sort by <OFFSET, SIZE>. */ |
| access_vec->qsort (compare_access_positions); |
| |
| i = 0; |
| while (i < access_count) |
| { |
| struct access *access = (*access_vec)[i]; |
| bool grp_write = access->write; |
| bool grp_read = !access->write; |
| bool grp_scalar_write = access->write |
| && is_gimple_reg_type (access->type); |
| bool grp_scalar_read = !access->write |
| && is_gimple_reg_type (access->type); |
| bool grp_assignment_read = access->grp_assignment_read; |
| bool grp_assignment_write = access->grp_assignment_write; |
| bool multiple_scalar_reads = false; |
| bool total_scalarization = access->grp_total_scalarization; |
| bool grp_partial_lhs = access->grp_partial_lhs; |
| bool first_scalar = is_gimple_reg_type (access->type); |
| bool unscalarizable_region = access->grp_unscalarizable_region; |
| |
| if (first || access->offset >= high) |
| { |
| first = false; |
| low = access->offset; |
| high = access->offset + access->size; |
| } |
| else if (access->offset > low && access->offset + access->size > high) |
| return NULL; |
| else |
| gcc_assert (access->offset >= low |
| && access->offset + access->size <= high); |
| |
| j = i + 1; |
| while (j < access_count) |
| { |
| struct access *ac2 = (*access_vec)[j]; |
| if (ac2->offset != access->offset || ac2->size != access->size) |
| break; |
| if (ac2->write) |
| { |
| grp_write = true; |
| grp_scalar_write = (grp_scalar_write |
| || is_gimple_reg_type (ac2->type)); |
| } |
| else |
| { |
| grp_read = true; |
| if (is_gimple_reg_type (ac2->type)) |
| { |
| if (grp_scalar_read) |
| multiple_scalar_reads = true; |
| else |
| grp_scalar_read = true; |
| } |
| } |
| grp_assignment_read |= ac2->grp_assignment_read; |
| grp_assignment_write |= ac2->grp_assignment_write; |
| grp_partial_lhs |= ac2->grp_partial_lhs; |
| unscalarizable_region |= ac2->grp_unscalarizable_region; |
| total_scalarization |= ac2->grp_total_scalarization; |
| relink_to_new_repr (access, ac2); |
| |
| /* If there are both aggregate-type and scalar-type accesses with |
| this combination of size and offset, the comparison function |
| should have put the scalars first. */ |
| gcc_assert (first_scalar || !is_gimple_reg_type (ac2->type)); |
| ac2->group_representative = access; |
| j++; |
| } |
| |
| i = j; |
| |
| access->group_representative = access; |
| access->grp_write = grp_write; |
| access->grp_read = grp_read; |
| access->grp_scalar_read = grp_scalar_read; |
| access->grp_scalar_write = grp_scalar_write; |
| access->grp_assignment_read = grp_assignment_read; |
| access->grp_assignment_write = grp_assignment_write; |
| access->grp_hint = multiple_scalar_reads || total_scalarization; |
| access->grp_total_scalarization = total_scalarization; |
| access->grp_partial_lhs = grp_partial_lhs; |
| access->grp_unscalarizable_region = unscalarizable_region; |
| if (access->first_link) |
| add_access_to_work_queue (access); |
| |
| *prev_acc_ptr = access; |
| prev_acc_ptr = &access->next_grp; |
| } |
| |
| gcc_assert (res == (*access_vec)[0]); |
| return res; |
| } |
| |
| /* Create a variable for the given ACCESS which determines the type, name and a |
| few other properties. Return the variable declaration and store it also to |
| ACCESS->replacement. */ |
| |
| static tree |
| create_access_replacement (struct access *access) |
| { |
| tree repl; |
| |
| if (access->grp_to_be_debug_replaced) |
| { |
| repl = create_tmp_var_raw (access->type, NULL); |
| DECL_CONTEXT (repl) = current_function_decl; |
| } |
| else |
| repl = create_tmp_var (access->type, "SR"); |
| if (TREE_CODE (access->type) == COMPLEX_TYPE |
| || TREE_CODE (access->type) == VECTOR_TYPE) |
| { |
| if (!access->grp_partial_lhs) |
| DECL_GIMPLE_REG_P (repl) = 1; |
| } |
| else if (access->grp_partial_lhs |
| && is_gimple_reg_type (access->type)) |
| TREE_ADDRESSABLE (repl) = 1; |
| |
| DECL_SOURCE_LOCATION (repl) = DECL_SOURCE_LOCATION (access->base); |
| DECL_ARTIFICIAL (repl) = 1; |
| DECL_IGNORED_P (repl) = DECL_IGNORED_P (access->base); |
| |
| if (DECL_NAME (access->base) |
| && !DECL_IGNORED_P (access->base) |
| && !DECL_ARTIFICIAL (access->base)) |
| { |
| char *pretty_name = make_fancy_name (access->expr); |
| tree debug_expr = unshare_expr_without_location (access->expr), d; |
| bool fail = false; |
| |
| DECL_NAME (repl) = get_identifier (pretty_name); |
| obstack_free (&name_obstack, pretty_name); |
| |
| /* Get rid of any SSA_NAMEs embedded in debug_expr, |
| as DECL_DEBUG_EXPR isn't considered when looking for still |
| used SSA_NAMEs and thus they could be freed. All debug info |
| generation cares is whether something is constant or variable |
| and that get_ref_base_and_extent works properly on the |
| expression. It cannot handle accesses at a non-constant offset |
| though, so just give up in those cases. */ |
| for (d = debug_expr; |
| !fail && (handled_component_p (d) || TREE_CODE (d) == MEM_REF); |
| d = TREE_OPERAND (d, 0)) |
| switch (TREE_CODE (d)) |
| { |
| case ARRAY_REF: |
| case ARRAY_RANGE_REF: |
| if (TREE_OPERAND (d, 1) |
| && TREE_CODE (TREE_OPERAND (d, 1)) != INTEGER_CST) |
| fail = true; |
| if (TREE_OPERAND (d, 3) |
| && TREE_CODE (TREE_OPERAND (d, 3)) != INTEGER_CST) |
| fail = true; |
| /* FALLTHRU */ |
| case COMPONENT_REF: |
| if (TREE_OPERAND (d, 2) |
| && TREE_CODE (TREE_OPERAND (d, 2)) != INTEGER_CST) |
| fail = true; |
| break; |
| case MEM_REF: |
| if (TREE_CODE (TREE_OPERAND (d, 0)) != ADDR_EXPR) |
| fail = true; |
| else |
| d = TREE_OPERAND (d, 0); |
| break; |
| default: |
| break; |
| } |
| if (!fail) |
| { |
| SET_DECL_DEBUG_EXPR (repl, debug_expr); |
| DECL_DEBUG_EXPR_IS_FROM (repl) = 1; |
| } |
| if (access->grp_no_warning) |
| TREE_NO_WARNING (repl) = 1; |
| else |
| TREE_NO_WARNING (repl) = TREE_NO_WARNING (access->base); |
| } |
| else |
| TREE_NO_WARNING (repl) = 1; |
| |
| if (dump_file) |
| { |
| if (access->grp_to_be_debug_replaced) |
| { |
| fprintf (dump_file, "Created a debug-only replacement for "); |
| print_generic_expr (dump_file, access->base, 0); |
| fprintf (dump_file, " offset: %u, size: %u\n", |
| (unsigned) access->offset, (unsigned) access->size); |
| } |
| else |
| { |
| fprintf (dump_file, "Created a replacement for "); |
| print_generic_expr (dump_file, access->base, 0); |
| fprintf (dump_file, " offset: %u, size: %u: ", |
| (unsigned) access->offset, (unsigned) access->size); |
| print_generic_expr (dump_file, repl, 0); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| sra_stats.replacements++; |
| |
| return repl; |
| } |
| |
| /* Return ACCESS scalar replacement, create it if it does not exist yet. */ |
| |
| static inline tree |
| get_access_replacement (struct access *access) |
| { |
| gcc_checking_assert (access->replacement_decl); |
| return access->replacement_decl; |
| } |
| |
| |
| /* Build a subtree of accesses rooted in *ACCESS, and move the pointer in the |
| linked list along the way. Stop when *ACCESS is NULL or the access pointed |
| to it is not "within" the root. Return false iff some accesses partially |
| overlap. */ |
| |
| static bool |
| build_access_subtree (struct access **access) |
| { |
| struct access *root = *access, *last_child = NULL; |
| HOST_WIDE_INT limit = root->offset + root->size; |
| |
| *access = (*access)->next_grp; |
| while (*access && (*access)->offset + (*access)->size <= limit) |
| { |
| if (!last_child) |
| root->first_child = *access; |
| else |
| last_child->next_sibling = *access; |
| last_child = *access; |
| |
| if (!build_access_subtree (access)) |
| return false; |
| } |
| |
| if (*access && (*access)->offset < limit) |
| return false; |
| |
| return true; |
| } |
| |
| /* Build a tree of access representatives, ACCESS is the pointer to the first |
| one, others are linked in a list by the next_grp field. Return false iff |
| some accesses partially overlap. */ |
| |
| static bool |
| build_access_trees (struct access *access) |
| { |
| while (access) |
| { |
| struct access *root = access; |
| |
| if (!build_access_subtree (&access)) |
| return false; |
| root->next_grp = access; |
| } |
| return true; |
| } |
| |
| /* Return true if expr contains some ARRAY_REFs into a variable bounded |
| array. */ |
| |
| static bool |
| expr_with_var_bounded_array_refs_p (tree expr) |
| { |
| while (handled_component_p (expr)) |
| { |
| if (TREE_CODE (expr) == ARRAY_REF |
| && !host_integerp (array_ref_low_bound (expr), 0)) |
| return true; |
| expr = TREE_OPERAND (expr, 0); |
| } |
| return false; |
| } |
| |
| /* Analyze the subtree of accesses rooted in ROOT, scheduling replacements when |
| both seeming beneficial and when ALLOW_REPLACEMENTS allows it. Also set all |
| sorts of access flags appropriately along the way, notably always set |
| grp_read and grp_assign_read according to MARK_READ and grp_write when |
| MARK_WRITE is true. |
| |
| Creating a replacement for a scalar access is considered beneficial if its |
| grp_hint is set (this means we are either attempting total scalarization or |
| there is more than one direct read access) or according to the following |
| table: |
| |
| Access written to through a scalar type (once or more times) |
| | |
| | Written to in an assignment statement |
| | | |
| | | Access read as scalar _once_ |
| | | | |
| | | | Read in an assignment statement |
| | | | | |
| | | | | Scalarize Comment |
| ----------------------------------------------------------------------------- |
| 0 0 0 0 No access for the scalar |
| 0 0 0 1 No access for the scalar |
| 0 0 1 0 No Single read - won't help |
| 0 0 1 1 No The same case |
| 0 1 0 0 No access for the scalar |
| 0 1 0 1 No access for the scalar |
| 0 1 1 0 Yes s = *g; return s.i; |
| 0 1 1 1 Yes The same case as above |
| 1 0 0 0 No Won't help |
| 1 0 0 1 Yes s.i = 1; *g = s; |
| 1 0 1 0 Yes s.i = 5; g = s.i; |
| 1 0 1 1 Yes The same case as above |
| 1 1 0 0 No Won't help. |
| 1 1 0 1 Yes s.i = 1; *g = s; |
| 1 1 1 0 Yes s = *g; return s.i; |
| 1 1 1 1 Yes Any of the above yeses */ |
| |
| static bool |
| analyze_access_subtree (struct access *root, struct access *parent, |
| bool allow_replacements) |
| { |
| struct access *child; |
| HOST_WIDE_INT limit = root->offset + root->size; |
| HOST_WIDE_INT covered_to = root->offset; |
| bool scalar = is_gimple_reg_type (root->type); |
| bool hole = false, sth_created = false; |
| |
| if (parent) |
| { |
| if (parent->grp_read) |
| root->grp_read = 1; |
| if (parent->grp_assignment_read) |
| root->grp_assignment_read = 1; |
| if (parent->grp_write) |
| root->grp_write = 1; |
| if (parent->grp_assignment_write) |
| root->grp_assignment_write = 1; |
| if (parent->grp_total_scalarization) |
| root->grp_total_scalarization = 1; |
| } |
| |
| if (root->grp_unscalarizable_region) |
| allow_replacements = false; |
| |
| if (allow_replacements && expr_with_var_bounded_array_refs_p (root->expr)) |
| allow_replacements = false; |
| |
| for (child = root->first_child; child; child = child->next_sibling) |
| { |
| hole |= covered_to < child->offset; |
| sth_created |= analyze_access_subtree (child, root, |
| allow_replacements && !scalar); |
| |
| root->grp_unscalarized_data |= child->grp_unscalarized_data; |
| root->grp_total_scalarization &= child->grp_total_scalarization; |
| if (child->grp_covered) |
| covered_to += child->size; |
| else |
| hole = true; |
| } |
| |
| if (allow_replacements && scalar && !root->first_child |
| && (root->grp_hint |
| || ((root->grp_scalar_read || root->grp_assignment_read) |
| && (root->grp_scalar_write || root->grp_assignment_write)))) |
| { |
| /* Always create access replacements that cover the whole access. |
| For integral types this means the precision has to match. |
| Avoid assumptions based on the integral type kind, too. */ |
| if (INTEGRAL_TYPE_P (root->type) |
| && (TREE_CODE (root->type) != INTEGER_TYPE |
| || TYPE_PRECISION (root->type) != root->size) |
| /* But leave bitfield accesses alone. */ |
| && (TREE_CODE (root->expr) != COMPONENT_REF |
| || !DECL_BIT_FIELD (TREE_OPERAND (root->expr, 1)))) |
| { |
| tree rt = root->type; |
| gcc_assert ((root->offset % BITS_PER_UNIT) == 0 |
| && (root->size % BITS_PER_UNIT) == 0); |
| root->type = build_nonstandard_integer_type (root->size, |
| TYPE_UNSIGNED (rt)); |
| root->expr = build_ref_for_offset (UNKNOWN_LOCATION, |
| root->base, root->offset, |
| root->type, NULL, false); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Changing the type of a replacement for "); |
| print_generic_expr (dump_file, root->base, 0); |
| fprintf (dump_file, " offset: %u, size: %u ", |
| (unsigned) root->offset, (unsigned) root->size); |
| fprintf (dump_file, " to an integer.\n"); |
| } |
| } |
| |
| root->grp_to_be_replaced = 1; |
| root->replacement_decl = create_access_replacement (root); |
| sth_created = true; |
| hole = false; |
| } |
| else |
| { |
| if (allow_replacements |
| && scalar && !root->first_child |
| && (root->grp_scalar_write || root->grp_assignment_write) |
| && !bitmap_bit_p (cannot_scalarize_away_bitmap, |
| DECL_UID (root->base))) |
| { |
| gcc_checking_assert (!root->grp_scalar_read |
| && !root->grp_assignment_read); |
| sth_created = true; |
| if (MAY_HAVE_DEBUG_STMTS) |
| { |
| root->grp_to_be_debug_replaced = 1; |
| root->replacement_decl = create_access_replacement (root); |
| } |
| } |
| |
| if (covered_to < limit) |
| hole = true; |
| if (scalar) |
| root->grp_total_scalarization = 0; |
| } |
| |
| if (!hole || root->grp_total_scalarization) |
| root->grp_covered = 1; |
| else if (root->grp_write || TREE_CODE (root->base) == PARM_DECL) |
| root->grp_unscalarized_data = 1; /* not covered and written to */ |
| return sth_created; |
| } |
| |
| /* Analyze all access trees linked by next_grp by the means of |
| analyze_access_subtree. */ |
| static bool |
| analyze_access_trees (struct access *access) |
| { |
| bool ret = false; |
| |
| while (access) |
| { |
| if (analyze_access_subtree (access, NULL, true)) |
| ret = true; |
| access = access->next_grp; |
| } |
| |
| return ret; |
| } |
| |
| /* Return true iff a potential new child of LACC at offset OFFSET and with size |
| SIZE would conflict with an already existing one. If exactly such a child |
| already exists in LACC, store a pointer to it in EXACT_MATCH. */ |
| |
| static bool |
| child_would_conflict_in_lacc (struct access *lacc, HOST_WIDE_INT norm_offset, |
| HOST_WIDE_INT size, struct access **exact_match) |
| { |
| struct access *child; |
| |
| for (child = lacc->first_child; child; child = child->next_sibling) |
| { |
| if (child->offset == norm_offset && child->size == size) |
| { |
| *exact_match = child; |
| return true; |
| } |
| |
| if (child->offset < norm_offset + size |
| && child->offset + child->size > norm_offset) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Create a new child access of PARENT, with all properties just like MODEL |
| except for its offset and with its grp_write false and grp_read true. |
| Return the new access or NULL if it cannot be created. Note that this access |
| is created long after all splicing and sorting, it's not located in any |
| access vector and is automatically a representative of its group. */ |
| |
| static struct access * |
| create_artificial_child_access (struct access *parent, struct access *model, |
| HOST_WIDE_INT new_offset) |
| { |
| struct access *access; |
| struct access **child; |
| tree expr = parent->base; |
| |
| gcc_assert (!model->grp_unscalarizable_region); |
| |
| access = (struct access *) pool_alloc (access_pool); |
| memset (access, 0, sizeof (struct access)); |
| if (!build_user_friendly_ref_for_offset (&expr, TREE_TYPE (expr), new_offset, |
| model->type)) |
| { |
| access->grp_no_warning = true; |
| expr = build_ref_for_model (EXPR_LOCATION (parent->base), parent->base, |
| new_offset, model, NULL, false); |
| } |
| |
| access->base = parent->base; |
| access->expr = expr; |
| access->offset = new_offset; |
| access->size = model->size; |
| access->type = model->type; |
| access->grp_write = true; |
| access->grp_read = false; |
| |
| child = &parent->first_child; |
| while (*child && (*child)->offset < new_offset) |
| child = &(*child)->next_sibling; |
| |
| access->next_sibling = *child; |
| *child = access; |
| |
| return access; |
| } |
| |
| |
| /* Propagate all subaccesses of RACC across an assignment link to LACC. Return |
| true if any new subaccess was created. Additionally, if RACC is a scalar |
| access but LACC is not, change the type of the latter, if possible. */ |
| |
| static bool |
| propagate_subaccesses_across_link (struct access *lacc, struct access *racc) |
| { |
| struct access *rchild; |
| HOST_WIDE_INT norm_delta = lacc->offset - racc->offset; |
| bool ret = false; |
| |
| if (is_gimple_reg_type (lacc->type) |
| || lacc->grp_unscalarizable_region |
| || racc->grp_unscalarizable_region) |
| return false; |
| |
| if (is_gimple_reg_type (racc->type)) |
| { |
| if (!lacc->first_child && !racc->first_child) |
| { |
| tree t = lacc->base; |
| |
| lacc->type = racc->type; |
| if (build_user_friendly_ref_for_offset (&t, TREE_TYPE (t), |
| lacc->offset, racc->type)) |
| lacc->expr = t; |
| else |
| { |
| lacc->expr = build_ref_for_model (EXPR_LOCATION (lacc->base), |
| lacc->base, lacc->offset, |
| racc, NULL, false); |
| lacc->grp_no_warning = true; |
| } |
| } |
| return false; |
| } |
| |
| for (rchild = racc->first_child; rchild; rchild = rchild->next_sibling) |
| { |
| struct access *new_acc = NULL; |
| HOST_WIDE_INT norm_offset = rchild->offset + norm_delta; |
| |
| if (rchild->grp_unscalarizable_region) |
| continue; |
| |
| if (child_would_conflict_in_lacc (lacc, norm_offset, rchild->size, |
| &new_acc)) |
| { |
| if (new_acc) |
| { |
| rchild->grp_hint = 1; |
| new_acc->grp_hint |= new_acc->grp_read; |
| if (rchild->first_child) |
| ret |= propagate_subaccesses_across_link (new_acc, rchild); |
| } |
| continue; |
| } |
| |
| rchild->grp_hint = 1; |
| new_acc = create_artificial_child_access (lacc, rchild, norm_offset); |
| if (new_acc) |
| { |
| ret = true; |
| if (racc->first_child) |
| propagate_subaccesses_across_link (new_acc, rchild); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* Propagate all subaccesses across assignment links. */ |
| |
| static void |
| propagate_all_subaccesses (void) |
| { |
| while (work_queue_head) |
| { |
| struct access *racc = pop_access_from_work_queue (); |
| struct assign_link *link; |
| |
| gcc_assert (racc->first_link); |
| |
| for (link = racc->first_link; link; link = link->next) |
| { |
| struct access *lacc = link->lacc; |
| |
| if (!bitmap_bit_p (candidate_bitmap, DECL_UID (lacc->base))) |
| continue; |
| lacc = lacc->group_representative; |
| if (propagate_subaccesses_across_link (lacc, racc) |
| && lacc->first_link) |
| add_access_to_work_queue (lacc); |
| } |
| } |
| } |
| |
| /* Go through all accesses collected throughout the (intraprocedural) analysis |
| stage, exclude overlapping ones, identify representatives and build trees |
| out of them, making decisions about scalarization on the way. Return true |
| iff there are any to-be-scalarized variables after this stage. */ |
| |
| static bool |
| analyze_all_variable_accesses (void) |
| { |
| int res = 0; |
| bitmap tmp = BITMAP_ALLOC (NULL); |
| bitmap_iterator bi; |
| unsigned i, max_total_scalarization_size; |
| |
| max_total_scalarization_size = UNITS_PER_WORD * BITS_PER_UNIT |
| * MOVE_RATIO (optimize_function_for_speed_p (cfun)); |
| |
| EXECUTE_IF_SET_IN_BITMAP (candidate_bitmap, 0, i, bi) |
| if (bitmap_bit_p (should_scalarize_away_bitmap, i) |
| && !bitmap_bit_p (cannot_scalarize_away_bitmap, i)) |
| { |
| tree var = candidate (i); |
| |
| if (TREE_CODE (var) == VAR_DECL |
| && type_consists_of_records_p (TREE_TYPE (var))) |
| { |
| if ((unsigned) tree_low_cst (TYPE_SIZE (TREE_TYPE (var)), 1) |
| <= max_total_scalarization_size) |
| { |
| completely_scalarize_var (var); |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Will attempt to totally scalarize "); |
| print_generic_expr (dump_file, var, 0); |
| fprintf (dump_file, " (UID: %u): \n", DECL_UID (var)); |
| } |
| } |
| else if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Too big to totally scalarize: "); |
| print_generic_expr (dump_file, var, 0); |
| fprintf (dump_file, " (UID: %u)\n", DECL_UID (var)); |
| } |
| } |
| } |
| |
| bitmap_copy (tmp, candidate_bitmap); |
| EXECUTE_IF_SET_IN_BITMAP (tmp, 0, i, bi) |
| { |
| tree var = candidate (i); |
| struct access *access; |
| |
| access = sort_and_splice_var_accesses (var); |
| if (!access || !build_access_trees (access)) |
| disqualify_candidate (var, |
| "No or inhibitingly overlapping accesses."); |
| } |
| |
| propagate_all_subaccesses (); |
| |
| bitmap_copy (tmp, candidate_bitmap); |
| EXECUTE_IF_SET_IN_BITMAP (tmp, 0, i, bi) |
| { |
| tree var = candidate (i); |
| struct access *access = get_first_repr_for_decl (var); |
| |
| if (analyze_access_trees (access)) |
| { |
| res++; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "\nAccess trees for "); |
| print_generic_expr (dump_file, var, 0); |
| fprintf (dump_file, " (UID: %u): \n", DECL_UID (var)); |
| dump_access_tree (dump_file, access); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| else |
| disqualify_candidate (var, "No scalar replacements to be created."); |
| } |
| |
| BITMAP_FREE (tmp); |
| |
| if (res) |
| { |
| statistics_counter_event (cfun, "Scalarized aggregates", res); |
| return true; |
| } |
| else |
| return false; |
| } |
| |
| /* Generate statements copying scalar replacements of accesses within a subtree |
| into or out of AGG. ACCESS, all its children, siblings and their children |
| are to be processed. AGG is an aggregate type expression (can be a |
| declaration but does not have to be, it can for example also be a mem_ref or |
| a series of handled components). TOP_OFFSET is the offset of the processed |
| subtree which has to be subtracted from offsets of individual accesses to |
| get corresponding offsets for AGG. If CHUNK_SIZE is non-null, copy only |
| replacements in the interval <start_offset, start_offset + chunk_size>, |
| otherwise copy all. GSI is a statement iterator used to place the new |
| statements. WRITE should be true when the statements should write from AGG |
| to the replacement and false if vice versa. if INSERT_AFTER is true, new |
| statements will be added after the current statement in GSI, they will be |
| added before the statement otherwise. */ |
| |
| static void |
| generate_subtree_copies (struct access *access, tree agg, |
| HOST_WIDE_INT top_offset, |
| HOST_WIDE_INT start_offset, HOST_WIDE_INT chunk_size, |
| gimple_stmt_iterator *gsi, bool write, |
| bool insert_after, location_t loc) |
| { |
| do |
| { |
| if (chunk_size && access->offset >= start_offset + chunk_size) |
| return; |
| |
| if (access->grp_to_be_replaced |
| && (chunk_size == 0 |
| || access->offset + access->size > start_offset)) |
| { |
| tree expr, repl = get_access_replacement (access); |
| gimple stmt; |
| |
| expr = build_ref_for_model (loc, agg, access->offset - top_offset, |
| access, gsi, insert_after); |
| |
| if (write) |
| { |
| if (access->grp_partial_lhs) |
| expr = force_gimple_operand_gsi (gsi, expr, true, NULL_TREE, |
| !insert_after, |
| insert_after ? GSI_NEW_STMT |
| : GSI_SAME_STMT); |
| stmt = gimple_build_assign (repl, expr); |
| } |
| else |
| { |
| TREE_NO_WARNING (repl) = 1; |
| if (access->grp_partial_lhs) |
| repl = force_gimple_operand_gsi (gsi, repl, true, NULL_TREE, |
| !insert_after, |
| insert_after ? GSI_NEW_STMT |
| : GSI_SAME_STMT); |
| stmt = gimple_build_assign (expr, repl); |
| } |
| gimple_set_location (stmt, loc); |
| |
| if (insert_after) |
| gsi_insert_after (gsi, stmt, GSI_NEW_STMT); |
| else |
| gsi_insert_before (gsi, stmt, GSI_SAME_STMT); |
| update_stmt (stmt); |
| sra_stats.subtree_copies++; |
| } |
| else if (write |
| && access->grp_to_be_debug_replaced |
| && (chunk_size == 0 |
| || access->offset + access->size > start_offset)) |
| { |
| gimple ds; |
| tree drhs = build_debug_ref_for_model (loc, agg, |
| access->offset - top_offset, |
| access); |
| ds = gimple_build_debug_bind (get_access_replacement (access), |
| drhs, gsi_stmt (*gsi)); |
| if (insert_after) |
| gsi_insert_after (gsi, ds, GSI_NEW_STMT); |
| else |
| gsi_insert_before (gsi, ds, GSI_SAME_STMT); |
| } |
| |
| if (access->first_child) |
| generate_subtree_copies (access->first_child, agg, top_offset, |
| start_offset, chunk_size, gsi, |
| write, insert_after, loc); |
| |
| access = access->next_sibling; |
| } |
| while (access); |
| } |
| |
| /* Assign zero to all scalar replacements in an access subtree. ACCESS is the |
| the root of the subtree to be processed. GSI is the statement iterator used |
| for inserting statements which are added after the current statement if |
| INSERT_AFTER is true or before it otherwise. */ |
| |
| static void |
| init_subtree_with_zero (struct access *access, gimple_stmt_iterator *gsi, |
| bool insert_after, location_t loc) |
| |
| { |
| struct access *child; |
| |
| if (access->grp_to_be_replaced) |
| { |
| gimple stmt; |
| |
| stmt = gimple_build_assign (get_access_replacement (access), |
| build_zero_cst (access->type)); |
| if (insert_after) |
| gsi_insert_after (gsi, stmt, GSI_NEW_STMT); |
| else |
| gsi_insert_before (gsi, stmt, GSI_SAME_STMT); |
| update_stmt (stmt); |
| gimple_set_location (stmt, loc); |
| } |
| else if (access->grp_to_be_debug_replaced) |
| { |
| gimple ds = gimple_build_debug_bind (get_access_replacement (access), |
| build_zero_cst (access->type), |
| gsi_stmt (*gsi)); |
| if (insert_after) |
| gsi_insert_after (gsi, ds, GSI_NEW_STMT); |
| else |
| gsi_insert_before (gsi, ds, GSI_SAME_STMT); |
| } |
| |
| for (child = access->first_child; child; child = child->next_sibling) |
| init_subtree_with_zero (child, gsi, insert_after, loc); |
| } |
| |
| /* Search for an access representative for the given expression EXPR and |
| return it or NULL if it cannot be found. */ |
| |
| static struct access * |
| get_access_for_expr (tree expr) |
| { |
| HOST_WIDE_INT offset, size, max_size; |
| tree base; |
| |
| /* FIXME: This should not be necessary but Ada produces V_C_Es with a type of |
| a different size than the size of its argument and we need the latter |
| one. */ |
| if (TREE_CODE (expr) == VIEW_CONVERT_EXPR) |
| expr = TREE_OPERAND (expr, 0); |
| |
| base = get_ref_base_and_extent (expr, &offset, &size, &max_size); |
| if (max_size == -1 || !DECL_P (base)) |
| return NULL; |
| |
| if (!bitmap_bit_p (candidate_bitmap, DECL_UID (base))) |
| return NULL; |
| |
| return get_var_base_offset_size_access (base, offset, max_size); |
| } |
| |
| /* Replace the expression EXPR with a scalar replacement if there is one and |
| generate other statements to do type conversion or subtree copying if |
| necessary. GSI is used to place newly created statements, WRITE is true if |
| the expression is being written to (it is on a LHS of a statement or output |
| in an assembly statement). */ |
| |
| static bool |
| sra_modify_expr (tree *expr, gimple_stmt_iterator *gsi, bool write) |
| { |
| location_t loc; |
| struct access *access; |
| tree type, bfr; |
| |
| if (TREE_CODE (*expr) == BIT_FIELD_REF) |
| { |
| bfr = *expr; |
| expr = &TREE_OPERAND (*expr, 0); |
| } |
| else |
| bfr = NULL_TREE; |
| |
| if (TREE_CODE (*expr) == REALPART_EXPR || TREE_CODE (*expr) == IMAGPART_EXPR) |
| expr = &TREE_OPERAND (*expr, 0); |
| access = get_access_for_expr (*expr); |
| if (!access) |
| return false; |
| type = TREE_TYPE (*expr); |
| |
| loc = gimple_location (gsi_stmt (*gsi)); |
| if (access->grp_to_be_replaced) |
| { |
| tree repl = get_access_replacement (access); |
| /* If we replace a non-register typed access simply use the original |
| access expression to extract the scalar component afterwards. |
| This happens if scalarizing a function return value or parameter |
| like in gcc.c-torture/execute/20041124-1.c, 20050316-1.c and |
| gcc.c-torture/compile/20011217-1.c. |
| |
| We also want to use this when accessing a complex or vector which can |
| be accessed as a different type too, potentially creating a need for |
| type conversion (see PR42196) and when scalarized unions are involved |
| in assembler statements (see PR42398). */ |
| if (!useless_type_conversion_p (type, access->type)) |
| { |
| tree ref; |
| |
| ref = build_ref_for_model (loc, access->base, access->offset, access, |
| NULL, false); |
| |
| if (write) |
| { |
| gimple stmt; |
| |
| if (access->grp_partial_lhs) |
| ref = force_gimple_operand_gsi (gsi, ref, true, NULL_TREE, |
| false, GSI_NEW_STMT); |
| stmt = gimple_build_assign (repl, ref); |
| gimple_set_location (stmt, loc); |
| gsi_insert_after (gsi, stmt, GSI_NEW_STMT); |
| } |
| else |
| { |
| gimple stmt; |
| |
| if (access->grp_partial_lhs) |
| repl = force_gimple_operand_gsi (gsi, repl, true, NULL_TREE, |
| true, GSI_SAME_STMT); |
| stmt = gimple_build_assign (ref, repl); |
| gimple_set_location (stmt, loc); |
| gsi_insert_before (gsi, stmt, GSI_SAME_STMT); |
| } |
| } |
| else |
| *expr = repl; |
| sra_stats.exprs++; |
| } |
| else if (write && access->grp_to_be_debug_replaced) |
| { |
| gimple ds = gimple_build_debug_bind (get_access_replacement (access), |
| NULL_TREE, |
| gsi_stmt (*gsi)); |
| gsi_insert_after (gsi, ds, GSI_NEW_STMT); |
| } |
| |
| if (access->first_child) |
| { |
| HOST_WIDE_INT start_offset, chunk_size; |
| if (bfr |
| && host_integerp (TREE_OPERAND (bfr, 1), 1) |
| && host_integerp (TREE_OPERAND (bfr, 2), 1)) |
| { |
| chunk_size = tree_low_cst (TREE_OPERAND (bfr, 1), 1); |
| start_offset = access->offset |
| + tree_low_cst (TREE_OPERAND (bfr, 2), 1); |
| } |
| else |
| start_offset = chunk_size = 0; |
| |
| generate_subtree_copies (access->first_child, access->base, 0, |
| start_offset, chunk_size, gsi, write, write, |
| loc); |
| } |
| return true; |
| } |
| |
| /* Where scalar replacements of the RHS have been written to when a replacement |
| of a LHS of an assigments cannot be direclty loaded from a replacement of |
| the RHS. */ |
| enum unscalarized_data_handling { SRA_UDH_NONE, /* Nothing done so far. */ |
| SRA_UDH_RIGHT, /* Data flushed to the RHS. */ |
| SRA_UDH_LEFT }; /* Data flushed to the LHS. */ |
| |
| /* Store all replacements in the access tree rooted in TOP_RACC either to their |
| base aggregate if there are unscalarized data or directly to LHS of the |
| statement that is pointed to by GSI otherwise. */ |
| |
| static enum unscalarized_data_handling |
| handle_unscalarized_data_in_subtree (struct access *top_racc, |
| gimple_stmt_iterator *gsi) |
| { |
| if (top_racc->grp_unscalarized_data) |
| { |
| generate_subtree_copies (top_racc->first_child, top_racc->base, 0, 0, 0, |
| gsi, false, false, |
| gimple_location (gsi_stmt (*gsi))); |
| return SRA_UDH_RIGHT; |
| } |
| else |
| { |
| tree lhs = gimple_assign_lhs (gsi_stmt (*gsi)); |
| generate_subtree_copies (top_racc->first_child, lhs, top_racc->offset, |
| 0, 0, gsi, false, false, |
| gimple_location (gsi_stmt (*gsi))); |
| return SRA_UDH_LEFT; |
| } |
| } |
| |
| |
| /* Try to generate statements to load all sub-replacements in an access subtree |
| formed by children of LACC from scalar replacements in the TOP_RACC subtree. |
| If that is not possible, refresh the TOP_RACC base aggregate and load the |
| accesses from it. LEFT_OFFSET is the offset of the left whole subtree being |
| copied. NEW_GSI is stmt iterator used for statement insertions after the |
| original assignment, OLD_GSI is used to insert statements before the |
| assignment. *REFRESHED keeps the information whether we have needed to |
| refresh replacements of the LHS and from which side of the assignments this |
| takes place. */ |
| |
| static void |
| load_assign_lhs_subreplacements (struct access *lacc, struct access *top_racc, |
| HOST_WIDE_INT left_offset, |
| gimple_stmt_iterator *old_gsi, |
| gimple_stmt_iterator *new_gsi, |
| enum unscalarized_data_handling *refreshed) |
| { |
| location_t loc = gimple_location (gsi_stmt (*old_gsi)); |
| for (lacc = lacc->first_child; lacc; lacc = lacc->next_sibling) |
| { |
| HOST_WIDE_INT offset = lacc->offset - left_offset + top_racc->offset; |
| |
| if (lacc->grp_to_be_replaced) |
| { |
| struct access *racc; |
| gimple stmt; |
| tree rhs; |
| |
| racc = find_access_in_subtree (top_racc, offset, lacc->size); |
| if (racc && racc->grp_to_be_replaced) |
| { |
| rhs = get_access_replacement (racc); |
| if (!useless_type_conversion_p (lacc->type, racc->type)) |
| rhs = fold_build1_loc (loc, VIEW_CONVERT_EXPR, lacc->type, rhs); |
| |
| if (racc->grp_partial_lhs && lacc->grp_partial_lhs) |
| rhs = force_gimple_operand_gsi (old_gsi, rhs, true, NULL_TREE, |
| true, GSI_SAME_STMT); |
| } |
| else |
| { |
| /* No suitable access on the right hand side, need to load from |
| the aggregate. See if we have to update it first... */ |
| if (*refreshed == SRA_UDH_NONE) |
| *refreshed = handle_unscalarized_data_in_subtree (top_racc, |
| old_gsi); |
| |
| if (*refreshed == SRA_UDH_LEFT) |
| rhs = build_ref_for_model (loc, lacc->base, lacc->offset, lacc, |
| new_gsi, true); |
| else |
| rhs = build_ref_for_model (loc, top_racc->base, offset, lacc, |
| new_gsi, true); |
| if (lacc->grp_partial_lhs) |
| rhs = force_gimple_operand_gsi (new_gsi, rhs, true, NULL_TREE, |
| false, GSI_NEW_STMT); |
| } |
| |
| stmt = gimple_build_assign (get_access_replacement (lacc), rhs); |
| gsi_insert_after (new_gsi, stmt, GSI_NEW_STMT); |
| gimple_set_location (stmt, loc); |
| update_stmt (stmt); |
| sra_stats.subreplacements++; |
| } |
| else |
| { |
| if (*refreshed == SRA_UDH_NONE |
| && lacc->grp_read && !lacc->grp_covered) |
| *refreshed = handle_unscalarized_data_in_subtree (top_racc, |
| old_gsi); |
| if (lacc && lacc->grp_to_be_debug_replaced) |
| { |
| gimple ds; |
| tree drhs; |
| struct access *racc = find_access_in_subtree (top_racc, offset, |
| lacc->size); |
| |
| if (racc && racc->grp_to_be_replaced) |
| { |
| if (racc->grp_write) |
| drhs = get_access_replacement (racc); |
| else |
| drhs = NULL; |
| } |
| else if (*refreshed == SRA_UDH_LEFT) |
| drhs = build_debug_ref_for_model (loc, lacc->base, lacc->offset, |
| lacc); |
| else if (*refreshed == SRA_UDH_RIGHT) |
| drhs = build_debug_ref_for_model (loc, top_racc->base, offset, |
| lacc); |
| else |
| drhs = NULL_TREE; |
| ds = gimple_build_debug_bind (get_access_replacement (lacc), |
| drhs, gsi_stmt (*old_gsi)); |
| gsi_insert_after (new_gsi, ds, GSI_NEW_STMT); |
| } |
| } |
| |
| if (lacc->first_child) |
| load_assign_lhs_subreplacements (lacc, top_racc, left_offset, |
| old_gsi, new_gsi, refreshed); |
| } |
| } |
| |
| /* Result code for SRA assignment modification. */ |
| enum assignment_mod_result { SRA_AM_NONE, /* nothing done for the stmt */ |
| SRA_AM_MODIFIED, /* stmt changed but not |
| removed */ |
| SRA_AM_REMOVED }; /* stmt eliminated */ |
| |
| /* Modify assignments with a CONSTRUCTOR on their RHS. STMT contains a pointer |
| to the assignment and GSI is the statement iterator pointing at it. Returns |
| the same values as sra_modify_assign. */ |
| |
| static enum assignment_mod_result |
| sra_modify_constructor_assign (gimple *stmt, gimple_stmt_iterator *gsi) |
| { |
| tree lhs = gimple_assign_lhs (*stmt); |
| struct access *acc; |
| location_t loc; |
| |
| acc = get_access_for_expr (lhs); |
| if (!acc) |
| return SRA_AM_NONE; |
| |
| if (gimple_clobber_p (*stmt)) |
| { |
| /* Remove clobbers of fully scalarized variables, otherwise |
| do nothing. */ |
| if (acc->grp_covered) |
| { |
| unlink_stmt_vdef (*stmt); |
| gsi_remove (gsi, true); |
| release_defs (*stmt); |
| return SRA_AM_REMOVED; |
| } |
| else |
| return SRA_AM_NONE; |
| } |
| |
| loc = gimple_location (*stmt); |
| if (vec_safe_length (CONSTRUCTOR_ELTS (gimple_assign_rhs1 (*stmt))) > 0) |
| { |
| /* I have never seen this code path trigger but if it can happen the |
| following should handle it gracefully. */ |
| if (access_has_children_p (acc)) |
| generate_subtree_copies (acc->first_child, acc->base, 0, 0, 0, gsi, |
| true, true, loc); |
| return SRA_AM_MODIFIED; |
| } |
| |
| if (acc->grp_covered) |
| { |
| init_subtree_with_zero (acc, gsi, false, loc); |
| unlink_stmt_vdef (*stmt); |
| gsi_remove (gsi, true); |
| release_defs (*stmt); |
| return SRA_AM_REMOVED; |
| } |
| else |
| { |
| init_subtree_with_zero (acc, gsi, true, loc); |
| return SRA_AM_MODIFIED; |
| } |
| } |
| |
| /* Create and return a new suitable default definition SSA_NAME for RACC which |
| is an access describing an uninitialized part of an aggregate that is being |
| loaded. */ |
| |
| static tree |
| get_repl_default_def_ssa_name (struct access *racc) |
| { |
| gcc_checking_assert (!racc->grp_to_be_replaced && |
| !racc->grp_to_be_debug_replaced); |
| if (!racc->replacement_decl) |
| racc->replacement_decl = create_access_replacement (racc); |
| return get_or_create_ssa_default_def (cfun, racc->replacement_decl); |
| } |
| |
| /* Return true if REF has an VIEW_CONVERT_EXPR or a COMPONENT_REF with a |
| bit-field field declaration somewhere in it. */ |
| |
| static inline bool |
| contains_vce_or_bfcref_p (const_tree ref) |
| { |
| while (handled_component_p (ref)) |
| { |
| if (TREE_CODE (ref) == VIEW_CONVERT_EXPR |
| || (TREE_CODE (ref) == COMPONENT_REF |
| && DECL_BIT_FIELD (TREE_OPERAND (ref, 1)))) |
| return true; |
| ref = TREE_OPERAND (ref, 0); |
| } |
| |
| return false; |
| } |
| |
| /* Examine both sides of the assignment statement pointed to by STMT, replace |
| them with a scalare replacement if there is one and generate copying of |
| replacements if scalarized aggregates have been used in the assignment. GSI |
| is used to hold generated statements for type conversions and subtree |
| copying. */ |
| |
| static enum assignment_mod_result |
| sra_modify_assign (gimple *stmt, gimple_stmt_iterator *gsi) |
| { |
| struct access *lacc, *racc; |
| tree lhs, rhs; |
| bool modify_this_stmt = false; |
| bool force_gimple_rhs = false; |
| location_t loc; |
| gimple_stmt_iterator orig_gsi = *gsi; |
| |
| if (!gimple_assign_single_p (*stmt)) |
| return SRA_AM_NONE; |
| lhs = gimple_assign_lhs (*stmt); |
| rhs = gimple_assign_rhs1 (*stmt); |
| |
| if (TREE_CODE (rhs) == CONSTRUCTOR) |
| return sra_modify_constructor_assign (stmt, gsi); |
| |
| if (TREE_CODE (rhs) == REALPART_EXPR || TREE_CODE (lhs) == REALPART_EXPR |
| || TREE_CODE (rhs) == IMAGPART_EXPR || TREE_CODE (lhs) == IMAGPART_EXPR |
| || TREE_CODE (rhs) == BIT_FIELD_REF || TREE_CODE (lhs) == BIT_FIELD_REF) |
| { |
| modify_this_stmt = sra_modify_expr (gimple_assign_rhs1_ptr (*stmt), |
| gsi, false); |
| modify_this_stmt |= sra_modify_expr (gimple_assign_lhs_ptr (*stmt), |
| gsi, true); |
| return modify_this_stmt ? SRA_AM_MODIFIED : SRA_AM_NONE; |
| } |
| |
| lacc = get_access_for_expr (lhs); |
| racc = get_access_for_expr (rhs); |
| if (!lacc && !racc) |
| return SRA_AM_NONE; |
| |
| loc = gimple_location (*stmt); |
| if (lacc && lacc->grp_to_be_replaced) |
| { |
| lhs = get_access_replacement (lacc); |
| gimple_assign_set_lhs (*stmt, lhs); |
| modify_this_stmt = true; |
| if (lacc->grp_partial_lhs) |
| force_gimple_rhs = true; |
| sra_stats.exprs++; |
| } |
| |
| if (racc && racc->grp_to_be_replaced) |
| { |
| rhs = get_access_replacement (racc); |
| modify_this_stmt = true; |
| if (racc->grp_partial_lhs) |
| force_gimple_rhs = true; |
| sra_stats.exprs++; |
| } |
| else if (racc |
| && !racc->grp_unscalarized_data |
| && TREE_CODE (lhs) == SSA_NAME |
| && !access_has_replacements_p (racc)) |
| { |
| rhs = get_repl_default_def_ssa_name (racc); |
| modify_this_stmt = true; |
| sra_stats.exprs++; |
| } |
| |
| if (modify_this_stmt) |
| { |
| if (!useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (rhs))) |
| { |
| /* If we can avoid creating a VIEW_CONVERT_EXPR do so. |
| ??? This should move to fold_stmt which we simply should |
| call after building a VIEW_CONVERT_EXPR here. */ |
| if (AGGREGATE_TYPE_P (TREE_TYPE (lhs)) |
| && !contains_bitfld_component_ref_p (lhs)) |
| { |
| lhs = build_ref_for_model (loc, lhs, 0, racc, gsi, false); |
| gimple_assign_set_lhs (*stmt, lhs); |
| } |
| else if (AGGREGATE_TYPE_P (TREE_TYPE (rhs)) |
| && !contains_vce_or_bfcref_p (rhs)) |
| rhs = build_ref_for_model (loc, rhs, 0, lacc, gsi, false); |
| |
| if (!useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (rhs))) |
| { |
| rhs = fold_build1_loc (loc, VIEW_CONVERT_EXPR, TREE_TYPE (lhs), |
| rhs); |
| if (is_gimple_reg_type (TREE_TYPE (lhs)) |
| && TREE_CODE (lhs) != SSA_NAME) |
| force_gimple_rhs = true; |
| } |
| } |
| } |
| |
| if (lacc && lacc->grp_to_be_debug_replaced) |
| { |
| tree dlhs = get_access_replacement (lacc); |
| tree drhs = unshare_expr (rhs); |
| if (!useless_type_conversion_p (TREE_TYPE (dlhs), TREE_TYPE (drhs))) |
| { |
| if (AGGREGATE_TYPE_P (TREE_TYPE (drhs)) |
| && !contains_vce_or_bfcref_p (drhs)) |
| drhs = build_debug_ref_for_model (loc, drhs, 0, lacc); |
| if (drhs |
| && !useless_type_conversion_p (TREE_TYPE (dlhs), |
| TREE_TYPE (drhs))) |
| drhs = fold_build1_loc (loc, VIEW_CONVERT_EXPR, |
| TREE_TYPE (dlhs), drhs); |
| } |
| gimple ds = gimple_build_debug_bind (dlhs, drhs, *stmt); |
| gsi_insert_before (gsi, ds, GSI_SAME_STMT); |
| } |
| |
| /* From this point on, the function deals with assignments in between |
| aggregates when at least one has scalar reductions of some of its |
| components. There are three possible scenarios: Both the LHS and RHS have |
| to-be-scalarized components, 2) only the RHS has or 3) only the LHS has. |
| |
| In the first case, we would like to load the LHS components from RHS |
| components whenever possible. If that is not possible, we would like to |
| read it directly from the RHS (after updating it by storing in it its own |
| components). If there are some necessary unscalarized data in the LHS, |
| those will be loaded by the original assignment too. If neither of these |
| cases happen, the original statement can be removed. Most of this is done |
| by load_assign_lhs_subreplacements. |
| |
| In the second case, we would like to store all RHS scalarized components |
| directly into LHS and if they cover the aggregate completely, remove the |
| statement too. In the third case, we want the LHS components to be loaded |
| directly from the RHS (DSE will remove the original statement if it |
| becomes redundant). |
| |
| This is a bit complex but manageable when types match and when unions do |
| not cause confusion in a way that we cannot really load a component of LHS |
| from the RHS or vice versa (the access representing this level can have |
| subaccesses that are accessible only through a different union field at a |
| higher level - different from the one used in the examined expression). |
| Unions are fun. |
| |
| Therefore, I specially handle a fourth case, happening when there is a |
| specific type cast or it is impossible to locate a scalarized subaccess on |
| the other side of the expression. If that happens, I simply "refresh" the |
| RHS by storing in it is scalarized components leave the original statement |
| there to do the copying and then load the scalar replacements of the LHS. |
| This is what the first branch does. */ |
| |
| if (modify_this_stmt |
| || gimple_has_volatile_ops (*stmt) |
| || contains_vce_or_bfcref_p (rhs) |
| || contains_vce_or_bfcref_p (lhs)) |
| { |
| if (access_has_children_p (racc)) |
| generate_subtree_copies (racc->first_child, racc->base, 0, 0, 0, |
| gsi, false, false, loc); |
| if (access_has_children_p (lacc)) |
| generate_subtree_copies (lacc->first_child, lacc->base, 0, 0, 0, |
| gsi, true, true, loc); |
| sra_stats.separate_lhs_rhs_handling++; |
| |
| /* This gimplification must be done after generate_subtree_copies, |
| lest we insert the subtree copies in the middle of the gimplified |
| sequence. */ |
| if (force_gimple_rhs) |
| rhs = force_gimple_operand_gsi (&orig_gsi, rhs, true, NULL_TREE, |
| true, GSI_SAME_STMT); |
| if (gimple_assign_rhs1 (*stmt) != rhs) |
| { |
| modify_this_stmt = true; |
| gimple_assign_set_rhs_from_tree (&orig_gsi, rhs); |
| gcc_assert (*stmt == gsi_stmt (orig_gsi)); |
| } |
| |
| return modify_this_stmt ? SRA_AM_MODIFIED : SRA_AM_NONE; |
| } |
| else |
| { |
| if (access_has_children_p (lacc) |
| && access_has_children_p (racc) |
| /* When an access represents an unscalarizable region, it usually |
| represents accesses with variable offset and thus must not be used |
| to generate new memory accesses. */ |
| && !lacc->grp_unscalarizable_region |
| && !racc->grp_unscalarizable_region) |
| { |
| gimple_stmt_iterator orig_gsi = *gsi; |
| enum unscalarized_data_handling refreshed; |
| |
| if (lacc->grp_read && !lacc->grp_covered) |
| refreshed = handle_unscalarized_data_in_subtree (racc, gsi); |
| else |
| refreshed = SRA_UDH_NONE; |
| |
| load_assign_lhs_subreplacements (lacc, racc, lacc->offset, |
| &orig_gsi, gsi, &refreshed); |
| if (refreshed != SRA_UDH_RIGHT) |
| { |
| gsi_next (gsi); |
| unlink_stmt_vdef (*stmt); |
| gsi_remove (&orig_gsi, true); |
| release_defs (*stmt); |
| sra_stats.deleted++; |
| return SRA_AM_REMOVED; |
| } |
| } |
| else |
| { |
| if (access_has_children_p (racc) |
| && !racc->grp_unscalarized_data) |
| { |
| if (dump_file) |
| { |
| fprintf (dump_file, "Removing load: "); |
| print_gimple_stmt (dump_file, *stmt, 0, 0); |
| } |
| generate_subtree_copies (racc->first_child, lhs, |
| racc->offset, 0, 0, gsi, |
| false, false, loc); |
| gcc_assert (*stmt == gsi_stmt (*gsi)); |
| unlink_stmt_vdef (*stmt); |
| gsi_remove (gsi, true); |
| release_defs (*stmt); |
| sra_stats.deleted++; |
| return SRA_AM_REMOVED; |
| } |
| /* Restore the aggregate RHS from its components so the |
| prevailing aggregate copy does the right thing. */ |
| if (access_has_children_p (racc)) |
| generate_subtree_copies (racc->first_child, racc->base, 0, 0, 0, |
| gsi, false, false, loc); |
| /* Re-load the components of the aggregate copy destination. |
| But use the RHS aggregate to load from to expose more |
| optimization opportunities. */ |
| if (access_has_children_p (lacc)) |
| generate_subtree_copies (lacc->first_child, rhs, lacc->offset, |
| 0, 0, gsi, true, true, loc); |
| } |
| |
| return SRA_AM_NONE; |
| } |
| } |
| |
| /* Traverse the function body and all modifications as decided in |
| analyze_all_variable_accesses. Return true iff the CFG has been |
| changed. */ |
| |
| static bool |
| sra_modify_function_body (void) |
| { |
| bool cfg_changed = false; |
| basic_block bb; |
| |
| FOR_EACH_BB (bb) |
| { |
| gimple_stmt_iterator gsi = gsi_start_bb (bb); |
| while (!gsi_end_p (gsi)) |
| { |
| gimple stmt = gsi_stmt (gsi); |
| enum assignment_mod_result assign_result; |
| bool modified = false, deleted = false; |
| tree *t; |
| unsigned i; |
| |
| switch (gimple_code (stmt)) |
| { |
| case GIMPLE_RETURN: |
| t = gimple_return_retval_ptr (stmt); |
| if (*t != NULL_TREE) |
| modified |= sra_modify_expr (t, &gsi, false); |
| break; |
| |
| case GIMPLE_ASSIGN: |
| assign_result = sra_modify_assign (&stmt, &gsi); |
| modified |= assign_result == SRA_AM_MODIFIED; |
| deleted = assign_result == SRA_AM_REMOVED; |
| break; |
| |
| case GIMPLE_CALL: |
| /* Operands must be processed before the lhs. */ |
| for (i = 0; i < gimple_call_num_args (stmt); i++) |
| { |
| t = gimple_call_arg_ptr (stmt, i); |
| modified |= sra_modify_expr (t, &gsi, false); |
| } |
| |
| if (gimple_call_lhs (stmt)) |
| { |
| t = gimple_call_lhs_ptr (stmt); |
| modified |= sra_modify_expr (t, &gsi, true); |
| } |
| break; |
| |
| case GIMPLE_ASM: |
| for (i = 0; i < gimple_asm_ninputs (stmt); i++) |
| { |
| t = &TREE_VALUE (gimple_asm_input_op (stmt, i)); |
| modified |= sra_modify_expr (t, &gsi, false); |
| } |
| for (i = 0; i < gimple_asm_noutputs (stmt); i++) |
| { |
| t = &TREE_VALUE (gimple_asm_output_op (stmt, i)); |
| modified |= sra_modify_expr (t, &gsi, true); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (modified) |
| { |
| update_stmt (stmt); |
| if (maybe_clean_eh_stmt (stmt) |
| && gimple_purge_dead_eh_edges (gimple_bb (stmt))) |
| cfg_changed = true; |
| } |
| if (!deleted) |
| gsi_next (&gsi); |
| } |
| } |
| |
| return cfg_changed; |
| } |
| |
| /* Generate statements initializing scalar replacements of parts of function |
| parameters. */ |
| |
| static void |
| initialize_parameter_reductions (void) |
| { |
| gimple_stmt_iterator gsi; |
| gimple_seq seq = NULL; |
| tree parm; |
| |
| gsi = gsi_start (seq); |
| for (parm = DECL_ARGUMENTS (current_function_decl); |
| parm; |
| parm = DECL_CHAIN (parm)) |
| { |
| vec<access_p> *access_vec; |
| struct access *access; |
| |
| if (!bitmap_bit_p (candidate_bitmap, DECL_UID (parm))) |
| continue; |
| access_vec = get_base_access_vector (parm); |
| if (!access_vec) |
| continue; |
| |
| for (access = (*access_vec)[0]; |
| access; |
| access = access->next_grp) |
| generate_subtree_copies (access, parm, 0, 0, 0, &gsi, true, true, |
| EXPR_LOCATION (parm)); |
| } |
| |
| seq = gsi_seq (gsi); |
| if (seq) |
| gsi_insert_seq_on_edge_immediate (single_succ_edge (ENTRY_BLOCK_PTR), seq); |
| } |
| |
| /* The "main" function of intraprocedural SRA passes. Runs the analysis and if |
| it reveals there are components of some aggregates to be scalarized, it runs |
| the required transformations. */ |
| static unsigned int |
| perform_intra_sra (void) |
| { |
| int ret = 0; |
| sra_initialize (); |
| |
| if (!find_var_candidates ()) |
| goto out; |
| |
| if (!scan_function ()) |
| goto out; |
| |
| if (!analyze_all_variable_accesses ()) |
| goto out; |
| |
| if (sra_modify_function_body ()) |
| ret = TODO_update_ssa | TODO_cleanup_cfg; |
| else |
| ret = TODO_update_ssa; |
| initialize_parameter_reductions (); |
| |
| statistics_counter_event (cfun, "Scalar replacements created", |
| sra_stats.replacements); |
| statistics_counter_event (cfun, "Modified expressions", sra_stats.exprs); |
| statistics_counter_event (cfun, "Subtree copy stmts", |
| sra_stats.subtree_copies); |
| statistics_counter_event (cfun, "Subreplacement stmts", |
| sra_stats.subreplacements); |
| statistics_counter_event (cfun, "Deleted stmts", sra_stats.deleted); |
| statistics_counter_event (cfun, "Separate LHS and RHS handling", |
| sra_stats.separate_lhs_rhs_handling); |
| |
| out: |
| sra_deinitialize (); |
| return ret; |
| } |
| |
| /* Perform early intraprocedural SRA. */ |
| static unsigned int |
| early_intra_sra (void) |
| { |
| sra_mode = SRA_MODE_EARLY_INTRA; |
| return perform_intra_sra (); |
| } |
| |
| /* Perform "late" intraprocedural SRA. */ |
| static unsigned int |
| late_intra_sra (void) |
| { |
| sra_mode = SRA_MODE_INTRA; |
| return perform_intra_sra (); |
| } |
| |
| |
| static bool |
| gate_intra_sra (void) |
| { |
| return flag_tree_sra != 0 && dbg_cnt (tree_sra); |
| } |
| |
| |
| struct gimple_opt_pass pass_sra_early = |
| { |
| { |
| GIMPLE_PASS, |
| "esra", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| gate_intra_sra, /* gate */ |
| early_intra_sra, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| TV_TREE_SRA, /* tv_id */ |
| PROP_cfg | PROP_ssa, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_update_ssa |
| | TODO_ggc_collect |
| | TODO_verify_ssa /* todo_flags_finish */ |
| } |
| }; |
| |
| struct gimple_opt_pass pass_sra = |
| { |
| { |
| GIMPLE_PASS, |
| "sra", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| gate_intra_sra, /* gate */ |
| late_intra_sra, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| TV_TREE_SRA, /* tv_id */ |
| PROP_cfg | PROP_ssa, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| TODO_update_address_taken, /* todo_flags_start */ |
| TODO_update_ssa |
| | TODO_ggc_collect |
| | TODO_verify_ssa /* todo_flags_finish */ |
| } |
| }; |
| |
| |
| /* Return true iff PARM (which must be a parm_decl) is an unused scalar |
| parameter. */ |
| |
| static bool |
| is_unused_scalar_param (tree parm) |
| { |
| tree name; |
| return (is_gimple_reg (parm) |
| && (!(name = ssa_default_def (cfun, parm)) |
| || has_zero_uses (name))); |
| } |
| |
| /* Scan immediate uses of a default definition SSA name of a parameter PARM and |
| examine whether there are any direct or otherwise infeasible ones. If so, |
| return true, otherwise return false. PARM must be a gimple register with a |
| non-NULL default definition. */ |
| |
| static bool |
| ptr_parm_has_direct_uses (tree parm) |
| { |
| imm_use_iterator ui; |
| gimple stmt; |
| tree name = ssa_default_def (cfun, parm); |
| bool ret = false; |
| |
| FOR_EACH_IMM_USE_STMT (stmt, ui, name) |
| { |
| int uses_ok = 0; |
| use_operand_p use_p; |
| |
| if (is_gimple_debug (stmt)) |
| continue; |
| |
| /* Valid uses include dereferences on the lhs and the rhs. */ |
| if (gimple_has_lhs (stmt)) |
| { |
| tree lhs = gimple_get_lhs (stmt); |
| while (handled_component_p (lhs)) |
| lhs = TREE_OPERAND (lhs, 0); |
| if (TREE_CODE (lhs) == MEM_REF |
| && TREE_OPERAND (lhs, 0) == name |
| && integer_zerop (TREE_OPERAND (lhs, 1)) |
| && types_compatible_p (TREE_TYPE (lhs), |
| TREE_TYPE (TREE_TYPE (name))) |
| && !TREE_THIS_VOLATILE (lhs)) |
| uses_ok++; |
| } |
| if (gimple_assign_single_p (stmt)) |
| { |
| tree rhs = gimple_assign_rhs1 (stmt); |
| while (handled_component_p (rhs)) |
| rhs = TREE_OPERAND (rhs, 0); |
| if (TREE_CODE (rhs) == MEM_REF |
| && TREE_OPERAND (rhs, 0) == name |
| && integer_zerop (TREE_OPERAND (rhs, 1)) |
| && types_compatible_p (TREE_TYPE (rhs), |
| TREE_TYPE (TREE_TYPE (name))) |
| && !TREE_THIS_VOLATILE (rhs)) |
| uses_ok++; |
| } |
| else if (is_gimple_call (stmt)) |
| { |
| unsigned i; |
| for (i = 0; i < gimple_call_num_args (stmt); ++i) |
| { |
| tree arg = gimple_call_arg (stmt, i); |
| while (handled_component_p (arg)) |
| arg = TREE_OPERAND (arg, 0); |
| if (TREE_CODE (arg) == MEM_REF |
| && TREE_OPERAND (arg, 0) == name |
| && integer_zerop (TREE_OPERAND (arg, 1)) |
| && types_compatible_p (TREE_TYPE (arg), |
| TREE_TYPE (TREE_TYPE (name))) |
| && !TREE_THIS_VOLATILE (arg)) |
| uses_ok++; |
| } |
| } |
| |
| /* If the number of valid uses does not match the number of |
| uses in this stmt there is an unhandled use. */ |
| FOR_EACH_IMM_USE_ON_STMT (use_p, ui) |
| --uses_ok; |
| |
| if (uses_ok != 0) |
| ret = true; |
| |
| if (ret) |
| BREAK_FROM_IMM_USE_STMT (ui); |
| } |
| |
| return ret; |
| } |
| |
| /* Identify candidates for reduction for IPA-SRA based on their type and mark |
| them in candidate_bitmap. Note that these do not necessarily include |
| parameter which are unused and thus can be removed. Return true iff any |
| such candidate has been found. */ |
| |
| static bool |
| find_param_candidates (void) |
| { |
| tree parm; |
| int count = 0; |
| bool ret = false; |
| const char *msg; |
| |
| for (parm = DECL_ARGUMENTS (current_function_decl); |
| parm; |
| parm = DECL_CHAIN (parm)) |
| { |
| tree type = TREE_TYPE (parm); |
| void **slot; |
| |
| count++; |
| |
| if (TREE_THIS_VOLATILE (parm) |
| || TREE_ADDRESSABLE (parm) |
| || (!is_gimple_reg_type (type) && is_va_list_type (type))) |
| continue; |
| |
| if (is_unused_scalar_param (parm)) |
| { |
| ret = true; |
| continue; |
| } |
| |
| if (POINTER_TYPE_P (type)) |
| { |
| type = TREE_TYPE (type); |
| |
| if (TREE_CODE (type) == FUNCTION_TYPE |
| || TYPE_VOLATILE (type) |
| || (TREE_CODE (type) == ARRAY_TYPE |
| && TYPE_NONALIASED_COMPONENT (type)) |
| || !is_gimple_reg (parm) |
| || is_va_list_type (type) |
| || ptr_parm_has_direct_uses (parm)) |
| continue; |
| } |
| else if (!AGGREGATE_TYPE_P (type)) |
| continue; |
| |
| if (!COMPLETE_TYPE_P (type) |
| || !host_integerp (TYPE_SIZE (type), 1) |
| || tree_low_cst (TYPE_SIZE (type), 1) == 0 |
| || (AGGREGATE_TYPE_P (type) |
| && type_internals_preclude_sra_p (type, &msg))) |
| continue; |
| |
| bitmap_set_bit (candidate_bitmap, DECL_UID (parm)); |
| slot = htab_find_slot_with_hash (candidates, parm, |
| DECL_UID (parm), INSERT); |
| *slot = (void *) parm; |
| |
| ret = true; |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Candidate (%d): ", DECL_UID (parm)); |
| print_generic_expr (dump_file, parm, 0); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| func_param_count = count; |
| return ret; |
| } |
| |
| /* Callback of walk_aliased_vdefs, marks the access passed as DATA as |
| maybe_modified. */ |
| |
| static bool |
| mark_maybe_modified (ao_ref *ao ATTRIBUTE_UNUSED, tree vdef ATTRIBUTE_UNUSED, |
| void *data) |
| { |
| struct access *repr = (struct access *) data; |
| |
| repr->grp_maybe_modified = 1; |
| return true; |
| } |
| |
| /* Analyze what representatives (in linked lists accessible from |
| REPRESENTATIVES) can be modified by side effects of statements in the |
| current function. */ |
| |
| static void |
| analyze_modified_params (vec<access_p> representatives) |
| { |
| int i; |
| |
| for (i = 0; i < func_param_count; i++) |
| { |
| struct access *repr; |
| |
| for (repr = representatives[i]; |
| repr; |
| repr = repr->next_grp) |
| { |
| struct access *access; |
| bitmap visited; |
| ao_ref ar; |
| |
| if (no_accesses_p (repr)) |
| continue; |
| if (!POINTER_TYPE_P (TREE_TYPE (repr->base)) |
| || repr->grp_maybe_modified) |
| continue; |
| |
| ao_ref_init (&ar, repr->expr); |
| visited = BITMAP_ALLOC (NULL); |
| for (access = repr; access; access = access->next_sibling) |
| { |
| /* All accesses are read ones, otherwise grp_maybe_modified would |
| be trivially set. */ |
| walk_aliased_vdefs (&ar, gimple_vuse (access->stmt), |
| mark_maybe_modified, repr, &visited); |
| if (repr->grp_maybe_modified) |
| break; |
| } |
| BITMAP_FREE (visited); |
| } |
| } |
| } |
| |
| /* Propagate distances in bb_dereferences in the opposite direction than the |
| control flow edges, in each step storing the maximum of the current value |
| and the minimum of all successors. These steps are repeated until the table |
| stabilizes. Note that BBs which might terminate the functions (according to |
| final_bbs bitmap) never updated in this way. */ |
| |
| static void |
| propagate_dereference_distances (void) |
| { |
| vec<basic_block> queue; |
| basic_block bb; |
| |
| queue.create (last_basic_block_for_function (cfun)); |
| queue.quick_push (ENTRY_BLOCK_PTR); |
| FOR_EACH_BB (bb) |
| { |
| queue.quick_push (bb); |
| bb->aux = bb; |
| } |
| |
| while (!queue.is_empty ()) |
| { |
| edge_iterator ei; |
| edge e; |
| bool change = false; |
| int i; |
| |
| bb = queue.pop (); |
| bb->aux = NULL; |
| |
| if (bitmap_bit_p (final_bbs, bb->index)) |
| continue; |
| |
| for (i = 0; i < func_param_count; i++) |
| { |
| int idx = bb->index * func_param_count + i; |
| bool first = true; |
| HOST_WIDE_INT inh = 0; |
| |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| int succ_idx = e->dest->index * func_param_count + i; |
| |
| if (e->src == EXIT_BLOCK_PTR) |
| continue; |
| |
| if (first) |
| { |
| first = false; |
| inh = bb_dereferences [succ_idx]; |
| } |
| else if (bb_dereferences [succ_idx] < inh) |
| inh = bb_dereferences [succ_idx]; |
| } |
| |
| if (!first && bb_dereferences[idx] < inh) |
| { |
| bb_dereferences[idx] = inh; |
| change = true; |
| } |
| } |
| |
| if (change && !bitmap_bit_p (final_bbs, bb->index)) |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| { |
| if (e->src->aux) |
| continue; |
| |
| e->src->aux = e->src; |
| queue.quick_push (e->src); |
| } |
| } |
| |
| queue.release (); |
| } |
| |
| /* Dump a dereferences TABLE with heading STR to file F. */ |
| |
| static void |
| dump_dereferences_table (FILE *f, const char *str, HOST_WIDE_INT *table) |
| { |
| basic_block bb; |
| |
| fprintf (dump_file, str); |
| FOR_BB_BETWEEN (bb, ENTRY_BLOCK_PTR, EXIT_BLOCK_PTR, next_bb) |
| { |
| fprintf (f, "%4i %i ", bb->index, bitmap_bit_p (final_bbs, bb->index)); |
| if (bb != EXIT_BLOCK_PTR) |
| { |
| int i; |
| for (i = 0; i < func_param_count; i++) |
| { |
| int idx = bb->index * func_param_count + i; |
| fprintf (f, " %4" HOST_WIDE_INT_PRINT "d", table[idx]); |
| } |
| } |
| fprintf (f, "\n"); |
| } |
| fprintf (dump_file, "\n"); |
| } |
| |
| /* Determine what (parts of) parameters passed by reference that are not |
| assigned to are not certainly dereferenced in this function and thus the |
| dereferencing cannot be safely moved to the caller without potentially |
| introducing a segfault. Mark such REPRESENTATIVES as |
| grp_not_necessarilly_dereferenced. |
| |
| The dereferenced maximum "distance," i.e. the offset + size of the accessed |
| part is calculated rather than simple booleans are calculated for each |
| pointer parameter to handle cases when only a fraction of the whole |
| aggregate is allocated (see testsuite/gcc.c-torture/execute/ipa-sra-2.c for |
| an example). |
| |
| The maximum dereference distances for each pointer parameter and BB are |
| already stored in bb_dereference. This routine simply propagates these |
| values upwards by propagate_dereference_distances and then compares the |
| distances of individual parameters in the ENTRY BB to the equivalent |
| distances of each representative of a (fraction of a) parameter. */ |
| |
| static void |
| analyze_caller_dereference_legality (vec<access_p> representatives) |
| { |
| int i; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| dump_dereferences_table (dump_file, |
| "Dereference table before propagation:\n", |
| bb_dereferences); |
| |
| propagate_dereference_distances (); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| dump_dereferences_table (dump_file, |
| "Dereference table after propagation:\n", |
| bb_dereferences); |
| |
| for (i = 0; i < func_param_count; i++) |
| { |
| struct access *repr = representatives[i]; |
| int idx = ENTRY_BLOCK_PTR->index * func_param_count + i; |
| |
| if (!repr || no_accesses_p (repr)) |
| continue; |
| |
| do |
| { |
| if ((repr->offset + repr->size) > bb_dereferences[idx]) |
| repr->grp_not_necessarilly_dereferenced = 1; |
| repr = repr->next_grp; |
| } |
| while (repr); |
| } |
| } |
| |
| /* Return the representative access for the parameter declaration PARM if it is |
| a scalar passed by reference which is not written to and the pointer value |
| is not used directly. Thus, if it is legal to dereference it in the caller |
| and we can rule out modifications through aliases, such parameter should be |
| turned into one passed by value. Return NULL otherwise. */ |
| |
| static struct access * |
| unmodified_by_ref_scalar_representative (tree parm) |
| { |
| int i, access_count; |
| struct access *repr; |
| vec<access_p> *access_vec; |
| |
| access_vec = get_base_access_vector (parm); |
| gcc_assert (access_vec); |
| repr = (*access_vec)[0]; |
| if (repr->write) |
| return NULL; |
| repr->group_representative = repr; |
| |
| access_count = access_vec->length (); |
| for (i = 1; i < access_count; i++) |
| { |
| struct access *access = (*access_vec)[i]; |
| if (access->write) |
| return NULL; |
| access->group_representative = repr; |
| access->next_sibling = repr->next_sibling; |
| repr->next_sibling = access; |
| } |
| |
| repr->grp_read = 1; |
| repr->grp_scalar_ptr = 1; |
| return repr; |
| } |
| |
| /* Return true iff this ACCESS precludes IPA-SRA of the parameter it is |
| associated with. REQ_ALIGN is the minimum required alignment. */ |
| |
| static bool |
| access_precludes_ipa_sra_p (struct access *access, unsigned int req_align) |
| { |
| unsigned int exp_align; |
| /* Avoid issues such as the second simple testcase in PR 42025. The problem |
| is incompatible assign in a call statement (and possibly even in asm |
| statements). This can be relaxed by using a new temporary but only for |
| non-TREE_ADDRESSABLE types and is probably not worth the complexity. (In |
| intraprocedural SRA we deal with this by keeping the old aggregate around, |
| something we cannot do in IPA-SRA.) */ |
| if (access->write |
| && (is_gimple_call (access->stmt) |
| || gimple_code (access->stmt) == GIMPLE_ASM)) |
| return true; |
| |
| exp_align = get_object_alignment (access->expr); |
| if (exp_align < req_align) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Sort collected accesses for parameter PARM, identify representatives for |
| each accessed region and link them together. Return NULL if there are |
| different but overlapping accesses, return the special ptr value meaning |
| there are no accesses for this parameter if that is the case and return the |
| first representative otherwise. Set *RO_GRP if there is a group of accesses |
| with only read (i.e. no write) accesses. */ |
| |
| static struct access * |
| splice_param_accesses (tree parm, bool *ro_grp) |
| { |
| int i, j, access_count, group_count; |
| int agg_size, total_size = 0; |
| struct access *access, *res, **prev_acc_ptr = &res; |
| vec<access_p> *access_vec; |
| |
| access_vec = get_base_access_vector (parm); |
| if (!access_vec) |
| return &no_accesses_representant; |
| access_count = access_vec->length (); |
| |
| access_vec->qsort (compare_access_positions); |
| |
| i = 0; |
| total_size = 0; |
| group_count = 0; |
| while (i < access_count) |
| { |
| bool modification; |
| tree a1_alias_type; |
| access = (*access_vec)[i]; |
| modification = access->write; |
| if (access_precludes_ipa_sra_p (access, TYPE_ALIGN (access->type))) |
| return NULL; |
| a1_alias_type = reference_alias_ptr_type (access->expr); |
| |
| /* Access is about to become group representative unless we find some |
| nasty overlap which would preclude us from breaking this parameter |
| apart. */ |
| |
| j = i + 1; |
| while (j < access_count) |
| { |
| struct access *ac2 = (*access_vec)[j]; |
| if (ac2->offset != access->offset) |
| { |
| /* All or nothing law for parameters. */ |
| if (access->offset + access->size > ac2->offset) |
| return NULL; |
| else |
| break; |
| } |
| else if (ac2->size != access->size) |
| return NULL; |
| |
| if (access_precludes_ipa_sra_p (ac2, TYPE_ALIGN (access->type)) |
| || (ac2->type != access->type |
| && (TREE_ADDRESSABLE (ac2->type) |
| || TREE_ADDRESSABLE (access->type))) |
| || (reference_alias_ptr_type (ac2->expr) != a1_alias_type)) |
| return NULL; |
| |
| modification |= ac2->write; |
| ac2->group_representative = access; |
| ac2->next_sibling = access->next_sibling; |
| access->next_sibling = ac2; |
| j++; |
| } |
| |
| group_count++; |
| access->grp_maybe_modified = modification; |
| if (!modification) |
| *ro_grp = true; |
| *prev_acc_ptr = access; |
| prev_acc_ptr = &access->next_grp; |
| total_size += access->size; |
| i = j; |
| } |
| |
| if (POINTER_TYPE_P (TREE_TYPE (parm))) |
| agg_size = tree_low_cst (TYPE_SIZE (TREE_TYPE (TREE_TYPE (parm))), 1); |
| else |
| agg_size = tree_low_cst (TYPE_SIZE (TREE_TYPE (parm)), 1); |
| if (total_size >= agg_size) |
| return NULL; |
| |
| gcc_assert (group_count > 0); |
| return res; |
| } |
| |
| /* Decide whether parameters with representative accesses given by REPR should |
| be reduced into components. */ |
| |
| static int |
| decide_one_param_reduction (struct access *repr) |
| { |
| int total_size, cur_parm_size, agg_size, new_param_count, parm_size_limit; |
| bool by_ref; |
| tree parm; |
| |
| parm = repr->base; |
| cur_parm_size = tree_low_cst (TYPE_SIZE (TREE_TYPE (parm)), 1); |
| gcc_assert (cur_parm_size > 0); |
| |
| if (POINTER_TYPE_P (TREE_TYPE (parm))) |
| { |
| by_ref = true; |
| agg_size = tree_low_cst (TYPE_SIZE (TREE_TYPE (TREE_TYPE (parm))), 1); |
| } |
| else |
| { |
| by_ref = false; |
| agg_size = cur_parm_size; |
| } |
| |
| if (dump_file) |
| { |
| struct access *acc; |
| fprintf (dump_file, "Evaluating PARAM group sizes for "); |
| print_generic_expr (dump_file, parm, 0); |
| fprintf (dump_file, " (UID: %u): \n", DECL_UID (parm)); |
| for (acc = repr; acc; acc = acc->next_grp) |
| dump_access (dump_file, acc, true); |
| } |
| |
| total_size = 0; |
| new_param_count = 0; |
| |
| for (; repr; repr = repr->next_grp) |
| { |
| gcc_assert (parm == repr->base); |
| |
| /* Taking the address of a non-addressable field is verboten. */ |
| if (by_ref && repr->non_addressable) |
| return 0; |
| |
| /* Do not decompose a non-BLKmode param in a way that would |
| create BLKmode params. Especially for by-reference passing |
| (thus, pointer-type param) this is hardly worthwhile. */ |
| if (DECL_MODE (parm) != BLKmode |
| && TYPE_MODE (repr->type) == BLKmode) |
| return 0; |
| |
| if (!by_ref || (!repr->grp_maybe_modified |
| && !repr->grp_not_necessarilly_dereferenced)) |
| total_size += repr->size; |
| else |
| total_size += cur_parm_size; |
| |
| new_param_count++; |
| } |
| |
| gcc_assert (new_param_count > 0); |
| |
| if (optimize_function_for_size_p (cfun)) |
| parm_size_limit = cur_parm_size; |
| else |
| parm_size_limit = (PARAM_VALUE (PARAM_IPA_SRA_PTR_GROWTH_FACTOR) |
| * cur_parm_size); |
| |
| if (total_size < agg_size |
| && total_size <= parm_size_limit) |
| { |
| if (dump_file) |
| fprintf (dump_file, " ....will be split into %i components\n", |
| new_param_count); |
| return new_param_count; |
| } |
| else |
| return 0; |
| } |
| |
| /* The order of the following enums is important, we need to do extra work for |
| UNUSED_PARAMS, BY_VAL_ACCESSES and UNMODIF_BY_REF_ACCESSES. */ |
| enum ipa_splicing_result { NO_GOOD_ACCESS, UNUSED_PARAMS, BY_VAL_ACCESSES, |
| MODIF_BY_REF_ACCESSES, UNMODIF_BY_REF_ACCESSES }; |
| |
| /* Identify representatives of all accesses to all candidate parameters for |
| IPA-SRA. Return result based on what representatives have been found. */ |
| |
| static enum ipa_splicing_result |
| splice_all_param_accesses (vec<access_p> &representatives) |
| { |
| enum ipa_splicing_result result = NO_GOOD_ACCESS; |
| tree parm; |
| struct access *repr; |
| |
| representatives.create (func_param_count); |
| |
| for (parm = DECL_ARGUMENTS (current_function_decl); |
| parm; |
| parm = DECL_CHAIN (parm)) |
| { |
| if (is_unused_scalar_param (parm)) |
| { |
| representatives.quick_push (&no_accesses_representant); |
| if (result == NO_GOOD_ACCESS) |
| result = UNUSED_PARAMS; |
| } |
| else if (POINTER_TYPE_P (TREE_TYPE (parm)) |
| && is_gimple_reg_type (TREE_TYPE (TREE_TYPE (parm))) |
| && bitmap_bit_p (candidate_bitmap, DECL_UID (parm))) |
| { |
| repr = unmodified_by_ref_scalar_representative (parm); |
| representatives.quick_push (repr); |
| if (repr) |
| result = UNMODIF_BY_REF_ACCESSES; |
| } |
| else if (bitmap_bit_p (candidate_bitmap, DECL_UID (parm))) |
| { |
| bool ro_grp = false; |
| repr = splice_param_accesses (parm, &ro_grp); |
| representatives.quick_push (repr); |
| |
| if (repr && !no_accesses_p (repr)) |
| { |
| if (POINTER_TYPE_P (TREE_TYPE (parm))) |
| { |
| if (ro_grp) |
| result = UNMODIF_BY_REF_ACCESSES; |
| else if (result < MODIF_BY_REF_ACCESSES) |
| result = MODIF_BY_REF_ACCESSES; |
| } |
| else if (result < BY_VAL_ACCESSES) |
| result = BY_VAL_ACCESSES; |
| } |
| else if (no_accesses_p (repr) && (result == NO_GOOD_ACCESS)) |
| result = UNUSED_PARAMS; |
| } |
| else |
| representatives.quick_push (NULL); |
| } |
| |
| if (result == NO_GOOD_ACCESS) |
| { |
| representatives.release (); |
| return NO_GOOD_ACCESS; |
| } |
| |
| return result; |
| } |
| |
| /* Return the index of BASE in PARMS. Abort if it is not found. */ |
| |
| static inline int |
| get_param_index (tree base, vec<tree> parms) |
| { |
| int i, len; |
| |
| len = parms.length (); |
| for (i = 0; i < len; i++) |
| if (parms[i] == base) |
| return i; |
| gcc_unreachable (); |
| } |
| |
| /* Convert the decisions made at the representative level into compact |
| parameter adjustments. REPRESENTATIVES are pointers to first |
| representatives of each param accesses, ADJUSTMENTS_COUNT is the expected |
| final number of adjustments. */ |
| |
| static ipa_parm_adjustment_vec |
| turn_representatives_into_adjustments (vec<access_p> representatives, |
| int adjustments_count) |
| { |
| vec<tree> parms; |
| ipa_parm_adjustment_vec adjustments; |
| tree parm; |
| int i; |
| |
| gcc_assert (adjustments_count > 0); |
| parms = ipa_get_vector_of_formal_parms (current_function_decl); |
| adjustments.create (adjustments_count); |
| parm = DECL_ARGUMENTS (current_function_decl); |
| for (i = 0; i < func_param_count; i++, parm = DECL_CHAIN (parm)) |
| { |
| struct access *repr = representatives[i]; |
| |
| if (!repr || no_accesses_p (repr)) |
| { |
| struct ipa_parm_adjustment adj; |
| |
| memset (&adj, 0, sizeof (adj)); |
| adj.base_index = get_param_index (parm, parms); |
| adj.base = parm; |
| if (!repr) |
| adj.copy_param = 1; |
| else |
| adj.remove_param = 1; |
| adjustments.quick_push (adj); |
| } |
| else |
| { |
| struct ipa_parm_adjustment adj; |
| int index = get_param_index (parm, parms); |
| |
| for (; repr; repr = repr->next_grp) |
| { |
| memset (&adj, 0, sizeof (adj)); |
| gcc_assert (repr->base == parm); |
| adj.base_index = index; |
| adj.base = repr->base; |
| adj.type = repr->type; |
| adj.alias_ptr_type = reference_alias_ptr_type (repr->expr); |
| adj.offset = repr->offset; |
| adj.by_ref = (POINTER_TYPE_P (TREE_TYPE (repr->base)) |
| && (repr->grp_maybe_modified |
| || repr->grp_not_necessarilly_dereferenced)); |
| adjustments.quick_push (adj); |
| } |
| } |
| } |
| parms.release (); |
| return adjustments; |
| } |
| |
| /* Analyze the collected accesses and produce a plan what to do with the |
| parameters in the form of adjustments, NULL meaning nothing. */ |
| |
| static ipa_parm_adjustment_vec |
| analyze_all_param_acesses (void) |
| { |
| enum ipa_splicing_result repr_state; |
| bool proceed = false; |
| int i, adjustments_count = 0; |
| vec<access_p> representatives; |
| ipa_parm_adjustment_vec adjustments; |
| |
| repr_state = splice_all_param_accesses (representatives); |
| if (repr_state == NO_GOOD_ACCESS) |
| return ipa_parm_adjustment_vec(); |
| |
| /* If there are any parameters passed by reference which are not modified |
| directly, we need to check whether they can be modified indirectly. */ |
| if (repr_state == UNMODIF_BY_REF_ACCESSES) |
| { |
| analyze_caller_dereference_legality (representatives); |
| analyze_modified_params (representatives); |
| } |
| |
| for (i = 0; i < func_param_count; i++) |
| { |
| struct access *repr = representatives[i]; |
| |
| if (repr && !no_accesses_p (repr)) |
| { |
| if (repr->grp_scalar_ptr) |
| { |
| adjustments_count++; |
| if (repr->grp_not_necessarilly_dereferenced |
| || repr->grp_maybe_modified) |
| representatives[i] = NULL; |
| else |
| { |
| proceed = true; |
| sra_stats.scalar_by_ref_to_by_val++; |
| } |
| } |
| else |
| { |
| int new_components = decide_one_param_reduction (repr); |
| |
| if (new_components == 0) |
| { |
| representatives[i] = NULL; |
| adjustments_count++; |
| } |
| else |
| { |
| adjustments_count += new_components; |
| sra_stats.aggregate_params_reduced++; |
| sra_stats.param_reductions_created += new_components; |
| proceed = true; |
| } |
| } |
| } |
| else |
| { |
| if (no_accesses_p (repr)) |
| { |
| proceed = true; |
| sra_stats.deleted_unused_parameters++; |
| } |
| adjustments_count++; |
| } |
| } |
| |
| if (!proceed && dump_file) |
| fprintf (dump_file, "NOT proceeding to change params.\n"); |
| |
| if (proceed) |
| adjustments = turn_representatives_into_adjustments (representatives, |
| adjustments_count); |
| else |
| adjustments = ipa_parm_adjustment_vec(); |
| |
| representatives.release (); |
| return adjustments; |
| } |
| |
| /* If a parameter replacement identified by ADJ does not yet exist in the form |
| of declaration, create it and record it, otherwise return the previously |
| created one. */ |
| |
| static tree |
| get_replaced_param_substitute (struct ipa_parm_adjustment *adj) |
| { |
| tree repl; |
| if (!adj->new_ssa_base) |
| { |
| char *pretty_name = make_fancy_name (adj->base); |
| |
| repl = create_tmp_reg (TREE_TYPE (adj->base), "ISR"); |
| DECL_NAME (repl) = get_identifier (pretty_name); |
| obstack_free (&name_obstack, pretty_name); |
| |
| adj->new_ssa_base = repl; |
| } |
| else |
| repl = adj->new_ssa_base; |
| return repl; |
| } |
| |
| /* Find the first adjustment for a particular parameter BASE in a vector of |
| ADJUSTMENTS which is not a copy_param. Return NULL if there is no such |
| adjustment. */ |
| |
| static struct ipa_parm_adjustment * |
| get_adjustment_for_base (ipa_parm_adjustment_vec adjustments, tree base) |
| { |
| int i, len; |
| |
| len = adjustments.length (); |
| for (i = 0; i < len; i++) |
| { |
| struct ipa_parm_adjustment *adj; |
| |
| adj = &adjustments[i]; |
| if (!adj->copy_param && adj->base == base) |
| return adj; |
| } |
| |
| return NULL; |
| } |
| |
| /* If the statement STMT defines an SSA_NAME of a parameter which is to be |
| removed because its value is not used, replace the SSA_NAME with a one |
| relating to a created VAR_DECL together all of its uses and return true. |
| ADJUSTMENTS is a pointer to an adjustments vector. */ |
| |
| static bool |
| replace_removed_params_ssa_names (gimple stmt, |
| ipa_parm_adjustment_vec adjustments) |
| { |
| struct ipa_parm_adjustment *adj; |
| tree lhs, decl, repl, name; |
| |
| if (gimple_code (stmt) == GIMPLE_PHI) |
| lhs = gimple_phi_result (stmt); |
| else if (is_gimple_assign (stmt)) |
| lhs = gimple_assign_lhs (stmt); |
| else if (is_gimple_call (stmt)) |
| lhs = gimple_call_lhs (stmt); |
| else |
| gcc_unreachable (); |
| |
| if (TREE_CODE (lhs) != SSA_NAME) |
| return false; |
| |
| decl = SSA_NAME_VAR (lhs); |
| if (decl == NULL_TREE |
| || TREE_CODE (decl) != PARM_DECL) |
| return false; |
| |
| adj = get_adjustment_for_base (adjustments, decl); |
| if (!adj) |
| return false; |
| |
| repl = get_replaced_param_substitute (adj); |
| name = make_ssa_name (repl, stmt); |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, "replacing an SSA name of a removed param "); |
| print_generic_expr (dump_file, lhs, 0); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, name, 0); |
| fprintf (dump_file, "\n"); |
| } |
| |
| if (is_gimple_assign (stmt)) |
| gimple_assign_set_lhs (stmt, name); |
| else if (is_gimple_call (stmt)) |
| gimple_call_set_lhs (stmt, name); |
| else |
| gimple_phi_set_result (stmt, name); |
| |
| replace_uses_by (lhs, name); |
| release_ssa_name (lhs); |
| return true; |
| } |
| |
| /* If the expression *EXPR should be replaced by a reduction of a parameter, do |
| so. ADJUSTMENTS is a pointer to a vector of adjustments. CONVERT |
| specifies whether the function should care about type incompatibility the |
| current and new expressions. If it is false, the function will leave |
| incompatibility issues to the caller. Return true iff the expression |
| was modified. */ |
| |
| static bool |
| sra_ipa_modify_expr (tree *expr, bool convert, |
| ipa_parm_adjustment_vec adjustments) |
| { |
| int i, len; |
| struct ipa_parm_adjustment *adj, *cand = NULL; |
| HOST_WIDE_INT offset, size, max_size; |
| tree base, src; |
| |
| len = adjustments.length (); |
| |
| if (TREE_CODE (*expr) == BIT_FIELD_REF |
| || TREE_CODE (*expr) == IMAGPART_EXPR |
| || TREE_CODE (*expr) == REALPART_EXPR) |
| { |
| expr = &TREE_OPERAND (*expr, 0); |
| convert = true; |
| } |
| |
| base = get_ref_base_and_extent (*expr, &offset, &size, &max_size); |
| if (!base || size == -1 || max_size == -1) |
| return false; |
| |
| if (TREE_CODE (base) == MEM_REF) |
| { |
| offset += mem_ref_offset (base).low * BITS_PER_UNIT; |
| base = TREE_OPERAND (base, 0); |
| } |
| |
| base = get_ssa_base_param (base); |
| if (!base || TREE_CODE (base) != PARM_DECL) |
| return false; |
| |
| for (i = 0; i < len; i++) |
| { |
| adj = &adjustments[i]; |
| |
| if (adj->base == base && |
| (adj->offset == offset || adj->remove_param)) |
| { |
| cand = adj; |
| break; |
| } |
| } |
| if (!cand || cand->copy_param || cand->remove_param) |
| return false; |
| |
| if (cand->by_ref) |
| src = build_simple_mem_ref (cand->reduction); |
| else |
| src = cand->reduction; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "About to replace expr "); |
| print_generic_expr (dump_file, *expr, 0); |
| fprintf (dump_file, " with "); |
| print_generic_expr (dump_file, src, 0); |
| fprintf (dump_file, "\n"); |
| } |
| |
| if (convert && !useless_type_conversion_p (TREE_TYPE (*expr), cand->type)) |
| { |
| tree vce = build1 (VIEW_CONVERT_EXPR, TREE_TYPE (*expr), src); |
| *expr = vce; |
| } |
| else |
| *expr = src; |
| return true; |
| } |
| |
| /* If the statement pointed to by STMT_PTR contains any expressions that need |
| to replaced with a different one as noted by ADJUSTMENTS, do so. Handle any |
| potential type incompatibilities (GSI is used to accommodate conversion |
| statements and must point to the statement). Return true iff the statement |
| was modified. */ |
| |
| static bool |
| sra_ipa_modify_assign (gimple *stmt_ptr, gimple_stmt_iterator *gsi, |
| ipa_parm_adjustment_vec adjustments) |
| { |
| gimple stmt = *stmt_ptr; |
| tree *lhs_p, *rhs_p; |
| bool any; |
| |
| if (!gimple_assign_single_p (stmt)) |
| return false; |
| |
| rhs_p = gimple_assign_rhs1_ptr (stmt); |
| lhs_p = gimple_assign_lhs_ptr (stmt); |
| |
| any = sra_ipa_modify_expr (rhs_p, false, adjustments); |
| any |= sra_ipa_modify_expr (lhs_p, false, adjustments); |
| if (any) |
| { |
| tree new_rhs = NULL_TREE; |
| |
| if (!useless_type_conversion_p (TREE_TYPE (*lhs_p), TREE_TYPE (*rhs_p))) |
| { |
| if (TREE_CODE (*rhs_p) == CONSTRUCTOR) |
| { |
| /* V_C_Es of constructors can cause trouble (PR 42714). */ |
| if (is_gimple_reg_type (TREE_TYPE (*lhs_p))) |
| *rhs_p = build_zero_cst (TREE_TYPE (*lhs_p)); |
| else |
| *rhs_p = build_constructor (TREE_TYPE (*lhs_p), |
| NULL); |
| } |
| else |
| new_rhs = fold_build1_loc (gimple_location (stmt), |
| VIEW_CONVERT_EXPR, TREE_TYPE (*lhs_p), |
| *rhs_p); |
| } |
| else if (REFERENCE_CLASS_P (*rhs_p) |
| && is_gimple_reg_type (TREE_TYPE (*lhs_p)) |
| && !is_gimple_reg (*lhs_p)) |
| /* This can happen when an assignment in between two single field |
| structures is turned into an assignment in between two pointers to |
| scalars (PR 42237). */ |
| new_rhs = *rhs_p; |
| |
| if (new_rhs) |
| { |
| tree tmp = force_gimple_operand_gsi (gsi, new_rhs, true, NULL_TREE, |
| true, GSI_SAME_STMT); |
| |
| gimple_assign_set_rhs_from_tree (gsi, tmp); |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Traverse the function body and all modifications as described in |
| ADJUSTMENTS. Return true iff the CFG has been changed. */ |
| |
| static bool |
| ipa_sra_modify_function_body (ipa_parm_adjustment_vec adjustments) |
| { |
| bool cfg_changed = false; |
| basic_block bb; |
| |
| FOR_EACH_BB (bb) |
| { |
| gimple_stmt_iterator gsi; |
| |
| for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi)) |
| replace_removed_params_ssa_names (gsi_stmt (gsi), adjustments); |
| |
| gsi = gsi_start_bb (bb); |
| while (!gsi_end_p (gsi)) |
| { |
| gimple stmt = gsi_stmt (gsi); |
| bool modified = false; |
| tree *t; |
| unsigned i; |
| |
| switch (gimple_code (stmt)) |
| { |
| case GIMPLE_RETURN: |
| t = gimple_return_retval_ptr (stmt); |
| if (*t != NULL_TREE) |
| modified |= sra_ipa_modify_expr (t, true, adjustments); |
| break; |
| |
| case GIMPLE_ASSIGN: |
| modified |= sra_ipa_modify_assign (&stmt, &gsi, adjustments); |
| modified |= replace_removed_params_ssa_names (stmt, adjustments); |
| break; |
| |
| case GIMPLE_CALL: |
| /* Operands must be processed before the lhs. */ |
| for (i = 0; i < gimple_call_num_args (stmt); i++) |
| { |
| t = gimple_call_arg_ptr (stmt, i); |
| modified |= sra_ipa_modify_expr (t, true, adjustments); |
| } |
| |
| if (gimple_call_lhs (stmt)) |
| { |
| t = gimple_call_lhs_ptr (stmt); |
| modified |= sra_ipa_modify_expr (t, false, adjustments); |
| modified |= replace_removed_params_ssa_names (stmt, |
| adjustments); |
| } |
| break; |
| |
| case GIMPLE_ASM: |
| for (i = 0; i < gimple_asm_ninputs (stmt); i++) |
| { |
| t = &TREE_VALUE (gimple_asm_input_op (stmt, i)); |
| modified |= sra_ipa_modify_expr (t, true, adjustments); |
| } |
| for (i = 0; i < gimple_asm_noutputs (stmt); i++) |
| { |
| t = &TREE_VALUE (gimple_asm_output_op (stmt, i)); |
| modified |= sra_ipa_modify_expr (t, false, adjustments); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (modified) |
| { |
| update_stmt (stmt); |
| if (maybe_clean_eh_stmt (stmt) |
| && gimple_purge_dead_eh_edges (gimple_bb (stmt))) |
| cfg_changed = true; |
| } |
| gsi_next (&gsi); |
| } |
| } |
| |
| return cfg_changed; |
| } |
| |
| /* Call gimple_debug_bind_reset_value on all debug statements describing |
| gimple register parameters that are being removed or replaced. */ |
| |
| static void |
| sra_ipa_reset_debug_stmts (ipa_parm_adjustment_vec adjustments) |
| { |
| int i, len; |
| gimple_stmt_iterator *gsip = NULL, gsi; |
| |
| if (MAY_HAVE_DEBUG_STMTS && single_succ_p (ENTRY_BLOCK_PTR)) |
| { |
| gsi = gsi_after_labels (single_succ (ENTRY_BLOCK_PTR)); |
| gsip = &gsi; |
| } |
| len = adjustments.length (); |
| for (i = 0; i < len; i++) |
| { |
| struct ipa_parm_adjustment *adj; |
| imm_use_iterator ui; |
| gimple stmt, def_temp; |
| tree name, vexpr, copy = NULL_TREE; |
| use_operand_p use_p; |
| |
| adj = &adjustments[i]; |
| if (adj->copy_param || !is_gimple_reg (adj->base)) |
| continue; |
| name = ssa_default_def (cfun, adj->base); |
| vexpr = NULL; |
| if (name) |
| FOR_EACH_IMM_USE_STMT (stmt, ui, name) |
| { |
| /* All other users must have been removed by |
| ipa_sra_modify_function_body. */ |
| gcc_assert (is_gimple_debug (stmt)); |
| if (vexpr == NULL && gsip != NULL) |
| { |
| gcc_assert (TREE_CODE (adj->base) == PARM_DECL); |
| vexpr = make_node (DEBUG_EXPR_DECL); |
| def_temp = gimple_build_debug_source_bind (vexpr, adj->base, |
| NULL); |
| DECL_ARTIFICIAL (vexpr) = 1; |
| TREE_TYPE (vexpr) = TREE_TYPE (name); |
| DECL_MODE (vexpr) = DECL_MODE (adj->base); |
| gsi_insert_before (gsip, def_temp, GSI_SAME_STMT); |
| } |
| if (vexpr) |
| { |
| FOR_EACH_IMM_USE_ON_STMT (use_p, ui) |
| SET_USE (use_p, vexpr); |
| } |
| else |
| gimple_debug_bind_reset_value (stmt); |
| update_stmt (stmt); |
| } |
| /* Create a VAR_DECL for debug info purposes. */ |
| if (!DECL_IGNORED_P (adj->base)) |
| { |
| copy = build_decl (DECL_SOURCE_LOCATION (current_function_decl), |
| VAR_DECL, DECL_NAME (adj->base), |
| TREE_TYPE (adj->base)); |
| if (DECL_PT_UID_SET_P (adj->base)) |
| SET_DECL_PT_UID (copy, DECL_PT_UID (adj->base)); |
| TREE_ADDRESSABLE (copy) = TREE_ADDRESSABLE (adj->base); |
| TREE_READONLY (copy) = TREE_READONLY (adj->base); |
| TREE_THIS_VOLATILE (copy) = TREE_THIS_VOLATILE (adj->base); |
| DECL_GIMPLE_REG_P (copy) = DECL_GIMPLE_REG_P (adj->base); |
| DECL_ARTIFICIAL (copy) = DECL_ARTIFICIAL (adj->base); |
| DECL_IGNORED_P (copy) = DECL_IGNORED_P (adj->base); |
| DECL_ABSTRACT_ORIGIN (copy) = DECL_ORIGIN (adj->base); |
| DECL_SEEN_IN_BIND_EXPR_P (copy) = 1; |
| SET_DECL_RTL (copy, 0); |
| TREE_USED (copy) = 1; |
| DECL_CONTEXT (copy) = current_function_decl; |
| add_local_decl (cfun, copy); |
| DECL_CHAIN (copy) = |
| BLOCK_VARS (DECL_INITIAL (current_function_decl)); |
| BLOCK_VARS (DECL_INITIAL (current_function_decl)) = copy; |
| } |
| if (gsip != NULL && copy && target_for_debug_bind (adj->base)) |
| { |
| gcc_assert (TREE_CODE (adj->base) == PARM_DECL); |
| if (vexpr) |
| def_temp = gimple_build_debug_bind (copy, vexpr, NULL); |
| else |
| def_temp = gimple_build_debug_source_bind (copy, adj->base, |
| NULL); |
| gsi_insert_before (gsip, def_temp, GSI_SAME_STMT); |
| } |
| } |
| } |
| |
| /* Return false iff all callers have at least as many actual arguments as there |
| are formal parameters in the current function. */ |
| |
| static bool |
| not_all_callers_have_enough_arguments_p (struct cgraph_node *node, |
| void *data ATTRIBUTE_UNUSED) |
| { |
| struct cgraph_edge *cs; |
| for (cs = node->callers; cs; cs = cs->next_caller) |
| if (!callsite_has_enough_arguments_p (cs->call_stmt)) |
| return true; |
| |
| return false; |
| } |
| |
| /* Convert all callers of NODE. */ |
| |
| static bool |
| convert_callers_for_node (struct cgraph_node *node, |
| void *data) |
| { |
| ipa_parm_adjustment_vec *adjustments = (ipa_parm_adjustment_vec *) data; |
| bitmap recomputed_callers = BITMAP_ALLOC (NULL); |
| struct cgraph_edge *cs; |
| |
| for (cs = node->callers; cs; cs = cs->next_caller) |
| { |
| push_cfun (DECL_STRUCT_FUNCTION (cs->caller->symbol.decl)); |
| |
| if (dump_file) |
| fprintf (dump_file, "Adjusting call (%i -> %i) %s -> %s\n", |
| cs->caller->uid, cs->callee->uid, |
| xstrdup (cgraph_node_name (cs->caller)), |
| xstrdup (cgraph_node_name (cs->callee))); |
| |
| if (cs->call_stmt) |
| ipa_modify_call_arguments (cs, cs->call_stmt, *adjustments); |
| |
| pop_cfun (); |
| } |
| |
| for (cs = node->callers; cs; cs = cs->next_caller) |
| if (bitmap_set_bit (recomputed_callers, cs->caller->uid) |
| && gimple_in_ssa_p (DECL_STRUCT_FUNCTION (cs->caller->symbol.decl))) |
| compute_inline_parameters (cs->caller, true); |
| BITMAP_FREE (recomputed_callers); |
| |
| return true; |
| } |
| |
| /* Convert all callers of NODE to pass parameters as given in ADJUSTMENTS. */ |
| |
| static void |
| convert_callers (struct cgraph_node *node, tree old_decl, |
| ipa_parm_adjustment_vec adjustments) |
| { |
| basic_block this_block; |
| |
| cgraph_for_node_and_aliases (node, convert_callers_for_node, |
| &adjustments, false); |
| |
| if (!encountered_recursive_call) |
| return; |
| |
| FOR_EACH_BB (this_block) |
| { |
| gimple_stmt_iterator gsi; |
| |
| for (gsi = gsi_start_bb (this_block); !gsi_end_p (gsi); gsi_next (&gsi)) |
| { |
| gimple stmt = gsi_stmt (gsi); |
| tree call_fndecl; |
| if (gimple_code (stmt) != GIMPLE_CALL) |
| continue; |
| call_fndecl = gimple_call_fndecl (stmt); |
| if (call_fndecl == old_decl) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Adjusting recursive call"); |
| gimple_call_set_fndecl (stmt, node->symbol.decl); |
| ipa_modify_call_arguments (NULL, stmt, adjustments); |
| } |
| } |
| } |
| |
| return; |
| } |
| |
| /* Perform all the modification required in IPA-SRA for NODE to have parameters |
| as given in ADJUSTMENTS. Return true iff the CFG has been changed. */ |
| |
| static bool |
| modify_function (struct cgraph_node *node, ipa_parm_adjustment_vec adjustments) |
| { |
| struct cgraph_node *new_node; |
| bool cfg_changed; |
| vec<cgraph_edge_p> redirect_callers = collect_callers_of_node (node); |
| |
| rebuild_cgraph_edges (); |
| free_dominance_info (CDI_DOMINATORS); |
| pop_cfun (); |
| |
| new_node = cgraph_function_versioning (node, redirect_callers, |
| NULL, |
| NULL, false, NULL, NULL, "isra"); |
| redirect_callers.release (); |
| |
| push_cfun (DECL_STRUCT_FUNCTION (new_node->symbol.decl)); |
| ipa_modify_formal_parameters (current_function_decl, adjustments, "ISRA"); |
| cfg_changed = ipa_sra_modify_function_body (adjustments); |
| sra_ipa_reset_debug_stmts (adjustments); |
| convert_callers (new_node, node->symbol.decl, adjustments); |
| cgraph_make_node_local (new_node); |
| |
| return cfg_changed; |
| } |
| |
| /* Return false the function is apparently unsuitable for IPA-SRA based on it's |
| attributes, return true otherwise. NODE is the cgraph node of the current |
| function. */ |
| |
| static bool |
| ipa_sra_preliminary_function_checks (struct cgraph_node *node) |
| { |
| if (!cgraph_node_can_be_local_p (node)) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function not local to this compilation unit.\n"); |
| return false; |
| } |
| |
| if (!node->local.can_change_signature) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function can not change signature.\n"); |
| return false; |
| } |
| |
| if (!tree_versionable_function_p (node->symbol.decl)) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function is not versionable.\n"); |
| return false; |
| } |
| |
| if (DECL_VIRTUAL_P (current_function_decl)) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function is a virtual method.\n"); |
| return false; |
| } |
| |
| if ((DECL_COMDAT (node->symbol.decl) || DECL_EXTERNAL (node->symbol.decl)) |
| && inline_summary(node)->size >= MAX_INLINE_INSNS_AUTO) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function too big to be made truly local.\n"); |
| return false; |
| } |
| |
| if (!node->callers) |
| { |
| if (dump_file) |
| fprintf (dump_file, |
| "Function has no callers in this compilation unit.\n"); |
| return false; |
| } |
| |
| if (cfun->stdarg) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function uses stdarg. \n"); |
| return false; |
| } |
| |
| if (TYPE_ATTRIBUTES (TREE_TYPE (node->symbol.decl))) |
| return false; |
| |
| return true; |
| } |
| |
| /* Perform early interprocedural SRA. */ |
| |
| static unsigned int |
| ipa_early_sra (void) |
| { |
| struct cgraph_node *node = cgraph_get_node (current_function_decl); |
| ipa_parm_adjustment_vec adjustments; |
| int ret = 0; |
| |
| if (!ipa_sra_preliminary_function_checks (node)) |
| return 0; |
| |
| sra_initialize (); |
| sra_mode = SRA_MODE_EARLY_IPA; |
| |
| if (!find_param_candidates ()) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function has no IPA-SRA candidates.\n"); |
| goto simple_out; |
| } |
| |
| if (cgraph_for_node_and_aliases (node, not_all_callers_have_enough_arguments_p, |
| NULL, true)) |
| { |
| if (dump_file) |
| fprintf (dump_file, "There are callers with insufficient number of " |
| "arguments.\n"); |
| goto simple_out; |
| } |
| |
| bb_dereferences = XCNEWVEC (HOST_WIDE_INT, |
| func_param_count |
| * last_basic_block_for_function (cfun)); |
| final_bbs = BITMAP_ALLOC (NULL); |
| |
| scan_function (); |
| if (encountered_apply_args) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function calls __builtin_apply_args().\n"); |
| goto out; |
| } |
| |
| if (encountered_unchangable_recursive_call) |
| { |
| if (dump_file) |
| fprintf (dump_file, "Function calls itself with insufficient " |
| "number of arguments.\n"); |
| goto out; |
| } |
| |
| adjustments = analyze_all_param_acesses (); |
| if (!adjustments.exists ()) |
| goto out; |
| if (dump_file) |
| ipa_dump_param_adjustments (dump_file, adjustments, current_function_decl); |
| |
| if (modify_function (node, adjustments)) |
| ret = TODO_update_ssa | TODO_cleanup_cfg; |
| else |
| ret = TODO_update_ssa; |
| adjustments.release (); |
| |
| statistics_counter_event (cfun, "Unused parameters deleted", |
| sra_stats.deleted_unused_parameters); |
| statistics_counter_event (cfun, "Scalar parameters converted to by-value", |
| sra_stats.scalar_by_ref_to_by_val); |
| statistics_counter_event (cfun, "Aggregate parameters broken up", |
| sra_stats.aggregate_params_reduced); |
| statistics_counter_event (cfun, "Aggregate parameter components created", |
| sra_stats.param_reductions_created); |
| |
| out: |
| BITMAP_FREE (final_bbs); |
| free (bb_dereferences); |
| simple_out: |
| sra_deinitialize (); |
| return ret; |
| } |
| |
| /* Return if early ipa sra shall be performed. */ |
| static bool |
| ipa_early_sra_gate (void) |
| { |
| return flag_ipa_sra && !flag_dyn_ipa && dbg_cnt (eipa_sra); |
| } |
| |
| struct gimple_opt_pass pass_early_ipa_sra = |
| { |
| { |
| GIMPLE_PASS, |
| "eipa_sra", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| ipa_early_sra_gate, /* gate */ |
| ipa_early_sra, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| TV_IPA_SRA, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_dump_symtab /* todo_flags_finish */ |
| } |
| }; |