blink_alloc/global/
local.rs

1use core::{
2    alloc::{GlobalAlloc, Layout},
3    cell::UnsafeCell,
4    ptr::{null_mut, NonNull},
5};
6
7#[cfg(debug_assertions)]
8use core::cell::Cell;
9
10#[cfg(feature = "nightly")]
11use core::alloc::{AllocError, Allocator};
12
13#[cfg(not(feature = "nightly"))]
14use allocator_api2::alloc::{AllocError, Allocator};
15
16use crate::{cold, local::BlinkAlloc};
17
18struct State<A: Allocator> {
19    blink: BlinkAlloc<A>,
20    enabled: bool,
21}
22
23impl<A: Allocator> State<A> {
24    #[inline(always)]
25    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
26        match self.enabled {
27            true => self.blink.allocate(layout),
28            false => {
29                cold();
30                self.blink.inner().allocate(layout)
31            }
32        }
33    }
34
35    #[inline(always)]
36    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
37        match self.enabled {
38            true => self.blink.allocate_zeroed(layout),
39            false => {
40                cold();
41                self.blink.inner().allocate_zeroed(layout)
42            }
43        }
44    }
45
46    #[inline(always)]
47    unsafe fn resize(
48        &self,
49        ptr: NonNull<u8>,
50        old_layout: Layout,
51        new_layout: Layout,
52    ) -> Result<NonNull<[u8]>, AllocError> {
53        match self.enabled {
54            true => self.blink.resize(ptr, old_layout, new_layout),
55            false => {
56                cold();
57                if old_layout.size() >= new_layout.size() {
58                    self.blink.inner().grow(ptr, old_layout, new_layout)
59                } else {
60                    self.blink.inner().shrink(ptr, old_layout, new_layout)
61                }
62            }
63        }
64    }
65
66    #[inline(always)]
67    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
68        match self.enabled {
69            true => self.blink.deallocate(ptr, layout.size()),
70            false => {
71                cold();
72                self.blink.inner().deallocate(ptr, layout)
73            }
74        }
75    }
76}
77
78switch_std_default! {
79    /// [`GlobalAlloc`] implementation based on [`BlinkAlloc`].
80    pub struct UnsafeGlobalBlinkAlloc<A: Allocator = +std::alloc::System> {
81        state: UnsafeCell<State<A>>,
82        #[cfg(debug_assertions)]
83        allocations: Cell<u64>,
84    }
85}
86
87// The user is responsible for ensuring that this allocator
88// won't be used concurrently.
89// To make this sound, `UnsafeGlobalBlinkAlloc::new` and `UnsafeGlobalBlinkAlloc::new_in`
90// are marked unsafe.
91unsafe impl<A: Allocator + Send> Send for UnsafeGlobalBlinkAlloc<A> {}
92unsafe impl<A: Allocator + Sync> Sync for UnsafeGlobalBlinkAlloc<A> {}
93
94#[cfg(feature = "std")]
95impl UnsafeGlobalBlinkAlloc<std::alloc::System> {
96    /// Create a new [`UnsafeGlobalBlinkAlloc`].
97    ///
98    /// Const function can be used to initialize a static variable.
99    ///
100    /// # Safety
101    ///
102    /// This method is unsafe because this type is not thread-safe
103    /// but implements `Sync`.
104    /// Allocator returned by this method must not be used concurrently.
105    ///
106    /// For safer alternative see [`GlobalBlinkAlloc`](https://docs.rs/blink-alloc/0.2.2/blink_alloc/struct.GlobalBlinkAlloc.html).
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use blink_alloc::UnsafeGlobalBlinkAlloc;
112    ///
113    /// // Safety: This program is single-threaded.
114    /// #[global_allocator]
115    /// static GLOBAL_ALLOC: UnsafeGlobalBlinkAlloc = unsafe { UnsafeGlobalBlinkAlloc::new() };
116    ///
117    /// let _ = Box::new(42);
118    /// let _ = vec![1, 2, 3];
119    /// ```
120    pub const unsafe fn new() -> Self {
121        UnsafeGlobalBlinkAlloc::new_in(std::alloc::System)
122    }
123
124    /// Create a new [`UnsafeGlobalBlinkAlloc`].
125    ///
126    /// This method allows to specify initial chunk size.
127    ///
128    /// Const function can be used to initialize a static variable.
129    ///
130    /// # Safety
131    ///
132    /// This method is unsafe because this type is not thread-safe
133    /// but implements `Sync`.
134    /// Allocator returned by this method must not be used concurrently.
135    ///
136    /// For safer alternative see [`GlobalBlinkAlloc`](https://docs.rs/blink-alloc/0.2.2/blink_alloc/struct.GlobalBlinkAlloc.html).
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// # #[cfg(feature = "std")] fn main() {
142    /// use blink_alloc::UnsafeGlobalBlinkAlloc;
143    ///
144    /// // Safety: This program is single-threaded.
145    /// #[global_allocator]
146    /// static GLOBAL_ALLOC: UnsafeGlobalBlinkAlloc<std::alloc::System> = unsafe { UnsafeGlobalBlinkAlloc::new_in(std::alloc::System) };
147    ///
148    /// let _ = Box::new(42);
149    /// let _ = vec![1, 2, 3];
150    /// # }
151    /// # #[cfg(not(feature = "std"))] fn main() {}
152    /// ```
153    pub const unsafe fn with_chunk_size(chunk_size: usize) -> Self {
154        UnsafeGlobalBlinkAlloc::with_chunk_size_in(chunk_size, std::alloc::System)
155    }
156}
157
158impl<A> UnsafeGlobalBlinkAlloc<A>
159where
160    A: Allocator,
161{
162    /// Create a new [`UnsafeGlobalBlinkAlloc`]
163    /// with specified underlying allocator.
164    ///
165    /// Const function can be used to initialize a static variable.
166    ///
167    /// # Safety
168    ///
169    /// This method is unsafe because this type is not thread-safe
170    /// but implements `Sync`.
171    /// Allocator returned by this method must not be used concurrently.
172    ///
173    /// For safer alternative see [`GlobalBlinkAlloc`](https://docs.rs/blink-alloc/0.2.2/blink_alloc/struct.GlobalBlinkAlloc.html).
174    ///
175    /// # Example
176    ///
177    /// ```
178    /// # #[cfg(feature = "std")] fn main() {
179    /// use blink_alloc::UnsafeGlobalBlinkAlloc;
180    ///
181    /// // Safety: This program is single-threaded.
182    /// #[global_allocator]
183    /// static GLOBAL_ALLOC: UnsafeGlobalBlinkAlloc<std::alloc::System> = unsafe { UnsafeGlobalBlinkAlloc::new_in(std::alloc::System) };
184    ///
185    /// let _ = Box::new(42);
186    /// let _ = vec![1, 2, 3];
187    /// # }
188    /// # #[cfg(not(feature = "std"))] fn main() {}
189    /// ```
190    pub const unsafe fn new_in(allocator: A) -> Self {
191        UnsafeGlobalBlinkAlloc {
192            state: UnsafeCell::new(State {
193                blink: BlinkAlloc::new_in(allocator),
194                enabled: false,
195            }),
196            #[cfg(debug_assertions)]
197            allocations: Cell::new(0),
198        }
199    }
200
201    /// Create a new [`UnsafeGlobalBlinkAlloc`]
202    /// with specified underlying allocator.
203    ///
204    /// This method allows to specify initial chunk size.
205    ///
206    /// Const function can be used to initialize a static variable.
207    ///
208    /// # Safety
209    ///
210    /// This method is unsafe because this type is not thread-safe
211    /// but implements `Sync`.
212    /// Allocator returned by this method must not be used concurrently.
213    ///
214    /// For safer alternative see [`GlobalBlinkAlloc`](https://docs.rs/blink-alloc/0.2.2/blink_alloc/struct.GlobalBlinkAlloc.html).
215    ///
216    /// # Example
217    ///
218    /// ```
219    /// # #[cfg(feature = "std")] fn main() {
220    /// use blink_alloc::UnsafeGlobalBlinkAlloc;
221    ///
222    /// // Safety: This program is single-threaded.
223    /// #[global_allocator]
224    /// static GLOBAL_ALLOC: UnsafeGlobalBlinkAlloc<std::alloc::System> = unsafe { UnsafeGlobalBlinkAlloc::new_in(std::alloc::System) };
225    ///
226    /// let _ = Box::new(42);
227    /// let _ = vec![1, 2, 3];
228    /// # }
229    /// # #[cfg(not(feature = "std"))] fn main() {}
230    /// ```
231    pub const unsafe fn with_chunk_size_in(chunk_size: usize, allocator: A) -> Self {
232        UnsafeGlobalBlinkAlloc {
233            state: UnsafeCell::new(State {
234                blink: BlinkAlloc::with_chunk_size_in(chunk_size, allocator),
235                enabled: false,
236            }),
237            #[cfg(debug_assertions)]
238            allocations: Cell::new(0),
239        }
240    }
241
242    /// Resets this allocator, deallocating all chunks except the last one.
243    /// Last chunk will be reused.
244    /// With steady memory usage after few iterations
245    /// one chunk should be sufficient for all allocations between resets.
246    ///
247    /// # Safety
248    ///
249    /// Memory allocated from this allocator in blink mode becomes invalidated.
250    /// The user is responsible to ensure that previously allocated memory
251    /// won't be used after reset.
252    ///
253    /// # Example
254    ///
255    /// ```
256    /// # #[cfg(feature = "std")] fn main() {
257    /// use blink_alloc::UnsafeGlobalBlinkAlloc;
258    ///
259    /// #[global_allocator]
260    /// static GLOBAL_ALLOC: UnsafeGlobalBlinkAlloc = unsafe { UnsafeGlobalBlinkAlloc::new() };
261    ///
262    /// unsafe { GLOBAL_ALLOC.blink_mode() };
263    ///
264    /// let b = Box::new(42);
265    /// let v = vec![1, 2, 3];
266    /// drop(b);
267    /// drop(v);
268    ///
269    /// // Safety: memory allocated in blink mode won't be used after reset.
270    /// unsafe {
271    ///     GLOBAL_ALLOC.reset();
272    ///     GLOBAL_ALLOC.direct_mode();
273    /// };
274    /// # }
275    /// # #[cfg(not(feature = "std"))] fn main() {}
276    /// ```
277    #[inline(always)]
278    pub unsafe fn reset(&self) {
279        #[cfg(debug_assertions)]
280        {
281            assert_eq!(self.allocations.get(), 0, "Not everything was deallocated");
282        }
283
284        (*self.state.get()).blink.reset_unchecked();
285    }
286
287    /// Switches allocator to blink mode.
288    /// All allocations will be served by blink-allocator.
289    ///
290    /// The type is created in direct mode.
291    /// When used as global allocator, user may manually switch into blink mode
292    /// in `main` or at any point later.
293    ///
294    /// However user must switch back to direct mode before returning from `main`.
295    ///
296    /// # Safety
297    ///
298    /// Must be externally synchronized with other threads accessing this allocator.
299    /// Memory allocated in direct mode must not be deallocated while in blink mode.
300    #[inline(always)]
301    pub unsafe fn blink_mode(&self) {
302        (*self.state.get()).enabled = true;
303    }
304
305    /// Switches allocator to blink mode.
306    /// All allocations will be served by underlying allocator.
307    ///
308    /// The type is created in direct mode.
309    /// When used as global allocator, user may manually switch into blink mode
310    /// in `main` or at any point later.
311    ///
312    /// However user must switch back to direct mode before returning from `main`.
313    ///
314    /// # Safety
315    ///
316    /// Must be externally synchronized with other threads accessing this allocator.
317    /// Memory allocated in blink mode must not be deallocated while in direct mode.
318    #[inline(always)]
319    pub unsafe fn direct_mode(&self) {
320        self.reset();
321        (*self.state.get()).enabled = false;
322    }
323}
324
325unsafe impl<A> GlobalAlloc for UnsafeGlobalBlinkAlloc<A>
326where
327    A: Allocator,
328{
329    #[inline]
330    unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
331        match (*self.state.get()).allocate(layout) {
332            Ok(ptr) => {
333                #[cfg(debug_assertions)]
334                if (*self.state.get()).enabled {
335                    self.allocations.set(self.allocations.get() + 1);
336                }
337                ptr.as_ptr().cast()
338            }
339            Err(_) => null_mut(),
340        }
341    }
342
343    #[inline]
344    unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
345        let ptr = NonNull::new_unchecked(ptr);
346        (*self.state.get()).deallocate(ptr, layout);
347        #[cfg(debug_assertions)]
348        if (*self.state.get()).enabled {
349            self.allocations
350                .set(self.allocations.get().saturating_sub(1));
351        }
352    }
353
354    #[inline]
355    unsafe fn alloc_zeroed(&self, layout: core::alloc::Layout) -> *mut u8 {
356        match (*self.state.get()).allocate_zeroed(layout) {
357            Ok(ptr) => {
358                #[cfg(debug_assertions)]
359                if (*self.state.get()).enabled {
360                    self.allocations.set(self.allocations.get() + 1);
361                }
362                ptr.as_ptr().cast()
363            }
364            Err(_) => null_mut(),
365        }
366    }
367
368    #[inline]
369    unsafe fn realloc(
370        &self,
371        ptr: *mut u8,
372        layout: core::alloc::Layout,
373        new_size: usize,
374    ) -> *mut u8 {
375        let Ok(new_layout) = Layout::from_size_align(new_size, layout.align()) else {
376            return null_mut();
377        };
378
379        let result = match NonNull::new(ptr) {
380            None => (*self.state.get()).allocate(new_layout),
381            Some(ptr) => (*self.state.get()).resize(ptr, layout, new_layout),
382        };
383
384        match result {
385            Ok(ptr) => ptr.as_ptr().cast(),
386            Err(_) => null_mut(),
387        }
388    }
389}