1use std::alloc::{self, Layout};
15use std::cell::RefCell;
16use std::marker::PhantomData;
17use std::mem;
18use std::ptr::NonNull;
19
20const DEFAULT_BLOCK_SIZE: usize = 64 * 1024;
22
23pub struct Arena {
43 chunks: RefCell<Vec<Chunk>>,
44 block_size: usize,
45}
46
47impl Arena {
48 pub fn new() -> Self {
60 Self {
61 chunks: RefCell::new(Vec::new()),
62 block_size: DEFAULT_BLOCK_SIZE,
63 }
64 }
65
66 pub fn with_capacity(block_size: usize) -> Self {
68 Self {
69 chunks: RefCell::new(Vec::new()),
70 block_size,
71 }
72 }
73
74 #[allow(clippy::mut_from_ref)]
95 pub fn allocate_with<T>(&self, init: impl FnOnce() -> T) -> &mut T {
96 let layout = Layout::new::<T>();
97 assert!(
98 layout.size() > 0,
99 "cannot allocate zero-sized types in Arena"
100 );
101 let ptr = self.alloc_raw(layout);
102
103 unsafe {
105 let typed = ptr.as_ptr().cast::<T>();
106 typed.write(init());
107 &mut *typed
108 }
109 }
110
111 pub fn allocate_slice<T: Copy>(&self, values: &[T]) -> &[T] {
131 if values.is_empty() {
132 return &[];
133 }
134
135 let layout = Layout::from_size_align(mem::size_of_val(values), mem::align_of::<T>())
136 .expect("invalid slice layout");
137 assert!(
138 layout.size() > 0,
139 "cannot allocate zero-sized types in Arena"
140 );
141
142 let ptr = self.alloc_raw(layout);
143
144 unsafe {
147 let typed = ptr.as_ptr().cast::<T>();
148 std::ptr::copy_nonoverlapping(values.as_ptr(), typed, values.len());
149 std::slice::from_raw_parts(typed, values.len())
150 }
151 }
152
153 fn alloc_raw(&self, layout: Layout) -> NonNull<u8> {
154 let mut chunks = self.chunks.borrow_mut();
155
156 if let Some(chunk) = chunks.last_mut()
158 && let Some(ptr) = chunk.try_alloc(layout)
159 {
160 return ptr;
161 }
162
163 let size = layout.size().max(self.block_size);
166 let align = layout.align();
167 let mut new_chunk = Chunk::new(size, align);
168 let ptr = new_chunk
169 .try_alloc(layout)
170 .expect("new chunk should fit any layout up to its size");
171 chunks.push(new_chunk);
172 ptr
173 }
174}
175
176impl Default for Arena {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl Drop for Arena {
183 fn drop(&mut self) {
184 }
187}
188
189struct Chunk {
190 memory: NonNull<u8>,
191 size: usize,
192 align: usize,
193 offset: usize,
194}
195
196impl Chunk {
197 fn new(size: usize, align: usize) -> Self {
198 let layout = Layout::from_size_align(size, align).expect("invalid chunk layout");
199 let memory = unsafe { NonNull::new_unchecked(alloc::alloc(layout)) };
201 Self {
202 memory,
203 size,
204 align,
205 offset: 0,
206 }
207 }
208
209 fn try_alloc(&mut self, layout: Layout) -> Option<NonNull<u8>> {
210 let aligned_offset = align_up(self.offset, layout.align());
211 let end = aligned_offset.checked_add(layout.size())?;
212 if end > self.size {
213 return None;
214 }
215
216 let ptr = unsafe { NonNull::new_unchecked(self.memory.as_ptr().add(aligned_offset)) };
218 self.offset = end;
219 Some(ptr)
220 }
221}
222
223impl Drop for Chunk {
224 fn drop(&mut self) {
225 let layout = Layout::from_size_align(self.size, self.align).expect("invalid chunk layout");
230 unsafe {
232 alloc::dealloc(self.memory.as_ptr(), layout);
233 }
234 }
235}
236
237fn align_up(offset: usize, align: usize) -> usize {
238 assert!(align.is_power_of_two(), "alignment must be a power of two");
239 (offset + align - 1) & !(align - 1)
240}
241
242pub struct OwnedExpr<T> {
244 #[allow(dead_code)]
245 arena: Box<Arena>,
246 root: *mut T,
247 _marker: PhantomData<T>,
248}
249
250impl<T> OwnedExpr<T> {
251 pub unsafe fn new(arena: Box<Arena>, root: *mut T) -> Self {
258 Self {
259 arena,
260 root,
261 _marker: PhantomData,
262 }
263 }
264
265 pub fn root(&self) -> &T {
267 unsafe { &*self.root }
269 }
270}
271
272unsafe impl<T: Send> Send for OwnedExpr<T> {}
273unsafe impl<T: Sync> Sync for OwnedExpr<T> {}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use proptest::prelude::*;
279
280 mod simple {
281 use super::*;
282
283 #[test]
284 fn allocate_single_integer() {
285 let arena = Arena::new();
286 let value = arena.allocate_with(|| 42);
287 assert_eq!(*value, 42);
288 }
289
290 #[test]
291 fn allocate_two_integers() {
292 let arena = Arena::new();
293 let a = arena.allocate_with(|| 1);
294 let b = arena.allocate_with(|| 2);
295 assert_eq!(*a, 1);
296 assert_eq!(*b, 2);
297 }
298
299 #[test]
300 fn allocate_empty_slice() {
301 let arena = Arena::new();
302 let slice: &[i32] = arena.allocate_slice(&[]);
303 assert!(slice.is_empty());
304 }
305
306 #[test]
307 fn allocate_small_slice() {
308 let arena = Arena::new();
309 let data = [10, 20, 30];
310 let slice = arena.allocate_slice(&data);
311 assert_eq!(slice, &data[..]);
312 }
313
314 #[test]
315 fn arena_default_matches_new() {
316 let arena: Arena = Default::default();
317 let value = arena.allocate_with(|| "x");
318 assert_eq!(*value, "x");
319 }
320 }
321
322 mod medium {
323 use super::*;
324
325 #[test]
326 fn allocate_larger_than_block() {
327 let arena = Arena::with_capacity(16);
328 let data = [0u8; 128];
329 let ptr = arena.allocate_with(|| data);
330 assert_eq!(*ptr, data);
331 }
332
333 #[test]
334 fn allocate_slice_larger_than_block() {
335 let arena = Arena::with_capacity(16);
336 let values: Vec<u8> = (0..=255).collect();
337 let slice = arena.allocate_slice(&values);
338 assert_eq!(slice, &values[..]);
339 }
340
341 #[test]
342 fn multiple_chunks_for_many_values() {
343 let arena = Arena::with_capacity(32);
344 let mut sum = 0i64;
345 for i in 0..100 {
346 let value = arena.allocate_with(|| i);
347 sum += *value;
348 }
349 assert_eq!(sum, 4950);
350 }
351
352 #[test]
353 fn multiple_chunks_for_many_slices() {
354 let arena = Arena::with_capacity(64);
355 let mut total = 0i64;
356 for i in 0..50 {
357 let values: Vec<i64> = (0..10).map(|j| i * 10 + j).collect();
358 let slice = arena.allocate_slice(&values);
359 total += slice.iter().sum::<i64>();
360 }
361 assert_eq!(total, 124_750);
362 }
363
364 #[test]
365 fn owned_expr_keeps_arena_alive() {
366 let arena = Box::new(Arena::new());
367 let root = arena.allocate_with(|| 123);
368 let root_ptr: *mut i32 = root;
369 let owned = unsafe { OwnedExpr::new(arena, root_ptr) };
371 assert_eq!(*owned.root(), 123);
372 }
373 }
374
375 mod complex {
376 use super::*;
377
378 #[test]
379 fn copy_values_survive_arena_drop() {
380 let value = {
381 let arena = Arena::new();
382 let ptr = arena.allocate_with(|| 42i32);
383 *ptr
384 };
385 assert_eq!(value, 42);
386 }
387
388 #[test]
389 fn alignment_of_large_type() {
390 #[derive(Clone, Copy)]
391 #[repr(C, align(64))]
392 struct BigAlign(u64);
393
394 let arena = Arena::with_capacity(4096);
398 let values = [BigAlign(7)];
399 let slice = arena.allocate_slice(&values);
400 assert!((slice.as_ptr() as usize).is_multiple_of(64));
401 assert_eq!(slice[0].0, 7);
402 }
403
404 #[test]
405 fn alignment_of_single_value() {
406 #[derive(Clone, Copy)]
407 #[repr(C, align(64))]
408 struct BigAlign(u64);
409
410 let arena = Arena::with_capacity(4096);
411 let value = arena.allocate_with(|| BigAlign(7));
412 assert_eq!((value as *const BigAlign) as usize % 64, 0);
413 assert_eq!(value.0, 7);
414 }
415
416 #[test]
417 #[should_panic(expected = "cannot allocate zero-sized types in Arena")]
418 fn zero_sized_type_panics() {
419 let arena = Arena::new();
420 let _: &mut () = arena.allocate_with(|| ());
421 }
422
423 #[test]
424 #[should_panic(expected = "cannot allocate zero-sized types in Arena")]
425 fn zero_sized_slice_panics() {
426 let arena = Arena::new();
427 let _: &[()] = arena.allocate_slice(&[()]);
428 }
429
430 #[test]
431 fn owned_expr_is_send_sync() {
432 fn assert_send_sync<T: Send + Sync>() {}
433 assert_send_sync::<OwnedExpr<u8>>();
434 }
435 }
436
437 mod extreme {
438 use super::*;
439
440 #[test]
441 fn stress_mixed_allocations() {
442 let arena = Arena::with_capacity(256);
443 let mut total = 0usize;
444 for size in (1usize..=1000).step_by(7) {
445 let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
446 let ptr = arena.allocate_with(|| data.clone());
447 total += ptr.iter().map(|&x| x as usize).sum::<usize>();
448 }
449 assert!(total > 0);
450 }
451
452 proptest! {
453 #[test]
454 fn allocate_random_sizes(size in 1usize..10_000) {
455 let arena = Arena::with_capacity(256);
456 let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
457 let ptr = arena.allocate_with(|| data.clone());
458 prop_assert_eq!(&ptr[..], &data[..]);
459 }
460
461 #[test]
462 fn allocate_many_random_values(sizes in prop::collection::vec(1usize..512, 1..50)) {
463 let arena = Arena::with_capacity(256);
464 let mut total = 0usize;
465 for (idx, size) in sizes.iter().enumerate() {
466 let expected: Vec<u8> = (0..*size).map(|i| (i.wrapping_add(idx)) as u8).collect();
467 let ptr = arena.allocate_with(|| expected.clone());
468 prop_assert_eq!(&ptr[..], &expected[..]);
469 total += size;
470 }
471 prop_assert!(total > 0);
472 }
473
474 #[test]
475 fn slice_roundtrip(values in prop::collection::vec(0i32..100, 0..512)) {
476 let arena = Arena::with_capacity(256);
477 let slice = arena.allocate_slice(&values);
478 prop_assert_eq!(slice, &values[..]);
479 }
480 }
481 }
482}