blink_alloc/
local.rs

1//! This module provides multi-threaded blink allocator\
2//! with sync resets.
3
4use core::{alloc::Layout, mem::ManuallyDrop, ptr::NonNull};
5
6#[cfg(feature = "nightly")]
7use core::alloc::{AllocError, Allocator};
8
9#[cfg(not(feature = "nightly"))]
10use allocator_api2::alloc::{AllocError, Allocator};
11
12#[cfg(all(feature = "nightly", feature = "alloc"))]
13use alloc::alloc::Global;
14
15#[cfg(all(not(feature = "nightly"), feature = "alloc"))]
16use allocator_api2::alloc::Global;
17
18use crate::{api::BlinkAllocator, arena::ArenaLocal};
19
20switch_alloc_default! {
21    /// Single-threaded blink allocator.
22    ///
23    /// Blink-allocator is arena-based allocator that
24    /// allocates memory in growing chunks and serve allocations from them.
25    /// When chunk is exhausted a new larger chunk is allocated.
26    ///
27    /// Deallocation is no-op. [`BlinkAlloc`] can be reset
28    /// to free all chunks except the last one, that will be reused.
29    ///
30    /// Blink allocator aims to allocate a chunk large enough to
31    /// serve all allocations between resets.
32    ///
33    /// A shared and mutable reference to the [`BlinkAlloc`] implement
34    /// [`Allocator`] trait.
35    /// When "nightly" feature is enabled, [`Allocator`] trait is
36    /// [`core::alloc::Allocator`]. Otherwise it is duplicated trait defined
37    /// in [`allocator-api2`](allocator_api2).
38    ///
39    /// Resetting blink allocator requires mutable borrow, so it is not possible
40    /// to do while shared borrow is alive. That matches requirement of
41    /// [`Allocator`] trait - while [`Allocator`] instance
42    /// (a shared reference to [`BlinkAlloc`]) or any of its clones are alive,
43    /// allocated memory must be valid.
44    ///
45    /// This version of blink-allocator is single-threaded. It is possible
46    /// to send to another thread, but cannot be shared.
47    /// Internally it uses [`Cell`](core::cell::Cell) for interior mutability and requires
48    /// that state cannot be changed from another thread.
49    ///
50    #[cfg_attr(feature = "sync", doc = "For multi-threaded version see [`SyncBlinkAlloc`](crate::sync::SyncBlinkAlloc).")]
51    #[cfg_attr(not(feature = "sync"), doc = "For multi-threaded version see `SyncBlinkAlloc`.")]
52    /// Requires `"sync"` feature.
53    ///
54    /// # Example
55    ///
56    /// ```
57    /// # #![cfg_attr(feature = "nightly", feature(allocator_api))]
58    /// # #[cfg(not(feature = "alloc"))] fn main() {}
59    /// # #[cfg(feature = "alloc")] fn main() {
60    /// # use blink_alloc::BlinkAlloc;
61    /// # use std::ptr::NonNull;
62    ///
63    /// let mut blink = BlinkAlloc::new();
64    /// let layout = std::alloc::Layout::new::<[u32; 8]>();
65    /// let ptr = blink.allocate(layout).unwrap();
66    /// let ptr = NonNull::new(ptr.as_ptr() as *mut u8).unwrap(); // Method for this is unstable.
67    ///
68    /// unsafe {
69    ///     std::ptr::write(ptr.as_ptr().cast(), [1, 2, 3, 4, 5, 6, 7, 8]);
70    /// }
71    ///
72    /// blink.reset();
73    /// # }
74    /// ```
75    ///
76    /// # Example that uses nightly's `allocator_api`
77    ///
78    /// ```
79    /// # #![cfg_attr(feature = "nightly", feature(allocator_api))]
80    /// # #[cfg(feature = "alloc")]
81    /// # fn main() {
82    /// # use blink_alloc::BlinkAlloc;
83    /// # #[cfg(feature = "nightly")]
84    /// # use std::vec::Vec;
85    /// # #[cfg(not(feature = "nightly"))]
86    /// # use allocator_api2::vec::Vec;
87    /// let mut blink = BlinkAlloc::new();
88    /// let mut vec = Vec::new_in(&blink);
89    /// vec.push(1);
90    /// vec.extend(1..3);
91    /// vec.extend(3..10);
92    /// drop(vec);
93    /// blink.reset();
94    /// # }
95    /// # #[cfg(not(feature = "alloc"))] fn main() {}
96    /// ```
97    pub struct BlinkAlloc<A: Allocator = +Global> {
98        arena: ArenaLocal,
99        allocator: A,
100    }
101}
102
103impl<A> Drop for BlinkAlloc<A>
104where
105    A: Allocator,
106{
107    #[inline]
108    fn drop(&mut self) {
109        // Safety:
110        // Same instance is used for all allocations and resets.
111        unsafe {
112            self.arena.reset(false, &self.allocator);
113        }
114    }
115}
116
117impl<A> Default for BlinkAlloc<A>
118where
119    A: Allocator + Default,
120{
121    #[inline]
122    fn default() -> Self {
123        Self::new_in(Default::default())
124    }
125}
126
127#[cfg(feature = "alloc")]
128impl BlinkAlloc<Global> {
129    /// Creates new blink allocator that uses global allocator
130    /// to allocate memory chunks.
131    ///
132    /// See [`BlinkAlloc::new_in`] for using custom allocator.
133    #[inline]
134    pub const fn new() -> Self {
135        BlinkAlloc::new_in(Global)
136    }
137
138    /// Creates new blink allocator that uses global allocator
139    /// to allocate memory chunks.
140    /// With this method you can specify initial chunk size.
141    ///
142    /// See [`BlinkAlloc::new_in`] for using custom allocator.
143    #[inline]
144    pub const fn with_chunk_size(chunk_size: usize) -> Self {
145        BlinkAlloc::with_chunk_size_in(chunk_size, Global)
146    }
147}
148
149impl<A> BlinkAlloc<A>
150where
151    A: Allocator,
152{
153    /// Creates new blink allocator that uses provided allocator
154    /// to allocate memory chunks.
155    ///
156    /// See [`BlinkAlloc::new`] for using global allocator.
157    #[inline]
158    pub const fn new_in(allocator: A) -> Self {
159        BlinkAlloc {
160            arena: ArenaLocal::new(),
161            allocator,
162        }
163    }
164
165    /// Returns reference to the underlying allocator used by this blink allocator.
166    #[inline(always)]
167    pub const fn inner(&self) -> &A {
168        &self.allocator
169    }
170
171    /// Creates new blink allocator that uses global allocator
172    /// to allocate memory chunks.
173    /// With this method you can specify initial chunk size.
174    ///
175    /// See [`BlinkAlloc::new_in`] for using custom allocator.
176    #[inline]
177    pub const fn with_chunk_size_in(chunk_size: usize, allocator: A) -> Self {
178        BlinkAlloc {
179            arena: ArenaLocal::with_chunk_size(chunk_size),
180            allocator,
181        }
182    }
183
184    /// Allocates memory with specified layout from this allocator.
185    /// If needed it will allocate new chunk using underlying allocator.
186    /// If chunk allocation fails, it will return `Err`.
187    #[inline(always)]
188    pub fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
189        // Safety:
190        // Same instance is used for all allocations and resets.
191        if let Some(ptr) = unsafe { self.arena.alloc_fast(layout) } {
192            return Ok(ptr);
193        }
194        unsafe { self.arena.alloc_slow(layout, &self.allocator) }
195    }
196
197    /// Resizes memory allocation.
198    /// Potentially happens in-place.
199    ///
200    /// # Safety
201    ///
202    /// `ptr` must be a pointer previously returned by [`allocate`](BlinkAlloc::allocate).
203    /// `old_size` must be in range `layout.size()..=slice.len()`
204    /// where `layout` is the layout used in the call to [`allocate`](BlinkAlloc::allocate).
205    /// and `slice` is the slice pointer returned by [`allocate`](BlinkAlloc::allocate).
206    ///
207    /// On success, the old pointer is invalidated and the new pointer is returned.
208    /// On error old allocation is still valid.
209    #[inline(always)]
210    pub unsafe fn resize(
211        &self,
212        ptr: NonNull<u8>,
213        old_layout: Layout,
214        new_layout: Layout,
215    ) -> Result<NonNull<[u8]>, AllocError> {
216        if let Some(ptr) = unsafe { self.arena.resize_fast(ptr, old_layout, new_layout) } {
217            return Ok(ptr);
218        }
219
220        // Safety:
221        // Same instance is used for all allocations and resets.
222        // `ptr` was allocated by this allocator.
223        unsafe {
224            self.arena
225                .resize_slow(ptr, old_layout, new_layout, &self.allocator)
226        }
227    }
228
229    /// Deallocates memory previously allocated from this allocator.
230    ///
231    /// This call may not actually free memory.
232    /// All memory is guaranteed to be freed on [`reset`](BlinkAlloc::reset) call.
233    ///
234    /// # Safety
235    ///
236    /// `ptr` must be a pointer previously returned by [`allocate`](BlinkAlloc::allocate).
237    /// `size` must be in range `layout.size()..=slice.len()`
238    /// where `layout` is the layout used in the call to [`allocate`](BlinkAlloc::allocate).
239    /// and `slice` is the slice pointer returned by [`allocate`](BlinkAlloc::allocate).
240    #[inline(always)]
241    pub unsafe fn deallocate(&self, ptr: NonNull<u8>, size: usize) {
242        // Safety:
243        // `ptr` was allocated by this allocator.
244        unsafe {
245            self.arena.dealloc(ptr, size);
246        }
247    }
248
249    /// Resets this allocator, deallocating all chunks except the last one.
250    /// Last chunk will be reused.
251    /// With steady memory usage after few iterations
252    /// one chunk should be sufficient for all allocations between resets.
253    #[inline(always)]
254    pub fn reset(&mut self) {
255        // Safety:
256        // Same instance is used for all allocations and resets.
257        unsafe {
258            self.arena.reset(true, &self.allocator);
259        }
260    }
261
262    /// Resets this allocator, deallocating all chunks.
263    #[inline(always)]
264    pub fn reset_final(&mut self) {
265        // Safety:
266        // Same instance is used for all allocations and resets.
267        unsafe {
268            self.arena.reset(false, &self.allocator);
269        }
270    }
271
272    /// Resets this allocator, deallocating all chunks except the last one.
273    /// Last chunk will be reused.
274    /// With steady memory usage after few iterations
275    /// one chunk should be sufficient for all allocations between resets.
276    ///
277    /// # Safety
278    ///
279    /// Blink-allocators guarantee that memory can be used while shared
280    /// borrow to the allocator is held, preventing safe `fn reset` call.
281    ///
282    /// With this method it becomes caller responsibility to ensure
283    /// that allocated memory won't be used after reset.
284    #[inline(always)]
285    pub unsafe fn reset_unchecked(&self) {
286        // Safety:
287        // Same instance is used for all allocations and resets.
288        unsafe {
289            self.arena.reset_unchecked(true, &self.allocator);
290        }
291    }
292
293    /// Unwrap this allocator, returning the underlying allocator.
294    /// Leaks allocated chunks.
295    ///
296    /// To deallocate all chunks call [`reset_final`](BlinkAlloc::reset_final) first.
297    pub fn into_inner(self) -> A {
298        let me = ManuallyDrop::new(self);
299        unsafe { core::ptr::read(&me.allocator) }
300    }
301}
302
303unsafe impl<A> Allocator for BlinkAlloc<A>
304where
305    A: Allocator,
306{
307    #[inline(always)]
308    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
309        BlinkAlloc::allocate(self, layout)
310    }
311
312    #[inline(always)]
313    unsafe fn shrink(
314        &self,
315        ptr: NonNull<u8>,
316        old_layout: Layout,
317        new_layout: Layout,
318    ) -> Result<NonNull<[u8]>, AllocError> {
319        BlinkAlloc::resize(self, ptr, old_layout, new_layout)
320    }
321
322    #[inline(always)]
323    unsafe fn grow(
324        &self,
325        ptr: NonNull<u8>,
326        old_layout: Layout,
327        new_layout: Layout,
328    ) -> Result<NonNull<[u8]>, AllocError> {
329        BlinkAlloc::resize(self, ptr, old_layout, new_layout)
330    }
331
332    #[inline(always)]
333    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
334        BlinkAlloc::deallocate(self, ptr, layout.size());
335    }
336}
337
338unsafe impl<A> BlinkAllocator for BlinkAlloc<A>
339where
340    A: Allocator,
341{
342    #[inline(always)]
343    fn reset(&mut self) {
344        BlinkAlloc::reset(self)
345    }
346}