oxihuman_core/
arena_allocator.rs1#[derive(Debug, Clone)]
8pub struct ArenaConfig {
9 pub chunk_size: usize,
10}
11
12#[derive(Debug, Clone)]
14pub struct ArenaAllocator {
15 pub buf: Vec<u8>,
16 pub used: usize,
17 alloc_count: usize,
18}
19
20#[derive(Debug, Clone)]
22pub struct ArenaStats {
23 pub used: usize,
24 pub capacity: usize,
25 pub alloc_count: usize,
26}
27
28pub fn default_arena_config() -> ArenaConfig {
30 ArenaConfig { chunk_size: 4096 }
31}
32
33pub fn new_arena(config: &ArenaConfig) -> ArenaAllocator {
35 ArenaAllocator {
36 buf: vec![0u8; config.chunk_size],
37 used: 0,
38 alloc_count: 0,
39 }
40}
41
42pub fn arena_alloc_bytes(arena: &mut ArenaAllocator, size: usize) -> Option<usize> {
44 if arena.used + size > arena.buf.len() {
45 return None;
46 }
47 let offset = arena.used;
48 arena.used += size;
49 arena.alloc_count += 1;
50 Some(offset)
51}
52
53pub fn arena_alloc_bytes_aligned(
58 arena: &mut ArenaAllocator,
59 size: usize,
60 align: usize,
61) -> Option<usize> {
62 let align = align.max(1);
63 let aligned_offset = (arena.used + align - 1) & !(align - 1);
64 if aligned_offset + size > arena.buf.len() {
65 return None;
66 }
67 arena.used = aligned_offset + size;
68 arena.alloc_count += 1;
69 Some(aligned_offset)
70}
71
72pub fn arena_reset(arena: &mut ArenaAllocator) {
74 arena.used = 0;
75 arena.alloc_count = 0;
76}
77
78pub fn arena_stats(arena: &ArenaAllocator) -> ArenaStats {
80 ArenaStats {
81 used: arena.used,
82 capacity: arena.buf.len(),
83 alloc_count: arena.alloc_count,
84 }
85}
86
87pub fn arena_remaining(arena: &ArenaAllocator) -> usize {
89 arena.buf.len().saturating_sub(arena.used)
90}
91
92pub fn arena_usage_ratio(arena: &ArenaAllocator) -> f32 {
94 if arena.buf.is_empty() {
95 return 1.0;
96 }
97 arena.used as f32 / arena.buf.len() as f32
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use proptest::prelude::*;
104
105 #[test]
108 fn test_default_config() {
109 let cfg = default_arena_config();
110 assert!(cfg.chunk_size > 0);
111 }
112
113 #[test]
114 fn test_new_arena_empty() {
115 let cfg = ArenaConfig { chunk_size: 1024 };
116 let arena = new_arena(&cfg);
117 assert_eq!(arena.used, 0);
118 assert_eq!(arena.buf.len(), 1024);
119 }
120
121 #[test]
122 fn test_alloc_bytes() {
123 let cfg = ArenaConfig { chunk_size: 256 };
124 let mut arena = new_arena(&cfg);
125 let off = arena_alloc_bytes(&mut arena, 64);
126 assert_eq!(off, Some(0));
127 assert_eq!(arena.used, 64);
128 }
129
130 #[test]
131 fn test_alloc_sequential() {
132 let cfg = ArenaConfig { chunk_size: 256 };
133 let mut arena = new_arena(&cfg);
134 let off1 = arena_alloc_bytes(&mut arena, 32).expect("should succeed");
135 let off2 = arena_alloc_bytes(&mut arena, 32).expect("should succeed");
136 assert_eq!(off1, 0);
137 assert_eq!(off2, 32);
138 }
139
140 #[test]
141 fn test_alloc_out_of_space() {
142 let cfg = ArenaConfig { chunk_size: 10 };
143 let mut arena = new_arena(&cfg);
144 assert!(arena_alloc_bytes(&mut arena, 20).is_none());
145 }
146
147 #[test]
148 fn test_reset() {
149 let cfg = ArenaConfig { chunk_size: 64 };
150 let mut arena = new_arena(&cfg);
151 arena_alloc_bytes(&mut arena, 32);
152 arena_reset(&mut arena);
153 assert_eq!(arena.used, 0);
154 let stats = arena_stats(&arena);
155 assert_eq!(stats.alloc_count, 0);
156 }
157
158 #[test]
159 fn test_remaining() {
160 let cfg = ArenaConfig { chunk_size: 100 };
161 let mut arena = new_arena(&cfg);
162 arena_alloc_bytes(&mut arena, 40);
163 assert_eq!(arena_remaining(&arena), 60);
164 }
165
166 #[test]
167 fn test_usage_ratio() {
168 let cfg = ArenaConfig { chunk_size: 100 };
169 let mut arena = new_arena(&cfg);
170 arena_alloc_bytes(&mut arena, 50);
171 let ratio = arena_usage_ratio(&arena);
172 assert!((ratio - 0.5).abs() < 1e-6);
173 }
174
175 #[test]
176 fn test_stats() {
177 let cfg = ArenaConfig { chunk_size: 128 };
178 let mut arena = new_arena(&cfg);
179 arena_alloc_bytes(&mut arena, 10);
180 arena_alloc_bytes(&mut arena, 20);
181 let stats = arena_stats(&arena);
182 assert_eq!(stats.alloc_count, 2);
183 assert_eq!(stats.used, 30);
184 assert_eq!(stats.capacity, 128);
185 }
186
187 #[test]
190 fn test_alloc_aligned() {
191 let cfg = ArenaConfig { chunk_size: 256 };
193 let mut arena = new_arena(&cfg);
194 let off1 = arena_alloc_bytes_aligned(&mut arena, 1, 1).expect("first alloc");
195 assert_eq!(off1, 0);
196 let off2 = arena_alloc_bytes_aligned(&mut arena, 4, 4).expect("second alloc");
197 assert_eq!(off2, 4);
198 }
199
200 #[test]
201 fn test_alloc_large_alignment() {
202 let cfg = ArenaConfig { chunk_size: 256 };
203 let mut arena = new_arena(&cfg);
204 arena_alloc_bytes(&mut arena, 1);
206 let off = arena_alloc_bytes_aligned(&mut arena, 8, 16).expect("aligned alloc");
208 assert_eq!(off % 16, 0);
209 }
210
211 #[test]
212 fn test_aligned_out_of_space() {
213 let cfg = ArenaConfig { chunk_size: 8 };
214 let mut arena = new_arena(&cfg);
215 assert!(arena_alloc_bytes_aligned(&mut arena, 16, 1).is_none());
216 }
217
218 proptest! {
221 #[test]
222 fn prop_aligned_alloc_invariants(
223 allocs in proptest::collection::vec(
224 (1usize..=64usize,
225 proptest::sample::select(vec![1usize, 2, 4, 8, 16, 32])),
226 0..50
227 )
228 ) {
229 let cfg = ArenaConfig { chunk_size: 8192 };
231 let mut arena = new_arena(&cfg);
232 let mut prev_end: usize = 0;
233
234 for (size, align) in &allocs {
235 let size = *size;
236 let align = *align;
237 let off = match arena_alloc_bytes_aligned(&mut arena, size, align) {
238 Some(o) => o,
239 None => break, };
241 prop_assert_eq!(off % align, 0, "offset {} not aligned to {}", off, align);
243 prop_assert!(off >= prev_end, "overlap: off={} prev_end={}", off, prev_end);
245 prev_end = off + size;
246 }
247 }
248 }
249}