https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127171
Bug ID: 127171
Summary: Missed inlining opportunity: constant string argument
enables massive DCE
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: ipa
Assignee: unassigned at gcc dot gnu.org
Reporter: Reshma.Roy at amd dot com
Target Milestone: ---
Created attachment 65467
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65467&action=edit
The preprocessed input file to be used for the command mentioned in the
description
The inliner misses an inlining opportunity. The test case is inspired from
cppcheck (SPEC CPU2026 727.cppcheck_r, lib/token.cpp) with the call chain,
caller_*() -> Token::Match() → multiCompareImpl() where on inlining Match and
multicompareImpl, dead code elimination happens eliminating many basic blocks
thereby reducing the dynamic instructions.
The call chain in the test case is as follows:
caller_X(node)
→ match(node, "constant")
→ dispatch_impl(node, p)
The caller_X passes constant pattern string, whereas "match" is a wrapper which
resembles Token::Match in the benchmark. The "dispatch_impl" is a heavy callee
like "multiCompareImpl" in the benchmark.
GCC refuses to inline dispatch_impl() into match() because its size exceeds
--param max-inline-insns-single and
--param max-inline-insns-auto exceeds to inline match into caller_X. However,
every top-level caller
passes a constant string literal. If the inliner could account for the constant
propagation chain (caller → match → dispatch_impl), it would see that p[0] is
known at compile time, enabling elimination of dead dispatch branches and
reducing effective inlined size to a fraction of the original.
=== How to reproduce ===
Preprocessed file is attached: match_spec_11_testcase.ii
In match_spec_11_testcase.ii change the following for force inlining and let's
say the changed preprocessed file name is match_spec_11_testcase_inline.ii.
1. static inline int dispatch_impl(..) -> static inline
__attribute__((always_inline)) int dispatch_impl(..)
2. static bool match(..) -> static inline __attribute__((always_inline)) bool
match(..)
Host/target: x86_64-pc-linux-gnu
gcc (GCC) 17.0.0 20260831 (experimental)
g++ -O3 -flto -std=c++17 -fopt-info-inline-missed match_spec_11_testcase.ii
Snippet of the relevant logs:
match_spec_11_testcase.cpp:274:39: missed: will not early inline: match(Node
const*, char const*)/5->dispatch_impl(Node const*, char const*&)/4, call is
cold and code would grow at least by 106
match_spec_11_testcase.cpp:335:25: missed: will not early inline:
multi_compare(Node const*, char const*)/11->dispatch_impl(Node const*, char
const*&)/4, call is cold and code would grow at least by 106
missed: not inlinable: caller_specifier.constprop/17 ->
_ZL5matchPK4NodePKc.part.0/2, --param max-inline-insns-auto limit reached
missed: not inlinable: caller_bracket.constprop/16 ->
_ZL5matchPK4NodePKc.part.0/2, --param max-inline-insns-auto limit reached
missed: not inlinable: caller_negation.constprop/15 ->
_ZL5matchPK4NodePKc.part.0/2, --param max-inline-insns-auto limit reached
missed: not inlinable: caller_alternatives.constprop/14 ->
_ZL5matchPK4NodePKc.part.0/2, --param max-inline-insns-auto limit reached
missed: not inlinable: caller_mixed.constprop/13 ->
_ZL5matchPK4NodePKc.part.0/2, --param max-inline-insns-auto limit reached
missed: not inlinable: caller_multi_compare.constprop/11 ->
multi_compare.constprop/12, function not inlinable
missed: not inlinable: main/10 -> caller_multi_compare.constprop/11, function
not inlinable
missed: not inlinable: main/10 -> caller_mixed.constprop/13, function not
inlinable
missed: not inlinable: main/10 -> caller_alternatives.constprop/14, function
not inlinable
missed: not inlinable: main/10 -> caller_negation.constprop/15, function not
inlinable
missed: not inlinable: main/10 -> caller_bracket.constprop/16, function not
inlinable
missed: not inlinable: main/10 -> caller_specifier.constprop/17, function not
inlinable
missed: not inlinable: _ZL5matchPK4NodePKc.part.0/2 -> dispatch_impl/1,
--param max-inline-insns-single limit reached
Next compile the force inlined(match and dispatch_impl) preprocessed tets case
using
g++ -O3 -flto -std=c++17 -fopt-info-inline-missed
match_spec_11_testcase_inline.ii
This will give the following logs where we can see the functions are inlined:
match_spec_11_testcase.cpp:236:37: optimized: Inlining find_char(char const*,
char)/2 into match(Node const*, char const*)/5.
match_spec_11_testcase.cpp:307:17: optimized: Inlining match(Node const*,
char const*)/5 into caller_specifier(Node const*)/6 (always_inline).
match_spec_11_testcase.cpp:312:17: optimized: Inlining match(Node const*,
char const*)/5 into caller_bracket(Node const*)/7 (always_inline).
match_spec_11_testcase.cpp:317:17: optimized: Inlining match(Node const*,
char const*)/5 into caller_negation(Node const*)/8 (always_inline).
match_spec_11_testcase.cpp:322:17: optimized: Inlining match(Node const*,
char const*)/5 into caller_alternatives(Node const*)/9 (always_inline).
match_spec_11_testcase.cpp:327:17: optimized: Inlining match(Node const*,
char const*)/5 into caller_mixed(Node const*)/10 (always_inline).
match_spec_11_testcase.cpp:335:25: optimized: Inlining dispatch_impl(Node
const*, char const*&)/4 into multi_compare(Node const*, char const*)/11
(always_inline).
=== Perf stat data ===
g++ -O3 -flto -std=c++17 -fdump-tree-optimized -o t-base-dump
match_spec_11_testcase.ii
g++ -O3 -flto -std=c++17 -fdump-tree-optimized -o t-force-dump
match_spec_11_testcase_inline.ii
perf stat -e instructions, cycles, branches, branch-misses -r 5 ./t-base-dump
Performance counter stats for './t-base-dump' (5 runs):
5816585252 instructions # 6.45 insn per
cycle ( +- 0.00% )
902306963 cycles
( +- 0.63% )
1921147517 branches
( +- 0.00% )
21138 branch-misses # 0.00% of all
branches ( +- 3.41% )
0.16821 +- 0.00603 seconds time elapsed ( +- 3.58% )
perf stat -e instructions, cycles, branches, branch-misses -r 5 ./t-force-dump
Performance counter stats for './t-force-dump' (5 runs):
4525805216 instructions # 6.48 insn per
cycle ( +- 0.00% )
698741702 cycles
( +- 0.99% )
1441005787 branches
( +- 0.00% )
20652 branch-misses # 0.00% of all
branches ( +- 2.66% )
0.13307 +- 0.00504 seconds time elapsed ( +- 3.79% )
Perf data clearly shows the reduction in the dynamic instructions after
inlining.
In the optimized logs after inlining we can see that for the function
caller_bracket.constprop, the body is just a return eliminating all other
blocks.
The attached testcase is large on purpose since --param max-inline-insns-single
is 200 at this -O3. In SPEC the inlining is stopped since the
max-inline-insns-single exceed the limit.