Skip to content

Commit c368860

Browse files
committed
Fix incorrect layouts in small-object arena alloc
As of now, the 'array' tests pass. Add cfg!(zerogc_simple_debug_alloc) which adds internal padding bytes. This was very helpful in finding this bug ;)
1 parent c526820 commit c368860

File tree

2 files changed

+130
-43
lines changed

2 files changed

+130
-43
lines changed

libs/simple/src/alloc.rs

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,88 @@ use std::cell::RefCell;
1515

1616
use zerogc_context::utils::AtomicCell;
1717

18+
const DEBUG_INTERNAL_ALLOCATOR: bool = cfg!(zerogc_simple_debug_alloc);
19+
mod debug {
20+
pub const PADDING: u32 = 0xDEADBEAF;
21+
pub const UNINIT: u32 = 0xCAFEBABE;
22+
pub const PADDING_TIMES: usize = 16;
23+
pub const PADDING_BYTES: usize = PADDING_TIMES * 4;
24+
pub unsafe fn pad_memory_block(ptr: *mut u8, size: usize) {
25+
assert!(super::DEBUG_INTERNAL_ALLOCATOR);
26+
let start = ptr.sub(PADDING_BYTES);
27+
for i in 0..PADDING_TIMES {
28+
(start as *mut u32).add(i).write(PADDING);
29+
}
30+
let end = ptr.add(size);
31+
for i in 0..PADDING_TIMES {
32+
(end as *mut u32).add(i).write(PADDING);
33+
}
34+
}
35+
pub unsafe fn mark_memory_uninit(ptr: *mut u8, size: usize) {
36+
assert!(super::DEBUG_INTERNAL_ALLOCATOR);
37+
let (blocks, leftover) = (size / 4, size % 4);
38+
for i in 0..blocks {
39+
(ptr as *mut u32).add(i).write(UNINIT);
40+
}
41+
let leftover_ptr = ptr.add(blocks * 4);
42+
debug_assert_eq!(leftover_ptr.wrapping_add(leftover), ptr.add(size));
43+
for i in 0..leftover {
44+
leftover_ptr.add(i).write(0xF0);
45+
}
46+
}
47+
pub unsafe fn assert_padded(ptr: *mut u8, size: usize) {
48+
assert!(super::DEBUG_INTERNAL_ALLOCATOR);
49+
let start = ptr.sub(PADDING_BYTES);
50+
let end = ptr.add(size);
51+
let start_padding = std::slice::from_raw_parts(
52+
start as *const u8 as *const u32,
53+
PADDING_TIMES
54+
);
55+
let region = std::slice::from_raw_parts(
56+
ptr as *const u8,
57+
size
58+
);
59+
let end_padding = std::slice::from_raw_parts(
60+
end as *const u8 as *const u32,
61+
PADDING_TIMES
62+
);
63+
let print_memory_region = || {
64+
use std::fmt::Write;
65+
let mut res = String::new();
66+
for &val in start_padding {
67+
write!(&mut res, "{:X}", val).unwrap();
68+
}
69+
res.push_str("||");
70+
for &b in region {
71+
write!(&mut res, "{:X}", b).unwrap();
72+
}
73+
res.push_str("||");
74+
for &val in end_padding {
75+
write!(&mut res, "{:X}", val).unwrap();
76+
}
77+
res
78+
};
79+
// Closest to farthest
80+
for (idx, &block) in start_padding.iter().rev().enumerate() {
81+
if block == PADDING { continue }
82+
assert_eq!(
83+
block, PADDING,
84+
"Unexpected start padding (offset -{}) w/ {}",
85+
idx * 4,
86+
print_memory_region()
87+
);
88+
}
89+
for (idx, &block) in end_padding.iter().enumerate() {
90+
if block == PADDING { continue }
91+
assert_eq!(
92+
block, PADDING,
93+
"Unexpected end padding (offset {}) w/ {}",
94+
idx * 4,
95+
print_memory_region()
96+
)
97+
}
98+
}
99+
}
18100
/// The minimum size of supported memory (in words)
19101
///
20102
/// Since the header takes at least one word,
@@ -73,10 +155,6 @@ impl Chunk {
73155
}
74156
}
75157
#[inline]
76-
fn current(&self) -> *mut u8 {
77-
self.current.load()
78-
}
79-
#[inline]
80158
fn capacity(&self) -> usize {
81159
self.end as usize - self.start as usize
82160
}
@@ -200,7 +278,11 @@ pub(crate) struct FreeList {
200278
next: AtomicCell<Option<NonNull<FreeSlot>>>
201279
}
202280
impl FreeList {
203-
pub(crate) unsafe fn add_free(&self, free: *mut UnknownHeader) {
281+
unsafe fn add_free(&self, free: *mut UnknownHeader, size: usize) {
282+
if DEBUG_INTERNAL_ALLOCATOR {
283+
debug::assert_padded(free as *mut u8, size);
284+
debug::mark_memory_uninit(free as *mut u8, size);
285+
}
204286
let new_slot = free as *mut FreeSlot;
205287
let mut next = self.next.load();
206288
loop {
@@ -214,14 +296,6 @@ impl FreeList {
214296
}
215297
}
216298
#[inline]
217-
pub(crate) fn next_free(&self) -> Option<NonNull<FreeSlot>> {
218-
self.next.load()
219-
}
220-
#[inline]
221-
pub(crate) unsafe fn set_next_free(&self, next: Option<NonNull<FreeSlot>>) {
222-
self.next.store(next)
223-
}
224-
#[inline]
225299
fn take_free(&self) -> Option<NonNull<u8>> {
226300
loop {
227301
let next_free = match self.next.load() {
@@ -248,7 +322,7 @@ pub struct SmallArena {
248322

249323
impl SmallArena {
250324
pub(crate) unsafe fn add_free(&self, obj: *mut UnknownHeader) {
251-
self.free.add_free(obj)
325+
self.free.add_free(obj, self.element_size)
252326
}
253327
#[cold] // Initialization is the slow path
254328
fn with_words(num_words: usize) -> SmallArena {
@@ -266,6 +340,15 @@ impl SmallArena {
266340
// Check the free list
267341
if let Some(free) = self.free.take_free() {
268342
free.cast()
343+
} else if DEBUG_INTERNAL_ALLOCATOR {
344+
let mem = self.state.alloc(self.element_size + debug::PADDING_BYTES * 2)
345+
.as_ptr() as *mut u8;
346+
unsafe {
347+
let mem = mem.add(debug::PADDING_BYTES);
348+
debug::pad_memory_block(mem, self.element_size);
349+
debug::mark_memory_uninit(mem, self.element_size);
350+
NonNull::new_unchecked(mem).cast()
351+
}
269352
} else {
270353
self.state.alloc(self.element_size)
271354
}
@@ -314,9 +397,6 @@ impl SmallArenaList {
314397
}
315398
}
316399
}
317-
pub fn iter(&self) -> impl Iterator<Item=&SmallArena> + '_ {
318-
self.arenas.iter().filter_map(OnceCell::get)
319-
}
320400
#[inline] // This should hopefully be constant folded away (layout is const)
321401
pub fn find(&self, layout: Layout) -> Option<&SmallArena> {
322402
if !fits_small_object(layout) {

libs/simple/src/lib.rs

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,13 @@ pub(crate) struct SimpleAlloc {
351351
mark_inverted: AtomicBool,
352352
allocated_size: AtomicUsize
353353
}
354+
#[derive(Debug)]
355+
struct TargetLayout<H> {
356+
value_layout: Layout,
357+
header_layout: HeaderLayout<H>,
358+
value_offset: usize,
359+
overall_layout: Layout
360+
}
354361
impl SimpleAlloc {
355362
fn new() -> SimpleAlloc {
356363
SimpleAlloc {
@@ -392,14 +399,25 @@ impl SimpleAlloc {
392399
unreachable!("Invalid collector id")
393400
}
394401
#[cfg(not(debug_assertions))] {
395-
std::hint::unreachable_unchecked()
402+
unsafe { std::hint::unreachable_unchecked() }
396403
}
397404
}
398405
};
399-
let (header, value_ptr) = if let Some(arena) = self.small_arenas.find(value_layout) {
400-
unsafe { self.alloc_layout_small(arena, header_layout, value_layout) }
406+
let (mut overall_layout, value_offset) = header_layout.layout()
407+
.extend(value_layout).unwrap();
408+
debug_assert_eq!(header_layout.value_offset(value_layout.align()), value_offset);
409+
overall_layout = overall_layout.pad_to_align();
410+
debug_assert_eq!(
411+
value_offset,
412+
header_layout.value_offset(value_layout.align())
413+
);
414+
let target_layout = TargetLayout {
415+
header_layout, value_offset, value_layout, overall_layout
416+
};
417+
let (header, value_ptr) = if let Some(arena) = self.small_arenas.find(target_layout.overall_layout) {
418+
unsafe { self.alloc_layout_small(arena, target_layout) }
401419
} else {
402-
self.alloc_layout_big(header_layout, value_layout)
420+
self.alloc_layout_big(target_layout)
403421
};
404422
unsafe {
405423
header_layout.common_header(header).write(GcHeader::new(
@@ -413,45 +431,34 @@ impl SimpleAlloc {
413431
(header, value_ptr)
414432
}
415433
#[inline]
416-
unsafe fn alloc_layout_small<H>(&self, arena: &SmallArena, header_layout: HeaderLayout<H>, value_layout: Layout) -> (*mut H, *mut u8) {
417-
let (mut overall_layout, value_offset) = header_layout.layout()
418-
.extend(value_layout).unwrap();
419-
overall_layout = overall_layout.pad_to_align();
420-
debug_assert_eq!(
421-
value_offset,
422-
header_layout.value_offset(value_layout.align())
423-
);
434+
unsafe fn alloc_layout_small<H>(&self, arena: &SmallArena, target_layout: TargetLayout<H>) -> (*mut H, *mut u8) {
424435
let ptr = arena.alloc();
425436
debug_assert_eq!(
426-
ptr.as_ptr() as usize % header_layout.layout().align(),
437+
ptr.as_ptr() as usize % target_layout.header_layout.layout().align(),
427438
0
428439
);
429-
self.add_allocated_size(overall_layout.size());
440+
self.add_allocated_size(target_layout.overall_layout.size());
430441
{
431442
let mut lock = self.small_objects.lock();
432-
lock.push((ptr.as_ptr() as *mut u8).add(header_layout.common_header_offset).cast());
443+
lock.push((ptr.as_ptr() as *mut u8).add(target_layout.header_layout.common_header_offset).cast());
433444
}
434-
(ptr.as_ptr().cast(), (ptr.as_ptr() as *mut u8).add(value_offset))
445+
(ptr.as_ptr().cast(), (ptr.as_ptr() as *mut u8).add(target_layout.value_offset))
435446
}
436-
fn alloc_layout_big<H>(&self, header_layout: HeaderLayout<H>, value_layout: Layout) -> (*mut H, *mut u8) {
437-
let (mut overall_layout, value_offset) = header_layout.layout()
438-
.extend(value_layout).unwrap();
439-
debug_assert_eq!(header_layout.value_offset(value_layout.align()), value_offset);
440-
overall_layout = overall_layout.pad_to_align();
447+
fn alloc_layout_big<H>(&self, target_layout: TargetLayout<H>) -> (*mut H, *mut u8) {
441448
let header: *mut H;
442449
let value_ptr = unsafe {
443-
header = std::alloc::alloc(overall_layout).cast();
444-
(header as *mut u8).add(value_offset)
450+
header = std::alloc::alloc(target_layout.overall_layout).cast();
451+
(header as *mut u8).add(target_layout.value_offset)
445452
};
446453
{
447454
unsafe {
448455
let mut objs = self.big_objects.lock();
449456
let common_header = (header as *mut u8)
450-
.add(header_layout.common_header_offset)
457+
.add(target_layout.header_layout.common_header_offset)
451458
.cast();
452459
objs.push(BigGcObject::from_ptr(common_header));
453460
}
454-
self.add_allocated_size(overall_layout.size());
461+
self.add_allocated_size(target_layout.overall_layout.size());
455462
}
456463
(header, value_ptr)
457464
}
@@ -486,7 +493,7 @@ impl SimpleAlloc {
486493
}
487494
let overall_layout = type_info.determine_total_layout(freed_common_header);
488495
let actual_start = type_info.header_layout()
489-
.from_common_header(freed_common_header) ;
496+
.from_common_header(freed_common_header);
490497
self.small_arenas.find(overall_layout).unwrap().add_free(actual_start)
491498
});
492499
// Clear large objects

0 commit comments

Comments
 (0)