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