pub use self::{
buddy::BuddyAllocator, bump::BumpAllocator, free_list::FreeListAllocator, region::Region,
};
use super::{align_down, AllocationHandle, DeviceAlignment, DeviceLayout};
use crate::{image::ImageTiling, DeviceSize};
use std::{
error::Error,
fmt::{self, Debug, Display},
ops::Range,
};
mod buddy;
mod bump;
mod free_list;
pub unsafe trait Suballocator {
type Suballocations<'a>: Iterator<Item = SuballocationNode>
+ DoubleEndedIterator
+ ExactSizeIterator
where
Self: Sized + 'a;
fn new(region: Region) -> Self
where
Self: Sized;
fn allocate(
&mut self,
layout: DeviceLayout,
allocation_type: AllocationType,
buffer_image_granularity: DeviceAlignment,
) -> Result<Suballocation, SuballocatorError>;
unsafe fn deallocate(&mut self, suballocation: Suballocation);
fn reset(&mut self);
fn free_size(&self) -> DeviceSize;
fn suballocations(&self) -> Self::Suballocations<'_>
where
Self: Sized;
}
impl Debug for dyn Suballocator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Suballocator").finish_non_exhaustive()
}
}
mod region {
use super::{DeviceLayout, DeviceSize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Region {
offset: DeviceSize,
size: DeviceSize,
}
impl Region {
#[inline]
pub const fn new(offset: DeviceSize, size: DeviceSize) -> Option<Self> {
if offset.saturating_add(size) <= DeviceLayout::MAX_SIZE {
Some(unsafe { Region::new_unchecked(offset, size) })
} else {
None
}
}
#[inline]
pub const unsafe fn new_unchecked(offset: DeviceSize, size: DeviceSize) -> Self {
Region { offset, size }
}
#[inline]
pub const fn offset(&self) -> DeviceSize {
self.offset
}
#[inline]
pub const fn size(&self) -> DeviceSize {
self.size
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AllocationType {
Unknown = 0,
Linear = 1,
NonLinear = 2,
}
impl From<ImageTiling> for AllocationType {
#[inline]
fn from(tiling: ImageTiling) -> Self {
match tiling {
ImageTiling::Optimal => AllocationType::NonLinear,
ImageTiling::Linear => AllocationType::Linear,
ImageTiling::DrmFormatModifier => AllocationType::Unknown,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Suballocation {
pub offset: DeviceSize,
pub size: DeviceSize,
pub allocation_type: AllocationType,
pub handle: AllocationHandle,
}
impl Suballocation {
#[inline]
pub fn as_range(&self) -> Range<DeviceSize> {
self.offset..self.offset + self.size
}
#[inline]
pub fn as_usize_range(&self) -> Range<usize> {
self.offset as usize..(self.offset + self.size) as usize
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SuballocatorError {
OutOfRegionMemory,
FragmentedRegion,
}
impl Error for SuballocatorError {}
impl Display for SuballocatorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Self::OutOfRegionMemory => "out of region memory",
Self::FragmentedRegion => "the region is too fragmented",
};
f.write_str(msg)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SuballocationNode {
pub offset: DeviceSize,
pub size: DeviceSize,
pub allocation_type: SuballocationType,
}
impl SuballocationNode {
#[inline]
pub fn as_range(&self) -> Range<DeviceSize> {
self.offset..self.offset + self.size
}
#[inline]
pub fn as_usize_range(&self) -> Range<usize> {
self.offset as usize..(self.offset + self.size) as usize
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SuballocationType {
Unknown = 0,
Linear = 1,
NonLinear = 2,
Free = 3,
}
impl From<AllocationType> for SuballocationType {
#[inline]
fn from(ty: AllocationType) -> Self {
match ty {
AllocationType::Unknown => SuballocationType::Unknown,
AllocationType::Linear => SuballocationType::Linear,
AllocationType::NonLinear => SuballocationType::NonLinear,
}
}
}
fn are_blocks_on_same_page(
a_offset: DeviceSize,
a_size: DeviceSize,
b_offset: DeviceSize,
page_size: DeviceAlignment,
) -> bool {
debug_assert!(a_offset + a_size > 0);
debug_assert!(a_offset + a_size <= b_offset);
let a_end = a_offset + a_size - 1;
let a_end_page = align_down(a_end, page_size);
let b_start_page = align_down(b_offset, page_size);
a_end_page == b_start_page
}
#[cfg(test)]
mod tests {
use super::*;
use crossbeam_queue::ArrayQueue;
use parking_lot::Mutex;
use std::thread;
const fn unwrap<T: Copy>(opt: Option<T>) -> T {
match opt {
Some(x) => x,
None => panic!(),
}
}
const DUMMY_LAYOUT: DeviceLayout = unwrap(DeviceLayout::from_size_alignment(1, 1));
#[test]
fn free_list_allocator_capacity() {
const THREADS: DeviceSize = 12;
const ALLOCATIONS_PER_THREAD: DeviceSize = 100;
const ALLOCATION_STEP: DeviceSize = 117;
const REGION_SIZE: DeviceSize =
(ALLOCATION_STEP * (THREADS + 1) * THREADS / 2) * ALLOCATIONS_PER_THREAD;
let allocator = Mutex::new(FreeListAllocator::new(Region::new(0, REGION_SIZE).unwrap()));
let allocs = ArrayQueue::new((ALLOCATIONS_PER_THREAD * THREADS) as usize);
thread::scope(|scope| {
for i in 1..=THREADS {
let (allocator, allocs) = (&allocator, &allocs);
scope.spawn(move || {
let layout = DeviceLayout::from_size_alignment(i * ALLOCATION_STEP, 1).unwrap();
for _ in 0..ALLOCATIONS_PER_THREAD {
allocs
.push(
allocator
.lock()
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap(),
)
.unwrap();
}
});
}
});
let mut allocator = allocator.into_inner();
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(allocator.free_size(), 0);
for alloc in allocs {
unsafe { allocator.deallocate(alloc) };
}
assert_eq!(allocator.free_size(), REGION_SIZE);
let alloc = allocator
.allocate(
DeviceLayout::from_size_alignment(REGION_SIZE, 1).unwrap(),
AllocationType::Unknown,
DeviceAlignment::MIN,
)
.unwrap();
unsafe { allocator.deallocate(alloc) };
}
#[test]
fn free_list_allocator_respects_alignment() {
const REGION_SIZE: DeviceSize = 10 * 256;
const LAYOUT: DeviceLayout = unwrap(DeviceLayout::from_size_alignment(1, 256));
let mut allocator = FreeListAllocator::new(Region::new(0, REGION_SIZE).unwrap());
let mut allocs = Vec::with_capacity(10);
for _ in 0..10 {
allocs.push(
allocator
.allocate(LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap(),
);
}
assert!(allocator
.allocate(LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(allocator.free_size(), REGION_SIZE - 10);
for alloc in allocs.drain(..) {
unsafe { allocator.deallocate(alloc) };
}
}
#[test]
fn free_list_allocator_respects_granularity() {
const GRANULARITY: DeviceAlignment = unwrap(DeviceAlignment::new(16));
const REGION_SIZE: DeviceSize = 2 * GRANULARITY.as_devicesize();
let mut allocator = FreeListAllocator::new(Region::new(0, REGION_SIZE).unwrap());
let mut linear_allocs = Vec::with_capacity(REGION_SIZE as usize / 2);
let mut nonlinear_allocs = Vec::with_capacity(REGION_SIZE as usize / 2);
for i in 0..REGION_SIZE {
if i % 2 == 0 {
linear_allocs.push(
allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.unwrap(),
);
} else {
nonlinear_allocs.push(
allocator
.allocate(DUMMY_LAYOUT, AllocationType::NonLinear, GRANULARITY)
.unwrap(),
);
}
}
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.is_err());
assert_eq!(allocator.free_size(), 0);
for alloc in linear_allocs.drain(..) {
unsafe { allocator.deallocate(alloc) };
}
let alloc = allocator
.allocate(
DeviceLayout::from_size_alignment(GRANULARITY.as_devicesize(), 1).unwrap(),
AllocationType::Unknown,
GRANULARITY,
)
.unwrap();
unsafe { allocator.deallocate(alloc) };
let alloc = allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, GRANULARITY)
.unwrap();
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, GRANULARITY)
.is_err());
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.is_err());
unsafe { allocator.deallocate(alloc) };
for alloc in nonlinear_allocs.drain(..) {
unsafe { allocator.deallocate(alloc) };
}
}
#[test]
fn buddy_allocator_capacity() {
const MAX_ORDER: usize = 10;
const REGION_SIZE: DeviceSize = BuddyAllocator::MIN_NODE_SIZE << MAX_ORDER;
let mut allocator = BuddyAllocator::new(Region::new(0, REGION_SIZE).unwrap());
let mut allocs = Vec::with_capacity(1 << MAX_ORDER);
for order in 0..=MAX_ORDER {
let layout =
DeviceLayout::from_size_alignment(BuddyAllocator::MIN_NODE_SIZE << order, 1)
.unwrap();
for _ in 0..1 << (MAX_ORDER - order) {
allocs.push(
allocator
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap(),
);
}
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(allocator.free_size(), 0);
for alloc in allocs.drain(..) {
unsafe { allocator.deallocate(alloc) };
}
}
let mut orders = (0..MAX_ORDER).collect::<Vec<_>>();
for mid in 0..MAX_ORDER {
orders.rotate_left(mid);
for &order in &orders {
let layout =
DeviceLayout::from_size_alignment(BuddyAllocator::MIN_NODE_SIZE << order, 1)
.unwrap();
allocs.push(
allocator
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap(),
);
}
let alloc = allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap();
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(allocator.free_size(), 0);
unsafe { allocator.deallocate(alloc) };
for alloc in allocs.drain(..) {
unsafe { allocator.deallocate(alloc) };
}
}
}
#[test]
fn buddy_allocator_respects_alignment() {
const REGION_SIZE: DeviceSize = 4096;
let mut allocator = BuddyAllocator::new(Region::new(0, REGION_SIZE).unwrap());
{
let layout = DeviceLayout::from_size_alignment(1, 4096).unwrap();
let alloc = allocator
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap();
assert!(allocator
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(
allocator.free_size(),
REGION_SIZE - BuddyAllocator::MIN_NODE_SIZE,
);
unsafe { allocator.deallocate(alloc) };
}
{
let layout_a = DeviceLayout::from_size_alignment(1, 256).unwrap();
let allocations_a = REGION_SIZE / layout_a.alignment().as_devicesize();
let layout_b = DeviceLayout::from_size_alignment(1, 16).unwrap();
let allocations_b = REGION_SIZE / layout_b.alignment().as_devicesize() - allocations_a;
let mut allocs =
Vec::with_capacity((REGION_SIZE / BuddyAllocator::MIN_NODE_SIZE) as usize);
for _ in 0..allocations_a {
allocs.push(
allocator
.allocate(layout_a, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap(),
);
}
assert!(allocator
.allocate(layout_a, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(
allocator.free_size(),
REGION_SIZE - allocations_a * BuddyAllocator::MIN_NODE_SIZE,
);
for _ in 0..allocations_b {
allocs.push(
allocator
.allocate(layout_b, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap(),
);
}
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(allocator.free_size(), 0);
for alloc in allocs {
unsafe { allocator.deallocate(alloc) };
}
}
}
#[test]
fn buddy_allocator_respects_granularity() {
const GRANULARITY: DeviceAlignment = unwrap(DeviceAlignment::new(256));
const REGION_SIZE: DeviceSize = 2 * GRANULARITY.as_devicesize();
let mut allocator = BuddyAllocator::new(Region::new(0, REGION_SIZE).unwrap());
{
const ALLOCATIONS: DeviceSize = REGION_SIZE / BuddyAllocator::MIN_NODE_SIZE;
let mut allocs = Vec::with_capacity(ALLOCATIONS as usize);
for _ in 0..ALLOCATIONS {
allocs.push(
allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.unwrap(),
);
}
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.is_err());
assert_eq!(allocator.free_size(), 0);
for alloc in allocs {
unsafe { allocator.deallocate(alloc) };
}
}
{
let alloc1 = allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, GRANULARITY)
.unwrap();
let alloc2 = allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, GRANULARITY)
.unwrap();
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.is_err());
assert_eq!(allocator.free_size(), 0);
unsafe { allocator.deallocate(alloc1) };
unsafe { allocator.deallocate(alloc2) };
}
}
#[test]
fn bump_allocator_respects_alignment() {
const ALIGNMENT: DeviceSize = 16;
const REGION_SIZE: DeviceSize = 10 * ALIGNMENT;
let layout = DeviceLayout::from_size_alignment(1, ALIGNMENT).unwrap();
let mut allocator = BumpAllocator::new(Region::new(0, REGION_SIZE).unwrap());
for _ in 0..10 {
allocator
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap();
}
assert!(allocator
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
for _ in 0..ALIGNMENT - 1 {
allocator
.allocate(DUMMY_LAYOUT, AllocationType::Unknown, DeviceAlignment::MIN)
.unwrap();
}
assert!(allocator
.allocate(layout, AllocationType::Unknown, DeviceAlignment::MIN)
.is_err());
assert_eq!(allocator.free_size(), 0);
allocator.reset();
assert_eq!(allocator.free_size(), REGION_SIZE);
}
#[test]
fn bump_allocator_respects_granularity() {
const ALLOCATIONS: DeviceSize = 10;
const GRANULARITY: DeviceAlignment = unwrap(DeviceAlignment::new(1024));
const REGION_SIZE: DeviceSize = ALLOCATIONS * GRANULARITY.as_devicesize();
let mut allocator = BumpAllocator::new(Region::new(0, REGION_SIZE).unwrap());
for i in 0..ALLOCATIONS {
for _ in 0..GRANULARITY.as_devicesize() {
allocator
.allocate(
DUMMY_LAYOUT,
if i % 2 == 0 {
AllocationType::NonLinear
} else {
AllocationType::Linear
},
GRANULARITY,
)
.unwrap();
}
}
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.is_err());
assert_eq!(allocator.free_size(), 0);
allocator.reset();
for i in 0..ALLOCATIONS {
allocator
.allocate(
DUMMY_LAYOUT,
if i % 2 == 0 {
AllocationType::Linear
} else {
AllocationType::NonLinear
},
GRANULARITY,
)
.unwrap();
}
assert!(allocator
.allocate(DUMMY_LAYOUT, AllocationType::Linear, GRANULARITY)
.is_err());
assert_eq!(allocator.free_size(), GRANULARITY.as_devicesize() - 1);
allocator.reset();
assert_eq!(allocator.free_size(), REGION_SIZE);
}
}