add write_box_via_move intrinsic and use it for vec!

This allows us to get rid of box_new entirely
This commit is contained in:
Ralf Jung 2025-10-26 11:04:37 +01:00
parent 93d45480aa
commit 5e65109f21
81 changed files with 1097 additions and 1400 deletions

View file

@ -479,23 +479,6 @@ unsafe impl const Allocator for Global {
}
}
/// The allocator for `Box`.
///
/// # Safety
///
/// `size` and `align` must satisfy the conditions in [`Layout::from_size_align`].
#[cfg(not(no_global_oom_handling))]
#[lang = "exchange_malloc"]
#[inline]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub(crate) unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
match Global.allocate(layout) {
Ok(ptr) => ptr.as_mut_ptr(),
Err(_) => handle_alloc_error(layout),
}
}
// # Allocation error handler
#[cfg(not(no_global_oom_handling))]

View file

@ -206,7 +206,7 @@ use core::task::{Context, Poll};
#[cfg(not(no_global_oom_handling))]
use crate::alloc::handle_alloc_error;
use crate::alloc::{AllocError, Allocator, Global, Layout, exchange_malloc};
use crate::alloc::{AllocError, Allocator, Global, Layout};
use crate::raw_vec::RawVec;
#[cfg(not(no_global_oom_handling))]
use crate::str::from_boxed_utf8_unchecked;
@ -236,14 +236,34 @@ pub struct Box<
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
>(Unique<T>, A);
/// Constructs a `Box<T>` by calling the `exchange_malloc` lang item and moving the argument into
/// the newly allocated memory. This is an intrinsic to avoid unnecessary copies.
/// Monomorphic function for allocating an uninit `Box`.
///
/// This is the surface syntax for `box <expr>` expressions.
/// # Safety
///
/// size and align need to be safe for `Layout::from_size_align_unchecked`.
#[inline]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
#[cfg(not(no_global_oom_handling))]
unsafe fn box_new_uninit(size: usize, align: usize) -> *mut u8 {
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
match Global.allocate(layout) {
Ok(ptr) => ptr.as_mut_ptr(),
Err(_) => handle_alloc_error(layout),
}
}
/// Helper for `vec!`.
///
/// This is unsafe, but has to be marked as safe or else we couldn't use it in `vec!`.
#[doc(hidden)]
#[rustc_intrinsic]
#[unstable(feature = "liballoc_internals", issue = "none")]
pub fn box_new<T>(x: T) -> Box<T>;
#[inline(always)]
#[cfg(not(no_global_oom_handling))]
pub fn box_assume_init_into_vec_unsafe<T, const N: usize>(
b: Box<MaybeUninit<[T; N]>>,
) -> crate::vec::Vec<T> {
unsafe { (b.assume_init() as Box<[T]>).into_vec() }
}
impl<T> Box<T> {
/// Allocates memory on the heap and then places `x` into it.
@ -262,9 +282,10 @@ impl<T> Box<T> {
#[rustc_diagnostic_item = "box_new"]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub fn new(x: T) -> Self {
// SAFETY: the size and align of a valid type `T` are always valid for `Layout`.
// This is `Box::new_uninit` but inlined to avoid build time regressions.
// SAFETY: The size and align of a valid type `T` are always valid for `Layout`.
let ptr = unsafe {
exchange_malloc(<T as SizedTypeProperties>::SIZE, <T as SizedTypeProperties>::ALIGN)
box_new_uninit(<T as SizedTypeProperties>::SIZE, <T as SizedTypeProperties>::ALIGN)
} as *mut T;
// Nothing below can panic so we do not have to worry about deallocating `ptr`.
// SAFETY: we just allocated the box to store `x`.
@ -288,9 +309,21 @@ impl<T> Box<T> {
#[cfg(not(no_global_oom_handling))]
#[stable(feature = "new_uninit", since = "1.82.0")]
#[must_use]
#[inline]
#[inline(always)]
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
pub fn new_uninit() -> Box<mem::MaybeUninit<T>> {
Self::new_uninit_in(Global)
// This is the same as `Self::new_uninit_in(Global)`, but manually inlined (just like
// `Box::new`).
// SAFETY:
// - The size and align of a valid type `T` are always valid for `Layout`.
// - If `allocate` succeeds, the returned pointer exactly matches what `Box` needs.
unsafe {
mem::transmute(box_new_uninit(
<T as SizedTypeProperties>::SIZE,
<T as SizedTypeProperties>::ALIGN,
))
}
}
/// Constructs a new `Box` with uninitialized contents, with the memory
@ -1150,10 +1183,12 @@ impl<T, A: Allocator> Box<mem::MaybeUninit<T>, A> {
/// assert_eq!(*five, 5)
/// ```
#[stable(feature = "new_uninit", since = "1.82.0")]
#[inline]
#[inline(always)]
pub unsafe fn assume_init(self) -> Box<T, A> {
let (raw, alloc) = Box::into_raw_with_allocator(self);
unsafe { Box::from_raw_in(raw as *mut T, alloc) }
// This is used in the `vec!` macro, so we optimize for minimal IR generation
// even in debug builds.
// SAFETY: `Box<T>` and `Box<MaybeUninit<T>>` have the same layout.
unsafe { core::intrinsics::transmute_unchecked(self) }
}
/// Writes the value and converts to `Box<T, A>`.

View file

@ -0,0 +1,15 @@
//! Intrinsics that cannot be moved to `core` because they depend on `alloc` types.
#![unstable(feature = "liballoc_internals", issue = "none")]
use core::mem::MaybeUninit;
use crate::boxed::Box;
/// Writes `x` into `b`.
///
/// This is needed for `vec!`, which can't afford any extra copies of the argument (or else debug
/// builds regress), has to be written fully as a call chain without `let` (or else this breaks inference
/// of e.g. unsizing coercions), and can't use an `unsafe` block as that would then also
/// include the user-provided `$x`.
#[rustc_intrinsic]
pub fn write_box_via_move<T>(b: Box<MaybeUninit<T>>, x: T) -> Box<MaybeUninit<T>>;

View file

@ -224,6 +224,7 @@ pub mod collections;
#[cfg(all(not(no_rc), not(no_sync), not(no_global_oom_handling)))]
pub mod ffi;
pub mod fmt;
pub mod intrinsics;
#[cfg(not(no_rc))]
pub mod rc;
pub mod slice;

View file

@ -47,10 +47,16 @@ macro_rules! vec {
$crate::vec::from_elem($elem, $n)
);
($($x:expr),+ $(,)?) => (
<[_]>::into_vec(
// Using the intrinsic produces a dramatic improvement in stack usage for
// unoptimized programs using this code path to construct large Vecs.
$crate::boxed::box_new([$($x),+])
// Using `write_box_via_move` produces a dramatic improvement in stack usage for unoptimized
// programs using this code path to construct large Vecs. We can't use `write_via_move`
// because this entire invocation has to remain a call chain without `let` bindings, or else
// inference and temporary lifetimes change and things break (see `vec-macro-rvalue-scope`,
// `vec-macro-coercions`, and `autoderef-vec-box-fn-36786` tests).
//
// `box_assume_init_into_vec_unsafe` isn't actually safe but the way we use it here is. We
// can't use an unsafe block as that would also wrap `$x`.
$crate::boxed::box_assume_init_into_vec_unsafe(
$crate::intrinsics::write_box_via_move($crate::boxed::Box::new_uninit(), [$($x),+])
)
);
}