gpu_alloc/
freelist.rs

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        // Size is within memory region started at `ptr`.
17        // size is within `chunk_size` that fits `isize`.
18        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        // Time to deallocate
147
148        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            // Otherwise there can't be any sufficiently large free blocks
393            if let Some(block) = self.freelist.get_block(align_mask, size) {
394                self.total_allocations += 1;
395                return Ok(block);
396            }
397        }
398
399        // New allocation is required.
400        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        // Map host visible allocations
416        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            // Double next chunk size
446            // Limit to final value.
447            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    /// Deallocates leftover memory objects.
477    /// Should be used before dropping.
478    ///
479    /// # Safety
480    ///
481    /// * `device` must be one with `DeviceProperties` that were provided to create this `GpuAllocator` instance
482    /// * Same `device` instance must be used for all interactions with one `GpuAllocator` instance
483    ///   and memory blocks allocated from it
484    #[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}