Skip to main content

cu29_runtime/
pool.rs

1use arrayvec::ArrayString;
2use bincode::de::Decoder;
3use bincode::enc::Encoder;
4use bincode::error::{DecodeError, EncodeError};
5use bincode::{Decode, Encode};
6use cu29_traits::CuResult;
7use hashbrown::HashMap;
8use object_pool::{Pool, ReusableOwned};
9use serde::de::{self, MapAccess, SeqAccess, Visitor};
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use smallvec::SmallVec;
12use std::alloc::{Layout, alloc, dealloc};
13use std::cell::Cell;
14#[cfg(feature = "remote-debug")]
15use std::cell::RefCell;
16use std::cell::UnsafeCell;
17use std::fmt::Debug;
18use std::fs::OpenOptions;
19use std::marker::PhantomData;
20use std::mem::{align_of, size_of};
21use std::ops::{Deref, DerefMut};
22use std::path::{Path, PathBuf};
23use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
24use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
25
26use memmap2::{MmapMut, MmapOptions};
27use tempfile::NamedTempFile;
28
29type PoolID = ArrayString<64>;
30
31/// Trait for a Pool to exposed to be monitored by the monitoring API.
32pub trait PoolMonitor: Send + Sync {
33    /// A unique and descriptive identifier for the pool.
34    fn id(&self) -> PoolID;
35
36    /// Number of buffer slots left in the pool.
37    fn space_left(&self) -> usize;
38
39    /// Total size of the pool in number of buffers.
40    fn total_size(&self) -> usize;
41
42    /// Size of one buffer
43    fn buffer_size(&self) -> usize;
44}
45
46static POOL_REGISTRY: OnceLock<Mutex<HashMap<String, Arc<dyn PoolMonitor>>>> = OnceLock::new();
47const MAX_POOLS: usize = 16;
48
49fn lock_unpoison<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
50    match mutex.lock() {
51        Ok(guard) => guard,
52        Err(poison) => poison.into_inner(),
53    }
54}
55
56// Register a pool to the global registry.
57fn register_pool(pool: Arc<dyn PoolMonitor>) {
58    POOL_REGISTRY
59        .get_or_init(|| Mutex::new(HashMap::new()))
60        .lock()
61        .unwrap_or_else(|poison| poison.into_inner())
62        .insert(pool.id().to_string(), pool);
63}
64
65type PoolStats = (PoolID, usize, usize, usize);
66
67/// Get the list of pools and their statistics.
68/// We use SmallVec here to avoid heap allocations while the stack is running.
69pub fn pools_statistics() -> SmallVec<[PoolStats; MAX_POOLS]> {
70    // Safely get the registry, returning empty stats if not initialized.
71    let registry_lock = match POOL_REGISTRY.get() {
72        Some(lock) => lock_unpoison(lock),
73        None => return SmallVec::new(), // Return empty if registry is not initialized
74    };
75    let mut result = SmallVec::with_capacity(MAX_POOLS);
76    for pool in registry_lock.values() {
77        result.push((
78            pool.id(),
79            pool.space_left(),
80            pool.total_size(),
81            pool.buffer_size(),
82        ));
83    }
84    result
85}
86
87/// Basic Type that can be used in a buffer in a CuPool.
88pub trait ElementType: Default + Sized + Copy + Debug + Unpin + Send + Sync {
89    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError>;
90    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError>;
91}
92
93/// Blanket implementation for all types that are Sized, Copy, Encode, Decode and Debug.
94impl<T> ElementType for T
95where
96    T: Default + Sized + Copy + Debug + Unpin + Send + Sync,
97    T: Encode,
98    T: Decode<()>,
99{
100    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
101        self.encode(encoder)
102    }
103
104    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
105        Self::decode(decoder)
106    }
107}
108
109pub trait ArrayLike: Deref<Target = [Self::Element]> + DerefMut + Debug + Sync + Send {
110    type Element: ElementType;
111}
112
113thread_local! {
114    static SHARED_HANDLE_SERIALIZATION_ENABLED: Cell<bool> = const { Cell::new(false) };
115}
116
117pub struct SharedHandleSerializationGuard {
118    previous: bool,
119}
120
121impl Drop for SharedHandleSerializationGuard {
122    fn drop(&mut self) {
123        SHARED_HANDLE_SERIALIZATION_ENABLED.with(|enabled| enabled.set(self.previous));
124    }
125}
126
127pub fn enable_shared_handle_serialization() -> SharedHandleSerializationGuard {
128    let previous = SHARED_HANDLE_SERIALIZATION_ENABLED.with(|enabled| {
129        let previous = enabled.get();
130        enabled.set(true);
131        previous
132    });
133    SharedHandleSerializationGuard { previous }
134}
135
136fn shared_handle_serialization_enabled() -> bool {
137    SHARED_HANDLE_SERIALIZATION_ENABLED.with(Cell::get)
138}
139
140#[cfg(feature = "remote-debug")]
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
142#[serde(rename_all = "snake_case")]
143pub enum DebugHandleEncoding {
144    RawLittleEndian,
145    Cbor,
146}
147
148#[cfg(feature = "remote-debug")]
149#[derive(Clone, Debug)]
150pub(crate) struct CollectedDebugHandleAttachment {
151    pub id: u32,
152    pub encoding: DebugHandleEncoding,
153    pub element_type: Option<CuSharedMemoryElementType>,
154    pub len_elements: Option<usize>,
155    pub data: Vec<u8>,
156}
157
158#[cfg(feature = "remote-debug")]
159#[derive(Serialize)]
160struct DebugHandleDescriptor {
161    #[serde(rename = "__cu_handle__")]
162    marker: bool,
163    attachment_id: Option<u32>,
164    encoding: DebugHandleEncoding,
165    element_type: Option<CuSharedMemoryElementType>,
166    len_elements: Option<usize>,
167    byte_len: Option<usize>,
168}
169
170#[cfg(feature = "remote-debug")]
171#[derive(Default)]
172struct DebugHandleSerializationState {
173    include_contents: bool,
174    attachments: Vec<CollectedDebugHandleAttachment>,
175}
176
177#[cfg(feature = "remote-debug")]
178thread_local! {
179    static DEBUG_HANDLE_SERIALIZATION_STATE: RefCell<Option<DebugHandleSerializationState>> =
180        const { RefCell::new(None) };
181}
182
183/// Runs debugger payload serialization with handle contents deferred by default.
184///
185/// This function and all state it uses are compiled only for `remote-debug`. It is
186/// deliberately independent from Copper's `Encode` path so unified logging and task
187/// execution never consult debugger policy.
188#[cfg(feature = "remote-debug")]
189pub(crate) fn collect_debug_handle_attachments<R>(
190    f: impl FnOnce() -> R,
191) -> (R, Vec<CollectedDebugHandleAttachment>) {
192    let previous = DEBUG_HANDLE_SERIALIZATION_STATE
193        .with(|state| state.replace(Some(DebugHandleSerializationState::default())));
194    let result = f();
195    let collected = DEBUG_HANDLE_SERIALIZATION_STATE.with(|state| {
196        let current = state.replace(previous);
197        current
198            .map(|current| current.attachments)
199            .unwrap_or_default()
200    });
201    (result, collected)
202}
203
204#[cfg(feature = "remote-debug")]
205pub(crate) fn with_debug_handle_contents<R>(include_contents: bool, f: impl FnOnce() -> R) -> R {
206    let previous = DEBUG_HANDLE_SERIALIZATION_STATE.with(|state| {
207        let mut state = state.borrow_mut();
208        state.as_mut().map(|state| {
209            let previous = state.include_contents;
210            state.include_contents = include_contents;
211            previous
212        })
213    });
214    let result = f();
215    if let Some(previous) = previous {
216        DEBUG_HANDLE_SERIALIZATION_STATE.with(|state| {
217            if let Some(state) = state.borrow_mut().as_mut() {
218                state.include_contents = previous;
219            }
220        });
221    }
222    result
223}
224
225#[cfg(feature = "remote-debug")]
226fn primitive_slice_bytes<U: ElementType + 'static>(
227    values: &[U],
228    element_type: CuSharedMemoryElementType,
229) -> Vec<u8> {
230    macro_rules! cast_slice {
231        ($ty:ty) => {{
232            // SAFETY: `element_type` was obtained from `TypeId::of::<U>()`, so
233            // `U` and the selected primitive are the same type.
234            unsafe { core::slice::from_raw_parts(values.as_ptr().cast::<$ty>(), values.len()) }
235        }};
236    }
237
238    match element_type {
239        CuSharedMemoryElementType::U8 => cast_slice!(u8).to_vec(),
240        CuSharedMemoryElementType::I8 => cast_slice!(i8).iter().map(|value| *value as u8).collect(),
241        CuSharedMemoryElementType::U16 => cast_slice!(u16)
242            .iter()
243            .flat_map(|value| value.to_le_bytes())
244            .collect(),
245        CuSharedMemoryElementType::U32 => cast_slice!(u32)
246            .iter()
247            .flat_map(|value| value.to_le_bytes())
248            .collect(),
249        CuSharedMemoryElementType::U64 => cast_slice!(u64)
250            .iter()
251            .flat_map(|value| value.to_le_bytes())
252            .collect(),
253        CuSharedMemoryElementType::I16 => cast_slice!(i16)
254            .iter()
255            .flat_map(|value| value.to_le_bytes())
256            .collect(),
257        CuSharedMemoryElementType::I32 => cast_slice!(i32)
258            .iter()
259            .flat_map(|value| value.to_le_bytes())
260            .collect(),
261        CuSharedMemoryElementType::I64 => cast_slice!(i64)
262            .iter()
263            .flat_map(|value| value.to_le_bytes())
264            .collect(),
265        CuSharedMemoryElementType::F32 => cast_slice!(f32)
266            .iter()
267            .flat_map(|value| value.to_le_bytes())
268            .collect(),
269        CuSharedMemoryElementType::F64 => cast_slice!(f64)
270            .iter()
271            .flat_map(|value| value.to_le_bytes())
272            .collect(),
273    }
274}
275
276#[cfg(feature = "remote-debug")]
277fn debug_handle_descriptor<U>(values: &[U]) -> Result<Option<DebugHandleDescriptor>, String>
278where
279    U: ElementType + Serialize + 'static,
280{
281    DEBUG_HANDLE_SERIALIZATION_STATE.with(|state| {
282        let mut state = state.borrow_mut();
283        let Some(state) = state.as_mut() else {
284            return Ok(None);
285        };
286
287        let element_type = CuSharedMemoryElementType::of::<U>();
288        let encoding = if element_type.is_some() {
289            DebugHandleEncoding::RawLittleEndian
290        } else {
291            DebugHandleEncoding::Cbor
292        };
293        let mut byte_len = None;
294        let attachment_id = if state.include_contents {
295            let data = match element_type {
296                Some(element_type) => primitive_slice_bytes(values, element_type),
297                None => minicbor_serde::to_vec(values)
298                    .map_err(|err| format!("failed to encode debug handle contents: {err}"))?,
299            };
300            byte_len = Some(data.len());
301            let id = state.attachments.len() as u32;
302            state.attachments.push(CollectedDebugHandleAttachment {
303                id,
304                encoding,
305                element_type,
306                len_elements: Some(values.len()),
307                data,
308            });
309            Some(id)
310        } else {
311            None
312        };
313
314        Ok(Some(DebugHandleDescriptor {
315            marker: true,
316            attachment_id,
317            encoding,
318            element_type,
319            len_elements: Some(values.len()),
320            byte_len,
321        }))
322    })
323}
324
325#[cfg(feature = "remote-debug")]
326fn debug_handle_value_descriptor<T>(value: &T) -> Result<Option<DebugHandleDescriptor>, String>
327where
328    T: Serialize,
329{
330    DEBUG_HANDLE_SERIALIZATION_STATE.with(|state| {
331        let mut state = state.borrow_mut();
332        let Some(state) = state.as_mut() else {
333            return Ok(None);
334        };
335
336        let mut byte_len = None;
337        let attachment_id = if state.include_contents {
338            let data = minicbor_serde::to_vec(value)
339                .map_err(|err| format!("failed to encode debug handle contents: {err}"))?;
340            byte_len = Some(data.len());
341            let id = state.attachments.len() as u32;
342            state.attachments.push(CollectedDebugHandleAttachment {
343                id,
344                encoding: DebugHandleEncoding::Cbor,
345                element_type: None,
346                len_elements: None,
347                data,
348            });
349            Some(id)
350        } else {
351            None
352        };
353
354        Ok(Some(DebugHandleDescriptor {
355            marker: true,
356            attachment_id,
357            encoding: DebugHandleEncoding::Cbor,
358            element_type: None,
359            len_elements: None,
360            byte_len,
361        }))
362    })
363}
364
365#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
366#[serde(rename_all = "snake_case")]
367pub enum CuSharedMemoryElementType {
368    U8,
369    U16,
370    U32,
371    U64,
372    I8,
373    I16,
374    I32,
375    I64,
376    F32,
377    F64,
378}
379
380impl CuSharedMemoryElementType {
381    pub fn of<E: ElementType + 'static>() -> Option<Self> {
382        let type_id = core::any::TypeId::of::<E>();
383        if type_id == core::any::TypeId::of::<u8>() {
384            Some(Self::U8)
385        } else if type_id == core::any::TypeId::of::<u16>() {
386            Some(Self::U16)
387        } else if type_id == core::any::TypeId::of::<u32>() {
388            Some(Self::U32)
389        } else if type_id == core::any::TypeId::of::<u64>() {
390            Some(Self::U64)
391        } else if type_id == core::any::TypeId::of::<i8>() {
392            Some(Self::I8)
393        } else if type_id == core::any::TypeId::of::<i16>() {
394            Some(Self::I16)
395        } else if type_id == core::any::TypeId::of::<i32>() {
396            Some(Self::I32)
397        } else if type_id == core::any::TypeId::of::<i64>() {
398            Some(Self::I64)
399        } else if type_id == core::any::TypeId::of::<f32>() {
400            Some(Self::F32)
401        } else if type_id == core::any::TypeId::of::<f64>() {
402            Some(Self::F64)
403        } else {
404            None
405        }
406    }
407}
408
409#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
410pub struct CuSharedMemoryHandleDescriptor {
411    #[serde(rename = "__cu_shm_handle__")]
412    pub marker: bool,
413    pub path: String,
414    pub offset_bytes: usize,
415    pub len_elements: usize,
416    pub element_type: CuSharedMemoryElementType,
417}
418
419impl CuSharedMemoryHandleDescriptor {
420    fn new(
421        path: String,
422        offset_bytes: usize,
423        len_elements: usize,
424        element_type: CuSharedMemoryElementType,
425    ) -> Self {
426        Self {
427            marker: true,
428            path,
429            offset_bytes,
430            len_elements,
431            element_type,
432        }
433    }
434}
435
436struct CuSharedMemoryRegion {
437    path: PathBuf,
438    mmap: UnsafeCell<MmapMut>,
439    _backing_file: Option<NamedTempFile>,
440}
441
442impl CuSharedMemoryRegion {
443    fn create(byte_len: usize) -> CuResult<Arc<Self>> {
444        let file = NamedTempFile::new()
445            .map_err(|e| cu29_traits::CuError::new_with_cause("create shared memory file", e))?;
446        file.as_file()
447            .set_len(byte_len as u64)
448            .map_err(|e| cu29_traits::CuError::new_with_cause("size shared memory file", e))?;
449        let mmap = unsafe {
450            MmapOptions::new()
451                .len(byte_len)
452                .map_mut(file.as_file())
453                .map_err(|e| cu29_traits::CuError::new_with_cause("map shared memory file", e))?
454        };
455        let region = Arc::new(Self {
456            path: file.path().to_path_buf(),
457            mmap: UnsafeCell::new(mmap),
458            _backing_file: Some(file),
459        });
460        cache_shared_region(region.clone());
461        Ok(region)
462    }
463
464    fn open(path: &Path) -> CuResult<Arc<Self>> {
465        if let Some(region) = cached_shared_region(path) {
466            return Ok(region);
467        }
468
469        let file = OpenOptions::new()
470            .read(true)
471            .write(true)
472            .open(path)
473            .map_err(|e| cu29_traits::CuError::new_with_cause("open shared memory file", e))?;
474        let len = file
475            .metadata()
476            .map_err(|e| cu29_traits::CuError::new_with_cause("stat shared memory file", e))?
477            .len() as usize;
478        let mmap = unsafe {
479            MmapOptions::new()
480                .len(len)
481                .map_mut(&file)
482                .map_err(|e| cu29_traits::CuError::new_with_cause("map shared memory file", e))?
483        };
484        let region = Arc::new(Self {
485            path: path.to_path_buf(),
486            mmap: UnsafeCell::new(mmap),
487            _backing_file: None,
488        });
489        cache_shared_region(region.clone());
490        Ok(region)
491    }
492}
493
494impl Debug for CuSharedMemoryRegion {
495    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496        f.debug_struct("CuSharedMemoryRegion")
497            .field("path", &self.path)
498            .finish_non_exhaustive()
499    }
500}
501
502// SAFETY:
503// Access to the mapped bytes is mediated through Copper handles and pool slot
504// leasing, so cross-thread aliasing follows the same external synchronization as
505// other mutable payload buffers.
506unsafe impl Send for CuSharedMemoryRegion {}
507// SAFETY:
508// See `Send` rationale above.
509unsafe impl Sync for CuSharedMemoryRegion {}
510
511fn shared_region_cache() -> &'static Mutex<HashMap<PathBuf, std::sync::Weak<CuSharedMemoryRegion>>>
512{
513    static CACHE: OnceLock<Mutex<HashMap<PathBuf, std::sync::Weak<CuSharedMemoryRegion>>>> =
514        OnceLock::new();
515    CACHE.get_or_init(|| Mutex::new(HashMap::new()))
516}
517
518fn cache_shared_region(region: Arc<CuSharedMemoryRegion>) {
519    lock_unpoison(shared_region_cache()).insert(region.path.clone(), Arc::downgrade(&region));
520}
521
522fn cached_shared_region(path: &Path) -> Option<Arc<CuSharedMemoryRegion>> {
523    lock_unpoison(shared_region_cache())
524        .get(path)
525        .and_then(std::sync::Weak::upgrade)
526}
527
528fn shared_slot_stride<E: ElementType>(len_elements: usize) -> usize {
529    let raw_bytes = len_elements
530        .checked_mul(size_of::<E>())
531        .expect("shared memory slot size overflow");
532    let alignment = align_of::<E>().max(1);
533    raw_bytes.div_ceil(alignment) * alignment
534}
535
536#[derive(Debug)]
537pub struct CuSharedMemoryBuffer<E: ElementType> {
538    region: Arc<CuSharedMemoryRegion>,
539    offset_bytes: usize,
540    len_elements: usize,
541    _marker: PhantomData<E>,
542}
543
544impl<E: ElementType + 'static> CuSharedMemoryBuffer<E> {
545    fn from_region(
546        region: Arc<CuSharedMemoryRegion>,
547        offset_bytes: usize,
548        len_elements: usize,
549    ) -> Self {
550        Self {
551            region,
552            offset_bytes,
553            len_elements,
554            _marker: PhantomData,
555        }
556    }
557
558    pub fn from_vec_detached(data: Vec<E>) -> CuResult<Self> {
559        let len_elements = data.len();
560        let slot_stride = shared_slot_stride::<E>(len_elements.max(1));
561        let region = CuSharedMemoryRegion::create(slot_stride)?;
562        let mut buffer = Self::from_region(region, 0, len_elements);
563        if !data.is_empty() {
564            buffer.copy_from_slice(&data);
565        }
566        Ok(buffer)
567    }
568
569    pub fn from_descriptor(descriptor: &CuSharedMemoryHandleDescriptor) -> CuResult<Self> {
570        let expected = CuSharedMemoryElementType::of::<E>()
571            .ok_or_else(|| cu29_traits::CuError::from("unsupported shared memory element type"))?;
572        if descriptor.element_type != expected {
573            return Err(cu29_traits::CuError::from(
574                "shared memory descriptor element type mismatch",
575            ));
576        }
577        let region = CuSharedMemoryRegion::open(Path::new(&descriptor.path))?;
578        Ok(Self::from_region(
579            region,
580            descriptor.offset_bytes,
581            descriptor.len_elements,
582        ))
583    }
584
585    pub fn descriptor(&self) -> Option<CuSharedMemoryHandleDescriptor>
586    where
587        E: 'static,
588    {
589        CuSharedMemoryElementType::of::<E>().map(|element_type| {
590            CuSharedMemoryHandleDescriptor::new(
591                self.region.path.display().to_string(),
592                self.offset_bytes,
593                self.len_elements,
594                element_type,
595            )
596        })
597    }
598}
599
600impl<E: ElementType> Deref for CuSharedMemoryBuffer<E> {
601    type Target = [E];
602
603    fn deref(&self) -> &Self::Target {
604        let ptr = unsafe { (*self.region.mmap.get()).as_ptr().add(self.offset_bytes) as *const E };
605        unsafe { std::slice::from_raw_parts(ptr, self.len_elements) }
606    }
607}
608
609impl<E: ElementType> DerefMut for CuSharedMemoryBuffer<E> {
610    fn deref_mut(&mut self) -> &mut Self::Target {
611        let ptr = unsafe {
612            (*self.region.mmap.get())
613                .as_mut_ptr()
614                .add(self.offset_bytes) as *mut E
615        };
616        unsafe { std::slice::from_raw_parts_mut(ptr, self.len_elements) }
617    }
618}
619
620impl<E: ElementType> ArrayLike for CuSharedMemoryBuffer<E> {
621    type Element = E;
622}
623
624impl<E: ElementType> Encode for CuSharedMemoryBuffer<E> {
625    fn encode<Enc: Encoder>(&self, encoder: &mut Enc) -> Result<(), EncodeError> {
626        let len = self.len_elements as u64;
627        Encode::encode(&len, encoder)?;
628        for value in self.deref() {
629            value.encode(encoder)?;
630        }
631        Ok(())
632    }
633}
634
635impl<E: ElementType + 'static> Decode<()> for CuSharedMemoryBuffer<E> {
636    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
637        let len = <u64 as Decode<()>>::decode(decoder)? as usize;
638        let mut vec = Vec::with_capacity(len);
639        for _ in 0..len {
640            vec.push(E::decode(decoder)?);
641        }
642        Self::from_vec_detached(vec).map_err(|e| DecodeError::OtherString(e.to_string()))
643    }
644}
645
646/// A handle to a pooled or detached object.
647///
648/// For onboard usages, large payloads should typically be pooled. The detached form exists for
649/// offline/deserialization flows and for payloads that are intentionally heap-backed instead of
650/// pool-backed.
651pub enum CuHandleInner<T: Debug + Send + Sync> {
652    Pooled(ReusableOwned<Box<T>>),
653    Detached(Box<T>), // Should only be used in offline cases (e.g. deserialization)
654}
655
656impl<T> Debug for CuHandleInner<T>
657where
658    T: Debug + Send + Sync,
659{
660    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
661        match self {
662            CuHandleInner::Pooled(r) => {
663                write!(f, "Pooled: {:?}", r.deref().deref())
664            }
665            CuHandleInner::Detached(r) => write!(f, "Detached: {r:?}"),
666        }
667    }
668}
669
670impl<T> CuHandleInner<T>
671where
672    T: Debug + Send + Sync,
673{
674    fn inner_ref(&self) -> &T {
675        match self {
676            CuHandleInner::Pooled(pooled) => pooled.deref().as_ref(),
677            CuHandleInner::Detached(detached) => detached.deref(),
678        }
679    }
680
681    fn inner_mut(&mut self) -> &mut T {
682        match self {
683            CuHandleInner::Pooled(pooled) => pooled.deref_mut().as_mut(),
684            CuHandleInner::Detached(detached) => detached.deref_mut(),
685        }
686    }
687}
688
689impl<T> AsRef<T> for CuHandleInner<T>
690where
691    T: Debug + Send + Sync,
692{
693    fn as_ref(&self) -> &T {
694        self.inner_ref()
695    }
696}
697
698impl<T> AsMut<T> for CuHandleInner<T>
699where
700    T: Debug + Send + Sync,
701{
702    fn as_mut(&mut self) -> &mut T {
703        self.inner_mut()
704    }
705}
706
707impl<T: ArrayLike> Deref for CuHandleInner<T> {
708    type Target = [T::Element];
709
710    fn deref(&self) -> &Self::Target {
711        self.inner_ref().deref()
712    }
713}
714
715impl<T: ArrayLike> DerefMut for CuHandleInner<T> {
716    fn deref_mut(&mut self) -> &mut Self::Target {
717        self.inner_mut().deref_mut()
718    }
719}
720
721// Re-export the configuration policy here for pool consumers that don't otherwise
722// reach for the config module.
723pub use crate::config::HandleContent;
724
725/// Backing storage for a [`CuHandle`]: the payload mutex plus the per-handle touched flag
726/// and logging mode. Shared across handle clones via [`Arc`].
727///
728/// `mode` is stored as an [`AtomicU8`] so the runtime can override it once after the
729/// source's `process()` returns (when the configured `handle_content` policy is known
730/// but the source itself didn't construct the handle with that policy in mind).
731#[derive(Debug)]
732struct CuHandleCell<T: Debug + Send + Sync> {
733    touched: AtomicBool,
734    mode: AtomicU8,
735    inner: Mutex<CuHandleInner<T>>,
736}
737
738/// A shareable handle to a pooled or detached object.
739///
740/// When `T: ArrayLike`, the handle also participates in Copper's buffer pool APIs.
741#[derive(Debug)]
742#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
743#[cfg_attr(
744    feature = "reflect",
745    reflect(opaque, from_reflect = false, type_path = false)
746)]
747pub struct CuHandle<T: Debug + Send + Sync>(Arc<CuHandleCell<T>>);
748
749#[cfg(feature = "reflect")]
750impl<T: Debug + Send + Sync + 'static> bevy_reflect::TypePath for CuHandle<T> {
751    fn type_path() -> &'static str {
752        core::any::type_name::<Self>()
753    }
754    fn short_type_path() -> &'static str {
755        Self::type_path()
756    }
757}
758
759impl<T: Debug + Send + Sync + bincode::ValueDecode> bincode::ValueDecode for CuHandle<T> {
760    const DECODE: &'static bincode::ValueDecodeSpec = T::DECODE;
761}
762
763impl<T: Debug + Send + Sync> Clone for CuHandle<T> {
764    fn clone(&self) -> Self {
765        Self(self.0.clone())
766    }
767}
768
769impl<T: Debug + Send + Sync> Deref for CuHandle<T> {
770    type Target = Mutex<CuHandleInner<T>>;
771
772    fn deref(&self) -> &Self::Target {
773        &self.0.inner
774    }
775}
776
777impl<T: Debug + Send + Sync> CuHandle<T> {
778    /// Wrap a raw [`CuHandleInner`] into a fresh handle with the given logging mode.
779    fn from_inner(inner: CuHandleInner<T>, mode: HandleContent) -> Self {
780        CuHandle(Arc::new(CuHandleCell {
781            touched: AtomicBool::new(false),
782            mode: AtomicU8::new(mode as u8),
783            inner: Mutex::new(inner),
784        }))
785    }
786
787    /// Create a new CuHandle not part of a Pool (not for onboard usages, use pools instead)
788    pub fn new_detached(inner: T) -> Self {
789        Self::new_detached_box(Box::new(inner))
790    }
791
792    /// Create a detached handle from an already heap-allocated object.
793    pub fn new_detached_box(inner: Box<T>) -> Self {
794        Self::from_inner(CuHandleInner::Detached(inner), HandleContent::default())
795    }
796
797    /// Create a detached handle with a non-default logging mode.
798    ///
799    /// Mostly useful for tests and for sources that want to mint detached handles
800    /// (instead of pool-acquired ones) under a specific [`HandleContent`] policy.
801    pub fn new_detached_with_mode(inner: T, mode: HandleContent) -> Self {
802        Self::from_inner(CuHandleInner::Detached(Box::new(inner)), mode)
803    }
804
805    /// Safely access the inner value, applying a closure to it.
806    pub fn with_inner<R>(&self, f: impl FnOnce(&CuHandleInner<T>) -> R) -> R {
807        let lock = lock_unpoison(&self.0.inner);
808        f(&*lock)
809    }
810
811    /// Mutably access the inner value, applying a closure to it.
812    pub fn with_inner_mut<R>(&self, f: impl FnOnce(&mut CuHandleInner<T>) -> R) -> R {
813        let mut lock = lock_unpoison(&self.0.inner);
814        f(&mut *lock)
815    }
816
817    /// Serialize a handle-backed value while allowing the remote debugger to replace
818    /// the value with a deferred descriptor.
819    ///
820    /// Outside a `remote-debug` build this compiles down to normal inner-value
821    /// serialization. Handle-backed payload wrappers should use this instead of
822    /// locking and serializing the inner value themselves.
823    pub fn serialize_value<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
824    where
825        T: Serialize,
826        S: Serializer,
827    {
828        let inner = lock_unpoison(&self.0.inner);
829        let value = inner.inner_ref();
830
831        #[cfg(feature = "remote-debug")]
832        if let Some(descriptor) =
833            debug_handle_value_descriptor(value).map_err(<S::Error as serde::ser::Error>::custom)?
834        {
835            return descriptor.serialize(serializer);
836        }
837
838        value.serialize(serializer)
839    }
840
841    /// Returns the number of handles sharing this payload storage.
842    pub fn strong_count(&self) -> usize {
843        Arc::strong_count(&self.0)
844    }
845
846    /// Returns true when this is the only handle to this payload storage.
847    pub fn is_unique(&self) -> bool {
848        self.strong_count() == 1
849    }
850
851    /// Returns a process-local identifier for the shared storage behind this handle.
852    ///
853    /// Clones of the same handle return the same identifier. This is intended for
854    /// diagnostics such as proving that a task graph forwarded a pooled allocation
855    /// without replacing it with a copy; it is not a device or host memory address.
856    pub fn storage_id(&self) -> usize {
857        Arc::as_ptr(&self.0) as usize
858    }
859
860    /// Mark this handle as read by a downstream consumer.
861    ///
862    /// When the source is configured with [`HandleContent::TouchedOnly`], the unified-log
863    /// encoder will only write the payload bytes if at least one consumer called this.
864    /// Cheap (one relaxed atomic store); safe to call multiple times.
865    pub fn mark_touched(&self) {
866        self.0.touched.store(true, Ordering::Relaxed);
867    }
868
869    /// Returns true if [`mark_touched`](Self::mark_touched) has ever been called on this
870    /// handle (or any clone of it).
871    pub fn was_touched(&self) -> bool {
872        self.0.touched.load(Ordering::Relaxed)
873    }
874
875    /// Logging mode currently in effect for this handle.
876    pub fn logging_mode(&self) -> HandleContent {
877        HandleContent::from_u8(self.0.mode.load(Ordering::Relaxed))
878    }
879
880    /// Convenience: [`with_inner`](Self::with_inner) plus [`mark_touched`](Self::mark_touched)
881    /// in one call, for the common consumer-side access pattern.
882    pub fn with_touched_inner<R>(&self, f: impl FnOnce(&CuHandleInner<T>) -> R) -> R {
883        self.mark_touched();
884        self.with_inner(f)
885    }
886
887    /// Decides whether the unified-log encoder should write this handle's payload bytes
888    /// for the current frame.
889    ///
890    /// This is the inherent "specific" arm of the autoref-specialization pattern; the
891    /// encoder resolves to this method for handle payloads (or composite payloads that
892    /// forward to it) and to [`PayloadDefaultLoggingPolicy::payload_should_log`] for
893    /// every other payload type.
894    pub fn payload_should_log(&self) -> bool {
895        match self.logging_mode() {
896            HandleContent::All => true,
897            HandleContent::None => false,
898            HandleContent::TouchedOnly => self.was_touched(),
899        }
900    }
901
902    /// Apply a source's configured [`HandleContent`] policy to this handle. Inherent
903    /// "specific" arm of the autoref-specialization pattern; visible to every clone.
904    /// `Relaxed` is sufficient — synchronization piggy-backs on the copperlist handoff.
905    pub fn apply_handle_content_policy(&self, mode: HandleContent) {
906        self.0.mode.store(mode as u8, Ordering::Relaxed);
907    }
908}
909
910/// Opt-in marker required when `NodeLogging.handle_content` is non-default. Codegen
911/// emits a compile-time bound check against this trait, so an unmarked payload
912/// surfaces as a clear compile error instead of a silent runtime no-op.
913///
914/// ```ignore
915/// impl cu29::pool::HandleContentAware for MyPayload {}
916/// ```
917///
918/// The impl is the gate; for the policy to actually fire, the payload must also
919/// forward [`apply_handle_content_policy`](CuHandle::apply_handle_content_policy)
920/// and [`payload_should_log`](CuHandle::payload_should_log) to an inner [`CuHandle`]
921/// (see `CuImage`). [`CuHandle`] itself satisfies both halves.
922pub trait HandleContentAware {}
923
924impl<T: Debug + Send + Sync> HandleContentAware for CuHandle<T> {}
925
926/// Default arm of the autoref-specialization pattern used by the unified-log encoder.
927///
928/// Blanket-impl'd for every type, so any payload that doesn't define its own inherent
929/// `payload_should_log` method inherits this default-true implementation. Types like
930/// [`CuHandle`] (and composite payloads wrapping one) provide an inherent method with
931/// the same name, which wins method resolution because inherent methods are tried
932/// before trait methods at the same candidate self-type.
933pub trait PayloadDefaultLoggingPolicy {
934    fn payload_should_log(&self) -> bool {
935        true
936    }
937}
938
939impl<T: ?Sized> PayloadDefaultLoggingPolicy for T {}
940
941/// Default arm of the autoref-specialization pattern used by the runtime to push a
942/// source's configured [`HandleContent`] policy into the [`CuHandle`]s that live inside
943/// a payload. Default is a no-op; [`CuHandle`] and composite payloads provide inherent
944/// overrides that propagate the mode.
945pub trait PayloadDefaultHandlePolicyApply {
946    fn apply_handle_content_policy(&self, _mode: HandleContent) {}
947}
948
949impl<T: ?Sized> PayloadDefaultHandlePolicyApply for T {}
950
951impl<U> Serialize for CuHandle<Vec<U>>
952where
953    U: ElementType + Serialize + 'static,
954{
955    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
956        let inner = lock_unpoison(&self.0.inner);
957        let values = inner.inner_ref();
958
959        #[cfg(feature = "remote-debug")]
960        if let Some(descriptor) = debug_handle_descriptor(values.as_slice())
961            .map_err(<S::Error as serde::ser::Error>::custom)?
962        {
963            return descriptor.serialize(serializer);
964        }
965
966        values.serialize(serializer)
967    }
968}
969
970impl<'de, U> Deserialize<'de> for CuHandle<Vec<U>>
971where
972    U: ElementType + Deserialize<'de> + 'static,
973{
974    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
975        Vec::<U>::deserialize(deserializer).map(CuHandle::new_detached)
976    }
977}
978
979impl<U> Serialize for CuHandle<CuSharedMemoryBuffer<U>>
980where
981    U: ElementType + Serialize + 'static,
982{
983    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
984        let inner = lock_unpoison(&self.0.inner);
985        let buffer = inner.inner_ref();
986
987        #[cfg(feature = "remote-debug")]
988        if let Some(descriptor) = debug_handle_descriptor(buffer.deref())
989            .map_err(<S::Error as serde::ser::Error>::custom)?
990        {
991            return descriptor.serialize(serializer);
992        }
993
994        if shared_handle_serialization_enabled()
995            && let Some(descriptor) = buffer.descriptor()
996        {
997            return descriptor.serialize(serializer);
998        }
999
1000        buffer.deref().serialize(serializer)
1001    }
1002}
1003
1004impl<'de, U> Deserialize<'de> for CuHandle<CuSharedMemoryBuffer<U>>
1005where
1006    U: ElementType + Deserialize<'de> + 'static,
1007{
1008    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1009        enum Repr<U> {
1010            Descriptor(CuSharedMemoryHandleDescriptor),
1011            Data(Vec<U>),
1012        }
1013
1014        impl<'de, U> Deserialize<'de> for Repr<U>
1015        where
1016            U: ElementType + Deserialize<'de>,
1017        {
1018            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1019                struct ReprVisitor<U>(PhantomData<U>);
1020
1021                impl<'de, U> Visitor<'de> for ReprVisitor<U>
1022                where
1023                    U: ElementType + Deserialize<'de>,
1024                {
1025                    type Value = Repr<U>;
1026
1027                    fn expecting(
1028                        &self,
1029                        formatter: &mut std::fmt::Formatter<'_>,
1030                    ) -> std::fmt::Result {
1031                        formatter
1032                            .write_str("a shared-memory handle descriptor or an element sequence")
1033                    }
1034
1035                    fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
1036                        let data =
1037                            Vec::<U>::deserialize(de::value::SeqAccessDeserializer::new(seq))?;
1038                        Ok(Repr::Data(data))
1039                    }
1040
1041                    fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
1042                        let descriptor = CuSharedMemoryHandleDescriptor::deserialize(
1043                            de::value::MapAccessDeserializer::new(map),
1044                        )?;
1045                        Ok(Repr::Descriptor(descriptor))
1046                    }
1047                }
1048
1049                deserializer.deserialize_any(ReprVisitor(PhantomData))
1050            }
1051        }
1052
1053        match Repr::<U>::deserialize(deserializer)? {
1054            Repr::Descriptor(descriptor) => CuSharedMemoryBuffer::from_descriptor(&descriptor)
1055                .map(CuHandle::new_detached)
1056                .map_err(de::Error::custom),
1057            Repr::Data(data) => CuSharedMemoryBuffer::from_vec_detached(data)
1058                .map(CuHandle::new_detached)
1059                .map_err(de::Error::custom),
1060        }
1061    }
1062}
1063
1064impl<T: ArrayLike + Encode> Encode for CuHandle<T>
1065where
1066    <T as ArrayLike>::Element: 'static,
1067{
1068    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1069        let inner = lock_unpoison(&self.0.inner);
1070        crate::monitoring::record_payload_handle_bytes(
1071            inner.inner_ref().len() * size_of::<T::Element>(),
1072        );
1073        inner.inner_ref().encode(encoder)
1074    }
1075}
1076
1077impl<T: Debug + Send + Sync> Default for CuHandle<T> {
1078    fn default() -> Self {
1079        panic!("Cannot create a default CuHandle")
1080    }
1081}
1082
1083impl<U: ElementType + Decode<()> + 'static> Decode<()> for CuHandle<Vec<U>> {
1084    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1085        let vec: Vec<U> = Vec::decode(decoder)?;
1086        Ok(CuHandle::new_detached(vec))
1087    }
1088}
1089
1090impl<U: ElementType + Decode<()> + 'static> Decode<()> for CuHandle<CuSharedMemoryBuffer<U>> {
1091    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1092        let buffer = CuSharedMemoryBuffer::<U>::decode(decoder)?;
1093        Ok(CuHandle::new_detached(buffer))
1094    }
1095}
1096
1097/// A CuPool is a pool of buffers that can be shared between different parts of the code.
1098/// Handles can be stored locally in the tasks and shared between them.
1099pub trait CuPool<T: ArrayLike>: PoolMonitor {
1100    /// Acquire a buffer from the pool.
1101    fn acquire(&self) -> Option<CuHandle<T>>;
1102
1103    /// Copy data from a handle to a new handle from the pool.
1104    fn copy_from<O>(&self, from: &mut CuHandle<O>) -> CuHandle<T>
1105    where
1106        O: ArrayLike<Element = T::Element>;
1107}
1108
1109/// A device memory pool can copy data from a device to a host memory pool on top.
1110pub trait DeviceCuPool<T: ArrayLike>: CuPool<T> {
1111    /// Takes a handle to a device buffer and copies it into a host buffer pool.
1112    /// It returns a new handle from the host pool with the data from the device handle given.
1113    fn copy_to_host_pool<O>(
1114        &self,
1115        from_device_handle: &CuHandle<T>,
1116        to_host_handle: &mut CuHandle<O>,
1117    ) -> CuResult<()>
1118    where
1119        O: ArrayLike<Element = T::Element>;
1120}
1121
1122/// A pool of host memory buffers.
1123pub struct CuHostMemoryPool<T> {
1124    /// Underlying pool of host buffers.
1125    // Being an Arc is a requirement of try_pull_owned() so buffers can refer back to the pool.
1126    id: PoolID,
1127    pool: Arc<Pool<Box<T>>>,
1128    size: usize,
1129    buffer_size: usize,
1130}
1131
1132impl<T: ArrayLike + 'static> CuHostMemoryPool<T> {
1133    pub fn new<F>(id: &str, size: usize, buffer_initializer: F) -> CuResult<Arc<Self>>
1134    where
1135        F: Fn() -> T,
1136    {
1137        let pool = Arc::new(Pool::new(size, move || Box::new(buffer_initializer())));
1138        let buffer_size = pool.try_pull().unwrap().len() * size_of::<T::Element>();
1139
1140        let og = Self {
1141            id: PoolID::from(id).map_err(|_| "Failed to create PoolID")?,
1142            pool,
1143            size,
1144            buffer_size,
1145        };
1146        let og = Arc::new(og);
1147        register_pool(og.clone());
1148        Ok(og)
1149    }
1150}
1151
1152impl<T: ArrayLike> PoolMonitor for CuHostMemoryPool<T> {
1153    fn id(&self) -> PoolID {
1154        self.id
1155    }
1156
1157    fn space_left(&self) -> usize {
1158        self.pool.len()
1159    }
1160
1161    fn total_size(&self) -> usize {
1162        self.size
1163    }
1164
1165    fn buffer_size(&self) -> usize {
1166        self.buffer_size
1167    }
1168}
1169
1170impl<T: ArrayLike> CuPool<T> for CuHostMemoryPool<T> {
1171    fn acquire(&self) -> Option<CuHandle<T>> {
1172        let owned_object = self.pool.try_pull_owned(); // Use the owned version
1173
1174        owned_object.map(|reusable| {
1175            CuHandle::from_inner(CuHandleInner::Pooled(reusable), HandleContent::default())
1176        })
1177    }
1178
1179    fn copy_from<O: ArrayLike<Element = T::Element>>(&self, from: &mut CuHandle<O>) -> CuHandle<T> {
1180        let to_handle = self.acquire().expect("No available buffers in the pool");
1181        {
1182            let from_lock = lock_unpoison(&from.0.inner);
1183            let mut to_lock = lock_unpoison(&to_handle.0.inner);
1184            to_lock.inner_mut().copy_from_slice(from_lock.inner_ref());
1185        }
1186        to_handle
1187    }
1188}
1189
1190/// A pool of fixed-size shared-memory buffers that can be leased to a child
1191/// process without copying the underlying bytes.
1192pub struct CuSharedMemoryPool<E: ElementType> {
1193    id: PoolID,
1194    pool: Arc<Pool<Box<CuSharedMemoryBuffer<E>>>>,
1195    size: usize,
1196    buffer_size: usize,
1197}
1198
1199impl<E: ElementType + 'static> CuSharedMemoryPool<E> {
1200    pub fn new(id: &str, size: usize, elements_per_buffer: usize) -> CuResult<Arc<Self>> {
1201        let slot_stride = shared_slot_stride::<E>(elements_per_buffer.max(1));
1202        let region = CuSharedMemoryRegion::create(
1203            slot_stride
1204                .checked_mul(size)
1205                .ok_or_else(|| cu29_traits::CuError::from("shared memory pool size overflow"))?,
1206        )?;
1207        let next_slot = Arc::new(std::sync::atomic::AtomicUsize::new(0));
1208        let initializer_region = region.clone();
1209        let initializer_next_slot = next_slot.clone();
1210        let pool = Arc::new(Pool::new(size, move || {
1211            let slot = initializer_next_slot.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1212            assert!(slot < size, "shared memory pool slot index overflow");
1213            Box::new(CuSharedMemoryBuffer::from_region(
1214                initializer_region.clone(),
1215                slot * slot_stride,
1216                elements_per_buffer,
1217            ))
1218        }));
1219
1220        let pool = Arc::new(Self {
1221            id: PoolID::from(id).map_err(|_| "Failed to create PoolID")?,
1222            pool,
1223            size,
1224            buffer_size: elements_per_buffer * size_of::<E>(),
1225        });
1226        register_pool(pool.clone());
1227        Ok(pool)
1228    }
1229}
1230
1231impl<E: ElementType> PoolMonitor for CuSharedMemoryPool<E> {
1232    fn id(&self) -> PoolID {
1233        self.id
1234    }
1235
1236    fn space_left(&self) -> usize {
1237        self.pool.len()
1238    }
1239
1240    fn total_size(&self) -> usize {
1241        self.size
1242    }
1243
1244    fn buffer_size(&self) -> usize {
1245        self.buffer_size
1246    }
1247}
1248
1249impl<E: ElementType> CuPool<CuSharedMemoryBuffer<E>> for CuSharedMemoryPool<E> {
1250    fn acquire(&self) -> Option<CuHandle<CuSharedMemoryBuffer<E>>> {
1251        self.pool.try_pull_owned().map(|reusable| {
1252            CuHandle::from_inner(CuHandleInner::Pooled(reusable), HandleContent::default())
1253        })
1254    }
1255
1256    fn copy_from<O>(&self, from: &mut CuHandle<O>) -> CuHandle<CuSharedMemoryBuffer<E>>
1257    where
1258        O: ArrayLike<Element = E>,
1259    {
1260        let to_handle = self.acquire().expect("No available buffers in the pool");
1261        {
1262            let from_lock = lock_unpoison(&from.0.inner);
1263            let mut to_lock = lock_unpoison(&to_handle.0.inner);
1264            to_lock.inner_mut().copy_from_slice(from_lock.inner_ref());
1265        }
1266        to_handle
1267    }
1268}
1269
1270impl<E: ElementType + 'static> ArrayLike for Vec<E> {
1271    type Element = E;
1272}
1273
1274#[cfg(all(feature = "cuda", not(target_os = "macos")))]
1275pub mod cuda {
1276    use super::*;
1277    use cu29_traits::CuError;
1278    use cudarc::driver::{
1279        CudaContext, CudaSlice, CudaStream, DeviceRepr, HostSlice, SyncOnDrop, ValidAsZeroBits,
1280    };
1281    use std::sync::Arc;
1282
1283    #[derive(Debug)]
1284    pub struct CudaSliceWrapper<E>(CudaSlice<E>);
1285
1286    impl<E> Deref for CudaSliceWrapper<E>
1287    where
1288        E: ElementType,
1289    {
1290        type Target = [E];
1291
1292        fn deref(&self) -> &Self::Target {
1293            // Implement logic to return a slice
1294            panic!("You need to copy data to host memory pool before accessing it.");
1295        }
1296    }
1297
1298    impl<E> DerefMut for CudaSliceWrapper<E>
1299    where
1300        E: ElementType,
1301    {
1302        fn deref_mut(&mut self) -> &mut Self::Target {
1303            panic!("You need to copy data to host memory pool before accessing it.");
1304        }
1305    }
1306
1307    impl<E: ElementType> ArrayLike for CudaSliceWrapper<E> {
1308        type Element = E;
1309    }
1310
1311    impl<E> CudaSliceWrapper<E> {
1312        /// Number of elements in the device allocation.
1313        pub fn len(&self) -> usize {
1314            self.0.len()
1315        }
1316
1317        /// Whether the device allocation is empty.
1318        pub fn is_empty(&self) -> bool {
1319            self.0.is_empty()
1320        }
1321
1322        /// Size of the device allocation in bytes.
1323        pub fn num_bytes(&self) -> usize {
1324            self.0.num_bytes()
1325        }
1326
1327        pub fn as_cuda_slice(&self) -> &CudaSlice<E> {
1328            &self.0
1329        }
1330
1331        pub fn as_cuda_slice_mut(&mut self) -> &mut CudaSlice<E> {
1332            &mut self.0
1333        }
1334    }
1335
1336    // Create a wrapper type to bridge between ArrayLike and HostSlice
1337    pub struct HostSliceWrapper<'a, T: ArrayLike> {
1338        inner: &'a T,
1339    }
1340
1341    impl<T: ArrayLike> HostSlice<T::Element> for HostSliceWrapper<'_, T> {
1342        fn len(&self) -> usize {
1343            self.inner.len()
1344        }
1345
1346        // SAFETY: HostSlice requires the returned slice to remain valid for 'b.
1347        unsafe fn stream_synced_slice<'b>(
1348            &'b self,
1349            stream: &'b CudaStream,
1350        ) -> (&'b [T::Element], SyncOnDrop<'b>) {
1351            (self.inner.deref(), SyncOnDrop::sync_stream(stream))
1352        }
1353
1354        // SAFETY: This wrapper cannot provide mutable access; callers must not rely on this.
1355        unsafe fn stream_synced_mut_slice<'b>(
1356            &'b mut self,
1357            _stream: &'b CudaStream,
1358        ) -> (&'b mut [T::Element], SyncOnDrop<'b>) {
1359            panic!("Cannot get mutable reference from immutable wrapper")
1360        }
1361    }
1362
1363    // Mutable wrapper
1364    pub struct HostSliceMutWrapper<'a, T: ArrayLike> {
1365        inner: &'a mut T,
1366    }
1367
1368    impl<T: ArrayLike> HostSlice<T::Element> for HostSliceMutWrapper<'_, T> {
1369        fn len(&self) -> usize {
1370            self.inner.len()
1371        }
1372
1373        // SAFETY: HostSlice requires the returned slice to remain valid for 'b.
1374        unsafe fn stream_synced_slice<'b>(
1375            &'b self,
1376            stream: &'b CudaStream,
1377        ) -> (&'b [T::Element], SyncOnDrop<'b>) {
1378            (self.inner.deref(), SyncOnDrop::sync_stream(stream))
1379        }
1380
1381        // SAFETY: HostSlice requires the returned slice to remain valid for 'b.
1382        unsafe fn stream_synced_mut_slice<'b>(
1383            &'b mut self,
1384            stream: &'b CudaStream,
1385        ) -> (&'b mut [T::Element], SyncOnDrop<'b>) {
1386            (self.inner.deref_mut(), SyncOnDrop::sync_stream(stream))
1387        }
1388    }
1389
1390    // Add helper methods to the CuCudaPool implementation
1391    impl<E: ElementType + ValidAsZeroBits + DeviceRepr> CuCudaPool<E> {
1392        // Helper method to get a HostSliceWrapper from a CuHandleInner
1393        fn get_host_slice_wrapper<O: ArrayLike<Element = E>>(
1394            handle_inner: &CuHandleInner<O>,
1395        ) -> HostSliceWrapper<'_, O> {
1396            HostSliceWrapper {
1397                inner: handle_inner.inner_ref(),
1398            }
1399        }
1400
1401        // Helper method to get a HostSliceMutWrapper from a CuHandleInner
1402        fn get_host_slice_mut_wrapper<O: ArrayLike<Element = E>>(
1403            handle_inner: &mut CuHandleInner<O>,
1404        ) -> HostSliceMutWrapper<'_, O> {
1405            HostSliceMutWrapper {
1406                inner: handle_inner.inner_mut(),
1407            }
1408        }
1409    }
1410    /// A pool of CUDA memory buffers.
1411    pub struct CuCudaPool<E>
1412    where
1413        E: ElementType + ValidAsZeroBits + DeviceRepr + Unpin,
1414    {
1415        id: PoolID,
1416        stream: Arc<CudaStream>,
1417        pool: Arc<Pool<Box<CudaSliceWrapper<E>>>>,
1418        nb_buffers: usize,
1419        nb_element_per_buffer: usize,
1420    }
1421
1422    impl<E: ElementType + ValidAsZeroBits + DeviceRepr + 'static> CuCudaPool<E> {
1423        pub fn new(
1424            id: &str,
1425            ctx: Arc<CudaContext>,
1426            nb_buffers: usize,
1427            nb_element_per_buffer: usize,
1428        ) -> CuResult<Arc<Self>> {
1429            let stream = ctx.default_stream();
1430            let pool = (0..nb_buffers)
1431                .map(|_| {
1432                    stream
1433                        .alloc_zeros(nb_element_per_buffer)
1434                        .map(CudaSliceWrapper)
1435                        .map(Box::new)
1436                        .map_err(|_| "Failed to allocate device memory")
1437                })
1438                .collect::<Result<Vec<_>, _>>()?;
1439
1440            let pool = Arc::new(Self {
1441                id: PoolID::from(id).map_err(|_| "Failed to create PoolID")?,
1442                stream,
1443                pool: Arc::new(Pool::from_vec(pool)),
1444                nb_buffers,
1445                nb_element_per_buffer,
1446            });
1447            register_pool(pool.clone());
1448            Ok(pool)
1449        }
1450    }
1451
1452    impl<E> PoolMonitor for CuCudaPool<E>
1453    where
1454        E: DeviceRepr + ElementType + ValidAsZeroBits,
1455    {
1456        fn id(&self) -> PoolID {
1457            self.id
1458        }
1459
1460        fn space_left(&self) -> usize {
1461            self.pool.len()
1462        }
1463
1464        fn total_size(&self) -> usize {
1465            self.nb_buffers
1466        }
1467
1468        fn buffer_size(&self) -> usize {
1469            self.nb_element_per_buffer * size_of::<E>()
1470        }
1471    }
1472
1473    impl<E> CuPool<CudaSliceWrapper<E>> for CuCudaPool<E>
1474    where
1475        E: DeviceRepr + ElementType + ValidAsZeroBits,
1476    {
1477        fn acquire(&self) -> Option<CuHandle<CudaSliceWrapper<E>>> {
1478            self.pool
1479                .try_pull_owned()
1480                .map(|x| CuHandle::from_inner(CuHandleInner::Pooled(x), HandleContent::default()))
1481        }
1482
1483        fn copy_from<O>(&self, from_handle: &mut CuHandle<O>) -> CuHandle<CudaSliceWrapper<E>>
1484        where
1485            O: ArrayLike<Element = E>,
1486        {
1487            let to_handle = self.acquire().expect("No available buffers in the pool");
1488
1489            {
1490                let from_lock = lock_unpoison(&from_handle.0.inner);
1491                let mut to_lock = lock_unpoison(&to_handle.0.inner);
1492
1493                match &mut *to_lock {
1494                    CuHandleInner::Detached(to) => {
1495                        let wrapper = Self::get_host_slice_wrapper(&*from_lock);
1496                        self.stream
1497                            .memcpy_htod(&wrapper, to.deref_mut().as_cuda_slice_mut())
1498                            .expect("Failed to copy data to device");
1499                    }
1500                    CuHandleInner::Pooled(to) => {
1501                        let wrapper = Self::get_host_slice_wrapper(&*from_lock);
1502                        self.stream
1503                            .memcpy_htod(&wrapper, to.deref_mut().as_mut().as_cuda_slice_mut())
1504                            .expect("Failed to copy data to device");
1505                    }
1506                }
1507            } // locks are dropped here
1508            to_handle // now we can safely return to_handle
1509        }
1510    }
1511
1512    impl<E> DeviceCuPool<CudaSliceWrapper<E>> for CuCudaPool<E>
1513    where
1514        E: ElementType + ValidAsZeroBits + DeviceRepr,
1515    {
1516        /// Copy from device to host
1517        fn copy_to_host_pool<O>(
1518            &self,
1519            device_handle: &CuHandle<CudaSliceWrapper<E>>,
1520            host_handle: &mut CuHandle<O>,
1521        ) -> Result<(), CuError>
1522        where
1523            O: ArrayLike<Element = E>,
1524        {
1525            let device_lock = device_handle.lock().map_err(|e| {
1526                CuError::from("Device handle mutex poisoned").add_cause(&e.to_string())
1527            })?;
1528            let mut host_lock = host_handle.lock().map_err(|e| {
1529                CuError::from("Host handle mutex poisoned").add_cause(&e.to_string())
1530            })?;
1531            let src = match &*device_lock {
1532                CuHandleInner::Pooled(source) => source.deref().as_ref().as_cuda_slice(),
1533                CuHandleInner::Detached(source) => source.deref().as_cuda_slice(),
1534            };
1535            let mut wrapper = Self::get_host_slice_mut_wrapper(&mut *host_lock);
1536            self.stream.memcpy_dtoh(src, &mut wrapper).map_err(|e| {
1537                CuError::from("Failed to copy data from device to host").add_cause(&e.to_string())
1538            })?;
1539            Ok(())
1540        }
1541    }
1542}
1543
1544#[derive(Debug)]
1545/// A buffer that is aligned to a specific size with the Element of type E.
1546pub struct AlignedBuffer<E: ElementType> {
1547    ptr: *mut E,
1548    size: usize,
1549    layout: Layout,
1550}
1551
1552impl<E: ElementType> AlignedBuffer<E> {
1553    pub fn new(num_elements: usize, alignment: usize) -> Self {
1554        assert!(
1555            num_elements > 0 && size_of::<E>() > 0,
1556            "AlignedBuffer requires a non-zero element count and non-zero-sized element type"
1557        );
1558        let alignment = alignment.max(align_of::<E>());
1559        let alloc_size = num_elements
1560            .checked_mul(size_of::<E>())
1561            .expect("AlignedBuffer allocation size overflow");
1562        let layout = Layout::from_size_align(alloc_size, alignment).unwrap();
1563        // SAFETY: layout describes a valid, non-zero allocation request.
1564        let ptr = unsafe { alloc(layout) as *mut E };
1565        if ptr.is_null() {
1566            panic!("Failed to allocate memory");
1567        }
1568        // SAFETY: ptr is valid for writes of `num_elements` elements.
1569        unsafe {
1570            for i in 0..num_elements {
1571                std::ptr::write(ptr.add(i), E::default());
1572            }
1573        }
1574        Self {
1575            ptr,
1576            size: num_elements,
1577            layout,
1578        }
1579    }
1580}
1581
1582impl<E: ElementType> Deref for AlignedBuffer<E> {
1583    type Target = [E];
1584
1585    fn deref(&self) -> &Self::Target {
1586        // SAFETY: `new` initializes all elements and keeps the pointer aligned.
1587        unsafe { std::slice::from_raw_parts(self.ptr, self.size) }
1588    }
1589}
1590
1591impl<E: ElementType> DerefMut for AlignedBuffer<E> {
1592    fn deref_mut(&mut self) -> &mut Self::Target {
1593        // SAFETY: `new` initializes all elements and keeps the pointer aligned.
1594        unsafe { std::slice::from_raw_parts_mut(self.ptr, self.size) }
1595    }
1596}
1597
1598impl<E: ElementType> Drop for AlignedBuffer<E> {
1599    fn drop(&mut self) {
1600        // SAFETY: `ptr` was allocated with `layout` in `new`.
1601        unsafe { dealloc(self.ptr as *mut u8, self.layout) }
1602    }
1603}
1604
1605#[cfg(test)]
1606mod tests {
1607    use super::*;
1608
1609    #[cfg(feature = "self-describing-logs")]
1610    #[test]
1611    fn test_catalog_decodes_native_handle_buffer() {
1612        use cu29_value::Value;
1613        use cu29_value::decode::{ValueDecodeDescription, ValueDecodeLimits};
1614
1615        let handle = CuHandle::new_detached(vec![1u16, 300, 65535]);
1616        let config = bincode::config::standard();
1617        let bytes = bincode::encode_to_vec(&handle, config).unwrap();
1618        let description = ValueDecodeDescription::from_type::<CuHandle<Vec<u16>>>().unwrap();
1619        let (decoded, used) = description
1620            .decode(&bytes, config, ValueDecodeLimits::default())
1621            .unwrap();
1622        assert_eq!(used, bytes.len());
1623        assert_eq!(
1624            decoded,
1625            Value::Seq(vec![Value::U16(1), Value::U16(300), Value::U16(65535)])
1626        );
1627        assert!(!handle.was_touched());
1628    }
1629
1630    #[test]
1631    fn test_handle_touched_flag_defaults_false() {
1632        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1633        assert!(!h.was_touched());
1634        assert_eq!(h.logging_mode(), HandleContent::All);
1635    }
1636
1637    #[test]
1638    fn test_handle_mark_touched_propagates_across_clones() {
1639        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1640        let clone = h.clone();
1641        assert!(!h.was_touched());
1642        assert!(!clone.was_touched());
1643
1644        clone.mark_touched();
1645        // Shared Arc<CuHandleCell>: any clone sees the flag flip.
1646        assert!(h.was_touched());
1647        assert!(clone.was_touched());
1648    }
1649
1650    #[test]
1651    fn test_handle_strong_count_tracks_clones() {
1652        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1653        assert_eq!(h.strong_count(), 1);
1654        assert!(h.is_unique());
1655
1656        let clone = h.clone();
1657        assert_eq!(h.strong_count(), 2);
1658        assert_eq!(clone.strong_count(), 2);
1659        assert!(!h.is_unique());
1660        assert!(!clone.is_unique());
1661
1662        drop(clone);
1663        assert_eq!(h.strong_count(), 1);
1664        assert!(h.is_unique());
1665    }
1666
1667    #[test]
1668    fn test_handle_storage_id_tracks_shared_allocation() {
1669        let first: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1]);
1670        let clone = first.clone();
1671        let second: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1]);
1672
1673        assert_eq!(first.storage_id(), clone.storage_id());
1674        assert_ne!(first.storage_id(), second.storage_id());
1675    }
1676
1677    #[test]
1678    fn test_with_touched_inner_marks_and_reads() {
1679        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![10, 20]);
1680        let first = h.with_touched_inner(|inner| inner.as_ref()[0]);
1681        assert_eq!(first, 10);
1682        assert!(h.was_touched());
1683    }
1684
1685    #[test]
1686    fn test_with_inner_does_not_mark_touched() {
1687        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![10, 20]);
1688        let _ = h.with_inner(|inner| inner.as_ref()[0]);
1689        assert!(
1690            !h.was_touched(),
1691            "with_inner must not flip the touched flag"
1692        );
1693    }
1694
1695    #[test]
1696    fn test_payload_should_log_mode_all() {
1697        let h: CuHandle<Vec<u8>> = CuHandle::new_detached_with_mode(vec![1], HandleContent::All);
1698        assert!(h.payload_should_log());
1699        h.mark_touched();
1700        assert!(h.payload_should_log());
1701    }
1702
1703    #[test]
1704    fn test_payload_should_log_mode_none() {
1705        let h: CuHandle<Vec<u8>> = CuHandle::new_detached_with_mode(vec![1], HandleContent::None);
1706        assert!(!h.payload_should_log());
1707        h.mark_touched();
1708        assert!(
1709            !h.payload_should_log(),
1710            "HandleContent::None must never log payload, even when touched"
1711        );
1712    }
1713
1714    #[test]
1715    fn test_payload_should_log_mode_touched_only() {
1716        let h: CuHandle<Vec<u8>> =
1717            CuHandle::new_detached_with_mode(vec![1], HandleContent::TouchedOnly);
1718        assert!(!h.payload_should_log(), "untouched + TouchedOnly => skip");
1719        h.mark_touched();
1720        assert!(h.payload_should_log(), "touched + TouchedOnly => log");
1721    }
1722
1723    #[test]
1724    fn test_default_policy_returns_true_for_non_handle_types() {
1725        use crate::pool::PayloadDefaultLoggingPolicy as _;
1726        // The autoref-specialization fallback applies to any type that doesn't define
1727        // its own inherent payload_should_log. A plain integer is a fine stand-in.
1728        let v: u64 = 42;
1729        assert!(v.payload_should_log());
1730    }
1731
1732    #[test]
1733    fn test_apply_handle_content_policy_overrides_mode() {
1734        // Handle minted with the default All policy; runtime hook flips it to
1735        // TouchedOnly before downstream consumers see it. Encoder decision tracks.
1736        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1737        assert_eq!(h.logging_mode(), HandleContent::All);
1738        assert!(h.payload_should_log());
1739
1740        h.apply_handle_content_policy(HandleContent::TouchedOnly);
1741        assert_eq!(h.logging_mode(), HandleContent::TouchedOnly);
1742        assert!(
1743            !h.payload_should_log(),
1744            "after switch to TouchedOnly an untouched handle must skip"
1745        );
1746
1747        h.mark_touched();
1748        assert!(h.payload_should_log(), "touched + TouchedOnly => log");
1749    }
1750
1751    #[test]
1752    fn test_default_apply_policy_is_noop_for_non_handle_types() {
1753        use crate::pool::PayloadDefaultHandlePolicyApply as _;
1754        // No-op for any payload that doesn't wrap a handle — compile-time check that
1755        // codegen can call this on every slot without knowing the payload shape.
1756        let v: u64 = 42;
1757        v.apply_handle_content_policy(HandleContent::TouchedOnly);
1758        // No observable change; the test just proves the call compiles and returns.
1759    }
1760
1761    #[cfg(feature = "remote-debug")]
1762    #[test]
1763    fn debug_handle_contents_are_deferred_until_explicitly_included() {
1764        let handle = CuHandle::new_detached(vec![1.25_f32, -2.5_f32]);
1765
1766        let (deferred, attachments) = collect_debug_handle_attachments(|| {
1767            with_debug_handle_contents(false, || {
1768                minicbor_serde::to_vec(&handle).expect("serialize deferred handle")
1769            })
1770        });
1771        let deferred: serde_json::Value =
1772            minicbor_serde::from_slice(&deferred).expect("decode deferred descriptor");
1773        assert_eq!(
1774            deferred.get("__cu_handle__"),
1775            Some(&serde_json::json!(true))
1776        );
1777        assert_eq!(
1778            deferred.get("attachment_id"),
1779            Some(&serde_json::Value::Null)
1780        );
1781        assert!(attachments.is_empty());
1782
1783        let (included, attachments) = collect_debug_handle_attachments(|| {
1784            with_debug_handle_contents(true, || {
1785                minicbor_serde::to_vec(&handle).expect("serialize included handle")
1786            })
1787        });
1788        let included: serde_json::Value =
1789            minicbor_serde::from_slice(&included).expect("decode included descriptor");
1790        assert_eq!(included.get("attachment_id"), Some(&serde_json::json!(0)));
1791        assert_eq!(attachments.len(), 1);
1792        assert_eq!(
1793            attachments[0].encoding,
1794            DebugHandleEncoding::RawLittleEndian
1795        );
1796        assert_eq!(
1797            attachments[0].element_type,
1798            Some(CuSharedMemoryElementType::F32)
1799        );
1800        assert_eq!(
1801            attachments[0].data,
1802            [1.25_f32.to_le_bytes(), (-2.5_f32).to_le_bytes()].concat()
1803        );
1804    }
1805
1806    #[test]
1807    fn test_pool() {
1808        use std::cell::RefCell;
1809        let objs = RefCell::new(vec![vec![1], vec![2], vec![3]]);
1810        let holding = objs.borrow().clone();
1811        let objs_as_slices = holding.iter().map(|x| x.as_slice()).collect::<Vec<_>>();
1812        let pool = CuHostMemoryPool::new("mytestcudapool", 3, || objs.borrow_mut().pop().unwrap())
1813            .unwrap();
1814
1815        let obj1 = pool.acquire().unwrap();
1816        {
1817            let obj2 = pool.acquire().unwrap();
1818            assert!(objs_as_slices.contains(&obj1.lock().unwrap().deref().deref()));
1819            assert!(objs_as_slices.contains(&obj2.lock().unwrap().deref().deref()));
1820            assert_eq!(pool.space_left(), 1);
1821        }
1822        assert_eq!(pool.space_left(), 2);
1823
1824        let obj3 = pool.acquire().unwrap();
1825        assert!(objs_as_slices.contains(&obj3.lock().unwrap().deref().deref()));
1826
1827        assert_eq!(pool.space_left(), 1);
1828
1829        let _obj4 = pool.acquire().unwrap();
1830        assert_eq!(pool.space_left(), 0);
1831
1832        let obj5 = pool.acquire();
1833        assert!(obj5.is_none());
1834    }
1835
1836    #[cfg(all(feature = "cuda", has_nvidia_gpu))]
1837    #[test]
1838    fn test_cuda_pool() {
1839        use crate::pool::cuda::CuCudaPool;
1840        use cudarc::driver::CudaContext;
1841        let ctx = CudaContext::new(0).unwrap();
1842        let pool = CuCudaPool::<f32>::new("mytestcudapool", ctx, 3, 1).unwrap();
1843
1844        let _obj1 = pool.acquire().unwrap();
1845
1846        {
1847            let _obj2 = pool.acquire().unwrap();
1848            assert_eq!(pool.space_left(), 1);
1849        }
1850        assert_eq!(pool.space_left(), 2);
1851
1852        let _obj3 = pool.acquire().unwrap();
1853
1854        assert_eq!(pool.space_left(), 1);
1855
1856        let _obj4 = pool.acquire().unwrap();
1857        assert_eq!(pool.space_left(), 0);
1858
1859        let obj5 = pool.acquire();
1860        assert!(obj5.is_none());
1861    }
1862
1863    #[cfg(all(feature = "cuda", has_nvidia_gpu))]
1864    #[test]
1865    fn test_copy_roundtrip() {
1866        use crate::pool::cuda::CuCudaPool;
1867        use cudarc::driver::CudaContext;
1868        let ctx = CudaContext::new(0).unwrap();
1869        let host_pool = CuHostMemoryPool::new("mytesthostpool", 3, || vec![0.0; 1]).unwrap();
1870        let cuda_pool = CuCudaPool::<f32>::new("mytestcudapool", ctx, 3, 1).unwrap();
1871
1872        let cuda_handle = {
1873            let mut initial_handle = host_pool.acquire().unwrap();
1874            {
1875                let mut inner_initial_handle = initial_handle.lock().unwrap();
1876                if let CuHandleInner::Pooled(ref mut pooled) = *inner_initial_handle {
1877                    pooled[0] = 42.0;
1878                } else {
1879                    panic!();
1880                }
1881            }
1882
1883            // send that to the GPU
1884            cuda_pool.copy_from(&mut initial_handle)
1885        };
1886
1887        // get it back to the host
1888        let mut final_handle = host_pool.acquire().unwrap();
1889        cuda_pool
1890            .copy_to_host_pool(&cuda_handle, &mut final_handle)
1891            .unwrap();
1892
1893        let value = final_handle.lock().unwrap().deref().deref()[0];
1894        assert_eq!(value, 42.0);
1895    }
1896}