pub mod common;
use std::{cell, hint, mem, num, rc::Rc};
use common::{
Counted,
alloc::{
AlignedUnit, AlignedZst, EmptyRows, Plan, Rows, Wrapped,
owner::{Aligned, Owner},
system::{Activity, Tracker},
},
};
use o3::alloc::{self, raw::Shape as _};
#[global_allocator]
static ALLOCATOR: Tracker = Tracker;
#[test]
fn planes_own_roots() {
let drops = cell::Cell::new(Some(0));
let calls = cell::Cell::new(0);
let owner = Rc::new(());
let weak = Rc::downgrade(&owner);
let plan = Plan::<Owner<'_>, u64>::try_new(3, 2).expect("representable plane layouts");
let bytes = plan.layout().size() + 4 * mem::size_of::<usize>();
let ((), activity) = Tracker::measure(|| {
let drops = &drops;
let calls = &calls;
let block = alloc::raw::Block::try_from_fn(
plan,
Owner {
drops,
value: Box::new(9),
},
move |index| {
assert_eq!(Rc::strong_count(&owner), 1);
calls.set(calls.get() + 1);
Owner {
drops,
value: Box::new(index + 1),
}
},
|index| {
assert!(weak.upgrade().is_some());
calls.set(calls.get() + 1);
index as u64 + 10
},
)
.expect("two initialized planes");
assert!(weak.upgrade().is_none());
assert_eq!(*block.as_ref()[2].value, 3);
assert_eq!(block.tail(), [10, 11]);
});
assert_eq!(calls.get(), 5);
assert_eq!(drops.get(), Some(1239));
assert_eq!(activity, [5, bytes, 5, bytes]);
}
#[test]
fn planes_parts() {
let calls = cell::Cell::new(0);
let bytes = alloc::raw::Shape::layout(&Rows).size();
let ((), activity) = Tracker::measure(|| {
let block = alloc::raw::Block::try_parts(Rows, (), |index| {
calls.set(calls.get() + 1);
(index as u8, index as u16 + 10)
})
.expect("four-plane allocation");
assert_eq!(block.extra(), (&[0, 1, 2][..], &[10, 11, 12][..]));
});
assert_eq!(calls.get(), 3);
assert_eq!(activity, [1, bytes, 1, bytes]);
}
#[test]
fn row_bounds_match_layout() {
use std::alloc::{Layout, LayoutError};
fn check<R, T, E, L>() {
fn layout<R, T, E, L>(count: usize) -> Result<Layout, LayoutError> {
let root = Layout::array::<R>(count)?;
let (layout, _) = root.extend(Layout::array::<T>(count)?)?;
let (layout, _) = layout.extend(Layout::array::<E>(count)?)?;
let (layout, _) = layout.extend(Layout::array::<L>(count)?)?;
Ok(layout.pad_to_align())
}
let maximum = <EmptyRows<R, T, E, L> as alloc::raw::Rows>::EXTRA_MAX;
assert!(layout::<R, T, E, L>(maximum).is_ok());
if let Some(overflow) = maximum.checked_add(1) {
assert!(layout::<R, T, E, L>(overflow).is_err());
}
}
check::<u32, u64, u8, u16>();
check::<u8, Aligned, u16, AlignedZst>();
check::<Aligned, u8, AlignedZst, u16>();
check::<u8, u16, Aligned, AlignedZst>();
check::<u8, u16, AlignedZst, Aligned>();
check::<(), (), (), ()>();
check::<AlignedZst, (), AlignedZst, AlignedZst>();
assert_eq!(
<EmptyRows<AlignedZst, (), AlignedZst, AlignedZst> as alloc::raw::Rows>::EXTRA_MAX,
usize::MAX
);
}
#[test]
fn planes_own_extra() {
let drops = cell::Cell::new(Some(0));
let calls = cell::Cell::new(0);
let owner = Rc::new(());
let weak = Rc::downgrade(&owner);
let plan =
Plan::<Owner<'_>, u64, Aligned>::try_new(3, 2).expect("representable three-plane layout");
let bytes = plan.layout().size() + 3 * mem::size_of::<usize>();
let ((), activity) = Tracker::measure(|| {
let drops = &drops;
let calls = &calls;
let mut block = alloc::raw::Block::try_planes(
plan,
(),
move |index| {
assert_eq!(Rc::strong_count(&owner), 1);
calls.set(calls.get() + 1);
(
Owner {
drops,
value: Box::new(index + 1),
},
Aligned(index as u8 + 20),
)
},
|index| {
assert!(weak.upgrade().is_some());
assert_eq!(calls.get(), 3);
index as u64 + 10
},
)
.expect("initialized three-plane owner");
assert!(weak.upgrade().is_none());
assert_eq!(*block.as_ref()[2].value, 3);
assert_eq!(block.tail(), [10, 11]);
assert_eq!(block.values().len(), 3);
assert_eq!(
[
block.values()[0].0,
block.values()[1].0,
block.values()[2].0
],
[20, 21, 22]
);
assert_eq!(block.values().as_ptr().addr() % align_of::<Aligned>(), 0);
block.parts_mut().2[1] = 29;
assert_eq!(block.tail(), [10, 29]);
assert_eq!(block.values()[1].0, 21);
});
assert_eq!(drops.get(), Some(123));
assert_eq!(activity, [4, bytes, 4, bytes]);
}
#[test]
fn planes_extra_failure() {
let calls = cell::Cell::new(0);
let plan = Plan::<u8, u64, Aligned>::try_new(3, 2).expect("representable three-plane layout");
let (result, activity) = Tracker::activity(|| {
Tracker::fail_after(0, || {
alloc::raw::Block::try_planes(
plan,
(),
|index| {
calls.set(calls.get() + 1);
(index as u8, Aligned(0))
},
|_| {
calls.set(calls.get() + 1);
0
},
)
})
});
assert!(matches!(result, Err(alloc::Exhausted)));
assert_eq!(calls.get(), 0);
assert!(activity == Activity::default());
}
#[test]
fn planes_extra_empty() {
let plan =
Plan::<u8, u64, Aligned>::try_new(0, 2).expect("empty root with aligned auxiliary plane");
let block = alloc::raw::Block::try_planes(plan, (), |_| (0, Aligned(0)), |index| index as u64)
.expect("tail-only allocation");
assert!(block.as_ref().is_empty());
assert!(block.values().is_empty());
assert_eq!(block.tail(), [0, 1]);
}
#[test]
fn planes_zero_size() {
let calls = cell::Cell::new(0);
let ((), activity) = Tracker::measure(|| {
Tracker::fail_after(0, || {
let block = alloc::raw::Block::try_from_fn(
Plan::<u64, u64>::try_new(0, 0).expect("representable plane layouts"),
(),
|_| {
calls.set(calls.get() + 1);
0
},
|_| {
calls.set(calls.get() + 1);
0
},
)
.expect("empty planes");
assert!(block.as_ref().is_empty());
assert!(block.tail().is_empty());
let block = alloc::raw::Block::try_from_fn(
Plan::<(), ()>::try_new(3, 2).expect("representable plane layouts"),
(),
|_| (),
|_| (),
)
.expect("zero-sized planes");
assert_eq!(block.as_ref().len(), 3);
assert_eq!(block.tail().len(), 2);
});
});
assert_eq!(calls.get(), 0);
assert_eq!(activity, [0; 4]);
}
#[test]
fn block_extent() {
use o3::alloc::array::Block;
let drops = cell::Cell::new(Some(0));
let (released, bytes) = {
let (block, activity) = Tracker::activity(|| {
Block::<mem::MaybeUninit<u64>, _, _>::try_new(7_usize, Counted(&drops))
});
let block = block.expect("seven-entry block allocation");
assert_eq!(block.len(), 7);
assert_eq!(block.as_ptr().addr() % align_of::<u64>(), 0);
assert_eq!(activity.allocations, 1);
assert_eq!(activity.allocated_bytes, 7 * size_of::<u64>());
assert_eq!(drops.get(), Some(0));
(Tracker::begin(), activity.allocated_bytes)
};
let released = released.finish();
assert_eq!(drops.get(), Some(1));
assert_eq!(released.deallocations, 1);
assert_eq!(released.deallocated_bytes, bytes);
}
#[test]
fn block_recovery() {
use o3::alloc::array::Block;
let state = Rc::new(());
let (result, activity) = Tracker::activity(|| {
Tracker::fail_after(0, || {
Block::<mem::MaybeUninit<u64>, _, _>::try_new(7_usize, state.clone())
})
});
assert!(matches!(result, Err(alloc::Error::Exhausted)));
assert_eq!(Rc::strong_count(&state), 1);
assert!(activity == Activity::default());
let (overflow, activity) = Tracker::activity(|| {
Block::<mem::MaybeUninit<u64>, _, _>::try_new(usize::MAX, state.clone())
});
assert!(matches!(overflow, Err(alloc::Error::Overflow)));
assert_eq!(Rc::strong_count(&state), 1);
assert!(activity == Activity::default());
}
#[test]
fn plan_reuse() {
use o3::alloc::array::Plan;
let len = hint::black_box(7_usize);
let (plan, activity) =
Tracker::activity(|| Tracker::fail_after(0, || Plan::<u64, _>::try_new(len)));
let plan = plan.expect("representable layout needs no allocation");
assert!(activity == Activity::default());
let mut first = plan.try_allocate(()).expect("first allocation");
first[6].write(29);
let (failed, activity) = Tracker::activity(|| Tracker::fail_after(0, || plan.try_allocate(())));
assert!(matches!(failed, Err(alloc::Exhausted)));
assert!(activity == Activity::default());
let second = plan.try_allocate(()).expect("retry preserves the plan");
assert_eq!(second.len(), first.len());
assert_ne!(second.as_ptr(), first.as_ptr());
assert_eq!(unsafe { first[6].assume_init() }, 29);
}
#[test]
fn plan_limits() {
use o3::alloc::array::Plan;
let limit = isize::MAX as usize / size_of::<u64>();
let ((), activity) = Tracker::activity(|| {
assert!(Plan::<u64, _>::try_new(limit).is_ok());
assert!(matches!(
Plan::<u64, _>::try_new(limit + 1),
Err(alloc::Overflow)
));
assert!(matches!(
Plan::<u64, _>::try_new(usize::MAX),
Err(alloc::Overflow)
));
});
assert!(activity == Activity::default());
let block = Plan::<u64, _>::try_new(num::NonZero::<u32>::MIN.saturating_add(6))
.expect("representable extent")
.try_allocate(())
.expect("admitted layout allocation");
assert_eq!(*block.extent(), num::NonZero::<u32>::MIN.saturating_add(6));
assert_eq!(size_of_val(&block), 2 * size_of::<usize>());
}
#[test]
fn block_zero_size() {
use o3::alloc::array::Block;
let ((), activity) = Tracker::activity(|| {
Tracker::fail_after(0, || {
let empty =
Block::<mem::MaybeUninit<u64>, _>::try_new(0_usize, ()).expect("empty block");
let zst = Block::<mem::MaybeUninit<AlignedUnit>, _>::try_new(usize::MAX, ())
.expect("zero-sized block");
assert!(empty.is_empty());
assert_eq!(zst.len(), usize::MAX);
assert_eq!(zst.as_ptr().addr() % align_of::<AlignedUnit>(), 0);
});
});
assert!(activity == Activity::default());
}
#[test]
fn wrapped_migration() {
use alloc::array::Block;
let released = {
let mut owner = Wrapped::try_new().expect("wrapped allocation");
let spare = Block::try_new(8, ()).expect("replacement allocation");
let (state, trace) = Tracker::activity(|| {
unsafe {
alloc::raw::relocate(&mut owner.block, spare, [(3, 0, 2), (0, 2, 2)]);
}
let state = *owner.block.state();
*owner.block.state_mut() = (0, 4);
state
});
assert_eq!(state, (3, 4));
assert_eq!(trace.allocations, 0);
assert_eq!(trace.deallocations, 1);
assert_eq!(owner.block.len(), 8);
assert_eq!(owner.values(), [10, 20, 30, 40]);
Tracker::begin()
};
let trace = released.finish();
assert_eq!(trace.allocations, 0);
assert_eq!(trace.deallocations, 5);
}