use ruda_core::bytes::{AllocationController, AllocationProperty};
use ruda_kernel::dsl::server::IoError;
use ruda::runtime::{
memory_management::{ManagedMemoryBinding, MemoryManagement},
storage::{BytesReadGuard, BytesStorage},
};
enum ReadbackBacking {
Shared(BytesReadGuard),
Owned(Vec<u8>),
}
pub struct CpuAllocController {
backing: ReadbackBacking,
_binding: ManagedMemoryBinding,
}
impl AllocationController for CpuAllocController {
fn memory_with_policy(&self, _: ruda_core::bytes::AccessPolicy) -> Result<&[std::mem::MaybeUninit<u8>], ruda_core::bytes::AccessError> {
Ok(self.memory())
}
unsafe fn memory_mut_with_policy(&mut self, policy: ruda_core::bytes::AccessPolicy) -> Result<&mut [std::mem::MaybeUninit<u8>], ruda_core::bytes::AccessError> {
if !policy.copy_allowed() && matches!(&self.backing, ReadbackBacking::Shared(_)) {
return Err(ruda_core::bytes::AccessError::WouldCopy);
}
Ok(unsafe { self.memory_mut() })
}
fn alloc_align(&self) -> usize { align_of::<u8>() }
fn property(&self) -> AllocationProperty { AllocationProperty::Other }
unsafe fn memory_mut(&mut self) -> &mut [std::mem::MaybeUninit<u8>] {
if let ReadbackBacking::Shared(resource) = &self.backing {
let copy = resource.to_vec();
self.backing = ReadbackBacking::Owned(copy);
}
let ReadbackBacking::Owned(bytes) = &mut self.backing else { unreachable!() };
unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr().cast(), bytes.len()) }
}
fn memory(&self) -> &[std::mem::MaybeUninit<u8>] {
let bytes: &[u8] = match &self.backing {
ReadbackBacking::Shared(resource) => resource,
ReadbackBacking::Owned(bytes) => bytes,
};
unsafe { std::slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len()) }
}
}
impl CpuAllocController {
pub fn init(
binding: ruda_kernel::dsl::server::Binding,
memory_management: &mut MemoryManagement<BytesStorage>,
) -> Result<Self, IoError> {
let memory = binding.memory.clone();
let resource = memory_management.get_resource(
binding.memory, binding.offset_start, binding.offset_end,
)?;
let resource = resource.try_read().map_err(|err| IoError::Unknown {
description: err.to_string(),
backtrace: ruda_core::backtrace::BackTrace::capture(),
})?;
Ok(Self { _binding: memory, backing: ReadbackBacking::Shared(resource) })
}
}
#[cfg(test)]
mod tests {
use super::*;
use ruda::runtime::{memory_management::ManagedMemoryHandle, storage::ComputeStorage};
use ruda_core::bytes::Bytes;
#[test]
fn overlapping_readbacks_share_reads_but_mutation_copies() {
let mut storage = BytesStorage::default();
let handle = storage.alloc(4).unwrap();
let resource = storage.get(&handle);
resource.write().copy_from_slice(&[1, 2, 3, 4]);
let controller = || CpuAllocController {
backing: ReadbackBacking::Shared(resource.read()),
_binding: ManagedMemoryHandle::new().binding(),
};
let mut first = unsafe { Bytes::from_controller(Box::new(controller()), 4) };
let second = unsafe { Bytes::from_controller(Box::new(controller()), 4) };
storage.dealloc(handle.id);
assert_eq!(first.read(ruda_core::bytes::Reader::new().no_copy()).unwrap(), [1, 2, 3, 4]);
assert!(matches!(first.write(ruda_core::bytes::Writer::new().no_copy()), Err(ruda_core::bytes::AccessError::WouldCopy)));
first[0] = 99;
assert!(first.write(ruda_core::bytes::Writer::new().no_copy()).is_ok());
assert_eq!(&first[..], &[99, 2, 3, 4]);
assert_eq!(&second[..], &[1, 2, 3, 4]);
assert_eq!(&resource.read()[..], &[1, 2, 3, 4]);
assert!(resource.try_write().is_err());
drop(second);
assert!(resource.try_write().is_ok());
}
}