rust-lang / rust-lang/rust

Large `MaybeUninit::assume_init` values no longer construct in place: 8 kB stack temporary + memcpy (regression in nightly-2026-06-26, caused by #158345)

Open
#159,454 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-LLVM A-mir-opt C-optimization regression-from-stable-to-beta T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

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;

pub struct Queue {
  buffer: [MaybeUninit<u64>; 1024],
  write_idx: AtomicUsize,
  read_idx: AtomicUsize,
}

impl Queue {
  pub const fn new() -> 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),
    }
  }
}

pub struct Bus {
  ctrl: [u32; 32],
  queue: Queue,
  armed: bool,
}

impl Bus {
  pub fn new() -> Self {
    Bus {
      ctrl: [0; 32],
      queue: Queue::new(),
      armed: false,
    }
  }
}

#[no_mangle]
pub fn init(slot: &'static mut MaybeUninit<Bus>) -> &'static mut Bus {
  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:

_init:
	movi.2d	v0, #0000000000000000
	stp	q0, q0, [x0, #96]
	stp	q0, q0, [x0, #64]
	stp	q0, q0, [x0, #32]
	stp	q0, q0, [x0]
	str	q0, [x0, #8320]
	strb	wzr, [x8, #16]
	ret

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):

_init:
	stp	x20, x19, [sp, #-32]!
	...
	sub	sp, sp, #1, lsl #12   ; 4096
	str	xzr, [sp]
	sub	sp, sp, #1, lsl #12   ; 4096 more (stack probing)
	str	xzr, [sp], #-128
	bl	_memcpy               ; copy 8328 bytes into slot
	...

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 #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 #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).

@rustbot label: regression-from-stable-to-nightly, I-heavy, A-codegen, A-mir-opt

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

Start with the minimal repro in repro.rs and compare optimized assembly between the last good and first bad nightly. Read the #158345 change to MaybeUninit::assume_init and investigate the MIR GVN and Transmute behavior, using -Zmir-enable-passes=-GVN as a comparison. Done means the regression is covered and large uninitialized values no longer create a full-size stack temporary, memcpy, or unnecessary zero-fill.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.