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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! `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 within a block, to ensure the reference is not live
// when we deallocate the chunk's memory (which includes the `ChunkFooter`)
{
// SAFETY: `footer_ptr` always points to a valid `ChunkFooter`
next_footer_ptr = unsafe { footer_ptr.as_ref() }.previous_chunk_footer_ptr.get();
}
// SAFETY: `footer_ptr` always points to a valid `ChunkFooter`
unsafe { dealloc_chunk(footer_ptr) };
}
}
/// Deallocate the chunk whose footer is pointed to by `footer_ptr`.
///
/// # SAFETY
/// `footer_ptr` must point to a valid `ChunkFooter`.
unsafe fn dealloc_chunk(footer_ptr: NonNull<ChunkFooter>) {
// Create `&ChunkFooter` reference within a block, to ensure the reference is not live
// when we deallocate the chunk's memory (which includes the `ChunkFooter`)
let (backing_alloc_ptr, layout, is_fixed_size) = {
// SAFETY: Caller guarantees that `footer_ptr` points to a valid `ChunkFooter`
let footer = unsafe { footer_ptr.as_ref() };
(footer.backing_alloc_ptr.as_ptr(), footer.layout, footer.is_fixed_size)
};
// If is a fixed-size allocator, delegate to `dealloc_fixed_size_arena_chunk`
if is_fixed_size {
#[cfg(all(feature = "fixed_size", target_pointer_width = "64", target_endian = "little"))]
{
// SAFETY: Caller guarantees that `footer_ptr` points to a valid `ChunkFooter`,
// and `is_fixed_size` indicates that the chunk is fixed-size
unsafe { super::fixed_size::dealloc_fixed_size_arena_chunk(footer_ptr) };
return;
}
// Note: `napi/parser` and Oxlint's `RuleTester` create `Arena`s with `is_fixed_size: true`,
// but in both cases the memory backing the `Arena` is owned by JS, and they prevent the `Arena`
// from being dropped, by wrapping it in `ManuallyDrop`. So this code is unreachable in those cases.
//
// Using `debug_assert!` not `panic!` here means this whole `if is_fixed_size { ... }` block
// is dead code in release builds where `fixed_size` feature is not enabled.
#[cfg(not(all(
feature = "fixed_size",
target_pointer_width = "64",
target_endian = "little"
)))]
{
debug_assert!(
false,
"`is_fixed_size` should be `false` when `fixed_size` feature is disabled"
);
}
}
// SAFETY: Each `ChunkFooter`'s `backing_alloc_ptr` and `layout` describe its backing allocation.
// `is_fixed_size` is `false`, so backing allocation was made via global allocator.
unsafe { alloc::dealloc(backing_alloc_ptr, layout) };
}