rust-lang / rust-lang/rust

Better codegen with `#[unsafe(no_mangle)]`, worse optimization with specific integer vs. general integers

Open
#150,470 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-LLVM A-mir-opt-inlining C-optimization I-slow T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

All of this is a little harder to demonstrate with godbolt since it involves using and not using #[unsafe(no_mangle)]
but here is at least the #[unsafe(no_mangle)] examples https://godbolt.org/z/5Tdvn9nK4

I'm fully aware that this is a suboptimal use of .any(), I just found this behavior in this context so I will use it in the demo, It also probably happens elsewhere too.
This works with any range values, so 0..u32::MAX isn't too important, this is just to demonstrate how bad the difference can be
(though 0..=u32::MAX fully optimizes with all of the examples since it covers the full u32 range)

We have the following function:

#[unsafe(no_mangle)]
fn any_general(num: u32) -> bool {
    (0..u32::MAX).any(|i| i == num)
}

there's two ways that I have seen it optimize:
either a very simple comparison and return

any_general:
        cmp     edi, -1
        setne   al
        ret

but when we instead use a specific u32, we get a really ugly naive loop based search

#[unsafe(no_mangle)]
fn any_specific() -> bool {
    (0..u32::MAX).any(|i| i == 312312234_u32)
}

results in:

any_specific:
        xor     eax, eax
.LBB1_1:
        lea     ecx, [rax + 1]
        cmp     eax, 312312234
        je      .LBB1_3
        cmp     eax, -1
        mov     eax, ecx
        jne     .LBB1_1
.LBB1_3:
        test    ecx, ecx
        setne   al
        ret

But then I tried messing with this and found that the optimization does not happen without #[unsafe(no_mangle)].
Here is a list of the different function versions I tried and the results:

// Execution time: 82551µs, not optimized
fn any_general(num: u32) -> bool {
    (0..u32::MAX).any(|i| i == num)
}

// Execution time: 87900µs, not optimized
fn any_specific() -> bool {
    (0..u32::MAX).any(|i| i == 312312234_u32)
}

// Execution time: 82149µs, not optimized
#[unsafe(no_mangle)]
fn any_specific_no_mangle() -> bool {
    (0..u32::MAX).any(|i| i == 312312234_u32)
}

// Execution time: <1µs, optimized, only optimizes with u32 / i32 and below
#[inline] // inline or inline always required for the optimization
#[unsafe(no_mangle)]
fn any_specific_no_mangle_inlined() -> bool {
    (0..u32::MAX).any(|i| i == 312312234_u32)
}

// Execution time: <1µs, optimized
// optimizes even with u64 / i64
#[unsafe(no_mangle)]
fn any_general_no_mangle(num: u32) -> bool {
    (0..u32::MAX).any(|i| i == num)
}

The optimization somehow only happens when #[unsafe(no_mangle)] is used, and using general case of the integer helps the compiler optimize

Additional info:

  • LTO has no effect on the optimization
  • going back to the old name mangling scheme has no effect
  • this happens on both nightly and current stable 1.92
  • when using u32 or i32 I can get the any_specific_no_mangle_inlined() to optimize, but using u64 or i64 leads to any_specific_no_mangle_inlined() not optimize and then only any_general_no_mangle() optimizes
  • adding a .cargo/config.toml and adding -Ctarget-cpu=native for znver3 does not change behavior

This seems to reveal 2 issues:

  1. #[unsafe(no_mangle)] can help functions to optimized, though I don't think this is supposed to happen (?)
  2. Optimizations that happen for general integers at least sometimes do not apply to specific integers. I don't think there should ever be a case where specific integer known at compile time gets a worse optimization than general case of the integer

I also don't really understand how inlining the any_specific_no_mangle_inlined() gets it to optimize, since all the information is within the function body, but something that I came across. Adding inline to the other functions does not optimize them.

Meta

running rustc --version --verbose:

rustc 1.94.0-nightly (1107bbac4 2025-12-26)
binary: rustc
commit-hash: 1107bbac4b303d49c3e67a2ec62710902bf4b341
commit-date: 2025-12-26
host: x86_64-pc-windows-msvc
release: 1.94.0-nightly
LLVM version: 21.1.8

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the provided any_general and any_specific examples, starting with the Godbolt link and the reported rustc 1.94.0-nightly environment. Compare generated assembly with and without #[unsafe(no_mangle)], #[inline], and different integer widths. Done means the causes of both optimization differences are isolated and covered by an appropriate compiler regression test or documented limitation.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.