Hi,

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.

Bootsstrapped and LTO-bootstrapped and tested on x86_64-linux.  OK for
master?

Thanks,

Martin


gcc/ChangeLog:

2025-10-24  Martin Jambor  <[email protected]>

        PR ipa/107666
        * 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.
        * 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.

gcc/testsuite/ChangeLog:

2025-10-24  Martin Jambor  <[email protected]>

        * 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.

Co-authored by: Christoph Müllner <[email protected]>
---
 gcc/cgraph.h                                  |  13 +-
 gcc/ipa-cp.cc                                 |   4 +
 gcc/ipa-devirt.cc                             | 195 +++++++----
 gcc/ipa-prop.cc                               | 330 +++++++++++++++++-
 gcc/ipa-prop.h                                |   5 +
 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_0.c |  41 +++
 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_1.c |  19 +
 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_0.c |  43 +++
 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_1.c |  22 ++
 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_0.c |  43 +++
 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_1.c |  19 +
 11 files changed, 651 insertions(+), 83 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_0.c
 create mode 100644 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_1.c
 create mode 100644 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_0.c
 create mode 100644 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_1.c
 create mode 100644 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_0.c
 create mode 100644 gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_1.c

diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index c1329015342..062ad0f65ad 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -1746,19 +1746,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;
diff --git a/gcc/ipa-cp.cc b/gcc/ipa-cp.cc
index 74ec8a7c4b2..915d29b4bb2 100644
--- a/gcc/ipa-cp.cc
+++ b/gcc/ipa-cp.cc
@@ -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 {
diff --git a/gcc/ipa-devirt.cc b/gcc/ipa-devirt.cc
index 69bd73f4240..34127f88171 100644
--- a/gcc/ipa-devirt.cc
+++ b/gcc/ipa-devirt.cc
@@ -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,48 @@ 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_devirtualize_speculatively))
+             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 +4051,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 {
diff --git a/gcc/ipa-prop.cc b/gcc/ipa-prop.cc
index 3d2a4c279b6..72ff8296165 100644
--- a/gcc/ipa-prop.cc
+++ b/gcc/ipa-prop.cc
@@ -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;
 
@@ -2690,6 +2733,60 @@ 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))
+    {
+      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
@@ -2861,22 +2958,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
@@ -3119,16 +3226,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.
@@ -3391,6 +3612,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.  */
@@ -5322,6 +5578,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);
@@ -5331,6 +5588,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);
@@ -5376,6 +5638,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);
 
@@ -5384,6 +5647,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);
     }
@@ -5657,6 +5925,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);
 }
@@ -5745,6 +6037,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);
diff --git a/gcc/ipa-prop.h b/gcc/ipa-prop.h
index 046e83a521f..6ebcb28b22d 100644
--- a/gcc/ipa-prop.h
+++ b/gcc/ipa-prop.h
@@ -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);
diff --git a/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_0.c 
b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_0.c
new file mode 100644
index 00000000000..0054885319b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_0.c
@@ -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" } 
} */
diff --git a/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_1.c 
b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_1.c
new file mode 100644
index 00000000000..983287af00b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-1_1.c
@@ -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;
+}
diff --git a/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_0.c 
b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_0.c
new file mode 100644
index 00000000000..1d7f22f7299
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_0.c
@@ -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"  
} } */
diff --git a/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_1.c 
b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_1.c
new file mode 100644
index 00000000000..a2212f25b32
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-2_1.c
@@ -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;
+}
diff --git a/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_0.c 
b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_0.c
new file mode 100644
index 00000000000..1d7f22f7299
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_0.c
@@ -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"  
} } */
diff --git a/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_1.c 
b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_1.c
new file mode 100644
index 00000000000..983287af00b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lto/fnptr-from-rec-3_1.c
@@ -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;
+}
-- 
2.51.0

Reply via email to