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 pub fn reset(&self) {
200 let mut chunks = self.chunks.borrow_mut();
201 chunks.truncate(1);
203 if let Some(first) = chunks.first_mut() {
204 first.offset = 0;
205 }
206 }
207
208 pub fn chunk_count(&self) -> usize {
210 self.chunks.borrow().len()
211 }
212}
213
214impl Default for Arena {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220impl Drop for Arena {
221 fn drop(&mut self) {
222 }
225}
226
227struct Chunk {
228 memory: NonNull<u8>,
229 size: usize,
230 align: usize,
231 offset: usize,
232}
233
234impl Chunk {
235 fn new(size: usize, align: usize) -> Self {
236 let layout = Layout::from_size_align(size, align).expect("invalid chunk layout");
237 let memory = unsafe { NonNull::new_unchecked(alloc::alloc(layout)) };
239 Self {
240 memory,
241 size,
242 align,
243 offset: 0,
244 }
245 }
246
247 fn try_alloc(&mut self, layout: Layout) -> Option<NonNull<u8>> {
248 if layout.align() > self.align {
252 return None;
253 }
254 let aligned_offset = align_up(self.offset, layout.align());
255 let end = aligned_offset.checked_add(layout.size())?;
256 if end > self.size {
257 return None;
258 }
259
260 let ptr = unsafe { NonNull::new_unchecked(self.memory.as_ptr().add(aligned_offset)) };
262 self.offset = end;
263 Some(ptr)
264 }
265}
266
267impl Drop for Chunk {
268 fn drop(&mut self) {
269 let layout = Layout::from_size_align(self.size, self.align).expect("invalid chunk layout");
274 unsafe {
276 alloc::dealloc(self.memory.as_ptr(), layout);
277 }
278 }
279}
280
281fn align_up(offset: usize, align: usize) -> usize {
282 assert!(align.is_power_of_two(), "alignment must be a power of two");
283 (offset + align - 1) & !(align - 1)
284}
285
286pub struct OwnedExpr<T> {
288 #[allow(dead_code)]
289 arena: Box<Arena>,
290 root: *mut T,
291 _marker: PhantomData<T>,
292}
293
294impl<T> OwnedExpr<T> {
295 pub unsafe fn new(arena: Box<Arena>, root: *mut T) -> Self {
302 Self {
303 arena,
304 root,
305 _marker: PhantomData,
306 }
307 }
308
309 pub fn root(&self) -> &T {
311 unsafe { &*self.root }
313 }
314}
315
316unsafe impl<T: Send> Send for OwnedExpr<T> {}
317unsafe impl<T: Sync> Sync for OwnedExpr<T> {}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use proptest::prelude::*;
323
324 mod reset {
325 use super::*;
326
327 #[test]
328 fn reset_keeps_first_chunk() {
329 let arena = Arena::with_capacity(64);
330 for i in 0..100u64 {
332 arena.allocate_with(|| i);
333 }
334 assert!(arena.chunk_count() > 1);
335 arena.reset();
336 assert_eq!(arena.chunk_count(), 1);
337 }
338
339 #[test]
340 fn reset_allows_reuse() {
341 let arena = Arena::new();
342 let _ = arena.allocate_with(|| 1u64);
343 arena.reset();
344 let value = arena.allocate_with(|| 2u64);
345 assert_eq!(*value, 2);
346 }
347
348 #[test]
349 fn reset_reuse_steady_state_allocates_nothing() {
350 let arena = Arena::with_capacity(4096);
351 for round in 0..1000 {
352 for i in 0..50u64 {
353 let v = arena.allocate_with(|| i + round);
354 assert_eq!(*v, i + round);
355 }
356 arena.reset();
357 }
358 assert_eq!(arena.chunk_count(), 1);
360 }
361
362 #[test]
363 fn overaligned_allocation_gets_own_chunk() {
364 let arena = Arena::new();
365 let _ = arena.allocate_with(|| 1u8);
366 #[repr(align(64))]
367 #[derive(Copy, Clone)]
368 struct Wide(u64);
369 let w = arena.allocate_with(|| Wide(7));
370 assert_eq!(w as *const Wide as usize % 64, 0);
371 assert_eq!(w.0, 7);
372 }
373 }
374
375 mod simple {
376 use super::*;
377
378 #[test]
379 fn allocate_single_integer() {
380 let arena = Arena::new();
381 let value = arena.allocate_with(|| 42);
382 assert_eq!(*value, 42);
383 }
384
385 #[test]
386 fn allocate_two_integers() {
387 let arena = Arena::new();
388 let a = arena.allocate_with(|| 1);
389 let b = arena.allocate_with(|| 2);
390 assert_eq!(*a, 1);
391 assert_eq!(*b, 2);
392 }
393
394 #[test]
395 fn allocate_empty_slice() {
396 let arena = Arena::new();
397 let slice: &[i32] = arena.allocate_slice(&[]);
398 assert!(slice.is_empty());
399 }
400
401 #[test]
402 fn allocate_small_slice() {
403 let arena = Arena::new();
404 let data = [10, 20, 30];
405 let slice = arena.allocate_slice(&data);
406 assert_eq!(slice, &data[..]);
407 }
408
409 #[test]
410 fn arena_default_matches_new() {
411 let arena: Arena = Default::default();
412 let value = arena.allocate_with(|| "x");
413 assert_eq!(*value, "x");
414 }
415 }
416
417 mod medium {
418 use super::*;
419
420 #[test]
421 fn allocate_larger_than_block() {
422 let arena = Arena::with_capacity(16);
423 let data = [0u8; 128];
424 let ptr = arena.allocate_with(|| data);
425 assert_eq!(*ptr, data);
426 }
427
428 #[test]
429 fn allocate_slice_larger_than_block() {
430 let arena = Arena::with_capacity(16);
431 let values: Vec<u8> = (0..=255).collect();
432 let slice = arena.allocate_slice(&values);
433 assert_eq!(slice, &values[..]);
434 }
435
436 #[test]
437 fn multiple_chunks_for_many_values() {
438 let arena = Arena::with_capacity(32);
439 let mut sum = 0i64;
440 for i in 0..100 {
441 let value = arena.allocate_with(|| i);
442 sum += *value;
443 }
444 assert_eq!(sum, 4950);
445 }
446
447 #[test]
448 fn multiple_chunks_for_many_slices() {
449 let arena = Arena::with_capacity(64);
450 let mut total = 0i64;
451 for i in 0..50 {
452 let values: Vec<i64> = (0..10).map(|j| i * 10 + j).collect();
453 let slice = arena.allocate_slice(&values);
454 total += slice.iter().sum::<i64>();
455 }
456 assert_eq!(total, 124_750);
457 }
458
459 #[test]
460 fn owned_expr_keeps_arena_alive() {
461 let arena = Box::new(Arena::new());
462 let root = arena.allocate_with(|| 123);
463 let root_ptr: *mut i32 = root;
464 let owned = unsafe { OwnedExpr::new(arena, root_ptr) };
466 assert_eq!(*owned.root(), 123);
467 }
468 }
469
470 mod complex {
471 use super::*;
472
473 #[test]
474 fn copy_values_survive_arena_drop() {
475 let value = {
476 let arena = Arena::new();
477 let ptr = arena.allocate_with(|| 42i32);
478 *ptr
479 };
480 assert_eq!(value, 42);
481 }
482
483 #[test]
484 fn alignment_of_large_type() {
485 #[derive(Clone, Copy)]
486 #[repr(C, align(64))]
487 struct BigAlign(u64);
488
489 let arena = Arena::with_capacity(4096);
493 let values = [BigAlign(7)];
494 let slice = arena.allocate_slice(&values);
495 assert!((slice.as_ptr() as usize).is_multiple_of(64));
496 assert_eq!(slice[0].0, 7);
497 }
498
499 #[test]
500 fn alignment_of_single_value() {
501 #[derive(Clone, Copy)]
502 #[repr(C, align(64))]
503 struct BigAlign(u64);
504
505 let arena = Arena::with_capacity(4096);
506 let value = arena.allocate_with(|| BigAlign(7));
507 assert_eq!((value as *const BigAlign) as usize % 64, 0);
508 assert_eq!(value.0, 7);
509 }
510
511 #[test]
512 #[should_panic(expected = "cannot allocate zero-sized types in Arena")]
513 fn zero_sized_type_panics() {
514 let arena = Arena::new();
515 let _: &mut () = arena.allocate_with(|| ());
516 }
517
518 #[test]
519 #[should_panic(expected = "cannot allocate zero-sized types in Arena")]
520 fn zero_sized_slice_panics() {
521 let arena = Arena::new();
522 let _: &[()] = arena.allocate_slice(&[()]);
523 }
524
525 #[test]
526 fn owned_expr_is_send_sync() {
527 fn assert_send_sync<T: Send + Sync>() {}
528 assert_send_sync::<OwnedExpr<u8>>();
529 }
530 }
531
532 mod extreme {
533 use super::*;
534
535 #[test]
536 fn stress_mixed_allocations() {
537 let arena = Arena::with_capacity(256);
538 let mut total = 0usize;
539 for size in (1usize..=1000).step_by(7) {
540 let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
541 let ptr = arena.allocate_with(|| data.clone());
542 total += ptr.iter().map(|&x| x as usize).sum::<usize>();
543 }
544 assert!(total > 0);
545 }
546
547 proptest! {
548 #[test]
549 fn allocate_random_sizes(size in 1usize..10_000) {
550 let arena = Arena::with_capacity(256);
551 let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
552 let ptr = arena.allocate_with(|| data.clone());
553 prop_assert_eq!(&ptr[..], &data[..]);
554 }
555
556 #[test]
557 fn allocate_many_random_values(sizes in prop::collection::vec(1usize..512, 1..50)) {
558 let arena = Arena::with_capacity(256);
559 let mut total = 0usize;
560 for (idx, size) in sizes.iter().enumerate() {
561 let expected: Vec<u8> = (0..*size).map(|i| (i.wrapping_add(idx)) as u8).collect();
562 let ptr = arena.allocate_with(|| expected.clone());
563 prop_assert_eq!(&ptr[..], &expected[..]);
564 total += size;
565 }
566 prop_assert!(total > 0);
567 }
568
569 #[test]
570 fn slice_roundtrip(values in prop::collection::vec(0i32..100, 0..512)) {
571 let arena = Arena::with_capacity(256);
572 let slice = arena.allocate_slice(&values);
573 prop_assert_eq!(slice, &values[..]);
574 }
575 }
576 }
577}