use crate::runtime::memory_management::MemoryHandle;
use alloc::sync::Arc;
use core::sync::atomic::AtomicUsize;
#[cfg(target_has_atomic = "64")]
use core::sync::atomic::{AtomicU64, Ordering};
#[cfg(not(target_has_atomic = "64"))]
use spin::Mutex;
#[derive(Debug)]
pub struct ManagedMemoryHandle {
descriptor: Arc<ManagedMemoryDescriptor>,
handle_count: Arc<()>,
}
#[derive(Debug)]
pub struct ManagedMemoryBinding {
descriptor: Arc<ManagedMemoryDescriptor>,
}
impl Clone for ManagedMemoryHandle {
fn clone(&self) -> Self {
Self {
descriptor: self.descriptor.clone(),
handle_count: self.handle_count.clone(),
}
}
}
pub(crate) struct ManagedMemoryDescriptor {
pub(crate) id: ManagedMemoryId,
pins: AtomicUsize,
#[cfg(target_has_atomic = "64")]
location: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
location: Mutex<MemoryLocation>,
}
impl core::fmt::Debug for ManagedMemoryDescriptor {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ManagedMemoryDescriptor")
.field("id", &self.id)
.field("location", &self.location())
.finish()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct ManagedMemoryId {
pub(crate) value: usize,
}
impl PartialEq for ManagedMemoryDescriptor {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for ManagedMemoryDescriptor {}
#[derive(Clone, Copy, Debug)]
pub(crate) struct MemoryLocation {
pub pool: u8,
pub page: u16,
pub slice: u32,
pub init: u8,
}
impl ManagedMemoryDescriptor {
pub(crate) fn update_location(&self, location: MemoryLocation) {
#[cfg(target_has_atomic = "64")]
{
self.location.store(location.to_bits(), Ordering::Release);
}
#[cfg(not(target_has_atomic = "64"))]
{
*self.location.lock() = location;
}
}
pub(crate) fn update_slice(&self, slice: u32) {
self.modify(|location| MemoryLocation { slice, ..location });
}
pub fn update_page(&self, page: u16) {
self.modify(|location| MemoryLocation { page, ..location });
}
pub(crate) fn location(&self) -> MemoryLocation {
#[cfg(target_has_atomic = "64")]
{
MemoryLocation::from_bits(self.location.load(Ordering::Acquire))
}
#[cfg(not(target_has_atomic = "64"))]
{
*self.location.lock()
}
}
pub(crate) fn slice(&self) -> usize {
self.location().slice as usize
}
pub(crate) fn page(&self) -> usize {
self.location().page as usize
}
fn modify(&self, update: impl Fn(MemoryLocation) -> MemoryLocation) {
#[cfg(target_has_atomic = "64")]
{
let _ = self.location.fetch_update(Ordering::AcqRel, Ordering::Acquire, |bits| {
Some(update(MemoryLocation::from_bits(bits)).to_bits())
});
}
#[cfg(not(target_has_atomic = "64"))]
{
let mut location = self.location.lock();
*location = update(*location);
}
}
}
impl MemoryLocation {
#[cfg(target_has_atomic = "64")]
fn to_bits(self) -> u64 {
self.pool as u64
| (self.page as u64) << 8
| (self.slice as u64) << 24
| (self.init as u64) << 56
}
#[cfg(target_has_atomic = "64")]
fn from_bits(bits: u64) -> Self {
Self {
pool: bits as u8,
page: (bits >> 8) as u16,
slice: (bits >> 24) as u32,
init: (bits >> 56) as u8,
}
}
pub(crate) fn new(pool: u8, page: u16, slice: u32) -> Self {
Self {
pool,
page,
slice,
init: 1,
}
}
pub(crate) fn uninit() -> Self {
Self {
pool: 0,
page: 0,
slice: 0,
init: 0,
}
}
}
impl ManagedMemoryHandle {
pub fn new() -> Self {
let value = Self::gen_id();
Self {
descriptor: Arc::new(ManagedMemoryDescriptor {
id: ManagedMemoryId { value },
pins: AtomicUsize::new(0),
#[cfg(target_has_atomic = "64")]
location: AtomicU64::new(MemoryLocation::uninit().to_bits()),
#[cfg(not(target_has_atomic = "64"))]
location: Mutex::new(MemoryLocation::uninit()),
}),
handle_count: Arc::new(()),
}
}
pub(crate) fn descriptor(&self) -> &ManagedMemoryDescriptor {
&self.descriptor
}
pub fn can_mut(&self) -> bool {
Arc::strong_count(&self.handle_count) <= 2
}
pub fn is_free(&self) -> bool {
Arc::strong_count(&self.descriptor) <= 1
}
pub(crate) fn is_pinned(&self) -> bool {
self.descriptor.pins.load(core::sync::atomic::Ordering::Acquire) != 0
}
pub fn binding(self) -> ManagedMemoryBinding {
ManagedMemoryBinding {
descriptor: self.descriptor.clone(),
}
}
fn gen_id() -> usize {
static COUNTER: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
let value = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if value == usize::MAX {
core::panic!("Memory ID overflowed");
}
value
}
}
impl ManagedMemoryBinding {
pub fn pin(&self) -> MemoryResourcePin {
self.descriptor.pins.fetch_add(1, core::sync::atomic::Ordering::AcqRel);
MemoryResourcePin { binding: self.clone() }
}
pub fn id(&self) -> ManagedMemoryId { self.descriptor.id }
pub(crate) fn descriptor(&self) -> &ManagedMemoryDescriptor {
&self.descriptor
}
}
#[derive(Debug)]
pub struct MemoryResourcePin {
binding: ManagedMemoryBinding,
}
impl Clone for MemoryResourcePin {
fn clone(&self) -> Self { self.binding.pin() }
}
impl Drop for MemoryResourcePin {
fn drop(&mut self) {
self.binding.descriptor.pins.fetch_sub(1, core::sync::atomic::Ordering::AcqRel);
}
}
impl Default for ManagedMemoryHandle {
fn default() -> Self {
Self::new()
}
}
impl Clone for ManagedMemoryBinding {
fn clone(&self) -> Self {
Self {
descriptor: self.descriptor.clone(),
}
}
}
impl MemoryHandle<ManagedMemoryBinding> for ManagedMemoryHandle {
fn can_mut(&self) -> bool {
self.can_mut()
}
fn binding(self) -> ManagedMemoryBinding {
self.binding()
}
}
pub fn optimal_align(shape: usize, elem_size: usize, buffer_align: usize) -> usize {
if shape == 1 {
elem_size
} else {
(shape * elem_size)
.next_power_of_two()
.clamp(16, buffer_align)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_id_mutability() {
let handle1 = ManagedMemoryHandle::new();
handle1.descriptor().update_slice(4);
assert_eq!(handle1.descriptor().slice(), 4);
let handle2 = ManagedMemoryHandle::new();
handle2
.clone()
.descriptor()
.update_location(handle1.descriptor().location());
assert_eq!(handle2.descriptor().slice(), 4);
}
#[test]
fn test_location_visible_through_shared_arc() {
let handle = ManagedMemoryHandle::new();
let handle2 = handle.clone();
let location = MemoryLocation::new(1, 2, 3);
handle.descriptor().update_location(location);
assert_eq!(handle2.descriptor().location().pool, 1);
assert_eq!(handle2.descriptor().location().page, 2);
assert_eq!(handle2.descriptor().location().slice, 3);
assert_eq!(handle2.descriptor().location().init, 1);
handle.descriptor().update_slice(42);
assert_eq!(handle2.descriptor().slice(), 42);
}
#[test]
#[cfg(feature = "runtime-std")]
fn concurrent_debug_reads_consistent_location_snapshots() {
let handle = ManagedMemoryHandle::new();
let writer = handle.clone();
let task = std::thread::spawn(move || {
for i in 1..=128_u32 {
writer.descriptor().update_location(MemoryLocation::new(i as u8, i as u16, i));
std::thread::yield_now();
}
});
for _ in 0..128 {
let _ = alloc::format!("{handle:?}");
let location = handle.descriptor().location();
assert_eq!(location.page as u32, location.slice);
assert_eq!(location.pool as u32, location.slice);
std::thread::yield_now();
}
task.join().unwrap();
assert_eq!(handle.descriptor().slice(), 128);
}
#[test]
#[cfg(feature = "runtime-std")]
fn concurrent_field_updates_do_not_overwrite_each_other() {
let handle = ManagedMemoryHandle::new();
let first = handle.clone();
let second = handle.clone();
let page_writer = std::thread::spawn(move || {
for page in 1..=128 { first.descriptor().update_page(page); }
});
let slice_writer = std::thread::spawn(move || {
for slice in 1..=128 { second.descriptor().update_slice(slice); }
});
page_writer.join().unwrap();
slice_writer.join().unwrap();
assert_eq!(handle.descriptor().page(), 128);
assert_eq!(handle.descriptor().slice(), 128);
}
}