1#![doc = include_str!("../README.md")]
2
3use allocator_api2::alloc::{AllocError, Allocator};
4use std::alloc::Layout;
5use std::cell::Cell;
6use std::ptr::NonNull;
7
8#[cfg(feature = "collections")]
9mod arena_box;
10#[cfg(feature = "collections")]
11mod arena_string;
12#[cfg(feature = "collections")]
13mod arena_vec;
14mod pool;
15#[cfg(feature = "collections")]
16mod raw_vec;
17mod vm;
18
19#[cfg(feature = "collections")]
20pub use arena_box::Box;
21#[cfg(feature = "collections")]
22pub use arena_string::String;
23#[cfg(feature = "collections")]
24pub use arena_vec::{Drain, IntoIter, Vec};
25
26#[cfg(all(target_pointer_width = "64", any(unix, windows)))]
29pub const BLOCK_ALIGN: usize = 4 * 1024 * 1024 * 1024;
30#[cfg(not(all(target_pointer_width = "64", any(unix, windows))))]
31pub const BLOCK_ALIGN: usize = 1;
32
33pub const MAX_BLOCK_SIZE: usize = 2 * 1024 * 1024 * 1024 - 16;
36
37const INITIAL_COMMIT: usize = 64 * 1024;
40
41const CHUNK_ALIGN: usize = 16;
44
45#[derive(Clone, Copy)]
47enum Backing {
48 Reservation { reservation: NonNull<u8>, reserved: usize },
50 Heap(Layout),
52 Borrowed,
54}
55
56#[derive(Clone, Copy)]
57struct Region {
58 base: NonNull<u8>,
59 size: usize,
60 backing: Backing,
61}
62
63struct Chunk {
65 region: Region,
66 used: usize,
68 prev: Option<NonNull<Chunk>>,
69}
70
71pub struct Arena {
76 region: Cell<Region>,
78 cursor: Cell<usize>,
80 resident: Cell<usize>,
83 #[cfg(windows)]
85 committed: Cell<usize>,
86 prev: Cell<Option<NonNull<Chunk>>>,
88 prev_used: Cell<usize>,
90 prev_size: Cell<usize>,
91}
92
93impl Arena {
94 #[inline]
100 pub fn new() -> Self {
101 let size = if vm::RESERVABLE { MAX_BLOCK_SIZE } else { INITIAL_COMMIT };
102 let region = Self::new_chunk(size).expect("arena backing reservation failed");
103 Self::from_region(region)
104 }
105
106 pub fn with_capacity(size: usize) -> Self {
114 let requested = size.clamp(1, MAX_BLOCK_SIZE);
115 let region = Self::new_chunk(requested).expect("arena backing reservation failed");
116 Self::from_region(region)
117 }
118
119 fn new_chunk(size: usize) -> Option<Region> {
122 let size = size.clamp(1, MAX_BLOCK_SIZE);
123 let reservation_size =
124 if vm::RESERVABLE { size.max(INITIAL_COMMIT).next_power_of_two().min(MAX_BLOCK_SIZE) } else { size };
125 pool::take(reservation_size)
126 .or_else(|| vm::reserve(reservation_size))
127 .map(|reservation| Region {
128 base: reservation.base,
129 size: reservation.size,
130 backing: Backing::Reservation { reservation: reservation.reservation, reserved: reservation.reserved },
131 })
132 .or_else(|| Self::heap_chunk(size, CHUNK_ALIGN))
133 }
134
135 fn heap_chunk(size: usize, align: usize) -> Option<Region> {
140 let layout = Layout::from_size_align(size, align).ok().filter(|layout| layout.size() > 0)?;
141 let base = NonNull::new(unsafe { std::alloc::alloc(layout) })?;
143 Some(Region { base, size: layout.size(), backing: Backing::Heap(layout) })
144 }
145
146 fn from_region(region: Region) -> Self {
147 Self {
148 region: Cell::new(region),
149 cursor: Cell::new(0),
150 resident: Cell::new(0),
151 #[cfg(windows)]
152 committed: Cell::new(if matches!(region.backing, Backing::Reservation { .. }) { 0 } else { region.size }),
153 prev: Cell::new(None),
154 prev_used: Cell::new(0),
155 prev_size: Cell::new(0),
156 }
157 }
158
159 pub unsafe fn from_raw_parts(ptr: NonNull<u8>, size: usize) -> Self {
168 debug_assert!((ptr.as_ptr() as usize).is_multiple_of(BLOCK_ALIGN), "borrowed arena base must be 4 GiB aligned");
169 debug_assert!(size <= MAX_BLOCK_SIZE, "borrowed arena must not exceed MAX_BLOCK_SIZE");
170 Self::from_region(Region { base: ptr, size, backing: Backing::Borrowed })
171 }
172 #[inline]
174 pub fn base_ptr(&self) -> NonNull<u8> {
175 self.region.get().base
176 }
177
178 #[inline]
189 pub fn transfer_base(&self) -> Option<usize> {
190 if !cfg!(target_pointer_width = "64") {
191 return Some(0);
192 }
193 let region = self.region.get();
196 let base = region.base.as_ptr() as usize;
197 (vm::RESERVABLE && self.prev.get().is_none() && base.is_multiple_of(BLOCK_ALIGN)).then_some(base)
198 }
199
200 #[inline]
202 pub fn used_bytes(&self) -> usize {
203 self.prev_used.get() + self.cursor.get()
204 }
205 #[inline]
207 pub fn capacity(&self) -> usize {
208 self.prev_size.get() + self.region.get().size
209 }
210
211 pub fn reset(&mut self) {
216 self.resident.set(self.resident_bytes());
217 self.cursor.set(0);
218 while let Some(node) = self.prev.get() {
219 let chunk = *unsafe { std::boxed::Box::from_raw(node.as_ptr()) };
222 unsafe { release(self.region.get(), self.resident.get()) };
224 self.region.set(chunk.region);
225 self.resident.set(chunk.used);
226 self.prev.set(chunk.prev);
227 #[cfg(windows)]
229 self.committed.set(chunk.region.size);
230 }
231 self.prev_used.set(0);
232 self.prev_size.set(0);
233 }
234
235 #[inline]
237 fn resident_bytes(&self) -> usize {
238 self.resident.get().max(self.cursor.get())
239 }
240}
241
242unsafe fn release(region: Region, used: usize) {
249 match region.backing {
250 Backing::Reservation { reservation, reserved } => {
251 let kept = vm::Reservation { reservation, reserved, base: region.base, size: region.size };
252 if unsafe { pool::give(kept, used) } {
254 return;
255 }
256 unsafe { vm::release(reservation, reserved) };
258 }
259 Backing::Heap(layout) => unsafe { std::alloc::dealloc(region.base.as_ptr(), layout) },
261 Backing::Borrowed => {}
262 }
263}
264
265#[inline]
267fn pad_to(addr: usize, align: usize) -> usize {
268 debug_assert!(align.is_power_of_two(), "Layout alignment is always a power of two");
269 (align - (addr & (align - 1))) & (align - 1)
270}
271
272#[inline]
277#[cfg(any(test, windows))]
278fn commit_target(end: usize, committed: usize, size: usize) -> usize {
279 end.max(committed.saturating_mul(2)).max(INITIAL_COMMIT).min(size).max(end)
280}
281
282impl Arena {
283 #[cfg(windows)]
285 #[cold]
286 #[inline(never)]
287 fn commit_to(&self, end: usize) -> bool {
288 let region = self.region.get();
289 let committed = self.committed.get();
290 let target = commit_target(end, committed, region.size);
291 let ptr = unsafe { NonNull::new_unchecked(region.base.as_ptr().add(committed)) };
293 if !unsafe { vm::commit(ptr, target - committed) } {
295 return false;
296 }
297 self.committed.set(target);
298 true
299 }
300 #[cold]
302 #[inline(never)]
303 fn grow_chunk(&self, layout: Layout) -> Option<NonNull<[u8]>> {
304 let region = self.region.get();
305 if matches!(region.backing, Backing::Borrowed) {
306 return None;
307 }
308 let total = self.capacity();
309 let spare = MAX_BLOCK_SIZE.checked_sub(total)?;
312 let size = layout.size().max(total.max(INITIAL_COMMIT).min(spare));
315 if size > spare {
316 return None;
317 }
318 let new_region = Self::heap_chunk(size, layout.align().max(CHUNK_ALIGN))?;
320 let retired = Chunk { region, used: self.resident_bytes(), prev: self.prev.get() };
321 self.prev_used.set(self.prev_used.get() + self.cursor.get());
322 self.prev_size.set(total);
323 self.prev
325 .set(Some(unsafe { NonNull::new_unchecked(std::boxed::Box::into_raw(std::boxed::Box::new(retired))) }));
326 self.region.set(new_region);
327 self.cursor.set(0);
328 self.resident.set(0);
329 #[cfg(windows)]
331 self.committed.set(new_region.size);
332 self.bump(layout)
334 }
335
336 #[inline]
340 fn bump(&self, layout: Layout) -> Option<NonNull<[u8]>> {
341 let align = layout.align();
342 let bytes = layout.size();
343 debug_assert!(align.is_power_of_two(), "Layout alignment is always a power of two");
344 let region = self.region.get();
345 let start = self.cursor.get();
346 debug_assert!(start <= region.size, "cursor must never exceed the chunk size");
347 let aligned = start + pad_to(region.base.as_ptr() as usize + start, align);
350 let end = aligned.checked_add(bytes)?;
351 if end > region.size {
352 return self.grow_chunk(layout);
353 }
354 #[cfg(windows)]
355 if end > self.committed.get() && !self.commit_to(end) {
356 return None;
357 }
358 debug_assert!(aligned >= start && end >= aligned, "bump must advance the cursor monotonically");
359 self.cursor.set(end);
360 let ptr = unsafe { NonNull::new_unchecked(region.base.as_ptr().add(aligned)) };
362 debug_assert_eq!(ptr.as_ptr() as usize % align, 0, "returned pointer must satisfy the requested alignment");
363 Some(NonNull::slice_from_raw_parts(ptr, bytes))
364 }
365
366 #[inline]
372 fn empty(&self, align: usize) -> Option<NonNull<[u8]>> {
373 let region = self.region.get();
374 let start = self.cursor.get();
375 let aligned = start + pad_to(region.base.as_ptr() as usize + start, align);
376 if aligned > region.size {
377 return None;
378 }
379 let ptr = unsafe { NonNull::new_unchecked(region.base.as_ptr().add(aligned)) };
381 Some(NonNull::slice_from_raw_parts(ptr, 0))
382 }
383}
384
385impl std::fmt::Debug for Arena {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 f.debug_struct("Arena").field("capacity", &self.capacity()).field("used", &self.used_bytes()).finish()
388 }
389}
390
391impl Default for Arena {
392 #[inline]
393 fn default() -> Self {
394 Self::new()
395 }
396}
397
398impl Drop for Arena {
399 fn drop(&mut self) {
400 unsafe { release(self.region.get(), self.resident_bytes()) };
402 let mut node = self.prev.get();
403 while let Some(chunk) = node {
404 let chunk = *unsafe { std::boxed::Box::from_raw(chunk.as_ptr()) };
406 node = chunk.prev;
407 unsafe { release(chunk.region, chunk.used) };
409 }
410 }
411}
412
413unsafe impl Allocator for &Arena {
414 #[inline]
415 fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
416 if layout.size() == 0 {
417 return self.empty(layout.align()).ok_or(AllocError);
418 }
419 self.bump(layout).ok_or(AllocError)
420 }
421
422 #[inline]
423 unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {
424 }
426
427 #[inline]
428 unsafe fn grow(
429 &self,
430 ptr: NonNull<u8>,
431 old_layout: Layout,
432 new_layout: Layout,
433 ) -> Result<NonNull<[u8]>, AllocError> {
434 if new_layout.size() == 0 {
435 return self.empty(new_layout.align()).ok_or(AllocError);
437 }
438 if old_layout.size() == 0 {
439 return self.bump(new_layout).ok_or(AllocError);
441 }
442 debug_assert!(new_layout.size() >= old_layout.size(), "grow must not shrink");
443 let region = self.region.get();
448 let addr = ptr.as_ptr() as usize;
449 let base = region.base.as_ptr() as usize;
450 let offset = addr.wrapping_sub(base);
451 if new_layout.align() <= old_layout.align()
452 && offset < region.size
453 && offset + old_layout.size() == self.cursor.get()
454 && offset + new_layout.size() <= region.size
455 {
456 let end = offset + new_layout.size();
457 #[cfg(windows)]
458 if end > self.committed.get() && !self.commit_to(end) {
459 return Err(AllocError);
460 }
461 self.cursor.set(end);
462 return Ok(NonNull::slice_from_raw_parts(ptr, new_layout.size()));
463 }
464 let new = self.bump(new_layout).ok_or(AllocError)?;
466 unsafe {
469 std::ptr::copy_nonoverlapping(ptr.as_ptr(), new.as_ptr() as *mut u8, old_layout.size());
470 }
471 Ok(new)
472 }
473}
474
475#[cfg(test)]
476mod test {
477 use crate::{Arena, BLOCK_ALIGN, INITIAL_COMMIT, MAX_BLOCK_SIZE, commit_target, vm};
478 use allocator_api2::alloc::Allocator;
479 use std::alloc::Layout;
480
481 fn growing(size: usize) -> Arena {
484 let region = Arena::heap_chunk(size, crate::CHUNK_ALIGN).unwrap();
485 Arena::from_region(region)
486 }
487
488 #[test]
489 fn commit_target_covers_the_request_and_doubles() {
490 let size = MAX_BLOCK_SIZE;
491 assert_eq!(commit_target(1, 0, size), INITIAL_COMMIT);
493 assert_eq!(commit_target(INITIAL_COMMIT + 1, INITIAL_COMMIT, size), INITIAL_COMMIT * 2);
495 assert_eq!(commit_target(9 << 20, 1 << 20, size), 9 << 20);
497 assert_eq!(commit_target(size, size - 1, size), size);
499 assert_eq!(commit_target(64, 0, 128), 128);
501 assert_eq!(commit_target(size, 0, size), size);
503 }
504
505 #[test]
506 fn base_is_4gib_aligned() {
507 if !vm::RESERVABLE {
508 return;
509 }
510 let arena = Arena::new();
511 assert!((arena.base_ptr().as_ptr() as usize).is_multiple_of(BLOCK_ALIGN));
512 let fixed = Arena::with_capacity(4096);
513 assert!((fixed.base_ptr().as_ptr() as usize).is_multiple_of(BLOCK_ALIGN));
514 }
515
516 #[test]
517 fn ptr_low_32_equals_offset() {
518 let arena = Arena::new();
519 let base = arena.base_ptr().as_ptr() as usize;
520 let layout = Layout::array::<u32>(2).unwrap();
521 let alloc = (&arena).allocate(layout).unwrap();
522 let data = alloc.as_ptr() as *mut u8 as usize;
523 let offset = data - base;
524 assert!(offset <= MAX_BLOCK_SIZE);
525 if vm::RESERVABLE {
526 assert_eq!(arena.transfer_base(), Some(base));
528 assert_eq!(data as u32 as usize, offset, "ptr low 32 bits must equal arena offset");
529 } else if cfg!(target_pointer_width = "64") {
530 assert_eq!(arena.transfer_base(), None);
532 } else {
533 assert_eq!(arena.transfer_base(), Some(0));
536 assert_eq!(data as u32 as usize, data);
537 }
538 }
539
540 #[test]
541 fn an_unaligned_region_promises_no_offsets() {
542 let arena = growing(1024);
545 if cfg!(target_pointer_width = "64") {
546 assert_eq!(arena.transfer_base(), None);
547 } else {
548 assert_eq!(arena.transfer_base(), Some(0));
549 }
550 }
551
552 #[test]
553 fn over_aligned_allocation_is_aligned() {
554 let arena = growing(INITIAL_COMMIT);
557 let _ = (&arena).allocate(Layout::from_size_align(1, 1).unwrap()).unwrap();
558 let a = (&arena).allocate(Layout::from_size_align(64, 64).unwrap()).unwrap();
559 assert_eq!(a.as_ptr() as *mut u8 as usize % 64, 0);
560 }
561
562 #[test]
563 fn bump_alignment_and_accounting() {
564 let arena = Arena::new();
565 let base = arena.base_ptr().as_ptr() as usize;
566 let a = (&arena).allocate(Layout::from_size_align(1, 1).unwrap()).unwrap();
568 assert_eq!(a.as_ptr() as *mut u8 as usize - base, 0);
569 let b = (&arena).allocate(Layout::from_size_align(8, 8).unwrap()).unwrap();
570 assert_eq!((b.as_ptr() as *mut u8 as usize - base) % 8, 0);
571 assert!(arena.used_bytes() >= 16);
572 }
573
574 #[test]
575 fn zero_sized_allocation_points_into_the_region() {
576 let arena = Arena::new();
577 let a = (&arena).allocate(Layout::from_size_align(0, 4).unwrap()).unwrap();
578 assert_eq!(a.len(), 0);
579 let offset = a.as_ptr() as *mut u8 as usize - arena.base_ptr().as_ptr() as usize;
581 assert_eq!(offset, 0);
582 assert_eq!(a.as_ptr() as *mut u8 as usize % 4, 0);
583 if vm::RESERVABLE {
584 assert_eq!(a.as_ptr() as *mut u8 as usize as u32 as usize, offset);
585 }
586 }
587
588 #[test]
589 fn growing_a_zero_sized_allocation_stays_empty() {
590 let arena = Arena::new();
591 let zst = Layout::from_size_align(0, 1).unwrap();
592 let a = (&arena).allocate(zst).unwrap();
593 let b = unsafe { (&arena).grow(a.cast::<u8>(), zst, zst) }.unwrap();
595 assert_eq!(b.len(), 0);
596 assert_eq!(arena.used_bytes(), 0);
597 let c = unsafe { (&arena).grow(b.cast::<u8>(), zst, Layout::from_size_align(32, 8).unwrap()) }.unwrap();
599 assert_eq!(c.len(), 32);
600 assert_eq!(c.as_ptr() as *mut u8 as usize, arena.base_ptr().as_ptr() as usize);
601 }
602
603 #[test]
604 fn from_raw_parts_over_borrowed_memory_never_frees() {
605 let backing = Arena::with_capacity(8192);
606 let backing_cap = backing.capacity();
607 let borrowed = unsafe { Arena::from_raw_parts(backing.base_ptr(), 4096) };
608 let a = (&borrowed).allocate(Layout::from_size_align(256, 1).unwrap()).unwrap();
609 assert_eq!(a.len(), 256);
610 assert!((&borrowed).allocate(Layout::from_size_align(8192, 1).unwrap()).is_err());
612 drop(borrowed);
613 assert_eq!(backing.capacity(), backing_cap, "capacity unchanged after dropping borrow");
616 assert!(backing_cap >= 8192, "capacity is at least the requested hint");
617 let b = (&backing).allocate(Layout::from_size_align(backing_cap, 1).unwrap()).unwrap();
618 unsafe { b.cast::<u8>().write_bytes(0x44, backing_cap) };
620 }
621
622 #[test]
623 fn tiny_alloc_commits_little_physical_memory() {
624 if !vm::RESERVABLE {
625 return;
626 }
627 let arena = Arena::new();
628 let _ = (&arena).allocate(Layout::new::<u64>()).unwrap();
629 assert!(arena.used_bytes() < 4096, "tiny alloc used {} bytes", arena.used_bytes());
630 assert!(arena.capacity() >= MAX_BLOCK_SIZE - 16);
631 }
632
633 #[test]
634 fn many_live_arenas_reserve_without_exhausting_memory() {
635 let arenas: Vec<Arena> = (0..64).map(|_| Arena::new()).collect();
638 for arena in &arenas {
639 let a = (&arena).allocate(Layout::from_size_align(64, 8).unwrap()).unwrap();
640 unsafe { a.cast::<u8>().write_bytes(0xAB, 64) };
642 }
643 assert_eq!(arenas.len(), 64);
644 }
645
646 #[test]
647 fn allocation_spanning_many_pages_is_usable() {
648 let arena = Arena::with_capacity(8 * 1024 * 1024);
649 let layout = Layout::from_size_align(4 * 1024 * 1024, 8).unwrap();
650 let a = (&arena).allocate(layout).unwrap();
651 unsafe { a.cast::<u8>().write_bytes(0xCD, layout.size()) };
653 let bytes = unsafe { a.as_ref() };
655 assert!(bytes.iter().all(|b| *b == 0xCD));
656 }
657
658 #[test]
659 fn borrowed_arena_exhaustion_returns_alloc_error() {
660 let backing = Arena::new();
661 let borrowed = unsafe { Arena::from_raw_parts(backing.base_ptr(), 128) };
662 assert!((&borrowed).allocate(Layout::from_size_align(64, 1).unwrap()).is_ok());
663 assert!((&borrowed).allocate(Layout::from_size_align(128, 1).unwrap()).is_err());
664 }
665
666 #[test]
667 fn with_capacity_is_a_growable_hint() {
668 let arena = Arena::with_capacity(128);
669 let initial = arena.capacity();
670 assert!((&arena).allocate(Layout::from_size_align(initial, 1).unwrap()).is_ok());
671 assert!((&arena).allocate(Layout::from_size_align(1, 1).unwrap()).is_ok());
672 assert!(arena.capacity() > initial, "the arena added a chunk when the initial region filled");
673 assert_eq!(arena.used_bytes(), initial + 1);
674 }
675
676 #[test]
677 fn capacity_hints_use_reusable_size_classes() {
678 if !vm::RESERVABLE {
679 return;
680 }
681 let above_initial = Arena::with_capacity(INITIAL_COMMIT + 1);
682 let next_class = Arena::with_capacity(INITIAL_COMMIT * 2);
683 assert_eq!(above_initial.capacity(), INITIAL_COMMIT * 2);
684 assert_eq!(above_initial.capacity(), next_class.capacity());
685 }
686
687 #[test]
688 fn growing_the_last_allocation_extends_it_in_place() {
689 let arena = Arena::new();
690 let old = Layout::from_size_align(64, 8).unwrap();
691 let a = (&arena).allocate(old).unwrap();
692 let new = Layout::from_size_align(4096, 8).unwrap();
693 let b = unsafe { (&arena).grow(a.cast::<u8>(), old, new) }.unwrap();
694 assert_eq!(a.as_ptr() as *mut u8, b.as_ptr() as *mut u8, "the last allocation grows without moving");
695 assert_eq!(arena.used_bytes(), 4096, "growing in place strands nothing");
696 }
697
698 #[test]
699 fn a_growing_arena_adds_chunks_and_keeps_counting() {
700 let arena = growing(1024);
701 let layout = Layout::from_size_align(256, 8).unwrap();
702 let allocs: Vec<_> = (0..64)
703 .map(|_| {
704 let a = (&arena).allocate(layout).unwrap();
705 unsafe { a.cast::<u8>().write_bytes(0xEE, layout.size()) };
707 a
708 })
709 .collect();
710 assert!(arena.capacity() > 1024, "the arena outgrew its first chunk");
711 assert_eq!(arena.used_bytes(), 64 * 256, "allocations in retired chunks are still counted");
712 if cfg!(target_pointer_width = "64") {
713 assert_eq!(arena.transfer_base(), None, "more than one chunk leaves nothing for offsets to be relative to");
714 } else {
715 assert_eq!(arena.transfer_base(), Some(0), "a 32 bit pointer is its own offset whatever the chunking");
716 }
717 for a in &allocs {
719 assert!(unsafe { a.as_ref() }.iter().all(|b| *b == 0xEE));
721 }
722 }
723
724 #[test]
725 fn a_growing_arena_serves_an_allocation_larger_than_a_chunk() {
726 let arena = growing(1024);
727 let layout = Layout::from_size_align(3 * 1024 * 1024, 8).unwrap();
728 let a = (&arena).allocate(layout).unwrap();
729 unsafe { a.cast::<u8>().write_bytes(0x11, layout.size()) };
731 assert!(arena.capacity() >= layout.size());
732 }
733
734 #[test]
735 fn growth_stops_at_the_offset_budget() {
736 let arena = growing(1024);
737 assert!((&arena).allocate(Layout::from_size_align(MAX_BLOCK_SIZE, 8).unwrap()).is_err());
739 assert!((&arena).allocate(Layout::from_size_align(64, 8).unwrap()).is_ok());
741 }
742
743 #[test]
744 fn reset_rewinds_to_the_first_chunk() {
745 let mut arena = growing(1024);
746 let layout = Layout::from_size_align(256, 8).unwrap();
747 for _ in 0..64 {
748 let _ = (&arena).allocate(layout).unwrap();
749 }
750 assert!(arena.capacity() > 1024, "the arena outgrew its first chunk");
751 arena.reset();
752 assert_eq!(arena.used_bytes(), 0);
753 assert_eq!(arena.capacity(), 1024, "chunks added since construction are freed");
754 let a = (&arena).allocate(layout).unwrap();
756 assert_eq!(a.as_ptr() as *mut u8, arena.base_ptr().as_ptr());
757 }
758
759 #[test]
760 fn reset_reuses_the_backing_region() {
761 let mut arena = Arena::with_capacity(2 * 1024 * 1024);
762 let base = arena.base_ptr();
763 let layout = Layout::from_size_align(1 << 20, 8).unwrap();
764 let a = (&arena).allocate(layout).unwrap();
765 unsafe { a.cast::<u8>().write_bytes(0x22, layout.size()) };
767 arena.reset();
768 assert_eq!(arena.used_bytes(), 0);
769 assert_eq!(arena.base_ptr(), base, "reset keeps the backing region");
770 let b = (&arena).allocate(layout).unwrap();
772 assert_eq!(b.as_ptr() as *mut u8, base.as_ptr());
773 unsafe { b.cast::<u8>().write_bytes(0x33, layout.size()) };
775 }
776}