use super::tables::{SIZE_TO_CLASS, class_to_size};
use crate::constants::{MAX_SMALL_ALLOC_SIZE, MIN_BLOCK_SIZE};
#[inline(always)]
pub const fn size_to_class(size: usize) -> Option<usize> {
if size == 0 {
return Some(0);
}
size_to_class_nonzero(size)
}
#[inline(always)]
pub const fn size_to_class_nonzero(size: usize) -> Option<usize> {
if size > MAX_SMALL_ALLOC_SIZE {
return None;
}
let class = SIZE_TO_CLASS[size.div_ceil(MIN_BLOCK_SIZE)];
if class == u8::MAX {
None
} else {
Some(class as usize)
}
}
#[inline(always)]
pub const fn round_up_size(size: usize) -> Option<usize> {
if size == 0 {
return Some(0);
}
let Some(class) = size_to_class_nonzero(size) else {
return None;
};
Some(class_to_size(class))
}
#[inline(always)]
pub const fn round_up_size_saturating(size: usize) -> usize {
if size == 0 {
return 0;
}
match round_up_size(size) {
Some(s) => s,
None => MAX_SMALL_ALLOC_SIZE,
}
}
#[inline]
pub fn size_class_fragmentation(size: usize) -> f64 {
if size == 0 {
return 0.0;
}
let Some(stride) = round_up_size(size) else {
return 0.0;
};
if stride == 0 {
return 0.0;
}
let waste = stride.saturating_sub(size);
waste as f64 / stride as f64
}
#[cfg(test)]
mod tests {
use super::super::tables::{block_index_in_page, class_to_max_blocks};
use super::*;
use crate::constants::NUM_SIZE_CLASSES;
#[test]
fn test_size_class_mapping() {
assert_eq!(size_to_class(0), Some(0));
assert_eq!(size_to_class(16), Some(0));
assert_eq!(size_to_class(17), Some(1));
assert_eq!(size_to_class(128), Some(7));
assert_eq!(size_to_class(129), Some(8));
assert_eq!(size_to_class(160), Some(8));
assert_eq!(size_to_class(512), Some(19));
assert_eq!(size_to_class(513), Some(20));
assert_eq!(size_to_class(2048), Some(31));
assert_eq!(size_to_class(2049), Some(32));
assert_eq!(size_to_class(8192), Some(43));
assert_eq!(size_to_class(8193), Some(44));
assert_eq!(size_to_class(9216), Some(44));
assert_eq!(size_to_class(9217), Some(45));
assert_eq!(size_to_class(10240), Some(45));
assert_eq!(size_to_class(10241), Some(46));
assert_eq!(size_to_class(11264), Some(46));
assert_eq!(size_to_class(11265), Some(47));
assert_eq!(size_to_class(12288), Some(47));
assert_eq!(size_to_class(12289), Some(48));
assert_eq!(size_to_class(13312), Some(48));
assert_eq!(size_to_class(13313), Some(49));
assert_eq!(size_to_class(14336), Some(49));
assert_eq!(size_to_class(14337), Some(50));
assert_eq!(size_to_class(15360), Some(50));
assert_eq!(size_to_class(15361), Some(51));
assert_eq!(size_to_class(16384), Some(51));
assert_eq!(size_to_class(16385), None);
for c in 0..NUM_SIZE_CLASSES {
let sz = class_to_size(c);
assert!(sz > 0, "class_to_size({c}) returned zero");
assert_eq!(size_to_class(sz), Some(c));
}
}
#[test]
fn size_class_boundaries_are_exact() {
for c in 0..NUM_SIZE_CLASSES {
let upper = class_to_size(c);
assert_eq!(
size_to_class(upper),
Some(c),
"class {c} upper bound {upper} must resolve to {c}"
);
if c + 1 < NUM_SIZE_CLASSES {
assert_eq!(
size_to_class(upper + 1),
Some(c + 1),
"class {} lower bound {} must resolve to {}",
c + 1,
upper + 1,
c + 1
);
} else {
assert_eq!(
size_to_class(upper + 1),
None,
"byte past final class must escape small routing"
);
}
}
}
#[test]
fn size_class_zero_maps_to_smallest_class() {
assert_eq!(size_to_class(0), Some(0));
assert_eq!(size_to_class(1), Some(0));
}
#[test]
fn block_index_in_page_matches_integer_division() {
for class in 0..NUM_SIZE_CLASSES {
let block_size = class_to_size(class);
let max_blocks = class_to_max_blocks(class);
for idx in 0..max_blocks {
let offset = idx * block_size;
let expected = offset / block_size;
let fast = block_index_in_page(class, offset);
assert_eq!(
fast, expected,
"class={class} block_size={block_size} offset={offset}: \
lemire={fast} != div={expected}"
);
}
}
}
#[test]
fn round_up_size_saturating_never_returns_zero_for_positive() {
for sz in [
1usize,
16,
17,
100,
512,
2048,
MAX_SMALL_ALLOC_SIZE,
MAX_SMALL_ALLOC_SIZE + 1,
] {
let result = round_up_size_saturating(sz);
if sz == 0 {
assert_eq!(result, 0);
} else {
assert!(result > 0, "round_up_size_saturating({sz}) returned 0");
}
}
}
}