1use core::{alloc::Layout, cell::Cell, mem::size_of, ptr::NonNull};
5
6const MIN_ALLOC_SIZE: usize = size_of::<usize>() * 2;
9
10pub const ARENA_CAPACITY: usize = 64 * 1024;
12pub const MAX_ARENA_ALLOC: usize = 16 * 1024;
14
15pub(crate) struct Hole {
17 pub offset: usize,
18 pub size: usize,
19}
20
21#[derive(Debug)]
23pub struct GcArena {
24 base: NonNull<u8>,
25 capacity: usize,
26 bump: Cell<usize>,
28 free_head: Cell<Option<NonNull<u8>>>,
30 live_count: Cell<usize>,
32}
33
34impl GcArena {
35 pub fn new(capacity: usize) -> Option<Self> {
37 let layout = Layout::from_size_align(capacity, 16).ok()?;
38 let ptr = unsafe { std::alloc::alloc(layout) };
39 if ptr.is_null() {
40 return None;
41 }
42 Some(Self {
43 base: NonNull::new(ptr)?,
44 capacity,
45 bump: Cell::new(0),
46 free_head: Cell::new(None),
47 live_count: Cell::new(0),
48 })
49 }
50
51 pub fn alloc(&self, layout: Layout) -> Option<NonNull<u8>> {
58 let needed = layout.size().max(MIN_ALLOC_SIZE);
59
60 let align = layout.align();
62 let offset = (self.bump.get() + align - 1) & !(align - 1);
63 if offset + needed <= self.capacity {
64 self.bump.set(offset + needed);
65 self.live_count.set(self.live_count.get() + 1);
66 let ptr = unsafe { self.base.as_ptr().add(offset) };
67 return Some(unsafe { NonNull::new_unchecked(ptr) });
68 }
69
70 if let Some(ptr) = self.alloc_from_free_list(needed) {
72 self.live_count.set(self.live_count.get() + 1);
73 return Some(ptr);
74 }
75
76 None
78 }
79
80 fn alloc_from_free_list(&self, needed: usize) -> Option<NonNull<u8>> {
82 let mut prev: Option<NonNull<u8>> = None;
83 let mut curr = self.free_head.get();
84 while let Some(entry) = curr {
85 let size = unsafe { read_free_size(entry) };
86 if size >= needed {
87 let next = unsafe { read_free_next(entry) };
89 if let Some(p) = prev {
90 unsafe { write_free_next(p, next) };
91 } else {
92 self.free_head.set(next);
93 }
94 return Some(entry);
95 }
96 prev = curr;
97 curr = unsafe { read_free_next(entry) };
98 }
99 None
100 }
101
102 pub(crate) fn collect_hole(&self, holes: &mut Vec<Hole>, node: NonNull<u8>, node_size: usize) {
107 let actual_size = node_size.max(MIN_ALLOC_SIZE);
110
111 let node_end = node.as_ptr() as usize + actual_size;
115 let bump_end = self.base.as_ptr() as usize + self.bump.get();
116 if node_end == bump_end {
117 self.bump
119 .set(node.as_ptr() as usize - self.base.as_ptr() as usize);
120 self.live_count.set(self.live_count.get().saturating_sub(1));
121 return;
122 }
123
124 let offset = node.as_ptr() as usize - self.base.as_ptr() as usize;
126 holes.push(Hole {
127 offset,
128 size: actual_size,
129 });
130 self.live_count.set(self.live_count.get().saturating_sub(1));
131 }
132
133 pub(crate) fn finish_sweep(&self, holes: &mut Vec<Hole>) {
135 if self.live_count.get() == 0 {
136 self.bump.set(0);
138 self.free_head.set(None);
139 holes.clear();
140 return;
141 }
142
143 holes.sort_by_key(|h| h.offset);
146
147 let merged_len = if holes.is_empty() {
150 0
151 } else {
152 let mut wi = 0;
153 for ri in 1..holes.len() {
154 if holes[wi].offset + holes[wi].size == holes[ri].offset {
155 holes[wi].size += holes[ri].size;
156 } else {
157 wi += 1;
158 if wi != ri {
159 holes.swap(wi, ri);
160 }
161 }
162 }
163 wi + 1
164 };
165 holes.truncate(merged_len);
166
167 self.free_head.set(None);
169 for hole in holes.drain(..).rev() {
170 let ptr = unsafe { NonNull::new_unchecked(self.base.as_ptr().add(hole.offset)) };
171 unsafe { write_free_size(ptr, hole.size) };
172 unsafe { write_free_next(ptr, self.free_head.get()) };
173 self.free_head.set(Some(ptr));
174 }
175 }
176}
177
178impl Drop for GcArena {
179 fn drop(&mut self) {
180 let layout = Layout::from_size_align(self.capacity, 16).unwrap();
181 unsafe { std::alloc::dealloc(self.base.as_ptr(), layout) };
182 }
183}
184
185unsafe fn read_free_size(entry: NonNull<u8>) -> usize {
192 unsafe { *(entry.as_ptr().cast::<usize>()) }
193}
194
195unsafe fn write_free_size(entry: NonNull<u8>, size: usize) {
196 unsafe {
197 *(entry.as_ptr().cast::<usize>()) = size;
198 }
199}
200
201unsafe fn read_free_next(entry: NonNull<u8>) -> Option<NonNull<u8>> {
202 unsafe { *(entry.as_ptr().add(size_of::<usize>()).cast::<Option<NonNull<u8>>>()) }
203}
204
205unsafe fn write_free_next(entry: NonNull<u8>, next: Option<NonNull<u8>>) {
206 unsafe {
207 *(entry.as_ptr().add(size_of::<usize>()).cast::<Option<NonNull<u8>>>()) = next;
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 fn alloc_one(arena: &GcArena) -> Option<NonNull<u8>> {
217 arena.alloc(Layout::new::<u64>())
218 }
219
220 unsafe fn read_u64(p: NonNull<u8>) -> u64 {
222 unsafe { *(p.as_ptr().cast::<u64>()) }
223 }
224
225 unsafe fn write_u64(p: NonNull<u8>, val: u64) {
227 unsafe {
228 *(p.as_ptr().cast::<u64>()) = val;
229 }
230 }
231
232 #[test]
235 fn test_arena_new_and_bump() {
236 let arena = GcArena::new(1024).unwrap();
237 let p1 = alloc_one(&arena).unwrap();
238 let p2 = alloc_one(&arena).unwrap();
239 assert_ne!(p1, p2);
241 let base = arena.base.as_ptr() as usize;
242 assert!((p1.as_ptr() as usize) >= base);
243 assert!((p2.as_ptr() as usize) < (base + 1024));
244 }
245
246 #[test]
247 fn test_arena_exhaustion() {
248 let arena = GcArena::new(64).unwrap();
249 assert!(alloc_one(&arena).is_some());
252 assert!(alloc_one(&arena).is_some());
253 assert!(alloc_one(&arena).is_some());
254 assert!(alloc_one(&arena).is_some());
255 assert!(alloc_one(&arena).is_none());
257 }
258
259 #[test]
260 fn test_bump_allocs_are_sequential_non_overlapping() {
261 let arena = GcArena::new(256).unwrap();
262 let p1 = alloc_one(&arena).unwrap();
263 let p2 = alloc_one(&arena).unwrap();
264 let p3 = alloc_one(&arena).unwrap();
265 assert!(p1.as_ptr() < p2.as_ptr());
267 assert!(p2.as_ptr() < p3.as_ptr());
268 }
269
270 fn exhaust_bump(arena: &GcArena) {
275 let remaining = arena.capacity - arena.bump.get();
276 if remaining > 0 {
277 let count = remaining / MIN_ALLOC_SIZE;
279 arena.bump.set(arena.capacity);
280 arena
281 .live_count
282 .set(arena.live_count.get() + count);
283 }
284 }
285
286 #[test]
287 fn test_free_list_reuses_hole() {
288 let arena = GcArena::new(256).unwrap();
289 let p1 = alloc_one(&arena).unwrap();
290 let p2 = alloc_one(&arena).unwrap();
291 let _p3 = alloc_one(&arena).unwrap();
292
293 let mut holes = Vec::new();
295 arena.collect_hole(&mut holes, p2, 8);
296
297 arena.finish_sweep(&mut holes);
299 assert!(holes.is_empty());
300
301 exhaust_bump(&arena);
304
305 let p_new = alloc_one(&arena).unwrap();
308 assert_eq!(p_new, p2, "free-list should reuse the hole at p2's address");
309 }
310
311 #[test]
312 fn test_free_list_two_adjacent_holes_merge() {
313 let arena = GcArena::new(256).unwrap();
314 let p1 = alloc_one(&arena).unwrap();
315 let p2 = alloc_one(&arena).unwrap();
316 let p3 = alloc_one(&arena).unwrap();
317 let _p4 = alloc_one(&arena).unwrap();
318
319 let mut holes = Vec::new();
321 arena.collect_hole(&mut holes, p2, 8);
322 arena.collect_hole(&mut holes, p3, 8);
323 assert_eq!(holes.len(), 2);
324
325 arena.finish_sweep(&mut holes);
326
327 exhaust_bump(&arena);
329
330 let big = arena
333 .alloc(Layout::from_size_align(16, 8).unwrap())
334 .unwrap();
335 assert_eq!(big, p2, "merged hole should cover p2..p3+8");
336 }
337
338 #[test]
339 fn test_free_list_three_adjacent_holes_merge() {
340 let arena = GcArena::new(256).unwrap();
341 let p1 = alloc_one(&arena).unwrap();
342 let p2 = alloc_one(&arena).unwrap();
343 let p3 = alloc_one(&arena).unwrap();
344 let p4 = alloc_one(&arena).unwrap();
345 let _p5 = alloc_one(&arena).unwrap();
346
347 let mut holes = Vec::new();
349 arena.collect_hole(&mut holes, p2, 8);
350 arena.collect_hole(&mut holes, p3, 8);
351 arena.collect_hole(&mut holes, p4, 8);
352 arena.finish_sweep(&mut holes);
353
354 exhaust_bump(&arena);
356
357 let big = arena
359 .alloc(Layout::from_size_align(24, 8).unwrap())
360 .unwrap();
361 assert_eq!(big, p2, "triple-merged hole should start at p2");
362 }
363
364 #[test]
367 fn test_frontier_rewind_last_node() {
368 let arena = GcArena::new(256).unwrap();
369 let p1 = alloc_one(&arena).unwrap();
370 let p2 = alloc_one(&arena).unwrap();
371 let p3 = alloc_one(&arena).unwrap();
372
373 let mut holes = Vec::new();
375 arena.collect_hole(&mut holes, p3, 8);
376 assert!(
379 holes.is_empty(),
380 "frontier rewind should not produce a hole"
381 );
382
383 let p_new = alloc_one(&arena).unwrap();
385 assert_eq!(p_new, p3, "frontier rewind should allow reuse of p3's slot");
386 }
387
388 #[test]
389 fn test_frontier_rewind_chain() {
390 let arena = GcArena::new(256).unwrap();
391 let p1 = alloc_one(&arena).unwrap();
392 let p2 = alloc_one(&arena).unwrap();
393 let p3 = alloc_one(&arena).unwrap();
394 let p4 = alloc_one(&arena).unwrap();
395
396 let mut holes = Vec::new();
398 arena.collect_hole(&mut holes, p4, 8); arena.collect_hole(&mut holes, p3, 8); assert!(holes.is_empty(), "both should use frontier rewind");
401
402 let p_new = alloc_one(&arena).unwrap();
404 assert_eq!(p_new, p3, "frontier rewind chain works");
405 }
406
407 #[test]
410 fn test_arena_full_reset() {
411 let arena = GcArena::new(256).unwrap();
412 let p1 = alloc_one(&arena).unwrap();
413 let p2 = alloc_one(&arena).unwrap();
414
415 let mut holes = Vec::new();
417 arena.collect_hole(&mut holes, p1, 8);
418 arena.collect_hole(&mut holes, p2, 8);
419 assert_eq!(arena.live_count.get(), 0);
420
421 arena.finish_sweep(&mut holes);
423 assert_eq!(arena.bump.get(), 0);
424 assert!(arena.free_head.get().is_none());
425
426 let p_new1 = alloc_one(&arena).unwrap();
428 assert_eq!(p_new1, p1, "full reset reuses from base");
429 }
430
431 #[test]
434 fn test_mixed_frontier_and_holes() {
435 let arena = GcArena::new(256).unwrap();
436 let p1 = alloc_one(&arena).unwrap();
437 let p2 = alloc_one(&arena).unwrap();
438 let p3 = alloc_one(&arena).unwrap();
439 let p4 = alloc_one(&arena).unwrap();
440 let p5 = alloc_one(&arena).unwrap();
441
442 let mut holes = Vec::new();
446 arena.collect_hole(&mut holes, p5, 8); arena.collect_hole(&mut holes, p3, 8); arena.collect_hole(&mut holes, p1, 8); assert_eq!(holes.len(), 2);
451
452 arena.finish_sweep(&mut holes);
453 assert!(holes.is_empty());
454
455 exhaust_bump(&arena);
457
458 let n1 = alloc_one(&arena).unwrap();
460 assert_eq!(n1, p1, "first free-list alloc should reuse p1");
463
464 let n2 = alloc_one(&arena).unwrap();
465 assert_eq!(n2, p3, "second free-list alloc should reuse p3");
466
467 assert!(
469 alloc_one(&arena).is_none(),
470 "arena should be full after exhausting bump and free-list"
471 );
472 }
473
474 #[test]
477 fn test_large_allocs_return_none() {
478 let arena = GcArena::new(1024).unwrap();
480 let large = arena.alloc(Layout::from_size_align(2048, 16).unwrap());
481 assert!(
482 large.is_none(),
483 "alloc larger than capacity should return None"
484 );
485 }
486
487 #[test]
490 fn test_data_integrity_after_free_list_reuse() {
491 let arena = GcArena::new(256).unwrap();
492 unsafe {
493 let p1 = alloc_one(&arena).unwrap();
494 write_u64(p1, 0xDEAD);
495 let p2 = alloc_one(&arena).unwrap();
496 write_u64(p2, 0xBEEF);
497
498 assert_eq!(read_u64(p1), 0xDEAD);
499 assert_eq!(read_u64(p2), 0xBEEF);
500
501 let mut holes = Vec::new();
503 arena.collect_hole(&mut holes, p1, 8);
504 arena.finish_sweep(&mut holes);
505
506 exhaust_bump(&arena);
508
509 let p_new = alloc_one(&arena).unwrap();
511 assert_eq!(p_new, p1);
512 write_u64(p_new, 0xCAFE);
513
514 assert_eq!(read_u64(p2), 0xBEEF);
516 assert_eq!(read_u64(p_new), 0xCAFE);
518 }
519 }
520}