Consider the following code (playground):
fn main() {
let err = Err(());
let _: usize = err.unwrap_or_else(|err| err_exit(err));
unreachable!();
}
fn err_exit(_: ()) -> ! {
std::process::exit(1);
}
When compiled with rustc 1.36, it gives the following assembly:
core::result::Result<T,E>::unwrap_or_else:
pushq %rax
callq playground::main::{{closure}}
ud2
playground::main:
pushq %rax
callq core::result::Result<T,E>::unwrap_or_else
ud2
playground::main::{{closure}}:
pushq %rax
callq playground::err_exit
ud2
playground::err_exit:
pushq %rax
movl $1, %edi
callq *std::process::exit@GOTPCREL(%rip)
ud2
Note how the closure is not inlined, even though it would be trivial to do so (replace callq playground::main::{{closure}} with callq playground::err_exit).
Your playground link is wrong, use the share button on playground to get a permalink
Does
````rust
fn err_exit(_: ()) -> ! {
std::process::exit(1);
}
````
do the trick? Perhaps llvm is missing something here...
That fixes it, yeah. #[inline] does not, which seems odd.
I don't want to use #[inline(always)] in my actual code because err_exit is a pretty big function and I'd like it to only be inlined if it doesn't trash the instruction cache. The closure seems like an ideal place to inline because the calling function is really small.
I noticed some time ago that if your code unconditionally ends with a function that returns ! then some or all of preceding functions may not get inlined unless marked #[inline(always)], which is pretty awful.
Interestingly, in this example print is not inlined on Stable 1.37 and Beta 1.38, but is inlined on Nightly.
beta 1.38 version 2019-08-13 e450539c2a8d7f791268 is showing print as inlined on playground, what version of the compiler did you use?
The one on playground, but I didn't check its version. Either it was updated since then, or I made a mistake, or something else.
LLVM intentionally discourages inlining for such call sites, see https://github.com/llvm/llvm-project/blob/master/llvm/lib/Analysis/InlineCost.cpp#L782
Though arguably in this case, the bonus for completely eliminating the called function should still be applied, but isn't because it's handled only at the end of that method, thus the early return skips it. Moving the check for allowSizeGrowth to the end gives the expected result, main directly calling process_exit, but that seems suboptimal. I'll try to prepare a testcase and bug report (or patch) for LLVM.
There was already a bug report at https://bugs.llvm.org/show_bug.cgi?id=26495
I commented there with what I found out so far.
Most helpful comment
LLVM intentionally discourages inlining for such call sites, see https://github.com/llvm/llvm-project/blob/master/llvm/lib/Analysis/InlineCost.cpp#L782
Though arguably in this case, the bonus for completely eliminating the called function should still be applied, but isn't because it's handled only at the end of that method, thus the early return skips it. Moving the check for
allowSizeGrowthto the end gives the expected result,maindirectly callingprocess_exit, but that seems suboptimal. I'll try to prepare a testcase and bug report (or patch) for LLVM.