/home/noah/src/ruchy/src/runtime/safe_arena.rs
Line | Count | Source |
1 | | //! Safe arena allocator without unsafe code |
2 | | //! |
3 | | //! Provides bounded memory allocation using safe Rust abstractions. |
4 | | |
5 | | use anyhow::{Result, anyhow}; |
6 | | use std::cell::RefCell; |
7 | | use std::rc::Rc; |
8 | | |
9 | | // ============================================================================ |
10 | | // Safe Arena Allocator |
11 | | // ============================================================================ |
12 | | |
13 | | /// Safe arena allocator using Rc for memory management |
14 | | #[derive(Debug)] |
15 | | pub struct SafeArena { |
16 | | /// Storage for allocated values |
17 | | storage: RefCell<Vec<Box<dyn std::any::Any>>>, |
18 | | /// Current memory usage estimate |
19 | | used: RefCell<usize>, |
20 | | /// Maximum allowed memory |
21 | | max_size: usize, |
22 | | } |
23 | | |
24 | | impl SafeArena { |
25 | | /// Create a new arena with the given size limit |
26 | 41 | pub fn new(max_size: usize) -> Self { |
27 | 41 | Self { |
28 | 41 | storage: RefCell::new(Vec::new()), |
29 | 41 | used: RefCell::new(0), |
30 | 41 | max_size, |
31 | 41 | } |
32 | 41 | } |
33 | | |
34 | | /// Allocate a value in the arena |
35 | 22 | pub fn alloc<T: 'static>(&self, value: T) -> Result<ArenaRef<'_, T>> { |
36 | 22 | let size = std::mem::size_of::<T>(); |
37 | | |
38 | | // Check memory limit |
39 | 22 | if *self.used.borrow() + size > self.max_size { |
40 | 2 | return Err(anyhow!("Arena memory limit exceeded")); |
41 | 20 | } |
42 | | |
43 | | // Store value in Rc |
44 | 20 | let rc_value = Rc::new(value); |
45 | 20 | self.storage.borrow_mut().push(Box::new(rc_value.clone()) as Box<dyn std::any::Any>); |
46 | 20 | *self.used.borrow_mut() += size; |
47 | | |
48 | 20 | Ok(ArenaRef { |
49 | 20 | value: rc_value, |
50 | 20 | _arena: self, |
51 | 20 | }) |
52 | 22 | } |
53 | | |
54 | | /// Reset the arena, clearing all allocations |
55 | 2 | pub fn reset(&self) { |
56 | 2 | self.storage.borrow_mut().clear(); |
57 | 2 | *self.used.borrow_mut() = 0; |
58 | 2 | } |
59 | | |
60 | | /// Get current memory usage |
61 | 24 | pub fn used(&self) -> usize { |
62 | 24 | *self.used.borrow() |
63 | 24 | } |
64 | | } |
65 | | |
66 | | /// Reference to a value in the arena |
67 | | #[derive(Debug)] |
68 | | pub struct ArenaRef<'a, T> { |
69 | | value: Rc<T>, |
70 | | _arena: &'a SafeArena, |
71 | | } |
72 | | |
73 | | impl<T> std::ops::Deref for ArenaRef<'_, T> { |
74 | | type Target = T; |
75 | | |
76 | 9 | fn deref(&self) -> &Self::Target { |
77 | 9 | &self.value |
78 | 9 | } |
79 | | } |
80 | | |
81 | | // ============================================================================ |
82 | | // Transactional Arena |
83 | | // ============================================================================ |
84 | | |
85 | | /// Arena with checkpoint/rollback support |
86 | | #[derive(Debug)] |
87 | | pub struct TransactionalArena { |
88 | | /// Current values |
89 | | current: Rc<SafeArena>, |
90 | | /// Checkpoints |
91 | | checkpoints: Vec<ArenaCheckpoint>, |
92 | | } |
93 | | |
94 | | #[derive(Clone, Debug)] |
95 | | struct ArenaCheckpoint { |
96 | | storage_size: usize, |
97 | | used: usize, |
98 | | } |
99 | | |
100 | | impl TransactionalArena { |
101 | 35 | pub fn new(max_size: usize) -> Self { |
102 | 35 | Self { |
103 | 35 | current: Rc::new(SafeArena::new(max_size)), |
104 | 35 | checkpoints: Vec::new(), |
105 | 35 | } |
106 | 35 | } |
107 | | |
108 | 11 | pub fn checkpoint(&mut self) -> usize { |
109 | 11 | let checkpoint = ArenaCheckpoint { |
110 | 11 | storage_size: self.current.storage.borrow().len(), |
111 | 11 | used: self.current.used(), |
112 | 11 | }; |
113 | 11 | self.checkpoints.push(checkpoint); |
114 | 11 | self.checkpoints.len() - 1 |
115 | 11 | } |
116 | | |
117 | 5 | pub fn rollback(&mut self, checkpoint_id: usize) -> Result<()> { |
118 | 5 | if checkpoint_id >= self.checkpoints.len() { |
119 | 1 | return Err(anyhow!("Invalid checkpoint")); |
120 | 4 | } |
121 | | |
122 | 4 | let checkpoint = &self.checkpoints[checkpoint_id]; |
123 | | |
124 | | // Truncate storage to checkpoint size |
125 | 4 | self.current.storage.borrow_mut().truncate(checkpoint.storage_size); |
126 | 4 | *self.current.used.borrow_mut() = checkpoint.used; |
127 | | |
128 | | // Remove later checkpoints |
129 | 4 | self.checkpoints.truncate(checkpoint_id + 1); |
130 | | |
131 | 4 | Ok(()) |
132 | 5 | } |
133 | | |
134 | 3 | pub fn commit(&mut self) -> Result<()> { |
135 | 3 | if self.checkpoints.is_empty() { |
136 | 1 | return Err(anyhow!("No checkpoint to commit")); |
137 | 2 | } |
138 | 2 | self.checkpoints.pop(); |
139 | 2 | Ok(()) |
140 | 3 | } |
141 | | |
142 | 19 | pub fn arena(&self) -> &SafeArena { |
143 | 19 | &self.current |
144 | 19 | } |
145 | | |
146 | 1 | pub fn reset(&mut self) { |
147 | 1 | self.current.reset(); |
148 | 1 | self.checkpoints.clear(); |
149 | 1 | } |
150 | | } |
151 | | |
152 | | #[cfg(test)] |
153 | | mod tests { |
154 | | use super::*; |
155 | | |
156 | | #[test] |
157 | 1 | fn test_safe_arena() { |
158 | 1 | let arena = SafeArena::new(1024); |
159 | | |
160 | 1 | let v1 = arena.alloc(42).unwrap(); |
161 | 1 | let v2 = arena.alloc("hello".to_string()).unwrap(); |
162 | | |
163 | 1 | assert_eq!(*v1, 42); |
164 | 1 | assert_eq!(*v2, "hello"); |
165 | | |
166 | 1 | arena.reset(); |
167 | 1 | assert_eq!(arena.used(), 0); |
168 | 1 | } |
169 | | |
170 | | #[test] |
171 | 1 | fn test_transactional() { |
172 | 1 | let mut arena = TransactionalArena::new(1024); |
173 | | |
174 | 1 | arena.arena().alloc(1).unwrap(); |
175 | 1 | let checkpoint = arena.checkpoint(); |
176 | | |
177 | 1 | arena.arena().alloc(2).unwrap(); |
178 | 1 | let used_before = arena.arena().used(); |
179 | | |
180 | 1 | arena.rollback(checkpoint).unwrap(); |
181 | 1 | let used_after = arena.arena().used(); |
182 | | |
183 | 1 | assert!(used_after < used_before); |
184 | 1 | } |
185 | | |
186 | | #[test] |
187 | 1 | fn test_arena_memory_limit() { |
188 | 1 | let arena = SafeArena::new(16); // Very small limit |
189 | | |
190 | | // First allocation should succeed |
191 | 1 | let _val1 = arena.alloc([0u8; 8]).unwrap(); |
192 | | |
193 | | // Second allocation should fail due to memory limit |
194 | 1 | let result = arena.alloc([0u8; 16]); |
195 | 1 | assert!(result.is_err()); |
196 | 1 | assert!(result.unwrap_err().to_string().contains("memory limit exceeded")); |
197 | 1 | } |
198 | | |
199 | | #[test] |
200 | 1 | fn test_arena_used_tracking() { |
201 | 1 | let arena = SafeArena::new(1024); |
202 | 1 | assert_eq!(arena.used(), 0); |
203 | | |
204 | 1 | let _val1 = arena.alloc(42i32).unwrap(); |
205 | 1 | let used_after_int = arena.used(); |
206 | 1 | assert!(used_after_int >= 4); // At least size of i32 |
207 | | |
208 | 1 | let _val2 = arena.alloc("test".to_string()).unwrap(); |
209 | 1 | let used_after_string = arena.used(); |
210 | 1 | assert!(used_after_string > used_after_int); |
211 | 1 | } |
212 | | |
213 | | #[test] |
214 | 1 | fn test_arena_ref_deref() { |
215 | 1 | let arena = SafeArena::new(1024); |
216 | 1 | let val = arena.alloc(vec![1, 2, 3, 4]).unwrap(); |
217 | | |
218 | | // Test Deref trait |
219 | 1 | assert_eq!(val.len(), 4); |
220 | 1 | assert_eq!(val[0], 1); |
221 | 1 | assert_eq!(val[3], 4); |
222 | 1 | } |
223 | | |
224 | | #[test] |
225 | 1 | fn test_transactional_arena_new() { |
226 | 1 | let arena = TransactionalArena::new(2048); |
227 | 1 | assert_eq!(arena.arena().used(), 0); |
228 | 1 | assert!(arena.checkpoints.is_empty()); |
229 | 1 | } |
230 | | |
231 | | #[test] |
232 | 1 | fn test_transactional_arena_multiple_checkpoints() { |
233 | 1 | let mut arena = TransactionalArena::new(1024); |
234 | | |
235 | | // Initial allocation |
236 | 1 | arena.arena().alloc(100).unwrap(); |
237 | | |
238 | | // First checkpoint |
239 | 1 | let cp1 = arena.checkpoint(); |
240 | 1 | arena.arena().alloc(200).unwrap(); |
241 | | |
242 | | // Second checkpoint |
243 | 1 | let _cp2 = arena.checkpoint(); |
244 | 1 | arena.arena().alloc(300).unwrap(); |
245 | | |
246 | | // Rollback to first checkpoint |
247 | 1 | arena.rollback(cp1).unwrap(); |
248 | | |
249 | | // Should only have allocations up to first checkpoint |
250 | 1 | let used = arena.arena().used(); |
251 | 1 | assert!(used >= 4); // At least the first allocation |
252 | 1 | } |
253 | | |
254 | | #[test] |
255 | 1 | fn test_transactional_arena_invalid_checkpoint() { |
256 | 1 | let mut arena = TransactionalArena::new(1024); |
257 | | |
258 | | // Try to rollback to invalid checkpoint |
259 | 1 | let result = arena.rollback(999); |
260 | 1 | assert!(result.is_err()); |
261 | 1 | assert!(result.unwrap_err().to_string().contains("Invalid checkpoint")); |
262 | 1 | } |
263 | | |
264 | | #[test] |
265 | 1 | fn test_transactional_arena_commit() { |
266 | 1 | let mut arena = TransactionalArena::new(1024); |
267 | | |
268 | | // Create checkpoint |
269 | 1 | let _cp = arena.checkpoint(); |
270 | 1 | assert!(!arena.checkpoints.is_empty()); |
271 | | |
272 | | // Commit should remove the checkpoint |
273 | 1 | arena.commit().unwrap(); |
274 | 1 | assert!(arena.checkpoints.is_empty()); |
275 | 1 | } |
276 | | |
277 | | #[test] |
278 | 1 | fn test_transactional_arena_commit_without_checkpoint() { |
279 | 1 | let mut arena = TransactionalArena::new(1024); |
280 | | |
281 | | // Try to commit without checkpoint |
282 | 1 | let result = arena.commit(); |
283 | 1 | assert!(result.is_err()); |
284 | 1 | assert!(result.unwrap_err().to_string().contains("No checkpoint to commit")); |
285 | 1 | } |
286 | | |
287 | | #[test] |
288 | 1 | fn test_transactional_arena_reset() { |
289 | 1 | let mut arena = TransactionalArena::new(1024); |
290 | | |
291 | | // Add some data and checkpoints |
292 | 1 | arena.arena().alloc(42).unwrap(); |
293 | 1 | arena.checkpoint(); |
294 | 1 | arena.arena().alloc(84).unwrap(); |
295 | | |
296 | 1 | assert!(arena.arena().used() > 0); |
297 | 1 | assert!(!arena.checkpoints.is_empty()); |
298 | | |
299 | | // Reset should clear everything |
300 | 1 | arena.reset(); |
301 | 1 | assert_eq!(arena.arena().used(), 0); |
302 | 1 | assert!(arena.checkpoints.is_empty()); |
303 | 1 | } |
304 | | |
305 | | #[test] |
306 | 1 | fn test_checkpoint_clone() { |
307 | 1 | let checkpoint1 = ArenaCheckpoint { |
308 | 1 | storage_size: 10, |
309 | 1 | used: 100, |
310 | 1 | }; |
311 | 1 | let checkpoint2 = checkpoint1.clone(); |
312 | | |
313 | 1 | assert_eq!(checkpoint1.storage_size, checkpoint2.storage_size); |
314 | 1 | assert_eq!(checkpoint1.used, checkpoint2.used); |
315 | 1 | } |
316 | | |
317 | | #[test] |
318 | 1 | fn test_arena_with_different_types() { |
319 | 1 | let arena = SafeArena::new(1024); |
320 | | |
321 | | // Allocate different types |
322 | 1 | let int_val = arena.alloc(42i32).unwrap(); |
323 | 1 | let string_val = arena.alloc("hello".to_string()).unwrap(); |
324 | 1 | let vec_val = arena.alloc(vec![1, 2, 3]).unwrap(); |
325 | 1 | let bool_val = arena.alloc(true).unwrap(); |
326 | | |
327 | | // Verify all values |
328 | 1 | assert_eq!(*int_val, 42); |
329 | 1 | assert_eq!(*string_val, "hello"); |
330 | 1 | assert_eq!(*vec_val, vec![1, 2, 3]); |
331 | 1 | assert!(*bool_val); |
332 | 1 | } |
333 | | |
334 | | #[test] |
335 | 1 | fn test_transactional_arena_nested_operations() { |
336 | 1 | let mut arena = TransactionalArena::new(1024); |
337 | | |
338 | | // Initial state |
339 | 1 | arena.arena().alloc(1).unwrap(); |
340 | 1 | let initial_used = arena.arena().used(); |
341 | | |
342 | | // Start transaction |
343 | 1 | let cp = arena.checkpoint(); |
344 | 1 | arena.arena().alloc(2).unwrap(); |
345 | 1 | arena.arena().alloc(3).unwrap(); |
346 | | |
347 | 1 | let mid_used = arena.arena().used(); |
348 | 1 | assert!(mid_used > initial_used); |
349 | | |
350 | | // Rollback |
351 | 1 | arena.rollback(cp).unwrap(); |
352 | 1 | let final_used = arena.arena().used(); |
353 | | |
354 | 1 | assert_eq!(final_used, initial_used); |
355 | 1 | } |
356 | | |
357 | | #[test] |
358 | 1 | fn test_arena_large_allocation() { |
359 | 1 | let arena = SafeArena::new(1024); |
360 | | |
361 | | // Try to allocate something larger than limit |
362 | 1 | let result = arena.alloc([0u8; 2048]); |
363 | 1 | assert!(result.is_err()); |
364 | 1 | } |
365 | | |
366 | | #[test] |
367 | 1 | fn test_transactional_checkpoint_return_value() { |
368 | 1 | let mut arena = TransactionalArena::new(1024); |
369 | | |
370 | 1 | let cp1 = arena.checkpoint(); |
371 | 1 | assert_eq!(cp1, 0); |
372 | | |
373 | 1 | let cp2 = arena.checkpoint(); |
374 | 1 | assert_eq!(cp2, 1); |
375 | | |
376 | 1 | let cp3 = arena.checkpoint(); |
377 | 1 | assert_eq!(cp3, 2); |
378 | 1 | } |
379 | | } |