You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Large MaybeUninit::assume_init values no longer construct in place: 8 kB stack temporary + memcpy (regression in nightly-2026-06-26, caused by #158345) #159454
MaybeUninit::assume_init() on a large value used to compile to in-place
initialization of the destination. Since nightly-2026-06-26 it materializes the
full-size temporary on the stack and copies it, and also zero-fills bytes that
were never required to be initialized. On our embedded target this inflated one
function's stack frame from 720 B to 8928 B and caused a hardware stack-overflow
fault (STKOF) at boot.
Minimal repro
use std::mem::MaybeUninit;use std::sync::atomic::AtomicUsize;pubstructQueue{buffer:[MaybeUninit<u64>;1024],write_idx:AtomicUsize,read_idx:AtomicUsize,}implQueue{pubconstfnnew() -> Self{Queue{// The classic "array of MaybeUninit needs no initialization" idiom.buffer:unsafe{MaybeUninit::<[MaybeUninit<u64>;1024]>::uninit().assume_init()},write_idx:AtomicUsize::new(0),read_idx:AtomicUsize::new(0),}}}pubstructBus{ctrl:[u32;32],queue:Queue,armed:bool,}implBus{pubfnnew() -> Self{Bus{ctrl:[0;32],queue:Queue::new(),armed:false,}}}#[no_mangle]pubfninit(slot:&'staticmutMaybeUninit<Bus>) -> &'staticmutBus{
slot.write(Bus::new())}
rustc -O --crate-type=lib --emit=asm repro.rs
nightly-2026-06-25 (good) — init writes the few initialized fields
directly into slot, no stack usage:
nightly-2026-06-26 and later (bad) — an 8320-byte stack temporary plus a
memcpy (aarch64-apple-darwin shown; identical shape on
thumbv8m.main-none-eabihf, x86_64 analogous):
A simpler variant (the Queue alone, slot.write(Queue::new())) shows a
related symptom: the good nightly emits a single str (only the atomics need
writing); the bad nightly emits bzero of all 8200 bytes — the transmuted
uninit buffer has become a materialized zero constant.
Bisection and cause
Bisected to nightly-2026-06-26 (nightly-2026-06-25 = f28ac764c36004fa6a6e098d15b4016a838c13c6 is good, nightly-2026-06-26 = bd08c9e71874a81670fe3938dbf85148e42c2b96 is bad).
Confirmed cause: reverting the one-line change from Use transmute_neo in assume_init #158345
(MaybeUninit::assume_init: (&raw const self.value).cast::<T>().read() → transmute_neo(self)) in the nightly-2026-06-28 sysroot sources and
rebuilding our firmware with -Zbuild-std=core,alloc restores the original
codegen (8936 B frame → 712 B).
-Zmir-enable-passes=-GVN on the bad nightly also restores the good codegen,
both on the minimal repro and on the full firmware. -Zmir-enable-passes=-DestinationPropagation does not help, so this is a
different trigger than the one diagnosed in Stack slots are not being reused due to missing LLVM IR lifetime annotations. #157676, though plausibly the
same underlying missing-lifetime/value-numbering weakness: GVN now sees
through the MIR-level Transmute of the uninit value, and the result is a
materialized full-size temporary that nothing eliminates.
Real-world impact
Flight-controller firmware (RP2350, thumbv8m.main-none-eabihf, opt-level 3 +
fat LTO). A static SPSC queue ([MaybeUninit<T>; 1024] buffer, ~8 kB)
constructed with the uninit().assume_init() idiom and moved into a static_cell::StaticCell inflated the main thread's stack frame from 720 B to
8928 B, overflowing its 12 kB stack at boot (ARMv8-M STKOF fault). The same
idiom is widespread in embedded code (heapless-style buffers, ring queues), so
this likely affects many no_std projects where stack is the scarcest
resource.
Workaround
Replacing the by-value transmute idiom with an inline-const array
initializer restores optimal codegen on the affected nightlies:
buffer:[const{MaybeUninit::uninit()};1024],
This avoids the large assume_init transmute entirely, so it sidesteps the
regression — but the huge amount of existing code using MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init() (the idiom
documented in the MaybeUninit docs for years) silently regresses.
Version
Last good: rustc 1.98.0-nightly (f28ac764c 2026-06-23) (nightly-2026-06-25)
First bad: nightly-2026-06-26 (bd08c9e71874a81670fe3938dbf85148e42c2b96),
still present in rustc 1.98.0-nightly (13f1859f2 2026-06-27)
(nightly-2026-06-28)
Related: #157676 (stack slots not reused due to missing LLVM lifetime
annotations), #158345 (cause).
MaybeUninit::assume_init()on a large value used to compile to in-placeinitialization of the destination. Since nightly-2026-06-26 it materializes the
full-size temporary on the stack and copies it, and also zero-fills bytes that
were never required to be initialized. On our embedded target this inflated one
function's stack frame from 720 B to 8928 B and caused a hardware stack-overflow
fault (STKOF) at boot.
Minimal repro
rustc -O --crate-type=lib --emit=asm repro.rsnightly-2026-06-25 (good) —
initwrites the few initialized fieldsdirectly into
slot, no stack usage:nightly-2026-06-26 and later (bad) — an 8320-byte stack temporary plus a
memcpy (aarch64-apple-darwin shown; identical shape on
thumbv8m.main-none-eabihf, x86_64 analogous):
A simpler variant (the
Queuealone,slot.write(Queue::new())) shows arelated symptom: the good nightly emits a single
str(only the atomics needwriting); the bad nightly emits
bzeroof all 8200 bytes — the transmuteduninit buffer has become a materialized zero constant.
Bisection and cause
f28ac764c36004fa6a6e098d15b4016a838c13c6is good, nightly-2026-06-26 =bd08c9e71874a81670fe3938dbf85148e42c2b96is bad).transmute_neoinassume_init#158345(
MaybeUninit::assume_init:(&raw const self.value).cast::<T>().read()→transmute_neo(self)) in the nightly-2026-06-28 sysroot sources andrebuilding our firmware with
-Zbuild-std=core,allocrestores the originalcodegen (8936 B frame → 712 B).
-Zmir-enable-passes=-GVNon the bad nightly also restores the good codegen,both on the minimal repro and on the full firmware.
-Zmir-enable-passes=-DestinationPropagationdoes not help, so this is adifferent trigger than the one diagnosed in Stack slots are not being reused due to missing LLVM IR lifetime annotations. #157676, though plausibly the
same underlying missing-lifetime/value-numbering weakness: GVN now sees
through the MIR-level
Transmuteof the uninit value, and the result is amaterialized full-size temporary that nothing eliminates.
Real-world impact
Flight-controller firmware (RP2350,
thumbv8m.main-none-eabihf, opt-level 3 +fat LTO). A static SPSC queue (
[MaybeUninit<T>; 1024]buffer, ~8 kB)constructed with the
uninit().assume_init()idiom and moved into astatic_cell::StaticCellinflated the main thread's stack frame from 720 B to8928 B, overflowing its 12 kB stack at boot (ARMv8-M STKOF fault). The same
idiom is widespread in embedded code (heapless-style buffers, ring queues), so
this likely affects many
no_stdprojects where stack is the scarcestresource.
Workaround
Replacing the by-value transmute idiom with an inline-const array
initializer restores optimal codegen on the affected nightlies:
This avoids the large
assume_inittransmute entirely, so it sidesteps theregression — but the huge amount of existing code using
MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init()(the idiomdocumented in the
MaybeUninitdocs for years) silently regresses.Version
rustc 1.98.0-nightly (f28ac764c 2026-06-23)(nightly-2026-06-25)bd08c9e71874a81670fe3938dbf85148e42c2b96),still present in
rustc 1.98.0-nightly (13f1859f2 2026-06-27)(nightly-2026-06-28)
Related: #157676 (stack slots not reused due to missing LLVM lifetime
annotations), #158345 (cause).
@rustbotlabel: regression-from-stable-to-nightly, I-heavy, A-codegen, A-mir-opt