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}