Generic methods are generally not marked #[inline]
, since generic methods have traditionally already been codegened into each compilation unit separately (and thus available to inline) as a matter of necessity. (This has AIUI been policy in std, and generally culturally adopted by ecosystem libraries as well.) Perhaps the rollout of -Zshare-generics
means we should reconsider the policy?
(Out of curiosity, are you using flags like -Zshare-generics
, or just the default --release
configuration? I'm not certain where we are in the rollout of sharable generic monomorphizations. It was my understanding that it wasn't on for cross-crate reuse yet, but it might be on for reuse between CGUs of a single crate.)
I'm impressed you managed to get a compilation of Iterator::fold
reused cross CGU (and thus not available for inlining). It must've been for function pointers, since most any other usage would end up depending on a call-site dependent type (i.e. closure) and probably in the same CGU as the calling scope. If it was for such a type, perhaps there's some better heuristic we can introduce to hopefully generally keep unique monomorphizations in the same CGU as their single caller.
The underlying complication is that #[inline]
doesn't just make a function available for inlining, it also applies the "good candidate for inlining" hint. For all but a select few generic methods, we'd prefer to have the former but not the latter. And maybe the aforementioned heuristic is rather the ability to specify e.g. #[inline(weak)]
on functions to get them always duplicated into the caller's CGU. The idea being that when used on methods generic over Fn*
, that generally still only means codegenning any given monomorphization once, just in a "closer" CGU. (Perhaps even lint against applying the attribute to functions not generic over Fn*
. On the other hand, maybe just leave it a heuristic rather than asking libs to annotate for it, if the idea is that it's always desirable for functions generic over Fn*
?)
If an upstream function isn't marked #[inline]
, isn't generic, and isn't const
, there's potentially nothing even really there to inline anymore. For nongeneric, noninline upstream functions, the .rlib
has typically already compiled the function down to target machine code and discarded the MIR. (MIR-only rlibs would change this, of course, but I'm considering (my understanding of) the current status.) Downstream pipelined compilation might even only have access to .rmeta
, which doesn't even have the machine code yet, just signatures and other such metadata (such as MIR for inline and/or generic functions).
So of the cases where a caller-side #[inline]
would actually be certain to have the upstream MIR available to inline into the same CGU:
- The upstream function is already
#[inline]
, thus doing so would have no impact;
- The upstream function is generic, so traditionally (before
-Zshare-generics
) would be available to inlining in any CGU it got used, thus doing it would just apply the "good inlining candidate" hint; or
- The upstream function is
const
, thus this both makes the function available to inline when it wouldn't otherwise be and applies the hint.
The clang attribute seems to be the equivalent of #[inline(always)]
. I don't think we'd want to permit that on the caller side unless we're willing to commit to the MIR for upstream functions always being available for inlining (minimally into the CGU) if requested. Either way, caller side #[inline]
should probably be an immediate error if used on extern
declarations without function bodies.
Additionally, there's the risk of caller side #[inline]
not really being strong enough for what you want. If the called function primarily consists of calls to other functions which are still cross-CGU, they still won't be able to be inlined, making inlining of the parent not all that significant. If your observed failure cases indeed come from -Zshare-generics
runtime pessimization, then it's not all that unlikely that this would in fact be the case, and what you actually are wanting for is less a caller-side #[inline]
and more a #[share_generics = false]
.
(My general inline annotation heuristic has been: if a nongeneric function compiles down to a leaf function which doesn't touch the stack, it should be marked #[inline]
. Otherwise, it shouldn't be marked #[inline]
until macrobenchmarks provide meaningful evidence otherwise. Some wiggle room is provided to "fundamental/primitive" vibe functions which happen to be generic and/or use some stack, but not enough of it to require stack pointer manipulation on targets that provide a red zone. There are, of course, still further refinements present.)