Skip to content

Commit 6d7e1ee

Browse files
committed
Auto merge of #157153 - joboet:global_allocator, r=<try>
allow `Allocator`s to be used as `#[global_allocator]`s try-job: x86_64-msvc-1
2 parents cb014fc + 862e366 commit 6d7e1ee

21 files changed

Lines changed: 583 additions & 469 deletions

File tree

library/core/src/alloc/global.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use super::{AllocError, GlobalAllocator};
12
use crate::alloc::Layout;
3+
use crate::hint::assert_unchecked;
4+
use crate::ptr::NonNull;
25
use crate::{cmp, ptr};
36

47
/// A memory allocator that can be registered as the standard library’s default
@@ -301,3 +304,72 @@ pub unsafe trait GlobalAlloc {
301304
new_ptr
302305
}
303306
}
307+
308+
/// Allows all [`GlobalAllocator`]s to be used with the legacy [`GlobalAlloc`] interface.
309+
#[stable(feature = "global_alloc", since = "1.28.0")]
310+
unsafe impl<A> GlobalAlloc for A
311+
where
312+
A: GlobalAllocator + ?Sized,
313+
{
314+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
315+
// SAFETY: guaranteed by the caller.
316+
// This might lead to the removal of zero-size checks inside the
317+
// `Allocator` implementation.
318+
unsafe { assert_unchecked(layout.size() != 0) };
319+
match self.allocate(layout) {
320+
Ok(ptr) => ptr.cast().as_ptr(),
321+
Err(AllocError) => ptr::null_mut(),
322+
}
323+
}
324+
325+
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
326+
// SAFETY: guaranteed by the caller.
327+
unsafe { assert_unchecked(layout.size() != 0) };
328+
// SAFETY: only non-null pointers can be currently allocated.
329+
let ptr = unsafe { NonNull::new_unchecked(ptr) };
330+
// SAFETY: guaranteed by caller.
331+
unsafe { self.deallocate(ptr, layout) };
332+
}
333+
334+
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
335+
// SAFETY: guaranteed by the caller.
336+
unsafe { assert_unchecked(layout.size() != 0) };
337+
match self.allocate_zeroed(layout) {
338+
Ok(ptr) => ptr.cast().as_ptr(),
339+
Err(AllocError) => ptr::null_mut(),
340+
}
341+
}
342+
343+
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
344+
// SAFETY: guaranteed by the caller.
345+
unsafe { assert_unchecked(layout.size() != 0) };
346+
// SAFETY: guaranteed by the caller.
347+
unsafe { assert_unchecked(new_size != 0) };
348+
349+
// SAFETY: only non-null pointers can be currently allocated.
350+
let ptr = unsafe { NonNull::new_unchecked(ptr) };
351+
let alignment = layout.alignment();
352+
// SAFETY: the caller must ensure that the `new_size` does not overflow
353+
// when rounded up to the next multiple of `alignment`.
354+
let new_layout = unsafe { Layout::from_size_alignment_unchecked(new_size, alignment) };
355+
356+
// SAFETY:
357+
// Two preconditions are guaranteed by the caller:
358+
// * `ptr` is currently allocated with this allocator.
359+
// * `layout` fits the block of memory.
360+
// The size precondition is upheld by selecting between `grow` and `shrink`
361+
// based on the size.
362+
let ptr = unsafe {
363+
if new_size >= layout.size() {
364+
self.grow(ptr, layout, new_layout)
365+
} else {
366+
self.shrink(ptr, layout, new_layout)
367+
}
368+
};
369+
370+
match ptr {
371+
Ok(ptr) => ptr.cast().as_ptr(),
372+
Err(AllocError) => ptr::null_mut(),
373+
}
374+
}
375+
}

library/core/src/alloc/mod.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,96 @@ pub const unsafe trait Allocator {
447447
}
448448
}
449449

450+
/// An [`Allocator`] that can be registered as the standard library’s default
451+
/// through the `#[global_allocator]` attribute.
452+
///
453+
/// Types implementing this trait can be used as the default allocator for
454+
/// memory allocations through `Box`, `Vec` and the collection types. For
455+
/// instance, the `System` allocator implements this trait, and thus can be
456+
/// explicitly set as the default like so:
457+
/// ```
458+
/// use std::alloc::System;
459+
///
460+
/// #[global_allocator]
461+
/// static ALLOCATOR: System = System;
462+
/// ```
463+
///
464+
/// The `Global` allocator forwards all memory allocation requests to the
465+
/// `static` annotated with `#[global_allocator]`. Hence, `Global` does not
466+
/// implement `GlobalAllocator` itself, as that would lead to infinite recursion.
467+
///
468+
/// # Note to implementors
469+
///
470+
/// This trait is used to prevent the infinite recursion that would occur if the
471+
/// default allocator were to attempt to allocate memory through `Global` (and
472+
/// thus from itself).
473+
///
474+
/// When to implement this trait:
475+
/// * for custom global allocators that only use system memory allocation
476+
/// services.
477+
/// * for allocators that wrap another allocator that implements `GlobalAllocator`.
478+
///
479+
/// When **not** to implement this trait:
480+
/// * for wrappers of arbitrary allocators (which might end up being `Global`,
481+
/// leading to infinite recursion).
482+
///
483+
/// # Safety
484+
///
485+
/// In addition to the safety requirements of `Allocator`, global allocators are
486+
/// subject to some additional constraints:
487+
///
488+
/// * It's undefined behavior if global allocators unwind. This restriction may
489+
/// be lifted in the future, but currently a panic from any of these
490+
/// functions may lead to memory unsafety.
491+
///
492+
/// * You must not rely on allocations actually happening, even if there are explicit
493+
/// heap allocations in the source. The optimizer may detect unused allocations that it can either
494+
/// eliminate entirely or move to the stack and thus never invoke the allocator. The
495+
/// optimizer may further assume that allocation is infallible, so code that used to fail due
496+
/// to allocator failures may now suddenly work because the optimizer worked around the
497+
/// need for an allocation. More concretely, the following code example is unsound, irrespective
498+
/// of whether your custom allocator allows counting how many allocations have happened.
499+
///
500+
/// ```rust,ignore (unsound and has placeholders)
501+
/// drop(Box::new(42));
502+
/// let number_of_heap_allocs = /* call private allocator API */;
503+
/// unsafe { std::hint::assert_unchecked(number_of_heap_allocs > 0); }
504+
/// ```
505+
///
506+
/// Note that the optimizations mentioned above are not the only
507+
/// optimization that can be applied. You may generally not rely on heap allocations
508+
/// happening if they can be removed without changing program behavior.
509+
/// Whether allocations happen or not is not part of the program behavior, even if it
510+
/// could be detected via an allocator that tracks allocations by printing or otherwise
511+
/// having side effects.
512+
///
513+
/// # Re-entrance
514+
///
515+
/// When implementing a global allocator, one has to be careful not to create an infinitely recursive
516+
/// implementation by accident, as many constructs in the Rust standard library may allocate in
517+
/// their implementation. For example, on some platforms, [`std::sync::Mutex`] may allocate, so using
518+
/// it is highly problematic in a global allocator.
519+
///
520+
/// For this reason, one should generally stick to library features available through
521+
/// [`core`], and avoid using [`std`] in a global allocator. A few features from [`std`] are
522+
/// guaranteed to not use `#[global_allocator]` to allocate:
523+
///
524+
/// - [`std::thread_local`],
525+
/// - [`std::thread::current`],
526+
/// - [`std::thread::park`] and [`std::thread::Thread`]'s [`unpark`] method and
527+
/// [`Clone`] implementation.
528+
///
529+
/// [`std`]: ../../std/index.html
530+
/// [`std::sync::Mutex`]: ../../std/sync/struct.Mutex.html
531+
/// [`std::thread_local`]: ../../std/macro.thread_local.html
532+
/// [`std::thread::current`]: ../../std/thread/fn.current.html
533+
/// [`std::thread::park`]: ../../std/thread/fn.park.html
534+
/// [`std::thread::Thread`]: ../../std/thread/struct.Thread.html
535+
/// [`unpark`]: ../../std/thread/struct.Thread.html#method.unpark
536+
#[unstable(feature = "allocator_api", issue = "32838")]
537+
#[expect(multiple_supertrait_upcastable)]
538+
pub unsafe trait GlobalAllocator: Allocator + Sync + 'static {}
539+
450540
#[unstable(feature = "allocator_api", issue = "32838")]
451541
#[rustc_const_unstable(feature = "const_heap", issue = "79597")]
452542
const unsafe impl<A> Allocator for &A

library/std/src/alloc.rs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,15 @@
6363
#![deny(unsafe_op_in_unsafe_fn)]
6464
#![stable(feature = "alloc_module", since = "1.28.0")]
6565

66-
use core::ptr::NonNull;
67-
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
68-
use core::{hint, mem, ptr};
69-
7066
#[stable(feature = "alloc_module", since = "1.28.0")]
7167
#[doc(inline)]
7268
pub use alloc_crate::alloc::*;
7369

70+
use crate::ptr::NonNull;
71+
use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
72+
use crate::sys::alloc as imp;
73+
use crate::{hint, mem, ptr};
74+
7475
/// The default memory allocator provided by the operating system.
7576
///
7677
/// This is based on `malloc` on Unix platforms and `HeapAlloc` on Windows,
@@ -145,11 +146,7 @@ impl System {
145146
0 => Ok(layout.dangling_ptr().cast_slice(0)),
146147
// SAFETY: `layout` is non-zero in size,
147148
size => unsafe {
148-
let raw_ptr = if zeroed {
149-
GlobalAlloc::alloc_zeroed(self, layout)
150-
} else {
151-
GlobalAlloc::alloc(self, layout)
152-
};
149+
let raw_ptr = if zeroed { imp::alloc_zeroed(layout) } else { imp::alloc(layout) };
153150
let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
154151
Ok(ptr.cast_slice(size))
155152
},
@@ -182,7 +179,7 @@ impl System {
182179
// `realloc` probably checks for `new_size >= old_layout.size()` or something similar.
183180
hint::assert_unchecked(new_size >= old_layout.size());
184181

185-
let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), old_layout, new_size);
182+
let raw_ptr = imp::realloc(ptr.as_ptr(), old_layout, new_size);
186183
let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
187184
if zeroed {
188185
raw_ptr.add(old_size).write_bytes(0, new_size - old_size);
@@ -205,8 +202,8 @@ impl System {
205202
}
206203
}
207204

208-
// The Allocator impl checks the layout size to be non-zero and forwards to the GlobalAlloc impl,
209-
// which is in `std::sys::*::alloc`.
205+
// The Allocator impl checks the layout size to be non-zero and forwards to the
206+
// platform functions in `std::sys::*::alloc`.
210207
#[unstable(feature = "allocator_api", issue = "32838")]
211208
unsafe impl Allocator for System {
212209
#[inline]
@@ -224,7 +221,7 @@ unsafe impl Allocator for System {
224221
if layout.size() != 0 {
225222
// SAFETY: `layout` is non-zero in size,
226223
// other conditions must be upheld by the caller
227-
unsafe { GlobalAlloc::dealloc(self, ptr.as_ptr(), layout) }
224+
unsafe { imp::dealloc(ptr.as_ptr(), layout) }
228225
}
229226
}
230227

@@ -274,7 +271,7 @@ unsafe impl Allocator for System {
274271
// `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
275272
hint::assert_unchecked(new_size <= old_layout.size());
276273

277-
let raw_ptr = GlobalAlloc::realloc(self, ptr.as_ptr(), old_layout, new_size);
274+
let raw_ptr = imp::realloc(ptr.as_ptr(), old_layout, new_size);
278275
let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
279276
Ok(ptr.cast_slice(new_size))
280277
},
@@ -294,6 +291,9 @@ unsafe impl Allocator for System {
294291
}
295292
}
296293

294+
#[unstable(feature = "allocator_api", issue = "32838")]
295+
unsafe impl GlobalAllocator for System {}
296+
297297
static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut());
298298

299299
/// Registers a custom allocation error hook, replacing any that was previously registered.
@@ -435,7 +435,12 @@ pub fn rust_oom(layout: Layout) -> ! {
435435
#[allow(unused_attributes)]
436436
#[unstable(feature = "alloc_internals", issue = "none")]
437437
pub mod __default_lib_allocator {
438-
use super::{GlobalAlloc, Layout, System};
438+
use super::Layout;
439+
// We call the system functions directly to avoid any overheads introduced
440+
// by the roundtrip through `impl Allocator for System` and
441+
// `impl<A: GlobalAllocator> GlobalAlloc for A`.
442+
use crate::sys::alloc as imp;
443+
439444
// These magic symbol names are used as a fallback for implementing the
440445
// `__rust_alloc` etc symbols (see `src/liballoc/alloc.rs`) when there is
441446
// no `#[global_allocator]` attribute.
@@ -452,15 +457,15 @@ pub mod __default_lib_allocator {
452457
// `GlobalAlloc::alloc`.
453458
unsafe {
454459
let layout = Layout::from_size_align_unchecked(size, align);
455-
System.alloc(layout)
460+
imp::alloc(layout)
456461
}
457462
}
458463

459464
#[rustc_std_internal_symbol]
460465
pub unsafe extern "C" fn __rdl_dealloc(ptr: *mut u8, size: usize, align: usize) {
461466
// SAFETY: see the guarantees expected by `Layout::from_size_align` and
462467
// `GlobalAlloc::dealloc`.
463-
unsafe { System.dealloc(ptr, Layout::from_size_align_unchecked(size, align)) }
468+
unsafe { imp::dealloc(ptr, Layout::from_size_align_unchecked(size, align)) }
464469
}
465470

466471
#[rustc_std_internal_symbol]
@@ -474,7 +479,7 @@ pub mod __default_lib_allocator {
474479
// `GlobalAlloc::realloc`.
475480
unsafe {
476481
let old_layout = Layout::from_size_align_unchecked(old_size, align);
477-
System.realloc(ptr, old_layout, new_size)
482+
imp::realloc(ptr, old_layout, new_size)
478483
}
479484
}
480485

@@ -484,7 +489,7 @@ pub mod __default_lib_allocator {
484489
// `GlobalAlloc::alloc_zeroed`.
485490
unsafe {
486491
let layout = Layout::from_size_align_unchecked(size, align);
487-
System.alloc_zeroed(layout)
492+
imp::alloc_zeroed(layout)
488493
}
489494
}
490495
}
Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
1-
use crate::alloc::{GlobalAlloc, Layout, System};
1+
use crate::alloc::Layout;
22

3-
#[stable(feature = "alloc_system_type", since = "1.28.0")]
4-
unsafe impl GlobalAlloc for System {
5-
#[inline]
6-
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
7-
let size = layout.size();
8-
let align = layout.align();
9-
unsafe { hermit_abi::malloc(size, align) }
10-
}
3+
#[inline]
4+
pub unsafe fn alloc(layout: Layout) -> *mut u8 {
5+
let size = layout.size();
6+
let align = layout.align();
7+
unsafe { hermit_abi::malloc(size, align) }
8+
}
119

12-
#[inline]
13-
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
14-
let size = layout.size();
15-
let align = layout.align();
16-
unsafe {
17-
hermit_abi::free(ptr, size, align);
18-
}
10+
#[inline]
11+
pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
12+
let size = layout.size();
13+
let align = layout.align();
14+
unsafe {
15+
hermit_abi::free(ptr, size, align);
1916
}
17+
}
2018

21-
#[inline]
22-
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
23-
let size = layout.size();
24-
let align = layout.align();
25-
unsafe { hermit_abi::realloc(ptr, size, align, new_size) }
26-
}
19+
#[inline]
20+
pub unsafe fn realloc(ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
21+
let size = layout.size();
22+
let align = layout.align();
23+
unsafe { hermit_abi::realloc(ptr, size, align, new_size) }
2724
}

0 commit comments

Comments
 (0)