Hi,
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.

The patch disables devirtualization in 3 ipa-cp testcases.  This is a
broken cost model of ipa-cp that is too conservative about cloning and
also does not account code size savings for devirutalization, so the
small increase of code size caused by adding extra call edges disables
it.  I will fill PR for that.

Bootstrapped/regtested x86_64-linux, comitted.

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.

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 76ecea550f3..26e6e5bea60 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -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
diff --git a/gcc/ipa-devirt.cc b/gcc/ipa-devirt.cc
index 18cb5a82195..9953833e924 100644
--- a/gcc/ipa-devirt.cc
+++ b/gcc/ipa-devirt.cc
@@ -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)
diff --git a/gcc/params.opt b/gcc/params.opt
index dd53d830895..ae617094db6 100644
--- a/gcc/params.opt
+++ b/gcc/params.opt
@@ -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.
diff --git a/gcc/testsuite/g++.dg/ipa/devirt-2.C 
b/gcc/testsuite/g++.dg/ipa/devirt-2.C
index 1797db6c81c..3fffe278ece 100644
--- a/gcc/testsuite/g++.dg/ipa/devirt-2.C
+++ b/gcc/testsuite/g++.dg/ipa/devirt-2.C
@@ -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);
 
diff --git a/gcc/testsuite/g++.dg/ipa/devirt-42.C 
b/gcc/testsuite/g++.dg/ipa/devirt-42.C
index 152b9689dc4..9ec39c1396f 100644
--- a/gcc/testsuite/g++.dg/ipa/devirt-42.C
+++ b/gcc/testsuite/g++.dg/ipa/devirt-42.C
@@ -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();}
diff --git a/gcc/testsuite/g++.dg/lto/devirt-2_0.C 
b/gcc/testsuite/g++.dg/lto/devirt-2_0.C
index 4e92bb6c2dd..e8c30fc5359 100644
--- a/gcc/testsuite/g++.dg/lto/devirt-2_0.C
+++ b/gcc/testsuite/g++.dg/lto/devirt-2_0.C
@@ -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"  } } */

Reply via email to