ipa-devirt: Add speculative direct calls based on stores to structures

This patch extends the ipa-devirt pass with the ability to add
speculative calls for indirect calls where the target was loaded from
a record_type data structure and we have seen writes of address of
only one particular function to the same offset of that record
type (including static initializers).

The idea is basically taken from Christoph Müllner's patch from 2022
(https://gcc.gnu.org/pipermail/gcc-patches/2022-November/605934.html)
but I have re-worked it so that it stores the required information in
GC memory (which I belive is necessary) and does not require an
additional pass through gimple statements of all functions because it
uses the analysis phase of ipa-cp/ipa-fnsummary (which was an approach
we agreed on with Honza).

It also performs simple verification that the collected types match
the type of the record field.  We could verify that the function
determined as the likely target matches the call statement
expectations, but for that we would need to stream both types which is
something I decided not to do.

Co-authored by: Christoph Müllner <christoph.muellner@vrull.eu>

gcc/ChangeLog:

2025-11-28  Martin Jambor  <mjambor@suse.cz>

	PR ipa/107666
	* common.opt (fspeculatively-call-stored-functions): New.
	* cgraph.h (cgraph_simple_indirect_info): New fields rec_type and
	fld_offset.
	* ipa-prop.h (ipa_analyze_var_static_initializer): Declare.
	(ipa_dump_noted_record_fnptrs): Likewise.
	(ipa_debug_noted_record_fnptrs): Likewise.
	(ipa_single_noted_fnptr_in_record): Likewise.
	(ipa_free_noted_fnptr_calls): Likewise.
	* ipa-cp.cc (ipcp_generate_summary): Call
	ipa_analyze_var_static_initializer on each varbool node with a static
	initializer.
	* ipa-devirt.cc (struct devirt_stats): New type.
	(devirt_target_ok_p): New function.
	(ipa_devirt): Move statistics counters to the new structure.  dump
	noted function pointers stored in records.  Check for edge hotness
	first and for odr_types only for polymorphic edges.  Moved a number of
	checks to devirt_target_ok_p.  Also add speculative direct calls for
	non-polymorphic indirect ones when ipa_single_noted_fnptr_in_record
	finds a likely target.  Call ipa_free_noted_fnptr_calls.
	(pass_ipa_devirt::gate): Also check the new flag.
	* ipa-prop.cc (noted_fnptr_store): New type.
	(struct noted_fnptr_hasher): Likewise.
	(noted_fnptr_hasher::hash): New function.
	(noted_fnptr_hasher::equal): Likewise.
	(noted_fnptrs_in_records): New.
	(is_func_ptr_from_record): New function.
	(ipa_analyze_indirect_call_uses): Also simple create indirect info
	structures with fnptr_loaded_from_record set.
	(note_fnptr_in_record): New function.
	(ipa_dump_noted_record_fnptrs): Likewise.
	(ipa_debug_noted_record_fnptrs): Likewise.
	(ipa_single_noted_fnptr_in_record): Likewise.
	(ipa_free_noted_fnptr_calls): Likewise.
	(ipa_analyze_stmt_uses): Also look for stroes of function pointers to
	record structures.
	(ipa_analyze_var_static_initializer): New function.
	(ipa_write_indirect_edge_info): Also stream fnptr_loaded_from_record
	indirec infos.
	(ipa_read_indirect_edge_info): Likewise.
	(ipa_prop_write_jump_functions): Also stream the contents of
	noted_fnptrs_in_records.
	(ipa_prop_read_section): Likewise.
	* opts.cc (default_options_table): Also turn on
	OPT_fspeculatively_call_stored_functions at -O2.
	(common_handle_option): Turn flag_speculatively_call_stored_functions
	when using profile feedback.
	* doc/invoke.texi (-fspeculatively-call-stored-functions): New.

gcc/testsuite/ChangeLog:

2025-10-24  Martin Jambor  <mjambor@suse.cz>

	* gcc.dg/lto/fnptr-from-rec-1_0.c: New test.
	* gcc.dg/lto/fnptr-from-rec-1_1.c: Likewise.
	* gcc.dg/lto/fnptr-from-rec-2_0.c: Likewise.
	* gcc.dg/lto/fnptr-from-rec-2_1.c: Likewise.
	* gcc.dg/lto/fnptr-from-rec-3_0.c: Likewise.
	* gcc.dg/lto/fnptr-from-rec-3_1.c: Likewise.
This commit is contained in:
Martin Jambor
2025-12-16 00:44:45 +01:00
committed by Martin Jambor
parent 14ee9a2b41
commit 7b3af2bfa1
14 changed files with 682 additions and 91 deletions

View File

@@ -1764,19 +1764,30 @@ class GTY((tag ("CIIK_SIMPLE")))
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;

View File

@@ -1937,6 +1937,10 @@ floop-nest-optimize
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.

View File

@@ -677,7 +677,7 @@ Objective-C and Objective-C++ Dialects}.
-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
@@ -13336,6 +13336,7 @@ also turns on the following optimization flags:
-frerun-cse-after-loop
-fschedule-insns -fschedule-insns2
-fsched-interblock -fsched-spec
-fspeculatively-call-stored-functions
-fstore-merging
-fstrict-aliasing
-fthread-jumps
@@ -14275,6 +14276,16 @@ This is enabled by default when scheduling is enabled, i.e.@:
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

View File

@@ -6513,6 +6513,10 @@ ipcp_generate_summary (void)
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 {

View File

@@ -3648,6 +3648,62 @@ try_speculative_devirtualization (tree otr_type, HOST_WIDE_INT otr_token,
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. */
@@ -3658,24 +3714,21 @@ ipa_devirt (void)
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 ();
@@ -3692,10 +3745,17 @@ ipa_devirt (void)
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;
@@ -3717,7 +3777,7 @@ ipa_devirt (void)
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
@@ -3735,7 +3795,7 @@ ipa_devirt (void)
&& !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");
@@ -3744,18 +3804,11 @@ ipa_devirt (void)
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)
@@ -3765,7 +3818,7 @@ ipa_devirt (void)
{
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;
@@ -3778,7 +3831,7 @@ ipa_devirt (void)
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]);
@@ -3802,12 +3855,12 @@ ipa_devirt (void)
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;
}
@@ -3815,42 +3868,8 @@ ipa_devirt (void)
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 ())
@@ -3869,7 +3888,7 @@ ipa_devirt (void)
likely_target = alias;
}
if (first)
nconverted++;
stats.nconverted++;
first = false;
update = true;
e->make_speculative
@@ -3886,10 +3905,49 @@ ipa_devirt (void)
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)
{
@@ -3994,10 +4052,12 @@ ipa_devirt (void)
"%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 {
@@ -4038,11 +4098,12 @@ public:
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 (); }

View File

@@ -283,6 +283,49 @@ public:
}
};
/* 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;
@@ -2691,6 +2734,61 @@ ipa_compute_jump_functions_for_bb (struct ipa_func_body_info *fbi, basic_block b
}
}
/* 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
@@ -2862,22 +2960,32 @@ ipa_analyze_indirect_call_uses (struct ipa_func_body_info *fbi, gcall *call,
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
@@ -3120,16 +3228,130 @@ ipa_analyze_call_uses (struct ipa_func_body_info *fbi, gcall *call)
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.
@@ -3392,6 +3614,41 @@ ipa_analyze_node (struct cgraph_node *node)
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. */
@@ -5323,6 +5580,7 @@ ipa_write_indirect_edge_info (struct output_block *ob,
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);
@@ -5332,6 +5590,11 @@ ipa_write_indirect_edge_info (struct output_block *ob,
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);
@@ -5377,6 +5640,7 @@ ipa_read_indirect_edge_info (class lto_input_block *ib,
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);
@@ -5385,6 +5649,11 @@ ipa_read_indirect_edge_info (class lto_input_block *ib,
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);
}
@@ -5658,6 +5927,30 @@ ipa_prop_write_jump_functions (void)
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);
}
@@ -5746,6 +6039,16 @@ ipa_prop_read_section (struct lto_file_decl_data *file_data, const char *data,
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);

View File

@@ -1209,6 +1209,7 @@ tree ipa_impossible_devirt_target (struct cgraph_edge *, tree);
/* 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,
@@ -1265,6 +1266,8 @@ void ipa_push_agg_values_from_jfunc (ipa_node_params *info, cgraph_node *node,
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 *);
@@ -1274,7 +1277,9 @@ tree ipcp_get_aggregate_const (struct function *func, tree parm, bool by_ref,
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);

View File

@@ -662,6 +662,7 @@ static const struct default_options default_options_table[] =
{ 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
@@ -3168,8 +3169,12 @@ common_handle_option (struct gcc_options *opts,
/* 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_:

View File

@@ -0,0 +1,41 @@
/* { 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" } } */

View File

@@ -0,0 +1,19 @@
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;
}

View File

@@ -0,0 +1,43 @@
/* { 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" } } */

View File

@@ -0,0 +1,22 @@
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;
}

View File

@@ -0,0 +1,43 @@
/* { 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" } } */

View File

@@ -0,0 +1,19 @@
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;
}