1#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Alignment {
11 Bytes4 = 4,
13 Bytes16 = 16,
15 Bytes64 = 64,
17 Bytes256 = 256,
19 Bytes4096 = 4096,
21}
22
23impl Alignment {
24 #[allow(dead_code)]
26 #[must_use]
27 pub const fn as_usize(self) -> usize {
28 self as usize
29 }
30
31 #[allow(dead_code)]
33 #[must_use]
34 pub const fn align_up(self, offset: usize) -> usize {
35 let align = self as usize;
36 (offset + align - 1) & !(align - 1)
37 }
38}
39
40#[allow(dead_code)]
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct AllocationHandle {
44 pub block_index: usize,
46 pub offset: usize,
48 pub size: usize,
50 pub alignment: usize,
52 pub id: u64,
54}
55
56#[allow(dead_code)]
58#[derive(Debug)]
59struct FreeRange {
60 offset: usize,
61 size: usize,
62}
63
64#[allow(dead_code)]
66#[derive(Debug)]
67struct Block {
68 capacity: usize,
70 free_ranges: Vec<FreeRange>,
72 live_count: usize,
74}
75
76impl Block {
77 fn new(capacity: usize) -> Self {
78 Self {
79 capacity,
80 free_ranges: vec![FreeRange {
81 offset: 0,
82 size: capacity,
83 }],
84 live_count: 0,
85 }
86 }
87
88 fn try_alloc(&mut self, size: usize, alignment: usize) -> Option<usize> {
91 for range in &mut self.free_ranges {
92 let aligned_offset = (range.offset + alignment - 1) & !(alignment - 1);
93 let waste = aligned_offset - range.offset;
94 if range.size >= waste + size {
95 let result_offset = aligned_offset;
96 range.offset += waste + size;
97 range.size -= waste + size;
98 self.live_count += 1;
99 return Some(result_offset);
100 }
101 }
102 self.free_ranges.retain(|r| r.size > 0);
104 None
105 }
106
107 fn free(&mut self, offset: usize, size: usize) {
109 self.free_ranges.push(FreeRange { offset, size });
110 if self.live_count > 0 {
111 self.live_count -= 1;
112 }
113 self.coalesce();
115 }
116
117 fn coalesce(&mut self) {
118 self.free_ranges.sort_by_key(|r| r.offset);
119 let mut i = 0;
120 while i + 1 < self.free_ranges.len() {
121 let end = self.free_ranges[i].offset + self.free_ranges[i].size;
122 if end >= self.free_ranges[i + 1].offset {
123 let merged_size = self.free_ranges[i + 1].offset + self.free_ranges[i + 1].size
125 - self.free_ranges[i].offset;
126 self.free_ranges[i].size = merged_size;
127 self.free_ranges.remove(i + 1);
128 } else {
129 i += 1;
130 }
131 }
132 }
133
134 fn free_bytes(&self) -> usize {
136 self.free_ranges.iter().map(|r| r.size).sum()
137 }
138}
139
140#[allow(dead_code)]
142#[derive(Debug, Clone, Default)]
143pub struct PoolStats {
144 pub total_reserved: usize,
146 pub total_allocated: usize,
148 pub block_count: usize,
150 pub alloc_count: u64,
152 pub free_count: u64,
154 pub failures: u64,
156}
157
158impl PoolStats {
159 #[allow(dead_code)]
161 #[must_use]
162 pub fn free_bytes(&self) -> usize {
163 self.total_reserved.saturating_sub(self.total_allocated)
164 }
165
166 #[allow(dead_code)]
168 #[must_use]
169 pub fn utilisation(&self) -> f64 {
170 if self.total_reserved == 0 {
171 0.0
172 } else {
173 self.total_allocated as f64 / self.total_reserved as f64
174 }
175 }
176}
177
178#[allow(dead_code)]
180pub struct GpuMemoryPool {
181 block_size: usize,
183 blocks: Vec<Block>,
185 stats: PoolStats,
187 next_id: u64,
189}
190
191impl GpuMemoryPool {
192 #[allow(dead_code)]
196 #[must_use]
197 pub fn new(block_size: usize) -> Self {
198 assert!(block_size > 0, "block_size must be > 0");
199 Self {
200 block_size,
201 blocks: Vec::new(),
202 stats: PoolStats::default(),
203 next_id: 0,
204 }
205 }
206
207 #[allow(dead_code)]
212 pub fn alloc(&mut self, size: usize, alignment: Alignment) -> Option<AllocationHandle> {
213 if size == 0 {
214 return None;
215 }
216 let align = alignment.as_usize();
217
218 for (i, block) in self.blocks.iter_mut().enumerate() {
220 if let Some(offset) = block.try_alloc(size, align) {
221 let id = self.next_id;
222 self.next_id += 1;
223 self.stats.alloc_count += 1;
224 self.stats.total_allocated += size;
225 return Some(AllocationHandle {
226 block_index: i,
227 offset,
228 size,
229 alignment: align,
230 id,
231 });
232 }
233 }
234
235 let new_block_size = self.block_size.max(size + align);
237 let mut block = Block::new(new_block_size);
238 if let Some(offset) = block.try_alloc(size, align) {
239 self.stats.total_reserved += new_block_size;
240 self.stats.block_count += 1;
241 let block_index = self.blocks.len();
242 self.blocks.push(block);
243
244 let id = self.next_id;
245 self.next_id += 1;
246 self.stats.alloc_count += 1;
247 self.stats.total_allocated += size;
248 Some(AllocationHandle {
249 block_index,
250 offset,
251 size,
252 alignment: align,
253 id,
254 })
255 } else {
256 self.stats.failures += 1;
257 None
258 }
259 }
260
261 #[allow(dead_code)]
263 pub fn free(&mut self, handle: &AllocationHandle) {
264 if handle.block_index < self.blocks.len() {
265 self.blocks[handle.block_index].free(handle.offset, handle.size);
266 self.stats.total_allocated = self.stats.total_allocated.saturating_sub(handle.size);
267 self.stats.free_count += 1;
268 }
269 }
270
271 #[allow(dead_code)]
273 #[must_use]
274 pub fn stats(&self) -> &PoolStats {
275 &self.stats
276 }
277
278 #[allow(dead_code)]
280 #[must_use]
281 pub fn block_count(&self) -> usize {
282 self.blocks.len()
283 }
284
285 #[allow(dead_code)]
287 #[must_use]
288 pub fn free_bytes(&self) -> usize {
289 self.blocks.iter().map(Block::free_bytes).sum()
290 }
291
292 #[allow(dead_code)]
294 pub fn reset(&mut self) {
295 self.blocks.clear();
296 self.stats = PoolStats::default();
297 self.next_id = 0;
298 }
299}
300
301#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn test_alignment_align_up() {
311 assert_eq!(Alignment::Bytes16.align_up(0), 0);
312 assert_eq!(Alignment::Bytes16.align_up(1), 16);
313 assert_eq!(Alignment::Bytes16.align_up(16), 16);
314 assert_eq!(Alignment::Bytes16.align_up(17), 32);
315 }
316
317 #[test]
318 fn test_alignment_as_usize() {
319 assert_eq!(Alignment::Bytes4.as_usize(), 4);
320 assert_eq!(Alignment::Bytes256.as_usize(), 256);
321 }
322
323 #[test]
324 fn test_simple_alloc() {
325 let mut pool = GpuMemoryPool::new(1024);
326 let handle = pool.alloc(64, Alignment::Bytes16);
327 assert!(handle.is_some());
328 let h = handle.unwrap();
329 assert_eq!(h.size, 64);
330 assert_eq!(h.offset % 16, 0);
331 }
332
333 #[test]
334 fn test_zero_size_alloc_returns_none() {
335 let mut pool = GpuMemoryPool::new(1024);
336 assert!(pool.alloc(0, Alignment::Bytes4).is_none());
337 }
338
339 #[test]
340 fn test_alloc_and_free_stats() {
341 let mut pool = GpuMemoryPool::new(1024);
342 let h = pool.alloc(100, Alignment::Bytes4).unwrap();
343 assert_eq!(pool.stats().total_allocated, 100);
344 pool.free(&h);
345 assert_eq!(pool.stats().total_allocated, 0);
346 }
347
348 #[test]
349 fn test_multiple_allocs_same_block() {
350 let mut pool = GpuMemoryPool::new(4096);
351 let h1 = pool.alloc(128, Alignment::Bytes64).unwrap();
352 let h2 = pool.alloc(128, Alignment::Bytes64).unwrap();
353 assert_eq!(h1.block_index, h2.block_index);
354 assert_eq!(pool.block_count(), 1);
355 }
356
357 #[test]
358 fn test_new_block_created_when_full() {
359 let mut pool = GpuMemoryPool::new(64);
360 let _h1 = pool.alloc(64, Alignment::Bytes4).unwrap();
362 let h2 = pool.alloc(64, Alignment::Bytes4).unwrap();
364 assert!(h2.block_index >= 1 || pool.block_count() == 2);
365 }
366
367 #[test]
368 fn test_pool_stats_utilisation() {
369 let mut pool = GpuMemoryPool::new(1000);
370 pool.alloc(500, Alignment::Bytes4);
371 let util = pool.stats().utilisation();
372 assert!(util > 0.0 && util <= 1.0);
373 }
374
375 #[test]
376 fn test_free_bytes_decreases_after_alloc() {
377 let mut pool = GpuMemoryPool::new(1024);
378 pool.alloc(256, Alignment::Bytes4);
379 assert!(pool.free_bytes() < 1024);
380 }
381
382 #[test]
383 fn test_reset_clears_all() {
384 let mut pool = GpuMemoryPool::new(512);
385 pool.alloc(100, Alignment::Bytes4);
386 pool.reset();
387 assert_eq!(pool.block_count(), 0);
388 assert_eq!(pool.stats().alloc_count, 0);
389 }
390
391 #[test]
392 fn test_alloc_id_increments() {
393 let mut pool = GpuMemoryPool::new(1024);
394 let h1 = pool.alloc(10, Alignment::Bytes4).unwrap();
395 let h2 = pool.alloc(10, Alignment::Bytes4).unwrap();
396 assert!(h2.id > h1.id);
397 }
398
399 #[test]
400 fn test_block_coalescing_after_free() {
401 let mut pool = GpuMemoryPool::new(256);
402 let h1 = pool.alloc(64, Alignment::Bytes4).unwrap();
403 let h2 = pool.alloc(64, Alignment::Bytes4).unwrap();
404 pool.free(&h1);
405 pool.free(&h2);
406 let h3 = pool.alloc(100, Alignment::Bytes4);
408 assert!(h3.is_some());
409 }
410
411 #[test]
412 fn test_stats_free_bytes() {
413 let mut stats = PoolStats {
414 total_reserved: 1000,
415 total_allocated: 400,
416 ..Default::default()
417 };
418 assert_eq!(stats.free_bytes(), 600);
419 stats.total_allocated = 1000;
420 assert_eq!(stats.free_bytes(), 0);
421 }
422}