Empirical demo for the claim in
scylladb/seastar#3519 (comment):
in modern compilers static inline is a materially stronger hint to inline
than plain static — the keyword is not just linkage/ODR relief.
With clang-20 at -O2, the same function body is:
static |
static inline |
|
|---|---|---|
| remark | not inlined (cost=405 > threshold=337) | inlined (cost=405 < threshold=487) |
| asm | 2 call sites + standalone body | fully inlined, body deleted |
$ ./demo.sh
== inliner remarks, plain static:
example.cpp:62:12: remark: '_ZL7computei' not inlined into '_Z7caller1i' because too costly to inline (cost=405, threshold=337)
example.cpp:66:12: remark: '_ZL7computei' not inlined into '_Z7caller2i' because too costly to inline (cost=405, threshold=337)
== inliner remarks, static inline (-DUSE_INLINE):
example.cpp:62:12: remark: '_ZL7computei' inlined into '_Z7caller1i' with (cost=405, threshold=487)
example.cpp:66:12: remark: '_ZL7computei' inlined into '_Z7caller2i' with (cost=-14595, threshold=487)
== asm evidence:
static.s: 2 call site(s) to compute, 1 standalone definition(s)
inline.s: 0 call site(s) to compute, 0 standalone definition(s)
The inline keyword makes clang attach the inlinehint attribute to the LLVM
function, which selects a higher inline-cost threshold: 225
(-inline-threshold) for plain functions vs 325 (-inlinehint-threshold)
at -O2/-O3. Both get a 1.5× bonus here because the callee is a single
basic block, giving the observed 337 vs 487. Any function whose inline cost
lands between the two thresholds is inlined only when declared inline.
sweep.py generates a compute() of N statements (each ≈ cost 15: xor, mul,
add) and probes both qualifiers:
| compiler | divergence window (N statements) | cost range |
|---|---|---|
clang-20 -O2 |
26 – 35 | 345 – 480 |
g++-13 -O2 |
11 – 24 | n/a (different cost model) |
Below the window both variants inline; above it neither does. example.cpp
uses N=30 (cost 405), mid-window for clang. Note the windows don't overlap, so
this exact file shows no divergence on gcc — regenerate with N≈15 for that.
- Two call sites minimum. A
static(local) function with a single call site gets LLVM's last-call-to-static bonus (15000): deleting the function afterwards pays for inlining, so it inlines at practically any size. You can see the bonus fire in the secondstatic inlineremark above: after site 1 is inlined, site 2 becomes the last call and its cost drops to -14595. - Tail calls aren't
call.return compute(a);compiles tojmp _ZL7computei, so grepping asm forcallalone reports false "inlined". The callers add+ 1after the call to force a realcall, and detection matchescall|jmp.
example.cpp— standalone repro (godbolt-friendly),-DUSE_INLINEtoggles the qualifierdemo.sh— compiles both variants, shows remarks + asm evidencesweep.py— the size sweep (python3 sweep.py [lo hi],CXX=/OPT=env overrides)