extern crate std;
use crate::tests::common::{N1, OwnedRegion, TestProvenance};
use crate::{
AllocError, CpuId, DepotAllocator, NoInterruptControl, PageSize, PhysRange, PhysicalAllocator,
RegionedAllocator, SummaryBuddyAllocator,
};
use core::cell::Cell;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering::Relaxed;
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use std::vec::Vec;
const BASE: PageSize = PageSize::from_log2(6);
const ORDERS: usize = 3;
const REGIONS: usize = 2;
const SLOTS: usize = 2;
const CAP: usize = 8;
fn max_block() -> usize {
BASE.bytes() << (ORDERS - 1)
}
std::thread_local! {
static CPU: Cell<usize> = const { Cell::new(0) };
}
fn set_cpu(v: usize) {
CPU.set(v);
}
struct TestCpuId;
impl CpuId for TestCpuId {
fn current_cpu() -> usize {
CPU.with(Cell::get)
}
}
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>;
type Comp =
RegionedDepot<REGIONS, SummaryBuddyAllocator<ORDERS, TestProvenance>, TestCpuId, SLOTS, CAP>;
fn pool(frames: usize) -> (Comp, OwnedRegion, OwnedRegion) {
let bytes = frames * BASE.bytes();
let r0 = OwnedRegion::new(bytes, max_block());
let r1 = OwnedRegion::new(bytes, max_block());
let alloc: Comp = RegionedAllocator::new(
BASE,
[const {
DepotAllocator::new(
BASE,
SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE),
)
}; REGIONS],
);
unsafe {
alloc.init_at(
0,
r0.addr(),
bytes,
&[PhysRange {
base: r0.addr(),
len: bytes,
}],
);
alloc.init_at(
1,
r1.addr(),
bytes,
&[PhysRange {
base: r1.addr(),
len: bytes,
}],
);
}
(alloc, r0, r1)
}
fn bounds(r: &OwnedRegion, frames: usize) -> (usize, usize) {
(r.addr(), r.addr() + frames * BASE.bytes())
}
static _STATIC_COMP: Comp = RegionedAllocator::new(
BASE,
[const {
DepotAllocator::new(
BASE,
SummaryBuddyAllocator::<ORDERS, TestProvenance>::new(BASE),
)
}; REGIONS],
);
#[test]
fn fast_path_cache_hit_stays_in_home_region() {
set_cpu(0);
let (a, r0, _r1) = pool(64);
let (lo0, hi0) = bounds(&r0, 64);
let p = a.allocate_physical(BASE, N1).expect("alloc failed");
assert!((lo0..hi0).contains(&p), "home alloc must land in region 0");
unsafe { a.deallocate_physical(BASE, N1, p) };
let p2 = a.allocate_physical(BASE, N1).expect("re-alloc failed");
assert_eq!(
p2, p,
"fast path should return the just-freed frame (LIFO cache)"
);
assert!(
a.regions().any(|m| m.cached_frames() > 0),
"the hit must have come from a per-region magazine, not a backend round-trip"
);
unsafe { a.deallocate_physical(BASE, N1, p2) };
}
#[test]
fn dealloc_routes_to_owning_region_not_freeing_cpu() {
let (a, r0, _r1) = pool(64);
let (lo0, hi0) = bounds(&r0, 64);
set_cpu(0);
let p = a
.alloc_in_region(0, BASE, N1)
.expect("region-0 alloc failed");
assert!((lo0..hi0).contains(&p), "frame not in region 0");
set_cpu(1);
unsafe { a.deallocate_physical(BASE, N1, p) };
let q = a
.alloc_in_region(0, BASE, N1)
.expect("region-0 re-alloc failed");
assert_eq!(
q, p,
"a frame freed on a foreign-home CPU must return to its owning region's cache"
);
unsafe { a.deallocate_physical(BASE, N1, q) };
}
#[test]
fn per_region_caches_are_private() {
set_cpu(0);
let (a, _r0, r1) = pool(64);
let (lo1, hi1) = bounds(&r1, 64);
let held: Vec<usize> = (0..CAP)
.map(|_| {
a.alloc_in_region(0, BASE, N1)
.expect("region-0 alloc failed")
})
.collect();
let cached0: HashSet<usize> = held.iter().copied().collect();
for &p in &held {
unsafe { a.deallocate_physical(BASE, N1, p) };
}
while let Ok(q) = a.alloc_in_region(1, BASE, N1) {
assert!(
(lo1..hi1).contains(&q),
"region-1 alloc strayed out of region 1: {q:#x}"
);
assert!(
!cached0.contains(&q),
"region-1 alloc handed out a frame cached for region 0 — a cache crossed a boundary"
);
}
}
#[test]
fn home_exhaustion_steals_from_other_region() {
let (a, r0, r1) = pool(32);
let (lo1, hi1) = bounds(&r1, 32);
set_cpu(0);
let mut held = Vec::new();
while let Ok(p) = a.alloc_in_region(0, BASE, N1) {
let (lo0, hi0) = bounds(&r0, 32);
assert!((lo0..hi0).contains(&p), "pinned drain left region 0");
held.push(p);
}
let stolen = a.allocate_physical(BASE, N1).expect("work-steal failed");
assert!(
(lo1..hi1).contains(&stolen),
"expected a region-1 frame via work-stealing, got {stolen:#x}"
);
unsafe { a.deallocate_physical(BASE, N1, stolen) };
for p in held {
unsafe { a.deallocate_physical(BASE, N1, p) };
}
}
#[test]
fn full_drain_through_both_layers_conserves() {
set_cpu(0);
let (a, r0, r1) = pool(32);
let in_pool = {
let (lo0, hi0) = bounds(&r0, 32);
let (lo1, hi1) = bounds(&r1, 32);
move |p: usize| (lo0..hi0).contains(&p) || (lo1..hi1).contains(&p)
};
let mut addrs = HashSet::new();
while let Ok(p) = a.allocate_physical(BASE, N1) {
assert!(in_pool(p), "frame {p:#x} outside both regions");
assert!(addrs.insert(p), "frame {p:#x} handed out twice");
}
let total = addrs.len();
assert!(total > 0, "pool drained nothing");
for &p in &addrs {
unsafe { a.deallocate_physical(BASE, N1, p) };
}
let mut recovered = 0;
while a.allocate_physical(BASE, N1).is_ok() {
recovered += 1;
}
assert_eq!(
recovered, total,
"frames leaked through the regioned-over-magazine stack"
);
}
#[test]
fn pinned_region_never_steals_even_with_cache() {
let (a, _r0, r1) = pool(32);
let (lo1, hi1) = bounds(&r1, 32);
set_cpu(0);
let mut held = Vec::new();
while let Ok(p) = a.alloc_in_region(0, BASE, N1) {
held.push(p);
}
assert_eq!(
a.alloc_in_region(0, BASE, N1),
Err(AllocError::OutOfMemory),
"an exhausted pinned region must not steal another region"
);
let other = a
.alloc_in_region(1, BASE, N1)
.expect("region 1 should be free");
assert!((lo1..hi1).contains(&other));
unsafe { a.deallocate_physical(BASE, N1, other) };
for p in held {
unsafe { a.deallocate_physical(BASE, N1, p) };
}
}
#[cfg_attr(audit, ignore = "audit builds are sequential-only")]
#[test]
fn concurrent_cross_cpu_no_double_alloc_or_leak() {
const ITERS: usize = if cfg!(miri) { 16 } else { 5_000 };
let (alloc, _r0, _r1) = pool(if cfg!(miri) { 64 } else { 1024 });
let alloc = Arc::new(alloc);
let checked_out: Arc<Mutex<HashSet<usize>>> = Arc::new(Mutex::new(HashSet::new()));
let next_cpu = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..4)
.map(|_| {
let alloc = Arc::clone(&alloc);
let checked_out = Arc::clone(&checked_out);
let next_cpu = Arc::clone(&next_cpu);
std::thread::spawn(move || {
let cpu = next_cpu.fetch_add(1, Relaxed);
let neighbour = cpu + 1;
for _ in 0..ITERS {
set_cpu(cpu);
if let Ok(p) = alloc.allocate_physical(BASE, N1) {
assert!(
checked_out.lock().unwrap().insert(p),
"frame {p:#x} handed out twice"
);
assert!(checked_out.lock().unwrap().remove(&p), "frame vanished");
set_cpu(neighbour);
unsafe { alloc.deallocate_physical(BASE, N1, p) };
}
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
assert!(
checked_out.lock().unwrap().is_empty(),
"frames still checked out after all workers finished"
);
set_cpu(0);
let mut remaining = 0;
while alloc.allocate_physical(BASE, N1).is_ok() {
remaining += 1;
}
assert!(remaining > 0, "pool empty after churn — frames leaked");
}