use crate::constants::{MAX_SMALL_ALLOC_SIZE, NUM_SIZE_CLASSES, PAGE_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];
if class == u8::MAX {
None
} else {
Some(class as usize)
}
}
const SIZE_TO_CLASS: [u8; MAX_SMALL_ALLOC_SIZE + 1] = {
let mut arr = [u8::MAX; MAX_SMALL_ALLOC_SIZE + 1];
arr[0] = 0;
let mut size = 1;
while size <= MAX_SMALL_ALLOC_SIZE {
arr[size] = match size_to_class_nonzero_arithmetic(size) {
Some(class) => class as u8,
None => u8::MAX,
};
size += 1;
}
arr
};
const fn size_to_class_nonzero_arithmetic(size: usize) -> Option<usize> {
if size > MAX_SMALL_ALLOC_SIZE {
return None;
}
struct SizeClassLookup {
base: u8,
shift: u8,
sub: u16,
}
const LOOKUP: [SizeClassLookup; 14] = [
SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 0,
shift: 4,
sub: 1,
}, SizeClassLookup {
base: 8,
shift: 5,
sub: 129,
}, SizeClassLookup {
base: 8,
shift: 5,
sub: 129,
}, SizeClassLookup {
base: 20,
shift: 7,
sub: 513,
}, SizeClassLookup {
base: 20,
shift: 7,
sub: 513,
}, SizeClassLookup {
base: 32,
shift: 9,
sub: 2049,
}, SizeClassLookup {
base: 32,
shift: 9,
sub: 2049,
}, ];
let bits = usize::BITS - (size - 1).leading_zeros();
if bits >= LOOKUP.len() as u32 {
return None;
}
let entry = &LOOKUP[bits as usize];
Some(entry.base as usize + ((size - entry.sub as usize) >> entry.shift))
}
#[inline(always)]
pub const fn round_up_size(size: usize) -> Option<usize> {
if size == 0 {
return Some(0);
}
if size > 8192 {
return None;
}
match size_to_class_nonzero(size) {
Some(class) => Some(class_to_size(class)),
None => None,
}
}
const CLASS_TO_SIZE: [u16; NUM_SIZE_CLASSES] = [
16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512,
640, 768, 896, 1024, 1152, 1280, 1408, 1536, 1664, 1792, 1920, 2048, 2560, 3072, 3584, 4096,
4608, 5120, 5632, 6144, 6656, 7168, 7680, 8192,
];
const CLASS_TO_MAX_BLOCKS: [u16; NUM_SIZE_CLASSES] = {
let mut arr = [0u16; NUM_SIZE_CLASSES];
let mut i = 0;
while i < NUM_SIZE_CLASSES {
arr[i] = (PAGE_SIZE / CLASS_TO_SIZE[i] as usize) as u16;
i += 1;
}
arr
};
#[inline(always)]
pub const fn class_to_size(class: usize) -> usize {
if class < NUM_SIZE_CLASSES {
CLASS_TO_SIZE[class] as usize
} else {
0
}
}
#[inline(always)]
pub const fn class_to_max_blocks(class: usize) -> usize {
if class < NUM_SIZE_CLASSES {
CLASS_TO_MAX_BLOCKS[class] as usize
} else {
0
}
}
const _: () = assert!(
class_to_size(NUM_SIZE_CLASSES - 1) == MAX_SMALL_ALLOC_SIZE,
"class_to_size(NUM_SIZE_CLASSES - 1) must reach MAX_SMALL_ALLOC_SIZE exactly"
);
const _: () = assert!(
class_to_size(NUM_SIZE_CLASSES) == 0,
"class_to_size(NUM_SIZE_CLASSES) must return the 0 sentinel"
);
#[cfg(test)]
mod tests {
use super::*;
#[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), 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));
}
}