use std::num::NonZeroUsize;
use crate::error::BuildError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuddyGeometry {
total_size: usize,
min_block_size: usize,
max_order: usize,
alignment: usize,
cap_capacity: bool,
}
impl BuddyGeometry {
pub fn exact(
total_size: NonZeroUsize,
min_block_size: NonZeroUsize,
) -> Result<Self, BuildError> {
let total = total_size.get();
let min_block = min_block_size.get();
let max_order =
validate_buddy_geometry(total, min_block).ok_or(BuildError::InvalidGeometry)?;
Ok(Self {
total_size: total,
min_block_size: min_block,
max_order,
alignment: 1,
cap_capacity: false,
})
}
pub fn nearest(
total_size: NonZeroUsize,
min_block_size: NonZeroUsize,
) -> Result<Self, BuildError> {
let min_block = min_block_size
.get()
.checked_next_power_of_two()
.ok_or(BuildError::SizeOverflow)?;
let (total, max_order) = snap_to_valid_geometry(total_size.get(), min_block)?;
Ok(Self {
total_size: total,
min_block_size: min_block,
max_order,
alignment: 1,
cap_capacity: true,
})
}
pub fn with_alignment(self, alignment: NonZeroUsize) -> Result<Self, BuildError> {
let a = alignment.get();
if !a.is_power_of_two() {
return Err(BuildError::InvalidAlignment);
}
if a <= self.min_block_size {
return Ok(Self {
alignment: a,
..self
});
}
if !self.cap_capacity {
return Err(BuildError::InvalidGeometry);
}
let min_block = a;
let (total, max_order) = snap_to_valid_geometry(self.total_size, min_block)?;
Ok(Self {
total_size: total,
min_block_size: min_block,
max_order,
alignment: a,
cap_capacity: self.cap_capacity,
})
}
pub fn total_size(&self) -> usize {
self.total_size
}
pub fn min_block_size(&self) -> usize {
self.min_block_size
}
pub fn max_order(&self) -> usize {
self.max_order
}
pub fn alignment(&self) -> usize {
self.alignment
}
pub(crate) fn cap_capacity(&self) -> bool {
self.cap_capacity
}
}
fn snap_to_valid_geometry(
total_hint: usize,
min_block: usize,
) -> Result<(usize, usize), BuildError> {
let t = total_hint.max(min_block);
let blocks = t
.checked_add(min_block - 1)
.ok_or(BuildError::SizeOverflow)?
/ min_block;
let pow2_blocks = blocks
.checked_next_power_of_two()
.ok_or(BuildError::SizeOverflow)?;
let total = pow2_blocks
.checked_mul(min_block)
.ok_or(BuildError::SizeOverflow)?;
let max_order = pow2_blocks.trailing_zeros() as usize;
Ok((total, max_order))
}
fn validate_buddy_geometry(total_size: usize, min_block_size: usize) -> Option<usize> {
if !min_block_size.is_power_of_two() {
return None;
}
if total_size < min_block_size {
return None;
}
if !total_size.is_multiple_of(min_block_size) {
return None;
}
let blocks = total_size / min_block_size;
if !blocks.is_power_of_two() {
return None;
}
let max_order = blocks.trailing_zeros() as usize;
if max_order >= usize::BITS as usize {
return None;
}
Some(max_order)
}
#[cfg(test)]
mod tests {
use super::*;
use std::num::NonZeroUsize;
fn nz(n: usize) -> NonZeroUsize {
NonZeroUsize::new(n).unwrap()
}
#[test]
fn exact_valid_geometry() {
let geo = BuddyGeometry::exact(nz(4096), nz(512)).unwrap();
assert_eq!(geo.total_size(), 4096);
assert_eq!(geo.min_block_size(), 512);
assert_eq!(geo.max_order(), 3);
assert_eq!(geo.alignment(), 1);
}
#[test]
fn exact_rejects_non_pow2_min_block() {
assert_eq!(
BuddyGeometry::exact(nz(4096), nz(768)).unwrap_err(),
BuildError::InvalidGeometry,
);
}
#[test]
fn exact_rejects_non_pow2_multiple_total() {
assert_eq!(
BuddyGeometry::exact(nz(6144), nz(1024)).unwrap_err(),
BuildError::InvalidGeometry,
);
}
#[test]
fn exact_rejects_total_smaller_than_min_block() {
assert_eq!(
BuddyGeometry::exact(nz(256), nz(512)).unwrap_err(),
BuildError::InvalidGeometry,
);
}
#[test]
fn exact_with_alignment() {
let geo = BuddyGeometry::exact(nz(4096), nz(512))
.unwrap()
.with_alignment(nz(512))
.unwrap();
assert_eq!(geo.alignment(), 512);
}
#[test]
fn exact_rejects_alignment_larger_than_min_block() {
let err = BuddyGeometry::exact(nz(4096), nz(512))
.unwrap()
.with_alignment(nz(1024))
.unwrap_err();
assert_eq!(err, BuildError::InvalidGeometry);
}
#[test]
fn exact_rejects_non_pow2_alignment() {
let err = BuddyGeometry::exact(nz(4096), nz(512))
.unwrap()
.with_alignment(nz(3))
.unwrap_err();
assert_eq!(err, BuildError::InvalidAlignment);
}
#[test]
fn nearest_valid_inputs_unchanged() {
let geo = BuddyGeometry::nearest(nz(4096), nz(512)).unwrap();
assert_eq!(geo.total_size(), 4096);
assert_eq!(geo.min_block_size(), 512);
assert_eq!(geo.max_order(), 3);
}
#[test]
fn nearest_snaps_min_block_to_pow2() {
let geo = BuddyGeometry::nearest(nz(4096), nz(768)).unwrap();
assert_eq!(geo.min_block_size(), 1024);
}
#[test]
fn nearest_snaps_total_up_to_valid_multiple() {
let geo = BuddyGeometry::nearest(nz(6000), nz(1024)).unwrap();
assert_eq!(geo.total_size(), 8192);
assert_eq!(geo.min_block_size(), 1024);
}
#[test]
fn nearest_snaps_total_up_when_less_than_min_block() {
let geo = BuddyGeometry::nearest(nz(256), nz(512)).unwrap();
assert_eq!(geo.total_size(), 512);
assert_eq!(geo.min_block_size(), 512);
}
#[test]
fn nearest_with_alignment_adjusts() {
let geo = BuddyGeometry::nearest(nz(4096), nz(512))
.unwrap()
.with_alignment(nz(1024))
.unwrap();
assert_eq!(geo.alignment(), 1024);
assert_eq!(geo.min_block_size(), 1024);
assert_eq!(geo.max_order(), 2);
}
#[test]
fn nearest_overflow_returns_err() {
let result = BuddyGeometry::nearest(nz(usize::MAX), nz(usize::MAX >> 1));
assert_eq!(result.unwrap_err(), BuildError::SizeOverflow);
}
}