link-dead-code does not include symbols for unused inlined functions

7eb17c4
Opened by Marco A L Barbosa at 2025-01-16 01:04:20

Consider a file x.rs:

#[inline]
fn gggg() {}
fn main() {}

The symbol gggg is not included in the compiled binary (tested with rustc -C link-dead-code x.rs && nm x | grep gggg). If the inline attribute is removed, the symbol is included.

  1. This is currently expected behavior as the compiler never generates any code for #[inline] functions unless they are referenced somewhere.

    Michael Woerister at 2017-10-10 15:16:20

  2. But this seems contrary to the intent of the link-dead-code (improve coverage accuracy, see https://github.com/rust-lang/rust/pull/31368)

    Marco A L Barbosa at 2017-10-10 15:24:48

  3. Yes, I can see how that would make sense when you are interested in code coverage.

    @rust-lang/dev-tools @rust-lang/compiler Do we have compiler options that are targeted specifically towards code coverage? Or should we just produce code for unused inline functions if -C link-dead-code is specified?

    Michael Woerister at 2017-10-10 15:43:28

  4. @michaelwoerister AFAIK we don't have many other options for code coverage, we're definitely long overdue for a flag for "I'd like to generate code coverage", but there's lots of hairy problems about implementing such a flag (e.g. inline functions today, generics, etc)

    Alex Crichton at 2017-10-10 18:25:56

  5. If we don't want to extend the meaning of link-dead-code, perhaps it makes sense to add something equivalent to gcc's/clang's -fkeep-inline-functions flag?

    I don't feel like the smaller emit-unused-inline-functions-in-the-binary feature needs to be blocked on a more general code coverage mode.

    Nick Fitzgerald at 2017-10-10 18:31:28

  6. Yeah, adding a targeted flag instead of changing the meaning of -C link-dead-code seems to preferable to me.

    Michael Woerister at 2017-10-11 07:50:15

  7. Triage: I'm not aware of any changes or additions here.

    Steve Klabnik at 2019-05-17 13:37:31

  8. As of rustc 1.72.0, the original repro, verbatim, doesn't reproduce the issue.

    These cases still do:

    • rustc -C link-dead-code -C opt-level=1 x.rs && nm x | grep gggg
    • rustc -C link-dead-code -C lto=thin x.rs && nm x | grep gggg
    • rustc -C link-dead-code -C opt-level=1 -C lto=fat x.rs && nm x | grep gggg

    While these cases do not:

    • rustc -C link-dead-code -C lto=fat x.rs && nm x | grep gggg
    • rustc -C link-dead-code -C opt-level=1 -C lto=off x.rs && nm x | grep gggg

    However, if the function is instead marked #[inline(always)], the issue reproduces in all cases, including the original one.


    For the #[inline(always)] issue (without the involvement of -C opt-level > 0 or -C lto=thin), the problem is that the mono item is never placed in any CGU. The reason is in part that MonoItem::instantiation_mode returns LocalCopy for the item, despite the presence of -C link-dead-code. place_mono_items doesn't place it because its instantiation mode LocalCopy, and because nothing else calls it.

    It would be tempting to have MonoItem::instantiation_mode not return LocalCopy if -C link-dead-code was used, but this would cause #[inline(always)] functions to not always be inlined, and comments like https://github.com/rust-lang/rust/pull/76896#issuecomment-758097233 make me wonder if that's a problem. On the other hand, I wonder if -C link-dead-code fundamentally relies on inlining not happening to work as desired.

    Tyson Nottingham at 2023-09-03 02:26:22

  9. The right fix for this mismatch in expectations is to make -Clink-dead-code override #[inline(always)]. We clearly document that #[inline(always)] is only a hint, contrary to the wording you linked to in https://github.com/rust-lang/rust/pull/76896#issuecomment-758097233. Certainly today there are no "ABI reasons" that #[inline(always)] must actually cause the given function to be inlined, and any codebase relying on that optimization for correctness has a latent bug.

    Ben Kimock at 2025-01-16 01:00:21

  10. Alternatively, both #[inline(always)] and -Clink-dead-code have the wrong names. #[inline(always)] should be #[inline(usually)] or something to that effect, and -Clink-dead-code should have been -Zhelp-bad-coverage-tools. Because that's what they are in practice.

    Ben Kimock at 2025-01-16 01:04:20