1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! `Drop` implementation for `Arena`, and `reset` method.
use std::{alloc, ptr::NonNull};
use super::{Arena, ChunkFooter, utils::is_pointer_aligned_to};
impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
/// Reset this arena.
///
/// Performs mass deallocation on everything allocated in this arena by resetting the pointer into
/// the underlying chunk of memory to the start of the chunk. Does not run any `Drop` implementations
/// on deallocated objects. See [the top-level documentation] for details.
///
/// If this arena has allocated multiple chunks to allocate into, then the excess chunks are returned to
/// the global allocator.
///
/// # Example
///
/// ```
/// # use oxc_allocator::arena::Arena;
///
/// let mut arena = Arena::new();
///
/// // Allocate a bunch of things
/// {
/// for i in 0..100 {
/// arena.alloc(i);
/// }
/// }
///
/// // Reset the arena
/// arena.reset();
///
/// // Allocate some new things in the space
/// // previously occupied by the original things
/// for j in 200..400 {
/// arena.alloc(j);
/// }
/// ```
///
/// [the top-level documentation]: Arena
pub fn reset(&mut self) {
#[cfg(all(feature = "track_allocations", not(feature = "disable_track_allocations")))]
self.stats.reset();
// Takes `&mut self` so `self` must be unique, and there can't be any borrows active
// that would get invalidated by resetting
unsafe {
let Some(current_footer_ptr) = self.current_chunk_footer_ptr.get() else {
return;
};
// Deallocate all chunks except the current one
let prev_footer_ptr =
current_footer_ptr.as_ref().previous_chunk_footer_ptr.replace(None);
dealloc_chunk_list(prev_footer_ptr);
// Reset the bump cursor to the end of the chunk.
// We don't need to reset `cursor_ptr` in `ChunkFooter`, as it'll be set if the chunk is retired later on.
// `iter_allocated_chunks_raw` ignores `cursor_ptr` of the current chunk.
debug_assert!(
is_pointer_aligned_to(current_footer_ptr, MIN_ALIGN),
"bump pointer {current_footer_ptr:#p} should be aligned to the minimum alignment of {MIN_ALIGN:#x}"
);
self.cursor_ptr.set(current_footer_ptr.cast::<u8>());
debug_assert!(
current_footer_ptr.as_ref().previous_chunk_footer_ptr.get().is_none(),
"We should only have a single chunk"
);
debug_assert_eq!(
self.cursor_ptr.get(),
current_footer_ptr.cast::<u8>(),
"Our chunk's bump cursor should be reset to the start of its allocation"
);
}
}
}
/// `Drop` implementation for `Arena`.
impl<const MIN_ALIGN: usize> Drop for Arena<MIN_ALIGN> {
fn drop(&mut self) {
unsafe {
dealloc_chunk_list(self.current_chunk_footer_ptr.get());
}
}
}
/// Deallocate all chunks in linked list, starting with the chunk whose footer is pointed to by `footer_ptr`.
///
/// # SAFETY
///
/// `footer_ptr` must point to a valid `ChunkFooter`.
#[inline]
unsafe fn dealloc_chunk_list(footer_ptr: Option<NonNull<ChunkFooter>>) {
let mut next_footer_ptr = footer_ptr;
while let Some(footer_ptr) = next_footer_ptr {
// Create `&ChunkFooter` reference to within a block, to ensure the reference is not live
// when we deallocate the chunk's memory (which includes the `ChunkFooter`)
let (start_ptr, layout) = {
// SAFETY: `footer_ptr` always points to a valid `ChunkFooter`
let footer = unsafe { footer_ptr.as_ref() };
next_footer_ptr = footer.previous_chunk_footer_ptr.get();
(footer.start_ptr, footer.layout)
};
// SAFETY: Each `ChunkFooter`'s `start_ptr` and `layout` describe its backing allocation,
// which was allocated from the global allocator
unsafe { alloc::dealloc(start_ptr.as_ptr(), layout) };
}
}