-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Description
I tried this code (godbolt):
use std::hint::black_box;
type N = u8; // Doesn't matter
#[no_mangle]
pub fn f(n: &N, f: fn(u64)) {
for x in (MyIter { n: *n, base: 999 }) {
f(x);
}
}
struct MyIter {
base: u64,
n: N,
}
impl Iterator for MyIter {
type Item = u64;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
do_next(&mut self.n, self.base)
}
}
#[inline(never)]
fn do_next(n: &mut N, base: u64) -> Option<u64> {
// Doesn't matter
black_box(n);
black_box(base);
black_box(None)
}
I expected to see this happen: self.base
is never modified, thus it gets constant-propagated at the f
callsite and the only stack allocation that remains is for n
Instead, this happened: base
is stored into the iterator allocation, and is loaded for every iteration of the loop even if never modified
This is to illustrate that even if only part of the iterator is ever mutated, LLVM won't recognize that and fail to constant propagate base
inside of the loop body, which can have pretty drastic consequences for performance if it's used e.g. for integer division.
This is not specific to iterators, but this is how I noticed the missed optimization in real world code in this PR.
You can see in the godbolt link that we can work around this by creating a new allocation for n
and passing that instead will correctly pass base
as a constant to do_next
.
The type of N
and the contents of do_next
don't matter. do_next
can be fully inlined and the work around will still have its effect.
Meta
rustc --version --verbose
:
rustc 1.90.0-nightly (5795086bd 2025-07-16)
binary: rustc
commit-hash: 5795086bdfe7ed988aa53a110bd0692c33d8755b
commit-date: 2025-07-16
host: x86_64-unknown-linux-gnu
release: 1.90.0-nightly
LLVM version: 20.1.8
@rustbot modify labels: +A-LLVM +I-slow +C-optimization -C-bug