Skip to main content

ruvector_cognitive_container/
memory.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::{ContainerError, Result};
4
5/// Configuration for memory slab layout.
6///
7/// Each budget defines the byte size of a sub-arena within the slab.
8/// The total slab size is the sum of all budgets.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct MemoryConfig {
11    /// Total slab size in bytes (must equal sum of budgets).
12    pub slab_size: usize,
13    /// Bytes reserved for graph adjacency data.
14    pub graph_budget: usize,
15    /// Bytes reserved for feature / embedding storage.
16    pub feature_budget: usize,
17    /// Bytes reserved for solver scratch space.
18    pub solver_budget: usize,
19    /// Bytes reserved for witness receipt storage.
20    pub witness_budget: usize,
21    /// Bytes reserved for evidence accumulation.
22    pub evidence_budget: usize,
23}
24
25impl Default for MemoryConfig {
26    fn default() -> Self {
27        Self {
28            slab_size: 4 * 1024 * 1024,   // 4 MB total
29            graph_budget: 1024 * 1024,    // 1 MB
30            feature_budget: 1024 * 1024,  // 1 MB
31            solver_budget: 512 * 1024,    // 512 KB
32            witness_budget: 512 * 1024,   // 512 KB
33            evidence_budget: 1024 * 1024, // 1 MB
34        }
35    }
36}
37
38impl MemoryConfig {
39    /// Validate that budget components sum to `slab_size`.
40    pub fn validate(&self) -> Result<()> {
41        let sum = self.graph_budget
42            + self.feature_budget
43            + self.solver_budget
44            + self.witness_budget
45            + self.evidence_budget;
46        if sum != self.slab_size {
47            return Err(ContainerError::InvalidConfig {
48                reason: format!(
49                    "budget sum ({sum}) does not equal slab_size ({})",
50                    self.slab_size
51                ),
52            });
53        }
54        Ok(())
55    }
56}
57
58/// A contiguous block of memory backing all container arenas.
59pub struct MemorySlab {
60    data: Vec<u8>,
61    config: MemoryConfig,
62}
63
64impl MemorySlab {
65    /// Allocate a new slab according to `config`.
66    pub fn new(config: MemoryConfig) -> Result<Self> {
67        config.validate()?;
68        Ok(Self {
69            data: vec![0u8; config.slab_size],
70            config,
71        })
72    }
73
74    /// Total slab size in bytes.
75    pub fn total_size(&self) -> usize {
76        self.data.len()
77    }
78
79    /// Immutable view of the raw slab bytes.
80    pub fn as_bytes(&self) -> &[u8] {
81        &self.data
82    }
83
84    /// Reference to the underlying config.
85    pub fn config(&self) -> &MemoryConfig {
86        &self.config
87    }
88}
89
90/// A bump-allocator arena within a `MemorySlab`.
91///
92/// `base_offset` is the starting position inside the slab.
93/// Allocations grow upward; `reset()` reclaims all space.
94pub struct Arena {
95    base_offset: usize,
96    size: usize,
97    offset: usize,
98}
99
100impl Arena {
101    /// Create a new arena starting at `base_offset` with the given `size`.
102    pub fn new(base_offset: usize, size: usize) -> Self {
103        Self {
104            base_offset,
105            size,
106            offset: 0,
107        }
108    }
109
110    /// Bump-allocate `size` bytes with the given `align`ment.
111    ///
112    /// Returns the absolute offset within the slab on success.
113    pub fn alloc(&mut self, size: usize, align: usize) -> Result<usize> {
114        let align = align.max(1);
115        let current = self.base_offset + self.offset;
116        let aligned = (current + align - 1) & !(align - 1);
117        let padding = aligned - current;
118        let total = padding + size;
119
120        if self.offset + total > self.size {
121            return Err(ContainerError::AllocationFailed {
122                requested: size,
123                available: self.remaining(),
124            });
125        }
126
127        self.offset += total;
128        Ok(aligned)
129    }
130
131    /// Reset the arena, reclaiming all allocated space.
132    pub fn reset(&mut self) {
133        self.offset = 0;
134    }
135
136    /// Number of bytes currently consumed (including alignment padding).
137    pub fn used(&self) -> usize {
138        self.offset
139    }
140
141    /// Number of bytes still available.
142    pub fn remaining(&self) -> usize {
143        self.size.saturating_sub(self.offset)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_memory_slab_creation() {
153        let config = MemoryConfig::default();
154        let slab = MemorySlab::new(config).expect("slab should allocate");
155        assert_eq!(slab.total_size(), 4 * 1024 * 1024);
156        assert_eq!(slab.as_bytes().len(), slab.total_size());
157        // Fresh slab is zero-filled.
158        assert!(slab.as_bytes().iter().all(|&b| b == 0));
159    }
160
161    #[test]
162    fn test_memory_config_validation_fails_on_mismatch() {
163        let config = MemoryConfig {
164            slab_size: 100,
165            graph_budget: 10,
166            feature_budget: 10,
167            solver_budget: 10,
168            witness_budget: 10,
169            evidence_budget: 10,
170        };
171        assert!(MemorySlab::new(config).is_err());
172    }
173
174    #[test]
175    fn test_arena_allocation() {
176        let mut arena = Arena::new(0, 256);
177        assert_eq!(arena.remaining(), 256);
178        assert_eq!(arena.used(), 0);
179
180        let off1 = arena.alloc(64, 8).expect("alloc 64");
181        assert_eq!(off1, 0); // base 0, align 8 => 0
182        assert_eq!(arena.used(), 64);
183        assert_eq!(arena.remaining(), 192);
184
185        let off2 = arena.alloc(32, 16).expect("alloc 32");
186        // 64 already used, align to 16 => 64 (already aligned)
187        assert_eq!(off2, 64);
188        assert_eq!(arena.used(), 96);
189
190        arena.reset();
191        assert_eq!(arena.used(), 0);
192        assert_eq!(arena.remaining(), 256);
193    }
194
195    #[test]
196    fn test_arena_allocation_overflow() {
197        let mut arena = Arena::new(0, 64);
198        assert!(arena.alloc(128, 1).is_err());
199    }
200
201    #[test]
202    fn test_arena_alignment_padding() {
203        let mut arena = Arena::new(0, 256);
204        // Allocate 1 byte at alignment 1
205        let _ = arena.alloc(1, 1).unwrap();
206        assert_eq!(arena.used(), 1);
207        // Next allocation with align 16: from offset 1, aligned to 16 => 16
208        let off = arena.alloc(8, 16).unwrap();
209        assert_eq!(off, 16);
210        // used = 1 (first) + 15 (padding) + 8 = 24
211        assert_eq!(arena.used(), 24);
212    }
213}