5671 lines
163 KiB
C
5671 lines
163 KiB
C
/* Scalar Replacement of Aggregates (SRA) converts some structure
|
|
references into scalar references, exposing them to the scalar
|
|
optimizers.
|
|
Copyright (C) 2008-2019 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 "backend.h"
|
|
#include "target.h"
|
|
#include "rtl.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "predict.h"
|
|
#include "alloc-pool.h"
|
|
#include "tree-pass.h"
|
|
#include "ssa.h"
|
|
#include "cgraph.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "alias.h"
|
|
#include "fold-const.h"
|
|
#include "tree-eh.h"
|
|
#include "stor-layout.h"
|
|
#include "gimplify.h"
|
|
#include "gimple-iterator.h"
|
|
#include "gimplify-me.h"
|
|
#include "gimple-walk.h"
|
|
#include "tree-cfg.h"
|
|
#include "tree-dfa.h"
|
|
#include "tree-ssa.h"
|
|
#include "symbol-summary.h"
|
|
#include "ipa-param-manipulation.h"
|
|
#include "ipa-prop.h"
|
|
#include "params.h"
|
|
#include "dbgcnt.h"
|
|
#include "tree-inline.h"
|
|
#include "ipa-fnsummary.h"
|
|
#include "ipa-utils.h"
|
|
#include "builtins.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;
|
|
|
|
/* After access tree has been constructed, this points to the parent of the
|
|
current access, if there is one. NULL for roots. */
|
|
struct access *parent;
|
|
|
|
/* 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 access an access to a non-addressable field? */
|
|
unsigned non_addressable : 1;
|
|
|
|
/* Is this access made in reverse storage order? */
|
|
unsigned reverse : 1;
|
|
|
|
/* Is this particular access write access? */
|
|
unsigned write : 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 object_allocator<struct access> access_pool ("SRA accesses");
|
|
|
|
/* 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 object_allocator<assign_link> assign_link_pool ("SRA links");
|
|
|
|
/* Base (tree) -> Vector (vec<access_p> *) map. */
|
|
static hash_map<tree, auto_vec<access_p> > *base_access_vec;
|
|
|
|
/* Candidate hash table helpers. */
|
|
|
|
struct uid_decl_hasher : nofree_ptr_hash <tree_node>
|
|
{
|
|
static inline hashval_t hash (const tree_node *);
|
|
static inline bool equal (const tree_node *, const tree_node *);
|
|
};
|
|
|
|
/* Hash a tree in a uid_decl_map. */
|
|
|
|
inline hashval_t
|
|
uid_decl_hasher::hash (const tree_node *item)
|
|
{
|
|
return item->decl_minimal.uid;
|
|
}
|
|
|
|
/* Return true if the DECL_UID in both trees are equal. */
|
|
|
|
inline bool
|
|
uid_decl_hasher::equal (const tree_node *a, const tree_node *b)
|
|
{
|
|
return (a->decl_minimal.uid == b->decl_minimal.uid);
|
|
}
|
|
|
|
/* Set of candidates. */
|
|
static bitmap candidate_bitmap;
|
|
static hash_table<uid_decl_hasher> *candidates;
|
|
|
|
/* For a candidate UID return the candidates decl. */
|
|
|
|
static inline tree
|
|
candidate (unsigned uid)
|
|
{
|
|
tree_node t;
|
|
t.decl_minimal.uid = uid;
|
|
return candidates->find_with_hash (&t, static_cast <hashval_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;
|
|
|
|
/* Bitmap of candidates in the constant pool, which cannot be scalarized
|
|
because this would produce non-constant expressions (e.g. Ada). */
|
|
static bitmap disqualified_constants;
|
|
|
|
/* 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);
|
|
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);
|
|
fprintf (f, ", type = ");
|
|
print_generic_expr (f, access->type);
|
|
fprintf (f, ", non_addressable = %d, reverse = %d",
|
|
access->non_addressable, access->reverse);
|
|
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 ("* ", f);
|
|
|
|
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)
|
|
{
|
|
return base_access_vec->get (base);
|
|
}
|
|
|
|
/* 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->first_link && !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 = new hash_table<uid_decl_hasher>
|
|
(vec_safe_length (cfun->local_decls) / 2);
|
|
should_scalarize_away_bitmap = BITMAP_ALLOC (NULL);
|
|
cannot_scalarize_away_bitmap = BITMAP_ALLOC (NULL);
|
|
disqualified_constants = BITMAP_ALLOC (NULL);
|
|
gcc_obstack_init (&name_obstack);
|
|
base_access_vec = new hash_map<tree, auto_vec<access_p> >;
|
|
memset (&sra_stats, 0, sizeof (sra_stats));
|
|
encountered_apply_args = false;
|
|
encountered_recursive_call = false;
|
|
encountered_unchangable_recursive_call = false;
|
|
}
|
|
|
|
/* Deallocate all general structures. */
|
|
|
|
static void
|
|
sra_deinitialize (void)
|
|
{
|
|
BITMAP_FREE (candidate_bitmap);
|
|
delete candidates;
|
|
candidates = NULL;
|
|
BITMAP_FREE (should_scalarize_away_bitmap);
|
|
BITMAP_FREE (cannot_scalarize_away_bitmap);
|
|
BITMAP_FREE (disqualified_constants);
|
|
access_pool.release ();
|
|
assign_link_pool.release ();
|
|
obstack_free (&name_obstack, NULL);
|
|
|
|
delete base_access_vec;
|
|
}
|
|
|
|
/* Return true if DECL is a VAR_DECL in the constant pool, false otherwise. */
|
|
|
|
static bool constant_decl_p (tree decl)
|
|
{
|
|
return VAR_P (decl) && DECL_IN_CONSTANT_POOL (decl);
|
|
}
|
|
|
|
/* 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)))
|
|
candidates->remove_elt_with_hash (decl, DECL_UID (decl));
|
|
if (constant_decl_p (decl))
|
|
bitmap_set_bit (disqualified_constants, DECL_UID (decl));
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "! Disqualifying ");
|
|
print_generic_expr (dump_file, decl);
|
|
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 (!tree_fits_uhwi_p (DECL_FIELD_OFFSET (fld)))
|
|
{
|
|
*msg = "structure field offset not fixed";
|
|
return true;
|
|
}
|
|
if (!tree_fits_uhwi_p (DECL_SIZE (fld)))
|
|
{
|
|
*msg = "structure field size not fixed";
|
|
return true;
|
|
}
|
|
if (!tree_fits_shwi_p (bit_position (fld)))
|
|
{
|
|
*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)
|
|
{
|
|
struct access *access = access_pool.allocate ();
|
|
|
|
memset (access, 0, sizeof (struct access));
|
|
access->base = base;
|
|
access->offset = offset;
|
|
access->size = size;
|
|
|
|
base_access_vec->get_or_insert (base).safe_push (access);
|
|
|
|
return access;
|
|
}
|
|
|
|
static bool maybe_add_sra_candidate (tree);
|
|
|
|
/* Create and insert access for EXPR. Return created access, or NULL if it is
|
|
not possible. Also scan for uses of constant pool as we go along and add
|
|
to candidates. */
|
|
|
|
static struct access *
|
|
create_access (tree expr, gimple *stmt, bool write)
|
|
{
|
|
struct access *access;
|
|
poly_int64 poffset, psize, pmax_size;
|
|
HOST_WIDE_INT offset, size, max_size;
|
|
tree base = expr;
|
|
bool reverse, ptr, unscalarizable_region = false;
|
|
|
|
base = get_ref_base_and_extent (expr, &poffset, &psize, &pmax_size,
|
|
&reverse);
|
|
if (!poffset.is_constant (&offset)
|
|
|| !psize.is_constant (&size)
|
|
|| !pmax_size.is_constant (&max_size))
|
|
{
|
|
disqualify_candidate (base, "Encountered a polynomial-sized access.");
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
|
|
/* For constant-pool entries, check we can substitute the constant value. */
|
|
if (constant_decl_p (base)
|
|
&& (sra_mode == SRA_MODE_EARLY_INTRA || sra_mode == SRA_MODE_INTRA))
|
|
{
|
|
gcc_assert (!bitmap_bit_p (disqualified_constants, DECL_UID (base)));
|
|
if (expr != base
|
|
&& !is_gimple_reg_type (TREE_TYPE (expr))
|
|
&& dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
/* This occurs in Ada with accesses to ARRAY_RANGE_REFs,
|
|
and elements of multidimensional arrays (which are
|
|
multi-element arrays in their own right). */
|
|
fprintf (dump_file, "Allowing non-reg-type load of part"
|
|
" of constant-pool entry: ");
|
|
print_generic_expr (dump_file, expr);
|
|
}
|
|
maybe_add_sra_candidate (base);
|
|
}
|
|
|
|
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;
|
|
access->reverse = reverse;
|
|
|
|
if (TREE_CODE (expr) == COMPONENT_REF
|
|
&& DECL_NONADDRESSABLE_P (TREE_OPERAND (expr, 1)))
|
|
access->non_addressable = 1;
|
|
|
|
return access;
|
|
}
|
|
|
|
|
|
/* Return true iff TYPE is scalarizable - i.e. a RECORD_TYPE or fixed-length
|
|
ARRAY_TYPE with fields that are either of gimple register types (excluding
|
|
bit-fields) or (recursively) scalarizable types. CONST_DECL must be true if
|
|
we are considering a decl from constant pool. If it is false, char arrays
|
|
will be refused. */
|
|
|
|
static bool
|
|
scalarizable_type_p (tree type, bool const_decl)
|
|
{
|
|
gcc_assert (!is_gimple_reg_type (type));
|
|
if (type_contains_placeholder_p (type))
|
|
return false;
|
|
|
|
switch (TREE_CODE (type))
|
|
{
|
|
case RECORD_TYPE:
|
|
for (tree 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)
|
|
&& !scalarizable_type_p (ft, const_decl))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
case ARRAY_TYPE:
|
|
{
|
|
HOST_WIDE_INT min_elem_size;
|
|
if (const_decl)
|
|
min_elem_size = 0;
|
|
else
|
|
min_elem_size = BITS_PER_UNIT;
|
|
|
|
if (TYPE_DOMAIN (type) == NULL_TREE
|
|
|| !tree_fits_shwi_p (TYPE_SIZE (type))
|
|
|| !tree_fits_shwi_p (TYPE_SIZE (TREE_TYPE (type)))
|
|
|| (tree_to_shwi (TYPE_SIZE (TREE_TYPE (type))) <= min_elem_size)
|
|
|| !tree_fits_shwi_p (TYPE_MIN_VALUE (TYPE_DOMAIN (type))))
|
|
return false;
|
|
if (tree_to_shwi (TYPE_SIZE (type)) == 0
|
|
&& TYPE_MAX_VALUE (TYPE_DOMAIN (type)) == NULL_TREE)
|
|
/* Zero-element array, should not prevent scalarization. */
|
|
;
|
|
else if ((tree_to_shwi (TYPE_SIZE (type)) <= 0)
|
|
|| !tree_fits_shwi_p (TYPE_MAX_VALUE (TYPE_DOMAIN (type))))
|
|
/* Variable-length array, do not allow scalarization. */
|
|
return false;
|
|
|
|
tree elem = TREE_TYPE (type);
|
|
if (!is_gimple_reg_type (elem)
|
|
&& !scalarizable_type_p (elem, const_decl))
|
|
return false;
|
|
return true;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void scalarize_elem (tree, HOST_WIDE_INT, HOST_WIDE_INT, bool, tree, tree);
|
|
|
|
/* Create total_scalarization accesses for all scalar fields of a member
|
|
of type DECL_TYPE conforming to scalarizable_type_p. BASE
|
|
must be the top-most VAR_DECL representing the variable; within that,
|
|
OFFSET locates the member and REF must be the memory reference expression for
|
|
the member. */
|
|
|
|
static void
|
|
completely_scalarize (tree base, tree decl_type, HOST_WIDE_INT offset, tree ref)
|
|
{
|
|
switch (TREE_CODE (decl_type))
|
|
{
|
|
case RECORD_TYPE:
|
|
for (tree 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, ft, ref, fld, NULL_TREE);
|
|
|
|
scalarize_elem (base, pos, tree_to_uhwi (DECL_SIZE (fld)),
|
|
TYPE_REVERSE_STORAGE_ORDER (decl_type),
|
|
nref, ft);
|
|
}
|
|
break;
|
|
case ARRAY_TYPE:
|
|
{
|
|
tree elemtype = TREE_TYPE (decl_type);
|
|
tree elem_size = TYPE_SIZE (elemtype);
|
|
gcc_assert (elem_size && tree_fits_shwi_p (elem_size));
|
|
HOST_WIDE_INT el_size = tree_to_shwi (elem_size);
|
|
gcc_assert (el_size > 0);
|
|
|
|
tree minidx = TYPE_MIN_VALUE (TYPE_DOMAIN (decl_type));
|
|
gcc_assert (TREE_CODE (minidx) == INTEGER_CST);
|
|
tree maxidx = TYPE_MAX_VALUE (TYPE_DOMAIN (decl_type));
|
|
/* Skip (some) zero-length arrays; others have MAXIDX == MINIDX - 1. */
|
|
if (maxidx)
|
|
{
|
|
gcc_assert (TREE_CODE (maxidx) == INTEGER_CST);
|
|
tree domain = TYPE_DOMAIN (decl_type);
|
|
/* MINIDX and MAXIDX are inclusive, and must be interpreted in
|
|
DOMAIN (e.g. signed int, whereas min/max may be size_int). */
|
|
offset_int idx = wi::to_offset (minidx);
|
|
offset_int max = wi::to_offset (maxidx);
|
|
if (!TYPE_UNSIGNED (domain))
|
|
{
|
|
idx = wi::sext (idx, TYPE_PRECISION (domain));
|
|
max = wi::sext (max, TYPE_PRECISION (domain));
|
|
}
|
|
for (int el_off = offset; idx <= max; ++idx)
|
|
{
|
|
tree nref = build4 (ARRAY_REF, elemtype,
|
|
ref,
|
|
wide_int_to_tree (domain, idx),
|
|
NULL_TREE, NULL_TREE);
|
|
scalarize_elem (base, el_off, el_size,
|
|
TYPE_REVERSE_STORAGE_ORDER (decl_type),
|
|
nref, elemtype);
|
|
el_off += el_size;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
/* Create total_scalarization accesses for a member of type TYPE, which must
|
|
satisfy either is_gimple_reg_type or scalarizable_type_p. BASE must be the
|
|
top-most VAR_DECL representing the variable; within that, POS and SIZE locate
|
|
the member, REVERSE gives its torage order. and REF must be the reference
|
|
expression for it. */
|
|
|
|
static void
|
|
scalarize_elem (tree base, HOST_WIDE_INT pos, HOST_WIDE_INT size, bool reverse,
|
|
tree ref, tree type)
|
|
{
|
|
if (is_gimple_reg_type (type))
|
|
{
|
|
struct access *access = create_access_1 (base, pos, size);
|
|
access->expr = ref;
|
|
access->type = type;
|
|
access->grp_total_scalarization = 1;
|
|
access->reverse = reverse;
|
|
/* Accesses for intraprocedural SRA can have their stmt NULL. */
|
|
}
|
|
else
|
|
completely_scalarize (base, type, pos, ref);
|
|
}
|
|
|
|
/* Create a total_scalarization access for VAR as a whole. VAR must be of a
|
|
RECORD_TYPE or ARRAY_TYPE conforming to scalarizable_type_p. */
|
|
|
|
static void
|
|
create_total_scalarization_access (tree var)
|
|
{
|
|
HOST_WIDE_INT size = tree_to_uhwi (DECL_SIZE (var));
|
|
struct access *access;
|
|
|
|
access = create_access_1 (var, 0, size);
|
|
access->expr = var;
|
|
access->type = TREE_TYPE (var);
|
|
access->grp_total_scalarization = 1;
|
|
}
|
|
|
|
/* Return true if REF has an VIEW_CONVERT_EXPR somewhere in it. */
|
|
|
|
static inline bool
|
|
contains_view_convert_expr_p (const_tree ref)
|
|
{
|
|
while (handled_component_p (ref))
|
|
{
|
|
if (TREE_CODE (ref) == VIEW_CONVERT_EXPR)
|
|
return true;
|
|
ref = TREE_OPERAND (ref, 0);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Return true if REF contains a VIEW_CONVERT_EXPR or a COMPONENT_REF with a
|
|
bit-field field declaration. If TYPE_CHANGING_P is non-NULL, set the bool
|
|
it points to will be set if REF contains any of the above or a MEM_REF
|
|
expression that effectively performs type conversion. */
|
|
|
|
static bool
|
|
contains_vce_or_bfcref_p (const_tree ref, bool *type_changing_p = NULL)
|
|
{
|
|
while (handled_component_p (ref))
|
|
{
|
|
if (TREE_CODE (ref) == VIEW_CONVERT_EXPR
|
|
|| (TREE_CODE (ref) == COMPONENT_REF
|
|
&& DECL_BIT_FIELD (TREE_OPERAND (ref, 1))))
|
|
{
|
|
if (type_changing_p)
|
|
*type_changing_p = true;
|
|
return true;
|
|
}
|
|
ref = TREE_OPERAND (ref, 0);
|
|
}
|
|
|
|
if (!type_changing_p
|
|
|| TREE_CODE (ref) != MEM_REF
|
|
|| TREE_CODE (TREE_OPERAND (ref, 0)) != ADDR_EXPR)
|
|
return false;
|
|
|
|
tree mem = TREE_OPERAND (TREE_OPERAND (ref, 0), 0);
|
|
if (TYPE_MAIN_VARIANT (TREE_TYPE (ref))
|
|
!= TYPE_MAIN_VARIANT (TREE_TYPE (mem)))
|
|
*type_changing_p = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
if (storage_order_barrier_p (expr))
|
|
{
|
|
disqualify_base_of_expr (expr, "storage order barrier.");
|
|
return NULL;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
if (TREE_THIS_VOLATILE (expr))
|
|
{
|
|
disqualify_base_of_expr (expr, "part of a volatile reference.");
|
|
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;
|
|
}
|
|
|
|
/* Return the single non-EH successor edge of BB or NULL if there is none or
|
|
more than one. */
|
|
|
|
static edge
|
|
single_non_eh_succ (basic_block bb)
|
|
{
|
|
edge e, res = NULL;
|
|
edge_iterator ei;
|
|
|
|
FOR_EACH_EDGE (e, ei, bb->succs)
|
|
if (!(e->flags & EDGE_EH))
|
|
{
|
|
if (res)
|
|
return NULL;
|
|
res = e;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Disqualify LHS and RHS for scalarization if STMT has to terminate its BB and
|
|
there is no alternative spot where to put statements SRA might need to
|
|
generate after it. The spot we are looking for is an edge leading to a
|
|
single non-EH successor, if it exists and is indeed single. RHS may be
|
|
NULL, in that case ignore it. */
|
|
|
|
static bool
|
|
disqualify_if_bad_bb_terminating_stmt (gimple *stmt, tree lhs, tree rhs)
|
|
{
|
|
if ((sra_mode == SRA_MODE_EARLY_INTRA || sra_mode == SRA_MODE_INTRA)
|
|
&& stmt_ends_bb_p (stmt))
|
|
{
|
|
if (single_non_eh_succ (gimple_bb (stmt)))
|
|
return false;
|
|
|
|
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;
|
|
}
|
|
|
|
/* Return true if the nature of BASE is such that it contains data even if
|
|
there is no write to it in the function. */
|
|
|
|
static bool
|
|
comes_initialized_p (tree base)
|
|
{
|
|
return TREE_CODE (base) == PARM_DECL || constant_decl_p (base);
|
|
}
|
|
|
|
/* 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_if_bad_bb_terminating_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 (storage_order_barrier_p (rhs))
|
|
lacc->grp_unscalarizable_region = 1;
|
|
|
|
if (should_scalarize_away_bitmap && !is_gimple_reg_type (lacc->type))
|
|
{
|
|
bool type_changing_p = false;
|
|
contains_vce_or_bfcref_p (lhs, &type_changing_p);
|
|
if (type_changing_p)
|
|
bitmap_set_bit (cannot_scalarize_away_bitmap,
|
|
DECL_UID (lacc->base));
|
|
}
|
|
}
|
|
|
|
if (racc)
|
|
{
|
|
racc->grp_assignment_read = 1;
|
|
if (should_scalarize_away_bitmap && !is_gimple_reg_type (racc->type))
|
|
{
|
|
bool type_changing_p = false;
|
|
contains_vce_or_bfcref_p (rhs, &type_changing_p);
|
|
|
|
if (type_changing_p || gimple_has_volatile_ops (stmt))
|
|
bitmap_set_bit (cannot_scalarize_away_bitmap,
|
|
DECL_UID (racc->base));
|
|
else
|
|
bitmap_set_bit (should_scalarize_away_bitmap,
|
|
DECL_UID (racc->base));
|
|
}
|
|
if (storage_order_barrier_p (lhs))
|
|
racc->grp_unscalarizable_region = 1;
|
|
}
|
|
|
|
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 = assign_link_pool.allocate ();
|
|
memset (link, 0, sizeof (struct assign_link));
|
|
|
|
link->lacc = lacc;
|
|
link->racc = racc;
|
|
add_link_to_rhs (racc, link);
|
|
add_access_to_work_queue (racc);
|
|
|
|
/* Let's delay marking the areas as written until propagation of accesses
|
|
across link, unless the nature of rhs tells us that its data comes
|
|
from elsewhere. */
|
|
if (!comes_initialized_p (racc->base))
|
|
lacc->write = false;
|
|
}
|
|
|
|
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 *, tree op, tree, void *)
|
|
{
|
|
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 and
|
|
that their types match. */
|
|
|
|
static inline bool
|
|
callsite_arguments_match_p (gimple *call)
|
|
{
|
|
if (gimple_call_num_args (call) < (unsigned) func_param_count)
|
|
return false;
|
|
|
|
tree parm;
|
|
int i;
|
|
for (parm = DECL_ARGUMENTS (current_function_decl), i = 0;
|
|
parm;
|
|
parm = DECL_CHAIN (parm), i++)
|
|
{
|
|
tree arg = gimple_call_arg (call, i);
|
|
if (!useless_type_conversion_p (TREE_TYPE (parm), TREE_TYPE (arg)))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* 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_FN (bb, cfun)
|
|
{
|
|
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 (cfun, stmt))
|
|
bitmap_set_bit (final_bbs, bb->index);
|
|
switch (gimple_code (stmt))
|
|
{
|
|
case GIMPLE_RETURN:
|
|
t = gimple_return_retval (as_a <greturn *> (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 (fndecl_built_in_p (dest, BUILT_IN_APPLY_ARGS))
|
|
encountered_apply_args = true;
|
|
if (recursive_call_p (current_function_decl, dest))
|
|
{
|
|
encountered_recursive_call = true;
|
|
if (!callsite_arguments_match_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_if_bad_bb_terminating_stmt (stmt, t, NULL))
|
|
ret |= build_access_from_expr (t, stmt, true);
|
|
break;
|
|
|
|
case GIMPLE_ASM:
|
|
{
|
|
gasm *asm_stmt = as_a <gasm *> (stmt);
|
|
walk_stmt_load_store_addr_ops (asm_stmt, NULL, NULL, NULL,
|
|
asm_visit_addr);
|
|
if (final_bbs)
|
|
bitmap_set_bit (final_bbs, bb->index);
|
|
|
|
for (i = 0; i < gimple_asm_ninputs (asm_stmt); i++)
|
|
{
|
|
t = TREE_VALUE (gimple_asm_input_op (asm_stmt, i));
|
|
ret |= build_access_from_expr (t, asm_stmt, false);
|
|
}
|
|
for (i = 0; i < gimple_asm_noutputs (asm_stmt); i++)
|
|
{
|
|
t = TREE_VALUE (gimple_asm_output_op (asm_stmt, i));
|
|
ret |= build_access_from_expr (t, asm_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 any integral type before any non-integral type. When splicing, we
|
|
make sure that those with insufficient precision and occupying the
|
|
same space are not scalarized. */
|
|
else if (INTEGRAL_TYPE_P (f1->type)
|
|
&& !INTEGRAL_TYPE_P (f2->type))
|
|
return -1;
|
|
else if (!INTEGRAL_TYPE_P (f1->type)
|
|
&& INTEGRAL_TYPE_P (f2->type))
|
|
return 1;
|
|
/* Put the integral type with the bigger precision first. */
|
|
else if (INTEGRAL_TYPE_P (f1->type)
|
|
&& INTEGRAL_TYPE_P (f2->type)
|
|
&& (TYPE_PRECISION (f2->type) != TYPE_PRECISION (f1->type)))
|
|
return TYPE_PRECISION (f2->type) - TYPE_PRECISION (f1->type);
|
|
/* 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 and with storage order REVERSE. 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. */
|
|
|
|
tree
|
|
build_ref_for_offset (location_t loc, tree base, poly_int64 offset,
|
|
bool reverse, tree exp_type, gimple_stmt_iterator *gsi,
|
|
bool insert_after)
|
|
{
|
|
tree prev_base = base;
|
|
tree off;
|
|
tree mem_ref;
|
|
poly_int64 base_offset;
|
|
unsigned HOST_WIDE_INT misalign;
|
|
unsigned int align;
|
|
|
|
/* Preserve address-space information. */
|
|
addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (base));
|
|
if (as != TYPE_ADDR_SPACE (exp_type))
|
|
exp_type = build_qualified_type (exp_type,
|
|
TYPE_QUALS (exp_type)
|
|
| ENCODE_QUAL_ADDR_SPACE (as));
|
|
|
|
poly_int64 byte_offset = exact_div (offset, BITS_PER_UNIT);
|
|
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)
|
|
{
|
|
gassign *stmt;
|
|
tree tmp, addr;
|
|
|
|
gcc_checking_assert (gsi);
|
|
tmp = make_ssa_name (build_pointer_type (TREE_TYPE (prev_base)));
|
|
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), byte_offset);
|
|
base = tmp;
|
|
}
|
|
else if (TREE_CODE (base) == MEM_REF)
|
|
{
|
|
off = build_int_cst (TREE_TYPE (TREE_OPERAND (base, 1)),
|
|
base_offset + byte_offset);
|
|
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 (prev_base),
|
|
base_offset + byte_offset);
|
|
base = build_fold_addr_expr (unshare_expr (base));
|
|
}
|
|
|
|
unsigned int align_bound = known_alignment (misalign + offset);
|
|
if (align_bound != 0)
|
|
align = MIN (align, align_bound);
|
|
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);
|
|
REF_REVERSE_STORAGE_ORDER (mem_ref) = reverse;
|
|
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, model->reverse, exp_type,
|
|
gsi, insert_after);
|
|
/* The flag will be set on the record type. */
|
|
REF_REVERSE_STORAGE_ORDER (t) = 0;
|
|
return fold_build3_loc (loc, COMPONENT_REF, TREE_TYPE (fld), t, fld,
|
|
NULL_TREE);
|
|
}
|
|
else
|
|
return
|
|
build_ref_for_offset (loc, base, offset, model->reverse, 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)
|
|
{
|
|
poly_int64 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 || !tree_fits_uhwi_p (tr_pos))
|
|
continue;
|
|
pos = tree_to_uhwi (tr_pos);
|
|
gcc_assert (TREE_CODE (type) == RECORD_TYPE || pos == 0);
|
|
tr_size = DECL_SIZE (fld);
|
|
if (!tr_size || !tree_fits_uhwi_p (tr_size))
|
|
continue;
|
|
size = tree_to_uhwi (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 || !tree_fits_uhwi_p (tr_size))
|
|
return false;
|
|
el_size = tree_to_uhwi (tr_size);
|
|
|
|
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);
|
|
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;
|
|
tree_node **slot;
|
|
|
|
if (!AGGREGATE_TYPE_P (type))
|
|
{
|
|
reject (var, "not aggregate");
|
|
return false;
|
|
}
|
|
/* Allow constant-pool entries (that "need to live in memory")
|
|
unless we are doing IPA SRA. */
|
|
if (needs_to_live_in_memory (var)
|
|
&& (sra_mode == SRA_MODE_EARLY_IPA || !constant_decl_p (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 (!tree_fits_uhwi_p (TYPE_SIZE (type)))
|
|
{
|
|
reject (var, "type size not fixed");
|
|
return false;
|
|
}
|
|
if (tree_to_uhwi (TYPE_SIZE (type)) == 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 = candidates->find_slot_with_hash (var, DECL_UID (var), INSERT);
|
|
*slot = var;
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Candidate (%d): ", DECL_UID (var));
|
|
print_generic_expr (dump_file, var);
|
|
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 (!VAR_P (var))
|
|
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;
|
|
bool bf_non_full_precision
|
|
= (INTEGRAL_TYPE_P (access->type)
|
|
&& TYPE_PRECISION (access->type) != access->size
|
|
&& TREE_CODE (access->expr) == COMPONENT_REF
|
|
&& DECL_BIT_FIELD (TREE_OPERAND (access->expr, 1)));
|
|
|
|
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));
|
|
/* It also prefers integral types to non-integral. However, when the
|
|
precision of the selected type does not span the entire area and
|
|
should also be used for a non-integer (i.e. float), we must not
|
|
let that happen. Normally analyze_access_subtree expands the type
|
|
to cover the entire area but for bit-fields it doesn't. */
|
|
if (bf_non_full_precision && !INTEGRAL_TYPE_P (ac2->type))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Cannot scalarize the following access "
|
|
"because insufficient precision integer type was "
|
|
"selected.\n ");
|
|
dump_access (dump_file, access, false);
|
|
}
|
|
unscalarizable_region = true;
|
|
}
|
|
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 = total_scalarization
|
|
|| (multiple_scalar_reads && !constant_decl_p (var));
|
|
access->grp_total_scalarization = total_scalarization;
|
|
access->grp_partial_lhs = grp_partial_lhs;
|
|
access->grp_unscalarizable_region = unscalarizable_region;
|
|
|
|
*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. REG_TREE is used when creating a declaration to base a
|
|
default-definition SSA name on on in order to facilitate an uninitialized
|
|
warning. It is used instead of the actual ACCESS type if that is not of a
|
|
gimple register type. */
|
|
|
|
static tree
|
|
create_access_replacement (struct access *access, tree reg_type = NULL_TREE)
|
|
{
|
|
tree repl;
|
|
|
|
tree type = access->type;
|
|
if (reg_type && !is_gimple_reg_type (type))
|
|
type = reg_type;
|
|
|
|
if (access->grp_to_be_debug_replaced)
|
|
{
|
|
repl = create_tmp_var_raw (access->type);
|
|
DECL_CONTEXT (repl) = current_function_decl;
|
|
}
|
|
else
|
|
/* Drop any special alignment on the type if it's not on the main
|
|
variant. This avoids issues with weirdo ABIs like AAPCS. */
|
|
repl = create_tmp_var (build_qualified_type (TYPE_MAIN_VARIANT (type),
|
|
TYPE_QUALS (type)), "SR");
|
|
if (TREE_CODE (type) == COMPLEX_TYPE
|
|
|| TREE_CODE (type) == VECTOR_TYPE)
|
|
{
|
|
if (!access->grp_partial_lhs)
|
|
DECL_GIMPLE_REG_P (repl) = 1;
|
|
}
|
|
else if (access->grp_partial_lhs
|
|
&& is_gimple_reg_type (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);
|
|
DECL_NAMELESS (repl) = 1;
|
|
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_HAS_DEBUG_EXPR_P (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);
|
|
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);
|
|
fprintf (dump_file, " offset: %u, size: %u: ",
|
|
(unsigned) access->offset, (unsigned) access->size);
|
|
print_generic_expr (dump_file, repl);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
sra_stats.replacements++;
|
|
|
|
return repl;
|
|
}
|
|
|
|
/* Return ACCESS scalar replacement, which must exist. */
|
|
|
|
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;
|
|
(*access)->parent = root;
|
|
(*access)->grp_write |= root->grp_write;
|
|
|
|
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
|
|
&& !tree_fits_shwi_p (array_ref_low_bound (expr)))
|
|
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->reverse,
|
|
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);
|
|
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_BIND_STMTS)
|
|
{
|
|
root->grp_to_be_debug_replaced = 1;
|
|
root->replacement_decl = create_access_replacement (root);
|
|
}
|
|
}
|
|
|
|
if (covered_to < limit)
|
|
hole = true;
|
|
if (scalar || !allow_replacements)
|
|
root->grp_total_scalarization = 0;
|
|
}
|
|
|
|
if (!hole || root->grp_total_scalarization)
|
|
root->grp_covered = 1;
|
|
else if (root->grp_write || comes_initialized_p (root->base))
|
|
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. Set
|
|
the gpr_write flag of the new accesss if SET_GRP_WRITE is true. */
|
|
|
|
static struct access *
|
|
create_artificial_child_access (struct access *parent, struct access *model,
|
|
HOST_WIDE_INT new_offset,
|
|
bool set_grp_write)
|
|
{
|
|
struct access **child;
|
|
tree expr = parent->base;
|
|
|
|
gcc_assert (!model->grp_unscalarizable_region);
|
|
|
|
struct access *access = access_pool.allocate ();
|
|
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 = set_grp_write;
|
|
access->grp_read = false;
|
|
access->reverse = model->reverse;
|
|
|
|
child = &parent->first_child;
|
|
while (*child && (*child)->offset < new_offset)
|
|
child = &(*child)->next_sibling;
|
|
|
|
access->next_sibling = *child;
|
|
*child = access;
|
|
|
|
return access;
|
|
}
|
|
|
|
|
|
/* Beginning with ACCESS, traverse its whole access subtree and mark all
|
|
sub-trees as written to. If any of them has not been marked so previously
|
|
and has assignment links leading from it, re-enqueue it. */
|
|
|
|
static void
|
|
subtree_mark_written_and_enqueue (struct access *access)
|
|
{
|
|
if (access->grp_write)
|
|
return;
|
|
access->grp_write = true;
|
|
add_access_to_work_queue (access);
|
|
|
|
struct access *child;
|
|
for (child = access->first_child; child; child = child->next_sibling)
|
|
subtree_mark_written_and_enqueue (child);
|
|
}
|
|
|
|
/* Propagate subaccesses and grp_write flags of RACC across an assignment link
|
|
to LACC. Enqueue sub-accesses as necessary so that the write flag is
|
|
propagated transitively. Return true if anything changed. 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 the LHS is still not marked as being written to, we only need to do so
|
|
if the RHS at this level actually was. */
|
|
if (!lacc->grp_write)
|
|
{
|
|
gcc_checking_assert (!comes_initialized_p (racc->base));
|
|
if (racc->grp_write)
|
|
{
|
|
subtree_mark_written_and_enqueue (lacc);
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
if (is_gimple_reg_type (lacc->type)
|
|
|| lacc->grp_unscalarizable_region
|
|
|| racc->grp_unscalarizable_region)
|
|
{
|
|
if (!lacc->grp_write)
|
|
{
|
|
ret = true;
|
|
subtree_mark_written_and_enqueue (lacc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
if (is_gimple_reg_type (racc->type))
|
|
{
|
|
if (!lacc->grp_write)
|
|
{
|
|
ret = true;
|
|
subtree_mark_written_and_enqueue (lacc);
|
|
}
|
|
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 ret;
|
|
}
|
|
|
|
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 (child_would_conflict_in_lacc (lacc, norm_offset, rchild->size,
|
|
&new_acc))
|
|
{
|
|
if (new_acc)
|
|
{
|
|
if (!new_acc->grp_write && rchild->grp_write)
|
|
{
|
|
gcc_assert (!lacc->grp_write);
|
|
subtree_mark_written_and_enqueue (new_acc);
|
|
ret = true;
|
|
}
|
|
|
|
rchild->grp_hint = 1;
|
|
new_acc->grp_hint |= new_acc->grp_read;
|
|
if (rchild->first_child
|
|
&& propagate_subaccesses_across_link (new_acc, rchild))
|
|
{
|
|
ret = 1;
|
|
add_access_to_work_queue (new_acc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!lacc->grp_write)
|
|
{
|
|
ret = true;
|
|
subtree_mark_written_and_enqueue (lacc);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (rchild->grp_unscalarizable_region)
|
|
{
|
|
if (rchild->grp_write && !lacc->grp_write)
|
|
{
|
|
ret = true;
|
|
subtree_mark_written_and_enqueue (lacc);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
rchild->grp_hint = 1;
|
|
new_acc = create_artificial_child_access (lacc, rchild, norm_offset,
|
|
lacc->grp_write
|
|
|| rchild->grp_write);
|
|
gcc_checking_assert (new_acc);
|
|
if (racc->first_child)
|
|
propagate_subaccesses_across_link (new_acc, rchild);
|
|
|
|
add_access_to_work_queue (lacc);
|
|
ret = true;
|
|
}
|
|
|
|
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;
|
|
|
|
if (racc->group_representative)
|
|
racc= racc->group_representative;
|
|
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;
|
|
|
|
bool reque_parents = false;
|
|
if (!bitmap_bit_p (candidate_bitmap, DECL_UID (racc->base)))
|
|
{
|
|
if (!lacc->grp_write)
|
|
{
|
|
subtree_mark_written_and_enqueue (lacc);
|
|
reque_parents = true;
|
|
}
|
|
}
|
|
else if (propagate_subaccesses_across_link (lacc, racc))
|
|
reque_parents = true;
|
|
|
|
if (reque_parents)
|
|
do
|
|
{
|
|
add_access_to_work_queue (lacc);
|
|
lacc = lacc->parent;
|
|
}
|
|
while (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;
|
|
bool optimize_speed_p = !optimize_function_for_size_p (cfun);
|
|
|
|
enum compiler_param param = optimize_speed_p
|
|
? PARAM_SRA_MAX_SCALARIZATION_SIZE_SPEED
|
|
: PARAM_SRA_MAX_SCALARIZATION_SIZE_SIZE;
|
|
|
|
/* If the user didn't set PARAM_SRA_MAX_SCALARIZATION_SIZE_<...>,
|
|
fall back to a target default. */
|
|
unsigned HOST_WIDE_INT max_scalarization_size
|
|
= global_options_set.x_param_values[param]
|
|
? PARAM_VALUE (param)
|
|
: get_move_ratio (optimize_speed_p) * UNITS_PER_WORD;
|
|
|
|
max_scalarization_size *= BITS_PER_UNIT;
|
|
|
|
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 (VAR_P (var) && scalarizable_type_p (TREE_TYPE (var),
|
|
constant_decl_p (var)))
|
|
{
|
|
if (tree_to_uhwi (TYPE_SIZE (TREE_TYPE (var)))
|
|
<= max_scalarization_size)
|
|
{
|
|
create_total_scalarization_access (var);
|
|
completely_scalarize (var, TREE_TYPE (var), 0, var);
|
|
statistics_counter_event (cfun,
|
|
"Totally-scalarized aggregates", 1);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Will attempt to totally scalarize ");
|
|
print_generic_expr (dump_file, var);
|
|
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);
|
|
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);
|
|
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)
|
|
{
|
|
/* Never write anything into constant pool decls. See PR70602. */
|
|
if (!write && constant_decl_p (agg))
|
|
return;
|
|
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);
|
|
gassign *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))
|
|
{
|
|
gdebug *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
|
|
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)
|
|
{
|
|
gassign *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)
|
|
{
|
|
gdebug *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);
|
|
}
|
|
|
|
/* Clobber all scalar replacements in an access subtree. ACCESS is 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
|
|
clobber_subtree (struct access *access, gimple_stmt_iterator *gsi,
|
|
bool insert_after, location_t loc)
|
|
|
|
{
|
|
struct access *child;
|
|
|
|
if (access->grp_to_be_replaced)
|
|
{
|
|
tree rep = get_access_replacement (access);
|
|
tree clobber = build_constructor (access->type, NULL);
|
|
TREE_THIS_VOLATILE (clobber) = 1;
|
|
gimple *stmt = gimple_build_assign (rep, clobber);
|
|
|
|
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);
|
|
}
|
|
|
|
for (child = access->first_child; child; child = child->next_sibling)
|
|
clobber_subtree (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)
|
|
{
|
|
poly_int64 poffset, psize, pmax_size;
|
|
HOST_WIDE_INT offset, max_size;
|
|
tree base;
|
|
bool reverse;
|
|
|
|
/* 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, &poffset, &psize, &pmax_size,
|
|
&reverse);
|
|
if (!known_size_p (pmax_size)
|
|
|| !pmax_size.is_constant (&max_size)
|
|
|| !poffset.is_constant (&offset)
|
|
|| !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, orig_expr;
|
|
|
|
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);
|
|
orig_expr = *expr;
|
|
|
|
loc = gimple_location (gsi_stmt (*gsi));
|
|
gimple_stmt_iterator alt_gsi = gsi_none ();
|
|
if (write && stmt_ends_bb_p (gsi_stmt (*gsi)))
|
|
{
|
|
alt_gsi = gsi_start_edge (single_non_eh_succ (gsi_bb (*gsi)));
|
|
gsi = &alt_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, orig_expr, 0, access, gsi, false);
|
|
|
|
if (write)
|
|
{
|
|
gassign *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
|
|
{
|
|
gassign *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)
|
|
{
|
|
gdebug *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
|
|
&& tree_fits_uhwi_p (TREE_OPERAND (bfr, 1))
|
|
&& tree_fits_uhwi_p (TREE_OPERAND (bfr, 2)))
|
|
{
|
|
chunk_size = tree_to_uhwi (TREE_OPERAND (bfr, 1));
|
|
start_offset = access->offset
|
|
+ tree_to_uhwi (TREE_OPERAND (bfr, 2));
|
|
}
|
|
else
|
|
start_offset = chunk_size = 0;
|
|
|
|
generate_subtree_copies (access->first_child, orig_expr, access->offset,
|
|
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. */
|
|
|
|
struct subreplacement_assignment_data
|
|
{
|
|
/* Offset of the access representing the lhs of the assignment. */
|
|
HOST_WIDE_INT left_offset;
|
|
|
|
/* LHS and RHS of the original assignment. */
|
|
tree assignment_lhs, assignment_rhs;
|
|
|
|
/* Access representing the rhs of the whole assignment. */
|
|
struct access *top_racc;
|
|
|
|
/* Stmt iterator used for statement insertions after the original assignment.
|
|
It points to the main GSI used to traverse a BB during function body
|
|
modification. */
|
|
gimple_stmt_iterator *new_gsi;
|
|
|
|
/* Stmt iterator used for statement insertions before the original
|
|
assignment. Keeps on pointing to the original statement. */
|
|
gimple_stmt_iterator old_gsi;
|
|
|
|
/* Location of the assignment. */
|
|
location_t loc;
|
|
|
|
/* Keeps the information whether we have needed to refresh replacements of
|
|
the LHS and from which side of the assignments this takes place. */
|
|
enum unscalarized_data_handling refreshed;
|
|
};
|
|
|
|
/* 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 void
|
|
handle_unscalarized_data_in_subtree (struct subreplacement_assignment_data *sad)
|
|
{
|
|
tree src;
|
|
if (sad->top_racc->grp_unscalarized_data)
|
|
{
|
|
src = sad->assignment_rhs;
|
|
sad->refreshed = SRA_UDH_RIGHT;
|
|
}
|
|
else
|
|
{
|
|
src = sad->assignment_lhs;
|
|
sad->refreshed = SRA_UDH_LEFT;
|
|
}
|
|
generate_subtree_copies (sad->top_racc->first_child, src,
|
|
sad->top_racc->offset, 0, 0,
|
|
&sad->old_gsi, false, false, sad->loc);
|
|
}
|
|
|
|
/* Try to generate statements to load all sub-replacements in an access subtree
|
|
formed by children of LACC from scalar replacements in the SAD->top_racc
|
|
subtree. If that is not possible, refresh the SAD->top_racc base aggregate
|
|
and load the accesses from it. */
|
|
|
|
static void
|
|
load_assign_lhs_subreplacements (struct access *lacc,
|
|
struct subreplacement_assignment_data *sad)
|
|
{
|
|
for (lacc = lacc->first_child; lacc; lacc = lacc->next_sibling)
|
|
{
|
|
HOST_WIDE_INT offset;
|
|
offset = lacc->offset - sad->left_offset + sad->top_racc->offset;
|
|
|
|
if (lacc->grp_to_be_replaced)
|
|
{
|
|
struct access *racc;
|
|
gassign *stmt;
|
|
tree rhs;
|
|
|
|
racc = find_access_in_subtree (sad->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 (sad->loc, VIEW_CONVERT_EXPR,
|
|
lacc->type, rhs);
|
|
|
|
if (racc->grp_partial_lhs && lacc->grp_partial_lhs)
|
|
rhs = force_gimple_operand_gsi (&sad->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 (sad->refreshed == SRA_UDH_NONE)
|
|
handle_unscalarized_data_in_subtree (sad);
|
|
|
|
if (sad->refreshed == SRA_UDH_LEFT)
|
|
rhs = build_ref_for_model (sad->loc, sad->assignment_lhs,
|
|
lacc->offset - sad->left_offset,
|
|
lacc, sad->new_gsi, true);
|
|
else
|
|
rhs = build_ref_for_model (sad->loc, sad->assignment_rhs,
|
|
lacc->offset - sad->left_offset,
|
|
lacc, sad->new_gsi, true);
|
|
if (lacc->grp_partial_lhs)
|
|
rhs = force_gimple_operand_gsi (sad->new_gsi,
|
|
rhs, true, NULL_TREE,
|
|
false, GSI_NEW_STMT);
|
|
}
|
|
|
|
stmt = gimple_build_assign (get_access_replacement (lacc), rhs);
|
|
gsi_insert_after (sad->new_gsi, stmt, GSI_NEW_STMT);
|
|
gimple_set_location (stmt, sad->loc);
|
|
update_stmt (stmt);
|
|
sra_stats.subreplacements++;
|
|
}
|
|
else
|
|
{
|
|
if (sad->refreshed == SRA_UDH_NONE
|
|
&& lacc->grp_read && !lacc->grp_covered)
|
|
handle_unscalarized_data_in_subtree (sad);
|
|
|
|
if (lacc && lacc->grp_to_be_debug_replaced)
|
|
{
|
|
gdebug *ds;
|
|
tree drhs;
|
|
struct access *racc = find_access_in_subtree (sad->top_racc,
|
|
offset,
|
|
lacc->size);
|
|
|
|
if (racc && racc->grp_to_be_replaced)
|
|
{
|
|
if (racc->grp_write || constant_decl_p (racc->base))
|
|
drhs = get_access_replacement (racc);
|
|
else
|
|
drhs = NULL;
|
|
}
|
|
else if (sad->refreshed == SRA_UDH_LEFT)
|
|
drhs = build_debug_ref_for_model (sad->loc, lacc->base,
|
|
lacc->offset, lacc);
|
|
else if (sad->refreshed == SRA_UDH_RIGHT)
|
|
drhs = build_debug_ref_for_model (sad->loc, sad->top_racc->base,
|
|
offset, lacc);
|
|
else
|
|
drhs = NULL_TREE;
|
|
if (drhs
|
|
&& !useless_type_conversion_p (lacc->type, TREE_TYPE (drhs)))
|
|
drhs = fold_build1_loc (sad->loc, VIEW_CONVERT_EXPR,
|
|
lacc->type, drhs);
|
|
ds = gimple_build_debug_bind (get_access_replacement (lacc),
|
|
drhs, gsi_stmt (sad->old_gsi));
|
|
gsi_insert_after (sad->new_gsi, ds, GSI_NEW_STMT);
|
|
}
|
|
}
|
|
|
|
if (lacc->first_child)
|
|
load_assign_lhs_subreplacements (lacc, sad);
|
|
}
|
|
}
|
|
|
|
/* 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 = get_access_for_expr (lhs);
|
|
if (!acc)
|
|
return SRA_AM_NONE;
|
|
location_t loc = gimple_location (stmt);
|
|
|
|
if (gimple_clobber_p (stmt))
|
|
{
|
|
/* Clobber the replacement variable. */
|
|
clobber_subtree (acc, gsi, !acc->grp_covered, loc);
|
|
/* Remove clobbers of fully scalarized variables, they are dead. */
|
|
if (acc->grp_covered)
|
|
{
|
|
unlink_stmt_vdef (stmt);
|
|
gsi_remove (gsi, true);
|
|
release_defs (stmt);
|
|
return SRA_AM_REMOVED;
|
|
}
|
|
else
|
|
return SRA_AM_MODIFIED;
|
|
}
|
|
|
|
if (CONSTRUCTOR_NELTS (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, lhs, acc->offset, 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. REG_TREE is used instead of the actual RACC type if that is not of
|
|
a gimple register type. */
|
|
|
|
static tree
|
|
get_repl_default_def_ssa_name (struct access *racc, tree reg_type)
|
|
{
|
|
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, reg_type);
|
|
return get_or_create_ssa_default_def (cfun, racc->replacement_decl);
|
|
}
|
|
|
|
/* 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;
|
|
/* Avoid modifying initializations of constant-pool replacements. */
|
|
if (racc && (racc->replacement_decl == lhs))
|
|
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
|
|
&& !racc->grp_unscalarizable_region
|
|
&& TREE_CODE (lhs) == SSA_NAME
|
|
&& !access_has_replacements_p (racc))
|
|
{
|
|
rhs = get_repl_default_def_ssa_name (racc, TREE_TYPE (lhs));
|
|
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 (lacc
|
|
&& 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);
|
|
}
|
|
gdebug *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)
|
|
|| stmt_ends_bb_p (stmt))
|
|
{
|
|
/* No need to copy into a constant-pool, it comes pre-initialized. */
|
|
if (access_has_children_p (racc) && !constant_decl_p (racc->base))
|
|
generate_subtree_copies (racc->first_child, rhs, racc->offset, 0, 0,
|
|
gsi, false, false, loc);
|
|
if (access_has_children_p (lacc))
|
|
{
|
|
gimple_stmt_iterator alt_gsi = gsi_none ();
|
|
if (stmt_ends_bb_p (stmt))
|
|
{
|
|
alt_gsi = gsi_start_edge (single_non_eh_succ (gsi_bb (*gsi)));
|
|
gsi = &alt_gsi;
|
|
}
|
|
generate_subtree_copies (lacc->first_child, lhs, lacc->offset, 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)
|
|
{
|
|
struct subreplacement_assignment_data sad;
|
|
|
|
sad.left_offset = lacc->offset;
|
|
sad.assignment_lhs = lhs;
|
|
sad.assignment_rhs = rhs;
|
|
sad.top_racc = racc;
|
|
sad.old_gsi = *gsi;
|
|
sad.new_gsi = gsi;
|
|
sad.loc = gimple_location (stmt);
|
|
sad.refreshed = SRA_UDH_NONE;
|
|
|
|
if (lacc->grp_read && !lacc->grp_covered)
|
|
handle_unscalarized_data_in_subtree (&sad);
|
|
|
|
load_assign_lhs_subreplacements (lacc, &sad);
|
|
if (sad.refreshed != SRA_UDH_RIGHT)
|
|
{
|
|
gsi_next (gsi);
|
|
unlink_stmt_vdef (stmt);
|
|
gsi_remove (&sad.old_gsi, true);
|
|
release_defs (stmt);
|
|
sra_stats.deleted++;
|
|
return SRA_AM_REMOVED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (access_has_children_p (racc)
|
|
&& !racc->grp_unscalarized_data
|
|
&& TREE_CODE (lhs) != SSA_NAME)
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Removing load: ");
|
|
print_gimple_stmt (dump_file, stmt, 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, rhs, racc->offset, 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;
|
|
}
|
|
}
|
|
|
|
/* Set any scalar replacements of values in the constant pool to the initial
|
|
value of the constant. (Constant-pool decls like *.LC0 have effectively
|
|
been initialized before the program starts, we must do the same for their
|
|
replacements.) Thus, we output statements like 'SR.1 = *.LC0[0];' into
|
|
the function's entry block. */
|
|
|
|
static void
|
|
initialize_constant_pool_replacements (void)
|
|
{
|
|
gimple_seq seq = NULL;
|
|
gimple_stmt_iterator gsi = gsi_start (seq);
|
|
bitmap_iterator bi;
|
|
unsigned i;
|
|
|
|
EXECUTE_IF_SET_IN_BITMAP (candidate_bitmap, 0, i, bi)
|
|
{
|
|
tree var = candidate (i);
|
|
if (!constant_decl_p (var))
|
|
continue;
|
|
vec<access_p> *access_vec = get_base_access_vector (var);
|
|
if (!access_vec)
|
|
continue;
|
|
for (unsigned i = 0; i < access_vec->length (); i++)
|
|
{
|
|
struct access *access = (*access_vec)[i];
|
|
if (!access->replacement_decl)
|
|
continue;
|
|
gassign *stmt
|
|
= gimple_build_assign (get_access_replacement (access),
|
|
unshare_expr (access->expr));
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Generating constant initializer: ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
gsi_insert_after (&gsi, stmt, GSI_NEW_STMT);
|
|
update_stmt (stmt);
|
|
}
|
|
}
|
|
|
|
seq = gsi_seq (gsi);
|
|
if (seq)
|
|
gsi_insert_seq_on_edge_immediate (
|
|
single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun)), seq);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
initialize_constant_pool_replacements ();
|
|
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
{
|
|
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 (as_a <greturn *> (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:
|
|
{
|
|
gasm *asm_stmt = as_a <gasm *> (stmt);
|
|
for (i = 0; i < gimple_asm_ninputs (asm_stmt); i++)
|
|
{
|
|
t = &TREE_VALUE (gimple_asm_input_op (asm_stmt, i));
|
|
modified |= sra_modify_expr (t, &gsi, false);
|
|
}
|
|
for (i = 0; i < gimple_asm_noutputs (asm_stmt); i++)
|
|
{
|
|
t = &TREE_VALUE (gimple_asm_output_op (asm_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);
|
|
}
|
|
}
|
|
|
|
gsi_commit_edge_inserts ();
|
|
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_FOR_FN (cfun)), 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);
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_sra_early =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"esra", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
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_flags_finish */
|
|
};
|
|
|
|
class pass_sra_early : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_sra_early (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_sra_early, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
virtual bool gate (function *) { return gate_intra_sra (); }
|
|
virtual unsigned int execute (function *) { return early_intra_sra (); }
|
|
|
|
}; // class pass_sra_early
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_sra_early (gcc::context *ctxt)
|
|
{
|
|
return new pass_sra_early (ctxt);
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_sra =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"sra", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
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_flags_finish */
|
|
};
|
|
|
|
class pass_sra : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_sra (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_sra, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
virtual bool gate (function *) { return gate_intra_sra (); }
|
|
virtual unsigned int execute (function *) { return late_intra_sra (); }
|
|
|
|
}; // class pass_sra
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_sra (gcc::context *ctxt)
|
|
{
|
|
return new pass_sra (ctxt);
|
|
}
|
|
|
|
|
|
/* 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);
|
|
tree_node **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)
|
|
|| !tree_fits_uhwi_p (TYPE_SIZE (type))
|
|
|| tree_to_uhwi (TYPE_SIZE (type)) == 0
|
|
|| (AGGREGATE_TYPE_P (type)
|
|
&& type_internals_preclude_sra_p (type, &msg)))
|
|
continue;
|
|
|
|
bitmap_set_bit (candidate_bitmap, DECL_UID (parm));
|
|
slot = candidates->find_slot_with_hash (parm, DECL_UID (parm), INSERT);
|
|
*slot = parm;
|
|
|
|
ret = true;
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Candidate (%d): ", DECL_UID (parm));
|
|
print_generic_expr (dump_file, parm);
|
|
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)
|
|
{
|
|
basic_block bb;
|
|
|
|
auto_vec<basic_block> queue (last_basic_block_for_fn (cfun));
|
|
queue.quick_push (ENTRY_BLOCK_PTR_FOR_FN (cfun));
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
{
|
|
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_FOR_FN (cfun))
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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, "%s", str);
|
|
FOR_BB_BETWEEN (bb, ENTRY_BLOCK_PTR_FOR_FN (cfun),
|
|
EXIT_BLOCK_PTR_FOR_FN (cfun), next_bb)
|
|
{
|
|
fprintf (f, "%4i %i ", bb->index, bitmap_bit_p (final_bbs, bb->index));
|
|
if (bb != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
|
{
|
|
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_FOR_FN (cfun)->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 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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
HOST_WIDE_INT total_size, cur_parm_size;
|
|
bool by_ref;
|
|
tree parm;
|
|
|
|
parm = repr->base;
|
|
cur_parm_size = tree_to_uhwi (TYPE_SIZE (TREE_TYPE (parm)));
|
|
gcc_assert (cur_parm_size > 0);
|
|
|
|
if (POINTER_TYPE_P (TREE_TYPE (parm)))
|
|
by_ref = true;
|
|
else
|
|
by_ref = false;
|
|
|
|
if (dump_file)
|
|
{
|
|
struct access *acc;
|
|
fprintf (dump_file, "Evaluating PARAM group sizes for ");
|
|
print_generic_expr (dump_file, parm);
|
|
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;
|
|
int 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 (!by_ref)
|
|
{
|
|
if (total_size >= cur_parm_size)
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
int parm_num_limit;
|
|
if (optimize_function_for_size_p (cfun))
|
|
parm_num_limit = 1;
|
|
else
|
|
parm_num_limit = PARAM_VALUE (PARAM_IPA_SRA_PTR_GROWTH_FACTOR);
|
|
|
|
if (new_param_count > parm_num_limit
|
|
|| total_size > (parm_num_limit * cur_parm_size))
|
|
return 0;
|
|
}
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, " ....will be split into %i components\n",
|
|
new_param_count);
|
|
return new_param_count;
|
|
}
|
|
|
|
/* 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.op = IPA_PARM_OP_COPY;
|
|
else
|
|
adj.op = IPA_PARM_OP_REMOVE;
|
|
adj.arg_prefix = "ISRA";
|
|
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.reverse = repr->reverse;
|
|
adj.by_ref = (POINTER_TYPE_P (TREE_TYPE (repr->base))
|
|
&& (repr->grp_maybe_modified
|
|
|| repr->grp_not_necessarilly_dereferenced));
|
|
adj.arg_prefix = "ISRA";
|
|
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);
|
|
DECL_NAMELESS (repl) = 1;
|
|
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->op != IPA_PARM_OP_COPY && adj->base == base)
|
|
return adj;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* If OLD_NAME, which is being defined by statement STMT, is an SSA_NAME of a
|
|
parameter which is to be removed because its value is not used, create a new
|
|
SSA_NAME relating to a replacement VAR_DECL, replace all uses of the
|
|
original with it and return it. If there is no need to re-map, return NULL.
|
|
ADJUSTMENTS is a pointer to a vector of IPA-SRA adjustments. */
|
|
|
|
static tree
|
|
replace_removed_params_ssa_names (tree old_name, gimple *stmt,
|
|
ipa_parm_adjustment_vec adjustments)
|
|
{
|
|
struct ipa_parm_adjustment *adj;
|
|
tree decl, repl, new_name;
|
|
|
|
if (TREE_CODE (old_name) != SSA_NAME)
|
|
return NULL;
|
|
|
|
decl = SSA_NAME_VAR (old_name);
|
|
if (decl == NULL_TREE
|
|
|| TREE_CODE (decl) != PARM_DECL)
|
|
return NULL;
|
|
|
|
adj = get_adjustment_for_base (adjustments, decl);
|
|
if (!adj)
|
|
return NULL;
|
|
|
|
repl = get_replaced_param_substitute (adj);
|
|
new_name = make_ssa_name (repl, stmt);
|
|
SSA_NAME_OCCURS_IN_ABNORMAL_PHI (new_name)
|
|
= SSA_NAME_OCCURS_IN_ABNORMAL_PHI (old_name);
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "replacing an SSA name of a removed param ");
|
|
print_generic_expr (dump_file, old_name);
|
|
fprintf (dump_file, " with ");
|
|
print_generic_expr (dump_file, new_name);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
replace_uses_by (old_name, new_name);
|
|
return new_name;
|
|
}
|
|
|
|
/* If the statement STMT 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, gimple_stmt_iterator *gsi,
|
|
ipa_parm_adjustment_vec adjustments)
|
|
{
|
|
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 = ipa_modify_expr (rhs_p, false, adjustments);
|
|
any |= 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. */
|
|
|
|
bool
|
|
ipa_sra_modify_function_body (ipa_parm_adjustment_vec adjustments)
|
|
{
|
|
bool cfg_changed = false;
|
|
basic_block bb;
|
|
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
{
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gphi *phi = as_a <gphi *> (gsi_stmt (gsi));
|
|
tree new_lhs, old_lhs = gimple_phi_result (phi);
|
|
new_lhs = replace_removed_params_ssa_names (old_lhs, phi, adjustments);
|
|
if (new_lhs)
|
|
{
|
|
gimple_phi_set_result (phi, new_lhs);
|
|
release_ssa_name (old_lhs);
|
|
}
|
|
}
|
|
|
|
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 (as_a <greturn *> (stmt));
|
|
if (*t != NULL_TREE)
|
|
modified |= ipa_modify_expr (t, true, adjustments);
|
|
break;
|
|
|
|
case GIMPLE_ASSIGN:
|
|
modified |= sra_ipa_modify_assign (stmt, &gsi, 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 |= ipa_modify_expr (t, true, adjustments);
|
|
}
|
|
|
|
if (gimple_call_lhs (stmt))
|
|
{
|
|
t = gimple_call_lhs_ptr (stmt);
|
|
modified |= ipa_modify_expr (t, false, adjustments);
|
|
}
|
|
break;
|
|
|
|
case GIMPLE_ASM:
|
|
{
|
|
gasm *asm_stmt = as_a <gasm *> (stmt);
|
|
for (i = 0; i < gimple_asm_ninputs (asm_stmt); i++)
|
|
{
|
|
t = &TREE_VALUE (gimple_asm_input_op (asm_stmt, i));
|
|
modified |= ipa_modify_expr (t, true, adjustments);
|
|
}
|
|
for (i = 0; i < gimple_asm_noutputs (asm_stmt); i++)
|
|
{
|
|
t = &TREE_VALUE (gimple_asm_output_op (asm_stmt, i));
|
|
modified |= ipa_modify_expr (t, false, adjustments);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
def_operand_p defp;
|
|
ssa_op_iter iter;
|
|
FOR_EACH_SSA_DEF_OPERAND (defp, stmt, iter, SSA_OP_DEF)
|
|
{
|
|
tree old_def = DEF_FROM_PTR (defp);
|
|
if (tree new_def = replace_removed_params_ssa_names (old_def, stmt,
|
|
adjustments))
|
|
{
|
|
SET_DEF (defp, new_def);
|
|
release_ssa_name (old_def);
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
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_FOR_FN (cfun)))
|
|
{
|
|
gsi = gsi_after_labels (single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
|
|
gsip = &gsi;
|
|
}
|
|
len = adjustments.length ();
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
struct ipa_parm_adjustment *adj;
|
|
imm_use_iterator ui;
|
|
gimple *stmt;
|
|
gdebug *def_temp;
|
|
tree name, vexpr, copy = NULL_TREE;
|
|
use_operand_p use_p;
|
|
|
|
adj = &adjustments[i];
|
|
if (adj->op == IPA_PARM_OP_COPY || !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)
|
|
{
|
|
if (gimple_clobber_p (stmt))
|
|
{
|
|
gimple_stmt_iterator cgsi = gsi_for_stmt (stmt);
|
|
unlink_stmt_vdef (stmt);
|
|
gsi_remove (&cgsi, true);
|
|
release_defs (stmt);
|
|
continue;
|
|
}
|
|
/* 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);
|
|
SET_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 if all callers have at least as many actual arguments as there
|
|
are formal parameters in the current function and that their types
|
|
match. */
|
|
|
|
static bool
|
|
some_callers_have_mismatched_arguments_p (struct cgraph_node *node,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
struct cgraph_edge *cs;
|
|
for (cs = node->callers; cs; cs = cs->next_caller)
|
|
if (!cs->call_stmt || !callsite_arguments_match_p (cs->call_stmt))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Return false if all callers have vuse attached to a call statement. */
|
|
|
|
static bool
|
|
some_callers_have_no_vuse_p (struct cgraph_node *node,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
struct cgraph_edge *cs;
|
|
for (cs = node->callers; cs; cs = cs->next_caller)
|
|
if (!cs->call_stmt || !gimple_vuse (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->decl));
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "Adjusting call %s -> %s\n",
|
|
cs->caller->dump_name (), cs->callee->dump_name ());
|
|
|
|
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->get_uid ())
|
|
&& gimple_in_ssa_p (DECL_STRUCT_FUNCTION (cs->caller->decl)))
|
|
compute_fn_summary (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;
|
|
|
|
node->call_for_symbol_and_aliases (convert_callers_for_node,
|
|
&adjustments, false);
|
|
|
|
if (!encountered_recursive_call)
|
|
return;
|
|
|
|
FOR_EACH_BB_FN (this_block, cfun)
|
|
{
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_bb (this_block); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gcall *stmt;
|
|
tree call_fndecl;
|
|
stmt = dyn_cast <gcall *> (gsi_stmt (gsi));
|
|
if (!stmt)
|
|
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->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;
|
|
|
|
cgraph_edge::rebuild_edges ();
|
|
free_dominance_info (CDI_DOMINATORS);
|
|
pop_cfun ();
|
|
|
|
/* This must be done after rebuilding cgraph edges for node above.
|
|
Otherwise any recursive calls to node that are recorded in
|
|
redirect_callers will be corrupted. */
|
|
vec<cgraph_edge *> redirect_callers = node->collect_callers ();
|
|
new_node = node->create_version_clone_with_body (redirect_callers, NULL,
|
|
NULL, false, NULL, NULL,
|
|
"isra");
|
|
redirect_callers.release ();
|
|
|
|
push_cfun (DECL_STRUCT_FUNCTION (new_node->decl));
|
|
ipa_modify_formal_parameters (current_function_decl, adjustments);
|
|
cfg_changed = ipa_sra_modify_function_body (adjustments);
|
|
sra_ipa_reset_debug_stmts (adjustments);
|
|
convert_callers (new_node, node->decl, adjustments);
|
|
new_node->make_local ();
|
|
return cfg_changed;
|
|
}
|
|
|
|
/* Means of communication between ipa_sra_check_caller and
|
|
ipa_sra_preliminary_function_checks. */
|
|
|
|
struct ipa_sra_check_caller_data
|
|
{
|
|
bool has_callers;
|
|
bool bad_arg_alignment;
|
|
bool has_thunk;
|
|
};
|
|
|
|
/* If NODE has a caller, mark that fact in DATA which is pointer to
|
|
ipa_sra_check_caller_data. Also check all aggregate arguments in all known
|
|
calls if they are unit aligned and if not, set the appropriate flag in DATA
|
|
too. */
|
|
|
|
static bool
|
|
ipa_sra_check_caller (struct cgraph_node *node, void *data)
|
|
{
|
|
if (!node->callers)
|
|
return false;
|
|
|
|
struct ipa_sra_check_caller_data *iscc;
|
|
iscc = (struct ipa_sra_check_caller_data *) data;
|
|
iscc->has_callers = true;
|
|
|
|
for (cgraph_edge *cs = node->callers; cs; cs = cs->next_caller)
|
|
{
|
|
if (cs->caller->thunk.thunk_p)
|
|
{
|
|
iscc->has_thunk = true;
|
|
return true;
|
|
}
|
|
gimple *call_stmt = cs->call_stmt;
|
|
unsigned count = gimple_call_num_args (call_stmt);
|
|
for (unsigned i = 0; i < count; i++)
|
|
{
|
|
tree arg = gimple_call_arg (call_stmt, i);
|
|
if (is_gimple_reg (arg))
|
|
continue;
|
|
|
|
tree offset;
|
|
poly_int64 bitsize, bitpos;
|
|
machine_mode mode;
|
|
int unsignedp, reversep, volatilep = 0;
|
|
get_inner_reference (arg, &bitsize, &bitpos, &offset, &mode,
|
|
&unsignedp, &reversep, &volatilep);
|
|
if (!multiple_p (bitpos, BITS_PER_UNIT))
|
|
{
|
|
iscc->bad_arg_alignment = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* 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 (!node->can_be_local_p ())
|
|
{
|
|
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 cannot change signature.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!tree_versionable_function_p (node->decl))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Function is not versionable.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!opt_for_fn (node->decl, optimize)
|
|
|| !opt_for_fn (node->decl, flag_ipa_sra))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Function not optimized.\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_ONE_ONLY (node->decl) || DECL_EXTERNAL (node->decl))
|
|
&& ipa_fn_summaries->get (node)
|
|
&& ipa_fn_summaries->get (node)->size >= MAX_INLINE_INSNS_AUTO)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Function too big to be made truly local.\n");
|
|
return false;
|
|
}
|
|
|
|
if (cfun->stdarg)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Function uses stdarg. \n");
|
|
return false;
|
|
}
|
|
|
|
if (TYPE_ATTRIBUTES (TREE_TYPE (node->decl)))
|
|
return false;
|
|
|
|
if (DECL_DISREGARD_INLINE_LIMITS (node->decl))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Always inline function will be inlined "
|
|
"anyway. \n");
|
|
return false;
|
|
}
|
|
|
|
struct ipa_sra_check_caller_data iscc;
|
|
memset (&iscc, 0, sizeof(iscc));
|
|
node->call_for_symbol_and_aliases (ipa_sra_check_caller, &iscc, true);
|
|
if (!iscc.has_callers)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"Function has no callers in this compilation unit.\n");
|
|
return false;
|
|
}
|
|
|
|
if (iscc.bad_arg_alignment)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"A function call has an argument with non-unit alignment.\n");
|
|
return false;
|
|
}
|
|
|
|
if (iscc.has_thunk)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"A has thunk.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Perform early interprocedural SRA. */
|
|
|
|
static unsigned int
|
|
ipa_early_sra (void)
|
|
{
|
|
struct cgraph_node *node = cgraph_node::get (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 (node->call_for_symbol_and_aliases
|
|
(some_callers_have_mismatched_arguments_p, NULL, true))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "There are callers with insufficient number of "
|
|
"arguments or arguments with type mismatches.\n");
|
|
goto simple_out;
|
|
}
|
|
|
|
if (node->call_for_symbol_and_aliases
|
|
(some_callers_have_no_vuse_p, NULL, true))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "There are callers with no VUSE attached "
|
|
"to a call stmt.\n");
|
|
goto simple_out;
|
|
}
|
|
|
|
bb_dereferences = XCNEWVEC (HOST_WIDE_INT,
|
|
func_param_count
|
|
* last_basic_block_for_fn (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;
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_early_ipa_sra =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"eipa_sra", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_IPA_SRA, /* tv_id */
|
|
0, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
TODO_dump_symtab, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_early_ipa_sra : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_early_ipa_sra (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_early_ipa_sra, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
virtual bool gate (function *) { return flag_ipa_sra && dbg_cnt (eipa_sra); }
|
|
virtual unsigned int execute (function *) { return ipa_early_sra (); }
|
|
|
|
}; // class pass_early_ipa_sra
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_early_ipa_sra (gcc::context *ctxt)
|
|
{
|
|
return new pass_early_ipa_sra (ctxt);
|
|
}
|