5533 lines
159 KiB
C++
5533 lines
159 KiB
C++
/* Search for references that a functions loads or stores.
|
|
Copyright (C) 2020-2022 Free Software Foundation, Inc.
|
|
Contributed by David Cepelik and Jan Hubicka
|
|
|
|
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/>. */
|
|
|
|
/* Mod/ref pass records summary about loads and stores performed by the
|
|
function. This is later used by alias analysis to disambiguate memory
|
|
accesses across function calls.
|
|
|
|
This file contains a tree pass and an IPA pass. Both performs the same
|
|
analysis however tree pass is executed during early and late optimization
|
|
passes to propagate info downwards in the compilation order. IPA pass
|
|
propagates across the callgraph and is able to handle recursion and works on
|
|
whole program during link-time analysis.
|
|
|
|
LTO mode differs from the local mode by not recording alias sets but types
|
|
that are translated to alias sets later. This is necessary in order stream
|
|
the information because the alias sets are rebuild at stream-in time and may
|
|
not correspond to ones seen during analysis. For this reason part of
|
|
analysis is duplicated.
|
|
|
|
The following information is computed
|
|
1) load/store access tree described in ipa-modref-tree.h
|
|
This is used by tree-ssa-alias to disambiguate load/stores
|
|
2) EAF flags used by points-to analysis (in tree-ssa-structalias).
|
|
and defined in tree-core.h.
|
|
and stored to optimization_summaries.
|
|
|
|
There are multiple summaries computed and used during the propagation:
|
|
- summaries holds summaries from analysis to IPA propagation
|
|
time.
|
|
- summaries_lto is same as summaries but holds them in a format
|
|
that can be streamed (as described above).
|
|
- fnspec_summary holds fnspec strings for call. This is
|
|
necessary because gimple_call_fnspec performs additional
|
|
analysis except for looking callee fndecl.
|
|
- escape_summary holds escape points for given call edge.
|
|
That is a vector recording what function parameters
|
|
may escape to a function call (and with what parameter index). */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "backend.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "alloc-pool.h"
|
|
#include "tree-pass.h"
|
|
#include "gimple-iterator.h"
|
|
#include "tree-dfa.h"
|
|
#include "cgraph.h"
|
|
#include "ipa-utils.h"
|
|
#include "symbol-summary.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "gimple-walk.h"
|
|
#include "print-tree.h"
|
|
#include "tree-streamer.h"
|
|
#include "alias.h"
|
|
#include "calls.h"
|
|
#include "ipa-modref-tree.h"
|
|
#include "ipa-modref.h"
|
|
#include "value-range.h"
|
|
#include "ipa-prop.h"
|
|
#include "ipa-fnsummary.h"
|
|
#include "attr-fnspec.h"
|
|
#include "symtab-clones.h"
|
|
#include "gimple-ssa.h"
|
|
#include "tree-phinodes.h"
|
|
#include "tree-ssa-operands.h"
|
|
#include "ssa-iterators.h"
|
|
#include "stringpool.h"
|
|
#include "tree-ssanames.h"
|
|
#include "attribs.h"
|
|
#include "tree-cfg.h"
|
|
#include "tree-eh.h"
|
|
|
|
|
|
namespace {
|
|
|
|
/* We record fnspec specifiers for call edges since they depends on actual
|
|
gimple statements. */
|
|
|
|
class fnspec_summary
|
|
{
|
|
public:
|
|
char *fnspec;
|
|
|
|
fnspec_summary ()
|
|
: fnspec (NULL)
|
|
{
|
|
}
|
|
|
|
~fnspec_summary ()
|
|
{
|
|
free (fnspec);
|
|
}
|
|
};
|
|
|
|
/* Summary holding fnspec string for a given call. */
|
|
|
|
class fnspec_summaries_t : public call_summary <fnspec_summary *>
|
|
{
|
|
public:
|
|
fnspec_summaries_t (symbol_table *symtab)
|
|
: call_summary <fnspec_summary *> (symtab) {}
|
|
/* Hook that is called by summary when an edge is duplicated. */
|
|
virtual void duplicate (cgraph_edge *,
|
|
cgraph_edge *,
|
|
fnspec_summary *src,
|
|
fnspec_summary *dst)
|
|
{
|
|
dst->fnspec = xstrdup (src->fnspec);
|
|
}
|
|
};
|
|
|
|
static fnspec_summaries_t *fnspec_summaries = NULL;
|
|
|
|
/* Escape summary holds a vector of param indexes that escape to
|
|
a given call. */
|
|
struct escape_entry
|
|
{
|
|
/* Parameter that escapes at a given call. */
|
|
int parm_index;
|
|
/* Argument it escapes to. */
|
|
unsigned int arg;
|
|
/* Minimal flags known about the argument. */
|
|
eaf_flags_t min_flags;
|
|
/* Does it escape directly or indirectly? */
|
|
bool direct;
|
|
};
|
|
|
|
/* Dump EAF flags. */
|
|
|
|
static void
|
|
dump_eaf_flags (FILE *out, int flags, bool newline = true)
|
|
{
|
|
if (flags & EAF_UNUSED)
|
|
fprintf (out, " unused");
|
|
if (flags & EAF_NO_DIRECT_CLOBBER)
|
|
fprintf (out, " no_direct_clobber");
|
|
if (flags & EAF_NO_INDIRECT_CLOBBER)
|
|
fprintf (out, " no_indirect_clobber");
|
|
if (flags & EAF_NO_DIRECT_ESCAPE)
|
|
fprintf (out, " no_direct_escape");
|
|
if (flags & EAF_NO_INDIRECT_ESCAPE)
|
|
fprintf (out, " no_indirect_escape");
|
|
if (flags & EAF_NOT_RETURNED_DIRECTLY)
|
|
fprintf (out, " not_returned_directly");
|
|
if (flags & EAF_NOT_RETURNED_INDIRECTLY)
|
|
fprintf (out, " not_returned_indirectly");
|
|
if (flags & EAF_NO_DIRECT_READ)
|
|
fprintf (out, " no_direct_read");
|
|
if (flags & EAF_NO_INDIRECT_READ)
|
|
fprintf (out, " no_indirect_read");
|
|
if (newline)
|
|
fprintf (out, "\n");
|
|
}
|
|
|
|
struct escape_summary
|
|
{
|
|
auto_vec <escape_entry> esc;
|
|
void dump (FILE *out)
|
|
{
|
|
for (unsigned int i = 0; i < esc.length (); i++)
|
|
{
|
|
fprintf (out, " parm %i arg %i %s min:",
|
|
esc[i].parm_index,
|
|
esc[i].arg,
|
|
esc[i].direct ? "(direct)" : "(indirect)");
|
|
dump_eaf_flags (out, esc[i].min_flags, false);
|
|
}
|
|
fprintf (out, "\n");
|
|
}
|
|
};
|
|
|
|
class escape_summaries_t : public call_summary <escape_summary *>
|
|
{
|
|
public:
|
|
escape_summaries_t (symbol_table *symtab)
|
|
: call_summary <escape_summary *> (symtab) {}
|
|
/* Hook that is called by summary when an edge is duplicated. */
|
|
virtual void duplicate (cgraph_edge *,
|
|
cgraph_edge *,
|
|
escape_summary *src,
|
|
escape_summary *dst)
|
|
{
|
|
dst->esc = src->esc.copy ();
|
|
}
|
|
};
|
|
|
|
static escape_summaries_t *escape_summaries = NULL;
|
|
|
|
} /* ANON namespace: GTY annotated summaries can not be anonymous. */
|
|
|
|
|
|
/* Class (from which there is one global instance) that holds modref summaries
|
|
for all analyzed functions. */
|
|
|
|
class GTY((user)) modref_summaries
|
|
: public fast_function_summary <modref_summary *, va_gc>
|
|
{
|
|
public:
|
|
modref_summaries (symbol_table *symtab)
|
|
: fast_function_summary <modref_summary *, va_gc> (symtab) {}
|
|
virtual void insert (cgraph_node *, modref_summary *state);
|
|
virtual void duplicate (cgraph_node *src_node,
|
|
cgraph_node *dst_node,
|
|
modref_summary *src_data,
|
|
modref_summary *dst_data);
|
|
static modref_summaries *create_ggc (symbol_table *symtab)
|
|
{
|
|
return new (ggc_alloc_no_dtor<modref_summaries> ())
|
|
modref_summaries (symtab);
|
|
}
|
|
};
|
|
|
|
class modref_summary_lto;
|
|
|
|
/* Class (from which there is one global instance) that holds modref summaries
|
|
for all analyzed functions. */
|
|
|
|
class GTY((user)) modref_summaries_lto
|
|
: public fast_function_summary <modref_summary_lto *, va_gc>
|
|
{
|
|
public:
|
|
modref_summaries_lto (symbol_table *symtab)
|
|
: fast_function_summary <modref_summary_lto *, va_gc> (symtab),
|
|
propagated (false) {}
|
|
virtual void insert (cgraph_node *, modref_summary_lto *state);
|
|
virtual void duplicate (cgraph_node *src_node,
|
|
cgraph_node *dst_node,
|
|
modref_summary_lto *src_data,
|
|
modref_summary_lto *dst_data);
|
|
static modref_summaries_lto *create_ggc (symbol_table *symtab)
|
|
{
|
|
return new (ggc_alloc_no_dtor<modref_summaries_lto> ())
|
|
modref_summaries_lto (symtab);
|
|
}
|
|
bool propagated;
|
|
};
|
|
|
|
/* Global variable holding all modref summaries
|
|
(from analysis to IPA propagation time). */
|
|
|
|
static GTY(()) fast_function_summary <modref_summary *, va_gc>
|
|
*summaries;
|
|
|
|
/* Global variable holding all modref optimization summaries
|
|
(from IPA propagation time or used by local optimization pass). */
|
|
|
|
static GTY(()) fast_function_summary <modref_summary *, va_gc>
|
|
*optimization_summaries;
|
|
|
|
/* LTO summaries hold info from analysis to LTO streaming or from LTO
|
|
stream-in through propagation to LTO stream-out. */
|
|
|
|
static GTY(()) fast_function_summary <modref_summary_lto *, va_gc>
|
|
*summaries_lto;
|
|
|
|
/* Summary for a single function which this pass produces. */
|
|
|
|
modref_summary::modref_summary ()
|
|
: loads (NULL), stores (NULL), retslot_flags (0), static_chain_flags (0),
|
|
writes_errno (false), side_effects (false), nondeterministic (false),
|
|
calls_interposable (false), global_memory_read (false),
|
|
global_memory_written (false), try_dse (false)
|
|
{
|
|
}
|
|
|
|
modref_summary::~modref_summary ()
|
|
{
|
|
if (loads)
|
|
ggc_delete (loads);
|
|
if (stores)
|
|
ggc_delete (stores);
|
|
}
|
|
|
|
/* Remove all flags from EAF_FLAGS that are implied by ECF_FLAGS and not
|
|
useful to track. If returns_void is true moreover clear
|
|
EAF_NOT_RETURNED. */
|
|
static int
|
|
remove_useless_eaf_flags (int eaf_flags, int ecf_flags, bool returns_void)
|
|
{
|
|
if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
eaf_flags &= ~implicit_const_eaf_flags;
|
|
else if (ecf_flags & ECF_PURE)
|
|
eaf_flags &= ~implicit_pure_eaf_flags;
|
|
else if ((ecf_flags & ECF_NORETURN) || returns_void)
|
|
eaf_flags &= ~(EAF_NOT_RETURNED_DIRECTLY | EAF_NOT_RETURNED_INDIRECTLY);
|
|
return eaf_flags;
|
|
}
|
|
|
|
/* Return true if FLAGS holds some useful information. */
|
|
|
|
static bool
|
|
eaf_flags_useful_p (vec <eaf_flags_t> &flags, int ecf_flags)
|
|
{
|
|
for (unsigned i = 0; i < flags.length (); i++)
|
|
if (remove_useless_eaf_flags (flags[i], ecf_flags, false))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if summary is potentially useful for optimization.
|
|
If CHECK_FLAGS is false assume that arg_flags are useful. */
|
|
|
|
bool
|
|
modref_summary::useful_p (int ecf_flags, bool check_flags)
|
|
{
|
|
if (arg_flags.length () && !check_flags)
|
|
return true;
|
|
if (check_flags && eaf_flags_useful_p (arg_flags, ecf_flags))
|
|
return true;
|
|
arg_flags.release ();
|
|
if (check_flags && remove_useless_eaf_flags (retslot_flags, ecf_flags, false))
|
|
return true;
|
|
if (check_flags
|
|
&& remove_useless_eaf_flags (static_chain_flags, ecf_flags, false))
|
|
return true;
|
|
if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
return ((!side_effects || !nondeterministic)
|
|
&& (ecf_flags & ECF_LOOPING_CONST_OR_PURE));
|
|
if (loads && !loads->every_base)
|
|
return true;
|
|
else
|
|
kills.release ();
|
|
if (ecf_flags & ECF_PURE)
|
|
return ((!side_effects || !nondeterministic)
|
|
&& (ecf_flags & ECF_LOOPING_CONST_OR_PURE));
|
|
return stores && !stores->every_base;
|
|
}
|
|
|
|
/* Single function summary used for LTO. */
|
|
|
|
typedef modref_tree <tree> modref_records_lto;
|
|
struct GTY(()) modref_summary_lto
|
|
{
|
|
/* Load and stores in functions using types rather then alias sets.
|
|
|
|
This is necessary to make the information streamable for LTO but is also
|
|
more verbose and thus more likely to hit the limits. */
|
|
modref_records_lto *loads;
|
|
modref_records_lto *stores;
|
|
auto_vec<modref_access_node> GTY((skip)) kills;
|
|
auto_vec<eaf_flags_t> GTY((skip)) arg_flags;
|
|
eaf_flags_t retslot_flags;
|
|
eaf_flags_t static_chain_flags;
|
|
unsigned writes_errno : 1;
|
|
unsigned side_effects : 1;
|
|
unsigned nondeterministic : 1;
|
|
unsigned calls_interposable : 1;
|
|
|
|
modref_summary_lto ();
|
|
~modref_summary_lto ();
|
|
void dump (FILE *);
|
|
bool useful_p (int ecf_flags, bool check_flags = true);
|
|
};
|
|
|
|
/* Summary for a single function which this pass produces. */
|
|
|
|
modref_summary_lto::modref_summary_lto ()
|
|
: loads (NULL), stores (NULL), retslot_flags (0), static_chain_flags (0),
|
|
writes_errno (false), side_effects (false), nondeterministic (false),
|
|
calls_interposable (false)
|
|
{
|
|
}
|
|
|
|
modref_summary_lto::~modref_summary_lto ()
|
|
{
|
|
if (loads)
|
|
ggc_delete (loads);
|
|
if (stores)
|
|
ggc_delete (stores);
|
|
}
|
|
|
|
|
|
/* Return true if lto summary is potentially useful for optimization.
|
|
If CHECK_FLAGS is false assume that arg_flags are useful. */
|
|
|
|
bool
|
|
modref_summary_lto::useful_p (int ecf_flags, bool check_flags)
|
|
{
|
|
if (arg_flags.length () && !check_flags)
|
|
return true;
|
|
if (check_flags && eaf_flags_useful_p (arg_flags, ecf_flags))
|
|
return true;
|
|
arg_flags.release ();
|
|
if (check_flags && remove_useless_eaf_flags (retslot_flags, ecf_flags, false))
|
|
return true;
|
|
if (check_flags
|
|
&& remove_useless_eaf_flags (static_chain_flags, ecf_flags, false))
|
|
return true;
|
|
if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
return ((!side_effects || !nondeterministic)
|
|
&& (ecf_flags & ECF_LOOPING_CONST_OR_PURE));
|
|
if (loads && !loads->every_base)
|
|
return true;
|
|
else
|
|
kills.release ();
|
|
if (ecf_flags & ECF_PURE)
|
|
return ((!side_effects || !nondeterministic)
|
|
&& (ecf_flags & ECF_LOOPING_CONST_OR_PURE));
|
|
return stores && !stores->every_base;
|
|
}
|
|
|
|
/* Dump records TT to OUT. */
|
|
|
|
static void
|
|
dump_records (modref_records *tt, FILE *out)
|
|
{
|
|
if (tt->every_base)
|
|
{
|
|
fprintf (out, " Every base\n");
|
|
return;
|
|
}
|
|
size_t i;
|
|
modref_base_node <alias_set_type> *n;
|
|
FOR_EACH_VEC_SAFE_ELT (tt->bases, i, n)
|
|
{
|
|
fprintf (out, " Base %i: alias set %i\n", (int)i, n->base);
|
|
if (n->every_ref)
|
|
{
|
|
fprintf (out, " Every ref\n");
|
|
continue;
|
|
}
|
|
size_t j;
|
|
modref_ref_node <alias_set_type> *r;
|
|
FOR_EACH_VEC_SAFE_ELT (n->refs, j, r)
|
|
{
|
|
fprintf (out, " Ref %i: alias set %i\n", (int)j, r->ref);
|
|
if (r->every_access)
|
|
{
|
|
fprintf (out, " Every access\n");
|
|
continue;
|
|
}
|
|
size_t k;
|
|
modref_access_node *a;
|
|
FOR_EACH_VEC_SAFE_ELT (r->accesses, k, a)
|
|
{
|
|
fprintf (out, " access:");
|
|
a->dump (out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Dump records TT to OUT. */
|
|
|
|
static void
|
|
dump_lto_records (modref_records_lto *tt, FILE *out)
|
|
{
|
|
if (tt->every_base)
|
|
{
|
|
fprintf (out, " Every base\n");
|
|
return;
|
|
}
|
|
size_t i;
|
|
modref_base_node <tree> *n;
|
|
FOR_EACH_VEC_SAFE_ELT (tt->bases, i, n)
|
|
{
|
|
fprintf (out, " Base %i:", (int)i);
|
|
print_generic_expr (dump_file, n->base);
|
|
fprintf (out, " (alias set %i)\n",
|
|
n->base ? get_alias_set (n->base) : 0);
|
|
if (n->every_ref)
|
|
{
|
|
fprintf (out, " Every ref\n");
|
|
continue;
|
|
}
|
|
size_t j;
|
|
modref_ref_node <tree> *r;
|
|
FOR_EACH_VEC_SAFE_ELT (n->refs, j, r)
|
|
{
|
|
fprintf (out, " Ref %i:", (int)j);
|
|
print_generic_expr (dump_file, r->ref);
|
|
fprintf (out, " (alias set %i)\n",
|
|
r->ref ? get_alias_set (r->ref) : 0);
|
|
if (r->every_access)
|
|
{
|
|
fprintf (out, " Every access\n");
|
|
continue;
|
|
}
|
|
size_t k;
|
|
modref_access_node *a;
|
|
FOR_EACH_VEC_SAFE_ELT (r->accesses, k, a)
|
|
{
|
|
fprintf (out, " access:");
|
|
a->dump (out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Dump all escape points of NODE to OUT. */
|
|
|
|
static void
|
|
dump_modref_edge_summaries (FILE *out, cgraph_node *node, int depth)
|
|
{
|
|
int i = 0;
|
|
if (!escape_summaries)
|
|
return;
|
|
for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
|
|
{
|
|
class escape_summary *sum = escape_summaries->get (e);
|
|
if (sum)
|
|
{
|
|
fprintf (out, "%*sIndirect call %i in %s escapes:",
|
|
depth, "", i, node->dump_name ());
|
|
sum->dump (out);
|
|
}
|
|
i++;
|
|
}
|
|
for (cgraph_edge *e = node->callees; e; e = e->next_callee)
|
|
{
|
|
if (!e->inline_failed)
|
|
dump_modref_edge_summaries (out, e->callee, depth + 1);
|
|
class escape_summary *sum = escape_summaries->get (e);
|
|
if (sum)
|
|
{
|
|
fprintf (out, "%*sCall %s->%s escapes:", depth, "",
|
|
node->dump_name (), e->callee->dump_name ());
|
|
sum->dump (out);
|
|
}
|
|
class fnspec_summary *fsum = fnspec_summaries->get (e);
|
|
if (fsum)
|
|
{
|
|
fprintf (out, "%*sCall %s->%s fnspec: %s\n", depth, "",
|
|
node->dump_name (), e->callee->dump_name (),
|
|
fsum->fnspec);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove all call edge summaries associated with NODE. */
|
|
|
|
static void
|
|
remove_modref_edge_summaries (cgraph_node *node)
|
|
{
|
|
if (!escape_summaries)
|
|
return;
|
|
for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
|
|
escape_summaries->remove (e);
|
|
for (cgraph_edge *e = node->callees; e; e = e->next_callee)
|
|
{
|
|
if (!e->inline_failed)
|
|
remove_modref_edge_summaries (e->callee);
|
|
escape_summaries->remove (e);
|
|
fnspec_summaries->remove (e);
|
|
}
|
|
}
|
|
|
|
/* Dump summary. */
|
|
|
|
void
|
|
modref_summary::dump (FILE *out)
|
|
{
|
|
if (loads)
|
|
{
|
|
fprintf (out, " loads:\n");
|
|
dump_records (loads, out);
|
|
}
|
|
if (stores)
|
|
{
|
|
fprintf (out, " stores:\n");
|
|
dump_records (stores, out);
|
|
}
|
|
if (kills.length ())
|
|
{
|
|
fprintf (out, " kills:\n");
|
|
for (auto kill : kills)
|
|
{
|
|
fprintf (out, " ");
|
|
kill.dump (out);
|
|
}
|
|
}
|
|
if (writes_errno)
|
|
fprintf (out, " Writes errno\n");
|
|
if (side_effects)
|
|
fprintf (out, " Side effects\n");
|
|
if (nondeterministic)
|
|
fprintf (out, " Nondeterministic\n");
|
|
if (calls_interposable)
|
|
fprintf (out, " Calls interposable\n");
|
|
if (global_memory_read)
|
|
fprintf (out, " Global memory read\n");
|
|
if (global_memory_written)
|
|
fprintf (out, " Global memory written\n");
|
|
if (try_dse)
|
|
fprintf (out, " Try dse\n");
|
|
if (arg_flags.length ())
|
|
{
|
|
for (unsigned int i = 0; i < arg_flags.length (); i++)
|
|
if (arg_flags[i])
|
|
{
|
|
fprintf (out, " parm %i flags:", i);
|
|
dump_eaf_flags (out, arg_flags[i]);
|
|
}
|
|
}
|
|
if (retslot_flags)
|
|
{
|
|
fprintf (out, " Retslot flags:");
|
|
dump_eaf_flags (out, retslot_flags);
|
|
}
|
|
if (static_chain_flags)
|
|
{
|
|
fprintf (out, " Static chain flags:");
|
|
dump_eaf_flags (out, static_chain_flags);
|
|
}
|
|
}
|
|
|
|
/* Dump summary. */
|
|
|
|
void
|
|
modref_summary_lto::dump (FILE *out)
|
|
{
|
|
fprintf (out, " loads:\n");
|
|
dump_lto_records (loads, out);
|
|
fprintf (out, " stores:\n");
|
|
dump_lto_records (stores, out);
|
|
if (kills.length ())
|
|
{
|
|
fprintf (out, " kills:\n");
|
|
for (auto kill : kills)
|
|
{
|
|
fprintf (out, " ");
|
|
kill.dump (out);
|
|
}
|
|
}
|
|
if (writes_errno)
|
|
fprintf (out, " Writes errno\n");
|
|
if (side_effects)
|
|
fprintf (out, " Side effects\n");
|
|
if (nondeterministic)
|
|
fprintf (out, " Nondeterministic\n");
|
|
if (calls_interposable)
|
|
fprintf (out, " Calls interposable\n");
|
|
if (arg_flags.length ())
|
|
{
|
|
for (unsigned int i = 0; i < arg_flags.length (); i++)
|
|
if (arg_flags[i])
|
|
{
|
|
fprintf (out, " parm %i flags:", i);
|
|
dump_eaf_flags (out, arg_flags[i]);
|
|
}
|
|
}
|
|
if (retslot_flags)
|
|
{
|
|
fprintf (out, " Retslot flags:");
|
|
dump_eaf_flags (out, retslot_flags);
|
|
}
|
|
if (static_chain_flags)
|
|
{
|
|
fprintf (out, " Static chain flags:");
|
|
dump_eaf_flags (out, static_chain_flags);
|
|
}
|
|
}
|
|
|
|
/* Called after summary is produced and before it is used by local analysis.
|
|
Can be called multiple times in case summary needs to update signature.
|
|
FUN is decl of function summary is attached to. */
|
|
void
|
|
modref_summary::finalize (tree fun)
|
|
{
|
|
global_memory_read = !loads || loads->global_access_p ();
|
|
global_memory_written = !stores || stores->global_access_p ();
|
|
|
|
/* We can do DSE if we know function has no side effects and
|
|
we can analyze all stores. Disable dse if there are too many
|
|
stores to try. */
|
|
if (side_effects || global_memory_written || writes_errno)
|
|
try_dse = false;
|
|
else
|
|
{
|
|
try_dse = true;
|
|
size_t i, j, k;
|
|
int num_tests = 0, max_tests
|
|
= opt_for_fn (fun, param_modref_max_tests);
|
|
modref_base_node <alias_set_type> *base_node;
|
|
modref_ref_node <alias_set_type> *ref_node;
|
|
modref_access_node *access_node;
|
|
FOR_EACH_VEC_SAFE_ELT (stores->bases, i, base_node)
|
|
{
|
|
if (base_node->every_ref)
|
|
{
|
|
try_dse = false;
|
|
break;
|
|
}
|
|
FOR_EACH_VEC_SAFE_ELT (base_node->refs, j, ref_node)
|
|
{
|
|
if (base_node->every_ref)
|
|
{
|
|
try_dse = false;
|
|
break;
|
|
}
|
|
FOR_EACH_VEC_SAFE_ELT (ref_node->accesses, k, access_node)
|
|
if (num_tests++ > max_tests
|
|
|| !access_node->parm_offset_known)
|
|
{
|
|
try_dse = false;
|
|
break;
|
|
}
|
|
if (!try_dse)
|
|
break;
|
|
}
|
|
if (!try_dse)
|
|
break;
|
|
}
|
|
}
|
|
if (loads->every_base)
|
|
load_accesses = 1;
|
|
else
|
|
{
|
|
load_accesses = 0;
|
|
for (auto base_node : loads->bases)
|
|
{
|
|
if (base_node->every_ref)
|
|
load_accesses++;
|
|
else
|
|
for (auto ref_node : base_node->refs)
|
|
if (ref_node->every_access)
|
|
load_accesses++;
|
|
else
|
|
load_accesses += ref_node->accesses->length ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Get function summary for FUNC if it exists, return NULL otherwise. */
|
|
|
|
modref_summary *
|
|
get_modref_function_summary (cgraph_node *func)
|
|
{
|
|
/* Avoid creation of the summary too early (e.g. when front-end calls us). */
|
|
if (!optimization_summaries)
|
|
return NULL;
|
|
|
|
/* A single function body may be represented by multiple symbols with
|
|
different visibility. For example, if FUNC is an interposable alias,
|
|
we don't want to return anything, even if we have summary for the target
|
|
function. */
|
|
enum availability avail;
|
|
func = func->ultimate_alias_target
|
|
(&avail, current_function_decl ?
|
|
cgraph_node::get (current_function_decl) : NULL);
|
|
if (avail <= AVAIL_INTERPOSABLE)
|
|
return NULL;
|
|
|
|
modref_summary *r = optimization_summaries->get (func);
|
|
return r;
|
|
}
|
|
|
|
/* Get function summary for CALL if it exists, return NULL otherwise.
|
|
If non-null set interposed to indicate whether function may not
|
|
bind to current def. In this case sometimes loads from function
|
|
needs to be ignored. */
|
|
|
|
modref_summary *
|
|
get_modref_function_summary (gcall *call, bool *interposed)
|
|
{
|
|
tree callee = gimple_call_fndecl (call);
|
|
if (!callee)
|
|
return NULL;
|
|
struct cgraph_node *node = cgraph_node::get (callee);
|
|
if (!node)
|
|
return NULL;
|
|
modref_summary *r = get_modref_function_summary (node);
|
|
if (interposed && r)
|
|
*interposed = r->calls_interposable
|
|
|| !node->binds_to_current_def_p ();
|
|
return r;
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
/* Return true if ECF flags says that nondeterminism can be ignored. */
|
|
|
|
static bool
|
|
ignore_nondeterminism_p (tree caller, int flags)
|
|
{
|
|
if (flags & (ECF_CONST | ECF_PURE))
|
|
return true;
|
|
if ((flags & (ECF_NORETURN | ECF_NOTHROW)) == (ECF_NORETURN | ECF_NOTHROW)
|
|
|| (!opt_for_fn (caller, flag_exceptions) && (flags & ECF_NORETURN)))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if ECF flags says that return value can be ignored. */
|
|
|
|
static bool
|
|
ignore_retval_p (tree caller, int flags)
|
|
{
|
|
if ((flags & (ECF_NORETURN | ECF_NOTHROW)) == (ECF_NORETURN | ECF_NOTHROW)
|
|
|| (!opt_for_fn (caller, flag_exceptions) && (flags & ECF_NORETURN)))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if ECF flags says that stores can be ignored. */
|
|
|
|
static bool
|
|
ignore_stores_p (tree caller, int flags)
|
|
{
|
|
if (flags & (ECF_PURE | ECF_CONST | ECF_NOVOPS))
|
|
return true;
|
|
if ((flags & (ECF_NORETURN | ECF_NOTHROW)) == (ECF_NORETURN | ECF_NOTHROW)
|
|
|| (!opt_for_fn (caller, flag_exceptions) && (flags & ECF_NORETURN)))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Determine parm_map for PTR which is supposed to be a pointer. */
|
|
|
|
modref_parm_map
|
|
parm_map_for_ptr (tree op)
|
|
{
|
|
bool offset_known;
|
|
poly_int64 offset;
|
|
struct modref_parm_map parm_map;
|
|
gcall *call;
|
|
|
|
parm_map.parm_offset_known = false;
|
|
parm_map.parm_offset = 0;
|
|
|
|
offset_known = unadjusted_ptr_and_unit_offset (op, &op, &offset);
|
|
if (TREE_CODE (op) == SSA_NAME
|
|
&& SSA_NAME_IS_DEFAULT_DEF (op)
|
|
&& TREE_CODE (SSA_NAME_VAR (op)) == PARM_DECL)
|
|
{
|
|
int index = 0;
|
|
|
|
if (cfun->static_chain_decl
|
|
&& op == ssa_default_def (cfun, cfun->static_chain_decl))
|
|
index = MODREF_STATIC_CHAIN_PARM;
|
|
else
|
|
for (tree t = DECL_ARGUMENTS (current_function_decl);
|
|
t != SSA_NAME_VAR (op); t = DECL_CHAIN (t))
|
|
index++;
|
|
parm_map.parm_index = index;
|
|
parm_map.parm_offset_known = offset_known;
|
|
parm_map.parm_offset = offset;
|
|
}
|
|
else if (points_to_local_or_readonly_memory_p (op))
|
|
parm_map.parm_index = MODREF_LOCAL_MEMORY_PARM;
|
|
/* Memory allocated in the function is not visible to caller before the
|
|
call and thus we do not need to record it as load/stores/kills. */
|
|
else if (TREE_CODE (op) == SSA_NAME
|
|
&& (call = dyn_cast<gcall *>(SSA_NAME_DEF_STMT (op))) != NULL
|
|
&& gimple_call_flags (call) & ECF_MALLOC)
|
|
parm_map.parm_index = MODREF_LOCAL_MEMORY_PARM;
|
|
else
|
|
parm_map.parm_index = MODREF_UNKNOWN_PARM;
|
|
return parm_map;
|
|
}
|
|
|
|
/* Return true if ARG with EAF flags FLAGS can not make any caller's parameter
|
|
used (if LOAD is true we check loads, otherwise stores). */
|
|
|
|
static bool
|
|
verify_arg (tree arg, int flags, bool load)
|
|
{
|
|
if (flags & EAF_UNUSED)
|
|
return true;
|
|
if (load && (flags & EAF_NO_DIRECT_READ))
|
|
return true;
|
|
if (!load
|
|
&& (flags & (EAF_NO_DIRECT_CLOBBER | EAF_NO_INDIRECT_CLOBBER))
|
|
== (EAF_NO_DIRECT_CLOBBER | EAF_NO_INDIRECT_CLOBBER))
|
|
return true;
|
|
if (is_gimple_constant (arg))
|
|
return true;
|
|
if (DECL_P (arg) && TREE_READONLY (arg))
|
|
return true;
|
|
if (TREE_CODE (arg) == ADDR_EXPR)
|
|
{
|
|
tree t = get_base_address (TREE_OPERAND (arg, 0));
|
|
if (is_gimple_constant (t))
|
|
return true;
|
|
if (DECL_P (t)
|
|
&& (TREE_READONLY (t) || TREE_CODE (t) == FUNCTION_DECL))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Return true if STMT may access memory that is pointed to by parameters
|
|
of caller and which is not seen as an escape by PTA.
|
|
CALLEE_ECF_FLAGS are ECF flags of callee. If LOAD is true then by access
|
|
we mean load, otherwise we mean store. */
|
|
|
|
static bool
|
|
may_access_nonescaping_parm_p (gcall *call, int callee_ecf_flags, bool load)
|
|
{
|
|
int implicit_flags = 0;
|
|
|
|
if (ignore_stores_p (current_function_decl, callee_ecf_flags))
|
|
implicit_flags |= ignore_stores_eaf_flags;
|
|
if (callee_ecf_flags & ECF_PURE)
|
|
implicit_flags |= implicit_pure_eaf_flags;
|
|
if (callee_ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
implicit_flags |= implicit_const_eaf_flags;
|
|
if (gimple_call_chain (call)
|
|
&& !verify_arg (gimple_call_chain (call),
|
|
gimple_call_static_chain_flags (call) | implicit_flags,
|
|
load))
|
|
return true;
|
|
for (unsigned int i = 0; i < gimple_call_num_args (call); i++)
|
|
if (!verify_arg (gimple_call_arg (call, i),
|
|
gimple_call_arg_flags (call, i) | implicit_flags,
|
|
load))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Analyze memory accesses (loads, stores and kills) performed
|
|
by the function. Set also side_effects, calls_interposable
|
|
and nondeterminism flags. */
|
|
|
|
class modref_access_analysis
|
|
{
|
|
public:
|
|
modref_access_analysis (bool ipa, modref_summary *summary,
|
|
modref_summary_lto *summary_lto)
|
|
: m_summary (summary), m_summary_lto (summary_lto), m_ipa (ipa)
|
|
{
|
|
}
|
|
void analyze ();
|
|
private:
|
|
bool set_side_effects ();
|
|
bool set_nondeterministic ();
|
|
static modref_access_node get_access (ao_ref *ref);
|
|
static void record_access (modref_records *, ao_ref *, modref_access_node &);
|
|
static void record_access_lto (modref_records_lto *, ao_ref *,
|
|
modref_access_node &a);
|
|
bool record_access_p (tree);
|
|
bool record_unknown_load ();
|
|
bool record_unknown_store ();
|
|
bool record_global_memory_load ();
|
|
bool record_global_memory_store ();
|
|
bool merge_call_side_effects (gimple *, modref_summary *,
|
|
cgraph_node *, bool);
|
|
modref_access_node get_access_for_fnspec (gcall *, attr_fnspec &,
|
|
unsigned int, modref_parm_map &);
|
|
void process_fnspec (gcall *);
|
|
void analyze_call (gcall *);
|
|
static bool analyze_load (gimple *, tree, tree, void *);
|
|
static bool analyze_store (gimple *, tree, tree, void *);
|
|
void analyze_stmt (gimple *, bool);
|
|
void propagate ();
|
|
|
|
/* Summary being computed.
|
|
We work either with m_summary or m_summary_lto. Never on both. */
|
|
modref_summary *m_summary;
|
|
modref_summary_lto *m_summary_lto;
|
|
/* Recursive calls needs simplistic dataflow after analysis finished.
|
|
Collect all calls into this vector during analysis and later process
|
|
them in propagate. */
|
|
auto_vec <gimple *, 32> m_recursive_calls;
|
|
/* ECF flags of function being analyzed. */
|
|
int m_ecf_flags;
|
|
/* True if IPA propagation will be done later. */
|
|
bool m_ipa;
|
|
/* Set true if statement currently analyze is known to be
|
|
executed each time function is called. */
|
|
bool m_always_executed;
|
|
};
|
|
|
|
/* Set side_effects flag and return if something changed. */
|
|
|
|
bool
|
|
modref_access_analysis::set_side_effects ()
|
|
{
|
|
bool changed = false;
|
|
|
|
if (m_summary && !m_summary->side_effects)
|
|
{
|
|
m_summary->side_effects = true;
|
|
changed = true;
|
|
}
|
|
if (m_summary_lto && !m_summary_lto->side_effects)
|
|
{
|
|
m_summary_lto->side_effects = true;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Set nondeterministic flag and return if something changed. */
|
|
|
|
bool
|
|
modref_access_analysis::set_nondeterministic ()
|
|
{
|
|
bool changed = false;
|
|
|
|
if (m_summary && !m_summary->nondeterministic)
|
|
{
|
|
m_summary->side_effects = m_summary->nondeterministic = true;
|
|
changed = true;
|
|
}
|
|
if (m_summary_lto && !m_summary_lto->nondeterministic)
|
|
{
|
|
m_summary_lto->side_effects = m_summary_lto->nondeterministic = true;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Construct modref_access_node from REF. */
|
|
|
|
modref_access_node
|
|
modref_access_analysis::get_access (ao_ref *ref)
|
|
{
|
|
tree base;
|
|
|
|
base = ao_ref_base (ref);
|
|
modref_access_node a = {ref->offset, ref->size, ref->max_size,
|
|
0, MODREF_UNKNOWN_PARM, false, 0};
|
|
if (TREE_CODE (base) == MEM_REF || TREE_CODE (base) == TARGET_MEM_REF)
|
|
{
|
|
tree memref = base;
|
|
modref_parm_map m = parm_map_for_ptr (TREE_OPERAND (base, 0));
|
|
|
|
a.parm_index = m.parm_index;
|
|
if (a.parm_index != MODREF_UNKNOWN_PARM && TREE_CODE (memref) == MEM_REF)
|
|
{
|
|
a.parm_offset_known
|
|
= wi::to_poly_wide (TREE_OPERAND
|
|
(memref, 1)).to_shwi (&a.parm_offset);
|
|
if (a.parm_offset_known && m.parm_offset_known)
|
|
a.parm_offset += m.parm_offset;
|
|
else
|
|
a.parm_offset_known = false;
|
|
}
|
|
}
|
|
else
|
|
a.parm_index = MODREF_UNKNOWN_PARM;
|
|
return a;
|
|
}
|
|
|
|
/* Record access into the modref_records data structure. */
|
|
|
|
void
|
|
modref_access_analysis::record_access (modref_records *tt,
|
|
ao_ref *ref,
|
|
modref_access_node &a)
|
|
{
|
|
alias_set_type base_set = !flag_strict_aliasing
|
|
|| !flag_ipa_strict_aliasing ? 0
|
|
: ao_ref_base_alias_set (ref);
|
|
alias_set_type ref_set = !flag_strict_aliasing
|
|
|| !flag_ipa_strict_aliasing ? 0
|
|
: (ao_ref_alias_set (ref));
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " - Recording base_set=%i ref_set=%i ",
|
|
base_set, ref_set);
|
|
a.dump (dump_file);
|
|
}
|
|
tt->insert (current_function_decl, base_set, ref_set, a, false);
|
|
}
|
|
|
|
/* IPA version of record_access_tree. */
|
|
|
|
void
|
|
modref_access_analysis::record_access_lto (modref_records_lto *tt, ao_ref *ref,
|
|
modref_access_node &a)
|
|
{
|
|
/* get_alias_set sometimes use different type to compute the alias set
|
|
than TREE_TYPE (base). Do same adjustments. */
|
|
tree base_type = NULL_TREE, ref_type = NULL_TREE;
|
|
if (flag_strict_aliasing && flag_ipa_strict_aliasing)
|
|
{
|
|
tree base;
|
|
|
|
base = ref->ref;
|
|
while (handled_component_p (base))
|
|
base = TREE_OPERAND (base, 0);
|
|
|
|
base_type = reference_alias_ptr_type_1 (&base);
|
|
|
|
if (!base_type)
|
|
base_type = TREE_TYPE (base);
|
|
else
|
|
base_type = TYPE_REF_CAN_ALIAS_ALL (base_type)
|
|
? NULL_TREE : TREE_TYPE (base_type);
|
|
|
|
tree ref_expr = ref->ref;
|
|
ref_type = reference_alias_ptr_type_1 (&ref_expr);
|
|
|
|
if (!ref_type)
|
|
ref_type = TREE_TYPE (ref_expr);
|
|
else
|
|
ref_type = TYPE_REF_CAN_ALIAS_ALL (ref_type)
|
|
? NULL_TREE : TREE_TYPE (ref_type);
|
|
|
|
/* Sanity check that we are in sync with what get_alias_set does. */
|
|
gcc_checking_assert ((!base_type && !ao_ref_base_alias_set (ref))
|
|
|| get_alias_set (base_type)
|
|
== ao_ref_base_alias_set (ref));
|
|
gcc_checking_assert ((!ref_type && !ao_ref_alias_set (ref))
|
|
|| get_alias_set (ref_type)
|
|
== ao_ref_alias_set (ref));
|
|
|
|
/* Do not bother to record types that have no meaningful alias set.
|
|
Also skip variably modified types since these go to local streams. */
|
|
if (base_type && (!get_alias_set (base_type)
|
|
|| variably_modified_type_p (base_type, NULL_TREE)))
|
|
base_type = NULL_TREE;
|
|
if (ref_type && (!get_alias_set (ref_type)
|
|
|| variably_modified_type_p (ref_type, NULL_TREE)))
|
|
ref_type = NULL_TREE;
|
|
}
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " - Recording base type:");
|
|
print_generic_expr (dump_file, base_type);
|
|
fprintf (dump_file, " (alias set %i) ref type:",
|
|
base_type ? get_alias_set (base_type) : 0);
|
|
print_generic_expr (dump_file, ref_type);
|
|
fprintf (dump_file, " (alias set %i) ",
|
|
ref_type ? get_alias_set (ref_type) : 0);
|
|
a.dump (dump_file);
|
|
}
|
|
|
|
tt->insert (current_function_decl, base_type, ref_type, a, false);
|
|
}
|
|
|
|
/* Returns true if and only if we should store the access to EXPR.
|
|
Some accesses, e.g. loads from automatic variables, are not interesting. */
|
|
|
|
bool
|
|
modref_access_analysis::record_access_p (tree expr)
|
|
{
|
|
if (TREE_THIS_VOLATILE (expr))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " (volatile; marking nondeterministic) ");
|
|
set_nondeterministic ();
|
|
}
|
|
if (cfun->can_throw_non_call_exceptions
|
|
&& tree_could_throw_p (expr))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " (can throw; marking side effects) ");
|
|
set_side_effects ();
|
|
}
|
|
|
|
if (refs_local_or_readonly_memory_p (expr))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - Read-only or local, ignoring.\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Collapse loads and return true if something changed. */
|
|
|
|
bool
|
|
modref_access_analysis::record_unknown_load ()
|
|
{
|
|
bool changed = false;
|
|
|
|
if (m_summary && !m_summary->loads->every_base)
|
|
{
|
|
m_summary->loads->collapse ();
|
|
changed = true;
|
|
}
|
|
if (m_summary_lto && !m_summary_lto->loads->every_base)
|
|
{
|
|
m_summary_lto->loads->collapse ();
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Collapse loads and return true if something changed. */
|
|
|
|
bool
|
|
modref_access_analysis::record_unknown_store ()
|
|
{
|
|
bool changed = false;
|
|
|
|
if (m_summary && !m_summary->stores->every_base)
|
|
{
|
|
m_summary->stores->collapse ();
|
|
changed = true;
|
|
}
|
|
if (m_summary_lto && !m_summary_lto->stores->every_base)
|
|
{
|
|
m_summary_lto->stores->collapse ();
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Record unknown load from global memory. */
|
|
|
|
bool
|
|
modref_access_analysis::record_global_memory_load ()
|
|
{
|
|
bool changed = false;
|
|
modref_access_node a = {0, -1, -1,
|
|
0, MODREF_GLOBAL_MEMORY_PARM, false, 0};
|
|
|
|
if (m_summary && !m_summary->loads->every_base)
|
|
changed |= m_summary->loads->insert (current_function_decl, 0, 0, a, false);
|
|
if (m_summary_lto && !m_summary_lto->loads->every_base)
|
|
changed |= m_summary_lto->loads->insert (current_function_decl,
|
|
0, 0, a, false);
|
|
return changed;
|
|
}
|
|
|
|
/* Record unknown store from global memory. */
|
|
|
|
bool
|
|
modref_access_analysis::record_global_memory_store ()
|
|
{
|
|
bool changed = false;
|
|
modref_access_node a = {0, -1, -1,
|
|
0, MODREF_GLOBAL_MEMORY_PARM, false, 0};
|
|
|
|
if (m_summary && !m_summary->stores->every_base)
|
|
changed |= m_summary->stores->insert (current_function_decl,
|
|
0, 0, a, false);
|
|
if (m_summary_lto && !m_summary_lto->stores->every_base)
|
|
changed |= m_summary_lto->stores->insert (current_function_decl,
|
|
0, 0, a, false);
|
|
return changed;
|
|
}
|
|
|
|
/* Merge side effects of call STMT to function with CALLEE_SUMMARY.
|
|
Return true if something changed.
|
|
If IGNORE_STORES is true, do not merge stores.
|
|
If RECORD_ADJUSTMENTS is true cap number of adjustments to
|
|
a given access to make dataflow finite. */
|
|
|
|
bool
|
|
modref_access_analysis::merge_call_side_effects
|
|
(gimple *stmt, modref_summary *callee_summary,
|
|
cgraph_node *callee_node, bool record_adjustments)
|
|
{
|
|
gcall *call = as_a <gcall *> (stmt);
|
|
int flags = gimple_call_flags (call);
|
|
|
|
/* Nothing to do for non-looping cont functions. */
|
|
if ((flags & (ECF_CONST | ECF_NOVOPS))
|
|
&& !(flags & ECF_LOOPING_CONST_OR_PURE))
|
|
return false;
|
|
|
|
bool changed = false;
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, " - Merging side effects of %s\n",
|
|
callee_node->dump_name ());
|
|
|
|
/* Merge side effects and non-determinism.
|
|
PURE/CONST flags makes functions deterministic and if there is
|
|
no LOOPING_CONST_OR_PURE they also have no side effects. */
|
|
if (!(flags & (ECF_CONST | ECF_NOVOPS | ECF_PURE))
|
|
|| (flags & ECF_LOOPING_CONST_OR_PURE))
|
|
{
|
|
if (!m_summary->side_effects && callee_summary->side_effects)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - merging side effects.\n");
|
|
m_summary->side_effects = true;
|
|
changed = true;
|
|
}
|
|
if (!m_summary->nondeterministic && callee_summary->nondeterministic
|
|
&& !ignore_nondeterminism_p (current_function_decl, flags))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - merging nondeterministic.\n");
|
|
m_summary->nondeterministic = true;
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
/* For const functions we are done. */
|
|
if (flags & (ECF_CONST | ECF_NOVOPS))
|
|
return changed;
|
|
|
|
/* Merge calls_interposable flags. */
|
|
if (!m_summary->calls_interposable && callee_summary->calls_interposable)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - merging calls interposable.\n");
|
|
m_summary->calls_interposable = true;
|
|
changed = true;
|
|
}
|
|
|
|
if (!callee_node->binds_to_current_def_p () && !m_summary->calls_interposable)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - May be interposed.\n");
|
|
m_summary->calls_interposable = true;
|
|
changed = true;
|
|
}
|
|
|
|
/* Now merge the actual load, store and kill vectors. For this we need
|
|
to compute map translating new parameters to old. */
|
|
if (dump_file)
|
|
fprintf (dump_file, " Parm map:");
|
|
|
|
auto_vec <modref_parm_map, 32> parm_map;
|
|
parm_map.safe_grow_cleared (gimple_call_num_args (call), true);
|
|
for (unsigned i = 0; i < gimple_call_num_args (call); i++)
|
|
{
|
|
parm_map[i] = parm_map_for_ptr (gimple_call_arg (call, i));
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " %i", parm_map[i].parm_index);
|
|
if (parm_map[i].parm_offset_known)
|
|
{
|
|
fprintf (dump_file, " offset:");
|
|
print_dec ((poly_int64_pod)parm_map[i].parm_offset,
|
|
dump_file, SIGNED);
|
|
}
|
|
}
|
|
}
|
|
|
|
modref_parm_map chain_map;
|
|
if (gimple_call_chain (call))
|
|
{
|
|
chain_map = parm_map_for_ptr (gimple_call_chain (call));
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "static chain %i", chain_map.parm_index);
|
|
if (chain_map.parm_offset_known)
|
|
{
|
|
fprintf (dump_file, " offset:");
|
|
print_dec ((poly_int64_pod)chain_map.parm_offset,
|
|
dump_file, SIGNED);
|
|
}
|
|
}
|
|
}
|
|
if (dump_file)
|
|
fprintf (dump_file, "\n");
|
|
|
|
/* Kills can me merged in only if we know the function is going to be
|
|
always executed. */
|
|
if (m_always_executed
|
|
&& callee_summary->kills.length ()
|
|
&& (!cfun->can_throw_non_call_exceptions
|
|
|| !stmt_could_throw_p (cfun, call)))
|
|
{
|
|
/* Watch for self recursive updates. */
|
|
auto_vec<modref_access_node, 32> saved_kills;
|
|
|
|
saved_kills.reserve_exact (callee_summary->kills.length ());
|
|
saved_kills.splice (callee_summary->kills);
|
|
for (auto kill : saved_kills)
|
|
{
|
|
if (kill.parm_index >= (int)parm_map.length ())
|
|
continue;
|
|
modref_parm_map &m
|
|
= kill.parm_index == MODREF_STATIC_CHAIN_PARM
|
|
? chain_map
|
|
: parm_map[kill.parm_index];
|
|
if (m.parm_index == MODREF_LOCAL_MEMORY_PARM
|
|
|| m.parm_index == MODREF_UNKNOWN_PARM
|
|
|| m.parm_index == MODREF_RETSLOT_PARM
|
|
|| !m.parm_offset_known)
|
|
continue;
|
|
modref_access_node n = kill;
|
|
n.parm_index = m.parm_index;
|
|
n.parm_offset += m.parm_offset;
|
|
if (modref_access_node::insert_kill (m_summary->kills, n,
|
|
record_adjustments))
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
/* Merge in loads. */
|
|
changed |= m_summary->loads->merge (current_function_decl,
|
|
callee_summary->loads,
|
|
&parm_map, &chain_map,
|
|
record_adjustments,
|
|
!may_access_nonescaping_parm_p
|
|
(call, flags, true));
|
|
/* Merge in stores. */
|
|
if (!ignore_stores_p (current_function_decl, flags))
|
|
{
|
|
changed |= m_summary->stores->merge (current_function_decl,
|
|
callee_summary->stores,
|
|
&parm_map, &chain_map,
|
|
record_adjustments,
|
|
!may_access_nonescaping_parm_p
|
|
(call, flags, false));
|
|
if (!m_summary->writes_errno
|
|
&& callee_summary->writes_errno)
|
|
{
|
|
m_summary->writes_errno = true;
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Return access mode for argument I of call STMT with FNSPEC. */
|
|
|
|
modref_access_node
|
|
modref_access_analysis::get_access_for_fnspec (gcall *call, attr_fnspec &fnspec,
|
|
unsigned int i,
|
|
modref_parm_map &map)
|
|
{
|
|
tree size = NULL_TREE;
|
|
unsigned int size_arg;
|
|
|
|
if (!fnspec.arg_specified_p (i))
|
|
;
|
|
else if (fnspec.arg_max_access_size_given_by_arg_p (i, &size_arg))
|
|
size = gimple_call_arg (call, size_arg);
|
|
else if (fnspec.arg_access_size_given_by_type_p (i))
|
|
{
|
|
tree callee = gimple_call_fndecl (call);
|
|
tree t = TYPE_ARG_TYPES (TREE_TYPE (callee));
|
|
|
|
for (unsigned int p = 0; p < i; p++)
|
|
t = TREE_CHAIN (t);
|
|
size = TYPE_SIZE_UNIT (TREE_TYPE (TREE_VALUE (t)));
|
|
}
|
|
modref_access_node a = {0, -1, -1,
|
|
map.parm_offset, map.parm_index,
|
|
map.parm_offset_known, 0};
|
|
poly_int64 size_hwi;
|
|
if (size
|
|
&& poly_int_tree_p (size, &size_hwi)
|
|
&& coeffs_in_range_p (size_hwi, 0,
|
|
HOST_WIDE_INT_MAX / BITS_PER_UNIT))
|
|
{
|
|
a.size = -1;
|
|
a.max_size = size_hwi << LOG2_BITS_PER_UNIT;
|
|
}
|
|
return a;
|
|
}
|
|
/* Apply side effects of call STMT to CUR_SUMMARY using FNSPEC.
|
|
If IGNORE_STORES is true ignore them.
|
|
Return false if no useful summary can be produced. */
|
|
|
|
void
|
|
modref_access_analysis::process_fnspec (gcall *call)
|
|
{
|
|
int flags = gimple_call_flags (call);
|
|
|
|
/* PURE/CONST flags makes functions deterministic and if there is
|
|
no LOOPING_CONST_OR_PURE they also have no side effects. */
|
|
if (!(flags & (ECF_CONST | ECF_NOVOPS | ECF_PURE))
|
|
|| (flags & ECF_LOOPING_CONST_OR_PURE)
|
|
|| (cfun->can_throw_non_call_exceptions
|
|
&& stmt_could_throw_p (cfun, call)))
|
|
{
|
|
set_side_effects ();
|
|
if (!ignore_nondeterminism_p (current_function_decl, flags))
|
|
set_nondeterministic ();
|
|
}
|
|
|
|
/* For const functions we are done. */
|
|
if (flags & (ECF_CONST | ECF_NOVOPS))
|
|
return;
|
|
|
|
attr_fnspec fnspec = gimple_call_fnspec (call);
|
|
/* If there is no fnpec we know nothing about loads & stores. */
|
|
if (!fnspec.known_p ())
|
|
{
|
|
if (dump_file && gimple_call_builtin_p (call, BUILT_IN_NORMAL))
|
|
fprintf (dump_file, " Builtin with no fnspec: %s\n",
|
|
IDENTIFIER_POINTER (DECL_NAME (gimple_call_fndecl (call))));
|
|
if (!ignore_stores_p (current_function_decl, flags))
|
|
{
|
|
if (!may_access_nonescaping_parm_p (call, flags, false))
|
|
record_global_memory_store ();
|
|
else
|
|
record_unknown_store ();
|
|
if (!may_access_nonescaping_parm_p (call, flags, true))
|
|
record_global_memory_load ();
|
|
else
|
|
record_unknown_load ();
|
|
}
|
|
else
|
|
{
|
|
if (!may_access_nonescaping_parm_p (call, flags, true))
|
|
record_global_memory_load ();
|
|
else
|
|
record_unknown_load ();
|
|
}
|
|
return;
|
|
}
|
|
/* Process fnspec. */
|
|
if (fnspec.global_memory_read_p ())
|
|
{
|
|
if (may_access_nonescaping_parm_p (call, flags, true))
|
|
record_unknown_load ();
|
|
else
|
|
record_global_memory_load ();
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 0; i < gimple_call_num_args (call); i++)
|
|
if (!POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (call, i))))
|
|
;
|
|
else if (!fnspec.arg_specified_p (i)
|
|
|| fnspec.arg_maybe_read_p (i))
|
|
{
|
|
modref_parm_map map = parm_map_for_ptr
|
|
(gimple_call_arg (call, i));
|
|
|
|
if (map.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
continue;
|
|
if (map.parm_index == MODREF_UNKNOWN_PARM)
|
|
{
|
|
record_unknown_load ();
|
|
break;
|
|
}
|
|
modref_access_node a = get_access_for_fnspec (call, fnspec, i, map);
|
|
if (a.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
continue;
|
|
if (m_summary)
|
|
m_summary->loads->insert (current_function_decl, 0, 0, a, false);
|
|
if (m_summary_lto)
|
|
m_summary_lto->loads->insert (current_function_decl, 0, 0, a,
|
|
false);
|
|
}
|
|
}
|
|
if (ignore_stores_p (current_function_decl, flags))
|
|
return;
|
|
if (fnspec.global_memory_written_p ())
|
|
{
|
|
if (may_access_nonescaping_parm_p (call, flags, false))
|
|
record_unknown_store ();
|
|
else
|
|
record_global_memory_store ();
|
|
}
|
|
else
|
|
{
|
|
for (unsigned int i = 0; i < gimple_call_num_args (call); i++)
|
|
if (!POINTER_TYPE_P (TREE_TYPE (gimple_call_arg (call, i))))
|
|
;
|
|
else if (!fnspec.arg_specified_p (i)
|
|
|| fnspec.arg_maybe_written_p (i))
|
|
{
|
|
modref_parm_map map = parm_map_for_ptr
|
|
(gimple_call_arg (call, i));
|
|
|
|
if (map.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
continue;
|
|
if (map.parm_index == MODREF_UNKNOWN_PARM)
|
|
{
|
|
record_unknown_store ();
|
|
break;
|
|
}
|
|
modref_access_node a = get_access_for_fnspec (call, fnspec, i, map);
|
|
if (a.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
continue;
|
|
if (m_summary)
|
|
m_summary->stores->insert (current_function_decl, 0, 0, a, false);
|
|
if (m_summary_lto)
|
|
m_summary_lto->stores->insert (current_function_decl,
|
|
0, 0, a, false);
|
|
}
|
|
if (fnspec.errno_maybe_written_p () && flag_errno_math)
|
|
{
|
|
if (m_summary)
|
|
m_summary->writes_errno = true;
|
|
if (m_summary_lto)
|
|
m_summary_lto->writes_errno = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Analyze function call STMT in function F.
|
|
Remember recursive calls in RECURSIVE_CALLS. */
|
|
|
|
void
|
|
modref_access_analysis::analyze_call (gcall *stmt)
|
|
{
|
|
/* Check flags on the function call. In certain cases, analysis can be
|
|
simplified. */
|
|
int flags = gimple_call_flags (stmt);
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " - Analyzing call:");
|
|
print_gimple_stmt (dump_file, stmt, 0);
|
|
}
|
|
|
|
if ((flags & (ECF_CONST | ECF_NOVOPS))
|
|
&& !(flags & ECF_LOOPING_CONST_OR_PURE))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
" - ECF_CONST | ECF_NOVOPS, ignoring all stores and all loads "
|
|
"except for args.\n");
|
|
return;
|
|
}
|
|
|
|
/* Next, we try to get the callee's function declaration. The goal is to
|
|
merge their summary with ours. */
|
|
tree callee = gimple_call_fndecl (stmt);
|
|
|
|
/* Check if this is an indirect call. */
|
|
if (!callee)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, gimple_call_internal_p (stmt)
|
|
? " - Internal call" : " - Indirect call.\n");
|
|
process_fnspec (stmt);
|
|
return;
|
|
}
|
|
/* We only need to handle internal calls in IPA mode. */
|
|
gcc_checking_assert (!m_summary_lto && !m_ipa);
|
|
|
|
struct cgraph_node *callee_node = cgraph_node::get_create (callee);
|
|
|
|
/* If this is a recursive call, the target summary is the same as ours, so
|
|
there's nothing to do. */
|
|
if (recursive_call_p (current_function_decl, callee))
|
|
{
|
|
m_recursive_calls.safe_push (stmt);
|
|
set_side_effects ();
|
|
if (dump_file)
|
|
fprintf (dump_file, " - Skipping recursive call.\n");
|
|
return;
|
|
}
|
|
|
|
gcc_assert (callee_node != NULL);
|
|
|
|
/* Get the function symbol and its availability. */
|
|
enum availability avail;
|
|
callee_node = callee_node->function_symbol (&avail);
|
|
bool looping;
|
|
if (builtin_safe_for_const_function_p (&looping, callee))
|
|
{
|
|
if (looping)
|
|
set_side_effects ();
|
|
if (dump_file)
|
|
fprintf (dump_file, " - Builtin is safe for const.\n");
|
|
return;
|
|
}
|
|
if (avail <= AVAIL_INTERPOSABLE)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
" - Function availability <= AVAIL_INTERPOSABLE.\n");
|
|
process_fnspec (stmt);
|
|
return;
|
|
}
|
|
|
|
/* Get callee's modref summary. As above, if there's no summary, we either
|
|
have to give up or, if stores are ignored, we can just purge loads. */
|
|
modref_summary *callee_summary = optimization_summaries->get (callee_node);
|
|
if (!callee_summary)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - No modref summary available for callee.\n");
|
|
process_fnspec (stmt);
|
|
return;
|
|
}
|
|
|
|
merge_call_side_effects (stmt, callee_summary, callee_node, false);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Helper for analyze_stmt. */
|
|
|
|
bool
|
|
modref_access_analysis::analyze_load (gimple *, tree, tree op, void *data)
|
|
{
|
|
modref_access_analysis *t = (modref_access_analysis *)data;
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " - Analyzing load: ");
|
|
print_generic_expr (dump_file, op);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
if (!t->record_access_p (op))
|
|
return false;
|
|
|
|
ao_ref r;
|
|
ao_ref_init (&r, op);
|
|
modref_access_node a = get_access (&r);
|
|
if (a.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
return false;
|
|
|
|
if (t->m_summary)
|
|
t->record_access (t->m_summary->loads, &r, a);
|
|
if (t->m_summary_lto)
|
|
t->record_access_lto (t->m_summary_lto->loads, &r, a);
|
|
return false;
|
|
}
|
|
|
|
/* Helper for analyze_stmt. */
|
|
|
|
bool
|
|
modref_access_analysis::analyze_store (gimple *stmt, tree, tree op, void *data)
|
|
{
|
|
modref_access_analysis *t = (modref_access_analysis *)data;
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " - Analyzing store: ");
|
|
print_generic_expr (dump_file, op);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
if (!t->record_access_p (op))
|
|
return false;
|
|
|
|
ao_ref r;
|
|
ao_ref_init (&r, op);
|
|
modref_access_node a = get_access (&r);
|
|
if (a.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
return false;
|
|
|
|
if (t->m_summary)
|
|
t->record_access (t->m_summary->stores, &r, a);
|
|
if (t->m_summary_lto)
|
|
t->record_access_lto (t->m_summary_lto->stores, &r, a);
|
|
if (t->m_always_executed
|
|
&& a.useful_for_kill_p ()
|
|
&& (!cfun->can_throw_non_call_exceptions
|
|
|| !stmt_could_throw_p (cfun, stmt)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - Recording kill\n");
|
|
if (t->m_summary)
|
|
modref_access_node::insert_kill (t->m_summary->kills, a, false);
|
|
if (t->m_summary_lto)
|
|
modref_access_node::insert_kill (t->m_summary_lto->kills, a, false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Analyze statement STMT of function F.
|
|
If IPA is true do not merge in side effects of calls. */
|
|
|
|
void
|
|
modref_access_analysis::analyze_stmt (gimple *stmt, bool always_executed)
|
|
{
|
|
m_always_executed = always_executed;
|
|
/* In general we can not ignore clobbers because they are barriers for code
|
|
motion, however after inlining it is safe to do because local optimization
|
|
passes do not consider clobbers from other functions.
|
|
Similar logic is in ipa-pure-const.cc. */
|
|
if ((m_ipa || cfun->after_inlining) && gimple_clobber_p (stmt))
|
|
{
|
|
if (always_executed && record_access_p (gimple_assign_lhs (stmt)))
|
|
{
|
|
ao_ref r;
|
|
ao_ref_init (&r, gimple_assign_lhs (stmt));
|
|
modref_access_node a = get_access (&r);
|
|
if (a.useful_for_kill_p ())
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - Recording kill\n");
|
|
if (m_summary)
|
|
modref_access_node::insert_kill (m_summary->kills, a, false);
|
|
if (m_summary_lto)
|
|
modref_access_node::insert_kill (m_summary_lto->kills,
|
|
a, false);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Analyze all loads and stores in STMT. */
|
|
walk_stmt_load_store_ops (stmt, this,
|
|
analyze_load, analyze_store);
|
|
|
|
switch (gimple_code (stmt))
|
|
{
|
|
case GIMPLE_ASM:
|
|
if (gimple_asm_volatile_p (as_a <gasm *> (stmt)))
|
|
set_nondeterministic ();
|
|
if (cfun->can_throw_non_call_exceptions
|
|
&& stmt_could_throw_p (cfun, stmt))
|
|
set_side_effects ();
|
|
/* If the ASM statement does not read nor write memory, there's nothing
|
|
to do. Otherwise just give up. */
|
|
if (!gimple_asm_clobbers_memory_p (as_a <gasm *> (stmt)))
|
|
return;
|
|
if (dump_file)
|
|
fprintf (dump_file, " - Function contains GIMPLE_ASM statement "
|
|
"which clobbers memory.\n");
|
|
record_unknown_load ();
|
|
record_unknown_store ();
|
|
return;
|
|
case GIMPLE_CALL:
|
|
if (!m_ipa || gimple_call_internal_p (stmt))
|
|
analyze_call (as_a <gcall *> (stmt));
|
|
else
|
|
{
|
|
attr_fnspec fnspec = gimple_call_fnspec (as_a <gcall *>(stmt));
|
|
|
|
if (fnspec.known_p ()
|
|
&& (!fnspec.global_memory_read_p ()
|
|
|| !fnspec.global_memory_written_p ()))
|
|
{
|
|
cgraph_edge *e = cgraph_node::get
|
|
(current_function_decl)->get_edge (stmt);
|
|
if (e->callee)
|
|
{
|
|
fnspec_summaries->get_create (e)->fnspec
|
|
= xstrdup (fnspec.get_str ());
|
|
if (dump_file)
|
|
fprintf (dump_file, " Recorded fnspec %s\n",
|
|
fnspec.get_str ());
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
default:
|
|
if (cfun->can_throw_non_call_exceptions
|
|
&& stmt_could_throw_p (cfun, stmt))
|
|
set_side_effects ();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Propagate load/stores across recursive calls. */
|
|
|
|
void
|
|
modref_access_analysis::propagate ()
|
|
{
|
|
if (m_ipa && m_summary)
|
|
return;
|
|
|
|
bool changed = true;
|
|
bool first = true;
|
|
cgraph_node *fnode = cgraph_node::get (current_function_decl);
|
|
|
|
m_always_executed = false;
|
|
while (changed && m_summary->useful_p (m_ecf_flags, false))
|
|
{
|
|
changed = false;
|
|
for (unsigned i = 0; i < m_recursive_calls.length (); i++)
|
|
{
|
|
changed |= merge_call_side_effects (m_recursive_calls[i], m_summary,
|
|
fnode, !first);
|
|
}
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
/* Analyze function. */
|
|
|
|
void
|
|
modref_access_analysis::analyze ()
|
|
{
|
|
m_ecf_flags = flags_from_decl_or_type (current_function_decl);
|
|
bool summary_useful = true;
|
|
|
|
/* Analyze each statement in each basic block of the function. If the
|
|
statement cannot be analyzed (for any reason), the entire function cannot
|
|
be analyzed by modref. */
|
|
basic_block bb;
|
|
FOR_EACH_BB_FN (bb, cfun)
|
|
{
|
|
gimple_stmt_iterator si;
|
|
bool always_executed
|
|
= bb == single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun))->dest;
|
|
|
|
for (si = gsi_start_nondebug_after_labels_bb (bb);
|
|
!gsi_end_p (si); gsi_next_nondebug (&si))
|
|
{
|
|
/* NULL memory accesses terminates BB. These accesses are known
|
|
to trip undefined behavior. gimple-ssa-isolate-paths turns them
|
|
to volatile accesses and adds builtin_trap call which would
|
|
confuse us otherwise. */
|
|
if (infer_nonnull_range_by_dereference (gsi_stmt (si),
|
|
null_pointer_node))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " - NULL memory access; terminating BB\n");
|
|
if (flag_non_call_exceptions)
|
|
set_side_effects ();
|
|
break;
|
|
}
|
|
analyze_stmt (gsi_stmt (si), always_executed);
|
|
|
|
/* Avoid doing useless work. */
|
|
if ((!m_summary || !m_summary->useful_p (m_ecf_flags, false))
|
|
&& (!m_summary_lto
|
|
|| !m_summary_lto->useful_p (m_ecf_flags, false)))
|
|
{
|
|
summary_useful = false;
|
|
break;
|
|
}
|
|
if (always_executed
|
|
&& stmt_can_throw_external (cfun, gsi_stmt (si)))
|
|
always_executed = false;
|
|
}
|
|
if (!summary_useful)
|
|
break;
|
|
}
|
|
/* In non-IPA mode we need to perform iterative dataflow on recursive calls.
|
|
This needs to be done after all other side effects are computed. */
|
|
if (summary_useful)
|
|
{
|
|
if (!m_ipa)
|
|
propagate ();
|
|
if (m_summary && !m_summary->side_effects && !finite_function_p ())
|
|
m_summary->side_effects = true;
|
|
if (m_summary_lto && !m_summary_lto->side_effects
|
|
&& !finite_function_p ())
|
|
m_summary_lto->side_effects = true;
|
|
}
|
|
}
|
|
|
|
/* Return true if OP accesses memory pointed to by SSA_NAME. */
|
|
|
|
bool
|
|
memory_access_to (tree op, tree ssa_name)
|
|
{
|
|
tree base = get_base_address (op);
|
|
if (!base)
|
|
return false;
|
|
if (TREE_CODE (base) != MEM_REF && TREE_CODE (base) != TARGET_MEM_REF)
|
|
return false;
|
|
return TREE_OPERAND (base, 0) == ssa_name;
|
|
}
|
|
|
|
/* Consider statement val = *arg.
|
|
return EAF flags of ARG that can be determined from EAF flags of VAL
|
|
(which are known to be FLAGS). If IGNORE_STORES is true we can ignore
|
|
all stores to VAL, i.e. when handling noreturn function. */
|
|
|
|
static int
|
|
deref_flags (int flags, bool ignore_stores)
|
|
{
|
|
/* Dereference is also a direct read but dereferenced value does not
|
|
yield any other direct use. */
|
|
int ret = EAF_NO_DIRECT_CLOBBER | EAF_NO_DIRECT_ESCAPE
|
|
| EAF_NOT_RETURNED_DIRECTLY;
|
|
/* If argument is unused just account for
|
|
the read involved in dereference. */
|
|
if (flags & EAF_UNUSED)
|
|
ret |= EAF_NO_INDIRECT_READ | EAF_NO_INDIRECT_CLOBBER
|
|
| EAF_NO_INDIRECT_ESCAPE;
|
|
else
|
|
{
|
|
/* Direct or indirect accesses leads to indirect accesses. */
|
|
if (((flags & EAF_NO_DIRECT_CLOBBER)
|
|
&& (flags & EAF_NO_INDIRECT_CLOBBER))
|
|
|| ignore_stores)
|
|
ret |= EAF_NO_INDIRECT_CLOBBER;
|
|
if (((flags & EAF_NO_DIRECT_ESCAPE)
|
|
&& (flags & EAF_NO_INDIRECT_ESCAPE))
|
|
|| ignore_stores)
|
|
ret |= EAF_NO_INDIRECT_ESCAPE;
|
|
if ((flags & EAF_NO_DIRECT_READ)
|
|
&& (flags & EAF_NO_INDIRECT_READ))
|
|
ret |= EAF_NO_INDIRECT_READ;
|
|
if ((flags & EAF_NOT_RETURNED_DIRECTLY)
|
|
&& (flags & EAF_NOT_RETURNED_INDIRECTLY))
|
|
ret |= EAF_NOT_RETURNED_INDIRECTLY;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Description of an escape point: a call which affects flags of a given
|
|
SSA name. */
|
|
|
|
struct escape_point
|
|
{
|
|
/* Value escapes to this call. */
|
|
gcall *call;
|
|
/* Argument it escapes to. */
|
|
int arg;
|
|
/* Flags already known about the argument (this can save us from recording
|
|
escape points if local analysis did good job already). */
|
|
eaf_flags_t min_flags;
|
|
/* Does value escape directly or indirectly? */
|
|
bool direct;
|
|
};
|
|
|
|
/* Lattice used during the eaf flags analysis dataflow. For a given SSA name
|
|
we aim to compute its flags and escape points. We also use the lattice
|
|
to dynamically build dataflow graph to propagate on. */
|
|
|
|
class modref_lattice
|
|
{
|
|
public:
|
|
/* EAF flags of the SSA name. */
|
|
eaf_flags_t flags;
|
|
/* Used during DFS walk to mark names where final value was determined
|
|
without need for dataflow. */
|
|
bool known;
|
|
/* Used during DFS walk to mark open vertices (for cycle detection). */
|
|
bool open;
|
|
/* Set during DFS walk for names that needs dataflow propagation. */
|
|
bool do_dataflow;
|
|
/* Used during the iterative dataflow. */
|
|
bool changed;
|
|
|
|
/* When doing IPA analysis we can not merge in callee escape points;
|
|
Only remember them and do the merging at IPA propagation time. */
|
|
vec <escape_point, va_heap, vl_ptr> escape_points;
|
|
|
|
/* Representation of a graph for dataflow. This graph is built on-demand
|
|
using modref_eaf_analysis::analyze_ssa and later solved by
|
|
modref_eaf_analysis::propagate.
|
|
Each edge represents the fact that flags of current lattice should be
|
|
propagated to lattice of SSA_NAME. */
|
|
struct propagate_edge
|
|
{
|
|
int ssa_name;
|
|
bool deref;
|
|
};
|
|
vec <propagate_edge, va_heap, vl_ptr> propagate_to;
|
|
|
|
void init ();
|
|
void release ();
|
|
bool merge (const modref_lattice &with);
|
|
bool merge (int flags);
|
|
bool merge_deref (const modref_lattice &with, bool ignore_stores);
|
|
bool merge_direct_load ();
|
|
bool merge_direct_store ();
|
|
bool add_escape_point (gcall *call, int arg, int min_flags, bool diret);
|
|
void dump (FILE *out, int indent = 0) const;
|
|
};
|
|
|
|
/* Lattices are saved to vectors, so keep them PODs. */
|
|
void
|
|
modref_lattice::init ()
|
|
{
|
|
/* All flags we track. */
|
|
int f = EAF_NO_DIRECT_CLOBBER | EAF_NO_INDIRECT_CLOBBER
|
|
| EAF_NO_DIRECT_ESCAPE | EAF_NO_INDIRECT_ESCAPE
|
|
| EAF_NO_DIRECT_READ | EAF_NO_INDIRECT_READ
|
|
| EAF_NOT_RETURNED_DIRECTLY | EAF_NOT_RETURNED_INDIRECTLY
|
|
| EAF_UNUSED;
|
|
flags = f;
|
|
/* Check that eaf_flags_t is wide enough to hold all flags. */
|
|
gcc_checking_assert (f == flags);
|
|
open = true;
|
|
known = false;
|
|
}
|
|
|
|
/* Release memory. */
|
|
void
|
|
modref_lattice::release ()
|
|
{
|
|
escape_points.release ();
|
|
propagate_to.release ();
|
|
}
|
|
|
|
/* Dump lattice to OUT; indent with INDENT spaces. */
|
|
|
|
void
|
|
modref_lattice::dump (FILE *out, int indent) const
|
|
{
|
|
dump_eaf_flags (out, flags);
|
|
if (escape_points.length ())
|
|
{
|
|
fprintf (out, "%*sEscapes:\n", indent, "");
|
|
for (unsigned int i = 0; i < escape_points.length (); i++)
|
|
{
|
|
fprintf (out, "%*s Arg %i (%s) min flags", indent, "",
|
|
escape_points[i].arg,
|
|
escape_points[i].direct ? "direct" : "indirect");
|
|
dump_eaf_flags (out, escape_points[i].min_flags, false);
|
|
fprintf (out, " in call ");
|
|
print_gimple_stmt (out, escape_points[i].call, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add escape point CALL, ARG, MIN_FLAGS, DIRECT. Return false if such escape
|
|
point exists. */
|
|
|
|
bool
|
|
modref_lattice::add_escape_point (gcall *call, int arg, int min_flags,
|
|
bool direct)
|
|
{
|
|
escape_point *ep;
|
|
unsigned int i;
|
|
|
|
/* If we already determined flags to be bad enough,
|
|
we do not need to record. */
|
|
if ((flags & min_flags) == flags || (min_flags & EAF_UNUSED))
|
|
return false;
|
|
|
|
FOR_EACH_VEC_ELT (escape_points, i, ep)
|
|
if (ep->call == call && ep->arg == arg && ep->direct == direct)
|
|
{
|
|
if ((ep->min_flags & min_flags) == min_flags)
|
|
return false;
|
|
ep->min_flags &= min_flags;
|
|
return true;
|
|
}
|
|
/* Give up if max escape points is met. */
|
|
if ((int)escape_points.length () > param_modref_max_escape_points)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "--param modref-max-escape-points limit reached\n");
|
|
merge (0);
|
|
return true;
|
|
}
|
|
escape_point new_ep = {call, arg, min_flags, direct};
|
|
escape_points.safe_push (new_ep);
|
|
return true;
|
|
}
|
|
|
|
/* Merge in flags from F. */
|
|
bool
|
|
modref_lattice::merge (int f)
|
|
{
|
|
if (f & EAF_UNUSED)
|
|
return false;
|
|
/* Check that flags seems sane: if function does not read the parameter
|
|
it can not access it indirectly. */
|
|
gcc_checking_assert (!(f & EAF_NO_DIRECT_READ)
|
|
|| ((f & EAF_NO_INDIRECT_READ)
|
|
&& (f & EAF_NO_INDIRECT_CLOBBER)
|
|
&& (f & EAF_NO_INDIRECT_ESCAPE)
|
|
&& (f & EAF_NOT_RETURNED_INDIRECTLY)));
|
|
if ((flags & f) != flags)
|
|
{
|
|
flags &= f;
|
|
/* Prune obviously useless flags;
|
|
We do not have ECF_FLAGS handy which is not big problem since
|
|
we will do final flags cleanup before producing summary.
|
|
Merging should be fast so it can work well with dataflow. */
|
|
flags = remove_useless_eaf_flags (flags, 0, false);
|
|
if (!flags)
|
|
escape_points.release ();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Merge in WITH. Return true if anything changed. */
|
|
|
|
bool
|
|
modref_lattice::merge (const modref_lattice &with)
|
|
{
|
|
if (!with.known)
|
|
do_dataflow = true;
|
|
|
|
bool changed = merge (with.flags);
|
|
|
|
if (!flags)
|
|
return changed;
|
|
for (unsigned int i = 0; i < with.escape_points.length (); i++)
|
|
changed |= add_escape_point (with.escape_points[i].call,
|
|
with.escape_points[i].arg,
|
|
with.escape_points[i].min_flags,
|
|
with.escape_points[i].direct);
|
|
return changed;
|
|
}
|
|
|
|
/* Merge in deref of WITH. If IGNORE_STORES is true do not consider
|
|
stores. Return true if anything changed. */
|
|
|
|
bool
|
|
modref_lattice::merge_deref (const modref_lattice &with, bool ignore_stores)
|
|
{
|
|
if (!with.known)
|
|
do_dataflow = true;
|
|
|
|
bool changed = merge (deref_flags (with.flags, ignore_stores));
|
|
|
|
if (!flags)
|
|
return changed;
|
|
for (unsigned int i = 0; i < with.escape_points.length (); i++)
|
|
{
|
|
int min_flags = with.escape_points[i].min_flags;
|
|
|
|
if (with.escape_points[i].direct)
|
|
min_flags = deref_flags (min_flags, ignore_stores);
|
|
else if (ignore_stores)
|
|
min_flags |= ignore_stores_eaf_flags;
|
|
changed |= add_escape_point (with.escape_points[i].call,
|
|
with.escape_points[i].arg,
|
|
min_flags,
|
|
false);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Merge in flags for direct load. */
|
|
|
|
bool
|
|
modref_lattice::merge_direct_load ()
|
|
{
|
|
return merge (~(EAF_UNUSED | EAF_NO_DIRECT_READ));
|
|
}
|
|
|
|
/* Merge in flags for direct store. */
|
|
|
|
bool
|
|
modref_lattice::merge_direct_store ()
|
|
{
|
|
return merge (~(EAF_UNUSED | EAF_NO_DIRECT_CLOBBER));
|
|
}
|
|
|
|
/* Analyzer of EAF flags.
|
|
This is generally dataflow problem over the SSA graph, however we only
|
|
care about flags of few selected ssa names (arguments, return slot and
|
|
static chain). So we first call analyze_ssa_name on all relevant names
|
|
and perform a DFS walk to discover SSA names where flags needs to be
|
|
determined. For acyclic graphs we try to determine final flags during
|
|
this walk. Once cycles or recursion depth is met we enlist SSA names
|
|
for dataflow which is done by propagate call.
|
|
|
|
After propagation the flags can be obtained using get_ssa_name_flags. */
|
|
|
|
class modref_eaf_analysis
|
|
{
|
|
public:
|
|
/* Mark NAME as relevant for analysis. */
|
|
void analyze_ssa_name (tree name, bool deferred = false);
|
|
/* Dataflow solver. */
|
|
void propagate ();
|
|
/* Return flags computed earlier for NAME. */
|
|
int get_ssa_name_flags (tree name)
|
|
{
|
|
int version = SSA_NAME_VERSION (name);
|
|
gcc_checking_assert (m_lattice[version].known);
|
|
return m_lattice[version].flags;
|
|
}
|
|
/* In IPA mode this will record all escape points
|
|
determined for NAME to PARM_IDNEX. Flags are minimal
|
|
flags known. */
|
|
void record_escape_points (tree name, int parm_index, int flags);
|
|
modref_eaf_analysis (bool ipa)
|
|
{
|
|
m_ipa = ipa;
|
|
m_depth = 0;
|
|
m_lattice.safe_grow_cleared (num_ssa_names, true);
|
|
}
|
|
~modref_eaf_analysis ()
|
|
{
|
|
gcc_checking_assert (!m_depth);
|
|
if (m_ipa || m_names_to_propagate.length ())
|
|
for (unsigned int i = 0; i < num_ssa_names; i++)
|
|
m_lattice[i].release ();
|
|
}
|
|
private:
|
|
/* If true, we produce analysis for IPA mode. In this case escape points are
|
|
collected. */
|
|
bool m_ipa;
|
|
/* Depth of recursion of analyze_ssa_name. */
|
|
int m_depth;
|
|
/* Propagation lattice for individual ssa names. */
|
|
auto_vec<modref_lattice> m_lattice;
|
|
auto_vec<tree> m_deferred_names;
|
|
auto_vec<int> m_names_to_propagate;
|
|
|
|
void merge_with_ssa_name (tree dest, tree src, bool deref);
|
|
void merge_call_lhs_flags (gcall *call, int arg, tree name, bool direct,
|
|
bool deref);
|
|
};
|
|
|
|
|
|
/* Call statements may return their parameters. Consider argument number
|
|
ARG of USE_STMT and determine flags that can needs to be cleared
|
|
in case pointer possibly indirectly references from ARG I is returned.
|
|
If DIRECT is true consider direct returns and if INDIRECT consider
|
|
indirect returns.
|
|
LATTICE, DEPTH and ipa are same as in analyze_ssa_name.
|
|
ARG is set to -1 for static chain. */
|
|
|
|
void
|
|
modref_eaf_analysis::merge_call_lhs_flags (gcall *call, int arg,
|
|
tree name, bool direct,
|
|
bool indirect)
|
|
{
|
|
int index = SSA_NAME_VERSION (name);
|
|
bool returned_directly = false;
|
|
|
|
/* If there is no return value, no flags are affected. */
|
|
if (!gimple_call_lhs (call))
|
|
return;
|
|
|
|
/* If we know that function returns given argument and it is not ARG
|
|
we can still be happy. */
|
|
if (arg >= 0)
|
|
{
|
|
int flags = gimple_call_return_flags (call);
|
|
if (flags & ERF_RETURNS_ARG)
|
|
{
|
|
if ((flags & ERF_RETURN_ARG_MASK) == arg)
|
|
returned_directly = true;
|
|
else
|
|
return;
|
|
}
|
|
}
|
|
/* Make ERF_RETURNS_ARG overwrite EAF_UNUSED. */
|
|
if (returned_directly)
|
|
{
|
|
direct = true;
|
|
indirect = false;
|
|
}
|
|
/* If value is not returned at all, do nothing. */
|
|
else if (!direct && !indirect)
|
|
return;
|
|
|
|
/* If return value is SSA name determine its flags. */
|
|
if (TREE_CODE (gimple_call_lhs (call)) == SSA_NAME)
|
|
{
|
|
tree lhs = gimple_call_lhs (call);
|
|
if (direct)
|
|
merge_with_ssa_name (name, lhs, false);
|
|
if (indirect)
|
|
merge_with_ssa_name (name, lhs, true);
|
|
}
|
|
/* In the case of memory store we can do nothing. */
|
|
else if (!direct)
|
|
m_lattice[index].merge (deref_flags (0, false));
|
|
else
|
|
m_lattice[index].merge (0);
|
|
}
|
|
|
|
/* CALL_FLAGS are EAF_FLAGS of the argument. Turn them
|
|
into flags for caller, update LATTICE of corresponding
|
|
argument if needed. */
|
|
|
|
static int
|
|
callee_to_caller_flags (int call_flags, bool ignore_stores,
|
|
modref_lattice &lattice)
|
|
{
|
|
/* call_flags is about callee returning a value
|
|
that is not the same as caller returning it. */
|
|
call_flags |= EAF_NOT_RETURNED_DIRECTLY
|
|
| EAF_NOT_RETURNED_INDIRECTLY;
|
|
if (!ignore_stores && !(call_flags & EAF_UNUSED))
|
|
{
|
|
/* If value escapes we are no longer able to track what happens
|
|
with it because we can read it from the escaped location
|
|
anytime. */
|
|
if (!(call_flags & EAF_NO_DIRECT_ESCAPE))
|
|
lattice.merge (0);
|
|
else if (!(call_flags & EAF_NO_INDIRECT_ESCAPE))
|
|
lattice.merge (~(EAF_NOT_RETURNED_INDIRECTLY
|
|
| EAF_NO_DIRECT_READ
|
|
| EAF_NO_INDIRECT_READ
|
|
| EAF_NO_INDIRECT_CLOBBER
|
|
| EAF_UNUSED));
|
|
}
|
|
else
|
|
call_flags |= ignore_stores_eaf_flags;
|
|
return call_flags;
|
|
}
|
|
|
|
/* Analyze EAF flags for SSA name NAME and store result to LATTICE.
|
|
LATTICE is an array of modref_lattices.
|
|
DEPTH is a recursion depth used to make debug output prettier.
|
|
If IPA is true we analyze for IPA propagation (and thus call escape points
|
|
are processed later) */
|
|
|
|
void
|
|
modref_eaf_analysis::analyze_ssa_name (tree name, bool deferred)
|
|
{
|
|
imm_use_iterator ui;
|
|
gimple *use_stmt;
|
|
int index = SSA_NAME_VERSION (name);
|
|
|
|
if (!deferred)
|
|
{
|
|
/* See if value is already computed. */
|
|
if (m_lattice[index].known || m_lattice[index].do_dataflow)
|
|
return;
|
|
if (m_lattice[index].open)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"%*sCycle in SSA graph\n",
|
|
m_depth * 4, "");
|
|
return;
|
|
}
|
|
/* Recursion guard. */
|
|
m_lattice[index].init ();
|
|
if (m_depth == param_modref_max_depth)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"%*sMax recursion depth reached; postponing\n",
|
|
m_depth * 4, "");
|
|
m_deferred_names.safe_push (name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file,
|
|
"%*sAnalyzing flags of ssa name: ", m_depth * 4, "");
|
|
print_generic_expr (dump_file, name);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
|
|
FOR_EACH_IMM_USE_STMT (use_stmt, ui, name)
|
|
{
|
|
if (m_lattice[index].flags == 0)
|
|
break;
|
|
if (is_gimple_debug (use_stmt))
|
|
continue;
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "%*s Analyzing stmt: ", m_depth * 4, "");
|
|
print_gimple_stmt (dump_file, use_stmt, 0);
|
|
}
|
|
/* If we see a direct non-debug use, clear unused bit.
|
|
All dereferences should be accounted below using deref_flags. */
|
|
m_lattice[index].merge (~EAF_UNUSED);
|
|
|
|
/* Gimple return may load the return value.
|
|
Returning name counts as an use by tree-ssa-structalias.cc */
|
|
if (greturn *ret = dyn_cast <greturn *> (use_stmt))
|
|
{
|
|
/* Returning through return slot is seen as memory write earlier. */
|
|
if (DECL_RESULT (current_function_decl)
|
|
&& DECL_BY_REFERENCE (DECL_RESULT (current_function_decl)))
|
|
;
|
|
else if (gimple_return_retval (ret) == name)
|
|
m_lattice[index].merge (~(EAF_UNUSED | EAF_NOT_RETURNED_DIRECTLY
|
|
| EAF_NOT_RETURNED_DIRECTLY));
|
|
else if (memory_access_to (gimple_return_retval (ret), name))
|
|
{
|
|
m_lattice[index].merge_direct_load ();
|
|
m_lattice[index].merge (~(EAF_UNUSED
|
|
| EAF_NOT_RETURNED_INDIRECTLY));
|
|
}
|
|
}
|
|
/* Account for LHS store, arg loads and flags from callee function. */
|
|
else if (gcall *call = dyn_cast <gcall *> (use_stmt))
|
|
{
|
|
tree callee = gimple_call_fndecl (call);
|
|
|
|
/* IPA PTA internally it treats calling a function as "writing" to
|
|
the argument space of all functions the function pointer points to
|
|
(PR101949). We can not drop EAF_NOCLOBBER only when ipa-pta
|
|
is on since that would allow propagation of this from -fno-ipa-pta
|
|
to -fipa-pta functions. */
|
|
if (gimple_call_fn (use_stmt) == name)
|
|
m_lattice[index].merge (~(EAF_NO_DIRECT_CLOBBER | EAF_UNUSED));
|
|
|
|
/* Recursion would require bit of propagation; give up for now. */
|
|
if (callee && !m_ipa && recursive_call_p (current_function_decl,
|
|
callee))
|
|
m_lattice[index].merge (0);
|
|
else
|
|
{
|
|
int ecf_flags = gimple_call_flags (call);
|
|
bool ignore_stores = ignore_stores_p (current_function_decl,
|
|
ecf_flags);
|
|
bool ignore_retval = ignore_retval_p (current_function_decl,
|
|
ecf_flags);
|
|
|
|
/* Handle *name = func (...). */
|
|
if (gimple_call_lhs (call)
|
|
&& memory_access_to (gimple_call_lhs (call), name))
|
|
{
|
|
m_lattice[index].merge_direct_store ();
|
|
/* Return slot optimization passes address of
|
|
LHS to callee via hidden parameter and this
|
|
may make LHS to escape. See PR 98499. */
|
|
if (gimple_call_return_slot_opt_p (call)
|
|
&& TREE_ADDRESSABLE (TREE_TYPE (gimple_call_lhs (call))))
|
|
{
|
|
int call_flags = gimple_call_retslot_flags (call);
|
|
bool isretslot = false;
|
|
|
|
if (DECL_RESULT (current_function_decl)
|
|
&& DECL_BY_REFERENCE
|
|
(DECL_RESULT (current_function_decl)))
|
|
isretslot = ssa_default_def
|
|
(cfun,
|
|
DECL_RESULT (current_function_decl))
|
|
== name;
|
|
|
|
/* Passing returnslot to return slot is special because
|
|
not_returned and escape has same meaning.
|
|
However passing arg to return slot is different. If
|
|
the callee's return slot is returned it means that
|
|
arg is written to itself which is an escape.
|
|
Since we do not track the memory it is written to we
|
|
need to give up on analyzing it. */
|
|
if (!isretslot)
|
|
{
|
|
if (!(call_flags & (EAF_NOT_RETURNED_DIRECTLY
|
|
| EAF_UNUSED)))
|
|
m_lattice[index].merge (0);
|
|
else gcc_checking_assert
|
|
(call_flags & (EAF_NOT_RETURNED_INDIRECTLY
|
|
| EAF_UNUSED));
|
|
call_flags = callee_to_caller_flags
|
|
(call_flags, false,
|
|
m_lattice[index]);
|
|
}
|
|
m_lattice[index].merge (call_flags);
|
|
}
|
|
}
|
|
|
|
if (gimple_call_chain (call)
|
|
&& (gimple_call_chain (call) == name))
|
|
{
|
|
int call_flags = gimple_call_static_chain_flags (call);
|
|
if (!ignore_retval && !(call_flags & EAF_UNUSED))
|
|
merge_call_lhs_flags
|
|
(call, -1, name,
|
|
!(call_flags & EAF_NOT_RETURNED_DIRECTLY),
|
|
!(call_flags & EAF_NOT_RETURNED_INDIRECTLY));
|
|
call_flags = callee_to_caller_flags
|
|
(call_flags, ignore_stores,
|
|
m_lattice[index]);
|
|
if (!(ecf_flags & (ECF_CONST | ECF_NOVOPS)))
|
|
m_lattice[index].merge (call_flags);
|
|
}
|
|
|
|
/* Process internal functions and right away. */
|
|
bool record_ipa = m_ipa && !gimple_call_internal_p (call);
|
|
|
|
/* Handle all function parameters. */
|
|
for (unsigned i = 0;
|
|
i < gimple_call_num_args (call)
|
|
&& m_lattice[index].flags; i++)
|
|
/* Name is directly passed to the callee. */
|
|
if (gimple_call_arg (call, i) == name)
|
|
{
|
|
int call_flags = gimple_call_arg_flags (call, i);
|
|
if (!ignore_retval)
|
|
merge_call_lhs_flags
|
|
(call, i, name,
|
|
!(call_flags & (EAF_NOT_RETURNED_DIRECTLY
|
|
| EAF_UNUSED)),
|
|
!(call_flags & (EAF_NOT_RETURNED_INDIRECTLY
|
|
| EAF_UNUSED)));
|
|
if (!(ecf_flags & (ECF_CONST | ECF_NOVOPS)))
|
|
{
|
|
call_flags = callee_to_caller_flags
|
|
(call_flags, ignore_stores,
|
|
m_lattice[index]);
|
|
if (!record_ipa)
|
|
m_lattice[index].merge (call_flags);
|
|
else
|
|
m_lattice[index].add_escape_point (call, i,
|
|
call_flags, true);
|
|
}
|
|
}
|
|
/* Name is dereferenced and passed to a callee. */
|
|
else if (memory_access_to (gimple_call_arg (call, i), name))
|
|
{
|
|
int call_flags = deref_flags
|
|
(gimple_call_arg_flags (call, i), ignore_stores);
|
|
if (!ignore_retval && !(call_flags & EAF_UNUSED)
|
|
&& !(call_flags & EAF_NOT_RETURNED_DIRECTLY)
|
|
&& !(call_flags & EAF_NOT_RETURNED_INDIRECTLY))
|
|
merge_call_lhs_flags (call, i, name, false, true);
|
|
if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
m_lattice[index].merge_direct_load ();
|
|
else
|
|
{
|
|
call_flags = callee_to_caller_flags
|
|
(call_flags, ignore_stores,
|
|
m_lattice[index]);
|
|
if (!record_ipa)
|
|
m_lattice[index].merge (call_flags);
|
|
else
|
|
m_lattice[index].add_escape_point (call, i,
|
|
call_flags, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (gimple_assign_load_p (use_stmt))
|
|
{
|
|
gassign *assign = as_a <gassign *> (use_stmt);
|
|
/* Memory to memory copy. */
|
|
if (gimple_store_p (assign))
|
|
{
|
|
/* Handle *lhs = *name.
|
|
|
|
We do not track memory locations, so assume that value
|
|
is used arbitrarily. */
|
|
if (memory_access_to (gimple_assign_rhs1 (assign), name))
|
|
m_lattice[index].merge (deref_flags (0, false));
|
|
/* Handle *name = *exp. */
|
|
else if (memory_access_to (gimple_assign_lhs (assign), name))
|
|
m_lattice[index].merge_direct_store ();
|
|
}
|
|
/* Handle lhs = *name. */
|
|
else if (memory_access_to (gimple_assign_rhs1 (assign), name))
|
|
{
|
|
tree lhs = gimple_assign_lhs (assign);
|
|
merge_with_ssa_name (name, lhs, true);
|
|
}
|
|
}
|
|
else if (gimple_store_p (use_stmt))
|
|
{
|
|
gassign *assign = dyn_cast <gassign *> (use_stmt);
|
|
|
|
/* Handle *lhs = name. */
|
|
if (assign && gimple_assign_rhs1 (assign) == name)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "%*s ssa name saved to memory\n",
|
|
m_depth * 4, "");
|
|
m_lattice[index].merge (0);
|
|
}
|
|
/* Handle *name = exp. */
|
|
else if (assign
|
|
&& memory_access_to (gimple_assign_lhs (assign), name))
|
|
{
|
|
/* In general we can not ignore clobbers because they are
|
|
barriers for code motion, however after inlining it is safe to
|
|
do because local optimization passes do not consider clobbers
|
|
from other functions.
|
|
Similar logic is in ipa-pure-const.cc. */
|
|
if (!cfun->after_inlining || !gimple_clobber_p (assign))
|
|
m_lattice[index].merge_direct_store ();
|
|
}
|
|
/* ASM statements etc. */
|
|
else if (!assign)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "%*s Unhandled store\n", m_depth * 4, "");
|
|
m_lattice[index].merge (0);
|
|
}
|
|
}
|
|
else if (gassign *assign = dyn_cast <gassign *> (use_stmt))
|
|
{
|
|
enum tree_code code = gimple_assign_rhs_code (assign);
|
|
|
|
/* See if operation is a merge as considered by
|
|
tree-ssa-structalias.cc:find_func_aliases. */
|
|
if (!truth_value_p (code)
|
|
&& code != POINTER_DIFF_EXPR
|
|
&& (code != POINTER_PLUS_EXPR
|
|
|| gimple_assign_rhs1 (assign) == name))
|
|
{
|
|
tree lhs = gimple_assign_lhs (assign);
|
|
merge_with_ssa_name (name, lhs, false);
|
|
}
|
|
}
|
|
else if (gphi *phi = dyn_cast <gphi *> (use_stmt))
|
|
{
|
|
tree result = gimple_phi_result (phi);
|
|
merge_with_ssa_name (name, result, false);
|
|
}
|
|
/* Conditions are not considered escape points
|
|
by tree-ssa-structalias. */
|
|
else if (gimple_code (use_stmt) == GIMPLE_COND)
|
|
;
|
|
else
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "%*s Unhandled stmt\n", m_depth * 4, "");
|
|
m_lattice[index].merge (0);
|
|
}
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "%*s current flags of ", m_depth * 4, "");
|
|
print_generic_expr (dump_file, name);
|
|
m_lattice[index].dump (dump_file, m_depth * 4 + 4);
|
|
}
|
|
}
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "%*sflags of ssa name ", m_depth * 4, "");
|
|
print_generic_expr (dump_file, name);
|
|
m_lattice[index].dump (dump_file, m_depth * 4 + 2);
|
|
}
|
|
m_lattice[index].open = false;
|
|
if (!m_lattice[index].do_dataflow)
|
|
m_lattice[index].known = true;
|
|
}
|
|
|
|
/* Propagate info from SRC to DEST. If DEREF it true, assume that SRC
|
|
is dereferenced. */
|
|
|
|
void
|
|
modref_eaf_analysis::merge_with_ssa_name (tree dest, tree src, bool deref)
|
|
{
|
|
int index = SSA_NAME_VERSION (dest);
|
|
int src_index = SSA_NAME_VERSION (src);
|
|
|
|
/* Merging lattice with itself is a no-op. */
|
|
if (!deref && src == dest)
|
|
return;
|
|
|
|
m_depth++;
|
|
analyze_ssa_name (src);
|
|
m_depth--;
|
|
if (deref)
|
|
m_lattice[index].merge_deref (m_lattice[src_index], false);
|
|
else
|
|
m_lattice[index].merge (m_lattice[src_index]);
|
|
|
|
/* If we failed to produce final solution add an edge to the dataflow
|
|
graph. */
|
|
if (!m_lattice[src_index].known)
|
|
{
|
|
modref_lattice::propagate_edge e = {index, deref};
|
|
|
|
if (!m_lattice[src_index].propagate_to.length ())
|
|
m_names_to_propagate.safe_push (src_index);
|
|
m_lattice[src_index].propagate_to.safe_push (e);
|
|
m_lattice[src_index].changed = true;
|
|
m_lattice[src_index].do_dataflow = true;
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"%*sWill propgate from ssa_name %i to %i%s\n",
|
|
m_depth * 4 + 4,
|
|
"", src_index, index, deref ? " (deref)" : "");
|
|
}
|
|
}
|
|
|
|
/* In the case we deferred some SSA names, reprocess them. In the case some
|
|
dataflow edges were introduced, do the actual iterative dataflow. */
|
|
|
|
void
|
|
modref_eaf_analysis::propagate ()
|
|
{
|
|
int iterations = 0;
|
|
size_t i;
|
|
int index;
|
|
bool changed = true;
|
|
|
|
while (m_deferred_names.length ())
|
|
{
|
|
tree name = m_deferred_names.pop ();
|
|
if (dump_file)
|
|
fprintf (dump_file, "Analyzing deferred SSA name\n");
|
|
analyze_ssa_name (name, true);
|
|
}
|
|
|
|
if (!m_names_to_propagate.length ())
|
|
return;
|
|
if (dump_file)
|
|
fprintf (dump_file, "Propagating EAF flags\n");
|
|
|
|
/* Compute reverse postorder. */
|
|
auto_vec <int> rpo;
|
|
struct stack_entry
|
|
{
|
|
int name;
|
|
unsigned pos;
|
|
};
|
|
auto_vec <struct stack_entry> stack;
|
|
int pos = m_names_to_propagate.length () - 1;
|
|
|
|
rpo.safe_grow (m_names_to_propagate.length (), true);
|
|
stack.reserve_exact (m_names_to_propagate.length ());
|
|
|
|
/* We reuse known flag for RPO DFS walk bookkeeping. */
|
|
if (flag_checking)
|
|
FOR_EACH_VEC_ELT (m_names_to_propagate, i, index)
|
|
gcc_assert (!m_lattice[index].known && m_lattice[index].changed);
|
|
|
|
FOR_EACH_VEC_ELT (m_names_to_propagate, i, index)
|
|
{
|
|
if (!m_lattice[index].known)
|
|
{
|
|
stack_entry e = {index, 0};
|
|
|
|
stack.quick_push (e);
|
|
m_lattice[index].known = true;
|
|
}
|
|
while (stack.length ())
|
|
{
|
|
bool found = false;
|
|
int index1 = stack.last ().name;
|
|
|
|
while (stack.last ().pos < m_lattice[index1].propagate_to.length ())
|
|
{
|
|
int index2 = m_lattice[index1]
|
|
.propagate_to[stack.last ().pos].ssa_name;
|
|
|
|
stack.last ().pos++;
|
|
if (!m_lattice[index2].known
|
|
&& m_lattice[index2].propagate_to.length ())
|
|
{
|
|
stack_entry e = {index2, 0};
|
|
|
|
stack.quick_push (e);
|
|
m_lattice[index2].known = true;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found
|
|
&& stack.last ().pos == m_lattice[index1].propagate_to.length ())
|
|
{
|
|
rpo[pos--] = index1;
|
|
stack.pop ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Perform iterative dataflow. */
|
|
while (changed)
|
|
{
|
|
changed = false;
|
|
iterations++;
|
|
if (dump_file)
|
|
fprintf (dump_file, " iteration %i\n", iterations);
|
|
FOR_EACH_VEC_ELT (rpo, i, index)
|
|
{
|
|
if (m_lattice[index].changed)
|
|
{
|
|
size_t j;
|
|
|
|
m_lattice[index].changed = false;
|
|
if (dump_file)
|
|
fprintf (dump_file, " Visiting ssa name %i\n", index);
|
|
for (j = 0; j < m_lattice[index].propagate_to.length (); j++)
|
|
{
|
|
bool ch;
|
|
int target = m_lattice[index].propagate_to[j].ssa_name;
|
|
bool deref = m_lattice[index].propagate_to[j].deref;
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, " Propagating flags of ssa name"
|
|
" %i to %i%s\n",
|
|
index, target, deref ? " (deref)" : "");
|
|
m_lattice[target].known = true;
|
|
if (!m_lattice[index].propagate_to[j].deref)
|
|
ch = m_lattice[target].merge (m_lattice[index]);
|
|
else
|
|
ch = m_lattice[target].merge_deref (m_lattice[index],
|
|
false);
|
|
if (!ch)
|
|
continue;
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " New lattice: ");
|
|
m_lattice[target].dump (dump_file);
|
|
}
|
|
changed = true;
|
|
m_lattice[target].changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (dump_file)
|
|
fprintf (dump_file, "EAF flags propagated in %i iterations\n", iterations);
|
|
}
|
|
|
|
/* Record escape points of PARM_INDEX according to LATTICE. */
|
|
|
|
void
|
|
modref_eaf_analysis::record_escape_points (tree name, int parm_index, int flags)
|
|
{
|
|
modref_lattice &lattice = m_lattice[SSA_NAME_VERSION (name)];
|
|
|
|
if (lattice.escape_points.length ())
|
|
{
|
|
escape_point *ep;
|
|
unsigned int ip;
|
|
cgraph_node *node = cgraph_node::get (current_function_decl);
|
|
|
|
gcc_assert (m_ipa);
|
|
FOR_EACH_VEC_ELT (lattice.escape_points, ip, ep)
|
|
if ((ep->min_flags & flags) != flags)
|
|
{
|
|
cgraph_edge *e = node->get_edge (ep->call);
|
|
struct escape_entry ee = {parm_index, ep->arg,
|
|
ep->min_flags, ep->direct};
|
|
|
|
escape_summaries->get_create (e)->esc.safe_push (ee);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Determine EAF flags for function parameters
|
|
and fill in SUMMARY/SUMMARY_LTO. If IPA is true work in IPA mode
|
|
where we also collect escape points.
|
|
PAST_FLAGS, PAST_RETSLOT_FLAGS, PAST_STATIC_CHAIN_FLAGS can be
|
|
used to preserve flags from previous (IPA) run for cases where
|
|
late optimizations changed code in a way we can no longer analyze
|
|
it easily. */
|
|
|
|
static void
|
|
analyze_parms (modref_summary *summary, modref_summary_lto *summary_lto,
|
|
bool ipa, vec<eaf_flags_t> &past_flags,
|
|
int past_retslot_flags, int past_static_chain_flags)
|
|
{
|
|
unsigned int parm_index = 0;
|
|
unsigned int count = 0;
|
|
int ecf_flags = flags_from_decl_or_type (current_function_decl);
|
|
tree retslot = NULL;
|
|
tree static_chain = NULL;
|
|
|
|
/* If there is return slot, look up its SSA name. */
|
|
if (DECL_RESULT (current_function_decl)
|
|
&& DECL_BY_REFERENCE (DECL_RESULT (current_function_decl)))
|
|
retslot = ssa_default_def (cfun, DECL_RESULT (current_function_decl));
|
|
if (cfun->static_chain_decl)
|
|
static_chain = ssa_default_def (cfun, cfun->static_chain_decl);
|
|
|
|
for (tree parm = DECL_ARGUMENTS (current_function_decl); parm;
|
|
parm = TREE_CHAIN (parm))
|
|
count++;
|
|
|
|
if (!count && !retslot && !static_chain)
|
|
return;
|
|
|
|
modref_eaf_analysis eaf_analysis (ipa);
|
|
|
|
/* Determine all SSA names we need to know flags for. */
|
|
for (tree parm = DECL_ARGUMENTS (current_function_decl); parm;
|
|
parm = TREE_CHAIN (parm))
|
|
{
|
|
tree name = ssa_default_def (cfun, parm);
|
|
if (name)
|
|
eaf_analysis.analyze_ssa_name (name);
|
|
}
|
|
if (retslot)
|
|
eaf_analysis.analyze_ssa_name (retslot);
|
|
if (static_chain)
|
|
eaf_analysis.analyze_ssa_name (static_chain);
|
|
|
|
/* Do the dataflow. */
|
|
eaf_analysis.propagate ();
|
|
|
|
tree attr = lookup_attribute ("fn spec",
|
|
TYPE_ATTRIBUTES
|
|
(TREE_TYPE (current_function_decl)));
|
|
attr_fnspec fnspec (attr
|
|
? TREE_STRING_POINTER (TREE_VALUE (TREE_VALUE (attr)))
|
|
: "");
|
|
|
|
|
|
/* Store results to summaries. */
|
|
for (tree parm = DECL_ARGUMENTS (current_function_decl); parm; parm_index++,
|
|
parm = TREE_CHAIN (parm))
|
|
{
|
|
tree name = ssa_default_def (cfun, parm);
|
|
if (!name || has_zero_uses (name))
|
|
{
|
|
/* We do not track non-SSA parameters,
|
|
but we want to track unused gimple_regs. */
|
|
if (!is_gimple_reg (parm))
|
|
continue;
|
|
if (summary)
|
|
{
|
|
if (parm_index >= summary->arg_flags.length ())
|
|
summary->arg_flags.safe_grow_cleared (count, true);
|
|
summary->arg_flags[parm_index] = EAF_UNUSED;
|
|
}
|
|
else if (summary_lto)
|
|
{
|
|
if (parm_index >= summary_lto->arg_flags.length ())
|
|
summary_lto->arg_flags.safe_grow_cleared (count, true);
|
|
summary_lto->arg_flags[parm_index] = EAF_UNUSED;
|
|
}
|
|
continue;
|
|
}
|
|
int flags = eaf_analysis.get_ssa_name_flags (name);
|
|
int attr_flags = fnspec.arg_eaf_flags (parm_index);
|
|
|
|
if (dump_file && (flags | attr_flags) != flags && !(flags & EAF_UNUSED))
|
|
{
|
|
fprintf (dump_file,
|
|
" Flags for param %i combined with fnspec flags:",
|
|
(int)parm_index);
|
|
dump_eaf_flags (dump_file, attr_flags, false);
|
|
fprintf (dump_file, " determined: ");
|
|
dump_eaf_flags (dump_file, flags, true);
|
|
}
|
|
flags |= attr_flags;
|
|
|
|
/* Eliminate useless flags so we do not end up storing unnecessary
|
|
summaries. */
|
|
|
|
flags = remove_useless_eaf_flags
|
|
(flags, ecf_flags,
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))));
|
|
if (past_flags.length () > parm_index)
|
|
{
|
|
int past = past_flags[parm_index];
|
|
past = remove_useless_eaf_flags
|
|
(past, ecf_flags,
|
|
VOID_TYPE_P (TREE_TYPE
|
|
(TREE_TYPE (current_function_decl))));
|
|
if (dump_file && (flags | past) != flags && !(flags & EAF_UNUSED))
|
|
{
|
|
fprintf (dump_file,
|
|
" Flags for param %i combined with IPA pass:",
|
|
(int)parm_index);
|
|
dump_eaf_flags (dump_file, past, false);
|
|
fprintf (dump_file, " determined: ");
|
|
dump_eaf_flags (dump_file, flags, true);
|
|
}
|
|
if (!(flags & EAF_UNUSED))
|
|
flags |= past;
|
|
}
|
|
|
|
if (flags)
|
|
{
|
|
if (summary)
|
|
{
|
|
if (parm_index >= summary->arg_flags.length ())
|
|
summary->arg_flags.safe_grow_cleared (count, true);
|
|
summary->arg_flags[parm_index] = flags;
|
|
}
|
|
else if (summary_lto)
|
|
{
|
|
if (parm_index >= summary_lto->arg_flags.length ())
|
|
summary_lto->arg_flags.safe_grow_cleared (count, true);
|
|
summary_lto->arg_flags[parm_index] = flags;
|
|
}
|
|
eaf_analysis.record_escape_points (name, parm_index, flags);
|
|
}
|
|
}
|
|
if (retslot)
|
|
{
|
|
int flags = eaf_analysis.get_ssa_name_flags (retslot);
|
|
int past = past_retslot_flags;
|
|
|
|
flags = remove_useless_eaf_flags (flags, ecf_flags, false);
|
|
past = remove_useless_eaf_flags
|
|
(past, ecf_flags,
|
|
VOID_TYPE_P (TREE_TYPE
|
|
(TREE_TYPE (current_function_decl))));
|
|
if (dump_file && (flags | past) != flags && !(flags & EAF_UNUSED))
|
|
{
|
|
fprintf (dump_file,
|
|
" Retslot flags combined with IPA pass:");
|
|
dump_eaf_flags (dump_file, past, false);
|
|
fprintf (dump_file, " determined: ");
|
|
dump_eaf_flags (dump_file, flags, true);
|
|
}
|
|
if (!(flags & EAF_UNUSED))
|
|
flags |= past;
|
|
if (flags)
|
|
{
|
|
if (summary)
|
|
summary->retslot_flags = flags;
|
|
if (summary_lto)
|
|
summary_lto->retslot_flags = flags;
|
|
eaf_analysis.record_escape_points (retslot,
|
|
MODREF_RETSLOT_PARM, flags);
|
|
}
|
|
}
|
|
if (static_chain)
|
|
{
|
|
int flags = eaf_analysis.get_ssa_name_flags (static_chain);
|
|
int past = past_static_chain_flags;
|
|
|
|
flags = remove_useless_eaf_flags (flags, ecf_flags, false);
|
|
past = remove_useless_eaf_flags
|
|
(past, ecf_flags,
|
|
VOID_TYPE_P (TREE_TYPE
|
|
(TREE_TYPE (current_function_decl))));
|
|
if (dump_file && (flags | past) != flags && !(flags & EAF_UNUSED))
|
|
{
|
|
fprintf (dump_file,
|
|
" Static chain flags combined with IPA pass:");
|
|
dump_eaf_flags (dump_file, past, false);
|
|
fprintf (dump_file, " determined: ");
|
|
dump_eaf_flags (dump_file, flags, true);
|
|
}
|
|
if (!(flags & EAF_UNUSED))
|
|
flags |= past;
|
|
if (flags)
|
|
{
|
|
if (summary)
|
|
summary->static_chain_flags = flags;
|
|
if (summary_lto)
|
|
summary_lto->static_chain_flags = flags;
|
|
eaf_analysis.record_escape_points (static_chain,
|
|
MODREF_STATIC_CHAIN_PARM,
|
|
flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Analyze function. IPA indicates whether we're running in local mode
|
|
(false) or the IPA mode (true).
|
|
Return true if fixup cfg is needed after the pass. */
|
|
|
|
static bool
|
|
analyze_function (bool ipa)
|
|
{
|
|
bool fixup_cfg = false;
|
|
if (dump_file)
|
|
fprintf (dump_file, "\n\nmodref analyzing '%s' (ipa=%i)%s%s\n",
|
|
cgraph_node::get (current_function_decl)->dump_name (), ipa,
|
|
TREE_READONLY (current_function_decl) ? " (const)" : "",
|
|
DECL_PURE_P (current_function_decl) ? " (pure)" : "");
|
|
|
|
/* Don't analyze this function if it's compiled with -fno-strict-aliasing. */
|
|
if (!flag_ipa_modref
|
|
|| lookup_attribute ("noipa", DECL_ATTRIBUTES (current_function_decl)))
|
|
return false;
|
|
|
|
/* Compute no-LTO summaries when local optimization is going to happen. */
|
|
bool nolto = (!ipa || ((!flag_lto || flag_fat_lto_objects) && !in_lto_p)
|
|
|| (in_lto_p && !flag_wpa
|
|
&& flag_incremental_link != INCREMENTAL_LINK_LTO));
|
|
/* Compute LTO when LTO streaming is going to happen. */
|
|
bool lto = ipa && ((flag_lto && !in_lto_p)
|
|
|| flag_wpa
|
|
|| flag_incremental_link == INCREMENTAL_LINK_LTO);
|
|
cgraph_node *fnode = cgraph_node::get (current_function_decl);
|
|
|
|
modref_summary *summary = NULL;
|
|
modref_summary_lto *summary_lto = NULL;
|
|
|
|
bool past_flags_known = false;
|
|
auto_vec <eaf_flags_t> past_flags;
|
|
int past_retslot_flags = 0;
|
|
int past_static_chain_flags = 0;
|
|
|
|
/* Initialize the summary.
|
|
If we run in local mode there is possibly pre-existing summary from
|
|
IPA pass. Dump it so it is easy to compare if mod-ref info has
|
|
improved. */
|
|
if (!ipa)
|
|
{
|
|
if (!optimization_summaries)
|
|
optimization_summaries = modref_summaries::create_ggc (symtab);
|
|
else /* Remove existing summary if we are re-running the pass. */
|
|
{
|
|
summary = optimization_summaries->get (fnode);
|
|
if (summary != NULL
|
|
&& summary->loads)
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Past summary:\n");
|
|
optimization_summaries->get (fnode)->dump (dump_file);
|
|
}
|
|
past_flags.reserve_exact (summary->arg_flags.length ());
|
|
past_flags.splice (summary->arg_flags);
|
|
past_retslot_flags = summary->retslot_flags;
|
|
past_static_chain_flags = summary->static_chain_flags;
|
|
past_flags_known = true;
|
|
}
|
|
optimization_summaries->remove (fnode);
|
|
}
|
|
summary = optimization_summaries->get_create (fnode);
|
|
gcc_checking_assert (nolto && !lto);
|
|
}
|
|
/* In IPA mode we analyze every function precisely once. Assert that. */
|
|
else
|
|
{
|
|
if (nolto)
|
|
{
|
|
if (!summaries)
|
|
summaries = modref_summaries::create_ggc (symtab);
|
|
else
|
|
summaries->remove (fnode);
|
|
summary = summaries->get_create (fnode);
|
|
}
|
|
if (lto)
|
|
{
|
|
if (!summaries_lto)
|
|
summaries_lto = modref_summaries_lto::create_ggc (symtab);
|
|
else
|
|
summaries_lto->remove (fnode);
|
|
summary_lto = summaries_lto->get_create (fnode);
|
|
}
|
|
if (!fnspec_summaries)
|
|
fnspec_summaries = new fnspec_summaries_t (symtab);
|
|
if (!escape_summaries)
|
|
escape_summaries = new escape_summaries_t (symtab);
|
|
}
|
|
|
|
|
|
/* Create and initialize summary for F.
|
|
Note that summaries may be already allocated from previous
|
|
run of the pass. */
|
|
if (nolto)
|
|
{
|
|
gcc_assert (!summary->loads);
|
|
summary->loads = modref_records::create_ggc ();
|
|
gcc_assert (!summary->stores);
|
|
summary->stores = modref_records::create_ggc ();
|
|
summary->writes_errno = false;
|
|
summary->side_effects = false;
|
|
summary->nondeterministic = false;
|
|
summary->calls_interposable = false;
|
|
}
|
|
if (lto)
|
|
{
|
|
gcc_assert (!summary_lto->loads);
|
|
summary_lto->loads = modref_records_lto::create_ggc ();
|
|
gcc_assert (!summary_lto->stores);
|
|
summary_lto->stores = modref_records_lto::create_ggc ();
|
|
summary_lto->writes_errno = false;
|
|
summary_lto->side_effects = false;
|
|
summary_lto->nondeterministic = false;
|
|
summary_lto->calls_interposable = false;
|
|
}
|
|
|
|
analyze_parms (summary, summary_lto, ipa,
|
|
past_flags, past_retslot_flags, past_static_chain_flags);
|
|
|
|
{
|
|
modref_access_analysis analyzer (ipa, summary, summary_lto);
|
|
analyzer.analyze ();
|
|
}
|
|
|
|
if (!ipa && flag_ipa_pure_const)
|
|
{
|
|
if (!summary->stores->every_base && !summary->stores->bases
|
|
&& !summary->nondeterministic)
|
|
{
|
|
if (!summary->loads->every_base && !summary->loads->bases
|
|
&& !summary->calls_interposable)
|
|
fixup_cfg = ipa_make_function_const (fnode,
|
|
summary->side_effects, true);
|
|
else
|
|
fixup_cfg = ipa_make_function_pure (fnode,
|
|
summary->side_effects, true);
|
|
}
|
|
}
|
|
int ecf_flags = flags_from_decl_or_type (current_function_decl);
|
|
if (summary && !summary->useful_p (ecf_flags))
|
|
{
|
|
if (!ipa)
|
|
optimization_summaries->remove (fnode);
|
|
else
|
|
summaries->remove (fnode);
|
|
summary = NULL;
|
|
}
|
|
if (summary)
|
|
summary->finalize (current_function_decl);
|
|
if (summary_lto && !summary_lto->useful_p (ecf_flags))
|
|
{
|
|
summaries_lto->remove (fnode);
|
|
summary_lto = NULL;
|
|
}
|
|
|
|
if (ipa && !summary && !summary_lto)
|
|
remove_modref_edge_summaries (fnode);
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " - modref done with result: tracked.\n");
|
|
if (summary)
|
|
summary->dump (dump_file);
|
|
if (summary_lto)
|
|
summary_lto->dump (dump_file);
|
|
dump_modref_edge_summaries (dump_file, fnode, 2);
|
|
/* To simplify debugging, compare IPA and local solutions. */
|
|
if (past_flags_known && summary)
|
|
{
|
|
size_t len = summary->arg_flags.length ();
|
|
|
|
if (past_flags.length () > len)
|
|
len = past_flags.length ();
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
int old_flags = i < past_flags.length () ? past_flags[i] : 0;
|
|
int new_flags = i < summary->arg_flags.length ()
|
|
? summary->arg_flags[i] : 0;
|
|
old_flags = remove_useless_eaf_flags
|
|
(old_flags, flags_from_decl_or_type (current_function_decl),
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))));
|
|
if (old_flags != new_flags)
|
|
{
|
|
if ((old_flags & ~new_flags) == 0
|
|
|| (new_flags & EAF_UNUSED))
|
|
fprintf (dump_file, " Flags for param %i improved:",
|
|
(int)i);
|
|
else
|
|
gcc_unreachable ();
|
|
dump_eaf_flags (dump_file, old_flags, false);
|
|
fprintf (dump_file, " -> ");
|
|
dump_eaf_flags (dump_file, new_flags, true);
|
|
}
|
|
}
|
|
past_retslot_flags = remove_useless_eaf_flags
|
|
(past_retslot_flags,
|
|
flags_from_decl_or_type (current_function_decl),
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))));
|
|
if (past_retslot_flags != summary->retslot_flags)
|
|
{
|
|
if ((past_retslot_flags & ~summary->retslot_flags) == 0
|
|
|| (summary->retslot_flags & EAF_UNUSED))
|
|
fprintf (dump_file, " Flags for retslot improved:");
|
|
else
|
|
gcc_unreachable ();
|
|
dump_eaf_flags (dump_file, past_retslot_flags, false);
|
|
fprintf (dump_file, " -> ");
|
|
dump_eaf_flags (dump_file, summary->retslot_flags, true);
|
|
}
|
|
past_static_chain_flags = remove_useless_eaf_flags
|
|
(past_static_chain_flags,
|
|
flags_from_decl_or_type (current_function_decl),
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))));
|
|
if (past_static_chain_flags != summary->static_chain_flags)
|
|
{
|
|
if ((past_static_chain_flags & ~summary->static_chain_flags) == 0
|
|
|| (summary->static_chain_flags & EAF_UNUSED))
|
|
fprintf (dump_file, " Flags for static chain improved:");
|
|
else
|
|
gcc_unreachable ();
|
|
dump_eaf_flags (dump_file, past_static_chain_flags, false);
|
|
fprintf (dump_file, " -> ");
|
|
dump_eaf_flags (dump_file, summary->static_chain_flags, true);
|
|
}
|
|
}
|
|
else if (past_flags_known && !summary)
|
|
{
|
|
for (size_t i = 0; i < past_flags.length (); i++)
|
|
{
|
|
int old_flags = past_flags[i];
|
|
old_flags = remove_useless_eaf_flags
|
|
(old_flags, flags_from_decl_or_type (current_function_decl),
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))));
|
|
if (old_flags)
|
|
{
|
|
fprintf (dump_file, " Flags for param %i worsened:",
|
|
(int)i);
|
|
dump_eaf_flags (dump_file, old_flags, false);
|
|
fprintf (dump_file, " -> \n");
|
|
}
|
|
}
|
|
past_retslot_flags = remove_useless_eaf_flags
|
|
(past_retslot_flags,
|
|
flags_from_decl_or_type (current_function_decl),
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))));
|
|
if (past_retslot_flags)
|
|
{
|
|
fprintf (dump_file, " Flags for retslot worsened:");
|
|
dump_eaf_flags (dump_file, past_retslot_flags, false);
|
|
fprintf (dump_file, " ->\n");
|
|
}
|
|
past_static_chain_flags = remove_useless_eaf_flags
|
|
(past_static_chain_flags,
|
|
flags_from_decl_or_type (current_function_decl),
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl))));
|
|
if (past_static_chain_flags)
|
|
{
|
|
fprintf (dump_file, " Flags for static chain worsened:");
|
|
dump_eaf_flags (dump_file, past_static_chain_flags, false);
|
|
fprintf (dump_file, " ->\n");
|
|
}
|
|
}
|
|
}
|
|
return fixup_cfg;
|
|
}
|
|
|
|
/* Callback for generate_summary. */
|
|
|
|
static void
|
|
modref_generate (void)
|
|
{
|
|
struct cgraph_node *node;
|
|
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
|
|
{
|
|
function *f = DECL_STRUCT_FUNCTION (node->decl);
|
|
if (!f)
|
|
continue;
|
|
push_cfun (f);
|
|
analyze_function (true);
|
|
pop_cfun ();
|
|
}
|
|
}
|
|
|
|
} /* ANON namespace. */
|
|
|
|
/* Debugging helper. */
|
|
|
|
void
|
|
debug_eaf_flags (int flags)
|
|
{
|
|
dump_eaf_flags (stderr, flags, true);
|
|
}
|
|
|
|
/* Called when a new function is inserted to callgraph late. */
|
|
|
|
void
|
|
modref_summaries::insert (struct cgraph_node *node, modref_summary *)
|
|
{
|
|
/* Local passes ought to be executed by the pass manager. */
|
|
if (this == optimization_summaries)
|
|
{
|
|
optimization_summaries->remove (node);
|
|
return;
|
|
}
|
|
if (!DECL_STRUCT_FUNCTION (node->decl)
|
|
|| !opt_for_fn (node->decl, flag_ipa_modref))
|
|
{
|
|
summaries->remove (node);
|
|
return;
|
|
}
|
|
push_cfun (DECL_STRUCT_FUNCTION (node->decl));
|
|
analyze_function (true);
|
|
pop_cfun ();
|
|
}
|
|
|
|
/* Called when a new function is inserted to callgraph late. */
|
|
|
|
void
|
|
modref_summaries_lto::insert (struct cgraph_node *node, modref_summary_lto *)
|
|
{
|
|
/* We do not support adding new function when IPA information is already
|
|
propagated. This is done only by SIMD cloning that is not very
|
|
critical. */
|
|
if (!DECL_STRUCT_FUNCTION (node->decl)
|
|
|| !opt_for_fn (node->decl, flag_ipa_modref)
|
|
|| propagated)
|
|
{
|
|
summaries_lto->remove (node);
|
|
return;
|
|
}
|
|
push_cfun (DECL_STRUCT_FUNCTION (node->decl));
|
|
analyze_function (true);
|
|
pop_cfun ();
|
|
}
|
|
|
|
/* Called when new clone is inserted to callgraph late. */
|
|
|
|
void
|
|
modref_summaries::duplicate (cgraph_node *, cgraph_node *dst,
|
|
modref_summary *src_data,
|
|
modref_summary *dst_data)
|
|
{
|
|
/* Do not duplicate optimization summaries; we do not handle parameter
|
|
transforms on them. */
|
|
if (this == optimization_summaries)
|
|
{
|
|
optimization_summaries->remove (dst);
|
|
return;
|
|
}
|
|
dst_data->stores = modref_records::create_ggc ();
|
|
dst_data->stores->copy_from (src_data->stores);
|
|
dst_data->loads = modref_records::create_ggc ();
|
|
dst_data->loads->copy_from (src_data->loads);
|
|
dst_data->kills.reserve_exact (src_data->kills.length ());
|
|
dst_data->kills.splice (src_data->kills);
|
|
dst_data->writes_errno = src_data->writes_errno;
|
|
dst_data->side_effects = src_data->side_effects;
|
|
dst_data->nondeterministic = src_data->nondeterministic;
|
|
dst_data->calls_interposable = src_data->calls_interposable;
|
|
if (src_data->arg_flags.length ())
|
|
dst_data->arg_flags = src_data->arg_flags.copy ();
|
|
dst_data->retslot_flags = src_data->retslot_flags;
|
|
dst_data->static_chain_flags = src_data->static_chain_flags;
|
|
}
|
|
|
|
/* Called when new clone is inserted to callgraph late. */
|
|
|
|
void
|
|
modref_summaries_lto::duplicate (cgraph_node *, cgraph_node *,
|
|
modref_summary_lto *src_data,
|
|
modref_summary_lto *dst_data)
|
|
{
|
|
/* Be sure that no further cloning happens after ipa-modref. If it does
|
|
we will need to update signatures for possible param changes. */
|
|
gcc_checking_assert (!((modref_summaries_lto *)summaries_lto)->propagated);
|
|
dst_data->stores = modref_records_lto::create_ggc ();
|
|
dst_data->stores->copy_from (src_data->stores);
|
|
dst_data->loads = modref_records_lto::create_ggc ();
|
|
dst_data->loads->copy_from (src_data->loads);
|
|
dst_data->kills.reserve_exact (src_data->kills.length ());
|
|
dst_data->kills.splice (src_data->kills);
|
|
dst_data->writes_errno = src_data->writes_errno;
|
|
dst_data->side_effects = src_data->side_effects;
|
|
dst_data->nondeterministic = src_data->nondeterministic;
|
|
dst_data->calls_interposable = src_data->calls_interposable;
|
|
if (src_data->arg_flags.length ())
|
|
dst_data->arg_flags = src_data->arg_flags.copy ();
|
|
dst_data->retslot_flags = src_data->retslot_flags;
|
|
dst_data->static_chain_flags = src_data->static_chain_flags;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/* Definition of the modref pass on GIMPLE. */
|
|
const pass_data pass_data_modref = {
|
|
GIMPLE_PASS,
|
|
"modref",
|
|
OPTGROUP_IPA,
|
|
TV_TREE_MODREF,
|
|
(PROP_cfg | PROP_ssa),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
};
|
|
|
|
class pass_modref : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_modref (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_modref, ctxt) {}
|
|
|
|
/* opt_pass methods: */
|
|
opt_pass *clone ()
|
|
{
|
|
return new pass_modref (m_ctxt);
|
|
}
|
|
virtual bool gate (function *)
|
|
{
|
|
return flag_ipa_modref;
|
|
}
|
|
virtual unsigned int execute (function *);
|
|
};
|
|
|
|
/* Encode TT to the output block OB using the summary streaming API. */
|
|
|
|
static void
|
|
write_modref_records (modref_records_lto *tt, struct output_block *ob)
|
|
{
|
|
streamer_write_uhwi (ob, tt->every_base);
|
|
streamer_write_uhwi (ob, vec_safe_length (tt->bases));
|
|
for (auto base_node : tt->bases)
|
|
{
|
|
stream_write_tree (ob, base_node->base, true);
|
|
|
|
streamer_write_uhwi (ob, base_node->every_ref);
|
|
streamer_write_uhwi (ob, vec_safe_length (base_node->refs));
|
|
|
|
for (auto ref_node : base_node->refs)
|
|
{
|
|
stream_write_tree (ob, ref_node->ref, true);
|
|
streamer_write_uhwi (ob, ref_node->every_access);
|
|
streamer_write_uhwi (ob, vec_safe_length (ref_node->accesses));
|
|
|
|
for (auto access_node : ref_node->accesses)
|
|
access_node.stream_out (ob);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Read a modref_tree from the input block IB using the data from DATA_IN.
|
|
This assumes that the tree was encoded using write_modref_tree.
|
|
Either nolto_ret or lto_ret is initialized by the tree depending whether
|
|
LTO streaming is expected or not. */
|
|
|
|
static void
|
|
read_modref_records (tree decl,
|
|
lto_input_block *ib, struct data_in *data_in,
|
|
modref_records **nolto_ret,
|
|
modref_records_lto **lto_ret)
|
|
{
|
|
size_t max_bases = opt_for_fn (decl, param_modref_max_bases);
|
|
size_t max_refs = opt_for_fn (decl, param_modref_max_refs);
|
|
size_t max_accesses = opt_for_fn (decl, param_modref_max_accesses);
|
|
|
|
if (lto_ret)
|
|
*lto_ret = modref_records_lto::create_ggc ();
|
|
if (nolto_ret)
|
|
*nolto_ret = modref_records::create_ggc ();
|
|
gcc_checking_assert (lto_ret || nolto_ret);
|
|
|
|
size_t every_base = streamer_read_uhwi (ib);
|
|
size_t nbase = streamer_read_uhwi (ib);
|
|
|
|
gcc_assert (!every_base || nbase == 0);
|
|
if (every_base)
|
|
{
|
|
if (nolto_ret)
|
|
(*nolto_ret)->collapse ();
|
|
if (lto_ret)
|
|
(*lto_ret)->collapse ();
|
|
}
|
|
for (size_t i = 0; i < nbase; i++)
|
|
{
|
|
tree base_tree = stream_read_tree (ib, data_in);
|
|
modref_base_node <alias_set_type> *nolto_base_node = NULL;
|
|
modref_base_node <tree> *lto_base_node = NULL;
|
|
|
|
/* At stream in time we have LTO alias info. Check if we streamed in
|
|
something obviously unnecessary. Do not glob types by alias sets;
|
|
it is not 100% clear that ltrans types will get merged same way.
|
|
Types may get refined based on ODR type conflicts. */
|
|
if (base_tree && !get_alias_set (base_tree))
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Streamed in alias set 0 type ");
|
|
print_generic_expr (dump_file, base_tree);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
base_tree = NULL;
|
|
}
|
|
|
|
if (nolto_ret)
|
|
nolto_base_node = (*nolto_ret)->insert_base (base_tree
|
|
? get_alias_set (base_tree)
|
|
: 0, 0, INT_MAX);
|
|
if (lto_ret)
|
|
lto_base_node = (*lto_ret)->insert_base (base_tree, 0, max_bases);
|
|
size_t every_ref = streamer_read_uhwi (ib);
|
|
size_t nref = streamer_read_uhwi (ib);
|
|
|
|
gcc_assert (!every_ref || nref == 0);
|
|
if (every_ref)
|
|
{
|
|
if (nolto_base_node)
|
|
nolto_base_node->collapse ();
|
|
if (lto_base_node)
|
|
lto_base_node->collapse ();
|
|
}
|
|
for (size_t j = 0; j < nref; j++)
|
|
{
|
|
tree ref_tree = stream_read_tree (ib, data_in);
|
|
|
|
if (ref_tree && !get_alias_set (ref_tree))
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Streamed in alias set 0 type ");
|
|
print_generic_expr (dump_file, ref_tree);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
ref_tree = NULL;
|
|
}
|
|
|
|
modref_ref_node <alias_set_type> *nolto_ref_node = NULL;
|
|
modref_ref_node <tree> *lto_ref_node = NULL;
|
|
|
|
if (nolto_base_node)
|
|
nolto_ref_node
|
|
= nolto_base_node->insert_ref (ref_tree
|
|
? get_alias_set (ref_tree) : 0,
|
|
max_refs);
|
|
if (lto_base_node)
|
|
lto_ref_node = lto_base_node->insert_ref (ref_tree, max_refs);
|
|
|
|
size_t every_access = streamer_read_uhwi (ib);
|
|
size_t naccesses = streamer_read_uhwi (ib);
|
|
|
|
if (nolto_ref_node && every_access)
|
|
nolto_ref_node->collapse ();
|
|
if (lto_ref_node && every_access)
|
|
lto_ref_node->collapse ();
|
|
|
|
for (size_t k = 0; k < naccesses; k++)
|
|
{
|
|
modref_access_node a = modref_access_node::stream_in (ib);
|
|
if (nolto_ref_node)
|
|
nolto_ref_node->insert_access (a, max_accesses, false);
|
|
if (lto_ref_node)
|
|
lto_ref_node->insert_access (a, max_accesses, false);
|
|
}
|
|
}
|
|
}
|
|
if (lto_ret)
|
|
(*lto_ret)->cleanup ();
|
|
if (nolto_ret)
|
|
(*nolto_ret)->cleanup ();
|
|
}
|
|
|
|
/* Write ESUM to BP. */
|
|
|
|
static void
|
|
modref_write_escape_summary (struct bitpack_d *bp, escape_summary *esum)
|
|
{
|
|
if (!esum)
|
|
{
|
|
bp_pack_var_len_unsigned (bp, 0);
|
|
return;
|
|
}
|
|
bp_pack_var_len_unsigned (bp, esum->esc.length ());
|
|
unsigned int i;
|
|
escape_entry *ee;
|
|
FOR_EACH_VEC_ELT (esum->esc, i, ee)
|
|
{
|
|
bp_pack_var_len_int (bp, ee->parm_index);
|
|
bp_pack_var_len_unsigned (bp, ee->arg);
|
|
bp_pack_var_len_unsigned (bp, ee->min_flags);
|
|
bp_pack_value (bp, ee->direct, 1);
|
|
}
|
|
}
|
|
|
|
/* Read escape summary for E from BP. */
|
|
|
|
static void
|
|
modref_read_escape_summary (struct bitpack_d *bp, cgraph_edge *e)
|
|
{
|
|
unsigned int n = bp_unpack_var_len_unsigned (bp);
|
|
if (!n)
|
|
return;
|
|
escape_summary *esum = escape_summaries->get_create (e);
|
|
esum->esc.reserve_exact (n);
|
|
for (unsigned int i = 0; i < n; i++)
|
|
{
|
|
escape_entry ee;
|
|
ee.parm_index = bp_unpack_var_len_int (bp);
|
|
ee.arg = bp_unpack_var_len_unsigned (bp);
|
|
ee.min_flags = bp_unpack_var_len_unsigned (bp);
|
|
ee.direct = bp_unpack_value (bp, 1);
|
|
esum->esc.quick_push (ee);
|
|
}
|
|
}
|
|
|
|
/* Callback for write_summary. */
|
|
|
|
static void
|
|
modref_write ()
|
|
{
|
|
struct output_block *ob = create_output_block (LTO_section_ipa_modref);
|
|
lto_symtab_encoder_t encoder = ob->decl_state->symtab_node_encoder;
|
|
unsigned int count = 0;
|
|
int i;
|
|
|
|
if (!summaries_lto)
|
|
{
|
|
streamer_write_uhwi (ob, 0);
|
|
streamer_write_char_stream (ob->main_stream, 0);
|
|
produce_asm (ob, NULL);
|
|
destroy_output_block (ob);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < lto_symtab_encoder_size (encoder); i++)
|
|
{
|
|
symtab_node *snode = lto_symtab_encoder_deref (encoder, i);
|
|
cgraph_node *cnode = dyn_cast <cgraph_node *> (snode);
|
|
modref_summary_lto *r;
|
|
|
|
if (cnode && cnode->definition && !cnode->alias
|
|
&& (r = summaries_lto->get (cnode))
|
|
&& r->useful_p (flags_from_decl_or_type (cnode->decl)))
|
|
count++;
|
|
}
|
|
streamer_write_uhwi (ob, count);
|
|
|
|
for (i = 0; i < lto_symtab_encoder_size (encoder); i++)
|
|
{
|
|
symtab_node *snode = lto_symtab_encoder_deref (encoder, i);
|
|
cgraph_node *cnode = dyn_cast <cgraph_node *> (snode);
|
|
|
|
if (cnode && cnode->definition && !cnode->alias)
|
|
{
|
|
modref_summary_lto *r = summaries_lto->get (cnode);
|
|
|
|
if (!r || !r->useful_p (flags_from_decl_or_type (cnode->decl)))
|
|
continue;
|
|
|
|
streamer_write_uhwi (ob, lto_symtab_encoder_encode (encoder, cnode));
|
|
|
|
streamer_write_uhwi (ob, r->arg_flags.length ());
|
|
for (unsigned int i = 0; i < r->arg_flags.length (); i++)
|
|
streamer_write_uhwi (ob, r->arg_flags[i]);
|
|
streamer_write_uhwi (ob, r->retslot_flags);
|
|
streamer_write_uhwi (ob, r->static_chain_flags);
|
|
|
|
write_modref_records (r->loads, ob);
|
|
write_modref_records (r->stores, ob);
|
|
streamer_write_uhwi (ob, r->kills.length ());
|
|
for (auto kill : r->kills)
|
|
kill.stream_out (ob);
|
|
|
|
struct bitpack_d bp = bitpack_create (ob->main_stream);
|
|
bp_pack_value (&bp, r->writes_errno, 1);
|
|
bp_pack_value (&bp, r->side_effects, 1);
|
|
bp_pack_value (&bp, r->nondeterministic, 1);
|
|
bp_pack_value (&bp, r->calls_interposable, 1);
|
|
if (!flag_wpa)
|
|
{
|
|
for (cgraph_edge *e = cnode->indirect_calls;
|
|
e; e = e->next_callee)
|
|
{
|
|
class fnspec_summary *sum = fnspec_summaries->get (e);
|
|
bp_pack_value (&bp, sum != NULL, 1);
|
|
if (sum)
|
|
bp_pack_string (ob, &bp, sum->fnspec, true);
|
|
class escape_summary *esum = escape_summaries->get (e);
|
|
modref_write_escape_summary (&bp,esum);
|
|
}
|
|
for (cgraph_edge *e = cnode->callees; e; e = e->next_callee)
|
|
{
|
|
class fnspec_summary *sum = fnspec_summaries->get (e);
|
|
bp_pack_value (&bp, sum != NULL, 1);
|
|
if (sum)
|
|
bp_pack_string (ob, &bp, sum->fnspec, true);
|
|
class escape_summary *esum = escape_summaries->get (e);
|
|
modref_write_escape_summary (&bp,esum);
|
|
}
|
|
}
|
|
streamer_write_bitpack (&bp);
|
|
}
|
|
}
|
|
streamer_write_char_stream (ob->main_stream, 0);
|
|
produce_asm (ob, NULL);
|
|
destroy_output_block (ob);
|
|
}
|
|
|
|
static void
|
|
read_section (struct lto_file_decl_data *file_data, const char *data,
|
|
size_t len)
|
|
{
|
|
const struct lto_function_header *header
|
|
= (const struct lto_function_header *) data;
|
|
const int cfg_offset = sizeof (struct lto_function_header);
|
|
const int main_offset = cfg_offset + header->cfg_size;
|
|
const int string_offset = main_offset + header->main_size;
|
|
struct data_in *data_in;
|
|
unsigned int i;
|
|
unsigned int f_count;
|
|
|
|
lto_input_block ib ((const char *) data + main_offset, header->main_size,
|
|
file_data->mode_table);
|
|
|
|
data_in
|
|
= lto_data_in_create (file_data, (const char *) data + string_offset,
|
|
header->string_size, vNULL);
|
|
f_count = streamer_read_uhwi (&ib);
|
|
for (i = 0; i < f_count; i++)
|
|
{
|
|
struct cgraph_node *node;
|
|
lto_symtab_encoder_t encoder;
|
|
|
|
unsigned int index = streamer_read_uhwi (&ib);
|
|
encoder = file_data->symtab_node_encoder;
|
|
node = dyn_cast <cgraph_node *> (lto_symtab_encoder_deref (encoder,
|
|
index));
|
|
|
|
modref_summary *modref_sum = summaries
|
|
? summaries->get_create (node) : NULL;
|
|
modref_summary_lto *modref_sum_lto = summaries_lto
|
|
? summaries_lto->get_create (node)
|
|
: NULL;
|
|
if (optimization_summaries)
|
|
modref_sum = optimization_summaries->get_create (node);
|
|
|
|
if (modref_sum)
|
|
{
|
|
modref_sum->writes_errno = false;
|
|
modref_sum->side_effects = false;
|
|
modref_sum->nondeterministic = false;
|
|
modref_sum->calls_interposable = false;
|
|
}
|
|
if (modref_sum_lto)
|
|
{
|
|
modref_sum_lto->writes_errno = false;
|
|
modref_sum_lto->side_effects = false;
|
|
modref_sum_lto->nondeterministic = false;
|
|
modref_sum_lto->calls_interposable = false;
|
|
}
|
|
|
|
gcc_assert (!modref_sum || (!modref_sum->loads
|
|
&& !modref_sum->stores));
|
|
gcc_assert (!modref_sum_lto || (!modref_sum_lto->loads
|
|
&& !modref_sum_lto->stores));
|
|
unsigned int args = streamer_read_uhwi (&ib);
|
|
if (args && modref_sum)
|
|
modref_sum->arg_flags.reserve_exact (args);
|
|
if (args && modref_sum_lto)
|
|
modref_sum_lto->arg_flags.reserve_exact (args);
|
|
for (unsigned int i = 0; i < args; i++)
|
|
{
|
|
eaf_flags_t flags = streamer_read_uhwi (&ib);
|
|
if (modref_sum)
|
|
modref_sum->arg_flags.quick_push (flags);
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->arg_flags.quick_push (flags);
|
|
}
|
|
eaf_flags_t flags = streamer_read_uhwi (&ib);
|
|
if (modref_sum)
|
|
modref_sum->retslot_flags = flags;
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->retslot_flags = flags;
|
|
|
|
flags = streamer_read_uhwi (&ib);
|
|
if (modref_sum)
|
|
modref_sum->static_chain_flags = flags;
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->static_chain_flags = flags;
|
|
|
|
read_modref_records (node->decl, &ib, data_in,
|
|
modref_sum ? &modref_sum->loads : NULL,
|
|
modref_sum_lto ? &modref_sum_lto->loads : NULL);
|
|
read_modref_records (node->decl, &ib, data_in,
|
|
modref_sum ? &modref_sum->stores : NULL,
|
|
modref_sum_lto ? &modref_sum_lto->stores : NULL);
|
|
int j = streamer_read_uhwi (&ib);
|
|
if (j && modref_sum)
|
|
modref_sum->kills.reserve_exact (j);
|
|
if (j && modref_sum_lto)
|
|
modref_sum_lto->kills.reserve_exact (j);
|
|
for (int k = 0; k < j; k++)
|
|
{
|
|
modref_access_node a = modref_access_node::stream_in (&ib);
|
|
|
|
if (modref_sum)
|
|
modref_sum->kills.quick_push (a);
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->kills.quick_push (a);
|
|
}
|
|
struct bitpack_d bp = streamer_read_bitpack (&ib);
|
|
if (bp_unpack_value (&bp, 1))
|
|
{
|
|
if (modref_sum)
|
|
modref_sum->writes_errno = true;
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->writes_errno = true;
|
|
}
|
|
if (bp_unpack_value (&bp, 1))
|
|
{
|
|
if (modref_sum)
|
|
modref_sum->side_effects = true;
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->side_effects = true;
|
|
}
|
|
if (bp_unpack_value (&bp, 1))
|
|
{
|
|
if (modref_sum)
|
|
modref_sum->nondeterministic = true;
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->nondeterministic = true;
|
|
}
|
|
if (bp_unpack_value (&bp, 1))
|
|
{
|
|
if (modref_sum)
|
|
modref_sum->calls_interposable = true;
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->calls_interposable = true;
|
|
}
|
|
if (!flag_ltrans)
|
|
{
|
|
for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
|
|
{
|
|
if (bp_unpack_value (&bp, 1))
|
|
{
|
|
class fnspec_summary *sum = fnspec_summaries->get_create (e);
|
|
sum->fnspec = xstrdup (bp_unpack_string (data_in, &bp));
|
|
}
|
|
modref_read_escape_summary (&bp, e);
|
|
}
|
|
for (cgraph_edge *e = node->callees; e; e = e->next_callee)
|
|
{
|
|
if (bp_unpack_value (&bp, 1))
|
|
{
|
|
class fnspec_summary *sum = fnspec_summaries->get_create (e);
|
|
sum->fnspec = xstrdup (bp_unpack_string (data_in, &bp));
|
|
}
|
|
modref_read_escape_summary (&bp, e);
|
|
}
|
|
}
|
|
if (flag_ltrans)
|
|
modref_sum->finalize (node->decl);
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Read modref for %s\n",
|
|
node->dump_name ());
|
|
if (modref_sum)
|
|
modref_sum->dump (dump_file);
|
|
if (modref_sum_lto)
|
|
modref_sum_lto->dump (dump_file);
|
|
dump_modref_edge_summaries (dump_file, node, 4);
|
|
}
|
|
}
|
|
|
|
lto_free_section_data (file_data, LTO_section_ipa_modref, NULL, data,
|
|
len);
|
|
lto_data_in_delete (data_in);
|
|
}
|
|
|
|
/* Callback for read_summary. */
|
|
|
|
static void
|
|
modref_read (void)
|
|
{
|
|
struct lto_file_decl_data **file_data_vec = lto_get_file_decl_data ();
|
|
struct lto_file_decl_data *file_data;
|
|
unsigned int j = 0;
|
|
|
|
gcc_checking_assert (!optimization_summaries && !summaries && !summaries_lto);
|
|
if (flag_ltrans)
|
|
optimization_summaries = modref_summaries::create_ggc (symtab);
|
|
else
|
|
{
|
|
if (flag_wpa || flag_incremental_link == INCREMENTAL_LINK_LTO)
|
|
summaries_lto = modref_summaries_lto::create_ggc (symtab);
|
|
if (!flag_wpa
|
|
|| (flag_incremental_link == INCREMENTAL_LINK_LTO
|
|
&& flag_fat_lto_objects))
|
|
summaries = modref_summaries::create_ggc (symtab);
|
|
if (!fnspec_summaries)
|
|
fnspec_summaries = new fnspec_summaries_t (symtab);
|
|
if (!escape_summaries)
|
|
escape_summaries = new escape_summaries_t (symtab);
|
|
}
|
|
|
|
while ((file_data = file_data_vec[j++]))
|
|
{
|
|
size_t len;
|
|
const char *data = lto_get_summary_section_data (file_data,
|
|
LTO_section_ipa_modref,
|
|
&len);
|
|
if (data)
|
|
read_section (file_data, data, len);
|
|
else
|
|
/* Fatal error here. We do not want to support compiling ltrans units
|
|
with different version of compiler or different flags than the WPA
|
|
unit, so this should never happen. */
|
|
fatal_error (input_location,
|
|
"IPA modref summary is missing in input file");
|
|
}
|
|
}
|
|
|
|
/* Recompute arg_flags for param adjustments in INFO. */
|
|
|
|
static void
|
|
remap_arg_flags (auto_vec <eaf_flags_t> &arg_flags, clone_info *info)
|
|
{
|
|
auto_vec<eaf_flags_t> old = arg_flags.copy ();
|
|
int max = -1;
|
|
size_t i;
|
|
ipa_adjusted_param *p;
|
|
|
|
arg_flags.release ();
|
|
|
|
FOR_EACH_VEC_SAFE_ELT (info->param_adjustments->m_adj_params, i, p)
|
|
{
|
|
int o = info->param_adjustments->get_original_index (i);
|
|
if (o >= 0 && (int)old.length () > o && old[o])
|
|
max = i;
|
|
}
|
|
if (max >= 0)
|
|
arg_flags.safe_grow_cleared (max + 1, true);
|
|
FOR_EACH_VEC_SAFE_ELT (info->param_adjustments->m_adj_params, i, p)
|
|
{
|
|
int o = info->param_adjustments->get_original_index (i);
|
|
if (o >= 0 && (int)old.length () > o && old[o])
|
|
arg_flags[i] = old[o];
|
|
}
|
|
}
|
|
|
|
/* Update kills according to the parm map MAP. */
|
|
|
|
static void
|
|
remap_kills (vec <modref_access_node> &kills, const vec <int> &map)
|
|
{
|
|
for (size_t i = 0; i < kills.length ();)
|
|
if (kills[i].parm_index >= 0)
|
|
{
|
|
if (kills[i].parm_index < (int)map.length ()
|
|
&& map[kills[i].parm_index] != MODREF_UNKNOWN_PARM)
|
|
{
|
|
kills[i].parm_index = map[kills[i].parm_index];
|
|
i++;
|
|
}
|
|
else
|
|
kills.unordered_remove (i);
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
|
|
/* If signature changed, update the summary. */
|
|
|
|
static void
|
|
update_signature (struct cgraph_node *node)
|
|
{
|
|
clone_info *info = clone_info::get (node);
|
|
if (!info || !info->param_adjustments)
|
|
return;
|
|
|
|
modref_summary *r = optimization_summaries
|
|
? optimization_summaries->get (node) : NULL;
|
|
modref_summary_lto *r_lto = summaries_lto
|
|
? summaries_lto->get (node) : NULL;
|
|
if (!r && !r_lto)
|
|
return;
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Updating summary for %s from:\n",
|
|
node->dump_name ());
|
|
if (r)
|
|
r->dump (dump_file);
|
|
if (r_lto)
|
|
r_lto->dump (dump_file);
|
|
}
|
|
|
|
size_t i, max = 0;
|
|
ipa_adjusted_param *p;
|
|
|
|
FOR_EACH_VEC_SAFE_ELT (info->param_adjustments->m_adj_params, i, p)
|
|
{
|
|
int idx = info->param_adjustments->get_original_index (i);
|
|
if (idx > (int)max)
|
|
max = idx;
|
|
}
|
|
|
|
auto_vec <int, 32> map;
|
|
|
|
map.reserve (max + 1);
|
|
for (i = 0; i <= max; i++)
|
|
map.quick_push (MODREF_UNKNOWN_PARM);
|
|
FOR_EACH_VEC_SAFE_ELT (info->param_adjustments->m_adj_params, i, p)
|
|
{
|
|
int idx = info->param_adjustments->get_original_index (i);
|
|
if (idx >= 0)
|
|
map[idx] = i;
|
|
}
|
|
if (r)
|
|
{
|
|
r->loads->remap_params (&map);
|
|
r->stores->remap_params (&map);
|
|
remap_kills (r->kills, map);
|
|
if (r->arg_flags.length ())
|
|
remap_arg_flags (r->arg_flags, info);
|
|
}
|
|
if (r_lto)
|
|
{
|
|
r_lto->loads->remap_params (&map);
|
|
r_lto->stores->remap_params (&map);
|
|
remap_kills (r_lto->kills, map);
|
|
if (r_lto->arg_flags.length ())
|
|
remap_arg_flags (r_lto->arg_flags, info);
|
|
}
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "to:\n");
|
|
if (r)
|
|
r->dump (dump_file);
|
|
if (r_lto)
|
|
r_lto->dump (dump_file);
|
|
}
|
|
if (r)
|
|
r->finalize (node->decl);
|
|
return;
|
|
}
|
|
|
|
/* Definition of the modref IPA pass. */
|
|
const pass_data pass_data_ipa_modref =
|
|
{
|
|
IPA_PASS, /* type */
|
|
"modref", /* name */
|
|
OPTGROUP_IPA, /* optinfo_flags */
|
|
TV_IPA_MODREF, /* tv_id */
|
|
0, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
( TODO_dump_symtab ), /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_ipa_modref : public ipa_opt_pass_d
|
|
{
|
|
public:
|
|
pass_ipa_modref (gcc::context *ctxt)
|
|
: ipa_opt_pass_d (pass_data_ipa_modref, ctxt,
|
|
modref_generate, /* generate_summary */
|
|
modref_write, /* write_summary */
|
|
modref_read, /* read_summary */
|
|
modref_write, /* write_optimization_summary */
|
|
modref_read, /* read_optimization_summary */
|
|
NULL, /* stmt_fixup */
|
|
0, /* function_transform_todo_flags_start */
|
|
NULL, /* function_transform */
|
|
NULL) /* variable_transform */
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
opt_pass *clone () { return new pass_ipa_modref (m_ctxt); }
|
|
virtual bool gate (function *)
|
|
{
|
|
return true;
|
|
}
|
|
virtual unsigned int execute (function *);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
unsigned int pass_modref::execute (function *)
|
|
{
|
|
if (analyze_function (false))
|
|
return execute_fixup_cfg ();
|
|
return 0;
|
|
}
|
|
|
|
gimple_opt_pass *
|
|
make_pass_modref (gcc::context *ctxt)
|
|
{
|
|
return new pass_modref (ctxt);
|
|
}
|
|
|
|
ipa_opt_pass_d *
|
|
make_pass_ipa_modref (gcc::context *ctxt)
|
|
{
|
|
return new pass_ipa_modref (ctxt);
|
|
}
|
|
|
|
namespace {
|
|
|
|
/* Skip edges from and to nodes without ipa_pure_const enabled.
|
|
Ignore not available symbols. */
|
|
|
|
static bool
|
|
ignore_edge (struct cgraph_edge *e)
|
|
{
|
|
/* We merge summaries of inline clones into summaries of functions they
|
|
are inlined to. For that reason the complete function bodies must
|
|
act as unit. */
|
|
if (!e->inline_failed)
|
|
return false;
|
|
enum availability avail;
|
|
cgraph_node *callee = e->callee->ultimate_alias_target
|
|
(&avail, e->caller);
|
|
|
|
return (avail <= AVAIL_INTERPOSABLE
|
|
|| ((!optimization_summaries || !optimization_summaries->get (callee))
|
|
&& (!summaries_lto || !summaries_lto->get (callee))));
|
|
}
|
|
|
|
/* Compute parm_map for CALLEE_EDGE. */
|
|
|
|
static bool
|
|
compute_parm_map (cgraph_edge *callee_edge, vec<modref_parm_map> *parm_map)
|
|
{
|
|
class ipa_edge_args *args;
|
|
if (ipa_node_params_sum
|
|
&& !callee_edge->call_stmt_cannot_inline_p
|
|
&& (args = ipa_edge_args_sum->get (callee_edge)) != NULL)
|
|
{
|
|
int i, count = ipa_get_cs_argument_count (args);
|
|
class ipa_node_params *caller_parms_info, *callee_pi;
|
|
class ipa_call_summary *es
|
|
= ipa_call_summaries->get (callee_edge);
|
|
cgraph_node *callee
|
|
= callee_edge->callee->ultimate_alias_target
|
|
(NULL, callee_edge->caller);
|
|
|
|
caller_parms_info
|
|
= ipa_node_params_sum->get (callee_edge->caller->inlined_to
|
|
? callee_edge->caller->inlined_to
|
|
: callee_edge->caller);
|
|
callee_pi = ipa_node_params_sum->get (callee);
|
|
|
|
(*parm_map).safe_grow_cleared (count, true);
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (es && es->param[i].points_to_local_or_readonly_memory)
|
|
{
|
|
(*parm_map)[i].parm_index = MODREF_LOCAL_MEMORY_PARM;
|
|
continue;
|
|
}
|
|
|
|
struct ipa_jump_func *jf
|
|
= ipa_get_ith_jump_func (args, i);
|
|
if (jf && callee_pi)
|
|
{
|
|
tree cst = ipa_value_from_jfunc (caller_parms_info,
|
|
jf,
|
|
ipa_get_type
|
|
(callee_pi, i));
|
|
if (cst && points_to_local_or_readonly_memory_p (cst))
|
|
{
|
|
(*parm_map)[i].parm_index = MODREF_LOCAL_MEMORY_PARM;
|
|
continue;
|
|
}
|
|
}
|
|
if (jf && jf->type == IPA_JF_PASS_THROUGH)
|
|
{
|
|
(*parm_map)[i].parm_index
|
|
= ipa_get_jf_pass_through_formal_id (jf);
|
|
if (ipa_get_jf_pass_through_operation (jf) == NOP_EXPR)
|
|
{
|
|
(*parm_map)[i].parm_offset_known = true;
|
|
(*parm_map)[i].parm_offset = 0;
|
|
}
|
|
else if (ipa_get_jf_pass_through_operation (jf)
|
|
== POINTER_PLUS_EXPR
|
|
&& ptrdiff_tree_p (ipa_get_jf_pass_through_operand (jf),
|
|
&(*parm_map)[i].parm_offset))
|
|
(*parm_map)[i].parm_offset_known = true;
|
|
else
|
|
(*parm_map)[i].parm_offset_known = false;
|
|
continue;
|
|
}
|
|
if (jf && jf->type == IPA_JF_ANCESTOR)
|
|
{
|
|
(*parm_map)[i].parm_index = ipa_get_jf_ancestor_formal_id (jf);
|
|
(*parm_map)[i].parm_offset_known = true;
|
|
gcc_checking_assert
|
|
(!(ipa_get_jf_ancestor_offset (jf) & (BITS_PER_UNIT - 1)));
|
|
(*parm_map)[i].parm_offset
|
|
= ipa_get_jf_ancestor_offset (jf) >> LOG2_BITS_PER_UNIT;
|
|
}
|
|
else
|
|
(*parm_map)[i].parm_index = -1;
|
|
}
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, " Parm map: ");
|
|
for (i = 0; i < count; i++)
|
|
fprintf (dump_file, " %i", (*parm_map)[i].parm_index);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Map used to translate escape infos. */
|
|
|
|
struct escape_map
|
|
{
|
|
int parm_index;
|
|
bool direct;
|
|
};
|
|
|
|
/* Update escape map for E. */
|
|
|
|
static void
|
|
update_escape_summary_1 (cgraph_edge *e,
|
|
vec <vec <escape_map>> &map,
|
|
bool ignore_stores)
|
|
{
|
|
escape_summary *sum = escape_summaries->get (e);
|
|
if (!sum)
|
|
return;
|
|
auto_vec <escape_entry> old = sum->esc.copy ();
|
|
sum->esc.release ();
|
|
|
|
unsigned int i;
|
|
escape_entry *ee;
|
|
FOR_EACH_VEC_ELT (old, i, ee)
|
|
{
|
|
unsigned int j;
|
|
struct escape_map *em;
|
|
/* TODO: We do not have jump functions for return slots, so we
|
|
never propagate them to outer function. */
|
|
if (ee->parm_index >= (int)map.length ()
|
|
|| ee->parm_index < 0)
|
|
continue;
|
|
FOR_EACH_VEC_ELT (map[ee->parm_index], j, em)
|
|
{
|
|
int min_flags = ee->min_flags;
|
|
if (ee->direct && !em->direct)
|
|
min_flags = deref_flags (min_flags, ignore_stores);
|
|
struct escape_entry entry = {em->parm_index, ee->arg,
|
|
min_flags,
|
|
ee->direct & em->direct};
|
|
sum->esc.safe_push (entry);
|
|
}
|
|
}
|
|
if (!sum->esc.length ())
|
|
escape_summaries->remove (e);
|
|
}
|
|
|
|
/* Update escape map for NODE. */
|
|
|
|
static void
|
|
update_escape_summary (cgraph_node *node,
|
|
vec <vec <escape_map>> &map,
|
|
bool ignore_stores)
|
|
{
|
|
if (!escape_summaries)
|
|
return;
|
|
for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
|
|
update_escape_summary_1 (e, map, ignore_stores);
|
|
for (cgraph_edge *e = node->callees; e; e = e->next_callee)
|
|
{
|
|
if (!e->inline_failed)
|
|
update_escape_summary (e->callee, map, ignore_stores);
|
|
else
|
|
update_escape_summary_1 (e, map, ignore_stores);
|
|
}
|
|
}
|
|
|
|
/* Get parameter type from DECL. This is only safe for special cases
|
|
like builtins we create fnspec for because the type match is checked
|
|
at fnspec creation time. */
|
|
|
|
static tree
|
|
get_parm_type (tree decl, unsigned int i)
|
|
{
|
|
tree t = TYPE_ARG_TYPES (TREE_TYPE (decl));
|
|
|
|
for (unsigned int p = 0; p < i; p++)
|
|
t = TREE_CHAIN (t);
|
|
return TREE_VALUE (t);
|
|
}
|
|
|
|
/* Return access mode for argument I of call E with FNSPEC. */
|
|
|
|
static modref_access_node
|
|
get_access_for_fnspec (cgraph_edge *e, attr_fnspec &fnspec,
|
|
unsigned int i, modref_parm_map &map)
|
|
{
|
|
tree size = NULL_TREE;
|
|
unsigned int size_arg;
|
|
|
|
if (!fnspec.arg_specified_p (i))
|
|
;
|
|
else if (fnspec.arg_max_access_size_given_by_arg_p (i, &size_arg))
|
|
{
|
|
cgraph_node *node = e->caller->inlined_to
|
|
? e->caller->inlined_to : e->caller;
|
|
ipa_node_params *caller_parms_info = ipa_node_params_sum->get (node);
|
|
ipa_edge_args *args = ipa_edge_args_sum->get (e);
|
|
struct ipa_jump_func *jf = ipa_get_ith_jump_func (args, size_arg);
|
|
|
|
if (jf)
|
|
size = ipa_value_from_jfunc (caller_parms_info, jf,
|
|
get_parm_type (e->callee->decl, size_arg));
|
|
}
|
|
else if (fnspec.arg_access_size_given_by_type_p (i))
|
|
size = TYPE_SIZE_UNIT (get_parm_type (e->callee->decl, i));
|
|
modref_access_node a = {0, -1, -1,
|
|
map.parm_offset, map.parm_index,
|
|
map.parm_offset_known, 0};
|
|
poly_int64 size_hwi;
|
|
if (size
|
|
&& poly_int_tree_p (size, &size_hwi)
|
|
&& coeffs_in_range_p (size_hwi, 0,
|
|
HOST_WIDE_INT_MAX / BITS_PER_UNIT))
|
|
{
|
|
a.size = -1;
|
|
a.max_size = size_hwi << LOG2_BITS_PER_UNIT;
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/* Collapse loads and return true if something changed. */
|
|
static bool
|
|
collapse_loads (modref_summary *cur_summary,
|
|
modref_summary_lto *cur_summary_lto)
|
|
{
|
|
bool changed = false;
|
|
|
|
if (cur_summary && !cur_summary->loads->every_base)
|
|
{
|
|
cur_summary->loads->collapse ();
|
|
changed = true;
|
|
}
|
|
if (cur_summary_lto
|
|
&& !cur_summary_lto->loads->every_base)
|
|
{
|
|
cur_summary_lto->loads->collapse ();
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Collapse loads and return true if something changed. */
|
|
|
|
static bool
|
|
collapse_stores (modref_summary *cur_summary,
|
|
modref_summary_lto *cur_summary_lto)
|
|
{
|
|
bool changed = false;
|
|
|
|
if (cur_summary && !cur_summary->stores->every_base)
|
|
{
|
|
cur_summary->stores->collapse ();
|
|
changed = true;
|
|
}
|
|
if (cur_summary_lto
|
|
&& !cur_summary_lto->stores->every_base)
|
|
{
|
|
cur_summary_lto->stores->collapse ();
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Call E in NODE with ECF_FLAGS has no summary; update MODREF_SUMMARY and
|
|
CUR_SUMMARY_LTO accordingly. Return true if something changed. */
|
|
|
|
static bool
|
|
propagate_unknown_call (cgraph_node *node,
|
|
cgraph_edge *e, int ecf_flags,
|
|
modref_summary *cur_summary,
|
|
modref_summary_lto *cur_summary_lto,
|
|
bool nontrivial_scc)
|
|
{
|
|
bool changed = false;
|
|
class fnspec_summary *fnspec_sum = fnspec_summaries->get (e);
|
|
auto_vec <modref_parm_map, 32> parm_map;
|
|
bool looping;
|
|
|
|
if (e->callee
|
|
&& builtin_safe_for_const_function_p (&looping, e->callee->decl))
|
|
{
|
|
if (looping && cur_summary && !cur_summary->side_effects)
|
|
{
|
|
cur_summary->side_effects = true;
|
|
changed = true;
|
|
}
|
|
if (looping && cur_summary_lto && !cur_summary_lto->side_effects)
|
|
{
|
|
cur_summary_lto->side_effects = true;
|
|
changed = true;
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
if (!(ecf_flags & (ECF_CONST | ECF_NOVOPS | ECF_PURE))
|
|
|| (ecf_flags & ECF_LOOPING_CONST_OR_PURE)
|
|
|| nontrivial_scc)
|
|
{
|
|
if (cur_summary && !cur_summary->side_effects)
|
|
{
|
|
cur_summary->side_effects = true;
|
|
changed = true;
|
|
}
|
|
if (cur_summary_lto && !cur_summary_lto->side_effects)
|
|
{
|
|
cur_summary_lto->side_effects = true;
|
|
changed = true;
|
|
}
|
|
if (cur_summary && !cur_summary->nondeterministic
|
|
&& !ignore_nondeterminism_p (node->decl, ecf_flags))
|
|
{
|
|
cur_summary->nondeterministic = true;
|
|
changed = true;
|
|
}
|
|
if (cur_summary_lto && !cur_summary_lto->nondeterministic
|
|
&& !ignore_nondeterminism_p (node->decl, ecf_flags))
|
|
{
|
|
cur_summary_lto->nondeterministic = true;
|
|
changed = true;
|
|
}
|
|
}
|
|
if (ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
return changed;
|
|
|
|
if (fnspec_sum
|
|
&& compute_parm_map (e, &parm_map))
|
|
{
|
|
attr_fnspec fnspec (fnspec_sum->fnspec);
|
|
|
|
gcc_checking_assert (fnspec.known_p ());
|
|
if (fnspec.global_memory_read_p ())
|
|
collapse_loads (cur_summary, cur_summary_lto);
|
|
else
|
|
{
|
|
tree t = TYPE_ARG_TYPES (TREE_TYPE (e->callee->decl));
|
|
for (unsigned i = 0; i < parm_map.length () && t;
|
|
i++, t = TREE_CHAIN (t))
|
|
if (!POINTER_TYPE_P (TREE_VALUE (t)))
|
|
;
|
|
else if (!fnspec.arg_specified_p (i)
|
|
|| fnspec.arg_maybe_read_p (i))
|
|
{
|
|
modref_parm_map map = parm_map[i];
|
|
if (map.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
continue;
|
|
if (map.parm_index == MODREF_UNKNOWN_PARM)
|
|
{
|
|
collapse_loads (cur_summary, cur_summary_lto);
|
|
break;
|
|
}
|
|
if (cur_summary)
|
|
changed |= cur_summary->loads->insert
|
|
(node->decl, 0, 0,
|
|
get_access_for_fnspec (e, fnspec, i, map), false);
|
|
if (cur_summary_lto)
|
|
changed |= cur_summary_lto->loads->insert
|
|
(node->decl, 0, 0,
|
|
get_access_for_fnspec (e, fnspec, i, map), false);
|
|
}
|
|
}
|
|
if (ignore_stores_p (node->decl, ecf_flags))
|
|
;
|
|
else if (fnspec.global_memory_written_p ())
|
|
collapse_stores (cur_summary, cur_summary_lto);
|
|
else
|
|
{
|
|
tree t = TYPE_ARG_TYPES (TREE_TYPE (e->callee->decl));
|
|
for (unsigned i = 0; i < parm_map.length () && t;
|
|
i++, t = TREE_CHAIN (t))
|
|
if (!POINTER_TYPE_P (TREE_VALUE (t)))
|
|
;
|
|
else if (!fnspec.arg_specified_p (i)
|
|
|| fnspec.arg_maybe_written_p (i))
|
|
{
|
|
modref_parm_map map = parm_map[i];
|
|
if (map.parm_index == MODREF_LOCAL_MEMORY_PARM)
|
|
continue;
|
|
if (map.parm_index == MODREF_UNKNOWN_PARM)
|
|
{
|
|
collapse_stores (cur_summary, cur_summary_lto);
|
|
break;
|
|
}
|
|
if (cur_summary)
|
|
changed |= cur_summary->stores->insert
|
|
(node->decl, 0, 0,
|
|
get_access_for_fnspec (e, fnspec, i, map), false);
|
|
if (cur_summary_lto)
|
|
changed |= cur_summary_lto->stores->insert
|
|
(node->decl, 0, 0,
|
|
get_access_for_fnspec (e, fnspec, i, map), false);
|
|
}
|
|
}
|
|
if (fnspec.errno_maybe_written_p () && flag_errno_math)
|
|
{
|
|
if (cur_summary && !cur_summary->writes_errno)
|
|
{
|
|
cur_summary->writes_errno = true;
|
|
changed = true;
|
|
}
|
|
if (cur_summary_lto && !cur_summary_lto->writes_errno)
|
|
{
|
|
cur_summary_lto->writes_errno = true;
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
if (dump_file)
|
|
fprintf (dump_file, " collapsing loads\n");
|
|
changed |= collapse_loads (cur_summary, cur_summary_lto);
|
|
if (!ignore_stores_p (node->decl, ecf_flags))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " collapsing stores\n");
|
|
changed |= collapse_stores (cur_summary, cur_summary_lto);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Maybe remove summaries of NODE pointed to by CUR_SUMMARY_PTR
|
|
and CUR_SUMMARY_LTO_PTR if they are useless according to ECF_FLAGS. */
|
|
|
|
static void
|
|
remove_useless_summaries (cgraph_node *node,
|
|
modref_summary **cur_summary_ptr,
|
|
modref_summary_lto **cur_summary_lto_ptr,
|
|
int ecf_flags)
|
|
{
|
|
if (*cur_summary_ptr && !(*cur_summary_ptr)->useful_p (ecf_flags, false))
|
|
{
|
|
optimization_summaries->remove (node);
|
|
*cur_summary_ptr = NULL;
|
|
}
|
|
if (*cur_summary_lto_ptr
|
|
&& !(*cur_summary_lto_ptr)->useful_p (ecf_flags, false))
|
|
{
|
|
summaries_lto->remove (node);
|
|
*cur_summary_lto_ptr = NULL;
|
|
}
|
|
}
|
|
|
|
/* Perform iterative dataflow on SCC component starting in COMPONENT_NODE
|
|
and propagate loads/stores. */
|
|
|
|
static bool
|
|
modref_propagate_in_scc (cgraph_node *component_node)
|
|
{
|
|
bool changed = true;
|
|
bool first = true;
|
|
int iteration = 0;
|
|
|
|
while (changed)
|
|
{
|
|
bool nontrivial_scc
|
|
= ((struct ipa_dfs_info *) component_node->aux)->next_cycle;
|
|
changed = false;
|
|
for (struct cgraph_node *cur = component_node; cur;
|
|
cur = ((struct ipa_dfs_info *) cur->aux)->next_cycle)
|
|
{
|
|
cgraph_node *node = cur->inlined_to ? cur->inlined_to : cur;
|
|
modref_summary *cur_summary = optimization_summaries
|
|
? optimization_summaries->get (node)
|
|
: NULL;
|
|
modref_summary_lto *cur_summary_lto = summaries_lto
|
|
? summaries_lto->get (node)
|
|
: NULL;
|
|
|
|
if (!cur_summary && !cur_summary_lto)
|
|
continue;
|
|
|
|
int cur_ecf_flags = flags_from_decl_or_type (node->decl);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, " Processing %s%s%s\n",
|
|
cur->dump_name (),
|
|
TREE_READONLY (cur->decl) ? " (const)" : "",
|
|
DECL_PURE_P (cur->decl) ? " (pure)" : "");
|
|
|
|
for (cgraph_edge *e = cur->indirect_calls; e; e = e->next_callee)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " Indirect call\n");
|
|
if (propagate_unknown_call
|
|
(node, e, e->indirect_info->ecf_flags,
|
|
cur_summary, cur_summary_lto,
|
|
nontrivial_scc))
|
|
{
|
|
changed = true;
|
|
remove_useless_summaries (node, &cur_summary,
|
|
&cur_summary_lto,
|
|
cur_ecf_flags);
|
|
if (!cur_summary && !cur_summary_lto)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!cur_summary && !cur_summary_lto)
|
|
continue;
|
|
|
|
for (cgraph_edge *callee_edge = cur->callees; callee_edge;
|
|
callee_edge = callee_edge->next_callee)
|
|
{
|
|
int flags = flags_from_decl_or_type (callee_edge->callee->decl);
|
|
modref_summary *callee_summary = NULL;
|
|
modref_summary_lto *callee_summary_lto = NULL;
|
|
struct cgraph_node *callee;
|
|
|
|
if (!callee_edge->inline_failed
|
|
|| ((flags & (ECF_CONST | ECF_NOVOPS))
|
|
&& !(flags & ECF_LOOPING_CONST_OR_PURE)))
|
|
continue;
|
|
|
|
/* Get the callee and its summary. */
|
|
enum availability avail;
|
|
callee = callee_edge->callee->ultimate_alias_target
|
|
(&avail, cur);
|
|
|
|
/* It is not necessary to re-process calls outside of the
|
|
SCC component. */
|
|
if (iteration > 0
|
|
&& (!callee->aux
|
|
|| ((struct ipa_dfs_info *)cur->aux)->scc_no
|
|
!= ((struct ipa_dfs_info *)callee->aux)->scc_no))
|
|
continue;
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, " Call to %s\n",
|
|
callee_edge->callee->dump_name ());
|
|
|
|
bool ignore_stores = ignore_stores_p (cur->decl, flags);
|
|
|
|
if (avail <= AVAIL_INTERPOSABLE)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " Call target interposable"
|
|
" or not available\n");
|
|
changed |= propagate_unknown_call
|
|
(node, callee_edge, flags,
|
|
cur_summary, cur_summary_lto,
|
|
nontrivial_scc);
|
|
if (!cur_summary && !cur_summary_lto)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
/* We don't know anything about CALLEE, hence we cannot tell
|
|
anything about the entire component. */
|
|
|
|
if (cur_summary
|
|
&& !(callee_summary = optimization_summaries->get (callee)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " No call target summary\n");
|
|
changed |= propagate_unknown_call
|
|
(node, callee_edge, flags,
|
|
cur_summary, NULL,
|
|
nontrivial_scc);
|
|
}
|
|
if (cur_summary_lto
|
|
&& !(callee_summary_lto = summaries_lto->get (callee)))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, " No call target summary\n");
|
|
changed |= propagate_unknown_call
|
|
(node, callee_edge, flags,
|
|
NULL, cur_summary_lto,
|
|
nontrivial_scc);
|
|
}
|
|
|
|
if (callee_summary && !cur_summary->side_effects
|
|
&& (callee_summary->side_effects
|
|
|| callee_edge->recursive_p ()))
|
|
{
|
|
cur_summary->side_effects = true;
|
|
changed = true;
|
|
}
|
|
if (callee_summary_lto && !cur_summary_lto->side_effects
|
|
&& (callee_summary_lto->side_effects
|
|
|| callee_edge->recursive_p ()))
|
|
{
|
|
cur_summary_lto->side_effects = true;
|
|
changed = true;
|
|
}
|
|
if (callee_summary && !cur_summary->nondeterministic
|
|
&& callee_summary->nondeterministic
|
|
&& !ignore_nondeterminism_p (cur->decl, flags))
|
|
{
|
|
cur_summary->nondeterministic = true;
|
|
changed = true;
|
|
}
|
|
if (callee_summary_lto && !cur_summary_lto->nondeterministic
|
|
&& callee_summary_lto->nondeterministic
|
|
&& !ignore_nondeterminism_p (cur->decl, flags))
|
|
{
|
|
cur_summary_lto->nondeterministic = true;
|
|
changed = true;
|
|
}
|
|
if (flags & (ECF_CONST | ECF_NOVOPS))
|
|
continue;
|
|
|
|
/* We can not safely optimize based on summary of callee if it
|
|
does not always bind to current def: it is possible that
|
|
memory load was optimized out earlier which may not happen in
|
|
the interposed variant. */
|
|
if (!callee_edge->binds_to_current_def_p ())
|
|
{
|
|
if (cur_summary && !cur_summary->calls_interposable)
|
|
{
|
|
cur_summary->calls_interposable = true;
|
|
changed = true;
|
|
}
|
|
if (cur_summary_lto && !cur_summary_lto->calls_interposable)
|
|
{
|
|
cur_summary_lto->calls_interposable = true;
|
|
changed = true;
|
|
}
|
|
if (dump_file)
|
|
fprintf (dump_file, " May not bind local;"
|
|
" collapsing loads\n");
|
|
}
|
|
|
|
|
|
auto_vec <modref_parm_map, 32> parm_map;
|
|
modref_parm_map chain_map;
|
|
/* TODO: Once we get jump functions for static chains we could
|
|
compute this. */
|
|
chain_map.parm_index = MODREF_UNKNOWN_PARM;
|
|
|
|
compute_parm_map (callee_edge, &parm_map);
|
|
|
|
/* Merge in callee's information. */
|
|
if (callee_summary)
|
|
{
|
|
changed |= cur_summary->loads->merge
|
|
(node->decl, callee_summary->loads,
|
|
&parm_map, &chain_map, !first);
|
|
if (!ignore_stores)
|
|
{
|
|
changed |= cur_summary->stores->merge
|
|
(node->decl, callee_summary->stores,
|
|
&parm_map, &chain_map, !first);
|
|
if (!cur_summary->writes_errno
|
|
&& callee_summary->writes_errno)
|
|
{
|
|
cur_summary->writes_errno = true;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
if (callee_summary_lto)
|
|
{
|
|
changed |= cur_summary_lto->loads->merge
|
|
(node->decl, callee_summary_lto->loads,
|
|
&parm_map, &chain_map, !first);
|
|
if (!ignore_stores)
|
|
{
|
|
changed |= cur_summary_lto->stores->merge
|
|
(node->decl, callee_summary_lto->stores,
|
|
&parm_map, &chain_map, !first);
|
|
if (!cur_summary_lto->writes_errno
|
|
&& callee_summary_lto->writes_errno)
|
|
{
|
|
cur_summary_lto->writes_errno = true;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
if (changed)
|
|
remove_useless_summaries (node, &cur_summary,
|
|
&cur_summary_lto,
|
|
cur_ecf_flags);
|
|
if (!cur_summary && !cur_summary_lto)
|
|
break;
|
|
if (dump_file && changed)
|
|
{
|
|
if (cur_summary)
|
|
cur_summary->dump (dump_file);
|
|
if (cur_summary_lto)
|
|
cur_summary_lto->dump (dump_file);
|
|
dump_modref_edge_summaries (dump_file, node, 4);
|
|
}
|
|
}
|
|
}
|
|
iteration++;
|
|
first = false;
|
|
}
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"Propagation finished in %i iterations\n", iteration);
|
|
bool pureconst = false;
|
|
for (struct cgraph_node *cur = component_node; cur;
|
|
cur = ((struct ipa_dfs_info *) cur->aux)->next_cycle)
|
|
if (!cur->inlined_to && opt_for_fn (cur->decl, flag_ipa_pure_const))
|
|
{
|
|
modref_summary *summary = optimization_summaries
|
|
? optimization_summaries->get (cur)
|
|
: NULL;
|
|
modref_summary_lto *summary_lto = summaries_lto
|
|
? summaries_lto->get (cur)
|
|
: NULL;
|
|
if (summary && !summary->stores->every_base && !summary->stores->bases
|
|
&& !summary->nondeterministic)
|
|
{
|
|
if (!summary->loads->every_base && !summary->loads->bases
|
|
&& !summary->calls_interposable)
|
|
pureconst |= ipa_make_function_const
|
|
(cur, summary->side_effects, false);
|
|
else
|
|
pureconst |= ipa_make_function_pure
|
|
(cur, summary->side_effects, false);
|
|
}
|
|
if (summary_lto && !summary_lto->stores->every_base
|
|
&& !summary_lto->stores->bases && !summary_lto->nondeterministic)
|
|
{
|
|
if (!summary_lto->loads->every_base && !summary_lto->loads->bases
|
|
&& !summary_lto->calls_interposable)
|
|
pureconst |= ipa_make_function_const
|
|
(cur, summary_lto->side_effects, false);
|
|
else
|
|
pureconst |= ipa_make_function_pure
|
|
(cur, summary_lto->side_effects, false);
|
|
}
|
|
}
|
|
return pureconst;
|
|
}
|
|
|
|
/* Dump results of propagation in SCC rooted in COMPONENT_NODE. */
|
|
|
|
static void
|
|
modref_propagate_dump_scc (cgraph_node *component_node)
|
|
{
|
|
for (struct cgraph_node *cur = component_node; cur;
|
|
cur = ((struct ipa_dfs_info *) cur->aux)->next_cycle)
|
|
if (!cur->inlined_to)
|
|
{
|
|
modref_summary *cur_summary = optimization_summaries
|
|
? optimization_summaries->get (cur)
|
|
: NULL;
|
|
modref_summary_lto *cur_summary_lto = summaries_lto
|
|
? summaries_lto->get (cur)
|
|
: NULL;
|
|
|
|
fprintf (dump_file, "Propagated modref for %s%s%s\n",
|
|
cur->dump_name (),
|
|
TREE_READONLY (cur->decl) ? " (const)" : "",
|
|
DECL_PURE_P (cur->decl) ? " (pure)" : "");
|
|
if (optimization_summaries)
|
|
{
|
|
if (cur_summary)
|
|
cur_summary->dump (dump_file);
|
|
else
|
|
fprintf (dump_file, " Not tracked\n");
|
|
}
|
|
if (summaries_lto)
|
|
{
|
|
if (cur_summary_lto)
|
|
cur_summary_lto->dump (dump_file);
|
|
else
|
|
fprintf (dump_file, " Not tracked (lto)\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Determine EAF flags know for call E with CALLEE_ECF_FLAGS and ARG. */
|
|
|
|
int
|
|
implicit_eaf_flags_for_edge_and_arg (cgraph_edge *e, int callee_ecf_flags,
|
|
bool ignore_stores, int arg)
|
|
{
|
|
/* Returning the value is already accounted to at local propagation. */
|
|
int implicit_flags = EAF_NOT_RETURNED_DIRECTLY
|
|
| EAF_NOT_RETURNED_INDIRECTLY;
|
|
if (ignore_stores)
|
|
implicit_flags |= ignore_stores_eaf_flags;
|
|
if (callee_ecf_flags & ECF_PURE)
|
|
implicit_flags |= implicit_pure_eaf_flags;
|
|
if (callee_ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
implicit_flags |= implicit_const_eaf_flags;
|
|
class fnspec_summary *fnspec_sum = fnspec_summaries->get (e);
|
|
if (fnspec_sum)
|
|
{
|
|
attr_fnspec fnspec (fnspec_sum->fnspec);
|
|
implicit_flags |= fnspec.arg_eaf_flags (arg);
|
|
}
|
|
return implicit_flags;
|
|
}
|
|
|
|
/* Process escapes in SUM and merge SUMMARY to CUR_SUMMARY
|
|
and SUMMARY_LTO to CUR_SUMMARY_LTO.
|
|
Return true if something changed. */
|
|
|
|
static bool
|
|
modref_merge_call_site_flags (escape_summary *sum,
|
|
modref_summary *cur_summary,
|
|
modref_summary_lto *cur_summary_lto,
|
|
modref_summary *summary,
|
|
modref_summary_lto *summary_lto,
|
|
tree caller,
|
|
cgraph_edge *e,
|
|
int caller_ecf_flags,
|
|
int callee_ecf_flags,
|
|
bool binds_to_current_def)
|
|
{
|
|
escape_entry *ee;
|
|
unsigned int i;
|
|
bool changed = false;
|
|
bool ignore_stores = ignore_stores_p (caller, callee_ecf_flags);
|
|
|
|
/* Return early if we have no useful info to propagate. */
|
|
if ((!cur_summary
|
|
|| (!cur_summary->arg_flags.length ()
|
|
&& !cur_summary->static_chain_flags
|
|
&& !cur_summary->retslot_flags))
|
|
&& (!cur_summary_lto
|
|
|| (!cur_summary_lto->arg_flags.length ()
|
|
&& !cur_summary_lto->static_chain_flags
|
|
&& !cur_summary_lto->retslot_flags)))
|
|
return false;
|
|
|
|
FOR_EACH_VEC_ELT (sum->esc, i, ee)
|
|
{
|
|
int flags = 0;
|
|
int flags_lto = 0;
|
|
int implicit_flags = implicit_eaf_flags_for_edge_and_arg
|
|
(e, callee_ecf_flags, ignore_stores, ee->arg);
|
|
|
|
if (summary && ee->arg < summary->arg_flags.length ())
|
|
flags = summary->arg_flags[ee->arg];
|
|
if (summary_lto
|
|
&& ee->arg < summary_lto->arg_flags.length ())
|
|
flags_lto = summary_lto->arg_flags[ee->arg];
|
|
if (!ee->direct)
|
|
{
|
|
flags = deref_flags (flags, ignore_stores);
|
|
flags_lto = deref_flags (flags_lto, ignore_stores);
|
|
}
|
|
if (ignore_stores)
|
|
implicit_flags |= ignore_stores_eaf_flags;
|
|
if (callee_ecf_flags & ECF_PURE)
|
|
implicit_flags |= implicit_pure_eaf_flags;
|
|
if (callee_ecf_flags & (ECF_CONST | ECF_NOVOPS))
|
|
implicit_flags |= implicit_const_eaf_flags;
|
|
class fnspec_summary *fnspec_sum = fnspec_summaries->get (e);
|
|
if (fnspec_sum)
|
|
{
|
|
attr_fnspec fnspec (fnspec_sum->fnspec);
|
|
implicit_flags |= fnspec.arg_eaf_flags (ee->arg);
|
|
}
|
|
if (!ee->direct)
|
|
implicit_flags = deref_flags (implicit_flags, ignore_stores);
|
|
flags |= implicit_flags;
|
|
flags_lto |= implicit_flags;
|
|
if (!binds_to_current_def && (flags || flags_lto))
|
|
{
|
|
flags = interposable_eaf_flags (flags, implicit_flags);
|
|
flags_lto = interposable_eaf_flags (flags_lto, implicit_flags);
|
|
}
|
|
if (!(flags & EAF_UNUSED)
|
|
&& cur_summary && ee->parm_index < (int)cur_summary->arg_flags.length ())
|
|
{
|
|
eaf_flags_t &f = ee->parm_index == MODREF_RETSLOT_PARM
|
|
? cur_summary->retslot_flags
|
|
: ee->parm_index == MODREF_STATIC_CHAIN_PARM
|
|
? cur_summary->static_chain_flags
|
|
: cur_summary->arg_flags[ee->parm_index];
|
|
if ((f & flags) != f)
|
|
{
|
|
f = remove_useless_eaf_flags
|
|
(f & flags, caller_ecf_flags,
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (caller))));
|
|
changed = true;
|
|
}
|
|
}
|
|
if (!(flags_lto & EAF_UNUSED)
|
|
&& cur_summary_lto
|
|
&& ee->parm_index < (int)cur_summary_lto->arg_flags.length ())
|
|
{
|
|
eaf_flags_t &f = ee->parm_index == MODREF_RETSLOT_PARM
|
|
? cur_summary_lto->retslot_flags
|
|
: ee->parm_index == MODREF_STATIC_CHAIN_PARM
|
|
? cur_summary_lto->static_chain_flags
|
|
: cur_summary_lto->arg_flags[ee->parm_index];
|
|
if ((f & flags_lto) != f)
|
|
{
|
|
f = remove_useless_eaf_flags
|
|
(f & flags_lto, caller_ecf_flags,
|
|
VOID_TYPE_P (TREE_TYPE (TREE_TYPE (caller))));
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Perform iterative dataflow on SCC component starting in COMPONENT_NODE
|
|
and propagate arg flags. */
|
|
|
|
static void
|
|
modref_propagate_flags_in_scc (cgraph_node *component_node)
|
|
{
|
|
bool changed = true;
|
|
int iteration = 0;
|
|
|
|
while (changed)
|
|
{
|
|
changed = false;
|
|
for (struct cgraph_node *cur = component_node; cur;
|
|
cur = ((struct ipa_dfs_info *) cur->aux)->next_cycle)
|
|
{
|
|
cgraph_node *node = cur->inlined_to ? cur->inlined_to : cur;
|
|
modref_summary *cur_summary = optimization_summaries
|
|
? optimization_summaries->get (node)
|
|
: NULL;
|
|
modref_summary_lto *cur_summary_lto = summaries_lto
|
|
? summaries_lto->get (node)
|
|
: NULL;
|
|
|
|
if (!cur_summary && !cur_summary_lto)
|
|
continue;
|
|
int caller_ecf_flags = flags_from_decl_or_type (cur->decl);
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, " Processing %s%s%s\n",
|
|
cur->dump_name (),
|
|
TREE_READONLY (cur->decl) ? " (const)" : "",
|
|
DECL_PURE_P (cur->decl) ? " (pure)" : "");
|
|
|
|
for (cgraph_edge *e = cur->indirect_calls; e; e = e->next_callee)
|
|
{
|
|
escape_summary *sum = escape_summaries->get (e);
|
|
|
|
if (!sum || (e->indirect_info->ecf_flags
|
|
& (ECF_CONST | ECF_NOVOPS)))
|
|
continue;
|
|
|
|
changed |= modref_merge_call_site_flags
|
|
(sum, cur_summary, cur_summary_lto,
|
|
NULL, NULL,
|
|
node->decl,
|
|
e,
|
|
caller_ecf_flags,
|
|
e->indirect_info->ecf_flags,
|
|
false);
|
|
}
|
|
|
|
if (!cur_summary && !cur_summary_lto)
|
|
continue;
|
|
|
|
for (cgraph_edge *callee_edge = cur->callees; callee_edge;
|
|
callee_edge = callee_edge->next_callee)
|
|
{
|
|
int ecf_flags = flags_from_decl_or_type
|
|
(callee_edge->callee->decl);
|
|
modref_summary *callee_summary = NULL;
|
|
modref_summary_lto *callee_summary_lto = NULL;
|
|
struct cgraph_node *callee;
|
|
|
|
if (ecf_flags & (ECF_CONST | ECF_NOVOPS)
|
|
|| !callee_edge->inline_failed)
|
|
continue;
|
|
|
|
/* Get the callee and its summary. */
|
|
enum availability avail;
|
|
callee = callee_edge->callee->ultimate_alias_target
|
|
(&avail, cur);
|
|
|
|
/* It is not necessary to re-process calls outside of the
|
|
SCC component. */
|
|
if (iteration > 0
|
|
&& (!callee->aux
|
|
|| ((struct ipa_dfs_info *)cur->aux)->scc_no
|
|
!= ((struct ipa_dfs_info *)callee->aux)->scc_no))
|
|
continue;
|
|
|
|
escape_summary *sum = escape_summaries->get (callee_edge);
|
|
if (!sum)
|
|
continue;
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, " Call to %s\n",
|
|
callee_edge->callee->dump_name ());
|
|
|
|
if (avail <= AVAIL_INTERPOSABLE
|
|
|| callee_edge->call_stmt_cannot_inline_p)
|
|
;
|
|
else
|
|
{
|
|
if (cur_summary)
|
|
callee_summary = optimization_summaries->get (callee);
|
|
if (cur_summary_lto)
|
|
callee_summary_lto = summaries_lto->get (callee);
|
|
}
|
|
changed |= modref_merge_call_site_flags
|
|
(sum, cur_summary, cur_summary_lto,
|
|
callee_summary, callee_summary_lto,
|
|
node->decl,
|
|
callee_edge,
|
|
caller_ecf_flags,
|
|
ecf_flags,
|
|
callee->binds_to_current_def_p ());
|
|
if (dump_file && changed)
|
|
{
|
|
if (cur_summary)
|
|
cur_summary->dump (dump_file);
|
|
if (cur_summary_lto)
|
|
cur_summary_lto->dump (dump_file);
|
|
}
|
|
}
|
|
}
|
|
iteration++;
|
|
}
|
|
if (dump_file)
|
|
fprintf (dump_file,
|
|
"Propagation of flags finished in %i iterations\n", iteration);
|
|
}
|
|
|
|
} /* ANON namespace. */
|
|
|
|
/* Call EDGE was inlined; merge summary from callee to the caller. */
|
|
|
|
void
|
|
ipa_merge_modref_summary_after_inlining (cgraph_edge *edge)
|
|
{
|
|
if (!summaries && !summaries_lto)
|
|
return;
|
|
|
|
struct cgraph_node *to = (edge->caller->inlined_to
|
|
? edge->caller->inlined_to : edge->caller);
|
|
class modref_summary *to_info = summaries ? summaries->get (to) : NULL;
|
|
class modref_summary_lto *to_info_lto = summaries_lto
|
|
? summaries_lto->get (to) : NULL;
|
|
|
|
if (!to_info && !to_info_lto)
|
|
{
|
|
if (summaries)
|
|
summaries->remove (edge->callee);
|
|
if (summaries_lto)
|
|
summaries_lto->remove (edge->callee);
|
|
remove_modref_edge_summaries (edge->callee);
|
|
return;
|
|
}
|
|
|
|
class modref_summary *callee_info = summaries ? summaries->get (edge->callee)
|
|
: NULL;
|
|
class modref_summary_lto *callee_info_lto
|
|
= summaries_lto ? summaries_lto->get (edge->callee) : NULL;
|
|
int flags = flags_from_decl_or_type (edge->callee->decl);
|
|
/* Combine in outer flags. */
|
|
cgraph_node *n;
|
|
for (n = edge->caller; n->inlined_to; n = n->callers->caller)
|
|
flags |= flags_from_decl_or_type (n->decl);
|
|
flags |= flags_from_decl_or_type (n->decl);
|
|
bool ignore_stores = ignore_stores_p (edge->caller->decl, flags);
|
|
|
|
if (!callee_info && to_info)
|
|
{
|
|
if (!(flags & (ECF_CONST | ECF_NOVOPS)))
|
|
to_info->loads->collapse ();
|
|
if (!ignore_stores)
|
|
to_info->stores->collapse ();
|
|
}
|
|
if (!callee_info_lto && to_info_lto)
|
|
{
|
|
if (!(flags & (ECF_CONST | ECF_NOVOPS)))
|
|
to_info_lto->loads->collapse ();
|
|
if (!ignore_stores)
|
|
to_info_lto->stores->collapse ();
|
|
}
|
|
/* Merge side effects and non-determinism.
|
|
PURE/CONST flags makes functions deterministic and if there is
|
|
no LOOPING_CONST_OR_PURE they also have no side effects. */
|
|
if (!(flags & (ECF_CONST | ECF_NOVOPS | ECF_PURE))
|
|
|| (flags & ECF_LOOPING_CONST_OR_PURE))
|
|
{
|
|
if (to_info)
|
|
{
|
|
if (!callee_info || callee_info->side_effects)
|
|
to_info->side_effects = true;
|
|
if ((!callee_info || callee_info->nondeterministic)
|
|
&& !ignore_nondeterminism_p (edge->caller->decl, flags))
|
|
to_info->nondeterministic = true;
|
|
}
|
|
if (to_info_lto)
|
|
{
|
|
if (!callee_info_lto || callee_info_lto->side_effects)
|
|
to_info_lto->side_effects = true;
|
|
if ((!callee_info_lto || callee_info_lto->nondeterministic)
|
|
&& !ignore_nondeterminism_p (edge->caller->decl, flags))
|
|
to_info_lto->nondeterministic = true;
|
|
}
|
|
}
|
|
if (callee_info || callee_info_lto)
|
|
{
|
|
auto_vec <modref_parm_map, 32> parm_map;
|
|
modref_parm_map chain_map;
|
|
/* TODO: Once we get jump functions for static chains we could
|
|
compute parm_index. */
|
|
|
|
compute_parm_map (edge, &parm_map);
|
|
|
|
if (!ignore_stores)
|
|
{
|
|
if (to_info && callee_info)
|
|
to_info->stores->merge (to->decl, callee_info->stores, &parm_map,
|
|
&chain_map, false);
|
|
if (to_info_lto && callee_info_lto)
|
|
to_info_lto->stores->merge (to->decl, callee_info_lto->stores,
|
|
&parm_map, &chain_map, false);
|
|
}
|
|
if (!(flags & (ECF_CONST | ECF_NOVOPS)))
|
|
{
|
|
if (to_info && callee_info)
|
|
to_info->loads->merge (to->decl, callee_info->loads, &parm_map,
|
|
&chain_map, false);
|
|
if (to_info_lto && callee_info_lto)
|
|
to_info_lto->loads->merge (to->decl, callee_info_lto->loads,
|
|
&parm_map, &chain_map, false);
|
|
}
|
|
}
|
|
|
|
/* Now merge escape summaries.
|
|
For every escape to the callee we need to merge callee flags
|
|
and remap callee's escapes. */
|
|
class escape_summary *sum = escape_summaries->get (edge);
|
|
int max_escape = -1;
|
|
escape_entry *ee;
|
|
unsigned int i;
|
|
|
|
if (sum && !(flags & (ECF_CONST | ECF_NOVOPS)))
|
|
FOR_EACH_VEC_ELT (sum->esc, i, ee)
|
|
if ((int)ee->arg > max_escape)
|
|
max_escape = ee->arg;
|
|
|
|
auto_vec <vec <struct escape_map>, 32> emap (max_escape + 1);
|
|
emap.safe_grow (max_escape + 1, true);
|
|
for (i = 0; (int)i < max_escape + 1; i++)
|
|
emap[i] = vNULL;
|
|
|
|
if (sum && !(flags & (ECF_CONST | ECF_NOVOPS)))
|
|
FOR_EACH_VEC_ELT (sum->esc, i, ee)
|
|
{
|
|
bool needed = false;
|
|
int implicit_flags = implicit_eaf_flags_for_edge_and_arg
|
|
(edge, flags, ignore_stores,
|
|
ee->arg);
|
|
if (!ee->direct)
|
|
implicit_flags = deref_flags (implicit_flags, ignore_stores);
|
|
if (to_info && (int)to_info->arg_flags.length () > ee->parm_index)
|
|
{
|
|
int flags = callee_info
|
|
&& callee_info->arg_flags.length () > ee->arg
|
|
? callee_info->arg_flags[ee->arg] : 0;
|
|
if (!ee->direct)
|
|
flags = deref_flags (flags, ignore_stores);
|
|
flags |= ee->min_flags | implicit_flags;
|
|
eaf_flags_t &f = ee->parm_index == MODREF_RETSLOT_PARM
|
|
? to_info->retslot_flags
|
|
: ee->parm_index == MODREF_STATIC_CHAIN_PARM
|
|
? to_info->static_chain_flags
|
|
: to_info->arg_flags[ee->parm_index];
|
|
f &= flags;
|
|
if (f)
|
|
needed = true;
|
|
}
|
|
if (to_info_lto
|
|
&& (int)to_info_lto->arg_flags.length () > ee->parm_index)
|
|
{
|
|
int flags = callee_info_lto
|
|
&& callee_info_lto->arg_flags.length () > ee->arg
|
|
? callee_info_lto->arg_flags[ee->arg] : 0;
|
|
if (!ee->direct)
|
|
flags = deref_flags (flags, ignore_stores);
|
|
flags |= ee->min_flags | implicit_flags;
|
|
eaf_flags_t &f = ee->parm_index == MODREF_RETSLOT_PARM
|
|
? to_info_lto->retslot_flags
|
|
: ee->parm_index == MODREF_STATIC_CHAIN_PARM
|
|
? to_info_lto->static_chain_flags
|
|
: to_info_lto->arg_flags[ee->parm_index];
|
|
f &= flags;
|
|
if (f)
|
|
needed = true;
|
|
}
|
|
struct escape_map entry = {ee->parm_index, ee->direct};
|
|
if (needed)
|
|
emap[ee->arg].safe_push (entry);
|
|
}
|
|
update_escape_summary (edge->callee, emap, ignore_stores);
|
|
for (i = 0; (int)i < max_escape + 1; i++)
|
|
emap[i].release ();
|
|
if (sum)
|
|
escape_summaries->remove (edge);
|
|
|
|
if (summaries)
|
|
{
|
|
if (to_info && !to_info->useful_p (flags))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Removed mod-ref summary for %s\n",
|
|
to->dump_name ());
|
|
summaries->remove (to);
|
|
to_info = NULL;
|
|
}
|
|
else if (to_info && dump_file)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Updated mod-ref summary for %s\n",
|
|
to->dump_name ());
|
|
to_info->dump (dump_file);
|
|
}
|
|
if (callee_info)
|
|
summaries->remove (edge->callee);
|
|
}
|
|
if (summaries_lto)
|
|
{
|
|
if (to_info_lto && !to_info_lto->useful_p (flags))
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Removed mod-ref summary for %s\n",
|
|
to->dump_name ());
|
|
summaries_lto->remove (to);
|
|
to_info_lto = NULL;
|
|
}
|
|
else if (to_info_lto && dump_file)
|
|
{
|
|
if (dump_file)
|
|
fprintf (dump_file, "Updated mod-ref summary for %s\n",
|
|
to->dump_name ());
|
|
to_info_lto->dump (dump_file);
|
|
}
|
|
if (callee_info_lto)
|
|
summaries_lto->remove (edge->callee);
|
|
}
|
|
if (!to_info && !to_info_lto)
|
|
remove_modref_edge_summaries (to);
|
|
return;
|
|
}
|
|
|
|
/* Run the IPA pass. This will take a function's summaries and calls and
|
|
construct new summaries which represent a transitive closure. So that
|
|
summary of an analyzed function contains information about the loads and
|
|
stores that the function or any function that it calls does. */
|
|
|
|
unsigned int
|
|
pass_ipa_modref::execute (function *)
|
|
{
|
|
if (!summaries && !summaries_lto)
|
|
return 0;
|
|
bool pureconst = false;
|
|
|
|
if (optimization_summaries)
|
|
ggc_delete (optimization_summaries);
|
|
optimization_summaries = summaries;
|
|
summaries = NULL;
|
|
|
|
struct cgraph_node **order = XCNEWVEC (struct cgraph_node *,
|
|
symtab->cgraph_count);
|
|
int order_pos;
|
|
order_pos = ipa_reduced_postorder (order, true, ignore_edge);
|
|
int i;
|
|
|
|
/* Iterate over all strongly connected components in post-order. */
|
|
for (i = 0; i < order_pos; i++)
|
|
{
|
|
/* Get the component's representative. That's just any node in the
|
|
component from which we can traverse the entire component. */
|
|
struct cgraph_node *component_node = order[i];
|
|
|
|
if (dump_file)
|
|
fprintf (dump_file, "\n\nStart of SCC component\n");
|
|
|
|
pureconst |= modref_propagate_in_scc (component_node);
|
|
modref_propagate_flags_in_scc (component_node);
|
|
if (optimization_summaries)
|
|
for (struct cgraph_node *cur = component_node; cur;
|
|
cur = ((struct ipa_dfs_info *) cur->aux)->next_cycle)
|
|
if (modref_summary *sum = optimization_summaries->get (cur))
|
|
sum->finalize (cur->decl);
|
|
if (dump_file)
|
|
modref_propagate_dump_scc (component_node);
|
|
}
|
|
cgraph_node *node;
|
|
FOR_EACH_FUNCTION (node)
|
|
update_signature (node);
|
|
if (summaries_lto)
|
|
((modref_summaries_lto *)summaries_lto)->propagated = true;
|
|
ipa_free_postorder_info ();
|
|
free (order);
|
|
delete fnspec_summaries;
|
|
fnspec_summaries = NULL;
|
|
delete escape_summaries;
|
|
escape_summaries = NULL;
|
|
|
|
/* If we possibly made constructors const/pure we may need to remove
|
|
them. */
|
|
return pureconst ? TODO_remove_functions : 0;
|
|
}
|
|
|
|
/* Summaries must stay alive until end of compilation. */
|
|
|
|
void
|
|
ipa_modref_cc_finalize ()
|
|
{
|
|
if (optimization_summaries)
|
|
ggc_delete (optimization_summaries);
|
|
optimization_summaries = NULL;
|
|
if (summaries_lto)
|
|
ggc_delete (summaries_lto);
|
|
summaries_lto = NULL;
|
|
if (fnspec_summaries)
|
|
delete fnspec_summaries;
|
|
fnspec_summaries = NULL;
|
|
if (escape_summaries)
|
|
delete escape_summaries;
|
|
escape_summaries = NULL;
|
|
}
|
|
|
|
#include "gt-ipa-modref.h"
|