Better codegen with `#[unsafe(no_mangle)]`, worse optimization with specific integer vs. general integers
Nobody has claimed this yet.
- 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
u32ori32I can get theany_specific_no_mangle_inlined()to optimize, but usingu64ori64leads toany_specific_no_mangle_inlined()not optimize and then onlyany_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:
#[unsafe(no_mangle)]can help functions to optimized, though I don't think this is supposed to happen (?)- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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