1const NUM_BINS: usize = 13;
36
37const SIZE_CLASSES: [usize; NUM_BINS] = [
39 16, 32, 48, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536,
40];
41
42fn bin_for_size(size: usize) -> Option<usize> {
45 for (i, &class_size) in SIZE_CLASSES.iter().enumerate() {
46 if size <= class_size {
47 return Some(i);
48 }
49 }
50 None
51}
52
53struct Bin {
55 size_class: usize,
57 free_list: Vec<usize>,
59 total_allocated: usize,
61}
62
63impl Bin {
64 fn new(size_class: usize) -> Self {
65 Bin {
66 size_class,
67 free_list: Vec::new(),
68 total_allocated: 0,
69 }
70 }
71}
72
73struct Block {
75 offset: usize,
77 size: usize,
79 in_use: bool,
81}
82
83pub struct BinnedAllocator {
88 bins: Vec<Bin>,
90 blocks: Vec<Block>,
92 storage: Vec<u8>,
94 overflow_free: Vec<usize>,
96 pub alloc_count: usize,
98 pub free_count: usize,
100}
101
102impl BinnedAllocator {
103 pub fn new() -> Self {
105 let bins = SIZE_CLASSES.iter().map(|&s| Bin::new(s)).collect();
106 BinnedAllocator {
107 bins,
108 blocks: Vec::new(),
109 storage: Vec::new(),
110 overflow_free: Vec::new(),
111 alloc_count: 0,
112 free_count: 0,
113 }
114 }
115
116 pub fn alloc(&mut self, size: usize) -> usize {
121 self.alloc_count += 1;
122
123 if let Some(bin_idx) = bin_for_size(size) {
124 let bin = &mut self.bins[bin_idx];
125 if let Some(block_idx) = bin.free_list.pop() {
127 self.blocks[block_idx].in_use = true;
128 return block_idx;
129 }
130 let class_size = bin.size_class;
132 bin.total_allocated += 1;
133 self.alloc_new_block(class_size)
134 } else {
135 if let Some(block_idx) = self.overflow_free.pop() {
137 if self.blocks[block_idx].size >= size {
138 self.blocks[block_idx].in_use = true;
139 return block_idx;
140 }
141 self.overflow_free.push(block_idx);
143 }
144 self.alloc_new_block(size)
145 }
146 }
147
148 pub fn free(&mut self, block_idx: usize) {
150 if block_idx >= self.blocks.len() {
151 return;
152 }
153 let block = &mut self.blocks[block_idx];
154 if !block.in_use {
155 return; }
157 block.in_use = false;
158 self.free_count += 1;
159
160 let size = block.size;
161 if let Some(bin_idx) = bin_for_size(size) {
162 self.bins[bin_idx].free_list.push(block_idx);
163 } else {
164 self.overflow_free.push(block_idx);
165 }
166 }
167
168 pub fn get(&self, block_idx: usize) -> Option<&[u8]> {
170 self.blocks.get(block_idx).and_then(|b| {
171 if b.in_use {
172 self.storage.get(b.offset..b.offset + b.size)
173 } else {
174 None
175 }
176 })
177 }
178
179 pub fn get_mut(&mut self, block_idx: usize) -> Option<&mut [u8]> {
181 if let Some(b) = self.blocks.get(block_idx) {
182 if b.in_use {
183 let offset = b.offset;
184 let size = b.size;
185 return self.storage.get_mut(offset..offset + size);
186 }
187 }
188 None
189 }
190
191 pub fn write(&mut self, block_idx: usize, data: &[u8]) -> bool {
193 if let Some(slice) = self.get_mut(block_idx) {
194 let len = data.len().min(slice.len());
195 slice[..len].copy_from_slice(&data[..len]);
196 true
197 } else {
198 false
199 }
200 }
201
202 pub fn live_count(&self) -> usize {
204 self.blocks.iter().filter(|b| b.in_use).count()
205 }
206
207 pub fn total_blocks(&self) -> usize {
209 self.blocks.len()
210 }
211
212 pub fn storage_bytes(&self) -> usize {
214 self.storage.len()
215 }
216
217 pub fn free_block_count(&self) -> usize {
219 self.bins.iter().map(|b| b.free_list.len()).sum::<usize>()
220 + self.overflow_free.len()
221 }
222
223 fn alloc_new_block(&mut self, size: usize) -> usize {
225 let offset = self.storage.len();
226 let aligned = (size + 7) & !7;
228 self.storage.resize(self.storage.len() + aligned, 0);
229 let idx = self.blocks.len();
230 self.blocks.push(Block {
231 offset,
232 size: aligned,
233 in_use: true,
234 });
235 idx
236 }
237}
238
239impl Default for BinnedAllocator {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_bin_selection() {
255 assert_eq!(bin_for_size(1), Some(0)); assert_eq!(bin_for_size(16), Some(0)); assert_eq!(bin_for_size(17), Some(1)); assert_eq!(bin_for_size(48), Some(2)); assert_eq!(bin_for_size(65), Some(4)); assert_eq!(bin_for_size(65536), Some(12)); assert_eq!(bin_for_size(65537), None); }
263
264 #[test]
265 fn test_alloc_and_read() {
266 let mut alloc = BinnedAllocator::new();
267 let b = alloc.alloc(8);
268 assert!(alloc.write(b, &[1, 2, 3, 4, 5, 6, 7, 8]));
269 assert_eq!(&alloc.get(b).unwrap()[..8], &[1, 2, 3, 4, 5, 6, 7, 8]);
270 }
271
272 #[test]
273 fn test_free_and_reuse() {
274 let mut alloc = BinnedAllocator::new();
275 let b1 = alloc.alloc(16);
276 alloc.free(b1);
277
278 let b2 = alloc.alloc(16);
280 assert_eq!(b1, b2, "LIFO reuse");
281 }
282
283 #[test]
284 fn test_double_free_harmless() {
285 let mut alloc = BinnedAllocator::new();
286 let b = alloc.alloc(16);
287 alloc.free(b);
288 alloc.free(b); assert_eq!(alloc.free_count, 1, "second free should be no-op");
290 }
291
292 #[test]
293 fn test_storage_never_shrinks() {
294 let mut alloc = BinnedAllocator::new();
295 for _ in 0..100 {
296 let b = alloc.alloc(64);
297 alloc.free(b);
298 }
299 let bytes = alloc.storage_bytes();
300 assert!(bytes > 0);
302 for _ in 0..100 {
304 let b = alloc.alloc(64);
305 alloc.free(b);
306 }
307 assert_eq!(alloc.storage_bytes(), bytes, "storage must not shrink");
308 }
309
310 #[test]
311 fn test_deterministic_alloc_order() {
312 let mut a1 = BinnedAllocator::new();
313 let mut a2 = BinnedAllocator::new();
314
315 let seq1: Vec<usize> = (0..10).map(|_| a1.alloc(32)).collect();
316 let seq2: Vec<usize> = (0..10).map(|_| a2.alloc(32)).collect();
317
318 assert_eq!(seq1, seq2, "same alloc sequence → same block indices");
319 }
320
321 #[test]
322 fn test_lifo_free_order() {
323 let mut alloc = BinnedAllocator::new();
324 let b1 = alloc.alloc(32);
325 let b2 = alloc.alloc(32);
326 let b3 = alloc.alloc(32);
327
328 alloc.free(b1);
330 alloc.free(b2);
331 alloc.free(b3);
332
333 assert_eq!(alloc.alloc(32), b3);
335 assert_eq!(alloc.alloc(32), b2);
336 assert_eq!(alloc.alloc(32), b1);
337 }
338
339 #[test]
340 fn test_overflow_alloc() {
341 let mut alloc = BinnedAllocator::new();
342 let b = alloc.alloc(100_000); assert!(alloc.write(b, &[0xAB; 100]));
344 assert_eq!(alloc.get(b).unwrap()[0], 0xAB);
345 }
346
347 #[test]
348 fn test_live_count() {
349 let mut alloc = BinnedAllocator::new();
350 let b1 = alloc.alloc(16);
351 let b2 = alloc.alloc(32);
352 let _b3 = alloc.alloc(64);
353 assert_eq!(alloc.live_count(), 3);
354
355 alloc.free(b1);
356 assert_eq!(alloc.live_count(), 2);
357
358 alloc.free(b2);
359 assert_eq!(alloc.live_count(), 1);
360 }
361
362 #[test]
363 fn test_freed_block_not_readable() {
364 let mut alloc = BinnedAllocator::new();
365 let b = alloc.alloc(16);
366 alloc.free(b);
367 assert!(alloc.get(b).is_none(), "freed block should not be readable");
368 }
369
370 #[test]
371 fn test_mixed_size_classes() {
372 let mut alloc = BinnedAllocator::new();
373 let small = alloc.alloc(8);
374 let medium = alloc.alloc(256);
375 let large = alloc.alloc(4096);
376
377 alloc.free(small);
378 alloc.free(medium);
379 alloc.free(large);
380
381 let s2 = alloc.alloc(8);
383 let m2 = alloc.alloc(256);
384 let l2 = alloc.alloc(4096);
385
386 assert_eq!(s2, small);
387 assert_eq!(m2, medium);
388 assert_eq!(l2, large);
389 }
390}