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// `HandleContent` is defined in `config.rs` so it lives in both the library and the
722// `cu29-rendercfg` bin (which includes config.rs standalone). Re-export it here for
723// pool consumers that don't otherwise reach for the config module.
724pub use crate::config::HandleContent;
725
726/// Backing storage for a [`CuHandle`]: the payload mutex plus the per-handle touched flag
727/// and logging mode. Shared across handle clones via [`Arc`].
728///
729/// `mode` is stored as an [`AtomicU8`] so the runtime can override it once after the
730/// source's `process()` returns (when the configured `handle_content` policy is known
731/// but the source itself didn't construct the handle with that policy in mind).
732#[derive(Debug)]
733struct CuHandleCell<T: Debug + Send + Sync> {
734    touched: AtomicBool,
735    mode: AtomicU8,
736    inner: Mutex<CuHandleInner<T>>,
737}
738
739/// A shareable handle to a pooled or detached object.
740///
741/// When `T: ArrayLike`, the handle also participates in Copper's buffer pool APIs.
742#[derive(Debug)]
743pub struct CuHandle<T: Debug + Send + Sync>(Arc<CuHandleCell<T>>);
744
745impl<T: Debug + Send + Sync> Clone for CuHandle<T> {
746    fn clone(&self) -> Self {
747        Self(self.0.clone())
748    }
749}
750
751impl<T: Debug + Send + Sync> Deref for CuHandle<T> {
752    type Target = Mutex<CuHandleInner<T>>;
753
754    fn deref(&self) -> &Self::Target {
755        &self.0.inner
756    }
757}
758
759impl<T: Debug + Send + Sync> CuHandle<T> {
760    /// Wrap a raw [`CuHandleInner`] into a fresh handle with the given logging mode.
761    fn from_inner(inner: CuHandleInner<T>, mode: HandleContent) -> Self {
762        CuHandle(Arc::new(CuHandleCell {
763            touched: AtomicBool::new(false),
764            mode: AtomicU8::new(mode as u8),
765            inner: Mutex::new(inner),
766        }))
767    }
768
769    /// Create a new CuHandle not part of a Pool (not for onboard usages, use pools instead)
770    pub fn new_detached(inner: T) -> Self {
771        Self::new_detached_box(Box::new(inner))
772    }
773
774    /// Create a detached handle from an already heap-allocated object.
775    pub fn new_detached_box(inner: Box<T>) -> Self {
776        Self::from_inner(CuHandleInner::Detached(inner), HandleContent::default())
777    }
778
779    /// Create a detached handle with a non-default logging mode.
780    ///
781    /// Mostly useful for tests and for sources that want to mint detached handles
782    /// (instead of pool-acquired ones) under a specific [`HandleContent`] policy.
783    pub fn new_detached_with_mode(inner: T, mode: HandleContent) -> Self {
784        Self::from_inner(CuHandleInner::Detached(Box::new(inner)), mode)
785    }
786
787    /// Safely access the inner value, applying a closure to it.
788    pub fn with_inner<R>(&self, f: impl FnOnce(&CuHandleInner<T>) -> R) -> R {
789        let lock = lock_unpoison(&self.0.inner);
790        f(&*lock)
791    }
792
793    /// Mutably access the inner value, applying a closure to it.
794    pub fn with_inner_mut<R>(&self, f: impl FnOnce(&mut CuHandleInner<T>) -> R) -> R {
795        let mut lock = lock_unpoison(&self.0.inner);
796        f(&mut *lock)
797    }
798
799    /// Serialize a handle-backed value while allowing the remote debugger to replace
800    /// the value with a deferred descriptor.
801    ///
802    /// Outside a `remote-debug` build this compiles down to normal inner-value
803    /// serialization. Handle-backed payload wrappers should use this instead of
804    /// locking and serializing the inner value themselves.
805    pub fn serialize_value<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
806    where
807        T: Serialize,
808        S: Serializer,
809    {
810        let inner = lock_unpoison(&self.0.inner);
811        let value = inner.inner_ref();
812
813        #[cfg(feature = "remote-debug")]
814        if let Some(descriptor) =
815            debug_handle_value_descriptor(value).map_err(<S::Error as serde::ser::Error>::custom)?
816        {
817            return descriptor.serialize(serializer);
818        }
819
820        value.serialize(serializer)
821    }
822
823    /// Returns the number of handles sharing this payload storage.
824    pub fn strong_count(&self) -> usize {
825        Arc::strong_count(&self.0)
826    }
827
828    /// Returns true when this is the only handle to this payload storage.
829    pub fn is_unique(&self) -> bool {
830        self.strong_count() == 1
831    }
832
833    /// Returns a process-local identifier for the shared storage behind this handle.
834    ///
835    /// Clones of the same handle return the same identifier. This is intended for
836    /// diagnostics such as proving that a task graph forwarded a pooled allocation
837    /// without replacing it with a copy; it is not a device or host memory address.
838    pub fn storage_id(&self) -> usize {
839        Arc::as_ptr(&self.0) as usize
840    }
841
842    /// Mark this handle as read by a downstream consumer.
843    ///
844    /// When the source is configured with [`HandleContent::TouchedOnly`], the unified-log
845    /// encoder will only write the payload bytes if at least one consumer called this.
846    /// Cheap (one relaxed atomic store); safe to call multiple times.
847    pub fn mark_touched(&self) {
848        self.0.touched.store(true, Ordering::Relaxed);
849    }
850
851    /// Returns true if [`mark_touched`](Self::mark_touched) has ever been called on this
852    /// handle (or any clone of it).
853    pub fn was_touched(&self) -> bool {
854        self.0.touched.load(Ordering::Relaxed)
855    }
856
857    /// Logging mode currently in effect for this handle.
858    pub fn logging_mode(&self) -> HandleContent {
859        HandleContent::from_u8(self.0.mode.load(Ordering::Relaxed))
860    }
861
862    /// Convenience: [`with_inner`](Self::with_inner) plus [`mark_touched`](Self::mark_touched)
863    /// in one call, for the common consumer-side access pattern.
864    pub fn with_touched_inner<R>(&self, f: impl FnOnce(&CuHandleInner<T>) -> R) -> R {
865        self.mark_touched();
866        self.with_inner(f)
867    }
868
869    /// Decides whether the unified-log encoder should write this handle's payload bytes
870    /// for the current frame.
871    ///
872    /// This is the inherent "specific" arm of the autoref-specialization pattern; the
873    /// encoder resolves to this method for handle payloads (or composite payloads that
874    /// forward to it) and to [`PayloadDefaultLoggingPolicy::payload_should_log`] for
875    /// every other payload type.
876    pub fn payload_should_log(&self) -> bool {
877        match self.logging_mode() {
878            HandleContent::All => true,
879            HandleContent::None => false,
880            HandleContent::TouchedOnly => self.was_touched(),
881        }
882    }
883
884    /// Apply a source's configured [`HandleContent`] policy to this handle. Inherent
885    /// "specific" arm of the autoref-specialization pattern; visible to every clone.
886    /// `Relaxed` is sufficient — synchronization piggy-backs on the copperlist handoff.
887    pub fn apply_handle_content_policy(&self, mode: HandleContent) {
888        self.0.mode.store(mode as u8, Ordering::Relaxed);
889    }
890}
891
892/// Opt-in marker required when `NodeLogging.handle_content` is non-default. Codegen
893/// emits a compile-time bound check against this trait, so an unmarked payload
894/// surfaces as a clear compile error instead of a silent runtime no-op.
895///
896/// ```ignore
897/// impl cu29::pool::HandleContentAware for MyPayload {}
898/// ```
899///
900/// The impl is the gate; for the policy to actually fire, the payload must also
901/// forward [`apply_handle_content_policy`](CuHandle::apply_handle_content_policy)
902/// and [`payload_should_log`](CuHandle::payload_should_log) to an inner [`CuHandle`]
903/// (see `CuImage`). [`CuHandle`] itself satisfies both halves.
904pub trait HandleContentAware {}
905
906impl<T: Debug + Send + Sync> HandleContentAware for CuHandle<T> {}
907
908/// Default arm of the autoref-specialization pattern used by the unified-log encoder.
909///
910/// Blanket-impl'd for every type, so any payload that doesn't define its own inherent
911/// `payload_should_log` method inherits this default-true implementation. Types like
912/// [`CuHandle`] (and composite payloads wrapping one) provide an inherent method with
913/// the same name, which wins method resolution because inherent methods are tried
914/// before trait methods at the same candidate self-type.
915pub trait PayloadDefaultLoggingPolicy {
916    fn payload_should_log(&self) -> bool {
917        true
918    }
919}
920
921impl<T: ?Sized> PayloadDefaultLoggingPolicy for T {}
922
923/// Default arm of the autoref-specialization pattern used by the runtime to push a
924/// source's configured [`HandleContent`] policy into the [`CuHandle`]s that live inside
925/// a payload. Default is a no-op; [`CuHandle`] and composite payloads provide inherent
926/// overrides that propagate the mode.
927pub trait PayloadDefaultHandlePolicyApply {
928    fn apply_handle_content_policy(&self, _mode: HandleContent) {}
929}
930
931impl<T: ?Sized> PayloadDefaultHandlePolicyApply for T {}
932
933impl<U> Serialize for CuHandle<Vec<U>>
934where
935    U: ElementType + Serialize + 'static,
936{
937    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
938        let inner = lock_unpoison(&self.0.inner);
939        let values = inner.inner_ref();
940
941        #[cfg(feature = "remote-debug")]
942        if let Some(descriptor) = debug_handle_descriptor(values.as_slice())
943            .map_err(<S::Error as serde::ser::Error>::custom)?
944        {
945            return descriptor.serialize(serializer);
946        }
947
948        values.serialize(serializer)
949    }
950}
951
952impl<'de, U> Deserialize<'de> for CuHandle<Vec<U>>
953where
954    U: ElementType + Deserialize<'de> + 'static,
955{
956    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
957        Vec::<U>::deserialize(deserializer).map(CuHandle::new_detached)
958    }
959}
960
961impl<U> Serialize for CuHandle<CuSharedMemoryBuffer<U>>
962where
963    U: ElementType + Serialize + 'static,
964{
965    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
966        let inner = lock_unpoison(&self.0.inner);
967        let buffer = inner.inner_ref();
968
969        #[cfg(feature = "remote-debug")]
970        if let Some(descriptor) = debug_handle_descriptor(buffer.deref())
971            .map_err(<S::Error as serde::ser::Error>::custom)?
972        {
973            return descriptor.serialize(serializer);
974        }
975
976        if shared_handle_serialization_enabled()
977            && let Some(descriptor) = buffer.descriptor()
978        {
979            return descriptor.serialize(serializer);
980        }
981
982        buffer.deref().serialize(serializer)
983    }
984}
985
986impl<'de, U> Deserialize<'de> for CuHandle<CuSharedMemoryBuffer<U>>
987where
988    U: ElementType + Deserialize<'de> + 'static,
989{
990    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
991        enum Repr<U> {
992            Descriptor(CuSharedMemoryHandleDescriptor),
993            Data(Vec<U>),
994        }
995
996        impl<'de, U> Deserialize<'de> for Repr<U>
997        where
998            U: ElementType + Deserialize<'de>,
999        {
1000            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1001                struct ReprVisitor<U>(PhantomData<U>);
1002
1003                impl<'de, U> Visitor<'de> for ReprVisitor<U>
1004                where
1005                    U: ElementType + Deserialize<'de>,
1006                {
1007                    type Value = Repr<U>;
1008
1009                    fn expecting(
1010                        &self,
1011                        formatter: &mut std::fmt::Formatter<'_>,
1012                    ) -> std::fmt::Result {
1013                        formatter
1014                            .write_str("a shared-memory handle descriptor or an element sequence")
1015                    }
1016
1017                    fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
1018                        let data =
1019                            Vec::<U>::deserialize(de::value::SeqAccessDeserializer::new(seq))?;
1020                        Ok(Repr::Data(data))
1021                    }
1022
1023                    fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
1024                        let descriptor = CuSharedMemoryHandleDescriptor::deserialize(
1025                            de::value::MapAccessDeserializer::new(map),
1026                        )?;
1027                        Ok(Repr::Descriptor(descriptor))
1028                    }
1029                }
1030
1031                deserializer.deserialize_any(ReprVisitor(PhantomData))
1032            }
1033        }
1034
1035        match Repr::<U>::deserialize(deserializer)? {
1036            Repr::Descriptor(descriptor) => CuSharedMemoryBuffer::from_descriptor(&descriptor)
1037                .map(CuHandle::new_detached)
1038                .map_err(de::Error::custom),
1039            Repr::Data(data) => CuSharedMemoryBuffer::from_vec_detached(data)
1040                .map(CuHandle::new_detached)
1041                .map_err(de::Error::custom),
1042        }
1043    }
1044}
1045
1046impl<T: ArrayLike + Encode> Encode for CuHandle<T>
1047where
1048    <T as ArrayLike>::Element: 'static,
1049{
1050    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
1051        let inner = lock_unpoison(&self.0.inner);
1052        crate::monitoring::record_payload_handle_bytes(
1053            inner.inner_ref().len() * size_of::<T::Element>(),
1054        );
1055        inner.inner_ref().encode(encoder)
1056    }
1057}
1058
1059impl<T: Debug + Send + Sync> Default for CuHandle<T> {
1060    fn default() -> Self {
1061        panic!("Cannot create a default CuHandle")
1062    }
1063}
1064
1065impl<U: ElementType + Decode<()> + 'static> Decode<()> for CuHandle<Vec<U>> {
1066    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1067        let vec: Vec<U> = Vec::decode(decoder)?;
1068        Ok(CuHandle::new_detached(vec))
1069    }
1070}
1071
1072impl<U: ElementType + Decode<()> + 'static> Decode<()> for CuHandle<CuSharedMemoryBuffer<U>> {
1073    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
1074        let buffer = CuSharedMemoryBuffer::<U>::decode(decoder)?;
1075        Ok(CuHandle::new_detached(buffer))
1076    }
1077}
1078
1079/// A CuPool is a pool of buffers that can be shared between different parts of the code.
1080/// Handles can be stored locally in the tasks and shared between them.
1081pub trait CuPool<T: ArrayLike>: PoolMonitor {
1082    /// Acquire a buffer from the pool.
1083    fn acquire(&self) -> Option<CuHandle<T>>;
1084
1085    /// Copy data from a handle to a new handle from the pool.
1086    fn copy_from<O>(&self, from: &mut CuHandle<O>) -> CuHandle<T>
1087    where
1088        O: ArrayLike<Element = T::Element>;
1089}
1090
1091/// A device memory pool can copy data from a device to a host memory pool on top.
1092pub trait DeviceCuPool<T: ArrayLike>: CuPool<T> {
1093    /// Takes a handle to a device buffer and copies it into a host buffer pool.
1094    /// It returns a new handle from the host pool with the data from the device handle given.
1095    fn copy_to_host_pool<O>(
1096        &self,
1097        from_device_handle: &CuHandle<T>,
1098        to_host_handle: &mut CuHandle<O>,
1099    ) -> CuResult<()>
1100    where
1101        O: ArrayLike<Element = T::Element>;
1102}
1103
1104/// A pool of host memory buffers.
1105pub struct CuHostMemoryPool<T> {
1106    /// Underlying pool of host buffers.
1107    // Being an Arc is a requirement of try_pull_owned() so buffers can refer back to the pool.
1108    id: PoolID,
1109    pool: Arc<Pool<Box<T>>>,
1110    size: usize,
1111    buffer_size: usize,
1112}
1113
1114impl<T: ArrayLike + 'static> CuHostMemoryPool<T> {
1115    pub fn new<F>(id: &str, size: usize, buffer_initializer: F) -> CuResult<Arc<Self>>
1116    where
1117        F: Fn() -> T,
1118    {
1119        let pool = Arc::new(Pool::new(size, move || Box::new(buffer_initializer())));
1120        let buffer_size = pool.try_pull().unwrap().len() * size_of::<T::Element>();
1121
1122        let og = Self {
1123            id: PoolID::from(id).map_err(|_| "Failed to create PoolID")?,
1124            pool,
1125            size,
1126            buffer_size,
1127        };
1128        let og = Arc::new(og);
1129        register_pool(og.clone());
1130        Ok(og)
1131    }
1132}
1133
1134impl<T: ArrayLike> PoolMonitor for CuHostMemoryPool<T> {
1135    fn id(&self) -> PoolID {
1136        self.id
1137    }
1138
1139    fn space_left(&self) -> usize {
1140        self.pool.len()
1141    }
1142
1143    fn total_size(&self) -> usize {
1144        self.size
1145    }
1146
1147    fn buffer_size(&self) -> usize {
1148        self.buffer_size
1149    }
1150}
1151
1152impl<T: ArrayLike> CuPool<T> for CuHostMemoryPool<T> {
1153    fn acquire(&self) -> Option<CuHandle<T>> {
1154        let owned_object = self.pool.try_pull_owned(); // Use the owned version
1155
1156        owned_object.map(|reusable| {
1157            CuHandle::from_inner(CuHandleInner::Pooled(reusable), HandleContent::default())
1158        })
1159    }
1160
1161    fn copy_from<O: ArrayLike<Element = T::Element>>(&self, from: &mut CuHandle<O>) -> CuHandle<T> {
1162        let to_handle = self.acquire().expect("No available buffers in the pool");
1163        {
1164            let from_lock = lock_unpoison(&from.0.inner);
1165            let mut to_lock = lock_unpoison(&to_handle.0.inner);
1166            to_lock.inner_mut().copy_from_slice(from_lock.inner_ref());
1167        }
1168        to_handle
1169    }
1170}
1171
1172/// A pool of fixed-size shared-memory buffers that can be leased to a child
1173/// process without copying the underlying bytes.
1174pub struct CuSharedMemoryPool<E: ElementType> {
1175    id: PoolID,
1176    pool: Arc<Pool<Box<CuSharedMemoryBuffer<E>>>>,
1177    size: usize,
1178    buffer_size: usize,
1179}
1180
1181impl<E: ElementType + 'static> CuSharedMemoryPool<E> {
1182    pub fn new(id: &str, size: usize, elements_per_buffer: usize) -> CuResult<Arc<Self>> {
1183        let slot_stride = shared_slot_stride::<E>(elements_per_buffer.max(1));
1184        let region = CuSharedMemoryRegion::create(
1185            slot_stride
1186                .checked_mul(size)
1187                .ok_or_else(|| cu29_traits::CuError::from("shared memory pool size overflow"))?,
1188        )?;
1189        let next_slot = Arc::new(std::sync::atomic::AtomicUsize::new(0));
1190        let initializer_region = region.clone();
1191        let initializer_next_slot = next_slot.clone();
1192        let pool = Arc::new(Pool::new(size, move || {
1193            let slot = initializer_next_slot.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1194            assert!(slot < size, "shared memory pool slot index overflow");
1195            Box::new(CuSharedMemoryBuffer::from_region(
1196                initializer_region.clone(),
1197                slot * slot_stride,
1198                elements_per_buffer,
1199            ))
1200        }));
1201
1202        let pool = Arc::new(Self {
1203            id: PoolID::from(id).map_err(|_| "Failed to create PoolID")?,
1204            pool,
1205            size,
1206            buffer_size: elements_per_buffer * size_of::<E>(),
1207        });
1208        register_pool(pool.clone());
1209        Ok(pool)
1210    }
1211}
1212
1213impl<E: ElementType> PoolMonitor for CuSharedMemoryPool<E> {
1214    fn id(&self) -> PoolID {
1215        self.id
1216    }
1217
1218    fn space_left(&self) -> usize {
1219        self.pool.len()
1220    }
1221
1222    fn total_size(&self) -> usize {
1223        self.size
1224    }
1225
1226    fn buffer_size(&self) -> usize {
1227        self.buffer_size
1228    }
1229}
1230
1231impl<E: ElementType> CuPool<CuSharedMemoryBuffer<E>> for CuSharedMemoryPool<E> {
1232    fn acquire(&self) -> Option<CuHandle<CuSharedMemoryBuffer<E>>> {
1233        self.pool.try_pull_owned().map(|reusable| {
1234            CuHandle::from_inner(CuHandleInner::Pooled(reusable), HandleContent::default())
1235        })
1236    }
1237
1238    fn copy_from<O>(&self, from: &mut CuHandle<O>) -> CuHandle<CuSharedMemoryBuffer<E>>
1239    where
1240        O: ArrayLike<Element = E>,
1241    {
1242        let to_handle = self.acquire().expect("No available buffers in the pool");
1243        {
1244            let from_lock = lock_unpoison(&from.0.inner);
1245            let mut to_lock = lock_unpoison(&to_handle.0.inner);
1246            to_lock.inner_mut().copy_from_slice(from_lock.inner_ref());
1247        }
1248        to_handle
1249    }
1250}
1251
1252impl<E: ElementType + 'static> ArrayLike for Vec<E> {
1253    type Element = E;
1254}
1255
1256#[cfg(all(feature = "cuda", not(target_os = "macos")))]
1257pub mod cuda {
1258    use super::*;
1259    use cu29_traits::CuError;
1260    use cudarc::driver::{
1261        CudaContext, CudaSlice, CudaStream, DeviceRepr, HostSlice, SyncOnDrop, ValidAsZeroBits,
1262    };
1263    use std::sync::Arc;
1264
1265    #[derive(Debug)]
1266    pub struct CudaSliceWrapper<E>(CudaSlice<E>);
1267
1268    impl<E> Deref for CudaSliceWrapper<E>
1269    where
1270        E: ElementType,
1271    {
1272        type Target = [E];
1273
1274        fn deref(&self) -> &Self::Target {
1275            // Implement logic to return a slice
1276            panic!("You need to copy data to host memory pool before accessing it.");
1277        }
1278    }
1279
1280    impl<E> DerefMut for CudaSliceWrapper<E>
1281    where
1282        E: ElementType,
1283    {
1284        fn deref_mut(&mut self) -> &mut Self::Target {
1285            panic!("You need to copy data to host memory pool before accessing it.");
1286        }
1287    }
1288
1289    impl<E: ElementType> ArrayLike for CudaSliceWrapper<E> {
1290        type Element = E;
1291    }
1292
1293    impl<E> CudaSliceWrapper<E> {
1294        /// Number of elements in the device allocation.
1295        pub fn len(&self) -> usize {
1296            self.0.len()
1297        }
1298
1299        /// Whether the device allocation is empty.
1300        pub fn is_empty(&self) -> bool {
1301            self.0.is_empty()
1302        }
1303
1304        /// Size of the device allocation in bytes.
1305        pub fn num_bytes(&self) -> usize {
1306            self.0.num_bytes()
1307        }
1308
1309        pub fn as_cuda_slice(&self) -> &CudaSlice<E> {
1310            &self.0
1311        }
1312
1313        pub fn as_cuda_slice_mut(&mut self) -> &mut CudaSlice<E> {
1314            &mut self.0
1315        }
1316    }
1317
1318    // Create a wrapper type to bridge between ArrayLike and HostSlice
1319    pub struct HostSliceWrapper<'a, T: ArrayLike> {
1320        inner: &'a T,
1321    }
1322
1323    impl<T: ArrayLike> HostSlice<T::Element> for HostSliceWrapper<'_, T> {
1324        fn len(&self) -> usize {
1325            self.inner.len()
1326        }
1327
1328        // SAFETY: HostSlice requires the returned slice to remain valid for 'b.
1329        unsafe fn stream_synced_slice<'b>(
1330            &'b self,
1331            stream: &'b CudaStream,
1332        ) -> (&'b [T::Element], SyncOnDrop<'b>) {
1333            (self.inner.deref(), SyncOnDrop::sync_stream(stream))
1334        }
1335
1336        // SAFETY: This wrapper cannot provide mutable access; callers must not rely on this.
1337        unsafe fn stream_synced_mut_slice<'b>(
1338            &'b mut self,
1339            _stream: &'b CudaStream,
1340        ) -> (&'b mut [T::Element], SyncOnDrop<'b>) {
1341            panic!("Cannot get mutable reference from immutable wrapper")
1342        }
1343    }
1344
1345    // Mutable wrapper
1346    pub struct HostSliceMutWrapper<'a, T: ArrayLike> {
1347        inner: &'a mut T,
1348    }
1349
1350    impl<T: ArrayLike> HostSlice<T::Element> for HostSliceMutWrapper<'_, T> {
1351        fn len(&self) -> usize {
1352            self.inner.len()
1353        }
1354
1355        // SAFETY: HostSlice requires the returned slice to remain valid for 'b.
1356        unsafe fn stream_synced_slice<'b>(
1357            &'b self,
1358            stream: &'b CudaStream,
1359        ) -> (&'b [T::Element], SyncOnDrop<'b>) {
1360            (self.inner.deref(), SyncOnDrop::sync_stream(stream))
1361        }
1362
1363        // SAFETY: HostSlice requires the returned slice to remain valid for 'b.
1364        unsafe fn stream_synced_mut_slice<'b>(
1365            &'b mut self,
1366            stream: &'b CudaStream,
1367        ) -> (&'b mut [T::Element], SyncOnDrop<'b>) {
1368            (self.inner.deref_mut(), SyncOnDrop::sync_stream(stream))
1369        }
1370    }
1371
1372    // Add helper methods to the CuCudaPool implementation
1373    impl<E: ElementType + ValidAsZeroBits + DeviceRepr> CuCudaPool<E> {
1374        // Helper method to get a HostSliceWrapper from a CuHandleInner
1375        fn get_host_slice_wrapper<O: ArrayLike<Element = E>>(
1376            handle_inner: &CuHandleInner<O>,
1377        ) -> HostSliceWrapper<'_, O> {
1378            HostSliceWrapper {
1379                inner: handle_inner.inner_ref(),
1380            }
1381        }
1382
1383        // Helper method to get a HostSliceMutWrapper from a CuHandleInner
1384        fn get_host_slice_mut_wrapper<O: ArrayLike<Element = E>>(
1385            handle_inner: &mut CuHandleInner<O>,
1386        ) -> HostSliceMutWrapper<'_, O> {
1387            HostSliceMutWrapper {
1388                inner: handle_inner.inner_mut(),
1389            }
1390        }
1391    }
1392    /// A pool of CUDA memory buffers.
1393    pub struct CuCudaPool<E>
1394    where
1395        E: ElementType + ValidAsZeroBits + DeviceRepr + Unpin,
1396    {
1397        id: PoolID,
1398        stream: Arc<CudaStream>,
1399        pool: Arc<Pool<Box<CudaSliceWrapper<E>>>>,
1400        nb_buffers: usize,
1401        nb_element_per_buffer: usize,
1402    }
1403
1404    impl<E: ElementType + ValidAsZeroBits + DeviceRepr + 'static> CuCudaPool<E> {
1405        pub fn new(
1406            id: &str,
1407            ctx: Arc<CudaContext>,
1408            nb_buffers: usize,
1409            nb_element_per_buffer: usize,
1410        ) -> CuResult<Arc<Self>> {
1411            let stream = ctx.default_stream();
1412            let pool = (0..nb_buffers)
1413                .map(|_| {
1414                    stream
1415                        .alloc_zeros(nb_element_per_buffer)
1416                        .map(CudaSliceWrapper)
1417                        .map(Box::new)
1418                        .map_err(|_| "Failed to allocate device memory")
1419                })
1420                .collect::<Result<Vec<_>, _>>()?;
1421
1422            let pool = Arc::new(Self {
1423                id: PoolID::from(id).map_err(|_| "Failed to create PoolID")?,
1424                stream,
1425                pool: Arc::new(Pool::from_vec(pool)),
1426                nb_buffers,
1427                nb_element_per_buffer,
1428            });
1429            register_pool(pool.clone());
1430            Ok(pool)
1431        }
1432    }
1433
1434    impl<E> PoolMonitor for CuCudaPool<E>
1435    where
1436        E: DeviceRepr + ElementType + ValidAsZeroBits,
1437    {
1438        fn id(&self) -> PoolID {
1439            self.id
1440        }
1441
1442        fn space_left(&self) -> usize {
1443            self.pool.len()
1444        }
1445
1446        fn total_size(&self) -> usize {
1447            self.nb_buffers
1448        }
1449
1450        fn buffer_size(&self) -> usize {
1451            self.nb_element_per_buffer * size_of::<E>()
1452        }
1453    }
1454
1455    impl<E> CuPool<CudaSliceWrapper<E>> for CuCudaPool<E>
1456    where
1457        E: DeviceRepr + ElementType + ValidAsZeroBits,
1458    {
1459        fn acquire(&self) -> Option<CuHandle<CudaSliceWrapper<E>>> {
1460            self.pool
1461                .try_pull_owned()
1462                .map(|x| CuHandle::from_inner(CuHandleInner::Pooled(x), HandleContent::default()))
1463        }
1464
1465        fn copy_from<O>(&self, from_handle: &mut CuHandle<O>) -> CuHandle<CudaSliceWrapper<E>>
1466        where
1467            O: ArrayLike<Element = E>,
1468        {
1469            let to_handle = self.acquire().expect("No available buffers in the pool");
1470
1471            {
1472                let from_lock = lock_unpoison(&from_handle.0.inner);
1473                let mut to_lock = lock_unpoison(&to_handle.0.inner);
1474
1475                match &mut *to_lock {
1476                    CuHandleInner::Detached(to) => {
1477                        let wrapper = Self::get_host_slice_wrapper(&*from_lock);
1478                        self.stream
1479                            .memcpy_htod(&wrapper, to.deref_mut().as_cuda_slice_mut())
1480                            .expect("Failed to copy data to device");
1481                    }
1482                    CuHandleInner::Pooled(to) => {
1483                        let wrapper = Self::get_host_slice_wrapper(&*from_lock);
1484                        self.stream
1485                            .memcpy_htod(&wrapper, to.deref_mut().as_mut().as_cuda_slice_mut())
1486                            .expect("Failed to copy data to device");
1487                    }
1488                }
1489            } // locks are dropped here
1490            to_handle // now we can safely return to_handle
1491        }
1492    }
1493
1494    impl<E> DeviceCuPool<CudaSliceWrapper<E>> for CuCudaPool<E>
1495    where
1496        E: ElementType + ValidAsZeroBits + DeviceRepr,
1497    {
1498        /// Copy from device to host
1499        fn copy_to_host_pool<O>(
1500            &self,
1501            device_handle: &CuHandle<CudaSliceWrapper<E>>,
1502            host_handle: &mut CuHandle<O>,
1503        ) -> Result<(), CuError>
1504        where
1505            O: ArrayLike<Element = E>,
1506        {
1507            let device_lock = device_handle.lock().map_err(|e| {
1508                CuError::from("Device handle mutex poisoned").add_cause(&e.to_string())
1509            })?;
1510            let mut host_lock = host_handle.lock().map_err(|e| {
1511                CuError::from("Host handle mutex poisoned").add_cause(&e.to_string())
1512            })?;
1513            let src = match &*device_lock {
1514                CuHandleInner::Pooled(source) => source.deref().as_ref().as_cuda_slice(),
1515                CuHandleInner::Detached(source) => source.deref().as_cuda_slice(),
1516            };
1517            let mut wrapper = Self::get_host_slice_mut_wrapper(&mut *host_lock);
1518            self.stream.memcpy_dtoh(src, &mut wrapper).map_err(|e| {
1519                CuError::from("Failed to copy data from device to host").add_cause(&e.to_string())
1520            })?;
1521            Ok(())
1522        }
1523    }
1524}
1525
1526#[derive(Debug)]
1527/// A buffer that is aligned to a specific size with the Element of type E.
1528pub struct AlignedBuffer<E: ElementType> {
1529    ptr: *mut E,
1530    size: usize,
1531    layout: Layout,
1532}
1533
1534impl<E: ElementType> AlignedBuffer<E> {
1535    pub fn new(num_elements: usize, alignment: usize) -> Self {
1536        assert!(
1537            num_elements > 0 && size_of::<E>() > 0,
1538            "AlignedBuffer requires a non-zero element count and non-zero-sized element type"
1539        );
1540        let alignment = alignment.max(align_of::<E>());
1541        let alloc_size = num_elements
1542            .checked_mul(size_of::<E>())
1543            .expect("AlignedBuffer allocation size overflow");
1544        let layout = Layout::from_size_align(alloc_size, alignment).unwrap();
1545        // SAFETY: layout describes a valid, non-zero allocation request.
1546        let ptr = unsafe { alloc(layout) as *mut E };
1547        if ptr.is_null() {
1548            panic!("Failed to allocate memory");
1549        }
1550        // SAFETY: ptr is valid for writes of `num_elements` elements.
1551        unsafe {
1552            for i in 0..num_elements {
1553                std::ptr::write(ptr.add(i), E::default());
1554            }
1555        }
1556        Self {
1557            ptr,
1558            size: num_elements,
1559            layout,
1560        }
1561    }
1562}
1563
1564impl<E: ElementType> Deref for AlignedBuffer<E> {
1565    type Target = [E];
1566
1567    fn deref(&self) -> &Self::Target {
1568        // SAFETY: `new` initializes all elements and keeps the pointer aligned.
1569        unsafe { std::slice::from_raw_parts(self.ptr, self.size) }
1570    }
1571}
1572
1573impl<E: ElementType> DerefMut for AlignedBuffer<E> {
1574    fn deref_mut(&mut self) -> &mut Self::Target {
1575        // SAFETY: `new` initializes all elements and keeps the pointer aligned.
1576        unsafe { std::slice::from_raw_parts_mut(self.ptr, self.size) }
1577    }
1578}
1579
1580impl<E: ElementType> Drop for AlignedBuffer<E> {
1581    fn drop(&mut self) {
1582        // SAFETY: `ptr` was allocated with `layout` in `new`.
1583        unsafe { dealloc(self.ptr as *mut u8, self.layout) }
1584    }
1585}
1586
1587#[cfg(test)]
1588mod tests {
1589    use super::*;
1590
1591    #[test]
1592    fn test_handle_touched_flag_defaults_false() {
1593        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1594        assert!(!h.was_touched());
1595        assert_eq!(h.logging_mode(), HandleContent::All);
1596    }
1597
1598    #[test]
1599    fn test_handle_mark_touched_propagates_across_clones() {
1600        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1601        let clone = h.clone();
1602        assert!(!h.was_touched());
1603        assert!(!clone.was_touched());
1604
1605        clone.mark_touched();
1606        // Shared Arc<CuHandleCell>: any clone sees the flag flip.
1607        assert!(h.was_touched());
1608        assert!(clone.was_touched());
1609    }
1610
1611    #[test]
1612    fn test_handle_strong_count_tracks_clones() {
1613        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1614        assert_eq!(h.strong_count(), 1);
1615        assert!(h.is_unique());
1616
1617        let clone = h.clone();
1618        assert_eq!(h.strong_count(), 2);
1619        assert_eq!(clone.strong_count(), 2);
1620        assert!(!h.is_unique());
1621        assert!(!clone.is_unique());
1622
1623        drop(clone);
1624        assert_eq!(h.strong_count(), 1);
1625        assert!(h.is_unique());
1626    }
1627
1628    #[test]
1629    fn test_handle_storage_id_tracks_shared_allocation() {
1630        let first: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1]);
1631        let clone = first.clone();
1632        let second: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1]);
1633
1634        assert_eq!(first.storage_id(), clone.storage_id());
1635        assert_ne!(first.storage_id(), second.storage_id());
1636    }
1637
1638    #[test]
1639    fn test_with_touched_inner_marks_and_reads() {
1640        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![10, 20]);
1641        let first = h.with_touched_inner(|inner| inner.as_ref()[0]);
1642        assert_eq!(first, 10);
1643        assert!(h.was_touched());
1644    }
1645
1646    #[test]
1647    fn test_with_inner_does_not_mark_touched() {
1648        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![10, 20]);
1649        let _ = h.with_inner(|inner| inner.as_ref()[0]);
1650        assert!(
1651            !h.was_touched(),
1652            "with_inner must not flip the touched flag"
1653        );
1654    }
1655
1656    #[test]
1657    fn test_payload_should_log_mode_all() {
1658        let h: CuHandle<Vec<u8>> = CuHandle::new_detached_with_mode(vec![1], HandleContent::All);
1659        assert!(h.payload_should_log());
1660        h.mark_touched();
1661        assert!(h.payload_should_log());
1662    }
1663
1664    #[test]
1665    fn test_payload_should_log_mode_none() {
1666        let h: CuHandle<Vec<u8>> = CuHandle::new_detached_with_mode(vec![1], HandleContent::None);
1667        assert!(!h.payload_should_log());
1668        h.mark_touched();
1669        assert!(
1670            !h.payload_should_log(),
1671            "HandleContent::None must never log payload, even when touched"
1672        );
1673    }
1674
1675    #[test]
1676    fn test_payload_should_log_mode_touched_only() {
1677        let h: CuHandle<Vec<u8>> =
1678            CuHandle::new_detached_with_mode(vec![1], HandleContent::TouchedOnly);
1679        assert!(!h.payload_should_log(), "untouched + TouchedOnly => skip");
1680        h.mark_touched();
1681        assert!(h.payload_should_log(), "touched + TouchedOnly => log");
1682    }
1683
1684    #[test]
1685    fn test_default_policy_returns_true_for_non_handle_types() {
1686        use crate::pool::PayloadDefaultLoggingPolicy as _;
1687        // The autoref-specialization fallback applies to any type that doesn't define
1688        // its own inherent payload_should_log. A plain integer is a fine stand-in.
1689        let v: u64 = 42;
1690        assert!(v.payload_should_log());
1691    }
1692
1693    #[test]
1694    fn test_apply_handle_content_policy_overrides_mode() {
1695        // Handle minted with the default All policy; runtime hook flips it to
1696        // TouchedOnly before downstream consumers see it. Encoder decision tracks.
1697        let h: CuHandle<Vec<u8>> = CuHandle::new_detached(vec![1, 2, 3]);
1698        assert_eq!(h.logging_mode(), HandleContent::All);
1699        assert!(h.payload_should_log());
1700
1701        h.apply_handle_content_policy(HandleContent::TouchedOnly);
1702        assert_eq!(h.logging_mode(), HandleContent::TouchedOnly);
1703        assert!(
1704            !h.payload_should_log(),
1705            "after switch to TouchedOnly an untouched handle must skip"
1706        );
1707
1708        h.mark_touched();
1709        assert!(h.payload_should_log(), "touched + TouchedOnly => log");
1710    }
1711
1712    #[test]
1713    fn test_default_apply_policy_is_noop_for_non_handle_types() {
1714        use crate::pool::PayloadDefaultHandlePolicyApply as _;
1715        // No-op for any payload that doesn't wrap a handle — compile-time check that
1716        // codegen can call this on every slot without knowing the payload shape.
1717        let v: u64 = 42;
1718        v.apply_handle_content_policy(HandleContent::TouchedOnly);
1719        // No observable change; the test just proves the call compiles and returns.
1720    }
1721
1722    #[cfg(feature = "remote-debug")]
1723    #[test]
1724    fn debug_handle_contents_are_deferred_until_explicitly_included() {
1725        let handle = CuHandle::new_detached(vec![1.25_f32, -2.5_f32]);
1726
1727        let (deferred, attachments) = collect_debug_handle_attachments(|| {
1728            with_debug_handle_contents(false, || {
1729                minicbor_serde::to_vec(&handle).expect("serialize deferred handle")
1730            })
1731        });
1732        let deferred: serde_json::Value =
1733            minicbor_serde::from_slice(&deferred).expect("decode deferred descriptor");
1734        assert_eq!(
1735            deferred.get("__cu_handle__"),
1736            Some(&serde_json::json!(true))
1737        );
1738        assert_eq!(
1739            deferred.get("attachment_id"),
1740            Some(&serde_json::Value::Null)
1741        );
1742        assert!(attachments.is_empty());
1743
1744        let (included, attachments) = collect_debug_handle_attachments(|| {
1745            with_debug_handle_contents(true, || {
1746                minicbor_serde::to_vec(&handle).expect("serialize included handle")
1747            })
1748        });
1749        let included: serde_json::Value =
1750            minicbor_serde::from_slice(&included).expect("decode included descriptor");
1751        assert_eq!(included.get("attachment_id"), Some(&serde_json::json!(0)));
1752        assert_eq!(attachments.len(), 1);
1753        assert_eq!(
1754            attachments[0].encoding,
1755            DebugHandleEncoding::RawLittleEndian
1756        );
1757        assert_eq!(
1758            attachments[0].element_type,
1759            Some(CuSharedMemoryElementType::F32)
1760        );
1761        assert_eq!(
1762            attachments[0].data,
1763            [1.25_f32.to_le_bytes(), (-2.5_f32).to_le_bytes()].concat()
1764        );
1765    }
1766
1767    #[test]
1768    fn test_pool() {
1769        use std::cell::RefCell;
1770        let objs = RefCell::new(vec![vec![1], vec![2], vec![3]]);
1771        let holding = objs.borrow().clone();
1772        let objs_as_slices = holding.iter().map(|x| x.as_slice()).collect::<Vec<_>>();
1773        let pool = CuHostMemoryPool::new("mytestcudapool", 3, || objs.borrow_mut().pop().unwrap())
1774            .unwrap();
1775
1776        let obj1 = pool.acquire().unwrap();
1777        {
1778            let obj2 = pool.acquire().unwrap();
1779            assert!(objs_as_slices.contains(&obj1.lock().unwrap().deref().deref()));
1780            assert!(objs_as_slices.contains(&obj2.lock().unwrap().deref().deref()));
1781            assert_eq!(pool.space_left(), 1);
1782        }
1783        assert_eq!(pool.space_left(), 2);
1784
1785        let obj3 = pool.acquire().unwrap();
1786        assert!(objs_as_slices.contains(&obj3.lock().unwrap().deref().deref()));
1787
1788        assert_eq!(pool.space_left(), 1);
1789
1790        let _obj4 = pool.acquire().unwrap();
1791        assert_eq!(pool.space_left(), 0);
1792
1793        let obj5 = pool.acquire();
1794        assert!(obj5.is_none());
1795    }
1796
1797    #[cfg(all(feature = "cuda", has_nvidia_gpu))]
1798    #[test]
1799    fn test_cuda_pool() {
1800        use crate::pool::cuda::CuCudaPool;
1801        use cudarc::driver::CudaContext;
1802        let ctx = CudaContext::new(0).unwrap();
1803        let pool = CuCudaPool::<f32>::new("mytestcudapool", ctx, 3, 1).unwrap();
1804
1805        let _obj1 = pool.acquire().unwrap();
1806
1807        {
1808            let _obj2 = pool.acquire().unwrap();
1809            assert_eq!(pool.space_left(), 1);
1810        }
1811        assert_eq!(pool.space_left(), 2);
1812
1813        let _obj3 = pool.acquire().unwrap();
1814
1815        assert_eq!(pool.space_left(), 1);
1816
1817        let _obj4 = pool.acquire().unwrap();
1818        assert_eq!(pool.space_left(), 0);
1819
1820        let obj5 = pool.acquire();
1821        assert!(obj5.is_none());
1822    }
1823
1824    #[cfg(all(feature = "cuda", has_nvidia_gpu))]
1825    #[test]
1826    fn test_copy_roundtrip() {
1827        use crate::pool::cuda::CuCudaPool;
1828        use cudarc::driver::CudaContext;
1829        let ctx = CudaContext::new(0).unwrap();
1830        let host_pool = CuHostMemoryPool::new("mytesthostpool", 3, || vec![0.0; 1]).unwrap();
1831        let cuda_pool = CuCudaPool::<f32>::new("mytestcudapool", ctx, 3, 1).unwrap();
1832
1833        let cuda_handle = {
1834            let mut initial_handle = host_pool.acquire().unwrap();
1835            {
1836                let mut inner_initial_handle = initial_handle.lock().unwrap();
1837                if let CuHandleInner::Pooled(ref mut pooled) = *inner_initial_handle {
1838                    pooled[0] = 42.0;
1839                } else {
1840                    panic!();
1841                }
1842            }
1843
1844            // send that to the GPU
1845            cuda_pool.copy_from(&mut initial_handle)
1846        };
1847
1848        // get it back to the host
1849        let mut final_handle = host_pool.acquire().unwrap();
1850        cuda_pool
1851            .copy_to_host_pool(&cuda_handle, &mut final_handle)
1852            .unwrap();
1853
1854        let value = final_handle.lock().unwrap().deref().deref()[0];
1855        assert_eq!(value, 42.0);
1856    }
1857}