forge_alloc/backing/system.rs
1//! `System` — a thin newtype that exposes the process's global allocator
2//! (via [`allocator_api2::alloc::Global`]) as an [`forge_alloc_core::Allocator`].
3//!
4//! Used as a fallback in `WithFallback<Inner, System>` and anywhere a higher
5//! layer needs to defer to the OS heap. Not intended for direct use on hot
6//! paths — the whole point of this library is to avoid the system heap.
7//!
8//! # Implementation note
9//!
10//! This primitive is named after [`std::alloc::System`]. The implementation
11//! routes through [`allocator_api2::alloc::Global`] instead, because `Global`
12//! plugs into both the stable and nightly `Allocator` ABIs through a single
13//! crate. On a default `cargo` build with no `#[global_allocator]` override,
14//! `Global` *is* `std::alloc::System`, so the behaviour is identical. If a
15//! downstream user installs a custom `#[global_allocator]`, `System` here will
16//! route through that allocator — which is consistent with the intent ("fall
17//! back to the OS heap") but worth knowing.
18
19use core::ptr::NonNull;
20
21use allocator_api2::alloc::Allocator as A2;
22use forge_alloc_core::{AllocError, Allocator, Deallocator, NonZeroLayout};
23
24/// Adapter exposing the standard `System` allocator as an `forge_alloc_core::Allocator`.
25///
26/// `System` does NOT implement [`forge_alloc_core::FixedRange`] (it owns no bounded
27/// region) nor [`forge_alloc_core::OsBacked`] (it does not expose page-level controls).
28/// This is intentional — wrappers requiring those traits cannot accidentally
29/// be applied to `System`.
30#[derive(Copy, Clone, Debug, Default)]
31pub struct System;
32
33impl System {
34 /// Construct. ZST — no fields.
35 #[inline]
36 pub const fn new() -> Self {
37 Self
38 }
39}
40
41// We delegate to `allocator_api2::alloc::Global`. On a default cargo build,
42// `Global` resolves to `std::alloc::System`; under a custom
43// `#[global_allocator]`, `Global` resolves to that allocator. Either way the
44// spec's "fall back to the OS heap" contract is honoured.
45//
46// Conversion at the boundary: forge-alloc-core takes `NonZeroLayout`, the std
47// allocator API takes `core::alloc::Layout`. `NonZeroLayout::to_layout()` is
48// infallible.
49
50unsafe impl Deallocator for System {
51 #[inline]
52 unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout) {
53 // SAFETY: caller upholds Deallocator contract — ptr came from our
54 // allocate(layout), and StdSystem (via the allocator_api2 shim) is
55 // a valid receiver for the corresponding deallocate call.
56 unsafe { A2::deallocate(&allocator_api2::alloc::Global, ptr, layout.to_layout()) }
57 }
58}
59
60unsafe impl Allocator for System {
61 #[inline]
62 fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError> {
63 A2::allocate(&allocator_api2::alloc::Global, layout.to_layout())
64 }
65
66 #[inline]
67 fn allocate_zeroed(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError> {
68 A2::allocate_zeroed(&allocator_api2::alloc::Global, layout.to_layout())
69 }
70
71 #[inline]
72 unsafe fn grow(
73 &self,
74 ptr: NonNull<u8>,
75 old: NonZeroLayout,
76 new: NonZeroLayout,
77 ) -> Result<NonNull<[u8]>, AllocError> {
78 // SAFETY: forwarded; caller upholds Allocator::grow contract.
79 unsafe {
80 A2::grow(
81 &allocator_api2::alloc::Global,
82 ptr,
83 old.to_layout(),
84 new.to_layout(),
85 )
86 }
87 }
88
89 #[inline]
90 unsafe fn shrink(
91 &self,
92 ptr: NonNull<u8>,
93 old: NonZeroLayout,
94 new: NonZeroLayout,
95 ) -> Result<NonNull<[u8]>, AllocError> {
96 // SAFETY: forwarded; caller upholds Allocator::shrink contract.
97 unsafe {
98 A2::shrink(
99 &allocator_api2::alloc::Global,
100 ptr,
101 old.to_layout(),
102 new.to_layout(),
103 )
104 }
105 }
106}
107
108// Sanity check: ensure System is Send + Sync. The std System allocator is.
109const _: () = {
110 fn assert_send<T: Send>() {}
111 fn assert_sync<T: Sync>() {}
112 let _ = assert_send::<System>;
113 let _ = assert_sync::<System>;
114};
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn allocate_then_deallocate() {
122 let s = System;
123 let layout = NonZeroLayout::from_size_align(64, 8).unwrap();
124 let block = s.allocate(layout).expect("System alloc should succeed");
125 let p = block.cast::<u8>();
126 unsafe {
127 core::ptr::write_bytes(p.as_ptr(), 0xAB, 64);
128 assert_eq!(*p.as_ptr(), 0xAB);
129 s.deallocate(p, layout);
130 }
131 }
132
133 #[test]
134 fn allocate_zeroed_returns_zeros() {
135 let s = System;
136 let layout = NonZeroLayout::from_size_align(32, 8).unwrap();
137 let block = s.allocate_zeroed(layout).unwrap();
138 let p = block.cast::<u8>();
139 unsafe {
140 for i in 0..32 {
141 assert_eq!(*p.as_ptr().add(i), 0, "byte {i} should be zeroed");
142 }
143 s.deallocate(p, layout);
144 }
145 }
146
147 #[test]
148 fn capacity_bytes_is_none() {
149 // System is unbounded.
150 assert_eq!(System.capacity_bytes(), None);
151 }
152}