461 lines
14 KiB
Rust
461 lines
14 KiB
Rust
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)
|
|
}
|
|
}
|