1use {
2 crate::{
3 align_down, align_up,
4 error::AllocationError,
5 heap::Heap,
6 util::{arc_unwrap, is_arc_unique},
7 MemoryBounds,
8 },
9 alloc::{sync::Arc, vec::Vec},
10 core::{cmp::Ordering, ptr::NonNull},
11 gpu_alloc_types::{AllocationFlags, DeviceMapError, MemoryDevice, MemoryPropertyFlags},
12};
13
14unsafe fn opt_ptr_add(ptr: Option<NonNull<u8>>, size: u64) -> Option<NonNull<u8>> {
15 ptr.map(|ptr| {
16 NonNull::new_unchecked(ptr.as_ptr().offset(size as isize))
19 })
20}
21
22#[derive(Debug)]
23pub(super) struct FreeList<M> {
24 array: Vec<FreeListRegion<M>>,
25 counter: u64,
26}
27
28impl<M> FreeList<M> {
29 pub fn new() -> Self {
30 FreeList {
31 array: Vec::new(),
32 counter: 0,
33 }
34 }
35
36 pub fn get_block_from_new_memory(
37 &mut self,
38 memory: Arc<M>,
39 memory_size: u64,
40 ptr: Option<NonNull<u8>>,
41 align_mask: u64,
42 size: u64,
43 ) -> FreeListBlock<M> {
44 debug_assert!(size <= memory_size);
45
46 self.counter += 1;
47 self.array.push(FreeListRegion {
48 memory,
49 ptr,
50 chunk: self.counter,
51 start: 0,
52 end: memory_size,
53 });
54 self.get_block_at(self.array.len() - 1, align_mask, size)
55 }
56
57 pub fn get_block(&mut self, align_mask: u64, size: u64) -> Option<FreeListBlock<M>> {
58 let (index, _) = self.array.iter().enumerate().rev().find(|(_, region)| {
59 match region.end.checked_sub(size) {
60 Some(start) => {
61 let aligned_start = align_down(start, align_mask);
62 aligned_start >= region.start
63 }
64 None => false,
65 }
66 })?;
67
68 Some(self.get_block_at(index, align_mask, size))
69 }
70
71 fn get_block_at(&mut self, index: usize, align_mask: u64, size: u64) -> FreeListBlock<M> {
72 let region = &mut self.array[index];
73
74 let start = region.end - size;
75 let aligned_start = align_down(start, align_mask);
76
77 if aligned_start > region.start {
78 let block = FreeListBlock {
79 offset: aligned_start,
80 size: region.end - aligned_start,
81 chunk: region.chunk,
82 ptr: unsafe { opt_ptr_add(region.ptr, aligned_start - region.start) },
83 memory: region.memory.clone(),
84 };
85
86 region.end = aligned_start;
87
88 block
89 } else {
90 debug_assert_eq!(aligned_start, region.start);
91 let region = self.array.remove(index);
92 region.into_block()
93 }
94 }
95
96 pub fn insert_block(&mut self, block: FreeListBlock<M>) {
97 match self.array.binary_search_by(|b| b.cmp(&block)) {
98 Ok(_) => {
99 panic!("Overlapping block found in free list");
100 }
101 Err(index) if self.array.len() > index => match &mut self.array[..=index] {
102 [] => unreachable!(),
103 [next] => {
104 debug_assert!(!next.is_suffix_block(&block));
105
106 if next.is_prefix_block(&block) {
107 next.merge_prefix_block(block);
108 } else {
109 self.array.insert(0, FreeListRegion::from_block(block));
110 }
111 }
112 [.., prev, next] => {
113 debug_assert!(!prev.is_prefix_block(&block));
114 debug_assert!(!next.is_suffix_block(&block));
115
116 if next.is_prefix_block(&block) {
117 next.merge_prefix_block(block);
118
119 if prev.consecutive(&*next) {
120 let next = self.array.remove(index);
121 let prev = &mut self.array[index - 1];
122 prev.merge(next);
123 }
124 } else if prev.is_suffix_block(&block) {
125 prev.merge_suffix_block(block);
126 } else {
127 self.array.insert(index, FreeListRegion::from_block(block));
128 }
129 }
130 },
131 Err(_) => match &mut self.array[..] {
132 [] => self.array.push(FreeListRegion::from_block(block)),
133 [.., prev] => {
134 debug_assert!(!prev.is_prefix_block(&block));
135 if prev.is_suffix_block(&block) {
136 prev.merge_suffix_block(block);
137 } else {
138 self.array.push(FreeListRegion::from_block(block));
139 }
140 }
141 },
142 }
143 }
144
145 pub fn drain(&mut self, keep_last: bool) -> Option<impl Iterator<Item = (M, u64)> + '_> {
146 let len = self.array.len();
149
150 let mut del = 0;
151 {
152 let regions = &mut self.array[..];
153
154 for i in 0..len {
155 if (i < len - 1 || !keep_last) && is_arc_unique(&mut regions[i].memory) {
156 del += 1;
157 } else if del > 0 {
158 regions.swap(i - del, i);
159 }
160 }
161 }
162
163 if del > 0 {
164 Some(self.array.drain(len - del..).map(move |region| {
165 debug_assert_eq!(region.start, 0);
166 (unsafe { arc_unwrap(region.memory) }, region.end)
167 }))
168 } else {
169 None
170 }
171 }
172}
173
174#[derive(Debug)]
175struct FreeListRegion<M> {
176 memory: Arc<M>,
177 ptr: Option<NonNull<u8>>,
178 chunk: u64,
179 start: u64,
180 end: u64,
181}
182
183unsafe impl<M> Sync for FreeListRegion<M> where M: Sync {}
184unsafe impl<M> Send for FreeListRegion<M> where M: Send {}
185
186impl<M> FreeListRegion<M> {
187 pub fn cmp(&self, block: &FreeListBlock<M>) -> Ordering {
188 debug_assert_eq!(
189 Arc::ptr_eq(&self.memory, &block.memory),
190 self.chunk == block.chunk
191 );
192
193 if self.chunk == block.chunk {
194 debug_assert_eq!(
195 Ord::cmp(&self.start, &block.offset),
196 Ord::cmp(&self.end, &(block.offset + block.size)),
197 "Free region {{ start: {}, end: {} }} overlaps with block {{ offset: {}, size: {} }}",
198 self.start,
199 self.end,
200 block.offset,
201 block.size,
202 );
203 }
204
205 Ord::cmp(&self.chunk, &block.chunk).then(Ord::cmp(&self.start, &block.offset))
206 }
207
208 fn from_block(block: FreeListBlock<M>) -> Self {
209 FreeListRegion {
210 memory: block.memory,
211 chunk: block.chunk,
212 ptr: block.ptr,
213 start: block.offset,
214 end: block.offset + block.size,
215 }
216 }
217
218 fn into_block(self) -> FreeListBlock<M> {
219 FreeListBlock {
220 memory: self.memory,
221 chunk: self.chunk,
222 ptr: self.ptr,
223 offset: self.start,
224 size: self.end - self.start,
225 }
226 }
227
228 fn consecutive(&self, other: &Self) -> bool {
229 if self.chunk != other.chunk {
230 return false;
231 }
232
233 debug_assert!(Arc::ptr_eq(&self.memory, &other.memory));
234
235 debug_assert_eq!(
236 Ord::cmp(&self.start, &other.start),
237 Ord::cmp(&self.end, &other.end)
238 );
239
240 self.end == other.start
241 }
242
243 fn merge(&mut self, next: FreeListRegion<M>) {
244 debug_assert!(self.consecutive(&next));
245 self.end = next.end;
246 }
247
248 fn is_prefix_block(&self, block: &FreeListBlock<M>) -> bool {
249 if self.chunk != block.chunk {
250 return false;
251 }
252
253 debug_assert!(Arc::ptr_eq(&self.memory, &block.memory));
254
255 debug_assert_eq!(
256 Ord::cmp(&self.start, &block.offset),
257 Ord::cmp(&self.end, &(block.offset + block.size))
258 );
259
260 self.start == (block.offset + block.size)
261 }
262
263 fn merge_prefix_block(&mut self, block: FreeListBlock<M>) {
264 debug_assert!(self.is_prefix_block(&block));
265 self.start = block.offset;
266 self.ptr = block.ptr;
267 }
268
269 fn is_suffix_block(&self, block: &FreeListBlock<M>) -> bool {
270 if self.chunk != block.chunk {
271 return false;
272 }
273
274 debug_assert!(Arc::ptr_eq(&self.memory, &block.memory));
275
276 debug_assert_eq!(
277 Ord::cmp(&self.start, &block.offset),
278 Ord::cmp(&self.end, &(block.offset + block.size))
279 );
280
281 self.end == block.offset
282 }
283
284 fn merge_suffix_block(&mut self, block: FreeListBlock<M>) {
285 debug_assert!(self.is_suffix_block(&block));
286 self.end += block.size;
287 }
288}
289
290#[derive(Debug)]
291pub struct FreeListBlock<M> {
292 pub memory: Arc<M>,
293 pub ptr: Option<NonNull<u8>>,
294 pub chunk: u64,
295 pub offset: u64,
296 pub size: u64,
297}
298
299unsafe impl<M> Sync for FreeListBlock<M> where M: Sync {}
300unsafe impl<M> Send for FreeListBlock<M> where M: Send {}
301
302#[derive(Debug)]
303pub(crate) struct FreeListAllocator<M> {
304 freelist: FreeList<M>,
305 chunk_size: u64,
306 final_chunk_size: u64,
307 memory_type: u32,
308 props: MemoryPropertyFlags,
309 atom_mask: u64,
310
311 total_allocations: u64,
312 total_deallocations: u64,
313}
314
315impl<M> Drop for FreeListAllocator<M> {
316 fn drop(&mut self) {
317 match Ord::cmp(&self.total_allocations, &self.total_deallocations) {
318 Ordering::Equal => {}
319 Ordering::Greater => {
320 report_error_on_drop!("Not all blocks were deallocated")
321 }
322 Ordering::Less => {
323 report_error_on_drop!("More blocks deallocated than allocated")
324 }
325 }
326
327 if !self.freelist.array.is_empty() {
328 report_error_on_drop!(
329 "FreeListAllocator has free blocks on drop. Allocator should be cleaned"
330 );
331 }
332 }
333}
334
335impl<M> FreeListAllocator<M>
336where
337 M: MemoryBounds + 'static,
338{
339 pub fn new(
340 starting_chunk_size: u64,
341 final_chunk_size: u64,
342 memory_type: u32,
343 props: MemoryPropertyFlags,
344 atom_mask: u64,
345 ) -> Self {
346 debug_assert_eq!(
347 align_down(starting_chunk_size, atom_mask),
348 starting_chunk_size
349 );
350
351 let starting_chunk_size = min(starting_chunk_size, isize::max_value());
352
353 debug_assert_eq!(align_down(final_chunk_size, atom_mask), final_chunk_size);
354 let final_chunk_size = min(final_chunk_size, isize::max_value());
355
356 FreeListAllocator {
357 freelist: FreeList::new(),
358 chunk_size: starting_chunk_size,
359 final_chunk_size,
360 memory_type,
361 props,
362 atom_mask,
363
364 total_allocations: 0,
365 total_deallocations: 0,
366 }
367 }
368
369 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
370 pub unsafe fn alloc(
371 &mut self,
372 device: &impl MemoryDevice<M>,
373 size: u64,
374 align_mask: u64,
375 flags: AllocationFlags,
376 heap: &mut Heap,
377 allocations_remains: &mut u32,
378 ) -> Result<FreeListBlock<M>, AllocationError> {
379 debug_assert!(
380 self.final_chunk_size >= size,
381 "GpuAllocator must not request allocations equal or greater to chunks size"
382 );
383
384 let size = align_up(size, self.atom_mask).expect(
385 "Any value not greater than final chunk size (which is aligned) has to fit for alignment",
386 );
387
388 let align_mask = align_mask | self.atom_mask;
389 let host_visible = self.host_visible();
390
391 if size <= self.chunk_size {
392 if let Some(block) = self.freelist.get_block(align_mask, size) {
394 self.total_allocations += 1;
395 return Ok(block);
396 }
397 }
398
399 if *allocations_remains == 0 {
401 return Err(AllocationError::TooManyObjects);
402 }
403
404 if size > self.chunk_size {
405 let multiple = (size - 1) / self.chunk_size + 1;
406 let multiple = multiple.next_power_of_two();
407
408 self.chunk_size = (self.chunk_size * multiple).min(self.final_chunk_size);
409 }
410
411 let mut memory = device.allocate_memory(self.chunk_size, self.memory_type, flags)?;
412 *allocations_remains -= 1;
413 heap.alloc(self.chunk_size);
414
415 let ptr = if host_visible {
417 match device.map_memory(&mut memory, 0, self.chunk_size) {
418 Ok(ptr) => Some(ptr),
419 Err(DeviceMapError::MapFailed) => {
420 #[cfg(feature = "tracing")]
421 tracing::error!("Failed to map host-visible memory in linear allocator");
422 device.deallocate_memory(memory);
423 *allocations_remains += 1;
424 heap.dealloc(self.chunk_size);
425
426 return Err(AllocationError::OutOfHostMemory);
427 }
428 Err(DeviceMapError::OutOfDeviceMemory) => {
429 return Err(AllocationError::OutOfDeviceMemory);
430 }
431 Err(DeviceMapError::OutOfHostMemory) => {
432 return Err(AllocationError::OutOfHostMemory);
433 }
434 }
435 } else {
436 None
437 };
438
439 let memory = Arc::new(memory);
440 let block =
441 self.freelist
442 .get_block_from_new_memory(memory, self.chunk_size, ptr, align_mask, size);
443
444 if self.chunk_size < self.final_chunk_size {
445 self.chunk_size = (self.chunk_size * 2).min(self.final_chunk_size);
448 }
449
450 self.total_allocations += 1;
451 Ok(block)
452 }
453
454 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
455 pub unsafe fn dealloc(
456 &mut self,
457 device: &impl MemoryDevice<M>,
458 block: FreeListBlock<M>,
459 heap: &mut Heap,
460 allocations_remains: &mut u32,
461 ) {
462 debug_assert!(block.size < self.chunk_size);
463 debug_assert_ne!(block.size, 0);
464 self.freelist.insert_block(block);
465 self.total_deallocations += 1;
466
467 if let Some(memory) = self.freelist.drain(true) {
468 memory.for_each(|(memory, size)| {
469 device.deallocate_memory(memory);
470 *allocations_remains += 1;
471 heap.dealloc(size);
472 });
473 }
474 }
475
476 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
485 pub unsafe fn cleanup(
486 &mut self,
487 device: &impl MemoryDevice<M>,
488 heap: &mut Heap,
489 allocations_remains: &mut u32,
490 ) {
491 if let Some(memory) = self.freelist.drain(false) {
492 memory.for_each(|(memory, size)| {
493 device.deallocate_memory(memory);
494 *allocations_remains += 1;
495 heap.dealloc(size);
496 });
497 }
498
499 #[cfg(feature = "tracing")]
500 {
501 if self.total_allocations == self.total_deallocations && !self.freelist.array.is_empty()
502 {
503 tracing::error!(
504 "Some regions were not deallocated on cleanup, although all blocks are free.
505 This is a bug in `FreeBlockAllocator`.
506 See array of free blocks left:
507 {:#?}",
508 self.freelist.array,
509 );
510 }
511 }
512 }
513
514 fn host_visible(&self) -> bool {
515 self.props.contains(MemoryPropertyFlags::HOST_VISIBLE)
516 }
517}
518
519fn min<L, R>(l: L, r: R) -> L
520where
521 R: core::convert::TryInto<L>,
522 L: Ord,
523{
524 match r.try_into() {
525 Ok(r) => core::cmp::min(l, r),
526 Err(_) => l,
527 }
528}