Repository navigation
Performance regression since 1.93 related to MaybeUninit being wrongfully initialized #157743
Description
Since rustc 1.93 (likely #147827), initializing newtype wrapper of a struct that contains an integer type and a MaybeUninit field generates code to initialize the entire struct at -O2, instead of writing only the initialized integer field.
First bad nightly: nightly-2025-11-24 (commit c23ed3e)
Last good nightly: nightly-2025-11-23 (commit 94b49fd)
Still broken as of: 1.98.0-nightly (2026-06-11, LLVM 22.1.6)
Real-world impact: this caused a 22% performance regression in a specific test in the Firefox performance suite, as reported in https://bugzilla.mozilla.org/show_bug.cgi?id=2046376.
The hot call site creates a struct of three empty SmallVecs (from the smallvec crate with the union feature enabled). With rustc 1.93 the hot path now calls memset for 1224-bytes instead of three 8-byte stores.
Minimal reproducer
use std::mem::MaybeUninit;
pub struct Vec { cap: usize, data: MaybeUninit<[u64; 2]> }
pub struct Wrap(Vec);
#[no_mangle]
#[inline(never)]
pub fn make() -> Wrap {
Wrap(Vec { cap: 0, data: MaybeUninit::uninit() })
}https://godbolt.org/z/KnarEEon8
1.92 generates:
make:
movq %rdi, %rax
movq $0, 16(%rdi)
retq
1.93 generates:
make:
movq %rdi, %rax
xorps %xmm0, %xmm0
movups %xmm0, (%rdi)
movq $0, 16(%rdi)
retq
With larger arrays in the MaybeUninit, more movups happen, up to the point where it may choose to use memset instead.
IOW, 19.3 is initializing the whole struct instead. Without the wrapping, the MaybeUninit is properly not initialized.
Activity
scottmcm commentedon Jun 11, 2026
LLVM emitted by rustc: https://godbolt.org/z/EKh7c671v
1.92
1.93 (and nightly)
Here's where LLVM's
MemCpyOptchanges the 16-byte memcpy of undef plus a 8-bytes store into a 24-byte memset: https://godbolt.org/z/WWvvxhE4nscottmcm commentedon Jun 11, 2026
Oh, found it: I think this is a dup of #152541 which is marked as fixed in upstream LLVM by llvm/llvm-project#181609
glandium commentedon Jun 11, 2026
Is there any chance this will be fixed/worked around in rustc before waiting for the next LLVM release being picked up?
zachs18 commentedon Jun 11, 2026
Bisected to nightly-2025-11-24 which included #147827, as noted in OP. CI artifacts from they aren't available so couldn't easily bisect further. Also,
-Zmir-enable-passes=-GVNgives the "good" asm again, which also points to #147827On the LLVM side, it seems like the
MemCpyOptPasspass is the main issue; at that point in the opt pipeline, the IR is basicallymemcpy(&mut dest.data, const { &MaybeUninit::uninit() }); dest.cap = 0;, andMemCpyOptPassseems to change it intomemset(&mut dest, 0): (godbolt link, see "Opt Pipeline" view)For comparison, the IR at this stage in the LLVM pipeline with
-Zmir-enable-passes=-GVN(I think):Maybe LLVM treatsedit: see hereconstant undefdifferently from an uninitializedalloca? The unoptimized LLVM IR that rustc emits in the "bad" version has a@anon.05d519269bd06692ed4427bd5298e9b5.0 = private unnamed_addr constant [16 x i8] undef, align 8that itmemcpys from, whereas the "good" version withoutGVNinstead copies from an uninitializedalloca(godbolt link comparison).cargo bisect-rustc output
rustbot commentedon Jun 11, 2026
Assigning P-high (discussion on Zulip).
Auto merge of #157797 - glandium:uninit, r=oli-obk
Auto merge of #157797 - glandium:uninit, r=oli-obk
Auto merge of #157797 - glandium:uninit, r=oli-obk
Rollup merge of rust-lang#160060 - glandium:issue159867, r=oli-obk
Unrolled build for #160060
Rollup merge of #160060 - glandium:issue159867, r=oli-obk
perf: box transient session states, keep relay batches compact
perf: box transient session states, keep relay batches compact