eda71a9eea
PR tree-optimization/85257 * fold-const.c (native_encode_vector): If not all elts could fit and off is -1, return 0 rather than offset. * tree-ssa-sccvn.c (vn_reference_lookup_3): Pass (offseti - offset2) / BITS_PER_UNIT as 4th argument to native_encode_expr. Verify len * BITS_PER_UNIT >= maxsizei. Don't adjust buffer in native_interpret_expr call. * gcc.dg/pr85257.c: New test. From-SVN: r259206
6034 lines
174 KiB
C
6034 lines
174 KiB
C
/* SCC value numbering for trees
|
|
Copyright (C) 2006-2018 Free Software Foundation, Inc.
|
|
Contributed by Daniel Berlin <dan@dberlin.org>
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GCC is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "backend.h"
|
|
#include "rtl.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "alloc-pool.h"
|
|
#include "ssa.h"
|
|
#include "expmed.h"
|
|
#include "insn-config.h"
|
|
#include "memmodel.h"
|
|
#include "emit-rtl.h"
|
|
#include "cgraph.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "alias.h"
|
|
#include "fold-const.h"
|
|
#include "stor-layout.h"
|
|
#include "cfganal.h"
|
|
#include "tree-inline.h"
|
|
#include "internal-fn.h"
|
|
#include "gimple-fold.h"
|
|
#include "tree-eh.h"
|
|
#include "gimplify.h"
|
|
#include "flags.h"
|
|
#include "dojump.h"
|
|
#include "explow.h"
|
|
#include "calls.h"
|
|
#include "varasm.h"
|
|
#include "stmt.h"
|
|
#include "expr.h"
|
|
#include "tree-dfa.h"
|
|
#include "tree-ssa.h"
|
|
#include "dumpfile.h"
|
|
#include "cfgloop.h"
|
|
#include "params.h"
|
|
#include "tree-ssa-propagate.h"
|
|
#include "tree-cfg.h"
|
|
#include "domwalk.h"
|
|
#include "gimple-iterator.h"
|
|
#include "gimple-match.h"
|
|
#include "stringpool.h"
|
|
#include "attribs.h"
|
|
#include "tree-pass.h"
|
|
#include "statistics.h"
|
|
#include "langhooks.h"
|
|
#include "ipa-utils.h"
|
|
#include "dbgcnt.h"
|
|
#include "tree-cfgcleanup.h"
|
|
#include "tree-ssa-loop.h"
|
|
#include "tree-scalar-evolution.h"
|
|
#include "tree-ssa-sccvn.h"
|
|
|
|
/* This algorithm is based on the SCC algorithm presented by Keith
|
|
Cooper and L. Taylor Simpson in "SCC-Based Value numbering"
|
|
(http://citeseer.ist.psu.edu/41805.html). In
|
|
straight line code, it is equivalent to a regular hash based value
|
|
numbering that is performed in reverse postorder.
|
|
|
|
For code with cycles, there are two alternatives, both of which
|
|
require keeping the hashtables separate from the actual list of
|
|
value numbers for SSA names.
|
|
|
|
1. Iterate value numbering in an RPO walk of the blocks, removing
|
|
all the entries from the hashtable after each iteration (but
|
|
keeping the SSA name->value number mapping between iterations).
|
|
Iterate until it does not change.
|
|
|
|
2. Perform value numbering as part of an SCC walk on the SSA graph,
|
|
iterating only the cycles in the SSA graph until they do not change
|
|
(using a separate, optimistic hashtable for value numbering the SCC
|
|
operands).
|
|
|
|
The second is not just faster in practice (because most SSA graph
|
|
cycles do not involve all the variables in the graph), it also has
|
|
some nice properties.
|
|
|
|
One of these nice properties is that when we pop an SCC off the
|
|
stack, we are guaranteed to have processed all the operands coming from
|
|
*outside of that SCC*, so we do not need to do anything special to
|
|
ensure they have value numbers.
|
|
|
|
Another nice property is that the SCC walk is done as part of a DFS
|
|
of the SSA graph, which makes it easy to perform combining and
|
|
simplifying operations at the same time.
|
|
|
|
The code below is deliberately written in a way that makes it easy
|
|
to separate the SCC walk from the other work it does.
|
|
|
|
In order to propagate constants through the code, we track which
|
|
expressions contain constants, and use those while folding. In
|
|
theory, we could also track expressions whose value numbers are
|
|
replaced, in case we end up folding based on expression
|
|
identities.
|
|
|
|
In order to value number memory, we assign value numbers to vuses.
|
|
This enables us to note that, for example, stores to the same
|
|
address of the same value from the same starting memory states are
|
|
equivalent.
|
|
TODO:
|
|
|
|
1. We can iterate only the changing portions of the SCC's, but
|
|
I have not seen an SCC big enough for this to be a win.
|
|
2. If you differentiate between phi nodes for loops and phi nodes
|
|
for if-then-else, you can properly consider phi nodes in different
|
|
blocks for equivalence.
|
|
3. We could value number vuses in more cases, particularly, whole
|
|
structure copies.
|
|
*/
|
|
|
|
|
|
static tree *last_vuse_ptr;
|
|
static vn_lookup_kind vn_walk_kind;
|
|
static vn_lookup_kind default_vn_walk_kind;
|
|
bitmap const_parms;
|
|
|
|
/* vn_nary_op hashtable helpers. */
|
|
|
|
struct vn_nary_op_hasher : nofree_ptr_hash <vn_nary_op_s>
|
|
{
|
|
typedef vn_nary_op_s *compare_type;
|
|
static inline hashval_t hash (const vn_nary_op_s *);
|
|
static inline bool equal (const vn_nary_op_s *, const vn_nary_op_s *);
|
|
};
|
|
|
|
/* Return the computed hashcode for nary operation P1. */
|
|
|
|
inline hashval_t
|
|
vn_nary_op_hasher::hash (const vn_nary_op_s *vno1)
|
|
{
|
|
return vno1->hashcode;
|
|
}
|
|
|
|
/* Compare nary operations P1 and P2 and return true if they are
|
|
equivalent. */
|
|
|
|
inline bool
|
|
vn_nary_op_hasher::equal (const vn_nary_op_s *vno1, const vn_nary_op_s *vno2)
|
|
{
|
|
return vn_nary_op_eq (vno1, vno2);
|
|
}
|
|
|
|
typedef hash_table<vn_nary_op_hasher> vn_nary_op_table_type;
|
|
typedef vn_nary_op_table_type::iterator vn_nary_op_iterator_type;
|
|
|
|
|
|
/* vn_phi hashtable helpers. */
|
|
|
|
static int
|
|
vn_phi_eq (const_vn_phi_t const vp1, const_vn_phi_t const vp2);
|
|
|
|
struct vn_phi_hasher : pointer_hash <vn_phi_s>
|
|
{
|
|
static inline hashval_t hash (const vn_phi_s *);
|
|
static inline bool equal (const vn_phi_s *, const vn_phi_s *);
|
|
static inline void remove (vn_phi_s *);
|
|
};
|
|
|
|
/* Return the computed hashcode for phi operation P1. */
|
|
|
|
inline hashval_t
|
|
vn_phi_hasher::hash (const vn_phi_s *vp1)
|
|
{
|
|
return vp1->hashcode;
|
|
}
|
|
|
|
/* Compare two phi entries for equality, ignoring VN_TOP arguments. */
|
|
|
|
inline bool
|
|
vn_phi_hasher::equal (const vn_phi_s *vp1, const vn_phi_s *vp2)
|
|
{
|
|
return vn_phi_eq (vp1, vp2);
|
|
}
|
|
|
|
/* Free a phi operation structure VP. */
|
|
|
|
inline void
|
|
vn_phi_hasher::remove (vn_phi_s *phi)
|
|
{
|
|
phi->phiargs.release ();
|
|
}
|
|
|
|
typedef hash_table<vn_phi_hasher> vn_phi_table_type;
|
|
typedef vn_phi_table_type::iterator vn_phi_iterator_type;
|
|
|
|
|
|
/* Compare two reference operands P1 and P2 for equality. Return true if
|
|
they are equal, and false otherwise. */
|
|
|
|
static int
|
|
vn_reference_op_eq (const void *p1, const void *p2)
|
|
{
|
|
const_vn_reference_op_t const vro1 = (const_vn_reference_op_t) p1;
|
|
const_vn_reference_op_t const vro2 = (const_vn_reference_op_t) p2;
|
|
|
|
return (vro1->opcode == vro2->opcode
|
|
/* We do not care for differences in type qualification. */
|
|
&& (vro1->type == vro2->type
|
|
|| (vro1->type && vro2->type
|
|
&& types_compatible_p (TYPE_MAIN_VARIANT (vro1->type),
|
|
TYPE_MAIN_VARIANT (vro2->type))))
|
|
&& expressions_equal_p (vro1->op0, vro2->op0)
|
|
&& expressions_equal_p (vro1->op1, vro2->op1)
|
|
&& expressions_equal_p (vro1->op2, vro2->op2));
|
|
}
|
|
|
|
/* Free a reference operation structure VP. */
|
|
|
|
static inline void
|
|
free_reference (vn_reference_s *vr)
|
|
{
|
|
vr->operands.release ();
|
|
}
|
|
|
|
|
|
/* vn_reference hashtable helpers. */
|
|
|
|
struct vn_reference_hasher : pointer_hash <vn_reference_s>
|
|
{
|
|
static inline hashval_t hash (const vn_reference_s *);
|
|
static inline bool equal (const vn_reference_s *, const vn_reference_s *);
|
|
static inline void remove (vn_reference_s *);
|
|
};
|
|
|
|
/* Return the hashcode for a given reference operation P1. */
|
|
|
|
inline hashval_t
|
|
vn_reference_hasher::hash (const vn_reference_s *vr1)
|
|
{
|
|
return vr1->hashcode;
|
|
}
|
|
|
|
inline bool
|
|
vn_reference_hasher::equal (const vn_reference_s *v, const vn_reference_s *c)
|
|
{
|
|
return vn_reference_eq (v, c);
|
|
}
|
|
|
|
inline void
|
|
vn_reference_hasher::remove (vn_reference_s *v)
|
|
{
|
|
free_reference (v);
|
|
}
|
|
|
|
typedef hash_table<vn_reference_hasher> vn_reference_table_type;
|
|
typedef vn_reference_table_type::iterator vn_reference_iterator_type;
|
|
|
|
|
|
/* The set of hashtables and alloc_pool's for their items. */
|
|
|
|
typedef struct vn_tables_s
|
|
{
|
|
vn_nary_op_table_type *nary;
|
|
vn_phi_table_type *phis;
|
|
vn_reference_table_type *references;
|
|
struct obstack nary_obstack;
|
|
object_allocator<vn_phi_s> *phis_pool;
|
|
object_allocator<vn_reference_s> *references_pool;
|
|
} *vn_tables_t;
|
|
|
|
|
|
/* vn_constant hashtable helpers. */
|
|
|
|
struct vn_constant_hasher : free_ptr_hash <vn_constant_s>
|
|
{
|
|
static inline hashval_t hash (const vn_constant_s *);
|
|
static inline bool equal (const vn_constant_s *, const vn_constant_s *);
|
|
};
|
|
|
|
/* Hash table hash function for vn_constant_t. */
|
|
|
|
inline hashval_t
|
|
vn_constant_hasher::hash (const vn_constant_s *vc1)
|
|
{
|
|
return vc1->hashcode;
|
|
}
|
|
|
|
/* Hash table equality function for vn_constant_t. */
|
|
|
|
inline bool
|
|
vn_constant_hasher::equal (const vn_constant_s *vc1, const vn_constant_s *vc2)
|
|
{
|
|
if (vc1->hashcode != vc2->hashcode)
|
|
return false;
|
|
|
|
return vn_constant_eq_with_type (vc1->constant, vc2->constant);
|
|
}
|
|
|
|
static hash_table<vn_constant_hasher> *constant_to_value_id;
|
|
static bitmap constant_value_ids;
|
|
|
|
|
|
/* Valid hashtables storing information we have proven to be
|
|
correct. */
|
|
|
|
static vn_tables_t valid_info;
|
|
|
|
/* Optimistic hashtables storing information we are making assumptions about
|
|
during iterations. */
|
|
|
|
static vn_tables_t optimistic_info;
|
|
|
|
/* Pointer to the set of hashtables that is currently being used.
|
|
Should always point to either the optimistic_info, or the
|
|
valid_info. */
|
|
|
|
static vn_tables_t current_info;
|
|
|
|
|
|
/* Reverse post order index for each basic block. */
|
|
|
|
static int *rpo_numbers;
|
|
|
|
#define SSA_VAL(x) (VN_INFO ((x))->valnum)
|
|
|
|
/* Return the SSA value of the VUSE x, supporting released VDEFs
|
|
during elimination which will value-number the VDEF to the
|
|
associated VUSE (but not substitute in the whole lattice). */
|
|
|
|
static inline tree
|
|
vuse_ssa_val (tree x)
|
|
{
|
|
if (!x)
|
|
return NULL_TREE;
|
|
|
|
do
|
|
{
|
|
tree tem = SSA_VAL (x);
|
|
/* stmt walking can walk over a backedge and reach code we didn't
|
|
value-number yet. */
|
|
if (tem == VN_TOP)
|
|
return x;
|
|
x = tem;
|
|
}
|
|
while (SSA_NAME_IN_FREE_LIST (x));
|
|
|
|
return x;
|
|
}
|
|
|
|
/* This represents the top of the VN lattice, which is the universal
|
|
value. */
|
|
|
|
tree VN_TOP;
|
|
|
|
/* Unique counter for our value ids. */
|
|
|
|
static unsigned int next_value_id;
|
|
|
|
/* Next DFS number and the stack for strongly connected component
|
|
detection. */
|
|
|
|
static unsigned int next_dfs_num;
|
|
static vec<tree> sccstack;
|
|
|
|
|
|
|
|
/* Table of vn_ssa_aux_t's, one per ssa_name. The vn_ssa_aux_t objects
|
|
are allocated on an obstack for locality reasons, and to free them
|
|
without looping over the vec. */
|
|
|
|
static vec<vn_ssa_aux_t> vn_ssa_aux_table;
|
|
static struct obstack vn_ssa_aux_obstack;
|
|
|
|
/* Return whether there is value numbering information for a given SSA name. */
|
|
|
|
bool
|
|
has_VN_INFO (tree name)
|
|
{
|
|
if (SSA_NAME_VERSION (name) < vn_ssa_aux_table.length ())
|
|
return vn_ssa_aux_table[SSA_NAME_VERSION (name)] != NULL;
|
|
return false;
|
|
}
|
|
|
|
/* Return the value numbering information for a given SSA name. */
|
|
|
|
vn_ssa_aux_t
|
|
VN_INFO (tree name)
|
|
{
|
|
vn_ssa_aux_t res = vn_ssa_aux_table[SSA_NAME_VERSION (name)];
|
|
gcc_checking_assert (res);
|
|
return res;
|
|
}
|
|
|
|
/* Set the value numbering info for a given SSA name to a given
|
|
value. */
|
|
|
|
static inline void
|
|
VN_INFO_SET (tree name, vn_ssa_aux_t value)
|
|
{
|
|
vn_ssa_aux_table[SSA_NAME_VERSION (name)] = value;
|
|
}
|
|
|
|
/* Initialize the value numbering info for a given SSA name.
|
|
This should be called just once for every SSA name. */
|
|
|
|
vn_ssa_aux_t
|
|
VN_INFO_GET (tree name)
|
|
{
|
|
vn_ssa_aux_t newinfo;
|
|
|
|
gcc_assert (SSA_NAME_VERSION (name) >= vn_ssa_aux_table.length ()
|
|
|| vn_ssa_aux_table[SSA_NAME_VERSION (name)] == NULL);
|
|
newinfo = XOBNEW (&vn_ssa_aux_obstack, struct vn_ssa_aux);
|
|
memset (newinfo, 0, sizeof (struct vn_ssa_aux));
|
|
if (SSA_NAME_VERSION (name) >= vn_ssa_aux_table.length ())
|
|
vn_ssa_aux_table.safe_grow_cleared (SSA_NAME_VERSION (name) + 1);
|
|
vn_ssa_aux_table[SSA_NAME_VERSION (name)] = newinfo;
|
|
return newinfo;
|
|
}
|
|
|
|
|
|
/* Return the vn_kind the expression computed by the stmt should be
|
|
associated with. */
|
|
|
|
enum vn_kind
|
|
vn_get_stmt_kind (gimple *stmt)
|
|
{
|
|
switch (gimple_code (stmt))
|
|
{
|
|
case GIMPLE_CALL:
|
|
return VN_REFERENCE;
|
|
case GIMPLE_PHI:
|
|
return VN_PHI;
|
|
case GIMPLE_ASSIGN:
|
|
{
|
|
enum tree_code code = gimple_assign_rhs_code (stmt);
|
|
tree rhs1 = gimple_assign_rhs1 (stmt);
|
|
switch (get_gimple_rhs_class (code))
|
|
{
|
|
case GIMPLE_UNARY_RHS:
|
|
case GIMPLE_BINARY_RHS:
|
|
case GIMPLE_TERNARY_RHS:
|
|
return VN_NARY;
|
|
case GIMPLE_SINGLE_RHS:
|
|
switch (TREE_CODE_CLASS (code))
|
|
{
|
|
case tcc_reference:
|
|
/* VOP-less references can go through unary case. */
|
|
if ((code == REALPART_EXPR
|
|
|| code == IMAGPART_EXPR
|
|
|| code == VIEW_CONVERT_EXPR
|
|
|| code == BIT_FIELD_REF)
|
|
&& TREE_CODE (TREE_OPERAND (rhs1, 0)) == SSA_NAME)
|
|
return VN_NARY;
|
|
|
|
/* Fallthrough. */
|
|
case tcc_declaration:
|
|
return VN_REFERENCE;
|
|
|
|
case tcc_constant:
|
|
return VN_CONSTANT;
|
|
|
|
default:
|
|
if (code == ADDR_EXPR)
|
|
return (is_gimple_min_invariant (rhs1)
|
|
? VN_CONSTANT : VN_REFERENCE);
|
|
else if (code == CONSTRUCTOR)
|
|
return VN_NARY;
|
|
return VN_NONE;
|
|
}
|
|
default:
|
|
return VN_NONE;
|
|
}
|
|
}
|
|
default:
|
|
return VN_NONE;
|
|
}
|
|
}
|
|
|
|
/* Lookup a value id for CONSTANT and return it. If it does not
|
|
exist returns 0. */
|
|
|
|
unsigned int
|
|
get_constant_value_id (tree constant)
|
|
{
|
|
vn_constant_s **slot;
|
|
struct vn_constant_s vc;
|
|
|
|
vc.hashcode = vn_hash_constant_with_type (constant);
|
|
vc.constant = constant;
|
|
slot = constant_to_value_id->find_slot (&vc, NO_INSERT);
|
|
if (slot)
|
|
return (*slot)->value_id;
|
|
return 0;
|
|
}
|
|
|
|
/* Lookup a value id for CONSTANT, and if it does not exist, create a
|
|
new one and return it. If it does exist, return it. */
|
|
|
|
unsigned int
|
|
get_or_alloc_constant_value_id (tree constant)
|
|
{
|
|
vn_constant_s **slot;
|
|
struct vn_constant_s vc;
|
|
vn_constant_t vcp;
|
|
|
|
vc.hashcode = vn_hash_constant_with_type (constant);
|
|
vc.constant = constant;
|
|
slot = constant_to_value_id->find_slot (&vc, INSERT);
|
|
if (*slot)
|
|
return (*slot)->value_id;
|
|
|
|
vcp = XNEW (struct vn_constant_s);
|
|
vcp->hashcode = vc.hashcode;
|
|
vcp->constant = constant;
|
|
vcp->value_id = get_next_value_id ();
|
|
*slot = vcp;
|
|
bitmap_set_bit (constant_value_ids, vcp->value_id);
|
|
return vcp->value_id;
|
|
}
|
|
|
|
/* Return true if V is a value id for a constant. */
|
|
|
|
bool
|
|
value_id_constant_p (unsigned int v)
|
|
{
|
|
return bitmap_bit_p (constant_value_ids, v);
|
|
}
|
|
|
|
/* Compute the hash for a reference operand VRO1. */
|
|
|
|
static void
|
|
vn_reference_op_compute_hash (const vn_reference_op_t vro1, inchash::hash &hstate)
|
|
{
|
|
hstate.add_int (vro1->opcode);
|
|
if (vro1->op0)
|
|
inchash::add_expr (vro1->op0, hstate);
|
|
if (vro1->op1)
|
|
inchash::add_expr (vro1->op1, hstate);
|
|
if (vro1->op2)
|
|
inchash::add_expr (vro1->op2, hstate);
|
|
}
|
|
|
|
/* Compute a hash for the reference operation VR1 and return it. */
|
|
|
|
static hashval_t
|
|
vn_reference_compute_hash (const vn_reference_t vr1)
|
|
{
|
|
inchash::hash hstate;
|
|
hashval_t result;
|
|
int i;
|
|
vn_reference_op_t vro;
|
|
poly_int64 off = -1;
|
|
bool deref = false;
|
|
|
|
FOR_EACH_VEC_ELT (vr1->operands, i, vro)
|
|
{
|
|
if (vro->opcode == MEM_REF)
|
|
deref = true;
|
|
else if (vro->opcode != ADDR_EXPR)
|
|
deref = false;
|
|
if (maybe_ne (vro->off, -1))
|
|
{
|
|
if (known_eq (off, -1))
|
|
off = 0;
|
|
off += vro->off;
|
|
}
|
|
else
|
|
{
|
|
if (maybe_ne (off, -1)
|
|
&& maybe_ne (off, 0))
|
|
hstate.add_poly_int (off);
|
|
off = -1;
|
|
if (deref
|
|
&& vro->opcode == ADDR_EXPR)
|
|
{
|
|
if (vro->op0)
|
|
{
|
|
tree op = TREE_OPERAND (vro->op0, 0);
|
|
hstate.add_int (TREE_CODE (op));
|
|
inchash::add_expr (op, hstate);
|
|
}
|
|
}
|
|
else
|
|
vn_reference_op_compute_hash (vro, hstate);
|
|
}
|
|
}
|
|
result = hstate.end ();
|
|
/* ??? We would ICE later if we hash instead of adding that in. */
|
|
if (vr1->vuse)
|
|
result += SSA_NAME_VERSION (vr1->vuse);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Return true if reference operations VR1 and VR2 are equivalent. This
|
|
means they have the same set of operands and vuses. */
|
|
|
|
bool
|
|
vn_reference_eq (const_vn_reference_t const vr1, const_vn_reference_t const vr2)
|
|
{
|
|
unsigned i, j;
|
|
|
|
/* Early out if this is not a hash collision. */
|
|
if (vr1->hashcode != vr2->hashcode)
|
|
return false;
|
|
|
|
/* The VOP needs to be the same. */
|
|
if (vr1->vuse != vr2->vuse)
|
|
return false;
|
|
|
|
/* If the operands are the same we are done. */
|
|
if (vr1->operands == vr2->operands)
|
|
return true;
|
|
|
|
if (!expressions_equal_p (TYPE_SIZE (vr1->type), TYPE_SIZE (vr2->type)))
|
|
return false;
|
|
|
|
if (INTEGRAL_TYPE_P (vr1->type)
|
|
&& INTEGRAL_TYPE_P (vr2->type))
|
|
{
|
|
if (TYPE_PRECISION (vr1->type) != TYPE_PRECISION (vr2->type))
|
|
return false;
|
|
}
|
|
else if (INTEGRAL_TYPE_P (vr1->type)
|
|
&& (TYPE_PRECISION (vr1->type)
|
|
!= TREE_INT_CST_LOW (TYPE_SIZE (vr1->type))))
|
|
return false;
|
|
else if (INTEGRAL_TYPE_P (vr2->type)
|
|
&& (TYPE_PRECISION (vr2->type)
|
|
!= TREE_INT_CST_LOW (TYPE_SIZE (vr2->type))))
|
|
return false;
|
|
|
|
i = 0;
|
|
j = 0;
|
|
do
|
|
{
|
|
poly_int64 off1 = 0, off2 = 0;
|
|
vn_reference_op_t vro1, vro2;
|
|
vn_reference_op_s tem1, tem2;
|
|
bool deref1 = false, deref2 = false;
|
|
for (; vr1->operands.iterate (i, &vro1); i++)
|
|
{
|
|
if (vro1->opcode == MEM_REF)
|
|
deref1 = true;
|
|
/* Do not look through a storage order barrier. */
|
|
else if (vro1->opcode == VIEW_CONVERT_EXPR && vro1->reverse)
|
|
return false;
|
|
if (known_eq (vro1->off, -1))
|
|
break;
|
|
off1 += vro1->off;
|
|
}
|
|
for (; vr2->operands.iterate (j, &vro2); j++)
|
|
{
|
|
if (vro2->opcode == MEM_REF)
|
|
deref2 = true;
|
|
/* Do not look through a storage order barrier. */
|
|
else if (vro2->opcode == VIEW_CONVERT_EXPR && vro2->reverse)
|
|
return false;
|
|
if (known_eq (vro2->off, -1))
|
|
break;
|
|
off2 += vro2->off;
|
|
}
|
|
if (maybe_ne (off1, off2))
|
|
return false;
|
|
if (deref1 && vro1->opcode == ADDR_EXPR)
|
|
{
|
|
memset (&tem1, 0, sizeof (tem1));
|
|
tem1.op0 = TREE_OPERAND (vro1->op0, 0);
|
|
tem1.type = TREE_TYPE (tem1.op0);
|
|
tem1.opcode = TREE_CODE (tem1.op0);
|
|
vro1 = &tem1;
|
|
deref1 = false;
|
|
}
|
|
if (deref2 && vro2->opcode == ADDR_EXPR)
|
|
{
|
|
memset (&tem2, 0, sizeof (tem2));
|
|
tem2.op0 = TREE_OPERAND (vro2->op0, 0);
|
|
tem2.type = TREE_TYPE (tem2.op0);
|
|
tem2.opcode = TREE_CODE (tem2.op0);
|
|
vro2 = &tem2;
|
|
deref2 = false;
|
|
}
|
|
if (deref1 != deref2)
|
|
return false;
|
|
if (!vn_reference_op_eq (vro1, vro2))
|
|
return false;
|
|
++j;
|
|
++i;
|
|
}
|
|
while (vr1->operands.length () != i
|
|
|| vr2->operands.length () != j);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Copy the operations present in load/store REF into RESULT, a vector of
|
|
vn_reference_op_s's. */
|
|
|
|
static void
|
|
copy_reference_ops_from_ref (tree ref, vec<vn_reference_op_s> *result)
|
|
{
|
|
if (TREE_CODE (ref) == TARGET_MEM_REF)
|
|
{
|
|
vn_reference_op_s temp;
|
|
|
|
result->reserve (3);
|
|
|
|
memset (&temp, 0, sizeof (temp));
|
|
temp.type = TREE_TYPE (ref);
|
|
temp.opcode = TREE_CODE (ref);
|
|
temp.op0 = TMR_INDEX (ref);
|
|
temp.op1 = TMR_STEP (ref);
|
|
temp.op2 = TMR_OFFSET (ref);
|
|
temp.off = -1;
|
|
temp.clique = MR_DEPENDENCE_CLIQUE (ref);
|
|
temp.base = MR_DEPENDENCE_BASE (ref);
|
|
result->quick_push (temp);
|
|
|
|
memset (&temp, 0, sizeof (temp));
|
|
temp.type = NULL_TREE;
|
|
temp.opcode = ERROR_MARK;
|
|
temp.op0 = TMR_INDEX2 (ref);
|
|
temp.off = -1;
|
|
result->quick_push (temp);
|
|
|
|
memset (&temp, 0, sizeof (temp));
|
|
temp.type = NULL_TREE;
|
|
temp.opcode = TREE_CODE (TMR_BASE (ref));
|
|
temp.op0 = TMR_BASE (ref);
|
|
temp.off = -1;
|
|
result->quick_push (temp);
|
|
return;
|
|
}
|
|
|
|
/* For non-calls, store the information that makes up the address. */
|
|
tree orig = ref;
|
|
while (ref)
|
|
{
|
|
vn_reference_op_s temp;
|
|
|
|
memset (&temp, 0, sizeof (temp));
|
|
temp.type = TREE_TYPE (ref);
|
|
temp.opcode = TREE_CODE (ref);
|
|
temp.off = -1;
|
|
|
|
switch (temp.opcode)
|
|
{
|
|
case MODIFY_EXPR:
|
|
temp.op0 = TREE_OPERAND (ref, 1);
|
|
break;
|
|
case WITH_SIZE_EXPR:
|
|
temp.op0 = TREE_OPERAND (ref, 1);
|
|
temp.off = 0;
|
|
break;
|
|
case MEM_REF:
|
|
/* The base address gets its own vn_reference_op_s structure. */
|
|
temp.op0 = TREE_OPERAND (ref, 1);
|
|
if (!mem_ref_offset (ref).to_shwi (&temp.off))
|
|
temp.off = -1;
|
|
temp.clique = MR_DEPENDENCE_CLIQUE (ref);
|
|
temp.base = MR_DEPENDENCE_BASE (ref);
|
|
temp.reverse = REF_REVERSE_STORAGE_ORDER (ref);
|
|
break;
|
|
case BIT_FIELD_REF:
|
|
/* Record bits, position and storage order. */
|
|
temp.op0 = TREE_OPERAND (ref, 1);
|
|
temp.op1 = TREE_OPERAND (ref, 2);
|
|
if (!multiple_p (bit_field_offset (ref), BITS_PER_UNIT, &temp.off))
|
|
temp.off = -1;
|
|
temp.reverse = REF_REVERSE_STORAGE_ORDER (ref);
|
|
break;
|
|
case COMPONENT_REF:
|
|
/* The field decl is enough to unambiguously specify the field,
|
|
a matching type is not necessary and a mismatching type
|
|
is always a spurious difference. */
|
|
temp.type = NULL_TREE;
|
|
temp.op0 = TREE_OPERAND (ref, 1);
|
|
temp.op1 = TREE_OPERAND (ref, 2);
|
|
{
|
|
tree this_offset = component_ref_field_offset (ref);
|
|
if (this_offset
|
|
&& poly_int_tree_p (this_offset))
|
|
{
|
|
tree bit_offset = DECL_FIELD_BIT_OFFSET (TREE_OPERAND (ref, 1));
|
|
if (TREE_INT_CST_LOW (bit_offset) % BITS_PER_UNIT == 0)
|
|
{
|
|
poly_offset_int off
|
|
= (wi::to_poly_offset (this_offset)
|
|
+ (wi::to_offset (bit_offset) >> LOG2_BITS_PER_UNIT));
|
|
/* Probibit value-numbering zero offset components
|
|
of addresses the same before the pass folding
|
|
__builtin_object_size had a chance to run
|
|
(checking cfun->after_inlining does the
|
|
trick here). */
|
|
if (TREE_CODE (orig) != ADDR_EXPR
|
|
|| maybe_ne (off, 0)
|
|
|| cfun->after_inlining)
|
|
off.to_shwi (&temp.off);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ARRAY_RANGE_REF:
|
|
case ARRAY_REF:
|
|
{
|
|
tree eltype = TREE_TYPE (TREE_TYPE (TREE_OPERAND (ref, 0)));
|
|
/* Record index as operand. */
|
|
temp.op0 = TREE_OPERAND (ref, 1);
|
|
/* Always record lower bounds and element size. */
|
|
temp.op1 = array_ref_low_bound (ref);
|
|
/* But record element size in units of the type alignment. */
|
|
temp.op2 = TREE_OPERAND (ref, 3);
|
|
temp.align = eltype->type_common.align;
|
|
if (! temp.op2)
|
|
temp.op2 = size_binop (EXACT_DIV_EXPR, TYPE_SIZE_UNIT (eltype),
|
|
size_int (TYPE_ALIGN_UNIT (eltype)));
|
|
if (poly_int_tree_p (temp.op0)
|
|
&& poly_int_tree_p (temp.op1)
|
|
&& TREE_CODE (temp.op2) == INTEGER_CST)
|
|
{
|
|
poly_offset_int off = ((wi::to_poly_offset (temp.op0)
|
|
- wi::to_poly_offset (temp.op1))
|
|
* wi::to_offset (temp.op2)
|
|
* vn_ref_op_align_unit (&temp));
|
|
off.to_shwi (&temp.off);
|
|
}
|
|
}
|
|
break;
|
|
case VAR_DECL:
|
|
if (DECL_HARD_REGISTER (ref))
|
|
{
|
|
temp.op0 = ref;
|
|
break;
|
|
}
|
|
/* Fallthru. */
|
|
case PARM_DECL:
|
|
case CONST_DECL:
|
|
case RESULT_DECL:
|
|
/* Canonicalize decls to MEM[&decl] which is what we end up with
|
|
when valueizing MEM[ptr] with ptr = &decl. */
|
|
temp.opcode = MEM_REF;
|
|
temp.op0 = build_int_cst (build_pointer_type (TREE_TYPE (ref)), 0);
|
|
temp.off = 0;
|
|
result->safe_push (temp);
|
|
temp.opcode = ADDR_EXPR;
|
|
temp.op0 = build1 (ADDR_EXPR, TREE_TYPE (temp.op0), ref);
|
|
temp.type = TREE_TYPE (temp.op0);
|
|
temp.off = -1;
|
|
break;
|
|
case STRING_CST:
|
|
case INTEGER_CST:
|
|
case COMPLEX_CST:
|
|
case VECTOR_CST:
|
|
case REAL_CST:
|
|
case FIXED_CST:
|
|
case CONSTRUCTOR:
|
|
case SSA_NAME:
|
|
temp.op0 = ref;
|
|
break;
|
|
case ADDR_EXPR:
|
|
if (is_gimple_min_invariant (ref))
|
|
{
|
|
temp.op0 = ref;
|
|
break;
|
|
}
|
|
break;
|
|
/* These are only interesting for their operands, their
|
|
existence, and their type. They will never be the last
|
|
ref in the chain of references (IE they require an
|
|
operand), so we don't have to put anything
|
|
for op* as it will be handled by the iteration */
|
|
case REALPART_EXPR:
|
|
temp.off = 0;
|
|
break;
|
|
case VIEW_CONVERT_EXPR:
|
|
temp.off = 0;
|
|
temp.reverse = storage_order_barrier_p (ref);
|
|
break;
|
|
case IMAGPART_EXPR:
|
|
/* This is only interesting for its constant offset. */
|
|
temp.off = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (TREE_TYPE (ref)));
|
|
break;
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
result->safe_push (temp);
|
|
|
|
if (REFERENCE_CLASS_P (ref)
|
|
|| TREE_CODE (ref) == MODIFY_EXPR
|
|
|| TREE_CODE (ref) == WITH_SIZE_EXPR
|
|
|| (TREE_CODE (ref) == ADDR_EXPR
|
|
&& !is_gimple_min_invariant (ref)))
|
|
ref = TREE_OPERAND (ref, 0);
|
|
else
|
|
ref = NULL_TREE;
|
|
}
|
|
}
|
|
|
|
/* Build a alias-oracle reference abstraction in *REF from the vn_reference
|
|
operands in *OPS, the reference alias set SET and the reference type TYPE.
|
|
Return true if something useful was produced. */
|
|
|
|
bool
|
|
ao_ref_init_from_vn_reference (ao_ref *ref,
|
|
alias_set_type set, tree type,
|
|
vec<vn_reference_op_s> ops)
|
|
{
|
|
vn_reference_op_t op;
|
|
unsigned i;
|
|
tree base = NULL_TREE;
|
|
tree *op0_p = &base;
|
|
poly_offset_int offset = 0;
|
|
poly_offset_int max_size;
|
|
poly_offset_int size = -1;
|
|
tree size_tree = NULL_TREE;
|
|
alias_set_type base_alias_set = -1;
|
|
|
|
/* First get the final access size from just the outermost expression. */
|
|
op = &ops[0];
|
|
if (op->opcode == COMPONENT_REF)
|
|
size_tree = DECL_SIZE (op->op0);
|
|
else if (op->opcode == BIT_FIELD_REF)
|
|
size_tree = op->op0;
|
|
else
|
|
{
|
|
machine_mode mode = TYPE_MODE (type);
|
|
if (mode == BLKmode)
|
|
size_tree = TYPE_SIZE (type);
|
|
else
|
|
size = GET_MODE_BITSIZE (mode);
|
|
}
|
|
if (size_tree != NULL_TREE
|
|
&& poly_int_tree_p (size_tree))
|
|
size = wi::to_poly_offset (size_tree);
|
|
|
|
/* Initially, maxsize is the same as the accessed element size.
|
|
In the following it will only grow (or become -1). */
|
|
max_size = size;
|
|
|
|
/* Compute cumulative bit-offset for nested component-refs and array-refs,
|
|
and find the ultimate containing object. */
|
|
FOR_EACH_VEC_ELT (ops, i, op)
|
|
{
|
|
switch (op->opcode)
|
|
{
|
|
/* These may be in the reference ops, but we cannot do anything
|
|
sensible with them here. */
|
|
case ADDR_EXPR:
|
|
/* Apart from ADDR_EXPR arguments to MEM_REF. */
|
|
if (base != NULL_TREE
|
|
&& TREE_CODE (base) == MEM_REF
|
|
&& op->op0
|
|
&& DECL_P (TREE_OPERAND (op->op0, 0)))
|
|
{
|
|
vn_reference_op_t pop = &ops[i-1];
|
|
base = TREE_OPERAND (op->op0, 0);
|
|
if (known_eq (pop->off, -1))
|
|
{
|
|
max_size = -1;
|
|
offset = 0;
|
|
}
|
|
else
|
|
offset += pop->off * BITS_PER_UNIT;
|
|
op0_p = NULL;
|
|
break;
|
|
}
|
|
/* Fallthru. */
|
|
case CALL_EXPR:
|
|
return false;
|
|
|
|
/* Record the base objects. */
|
|
case MEM_REF:
|
|
base_alias_set = get_deref_alias_set (op->op0);
|
|
*op0_p = build2 (MEM_REF, op->type,
|
|
NULL_TREE, op->op0);
|
|
MR_DEPENDENCE_CLIQUE (*op0_p) = op->clique;
|
|
MR_DEPENDENCE_BASE (*op0_p) = op->base;
|
|
op0_p = &TREE_OPERAND (*op0_p, 0);
|
|
break;
|
|
|
|
case VAR_DECL:
|
|
case PARM_DECL:
|
|
case RESULT_DECL:
|
|
case SSA_NAME:
|
|
*op0_p = op->op0;
|
|
op0_p = NULL;
|
|
break;
|
|
|
|
/* And now the usual component-reference style ops. */
|
|
case BIT_FIELD_REF:
|
|
offset += wi::to_offset (op->op1);
|
|
break;
|
|
|
|
case COMPONENT_REF:
|
|
{
|
|
tree field = op->op0;
|
|
/* We do not have a complete COMPONENT_REF tree here so we
|
|
cannot use component_ref_field_offset. Do the interesting
|
|
parts manually. */
|
|
tree this_offset = DECL_FIELD_OFFSET (field);
|
|
|
|
if (op->op1 || !poly_int_tree_p (this_offset))
|
|
max_size = -1;
|
|
else
|
|
{
|
|
poly_offset_int woffset = (wi::to_poly_offset (this_offset)
|
|
<< LOG2_BITS_PER_UNIT);
|
|
woffset += wi::to_offset (DECL_FIELD_BIT_OFFSET (field));
|
|
offset += woffset;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ARRAY_RANGE_REF:
|
|
case ARRAY_REF:
|
|
/* We recorded the lower bound and the element size. */
|
|
if (!poly_int_tree_p (op->op0)
|
|
|| !poly_int_tree_p (op->op1)
|
|
|| TREE_CODE (op->op2) != INTEGER_CST)
|
|
max_size = -1;
|
|
else
|
|
{
|
|
poly_offset_int woffset
|
|
= wi::sext (wi::to_poly_offset (op->op0)
|
|
- wi::to_poly_offset (op->op1),
|
|
TYPE_PRECISION (TREE_TYPE (op->op0)));
|
|
woffset *= wi::to_offset (op->op2) * vn_ref_op_align_unit (op);
|
|
woffset <<= LOG2_BITS_PER_UNIT;
|
|
offset += woffset;
|
|
}
|
|
break;
|
|
|
|
case REALPART_EXPR:
|
|
break;
|
|
|
|
case IMAGPART_EXPR:
|
|
offset += size;
|
|
break;
|
|
|
|
case VIEW_CONVERT_EXPR:
|
|
break;
|
|
|
|
case STRING_CST:
|
|
case INTEGER_CST:
|
|
case COMPLEX_CST:
|
|
case VECTOR_CST:
|
|
case REAL_CST:
|
|
case CONSTRUCTOR:
|
|
case CONST_DECL:
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (base == NULL_TREE)
|
|
return false;
|
|
|
|
ref->ref = NULL_TREE;
|
|
ref->base = base;
|
|
ref->ref_alias_set = set;
|
|
if (base_alias_set != -1)
|
|
ref->base_alias_set = base_alias_set;
|
|
else
|
|
ref->base_alias_set = get_alias_set (base);
|
|
/* We discount volatiles from value-numbering elsewhere. */
|
|
ref->volatile_p = false;
|
|
|
|
if (!size.to_shwi (&ref->size) || maybe_lt (ref->size, 0))
|
|
{
|
|
ref->offset = 0;
|
|
ref->size = -1;
|
|
ref->max_size = -1;
|
|
return true;
|
|
}
|
|
|
|
if (!offset.to_shwi (&ref->offset))
|
|
{
|
|
ref->offset = 0;
|
|
ref->max_size = -1;
|
|
return true;
|
|
}
|
|
|
|
if (!max_size.to_shwi (&ref->max_size) || maybe_lt (ref->max_size, 0))
|
|
ref->max_size = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Copy the operations present in load/store/call REF into RESULT, a vector of
|
|
vn_reference_op_s's. */
|
|
|
|
static void
|
|
copy_reference_ops_from_call (gcall *call,
|
|
vec<vn_reference_op_s> *result)
|
|
{
|
|
vn_reference_op_s temp;
|
|
unsigned i;
|
|
tree lhs = gimple_call_lhs (call);
|
|
int lr;
|
|
|
|
/* If 2 calls have a different non-ssa lhs, vdef value numbers should be
|
|
different. By adding the lhs here in the vector, we ensure that the
|
|
hashcode is different, guaranteeing a different value number. */
|
|
if (lhs && TREE_CODE (lhs) != SSA_NAME)
|
|
{
|
|
memset (&temp, 0, sizeof (temp));
|
|
temp.opcode = MODIFY_EXPR;
|
|
temp.type = TREE_TYPE (lhs);
|
|
temp.op0 = lhs;
|
|
temp.off = -1;
|
|
result->safe_push (temp);
|
|
}
|
|
|
|
/* Copy the type, opcode, function, static chain and EH region, if any. */
|
|
memset (&temp, 0, sizeof (temp));
|
|
temp.type = gimple_call_return_type (call);
|
|
temp.opcode = CALL_EXPR;
|
|
temp.op0 = gimple_call_fn (call);
|
|
temp.op1 = gimple_call_chain (call);
|
|
if (stmt_could_throw_p (call) && (lr = lookup_stmt_eh_lp (call)) > 0)
|
|
temp.op2 = size_int (lr);
|
|
temp.off = -1;
|
|
if (gimple_call_with_bounds_p (call))
|
|
temp.with_bounds = 1;
|
|
result->safe_push (temp);
|
|
|
|
/* Copy the call arguments. As they can be references as well,
|
|
just chain them together. */
|
|
for (i = 0; i < gimple_call_num_args (call); ++i)
|
|
{
|
|
tree callarg = gimple_call_arg (call, i);
|
|
copy_reference_ops_from_ref (callarg, result);
|
|
}
|
|
}
|
|
|
|
/* Fold *& at position *I_P in a vn_reference_op_s vector *OPS. Updates
|
|
*I_P to point to the last element of the replacement. */
|
|
static bool
|
|
vn_reference_fold_indirect (vec<vn_reference_op_s> *ops,
|
|
unsigned int *i_p)
|
|
{
|
|
unsigned int i = *i_p;
|
|
vn_reference_op_t op = &(*ops)[i];
|
|
vn_reference_op_t mem_op = &(*ops)[i - 1];
|
|
tree addr_base;
|
|
poly_int64 addr_offset = 0;
|
|
|
|
/* The only thing we have to do is from &OBJ.foo.bar add the offset
|
|
from .foo.bar to the preceding MEM_REF offset and replace the
|
|
address with &OBJ. */
|
|
addr_base = get_addr_base_and_unit_offset (TREE_OPERAND (op->op0, 0),
|
|
&addr_offset);
|
|
gcc_checking_assert (addr_base && TREE_CODE (addr_base) != MEM_REF);
|
|
if (addr_base != TREE_OPERAND (op->op0, 0))
|
|
{
|
|
poly_offset_int off
|
|
= (poly_offset_int::from (wi::to_poly_wide (mem_op->op0),
|
|
SIGNED)
|
|
+ addr_offset);
|
|
mem_op->op0 = wide_int_to_tree (TREE_TYPE (mem_op->op0), off);
|
|
op->op0 = build_fold_addr_expr (addr_base);
|
|
if (tree_fits_shwi_p (mem_op->op0))
|
|
mem_op->off = tree_to_shwi (mem_op->op0);
|
|
else
|
|
mem_op->off = -1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Fold *& at position *I_P in a vn_reference_op_s vector *OPS. Updates
|
|
*I_P to point to the last element of the replacement. */
|
|
static bool
|
|
vn_reference_maybe_forwprop_address (vec<vn_reference_op_s> *ops,
|
|
unsigned int *i_p)
|
|
{
|
|
unsigned int i = *i_p;
|
|
vn_reference_op_t op = &(*ops)[i];
|
|
vn_reference_op_t mem_op = &(*ops)[i - 1];
|
|
gimple *def_stmt;
|
|
enum tree_code code;
|
|
poly_offset_int off;
|
|
|
|
def_stmt = SSA_NAME_DEF_STMT (op->op0);
|
|
if (!is_gimple_assign (def_stmt))
|
|
return false;
|
|
|
|
code = gimple_assign_rhs_code (def_stmt);
|
|
if (code != ADDR_EXPR
|
|
&& code != POINTER_PLUS_EXPR)
|
|
return false;
|
|
|
|
off = poly_offset_int::from (wi::to_poly_wide (mem_op->op0), SIGNED);
|
|
|
|
/* The only thing we have to do is from &OBJ.foo.bar add the offset
|
|
from .foo.bar to the preceding MEM_REF offset and replace the
|
|
address with &OBJ. */
|
|
if (code == ADDR_EXPR)
|
|
{
|
|
tree addr, addr_base;
|
|
poly_int64 addr_offset;
|
|
|
|
addr = gimple_assign_rhs1 (def_stmt);
|
|
addr_base = get_addr_base_and_unit_offset (TREE_OPERAND (addr, 0),
|
|
&addr_offset);
|
|
/* If that didn't work because the address isn't invariant propagate
|
|
the reference tree from the address operation in case the current
|
|
dereference isn't offsetted. */
|
|
if (!addr_base
|
|
&& *i_p == ops->length () - 1
|
|
&& known_eq (off, 0)
|
|
/* This makes us disable this transform for PRE where the
|
|
reference ops might be also used for code insertion which
|
|
is invalid. */
|
|
&& default_vn_walk_kind == VN_WALKREWRITE)
|
|
{
|
|
auto_vec<vn_reference_op_s, 32> tem;
|
|
copy_reference_ops_from_ref (TREE_OPERAND (addr, 0), &tem);
|
|
/* Make sure to preserve TBAA info. The only objects not
|
|
wrapped in MEM_REFs that can have their address taken are
|
|
STRING_CSTs. */
|
|
if (tem.length () >= 2
|
|
&& tem[tem.length () - 2].opcode == MEM_REF)
|
|
{
|
|
vn_reference_op_t new_mem_op = &tem[tem.length () - 2];
|
|
new_mem_op->op0
|
|
= wide_int_to_tree (TREE_TYPE (mem_op->op0),
|
|
wi::to_poly_wide (new_mem_op->op0));
|
|
}
|
|
else
|
|
gcc_assert (tem.last ().opcode == STRING_CST);
|
|
ops->pop ();
|
|
ops->pop ();
|
|
ops->safe_splice (tem);
|
|
--*i_p;
|
|
return true;
|
|
}
|
|
if (!addr_base
|
|
|| TREE_CODE (addr_base) != MEM_REF
|
|
|| (TREE_CODE (TREE_OPERAND (addr_base, 0)) == SSA_NAME
|
|
&& SSA_NAME_OCCURS_IN_ABNORMAL_PHI (TREE_OPERAND (addr_base, 0))))
|
|
return false;
|
|
|
|
off += addr_offset;
|
|
off += mem_ref_offset (addr_base);
|
|
op->op0 = TREE_OPERAND (addr_base, 0);
|
|
}
|
|
else
|
|
{
|
|
tree ptr, ptroff;
|
|
ptr = gimple_assign_rhs1 (def_stmt);
|
|
ptroff = gimple_assign_rhs2 (def_stmt);
|
|
if (TREE_CODE (ptr) != SSA_NAME
|
|
|| SSA_NAME_OCCURS_IN_ABNORMAL_PHI (ptr)
|
|
|| TREE_CODE (ptroff) != INTEGER_CST)
|
|
return false;
|
|
|
|
off += wi::to_offset (ptroff);
|
|
op->op0 = ptr;
|
|
}
|
|
|
|
mem_op->op0 = wide_int_to_tree (TREE_TYPE (mem_op->op0), off);
|
|
if (tree_fits_shwi_p (mem_op->op0))
|
|
mem_op->off = tree_to_shwi (mem_op->op0);
|
|
else
|
|
mem_op->off = -1;
|
|
if (TREE_CODE (op->op0) == SSA_NAME)
|
|
op->op0 = SSA_VAL (op->op0);
|
|
if (TREE_CODE (op->op0) != SSA_NAME)
|
|
op->opcode = TREE_CODE (op->op0);
|
|
|
|
/* And recurse. */
|
|
if (TREE_CODE (op->op0) == SSA_NAME)
|
|
vn_reference_maybe_forwprop_address (ops, i_p);
|
|
else if (TREE_CODE (op->op0) == ADDR_EXPR)
|
|
vn_reference_fold_indirect (ops, i_p);
|
|
return true;
|
|
}
|
|
|
|
/* Optimize the reference REF to a constant if possible or return
|
|
NULL_TREE if not. */
|
|
|
|
tree
|
|
fully_constant_vn_reference_p (vn_reference_t ref)
|
|
{
|
|
vec<vn_reference_op_s> operands = ref->operands;
|
|
vn_reference_op_t op;
|
|
|
|
/* Try to simplify the translated expression if it is
|
|
a call to a builtin function with at most two arguments. */
|
|
op = &operands[0];
|
|
if (op->opcode == CALL_EXPR
|
|
&& TREE_CODE (op->op0) == ADDR_EXPR
|
|
&& TREE_CODE (TREE_OPERAND (op->op0, 0)) == FUNCTION_DECL
|
|
&& DECL_BUILT_IN (TREE_OPERAND (op->op0, 0))
|
|
&& operands.length () >= 2
|
|
&& operands.length () <= 3)
|
|
{
|
|
vn_reference_op_t arg0, arg1 = NULL;
|
|
bool anyconst = false;
|
|
arg0 = &operands[1];
|
|
if (operands.length () > 2)
|
|
arg1 = &operands[2];
|
|
if (TREE_CODE_CLASS (arg0->opcode) == tcc_constant
|
|
|| (arg0->opcode == ADDR_EXPR
|
|
&& is_gimple_min_invariant (arg0->op0)))
|
|
anyconst = true;
|
|
if (arg1
|
|
&& (TREE_CODE_CLASS (arg1->opcode) == tcc_constant
|
|
|| (arg1->opcode == ADDR_EXPR
|
|
&& is_gimple_min_invariant (arg1->op0))))
|
|
anyconst = true;
|
|
if (anyconst)
|
|
{
|
|
tree folded = build_call_expr (TREE_OPERAND (op->op0, 0),
|
|
arg1 ? 2 : 1,
|
|
arg0->op0,
|
|
arg1 ? arg1->op0 : NULL);
|
|
if (folded
|
|
&& TREE_CODE (folded) == NOP_EXPR)
|
|
folded = TREE_OPERAND (folded, 0);
|
|
if (folded
|
|
&& is_gimple_min_invariant (folded))
|
|
return folded;
|
|
}
|
|
}
|
|
|
|
/* Simplify reads from constants or constant initializers. */
|
|
else if (BITS_PER_UNIT == 8
|
|
&& is_gimple_reg_type (ref->type)
|
|
&& (!INTEGRAL_TYPE_P (ref->type)
|
|
|| TYPE_PRECISION (ref->type) % BITS_PER_UNIT == 0))
|
|
{
|
|
poly_int64 off = 0;
|
|
HOST_WIDE_INT size;
|
|
if (INTEGRAL_TYPE_P (ref->type))
|
|
size = TYPE_PRECISION (ref->type);
|
|
else
|
|
size = tree_to_shwi (TYPE_SIZE (ref->type));
|
|
if (size % BITS_PER_UNIT != 0
|
|
|| size > MAX_BITSIZE_MODE_ANY_MODE)
|
|
return NULL_TREE;
|
|
size /= BITS_PER_UNIT;
|
|
unsigned i;
|
|
for (i = 0; i < operands.length (); ++i)
|
|
{
|
|
if (TREE_CODE_CLASS (operands[i].opcode) == tcc_constant)
|
|
{
|
|
++i;
|
|
break;
|
|
}
|
|
if (known_eq (operands[i].off, -1))
|
|
return NULL_TREE;
|
|
off += operands[i].off;
|
|
if (operands[i].opcode == MEM_REF)
|
|
{
|
|
++i;
|
|
break;
|
|
}
|
|
}
|
|
vn_reference_op_t base = &operands[--i];
|
|
tree ctor = error_mark_node;
|
|
tree decl = NULL_TREE;
|
|
if (TREE_CODE_CLASS (base->opcode) == tcc_constant)
|
|
ctor = base->op0;
|
|
else if (base->opcode == MEM_REF
|
|
&& base[1].opcode == ADDR_EXPR
|
|
&& (TREE_CODE (TREE_OPERAND (base[1].op0, 0)) == VAR_DECL
|
|
|| TREE_CODE (TREE_OPERAND (base[1].op0, 0)) == CONST_DECL
|
|
|| TREE_CODE (TREE_OPERAND (base[1].op0, 0)) == STRING_CST))
|
|
{
|
|
decl = TREE_OPERAND (base[1].op0, 0);
|
|
if (TREE_CODE (decl) == STRING_CST)
|
|
ctor = decl;
|
|
else
|
|
ctor = ctor_for_folding (decl);
|
|
}
|
|
if (ctor == NULL_TREE)
|
|
return build_zero_cst (ref->type);
|
|
else if (ctor != error_mark_node)
|
|
{
|
|
HOST_WIDE_INT const_off;
|
|
if (decl)
|
|
{
|
|
tree res = fold_ctor_reference (ref->type, ctor,
|
|
off * BITS_PER_UNIT,
|
|
size * BITS_PER_UNIT, decl);
|
|
if (res)
|
|
{
|
|
STRIP_USELESS_TYPE_CONVERSION (res);
|
|
if (is_gimple_min_invariant (res))
|
|
return res;
|
|
}
|
|
}
|
|
else if (off.is_constant (&const_off))
|
|
{
|
|
unsigned char buf[MAX_BITSIZE_MODE_ANY_MODE / BITS_PER_UNIT];
|
|
int len = native_encode_expr (ctor, buf, size, const_off);
|
|
if (len > 0)
|
|
return native_interpret_expr (ref->type, buf, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Return true if OPS contain a storage order barrier. */
|
|
|
|
static bool
|
|
contains_storage_order_barrier_p (vec<vn_reference_op_s> ops)
|
|
{
|
|
vn_reference_op_t op;
|
|
unsigned i;
|
|
|
|
FOR_EACH_VEC_ELT (ops, i, op)
|
|
if (op->opcode == VIEW_CONVERT_EXPR && op->reverse)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Transform any SSA_NAME's in a vector of vn_reference_op_s
|
|
structures into their value numbers. This is done in-place, and
|
|
the vector passed in is returned. *VALUEIZED_ANYTHING will specify
|
|
whether any operands were valueized. */
|
|
|
|
static vec<vn_reference_op_s>
|
|
valueize_refs_1 (vec<vn_reference_op_s> orig, bool *valueized_anything)
|
|
{
|
|
vn_reference_op_t vro;
|
|
unsigned int i;
|
|
|
|
*valueized_anything = false;
|
|
|
|
FOR_EACH_VEC_ELT (orig, i, vro)
|
|
{
|
|
if (vro->opcode == SSA_NAME
|
|
|| (vro->op0 && TREE_CODE (vro->op0) == SSA_NAME))
|
|
{
|
|
tree tem = SSA_VAL (vro->op0);
|
|
if (tem != vro->op0)
|
|
{
|
|
*valueized_anything = true;
|
|
vro->op0 = tem;
|
|
}
|
|
/* If it transforms from an SSA_NAME to a constant, update
|
|
the opcode. */
|
|
if (TREE_CODE (vro->op0) != SSA_NAME && vro->opcode == SSA_NAME)
|
|
vro->opcode = TREE_CODE (vro->op0);
|
|
}
|
|
if (vro->op1 && TREE_CODE (vro->op1) == SSA_NAME)
|
|
{
|
|
tree tem = SSA_VAL (vro->op1);
|
|
if (tem != vro->op1)
|
|
{
|
|
*valueized_anything = true;
|
|
vro->op1 = tem;
|
|
}
|
|
}
|
|
if (vro->op2 && TREE_CODE (vro->op2) == SSA_NAME)
|
|
{
|
|
tree tem = SSA_VAL (vro->op2);
|
|
if (tem != vro->op2)
|
|
{
|
|
*valueized_anything = true;
|
|
vro->op2 = tem;
|
|
}
|
|
}
|
|
/* If it transforms from an SSA_NAME to an address, fold with
|
|
a preceding indirect reference. */
|
|
if (i > 0
|
|
&& vro->op0
|
|
&& TREE_CODE (vro->op0) == ADDR_EXPR
|
|
&& orig[i - 1].opcode == MEM_REF)
|
|
{
|
|
if (vn_reference_fold_indirect (&orig, &i))
|
|
*valueized_anything = true;
|
|
}
|
|
else if (i > 0
|
|
&& vro->opcode == SSA_NAME
|
|
&& orig[i - 1].opcode == MEM_REF)
|
|
{
|
|
if (vn_reference_maybe_forwprop_address (&orig, &i))
|
|
*valueized_anything = true;
|
|
}
|
|
/* If it transforms a non-constant ARRAY_REF into a constant
|
|
one, adjust the constant offset. */
|
|
else if (vro->opcode == ARRAY_REF
|
|
&& known_eq (vro->off, -1)
|
|
&& poly_int_tree_p (vro->op0)
|
|
&& poly_int_tree_p (vro->op1)
|
|
&& TREE_CODE (vro->op2) == INTEGER_CST)
|
|
{
|
|
poly_offset_int off = ((wi::to_poly_offset (vro->op0)
|
|
- wi::to_poly_offset (vro->op1))
|
|
* wi::to_offset (vro->op2)
|
|
* vn_ref_op_align_unit (vro));
|
|
off.to_shwi (&vro->off);
|
|
}
|
|
}
|
|
|
|
return orig;
|
|
}
|
|
|
|
static vec<vn_reference_op_s>
|
|
valueize_refs (vec<vn_reference_op_s> orig)
|
|
{
|
|
bool tem;
|
|
return valueize_refs_1 (orig, &tem);
|
|
}
|
|
|
|
static vec<vn_reference_op_s> shared_lookup_references;
|
|
|
|
/* Create a vector of vn_reference_op_s structures from REF, a
|
|
REFERENCE_CLASS_P tree. The vector is shared among all callers of
|
|
this function. *VALUEIZED_ANYTHING will specify whether any
|
|
operands were valueized. */
|
|
|
|
static vec<vn_reference_op_s>
|
|
valueize_shared_reference_ops_from_ref (tree ref, bool *valueized_anything)
|
|
{
|
|
if (!ref)
|
|
return vNULL;
|
|
shared_lookup_references.truncate (0);
|
|
copy_reference_ops_from_ref (ref, &shared_lookup_references);
|
|
shared_lookup_references = valueize_refs_1 (shared_lookup_references,
|
|
valueized_anything);
|
|
return shared_lookup_references;
|
|
}
|
|
|
|
/* Create a vector of vn_reference_op_s structures from CALL, a
|
|
call statement. The vector is shared among all callers of
|
|
this function. */
|
|
|
|
static vec<vn_reference_op_s>
|
|
valueize_shared_reference_ops_from_call (gcall *call)
|
|
{
|
|
if (!call)
|
|
return vNULL;
|
|
shared_lookup_references.truncate (0);
|
|
copy_reference_ops_from_call (call, &shared_lookup_references);
|
|
shared_lookup_references = valueize_refs (shared_lookup_references);
|
|
return shared_lookup_references;
|
|
}
|
|
|
|
/* Lookup a SCCVN reference operation VR in the current hash table.
|
|
Returns the resulting value number if it exists in the hash table,
|
|
NULL_TREE otherwise. VNRESULT will be filled in with the actual
|
|
vn_reference_t stored in the hashtable if something is found. */
|
|
|
|
static tree
|
|
vn_reference_lookup_1 (vn_reference_t vr, vn_reference_t *vnresult)
|
|
{
|
|
vn_reference_s **slot;
|
|
hashval_t hash;
|
|
|
|
hash = vr->hashcode;
|
|
slot = current_info->references->find_slot_with_hash (vr, hash, NO_INSERT);
|
|
if (!slot && current_info == optimistic_info)
|
|
slot = valid_info->references->find_slot_with_hash (vr, hash, NO_INSERT);
|
|
if (slot)
|
|
{
|
|
if (vnresult)
|
|
*vnresult = (vn_reference_t)*slot;
|
|
return ((vn_reference_t)*slot)->result;
|
|
}
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Callback for walk_non_aliased_vuses. Adjusts the vn_reference_t VR_
|
|
with the current VUSE and performs the expression lookup. */
|
|
|
|
static void *
|
|
vn_reference_lookup_2 (ao_ref *op ATTRIBUTE_UNUSED, tree vuse,
|
|
unsigned int cnt, void *vr_)
|
|
{
|
|
vn_reference_t vr = (vn_reference_t)vr_;
|
|
vn_reference_s **slot;
|
|
hashval_t hash;
|
|
|
|
/* This bounds the stmt walks we perform on reference lookups
|
|
to O(1) instead of O(N) where N is the number of dominating
|
|
stores. */
|
|
if (cnt > (unsigned) PARAM_VALUE (PARAM_SCCVN_MAX_ALIAS_QUERIES_PER_ACCESS))
|
|
return (void *)-1;
|
|
|
|
if (last_vuse_ptr)
|
|
*last_vuse_ptr = vuse;
|
|
|
|
/* Fixup vuse and hash. */
|
|
if (vr->vuse)
|
|
vr->hashcode = vr->hashcode - SSA_NAME_VERSION (vr->vuse);
|
|
vr->vuse = vuse_ssa_val (vuse);
|
|
if (vr->vuse)
|
|
vr->hashcode = vr->hashcode + SSA_NAME_VERSION (vr->vuse);
|
|
|
|
hash = vr->hashcode;
|
|
slot = current_info->references->find_slot_with_hash (vr, hash, NO_INSERT);
|
|
if (!slot && current_info == optimistic_info)
|
|
slot = valid_info->references->find_slot_with_hash (vr, hash, NO_INSERT);
|
|
if (slot)
|
|
return *slot;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Lookup an existing or insert a new vn_reference entry into the
|
|
value table for the VUSE, SET, TYPE, OPERANDS reference which
|
|
has the value VALUE which is either a constant or an SSA name. */
|
|
|
|
static vn_reference_t
|
|
vn_reference_lookup_or_insert_for_pieces (tree vuse,
|
|
alias_set_type set,
|
|
tree type,
|
|
vec<vn_reference_op_s,
|
|
va_heap> operands,
|
|
tree value)
|
|
{
|
|
vn_reference_s vr1;
|
|
vn_reference_t result;
|
|
unsigned value_id;
|
|
vr1.vuse = vuse ? SSA_VAL (vuse) : NULL_TREE;
|
|
vr1.operands = operands;
|
|
vr1.type = type;
|
|
vr1.set = set;
|
|
vr1.hashcode = vn_reference_compute_hash (&vr1);
|
|
if (vn_reference_lookup_1 (&vr1, &result))
|
|
return result;
|
|
if (TREE_CODE (value) == SSA_NAME)
|
|
value_id = VN_INFO (value)->value_id;
|
|
else
|
|
value_id = get_or_alloc_constant_value_id (value);
|
|
return vn_reference_insert_pieces (vuse, set, type,
|
|
operands.copy (), value, value_id);
|
|
}
|
|
|
|
static vn_nary_op_t vn_nary_op_insert_stmt (gimple *stmt, tree result);
|
|
static unsigned mprts_hook_cnt;
|
|
|
|
/* Hook for maybe_push_res_to_seq, lookup the expression in the VN tables. */
|
|
|
|
static tree
|
|
vn_lookup_simplify_result (code_helper rcode, tree type, tree *ops_)
|
|
{
|
|
if (!rcode.is_tree_code ())
|
|
return NULL_TREE;
|
|
tree *ops = ops_;
|
|
unsigned int length = TREE_CODE_LENGTH ((tree_code) rcode);
|
|
if (rcode == CONSTRUCTOR
|
|
/* ??? We're arriving here with SCCVNs view, decomposed CONSTRUCTOR
|
|
and GIMPLEs / match-and-simplifies, CONSTRUCTOR as GENERIC tree. */
|
|
&& TREE_CODE (ops_[0]) == CONSTRUCTOR)
|
|
{
|
|
length = CONSTRUCTOR_NELTS (ops_[0]);
|
|
ops = XALLOCAVEC (tree, length);
|
|
for (unsigned i = 0; i < length; ++i)
|
|
ops[i] = CONSTRUCTOR_ELT (ops_[0], i)->value;
|
|
}
|
|
vn_nary_op_t vnresult = NULL;
|
|
tree res = vn_nary_op_lookup_pieces (length, (tree_code) rcode,
|
|
type, ops, &vnresult);
|
|
/* We can end up endlessly recursing simplifications if the lookup above
|
|
presents us with a def-use chain that mirrors the original simplification.
|
|
See PR80887 for an example. Limit successful lookup artificially
|
|
to 10 times if we are called as mprts_hook. */
|
|
if (res
|
|
&& mprts_hook
|
|
&& --mprts_hook_cnt == 0)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Resetting mprts_hook after too many "
|
|
"invocations.\n");
|
|
mprts_hook = NULL;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* Return a value-number for RCODE OPS... either by looking up an existing
|
|
value-number for the simplified result or by inserting the operation if
|
|
INSERT is true. */
|
|
|
|
static tree
|
|
vn_nary_build_or_lookup_1 (code_helper rcode, tree type, tree *ops,
|
|
bool insert)
|
|
{
|
|
tree result = NULL_TREE;
|
|
/* We will be creating a value number for
|
|
RCODE (OPS...).
|
|
So first simplify and lookup this expression to see if it
|
|
is already available. */
|
|
mprts_hook = vn_lookup_simplify_result;
|
|
mprts_hook_cnt = 9;
|
|
bool res = false;
|
|
switch (TREE_CODE_LENGTH ((tree_code) rcode))
|
|
{
|
|
case 1:
|
|
res = gimple_resimplify1 (NULL, &rcode, type, ops, vn_valueize);
|
|
break;
|
|
case 2:
|
|
res = gimple_resimplify2 (NULL, &rcode, type, ops, vn_valueize);
|
|
break;
|
|
case 3:
|
|
res = gimple_resimplify3 (NULL, &rcode, type, ops, vn_valueize);
|
|
break;
|
|
}
|
|
mprts_hook = NULL;
|
|
gimple *new_stmt = NULL;
|
|
if (res
|
|
&& gimple_simplified_result_is_gimple_val (rcode, ops))
|
|
/* The expression is already available. */
|
|
result = ops[0];
|
|
else
|
|
{
|
|
tree val = vn_lookup_simplify_result (rcode, type, ops);
|
|
if (!val && insert)
|
|
{
|
|
gimple_seq stmts = NULL;
|
|
result = maybe_push_res_to_seq (rcode, type, ops, &stmts);
|
|
if (result)
|
|
{
|
|
gcc_assert (gimple_seq_singleton_p (stmts));
|
|
new_stmt = gimple_seq_first_stmt (stmts);
|
|
}
|
|
}
|
|
else
|
|
/* The expression is already available. */
|
|
result = val;
|
|
}
|
|
if (new_stmt)
|
|
{
|
|
/* The expression is not yet available, value-number lhs to
|
|
the new SSA_NAME we created. */
|
|
/* Initialize value-number information properly. */
|
|
VN_INFO_GET (result)->valnum = result;
|
|
VN_INFO (result)->value_id = get_next_value_id ();
|
|
gimple_seq_add_stmt_without_update (&VN_INFO (result)->expr,
|
|
new_stmt);
|
|
VN_INFO (result)->needs_insertion = true;
|
|
/* ??? PRE phi-translation inserts NARYs without corresponding
|
|
SSA name result. Re-use those but set their result according
|
|
to the stmt we just built. */
|
|
vn_nary_op_t nary = NULL;
|
|
vn_nary_op_lookup_stmt (new_stmt, &nary);
|
|
if (nary)
|
|
{
|
|
gcc_assert (nary->result == NULL_TREE);
|
|
nary->result = gimple_assign_lhs (new_stmt);
|
|
}
|
|
/* As all "inserted" statements are singleton SCCs, insert
|
|
to the valid table. This is strictly needed to
|
|
avoid re-generating new value SSA_NAMEs for the same
|
|
expression during SCC iteration over and over (the
|
|
optimistic table gets cleared after each iteration).
|
|
We do not need to insert into the optimistic table, as
|
|
lookups there will fall back to the valid table. */
|
|
else if (current_info == optimistic_info)
|
|
{
|
|
current_info = valid_info;
|
|
vn_nary_op_insert_stmt (new_stmt, result);
|
|
current_info = optimistic_info;
|
|
}
|
|
else
|
|
vn_nary_op_insert_stmt (new_stmt, result);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Inserting name ");
|
|
print_generic_expr (dump_file, result);
|
|
fprintf (dump_file, " for expression ");
|
|
print_gimple_expr (dump_file, new_stmt, 0, TDF_SLIM);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Return a value-number for RCODE OPS... either by looking up an existing
|
|
value-number for the simplified result or by inserting the operation. */
|
|
|
|
static tree
|
|
vn_nary_build_or_lookup (code_helper rcode, tree type, tree *ops)
|
|
{
|
|
return vn_nary_build_or_lookup_1 (rcode, type, ops, true);
|
|
}
|
|
|
|
/* Try to simplify the expression RCODE OPS... of type TYPE and return
|
|
its value if present. */
|
|
|
|
tree
|
|
vn_nary_simplify (vn_nary_op_t nary)
|
|
{
|
|
if (nary->length > 3)
|
|
return NULL_TREE;
|
|
tree ops[3];
|
|
memcpy (ops, nary->op, sizeof (tree) * nary->length);
|
|
return vn_nary_build_or_lookup_1 (nary->opcode, nary->type, ops, false);
|
|
}
|
|
|
|
|
|
/* Callback for walk_non_aliased_vuses. Tries to perform a lookup
|
|
from the statement defining VUSE and if not successful tries to
|
|
translate *REFP and VR_ through an aggregate copy at the definition
|
|
of VUSE. If *DISAMBIGUATE_ONLY is true then do not perform translation
|
|
of *REF and *VR. If only disambiguation was performed then
|
|
*DISAMBIGUATE_ONLY is set to true. */
|
|
|
|
static void *
|
|
vn_reference_lookup_3 (ao_ref *ref, tree vuse, void *vr_,
|
|
bool *disambiguate_only)
|
|
{
|
|
vn_reference_t vr = (vn_reference_t)vr_;
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (vuse);
|
|
tree base = ao_ref_base (ref);
|
|
HOST_WIDE_INT offseti, maxsizei;
|
|
static vec<vn_reference_op_s> lhs_ops;
|
|
ao_ref lhs_ref;
|
|
bool lhs_ref_ok = false;
|
|
poly_int64 copy_size;
|
|
|
|
/* If the reference is based on a parameter that was determined as
|
|
pointing to readonly memory it doesn't change. */
|
|
if (TREE_CODE (base) == MEM_REF
|
|
&& TREE_CODE (TREE_OPERAND (base, 0)) == SSA_NAME
|
|
&& SSA_NAME_IS_DEFAULT_DEF (TREE_OPERAND (base, 0))
|
|
&& bitmap_bit_p (const_parms,
|
|
SSA_NAME_VERSION (TREE_OPERAND (base, 0))))
|
|
{
|
|
*disambiguate_only = true;
|
|
return NULL;
|
|
}
|
|
|
|
/* First try to disambiguate after value-replacing in the definitions LHS. */
|
|
if (is_gimple_assign (def_stmt))
|
|
{
|
|
tree lhs = gimple_assign_lhs (def_stmt);
|
|
bool valueized_anything = false;
|
|
/* Avoid re-allocation overhead. */
|
|
lhs_ops.truncate (0);
|
|
copy_reference_ops_from_ref (lhs, &lhs_ops);
|
|
lhs_ops = valueize_refs_1 (lhs_ops, &valueized_anything);
|
|
if (valueized_anything)
|
|
{
|
|
lhs_ref_ok = ao_ref_init_from_vn_reference (&lhs_ref,
|
|
get_alias_set (lhs),
|
|
TREE_TYPE (lhs), lhs_ops);
|
|
if (lhs_ref_ok
|
|
&& !refs_may_alias_p_1 (ref, &lhs_ref, true))
|
|
{
|
|
*disambiguate_only = true;
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ao_ref_init (&lhs_ref, lhs);
|
|
lhs_ref_ok = true;
|
|
}
|
|
|
|
/* If we reach a clobbering statement try to skip it and see if
|
|
we find a VN result with exactly the same value as the
|
|
possible clobber. In this case we can ignore the clobber
|
|
and return the found value.
|
|
Note that we don't need to worry about partial overlapping
|
|
accesses as we then can use TBAA to disambiguate against the
|
|
clobbering statement when looking up a load (thus the
|
|
VN_WALKREWRITE guard). */
|
|
if (vn_walk_kind == VN_WALKREWRITE
|
|
&& is_gimple_reg_type (TREE_TYPE (lhs))
|
|
&& types_compatible_p (TREE_TYPE (lhs), vr->type))
|
|
{
|
|
tree *saved_last_vuse_ptr = last_vuse_ptr;
|
|
/* Do not update last_vuse_ptr in vn_reference_lookup_2. */
|
|
last_vuse_ptr = NULL;
|
|
tree saved_vuse = vr->vuse;
|
|
hashval_t saved_hashcode = vr->hashcode;
|
|
void *res = vn_reference_lookup_2 (ref,
|
|
gimple_vuse (def_stmt), 0, vr);
|
|
/* Need to restore vr->vuse and vr->hashcode. */
|
|
vr->vuse = saved_vuse;
|
|
vr->hashcode = saved_hashcode;
|
|
last_vuse_ptr = saved_last_vuse_ptr;
|
|
if (res && res != (void *)-1)
|
|
{
|
|
vn_reference_t vnresult = (vn_reference_t) res;
|
|
if (vnresult->result
|
|
&& operand_equal_p (vnresult->result,
|
|
gimple_assign_rhs1 (def_stmt), 0))
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
else if (gimple_call_builtin_p (def_stmt, BUILT_IN_NORMAL)
|
|
&& gimple_call_num_args (def_stmt) <= 4)
|
|
{
|
|
/* For builtin calls valueize its arguments and call the
|
|
alias oracle again. Valueization may improve points-to
|
|
info of pointers and constify size and position arguments.
|
|
Originally this was motivated by PR61034 which has
|
|
conditional calls to free falsely clobbering ref because
|
|
of imprecise points-to info of the argument. */
|
|
tree oldargs[4];
|
|
bool valueized_anything = false;
|
|
for (unsigned i = 0; i < gimple_call_num_args (def_stmt); ++i)
|
|
{
|
|
oldargs[i] = gimple_call_arg (def_stmt, i);
|
|
tree val = vn_valueize (oldargs[i]);
|
|
if (val != oldargs[i])
|
|
{
|
|
gimple_call_set_arg (def_stmt, i, val);
|
|
valueized_anything = true;
|
|
}
|
|
}
|
|
if (valueized_anything)
|
|
{
|
|
bool res = call_may_clobber_ref_p_1 (as_a <gcall *> (def_stmt),
|
|
ref);
|
|
for (unsigned i = 0; i < gimple_call_num_args (def_stmt); ++i)
|
|
gimple_call_set_arg (def_stmt, i, oldargs[i]);
|
|
if (!res)
|
|
{
|
|
*disambiguate_only = true;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*disambiguate_only)
|
|
return (void *)-1;
|
|
|
|
/* If we cannot constrain the size of the reference we cannot
|
|
test if anything kills it. */
|
|
if (!ref->max_size_known_p ())
|
|
return (void *)-1;
|
|
|
|
poly_int64 offset = ref->offset;
|
|
poly_int64 maxsize = ref->max_size;
|
|
|
|
/* We can't deduce anything useful from clobbers. */
|
|
if (gimple_clobber_p (def_stmt))
|
|
return (void *)-1;
|
|
|
|
/* def_stmt may-defs *ref. See if we can derive a value for *ref
|
|
from that definition.
|
|
1) Memset. */
|
|
if (is_gimple_reg_type (vr->type)
|
|
&& gimple_call_builtin_p (def_stmt, BUILT_IN_MEMSET)
|
|
&& integer_zerop (gimple_call_arg (def_stmt, 1))
|
|
&& poly_int_tree_p (gimple_call_arg (def_stmt, 2))
|
|
&& TREE_CODE (gimple_call_arg (def_stmt, 0)) == ADDR_EXPR)
|
|
{
|
|
tree ref2 = TREE_OPERAND (gimple_call_arg (def_stmt, 0), 0);
|
|
tree base2;
|
|
poly_int64 offset2, size2, maxsize2;
|
|
bool reverse;
|
|
base2 = get_ref_base_and_extent (ref2, &offset2, &size2, &maxsize2,
|
|
&reverse);
|
|
tree len = gimple_call_arg (def_stmt, 2);
|
|
if (known_size_p (maxsize2)
|
|
&& operand_equal_p (base, base2, 0)
|
|
&& known_subrange_p (offset, maxsize, offset2,
|
|
wi::to_poly_offset (len) << LOG2_BITS_PER_UNIT))
|
|
{
|
|
tree val = build_zero_cst (vr->type);
|
|
return vn_reference_lookup_or_insert_for_pieces
|
|
(vuse, vr->set, vr->type, vr->operands, val);
|
|
}
|
|
}
|
|
|
|
/* 2) Assignment from an empty CONSTRUCTOR. */
|
|
else if (is_gimple_reg_type (vr->type)
|
|
&& gimple_assign_single_p (def_stmt)
|
|
&& gimple_assign_rhs_code (def_stmt) == CONSTRUCTOR
|
|
&& CONSTRUCTOR_NELTS (gimple_assign_rhs1 (def_stmt)) == 0)
|
|
{
|
|
tree base2;
|
|
poly_int64 offset2, size2, maxsize2;
|
|
bool reverse;
|
|
base2 = get_ref_base_and_extent (gimple_assign_lhs (def_stmt),
|
|
&offset2, &size2, &maxsize2, &reverse);
|
|
if (known_size_p (maxsize2)
|
|
&& operand_equal_p (base, base2, 0)
|
|
&& known_subrange_p (offset, maxsize, offset2, size2))
|
|
{
|
|
tree val = build_zero_cst (vr->type);
|
|
return vn_reference_lookup_or_insert_for_pieces
|
|
(vuse, vr->set, vr->type, vr->operands, val);
|
|
}
|
|
}
|
|
|
|
/* 3) Assignment from a constant. We can use folds native encode/interpret
|
|
routines to extract the assigned bits. */
|
|
else if (known_eq (ref->size, maxsize)
|
|
&& is_gimple_reg_type (vr->type)
|
|
&& !contains_storage_order_barrier_p (vr->operands)
|
|
&& gimple_assign_single_p (def_stmt)
|
|
&& CHAR_BIT == 8 && BITS_PER_UNIT == 8
|
|
/* native_encode and native_decode operate on arrays of bytes
|
|
and so fundamentally need a compile-time size and offset. */
|
|
&& maxsize.is_constant (&maxsizei)
|
|
&& maxsizei % BITS_PER_UNIT == 0
|
|
&& offset.is_constant (&offseti)
|
|
&& offseti % BITS_PER_UNIT == 0
|
|
&& (is_gimple_min_invariant (gimple_assign_rhs1 (def_stmt))
|
|
|| (TREE_CODE (gimple_assign_rhs1 (def_stmt)) == SSA_NAME
|
|
&& is_gimple_min_invariant (SSA_VAL (gimple_assign_rhs1 (def_stmt))))))
|
|
{
|
|
tree base2;
|
|
HOST_WIDE_INT offset2, size2;
|
|
bool reverse;
|
|
base2 = get_ref_base_and_extent_hwi (gimple_assign_lhs (def_stmt),
|
|
&offset2, &size2, &reverse);
|
|
if (base2
|
|
&& !reverse
|
|
&& size2 % BITS_PER_UNIT == 0
|
|
&& offset2 % BITS_PER_UNIT == 0
|
|
&& operand_equal_p (base, base2, 0)
|
|
&& known_subrange_p (offseti, maxsizei, offset2, size2))
|
|
{
|
|
/* We support up to 512-bit values (for V8DFmode). */
|
|
unsigned char buffer[64];
|
|
int len;
|
|
|
|
tree rhs = gimple_assign_rhs1 (def_stmt);
|
|
if (TREE_CODE (rhs) == SSA_NAME)
|
|
rhs = SSA_VAL (rhs);
|
|
len = native_encode_expr (gimple_assign_rhs1 (def_stmt),
|
|
buffer, sizeof (buffer),
|
|
(offseti - offset2) / BITS_PER_UNIT);
|
|
if (len > 0 && len * BITS_PER_UNIT >= maxsizei)
|
|
{
|
|
tree type = vr->type;
|
|
/* Make sure to interpret in a type that has a range
|
|
covering the whole access size. */
|
|
if (INTEGRAL_TYPE_P (vr->type)
|
|
&& maxsizei != TYPE_PRECISION (vr->type))
|
|
type = build_nonstandard_integer_type (maxsizei,
|
|
TYPE_UNSIGNED (type));
|
|
tree val = native_interpret_expr (type, buffer,
|
|
maxsizei / BITS_PER_UNIT);
|
|
/* If we chop off bits because the types precision doesn't
|
|
match the memory access size this is ok when optimizing
|
|
reads but not when called from the DSE code during
|
|
elimination. */
|
|
if (val
|
|
&& type != vr->type)
|
|
{
|
|
if (! int_fits_type_p (val, vr->type))
|
|
val = NULL_TREE;
|
|
else
|
|
val = fold_convert (vr->type, val);
|
|
}
|
|
|
|
if (val)
|
|
return vn_reference_lookup_or_insert_for_pieces
|
|
(vuse, vr->set, vr->type, vr->operands, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 4) Assignment from an SSA name which definition we may be able
|
|
to access pieces from. */
|
|
else if (known_eq (ref->size, maxsize)
|
|
&& is_gimple_reg_type (vr->type)
|
|
&& !contains_storage_order_barrier_p (vr->operands)
|
|
&& gimple_assign_single_p (def_stmt)
|
|
&& TREE_CODE (gimple_assign_rhs1 (def_stmt)) == SSA_NAME)
|
|
{
|
|
tree base2;
|
|
poly_int64 offset2, size2, maxsize2;
|
|
bool reverse;
|
|
base2 = get_ref_base_and_extent (gimple_assign_lhs (def_stmt),
|
|
&offset2, &size2, &maxsize2,
|
|
&reverse);
|
|
if (!reverse
|
|
&& known_size_p (maxsize2)
|
|
&& known_eq (maxsize2, size2)
|
|
&& operand_equal_p (base, base2, 0)
|
|
&& known_subrange_p (offset, maxsize, offset2, size2)
|
|
/* ??? We can't handle bitfield precision extracts without
|
|
either using an alternate type for the BIT_FIELD_REF and
|
|
then doing a conversion or possibly adjusting the offset
|
|
according to endianness. */
|
|
&& (! INTEGRAL_TYPE_P (vr->type)
|
|
|| known_eq (ref->size, TYPE_PRECISION (vr->type)))
|
|
&& multiple_p (ref->size, BITS_PER_UNIT))
|
|
{
|
|
code_helper rcode = BIT_FIELD_REF;
|
|
tree ops[3];
|
|
ops[0] = SSA_VAL (gimple_assign_rhs1 (def_stmt));
|
|
ops[1] = bitsize_int (ref->size);
|
|
ops[2] = bitsize_int (offset - offset2);
|
|
tree val = vn_nary_build_or_lookup (rcode, vr->type, ops);
|
|
if (val
|
|
&& (TREE_CODE (val) != SSA_NAME
|
|
|| ! SSA_NAME_OCCURS_IN_ABNORMAL_PHI (val)))
|
|
{
|
|
vn_reference_t res = vn_reference_lookup_or_insert_for_pieces
|
|
(vuse, vr->set, vr->type, vr->operands, val);
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 5) For aggregate copies translate the reference through them if
|
|
the copy kills ref. */
|
|
else if (vn_walk_kind == VN_WALKREWRITE
|
|
&& gimple_assign_single_p (def_stmt)
|
|
&& (DECL_P (gimple_assign_rhs1 (def_stmt))
|
|
|| TREE_CODE (gimple_assign_rhs1 (def_stmt)) == MEM_REF
|
|
|| handled_component_p (gimple_assign_rhs1 (def_stmt))))
|
|
{
|
|
tree base2;
|
|
int i, j, k;
|
|
auto_vec<vn_reference_op_s> rhs;
|
|
vn_reference_op_t vro;
|
|
ao_ref r;
|
|
|
|
if (!lhs_ref_ok)
|
|
return (void *)-1;
|
|
|
|
/* See if the assignment kills REF. */
|
|
base2 = ao_ref_base (&lhs_ref);
|
|
if (!lhs_ref.max_size_known_p ()
|
|
|| (base != base2
|
|
&& (TREE_CODE (base) != MEM_REF
|
|
|| TREE_CODE (base2) != MEM_REF
|
|
|| TREE_OPERAND (base, 0) != TREE_OPERAND (base2, 0)
|
|
|| !tree_int_cst_equal (TREE_OPERAND (base, 1),
|
|
TREE_OPERAND (base2, 1))))
|
|
|| !stmt_kills_ref_p (def_stmt, ref))
|
|
return (void *)-1;
|
|
|
|
/* Find the common base of ref and the lhs. lhs_ops already
|
|
contains valueized operands for the lhs. */
|
|
i = vr->operands.length () - 1;
|
|
j = lhs_ops.length () - 1;
|
|
while (j >= 0 && i >= 0
|
|
&& vn_reference_op_eq (&vr->operands[i], &lhs_ops[j]))
|
|
{
|
|
i--;
|
|
j--;
|
|
}
|
|
|
|
/* ??? The innermost op should always be a MEM_REF and we already
|
|
checked that the assignment to the lhs kills vr. Thus for
|
|
aggregate copies using char[] types the vn_reference_op_eq
|
|
may fail when comparing types for compatibility. But we really
|
|
don't care here - further lookups with the rewritten operands
|
|
will simply fail if we messed up types too badly. */
|
|
poly_int64 extra_off = 0;
|
|
if (j == 0 && i >= 0
|
|
&& lhs_ops[0].opcode == MEM_REF
|
|
&& maybe_ne (lhs_ops[0].off, -1))
|
|
{
|
|
if (known_eq (lhs_ops[0].off, vr->operands[i].off))
|
|
i--, j--;
|
|
else if (vr->operands[i].opcode == MEM_REF
|
|
&& maybe_ne (vr->operands[i].off, -1))
|
|
{
|
|
extra_off = vr->operands[i].off - lhs_ops[0].off;
|
|
i--, j--;
|
|
}
|
|
}
|
|
|
|
/* i now points to the first additional op.
|
|
??? LHS may not be completely contained in VR, one or more
|
|
VIEW_CONVERT_EXPRs could be in its way. We could at least
|
|
try handling outermost VIEW_CONVERT_EXPRs. */
|
|
if (j != -1)
|
|
return (void *)-1;
|
|
|
|
/* Punt if the additional ops contain a storage order barrier. */
|
|
for (k = i; k >= 0; k--)
|
|
{
|
|
vro = &vr->operands[k];
|
|
if (vro->opcode == VIEW_CONVERT_EXPR && vro->reverse)
|
|
return (void *)-1;
|
|
}
|
|
|
|
/* Now re-write REF to be based on the rhs of the assignment. */
|
|
copy_reference_ops_from_ref (gimple_assign_rhs1 (def_stmt), &rhs);
|
|
|
|
/* Apply an extra offset to the inner MEM_REF of the RHS. */
|
|
if (maybe_ne (extra_off, 0))
|
|
{
|
|
if (rhs.length () < 2
|
|
|| rhs[0].opcode != MEM_REF
|
|
|| known_eq (rhs[0].off, -1))
|
|
return (void *)-1;
|
|
rhs[0].off += extra_off;
|
|
rhs[0].op0 = int_const_binop (PLUS_EXPR, rhs[0].op0,
|
|
build_int_cst (TREE_TYPE (rhs[0].op0),
|
|
extra_off));
|
|
}
|
|
|
|
/* We need to pre-pend vr->operands[0..i] to rhs. */
|
|
vec<vn_reference_op_s> old = vr->operands;
|
|
if (i + 1 + rhs.length () > vr->operands.length ())
|
|
vr->operands.safe_grow (i + 1 + rhs.length ());
|
|
else
|
|
vr->operands.truncate (i + 1 + rhs.length ());
|
|
FOR_EACH_VEC_ELT (rhs, j, vro)
|
|
vr->operands[i + 1 + j] = *vro;
|
|
vr->operands = valueize_refs (vr->operands);
|
|
if (old == shared_lookup_references)
|
|
shared_lookup_references = vr->operands;
|
|
vr->hashcode = vn_reference_compute_hash (vr);
|
|
|
|
/* Try folding the new reference to a constant. */
|
|
tree val = fully_constant_vn_reference_p (vr);
|
|
if (val)
|
|
return vn_reference_lookup_or_insert_for_pieces
|
|
(vuse, vr->set, vr->type, vr->operands, val);
|
|
|
|
/* Adjust *ref from the new operands. */
|
|
if (!ao_ref_init_from_vn_reference (&r, vr->set, vr->type, vr->operands))
|
|
return (void *)-1;
|
|
/* This can happen with bitfields. */
|
|
if (maybe_ne (ref->size, r.size))
|
|
return (void *)-1;
|
|
*ref = r;
|
|
|
|
/* Do not update last seen VUSE after translating. */
|
|
last_vuse_ptr = NULL;
|
|
|
|
/* Keep looking for the adjusted *REF / VR pair. */
|
|
return NULL;
|
|
}
|
|
|
|
/* 6) For memcpy copies translate the reference through them if
|
|
the copy kills ref. */
|
|
else if (vn_walk_kind == VN_WALKREWRITE
|
|
&& is_gimple_reg_type (vr->type)
|
|
/* ??? Handle BCOPY as well. */
|
|
&& (gimple_call_builtin_p (def_stmt, BUILT_IN_MEMCPY)
|
|
|| gimple_call_builtin_p (def_stmt, BUILT_IN_MEMPCPY)
|
|
|| gimple_call_builtin_p (def_stmt, BUILT_IN_MEMMOVE))
|
|
&& (TREE_CODE (gimple_call_arg (def_stmt, 0)) == ADDR_EXPR
|
|
|| TREE_CODE (gimple_call_arg (def_stmt, 0)) == SSA_NAME)
|
|
&& (TREE_CODE (gimple_call_arg (def_stmt, 1)) == ADDR_EXPR
|
|
|| TREE_CODE (gimple_call_arg (def_stmt, 1)) == SSA_NAME)
|
|
&& poly_int_tree_p (gimple_call_arg (def_stmt, 2), ©_size))
|
|
{
|
|
tree lhs, rhs;
|
|
ao_ref r;
|
|
poly_int64 rhs_offset, lhs_offset;
|
|
vn_reference_op_s op;
|
|
poly_uint64 mem_offset;
|
|
poly_int64 at, byte_maxsize;
|
|
|
|
/* Only handle non-variable, addressable refs. */
|
|
if (maybe_ne (ref->size, maxsize)
|
|
|| !multiple_p (offset, BITS_PER_UNIT, &at)
|
|
|| !multiple_p (maxsize, BITS_PER_UNIT, &byte_maxsize))
|
|
return (void *)-1;
|
|
|
|
/* Extract a pointer base and an offset for the destination. */
|
|
lhs = gimple_call_arg (def_stmt, 0);
|
|
lhs_offset = 0;
|
|
if (TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
lhs = SSA_VAL (lhs);
|
|
if (TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (lhs);
|
|
if (gimple_assign_single_p (def_stmt)
|
|
&& gimple_assign_rhs_code (def_stmt) == ADDR_EXPR)
|
|
lhs = gimple_assign_rhs1 (def_stmt);
|
|
}
|
|
}
|
|
if (TREE_CODE (lhs) == ADDR_EXPR)
|
|
{
|
|
tree tem = get_addr_base_and_unit_offset (TREE_OPERAND (lhs, 0),
|
|
&lhs_offset);
|
|
if (!tem)
|
|
return (void *)-1;
|
|
if (TREE_CODE (tem) == MEM_REF
|
|
&& poly_int_tree_p (TREE_OPERAND (tem, 1), &mem_offset))
|
|
{
|
|
lhs = TREE_OPERAND (tem, 0);
|
|
if (TREE_CODE (lhs) == SSA_NAME)
|
|
lhs = SSA_VAL (lhs);
|
|
lhs_offset += mem_offset;
|
|
}
|
|
else if (DECL_P (tem))
|
|
lhs = build_fold_addr_expr (tem);
|
|
else
|
|
return (void *)-1;
|
|
}
|
|
if (TREE_CODE (lhs) != SSA_NAME
|
|
&& TREE_CODE (lhs) != ADDR_EXPR)
|
|
return (void *)-1;
|
|
|
|
/* Extract a pointer base and an offset for the source. */
|
|
rhs = gimple_call_arg (def_stmt, 1);
|
|
rhs_offset = 0;
|
|
if (TREE_CODE (rhs) == SSA_NAME)
|
|
rhs = SSA_VAL (rhs);
|
|
if (TREE_CODE (rhs) == ADDR_EXPR)
|
|
{
|
|
tree tem = get_addr_base_and_unit_offset (TREE_OPERAND (rhs, 0),
|
|
&rhs_offset);
|
|
if (!tem)
|
|
return (void *)-1;
|
|
if (TREE_CODE (tem) == MEM_REF
|
|
&& poly_int_tree_p (TREE_OPERAND (tem, 1), &mem_offset))
|
|
{
|
|
rhs = TREE_OPERAND (tem, 0);
|
|
rhs_offset += mem_offset;
|
|
}
|
|
else if (DECL_P (tem)
|
|
|| TREE_CODE (tem) == STRING_CST)
|
|
rhs = build_fold_addr_expr (tem);
|
|
else
|
|
return (void *)-1;
|
|
}
|
|
if (TREE_CODE (rhs) != SSA_NAME
|
|
&& TREE_CODE (rhs) != ADDR_EXPR)
|
|
return (void *)-1;
|
|
|
|
/* The bases of the destination and the references have to agree. */
|
|
if (TREE_CODE (base) == MEM_REF)
|
|
{
|
|
if (TREE_OPERAND (base, 0) != lhs
|
|
|| !poly_int_tree_p (TREE_OPERAND (base, 1), &mem_offset))
|
|
return (void *) -1;
|
|
at += mem_offset;
|
|
}
|
|
else if (!DECL_P (base)
|
|
|| TREE_CODE (lhs) != ADDR_EXPR
|
|
|| TREE_OPERAND (lhs, 0) != base)
|
|
return (void *)-1;
|
|
|
|
/* If the access is completely outside of the memcpy destination
|
|
area there is no aliasing. */
|
|
if (!ranges_maybe_overlap_p (lhs_offset, copy_size, at, byte_maxsize))
|
|
return NULL;
|
|
/* And the access has to be contained within the memcpy destination. */
|
|
if (!known_subrange_p (at, byte_maxsize, lhs_offset, copy_size))
|
|
return (void *)-1;
|
|
|
|
/* Make room for 2 operands in the new reference. */
|
|
if (vr->operands.length () < 2)
|
|
{
|
|
vec<vn_reference_op_s> old = vr->operands;
|
|
vr->operands.safe_grow_cleared (2);
|
|
if (old == shared_lookup_references)
|
|
shared_lookup_references = vr->operands;
|
|
}
|
|
else
|
|
vr->operands.truncate (2);
|
|
|
|
/* The looked-through reference is a simple MEM_REF. */
|
|
memset (&op, 0, sizeof (op));
|
|
op.type = vr->type;
|
|
op.opcode = MEM_REF;
|
|
op.op0 = build_int_cst (ptr_type_node, at - lhs_offset + rhs_offset);
|
|
op.off = at - lhs_offset + rhs_offset;
|
|
vr->operands[0] = op;
|
|
op.type = TREE_TYPE (rhs);
|
|
op.opcode = TREE_CODE (rhs);
|
|
op.op0 = rhs;
|
|
op.off = -1;
|
|
vr->operands[1] = op;
|
|
vr->hashcode = vn_reference_compute_hash (vr);
|
|
|
|
/* Try folding the new reference to a constant. */
|
|
tree val = fully_constant_vn_reference_p (vr);
|
|
if (val)
|
|
return vn_reference_lookup_or_insert_for_pieces
|
|
(vuse, vr->set, vr->type, vr->operands, val);
|
|
|
|
/* Adjust *ref from the new operands. */
|
|
if (!ao_ref_init_from_vn_reference (&r, vr->set, vr->type, vr->operands))
|
|
return (void *)-1;
|
|
/* This can happen with bitfields. */
|
|
if (maybe_ne (ref->size, r.size))
|
|
return (void *)-1;
|
|
*ref = r;
|
|
|
|
/* Do not update last seen VUSE after translating. */
|
|
last_vuse_ptr = NULL;
|
|
|
|
/* Keep looking for the adjusted *REF / VR pair. */
|
|
return NULL;
|
|
}
|
|
|
|
/* Bail out and stop walking. */
|
|
return (void *)-1;
|
|
}
|
|
|
|
/* Return a reference op vector from OP that can be used for
|
|
vn_reference_lookup_pieces. The caller is responsible for releasing
|
|
the vector. */
|
|
|
|
vec<vn_reference_op_s>
|
|
vn_reference_operands_for_lookup (tree op)
|
|
{
|
|
bool valueized;
|
|
return valueize_shared_reference_ops_from_ref (op, &valueized).copy ();
|
|
}
|
|
|
|
/* Lookup a reference operation by it's parts, in the current hash table.
|
|
Returns the resulting value number if it exists in the hash table,
|
|
NULL_TREE otherwise. VNRESULT will be filled in with the actual
|
|
vn_reference_t stored in the hashtable if something is found. */
|
|
|
|
tree
|
|
vn_reference_lookup_pieces (tree vuse, alias_set_type set, tree type,
|
|
vec<vn_reference_op_s> operands,
|
|
vn_reference_t *vnresult, vn_lookup_kind kind)
|
|
{
|
|
struct vn_reference_s vr1;
|
|
vn_reference_t tmp;
|
|
tree cst;
|
|
|
|
if (!vnresult)
|
|
vnresult = &tmp;
|
|
*vnresult = NULL;
|
|
|
|
vr1.vuse = vuse_ssa_val (vuse);
|
|
shared_lookup_references.truncate (0);
|
|
shared_lookup_references.safe_grow (operands.length ());
|
|
memcpy (shared_lookup_references.address (),
|
|
operands.address (),
|
|
sizeof (vn_reference_op_s)
|
|
* operands.length ());
|
|
vr1.operands = operands = shared_lookup_references
|
|
= valueize_refs (shared_lookup_references);
|
|
vr1.type = type;
|
|
vr1.set = set;
|
|
vr1.hashcode = vn_reference_compute_hash (&vr1);
|
|
if ((cst = fully_constant_vn_reference_p (&vr1)))
|
|
return cst;
|
|
|
|
vn_reference_lookup_1 (&vr1, vnresult);
|
|
if (!*vnresult
|
|
&& kind != VN_NOWALK
|
|
&& vr1.vuse)
|
|
{
|
|
ao_ref r;
|
|
vn_walk_kind = kind;
|
|
if (ao_ref_init_from_vn_reference (&r, set, type, vr1.operands))
|
|
*vnresult =
|
|
(vn_reference_t)walk_non_aliased_vuses (&r, vr1.vuse,
|
|
vn_reference_lookup_2,
|
|
vn_reference_lookup_3,
|
|
vuse_ssa_val, &vr1);
|
|
gcc_checking_assert (vr1.operands == shared_lookup_references);
|
|
}
|
|
|
|
if (*vnresult)
|
|
return (*vnresult)->result;
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Lookup OP in the current hash table, and return the resulting value
|
|
number if it exists in the hash table. Return NULL_TREE if it does
|
|
not exist in the hash table or if the result field of the structure
|
|
was NULL.. VNRESULT will be filled in with the vn_reference_t
|
|
stored in the hashtable if one exists. When TBAA_P is false assume
|
|
we are looking up a store and treat it as having alias-set zero. */
|
|
|
|
tree
|
|
vn_reference_lookup (tree op, tree vuse, vn_lookup_kind kind,
|
|
vn_reference_t *vnresult, bool tbaa_p)
|
|
{
|
|
vec<vn_reference_op_s> operands;
|
|
struct vn_reference_s vr1;
|
|
tree cst;
|
|
bool valuezied_anything;
|
|
|
|
if (vnresult)
|
|
*vnresult = NULL;
|
|
|
|
vr1.vuse = vuse_ssa_val (vuse);
|
|
vr1.operands = operands
|
|
= valueize_shared_reference_ops_from_ref (op, &valuezied_anything);
|
|
vr1.type = TREE_TYPE (op);
|
|
vr1.set = tbaa_p ? get_alias_set (op) : 0;
|
|
vr1.hashcode = vn_reference_compute_hash (&vr1);
|
|
if ((cst = fully_constant_vn_reference_p (&vr1)))
|
|
return cst;
|
|
|
|
if (kind != VN_NOWALK
|
|
&& vr1.vuse)
|
|
{
|
|
vn_reference_t wvnresult;
|
|
ao_ref r;
|
|
/* Make sure to use a valueized reference if we valueized anything.
|
|
Otherwise preserve the full reference for advanced TBAA. */
|
|
if (!valuezied_anything
|
|
|| !ao_ref_init_from_vn_reference (&r, vr1.set, vr1.type,
|
|
vr1.operands))
|
|
ao_ref_init (&r, op);
|
|
if (! tbaa_p)
|
|
r.ref_alias_set = r.base_alias_set = 0;
|
|
vn_walk_kind = kind;
|
|
wvnresult =
|
|
(vn_reference_t)walk_non_aliased_vuses (&r, vr1.vuse,
|
|
vn_reference_lookup_2,
|
|
vn_reference_lookup_3,
|
|
vuse_ssa_val, &vr1);
|
|
gcc_checking_assert (vr1.operands == shared_lookup_references);
|
|
if (wvnresult)
|
|
{
|
|
if (vnresult)
|
|
*vnresult = wvnresult;
|
|
return wvnresult->result;
|
|
}
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
return vn_reference_lookup_1 (&vr1, vnresult);
|
|
}
|
|
|
|
/* Lookup CALL in the current hash table and return the entry in
|
|
*VNRESULT if found. Populates *VR for the hashtable lookup. */
|
|
|
|
void
|
|
vn_reference_lookup_call (gcall *call, vn_reference_t *vnresult,
|
|
vn_reference_t vr)
|
|
{
|
|
if (vnresult)
|
|
*vnresult = NULL;
|
|
|
|
tree vuse = gimple_vuse (call);
|
|
|
|
vr->vuse = vuse ? SSA_VAL (vuse) : NULL_TREE;
|
|
vr->operands = valueize_shared_reference_ops_from_call (call);
|
|
vr->type = gimple_expr_type (call);
|
|
vr->set = 0;
|
|
vr->hashcode = vn_reference_compute_hash (vr);
|
|
vn_reference_lookup_1 (vr, vnresult);
|
|
}
|
|
|
|
/* Insert OP into the current hash table with a value number of
|
|
RESULT, and return the resulting reference structure we created. */
|
|
|
|
static vn_reference_t
|
|
vn_reference_insert (tree op, tree result, tree vuse, tree vdef)
|
|
{
|
|
vn_reference_s **slot;
|
|
vn_reference_t vr1;
|
|
bool tem;
|
|
|
|
vr1 = current_info->references_pool->allocate ();
|
|
if (TREE_CODE (result) == SSA_NAME)
|
|
vr1->value_id = VN_INFO (result)->value_id;
|
|
else
|
|
vr1->value_id = get_or_alloc_constant_value_id (result);
|
|
vr1->vuse = vuse ? SSA_VAL (vuse) : NULL_TREE;
|
|
vr1->operands = valueize_shared_reference_ops_from_ref (op, &tem).copy ();
|
|
vr1->type = TREE_TYPE (op);
|
|
vr1->set = get_alias_set (op);
|
|
vr1->hashcode = vn_reference_compute_hash (vr1);
|
|
vr1->result = TREE_CODE (result) == SSA_NAME ? SSA_VAL (result) : result;
|
|
vr1->result_vdef = vdef;
|
|
|
|
slot = current_info->references->find_slot_with_hash (vr1, vr1->hashcode,
|
|
INSERT);
|
|
|
|
/* Because we lookup stores using vuses, and value number failures
|
|
using the vdefs (see visit_reference_op_store for how and why),
|
|
it's possible that on failure we may try to insert an already
|
|
inserted store. This is not wrong, there is no ssa name for a
|
|
store that we could use as a differentiator anyway. Thus, unlike
|
|
the other lookup functions, you cannot gcc_assert (!*slot)
|
|
here. */
|
|
|
|
/* But free the old slot in case of a collision. */
|
|
if (*slot)
|
|
free_reference (*slot);
|
|
|
|
*slot = vr1;
|
|
return vr1;
|
|
}
|
|
|
|
/* Insert a reference by it's pieces into the current hash table with
|
|
a value number of RESULT. Return the resulting reference
|
|
structure we created. */
|
|
|
|
vn_reference_t
|
|
vn_reference_insert_pieces (tree vuse, alias_set_type set, tree type,
|
|
vec<vn_reference_op_s> operands,
|
|
tree result, unsigned int value_id)
|
|
|
|
{
|
|
vn_reference_s **slot;
|
|
vn_reference_t vr1;
|
|
|
|
vr1 = current_info->references_pool->allocate ();
|
|
vr1->value_id = value_id;
|
|
vr1->vuse = vuse ? SSA_VAL (vuse) : NULL_TREE;
|
|
vr1->operands = valueize_refs (operands);
|
|
vr1->type = type;
|
|
vr1->set = set;
|
|
vr1->hashcode = vn_reference_compute_hash (vr1);
|
|
if (result && TREE_CODE (result) == SSA_NAME)
|
|
result = SSA_VAL (result);
|
|
vr1->result = result;
|
|
|
|
slot = current_info->references->find_slot_with_hash (vr1, vr1->hashcode,
|
|
INSERT);
|
|
|
|
/* At this point we should have all the things inserted that we have
|
|
seen before, and we should never try inserting something that
|
|
already exists. */
|
|
gcc_assert (!*slot);
|
|
if (*slot)
|
|
free_reference (*slot);
|
|
|
|
*slot = vr1;
|
|
return vr1;
|
|
}
|
|
|
|
/* Compute and return the hash value for nary operation VBO1. */
|
|
|
|
static hashval_t
|
|
vn_nary_op_compute_hash (const vn_nary_op_t vno1)
|
|
{
|
|
inchash::hash hstate;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < vno1->length; ++i)
|
|
if (TREE_CODE (vno1->op[i]) == SSA_NAME)
|
|
vno1->op[i] = SSA_VAL (vno1->op[i]);
|
|
|
|
if (((vno1->length == 2
|
|
&& commutative_tree_code (vno1->opcode))
|
|
|| (vno1->length == 3
|
|
&& commutative_ternary_tree_code (vno1->opcode)))
|
|
&& tree_swap_operands_p (vno1->op[0], vno1->op[1]))
|
|
std::swap (vno1->op[0], vno1->op[1]);
|
|
else if (TREE_CODE_CLASS (vno1->opcode) == tcc_comparison
|
|
&& tree_swap_operands_p (vno1->op[0], vno1->op[1]))
|
|
{
|
|
std::swap (vno1->op[0], vno1->op[1]);
|
|
vno1->opcode = swap_tree_comparison (vno1->opcode);
|
|
}
|
|
|
|
hstate.add_int (vno1->opcode);
|
|
for (i = 0; i < vno1->length; ++i)
|
|
inchash::add_expr (vno1->op[i], hstate);
|
|
|
|
return hstate.end ();
|
|
}
|
|
|
|
/* Compare nary operations VNO1 and VNO2 and return true if they are
|
|
equivalent. */
|
|
|
|
bool
|
|
vn_nary_op_eq (const_vn_nary_op_t const vno1, const_vn_nary_op_t const vno2)
|
|
{
|
|
unsigned i;
|
|
|
|
if (vno1->hashcode != vno2->hashcode)
|
|
return false;
|
|
|
|
if (vno1->length != vno2->length)
|
|
return false;
|
|
|
|
if (vno1->opcode != vno2->opcode
|
|
|| !types_compatible_p (vno1->type, vno2->type))
|
|
return false;
|
|
|
|
for (i = 0; i < vno1->length; ++i)
|
|
if (!expressions_equal_p (vno1->op[i], vno2->op[i]))
|
|
return false;
|
|
|
|
/* BIT_INSERT_EXPR has an implict operand as the type precision
|
|
of op1. Need to check to make sure they are the same. */
|
|
if (vno1->opcode == BIT_INSERT_EXPR
|
|
&& TREE_CODE (vno1->op[1]) == INTEGER_CST
|
|
&& TYPE_PRECISION (TREE_TYPE (vno1->op[1]))
|
|
!= TYPE_PRECISION (TREE_TYPE (vno2->op[1])))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Initialize VNO from the pieces provided. */
|
|
|
|
static void
|
|
init_vn_nary_op_from_pieces (vn_nary_op_t vno, unsigned int length,
|
|
enum tree_code code, tree type, tree *ops)
|
|
{
|
|
vno->opcode = code;
|
|
vno->length = length;
|
|
vno->type = type;
|
|
memcpy (&vno->op[0], ops, sizeof (tree) * length);
|
|
}
|
|
|
|
/* Initialize VNO from OP. */
|
|
|
|
static void
|
|
init_vn_nary_op_from_op (vn_nary_op_t vno, tree op)
|
|
{
|
|
unsigned i;
|
|
|
|
vno->opcode = TREE_CODE (op);
|
|
vno->length = TREE_CODE_LENGTH (TREE_CODE (op));
|
|
vno->type = TREE_TYPE (op);
|
|
for (i = 0; i < vno->length; ++i)
|
|
vno->op[i] = TREE_OPERAND (op, i);
|
|
}
|
|
|
|
/* Return the number of operands for a vn_nary ops structure from STMT. */
|
|
|
|
static unsigned int
|
|
vn_nary_length_from_stmt (gimple *stmt)
|
|
{
|
|
switch (gimple_assign_rhs_code (stmt))
|
|
{
|
|
case REALPART_EXPR:
|
|
case IMAGPART_EXPR:
|
|
case VIEW_CONVERT_EXPR:
|
|
return 1;
|
|
|
|
case BIT_FIELD_REF:
|
|
return 3;
|
|
|
|
case CONSTRUCTOR:
|
|
return CONSTRUCTOR_NELTS (gimple_assign_rhs1 (stmt));
|
|
|
|
default:
|
|
return gimple_num_ops (stmt) - 1;
|
|
}
|
|
}
|
|
|
|
/* Initialize VNO from STMT. */
|
|
|
|
static void
|
|
init_vn_nary_op_from_stmt (vn_nary_op_t vno, gimple *stmt)
|
|
{
|
|
unsigned i;
|
|
|
|
vno->opcode = gimple_assign_rhs_code (stmt);
|
|
vno->type = gimple_expr_type (stmt);
|
|
switch (vno->opcode)
|
|
{
|
|
case REALPART_EXPR:
|
|
case IMAGPART_EXPR:
|
|
case VIEW_CONVERT_EXPR:
|
|
vno->length = 1;
|
|
vno->op[0] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 0);
|
|
break;
|
|
|
|
case BIT_FIELD_REF:
|
|
vno->length = 3;
|
|
vno->op[0] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 0);
|
|
vno->op[1] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 1);
|
|
vno->op[2] = TREE_OPERAND (gimple_assign_rhs1 (stmt), 2);
|
|
break;
|
|
|
|
case CONSTRUCTOR:
|
|
vno->length = CONSTRUCTOR_NELTS (gimple_assign_rhs1 (stmt));
|
|
for (i = 0; i < vno->length; ++i)
|
|
vno->op[i] = CONSTRUCTOR_ELT (gimple_assign_rhs1 (stmt), i)->value;
|
|
break;
|
|
|
|
default:
|
|
gcc_checking_assert (!gimple_assign_single_p (stmt));
|
|
vno->length = gimple_num_ops (stmt) - 1;
|
|
for (i = 0; i < vno->length; ++i)
|
|
vno->op[i] = gimple_op (stmt, i + 1);
|
|
}
|
|
}
|
|
|
|
/* Compute the hashcode for VNO and look for it in the hash table;
|
|
return the resulting value number if it exists in the hash table.
|
|
Return NULL_TREE if it does not exist in the hash table or if the
|
|
result field of the operation is NULL. VNRESULT will contain the
|
|
vn_nary_op_t from the hashtable if it exists. */
|
|
|
|
static tree
|
|
vn_nary_op_lookup_1 (vn_nary_op_t vno, vn_nary_op_t *vnresult)
|
|
{
|
|
vn_nary_op_s **slot;
|
|
|
|
if (vnresult)
|
|
*vnresult = NULL;
|
|
|
|
vno->hashcode = vn_nary_op_compute_hash (vno);
|
|
slot = current_info->nary->find_slot_with_hash (vno, vno->hashcode,
|
|
NO_INSERT);
|
|
if (!slot && current_info == optimistic_info)
|
|
slot = valid_info->nary->find_slot_with_hash (vno, vno->hashcode,
|
|
NO_INSERT);
|
|
if (!slot)
|
|
return NULL_TREE;
|
|
if (vnresult)
|
|
*vnresult = *slot;
|
|
return (*slot)->result;
|
|
}
|
|
|
|
/* Lookup a n-ary operation by its pieces and return the resulting value
|
|
number if it exists in the hash table. Return NULL_TREE if it does
|
|
not exist in the hash table or if the result field of the operation
|
|
is NULL. VNRESULT will contain the vn_nary_op_t from the hashtable
|
|
if it exists. */
|
|
|
|
tree
|
|
vn_nary_op_lookup_pieces (unsigned int length, enum tree_code code,
|
|
tree type, tree *ops, vn_nary_op_t *vnresult)
|
|
{
|
|
vn_nary_op_t vno1 = XALLOCAVAR (struct vn_nary_op_s,
|
|
sizeof_vn_nary_op (length));
|
|
init_vn_nary_op_from_pieces (vno1, length, code, type, ops);
|
|
return vn_nary_op_lookup_1 (vno1, vnresult);
|
|
}
|
|
|
|
/* Lookup OP in the current hash table, and return the resulting value
|
|
number if it exists in the hash table. Return NULL_TREE if it does
|
|
not exist in the hash table or if the result field of the operation
|
|
is NULL. VNRESULT will contain the vn_nary_op_t from the hashtable
|
|
if it exists. */
|
|
|
|
tree
|
|
vn_nary_op_lookup (tree op, vn_nary_op_t *vnresult)
|
|
{
|
|
vn_nary_op_t vno1
|
|
= XALLOCAVAR (struct vn_nary_op_s,
|
|
sizeof_vn_nary_op (TREE_CODE_LENGTH (TREE_CODE (op))));
|
|
init_vn_nary_op_from_op (vno1, op);
|
|
return vn_nary_op_lookup_1 (vno1, vnresult);
|
|
}
|
|
|
|
/* Lookup the rhs of STMT in the current hash table, and return the resulting
|
|
value number if it exists in the hash table. Return NULL_TREE if
|
|
it does not exist in the hash table. VNRESULT will contain the
|
|
vn_nary_op_t from the hashtable if it exists. */
|
|
|
|
tree
|
|
vn_nary_op_lookup_stmt (gimple *stmt, vn_nary_op_t *vnresult)
|
|
{
|
|
vn_nary_op_t vno1
|
|
= XALLOCAVAR (struct vn_nary_op_s,
|
|
sizeof_vn_nary_op (vn_nary_length_from_stmt (stmt)));
|
|
init_vn_nary_op_from_stmt (vno1, stmt);
|
|
return vn_nary_op_lookup_1 (vno1, vnresult);
|
|
}
|
|
|
|
/* Allocate a vn_nary_op_t with LENGTH operands on STACK. */
|
|
|
|
static vn_nary_op_t
|
|
alloc_vn_nary_op_noinit (unsigned int length, struct obstack *stack)
|
|
{
|
|
return (vn_nary_op_t) obstack_alloc (stack, sizeof_vn_nary_op (length));
|
|
}
|
|
|
|
/* Allocate and initialize a vn_nary_op_t on CURRENT_INFO's
|
|
obstack. */
|
|
|
|
static vn_nary_op_t
|
|
alloc_vn_nary_op (unsigned int length, tree result, unsigned int value_id)
|
|
{
|
|
vn_nary_op_t vno1 = alloc_vn_nary_op_noinit (length,
|
|
¤t_info->nary_obstack);
|
|
|
|
vno1->value_id = value_id;
|
|
vno1->length = length;
|
|
vno1->result = result;
|
|
|
|
return vno1;
|
|
}
|
|
|
|
/* Insert VNO into TABLE. If COMPUTE_HASH is true, then compute
|
|
VNO->HASHCODE first. */
|
|
|
|
static vn_nary_op_t
|
|
vn_nary_op_insert_into (vn_nary_op_t vno, vn_nary_op_table_type *table,
|
|
bool compute_hash)
|
|
{
|
|
vn_nary_op_s **slot;
|
|
|
|
if (compute_hash)
|
|
vno->hashcode = vn_nary_op_compute_hash (vno);
|
|
|
|
slot = table->find_slot_with_hash (vno, vno->hashcode, INSERT);
|
|
/* While we do not want to insert things twice it's awkward to
|
|
avoid it in the case where visit_nary_op pattern-matches stuff
|
|
and ends up simplifying the replacement to itself. We then
|
|
get two inserts, one from visit_nary_op and one from
|
|
vn_nary_build_or_lookup.
|
|
So allow inserts with the same value number. */
|
|
if (*slot && (*slot)->result == vno->result)
|
|
return *slot;
|
|
|
|
gcc_assert (!*slot);
|
|
|
|
*slot = vno;
|
|
return vno;
|
|
}
|
|
|
|
/* Insert a n-ary operation into the current hash table using it's
|
|
pieces. Return the vn_nary_op_t structure we created and put in
|
|
the hashtable. */
|
|
|
|
vn_nary_op_t
|
|
vn_nary_op_insert_pieces (unsigned int length, enum tree_code code,
|
|
tree type, tree *ops,
|
|
tree result, unsigned int value_id)
|
|
{
|
|
vn_nary_op_t vno1 = alloc_vn_nary_op (length, result, value_id);
|
|
init_vn_nary_op_from_pieces (vno1, length, code, type, ops);
|
|
return vn_nary_op_insert_into (vno1, current_info->nary, true);
|
|
}
|
|
|
|
/* Insert OP into the current hash table with a value number of
|
|
RESULT. Return the vn_nary_op_t structure we created and put in
|
|
the hashtable. */
|
|
|
|
vn_nary_op_t
|
|
vn_nary_op_insert (tree op, tree result)
|
|
{
|
|
unsigned length = TREE_CODE_LENGTH (TREE_CODE (op));
|
|
vn_nary_op_t vno1;
|
|
|
|
vno1 = alloc_vn_nary_op (length, result, VN_INFO (result)->value_id);
|
|
init_vn_nary_op_from_op (vno1, op);
|
|
return vn_nary_op_insert_into (vno1, current_info->nary, true);
|
|
}
|
|
|
|
/* Insert the rhs of STMT into the current hash table with a value number of
|
|
RESULT. */
|
|
|
|
static vn_nary_op_t
|
|
vn_nary_op_insert_stmt (gimple *stmt, tree result)
|
|
{
|
|
vn_nary_op_t vno1
|
|
= alloc_vn_nary_op (vn_nary_length_from_stmt (stmt),
|
|
result, VN_INFO (result)->value_id);
|
|
init_vn_nary_op_from_stmt (vno1, stmt);
|
|
return vn_nary_op_insert_into (vno1, current_info->nary, true);
|
|
}
|
|
|
|
/* Compute a hashcode for PHI operation VP1 and return it. */
|
|
|
|
static inline hashval_t
|
|
vn_phi_compute_hash (vn_phi_t vp1)
|
|
{
|
|
inchash::hash hstate (vp1->phiargs.length () > 2
|
|
? vp1->block->index : vp1->phiargs.length ());
|
|
tree phi1op;
|
|
tree type;
|
|
edge e;
|
|
edge_iterator ei;
|
|
|
|
/* If all PHI arguments are constants we need to distinguish
|
|
the PHI node via its type. */
|
|
type = vp1->type;
|
|
hstate.merge_hash (vn_hash_type (type));
|
|
|
|
FOR_EACH_EDGE (e, ei, vp1->block->preds)
|
|
{
|
|
/* Don't hash backedge values they need to be handled as VN_TOP
|
|
for optimistic value-numbering. */
|
|
if (e->flags & EDGE_DFS_BACK)
|
|
continue;
|
|
|
|
phi1op = vp1->phiargs[e->dest_idx];
|
|
if (phi1op == VN_TOP)
|
|
continue;
|
|
inchash::add_expr (phi1op, hstate);
|
|
}
|
|
|
|
return hstate.end ();
|
|
}
|
|
|
|
|
|
/* Return true if COND1 and COND2 represent the same condition, set
|
|
*INVERTED_P if one needs to be inverted to make it the same as
|
|
the other. */
|
|
|
|
static bool
|
|
cond_stmts_equal_p (gcond *cond1, tree lhs1, tree rhs1,
|
|
gcond *cond2, tree lhs2, tree rhs2, bool *inverted_p)
|
|
{
|
|
enum tree_code code1 = gimple_cond_code (cond1);
|
|
enum tree_code code2 = gimple_cond_code (cond2);
|
|
|
|
*inverted_p = false;
|
|
if (code1 == code2)
|
|
;
|
|
else if (code1 == swap_tree_comparison (code2))
|
|
std::swap (lhs2, rhs2);
|
|
else if (code1 == invert_tree_comparison (code2, HONOR_NANS (lhs2)))
|
|
*inverted_p = true;
|
|
else if (code1 == invert_tree_comparison
|
|
(swap_tree_comparison (code2), HONOR_NANS (lhs2)))
|
|
{
|
|
std::swap (lhs2, rhs2);
|
|
*inverted_p = true;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
return ((expressions_equal_p (lhs1, lhs2)
|
|
&& expressions_equal_p (rhs1, rhs2))
|
|
|| (commutative_tree_code (code1)
|
|
&& expressions_equal_p (lhs1, rhs2)
|
|
&& expressions_equal_p (rhs1, lhs2)));
|
|
}
|
|
|
|
/* Compare two phi entries for equality, ignoring VN_TOP arguments. */
|
|
|
|
static int
|
|
vn_phi_eq (const_vn_phi_t const vp1, const_vn_phi_t const vp2)
|
|
{
|
|
if (vp1->hashcode != vp2->hashcode)
|
|
return false;
|
|
|
|
if (vp1->block != vp2->block)
|
|
{
|
|
if (vp1->phiargs.length () != vp2->phiargs.length ())
|
|
return false;
|
|
|
|
switch (vp1->phiargs.length ())
|
|
{
|
|
case 1:
|
|
/* Single-arg PHIs are just copies. */
|
|
break;
|
|
|
|
case 2:
|
|
{
|
|
/* Rule out backedges into the PHI. */
|
|
if (vp1->block->loop_father->header == vp1->block
|
|
|| vp2->block->loop_father->header == vp2->block)
|
|
return false;
|
|
|
|
/* If the PHI nodes do not have compatible types
|
|
they are not the same. */
|
|
if (!types_compatible_p (vp1->type, vp2->type))
|
|
return false;
|
|
|
|
basic_block idom1
|
|
= get_immediate_dominator (CDI_DOMINATORS, vp1->block);
|
|
basic_block idom2
|
|
= get_immediate_dominator (CDI_DOMINATORS, vp2->block);
|
|
/* If the immediate dominator end in switch stmts multiple
|
|
values may end up in the same PHI arg via intermediate
|
|
CFG merges. */
|
|
if (EDGE_COUNT (idom1->succs) != 2
|
|
|| EDGE_COUNT (idom2->succs) != 2)
|
|
return false;
|
|
|
|
/* Verify the controlling stmt is the same. */
|
|
gcond *last1 = safe_dyn_cast <gcond *> (last_stmt (idom1));
|
|
gcond *last2 = safe_dyn_cast <gcond *> (last_stmt (idom2));
|
|
if (! last1 || ! last2)
|
|
return false;
|
|
bool inverted_p;
|
|
if (! cond_stmts_equal_p (last1, vp1->cclhs, vp1->ccrhs,
|
|
last2, vp2->cclhs, vp2->ccrhs,
|
|
&inverted_p))
|
|
return false;
|
|
|
|
/* Get at true/false controlled edges into the PHI. */
|
|
edge te1, te2, fe1, fe2;
|
|
if (! extract_true_false_controlled_edges (idom1, vp1->block,
|
|
&te1, &fe1)
|
|
|| ! extract_true_false_controlled_edges (idom2, vp2->block,
|
|
&te2, &fe2))
|
|
return false;
|
|
|
|
/* Swap edges if the second condition is the inverted of the
|
|
first. */
|
|
if (inverted_p)
|
|
std::swap (te2, fe2);
|
|
|
|
/* ??? Handle VN_TOP specially. */
|
|
if (! expressions_equal_p (vp1->phiargs[te1->dest_idx],
|
|
vp2->phiargs[te2->dest_idx])
|
|
|| ! expressions_equal_p (vp1->phiargs[fe1->dest_idx],
|
|
vp2->phiargs[fe2->dest_idx]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* If the PHI nodes do not have compatible types
|
|
they are not the same. */
|
|
if (!types_compatible_p (vp1->type, vp2->type))
|
|
return false;
|
|
|
|
/* Any phi in the same block will have it's arguments in the
|
|
same edge order, because of how we store phi nodes. */
|
|
int i;
|
|
tree phi1op;
|
|
FOR_EACH_VEC_ELT (vp1->phiargs, i, phi1op)
|
|
{
|
|
tree phi2op = vp2->phiargs[i];
|
|
if (phi1op == VN_TOP || phi2op == VN_TOP)
|
|
continue;
|
|
if (!expressions_equal_p (phi1op, phi2op))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static vec<tree> shared_lookup_phiargs;
|
|
|
|
/* Lookup PHI in the current hash table, and return the resulting
|
|
value number if it exists in the hash table. Return NULL_TREE if
|
|
it does not exist in the hash table. */
|
|
|
|
static tree
|
|
vn_phi_lookup (gimple *phi)
|
|
{
|
|
vn_phi_s **slot;
|
|
struct vn_phi_s vp1;
|
|
edge e;
|
|
edge_iterator ei;
|
|
|
|
shared_lookup_phiargs.truncate (0);
|
|
shared_lookup_phiargs.safe_grow (gimple_phi_num_args (phi));
|
|
|
|
/* Canonicalize the SSA_NAME's to their value number. */
|
|
FOR_EACH_EDGE (e, ei, gimple_bb (phi)->preds)
|
|
{
|
|
tree def = PHI_ARG_DEF_FROM_EDGE (phi, e);
|
|
def = TREE_CODE (def) == SSA_NAME ? SSA_VAL (def) : def;
|
|
shared_lookup_phiargs[e->dest_idx] = def;
|
|
}
|
|
vp1.type = TREE_TYPE (gimple_phi_result (phi));
|
|
vp1.phiargs = shared_lookup_phiargs;
|
|
vp1.block = gimple_bb (phi);
|
|
/* Extract values of the controlling condition. */
|
|
vp1.cclhs = NULL_TREE;
|
|
vp1.ccrhs = NULL_TREE;
|
|
basic_block idom1 = get_immediate_dominator (CDI_DOMINATORS, vp1.block);
|
|
if (EDGE_COUNT (idom1->succs) == 2)
|
|
if (gcond *last1 = safe_dyn_cast <gcond *> (last_stmt (idom1)))
|
|
{
|
|
vp1.cclhs = vn_valueize (gimple_cond_lhs (last1));
|
|
vp1.ccrhs = vn_valueize (gimple_cond_rhs (last1));
|
|
}
|
|
vp1.hashcode = vn_phi_compute_hash (&vp1);
|
|
slot = current_info->phis->find_slot_with_hash (&vp1, vp1.hashcode,
|
|
NO_INSERT);
|
|
if (!slot && current_info == optimistic_info)
|
|
slot = valid_info->phis->find_slot_with_hash (&vp1, vp1.hashcode,
|
|
NO_INSERT);
|
|
if (!slot)
|
|
return NULL_TREE;
|
|
return (*slot)->result;
|
|
}
|
|
|
|
/* Insert PHI into the current hash table with a value number of
|
|
RESULT. */
|
|
|
|
static vn_phi_t
|
|
vn_phi_insert (gimple *phi, tree result)
|
|
{
|
|
vn_phi_s **slot;
|
|
vn_phi_t vp1 = current_info->phis_pool->allocate ();
|
|
vec<tree> args = vNULL;
|
|
edge e;
|
|
edge_iterator ei;
|
|
|
|
args.safe_grow (gimple_phi_num_args (phi));
|
|
|
|
/* Canonicalize the SSA_NAME's to their value number. */
|
|
FOR_EACH_EDGE (e, ei, gimple_bb (phi)->preds)
|
|
{
|
|
tree def = PHI_ARG_DEF_FROM_EDGE (phi, e);
|
|
def = TREE_CODE (def) == SSA_NAME ? SSA_VAL (def) : def;
|
|
args[e->dest_idx] = def;
|
|
}
|
|
vp1->value_id = VN_INFO (result)->value_id;
|
|
vp1->type = TREE_TYPE (gimple_phi_result (phi));
|
|
vp1->phiargs = args;
|
|
vp1->block = gimple_bb (phi);
|
|
/* Extract values of the controlling condition. */
|
|
vp1->cclhs = NULL_TREE;
|
|
vp1->ccrhs = NULL_TREE;
|
|
basic_block idom1 = get_immediate_dominator (CDI_DOMINATORS, vp1->block);
|
|
if (EDGE_COUNT (idom1->succs) == 2)
|
|
if (gcond *last1 = safe_dyn_cast <gcond *> (last_stmt (idom1)))
|
|
{
|
|
vp1->cclhs = vn_valueize (gimple_cond_lhs (last1));
|
|
vp1->ccrhs = vn_valueize (gimple_cond_rhs (last1));
|
|
}
|
|
vp1->result = result;
|
|
vp1->hashcode = vn_phi_compute_hash (vp1);
|
|
|
|
slot = current_info->phis->find_slot_with_hash (vp1, vp1->hashcode, INSERT);
|
|
|
|
/* Because we iterate over phi operations more than once, it's
|
|
possible the slot might already exist here, hence no assert.*/
|
|
*slot = vp1;
|
|
return vp1;
|
|
}
|
|
|
|
|
|
/* Print set of components in strongly connected component SCC to OUT. */
|
|
|
|
static void
|
|
print_scc (FILE *out, vec<tree> scc)
|
|
{
|
|
tree var;
|
|
unsigned int i;
|
|
|
|
fprintf (out, "SCC consists of %u:", scc.length ());
|
|
FOR_EACH_VEC_ELT (scc, i, var)
|
|
{
|
|
fprintf (out, " ");
|
|
print_generic_expr (out, var);
|
|
}
|
|
fprintf (out, "\n");
|
|
}
|
|
|
|
/* Return true if BB1 is dominated by BB2 taking into account edges
|
|
that are not executable. */
|
|
|
|
static bool
|
|
dominated_by_p_w_unex (basic_block bb1, basic_block bb2)
|
|
{
|
|
edge_iterator ei;
|
|
edge e;
|
|
|
|
if (dominated_by_p (CDI_DOMINATORS, bb1, bb2))
|
|
return true;
|
|
|
|
/* Before iterating we'd like to know if there exists a
|
|
(executable) path from bb2 to bb1 at all, if not we can
|
|
directly return false. For now simply iterate once. */
|
|
|
|
/* Iterate to the single executable bb1 predecessor. */
|
|
if (EDGE_COUNT (bb1->preds) > 1)
|
|
{
|
|
edge prede = NULL;
|
|
FOR_EACH_EDGE (e, ei, bb1->preds)
|
|
if (e->flags & EDGE_EXECUTABLE)
|
|
{
|
|
if (prede)
|
|
{
|
|
prede = NULL;
|
|
break;
|
|
}
|
|
prede = e;
|
|
}
|
|
if (prede)
|
|
{
|
|
bb1 = prede->src;
|
|
|
|
/* Re-do the dominance check with changed bb1. */
|
|
if (dominated_by_p (CDI_DOMINATORS, bb1, bb2))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Iterate to the single executable bb2 successor. */
|
|
edge succe = NULL;
|
|
FOR_EACH_EDGE (e, ei, bb2->succs)
|
|
if (e->flags & EDGE_EXECUTABLE)
|
|
{
|
|
if (succe)
|
|
{
|
|
succe = NULL;
|
|
break;
|
|
}
|
|
succe = e;
|
|
}
|
|
if (succe)
|
|
{
|
|
/* Verify the reached block is only reached through succe.
|
|
If there is only one edge we can spare us the dominator
|
|
check and iterate directly. */
|
|
if (EDGE_COUNT (succe->dest->preds) > 1)
|
|
{
|
|
FOR_EACH_EDGE (e, ei, succe->dest->preds)
|
|
if (e != succe
|
|
&& (e->flags & EDGE_EXECUTABLE))
|
|
{
|
|
succe = NULL;
|
|
break;
|
|
}
|
|
}
|
|
if (succe)
|
|
{
|
|
bb2 = succe->dest;
|
|
|
|
/* Re-do the dominance check with changed bb2. */
|
|
if (dominated_by_p (CDI_DOMINATORS, bb1, bb2))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* We could now iterate updating bb1 / bb2. */
|
|
return false;
|
|
}
|
|
|
|
/* Set the value number of FROM to TO, return true if it has changed
|
|
as a result. */
|
|
|
|
static inline bool
|
|
set_ssa_val_to (tree from, tree to)
|
|
{
|
|
tree currval = SSA_VAL (from);
|
|
poly_int64 toff, coff;
|
|
|
|
/* The only thing we allow as value numbers are ssa_names
|
|
and invariants. So assert that here. We don't allow VN_TOP
|
|
as visiting a stmt should produce a value-number other than
|
|
that.
|
|
??? Still VN_TOP can happen for unreachable code, so force
|
|
it to varying in that case. Not all code is prepared to
|
|
get VN_TOP on valueization. */
|
|
if (to == VN_TOP)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Forcing value number to varying on "
|
|
"receiving VN_TOP\n");
|
|
to = from;
|
|
}
|
|
|
|
gcc_assert (to != NULL_TREE
|
|
&& ((TREE_CODE (to) == SSA_NAME
|
|
&& (to == from || SSA_VAL (to) == to))
|
|
|| is_gimple_min_invariant (to)));
|
|
|
|
if (from != to)
|
|
{
|
|
if (currval == from)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Not changing value number of ");
|
|
print_generic_expr (dump_file, from);
|
|
fprintf (dump_file, " from VARYING to ");
|
|
print_generic_expr (dump_file, to);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return false;
|
|
}
|
|
else if (currval != VN_TOP
|
|
&& ! is_gimple_min_invariant (currval)
|
|
&& is_gimple_min_invariant (to))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Forcing VARYING instead of changing "
|
|
"value number of ");
|
|
print_generic_expr (dump_file, from);
|
|
fprintf (dump_file, " from ");
|
|
print_generic_expr (dump_file, currval);
|
|
fprintf (dump_file, " (non-constant) to ");
|
|
print_generic_expr (dump_file, to);
|
|
fprintf (dump_file, " (constant)\n");
|
|
}
|
|
to = from;
|
|
}
|
|
else if (TREE_CODE (to) == SSA_NAME
|
|
&& SSA_NAME_OCCURS_IN_ABNORMAL_PHI (to))
|
|
to = from;
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Setting value number of ");
|
|
print_generic_expr (dump_file, from);
|
|
fprintf (dump_file, " to ");
|
|
print_generic_expr (dump_file, to);
|
|
}
|
|
|
|
if (currval != to
|
|
&& !operand_equal_p (currval, to, 0)
|
|
/* Different undefined SSA names are not actually different. See
|
|
PR82320 for a testcase were we'd otherwise not terminate iteration. */
|
|
&& !(TREE_CODE (currval) == SSA_NAME
|
|
&& TREE_CODE (to) == SSA_NAME
|
|
&& ssa_undefined_value_p (currval, false)
|
|
&& ssa_undefined_value_p (to, false))
|
|
/* ??? For addresses involving volatile objects or types operand_equal_p
|
|
does not reliably detect ADDR_EXPRs as equal. We know we are only
|
|
getting invariant gimple addresses here, so can use
|
|
get_addr_base_and_unit_offset to do this comparison. */
|
|
&& !(TREE_CODE (currval) == ADDR_EXPR
|
|
&& TREE_CODE (to) == ADDR_EXPR
|
|
&& (get_addr_base_and_unit_offset (TREE_OPERAND (currval, 0), &coff)
|
|
== get_addr_base_and_unit_offset (TREE_OPERAND (to, 0), &toff))
|
|
&& known_eq (coff, toff)))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, " (changed)\n");
|
|
|
|
/* If we equate two SSA names we have to make the side-band info
|
|
of the leader conservative (and remember whatever original value
|
|
was present). */
|
|
if (TREE_CODE (to) == SSA_NAME)
|
|
{
|
|
if (INTEGRAL_TYPE_P (TREE_TYPE (to))
|
|
&& SSA_NAME_RANGE_INFO (to))
|
|
{
|
|
if (SSA_NAME_IS_DEFAULT_DEF (to)
|
|
|| dominated_by_p_w_unex
|
|
(gimple_bb (SSA_NAME_DEF_STMT (from)),
|
|
gimple_bb (SSA_NAME_DEF_STMT (to))))
|
|
/* Keep the info from the dominator. */
|
|
;
|
|
else
|
|
{
|
|
/* Save old info. */
|
|
if (! VN_INFO (to)->info.range_info)
|
|
{
|
|
VN_INFO (to)->info.range_info = SSA_NAME_RANGE_INFO (to);
|
|
VN_INFO (to)->range_info_anti_range_p
|
|
= SSA_NAME_ANTI_RANGE_P (to);
|
|
}
|
|
/* Rather than allocating memory and unioning the info
|
|
just clear it. */
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "clearing range info of ");
|
|
print_generic_expr (dump_file, to);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
SSA_NAME_RANGE_INFO (to) = NULL;
|
|
}
|
|
}
|
|
else if (POINTER_TYPE_P (TREE_TYPE (to))
|
|
&& SSA_NAME_PTR_INFO (to))
|
|
{
|
|
if (SSA_NAME_IS_DEFAULT_DEF (to)
|
|
|| dominated_by_p_w_unex
|
|
(gimple_bb (SSA_NAME_DEF_STMT (from)),
|
|
gimple_bb (SSA_NAME_DEF_STMT (to))))
|
|
/* Keep the info from the dominator. */
|
|
;
|
|
else if (! SSA_NAME_PTR_INFO (from)
|
|
/* Handle the case of trivially equivalent info. */
|
|
|| memcmp (SSA_NAME_PTR_INFO (to),
|
|
SSA_NAME_PTR_INFO (from),
|
|
sizeof (ptr_info_def)) != 0)
|
|
{
|
|
/* Save old info. */
|
|
if (! VN_INFO (to)->info.ptr_info)
|
|
VN_INFO (to)->info.ptr_info = SSA_NAME_PTR_INFO (to);
|
|
/* Rather than allocating memory and unioning the info
|
|
just clear it. */
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "clearing points-to info of ");
|
|
print_generic_expr (dump_file, to);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
SSA_NAME_PTR_INFO (to) = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
VN_INFO (from)->valnum = to;
|
|
return true;
|
|
}
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "\n");
|
|
return false;
|
|
}
|
|
|
|
/* Mark as processed all the definitions in the defining stmt of USE, or
|
|
the USE itself. */
|
|
|
|
static void
|
|
mark_use_processed (tree use)
|
|
{
|
|
ssa_op_iter iter;
|
|
def_operand_p defp;
|
|
gimple *stmt = SSA_NAME_DEF_STMT (use);
|
|
|
|
if (SSA_NAME_IS_DEFAULT_DEF (use) || gimple_code (stmt) == GIMPLE_PHI)
|
|
{
|
|
VN_INFO (use)->use_processed = true;
|
|
return;
|
|
}
|
|
|
|
FOR_EACH_SSA_DEF_OPERAND (defp, stmt, iter, SSA_OP_ALL_DEFS)
|
|
{
|
|
tree def = DEF_FROM_PTR (defp);
|
|
|
|
VN_INFO (def)->use_processed = true;
|
|
}
|
|
}
|
|
|
|
/* Set all definitions in STMT to value number to themselves.
|
|
Return true if a value number changed. */
|
|
|
|
static bool
|
|
defs_to_varying (gimple *stmt)
|
|
{
|
|
bool changed = false;
|
|
ssa_op_iter iter;
|
|
def_operand_p defp;
|
|
|
|
FOR_EACH_SSA_DEF_OPERAND (defp, stmt, iter, SSA_OP_ALL_DEFS)
|
|
{
|
|
tree def = DEF_FROM_PTR (defp);
|
|
changed |= set_ssa_val_to (def, def);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Visit a copy between LHS and RHS, return true if the value number
|
|
changed. */
|
|
|
|
static bool
|
|
visit_copy (tree lhs, tree rhs)
|
|
{
|
|
/* Valueize. */
|
|
rhs = SSA_VAL (rhs);
|
|
|
|
return set_ssa_val_to (lhs, rhs);
|
|
}
|
|
|
|
/* Lookup a value for OP in type WIDE_TYPE where the value in type of OP
|
|
is the same. */
|
|
|
|
static tree
|
|
valueized_wider_op (tree wide_type, tree op)
|
|
{
|
|
if (TREE_CODE (op) == SSA_NAME)
|
|
op = SSA_VAL (op);
|
|
|
|
/* Either we have the op widened available. */
|
|
tree ops[3] = {};
|
|
ops[0] = op;
|
|
tree tem = vn_nary_op_lookup_pieces (1, NOP_EXPR,
|
|
wide_type, ops, NULL);
|
|
if (tem)
|
|
return tem;
|
|
|
|
/* Or the op is truncated from some existing value. */
|
|
if (TREE_CODE (op) == SSA_NAME)
|
|
{
|
|
gimple *def = SSA_NAME_DEF_STMT (op);
|
|
if (is_gimple_assign (def)
|
|
&& CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (def)))
|
|
{
|
|
tem = gimple_assign_rhs1 (def);
|
|
if (useless_type_conversion_p (wide_type, TREE_TYPE (tem)))
|
|
{
|
|
if (TREE_CODE (tem) == SSA_NAME)
|
|
tem = SSA_VAL (tem);
|
|
return tem;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* For constants simply extend it. */
|
|
if (TREE_CODE (op) == INTEGER_CST)
|
|
return wide_int_to_tree (wide_type, wi::to_wide (op));
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Visit a nary operator RHS, value number it, and return true if the
|
|
value number of LHS has changed as a result. */
|
|
|
|
static bool
|
|
visit_nary_op (tree lhs, gassign *stmt)
|
|
{
|
|
tree result = vn_nary_op_lookup_stmt (stmt, NULL);
|
|
if (result)
|
|
return set_ssa_val_to (lhs, result);
|
|
|
|
/* Do some special pattern matching for redundancies of operations
|
|
in different types. */
|
|
enum tree_code code = gimple_assign_rhs_code (stmt);
|
|
tree type = TREE_TYPE (lhs);
|
|
tree rhs1 = gimple_assign_rhs1 (stmt);
|
|
switch (code)
|
|
{
|
|
CASE_CONVERT:
|
|
/* Match arithmetic done in a different type where we can easily
|
|
substitute the result from some earlier sign-changed or widened
|
|
operation. */
|
|
if (INTEGRAL_TYPE_P (type)
|
|
&& TREE_CODE (rhs1) == SSA_NAME
|
|
/* We only handle sign-changes or zero-extension -> & mask. */
|
|
&& ((TYPE_UNSIGNED (TREE_TYPE (rhs1))
|
|
&& TYPE_PRECISION (type) > TYPE_PRECISION (TREE_TYPE (rhs1)))
|
|
|| TYPE_PRECISION (type) == TYPE_PRECISION (TREE_TYPE (rhs1))))
|
|
{
|
|
gassign *def = dyn_cast <gassign *> (SSA_NAME_DEF_STMT (rhs1));
|
|
if (def
|
|
&& (gimple_assign_rhs_code (def) == PLUS_EXPR
|
|
|| gimple_assign_rhs_code (def) == MINUS_EXPR
|
|
|| gimple_assign_rhs_code (def) == MULT_EXPR))
|
|
{
|
|
tree ops[3] = {};
|
|
/* Either we have the op widened available. */
|
|
ops[0] = valueized_wider_op (type,
|
|
gimple_assign_rhs1 (def));
|
|
if (ops[0])
|
|
ops[1] = valueized_wider_op (type,
|
|
gimple_assign_rhs2 (def));
|
|
if (ops[0] && ops[1])
|
|
{
|
|
ops[0] = vn_nary_op_lookup_pieces
|
|
(2, gimple_assign_rhs_code (def), type, ops, NULL);
|
|
/* We have wider operation available. */
|
|
if (ops[0])
|
|
{
|
|
unsigned lhs_prec = TYPE_PRECISION (type);
|
|
unsigned rhs_prec = TYPE_PRECISION (TREE_TYPE (rhs1));
|
|
if (lhs_prec == rhs_prec)
|
|
{
|
|
ops[1] = NULL_TREE;
|
|
result = vn_nary_build_or_lookup (NOP_EXPR,
|
|
type, ops);
|
|
if (result)
|
|
{
|
|
bool changed = set_ssa_val_to (lhs, result);
|
|
vn_nary_op_insert_stmt (stmt, result);
|
|
return changed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ops[1] = wide_int_to_tree (type,
|
|
wi::mask (rhs_prec, false,
|
|
lhs_prec));
|
|
result = vn_nary_build_or_lookup (BIT_AND_EXPR,
|
|
TREE_TYPE (lhs),
|
|
ops);
|
|
if (result)
|
|
{
|
|
bool changed = set_ssa_val_to (lhs, result);
|
|
vn_nary_op_insert_stmt (stmt, result);
|
|
return changed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
default:;
|
|
}
|
|
|
|
bool changed = set_ssa_val_to (lhs, lhs);
|
|
vn_nary_op_insert_stmt (stmt, lhs);
|
|
return changed;
|
|
}
|
|
|
|
/* Visit a call STMT storing into LHS. Return true if the value number
|
|
of the LHS has changed as a result. */
|
|
|
|
static bool
|
|
visit_reference_op_call (tree lhs, gcall *stmt)
|
|
{
|
|
bool changed = false;
|
|
struct vn_reference_s vr1;
|
|
vn_reference_t vnresult = NULL;
|
|
tree vdef = gimple_vdef (stmt);
|
|
|
|
/* Non-ssa lhs is handled in copy_reference_ops_from_call. */
|
|
if (lhs && TREE_CODE (lhs) != SSA_NAME)
|
|
lhs = NULL_TREE;
|
|
|
|
vn_reference_lookup_call (stmt, &vnresult, &vr1);
|
|
if (vnresult)
|
|
{
|
|
if (vnresult->result_vdef && vdef)
|
|
changed |= set_ssa_val_to (vdef, vnresult->result_vdef);
|
|
else if (vdef)
|
|
/* If the call was discovered to be pure or const reflect
|
|
that as far as possible. */
|
|
changed |= set_ssa_val_to (vdef, vuse_ssa_val (gimple_vuse (stmt)));
|
|
|
|
if (!vnresult->result && lhs)
|
|
vnresult->result = lhs;
|
|
|
|
if (vnresult->result && lhs)
|
|
changed |= set_ssa_val_to (lhs, vnresult->result);
|
|
}
|
|
else
|
|
{
|
|
vn_reference_t vr2;
|
|
vn_reference_s **slot;
|
|
tree vdef_val = vdef;
|
|
if (vdef)
|
|
{
|
|
/* If we value numbered an indirect functions function to
|
|
one not clobbering memory value number its VDEF to its
|
|
VUSE. */
|
|
tree fn = gimple_call_fn (stmt);
|
|
if (fn && TREE_CODE (fn) == SSA_NAME)
|
|
{
|
|
fn = SSA_VAL (fn);
|
|
if (TREE_CODE (fn) == ADDR_EXPR
|
|
&& TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL
|
|
&& (flags_from_decl_or_type (TREE_OPERAND (fn, 0))
|
|
& (ECF_CONST | ECF_PURE)))
|
|
vdef_val = vuse_ssa_val (gimple_vuse (stmt));
|
|
}
|
|
changed |= set_ssa_val_to (vdef, vdef_val);
|
|
}
|
|
if (lhs)
|
|
changed |= set_ssa_val_to (lhs, lhs);
|
|
vr2 = current_info->references_pool->allocate ();
|
|
vr2->vuse = vr1.vuse;
|
|
/* As we are not walking the virtual operand chain we know the
|
|
shared_lookup_references are still original so we can re-use
|
|
them here. */
|
|
vr2->operands = vr1.operands.copy ();
|
|
vr2->type = vr1.type;
|
|
vr2->set = vr1.set;
|
|
vr2->hashcode = vr1.hashcode;
|
|
vr2->result = lhs;
|
|
vr2->result_vdef = vdef_val;
|
|
slot = current_info->references->find_slot_with_hash (vr2, vr2->hashcode,
|
|
INSERT);
|
|
gcc_assert (!*slot);
|
|
*slot = vr2;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* Visit a load from a reference operator RHS, part of STMT, value number it,
|
|
and return true if the value number of the LHS has changed as a result. */
|
|
|
|
static bool
|
|
visit_reference_op_load (tree lhs, tree op, gimple *stmt)
|
|
{
|
|
bool changed = false;
|
|
tree last_vuse;
|
|
tree result;
|
|
|
|
last_vuse = gimple_vuse (stmt);
|
|
last_vuse_ptr = &last_vuse;
|
|
result = vn_reference_lookup (op, gimple_vuse (stmt),
|
|
default_vn_walk_kind, NULL, true);
|
|
last_vuse_ptr = NULL;
|
|
|
|
/* We handle type-punning through unions by value-numbering based
|
|
on offset and size of the access. Be prepared to handle a
|
|
type-mismatch here via creating a VIEW_CONVERT_EXPR. */
|
|
if (result
|
|
&& !useless_type_conversion_p (TREE_TYPE (result), TREE_TYPE (op)))
|
|
{
|
|
/* We will be setting the value number of lhs to the value number
|
|
of VIEW_CONVERT_EXPR <TREE_TYPE (result)> (result).
|
|
So first simplify and lookup this expression to see if it
|
|
is already available. */
|
|
code_helper rcode = VIEW_CONVERT_EXPR;
|
|
tree ops[3] = { result };
|
|
result = vn_nary_build_or_lookup (rcode, TREE_TYPE (op), ops);
|
|
}
|
|
|
|
if (result)
|
|
changed = set_ssa_val_to (lhs, result);
|
|
else
|
|
{
|
|
changed = set_ssa_val_to (lhs, lhs);
|
|
vn_reference_insert (op, lhs, last_vuse, NULL_TREE);
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
|
|
/* Visit a store to a reference operator LHS, part of STMT, value number it,
|
|
and return true if the value number of the LHS has changed as a result. */
|
|
|
|
static bool
|
|
visit_reference_op_store (tree lhs, tree op, gimple *stmt)
|
|
{
|
|
bool changed = false;
|
|
vn_reference_t vnresult = NULL;
|
|
tree assign;
|
|
bool resultsame = false;
|
|
tree vuse = gimple_vuse (stmt);
|
|
tree vdef = gimple_vdef (stmt);
|
|
|
|
if (TREE_CODE (op) == SSA_NAME)
|
|
op = SSA_VAL (op);
|
|
|
|
/* First we want to lookup using the *vuses* from the store and see
|
|
if there the last store to this location with the same address
|
|
had the same value.
|
|
|
|
The vuses represent the memory state before the store. If the
|
|
memory state, address, and value of the store is the same as the
|
|
last store to this location, then this store will produce the
|
|
same memory state as that store.
|
|
|
|
In this case the vdef versions for this store are value numbered to those
|
|
vuse versions, since they represent the same memory state after
|
|
this store.
|
|
|
|
Otherwise, the vdefs for the store are used when inserting into
|
|
the table, since the store generates a new memory state. */
|
|
|
|
vn_reference_lookup (lhs, vuse, VN_NOWALK, &vnresult, false);
|
|
if (vnresult
|
|
&& vnresult->result)
|
|
{
|
|
tree result = vnresult->result;
|
|
if (TREE_CODE (result) == SSA_NAME)
|
|
result = SSA_VAL (result);
|
|
resultsame = expressions_equal_p (result, op);
|
|
if (resultsame)
|
|
{
|
|
/* If the TBAA state isn't compatible for downstream reads
|
|
we cannot value-number the VDEFs the same. */
|
|
alias_set_type set = get_alias_set (lhs);
|
|
if (vnresult->set != set
|
|
&& ! alias_set_subset_of (set, vnresult->set))
|
|
resultsame = false;
|
|
}
|
|
}
|
|
|
|
if (!resultsame)
|
|
{
|
|
/* Only perform the following when being called from PRE
|
|
which embeds tail merging. */
|
|
if (default_vn_walk_kind == VN_WALK)
|
|
{
|
|
assign = build2 (MODIFY_EXPR, TREE_TYPE (lhs), lhs, op);
|
|
vn_reference_lookup (assign, vuse, VN_NOWALK, &vnresult, false);
|
|
if (vnresult)
|
|
{
|
|
VN_INFO (vdef)->use_processed = true;
|
|
return set_ssa_val_to (vdef, vnresult->result_vdef);
|
|
}
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "No store match\n");
|
|
fprintf (dump_file, "Value numbering store ");
|
|
print_generic_expr (dump_file, lhs);
|
|
fprintf (dump_file, " to ");
|
|
print_generic_expr (dump_file, op);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
/* Have to set value numbers before insert, since insert is
|
|
going to valueize the references in-place. */
|
|
if (vdef)
|
|
changed |= set_ssa_val_to (vdef, vdef);
|
|
|
|
/* Do not insert structure copies into the tables. */
|
|
if (is_gimple_min_invariant (op)
|
|
|| is_gimple_reg (op))
|
|
vn_reference_insert (lhs, op, vdef, NULL);
|
|
|
|
/* Only perform the following when being called from PRE
|
|
which embeds tail merging. */
|
|
if (default_vn_walk_kind == VN_WALK)
|
|
{
|
|
assign = build2 (MODIFY_EXPR, TREE_TYPE (lhs), lhs, op);
|
|
vn_reference_insert (assign, lhs, vuse, vdef);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We had a match, so value number the vdef to have the value
|
|
number of the vuse it came from. */
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Store matched earlier value, "
|
|
"value numbering store vdefs to matching vuses.\n");
|
|
|
|
changed |= set_ssa_val_to (vdef, SSA_VAL (vuse));
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* Visit and value number PHI, return true if the value number
|
|
changed. */
|
|
|
|
static bool
|
|
visit_phi (gimple *phi)
|
|
{
|
|
tree result, sameval = VN_TOP, seen_undef = NULL_TREE;
|
|
unsigned n_executable = 0;
|
|
bool allsame = true;
|
|
edge_iterator ei;
|
|
edge e;
|
|
|
|
/* TODO: We could check for this in init_sccvn, and replace this
|
|
with a gcc_assert. */
|
|
if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (PHI_RESULT (phi)))
|
|
return set_ssa_val_to (PHI_RESULT (phi), PHI_RESULT (phi));
|
|
|
|
/* See if all non-TOP arguments have the same value. TOP is
|
|
equivalent to everything, so we can ignore it. */
|
|
FOR_EACH_EDGE (e, ei, gimple_bb (phi)->preds)
|
|
if (e->flags & EDGE_EXECUTABLE)
|
|
{
|
|
tree def = PHI_ARG_DEF_FROM_EDGE (phi, e);
|
|
|
|
++n_executable;
|
|
if (TREE_CODE (def) == SSA_NAME)
|
|
def = SSA_VAL (def);
|
|
if (def == VN_TOP)
|
|
;
|
|
/* Ignore undefined defs for sameval but record one. */
|
|
else if (TREE_CODE (def) == SSA_NAME
|
|
&& ssa_undefined_value_p (def, false))
|
|
seen_undef = def;
|
|
else if (sameval == VN_TOP)
|
|
sameval = def;
|
|
else if (!expressions_equal_p (def, sameval))
|
|
{
|
|
allsame = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* If none of the edges was executable keep the value-number at VN_TOP,
|
|
if only a single edge is exectuable use its value. */
|
|
if (n_executable <= 1)
|
|
result = seen_undef ? seen_undef : sameval;
|
|
/* If we saw only undefined values and VN_TOP use one of the
|
|
undefined values. */
|
|
else if (sameval == VN_TOP)
|
|
result = seen_undef ? seen_undef : sameval;
|
|
/* First see if it is equivalent to a phi node in this block. We prefer
|
|
this as it allows IV elimination - see PRs 66502 and 67167. */
|
|
else if ((result = vn_phi_lookup (phi)))
|
|
;
|
|
/* If all values are the same use that, unless we've seen undefined
|
|
values as well and the value isn't constant.
|
|
CCP/copyprop have the same restriction to not remove uninit warnings. */
|
|
else if (allsame
|
|
&& (! seen_undef || is_gimple_min_invariant (sameval)))
|
|
result = sameval;
|
|
else
|
|
{
|
|
result = PHI_RESULT (phi);
|
|
/* Only insert PHIs that are varying, for constant value numbers
|
|
we mess up equivalences otherwise as we are only comparing
|
|
the immediate controlling predicates. */
|
|
vn_phi_insert (phi, result);
|
|
}
|
|
|
|
return set_ssa_val_to (PHI_RESULT (phi), result);
|
|
}
|
|
|
|
/* Try to simplify RHS using equivalences and constant folding. */
|
|
|
|
static tree
|
|
try_to_simplify (gassign *stmt)
|
|
{
|
|
enum tree_code code = gimple_assign_rhs_code (stmt);
|
|
tree tem;
|
|
|
|
/* For stores we can end up simplifying a SSA_NAME rhs. Just return
|
|
in this case, there is no point in doing extra work. */
|
|
if (code == SSA_NAME)
|
|
return NULL_TREE;
|
|
|
|
/* First try constant folding based on our current lattice. */
|
|
mprts_hook = vn_lookup_simplify_result;
|
|
mprts_hook_cnt = 9;
|
|
tem = gimple_fold_stmt_to_constant_1 (stmt, vn_valueize, vn_valueize);
|
|
mprts_hook = NULL;
|
|
if (tem
|
|
&& (TREE_CODE (tem) == SSA_NAME
|
|
|| is_gimple_min_invariant (tem)))
|
|
return tem;
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Visit and value number USE, return true if the value number
|
|
changed. */
|
|
|
|
static bool
|
|
visit_use (tree use)
|
|
{
|
|
bool changed = false;
|
|
gimple *stmt = SSA_NAME_DEF_STMT (use);
|
|
|
|
mark_use_processed (use);
|
|
|
|
gcc_assert (!SSA_NAME_IN_FREE_LIST (use)
|
|
&& !SSA_NAME_IS_DEFAULT_DEF (use));
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Value numbering ");
|
|
print_generic_expr (dump_file, use);
|
|
fprintf (dump_file, " stmt = ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
if (gimple_code (stmt) == GIMPLE_PHI)
|
|
changed = visit_phi (stmt);
|
|
else if (gimple_has_volatile_ops (stmt))
|
|
changed = defs_to_varying (stmt);
|
|
else if (gassign *ass = dyn_cast <gassign *> (stmt))
|
|
{
|
|
enum tree_code code = gimple_assign_rhs_code (ass);
|
|
tree lhs = gimple_assign_lhs (ass);
|
|
tree rhs1 = gimple_assign_rhs1 (ass);
|
|
tree simplified;
|
|
|
|
/* Shortcut for copies. Simplifying copies is pointless,
|
|
since we copy the expression and value they represent. */
|
|
if (code == SSA_NAME
|
|
&& TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
changed = visit_copy (lhs, rhs1);
|
|
goto done;
|
|
}
|
|
simplified = try_to_simplify (ass);
|
|
if (simplified)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "RHS ");
|
|
print_gimple_expr (dump_file, ass, 0);
|
|
fprintf (dump_file, " simplified to ");
|
|
print_generic_expr (dump_file, simplified);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
/* Setting value numbers to constants will occasionally
|
|
screw up phi congruence because constants are not
|
|
uniquely associated with a single ssa name that can be
|
|
looked up. */
|
|
if (simplified
|
|
&& is_gimple_min_invariant (simplified)
|
|
&& TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
changed = set_ssa_val_to (lhs, simplified);
|
|
goto done;
|
|
}
|
|
else if (simplified
|
|
&& TREE_CODE (simplified) == SSA_NAME
|
|
&& TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
changed = visit_copy (lhs, simplified);
|
|
goto done;
|
|
}
|
|
|
|
if ((TREE_CODE (lhs) == SSA_NAME
|
|
/* We can substitute SSA_NAMEs that are live over
|
|
abnormal edges with their constant value. */
|
|
&& !(gimple_assign_copy_p (ass)
|
|
&& is_gimple_min_invariant (rhs1))
|
|
&& !(simplified
|
|
&& is_gimple_min_invariant (simplified))
|
|
&& SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs))
|
|
/* Stores or copies from SSA_NAMEs that are live over
|
|
abnormal edges are a problem. */
|
|
|| (code == SSA_NAME
|
|
&& SSA_NAME_OCCURS_IN_ABNORMAL_PHI (rhs1)))
|
|
changed = defs_to_varying (ass);
|
|
else if (REFERENCE_CLASS_P (lhs)
|
|
|| DECL_P (lhs))
|
|
changed = visit_reference_op_store (lhs, rhs1, ass);
|
|
else if (TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
if ((gimple_assign_copy_p (ass)
|
|
&& is_gimple_min_invariant (rhs1))
|
|
|| (simplified
|
|
&& is_gimple_min_invariant (simplified)))
|
|
{
|
|
if (simplified)
|
|
changed = set_ssa_val_to (lhs, simplified);
|
|
else
|
|
changed = set_ssa_val_to (lhs, rhs1);
|
|
}
|
|
else
|
|
{
|
|
/* Visit the original statement. */
|
|
switch (vn_get_stmt_kind (ass))
|
|
{
|
|
case VN_NARY:
|
|
changed = visit_nary_op (lhs, ass);
|
|
break;
|
|
case VN_REFERENCE:
|
|
changed = visit_reference_op_load (lhs, rhs1, ass);
|
|
break;
|
|
default:
|
|
changed = defs_to_varying (ass);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
changed = defs_to_varying (ass);
|
|
}
|
|
else if (gcall *call_stmt = dyn_cast <gcall *> (stmt))
|
|
{
|
|
tree lhs = gimple_call_lhs (call_stmt);
|
|
if (lhs && TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
/* Try constant folding based on our current lattice. */
|
|
tree simplified = gimple_fold_stmt_to_constant_1 (call_stmt,
|
|
vn_valueize);
|
|
if (simplified)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "call ");
|
|
print_gimple_expr (dump_file, call_stmt, 0);
|
|
fprintf (dump_file, " simplified to ");
|
|
print_generic_expr (dump_file, simplified);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
/* Setting value numbers to constants will occasionally
|
|
screw up phi congruence because constants are not
|
|
uniquely associated with a single ssa name that can be
|
|
looked up. */
|
|
if (simplified
|
|
&& is_gimple_min_invariant (simplified))
|
|
{
|
|
changed = set_ssa_val_to (lhs, simplified);
|
|
if (gimple_vdef (call_stmt))
|
|
changed |= set_ssa_val_to (gimple_vdef (call_stmt),
|
|
SSA_VAL (gimple_vuse (call_stmt)));
|
|
goto done;
|
|
}
|
|
else if (simplified
|
|
&& TREE_CODE (simplified) == SSA_NAME)
|
|
{
|
|
changed = visit_copy (lhs, simplified);
|
|
if (gimple_vdef (call_stmt))
|
|
changed |= set_ssa_val_to (gimple_vdef (call_stmt),
|
|
SSA_VAL (gimple_vuse (call_stmt)));
|
|
goto done;
|
|
}
|
|
else if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (lhs))
|
|
{
|
|
changed = defs_to_varying (call_stmt);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* Pick up flags from a devirtualization target. */
|
|
tree fn = gimple_call_fn (stmt);
|
|
int extra_fnflags = 0;
|
|
if (fn && TREE_CODE (fn) == SSA_NAME)
|
|
{
|
|
fn = SSA_VAL (fn);
|
|
if (TREE_CODE (fn) == ADDR_EXPR
|
|
&& TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL)
|
|
extra_fnflags = flags_from_decl_or_type (TREE_OPERAND (fn, 0));
|
|
}
|
|
if (!gimple_call_internal_p (call_stmt)
|
|
&& (/* Calls to the same function with the same vuse
|
|
and the same operands do not necessarily return the same
|
|
value, unless they're pure or const. */
|
|
((gimple_call_flags (call_stmt) | extra_fnflags)
|
|
& (ECF_PURE | ECF_CONST))
|
|
/* If calls have a vdef, subsequent calls won't have
|
|
the same incoming vuse. So, if 2 calls with vdef have the
|
|
same vuse, we know they're not subsequent.
|
|
We can value number 2 calls to the same function with the
|
|
same vuse and the same operands which are not subsequent
|
|
the same, because there is no code in the program that can
|
|
compare the 2 values... */
|
|
|| (gimple_vdef (call_stmt)
|
|
/* ... unless the call returns a pointer which does
|
|
not alias with anything else. In which case the
|
|
information that the values are distinct are encoded
|
|
in the IL. */
|
|
&& !(gimple_call_return_flags (call_stmt) & ERF_NOALIAS)
|
|
/* Only perform the following when being called from PRE
|
|
which embeds tail merging. */
|
|
&& default_vn_walk_kind == VN_WALK)))
|
|
changed = visit_reference_op_call (lhs, call_stmt);
|
|
else
|
|
changed = defs_to_varying (call_stmt);
|
|
}
|
|
else
|
|
changed = defs_to_varying (stmt);
|
|
done:
|
|
return changed;
|
|
}
|
|
|
|
/* Compare two operands by reverse postorder index */
|
|
|
|
static int
|
|
compare_ops (const void *pa, const void *pb)
|
|
{
|
|
const tree opa = *((const tree *)pa);
|
|
const tree opb = *((const tree *)pb);
|
|
gimple *opstmta = SSA_NAME_DEF_STMT (opa);
|
|
gimple *opstmtb = SSA_NAME_DEF_STMT (opb);
|
|
basic_block bba;
|
|
basic_block bbb;
|
|
|
|
if (gimple_nop_p (opstmta) && gimple_nop_p (opstmtb))
|
|
return SSA_NAME_VERSION (opa) - SSA_NAME_VERSION (opb);
|
|
else if (gimple_nop_p (opstmta))
|
|
return -1;
|
|
else if (gimple_nop_p (opstmtb))
|
|
return 1;
|
|
|
|
bba = gimple_bb (opstmta);
|
|
bbb = gimple_bb (opstmtb);
|
|
|
|
if (!bba && !bbb)
|
|
return SSA_NAME_VERSION (opa) - SSA_NAME_VERSION (opb);
|
|
else if (!bba)
|
|
return -1;
|
|
else if (!bbb)
|
|
return 1;
|
|
|
|
if (bba == bbb)
|
|
{
|
|
if (gimple_code (opstmta) == GIMPLE_PHI
|
|
&& gimple_code (opstmtb) == GIMPLE_PHI)
|
|
return SSA_NAME_VERSION (opa) - SSA_NAME_VERSION (opb);
|
|
else if (gimple_code (opstmta) == GIMPLE_PHI)
|
|
return -1;
|
|
else if (gimple_code (opstmtb) == GIMPLE_PHI)
|
|
return 1;
|
|
else if (gimple_uid (opstmta) != gimple_uid (opstmtb))
|
|
return gimple_uid (opstmta) - gimple_uid (opstmtb);
|
|
else
|
|
return SSA_NAME_VERSION (opa) - SSA_NAME_VERSION (opb);
|
|
}
|
|
return rpo_numbers[bba->index] - rpo_numbers[bbb->index];
|
|
}
|
|
|
|
/* Sort an array containing members of a strongly connected component
|
|
SCC so that the members are ordered by RPO number.
|
|
This means that when the sort is complete, iterating through the
|
|
array will give you the members in RPO order. */
|
|
|
|
static void
|
|
sort_scc (vec<tree> scc)
|
|
{
|
|
scc.qsort (compare_ops);
|
|
}
|
|
|
|
/* Insert the no longer used nary ONARY to the hash INFO. */
|
|
|
|
static void
|
|
copy_nary (vn_nary_op_t onary, vn_tables_t info)
|
|
{
|
|
size_t size = sizeof_vn_nary_op (onary->length);
|
|
vn_nary_op_t nary = alloc_vn_nary_op_noinit (onary->length,
|
|
&info->nary_obstack);
|
|
memcpy (nary, onary, size);
|
|
vn_nary_op_insert_into (nary, info->nary, false);
|
|
}
|
|
|
|
/* Insert the no longer used phi OPHI to the hash INFO. */
|
|
|
|
static void
|
|
copy_phi (vn_phi_t ophi, vn_tables_t info)
|
|
{
|
|
vn_phi_t phi = info->phis_pool->allocate ();
|
|
vn_phi_s **slot;
|
|
memcpy (phi, ophi, sizeof (*phi));
|
|
ophi->phiargs.create (0);
|
|
slot = info->phis->find_slot_with_hash (phi, phi->hashcode, INSERT);
|
|
gcc_assert (!*slot);
|
|
*slot = phi;
|
|
}
|
|
|
|
/* Insert the no longer used reference OREF to the hash INFO. */
|
|
|
|
static void
|
|
copy_reference (vn_reference_t oref, vn_tables_t info)
|
|
{
|
|
vn_reference_t ref;
|
|
vn_reference_s **slot;
|
|
ref = info->references_pool->allocate ();
|
|
memcpy (ref, oref, sizeof (*ref));
|
|
oref->operands.create (0);
|
|
slot = info->references->find_slot_with_hash (ref, ref->hashcode, INSERT);
|
|
if (*slot)
|
|
free_reference (*slot);
|
|
*slot = ref;
|
|
}
|
|
|
|
/* Process a strongly connected component in the SSA graph. */
|
|
|
|
static void
|
|
process_scc (vec<tree> scc)
|
|
{
|
|
tree var;
|
|
unsigned int i;
|
|
unsigned int iterations = 0;
|
|
bool changed = true;
|
|
vn_nary_op_iterator_type hin;
|
|
vn_phi_iterator_type hip;
|
|
vn_reference_iterator_type hir;
|
|
vn_nary_op_t nary;
|
|
vn_phi_t phi;
|
|
vn_reference_t ref;
|
|
|
|
/* If the SCC has a single member, just visit it. */
|
|
if (scc.length () == 1)
|
|
{
|
|
tree use = scc[0];
|
|
if (VN_INFO (use)->use_processed)
|
|
return;
|
|
/* We need to make sure it doesn't form a cycle itself, which can
|
|
happen for self-referential PHI nodes. In that case we would
|
|
end up inserting an expression with VN_TOP operands into the
|
|
valid table which makes us derive bogus equivalences later.
|
|
The cheapest way to check this is to assume it for all PHI nodes. */
|
|
if (gimple_code (SSA_NAME_DEF_STMT (use)) == GIMPLE_PHI)
|
|
/* Fallthru to iteration. */ ;
|
|
else
|
|
{
|
|
visit_use (use);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
print_scc (dump_file, scc);
|
|
|
|
/* Iterate over the SCC with the optimistic table until it stops
|
|
changing. */
|
|
current_info = optimistic_info;
|
|
while (changed)
|
|
{
|
|
changed = false;
|
|
iterations++;
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Starting iteration %d\n", iterations);
|
|
/* As we are value-numbering optimistically we have to
|
|
clear the expression tables and the simplified expressions
|
|
in each iteration until we converge. */
|
|
optimistic_info->nary->empty ();
|
|
optimistic_info->phis->empty ();
|
|
optimistic_info->references->empty ();
|
|
obstack_free (&optimistic_info->nary_obstack, NULL);
|
|
gcc_obstack_init (&optimistic_info->nary_obstack);
|
|
optimistic_info->phis_pool->release ();
|
|
optimistic_info->references_pool->release ();
|
|
FOR_EACH_VEC_ELT (scc, i, var)
|
|
gcc_assert (!VN_INFO (var)->needs_insertion
|
|
&& VN_INFO (var)->expr == NULL);
|
|
FOR_EACH_VEC_ELT (scc, i, var)
|
|
changed |= visit_use (var);
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Processing SCC needed %d iterations\n", iterations);
|
|
statistics_histogram_event (cfun, "SCC iterations", iterations);
|
|
|
|
/* Finally, copy the contents of the no longer used optimistic
|
|
table to the valid table. */
|
|
FOR_EACH_HASH_TABLE_ELEMENT (*optimistic_info->nary, nary, vn_nary_op_t, hin)
|
|
copy_nary (nary, valid_info);
|
|
FOR_EACH_HASH_TABLE_ELEMENT (*optimistic_info->phis, phi, vn_phi_t, hip)
|
|
copy_phi (phi, valid_info);
|
|
FOR_EACH_HASH_TABLE_ELEMENT (*optimistic_info->references,
|
|
ref, vn_reference_t, hir)
|
|
copy_reference (ref, valid_info);
|
|
|
|
current_info = valid_info;
|
|
}
|
|
|
|
|
|
/* Pop the components of the found SCC for NAME off the SCC stack
|
|
and process them. Returns true if all went well, false if
|
|
we run into resource limits. */
|
|
|
|
static void
|
|
extract_and_process_scc_for_name (tree name)
|
|
{
|
|
auto_vec<tree> scc;
|
|
tree x;
|
|
|
|
/* Found an SCC, pop the components off the SCC stack and
|
|
process them. */
|
|
do
|
|
{
|
|
x = sccstack.pop ();
|
|
|
|
VN_INFO (x)->on_sccstack = false;
|
|
scc.safe_push (x);
|
|
} while (x != name);
|
|
|
|
/* Drop all defs in the SCC to varying in case a SCC turns out to be
|
|
incredibly large.
|
|
??? Just switch to a non-optimistic mode that avoids any iteration. */
|
|
if (scc.length () > (unsigned)PARAM_VALUE (PARAM_SCCVN_MAX_SCC_SIZE))
|
|
{
|
|
if (dump_file)
|
|
{
|
|
print_scc (dump_file, scc);
|
|
fprintf (dump_file, "WARNING: Giving up value-numbering SCC due to "
|
|
"size %u exceeding %u\n", scc.length (),
|
|
(unsigned)PARAM_VALUE (PARAM_SCCVN_MAX_SCC_SIZE));
|
|
}
|
|
tree var;
|
|
unsigned i;
|
|
FOR_EACH_VEC_ELT (scc, i, var)
|
|
{
|
|
gimple *def = SSA_NAME_DEF_STMT (var);
|
|
mark_use_processed (var);
|
|
if (SSA_NAME_IS_DEFAULT_DEF (var)
|
|
|| gimple_code (def) == GIMPLE_PHI)
|
|
set_ssa_val_to (var, var);
|
|
else
|
|
defs_to_varying (def);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (scc.length () > 1)
|
|
sort_scc (scc);
|
|
|
|
process_scc (scc);
|
|
}
|
|
|
|
/* Depth first search on NAME to discover and process SCC's in the SSA
|
|
graph.
|
|
Execution of this algorithm relies on the fact that the SCC's are
|
|
popped off the stack in topological order.
|
|
Returns true if successful, false if we stopped processing SCC's due
|
|
to resource constraints. */
|
|
|
|
static void
|
|
DFS (tree name)
|
|
{
|
|
auto_vec<ssa_op_iter> itervec;
|
|
auto_vec<tree> namevec;
|
|
use_operand_p usep = NULL;
|
|
gimple *defstmt;
|
|
tree use;
|
|
ssa_op_iter iter;
|
|
|
|
start_over:
|
|
/* SCC info */
|
|
VN_INFO (name)->dfsnum = next_dfs_num++;
|
|
VN_INFO (name)->visited = true;
|
|
VN_INFO (name)->low = VN_INFO (name)->dfsnum;
|
|
|
|
sccstack.safe_push (name);
|
|
VN_INFO (name)->on_sccstack = true;
|
|
defstmt = SSA_NAME_DEF_STMT (name);
|
|
|
|
/* Recursively DFS on our operands, looking for SCC's. */
|
|
if (!gimple_nop_p (defstmt))
|
|
{
|
|
/* Push a new iterator. */
|
|
if (gphi *phi = dyn_cast <gphi *> (defstmt))
|
|
usep = op_iter_init_phiuse (&iter, phi, SSA_OP_ALL_USES);
|
|
else
|
|
usep = op_iter_init_use (&iter, defstmt, SSA_OP_ALL_USES);
|
|
}
|
|
else
|
|
clear_and_done_ssa_iter (&iter);
|
|
|
|
while (1)
|
|
{
|
|
/* If we are done processing uses of a name, go up the stack
|
|
of iterators and process SCCs as we found them. */
|
|
if (op_iter_done (&iter))
|
|
{
|
|
/* See if we found an SCC. */
|
|
if (VN_INFO (name)->low == VN_INFO (name)->dfsnum)
|
|
extract_and_process_scc_for_name (name);
|
|
|
|
/* Check if we are done. */
|
|
if (namevec.is_empty ())
|
|
return;
|
|
|
|
/* Restore the last use walker and continue walking there. */
|
|
use = name;
|
|
name = namevec.pop ();
|
|
memcpy (&iter, &itervec.last (),
|
|
sizeof (ssa_op_iter));
|
|
itervec.pop ();
|
|
goto continue_walking;
|
|
}
|
|
|
|
use = USE_FROM_PTR (usep);
|
|
|
|
/* Since we handle phi nodes, we will sometimes get
|
|
invariants in the use expression. */
|
|
if (TREE_CODE (use) == SSA_NAME)
|
|
{
|
|
if (! (VN_INFO (use)->visited))
|
|
{
|
|
/* Recurse by pushing the current use walking state on
|
|
the stack and starting over. */
|
|
itervec.safe_push (iter);
|
|
namevec.safe_push (name);
|
|
name = use;
|
|
goto start_over;
|
|
|
|
continue_walking:
|
|
VN_INFO (name)->low = MIN (VN_INFO (name)->low,
|
|
VN_INFO (use)->low);
|
|
}
|
|
if (VN_INFO (use)->dfsnum < VN_INFO (name)->dfsnum
|
|
&& VN_INFO (use)->on_sccstack)
|
|
{
|
|
VN_INFO (name)->low = MIN (VN_INFO (use)->dfsnum,
|
|
VN_INFO (name)->low);
|
|
}
|
|
}
|
|
|
|
usep = op_iter_next_use (&iter);
|
|
}
|
|
}
|
|
|
|
/* Allocate a value number table. */
|
|
|
|
static void
|
|
allocate_vn_table (vn_tables_t table)
|
|
{
|
|
table->phis = new vn_phi_table_type (23);
|
|
table->nary = new vn_nary_op_table_type (23);
|
|
table->references = new vn_reference_table_type (23);
|
|
|
|
gcc_obstack_init (&table->nary_obstack);
|
|
table->phis_pool = new object_allocator<vn_phi_s> ("VN phis");
|
|
table->references_pool = new object_allocator<vn_reference_s>
|
|
("VN references");
|
|
}
|
|
|
|
/* Free a value number table. */
|
|
|
|
static void
|
|
free_vn_table (vn_tables_t table)
|
|
{
|
|
delete table->phis;
|
|
table->phis = NULL;
|
|
delete table->nary;
|
|
table->nary = NULL;
|
|
delete table->references;
|
|
table->references = NULL;
|
|
obstack_free (&table->nary_obstack, NULL);
|
|
delete table->phis_pool;
|
|
delete table->references_pool;
|
|
}
|
|
|
|
static void
|
|
init_scc_vn (void)
|
|
{
|
|
int j;
|
|
int *rpo_numbers_temp;
|
|
|
|
calculate_dominance_info (CDI_DOMINATORS);
|
|
mark_dfs_back_edges ();
|
|
|
|
sccstack.create (0);
|
|
constant_to_value_id = new hash_table<vn_constant_hasher> (23);
|
|
|
|
constant_value_ids = BITMAP_ALLOC (NULL);
|
|
|
|
next_dfs_num = 1;
|
|
next_value_id = 1;
|
|
|
|
vn_ssa_aux_table.create (num_ssa_names + 1);
|
|
/* VEC_alloc doesn't actually grow it to the right size, it just
|
|
preallocates the space to do so. */
|
|
vn_ssa_aux_table.safe_grow_cleared (num_ssa_names + 1);
|
|
gcc_obstack_init (&vn_ssa_aux_obstack);
|
|
|
|
shared_lookup_phiargs.create (0);
|
|
shared_lookup_references.create (0);
|
|
rpo_numbers = XNEWVEC (int, last_basic_block_for_fn (cfun));
|
|
rpo_numbers_temp =
|
|
XNEWVEC (int, n_basic_blocks_for_fn (cfun) - NUM_FIXED_BLOCKS);
|
|
pre_and_rev_post_order_compute (NULL, rpo_numbers_temp, false);
|
|
|
|
/* RPO numbers is an array of rpo ordering, rpo[i] = bb means that
|
|
the i'th block in RPO order is bb. We want to map bb's to RPO
|
|
numbers, so we need to rearrange this array. */
|
|
for (j = 0; j < n_basic_blocks_for_fn (cfun) - NUM_FIXED_BLOCKS; j++)
|
|
rpo_numbers[rpo_numbers_temp[j]] = j;
|
|
|
|
XDELETE (rpo_numbers_temp);
|
|
|
|
VN_TOP = build_decl (UNKNOWN_LOCATION, VAR_DECL,
|
|
get_identifier ("VN_TOP"), error_mark_node);
|
|
|
|
renumber_gimple_stmt_uids ();
|
|
|
|
/* Create the valid and optimistic value numbering tables. */
|
|
valid_info = XCNEW (struct vn_tables_s);
|
|
allocate_vn_table (valid_info);
|
|
optimistic_info = XCNEW (struct vn_tables_s);
|
|
allocate_vn_table (optimistic_info);
|
|
current_info = valid_info;
|
|
|
|
/* Create the VN_INFO structures, and initialize value numbers to
|
|
TOP or VARYING for parameters. */
|
|
size_t i;
|
|
tree name;
|
|
|
|
FOR_EACH_SSA_NAME (i, name, cfun)
|
|
{
|
|
VN_INFO_GET (name)->valnum = VN_TOP;
|
|
VN_INFO (name)->needs_insertion = false;
|
|
VN_INFO (name)->expr = NULL;
|
|
VN_INFO (name)->value_id = 0;
|
|
|
|
if (!SSA_NAME_IS_DEFAULT_DEF (name))
|
|
continue;
|
|
|
|
switch (TREE_CODE (SSA_NAME_VAR (name)))
|
|
{
|
|
case VAR_DECL:
|
|
/* All undefined vars are VARYING. */
|
|
VN_INFO (name)->valnum = name;
|
|
VN_INFO (name)->visited = true;
|
|
break;
|
|
|
|
case PARM_DECL:
|
|
/* Parameters are VARYING but we can record a condition
|
|
if we know it is a non-NULL pointer. */
|
|
VN_INFO (name)->visited = true;
|
|
VN_INFO (name)->valnum = name;
|
|
if (POINTER_TYPE_P (TREE_TYPE (name))
|
|
&& nonnull_arg_p (SSA_NAME_VAR (name)))
|
|
{
|
|
tree ops[2];
|
|
ops[0] = name;
|
|
ops[1] = build_int_cst (TREE_TYPE (name), 0);
|
|
vn_nary_op_insert_pieces (2, NE_EXPR, boolean_type_node, ops,
|
|
boolean_true_node, 0);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Recording ");
|
|
print_generic_expr (dump_file, name, TDF_SLIM);
|
|
fprintf (dump_file, " != 0\n");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RESULT_DECL:
|
|
/* If the result is passed by invisible reference the default
|
|
def is initialized, otherwise it's uninitialized. Still
|
|
undefined is varying. */
|
|
VN_INFO (name)->visited = true;
|
|
VN_INFO (name)->valnum = name;
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Restore SSA info that has been reset on value leaders. */
|
|
|
|
void
|
|
scc_vn_restore_ssa_info (void)
|
|
{
|
|
unsigned i;
|
|
tree name;
|
|
|
|
FOR_EACH_SSA_NAME (i, name, cfun)
|
|
{
|
|
if (has_VN_INFO (name))
|
|
{
|
|
if (VN_INFO (name)->needs_insertion)
|
|
;
|
|
else if (POINTER_TYPE_P (TREE_TYPE (name))
|
|
&& VN_INFO (name)->info.ptr_info)
|
|
SSA_NAME_PTR_INFO (name) = VN_INFO (name)->info.ptr_info;
|
|
else if (INTEGRAL_TYPE_P (TREE_TYPE (name))
|
|
&& VN_INFO (name)->info.range_info)
|
|
{
|
|
SSA_NAME_RANGE_INFO (name) = VN_INFO (name)->info.range_info;
|
|
SSA_NAME_ANTI_RANGE_P (name)
|
|
= VN_INFO (name)->range_info_anti_range_p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
free_scc_vn (void)
|
|
{
|
|
size_t i;
|
|
tree name;
|
|
|
|
delete constant_to_value_id;
|
|
constant_to_value_id = NULL;
|
|
BITMAP_FREE (constant_value_ids);
|
|
shared_lookup_phiargs.release ();
|
|
shared_lookup_references.release ();
|
|
XDELETEVEC (rpo_numbers);
|
|
|
|
FOR_EACH_SSA_NAME (i, name, cfun)
|
|
{
|
|
if (has_VN_INFO (name)
|
|
&& VN_INFO (name)->needs_insertion)
|
|
release_ssa_name (name);
|
|
}
|
|
obstack_free (&vn_ssa_aux_obstack, NULL);
|
|
vn_ssa_aux_table.release ();
|
|
|
|
sccstack.release ();
|
|
free_vn_table (valid_info);
|
|
XDELETE (valid_info);
|
|
free_vn_table (optimistic_info);
|
|
XDELETE (optimistic_info);
|
|
|
|
BITMAP_FREE (const_parms);
|
|
}
|
|
|
|
/* Set *ID according to RESULT. */
|
|
|
|
static void
|
|
set_value_id_for_result (tree result, unsigned int *id)
|
|
{
|
|
if (result && TREE_CODE (result) == SSA_NAME)
|
|
*id = VN_INFO (result)->value_id;
|
|
else if (result && is_gimple_min_invariant (result))
|
|
*id = get_or_alloc_constant_value_id (result);
|
|
else
|
|
*id = get_next_value_id ();
|
|
}
|
|
|
|
/* Set the value ids in the valid hash tables. */
|
|
|
|
static void
|
|
set_hashtable_value_ids (void)
|
|
{
|
|
vn_nary_op_iterator_type hin;
|
|
vn_phi_iterator_type hip;
|
|
vn_reference_iterator_type hir;
|
|
vn_nary_op_t vno;
|
|
vn_reference_t vr;
|
|
vn_phi_t vp;
|
|
|
|
/* Now set the value ids of the things we had put in the hash
|
|
table. */
|
|
|
|
FOR_EACH_HASH_TABLE_ELEMENT (*valid_info->nary, vno, vn_nary_op_t, hin)
|
|
set_value_id_for_result (vno->result, &vno->value_id);
|
|
|
|
FOR_EACH_HASH_TABLE_ELEMENT (*valid_info->phis, vp, vn_phi_t, hip)
|
|
set_value_id_for_result (vp->result, &vp->value_id);
|
|
|
|
FOR_EACH_HASH_TABLE_ELEMENT (*valid_info->references, vr, vn_reference_t,
|
|
hir)
|
|
set_value_id_for_result (vr->result, &vr->value_id);
|
|
}
|
|
|
|
class sccvn_dom_walker : public dom_walker
|
|
{
|
|
public:
|
|
sccvn_dom_walker ()
|
|
: dom_walker (CDI_DOMINATORS, REACHABLE_BLOCKS), cond_stack (0) {}
|
|
|
|
virtual edge before_dom_children (basic_block);
|
|
virtual void after_dom_children (basic_block);
|
|
|
|
void record_cond (basic_block,
|
|
enum tree_code code, tree lhs, tree rhs, bool value);
|
|
void record_conds (basic_block,
|
|
enum tree_code code, tree lhs, tree rhs, bool value);
|
|
|
|
auto_vec<std::pair <basic_block, std::pair <vn_nary_op_t, vn_nary_op_t> > >
|
|
cond_stack;
|
|
};
|
|
|
|
/* Record a temporary condition for the BB and its dominated blocks. */
|
|
|
|
void
|
|
sccvn_dom_walker::record_cond (basic_block bb,
|
|
enum tree_code code, tree lhs, tree rhs,
|
|
bool value)
|
|
{
|
|
tree ops[2] = { lhs, rhs };
|
|
vn_nary_op_t old = NULL;
|
|
if (vn_nary_op_lookup_pieces (2, code, boolean_type_node, ops, &old))
|
|
current_info->nary->remove_elt_with_hash (old, old->hashcode);
|
|
vn_nary_op_t cond
|
|
= vn_nary_op_insert_pieces (2, code, boolean_type_node, ops,
|
|
value
|
|
? boolean_true_node
|
|
: boolean_false_node, 0);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Recording temporarily ");
|
|
print_generic_expr (dump_file, ops[0], TDF_SLIM);
|
|
fprintf (dump_file, " %s ", get_tree_code_name (code));
|
|
print_generic_expr (dump_file, ops[1], TDF_SLIM);
|
|
fprintf (dump_file, " == %s%s\n",
|
|
value ? "true" : "false",
|
|
old ? " (old entry saved)" : "");
|
|
}
|
|
cond_stack.safe_push (std::make_pair (bb, std::make_pair (cond, old)));
|
|
}
|
|
|
|
/* Record temporary conditions for the BB and its dominated blocks
|
|
according to LHS CODE RHS == VALUE and its dominated conditions. */
|
|
|
|
void
|
|
sccvn_dom_walker::record_conds (basic_block bb,
|
|
enum tree_code code, tree lhs, tree rhs,
|
|
bool value)
|
|
{
|
|
/* Record the original condition. */
|
|
record_cond (bb, code, lhs, rhs, value);
|
|
|
|
if (!value)
|
|
return;
|
|
|
|
/* Record dominated conditions if the condition is true. Note that
|
|
the inversion is already recorded. */
|
|
switch (code)
|
|
{
|
|
case LT_EXPR:
|
|
case GT_EXPR:
|
|
record_cond (bb, code == LT_EXPR ? LE_EXPR : GE_EXPR, lhs, rhs, true);
|
|
record_cond (bb, NE_EXPR, lhs, rhs, true);
|
|
record_cond (bb, EQ_EXPR, lhs, rhs, false);
|
|
break;
|
|
|
|
case EQ_EXPR:
|
|
record_cond (bb, LE_EXPR, lhs, rhs, true);
|
|
record_cond (bb, GE_EXPR, lhs, rhs, true);
|
|
record_cond (bb, LT_EXPR, lhs, rhs, false);
|
|
record_cond (bb, GT_EXPR, lhs, rhs, false);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Restore expressions and values derived from conditionals. */
|
|
|
|
void
|
|
sccvn_dom_walker::after_dom_children (basic_block bb)
|
|
{
|
|
while (!cond_stack.is_empty ()
|
|
&& cond_stack.last ().first == bb)
|
|
{
|
|
vn_nary_op_t cond = cond_stack.last ().second.first;
|
|
vn_nary_op_t old = cond_stack.last ().second.second;
|
|
current_info->nary->remove_elt_with_hash (cond, cond->hashcode);
|
|
if (old)
|
|
vn_nary_op_insert_into (old, current_info->nary, false);
|
|
cond_stack.pop ();
|
|
}
|
|
}
|
|
|
|
/* Value number all statements in BB. */
|
|
|
|
edge
|
|
sccvn_dom_walker::before_dom_children (basic_block bb)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Visiting BB %d\n", bb->index);
|
|
|
|
/* If we have a single predecessor record the equivalence from a
|
|
possible condition on the predecessor edge. */
|
|
edge pred_e = single_pred_edge_ignoring_loop_edges (bb, false);
|
|
if (pred_e)
|
|
{
|
|
/* Check if there are multiple executable successor edges in
|
|
the source block. Otherwise there is no additional info
|
|
to be recorded. */
|
|
edge_iterator ei;
|
|
edge e2;
|
|
FOR_EACH_EDGE (e2, ei, pred_e->src->succs)
|
|
if (e2 != pred_e
|
|
&& e2->flags & EDGE_EXECUTABLE)
|
|
break;
|
|
if (e2 && (e2->flags & EDGE_EXECUTABLE))
|
|
{
|
|
gimple *stmt = last_stmt (pred_e->src);
|
|
if (stmt
|
|
&& gimple_code (stmt) == GIMPLE_COND)
|
|
{
|
|
enum tree_code code = gimple_cond_code (stmt);
|
|
tree lhs = gimple_cond_lhs (stmt);
|
|
tree rhs = gimple_cond_rhs (stmt);
|
|
record_conds (bb, code, lhs, rhs,
|
|
(pred_e->flags & EDGE_TRUE_VALUE) != 0);
|
|
code = invert_tree_comparison (code, HONOR_NANS (lhs));
|
|
if (code != ERROR_MARK)
|
|
record_conds (bb, code, lhs, rhs,
|
|
(pred_e->flags & EDGE_TRUE_VALUE) == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Value-number all defs in the basic-block. */
|
|
for (gphi_iterator gsi = gsi_start_phis (bb);
|
|
!gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gphi *phi = gsi.phi ();
|
|
tree res = PHI_RESULT (phi);
|
|
if (!VN_INFO (res)->visited)
|
|
DFS (res);
|
|
}
|
|
for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
|
|
!gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
ssa_op_iter i;
|
|
tree op;
|
|
FOR_EACH_SSA_TREE_OPERAND (op, gsi_stmt (gsi), i, SSA_OP_ALL_DEFS)
|
|
if (!VN_INFO (op)->visited)
|
|
DFS (op);
|
|
}
|
|
|
|
/* Finally look at the last stmt. */
|
|
gimple *stmt = last_stmt (bb);
|
|
if (!stmt)
|
|
return NULL;
|
|
|
|
enum gimple_code code = gimple_code (stmt);
|
|
if (code != GIMPLE_COND
|
|
&& code != GIMPLE_SWITCH
|
|
&& code != GIMPLE_GOTO)
|
|
return NULL;
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Visiting control stmt ending BB %d: ", bb->index);
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
/* ??? We can even handle stmts with outgoing EH or ABNORMAL edges
|
|
if value-numbering can prove they are not reachable. Handling
|
|
computed gotos is also possible. */
|
|
tree val;
|
|
switch (code)
|
|
{
|
|
case GIMPLE_COND:
|
|
{
|
|
tree lhs = vn_valueize (gimple_cond_lhs (stmt));
|
|
tree rhs = vn_valueize (gimple_cond_rhs (stmt));
|
|
val = gimple_simplify (gimple_cond_code (stmt),
|
|
boolean_type_node, lhs, rhs,
|
|
NULL, vn_valueize);
|
|
/* If that didn't simplify to a constant see if we have recorded
|
|
temporary expressions from taken edges. */
|
|
if (!val || TREE_CODE (val) != INTEGER_CST)
|
|
{
|
|
tree ops[2];
|
|
ops[0] = lhs;
|
|
ops[1] = rhs;
|
|
val = vn_nary_op_lookup_pieces (2, gimple_cond_code (stmt),
|
|
boolean_type_node, ops, NULL);
|
|
}
|
|
break;
|
|
}
|
|
case GIMPLE_SWITCH:
|
|
val = gimple_switch_index (as_a <gswitch *> (stmt));
|
|
break;
|
|
case GIMPLE_GOTO:
|
|
val = gimple_goto_dest (stmt);
|
|
break;
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
if (!val)
|
|
return NULL;
|
|
|
|
edge taken = find_taken_edge (bb, vn_valueize (val));
|
|
if (!taken)
|
|
return NULL;
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Marking all edges out of BB %d but (%d -> %d) as "
|
|
"not executable\n", bb->index, bb->index, taken->dest->index);
|
|
|
|
return taken;
|
|
}
|
|
|
|
/* Do SCCVN. Returns true if it finished, false if we bailed out
|
|
due to resource constraints. DEFAULT_VN_WALK_KIND_ specifies
|
|
how we use the alias oracle walking during the VN process. */
|
|
|
|
void
|
|
run_scc_vn (vn_lookup_kind default_vn_walk_kind_)
|
|
{
|
|
size_t i;
|
|
|
|
default_vn_walk_kind = default_vn_walk_kind_;
|
|
|
|
init_scc_vn ();
|
|
|
|
/* Collect pointers we know point to readonly memory. */
|
|
const_parms = BITMAP_ALLOC (NULL);
|
|
tree fnspec = lookup_attribute ("fn spec",
|
|
TYPE_ATTRIBUTES (TREE_TYPE (cfun->decl)));
|
|
if (fnspec)
|
|
{
|
|
fnspec = TREE_VALUE (TREE_VALUE (fnspec));
|
|
i = 1;
|
|
for (tree arg = DECL_ARGUMENTS (cfun->decl);
|
|
arg; arg = DECL_CHAIN (arg), ++i)
|
|
{
|
|
if (i >= (unsigned) TREE_STRING_LENGTH (fnspec))
|
|
break;
|
|
if (TREE_STRING_POINTER (fnspec)[i] == 'R'
|
|
|| TREE_STRING_POINTER (fnspec)[i] == 'r')
|
|
{
|
|
tree name = ssa_default_def (cfun, arg);
|
|
if (name)
|
|
bitmap_set_bit (const_parms, SSA_NAME_VERSION (name));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Walk all blocks in dominator order, value-numbering stmts
|
|
SSA defs and decide whether outgoing edges are not executable. */
|
|
sccvn_dom_walker walker;
|
|
walker.walk (ENTRY_BLOCK_PTR_FOR_FN (cfun));
|
|
|
|
/* Initialize the value ids and prune out remaining VN_TOPs
|
|
from dead code. */
|
|
tree name;
|
|
FOR_EACH_SSA_NAME (i, name, cfun)
|
|
{
|
|
vn_ssa_aux_t info = VN_INFO (name);
|
|
if (!info->visited
|
|
|| info->valnum == VN_TOP)
|
|
info->valnum = name;
|
|
if (info->valnum == name)
|
|
info->value_id = get_next_value_id ();
|
|
else if (is_gimple_min_invariant (info->valnum))
|
|
info->value_id = get_or_alloc_constant_value_id (info->valnum);
|
|
}
|
|
|
|
/* Propagate. */
|
|
FOR_EACH_SSA_NAME (i, name, cfun)
|
|
{
|
|
vn_ssa_aux_t info = VN_INFO (name);
|
|
if (TREE_CODE (info->valnum) == SSA_NAME
|
|
&& info->valnum != name
|
|
&& info->value_id != VN_INFO (info->valnum)->value_id)
|
|
info->value_id = VN_INFO (info->valnum)->value_id;
|
|
}
|
|
|
|
set_hashtable_value_ids ();
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Value numbers:\n");
|
|
FOR_EACH_SSA_NAME (i, name, cfun)
|
|
{
|
|
if (VN_INFO (name)->visited
|
|
&& SSA_VAL (name) != name)
|
|
{
|
|
print_generic_expr (dump_file, name);
|
|
fprintf (dump_file, " = ");
|
|
print_generic_expr (dump_file, SSA_VAL (name));
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return the maximum value id we have ever seen. */
|
|
|
|
unsigned int
|
|
get_max_value_id (void)
|
|
{
|
|
return next_value_id;
|
|
}
|
|
|
|
/* Return the next unique value id. */
|
|
|
|
unsigned int
|
|
get_next_value_id (void)
|
|
{
|
|
return next_value_id++;
|
|
}
|
|
|
|
|
|
/* Compare two expressions E1 and E2 and return true if they are equal. */
|
|
|
|
bool
|
|
expressions_equal_p (tree e1, tree e2)
|
|
{
|
|
/* The obvious case. */
|
|
if (e1 == e2)
|
|
return true;
|
|
|
|
/* If either one is VN_TOP consider them equal. */
|
|
if (e1 == VN_TOP || e2 == VN_TOP)
|
|
return true;
|
|
|
|
/* If only one of them is null, they cannot be equal. */
|
|
if (!e1 || !e2)
|
|
return false;
|
|
|
|
/* Now perform the actual comparison. */
|
|
if (TREE_CODE (e1) == TREE_CODE (e2)
|
|
&& operand_equal_p (e1, e2, OEP_PURE_SAME))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Return true if the nary operation NARY may trap. This is a copy
|
|
of stmt_could_throw_1_p adjusted to the SCCVN IL. */
|
|
|
|
bool
|
|
vn_nary_may_trap (vn_nary_op_t nary)
|
|
{
|
|
tree type;
|
|
tree rhs2 = NULL_TREE;
|
|
bool honor_nans = false;
|
|
bool honor_snans = false;
|
|
bool fp_operation = false;
|
|
bool honor_trapv = false;
|
|
bool handled, ret;
|
|
unsigned i;
|
|
|
|
if (TREE_CODE_CLASS (nary->opcode) == tcc_comparison
|
|
|| TREE_CODE_CLASS (nary->opcode) == tcc_unary
|
|
|| TREE_CODE_CLASS (nary->opcode) == tcc_binary)
|
|
{
|
|
type = nary->type;
|
|
fp_operation = FLOAT_TYPE_P (type);
|
|
if (fp_operation)
|
|
{
|
|
honor_nans = flag_trapping_math && !flag_finite_math_only;
|
|
honor_snans = flag_signaling_nans != 0;
|
|
}
|
|
else if (INTEGRAL_TYPE_P (type)
|
|
&& TYPE_OVERFLOW_TRAPS (type))
|
|
honor_trapv = true;
|
|
}
|
|
if (nary->length >= 2)
|
|
rhs2 = nary->op[1];
|
|
ret = operation_could_trap_helper_p (nary->opcode, fp_operation,
|
|
honor_trapv,
|
|
honor_nans, honor_snans, rhs2,
|
|
&handled);
|
|
if (handled
|
|
&& ret)
|
|
return true;
|
|
|
|
for (i = 0; i < nary->length; ++i)
|
|
if (tree_could_trap_p (nary->op[i]))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
class eliminate_dom_walker : public dom_walker
|
|
{
|
|
public:
|
|
eliminate_dom_walker (cdi_direction, bitmap);
|
|
~eliminate_dom_walker ();
|
|
|
|
virtual edge before_dom_children (basic_block);
|
|
virtual void after_dom_children (basic_block);
|
|
|
|
tree eliminate_avail (tree op);
|
|
void eliminate_push_avail (tree op);
|
|
tree eliminate_insert (gimple_stmt_iterator *gsi, tree val);
|
|
|
|
bool do_pre;
|
|
unsigned int el_todo;
|
|
unsigned int eliminations;
|
|
unsigned int insertions;
|
|
|
|
/* SSA names that had their defs inserted by PRE if do_pre. */
|
|
bitmap inserted_exprs;
|
|
|
|
/* Blocks with statements that have had their EH properties changed. */
|
|
bitmap need_eh_cleanup;
|
|
|
|
/* Blocks with statements that have had their AB properties changed. */
|
|
bitmap need_ab_cleanup;
|
|
|
|
auto_vec<gimple *> to_remove;
|
|
auto_vec<gimple *> to_fixup;
|
|
auto_vec<tree> avail;
|
|
auto_vec<tree> avail_stack;
|
|
};
|
|
|
|
eliminate_dom_walker::eliminate_dom_walker (cdi_direction direction,
|
|
bitmap inserted_exprs_)
|
|
: dom_walker (direction), do_pre (inserted_exprs_ != NULL),
|
|
el_todo (0), eliminations (0), insertions (0),
|
|
inserted_exprs (inserted_exprs_)
|
|
{
|
|
need_eh_cleanup = BITMAP_ALLOC (NULL);
|
|
need_ab_cleanup = BITMAP_ALLOC (NULL);
|
|
}
|
|
|
|
eliminate_dom_walker::~eliminate_dom_walker ()
|
|
{
|
|
BITMAP_FREE (need_eh_cleanup);
|
|
BITMAP_FREE (need_ab_cleanup);
|
|
}
|
|
|
|
/* Return a leader for OP that is available at the current point of the
|
|
eliminate domwalk. */
|
|
|
|
tree
|
|
eliminate_dom_walker::eliminate_avail (tree op)
|
|
{
|
|
tree valnum = VN_INFO (op)->valnum;
|
|
if (TREE_CODE (valnum) == SSA_NAME)
|
|
{
|
|
if (SSA_NAME_IS_DEFAULT_DEF (valnum))
|
|
return valnum;
|
|
if (avail.length () > SSA_NAME_VERSION (valnum))
|
|
return avail[SSA_NAME_VERSION (valnum)];
|
|
}
|
|
else if (is_gimple_min_invariant (valnum))
|
|
return valnum;
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* At the current point of the eliminate domwalk make OP available. */
|
|
|
|
void
|
|
eliminate_dom_walker::eliminate_push_avail (tree op)
|
|
{
|
|
tree valnum = VN_INFO (op)->valnum;
|
|
if (TREE_CODE (valnum) == SSA_NAME)
|
|
{
|
|
if (avail.length () <= SSA_NAME_VERSION (valnum))
|
|
avail.safe_grow_cleared (SSA_NAME_VERSION (valnum) + 1);
|
|
tree pushop = op;
|
|
if (avail[SSA_NAME_VERSION (valnum)])
|
|
pushop = avail[SSA_NAME_VERSION (valnum)];
|
|
avail_stack.safe_push (pushop);
|
|
avail[SSA_NAME_VERSION (valnum)] = op;
|
|
}
|
|
}
|
|
|
|
/* Insert the expression recorded by SCCVN for VAL at *GSI. Returns
|
|
the leader for the expression if insertion was successful. */
|
|
|
|
tree
|
|
eliminate_dom_walker::eliminate_insert (gimple_stmt_iterator *gsi, tree val)
|
|
{
|
|
/* We can insert a sequence with a single assignment only. */
|
|
gimple_seq stmts = VN_INFO (val)->expr;
|
|
if (!gimple_seq_singleton_p (stmts))
|
|
return NULL_TREE;
|
|
gassign *stmt = dyn_cast <gassign *> (gimple_seq_first_stmt (stmts));
|
|
if (!stmt
|
|
|| (!CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (stmt))
|
|
&& gimple_assign_rhs_code (stmt) != VIEW_CONVERT_EXPR
|
|
&& gimple_assign_rhs_code (stmt) != BIT_FIELD_REF
|
|
&& (gimple_assign_rhs_code (stmt) != BIT_AND_EXPR
|
|
|| TREE_CODE (gimple_assign_rhs2 (stmt)) != INTEGER_CST)))
|
|
return NULL_TREE;
|
|
|
|
tree op = gimple_assign_rhs1 (stmt);
|
|
if (gimple_assign_rhs_code (stmt) == VIEW_CONVERT_EXPR
|
|
|| gimple_assign_rhs_code (stmt) == BIT_FIELD_REF)
|
|
op = TREE_OPERAND (op, 0);
|
|
tree leader = TREE_CODE (op) == SSA_NAME ? eliminate_avail (op) : op;
|
|
if (!leader)
|
|
return NULL_TREE;
|
|
|
|
tree res;
|
|
stmts = NULL;
|
|
if (gimple_assign_rhs_code (stmt) == BIT_FIELD_REF)
|
|
res = gimple_build (&stmts, BIT_FIELD_REF,
|
|
TREE_TYPE (val), leader,
|
|
TREE_OPERAND (gimple_assign_rhs1 (stmt), 1),
|
|
TREE_OPERAND (gimple_assign_rhs1 (stmt), 2));
|
|
else if (gimple_assign_rhs_code (stmt) == BIT_AND_EXPR)
|
|
res = gimple_build (&stmts, BIT_AND_EXPR,
|
|
TREE_TYPE (val), leader, gimple_assign_rhs2 (stmt));
|
|
else
|
|
res = gimple_build (&stmts, gimple_assign_rhs_code (stmt),
|
|
TREE_TYPE (val), leader);
|
|
if (TREE_CODE (res) != SSA_NAME
|
|
|| SSA_NAME_IS_DEFAULT_DEF (res)
|
|
|| gimple_bb (SSA_NAME_DEF_STMT (res)))
|
|
{
|
|
gimple_seq_discard (stmts);
|
|
|
|
/* During propagation we have to treat SSA info conservatively
|
|
and thus we can end up simplifying the inserted expression
|
|
at elimination time to sth not defined in stmts. */
|
|
/* But then this is a redundancy we failed to detect. Which means
|
|
res now has two values. That doesn't play well with how
|
|
we track availability here, so give up. */
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
if (TREE_CODE (res) == SSA_NAME)
|
|
res = eliminate_avail (res);
|
|
if (res)
|
|
{
|
|
fprintf (dump_file, "Failed to insert expression for value ");
|
|
print_generic_expr (dump_file, val);
|
|
fprintf (dump_file, " which is really fully redundant to ");
|
|
print_generic_expr (dump_file, res);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
|
|
return NULL_TREE;
|
|
}
|
|
else
|
|
{
|
|
gsi_insert_seq_before (gsi, stmts, GSI_SAME_STMT);
|
|
VN_INFO_GET (res)->valnum = val;
|
|
}
|
|
|
|
insertions++;
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Inserted ");
|
|
print_gimple_stmt (dump_file, SSA_NAME_DEF_STMT (res), 0);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
/* Perform elimination for the basic-block B during the domwalk. */
|
|
|
|
edge
|
|
eliminate_dom_walker::before_dom_children (basic_block b)
|
|
{
|
|
/* Mark new bb. */
|
|
avail_stack.safe_push (NULL_TREE);
|
|
|
|
/* Skip unreachable blocks marked unreachable during the SCCVN domwalk. */
|
|
edge_iterator ei;
|
|
edge e;
|
|
FOR_EACH_EDGE (e, ei, b->preds)
|
|
if (e->flags & EDGE_EXECUTABLE)
|
|
break;
|
|
if (! e)
|
|
return NULL;
|
|
|
|
for (gphi_iterator gsi = gsi_start_phis (b); !gsi_end_p (gsi);)
|
|
{
|
|
gphi *phi = gsi.phi ();
|
|
tree res = PHI_RESULT (phi);
|
|
|
|
if (virtual_operand_p (res))
|
|
{
|
|
gsi_next (&gsi);
|
|
continue;
|
|
}
|
|
|
|
tree sprime = eliminate_avail (res);
|
|
if (sprime
|
|
&& sprime != res)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Replaced redundant PHI node defining ");
|
|
print_generic_expr (dump_file, res);
|
|
fprintf (dump_file, " with ");
|
|
print_generic_expr (dump_file, sprime);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
/* If we inserted this PHI node ourself, it's not an elimination. */
|
|
if (! inserted_exprs
|
|
|| ! bitmap_bit_p (inserted_exprs, SSA_NAME_VERSION (res)))
|
|
eliminations++;
|
|
|
|
/* If we will propagate into all uses don't bother to do
|
|
anything. */
|
|
if (may_propagate_copy (res, sprime))
|
|
{
|
|
/* Mark the PHI for removal. */
|
|
to_remove.safe_push (phi);
|
|
gsi_next (&gsi);
|
|
continue;
|
|
}
|
|
|
|
remove_phi_node (&gsi, false);
|
|
|
|
if (!useless_type_conversion_p (TREE_TYPE (res), TREE_TYPE (sprime)))
|
|
sprime = fold_convert (TREE_TYPE (res), sprime);
|
|
gimple *stmt = gimple_build_assign (res, sprime);
|
|
gimple_stmt_iterator gsi2 = gsi_after_labels (b);
|
|
gsi_insert_before (&gsi2, stmt, GSI_NEW_STMT);
|
|
continue;
|
|
}
|
|
|
|
eliminate_push_avail (res);
|
|
gsi_next (&gsi);
|
|
}
|
|
|
|
for (gimple_stmt_iterator gsi = gsi_start_bb (b);
|
|
!gsi_end_p (gsi);
|
|
gsi_next (&gsi))
|
|
{
|
|
tree sprime = NULL_TREE;
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
tree lhs = gimple_get_lhs (stmt);
|
|
if (lhs && TREE_CODE (lhs) == SSA_NAME
|
|
&& !gimple_has_volatile_ops (stmt)
|
|
/* See PR43491. Do not replace a global register variable when
|
|
it is a the RHS of an assignment. Do replace local register
|
|
variables since gcc does not guarantee a local variable will
|
|
be allocated in register.
|
|
??? The fix isn't effective here. This should instead
|
|
be ensured by not value-numbering them the same but treating
|
|
them like volatiles? */
|
|
&& !(gimple_assign_single_p (stmt)
|
|
&& (TREE_CODE (gimple_assign_rhs1 (stmt)) == VAR_DECL
|
|
&& DECL_HARD_REGISTER (gimple_assign_rhs1 (stmt))
|
|
&& is_global_var (gimple_assign_rhs1 (stmt)))))
|
|
{
|
|
sprime = eliminate_avail (lhs);
|
|
if (!sprime)
|
|
{
|
|
/* If there is no existing usable leader but SCCVN thinks
|
|
it has an expression it wants to use as replacement,
|
|
insert that. */
|
|
tree val = VN_INFO (lhs)->valnum;
|
|
if (val != VN_TOP
|
|
&& TREE_CODE (val) == SSA_NAME
|
|
&& VN_INFO (val)->needs_insertion
|
|
&& VN_INFO (val)->expr != NULL
|
|
&& (sprime = eliminate_insert (&gsi, val)) != NULL_TREE)
|
|
eliminate_push_avail (sprime);
|
|
}
|
|
|
|
/* If this now constitutes a copy duplicate points-to
|
|
and range info appropriately. This is especially
|
|
important for inserted code. See tree-ssa-copy.c
|
|
for similar code. */
|
|
if (sprime
|
|
&& TREE_CODE (sprime) == SSA_NAME)
|
|
{
|
|
basic_block sprime_b = gimple_bb (SSA_NAME_DEF_STMT (sprime));
|
|
if (POINTER_TYPE_P (TREE_TYPE (lhs))
|
|
&& VN_INFO_PTR_INFO (lhs)
|
|
&& ! VN_INFO_PTR_INFO (sprime))
|
|
{
|
|
duplicate_ssa_name_ptr_info (sprime,
|
|
VN_INFO_PTR_INFO (lhs));
|
|
if (b != sprime_b)
|
|
mark_ptr_info_alignment_unknown
|
|
(SSA_NAME_PTR_INFO (sprime));
|
|
}
|
|
else if (INTEGRAL_TYPE_P (TREE_TYPE (lhs))
|
|
&& VN_INFO_RANGE_INFO (lhs)
|
|
&& ! VN_INFO_RANGE_INFO (sprime)
|
|
&& b == sprime_b)
|
|
duplicate_ssa_name_range_info (sprime,
|
|
VN_INFO_RANGE_TYPE (lhs),
|
|
VN_INFO_RANGE_INFO (lhs));
|
|
}
|
|
|
|
/* Inhibit the use of an inserted PHI on a loop header when
|
|
the address of the memory reference is a simple induction
|
|
variable. In other cases the vectorizer won't do anything
|
|
anyway (either it's loop invariant or a complicated
|
|
expression). */
|
|
if (sprime
|
|
&& TREE_CODE (sprime) == SSA_NAME
|
|
&& do_pre
|
|
&& (flag_tree_loop_vectorize || flag_tree_parallelize_loops > 1)
|
|
&& loop_outer (b->loop_father)
|
|
&& has_zero_uses (sprime)
|
|
&& bitmap_bit_p (inserted_exprs, SSA_NAME_VERSION (sprime))
|
|
&& gimple_assign_load_p (stmt))
|
|
{
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (sprime);
|
|
basic_block def_bb = gimple_bb (def_stmt);
|
|
if (gimple_code (def_stmt) == GIMPLE_PHI
|
|
&& def_bb->loop_father->header == def_bb)
|
|
{
|
|
loop_p loop = def_bb->loop_father;
|
|
ssa_op_iter iter;
|
|
tree op;
|
|
bool found = false;
|
|
FOR_EACH_SSA_TREE_OPERAND (op, stmt, iter, SSA_OP_USE)
|
|
{
|
|
affine_iv iv;
|
|
def_bb = gimple_bb (SSA_NAME_DEF_STMT (op));
|
|
if (def_bb
|
|
&& flow_bb_inside_loop_p (loop, def_bb)
|
|
&& simple_iv (loop, loop, op, &iv, true))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Not replacing ");
|
|
print_gimple_expr (dump_file, stmt, 0);
|
|
fprintf (dump_file, " with ");
|
|
print_generic_expr (dump_file, sprime);
|
|
fprintf (dump_file, " which would add a loop"
|
|
" carried dependence to loop %d\n",
|
|
loop->num);
|
|
}
|
|
/* Don't keep sprime available. */
|
|
sprime = NULL_TREE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sprime)
|
|
{
|
|
/* If we can propagate the value computed for LHS into
|
|
all uses don't bother doing anything with this stmt. */
|
|
if (may_propagate_copy (lhs, sprime))
|
|
{
|
|
/* Mark it for removal. */
|
|
to_remove.safe_push (stmt);
|
|
|
|
/* ??? Don't count copy/constant propagations. */
|
|
if (gimple_assign_single_p (stmt)
|
|
&& (TREE_CODE (gimple_assign_rhs1 (stmt)) == SSA_NAME
|
|
|| gimple_assign_rhs1 (stmt) == sprime))
|
|
continue;
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Replaced ");
|
|
print_gimple_expr (dump_file, stmt, 0);
|
|
fprintf (dump_file, " with ");
|
|
print_generic_expr (dump_file, sprime);
|
|
fprintf (dump_file, " in all uses of ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
eliminations++;
|
|
continue;
|
|
}
|
|
|
|
/* If this is an assignment from our leader (which
|
|
happens in the case the value-number is a constant)
|
|
then there is nothing to do. */
|
|
if (gimple_assign_single_p (stmt)
|
|
&& sprime == gimple_assign_rhs1 (stmt))
|
|
continue;
|
|
|
|
/* Else replace its RHS. */
|
|
bool can_make_abnormal_goto
|
|
= is_gimple_call (stmt)
|
|
&& stmt_can_make_abnormal_goto (stmt);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Replaced ");
|
|
print_gimple_expr (dump_file, stmt, 0);
|
|
fprintf (dump_file, " with ");
|
|
print_generic_expr (dump_file, sprime);
|
|
fprintf (dump_file, " in ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
eliminations++;
|
|
gimple *orig_stmt = stmt;
|
|
if (!useless_type_conversion_p (TREE_TYPE (lhs),
|
|
TREE_TYPE (sprime)))
|
|
sprime = fold_convert (TREE_TYPE (lhs), sprime);
|
|
tree vdef = gimple_vdef (stmt);
|
|
tree vuse = gimple_vuse (stmt);
|
|
propagate_tree_value_into_stmt (&gsi, sprime);
|
|
stmt = gsi_stmt (gsi);
|
|
update_stmt (stmt);
|
|
if (vdef != gimple_vdef (stmt))
|
|
VN_INFO (vdef)->valnum = vuse;
|
|
|
|
/* If we removed EH side-effects from the statement, clean
|
|
its EH information. */
|
|
if (maybe_clean_or_replace_eh_stmt (orig_stmt, stmt))
|
|
{
|
|
bitmap_set_bit (need_eh_cleanup,
|
|
gimple_bb (stmt)->index);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, " Removed EH side-effects.\n");
|
|
}
|
|
|
|
/* Likewise for AB side-effects. */
|
|
if (can_make_abnormal_goto
|
|
&& !stmt_can_make_abnormal_goto (stmt))
|
|
{
|
|
bitmap_set_bit (need_ab_cleanup,
|
|
gimple_bb (stmt)->index);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, " Removed AB side-effects.\n");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* If the statement is a scalar store, see if the expression
|
|
has the same value number as its rhs. If so, the store is
|
|
dead. */
|
|
if (gimple_assign_single_p (stmt)
|
|
&& !gimple_has_volatile_ops (stmt)
|
|
&& !is_gimple_reg (gimple_assign_lhs (stmt))
|
|
&& (TREE_CODE (gimple_assign_rhs1 (stmt)) == SSA_NAME
|
|
|| is_gimple_min_invariant (gimple_assign_rhs1 (stmt))))
|
|
{
|
|
tree val;
|
|
tree rhs = gimple_assign_rhs1 (stmt);
|
|
vn_reference_t vnresult;
|
|
val = vn_reference_lookup (lhs, gimple_vuse (stmt), VN_WALKREWRITE,
|
|
&vnresult, false);
|
|
if (TREE_CODE (rhs) == SSA_NAME)
|
|
rhs = VN_INFO (rhs)->valnum;
|
|
if (val
|
|
&& operand_equal_p (val, rhs, 0))
|
|
{
|
|
/* We can only remove the later store if the former aliases
|
|
at least all accesses the later one does or if the store
|
|
was to readonly memory storing the same value. */
|
|
alias_set_type set = get_alias_set (lhs);
|
|
if (! vnresult
|
|
|| vnresult->set == set
|
|
|| alias_set_subset_of (set, vnresult->set))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Deleted redundant store ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
/* Queue stmt for removal. */
|
|
to_remove.safe_push (stmt);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If this is a control statement value numbering left edges
|
|
unexecuted on force the condition in a way consistent with
|
|
that. */
|
|
if (gcond *cond = dyn_cast <gcond *> (stmt))
|
|
{
|
|
if ((EDGE_SUCC (b, 0)->flags & EDGE_EXECUTABLE)
|
|
^ (EDGE_SUCC (b, 1)->flags & EDGE_EXECUTABLE))
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Removing unexecutable edge from ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
if (((EDGE_SUCC (b, 0)->flags & EDGE_TRUE_VALUE) != 0)
|
|
== ((EDGE_SUCC (b, 0)->flags & EDGE_EXECUTABLE) != 0))
|
|
gimple_cond_make_true (cond);
|
|
else
|
|
gimple_cond_make_false (cond);
|
|
update_stmt (cond);
|
|
el_todo |= TODO_cleanup_cfg;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bool can_make_abnormal_goto = stmt_can_make_abnormal_goto (stmt);
|
|
bool was_noreturn = (is_gimple_call (stmt)
|
|
&& gimple_call_noreturn_p (stmt));
|
|
tree vdef = gimple_vdef (stmt);
|
|
tree vuse = gimple_vuse (stmt);
|
|
|
|
/* If we didn't replace the whole stmt (or propagate the result
|
|
into all uses), replace all uses on this stmt with their
|
|
leaders. */
|
|
bool modified = false;
|
|
use_operand_p use_p;
|
|
ssa_op_iter iter;
|
|
FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, SSA_OP_USE)
|
|
{
|
|
tree use = USE_FROM_PTR (use_p);
|
|
/* ??? The call code above leaves stmt operands un-updated. */
|
|
if (TREE_CODE (use) != SSA_NAME)
|
|
continue;
|
|
tree sprime = eliminate_avail (use);
|
|
if (sprime && sprime != use
|
|
&& may_propagate_copy (use, sprime)
|
|
/* We substitute into debug stmts to avoid excessive
|
|
debug temporaries created by removed stmts, but we need
|
|
to avoid doing so for inserted sprimes as we never want
|
|
to create debug temporaries for them. */
|
|
&& (!inserted_exprs
|
|
|| TREE_CODE (sprime) != SSA_NAME
|
|
|| !is_gimple_debug (stmt)
|
|
|| !bitmap_bit_p (inserted_exprs, SSA_NAME_VERSION (sprime))))
|
|
{
|
|
propagate_value (use_p, sprime);
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
/* Fold the stmt if modified, this canonicalizes MEM_REFs we propagated
|
|
into which is a requirement for the IPA devirt machinery. */
|
|
gimple *old_stmt = stmt;
|
|
if (modified)
|
|
{
|
|
/* If a formerly non-invariant ADDR_EXPR is turned into an
|
|
invariant one it was on a separate stmt. */
|
|
if (gimple_assign_single_p (stmt)
|
|
&& TREE_CODE (gimple_assign_rhs1 (stmt)) == ADDR_EXPR)
|
|
recompute_tree_invariant_for_addr_expr (gimple_assign_rhs1 (stmt));
|
|
gimple_stmt_iterator prev = gsi;
|
|
gsi_prev (&prev);
|
|
if (fold_stmt (&gsi))
|
|
{
|
|
/* fold_stmt may have created new stmts inbetween
|
|
the previous stmt and the folded stmt. Mark
|
|
all defs created there as varying to not confuse
|
|
the SCCVN machinery as we're using that even during
|
|
elimination. */
|
|
if (gsi_end_p (prev))
|
|
prev = gsi_start_bb (b);
|
|
else
|
|
gsi_next (&prev);
|
|
if (gsi_stmt (prev) != gsi_stmt (gsi))
|
|
do
|
|
{
|
|
tree def;
|
|
ssa_op_iter dit;
|
|
FOR_EACH_SSA_TREE_OPERAND (def, gsi_stmt (prev),
|
|
dit, SSA_OP_ALL_DEFS)
|
|
/* As existing DEFs may move between stmts
|
|
we have to guard VN_INFO_GET. */
|
|
if (! has_VN_INFO (def))
|
|
VN_INFO_GET (def)->valnum = def;
|
|
if (gsi_stmt (prev) == gsi_stmt (gsi))
|
|
break;
|
|
gsi_next (&prev);
|
|
}
|
|
while (1);
|
|
}
|
|
stmt = gsi_stmt (gsi);
|
|
/* In case we folded the stmt away schedule the NOP for removal. */
|
|
if (gimple_nop_p (stmt))
|
|
to_remove.safe_push (stmt);
|
|
}
|
|
|
|
/* Visit indirect calls and turn them into direct calls if
|
|
possible using the devirtualization machinery. Do this before
|
|
checking for required EH/abnormal/noreturn cleanup as devird
|
|
may expose more of those. */
|
|
if (gcall *call_stmt = dyn_cast <gcall *> (stmt))
|
|
{
|
|
tree fn = gimple_call_fn (call_stmt);
|
|
if (fn
|
|
&& flag_devirtualize
|
|
&& virtual_method_call_p (fn))
|
|
{
|
|
tree otr_type = obj_type_ref_class (fn);
|
|
unsigned HOST_WIDE_INT otr_tok
|
|
= tree_to_uhwi (OBJ_TYPE_REF_TOKEN (fn));
|
|
tree instance;
|
|
ipa_polymorphic_call_context context (current_function_decl,
|
|
fn, stmt, &instance);
|
|
context.get_dynamic_type (instance, OBJ_TYPE_REF_OBJECT (fn),
|
|
otr_type, stmt);
|
|
bool final;
|
|
vec <cgraph_node *> targets
|
|
= possible_polymorphic_call_targets (obj_type_ref_class (fn),
|
|
otr_tok, context, &final);
|
|
if (dump_file)
|
|
dump_possible_polymorphic_call_targets (dump_file,
|
|
obj_type_ref_class (fn),
|
|
otr_tok, context);
|
|
if (final && targets.length () <= 1 && dbg_cnt (devirt))
|
|
{
|
|
tree fn;
|
|
if (targets.length () == 1)
|
|
fn = targets[0]->decl;
|
|
else
|
|
fn = builtin_decl_implicit (BUILT_IN_UNREACHABLE);
|
|
if (dump_enabled_p ())
|
|
{
|
|
location_t loc = gimple_location (stmt);
|
|
dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, loc,
|
|
"converting indirect call to "
|
|
"function %s\n",
|
|
lang_hooks.decl_printable_name (fn, 2));
|
|
}
|
|
gimple_call_set_fndecl (call_stmt, fn);
|
|
/* If changing the call to __builtin_unreachable
|
|
or similar noreturn function, adjust gimple_call_fntype
|
|
too. */
|
|
if (gimple_call_noreturn_p (call_stmt)
|
|
&& VOID_TYPE_P (TREE_TYPE (TREE_TYPE (fn)))
|
|
&& TYPE_ARG_TYPES (TREE_TYPE (fn))
|
|
&& (TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (fn)))
|
|
== void_type_node))
|
|
gimple_call_set_fntype (call_stmt, TREE_TYPE (fn));
|
|
maybe_remove_unused_call_args (cfun, call_stmt);
|
|
modified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (modified)
|
|
{
|
|
/* When changing a call into a noreturn call, cfg cleanup
|
|
is needed to fix up the noreturn call. */
|
|
if (!was_noreturn
|
|
&& is_gimple_call (stmt) && gimple_call_noreturn_p (stmt))
|
|
to_fixup.safe_push (stmt);
|
|
/* When changing a condition or switch into one we know what
|
|
edge will be executed, schedule a cfg cleanup. */
|
|
if ((gimple_code (stmt) == GIMPLE_COND
|
|
&& (gimple_cond_true_p (as_a <gcond *> (stmt))
|
|
|| gimple_cond_false_p (as_a <gcond *> (stmt))))
|
|
|| (gimple_code (stmt) == GIMPLE_SWITCH
|
|
&& TREE_CODE (gimple_switch_index
|
|
(as_a <gswitch *> (stmt))) == INTEGER_CST))
|
|
el_todo |= TODO_cleanup_cfg;
|
|
/* If we removed EH side-effects from the statement, clean
|
|
its EH information. */
|
|
if (maybe_clean_or_replace_eh_stmt (old_stmt, stmt))
|
|
{
|
|
bitmap_set_bit (need_eh_cleanup,
|
|
gimple_bb (stmt)->index);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, " Removed EH side-effects.\n");
|
|
}
|
|
/* Likewise for AB side-effects. */
|
|
if (can_make_abnormal_goto
|
|
&& !stmt_can_make_abnormal_goto (stmt))
|
|
{
|
|
bitmap_set_bit (need_ab_cleanup,
|
|
gimple_bb (stmt)->index);
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, " Removed AB side-effects.\n");
|
|
}
|
|
update_stmt (stmt);
|
|
if (vdef != gimple_vdef (stmt))
|
|
VN_INFO (vdef)->valnum = vuse;
|
|
}
|
|
|
|
/* Make new values available - for fully redundant LHS we
|
|
continue with the next stmt above and skip this. */
|
|
def_operand_p defp;
|
|
FOR_EACH_SSA_DEF_OPERAND (defp, stmt, iter, SSA_OP_DEF)
|
|
eliminate_push_avail (DEF_FROM_PTR (defp));
|
|
}
|
|
|
|
/* Replace destination PHI arguments. */
|
|
FOR_EACH_EDGE (e, ei, b->succs)
|
|
if (e->flags & EDGE_EXECUTABLE)
|
|
for (gphi_iterator gsi = gsi_start_phis (e->dest);
|
|
!gsi_end_p (gsi);
|
|
gsi_next (&gsi))
|
|
{
|
|
gphi *phi = gsi.phi ();
|
|
use_operand_p use_p = PHI_ARG_DEF_PTR_FROM_EDGE (phi, e);
|
|
tree arg = USE_FROM_PTR (use_p);
|
|
if (TREE_CODE (arg) != SSA_NAME
|
|
|| virtual_operand_p (arg))
|
|
continue;
|
|
tree sprime = eliminate_avail (arg);
|
|
if (sprime && may_propagate_copy (arg, sprime))
|
|
propagate_value (use_p, sprime);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Make no longer available leaders no longer available. */
|
|
|
|
void
|
|
eliminate_dom_walker::after_dom_children (basic_block)
|
|
{
|
|
tree entry;
|
|
while ((entry = avail_stack.pop ()) != NULL_TREE)
|
|
{
|
|
tree valnum = VN_INFO (entry)->valnum;
|
|
tree old = avail[SSA_NAME_VERSION (valnum)];
|
|
if (old == entry)
|
|
avail[SSA_NAME_VERSION (valnum)] = NULL_TREE;
|
|
else
|
|
avail[SSA_NAME_VERSION (valnum)] = entry;
|
|
}
|
|
}
|
|
|
|
/* Eliminate fully redundant computations. */
|
|
|
|
unsigned int
|
|
vn_eliminate (bitmap inserted_exprs)
|
|
{
|
|
eliminate_dom_walker el (CDI_DOMINATORS, inserted_exprs);
|
|
el.avail.reserve (num_ssa_names);
|
|
|
|
el.walk (cfun->cfg->x_entry_block_ptr);
|
|
|
|
/* We cannot remove stmts during BB walk, especially not release SSA
|
|
names there as this confuses the VN machinery. The stmts ending
|
|
up in to_remove are either stores or simple copies.
|
|
Remove stmts in reverse order to make debug stmt creation possible. */
|
|
while (!el.to_remove.is_empty ())
|
|
{
|
|
gimple *stmt = el.to_remove.pop ();
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Removing dead stmt ");
|
|
print_gimple_stmt (dump_file, stmt, 0, 0);
|
|
}
|
|
|
|
gimple_stmt_iterator gsi = gsi_for_stmt (stmt);
|
|
if (gimple_code (stmt) == GIMPLE_PHI)
|
|
remove_phi_node (&gsi, true);
|
|
else
|
|
{
|
|
basic_block bb = gimple_bb (stmt);
|
|
unlink_stmt_vdef (stmt);
|
|
if (gsi_remove (&gsi, true))
|
|
bitmap_set_bit (el.need_eh_cleanup, bb->index);
|
|
if (is_gimple_call (stmt) && stmt_can_make_abnormal_goto (stmt))
|
|
bitmap_set_bit (el.need_ab_cleanup, bb->index);
|
|
release_defs (stmt);
|
|
}
|
|
|
|
/* Removing a stmt may expose a forwarder block. */
|
|
el.el_todo |= TODO_cleanup_cfg;
|
|
}
|
|
|
|
/* Fixup stmts that became noreturn calls. This may require splitting
|
|
blocks and thus isn't possible during the dominator walk. Do this
|
|
in reverse order so we don't inadvertedly remove a stmt we want to
|
|
fixup by visiting a dominating now noreturn call first. */
|
|
while (!el.to_fixup.is_empty ())
|
|
{
|
|
gimple *stmt = el.to_fixup.pop ();
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Fixing up noreturn call ");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
if (fixup_noreturn_call (stmt))
|
|
el.el_todo |= TODO_cleanup_cfg;
|
|
}
|
|
|
|
bool do_eh_cleanup = !bitmap_empty_p (el.need_eh_cleanup);
|
|
bool do_ab_cleanup = !bitmap_empty_p (el.need_ab_cleanup);
|
|
|
|
if (do_eh_cleanup)
|
|
gimple_purge_all_dead_eh_edges (el.need_eh_cleanup);
|
|
|
|
if (do_ab_cleanup)
|
|
gimple_purge_all_dead_abnormal_call_edges (el.need_ab_cleanup);
|
|
|
|
if (do_eh_cleanup || do_ab_cleanup)
|
|
el.el_todo |= TODO_cleanup_cfg;
|
|
|
|
statistics_counter_event (cfun, "Eliminated", el.eliminations);
|
|
statistics_counter_event (cfun, "Insertions", el.insertions);
|
|
|
|
return el.el_todo;
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_fre =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"fre", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_TREE_FRE, /* tv_id */
|
|
( PROP_cfg | PROP_ssa ), /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
0, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_fre : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_fre (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_fre, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
opt_pass * clone () { return new pass_fre (m_ctxt); }
|
|
virtual bool gate (function *) { return flag_tree_fre != 0; }
|
|
virtual unsigned int execute (function *);
|
|
|
|
}; // class pass_fre
|
|
|
|
unsigned int
|
|
pass_fre::execute (function *)
|
|
{
|
|
unsigned int todo = 0;
|
|
|
|
run_scc_vn (VN_WALKREWRITE);
|
|
|
|
/* Remove all the redundant expressions. */
|
|
todo |= vn_eliminate (NULL);
|
|
|
|
scc_vn_restore_ssa_info ();
|
|
free_scc_vn ();
|
|
|
|
return todo;
|
|
}
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_fre (gcc::context *ctxt)
|
|
{
|
|
return new pass_fre (ctxt);
|
|
}
|