From: Philipp Tomsich <[email protected]>
Record an indirect call through a non-volatile function-pointer field
of an object passed by pointer as a "deferred call" in the local modref
summary, given by the parameter index and the field offset. Also record
the stores the function makes when these calls are ignored
(deferred_clean_stores), computed by a second run of the access
analysis that skips them. That run treats a self-recursive call as
unknown, since callers do not resolve the deferred calls of the
recursive invocation. A caller that knows the targets of the deferred
calls can combine this with the targets' summaries to prove that the
call does not write a given object.
Only the modref pass before IPA records the new fields. They are not
streamed, since their users run before the summaries are written.
The fields have no user yet, so on its own this patch only adds the
second analysis for functions that make deferred calls.
Bootstrapped and regtested on aarch64-linux-gnu and x86_64-linux-gnu.
PR ipa/126241
gcc/ChangeLog:
* ipa-modref.cc (modref_summary::modref_summary): Initialize
deferred_clean_stores.
(modref_summary::~modref_summary): Free it.
(modref_summary::useful_p): Keep summaries with deferred calls.
(modref_summary::dump): Dump the new fields.
(modref_access_analysis::deferred_dispatch_call_p): New.
(modref_access_analysis::analyze_call): Record deferred calls
before IPA, or skip them and treat recursive calls as unknown when
m_skip_deferred is set.
(analyze_function): Compute deferred_clean_stores.
* ipa-modref.h (struct modref_deferred_call): New.
(struct modref_summary): Add deferred_calls and
deferred_clean_stores.
Signed-off-by: Konstantinos Eleftheriou <[email protected]>
---
gcc/ipa-modref.cc | 112 ++++++++++++++++++++++++++++++++++++++++++++--
gcc/ipa-modref.h | 21 +++++++++
2 files changed, 130 insertions(+), 3 deletions(-)
diff --git a/gcc/ipa-modref.cc b/gcc/ipa-modref.cc
index 7883a90bcdd0..e9236af28954 100644
--- a/gcc/ipa-modref.cc
+++ b/gcc/ipa-modref.cc
@@ -277,7 +277,8 @@ static GTY(()) fast_function_summary <modref_summary_lto *,
va_gc>
/* Summary for a single function which this pass produces. */
modref_summary::modref_summary ()
- : loads (NULL), stores (NULL), retslot_flags (0), static_chain_flags (0),
+ : loads (NULL), stores (NULL), deferred_clean_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)
@@ -290,6 +291,8 @@ modref_summary::~modref_summary ()
ggc_delete (loads);
if (stores)
ggc_delete (stores);
+ if (deferred_clean_stores)
+ ggc_delete (deferred_clean_stores);
}
/* Remove all flags from EAF_FLAGS that are implied by ECF_FLAGS and not
@@ -326,6 +329,9 @@ modref_summary::useful_p (int ecf_flags, bool check_flags)
{
if (arg_flags.length () && !check_flags)
return true;
+ /* A caller that resolves the deferred calls can still use the summary. */
+ if (deferred_calls.length ())
+ return true;
if (check_flags && eaf_flags_useful_p (arg_flags, ecf_flags))
return true;
arg_flags.release ();
@@ -600,6 +606,18 @@ modref_summary::dump (FILE *out) const
fprintf (out, " Global memory written\n");
if (try_dse)
fprintf (out, " Try dse\n");
+ if (deferred_calls.length ())
+ {
+ fprintf (out, " deferred calls:\n");
+ for (auto dc : deferred_calls)
+ fprintf (out, " through parm %i field offset "
+ HOST_WIDE_INT_PRINT_DEC "\n", dc.parm_index, dc.field_offset);
+ }
+ if (deferred_clean_stores)
+ {
+ fprintf (out, " stores ignoring deferred calls:\n");
+ dump_records (deferred_clean_stores, out);
+ }
if (arg_flags.length ())
{
for (unsigned int i = 0; i < arg_flags.length (); i++)
@@ -947,8 +965,10 @@ 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)
+ modref_summary_lto *summary_lto,
+ bool skip_deferred = false)
+ : m_summary (summary), m_summary_lto (summary_lto), m_ipa (ipa),
+ m_skip_deferred (skip_deferred)
{
}
void analyze ();
@@ -969,6 +989,7 @@ private:
modref_access_node get_access_for_fnspec (gcall *, attr_fnspec &,
unsigned int, modref_parm_map &);
void process_fnspec (gcall *);
+ static bool deferred_dispatch_call_p (gcall *, modref_deferred_call *);
void analyze_call (gcall *);
static bool analyze_load (gimple *, tree, tree, void *);
static bool analyze_store (gimple *, tree, tree, void *);
@@ -987,6 +1008,8 @@ private:
int m_ecf_flags;
/* True if IPA propagation will be done later. */
bool m_ipa;
+ /* True if deferred dispatch calls are ignored. */
+ bool m_skip_deferred;
/* Set true if statement currently analyze is known to be
executed each time function is called. */
bool m_always_executed;
@@ -1600,6 +1623,47 @@ modref_access_analysis::process_fnspec (gcall *call)
}
}
+/* If CALL calls a function pointer loaded from a non-volatile field at a fixed
+ offset in an object passed by pointer, return true and describe the call in
+ *DC. */
+
+bool
+modref_access_analysis::deferred_dispatch_call_p (gcall *call,
+ modref_deferred_call *dc)
+{
+ tree fnptr = gimple_call_fn (call);
+ if (!fnptr || TREE_CODE (fnptr) != SSA_NAME)
+ return false;
+
+ gimple *def = SSA_NAME_DEF_STMT (fnptr);
+ if (!gimple_assign_load_p (def))
+ return false;
+
+ ao_ref ref;
+ ao_ref_init (&ref, gimple_assign_rhs1 (def));
+ if (ref.volatile_p)
+ return false;
+
+ modref_access_node access = get_access (&ref);
+ if (access.parm_index < 0
+ || !access.parm_offset_known
+ || !known_eq (access.size, access.max_size))
+ return false;
+
+ /* PARM_OFFSET can be negative, so do not shift it as poly_int64. */
+ poly_int64 off;
+ HOST_WIDE_INT foff;
+ if (!((poly_offset_int) access.offset
+ + ((poly_offset_int) access.parm_offset << LOG2_BITS_PER_UNIT))
+ .to_shwi (&off)
+ || !off.is_constant (&foff))
+ return false;
+
+ dc->parm_index = access.parm_index;
+ dc->field_offset = foff;
+ return true;
+}
+
/* Analyze function call STMT in function F.
Remember recursive calls in RECURSIVE_CALLS. */
@@ -1636,6 +1700,26 @@ modref_access_analysis::analyze_call (gcall *stmt)
if (dump_file)
fprintf (dump_file, gimple_call_internal_p (stmt)
? " - Internal call" : " - Indirect call.\n");
+ /* Record a call through a function-pointer field of an object passed
+ by pointer, for callers that know the field's value. They use only
+ the local summary from before IPA. process_fnspec still records the
+ worst case, except in the analysis that skips the deferred calls. */
+ modref_deferred_call dc;
+ if (!m_ipa
+ && !cfun->after_inlining
+ && deferred_dispatch_call_p (stmt, &dc))
+ {
+ if (m_skip_deferred)
+ return;
+
+ if (m_summary && !m_summary->deferred_calls.contains (dc))
+ m_summary->deferred_calls.safe_push (dc);
+ if (dump_file)
+ fprintf (dump_file,
+ " - Recorded deferred call through parm %i "
+ "field offset " HOST_WIDE_INT_PRINT_DEC "\n",
+ dc.parm_index, dc.field_offset);
+ }
process_fnspec (stmt);
return;
}
@@ -1648,6 +1732,14 @@ modref_access_analysis::analyze_call (gcall *stmt)
there's nothing to do. */
if (recursive_call_p (current_function_decl, callee))
{
+ /* Callers resolve only the deferred calls of this invocation, not
+ those of the recursive one. */
+ if (m_skip_deferred)
+ {
+ process_fnspec (stmt);
+ return;
+ }
+
m_recursive_calls.safe_push (stmt);
set_side_effects ();
if (dump_file)
@@ -3241,6 +3333,20 @@ analyze_function (bool ipa)
analyzer.analyze ();
}
+ /* Compute the stores of the function without its deferred calls. */
+ if (summary && summary->deferred_calls.length ())
+ {
+ if (dump_file)
+ fprintf (dump_file, "\nAnalyzing the stores without deferred calls\n");
+ modref_summary clean;
+ clean.loads = modref_records::create_ggc ();
+ clean.stores = modref_records::create_ggc ();
+ modref_access_analysis analyzer (ipa, &clean, NULL, true);
+ analyzer.analyze ();
+ summary->deferred_clean_stores = clean.stores;
+ clean.stores = NULL;
+ }
+
if (!ipa && flag_ipa_pure_const)
{
if (!summary->stores->every_base && !summary->stores->bases
diff --git a/gcc/ipa-modref.h b/gcc/ipa-modref.h
index 8558dff8f2ca..4d5051ea841e 100644
--- a/gcc/ipa-modref.h
+++ b/gcc/ipa-modref.h
@@ -23,6 +23,20 @@ along with GCC; see the file COPYING3. If not see
typedef modref_tree <alias_set_type> modref_records;
typedef unsigned short eaf_flags_t;
+/* A call through a function-pointer field of an object passed by pointer,
+ such as std::function's _M_invoker. */
+struct modref_deferred_call
+{
+ int parm_index;
+ HOST_WIDE_INT field_offset; /* In bits. */
+
+ bool operator== (const modref_deferred_call &other) const
+ {
+ return (parm_index == other.parm_index
+ && field_offset == other.field_offset);
+ }
+};
+
/* Single function summary. */
struct GTY(()) modref_summary
@@ -30,8 +44,15 @@ struct GTY(()) modref_summary
/* Load and stores in function (transitively closed to all callees) */
modref_records *loads;
modref_records *stores;
+ /* Stores ignoring DEFERRED_CALLS. */
+ modref_records *deferred_clean_stores;
auto_vec<modref_access_node> GTY((skip)) kills;
auto_vec<eaf_flags_t> GTY((skip)) arg_flags;
+ /* Calls that a caller knowing the function-pointer fields can resolve.
+ Like DEFERRED_CLEAN_STORES, set only in the local summaries from before
+ IPA. IPA passes never update those summaries, clones get none, and
+ pass_ipa_modref replaces them. */
+ auto_vec<modref_deferred_call> GTY((skip)) deferred_calls;
eaf_flags_t retslot_flags;
eaf_flags_t static_chain_flags;
--
2.55.0