From 9ee937b2f92a930eb5407260a56e5fe0fa137e85 Mon Sep 17 00:00:00 2001 From: Jan Hubicka Date: Sat, 20 Sep 2025 15:11:50 +0200 Subject: [PATCH] Add --param max-devirt-targets 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 | 4 + gcc/ipa-devirt.cc | 145 +++++++++++++++----------- gcc/params.opt | 4 + gcc/testsuite/g++.dg/ipa/devirt-2.C | 2 +- gcc/testsuite/g++.dg/ipa/devirt-42.C | 2 +- gcc/testsuite/g++.dg/lto/devirt-2_0.C | 2 +- 6 files changed, 95 insertions(+), 64 deletions(-) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 76ecea550f31..26e6e5bea609 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 18cb5a821953..9953833e9247 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 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 (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 (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 dd53d830895f..ae617094db65 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 1797db6c81c7..3fffe278ecea 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 152b9689dc4d..9ec39c1396fc 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 4e92bb6c2dd0..e8c30fc53593 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" } } */ -- 2.47.3