]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
Add --param max-devirt-targets
authorJan Hubicka <jh@suse.cz>
Sat, 20 Sep 2025 13:11:50 +0000 (15:11 +0200)
committerJan Hubicka <jh@suse.cz>
Sat, 20 Sep 2025 13:12:26 +0000 (15:12 +0200)
Currently we speculatively devirtualize using static analysis (with no profile
feedback) only when there is one possible target found.  With profile feedback
we support multiple targets which seems useful in some scenarios.

This patch adds --param max-devirt-targets which enables devirtualization up to
given number of targets and defaults it to 3.  This happens i.e. in spec2017
omnetpp, though the devirtualizaton is later undone by inliner since it is not
considered useful.  The patch still seems to improve omnetpp by 2% in some
setups.

Other advantage of the patch is that the multi-target devirtualizatoin gets
tested without profile feedback so we more likely notice problems with it.

gcc/ChangeLog:

* doc/invoke.texi (--param max-devirt-targets) Document.
* ipa-devirt.cc (ipa_devirt): Implement muti-target
devirtualization.
* params.opt (max-devirt-targets): New parameter.

gcc/testsuite/ChangeLog:

* g++.dg/ipa/devirt-2.C: Update template.
* g++.dg/ipa/devirt-42.C: Update template.
* g++.dg/lto/devirt-2_0.C: Update template.

gcc/doc/invoke.texi
gcc/ipa-devirt.cc
gcc/params.opt
gcc/testsuite/g++.dg/ipa/devirt-2.C
gcc/testsuite/g++.dg/ipa/devirt-42.C
gcc/testsuite/g++.dg/lto/devirt-2_0.C

index 76ecea550f31c2ca43d1ebf522d06105a973729e..26e6e5bea60976b649cb35f8ef4851ad18c2fee7 100644 (file)
@@ -16312,6 +16312,10 @@ aggressive optimization, increasing the compilation time.  This parameter
 should be removed when the delay slot code is rewritten to maintain the
 control-flow graph.
 
+@item max-devirt-targets
+This limits number of function a virtual call may be speculatively
+devirtualized to using static analysis (without profile feedback).
+
 @item max-gcse-memory
 The approximate maximum amount of memory in @code{kB} that can be allocated in
 order to perform the global common subexpression elimination
index 18cb5a82195338504acc57787f69c6079779802b..9953833e9247a722557a0e5c1be2242ec6ce89a7 100644 (file)
@@ -3694,7 +3694,6 @@ ipa_devirt (void)
       for (e = n->indirect_calls; e; e = e->next_callee)
        if (e->indirect_info->polymorphic)
          {
-           struct cgraph_node *likely_target = NULL;
            void *cache_token;
            bool final;
 
@@ -3765,20 +3764,22 @@ ipa_devirt (void)
                nmultiple++;
                continue;
              }
+           auto_vec <cgraph_node *, 20> likely_targets;
            for (i = 0; i < targets.length (); i++)
              if (likely_target_p (targets[i]))
                {
-                 if (likely_target)
+                 if ((int)likely_targets.length () >= param_max_devirt_targets)
                    {
-                     likely_target = NULL;
+                     likely_targets.truncate (0);
                      if (dump_file)
-                       fprintf (dump_file, "More than one likely target\n\n");
+                       fprintf (dump_file, "More than %i likely targets\n\n",
+                                param_max_devirt_targets);
                      nmultiple++;
                      break;
                    }
-                 likely_target = targets[i];
+                 likely_targets.safe_push (targets[i]);
                }
-           if (!likely_target)
+           if (!likely_targets.length ())
              {
                bad_call_targets.add (cache_token);
                continue;
@@ -3787,7 +3788,13 @@ ipa_devirt (void)
               with the speculation.  */
            if (e->speculative)
              {
-               bool found = e->speculative_call_for_target (likely_target);
+               bool found = false;
+               for (cgraph_node * likely_target: likely_targets)
+                 if (e->speculative_call_for_target (likely_target))
+                   {
+                     found = true;
+                     break;
+                   }
                if (found)
                  {
                    fprintf (dump_file, "We agree with speculation\n\n");
@@ -3800,63 +3807,79 @@ ipa_devirt (void)
                  }
                continue;
              }
-           if (!likely_target->definition)
+           bool first = true;
+           unsigned speculative_id = 0;
+           for (cgraph_node * likely_target: likely_targets)
              {
-               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;
-             }
-           else if (dbg_cnt (devirt))
-             {
-               if (dump_enabled_p ())
-                  {
-                    dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, e->call_stmt,
-                                    "speculatively devirtualizing call "
-                                    "in %s to %s\n",
-                                    n->dump_name (),
-                                    likely_target->dump_name ());
-                  }
-               if (!likely_target->can_be_discarded_p ())
+               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 ())
                  {
-                   cgraph_node *alias;
-                   alias = dyn_cast<cgraph_node *> (likely_target->noninterposable_alias ());
-                   if (alias)
-                     likely_target = alias;
+                   if (dump_file)
+                     fprintf (dump_file, "Target is overwritable\n\n");
+                   noverwritable++;
+                   continue;
                  }
-               nconverted++;
-               update = true;
-               e->make_speculative
-                 (likely_target, e->count.apply_scale (8, 10));
+               else if (dbg_cnt (devirt))
+                 {
+                   if (dump_enabled_p ())
+                     {
+                       dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, e->call_stmt,
+                                        "speculatively devirtualizing call "
+                                        "in %s to %s\n",
+                                        n->dump_name (),
+                                        likely_target->dump_name ());
+                     }
+                   if (!likely_target->can_be_discarded_p ())
+                     {
+                       cgraph_node *alias;
+                       alias = dyn_cast<cgraph_node *> (likely_target->noninterposable_alias ());
+                       if (alias)
+                         likely_target = alias;
+                     }
+                   if (first)
+                     nconverted++;
+                   first = false;
+                   update = true;
+                   e->make_speculative
+                     (likely_target,
+                      e->count.apply_scale (8, 10 * likely_targets.length ()),
+                      speculative_id++);
+                 }
+             }
+           if (speculative_id > 1 && dump_enabled_p ())
+             {
+               dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, e->call_stmt,
+                                "devirtualized call in %s to %i targets\n",
+                                n->dump_name (),
+                                speculative_id);
              }
          }
       if (update)
index dd53d830895f028ef69c913edbfbc2a7cc1da041..ae617094db651c881f9e12c659b3b7f4b5a0bdc4 100644 (file)
@@ -548,6 +548,10 @@ The maximum number of instructions to consider to find accurate live register in
 Common Joined UInteger Var(param_max_dse_active_local_stores) Init(5000) Param Optimization
 Maximum number of active local stores in RTL dead store elimination.
 
+-param=max-devirt-targets=
+Common Joined UInteger Var(param_max_devirt_targets) Init(3) Param Optimization
+Maximum number of targets to devirutalize.
+
 -param=max-early-inliner-iterations=
 Common Joined UInteger Var(param_early_inliner_max_iterations) Init(1) Param Optimization
 The maximum number of nested indirect inlining performed by early inliner.
index 1797db6c81c7e570ef364275319c91e5332666e0..3fffe278ecea9bdf5bde271a74ad2b9c3624a73e 100644 (file)
@@ -1,7 +1,7 @@
 /* Verify that simple virtual calls using this pointer are converted
    to direct calls by ipa-cp.  */
 /* { dg-do run } */
-/* { dg-options "-O3 -fno-early-inlining -fno-inline -fdump-ipa-cp"  } */
+/* { dg-options "-O3 -fno-early-inlining -fno-inline -fdump-ipa-cp --param max-devirt-targets=1"  } */
 
 extern "C" void abort (void);
 
index 152b9689dc4d6aff51cbbdada225497ea8ccd406..9ec39c1396fcafefaaecc2075921c74b25d522f3 100644 (file)
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fno-ipa-cp -fdump-ipa-inline-details -fno-early-inlining -fdump-tree-optimized" } */
+/* { dg-options "-O3 -fno-ipa-cp -fdump-ipa-inline-details -fno-early-inlining -fdump-tree-optimized --param max-devirt-targets=1" } */
 struct A {
   virtual int foo () {return 1;}
   int bar () {return foo();}
index 4e92bb6c2dd050fca7e42bc8f8e03671bed68100..e8c30fc535939ef63db3f322ae7d1cb115a9b8bf 100644 (file)
@@ -1,4 +1,4 @@
 /* { dg-lto-do run } */
-/* { dg-lto-options { "-O3 -fno-early-inlining -fno-inline -fdump-ipa-cp -fdump-tree-optimized -flto" } } */
+/* { dg-lto-options { "-O3 -fno-early-inlining -fno-inline -fdump-ipa-cp -fdump-tree-optimized -flto --param max-devirt-targets=1" } } */
 #include "../ipa/devirt-2.C"
 /* { dg-final { scan-wpa-ipa-dump "Discovered a virtual call to a known target.*foo"  "cp"  } } */