Rust: Some closures are not inlined in release mode

Created on 17 Aug 2019  路  8Comments  路  Source: rust-lang/rust

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).

A-codegen C-bug I-slow T-compiler

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 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.

All 8 comments

Your playground link is wrong, use the share button on playground to get a permalink

Does
````rust

[inline(always)]

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.

Was this page helpful?
0 / 5 - 0 ratings