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