public:
cgraph_simple_indirect_info (int flags)
: cgraph_indirect_call_info (CIIK_SIMPLE, flags), offset (0),
- agg_contents (false), member_ptr (false), by_ref (false),
+ rec_type (NULL_TREE), fld_offset (0), agg_contents (false),
+ member_ptr (false), fnptr_loaded_from_record (false), by_ref (false),
guaranteed_unmodified (false)
{}
/* When agg_content is set, an offset where the call pointer is located
within the aggregate. */
HOST_WIDE_INT offset;
+ /* Only meaningful if fnptr_loaded_from_record is set. Then it contains the
+ type of the record from which the target of the call was loaded. */
+ tree rec_type;
+ /* Only meaningful if fnptr_loaded_from_record is set. Then it contains the
+ offset in bytes within the type above from which the target of the call
+ was loaded. */
+ unsigned fld_offset;
/* Set when the call is a call of a pointer loaded from contents of an
aggregate at offset. */
unsigned agg_contents : 1;
/* Set when this is a call through a member pointer. */
unsigned member_ptr : 1;
+ /* Set if the function is a call of a pointer loaded from a record type
+ stored in otr_type at offset offset. */
+ unsigned fnptr_loaded_from_record : 1;
/* When the agg_contents bit is set, this one determines whether the
destination is loaded from a parameter passed by reference. */
unsigned by_ref : 1;
Common Var(flag_loop_nest_optimize) Optimization
Enable the loop nest optimizer.
+fspeculatively-call-stored-functions
+Common Var(flag_speculatively_call_stored_functions) Optimization
+Turn indirect calls of pointers loaded from structures to speculative calls if there is one known candidate.
+
fstrict-volatile-bitfields
Common Var(flag_strict_volatile_bitfields) Init(-1) Optimization
Force bitfield accesses to match their type width.
-fsemantic-interposition -fshrink-wrap -fshrink-wrap-separate
-fsignaling-nans
-fsingle-precision-constant -fsplit-ivs-in-unroller -fsplit-loops
--fsplit-paths
+-fspeculatively-call-stored-functions -fsplit-paths
-fsplit-wide-types -fsplit-wide-types-early -fssa-backprop -fssa-phiopt
-fstdarg-opt -fstore-merging -fstrict-aliasing -fipa-strict-aliasing
-fthread-jumps -ftracer -ftree-bit-ccp
-frerun-cse-after-loop
-fschedule-insns -fschedule-insns2
-fsched-interblock -fsched-spec
+-fspeculatively-call-stored-functions
-fstore-merging
-fstrict-aliasing
-fthread-jumps
with @option{-fschedule-insns} or @option{-fschedule-insns2} or
at @option{-O2} or higher.
+@opindex fspeculatively-call-stored-functions
+@item -fspeculatively-call-stored-functions
+Attempt to convert indirect calls of function pointers to pointers
+loaded from a structure field if all visible stores to that field store
+just a single candidate. When doing so, turn the call into a
+conditional deciding between the direct call and the original indirect
+one. These speculative calls often enable more optimizations, such as
+inlining. When they seem useless after further optimization, they are
+converted back into original form.
+
@opindex freschedule-modulo-scheduled-loops
@item -freschedule-modulo-scheduled-loops
Modulo scheduling is performed before traditional scheduling. If a loop
FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
ipa_analyze_node (node);
+
+ varpool_node *vnode;
+ FOR_EACH_STATIC_INITIALIZER (vnode)
+ ipa_analyze_var_static_initializer (vnode);
}
namespace {
return likely_target;
}
+/* Various statistics counters collected during devirtualization. */
+
+struct devirt_stats
+{
+ int npolymorphic, nspeculated, nconverted, ncold;
+ int nmultiple, noverwritable, ndevirtualized, nnotdefined;
+ int nwrong, nok, nexternal, nartificial;
+ int ndropped;
+};
+
+/* Check LIKELY_TARGET and return true if it a suitable target for
+ devirtualization or speculative devirtualization. Increase the respective
+ counter in STATS if any check fails. */
+
+static bool
+devirt_target_ok_p (cgraph_node *likely_target, struct devirt_stats *stats)
+{
+ if (!likely_target->definition)
+ {
+ if (dump_file)
+ fprintf (dump_file, "Target is not a definition\n\n");
+ stats->nnotdefined++;
+ return false;
+ }
+ /* Do not introduce new references to external symbols. While we
+ can handle these just well, it is common for programs to
+ incorrectly with headers defining methods they are linked
+ with. */
+ if (DECL_EXTERNAL (likely_target->decl))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Target is external\n\n");
+ stats->nexternal++;
+ return false;
+ }
+ /* Don't use an implicitly-declared destructor (c++/58678). */
+ struct cgraph_node *non_thunk_target
+ = likely_target->function_symbol ();
+ if (DECL_ARTIFICIAL (non_thunk_target->decl))
+ {
+ if (dump_file)
+ fprintf (dump_file, "Target is artificial\n\n");
+ stats->nartificial++;
+ return false;
+ }
+ if (likely_target->get_availability () <= AVAIL_INTERPOSABLE
+ && likely_target->can_be_discarded_p ())
+ {
+ if (dump_file)
+ fprintf (dump_file, "Target is overwritable\n\n");
+ stats->noverwritable++;
+ return false;
+ }
+ return true;
+}
+
/* The ipa-devirt pass.
When polymorphic call has only one likely target in the unit,
turn it into a speculative call. */
struct cgraph_node *n;
hash_set<void *> bad_call_targets;
struct cgraph_edge *e;
-
- int npolymorphic = 0, nspeculated = 0, nconverted = 0, ncold = 0;
- int nmultiple = 0, noverwritable = 0, ndevirtualized = 0, nnotdefined = 0;
- int nwrong = 0, nok = 0, nexternal = 0, nartificial = 0;
- int ndropped = 0;
-
- if (!odr_types_ptr)
- return 0;
+ struct devirt_stats stats;
+ memset (&stats, 0, sizeof (stats));
if (dump_file)
- dump_type_inheritance_graph (dump_file);
+ {
+ dump_type_inheritance_graph (dump_file);
+ ipa_dump_noted_record_fnptrs (dump_file);
+ }
/* We can output -Wsuggest-final-methods and -Wsuggest-final-types warnings.
This is implemented by setting up final_warning_records that are updated
by get_polymorphic_call_targets.
We need to clear cache in this case to trigger recomputation of all
entries. */
- if (warn_suggest_final_methods || warn_suggest_final_types)
+ if (odr_types_ptr && (warn_suggest_final_methods || warn_suggest_final_types))
{
final_warning_records = new (final_warning_record);
final_warning_records->dyn_count = profile_count::zero ();
fprintf (dump_file, "\n\nProcesing function %s\n",
n->dump_name ());
for (e = n->indirect_calls; e; e = e->next_callee)
- if (cgraph_polymorphic_indirect_info *pii
+ if (!e->maybe_hot_p ())
+ {
+ if (dump_file)
+ fprintf (dump_file, "Call is cold\n\n");
+ stats.ncold++;
+ continue;
+ }
+ else if (cgraph_polymorphic_indirect_info *pii
= dyn_cast <cgraph_polymorphic_indirect_info *> (e->indirect_info))
{
- if (!pii->usable_p ())
+ if (!pii->usable_p () || !odr_types_ptr)
continue;
void *cache_token;
dump_possible_polymorphic_call_targets
(dump_file, e, (dump_flags & TDF_DETAILS));
- npolymorphic++;
+ stats.npolymorphic++;
/* See if the call can be devirtualized by means of ipa-prop's
polymorphic call context propagation. If not, we can just
&& !flag_ltrans_devirtualize)
{
pii->mark_unusable ();
- ndropped++;
+ stats.ndropped++;
if (dump_file)
fprintf (dump_file, "Dropping polymorphic call info;"
" it cannot be used by ipa-prop\n");
if (!opt_for_fn (n->decl, flag_devirtualize_speculatively))
continue;
- if (!e->maybe_hot_p ())
- {
- if (dump_file)
- fprintf (dump_file, "Call is cold\n\n");
- ncold++;
- continue;
- }
if (e->speculative)
{
if (dump_file)
fprintf (dump_file, "Call is already speculated\n\n");
- nspeculated++;
+ stats.nspeculated++;
/* When dumping see if we agree with speculation. */
if (!dump_file)
{
if (dump_file)
fprintf (dump_file, "Target list is known to be useless\n\n");
- nmultiple++;
+ stats.nmultiple++;
continue;
}
auto_vec <cgraph_node *, 20> likely_targets;
if (dump_file)
fprintf (dump_file, "More than %i likely targets\n\n",
param_max_devirt_targets);
- nmultiple++;
+ stats.nmultiple++;
break;
}
likely_targets.safe_push (targets[i]);
if (found)
{
fprintf (dump_file, "We agree with speculation\n\n");
- nok++;
+ stats.nok++;
}
else
{
fprintf (dump_file, "We disagree with speculation\n\n");
- nwrong++;
+ stats.nwrong++;
}
continue;
}
unsigned speculative_id = 0;
for (cgraph_node * likely_target: likely_targets)
{
- if (!likely_target->definition)
- {
- if (dump_file)
- fprintf (dump_file, "Target is not a definition\n\n");
- nnotdefined++;
- continue;
- }
- /* Do not introduce new references to external symbols. While we
- can handle these just well, it is common for programs to
- incorrectly with headers defining methods they are linked
- with. */
- if (DECL_EXTERNAL (likely_target->decl))
- {
- if (dump_file)
- fprintf (dump_file, "Target is external\n\n");
- nexternal++;
- continue;
- }
- /* Don't use an implicitly-declared destructor (c++/58678). */
- struct cgraph_node *non_thunk_target
- = likely_target->function_symbol ();
- if (DECL_ARTIFICIAL (non_thunk_target->decl))
- {
- if (dump_file)
- fprintf (dump_file, "Target is artificial\n\n");
- nartificial++;
- continue;
- }
- if (likely_target->get_availability () <= AVAIL_INTERPOSABLE
- && likely_target->can_be_discarded_p ())
- {
- if (dump_file)
- fprintf (dump_file, "Target is overwritable\n\n");
- noverwritable++;
- continue;
- }
+ if (!devirt_target_ok_p (likely_target, &stats))
+ continue;
else if (dbg_cnt (devirt))
{
if (dump_enabled_p ())
likely_target = alias;
}
if (first)
- nconverted++;
+ stats.nconverted++;
first = false;
update = true;
e->make_speculative
speculative_id);
}
}
+ else if (cgraph_simple_indirect_info *sii
+ = dyn_cast <cgraph_simple_indirect_info *> (e->indirect_info))
+ {
+ if (!sii->fnptr_loaded_from_record
+ || !opt_for_fn (n->decl,
+ flag_speculatively_call_stored_functions))
+ continue;
+
+ tree rec_type = sii->rec_type;
+ unsigned fld_off = sii->fld_offset;
+ tree likely_tgt_decl = ipa_single_noted_fnptr_in_record (rec_type,
+ fld_off);
+ cgraph_node *likely_tgt_node;
+ if (likely_tgt_decl
+ && (likely_tgt_node = cgraph_node::get (likely_tgt_decl))
+ && devirt_target_ok_p (likely_tgt_node, &stats))
+ {
+ if (!likely_tgt_node->can_be_discarded_p ())
+ {
+ cgraph_node *alias;
+ alias = dyn_cast<cgraph_node *> (likely_tgt_node
+ ->noninterposable_alias ());
+ if (alias)
+ likely_tgt_node = alias;
+ }
+
+ if (dump_enabled_p ())
+ dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, e->call_stmt,
+ "speculatively turning an indirect call "
+ "in %s to a direct one to %s\n",
+ n->dump_name (),
+ likely_tgt_node->dump_name ());
+
+ update = true;
+ e->make_speculative (likely_tgt_node,
+ e->count.apply_scale (8, 10));
+ }
+ }
if (update)
ipa_update_overall_fn_summary (n);
}
- if (warn_suggest_final_methods || warn_suggest_final_types)
+ ipa_free_noted_fnptr_calls ();
+ if (odr_types_ptr && (warn_suggest_final_methods || warn_suggest_final_types))
{
if (warn_suggest_final_types)
{
"%i have multiple targets, %i overwritable,"
" %i already speculated (%i agree, %i disagree),"
" %i external, %i not defined, %i artificial, %i infos dropped\n",
- npolymorphic, ndevirtualized, nconverted, ncold,
- nmultiple, noverwritable, nspeculated, nok, nwrong,
- nexternal, nnotdefined, nartificial, ndropped);
- return ndevirtualized || ndropped ? TODO_remove_functions : 0;
+ stats.npolymorphic, stats.ndevirtualized, stats.nconverted,
+ stats.ncold, stats.nmultiple, stats.noverwritable,
+ stats.nspeculated, stats.nok, stats.nwrong,
+ stats.nexternal, stats.nnotdefined, stats.nartificial,
+ stats.ndropped);
+ return stats.ndevirtualized || stats.ndropped ? TODO_remove_functions : 0;
}
namespace {
pass is enabled. */
if (in_lto_p)
return true;
- return (flag_devirtualize
- && (flag_devirtualize_speculatively
- || (warn_suggest_final_methods
- || warn_suggest_final_types))
- && optimize);
+ return (optimize
+ && ((flag_devirtualize
+ && (flag_devirtualize_speculatively
+ || (warn_suggest_final_methods
+ || warn_suggest_final_types)))
+ || flag_speculatively_call_stored_functions));
}
unsigned int execute (function *) final override { return ipa_devirt (); }
}
};
+/* Structure holding the information that all stores to FLD_OFFSET (measured in
+ bytes) of a particular record type REC_TYPE was storing a pointer to
+ function FN or that there were multiple functions, which is denoted by fn
+ being nullptr. */
+
+struct GTY((for_user)) noted_fnptr_store
+{
+ tree rec_type;
+ tree fn;
+ unsigned fld_offset;
+};
+
+/* Hash traits to have a hash table of noted_fnptr_stores. */
+
+struct noted_fnptr_hasher : ggc_ptr_hash <noted_fnptr_store>
+{
+ static hashval_t hash (noted_fnptr_store *);
+ static bool equal (noted_fnptr_store *,
+ noted_fnptr_store *);
+};
+
+hashval_t
+noted_fnptr_hasher::hash (noted_fnptr_store *val)
+{
+ return iterative_hash_host_wide_int (val->fld_offset,
+ TYPE_UID (val->rec_type));
+}
+
+bool
+noted_fnptr_hasher::equal (noted_fnptr_store *v1,
+ noted_fnptr_store *v2)
+{
+ return (v1->rec_type == v2->rec_type
+ && v1->fld_offset == v2->fld_offset);
+}
+
+
+/* Structore holding the information that all stores to OFFSET of a particular
+ record type RECTYPE was storing a pointer to specific function or that there
+ were multiple such functions. */
+
+static GTY(()) hash_table <noted_fnptr_hasher> *noted_fnptrs_in_records;
+
/* Variable hoding the return value summary. */
static GTY(()) function_summary <ipa_return_value_summary *> *ipa_return_value_sum;
}
}
+/* If REF is a memory access that loads a function pointer (but not a method
+ pointer) from a RECORD_TYPE, return true and store the type of the RECORD to
+ *REC_TYPE and the byte offset of the field to *FLD_OFFSET. Otherwise return
+ false. OHS es the "other hand side" which is used to check type
+ compatibility with field in question, when possible. */
+
+static bool
+is_func_ptr_from_record (tree ref, tree *rec_type, unsigned *fld_offset,
+ tree ohs)
+{
+ if (!POINTER_TYPE_P (TREE_TYPE (ref))
+ || TREE_CODE (TREE_TYPE (TREE_TYPE (ref))) != FUNCTION_TYPE)
+ return false;
+
+ if (TREE_CODE (ref) == COMPONENT_REF
+ && TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 0))) == RECORD_TYPE)
+ {
+ gcc_assert (POINTER_TYPE_P (TREE_TYPE (ohs)));
+ ohs = TREE_TYPE (TREE_TYPE (ohs));
+ tree ftype = TREE_TYPE (TREE_OPERAND (ref, 1));
+ if (!POINTER_TYPE_P (ftype))
+ return false;
+ ftype = TREE_TYPE (ftype);
+ if (!types_compatible_p (ohs, ftype))
+ return false;
+
+ tree tree_off = bit_position (TREE_OPERAND (ref, 1));
+ if (!tree_fits_shwi_p (tree_off))
+ return false;
+ HOST_WIDE_INT bit_offset = tree_to_shwi (tree_off);
+ if (bit_offset % BITS_PER_UNIT)
+ return false;
+ HOST_WIDE_INT unit_offset = bit_offset / BITS_PER_UNIT;
+ if (unit_offset > UINT_MAX)
+ return false;
+ *rec_type = TREE_TYPE (TREE_OPERAND (ref, 0));
+ *fld_offset = unit_offset;
+ return true;
+ }
+ else if (TREE_CODE (ref) == MEM_REF
+ && POINTER_TYPE_P (TREE_TYPE (TREE_OPERAND (ref, 0)))
+ && (TREE_CODE (TREE_TYPE (TREE_TYPE (TREE_OPERAND (ref, 0))))
+ == RECORD_TYPE)
+ && tree_fits_shwi_p (TREE_OPERAND (ref, 1)))
+ {
+ HOST_WIDE_INT unit_offset = tree_to_shwi (TREE_OPERAND (ref, 1));
+ if (unit_offset > UINT_MAX)
+ return false;
+ *rec_type = TREE_TYPE (TREE_TYPE (TREE_OPERAND (ref, 0)));
+ *fld_offset = unit_offset;
+ return true;
+ }
+ return false;
+}
+
/* If STMT looks like a statement loading a value from a member pointer formal
parameter, return that parameter and store the offset of the field to
*OFFSET_P, if it is non-NULL. Otherwise return NULL (but *OFFSET_P still
int index;
gimple *def = SSA_NAME_DEF_STMT (target);
bool guaranteed_unmodified;
- if (gimple_assign_single_p (def)
- && ipa_load_from_parm_agg (fbi, info->descriptors, def,
- gimple_assign_rhs1 (def), &index, &offset,
- NULL, &by_ref, &guaranteed_unmodified))
+ if (gimple_assign_single_p (def))
{
cgraph_edge *cs = fbi->node->get_edge (call);
cgraph_simple_indirect_info *sii =
as_a <cgraph_simple_indirect_info *> (cs->indirect_info);
- sii->param_index = index;
- sii->offset = offset;
- sii->agg_contents = 1;
- sii->by_ref = by_ref;
- sii->guaranteed_unmodified = guaranteed_unmodified;
- gcc_assert (!sii->member_ptr);
- ipa_set_param_used_by_indirect_call (info, index, true);
- return;
+ tree rectype;
+ unsigned fldoff;
+ if (is_func_ptr_from_record (gimple_assign_rhs1 (def), &rectype, &fldoff,
+ target))
+ {
+ sii->fnptr_loaded_from_record = 1;
+ sii->fld_offset = fldoff;
+ sii->rec_type = rectype;
+ }
+ if (ipa_load_from_parm_agg (fbi, info->descriptors, def,
+ gimple_assign_rhs1 (def), &index, &offset,
+ NULL, &by_ref, &guaranteed_unmodified))
+ {
+ sii->param_index = index;
+ sii->offset = offset;
+ sii->agg_contents = 1;
+ sii->by_ref = by_ref;
+ sii->guaranteed_unmodified = guaranteed_unmodified;
+ ipa_set_param_used_by_indirect_call (info, index, true);
+ return;
+ }
}
/* Now we need to try to match the complex pattern of calling a member
ipa_analyze_virtual_call_uses (fbi, call, target);
}
+/* Store that that there was a store of FN to a record of type REC_TYPE and
+ FLD_OFFSET. */
+
+static void
+note_fnptr_in_record (tree rec_type, unsigned fld_offset, tree fn)
+{
+ gcc_assert (TREE_CODE (fn) == FUNCTION_DECL);
+ gcc_assert (TREE_CODE (rec_type) == RECORD_TYPE);
+ if (!noted_fnptrs_in_records)
+ noted_fnptrs_in_records = hash_table<noted_fnptr_hasher>::create_ggc (37);
+
+ noted_fnptr_store repr;
+ repr.rec_type = rec_type;
+ repr.fld_offset = fld_offset;
+
+ noted_fnptr_store **slot = noted_fnptrs_in_records->find_slot (&repr,
+ NO_INSERT);
+ if (slot)
+ {
+ if ((*slot)->fn && (*slot)->fn != fn)
+ (*slot)->fn = nullptr;
+ return;
+ }
+
+ slot = noted_fnptrs_in_records->find_slot (&repr, INSERT);
+ *slot = ggc_cleared_alloc<noted_fnptr_store> ();
+ (*slot)->rec_type = rec_type;
+ (*slot)->fn = fn;
+ (*slot)->fld_offset = fld_offset;
+
+ return;
+}
+
+/* Dump contents of noted_fnptrs_in_records to F in humad readable form. */
+
+void DEBUG_FUNCTION
+ipa_dump_noted_record_fnptrs (FILE *f)
+{
+ if (!noted_fnptrs_in_records)
+ {
+ fprintf (f, "No noted function pointers stored in records.\n\n");
+ return;
+ }
+
+ fprintf (f, "Noted function pointers stored in records:\n");
+ for (auto iter = noted_fnptrs_in_records->begin ();
+ iter != noted_fnptrs_in_records->end ();
+ ++iter)
+ {
+ const noted_fnptr_store *elem = *iter;
+ fprintf (f, " Type:");
+ print_generic_expr (f, elem->rec_type);
+ fprintf (f, ", offset %ul, function: ", elem->fld_offset);
+ print_generic_expr (f, elem->fn);
+ fprintf (f, "\n");
+ }
+ fprintf (f, "\n");
+}
+
+/* Dump contents of noted_fnptrs_in_records to stderr in humad readable
+ form. */
+
+void DEBUG_FUNCTION
+ipa_debug_noted_record_fnptrs (void)
+{
+ ipa_dump_noted_record_fnptrs (stderr);
+}
+
+
+/* If we have noticed a single function pointer stored into a record of type
+ REC_TYPE at the given FLD_OFFSET (measured in bytes), return its
+ declaration. Otherwise return NULL_TREE. */
+
+tree
+ipa_single_noted_fnptr_in_record (tree rec_type, unsigned fld_offset)
+{
+ if (!noted_fnptrs_in_records)
+ return NULL_TREE;
+
+ noted_fnptr_store repr;
+ repr.rec_type = rec_type;
+ repr.fld_offset = fld_offset;
+
+ noted_fnptr_store **slot = noted_fnptrs_in_records->find_slot (&repr,
+ NO_INSERT);
+ if (!slot)
+ return NULL_TREE;
+ return (*slot)->fn;
+}
+
+/* Free the hash table storing the information about function pointers stored
+ to a particular position in record typed strucutres. */
+
+void
+ipa_free_noted_fnptr_calls ()
+{
+ if (noted_fnptrs_in_records)
+ {
+ noted_fnptrs_in_records->empty ();
+ noted_fnptrs_in_records = nullptr;
+ }
+}
/* Analyze the call statement STMT with respect to formal parameters (described
- in INFO) of caller given by FBI->NODE. Currently it only checks whether
- formal parameters are called. */
+ in INFO) of caller given by FBI->NODE. Also note any stores of function
+ pointers to record typed memory. */
static void
ipa_analyze_stmt_uses (struct ipa_func_body_info *fbi, gimple *stmt)
{
if (is_gimple_call (stmt))
ipa_analyze_call_uses (fbi, as_a <gcall *> (stmt));
+ else if (gimple_assign_single_p (stmt)
+ && TREE_CODE (gimple_assign_rhs1 (stmt)) == ADDR_EXPR
+ && (TREE_CODE (TREE_OPERAND (gimple_assign_rhs1 (stmt), 0))
+ == FUNCTION_DECL))
+ {
+ tree rec_type;
+ unsigned fld_offset;
+ if (is_func_ptr_from_record (gimple_assign_lhs (stmt), &rec_type,
+ &fld_offset, gimple_assign_rhs1 (stmt)))
+ note_fnptr_in_record (rec_type, fld_offset,
+ TREE_OPERAND (gimple_assign_rhs1 (stmt), 0));
+ }
}
/* Callback of walk_stmt_load_store_addr_ops for the visit_load.
pop_cfun ();
}
+/* Analyze NODE and note any function pointers in record-typed static
+ initializers.
+
+ TODO: The current implementation does not traverse the initializers to scan
+ records nested inside other types. It should catch the most basic way of
+ writing "virtual functions" in C but can be extended, of course.
+*/
+
+void
+ipa_analyze_var_static_initializer (varpool_node *node)
+{
+ tree decl = node->decl;
+ tree rec_type = TREE_TYPE (decl);
+ if (TREE_CODE (rec_type) != RECORD_TYPE
+ || TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR)
+ return;
+
+ unsigned ix;
+ tree index, val;
+ FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (DECL_INITIAL (decl)), ix, index,
+ val)
+ {
+ if (TREE_CODE (val) != ADDR_EXPR
+ || TREE_CODE (TREE_OPERAND (val, 0)) != FUNCTION_DECL)
+ continue;
+ HOST_WIDE_INT elt_offset = int_bit_position (index);
+ if ((elt_offset % BITS_PER_UNIT) != 0)
+ continue;
+ elt_offset = elt_offset / BITS_PER_UNIT;
+ if (elt_offset > UINT_MAX)
+ continue;
+ note_fnptr_in_record (rec_type, elt_offset, TREE_OPERAND (val, 0));
+ }
+}
+
/* Update the jump functions associated with call graph edge E when the call
graph edge CS is being inlined, assuming that E->caller is already (possibly
indirectly) inlined into CS->callee and that E has not been inlined. */
bp = bitpack_create (ob->main_stream);
bp_pack_value (&bp, sii->agg_contents, 1);
bp_pack_value (&bp, sii->member_ptr, 1);
+ bp_pack_value (&bp, sii->fnptr_loaded_from_record, 1);
bp_pack_value (&bp, sii->by_ref, 1);
bp_pack_value (&bp, sii->guaranteed_unmodified, 1);
streamer_write_bitpack (&bp);
streamer_write_hwi (ob, sii->offset);
else
gcc_assert (sii->offset == 0);
+ if (sii->fnptr_loaded_from_record)
+ {
+ stream_write_tree (ob, sii->rec_type, true);
+ streamer_write_uhwi (ob, sii->fld_offset);
+ }
}
else
gcc_assert (cs->indirect_info->param_index == -1);
bp = streamer_read_bitpack (ib);
sii->agg_contents = bp_unpack_value (&bp, 1);
sii->member_ptr = bp_unpack_value (&bp, 1);
+ sii->fnptr_loaded_from_record = bp_unpack_value (&bp, 1);
sii->by_ref = bp_unpack_value (&bp, 1);
sii->guaranteed_unmodified = bp_unpack_value (&bp, 1);
sii->offset = (HOST_WIDE_INT) streamer_read_hwi (ib);
else
sii->offset = 0;
+ if (sii->fnptr_loaded_from_record)
+ {
+ sii->rec_type = stream_read_tree (ib, data_in);
+ sii->fld_offset = (unsigned) streamer_read_uhwi (ib);
+ }
if (info && sii->param_index >= 0)
ipa_set_param_used_by_indirect_call (info, sii->param_index, true);
}
ipa_write_node_info (ob, node);
}
ipa_write_return_summaries (ob);
+
+ if (noted_fnptrs_in_records)
+ {
+ count = 0;
+ for (auto iter = noted_fnptrs_in_records->begin ();
+ iter != noted_fnptrs_in_records->end();
+ ++iter)
+ if ((*iter)->fn)
+ count++;
+ streamer_write_uhwi (ob, count);
+
+ for (auto iter = noted_fnptrs_in_records->begin ();
+ iter != noted_fnptrs_in_records->end();
+ ++iter)
+ if ((*iter)->fn)
+ {
+ stream_write_tree (ob, (*iter)->rec_type, true);
+ stream_write_tree (ob, (*iter)->fn, true);
+ streamer_write_uhwi (ob, (*iter)->fld_offset);
+ }
+ }
+ else
+ streamer_write_uhwi (ob, 0);
+
produce_asm (ob);
destroy_output_block (ob);
}
ipa_read_node_info (&ib_main, node, data_in);
}
ipa_read_return_summaries (&ib_main, file_data, data_in);
+
+ count = streamer_read_uhwi (&ib_main);
+ for (i = 0; i < count; i++)
+ {
+ tree rec_type = stream_read_tree (&ib_main, data_in);
+ tree fn = stream_read_tree (&ib_main, data_in);
+ unsigned fld_offset = (unsigned) streamer_read_uhwi (&ib_main);
+ note_fnptr_in_record (rec_type, fld_offset, fn);
+ }
+
lto_free_section_data (file_data, LTO_section_jump_functions, NULL, data,
len);
lto_data_in_delete (data_in);
/* Functions related to both. */
void ipa_analyze_node (struct cgraph_node *);
+void ipa_analyze_var_static_initializer (varpool_node *node);
/* Aggregate jump function related functions. */
tree ipa_find_agg_cst_from_init (tree scalar, HOST_WIDE_INT offset,
void ipa_dump_param (FILE *, class ipa_node_params *info, int i);
void ipa_dump_jump_function (FILE *f, ipa_jump_func *jfunc,
class ipa_polymorphic_call_context *ctx = NULL);
+void ipa_dump_noted_record_fnptrs (FILE *f);
+void ipa_debug_noted_record_fnptrs (void);
void ipa_release_body_info (struct ipa_func_body_info *);
tree ipa_get_callee_param_type (struct cgraph_edge *e, int i);
bool ipcp_get_parm_bits (tree, tree *, widest_int *);
bool unadjusted_ptr_and_unit_offset (tree op, tree *ret,
poly_int64 *offset_ret);
void ipa_get_range_from_ip_invariant (vrange &r, tree val, cgraph_node *node);
+tree ipa_single_noted_fnptr_in_record (tree rectype, unsigned offset);
void ipa_prop_cc_finalize (void);
+void ipa_free_noted_fnptr_calls ();
/* In ipa-cp.cc */
void ipa_cp_cc_finalize (void);
{ OPT_LEVELS_2_PLUS, OPT_fpeephole2, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_freorder_functions, NULL, 1 },
{ OPT_LEVELS_2_PLUS, OPT_frerun_cse_after_loop, NULL, 1 },
+ { OPT_LEVELS_2_PLUS, OPT_fspeculatively_call_stored_functions, NULL, 1 },
#ifdef INSN_SCHEDULING
{ OPT_LEVELS_2_PLUS, OPT_fschedule_insns2, NULL, 1 },
#endif
/* Indirect call profiling should do all useful transformations
speculative devirtualization does. */
if (opts->x_flag_value_profile_transformations)
- SET_OPTION_IF_UNSET (opts, opts_set, flag_devirtualize_speculatively,
- false);
+ {
+ SET_OPTION_IF_UNSET (opts, opts_set, flag_devirtualize_speculatively,
+ false);
+ SET_OPTION_IF_UNSET (opts, opts_set,
+ flag_speculatively_call_stored_functions, false);
+ }
break;
case OPT_fauto_profile_:
--- /dev/null
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -O3 -flto -fdump-ipa-devirt } } } */
+
+
+struct fnptrs
+{
+ void (*first)(void);
+ int (*dostuff)(int);
+};
+
+struct fnptrs fp;
+
+
+void __attribute__ ((noinline)) init (void);
+
+volatile int v;
+
+int __attribute__ ((noipa))
+get_count (void)
+{
+ return 4;
+};
+
+
+void __attribute__ ((noinline))
+loop (void)
+{
+ for (int i = 0; i < get_count (); i++)
+ {
+ v = v + fp.dostuff (v);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ init ();
+ loop ();
+ return 0;
+}
+
+/* { dg-final { scan-wpa-ipa-dump "num speculative call targets: 1" "devirt" } } */
--- /dev/null
+struct fnptrs
+{
+ void (*first)(void);
+ int (*dostuff)(int);
+};
+
+extern struct fnptrs fp;
+
+int noop (int n)
+{
+ return n;
+}
+
+
+void __attribute__ ((noinline))
+init (void)
+{
+ fp.dostuff = noop;
+}
--- /dev/null
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -O3 -flto -fdump-ipa-devirt } } } */
+
+
+struct fnptrs
+{
+ void (*first)(void);
+ int (*dostuff)(int);
+};
+
+int noop (int n);
+
+struct fnptrs fp = {(void (*)(void)) 0, noop};
+
+
+void __attribute__ ((noinline)) init (void);
+
+volatile int v;
+
+int __attribute__ ((noipa))
+get_count (void)
+{
+ return 4;
+};
+
+
+void __attribute__ ((noinline))
+loop (void)
+{
+ for (int i = 0; i < get_count (); i++)
+ {
+ v = v + fp.dostuff (v);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ init ();
+ loop ();
+ return 0;
+}
+
+/* { dg-final { scan-wpa-ipa-dump "num speculative call targets: 1" "devirt" } } */
--- /dev/null
+struct fnptrs
+{
+ void (*first)(void);
+ int (*dostuff)(int);
+};
+
+extern struct fnptrs fp;
+
+int noop (int n)
+{
+ return n;
+}
+
+void distraction (void)
+{
+}
+
+void __attribute__ ((noinline))
+init (void)
+{
+ fp.first = distraction;
+}
--- /dev/null
+/* { dg-lto-do link } */
+/* { dg-lto-options { { -O3 -flto -fdump-ipa-devirt } } } */
+
+
+struct fnptrs
+{
+ void (*first)(void);
+ int (*dostuff)(int);
+};
+
+int noop (int n);
+
+struct fnptrs fp = {(void (*)(void)) 0, noop};
+
+
+void __attribute__ ((noinline)) init (void);
+
+volatile int v;
+
+int __attribute__ ((noipa))
+get_count (void)
+{
+ return 4;
+};
+
+
+void __attribute__ ((noinline))
+loop (void)
+{
+ for (int i = 0; i < get_count (); i++)
+ {
+ v = v + fp.dostuff (v);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ init ();
+ loop ();
+ return 0;
+}
+
+/* { dg-final { scan-wpa-ipa-dump "num speculative call targets: 1" "devirt" } } */
--- /dev/null
+struct fnptrs
+{
+ void (*first)(void);
+ int (*dostuff)(int);
+};
+
+extern struct fnptrs fp;
+
+int noop (int n)
+{
+ return n;
+}
+
+
+void __attribute__ ((noinline))
+init (void)
+{
+ fp.dostuff = noop;
+}