mod common;
use common::{HhdmProvenance, OwnedRegion, TestProvenance, n};
use core::sync::atomic::{AtomicUsize, Ordering};
use frame_alloc::{
CpuId, DepotAllocator, NoCpuId, NoInterruptControl, PageSize, PhysRange, PhysicalAllocator,
RegionInit, RegionedAllocator, SummaryBuddyAllocator,
};
use std::collections::HashSet;
pub type RegionedDepot<
const REGIONS: usize,
A,
S,
const SLOTS: usize,
const CAP: usize = 128,
const DEPOT_CAP: usize = 512,
I = NoInterruptControl,
> = RegionedAllocator<REGIONS, DepotAllocator<A, S, SLOTS, CAP, DEPOT_CAP, I>, S>;
const BASE: PageSize = PageSize::from_log2(6);
const ORDERS: usize = 4;
const FRAMES: usize = 256;
const STEPS: usize = 50_000;
const SEEDS: [u64; 6] = [
0x1D4C_E550_4242_1AD1,
0x9E37_79B9_7F4A_7C15,
0xD1B5_4A32_D192_ED03,
0xA076_1D64_78BD_642F,
0xE703_7ED1_A0B4_28DB,
0x2545_F491_4F6C_DD1D,
];
fn max_block() -> usize {
BASE.bytes() << (ORDERS - 1)
}
fn measure_capacity<A: PhysicalAllocator>(a: &A) -> usize {
let mut drained = Vec::new();
while let Ok(addr) = a.allocate_physical(BASE, n(1)) {
drained.push(addr);
}
let count = drained.len();
for addr in drained {
unsafe { a.deallocate_physical(BASE, n(1), addr) };
}
count
}
fn oracle_churn<A: PhysicalAllocator>(a: &A, usable: &[(usize, usize)], seed: u64) {
let fb = BASE.bytes();
let capacity = measure_capacity(a);
assert!(capacity > 0, "pool has no allocatable frames");
let mut occupied: HashSet<usize> = HashSet::new();
let mut live: Vec<(usize, usize)> = Vec::new();
let mut rng = seed;
let mut next = || {
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
rng
};
for step in 0..STEPS {
let do_alloc = live.is_empty() || (next() & 1 == 0);
if do_alloc {
let count = 1usize << ((next() as usize) % ORDERS);
if let Ok(addr) = a.allocate_physical(BASE, n(count)) {
assert_eq!(
addr % fb,
0,
"step {step}: address {addr:#x} not frame-aligned"
);
let end = addr + count * fb;
assert!(
usable.iter().any(|&(lo, hi)| addr >= lo && end <= hi),
"step {step}: block [{addr:#x}, {end:#x}) is not contained in any usable range \
(fell in a hole or outside the pool)"
);
for f in 0..count {
let fa = addr + f * fb;
assert!(
occupied.insert(fa),
"step {step}: ALIASING — frame {fa:#x} already held by a live allocation"
);
}
live.push((addr, count));
}
} else {
let idx = (next() as usize) % live.len();
let (addr, count) = live.swap_remove(idx);
for f in 0..count {
occupied.remove(&(addr + f * fb));
}
unsafe { a.deallocate_physical(BASE, n(count), addr) };
}
}
for &(addr, count) in &live {
unsafe { a.deallocate_physical(BASE, n(count), addr) };
}
let mut recovered: Vec<usize> = Vec::new();
while let Ok(addr) = a.allocate_physical(BASE, n(1)) {
recovered.push(addr);
}
assert_eq!(
recovered.len(),
capacity,
"frames leaked: recovered {} of {capacity}",
recovered.len()
);
for addr in recovered {
unsafe { a.deallocate_physical(BASE, n(1), addr) };
}
}
fn holed_usable(base: usize, total: usize, hole_lo: usize, hole_hi: usize) -> [(usize, usize); 2] {
let fb = BASE.bytes();
[
(base, base + hole_lo * fb),
(base + hole_hi * fb, base + total * fb),
]
}
fn holed_ranges(base: usize, total: usize, hole_lo: usize, hole_hi: usize) -> [PhysRange; 2] {
let fb = BASE.bytes();
[
PhysRange {
base,
len: hole_lo * fb,
},
PhysRange {
base: base + hole_hi * fb,
len: (total - hole_hi) * fb,
},
]
}
const HOLE: (usize, usize) = (64, 96);
#[test]
fn summary_buddy_matches_occupancy_oracle() {
let span = FRAMES * BASE.bytes();
let region = OwnedRegion::new(span, max_block());
let a = SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE);
unsafe { a.init_region(region.addr(), span) };
let usable = [(region.addr(), region.addr() + span)];
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
#[test]
fn summary_buddy_matches_occupancy_oracle_hhdm() {
let span = FRAMES * BASE.bytes();
let region = OwnedRegion::new(span, max_block());
let phys_base = region.phys_addr();
let a = SummaryBuddyAllocator::<ORDERS, HhdmProvenance>::new(BASE);
unsafe {
a.init(
phys_base,
span,
&[PhysRange {
base: phys_base,
len: span,
}],
)
};
let usable = [(phys_base, phys_base + span)];
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
#[test]
fn summary_buddy_matches_occupancy_oracle_holed() {
let span = FRAMES * BASE.bytes();
let region = OwnedRegion::new(span, max_block());
let base = region.addr();
let usable = holed_usable(base, FRAMES, HOLE.0, HOLE.1);
let ranges = holed_ranges(base, FRAMES, HOLE.0, HOLE.1);
let a = SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE);
unsafe { a.init(base, span, &ranges) };
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
static ROTATING_CPU: AtomicUsize = AtomicUsize::new(0);
struct RotatingCpu;
impl CpuId for RotatingCpu {
fn current_cpu() -> usize {
ROTATING_CPU.fetch_add(1, Ordering::Relaxed)
}
}
type RegionedPool<const REGIONS: usize> = (
RegionedAllocator<REGIONS, SummaryBuddyAllocator<ORDERS, TestProvenance>, RotatingCpu>,
[OwnedRegion; REGIONS],
Vec<(usize, usize)>,
);
fn regioned_pool<const REGIONS: usize>(frames: usize) -> RegionedPool<REGIONS> {
let bytes = frames * BASE.bytes();
let regions: [OwnedRegion; REGIONS] =
core::array::from_fn(|_| OwnedRegion::new(bytes, max_block()));
let a: RegionedAllocator<REGIONS, SummaryBuddyAllocator<ORDERS, TestProvenance>, RotatingCpu> =
RegionedAllocator::new(
BASE,
[const { SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE) }; REGIONS],
);
let mut usable = Vec::new();
for (i, r) in regions.iter().enumerate() {
unsafe {
a.init_at(
i,
r.addr(),
bytes,
&[PhysRange {
base: r.addr(),
len: bytes,
}],
);
}
usable.push((r.addr(), r.addr() + bytes));
}
(a, regions, usable)
}
fn regioned_pool_holed<const REGIONS: usize>(
frames: usize,
hole: (usize, usize),
) -> RegionedPool<REGIONS> {
let bytes = frames * BASE.bytes();
let regions: [OwnedRegion; REGIONS] =
core::array::from_fn(|_| OwnedRegion::new(bytes, max_block()));
let a: RegionedAllocator<REGIONS, SummaryBuddyAllocator<ORDERS, TestProvenance>, RotatingCpu> =
RegionedAllocator::new(
BASE,
[const { SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE) }; REGIONS],
);
let mut usable = Vec::new();
for (i, r) in regions.iter().enumerate() {
let ranges = holed_ranges(r.addr(), frames, hole.0, hole.1);
unsafe { a.init_at(i, r.addr(), bytes, &ranges) };
usable.extend(holed_usable(r.addr(), frames, hole.0, hole.1));
}
(a, regions, usable)
}
#[test]
fn regioned_matches_occupancy_oracle_two_regions() {
let (a, _regions, usable) = regioned_pool::<2>(FRAMES / 2);
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
#[test]
fn regioned_matches_occupancy_oracle_three_regions() {
let (a, _regions, usable) = regioned_pool::<3>(FRAMES / 2);
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
#[test]
fn regioned_matches_occupancy_oracle_holed() {
let (a, _regions, usable) = regioned_pool_holed::<2>(FRAMES / 2, (32, 48));
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
type RegionedDep<const REGIONS: usize> =
RegionedDepot<REGIONS, SummaryBuddyAllocator<ORDERS, TestProvenance>, RotatingCpu, 1, 8>;
fn regioned_mag_pool<const REGIONS: usize>(
frames: usize,
) -> (
RegionedDep<REGIONS>,
[OwnedRegion; REGIONS],
Vec<(usize, usize)>,
) {
let bytes = frames * BASE.bytes();
let regions: [OwnedRegion; REGIONS] =
core::array::from_fn(|_| OwnedRegion::new(bytes, max_block()));
let a: RegionedDep<REGIONS> = RegionedAllocator::new(
BASE,
[const {
DepotAllocator::new(
BASE,
SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE),
)
}; REGIONS],
);
let mut usable = Vec::new();
for (i, r) in regions.iter().enumerate() {
unsafe {
a.init_at(
i,
r.addr(),
bytes,
&[PhysRange {
base: r.addr(),
len: bytes,
}],
);
}
usable.push((r.addr(), r.addr() + bytes));
}
(a, regions, usable)
}
#[test]
fn regioned_over_magazine_matches_occupancy_oracle() {
let (a, _regions, usable) = regioned_mag_pool::<2>(FRAMES / 2);
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
fn depot_pool() -> DepotAllocator<SummaryBuddyAllocator<ORDERS, TestProvenance>, NoCpuId, 4, 8, 16>
{
DepotAllocator::new(
BASE,
SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE),
)
}
#[test]
fn depot_matches_occupancy_oracle() {
let span = FRAMES * BASE.bytes();
let region = OwnedRegion::new(span, max_block());
let a = depot_pool();
unsafe { a.init_region(region.addr(), span) };
let usable = [(region.addr(), region.addr() + span)];
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}
#[test]
fn depot_matches_occupancy_oracle_holed() {
let span = FRAMES * BASE.bytes();
let region = OwnedRegion::new(span, max_block());
let base = region.addr();
let usable = holed_usable(base, FRAMES, HOLE.0, HOLE.1);
let ranges = holed_ranges(base, FRAMES, HOLE.0, HOLE.1);
let a = depot_pool();
unsafe { a.init(base, span, &ranges) };
for &seed in &SEEDS {
oracle_churn(&a, &usable, seed);
}
}