mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-21 19:35:36 -05:00
2148 lines
61 KiB
C++
2148 lines
61 KiB
C++
/* Tree based points-to analysis
|
|
Copyright (C) 2005-2026 Free Software Foundation, Inc.
|
|
Contributed by Daniel Berlin <dberlin@dberlin.org>
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, 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 "tree-pass.h"
|
|
#include "ssa.h"
|
|
#include "cgraph.h"
|
|
#include "tree-pretty-print.h"
|
|
#include "diagnostic-core.h"
|
|
#include "fold-const.h"
|
|
#include "stor-layout.h"
|
|
#include "stmt.h"
|
|
#include "gimple-iterator.h"
|
|
#include "tree-into-ssa.h"
|
|
#include "tree-dfa.h"
|
|
#include "gimple-walk.h"
|
|
#include "varasm.h"
|
|
#include "stringpool.h"
|
|
#include "attribs.h"
|
|
#include "tree-ssa.h"
|
|
#include "tree-cfg.h"
|
|
#include "gimple-range.h"
|
|
#include "ipa-modref-tree.h"
|
|
#include "ipa-modref.h"
|
|
#include "attr-fnspec.h"
|
|
|
|
#include "tree-ssa-structalias.h"
|
|
#include "pta-andersen.h"
|
|
#include "gimple-ssa-pta-constraints.h"
|
|
|
|
/* The idea behind this analyzer is to generate set constraints from the
|
|
program, then solve the resulting constraints in order to generate the
|
|
points-to sets.
|
|
|
|
Set constraints are a way of modeling program analysis problems that
|
|
involve sets. They consist of an inclusion constraint language,
|
|
describing the variables (each variable is a set) and operations that
|
|
are involved on the variables, and a set of rules that derive facts
|
|
from these operations. To solve a system of set constraints, you derive
|
|
all possible facts under the rules, which gives you the correct sets
|
|
as a consequence.
|
|
|
|
See "Efficient Field-sensitive pointer analysis for C" by "David
|
|
J. Pearce and Paul H. J. Kelly and Chris Hankin", at
|
|
http://citeseer.ist.psu.edu/pearce04efficient.html
|
|
|
|
Also see "Ultra-fast Aliasing Analysis using CLA: A Million Lines
|
|
of C Code in a Second" by "Nevin Heintze and Olivier Tardieu" at
|
|
http://citeseer.ist.psu.edu/heintze01ultrafast.html
|
|
|
|
There are three types of real constraint expressions, DEREF,
|
|
ADDRESSOF, and SCALAR. Each constraint expression consists
|
|
of a constraint type, a variable, and an offset.
|
|
|
|
SCALAR is a constraint expression type used to represent x, whether
|
|
it appears on the LHS or the RHS of a statement.
|
|
DEREF is a constraint expression type used to represent *x, whether
|
|
it appears on the LHS or the RHS of a statement.
|
|
ADDRESSOF is a constraint expression used to represent &x, whether
|
|
it appears on the LHS or the RHS of a statement.
|
|
|
|
Each pointer variable in the program is assigned an integer id, and
|
|
each field of a structure variable is assigned an integer id as well.
|
|
|
|
Structure variables are linked to their list of fields through a "next
|
|
field" in each variable that points to the next field in offset
|
|
order.
|
|
Each variable for a structure field has
|
|
|
|
1. "size", that tells the size in bits of that field.
|
|
2. "fullsize", that tells the size in bits of the entire structure.
|
|
3. "offset", that tells the offset in bits from the beginning of the
|
|
structure to this field.
|
|
|
|
Thus,
|
|
struct f
|
|
{
|
|
int a;
|
|
int b;
|
|
} foo;
|
|
int *bar;
|
|
|
|
looks like
|
|
|
|
foo.a -> id 1, size 32, offset 0, fullsize 64, next foo.b
|
|
foo.b -> id 2, size 32, offset 32, fullsize 64, next NULL
|
|
bar -> id 3, size 32, offset 0, fullsize 32, next NULL
|
|
|
|
|
|
In order to solve the system of set constraints, the following is
|
|
done:
|
|
|
|
1. Each constraint variable x has a solution set associated with it,
|
|
Sol(x).
|
|
|
|
2. Constraints are separated into direct, copy, and complex.
|
|
Direct constraints are ADDRESSOF constraints that require no extra
|
|
processing, such as P = &Q
|
|
Copy constraints are those of the form P = Q.
|
|
Complex constraints are all the constraints involving dereferences
|
|
and offsets (including offsetted copies).
|
|
|
|
3. All direct constraints of the form P = &Q are processed, such
|
|
that Q is added to Sol(P)
|
|
|
|
4. All complex constraints for a given constraint variable are stored in a
|
|
linked list attached to that variable's node.
|
|
|
|
5. A directed graph is built out of the copy constraints. Each
|
|
constraint variable is a node in the graph, and an edge from
|
|
Q to P is added for each copy constraint of the form P = Q
|
|
|
|
6. The graph is then walked, and solution sets are
|
|
propagated along the copy edges, such that an edge from Q to P
|
|
causes Sol(P) <- Sol(P) union Sol(Q).
|
|
|
|
7. As we visit each node, all complex constraints associated with
|
|
that node are processed by adding appropriate copy edges to the graph, or the
|
|
appropriate variables to the solution set.
|
|
|
|
8. The process of walking the graph is iterated until no solution
|
|
sets change.
|
|
|
|
Prior to walking the graph in steps 6 and 7, We perform static
|
|
cycle elimination on the constraint graph, as well
|
|
as off-line variable substitution.
|
|
|
|
TODO: Adding offsets to pointer-to-structures can be handled (IE not punted
|
|
on and turned into anything), but isn't. You can just see what offset
|
|
inside the pointed-to struct it's going to access.
|
|
|
|
TODO: Constant bounded arrays can be handled as if they were structs of the
|
|
same number of elements.
|
|
|
|
TODO: Modeling heap and incoming pointers becomes much better if we
|
|
add fields to them as we discover them, which we could do.
|
|
|
|
TODO: We could handle unions, but to be honest, it's probably not
|
|
worth the pain or slowdown. */
|
|
|
|
/* IPA-PTA optimizations possible.
|
|
|
|
When the indirect function called is ANYTHING we can add disambiguation
|
|
based on the function signatures (or simply the parameter count which
|
|
is the varinfo size). We also do not need to consider functions that
|
|
do not have their address taken.
|
|
|
|
The is_global_var bit which marks escape points is overly conservative
|
|
in IPA mode. Split it to is_escape_point and is_global_var - only
|
|
externally visible globals are escape points in IPA mode.
|
|
There is now is_ipa_escape_point but this is only used in a few
|
|
selected places.
|
|
|
|
The way we introduce DECL_PT_UID to avoid fixing up all points-to
|
|
sets in the translation unit when we copy a DECL during inlining
|
|
pessimizes precision. The advantage is that the DECL_PT_UID keeps
|
|
compile-time and memory usage overhead low - the points-to sets
|
|
do not grow or get unshared as they would during a fixup phase.
|
|
An alternative solution is to delay IPA PTA until after all
|
|
inlining transformations have been applied.
|
|
|
|
The way we propagate clobber/use information isn't optimized.
|
|
It should use a new complex constraint that properly filters
|
|
out local variables of the callee (though that would make
|
|
the sets invalid after inlining). OTOH we might as well
|
|
admit defeat to WHOPR and simply do all the clobber/use analysis
|
|
and propagation after PTA finished but before we threw away
|
|
points-to information for memory variables. WHOPR and PTA
|
|
do not play along well anyway - the whole constraint solving
|
|
would need to be done in WPA phase and it will be very interesting
|
|
to apply the results to local SSA names during LTRANS phase.
|
|
|
|
We probably should compute a per-function unit-ESCAPE solution
|
|
propagating it simply like the clobber / uses solutions. The
|
|
solution can go alongside the non-IPA escaped solution and be
|
|
used to query which vars escape the unit through a function.
|
|
This is also required to make the escaped-HEAP trick work in IPA mode.
|
|
|
|
We never put function decls in points-to sets so we do not
|
|
keep the set of called functions for indirect calls.
|
|
|
|
And probably more. */
|
|
|
|
using namespace pointer_analysis;
|
|
|
|
/* Pool of variable info structures. */
|
|
static object_allocator<variable_info> variable_info_pool
|
|
("Variable info pool");
|
|
|
|
/* Map varinfo to final pt_solution. */
|
|
static hash_map<varinfo_t, pt_solution *> *final_solutions;
|
|
static struct obstack final_solutions_obstack;
|
|
|
|
|
|
namespace pointer_analysis {
|
|
|
|
bool use_field_sensitive = true;
|
|
int in_ipa_mode = 0;
|
|
|
|
/* Used for points-to sets. */
|
|
bitmap_obstack pta_obstack;
|
|
|
|
/* Used for oldsolution members of variables. */
|
|
bitmap_obstack oldpta_obstack;
|
|
|
|
/* Table of variable info structures for constraint variables.
|
|
Indexed directly by variable info id. */
|
|
vec<varinfo_t> varmap;
|
|
|
|
/* List of constraints that we use to build the constraint graph from. */
|
|
vec<constraint_t> constraints;
|
|
|
|
/* The representative variable for a variable. The points-to solution for a
|
|
var can be found in its rep. Trivially, a var can be its own rep.
|
|
|
|
The solver provides this array once it is done solving. */
|
|
unsigned int *var_rep;
|
|
|
|
struct constraint_stats stats;
|
|
|
|
/* Find the first varinfo in the same variable as START that overlaps with
|
|
OFFSET. Return NULL if we can't find one. */
|
|
|
|
varinfo_t
|
|
first_vi_for_offset (varinfo_t start, unsigned HOST_WIDE_INT offset)
|
|
{
|
|
/* If the offset is outside of the variable, bail out. */
|
|
if (offset >= start->fullsize)
|
|
return NULL;
|
|
|
|
/* If we cannot reach offset from start, lookup the first field
|
|
and start from there. */
|
|
if (start->offset > offset)
|
|
start = get_varinfo (start->head);
|
|
|
|
while (start)
|
|
{
|
|
/* We may not find a variable in the field list with the actual
|
|
offset when we have glommed a structure to a variable.
|
|
In that case, however, offset should still be within the size
|
|
of the variable. */
|
|
if (offset >= start->offset
|
|
&& (offset - start->offset) < start->size)
|
|
return start;
|
|
|
|
start = vi_next (start);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Find the first varinfo in the same variable as START that overlaps with
|
|
OFFSET. If there is no such varinfo the varinfo directly preceding
|
|
OFFSET is returned. */
|
|
|
|
varinfo_t
|
|
first_or_preceding_vi_for_offset (varinfo_t start,
|
|
unsigned HOST_WIDE_INT offset)
|
|
{
|
|
/* If we cannot reach offset from start, lookup the first field
|
|
and start from there. */
|
|
if (start->offset > offset)
|
|
start = get_varinfo (start->head);
|
|
|
|
/* We may not find a variable in the field list with the actual
|
|
offset when we have glommed a structure to a variable.
|
|
In that case, however, offset should still be within the size
|
|
of the variable.
|
|
If we got beyond the offset we look for return the field
|
|
directly preceding offset which may be the last field. */
|
|
while (start->next
|
|
&& offset >= start->offset
|
|
&& !((offset - start->offset) < start->size))
|
|
start = vi_next (start);
|
|
|
|
return start;
|
|
}
|
|
|
|
/* Determine global memory access of call STMT and update
|
|
WRITES_GLOBAL_MEMORY, READS_GLOBAL_MEMORY and USES_GLOBAL_MEMORY. */
|
|
|
|
void
|
|
determine_global_memory_access (gcall *stmt,
|
|
bool *writes_global_memory,
|
|
bool *reads_global_memory,
|
|
bool *uses_global_memory)
|
|
{
|
|
tree callee;
|
|
cgraph_node *node;
|
|
modref_summary *summary;
|
|
|
|
/* We need to detrmine reads to set uses. */
|
|
gcc_assert (!uses_global_memory || reads_global_memory);
|
|
|
|
if ((callee = gimple_call_fndecl (stmt)) != NULL_TREE
|
|
&& (node = cgraph_node::get (callee)) != NULL
|
|
&& (summary = get_modref_function_summary (node)))
|
|
{
|
|
if (writes_global_memory && *writes_global_memory)
|
|
*writes_global_memory = summary->global_memory_written;
|
|
if (reads_global_memory && *reads_global_memory)
|
|
*reads_global_memory = summary->global_memory_read;
|
|
if (reads_global_memory && uses_global_memory
|
|
&& !summary->calls_interposable
|
|
&& !*reads_global_memory && node->binds_to_current_def_p ())
|
|
*uses_global_memory = false;
|
|
}
|
|
if ((writes_global_memory && *writes_global_memory)
|
|
|| (uses_global_memory && *uses_global_memory)
|
|
|| (reads_global_memory && *reads_global_memory))
|
|
{
|
|
attr_fnspec fnspec = gimple_call_fnspec (stmt);
|
|
if (fnspec.known_p ())
|
|
{
|
|
if (writes_global_memory
|
|
&& !fnspec.global_memory_written_p ())
|
|
*writes_global_memory = false;
|
|
if (reads_global_memory && !fnspec.global_memory_read_p ())
|
|
{
|
|
*reads_global_memory = false;
|
|
if (uses_global_memory)
|
|
*uses_global_memory = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return true if FNDECL may be part of another lto partition. */
|
|
|
|
bool
|
|
fndecl_maybe_in_other_partition (tree fndecl)
|
|
{
|
|
cgraph_node *fn_node = cgraph_node::get (fndecl);
|
|
if (fn_node == NULL)
|
|
return true;
|
|
|
|
return fn_node->in_other_partition;
|
|
}
|
|
|
|
/* Return a new variable info structure consisting for a variable
|
|
named NAME, and using constraint graph node NODE. Append it
|
|
to the vector of variable info structures. */
|
|
|
|
varinfo_t
|
|
new_var_info (tree t, const char *name, bool add_id)
|
|
{
|
|
unsigned index = varmap.length ();
|
|
varinfo_t ret = variable_info_pool.allocate ();
|
|
|
|
if (dump_file && add_id)
|
|
{
|
|
char *tempname = xasprintf ("%s(%d)", name, index);
|
|
name = ggc_strdup (tempname);
|
|
free (tempname);
|
|
}
|
|
|
|
ret->id = index;
|
|
ret->name = name;
|
|
ret->decl = t;
|
|
/* Vars without decl are artificial and do not have sub-variables. */
|
|
ret->is_artificial_var = (t == NULL_TREE);
|
|
ret->is_special_var = false;
|
|
ret->is_unknown_size_var = false;
|
|
ret->is_full_var = (t == NULL_TREE);
|
|
ret->is_heap_var = false;
|
|
ret->may_have_pointers = true;
|
|
ret->only_restrict_pointers = false;
|
|
ret->is_restrict_var = false;
|
|
ret->ruid = 0;
|
|
ret->is_global_var = (t == NULL_TREE);
|
|
ret->is_ipa_escape_point = false;
|
|
ret->is_fn_info = false;
|
|
ret->address_taken = false;
|
|
if (t && DECL_P (t))
|
|
ret->is_global_var = (is_global_var (t)
|
|
/* We have to treat even local register variables
|
|
as escape points. */
|
|
|| (VAR_P (t) && DECL_HARD_REGISTER (t)));
|
|
ret->is_reg_var = (t && TREE_CODE (t) == SSA_NAME);
|
|
ret->solution = BITMAP_ALLOC (&pta_obstack);
|
|
ret->oldsolution = NULL;
|
|
ret->next = 0;
|
|
ret->shadow_var_uid = 0;
|
|
ret->head = ret->id;
|
|
|
|
stats.total_vars++;
|
|
|
|
varmap.safe_push (ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Print out constraint C to FILE. */
|
|
|
|
void
|
|
dump_constraint (FILE *file, constraint_t c)
|
|
{
|
|
if (c->lhs.type == ADDRESSOF)
|
|
fprintf (file, "&");
|
|
else if (c->lhs.type == DEREF)
|
|
fprintf (file, "*");
|
|
if (dump_file)
|
|
fprintf (file, "%s", get_varinfo (c->lhs.var)->name);
|
|
else
|
|
fprintf (file, "V%d", c->lhs.var);
|
|
if (c->lhs.offset == UNKNOWN_OFFSET)
|
|
fprintf (file, " + UNKNOWN");
|
|
else if (c->lhs.offset != 0)
|
|
fprintf (file, " + " HOST_WIDE_INT_PRINT_DEC, c->lhs.offset);
|
|
fprintf (file, " = ");
|
|
if (c->rhs.type == ADDRESSOF)
|
|
fprintf (file, "&");
|
|
else if (c->rhs.type == DEREF)
|
|
fprintf (file, "*");
|
|
if (dump_file)
|
|
fprintf (file, "%s", get_varinfo (c->rhs.var)->name);
|
|
else
|
|
fprintf (file, "V%d", c->rhs.var);
|
|
if (c->rhs.offset == UNKNOWN_OFFSET)
|
|
fprintf (file, " + UNKNOWN");
|
|
else if (c->rhs.offset != 0)
|
|
fprintf (file, " + " HOST_WIDE_INT_PRINT_DEC, c->rhs.offset);
|
|
}
|
|
|
|
/* Print out constraint C to stderr. */
|
|
|
|
DEBUG_FUNCTION void
|
|
debug_constraint (constraint_t c)
|
|
{
|
|
dump_constraint (stderr, c);
|
|
fprintf (stderr, "\n");
|
|
}
|
|
|
|
/* Print out all constraints to FILE. */
|
|
|
|
void
|
|
dump_constraints (FILE *file, int from)
|
|
{
|
|
int i;
|
|
constraint_t c;
|
|
for (i = from; constraints.iterate (i, &c); i++)
|
|
if (c)
|
|
{
|
|
dump_constraint (file, c);
|
|
fprintf (file, "\n");
|
|
}
|
|
}
|
|
|
|
/* Print out all constraints to stderr. */
|
|
|
|
DEBUG_FUNCTION void
|
|
debug_constraints (void)
|
|
{
|
|
dump_constraints (stderr, 0);
|
|
}
|
|
|
|
/* Print out the points-to solution for VAR to FILE. */
|
|
|
|
void
|
|
dump_solution_for_var (FILE *file, unsigned int var)
|
|
{
|
|
varinfo_t vi = get_varinfo (var);
|
|
unsigned int i;
|
|
bitmap_iterator bi;
|
|
|
|
/* Dump the solution for unified vars anyway, this avoids difficulties
|
|
in scanning dumps in the testsuite. */
|
|
fprintf (file, "%s = { ", vi->name);
|
|
vi = get_varinfo (var_rep[var]);
|
|
EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, i, bi)
|
|
fprintf (file, "%s ", get_varinfo (i)->name);
|
|
fprintf (file, "}");
|
|
|
|
/* But note when the variable was unified. */
|
|
if (vi->id != var)
|
|
fprintf (file, " same as %s", vi->name);
|
|
|
|
fprintf (file, "\n");
|
|
}
|
|
|
|
/* Print the points-to solution for VAR to stderr. */
|
|
|
|
DEBUG_FUNCTION void
|
|
debug_solution_for_var (unsigned int var)
|
|
{
|
|
dump_solution_for_var (stderr, var);
|
|
}
|
|
|
|
/* Dump stats information to OUTFILE. */
|
|
|
|
void
|
|
dump_sa_stats (FILE *outfile)
|
|
{
|
|
fprintf (outfile, "Points-to Stats:\n");
|
|
fprintf (outfile, "Total vars: %d\n", stats.total_vars);
|
|
fprintf (outfile, "Non-pointer vars: %d\n",
|
|
stats.nonpointer_vars);
|
|
fprintf (outfile, "Statically unified vars: %d\n",
|
|
stats.unified_vars_static);
|
|
fprintf (outfile, "Dynamically unified vars: %d\n",
|
|
stats.unified_vars_dynamic);
|
|
fprintf (outfile, "Iterations: %d\n", stats.iterations);
|
|
fprintf (outfile, "Number of edges: %d\n", stats.num_edges);
|
|
fprintf (outfile, "Number of implicit edges: %d\n",
|
|
stats.num_implicit_edges);
|
|
fprintf (outfile, "Number of avoided edges: %d\n",
|
|
stats.num_avoided_edges);
|
|
}
|
|
|
|
/* Dump points-to information to OUTFILE. */
|
|
|
|
void
|
|
dump_sa_points_to_info (FILE *outfile)
|
|
{
|
|
fprintf (outfile, "\nPoints-to sets\n\n");
|
|
|
|
for (unsigned i = 1; i < varmap.length (); i++)
|
|
{
|
|
varinfo_t vi = get_varinfo (i);
|
|
if (!vi->may_have_pointers)
|
|
continue;
|
|
dump_solution_for_var (outfile, i);
|
|
}
|
|
}
|
|
|
|
|
|
/* Debug points-to information to stderr. */
|
|
|
|
DEBUG_FUNCTION void
|
|
debug_sa_points_to_info (void)
|
|
{
|
|
dump_sa_points_to_info (stderr);
|
|
}
|
|
|
|
/* Dump varinfo VI to FILE. */
|
|
|
|
void
|
|
dump_varinfo (FILE *file, varinfo_t vi)
|
|
{
|
|
if (vi == NULL)
|
|
return;
|
|
|
|
fprintf (file, "%u: %s\n", vi->id, vi->name);
|
|
|
|
const char *sep = " ";
|
|
if (vi->is_artificial_var)
|
|
fprintf (file, "%sartificial", sep);
|
|
if (vi->is_special_var)
|
|
fprintf (file, "%sspecial", sep);
|
|
if (vi->is_unknown_size_var)
|
|
fprintf (file, "%sunknown-size", sep);
|
|
if (vi->is_full_var)
|
|
fprintf (file, "%sfull", sep);
|
|
if (vi->is_heap_var)
|
|
fprintf (file, "%sheap", sep);
|
|
if (vi->may_have_pointers)
|
|
fprintf (file, "%smay-have-pointers", sep);
|
|
if (vi->only_restrict_pointers)
|
|
fprintf (file, "%sonly-restrict-pointers", sep);
|
|
if (vi->is_restrict_var)
|
|
fprintf (file, "%sis-restrict-var", sep);
|
|
if (vi->is_global_var)
|
|
fprintf (file, "%sglobal", sep);
|
|
if (vi->is_ipa_escape_point)
|
|
fprintf (file, "%sipa-escape-point", sep);
|
|
if (vi->is_fn_info)
|
|
fprintf (file, "%sfn-info", sep);
|
|
if (vi->ruid)
|
|
fprintf (file, "%srestrict-uid:%u", sep, vi->ruid);
|
|
if (vi->next)
|
|
fprintf (file, "%snext:%u", sep, vi->next);
|
|
if (vi->head != vi->id)
|
|
fprintf (file, "%shead:%u", sep, vi->head);
|
|
if (vi->offset)
|
|
fprintf (file, "%soffset:" HOST_WIDE_INT_PRINT_DEC, sep, vi->offset);
|
|
if (vi->size != ~HOST_WIDE_INT_0U)
|
|
fprintf (file, "%ssize:" HOST_WIDE_INT_PRINT_DEC, sep, vi->size);
|
|
if (vi->fullsize != ~HOST_WIDE_INT_0U && vi->fullsize != vi->size)
|
|
fprintf (file, "%sfullsize:" HOST_WIDE_INT_PRINT_DEC, sep,
|
|
vi->fullsize);
|
|
fprintf (file, "\n");
|
|
|
|
if (vi->solution && !bitmap_empty_p (vi->solution))
|
|
{
|
|
bitmap_iterator bi;
|
|
unsigned i;
|
|
fprintf (file, " solution: {");
|
|
EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, i, bi)
|
|
fprintf (file, " %u", i);
|
|
fprintf (file, " }\n");
|
|
}
|
|
|
|
if (vi->oldsolution && !bitmap_empty_p (vi->oldsolution)
|
|
&& !bitmap_equal_p (vi->solution, vi->oldsolution))
|
|
{
|
|
bitmap_iterator bi;
|
|
unsigned i;
|
|
fprintf (file, " oldsolution: {");
|
|
EXECUTE_IF_SET_IN_BITMAP (vi->oldsolution, 0, i, bi)
|
|
fprintf (file, " %u", i);
|
|
fprintf (file, " }\n");
|
|
}
|
|
}
|
|
|
|
/* Dump varinfo VI to stderr. */
|
|
|
|
DEBUG_FUNCTION void
|
|
debug_varinfo (varinfo_t vi)
|
|
{
|
|
dump_varinfo (stderr, vi);
|
|
}
|
|
|
|
/* Dump varmap to FILE. */
|
|
|
|
void
|
|
dump_varmap (FILE *file)
|
|
{
|
|
if (varmap.length () == 0)
|
|
return;
|
|
|
|
fprintf (file, "variables:\n");
|
|
|
|
for (unsigned int i = 0; i < varmap.length (); ++i)
|
|
{
|
|
varinfo_t vi = get_varinfo (i);
|
|
dump_varinfo (file, vi);
|
|
}
|
|
|
|
fprintf (file, "\n");
|
|
}
|
|
|
|
/* Dump varmap to stderr. */
|
|
|
|
DEBUG_FUNCTION void
|
|
debug_varmap (void)
|
|
{
|
|
dump_varmap (stderr);
|
|
}
|
|
|
|
} // namespace pointer_analysis
|
|
|
|
|
|
/* Structure used to put solution bitmaps in a hashtable so they can
|
|
be shared among variables with the same points-to set. */
|
|
|
|
typedef struct shared_bitmap_info
|
|
{
|
|
bitmap pt_vars;
|
|
hashval_t hashcode;
|
|
} *shared_bitmap_info_t;
|
|
typedef const struct shared_bitmap_info *const_shared_bitmap_info_t;
|
|
|
|
/* Shared_bitmap hashtable helpers. */
|
|
|
|
struct shared_bitmap_hasher : free_ptr_hash <shared_bitmap_info>
|
|
{
|
|
static inline hashval_t hash (const shared_bitmap_info *);
|
|
static inline bool equal (const shared_bitmap_info *,
|
|
const shared_bitmap_info *);
|
|
};
|
|
|
|
/* Hash function for a shared_bitmap_info_t. */
|
|
|
|
inline hashval_t
|
|
shared_bitmap_hasher::hash (const shared_bitmap_info *bi)
|
|
{
|
|
return bi->hashcode;
|
|
}
|
|
|
|
/* Equality function for two shared_bitmap_info_t's. */
|
|
|
|
inline bool
|
|
shared_bitmap_hasher::equal (const shared_bitmap_info *sbi1,
|
|
const shared_bitmap_info *sbi2)
|
|
{
|
|
return bitmap_equal_p (sbi1->pt_vars, sbi2->pt_vars);
|
|
}
|
|
|
|
/* Shared_bitmap hashtable. */
|
|
|
|
static hash_table<shared_bitmap_hasher> *shared_bitmap_table;
|
|
|
|
/* Lookup a bitmap in the shared bitmap hashtable, and return an already
|
|
existing instance if there is one, NULL otherwise. */
|
|
|
|
static bitmap
|
|
shared_bitmap_lookup (bitmap pt_vars)
|
|
{
|
|
shared_bitmap_info **slot;
|
|
struct shared_bitmap_info sbi;
|
|
|
|
sbi.pt_vars = pt_vars;
|
|
sbi.hashcode = bitmap_hash (pt_vars);
|
|
|
|
slot = shared_bitmap_table->find_slot (&sbi, NO_INSERT);
|
|
if (!slot)
|
|
return NULL;
|
|
else
|
|
return (*slot)->pt_vars;
|
|
}
|
|
|
|
/* Add a bitmap to the shared bitmap hashtable. */
|
|
|
|
static void
|
|
shared_bitmap_add (bitmap pt_vars)
|
|
{
|
|
shared_bitmap_info **slot;
|
|
shared_bitmap_info_t sbi = XNEW (struct shared_bitmap_info);
|
|
|
|
sbi->pt_vars = pt_vars;
|
|
sbi->hashcode = bitmap_hash (pt_vars);
|
|
|
|
slot = shared_bitmap_table->find_slot (sbi, INSERT);
|
|
gcc_assert (!*slot);
|
|
*slot = sbi;
|
|
}
|
|
|
|
/* Set bits in INTO corresponding to the variable uids in solution set FROM. */
|
|
|
|
static void
|
|
set_uids_in_ptset (bitmap into, bitmap from, struct pt_solution *pt,
|
|
tree fndecl)
|
|
{
|
|
unsigned int i;
|
|
bitmap_iterator bi;
|
|
varinfo_t escaped_vi = get_varinfo (var_rep[escaped_id]);
|
|
varinfo_t escaped_return_vi = get_varinfo (var_rep[escaped_return_id]);
|
|
bool everything_escaped
|
|
= escaped_vi->solution && bitmap_bit_p (escaped_vi->solution, anything_id);
|
|
|
|
EXECUTE_IF_SET_IN_BITMAP (from, 0, i, bi)
|
|
{
|
|
varinfo_t vi = get_varinfo (i);
|
|
|
|
if (vi->is_artificial_var)
|
|
continue;
|
|
|
|
if (everything_escaped
|
|
|| (escaped_vi->solution
|
|
&& bitmap_bit_p (escaped_vi->solution, i)))
|
|
{
|
|
pt->vars_contains_escaped = true;
|
|
pt->vars_contains_escaped_heap |= vi->is_heap_var;
|
|
}
|
|
if (escaped_return_vi->solution
|
|
&& bitmap_bit_p (escaped_return_vi->solution, i))
|
|
pt->vars_contains_escaped_heap |= vi->is_heap_var;
|
|
|
|
if (vi->is_restrict_var)
|
|
pt->vars_contains_restrict = true;
|
|
|
|
if (VAR_P (vi->decl)
|
|
|| TREE_CODE (vi->decl) == PARM_DECL
|
|
|| TREE_CODE (vi->decl) == RESULT_DECL)
|
|
{
|
|
/* If we are in IPA mode we will not recompute points-to
|
|
sets after inlining so make sure they stay valid. */
|
|
if (in_ipa_mode
|
|
&& !DECL_PT_UID_SET_P (vi->decl))
|
|
SET_DECL_PT_UID (vi->decl, DECL_UID (vi->decl));
|
|
|
|
/* Add the decl to the points-to set. Note that the points-to
|
|
set contains global variables. */
|
|
bitmap_set_bit (into, DECL_PT_UID (vi->decl));
|
|
if (vi->is_global_var
|
|
/* In IPA mode the escaped_heap trick doesn't work as
|
|
ESCAPED is escaped from the unit but
|
|
pt_solution_includes_global needs to answer true for
|
|
all variables not automatic within a function.
|
|
For the same reason is_global_var is not the
|
|
correct flag to track - local variables from other
|
|
functions also need to be considered global.
|
|
Conveniently all HEAP vars are not put in function
|
|
scope. */
|
|
|| (in_ipa_mode
|
|
&& fndecl
|
|
&& ! auto_var_in_fn_p (vi->decl, fndecl)))
|
|
pt->vars_contains_nonlocal = true;
|
|
|
|
/* If we have a variable that is interposable record that fact
|
|
for pointer comparison simplification. */
|
|
if (VAR_P (vi->decl)
|
|
&& (TREE_STATIC (vi->decl) || DECL_EXTERNAL (vi->decl))
|
|
&& ! decl_binds_to_current_def_p (vi->decl))
|
|
pt->vars_contains_interposable = true;
|
|
|
|
/* If this is a local variable we can have overlapping lifetime
|
|
of different function invocations through recursion duplicate
|
|
it with its shadow variable. */
|
|
if (in_ipa_mode
|
|
&& vi->shadow_var_uid != 0)
|
|
{
|
|
bitmap_set_bit (into, vi->shadow_var_uid);
|
|
pt->vars_contains_nonlocal = true;
|
|
}
|
|
}
|
|
|
|
else if (TREE_CODE (vi->decl) == FUNCTION_DECL
|
|
|| TREE_CODE (vi->decl) == LABEL_DECL)
|
|
{
|
|
/* Nothing should read/write from/to code so we can
|
|
save bits by not including them in the points-to bitmaps.
|
|
Still mark the points-to set as containing global memory
|
|
to make code-patching possible - see PR70128. */
|
|
pt->vars_contains_nonlocal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Compute the points-to solution *PT for the variable VI. */
|
|
|
|
static struct pt_solution
|
|
find_what_var_points_to (tree fndecl, varinfo_t orig_vi)
|
|
{
|
|
unsigned int i;
|
|
bitmap_iterator bi;
|
|
bitmap finished_solution;
|
|
bitmap result;
|
|
varinfo_t vi;
|
|
struct pt_solution *pt;
|
|
|
|
/* This variable may have been collapsed, let's get the real
|
|
variable. */
|
|
vi = get_varinfo (var_rep[orig_vi->id]);
|
|
|
|
/* See if we have already computed the solution and return it. */
|
|
pt_solution **slot = &final_solutions->get_or_insert (vi);
|
|
if (*slot != NULL)
|
|
return **slot;
|
|
|
|
*slot = pt = XOBNEW (&final_solutions_obstack, struct pt_solution);
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
|
|
/* Translate artificial variables into SSA_NAME_PTR_INFO
|
|
attributes. */
|
|
EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, i, bi)
|
|
{
|
|
varinfo_t vi = get_varinfo (i);
|
|
|
|
if (vi->is_artificial_var)
|
|
{
|
|
if (vi->id == nothing_id)
|
|
pt->null = 1;
|
|
else if (vi->id == escaped_id)
|
|
{
|
|
if (in_ipa_mode)
|
|
pt->ipa_escaped = 1;
|
|
else
|
|
pt->escaped = 1;
|
|
/* Expand some special vars of ESCAPED in-place here. */
|
|
varinfo_t evi = get_varinfo (var_rep[escaped_id]);
|
|
if (bitmap_bit_p (evi->solution, nonlocal_id))
|
|
pt->nonlocal = 1;
|
|
}
|
|
else if (vi->id == nonlocal_id)
|
|
pt->nonlocal = 1;
|
|
else if (vi->id == string_id)
|
|
pt->const_pool = 1;
|
|
else if (vi->id == anything_id
|
|
|| vi->id == integer_id)
|
|
pt->anything = 1;
|
|
}
|
|
}
|
|
|
|
/* Instead of doing extra work, simply do not create
|
|
elaborate points-to information for pt_anything pointers. */
|
|
if (pt->anything)
|
|
return *pt;
|
|
|
|
/* Share the final set of variables when possible. */
|
|
finished_solution = BITMAP_GGC_ALLOC ();
|
|
stats.points_to_sets_created++;
|
|
|
|
set_uids_in_ptset (finished_solution, vi->solution, pt, fndecl);
|
|
result = shared_bitmap_lookup (finished_solution);
|
|
if (!result)
|
|
{
|
|
shared_bitmap_add (finished_solution);
|
|
pt->vars = finished_solution;
|
|
}
|
|
else
|
|
{
|
|
pt->vars = result;
|
|
bitmap_clear (finished_solution);
|
|
}
|
|
|
|
return *pt;
|
|
}
|
|
|
|
/* Given a pointer variable P, fill in its points-to set. */
|
|
|
|
static void
|
|
find_what_p_points_to (tree fndecl, tree p)
|
|
{
|
|
struct ptr_info_def *pi;
|
|
tree lookup_p = p;
|
|
varinfo_t vi;
|
|
prange vr;
|
|
get_range_query (DECL_STRUCT_FUNCTION (fndecl))->range_of_expr (vr, p);
|
|
bool nonnull = vr.nonzero_p ();
|
|
|
|
/* For parameters, get at the points-to set for the actual parm
|
|
decl. */
|
|
if (TREE_CODE (p) == SSA_NAME
|
|
&& SSA_NAME_IS_DEFAULT_DEF (p)
|
|
&& (TREE_CODE (SSA_NAME_VAR (p)) == PARM_DECL
|
|
|| TREE_CODE (SSA_NAME_VAR (p)) == RESULT_DECL))
|
|
lookup_p = SSA_NAME_VAR (p);
|
|
|
|
vi = lookup_vi_for_tree (lookup_p);
|
|
if (!vi)
|
|
return;
|
|
|
|
pi = get_ptr_info (p);
|
|
pi->pt = find_what_var_points_to (fndecl, vi);
|
|
/* Conservatively set to NULL from PTA (to true). */
|
|
pi->pt.null = 1;
|
|
/* Preserve pointer nonnull globally computed. */
|
|
if (nonnull)
|
|
set_ptr_nonnull (p);
|
|
}
|
|
|
|
|
|
/* Query statistics for points-to solutions. */
|
|
|
|
static struct {
|
|
unsigned HOST_WIDE_INT pt_solution_includes_may_alias;
|
|
unsigned HOST_WIDE_INT pt_solution_includes_no_alias;
|
|
unsigned HOST_WIDE_INT pt_solutions_intersect_may_alias;
|
|
unsigned HOST_WIDE_INT pt_solutions_intersect_no_alias;
|
|
} pta_stats;
|
|
|
|
void
|
|
dump_pta_stats (FILE *s)
|
|
{
|
|
fprintf (s, "\nPTA query stats:\n");
|
|
fprintf (s, " pt_solution_includes: "
|
|
HOST_WIDE_INT_PRINT_DEC" disambiguations, "
|
|
HOST_WIDE_INT_PRINT_DEC" queries\n",
|
|
pta_stats.pt_solution_includes_no_alias,
|
|
pta_stats.pt_solution_includes_no_alias
|
|
+ pta_stats.pt_solution_includes_may_alias);
|
|
fprintf (s, " pt_solutions_intersect: "
|
|
HOST_WIDE_INT_PRINT_DEC" disambiguations, "
|
|
HOST_WIDE_INT_PRINT_DEC" queries\n",
|
|
pta_stats.pt_solutions_intersect_no_alias,
|
|
pta_stats.pt_solutions_intersect_no_alias
|
|
+ pta_stats.pt_solutions_intersect_may_alias);
|
|
}
|
|
|
|
|
|
/* Reset the points-to solution *PT to a conservative default
|
|
(point to anything). */
|
|
|
|
void
|
|
pt_solution_reset (struct pt_solution *pt)
|
|
{
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
pt->anything = true;
|
|
pt->null = true;
|
|
}
|
|
|
|
/* Set the points-to solution *PT to point only to the variables
|
|
in VARS. VARS_CONTAINS_GLOBAL specifies whether that contains
|
|
global variables and VARS_CONTAINS_RESTRICT specifies whether
|
|
it contains restrict tag variables. */
|
|
|
|
void
|
|
pt_solution_set (struct pt_solution *pt, bitmap vars,
|
|
bool vars_contains_nonlocal)
|
|
{
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
pt->vars = vars;
|
|
pt->vars_contains_nonlocal = vars_contains_nonlocal;
|
|
pt->vars_contains_escaped
|
|
= (cfun->gimple_df->escaped.anything
|
|
|| bitmap_intersect_p (cfun->gimple_df->escaped.vars, vars));
|
|
}
|
|
|
|
/* Set the points-to solution *PT to point only to the variable VAR. */
|
|
|
|
void
|
|
pt_solution_set_var (struct pt_solution *pt, tree var)
|
|
{
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
pt->vars = BITMAP_GGC_ALLOC ();
|
|
bitmap_set_bit (pt->vars, DECL_PT_UID (var));
|
|
pt->vars_contains_nonlocal = is_global_var (var);
|
|
pt->vars_contains_escaped
|
|
= (cfun->gimple_df->escaped.anything
|
|
|| bitmap_bit_p (cfun->gimple_df->escaped.vars, DECL_PT_UID (var)));
|
|
}
|
|
|
|
/* Computes the union of the points-to solutions *DEST and *SRC and
|
|
stores the result in *DEST. This changes the points-to bitmap
|
|
of *DEST and thus may not be used if that might be shared.
|
|
The points-to bitmap of *SRC and *DEST will not be shared after
|
|
this function if they were not before. */
|
|
|
|
static void
|
|
pt_solution_ior_into (struct pt_solution *dest, struct pt_solution *src)
|
|
{
|
|
dest->anything |= src->anything;
|
|
if (dest->anything)
|
|
{
|
|
pt_solution_reset (dest);
|
|
return;
|
|
}
|
|
|
|
dest->nonlocal |= src->nonlocal;
|
|
dest->escaped |= src->escaped;
|
|
dest->ipa_escaped |= src->ipa_escaped;
|
|
dest->null |= src->null;
|
|
dest->const_pool |= src->const_pool ;
|
|
dest->vars_contains_nonlocal |= src->vars_contains_nonlocal;
|
|
dest->vars_contains_escaped |= src->vars_contains_escaped;
|
|
dest->vars_contains_escaped_heap |= src->vars_contains_escaped_heap;
|
|
if (!src->vars)
|
|
return;
|
|
|
|
if (!dest->vars)
|
|
dest->vars = BITMAP_GGC_ALLOC ();
|
|
bitmap_ior_into (dest->vars, src->vars);
|
|
}
|
|
|
|
/* Return true if the points-to solution *PT is empty. */
|
|
|
|
bool
|
|
pt_solution_empty_p (const pt_solution *pt)
|
|
{
|
|
if (pt->anything
|
|
|| pt->nonlocal)
|
|
return false;
|
|
|
|
if (pt->vars
|
|
&& !bitmap_empty_p (pt->vars))
|
|
return false;
|
|
|
|
/* If the solution includes ESCAPED, check if that is empty. */
|
|
if (pt->escaped
|
|
&& !pt_solution_empty_p (&cfun->gimple_df->escaped))
|
|
return false;
|
|
|
|
/* If the solution includes ESCAPED, check if that is empty. */
|
|
if (pt->ipa_escaped
|
|
&& !pt_solution_empty_p (&ipa_escaped_pt))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Return true if the points-to solution *PT only point to a single var, and
|
|
return the var uid in *UID. */
|
|
|
|
bool
|
|
pt_solution_singleton_or_null_p (struct pt_solution *pt, unsigned *uid)
|
|
{
|
|
if (pt->anything || pt->nonlocal || pt->escaped || pt->ipa_escaped
|
|
|| pt->vars == NULL
|
|
|| !bitmap_single_bit_set_p (pt->vars))
|
|
return false;
|
|
|
|
*uid = bitmap_first_set_bit (pt->vars);
|
|
return true;
|
|
}
|
|
|
|
/* Return true if the points-to solution *PT includes global memory.
|
|
If ESCAPED_LOCAL_P is true then escaped local variables are also
|
|
considered global. */
|
|
|
|
bool
|
|
pt_solution_includes_global (struct pt_solution *pt, bool escaped_local_p)
|
|
{
|
|
if (pt->anything
|
|
|| pt->nonlocal
|
|
|| pt->vars_contains_nonlocal
|
|
/* The following is a hack to make the malloc escape hack work.
|
|
In reality we'd need different sets for escaped-through-return
|
|
and escaped-to-callees and passes would need to be updated. */
|
|
|| pt->vars_contains_escaped_heap)
|
|
return true;
|
|
|
|
if (escaped_local_p && pt->vars_contains_escaped)
|
|
return true;
|
|
|
|
/* 'escaped' is also a placeholder so we have to look into it. */
|
|
if (pt->escaped)
|
|
return pt_solution_includes_global (&cfun->gimple_df->escaped,
|
|
escaped_local_p);
|
|
|
|
if (pt->ipa_escaped)
|
|
return pt_solution_includes_global (&ipa_escaped_pt,
|
|
escaped_local_p);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Return true if the points-to solution *PT includes the variable
|
|
declaration DECL. */
|
|
|
|
static bool
|
|
pt_solution_includes_1 (struct pt_solution *pt, const_tree decl)
|
|
{
|
|
if (pt->anything)
|
|
return true;
|
|
|
|
if (pt->nonlocal
|
|
&& is_global_var (decl))
|
|
return true;
|
|
|
|
if (pt->vars
|
|
&& bitmap_bit_p (pt->vars, DECL_PT_UID (decl)))
|
|
return true;
|
|
|
|
/* If the solution includes ESCAPED, check it. */
|
|
if (pt->escaped
|
|
&& pt_solution_includes_1 (&cfun->gimple_df->escaped, decl))
|
|
return true;
|
|
|
|
/* If the solution includes ESCAPED, check it. */
|
|
if (pt->ipa_escaped
|
|
&& pt_solution_includes_1 (&ipa_escaped_pt, decl))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
pt_solution_includes (struct pt_solution *pt, const_tree decl)
|
|
{
|
|
bool res = pt_solution_includes_1 (pt, decl);
|
|
if (res)
|
|
++pta_stats.pt_solution_includes_may_alias;
|
|
else
|
|
++pta_stats.pt_solution_includes_no_alias;
|
|
return res;
|
|
}
|
|
|
|
/* Return true if the points-to solution *PT contains a reference to a
|
|
constant pool entry. */
|
|
|
|
bool
|
|
pt_solution_includes_const_pool (struct pt_solution *pt)
|
|
{
|
|
return (pt->const_pool
|
|
|| pt->nonlocal
|
|
|| (pt->escaped && (!cfun || cfun->gimple_df->escaped.const_pool))
|
|
|| (pt->ipa_escaped && ipa_escaped_pt.const_pool));
|
|
}
|
|
|
|
/* Return true if both points-to solutions PT1 and PT2 have a non-empty
|
|
intersection. */
|
|
|
|
static bool
|
|
pt_solutions_intersect_1 (struct pt_solution *pt1, struct pt_solution *pt2)
|
|
{
|
|
if (pt1->anything || pt2->anything)
|
|
return true;
|
|
|
|
/* If either points to unknown global memory and the other points to
|
|
any global memory they alias. */
|
|
if ((pt1->nonlocal
|
|
&& (pt2->nonlocal
|
|
|| pt2->vars_contains_nonlocal))
|
|
|| (pt2->nonlocal
|
|
&& pt1->vars_contains_nonlocal))
|
|
return true;
|
|
|
|
/* If either points to all escaped memory and the other points to
|
|
any escaped memory they alias. */
|
|
if ((pt1->escaped
|
|
&& (pt2->escaped
|
|
|| pt2->vars_contains_escaped))
|
|
|| (pt2->escaped
|
|
&& pt1->vars_contains_escaped))
|
|
return true;
|
|
|
|
/* Check the escaped solution if required.
|
|
??? Do we need to check the local against the IPA escaped sets? */
|
|
if ((pt1->ipa_escaped || pt2->ipa_escaped)
|
|
&& !pt_solution_empty_p (&ipa_escaped_pt))
|
|
{
|
|
/* If both point to escaped memory and that solution
|
|
is not empty they alias. */
|
|
if (pt1->ipa_escaped && pt2->ipa_escaped)
|
|
return true;
|
|
|
|
/* If either points to escaped memory see if the escaped solution
|
|
intersects with the other. */
|
|
if ((pt1->ipa_escaped
|
|
&& pt_solutions_intersect_1 (&ipa_escaped_pt, pt2))
|
|
|| (pt2->ipa_escaped
|
|
&& pt_solutions_intersect_1 (&ipa_escaped_pt, pt1)))
|
|
return true;
|
|
}
|
|
|
|
/* Now both pointers alias if their points-to solution intersects. */
|
|
return (pt1->vars
|
|
&& pt2->vars
|
|
&& bitmap_intersect_p (pt1->vars, pt2->vars));
|
|
}
|
|
|
|
bool
|
|
pt_solutions_intersect (struct pt_solution *pt1, struct pt_solution *pt2)
|
|
{
|
|
bool res = pt_solutions_intersect_1 (pt1, pt2);
|
|
if (res)
|
|
++pta_stats.pt_solutions_intersect_may_alias;
|
|
else
|
|
++pta_stats.pt_solutions_intersect_no_alias;
|
|
return res;
|
|
}
|
|
|
|
|
|
/* Initialize things necessary to perform PTA. */
|
|
|
|
static void
|
|
init_alias_vars (void)
|
|
{
|
|
use_field_sensitive = (param_max_fields_for_field_sensitive > 1);
|
|
|
|
bitmap_obstack_initialize (&pta_obstack);
|
|
bitmap_obstack_initialize (&oldpta_obstack);
|
|
|
|
constraints.create (8);
|
|
varmap.create (8);
|
|
|
|
memset (&stats, 0, sizeof (stats));
|
|
shared_bitmap_table = new hash_table<shared_bitmap_hasher> (511);
|
|
|
|
final_solutions = new hash_map<varinfo_t, pt_solution *>;
|
|
gcc_obstack_init (&final_solutions_obstack);
|
|
|
|
init_constraint_builder ();
|
|
}
|
|
|
|
/* Create points-to sets for the current function. See the comments
|
|
at the start of the file for an algorithmic overview. */
|
|
|
|
static void
|
|
compute_points_to_sets (void)
|
|
{
|
|
basic_block bb;
|
|
varinfo_t vi;
|
|
|
|
timevar_push (TV_TREE_PTA);
|
|
|
|
init_alias_vars ();
|
|
|
|
intra_build_constraints ();
|
|
|
|
/* From the constraints compute the points-to sets. */
|
|
solve_constraints ();
|
|
|
|
if (dump_file && (dump_flags & TDF_STATS))
|
|
dump_sa_stats (dump_file);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
dump_sa_points_to_info (dump_file);
|
|
|
|
/* Compute the points-to set for ESCAPED used for call-clobber analysis. */
|
|
cfun->gimple_df->escaped = find_what_var_points_to (cfun->decl,
|
|
get_varinfo (escaped_id));
|
|
|
|
/* Make sure the ESCAPED solution (which is used as placeholder in
|
|
other solutions) does not reference itself. This simplifies
|
|
points-to solution queries. */
|
|
cfun->gimple_df->escaped.escaped = 0;
|
|
|
|
/* The ESCAPED_RETURN solution is what contains all memory that needs
|
|
to be considered global. */
|
|
cfun->gimple_df->escaped_return
|
|
= find_what_var_points_to (cfun->decl, get_varinfo (escaped_return_id));
|
|
cfun->gimple_df->escaped_return.escaped = 1;
|
|
|
|
/* Compute the points-to sets for pointer SSA_NAMEs. */
|
|
unsigned i;
|
|
tree ptr;
|
|
|
|
FOR_EACH_SSA_NAME (i, ptr, cfun)
|
|
{
|
|
if (POINTER_TYPE_P (TREE_TYPE (ptr)))
|
|
find_what_p_points_to (cfun->decl, ptr);
|
|
}
|
|
|
|
/* Compute the call-used/clobbered sets. */
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
{
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gcall *stmt;
|
|
struct pt_solution *pt;
|
|
|
|
stmt = dyn_cast <gcall *> (gsi_stmt (gsi));
|
|
if (!stmt)
|
|
continue;
|
|
|
|
pt = gimple_call_use_set (stmt);
|
|
if (gimple_call_flags (stmt) & ECF_CONST)
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
else
|
|
{
|
|
bool uses_global_memory = true;
|
|
bool reads_global_memory = true;
|
|
|
|
determine_global_memory_access (stmt, NULL,
|
|
&reads_global_memory,
|
|
&uses_global_memory);
|
|
if ((vi = lookup_call_use_vi (stmt)) != NULL)
|
|
{
|
|
*pt = find_what_var_points_to (cfun->decl, vi);
|
|
/* Escaped (and thus nonlocal) variables are always
|
|
implicitly used by calls. */
|
|
/* ??? ESCAPED can be empty even though NONLOCAL
|
|
always escaped. */
|
|
if (uses_global_memory)
|
|
{
|
|
pt->nonlocal = 1;
|
|
pt->escaped = 1;
|
|
}
|
|
}
|
|
else if (uses_global_memory)
|
|
{
|
|
/* If there is nothing special about this call then
|
|
we have made everything that is used also escape. */
|
|
*pt = cfun->gimple_df->escaped;
|
|
pt->nonlocal = 1;
|
|
}
|
|
else
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
}
|
|
|
|
pt = gimple_call_clobber_set (stmt);
|
|
if (gimple_call_flags (stmt) & (ECF_CONST|ECF_PURE|ECF_NOVOPS))
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
else
|
|
{
|
|
bool writes_global_memory = true;
|
|
|
|
determine_global_memory_access (stmt, &writes_global_memory,
|
|
NULL, NULL);
|
|
|
|
if ((vi = lookup_call_clobber_vi (stmt)) != NULL)
|
|
{
|
|
*pt = find_what_var_points_to (cfun->decl, vi);
|
|
/* Escaped (and thus nonlocal) variables are always
|
|
implicitly clobbered by calls. */
|
|
/* ??? ESCAPED can be empty even though NONLOCAL
|
|
always escaped. */
|
|
if (writes_global_memory)
|
|
{
|
|
pt->nonlocal = 1;
|
|
pt->escaped = 1;
|
|
}
|
|
}
|
|
else if (writes_global_memory)
|
|
{
|
|
/* If there is nothing special about this call then
|
|
we have made everything that is used also escape. */
|
|
*pt = cfun->gimple_df->escaped;
|
|
pt->nonlocal = 1;
|
|
}
|
|
else
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
}
|
|
}
|
|
}
|
|
|
|
timevar_pop (TV_TREE_PTA);
|
|
}
|
|
|
|
/* Delete created points-to sets. */
|
|
|
|
static void
|
|
delete_points_to_sets (void)
|
|
{
|
|
delete shared_bitmap_table;
|
|
shared_bitmap_table = NULL;
|
|
if (dump_file && (dump_flags & TDF_STATS))
|
|
fprintf (dump_file, "Points to sets created:%d\n",
|
|
stats.points_to_sets_created);
|
|
|
|
bitmap_obstack_release (&pta_obstack);
|
|
constraints.release ();
|
|
|
|
free (var_rep);
|
|
|
|
varmap.release ();
|
|
variable_info_pool.release ();
|
|
|
|
delete final_solutions;
|
|
obstack_free (&final_solutions_obstack, NULL);
|
|
|
|
delete_constraint_builder ();
|
|
}
|
|
|
|
|
|
struct vls_data
|
|
{
|
|
unsigned short clique;
|
|
bool escaped_p;
|
|
bitmap rvars;
|
|
};
|
|
|
|
/* Mark "other" loads and stores as belonging to CLIQUE and with
|
|
base zero. */
|
|
|
|
static bool
|
|
visit_loadstore (gimple *, tree base, tree ref, void *data)
|
|
{
|
|
unsigned short clique = ((vls_data *) data)->clique;
|
|
bitmap rvars = ((vls_data *) data)->rvars;
|
|
bool escaped_p = ((vls_data *) data)->escaped_p;
|
|
if (TREE_CODE (base) == MEM_REF
|
|
|| TREE_CODE (base) == TARGET_MEM_REF)
|
|
{
|
|
tree ptr = TREE_OPERAND (base, 0);
|
|
if (TREE_CODE (ptr) == SSA_NAME)
|
|
{
|
|
/* For parameters, get at the points-to set for the actual parm
|
|
decl. */
|
|
if (SSA_NAME_IS_DEFAULT_DEF (ptr)
|
|
&& (TREE_CODE (SSA_NAME_VAR (ptr)) == PARM_DECL
|
|
|| TREE_CODE (SSA_NAME_VAR (ptr)) == RESULT_DECL))
|
|
ptr = SSA_NAME_VAR (ptr);
|
|
|
|
/* We need to make sure 'ptr' doesn't include any of
|
|
the restrict tags we added bases for in its points-to set. */
|
|
varinfo_t vi = lookup_vi_for_tree (ptr);
|
|
if (! vi)
|
|
return false;
|
|
|
|
vi = get_varinfo (var_rep[vi->id]);
|
|
if (bitmap_intersect_p (rvars, vi->solution)
|
|
|| (escaped_p && bitmap_bit_p (vi->solution, escaped_id)))
|
|
return false;
|
|
}
|
|
|
|
/* Do not overwrite existing cliques (that includes clique, base
|
|
pairs we just set). */
|
|
if (MR_DEPENDENCE_CLIQUE (base) == 0)
|
|
{
|
|
MR_DEPENDENCE_CLIQUE (base) = clique;
|
|
MR_DEPENDENCE_BASE (base) = 0;
|
|
}
|
|
}
|
|
|
|
/* For plain decl accesses see whether they are accesses to globals
|
|
and rewrite them to MEM_REFs with { clique, 0 }. */
|
|
if (VAR_P (base)
|
|
&& is_global_var (base)
|
|
/* ??? We can't rewrite a plain decl with the walk_stmt_load_store
|
|
ops callback. */
|
|
&& base != ref)
|
|
{
|
|
tree *basep = &ref;
|
|
while (handled_component_p (*basep))
|
|
basep = &TREE_OPERAND (*basep, 0);
|
|
gcc_assert (VAR_P (*basep));
|
|
tree ptr = build_fold_addr_expr (*basep);
|
|
tree zero = build_int_cst (TREE_TYPE (ptr), 0);
|
|
*basep = build2 (MEM_REF, TREE_TYPE (*basep), ptr, zero);
|
|
MR_DEPENDENCE_CLIQUE (*basep) = clique;
|
|
MR_DEPENDENCE_BASE (*basep) = 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct msdi_data {
|
|
tree ptr;
|
|
unsigned short *clique;
|
|
unsigned short *last_ruid;
|
|
varinfo_t restrict_var;
|
|
};
|
|
|
|
/* If BASE is a MEM_REF then assign a clique, base pair to it, updating
|
|
CLIQUE, *RESTRICT_VAR and LAST_RUID as passed via DATA.
|
|
Return whether dependence info was assigned to BASE. */
|
|
|
|
static bool
|
|
maybe_set_dependence_info (gimple *, tree base, tree, void *data)
|
|
{
|
|
tree ptr = ((msdi_data *)data)->ptr;
|
|
unsigned short &clique = *((msdi_data *)data)->clique;
|
|
unsigned short &last_ruid = *((msdi_data *)data)->last_ruid;
|
|
varinfo_t restrict_var = ((msdi_data *)data)->restrict_var;
|
|
if ((TREE_CODE (base) == MEM_REF
|
|
|| TREE_CODE (base) == TARGET_MEM_REF)
|
|
&& TREE_OPERAND (base, 0) == ptr)
|
|
{
|
|
/* Do not overwrite existing cliques. This avoids overwriting dependence
|
|
info inlined from a function with restrict parameters inlined
|
|
into a function with restrict parameters. This usually means we
|
|
prefer to be precise in innermost loops. */
|
|
if (MR_DEPENDENCE_CLIQUE (base) == 0)
|
|
{
|
|
if (clique == 0)
|
|
{
|
|
if (cfun->last_clique == 0)
|
|
cfun->last_clique = 1;
|
|
clique = 1;
|
|
}
|
|
if (restrict_var->ruid == 0)
|
|
restrict_var->ruid = ++last_ruid;
|
|
MR_DEPENDENCE_CLIQUE (base) = clique;
|
|
MR_DEPENDENCE_BASE (base) = restrict_var->ruid;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Clear dependence info for the clique DATA. */
|
|
|
|
static bool
|
|
clear_dependence_clique (gimple *, tree base, tree, void *data)
|
|
{
|
|
unsigned short clique = (uintptr_t)data;
|
|
if ((TREE_CODE (base) == MEM_REF
|
|
|| TREE_CODE (base) == TARGET_MEM_REF)
|
|
&& MR_DEPENDENCE_CLIQUE (base) == clique)
|
|
{
|
|
MR_DEPENDENCE_CLIQUE (base) = 0;
|
|
MR_DEPENDENCE_BASE (base) = 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Compute the set of independend memory references based on restrict
|
|
tags and their conservative propagation to the points-to sets. */
|
|
|
|
static void
|
|
compute_dependence_clique (void)
|
|
{
|
|
/* First clear the special "local" clique. */
|
|
basic_block bb;
|
|
if (cfun->last_clique != 0)
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
|
|
!gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
walk_stmt_load_store_ops (stmt, (void *)(uintptr_t) 1,
|
|
clear_dependence_clique,
|
|
clear_dependence_clique);
|
|
}
|
|
|
|
unsigned short clique = 0;
|
|
unsigned short last_ruid = 0;
|
|
bitmap rvars = BITMAP_ALLOC (NULL);
|
|
bool escaped_p = false;
|
|
for (unsigned i = 0; i < num_ssa_names; ++i)
|
|
{
|
|
tree ptr = ssa_name (i);
|
|
if (!ptr || !POINTER_TYPE_P (TREE_TYPE (ptr)))
|
|
continue;
|
|
|
|
/* Avoid all this when ptr is not dereferenced? */
|
|
tree p = ptr;
|
|
if (SSA_NAME_IS_DEFAULT_DEF (ptr)
|
|
&& (TREE_CODE (SSA_NAME_VAR (ptr)) == PARM_DECL
|
|
|| TREE_CODE (SSA_NAME_VAR (ptr)) == RESULT_DECL))
|
|
p = SSA_NAME_VAR (ptr);
|
|
varinfo_t vi = lookup_vi_for_tree (p);
|
|
if (!vi)
|
|
continue;
|
|
vi = get_varinfo (var_rep[vi->id]);
|
|
bitmap_iterator bi;
|
|
unsigned j;
|
|
varinfo_t restrict_var = NULL;
|
|
EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, j, bi)
|
|
{
|
|
varinfo_t oi = get_varinfo (j);
|
|
if (oi->head != j)
|
|
oi = get_varinfo (oi->head);
|
|
if (oi->is_restrict_var)
|
|
{
|
|
if (restrict_var
|
|
&& restrict_var != oi)
|
|
{
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "found restrict pointed-to "
|
|
"for ");
|
|
print_generic_expr (dump_file, ptr);
|
|
fprintf (dump_file, " but not exclusively\n");
|
|
}
|
|
restrict_var = NULL;
|
|
break;
|
|
}
|
|
restrict_var = oi;
|
|
}
|
|
/* NULL is the only other valid points-to entry. */
|
|
else if (oi->id != nothing_id)
|
|
{
|
|
restrict_var = NULL;
|
|
break;
|
|
}
|
|
}
|
|
/* Ok, found that ptr must(!) point to a single(!) restrict
|
|
variable. */
|
|
/* ??? PTA isn't really a proper propagation engine to compute
|
|
this property.
|
|
??? We could handle merging of two restricts by unifying them. */
|
|
if (restrict_var)
|
|
{
|
|
/* Now look at possible dereferences of ptr. */
|
|
imm_use_iterator ui;
|
|
gimple *use_stmt;
|
|
bool used = false;
|
|
msdi_data data = { ptr, &clique, &last_ruid, restrict_var };
|
|
FOR_EACH_IMM_USE_STMT (use_stmt, ui, ptr)
|
|
used |= walk_stmt_load_store_ops (use_stmt, &data,
|
|
maybe_set_dependence_info,
|
|
maybe_set_dependence_info);
|
|
if (used)
|
|
{
|
|
/* Add all subvars to the set of restrict pointed-to set. */
|
|
for (unsigned sv = restrict_var->head; sv != 0;
|
|
sv = get_varinfo (sv)->next)
|
|
bitmap_set_bit (rvars, sv);
|
|
varinfo_t escaped = get_varinfo (var_rep[escaped_id]);
|
|
if (bitmap_bit_p (escaped->solution, restrict_var->id))
|
|
escaped_p = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clique != 0)
|
|
{
|
|
/* Assign the BASE id zero to all accesses not based on a restrict
|
|
pointer. That way they get disambiguated against restrict
|
|
accesses but not against each other. */
|
|
/* ??? For restricts derived from globals (thus not incoming
|
|
parameters) we can't restrict scoping properly thus the following
|
|
is too aggressive there. For now we have excluded those globals from
|
|
getting into the MR_DEPENDENCE machinery. */
|
|
vls_data data = { clique, escaped_p, rvars };
|
|
basic_block bb;
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
|
|
!gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
walk_stmt_load_store_ops (stmt, &data,
|
|
visit_loadstore, visit_loadstore);
|
|
}
|
|
}
|
|
|
|
BITMAP_FREE (rvars);
|
|
}
|
|
|
|
|
|
/* Compute points-to information for every SSA_NAME pointer in the
|
|
current function and compute the transitive closure of escaped
|
|
variables to re-initialize the call-clobber states of local variables. */
|
|
|
|
unsigned int
|
|
compute_may_aliases (void)
|
|
{
|
|
if (cfun->gimple_df->ipa_pta)
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "\nNot re-computing points-to information "
|
|
"because IPA points-to information is available.\n\n");
|
|
|
|
/* But still dump what we have remaining it. */
|
|
if (dump_flags & (TDF_DETAILS|TDF_ALIAS))
|
|
dump_alias_info (dump_file);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* For each pointer P_i, determine the sets of variables that P_i may
|
|
point-to. Compute the reachability set of escaped and call-used
|
|
variables. */
|
|
compute_points_to_sets ();
|
|
|
|
/* Debugging dumps. */
|
|
if (dump_file && (dump_flags & (TDF_DETAILS|TDF_ALIAS)))
|
|
dump_alias_info (dump_file);
|
|
|
|
/* Compute restrict-based memory disambiguations. */
|
|
compute_dependence_clique ();
|
|
|
|
/* Deallocate memory used by aliasing data structures and the internal
|
|
points-to solution. */
|
|
delete_points_to_sets ();
|
|
|
|
gcc_assert (!need_ssa_update_p (cfun));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* A dummy pass to cause points-to information to be computed via
|
|
TODO_rebuild_alias. */
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_build_alias =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"alias", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_NONE, /* tv_id */
|
|
( PROP_cfg | PROP_ssa ), /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
TODO_rebuild_alias, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_build_alias : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_build_alias (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_build_alias, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
bool gate (function *) final override { return flag_tree_pta; }
|
|
|
|
}; // class pass_build_alias
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_build_alias (gcc::context *ctxt)
|
|
{
|
|
return new pass_build_alias (ctxt);
|
|
}
|
|
|
|
/* A dummy pass to cause points-to information to be computed via
|
|
TODO_rebuild_alias. */
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_build_ealias =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"ealias", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_NONE, /* tv_id */
|
|
( PROP_cfg | PROP_ssa ), /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
TODO_rebuild_alias, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_build_ealias : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_build_ealias (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_build_ealias, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
bool gate (function *) final override { return flag_tree_pta; }
|
|
|
|
}; // class pass_build_ealias
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_build_ealias (gcc::context *ctxt)
|
|
{
|
|
return new pass_build_ealias (ctxt);
|
|
}
|
|
|
|
|
|
/* IPA PTA solutions for ESCAPED. */
|
|
struct pt_solution ipa_escaped_pt
|
|
= { true, false, false, false, false, false,
|
|
false, false, false, false, false, NULL };
|
|
|
|
|
|
/* Execute the driver for IPA PTA. */
|
|
static unsigned int
|
|
ipa_pta_execute (void)
|
|
{
|
|
struct cgraph_node *node;
|
|
|
|
in_ipa_mode = 1;
|
|
|
|
init_alias_vars ();
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
symtab->dump (dump_file);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
{
|
|
fprintf (dump_file, "Generating generic constraints\n\n");
|
|
dump_constraints (dump_file, 0);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
ipa_build_constraints ();
|
|
|
|
/* From the constraints compute the points-to sets. */
|
|
solve_constraints ();
|
|
|
|
if (dump_file && (dump_flags & TDF_STATS))
|
|
dump_sa_stats (dump_file);
|
|
|
|
if (dump_file && (dump_flags & TDF_DETAILS))
|
|
dump_sa_points_to_info (dump_file);
|
|
|
|
/* Now post-process solutions to handle locals from different
|
|
runtime instantiations coming in through recursive invocations. */
|
|
unsigned shadow_var_cnt = 0;
|
|
for (unsigned i = 1; i < varmap.length (); ++i)
|
|
{
|
|
varinfo_t fi = get_varinfo (i);
|
|
if (fi->is_fn_info
|
|
&& fi->decl)
|
|
/* Automatic variables pointed to by their containing functions
|
|
parameters need this treatment. */
|
|
for (varinfo_t ai = first_vi_for_offset (fi, fi_parm_base);
|
|
ai; ai = vi_next (ai))
|
|
{
|
|
varinfo_t vi = get_varinfo (var_rep[ai->id]);
|
|
bitmap_iterator bi;
|
|
unsigned j;
|
|
EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, j, bi)
|
|
{
|
|
varinfo_t pt = get_varinfo (j);
|
|
if (pt->shadow_var_uid == 0
|
|
&& pt->decl
|
|
&& auto_var_in_fn_p (pt->decl, fi->decl))
|
|
{
|
|
pt->shadow_var_uid = allocate_decl_uid ();
|
|
shadow_var_cnt++;
|
|
}
|
|
}
|
|
}
|
|
/* As well as global variables which are another way of passing
|
|
arguments to recursive invocations. */
|
|
else if (fi->is_global_var)
|
|
{
|
|
for (varinfo_t ai = fi; ai; ai = vi_next (ai))
|
|
{
|
|
varinfo_t vi = get_varinfo (var_rep[ai->id]);
|
|
bitmap_iterator bi;
|
|
unsigned j;
|
|
EXECUTE_IF_SET_IN_BITMAP (vi->solution, 0, j, bi)
|
|
{
|
|
varinfo_t pt = get_varinfo (j);
|
|
if (pt->shadow_var_uid == 0
|
|
&& pt->decl
|
|
&& auto_var_p (pt->decl))
|
|
{
|
|
pt->shadow_var_uid = allocate_decl_uid ();
|
|
shadow_var_cnt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (shadow_var_cnt && dump_file && (dump_flags & TDF_DETAILS))
|
|
fprintf (dump_file, "Allocated %u shadow variables for locals "
|
|
"maybe leaking into recursive invocations of their containing "
|
|
"functions\n", shadow_var_cnt);
|
|
|
|
/* Compute the global points-to sets for ESCAPED.
|
|
??? Note that the computed escape set is not correct
|
|
for the whole unit as we fail to consider graph edges to
|
|
externally visible functions. */
|
|
ipa_escaped_pt = find_what_var_points_to (NULL, get_varinfo (escaped_id));
|
|
|
|
/* Make sure the ESCAPED solution (which is used as placeholder in
|
|
other solutions) does not reference itself. This simplifies
|
|
points-to solution queries. */
|
|
ipa_escaped_pt.ipa_escaped = 0;
|
|
|
|
/* Assign the points-to sets to the SSA names in the unit. */
|
|
FOR_EACH_DEFINED_FUNCTION (node)
|
|
{
|
|
tree ptr;
|
|
struct function *fn;
|
|
unsigned i;
|
|
basic_block bb;
|
|
|
|
/* Nodes without a body in this partition are not interesting. */
|
|
if (!node->has_gimple_body_p ()
|
|
|| node->in_other_partition
|
|
|| node->clone_of)
|
|
continue;
|
|
|
|
fn = DECL_STRUCT_FUNCTION (node->decl);
|
|
|
|
/* Compute the points-to sets for pointer SSA_NAMEs. */
|
|
FOR_EACH_VEC_ELT (*fn->gimple_df->ssa_names, i, ptr)
|
|
{
|
|
if (ptr
|
|
&& POINTER_TYPE_P (TREE_TYPE (ptr)))
|
|
find_what_p_points_to (node->decl, ptr);
|
|
}
|
|
|
|
/* Compute the call-use and call-clobber sets for indirect calls
|
|
and calls to external functions. */
|
|
FOR_EACH_BB_FN (bb, fn)
|
|
{
|
|
gimple_stmt_iterator gsi;
|
|
|
|
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
|
{
|
|
gcall *stmt;
|
|
struct pt_solution *pt;
|
|
varinfo_t vi, fi;
|
|
tree decl;
|
|
|
|
stmt = dyn_cast <gcall *> (gsi_stmt (gsi));
|
|
if (!stmt)
|
|
continue;
|
|
|
|
/* Handle direct calls to functions with body. */
|
|
decl = gimple_call_fndecl (stmt);
|
|
|
|
{
|
|
tree called_decl = NULL_TREE;
|
|
if (gimple_call_builtin_p (stmt, BUILT_IN_GOMP_PARALLEL))
|
|
called_decl = TREE_OPERAND (gimple_call_arg (stmt, 0), 0);
|
|
else if (gimple_call_builtin_p (stmt, BUILT_IN_GOACC_PARALLEL))
|
|
called_decl = TREE_OPERAND (gimple_call_arg (stmt, 1), 0);
|
|
|
|
if (called_decl != NULL_TREE
|
|
&& !fndecl_maybe_in_other_partition (called_decl))
|
|
decl = called_decl;
|
|
}
|
|
|
|
if (decl
|
|
&& (fi = lookup_vi_for_tree (decl))
|
|
&& fi->is_fn_info)
|
|
{
|
|
*gimple_call_clobber_set (stmt)
|
|
= find_what_var_points_to
|
|
(node->decl, first_vi_for_offset (fi, fi_clobbers));
|
|
*gimple_call_use_set (stmt)
|
|
= find_what_var_points_to
|
|
(node->decl, first_vi_for_offset (fi, fi_uses));
|
|
}
|
|
/* Handle direct calls to external functions. */
|
|
else if (decl && (!fi || fi->decl))
|
|
{
|
|
pt = gimple_call_use_set (stmt);
|
|
if (gimple_call_flags (stmt) & ECF_CONST)
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
else if ((vi = lookup_call_use_vi (stmt)) != NULL)
|
|
{
|
|
*pt = find_what_var_points_to (node->decl, vi);
|
|
/* Escaped (and thus nonlocal) variables are always
|
|
implicitly used by calls. */
|
|
/* ??? ESCAPED can be empty even though NONLOCAL
|
|
always escaped. */
|
|
pt->nonlocal = 1;
|
|
pt->ipa_escaped = 1;
|
|
}
|
|
else
|
|
{
|
|
/* If there is nothing special about this call then
|
|
we have made everything that is used also escape. */
|
|
*pt = ipa_escaped_pt;
|
|
pt->nonlocal = 1;
|
|
}
|
|
|
|
pt = gimple_call_clobber_set (stmt);
|
|
if (gimple_call_flags (stmt) &
|
|
(ECF_CONST|ECF_PURE|ECF_NOVOPS))
|
|
memset (pt, 0, sizeof (struct pt_solution));
|
|
else if ((vi = lookup_call_clobber_vi (stmt)) != NULL)
|
|
{
|
|
*pt = find_what_var_points_to (node->decl, vi);
|
|
/* Escaped (and thus nonlocal) variables are always
|
|
implicitly clobbered by calls. */
|
|
/* ??? ESCAPED can be empty even though NONLOCAL
|
|
always escaped. */
|
|
pt->nonlocal = 1;
|
|
pt->ipa_escaped = 1;
|
|
}
|
|
else
|
|
{
|
|
/* If there is nothing special about this call then
|
|
we have made everything that is used also escape. */
|
|
*pt = ipa_escaped_pt;
|
|
pt->nonlocal = 1;
|
|
}
|
|
}
|
|
/* Handle indirect calls. */
|
|
else if ((fi = get_fi_for_callee (stmt)))
|
|
{
|
|
/* We need to accumulate all clobbers/uses of all possible
|
|
callees. */
|
|
fi = get_varinfo (var_rep[fi->id]);
|
|
/* If we cannot constrain the set of functions we'll end up
|
|
calling we end up using/clobbering everything. */
|
|
if (bitmap_bit_p (fi->solution, anything_id)
|
|
|| bitmap_bit_p (fi->solution, nonlocal_id)
|
|
|| bitmap_bit_p (fi->solution, escaped_id))
|
|
{
|
|
pt_solution_reset (gimple_call_clobber_set (stmt));
|
|
pt_solution_reset (gimple_call_use_set (stmt));
|
|
}
|
|
else
|
|
{
|
|
bitmap_iterator bi;
|
|
unsigned i;
|
|
struct pt_solution *uses, *clobbers;
|
|
|
|
uses = gimple_call_use_set (stmt);
|
|
clobbers = gimple_call_clobber_set (stmt);
|
|
memset (uses, 0, sizeof (struct pt_solution));
|
|
memset (clobbers, 0, sizeof (struct pt_solution));
|
|
EXECUTE_IF_SET_IN_BITMAP (fi->solution, 0, i, bi)
|
|
{
|
|
struct pt_solution sol;
|
|
|
|
vi = get_varinfo (i);
|
|
if (!vi->is_fn_info)
|
|
{
|
|
/* ??? We could be more precise here? */
|
|
uses->nonlocal = 1;
|
|
uses->ipa_escaped = 1;
|
|
clobbers->nonlocal = 1;
|
|
clobbers->ipa_escaped = 1;
|
|
continue;
|
|
}
|
|
|
|
if (!uses->anything)
|
|
{
|
|
sol = find_what_var_points_to
|
|
(node->decl,
|
|
first_vi_for_offset (vi, fi_uses));
|
|
pt_solution_ior_into (uses, &sol);
|
|
}
|
|
if (!clobbers->anything)
|
|
{
|
|
sol = find_what_var_points_to
|
|
(node->decl,
|
|
first_vi_for_offset (vi, fi_clobbers));
|
|
pt_solution_ior_into (clobbers, &sol);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
fn->gimple_df->ipa_pta = true;
|
|
|
|
/* We have to re-set the final-solution cache after each function
|
|
because what is a "global" is dependent on function context. */
|
|
final_solutions->empty ();
|
|
obstack_free (&final_solutions_obstack, NULL);
|
|
gcc_obstack_init (&final_solutions_obstack);
|
|
}
|
|
|
|
delete_points_to_sets ();
|
|
|
|
in_ipa_mode = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_ipa_pta =
|
|
{
|
|
SIMPLE_IPA_PASS, /* type */
|
|
"pta", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_IPA_PTA, /* tv_id */
|
|
0, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
0, /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_ipa_pta : public simple_ipa_opt_pass
|
|
{
|
|
public:
|
|
pass_ipa_pta (gcc::context *ctxt)
|
|
: simple_ipa_opt_pass (pass_data_ipa_pta, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
bool gate (function *) final override
|
|
{
|
|
return (optimize
|
|
&& flag_ipa_pta
|
|
/* Don't bother doing anything if the program has errors. */
|
|
&& !seen_error ());
|
|
}
|
|
|
|
opt_pass * clone () final override { return new pass_ipa_pta (m_ctxt); }
|
|
|
|
unsigned int execute (function *) final override
|
|
{
|
|
return ipa_pta_execute ();
|
|
}
|
|
|
|
}; // class pass_ipa_pta
|
|
|
|
} // anon namespace
|
|
|
|
simple_ipa_opt_pass *
|
|
make_pass_ipa_pta (gcc::context *ctxt)
|
|
{
|
|
return new pass_ipa_pta (ctxt);
|
|
}
|