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}