#![cfg(feature = "alloc-core")]
use sefer_alloc::SegmentLayout;
fn linear_scan_class_for(size: usize, align: usize) -> Option<usize> {
if align > SegmentLayout::SMALL_ALIGN_MAX {
return None;
}
let need = if size > align { size } else { align };
let table = SegmentLayout::SIZE_CLASS_TABLE;
let mut i = 0;
while i < table.len() {
if table[i] >= need {
return Some(i);
}
i += 1;
}
None
}
fn o1_class_for(size: usize, align: usize) -> Option<usize> {
SegmentLayout::class_for(size, align)
}
#[test]
fn o1_lookup_matches_linear_scan_for_every_small_size_and_align() {
let small_aligns = [1usize, 2, 4, 8, 16];
for align in small_aligns {
for size in 1..=SegmentLayout::SMALL_MAX {
let got = o1_class_for(size, align);
let want = linear_scan_class_for(size, align);
assert_eq!(
got, want,
"drift at size={size} align={align}: O(1)={got:?} scan={want:?}"
);
if let Some(idx) = got {
let block = SegmentLayout::SIZE_CLASS_TABLE[idx];
let need = if size > align { size } else { align };
assert!(
block >= need,
"size={size} align={align}: class {idx} block={block} < need={need}"
);
}
}
}
}
#[test]
fn size2class_table_matches_linear_scan_for_every_bucket() {
let table = SegmentLayout::SIZE_CLASS_TABLE;
let s2c = SegmentLayout::SIZE2CLASS;
let min_block = SegmentLayout::MIN_BLOCK;
let small_max = SegmentLayout::SMALL_MAX;
for (k, &class_idx) in s2c.iter().enumerate() {
let need = ((k + 1) * min_block).min(small_max);
let mut want = 0;
while want < table.len() {
if table[want] >= need {
break;
}
want += 1;
}
assert_eq!(
class_idx as usize, want,
"SIZE2CLASS[{k}] = {class_idx} but scan says {want} (need={need})"
);
assert!(
table[class_idx as usize] >= need,
"SIZE2CLASS[{k}] → block {} < need {need}",
table[class_idx as usize]
);
}
}
#[test]
fn boundary_size_one_returns_class_zero() {
assert_eq!(o1_class_for(1, 1), Some(0));
assert_eq!(linear_scan_class_for(1, 1), Some(0));
}
#[test]
fn boundary_size_min_block_returns_class_zero() {
let mb = SegmentLayout::MIN_BLOCK;
assert_eq!(o1_class_for(mb, 1), Some(0));
assert_eq!(o1_class_for(mb, mb), Some(0));
}
#[test]
fn boundary_size_just_above_a_class_uses_next_class() {
let table = SegmentLayout::SIZE_CLASS_TABLE;
let just_over = table[0] + 1; assert_eq!(o1_class_for(just_over, 1), Some(1));
assert_eq!(
o1_class_for(just_over, 1),
linear_scan_class_for(just_over, 1)
);
let on_boundary = table[5]; let idx = o1_class_for(on_boundary, 1).expect("interior class is small");
assert_eq!(table[idx], on_boundary);
}
#[test]
fn boundary_size_small_max_is_small_not_large() {
let smax = SegmentLayout::SMALL_MAX;
let last = SegmentLayout::SIZE_CLASS_TABLE.len() - 1;
assert_eq!(o1_class_for(smax, 1), Some(last));
assert_eq!(
o1_class_for(smax, SegmentLayout::SMALL_ALIGN_MAX),
Some(last)
);
}
#[test]
fn boundary_size_above_small_max_is_large() {
let smax = SegmentLayout::SMALL_MAX;
assert_eq!(o1_class_for(smax + 1, 1), None);
assert_eq!(
o1_class_for(smax + 1, 1),
linear_scan_class_for(smax + 1, 1)
);
}
#[test]
fn boundary_align_above_small_align_max_is_large_even_for_tiny_size() {
let over = SegmentLayout::SMALL_ALIGN_MAX * 2; assert_eq!(o1_class_for(1, over), None);
assert_eq!(o1_class_for(SegmentLayout::SMALL_MAX, over), None);
assert_eq!(o1_class_for(1, over), linear_scan_class_for(1, over));
}
#[test]
fn min_block_shift_is_log2_of_min_block() {
let mb = SegmentLayout::MIN_BLOCK;
assert!(mb.is_power_of_two(), "MIN_BLOCK must be a power of two");
assert_eq!(
SegmentLayout::MIN_BLOCK_SHIFT,
mb.trailing_zeros(),
"MIN_BLOCK_SHIFT must equal log2(MIN_BLOCK)"
);
assert_eq!(1usize << SegmentLayout::MIN_BLOCK_SHIFT, mb);
}
#[test]
fn small_max_is_a_multiple_of_min_block_so_top_index_is_in_bounds() {
let mb = SegmentLayout::MIN_BLOCK;
let smax = SegmentLayout::SMALL_MAX;
assert_eq!(
smax % mb,
0,
"SMALL_MAX must be a multiple of MIN_BLOCK (else SIZE2CLASS top index is wrong)"
);
let top_idx = (smax - 1) >> SegmentLayout::MIN_BLOCK_SHIFT;
assert!(top_idx < SegmentLayout::SIZE2CLASS.len());
}
#[test]
fn counterfactual_naive_shift_without_minus_one_would_be_wrong() {
let mb = SegmentLayout::MIN_BLOCK;
let shift = SegmentLayout::MIN_BLOCK_SHIFT;
let correct = (mb - 1) >> shift;
let naive = mb >> shift;
assert_ne!(correct, naive, "counterfactual no longer distinguishes");
assert_eq!(correct, 0);
}