From: Jan Hubicka Date: Sat, 20 Sep 2025 13:11:50 +0000 (+0200) Subject: Add --param max-devirt-targets X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9ee937b2f92a930eb5407260a56e5fe0fa137e85;p=thirdparty%2Fgcc.git 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. --- 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 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 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" } } */