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}