Source code
Revision control
Copy as Markdown
Other Tools
use {↩
crate::{↩
align_up, error::AllocationError, heap::Heap, slab::Slab, unreachable_unchecked,↩
util::try_arc_unwrap, MemoryBounds,↩
},↩
alloc::{sync::Arc, vec::Vec},↩
core::{convert::TryFrom as _, mem::replace, ptr::NonNull},↩
gpu_alloc_types::{AllocationFlags, DeviceMapError, MemoryDevice, MemoryPropertyFlags},↩
};↩
↩
#[derive(Debug)]↩
pub(crate) struct BuddyBlock<M> {↩
pub memory: Arc<M>,↩
pub ptr: Option<NonNull<u8>>,↩
pub offset: u64,↩
pub size: u64,↩
pub chunk: usize,↩
pub index: usize,↩
}↩
↩
unsafe impl<M> Sync for BuddyBlock<M> where M: Sync {}↩
unsafe impl<M> Send for BuddyBlock<M> where M: Send {}↩
↩
#[derive(Clone, Copy, Debug)]↩
enum PairState {↩
Exhausted,↩
Ready {↩
ready: Side,↩
next: usize,↩
prev: usize,↩
},↩
}↩
↩
impl PairState {↩
unsafe fn replace_next(&mut self, value: usize) -> usize {↩
match self {↩
PairState::Exhausted => unreachable_unchecked(),↩
PairState::Ready { next, .. } => replace(next, value),↩
}↩
}↩
↩
unsafe fn replace_prev(&mut self, value: usize) -> usize {↩
match self {↩
PairState::Exhausted => unreachable_unchecked(),↩
PairState::Ready { prev, .. } => replace(prev, value),↩
}↩
}↩
}↩
↩
#[derive(Clone, Copy, Debug, PartialEq, Eq)]↩
enum Side {↩
Left,↩
Right,↩
}↩
use Side::*;↩
↩
#[derive(Debug)]↩
struct PairEntry {↩
state: PairState,↩
chunk: usize,↩
offset: u64,↩
parent: Option<usize>,↩
}↩
↩
struct SizeBlockEntry {↩
chunk: usize,↩
offset: u64,↩
index: usize,↩
}↩
↩
#[derive(Debug)]↩
struct Size {↩
next_ready: usize,↩
pairs: Slab<PairEntry>,↩
}↩
#[derive(Debug)]↩
enum Release {↩
None,↩
Parent(usize),↩
Chunk(usize),↩
}↩
↩
impl Size {↩
fn new() -> Self {↩
Size {↩
pairs: Slab::new(),↩
next_ready: 0,↩
}↩
}↩
↩
unsafe fn add_pair_and_acquire_left(↩
&mut self,↩
chunk: usize,↩
offset: u64,↩
parent: Option<usize>,↩
) -> SizeBlockEntry {↩
if self.next_ready < self.pairs.len() {↩
unreachable_unchecked()↩
}↩
↩
let index = self.pairs.insert(PairEntry {↩
state: PairState::Exhausted,↩
chunk,↩
offset,↩
parent,↩
});↩
↩
let entry = self.pairs.get_unchecked_mut(index);↩
entry.state = PairState::Ready {↩
next: index,↩
prev: index,↩
ready: Right, // Left is allocated.↩
};↩
self.next_ready = index;↩
↩
SizeBlockEntry {↩
chunk,↩
offset,↩
index: index << 1,↩
}↩
}↩
↩
fn acquire(&mut self, size: u64) -> Option<SizeBlockEntry> {↩
if self.next_ready >= self.pairs.len() {↩
return None;↩
}↩
↩
let ready = self.next_ready;↩
↩
let entry = unsafe { self.pairs.get_unchecked_mut(ready) };↩
let chunk = entry.chunk;↩
let offset = entry.offset;↩
↩
let bit = match entry.state {↩
PairState::Exhausted => unsafe { unreachable_unchecked() },↩
PairState::Ready { ready, next, prev } => {↩
entry.state = PairState::Exhausted;↩
↩
if prev == self.next_ready {↩
// The only ready entry.↩
debug_assert_eq!(next, self.next_ready);↩
self.next_ready = self.pairs.len();↩
} else {↩
let prev_entry = unsafe { self.pairs.get_unchecked_mut(prev) };↩
let prev_next = unsafe { prev_entry.state.replace_next(next) };↩
debug_assert_eq!(prev_next, self.next_ready);↩
↩
let next_entry = unsafe { self.pairs.get_unchecked_mut(next) };↩
let next_prev = unsafe { next_entry.state.replace_prev(prev) };↩
debug_assert_eq!(next_prev, self.next_ready);↩
↩
self.next_ready = next;↩
}↩
↩
match ready {↩
Left => 0,↩
Right => 1,↩
}↩
}↩
};↩
↩
Some(SizeBlockEntry {↩
chunk,↩
offset: offset + bit as u64 * size,↩
index: (ready << 1) | bit as usize,↩
})↩
}↩
↩
fn release(&mut self, index: usize) -> Release {↩
let side = match index & 1 {↩
0 => Side::Left,↩
1 => Side::Right,↩
_ => unsafe { unreachable_unchecked() },↩
};↩
let entry_index = index >> 1;↩
↩
let len = self.pairs.len();↩
↩
let entry = self.pairs.get_mut(entry_index);↩
↩
let chunk = entry.chunk;↩
let offset = entry.offset;↩
let parent = entry.parent;↩
↩
match entry.state {↩
PairState::Exhausted => {↩
if self.next_ready == len {↩
entry.state = PairState::Ready {↩
ready: side,↩
next: entry_index,↩
prev: entry_index,↩
};↩
self.next_ready = entry_index;↩
} else {↩
debug_assert!(self.next_ready < len);↩
↩
let next = self.next_ready;↩
let next_entry = unsafe { self.pairs.get_unchecked_mut(next) };↩
let prev = unsafe { next_entry.state.replace_prev(entry_index) };↩
↩
let prev_entry = unsafe { self.pairs.get_unchecked_mut(prev) };↩
let prev_next = unsafe { prev_entry.state.replace_next(entry_index) };↩
debug_assert_eq!(prev_next, next);↩
↩
let entry = unsafe { self.pairs.get_unchecked_mut(entry_index) };↩
entry.state = PairState::Ready {↩
ready: side,↩
next,↩
prev,↩
};↩
}↩
Release::None↩
}↩
↩
PairState::Ready { ready, .. } if ready == side => {↩
panic!("Attempt to dealloate already free block")↩
}↩
↩
PairState::Ready { next, prev, .. } => {↩
unsafe {↩
self.pairs.remove_unchecked(entry_index);↩
}↩
↩
if prev == entry_index {↩
debug_assert_eq!(next, entry_index);↩
self.next_ready = self.pairs.len();↩
} else {↩
let prev_entry = unsafe { self.pairs.get_unchecked_mut(prev) };↩
let prev_next = unsafe { prev_entry.state.replace_next(next) };↩
debug_assert_eq!(prev_next, entry_index);↩
↩
let next_entry = unsafe { self.pairs.get_unchecked_mut(next) };↩
let next_prev = unsafe { next_entry.state.replace_prev(prev) };↩
debug_assert_eq!(next_prev, entry_index);↩
↩
self.next_ready = next;↩
}↩
↩
match parent {↩
Some(parent) => Release::Parent(parent),↩
None => {↩
debug_assert_eq!(offset, 0);↩
Release::Chunk(chunk)↩
}↩
}↩
}↩
}↩
}↩
}↩
↩
#[derive(Debug)]↩
struct Chunk<M> {↩
memory: Arc<M>,↩
ptr: Option<NonNull<u8>>,↩
size: u64,↩
}↩
↩
#[derive(Debug)]↩
pub(crate) struct BuddyAllocator<M> {↩
minimal_size: u64,↩
chunks: Slab<Chunk<M>>,↩
sizes: Vec<Size>,↩
memory_type: u32,↩
props: MemoryPropertyFlags,↩
atom_mask: u64,↩
}↩
↩
unsafe impl<M> Sync for BuddyAllocator<M> where M: Sync {}↩
unsafe impl<M> Send for BuddyAllocator<M> where M: Send {}↩
↩
impl<M> BuddyAllocator<M>↩
where↩
M: MemoryBounds + 'static,↩
{↩
pub fn new(↩
minimal_size: u64,↩
initial_dedicated_size: u64,↩
memory_type: u32,↩
props: MemoryPropertyFlags,↩
atom_mask: u64,↩
) -> Self {↩
assert!(↩
minimal_size.is_power_of_two(),↩
"Minimal allocation size of buddy allocator must be power of two"↩
);↩
assert!(↩
initial_dedicated_size.is_power_of_two(),↩
"Dedicated allocation size of buddy allocator must be power of two"↩
);↩
↩
let initial_sizes = (initial_dedicated_size↩
.trailing_zeros()↩
.saturating_sub(minimal_size.trailing_zeros())) as usize;↩
↩
BuddyAllocator {↩
minimal_size,↩
chunks: Slab::new(),↩
sizes: (0..initial_sizes).map(|_| Size::new()).collect(),↩
memory_type,↩
props,↩
atom_mask: atom_mask | (minimal_size - 1),↩
}↩
}↩
↩
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]↩
pub unsafe fn alloc(↩
&mut self,↩
device: &impl MemoryDevice<M>,↩
size: u64,↩
align_mask: u64,↩
flags: AllocationFlags,↩
heap: &mut Heap,↩
allocations_remains: &mut u32,↩
) -> Result<BuddyBlock<M>, AllocationError> {↩
let align_mask = align_mask | self.atom_mask;↩
↩
let size = align_up(size, align_mask)↩
.and_then(|size| size.checked_next_power_of_two())↩
.ok_or(AllocationError::OutOfDeviceMemory)?;↩
↩
let size = size.max(self.minimal_size);↩
↩
let size_index = size.trailing_zeros() - self.minimal_size.trailing_zeros();↩
let size_index =↩
usize::try_from(size_index).map_err(|_| AllocationError::OutOfDeviceMemory)?;↩
↩
while self.sizes.len() <= size_index {↩
self.sizes.push(Size::new());↩
}↩
↩
let host_visible = self.host_visible();↩
↩
let mut candidate_size_index = size_index;↩
↩
let (mut entry, entry_size_index) = loop {↩
let sizes_len = self.sizes.len();↩
↩
let candidate_size_entry = &mut self.sizes[candidate_size_index];↩
let candidate_size = self.minimal_size << candidate_size_index;↩
↩
if let Some(entry) = candidate_size_entry.acquire(candidate_size) {↩
break (entry, candidate_size_index);↩
}↩
↩
if sizes_len == candidate_size_index + 1 {↩
// That's size of device allocation.↩
if *allocations_remains == 0 {↩
return Err(AllocationError::TooManyObjects);↩
}↩
↩
let chunk_size = self.minimal_size << (candidate_size_index + 1);↩
let mut memory = device.allocate_memory(chunk_size, self.memory_type, flags)?;↩
*allocations_remains -= 1;↩
heap.alloc(chunk_size);↩
↩
let ptr = if host_visible {↩
match device.map_memory(&mut memory, 0, chunk_size) {↩
Ok(ptr) => Some(ptr),↩
Err(DeviceMapError::OutOfDeviceMemory) => {↩
return Err(AllocationError::OutOfDeviceMemory)↩
}↩
Err(DeviceMapError::MapFailed) | Err(DeviceMapError::OutOfHostMemory) => {↩
return Err(AllocationError::OutOfHostMemory)↩
}↩
}↩
} else {↩
None↩
};↩
↩
let chunk = self.chunks.insert(Chunk {↩
memory: Arc::new(memory),↩
ptr,↩
size: chunk_size,↩
});↩
↩
let entry = candidate_size_entry.add_pair_and_acquire_left(chunk, 0, None);↩
↩
break (entry, candidate_size_index);↩
}↩
↩
candidate_size_index += 1;↩
};↩
↩
for size_index in (size_index..entry_size_index).rev() {↩
let size_entry = &mut self.sizes[size_index];↩
entry =↩
size_entry.add_pair_and_acquire_left(entry.chunk, entry.offset, Some(entry.index));↩
}↩
↩
let chunk_entry = self.chunks.get_unchecked(entry.chunk);↩
↩
debug_assert!(↩
entry↩
.offset↩
.checked_add(size)↩
.map_or(false, |end| end <= chunk_entry.size),↩
"Offset + size is not in chunk bounds"↩
);↩
↩
Ok(BuddyBlock {↩
memory: chunk_entry.memory.clone(),↩
ptr: chunk_entry↩
.ptr↩
.map(|ptr| NonNull::new_unchecked(ptr.as_ptr().add(entry.offset as usize))),↩
offset: entry.offset,↩
size,↩
chunk: entry.chunk,↩
index: entry.index,↩
})↩
}↩
↩
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]↩
pub unsafe fn dealloc(↩
&mut self,↩
device: &impl MemoryDevice<M>,↩
block: BuddyBlock<M>,↩
heap: &mut Heap,↩
allocations_remains: &mut u32,↩
) {↩
debug_assert!(block.size.is_power_of_two());↩
↩
let size_index =↩
(block.size.trailing_zeros() - self.minimal_size.trailing_zeros()) as usize;↩
↩
let mut release_index = block.index;↩
let mut release_size_index = size_index;↩
↩
loop {↩
match self.sizes[release_size_index].release(release_index) {↩
Release::Parent(parent) => {↩
release_size_index += 1;↩
release_index = parent;↩
}↩
Release::Chunk(chunk) => {↩
debug_assert_eq!(chunk, block.chunk);↩
debug_assert_eq!(↩
self.chunks.get(chunk).size,↩
self.minimal_size << (release_size_index + 1)↩
);↩
let chunk = self.chunks.remove(chunk);↩
drop(block);↩
↩
let memory = try_arc_unwrap(chunk.memory)↩
.expect("Memory shared after last block deallocated");↩
↩
device.deallocate_memory(memory);↩
*allocations_remains += 1;↩
heap.dealloc(chunk.size);↩
↩
return;↩
}↩
Release::None => return,↩
}↩
}↩
}↩
↩
fn host_visible(&self) -> bool {↩
self.props.contains(MemoryPropertyFlags::HOST_VISIBLE)↩
}↩
}↩