use std::any::Any;
use std::fmt::Debug;
use std::ptr::NonNull;
use crate::capability::{SharedMapping, VirtualBacking};
use crate::deferred::{AllocationReleaseOutcome, ReleaseAccounting};
use crate::{MemoryError, Tier};
#[derive(Clone, Copy, Debug)]
pub struct AllocationCommitRange {
pub ptr: NonNull<u8>,
pub allocation_bytes: usize,
pub align: usize,
pub offset: usize,
pub bytes: usize,
}
#[derive(Debug)]
pub struct MappedAllocation<T> {
pub allocation: T,
pub additional_owned_bytes: u64,
pub newly_mapped_bytes: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DeviceKey {
pub tier: Tier,
pub index: u32,
}
impl DeviceKey {
pub const HOST: Self = Self {
tier: Tier::Host,
index: 0,
};
pub const fn device(index: u32) -> Self {
Self {
tier: Tier::Device,
index,
}
}
}
pub trait SharedDevicePrefix: Send + Sync + Debug {
fn device_ptr(&self) -> u64;
fn committed_physical_bytes(&self) -> u64;
fn mapped_bytes(&self) -> usize;
fn requested_bytes(&self) -> usize;
fn as_any(&self) -> &dyn Any;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SharedPrefixCommitInfo {
pub additional_owned_bytes: u64,
pub newly_mapped_bytes: u64,
pub granules: usize,
}
pub trait DeviceAllocator: Send + Sync + Debug {
fn allocate(&self, bytes: usize, align: usize) -> Result<NonNull<u8>, MemoryError>;
unsafe fn deallocate(&self, ptr: NonNull<u8>, bytes: usize, align: usize);
unsafe fn deallocate_with_unmapped(&self, ptr: NonNull<u8>, bytes: usize, align: usize) -> u64 {
unsafe { self.deallocate(ptr, bytes, align) };
0
}
unsafe fn release(
&self,
ptr: NonNull<u8>,
bytes: usize,
align: usize,
) -> AllocationReleaseOutcome {
let unmapped_bytes = unsafe { self.deallocate_with_unmapped(ptr, bytes, align) };
AllocationReleaseOutcome::complete(ReleaseAccounting {
allocation_bytes: bytes as u64,
unmapped_bytes,
})
}
fn device(&self) -> DeviceKey;
fn commits_on_demand(&self) -> bool {
false
}
fn as_virtual_backing(&self) -> Option<&dyn VirtualBacking> {
None
}
fn as_shared_mapping(&self) -> Option<&dyn SharedMapping> {
None
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct HostAllocator;
impl DeviceAllocator for HostAllocator {
fn allocate(&self, bytes: usize, align: usize) -> Result<NonNull<u8>, MemoryError> {
let layout = std::alloc::Layout::from_size_align(bytes.max(1), align).map_err(|_| {
MemoryError::InvalidRequest {
tier: Tier::Host.name(),
requested: bytes as u64,
reason: "the requested size and alignment are not a valid layout; the alignment \
must be a power of two and the rounded size must not overflow",
}
})?;
let ptr = unsafe { std::alloc::alloc(layout) };
NonNull::new(ptr).ok_or_else(|| MemoryError::AllocationFailed {
tier: Tier::Host.name(),
requested: bytes as u64,
reason: String::from(
"the system allocator refused bytes the governor had granted; the process is \
out of address space or the host is out of memory",
),
})
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, bytes: usize, align: usize) {
let Ok(layout) = std::alloc::Layout::from_size_align(bytes.max(1), align) else {
return;
};
unsafe { std::alloc::dealloc(ptr.as_ptr(), layout) };
}
fn device(&self) -> DeviceKey {
DeviceKey::HOST
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct EagerOnly;
impl DeviceAllocator for EagerOnly {
fn allocate(&self, bytes: usize, align: usize) -> Result<NonNull<u8>, MemoryError> {
HostAllocator.allocate(bytes, align)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, bytes: usize, align: usize) {
unsafe { HostAllocator.deallocate(ptr, bytes, align) };
}
fn device(&self) -> DeviceKey {
DeviceKey::HOST
}
}
#[test]
fn eager_allocator_requires_only_the_ordinary_contract() {
let allocator: &dyn DeviceAllocator = &EagerOnly;
assert!(!allocator.commits_on_demand());
assert!(allocator.as_virtual_backing().is_none());
assert!(allocator.as_shared_mapping().is_none());
let ptr = allocator.allocate(64, 16).expect("ordinary allocation");
unsafe { allocator.deallocate(ptr, 64, 16) };
}
#[test]
fn host_allocations_are_aligned_as_requested() {
for (bytes, align) in [(1usize, 64usize), (100, 64), (4096, 256), (7, 8)] {
let ptr = HostAllocator.allocate(bytes, align).expect("granted");
assert_eq!(ptr.as_ptr() as usize % align, 0);
unsafe { HostAllocator.deallocate(ptr, bytes, align) };
}
}
#[test]
fn zero_byte_allocation_is_non_null() {
let ptr = HostAllocator.allocate(0, 64).expect("zero bytes is valid");
unsafe { HostAllocator.deallocate(ptr, 0, 64) };
}
#[test]
fn invalid_alignment_is_refused_with_a_reason() {
let error = HostAllocator
.allocate(64, 3)
.expect_err("alignment must be a power of two");
assert!(error.to_string().contains("power of two"), "{error}");
}
#[test]
fn live_host_allocations_are_distinct_and_writable() {
let first = HostAllocator.allocate(256, 64).expect("first");
let second = HostAllocator.allocate(256, 64).expect("second");
unsafe {
std::ptr::write_bytes(first.as_ptr(), 0x11, 256);
std::ptr::write_bytes(second.as_ptr(), 0x22, 256);
for offset in 0..256 {
assert_eq!(*first.as_ptr().add(offset), 0x11);
assert_eq!(*second.as_ptr().add(offset), 0x22);
}
HostAllocator.deallocate(first, 256, 64);
HostAllocator.deallocate(second, 256, 64);
}
}
#[test]
fn device_keys_distinguish_host_and_accelerators() {
assert_eq!(HostAllocator.device(), DeviceKey::HOST);
assert_ne!(DeviceKey::device(0), DeviceKey::device(1));
assert_eq!(DeviceKey::device(1).tier, Tier::Device);
}
}