Skip to main content

limen_core/
memory.rs

1//! Memory classes, placement policy, and token-backed message storage.
2//!
3//! Edges carry [`MessageToken`](crate::types::MessageToken) handles rather than
4//! full messages. All message data lives in a [`MemoryManager`](manager::MemoryManager),
5//! keyed by token. This module provides:
6//!
7//! - [`MemoryClass`] — tag describing where a payload resides (host, pinned, device, shared).
8//! - [`PlacementAcceptance`] / [`PlacementDecision`] — zero-copy routing policy.
9//! - [`BufferDescriptor`] — lightweight byte-size descriptor returned by [`Payload`](crate::message::payload::Payload).
10//!
11//! Submodules:
12//! - [`header_store`] — [`HeaderStore`](header_store::HeaderStore) supertrait for payload-agnostic header access.
13//! - [`manager`] — [`MemoryManager`](manager::MemoryManager) typed storage interface.
14//! - [`static_manager`] — `no_std`/`no_alloc` fixed-capacity implementation.
15//! - `heap_manager` (`alloc`) — heap-backed fixed-capacity implementation.
16//! - `concurrent_manager` (`std`) — lock-free freelist + per-slot `RwLock` implementation.
17
18pub mod header_store;
19pub mod manager;
20pub mod static_manager;
21
22#[cfg(feature = "alloc")]
23pub mod heap_manager;
24
25#[cfg(feature = "std")]
26pub mod concurrent_manager;
27
28/// The memory class associated with a payload.
29#[non_exhaustive]
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum MemoryClass {
32    /// Regular host memory.
33    Host,
34    /// Pinned (page-locked) host memory, suitable for DMA.
35    PinnedHost,
36    /// A device-specific memory region (e.g., GPU/NPU).
37    Device(u8),
38    /// A shared region accessible by multiple devices.
39    Shared,
40}
41
42#[allow(clippy::derivable_impls)]
43impl Default for MemoryClass {
44    fn default() -> Self {
45        MemoryClass::Host
46    }
47}
48
49/* ------------------------ Bit layout (no magic numbers) ------------------------ */
50
51const HOST_BIT: u32 = 0;
52const PINNED_HOST_BIT: u32 = 1;
53const DEVICE_BASE_BIT: u32 = 2;
54const DEVICE_MAX_ORDINAL: u8 = 15; // supports Device(0..=15)
55const SHARED_BIT: u32 = 18;
56
57const fn device_bit(ordinal: u8) -> u32 {
58    DEVICE_BASE_BIT + ordinal as u32
59}
60
61const DEVICE_MASK: u32 = {
62    // 16 device bits starting at DEVICE_BASE_BIT
63    ((1u32 << ((DEVICE_MAX_ORDINAL as u32) + 1)) - 1) << DEVICE_BASE_BIT
64};
65
66/* -------------------- Acceptance set (payload placement policy) -------------------- */
67
68/// A bitfield describing which memory classes a port can accept zero-copy.
69#[non_exhaustive]
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct PlacementAcceptance {
72    bits: u32,
73}
74
75impl Default for PlacementAcceptance {
76    fn default() -> Self {
77        // default: accept host memory only
78        Self::empty().with_host()
79    }
80}
81
82impl PlacementAcceptance {
83    /// Create an empty acceptance set.
84    #[inline]
85    pub const fn empty() -> Self {
86        Self { bits: 0 }
87    }
88
89    /// Construct from raw bits (advanced).
90    #[inline]
91    pub const fn from_bits(bits: u32) -> Self {
92        Self { bits }
93    }
94
95    /// Return the raw bits.
96    #[inline]
97    pub const fn bits(&self) -> &u32 {
98        &self.bits
99    }
100
101    /// Return true if there are no accepted classes.
102    #[inline]
103    pub const fn is_empty(&self) -> bool {
104        self.bits == 0
105    }
106
107    /// Union (set OR).
108    #[inline]
109    pub const fn union(self, other: Self) -> Self {
110        Self {
111            bits: self.bits | other.bits,
112        }
113    }
114
115    /// Intersection (set AND).
116    #[inline]
117    pub const fn intersect(self, other: Self) -> Self {
118        Self {
119            bits: self.bits & other.bits,
120        }
121    }
122
123    /// Superset test: does `self` include every class in `other`?
124    #[inline]
125    pub const fn contains(self, other: Self) -> bool {
126        (self.bits & other.bits) == other.bits
127    }
128
129    /// Create an acceptance set that includes all host classes.
130    #[inline]
131    pub const fn host_all() -> Self {
132        Self::empty().with_host().with_pinned_host()
133    }
134
135    /// Accept regular host memory.
136    #[inline]
137    pub const fn with_host(mut self) -> Self {
138        self.bits |= 1 << HOST_BIT;
139        self
140    }
141
142    /// Accept pinned host memory.
143    #[inline]
144    pub const fn with_pinned_host(mut self) -> Self {
145        self.bits |= 1 << PINNED_HOST_BIT;
146        self
147    }
148
149    /// Accept a specific device ordinal (0..=15).
150    #[inline]
151    pub const fn with_device(mut self, ordinal: u8) -> Self {
152        // Mask to supported range (keeps behavior compatible with your original code).
153        let o = (ordinal & DEVICE_MAX_ORDINAL) as u32;
154        self.bits |= 1 << (DEVICE_BASE_BIT + o);
155        self
156    }
157
158    /// Like `with_device` but returns `None` if the ordinal is out of range.
159    #[inline]
160    pub const fn try_with_device(self, ordinal: u8) -> Option<Self> {
161        if ordinal <= DEVICE_MAX_ORDINAL {
162            Some(self.with_device(ordinal))
163        } else {
164            None
165        }
166    }
167
168    /// Accept all supported device ordinals (0..=15).
169    #[inline]
170    pub const fn with_all_devices(mut self) -> Self {
171        self.bits |= DEVICE_MASK;
172        self
173    }
174
175    /// Accept shared memory regions.
176    #[inline]
177    pub const fn with_shared(mut self) -> Self {
178        self.bits |= 1 << SHARED_BIT;
179        self
180    }
181
182    /// Test whether the given memory class is accepted without copy.
183    #[inline]
184    pub const fn accepts(&self, class: MemoryClass) -> bool {
185        match class {
186            MemoryClass::Host => (self.bits & (1 << HOST_BIT)) != 0,
187            MemoryClass::PinnedHost => (self.bits & (1 << PINNED_HOST_BIT)) != 0,
188            MemoryClass::Device(o) => {
189                let o = (o & DEVICE_MAX_ORDINAL) as u32;
190                (self.bits & (1 << device_bit(o as u8))) != 0
191            }
192            MemoryClass::Shared => (self.bits & (1 << SHARED_BIT)) != 0,
193        }
194    }
195
196    /// Convenience: build an acceptance set that accepts exactly one class.
197    #[inline]
198    pub const fn exactly(class: MemoryClass) -> Self {
199        match class {
200            MemoryClass::Host => Self::empty().with_host(),
201            MemoryClass::PinnedHost => Self::empty().with_pinned_host(),
202            MemoryClass::Device(o) => Self::empty().with_device(o),
203            MemoryClass::Shared => Self::empty().with_shared(),
204        }
205    }
206}
207
208/// A descriptor of a buffer/payload view for size accounting.
209///
210/// Memory class information is now owned by the [`manager::MemoryManager`] rather than
211/// the payload itself — see `MemoryManager::memory_class()`.
212#[non_exhaustive]
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub struct BufferDescriptor {
215    /// The byte size of the payload.
216    bytes: usize,
217}
218
219impl BufferDescriptor {
220    /// Construct a new buffer descriptor.
221    #[inline]
222    pub const fn new(bytes: usize) -> Self {
223        Self { bytes }
224    }
225
226    /// Return the byte size of the payload.
227    #[inline]
228    pub const fn bytes(&self) -> &usize {
229        &self.bytes
230    }
231}
232
233/// The edge-level placement decision for a message about to cross a port.
234#[non_exhaustive]
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum PlacementDecision {
237    /// Input port accepts the current placement; pass through zero-copy.
238    ZeroCopy,
239    /// Input port does not accept the current placement; adapt (copy/transfer) required.
240    AdaptRequired,
241}
242
243/// Decide whether an edge can pass a message zero-copy or requires adaptation.
244#[inline]
245pub const fn decide_placement(
246    acceptance: PlacementAcceptance,
247    current: MemoryClass,
248) -> PlacementDecision {
249    if acceptance.accepts(current) {
250        PlacementDecision::ZeroCopy
251    } else {
252        PlacementDecision::AdaptRequired
253    }
254}