Skip to main content

unlit_wgpu/
offset_allocator.rs

1/*
2 * A port of Sebastian Aaltonen's `OffsetAllocator`, taken from
3 * <https://github.com/pcwalton/offset-allocator>.
4 *
5 * Copyright (c) 2023 Sebastian Aaltonen, Patrick Walton
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 */
25
26//! A fast, hard-real-time sub-allocator over one contiguous range.
27//!
28//! An [`Allocator`] owns a single block of `size` units — units being whatever
29//! the caller needs, such as bytes, vertices or indices — and hands out
30//! contiguous sub-ranges of it. It knows nothing about the resource behind the
31//! range, which is what makes it usable for packing many small ranges into one
32//! GPU buffer: allocate the range, then write the data at `offset * unit_size`.
33//!
34//! The algorithm is the two-level segregated fit used by Sebastian Aaltonen's
35//! `OffsetAllocator`: free ranges ("nodes") are filed into 256 bins whose
36//! boundaries follow an 8-bit float distribution (a 3-bit mantissa and a 5-bit
37//! exponent), so that the relative overhead of rounding to a bin stays under
38//! 12.5% for every size class. Two bitfields — one per top-level bin, one per
39//! leaf bin — make the search for the next fitting bin two `trailing_zeros`
40//! instructions, so [`Allocator::allocate`] and [`Allocator::free`] are O(1)
41//! and never allocate.
42//!
43//! Every allocation starts at a multiple of the alignment fixed at
44//! construction, which is what a GPU buffer sub-allocation needs: the size
45//! charged for an allocation is the requested size rounded up to that
46//! alignment.
47//!
48//! # Known limitation
49//!
50//! A free range is filed under the bin of its *rounded-down* size, while an
51//! allocation searches from the bin of its *rounded-up* size. A range whose
52//! size is not exactly representable by a bin is therefore never found by an
53//! allocation of exactly that size, even though it would fit — so
54//! `Allocator::new(size).allocate(size)` fails for most `size` values. Size the
55//! allocator with [`min_allocator_size`] (and, for aligned allocators, pass the
56//! same alignment) to make the initial free range representable:
57//!
58//! ```rust
59//! use unlit_wgpu::offset_allocator::{Allocator, min_allocator_size};
60//! use core::num::NonZeroU32;
61//!
62//! let mut allocator = Allocator::new(min_allocator_size(1000, NonZeroU32::MIN));
63//! assert!(allocator.allocate(1000).is_some());
64//! ```
65
66use core::fmt::{Debug, Formatter, Result as FmtResult};
67use core::num::NonZeroU32;
68
69/// The number of top-level bins.
70const NUM_TOP_BINS: usize = 32;
71
72/// The number of leaf bins per top-level bin.
73const BINS_PER_LEAF: usize = 8;
74
75/// The number of leaf bins, i.e. of distinct size classes.
76const NUM_LEAF_BINS: usize = NUM_TOP_BINS * BINS_PER_LEAF;
77
78/// The bit position of a top-level bin index within a leaf bin index.
79const TOP_BINS_INDEX_SHIFT: u32 = 3;
80
81/// The mask of the leaf bin index within a leaf bin index.
82const LEAF_BINS_INDEX_MASK: u32 = 7;
83
84/// The number of nodes an allocator tracks unless the caller says otherwise.
85///
86/// This matches the default of the original C++ `OffsetAllocator`; it costs
87/// roughly 3.5 MB of metadata, so an allocator that knows it holds fewer
88/// ranges should use [`Allocator::with_max_nodes_and_alignment`].
89const DEFAULT_MAX_NODES: u32 = 128 * 1024;
90
91/// The smallest allocator size that can hold an allocation of `size` units at
92/// the given `alignment`.
93///
94/// See the [module documentation](self#known-limitation) for why an allocator
95/// must be sized this way rather than with `size` itself.
96///
97/// # Panics
98///
99/// Panics if `size` cannot be rounded up to `alignment` within a `u32`.
100pub fn min_allocator_size(size: u32, alignment: NonZeroU32) -> u32 {
101    let aligned = align_up(size, alignment.get()).expect("the aligned size does not fit in a u32");
102    small_float::float_to_uint(small_float::uint_to_float_round_up(aligned))
103}
104
105/// Rounds `size` up to the next multiple of `alignment`.
106///
107/// Returns `None` if the rounded value does not fit in a `u32`.
108fn align_up(size: u32, alignment: u32) -> Option<u32> {
109    let rounded = size.checked_add(alignment - 1)? / alignment * alignment;
110    Some(rounded)
111}
112
113/// An allocator that manages a single contiguous chunk of space and hands out
114/// portions of it as requested.
115///
116/// Allocations never move, and every offset it returns is a multiple of the
117/// alignment it was constructed with.
118///
119/// # Alignment invariant
120///
121/// Every node — allocated or free — starts at a multiple of the alignment. The
122/// initial node starts at zero, and [`Allocator::allocate`] rounds the size it
123/// charges up to the alignment, so the remainder it splits off also starts at
124/// a multiple of it; [`Allocator::free`] merges neighbours without moving any
125/// start. An allocation's offset is the start of its node, so it is aligned by
126/// construction rather than by aligning an offset up and padding in front.
127pub struct Allocator {
128    /// The total size of the managed chunk, in units.
129    size: u32,
130    /// The alignment, in units, that every allocation starts at.
131    alignment: u32,
132    /// The maximum number of ranges — allocated or free — the allocator tracks.
133    ///
134    /// Every allocation needs at least one node, and the free space that
135    /// remains after it needs one more, so this bounds the number of live
136    /// allocations to `max_nodes - 1`.
137    max_nodes: u32,
138    /// The total size of the free ranges, in units.
139    ///
140    /// Fragmentation and rounding mean this is not always allocatable at once,
141    /// but while it is non-zero a one-unit allocation is always possible.
142    free_storage: u32,
143
144    /// A bit per top-level bin, set when that bin holds at least one range.
145    used_bins_top: u32,
146    /// A bit per leaf bin of each top-level bin, set when that leaf bin holds
147    /// at least one range.
148    used_bins: [u8; NUM_TOP_BINS],
149    /// The first node of each leaf bin's linked list of free ranges.
150    bin_indices: [Option<NodeIndex>; NUM_LEAF_BINS],
151
152    /// Every range, allocated or free, indexed by [`NodeIndex`].
153    nodes: Vec<Node>,
154    /// The indexes of the nodes that no range uses, as a stack.
155    free_nodes: Vec<NodeIndex>,
156    /// How many entries of `free_nodes` are part of the stack.
157    num_free_nodes: u32,
158    /// The last node of the neighbor list, i.e. the one that ends the chunk.
159    ///
160    /// Tracked so that [`Allocator::extend`] can append to the chunk in O(1).
161    tail: Option<NodeIndex>,
162}
163
164/// A single allocation, handed out by [`Allocator::allocate`].
165#[derive(Clone, Copy, Debug, PartialEq, Eq)]
166pub struct Allocation {
167    /// The location of this allocation within the managed chunk, in units.
168    ///
169    /// This is a multiple of the allocator's alignment.
170    pub offset: u32,
171    /// The node that tracks this allocation; only meaningful to the allocator.
172    metadata: NodeIndex,
173}
174
175/// Provides a summary of the state of the allocator, including space remaining.
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub struct StorageReport {
178    /// The amount of free space left.
179    pub total_free_space: u32,
180    /// The maximum potential size of a single contiguous allocation.
181    pub largest_free_region: u32,
182}
183
184/// A range of the managed chunk: either an allocation or the free space around
185/// one.
186#[derive(Clone, Copy, Default)]
187struct Node {
188    /// Where the range starts, in units.
189    data_offset: u32,
190    /// How large the range is, in units.
191    data_size: u32,
192    /// The previous node of the size bin's list, when the range is free.
193    bin_list_prev: Option<NodeIndex>,
194    /// The next node of the size bin's list, when the range is free.
195    bin_list_next: Option<NodeIndex>,
196    /// The range that precedes this one in the chunk, allocated or free.
197    neighbor_prev: Option<NodeIndex>,
198    /// The range that follows this one in the chunk, allocated or free.
199    neighbor_next: Option<NodeIndex>,
200    /// Whether this range is handed out.
201    used: bool,
202}
203
204/// The index of a [`Node`].
205///
206/// The index is stored biased by one inside a [`NonZeroU32`], so that
207/// `Option<NodeIndex>` stays four bytes wide without depending on `nonmax`.
208#[derive(Clone, Copy, PartialEq, Eq)]
209struct NodeIndex(NonZeroU32);
210
211impl NodeIndex {
212    /// Wraps the zero-based `index` of a node.
213    ///
214    /// # Panics
215    ///
216    /// Panics if `index` is `u32::MAX`, which [`Allocator::with_max_nodes_and_alignment`]
217    /// rules out.
218    fn new(index: u32) -> Self {
219        match NonZeroU32::new(index + 1) {
220            Some(index) => Self(index),
221            None => unreachable!(
222                "node indices are bounded by `Allocator::with_max_nodes_and_alignment`"
223            ),
224        }
225    }
226
227    /// The zero-based index of the node.
228    fn get(self) -> usize {
229        (self.0.get() - 1) as usize
230    }
231}
232
233impl Debug for NodeIndex {
234    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
235        self.get().fmt(f)
236    }
237}
238
239impl Allocator {
240    /// Creates an allocator over `size` units, with unit alignment and the
241    /// default node limit.
242    pub fn new(size: u32) -> Self {
243        Self::with_max_nodes_and_alignment(size, DEFAULT_MAX_NODES, NonZeroU32::MIN)
244    }
245
246    /// Creates an allocator over `size` units where every allocation starts at
247    /// a multiple of `alignment`, tracking at most `max_nodes` ranges.
248    ///
249    /// # Panics
250    ///
251    /// Panics if `max_nodes` is zero or leaves no room for the node index bias.
252    pub fn with_max_nodes_and_alignment(size: u32, max_nodes: u32, alignment: NonZeroU32) -> Self {
253        assert!(max_nodes > 0, "an allocator needs at least one node");
254        assert!(
255            max_nodes < u32::MAX,
256            "an allocator cannot track u32::MAX nodes"
257        );
258
259        let mut this = Self {
260            size,
261            alignment: alignment.get(),
262            max_nodes,
263            free_storage: 0,
264            used_bins_top: 0,
265            used_bins: [0; NUM_TOP_BINS],
266            bin_indices: [None; NUM_LEAF_BINS],
267            nodes: Vec::new(),
268            free_nodes: Vec::new(),
269            num_free_nodes: 0,
270            tail: None,
271        };
272        this.reset();
273        this
274    }
275
276    /// Clears out all allocations, returning the allocator to its initial state.
277    pub fn reset(&mut self) {
278        self.free_storage = 0;
279        self.used_bins_top = 0;
280        self.used_bins = [0; NUM_TOP_BINS];
281        self.bin_indices = [None; NUM_LEAF_BINS];
282        self.nodes = vec![Node::default(); self.max_nodes as usize];
283
284        // The free list is a stack. Nodes go in in reverse order so that index
285        // zero pops first.
286        self.free_nodes = (0..self.max_nodes)
287            .map(|i| NodeIndex::new(self.max_nodes - i - 1))
288            .collect();
289        self.num_free_nodes = self.max_nodes;
290
291        // Start with the whole chunk as one free range; the algorithm splits
292        // remainders off it as it allocates.
293        let only_node = self.insert_node_into_bin(self.size, 0);
294        self.tail = Some(only_node);
295    }
296
297    /// Allocates a range of `size` units and returns its allocation.
298    ///
299    /// The range starts at a multiple of the allocator's alignment, and the
300    /// size charged for it is `size` rounded up to that alignment; see
301    /// [`Allocator::allocation_size`]. Returns `None` if no free range is large
302    /// enough, or if the allocator has no node left to describe the allocation.
303    pub fn allocate(&mut self, size: u32) -> Option<Allocation> {
304        // Out of nodes?
305        if self.num_free_nodes == 0 {
306            return None;
307        }
308
309        // Round the size up to the alignment. Every node starts at a multiple
310        // of the alignment (see the invariant on `Allocator`), so rounding the
311        // size up leaves the remainder starting at a multiple of it too, and
312        // the offset handed out is aligned without any padding in front.
313        let size = align_up(size, self.alignment)?;
314
315        // Round up to the bin index that is guaranteed to fit the allocation:
316        // the lowest bin whose ranges are all at least `size` units.
317        let min_bin_index = small_float::uint_to_float_round_up(size);
318
319        let min_top_bin_index = min_bin_index >> TOP_BINS_INDEX_SHIFT;
320        let min_leaf_bin_index = min_bin_index & LEAF_BINS_INDEX_MASK;
321
322        let mut top_bin_index = min_top_bin_index;
323        let mut leaf_bin_index = None;
324
325        // If the top-level bin exists, scan its leaf bins. This can fail (no
326        // space).
327        if (self.used_bins_top & (1 << top_bin_index)) != 0 {
328            leaf_bin_index = find_lowest_bit_set_after(
329                u32::from(self.used_bins[top_bin_index as usize]),
330                min_leaf_bin_index,
331            );
332        }
333
334        // If we didn't find space in the top-level bin, search the next occupied
335        // top-level bin. Every leaf bin there fits the allocation, since the
336        // top-level bin index was rounded up, so start the leaf search at bit 0.
337        let leaf_bin_index = match leaf_bin_index {
338            Some(leaf_bin_index) => leaf_bin_index,
339            None => {
340                top_bin_index =
341                    find_lowest_bit_set_after(self.used_bins_top, min_top_bin_index + 1)?;
342
343                // This search can't fail: the top-level bit is set, so at least
344                // one leaf bit of that bin is set too.
345                self.used_bins[top_bin_index as usize].trailing_zeros()
346            }
347        };
348
349        let bin_index = (top_bin_index << TOP_BINS_INDEX_SHIFT) | leaf_bin_index;
350
351        // Pop the first node of the bin. The bin's list head becomes the next
352        // node.
353        let node_index = self.bin_indices[bin_index as usize].unwrap();
354        let node = &mut self.nodes[node_index.get()];
355        let node_total_size = node.data_size;
356        node.data_size = size;
357        node.used = true;
358        self.bin_indices[bin_index as usize] = node.bin_list_next;
359        if let Some(bin_list_next) = node.bin_list_next {
360            self.nodes[bin_list_next.get()].bin_list_prev = None;
361        }
362        self.free_storage -= node_total_size;
363
364        // Bin empty?
365        if self.bin_indices[bin_index as usize].is_none() {
366            // Remove a leaf bin mask bit.
367            self.used_bins[top_bin_index as usize] &= !(1 << leaf_bin_index);
368
369            // All leaf bins empty?
370            if self.used_bins[top_bin_index as usize] == 0 {
371                // Remove a top-level bin mask bit.
372                self.used_bins_top &= !(1 << top_bin_index);
373            }
374        }
375
376        // Push the remainder back into a lower bin.
377        let remainder_size = node_total_size - size;
378        if remainder_size > 0 {
379            let Node {
380                data_offset,
381                neighbor_next,
382                ..
383            } = self.nodes[node_index.get()];
384
385            let new_node_index = self.insert_node_into_bin(remainder_size, data_offset + size);
386
387            // Link the nodes next to each other so that we can merge them later
388            // if both are free, and point the old next neighbor at the node in
389            // the middle.
390            let node = &mut self.nodes[node_index.get()];
391            if let Some(neighbor_next) = node.neighbor_next {
392                self.nodes[neighbor_next.get()].neighbor_prev = Some(new_node_index);
393            } else {
394                // The split node ended the chunk; the remainder does now.
395                self.tail = Some(new_node_index);
396            }
397            self.nodes[new_node_index.get()].neighbor_prev = Some(node_index);
398            self.nodes[new_node_index.get()].neighbor_next = neighbor_next;
399            self.nodes[node_index.get()].neighbor_next = Some(new_node_index);
400        }
401
402        let node = &self.nodes[node_index.get()];
403        Some(Allocation {
404            offset: node.data_offset,
405            metadata: node_index,
406        })
407    }
408
409    /// Frees an allocation, returning the data to the allocator.
410    ///
411    /// # Panics
412    ///
413    /// Panics if the allocation was already freed.
414    pub fn free(&mut self, allocation: Allocation) {
415        let node_index = allocation.metadata;
416
417        // Merge with the neighbors, reading them before they are removed from
418        // their bin.
419        let Node {
420            data_offset: mut offset,
421            data_size: mut size,
422            used,
423            ..
424        } = self.nodes[node_index.get()];
425
426        // Double free check.
427        assert!(used, "the allocation was already freed");
428
429        if let Some(neighbor_prev) = self.nodes[node_index.get()].neighbor_prev {
430            let prev_node = self.nodes[neighbor_prev.get()];
431            if !prev_node.used {
432                // Previous (contiguous) free node: change the offset to the
433                // previous node's offset and sum the sizes.
434                offset = prev_node.data_offset;
435                size += prev_node.data_size;
436
437                debug_assert_eq!(prev_node.neighbor_next, Some(node_index));
438                self.nodes[node_index.get()].neighbor_prev = prev_node.neighbor_prev;
439
440                // Remove the node from the bin's list and put it in the free
441                // list.
442                self.remove_node_from_bin(neighbor_prev);
443            }
444        }
445
446        if let Some(neighbor_next) = self.nodes[node_index.get()].neighbor_next {
447            let next_node = self.nodes[neighbor_next.get()];
448            if !next_node.used {
449                // Next (contiguous) free node: the offset stays the same and
450                // the sizes are summed.
451                size += next_node.data_size;
452
453                debug_assert_eq!(next_node.neighbor_prev, Some(node_index));
454                self.nodes[node_index.get()].neighbor_next = next_node.neighbor_next;
455
456                // Remove the node from the bin's list and put it in the free
457                // list.
458                self.remove_node_from_bin(neighbor_next);
459            }
460        }
461
462        let Node {
463            neighbor_next,
464            neighbor_prev,
465            ..
466        } = self.nodes[node_index.get()];
467
468        // Put the node back into the free list. It is the one that
469        // `insert_node_into_bin` pops below, so the combined free range reuses
470        // its index.
471        self.free_nodes[self.num_free_nodes as usize] = node_index;
472        self.num_free_nodes += 1;
473
474        // Insert the combined free range into a bin.
475        let combined_node_index = self.insert_node_into_bin(size, offset);
476
477        // Connect the neighbors to the new combined node.
478        if let Some(neighbor_next) = neighbor_next {
479            self.nodes[combined_node_index.get()].neighbor_next = Some(neighbor_next);
480            self.nodes[neighbor_next.get()].neighbor_prev = Some(combined_node_index);
481        } else {
482            // Nothing follows the combined range, so it ends the chunk.
483            self.tail = Some(combined_node_index);
484        }
485        if let Some(neighbor_prev) = neighbor_prev {
486            self.nodes[combined_node_index.get()].neighbor_prev = Some(neighbor_prev);
487            self.nodes[neighbor_prev.get()].neighbor_next = Some(combined_node_index);
488        }
489    }
490
491    /// Grows the managed chunk by `additional` units, appended at its end.
492    ///
493    /// The new space starts as one free range. Existing allocations keep their
494    /// offsets, which is what lets a caller grow the buffer behind the
495    /// allocator — copying its contents to a larger one — without reallocating
496    /// anything.
497    ///
498    /// Returns `false` without changing anything if the allocator has no free
499    /// node to describe the new range, or if the size would overflow a `u32`.
500    /// The added size is rounded up to the alignment, so the appended range
501    /// starts aligned like every other node.
502    pub fn extend(&mut self, additional: u32) -> bool {
503        let Some(additional) = align_up(additional, self.alignment) else {
504            return false;
505        };
506        let Some(size) = self.size.checked_add(additional) else {
507            return false;
508        };
509
510        // The chunk used to end at `self.size`, which is aligned because every
511        // node's size is rounded up to the alignment.
512        match self.tail {
513            // The chunk ends in free space, which the appended space is
514            // contiguous with: grow that node instead of adding a neighbour, so
515            // the two never end up as separate ranges that can only be merged
516            // by a later `free`.
517            Some(tail) if !self.nodes[tail.get()].used => {
518                let tail_size = self.nodes[tail.get()].data_size;
519                let Some(grown) = tail_size.checked_add(additional) else {
520                    return false;
521                };
522                self.rebin_node(tail, grown);
523            }
524            // The chunk ends in an allocation (or is empty): append a new free
525            // range after it.
526            _ => {
527                if self.num_free_nodes == 0 {
528                    return false;
529                }
530                let new_node_index = self.insert_node_into_bin(additional, self.size);
531                if let Some(tail) = self.tail {
532                    self.nodes[tail.get()].neighbor_next = Some(new_node_index);
533                }
534                self.nodes[new_node_index.get()].neighbor_prev = self.tail;
535                self.tail = Some(new_node_index);
536            }
537        }
538
539        self.size = size;
540        true
541    }
542
543    /// Moves a free node to the bin for its new `size`, keeping its place in
544    /// the neighbor list.
545    ///
546    /// Used to grow a free range in place when the space behind it is added to
547    /// the chunk.
548    fn rebin_node(&mut self, node_index: NodeIndex, size: u32) {
549        self.remove_node_from_bin(node_index);
550
551        // `remove_node_from_bin` put the node into the free list and took its
552        // old size out of `free_storage`; take it back out of the free list and
553        // re-insert it under its new size. Its neighbor links survive
554        // `insert_node_into_bin`, which only resets the bin links.
555        let node = self.nodes[node_index.get()];
556        self.num_free_nodes -= 1;
557
558        let bin_index = small_float::uint_to_float_round_down(size);
559        let top_bin_index = bin_index >> TOP_BINS_INDEX_SHIFT;
560        let leaf_bin_index = bin_index & LEAF_BINS_INDEX_MASK;
561        if self.bin_indices[bin_index as usize].is_none() {
562            self.used_bins[top_bin_index as usize] |= 1 << leaf_bin_index;
563            self.used_bins_top |= 1 << top_bin_index;
564        }
565
566        let top_node_index = self.bin_indices[bin_index as usize];
567        self.nodes[node_index.get()] = Node {
568            data_size: size,
569            // The node goes in at the head of its new bin's list, so it has no
570            // predecessor there; `node.bin_list_prev` is stale at this point.
571            bin_list_prev: None,
572            bin_list_next: top_node_index,
573            ..node
574        };
575        if let Some(top_node_index) = top_node_index {
576            self.nodes[top_node_index.get()].bin_list_prev = Some(node_index);
577        }
578        self.bin_indices[bin_index as usize] = Some(node_index);
579        self.free_storage += size;
580    }
581
582    /// Creates a free node and inserts it at the head of the bin for its size.
583    ///
584    /// The caller is responsible for linking the node into the neighbor list.
585    fn insert_node_into_bin(&mut self, size: u32, data_offset: u32) -> NodeIndex {
586        // Round down to a bin index so that every range in a bin can hold any
587        // allocation that bin is searched for.
588        let bin_index = small_float::uint_to_float_round_down(size);
589
590        let top_bin_index = bin_index >> TOP_BINS_INDEX_SHIFT;
591        let leaf_bin_index = bin_index & LEAF_BINS_INDEX_MASK;
592
593        // Was the bin empty before?
594        if self.bin_indices[bin_index as usize].is_none() {
595            // Set the bin mask bits.
596            self.used_bins[top_bin_index as usize] |= 1 << leaf_bin_index;
597            self.used_bins_top |= 1 << top_bin_index;
598        }
599
600        // Take a node from the free list and insert it at the head of the bin's
601        // list (its next node is the old head).
602        let top_node_index = self.bin_indices[bin_index as usize];
603        self.num_free_nodes -= 1;
604        let node_index = self.free_nodes[self.num_free_nodes as usize];
605        self.nodes[node_index.get()] = Node {
606            data_offset,
607            data_size: size,
608            bin_list_next: top_node_index,
609            ..Node::default()
610        };
611        if let Some(top_node_index) = top_node_index {
612            self.nodes[top_node_index.get()].bin_list_prev = Some(node_index);
613        }
614        self.bin_indices[bin_index as usize] = Some(node_index);
615
616        self.free_storage += size;
617        node_index
618    }
619
620    /// Removes a node from its bin and puts it back into the free list.
621    ///
622    /// The caller is responsible for fixing up the neighbor list, and must read
623    /// anything it still needs from the node *before* calling this: the node
624    /// may be handed out again by the next [`Allocator::insert_node_into_bin`].
625    fn remove_node_from_bin(&mut self, node_index: NodeIndex) {
626        // Copy the node to work around the borrow checker.
627        let node = self.nodes[node_index.get()];
628
629        match node.bin_list_prev {
630            Some(bin_list_prev) => {
631                // Easy case: the node has a predecessor, so unlink it from the
632                // middle of the list.
633                self.nodes[bin_list_prev.get()].bin_list_next = node.bin_list_next;
634                if let Some(bin_list_next) = node.bin_list_next {
635                    self.nodes[bin_list_next.get()].bin_list_prev = node.bin_list_prev;
636                }
637            }
638            None => {
639                // Hard case: the node is the head of a bin, so find the bin.
640                //
641                // Round down to a bin index to stay consistent with
642                // `insert_node_into_bin`.
643                let bin_index = small_float::uint_to_float_round_down(node.data_size);
644
645                let top_bin_index = bin_index >> TOP_BINS_INDEX_SHIFT;
646                let leaf_bin_index = bin_index & LEAF_BINS_INDEX_MASK;
647
648                self.bin_indices[bin_index as usize] = node.bin_list_next;
649                if let Some(bin_list_next) = node.bin_list_next {
650                    self.nodes[bin_list_next.get()].bin_list_prev = None;
651                }
652
653                // Bin empty?
654                if self.bin_indices[bin_index as usize].is_none() {
655                    // Remove a leaf bin mask bit.
656                    self.used_bins[top_bin_index as usize] &= !(1 << leaf_bin_index);
657
658                    // All leaf bins empty?
659                    if self.used_bins[top_bin_index as usize] == 0 {
660                        // Remove a top-level bin mask bit.
661                        self.used_bins_top &= !(1 << top_bin_index);
662                    }
663                }
664            }
665        }
666
667        // Put the node back into the free list.
668        self.free_nodes[self.num_free_nodes as usize] = node_index;
669        self.num_free_nodes += 1;
670
671        self.free_storage -= node.data_size;
672    }
673
674    /// Returns the size an allocation reserved, in units.
675    ///
676    /// This is the size requested at allocation time rounded up to the
677    /// allocator's alignment, which may be larger than what the caller asked
678    /// for.
679    pub fn allocation_size(&self, allocation: Allocation) -> u32 {
680        self.nodes[allocation.metadata.get()].data_size
681    }
682
683    /// The total size of the managed chunk, in units.
684    ///
685    /// This grows with [`Allocator::extend`].
686    pub fn size(&self) -> u32 {
687        self.size
688    }
689
690    /// The alignment, in units, that every allocation starts at.
691    pub fn alignment(&self) -> u32 {
692        self.alignment
693    }
694
695    /// Returns a summary of the free space remaining, and of the largest
696    /// allocation that can be made in one piece.
697    pub fn storage_report(&self) -> StorageReport {
698        let mut largest_free_region = 0;
699        let mut free_storage = 0;
700
701        // Out of nodes? Then no free space can be handed out.
702        if self.num_free_nodes > 0 {
703            free_storage = self.free_storage;
704            if self.used_bins_top > 0 {
705                let top_bin_index = 31 - self.used_bins_top.leading_zeros();
706                let leaf_bin_index =
707                    31 - u32::from(self.used_bins[top_bin_index as usize]).leading_zeros();
708                largest_free_region = small_float::float_to_uint(
709                    (top_bin_index << TOP_BINS_INDEX_SHIFT) | leaf_bin_index,
710                );
711                debug_assert!(free_storage >= largest_free_region);
712            }
713        }
714
715        StorageReport {
716            total_free_space: free_storage,
717            largest_free_region,
718        }
719    }
720}
721
722impl Debug for Allocator {
723    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
724        let report = self.storage_report();
725        f.debug_struct("Allocator")
726            .field("size", &self.size)
727            .field("alignment", &self.alignment)
728            .field("max_nodes", &self.max_nodes)
729            .field("total_free_space", &report.total_free_space)
730            .field("largest_free_region", &report.largest_free_region)
731            .field("free_nodes", &self.num_free_nodes)
732            .finish()
733    }
734}
735
736/// Returns the index of the lowest set bit of `bit_mask` at or after
737/// `start_bit_index`, or `None` if there is none.
738fn find_lowest_bit_set_after(bit_mask: u32, start_bit_index: u32) -> Option<u32> {
739    debug_assert!(start_bit_index < 32);
740    let mask_before_start_index = (1 << start_bit_index) - 1;
741    let bits_after = bit_mask & !mask_before_start_index;
742    if bits_after == 0 {
743        None
744    } else {
745        Some(bits_after.trailing_zeros())
746    }
747}
748
749/// The 8-bit float size distribution the bins follow.
750mod small_float {
751    /// The number of bits of the mantissa.
752    pub const MANTISSA_BITS: u32 = 3;
753    /// The value of the hidden high mantissa bit.
754    pub const MANTISSA_VALUE: u32 = 1 << MANTISSA_BITS;
755    /// The mask of the mantissa.
756    pub const MANTISSA_MASK: u32 = MANTISSA_VALUE - 1;
757
758    /// Returns the bin index of the lowest bin that can hold `size` units.
759    ///
760    /// Bin sizes follow a floating point (exponent + mantissa) distribution —
761    /// a piecewise linear approximation of a logarithm — which keeps the
762    /// average overhead of rounding to a bin the same for every size class.
763    pub fn uint_to_float_round_up(size: u32) -> u32 {
764        let mut exp = 0;
765        let mut mantissa;
766
767        if size < MANTISSA_VALUE {
768            // Denorm: 0..(MANTISSA_VALUE-1)
769            mantissa = size
770        } else {
771            // Normalized: the hidden high bit is always 1 and is not stored,
772            // just like a float.
773            let highest_set_bit = 31 - size.leading_zeros();
774            let mantissa_start_bit = highest_set_bit - MANTISSA_BITS;
775            exp = mantissa_start_bit + 1;
776            mantissa = (size >> mantissa_start_bit) & MANTISSA_MASK;
777
778            // Round up.
779            let low_bits_mask = (1 << mantissa_start_bit) - 1;
780            if (size & low_bits_mask) != 0 {
781                mantissa += 1;
782            }
783        }
784
785        // `+` rather than `|` lets a rounded-up mantissa overflow into the
786        // exponent.
787        (exp << MANTISSA_BITS) + mantissa
788    }
789
790    /// Returns the bin index of the highest bin that can hold `size` units.
791    pub fn uint_to_float_round_down(size: u32) -> u32 {
792        let mut exp = 0;
793        let mantissa;
794
795        if size < MANTISSA_VALUE {
796            // Denorm: 0..(MANTISSA_VALUE-1)
797            mantissa = size
798        } else {
799            // Normalized: the hidden high bit is always 1 and is not stored,
800            // just like a float.
801            let highest_set_bit = 31 - size.leading_zeros();
802            let mantissa_start_bit = highest_set_bit - MANTISSA_BITS;
803            exp = mantissa_start_bit + 1;
804            mantissa = (size >> mantissa_start_bit) & MANTISSA_MASK;
805        }
806
807        (exp << MANTISSA_BITS) | mantissa
808    }
809
810    /// Returns the size that the bin with the given index represents.
811    pub fn float_to_uint(float_value: u32) -> u32 {
812        let exponent = float_value >> MANTISSA_BITS;
813        let mantissa = float_value & MANTISSA_MASK;
814        if exponent == 0 {
815            mantissa
816        } else {
817            (mantissa | MANTISSA_VALUE) << (exponent - 1)
818        }
819    }
820}
821
822#[cfg(test)]
823mod tests {
824    use core::num::NonZeroU32;
825
826    use super::*;
827
828    /// The alignment every allocation of a buffer sub-allocator needs.
829    const COPY_ALIGNMENT: NonZeroU32 = NonZeroU32::new(wgpu::COPY_BUFFER_ALIGNMENT as u32).unwrap();
830
831    #[test]
832    fn small_float_uint_to_float() {
833        // Denorms, exp=1 and exp=2 + mantissa = 0 are all precise.
834        // NOTE: Assuming 8 values (3 bit) mantissa.
835        // If this test fails, please change this assumption!
836        let precise_number_count = 17;
837        for i in 0..precise_number_count {
838            let round_up = small_float::uint_to_float_round_up(i);
839            let round_down = small_float::uint_to_float_round_down(i);
840            assert_eq!(i, round_up);
841            assert_eq!(i, round_down);
842        }
843
844        // Test some random picked numbers.
845        struct NumberFloatUpDown {
846            number: u32,
847            up: u32,
848            down: u32,
849        }
850
851        let test_data = [
852            NumberFloatUpDown {
853                number: 17,
854                up: 17,
855                down: 16,
856            },
857            NumberFloatUpDown {
858                number: 118,
859                up: 39,
860                down: 38,
861            },
862            NumberFloatUpDown {
863                number: 1024,
864                up: 64,
865                down: 64,
866            },
867            NumberFloatUpDown {
868                number: 65536,
869                up: 112,
870                down: 112,
871            },
872            NumberFloatUpDown {
873                number: 529445,
874                up: 137,
875                down: 136,
876            },
877            NumberFloatUpDown {
878                number: 1048575,
879                up: 144,
880                down: 143,
881            },
882        ];
883
884        for v in test_data {
885            let round_up = small_float::uint_to_float_round_up(v.number);
886            let round_down = small_float::uint_to_float_round_down(v.number);
887            assert_eq!(round_up, v.up);
888            assert_eq!(round_down, v.down);
889        }
890    }
891
892    #[test]
893    fn small_float_float_to_uint() {
894        // Denorms, exp=1 and exp=2 + mantissa = 0 are all precise.
895        // NOTE: Assuming 8 values (3 bit) mantissa.
896        // If this test fails, please change this assumption!
897        let precise_number_count = 17;
898        for i in 0..precise_number_count {
899            let v = small_float::float_to_uint(i);
900            assert_eq!(i, v);
901        }
902
903        // Test that float->uint->float conversion is precise for all numbers.
904        // NOTE: Test values < 240. 240->4G = overflows 32 bit integer.
905        for i in 0..240 {
906            let v = small_float::float_to_uint(i);
907            let round_up = small_float::uint_to_float_round_up(v);
908            let round_down = small_float::uint_to_float_round_down(v);
909            assert_eq!(i, round_up);
910            assert_eq!(i, round_down);
911        }
912    }
913
914    #[test]
915    fn basic_offset_allocator() {
916        let mut allocator = Allocator::new(1024 * 1024 * 256);
917        let a = allocator.allocate(1337).unwrap();
918        let offset: u32 = a.offset;
919        assert_eq!(offset, 0);
920        allocator.free(a);
921    }
922
923    #[test]
924    fn allocate_offset_allocator_simple() {
925        let mut allocator = Allocator::new(1024 * 1024 * 256);
926
927        // Free merges neighbor empty nodes. Next allocation should also have
928        // offset = 0.
929        let a = allocator.allocate(0).unwrap();
930        assert_eq!(a.offset, 0);
931
932        let b = allocator.allocate(1).unwrap();
933        assert_eq!(b.offset, 0);
934
935        let c = allocator.allocate(123).unwrap();
936        assert_eq!(c.offset, 1);
937
938        let d = allocator.allocate(1234).unwrap();
939        assert_eq!(d.offset, 124);
940
941        allocator.free(a);
942        allocator.free(b);
943        allocator.free(c);
944        allocator.free(d);
945
946        // End: Validate that allocator has no fragmentation left. Should be
947        // 100% clean.
948        let validate_all = allocator.allocate(1024 * 1024 * 256).unwrap();
949        assert_eq!(validate_all.offset, 0);
950        allocator.free(validate_all);
951    }
952
953    #[test]
954    fn allocate_offset_allocator_merge_trivial() {
955        let mut allocator = Allocator::new(1024 * 1024 * 256);
956
957        // Free merges neighbor empty nodes. Next allocation should also have
958        // offset = 0.
959        let a = allocator.allocate(1337).unwrap();
960        assert_eq!(a.offset, 0);
961        allocator.free(a);
962
963        let b = allocator.allocate(1337).unwrap();
964        assert_eq!(b.offset, 0);
965        allocator.free(b);
966
967        // End: Validate that allocator has no fragmentation left. Should be
968        // 100% clean.
969        let validate_all = allocator.allocate(1024 * 1024 * 256).unwrap();
970        assert_eq!(validate_all.offset, 0);
971        allocator.free(validate_all);
972    }
973
974    #[test]
975    fn allocate_offset_allocator_reuse_trivial() {
976        let mut allocator = Allocator::new(1024 * 1024 * 256);
977
978        // The allocator should reuse the node freed by A, since the allocation
979        // C fits in the same bin (using a pow2 size to be sure).
980        let a = allocator.allocate(1024).unwrap();
981        assert_eq!(a.offset, 0);
982
983        let b = allocator.allocate(3456).unwrap();
984        assert_eq!(b.offset, 1024);
985
986        allocator.free(a);
987
988        let c = allocator.allocate(1024).unwrap();
989        assert_eq!(c.offset, 0);
990
991        allocator.free(c);
992        allocator.free(b);
993
994        // End: Validate that allocator has no fragmentation left. Should be
995        // 100% clean.
996        let validate_all = allocator.allocate(1024 * 1024 * 256).unwrap();
997        assert_eq!(validate_all.offset, 0);
998        allocator.free(validate_all);
999    }
1000
1001    #[test]
1002    fn allocate_offset_allocator_reuse_complex() {
1003        let mut allocator = Allocator::new(1024 * 1024 * 256);
1004
1005        // The allocator should not reuse the node freed by A, since the
1006        // allocation C doesn't fit in the same bin. However nodes D and E fit
1007        // there and should reuse the node from A.
1008        let a = allocator.allocate(1024).unwrap();
1009        assert_eq!(a.offset, 0);
1010
1011        let b = allocator.allocate(3456).unwrap();
1012        assert_eq!(b.offset, 1024);
1013
1014        allocator.free(a);
1015
1016        let c = allocator.allocate(2345).unwrap();
1017        assert_eq!(c.offset, 1024 + 3456);
1018
1019        let d = allocator.allocate(456).unwrap();
1020        assert_eq!(d.offset, 0);
1021
1022        let e = allocator.allocate(512).unwrap();
1023        assert_eq!(e.offset, 456);
1024
1025        let report = allocator.storage_report();
1026        assert_eq!(
1027            report.total_free_space,
1028            1024 * 1024 * 256 - 3456 - 2345 - 456 - 512
1029        );
1030        assert_ne!(report.largest_free_region, report.total_free_space);
1031
1032        allocator.free(c);
1033        allocator.free(d);
1034        allocator.free(b);
1035        allocator.free(e);
1036
1037        // End: Validate that allocator has no fragmentation left. Should be
1038        // 100% clean.
1039        let validate_all = allocator.allocate(1024 * 1024 * 256).unwrap();
1040        assert_eq!(validate_all.offset, 0);
1041        allocator.free(validate_all);
1042    }
1043
1044    #[test]
1045    fn allocate_offset_allocator_zero_fragmentation() {
1046        let mut allocator = Allocator::new(1024 * 1024 * 256);
1047
1048        // Allocate 256x 1MB. Should fit. Then free four random slots and
1049        // reallocate four slots. Plus free four contiguous slots and allocate a
1050        // 4x larger slot. All must be zero fragmentation!
1051        let mut allocations: [_; 256] = core::array::from_fn(|i| {
1052            let allocation = allocator.allocate(1024 * 1024).unwrap();
1053            assert_eq!(allocation.offset, i as u32 * 1024 * 1024);
1054            allocation
1055        });
1056
1057        let report = allocator.storage_report();
1058        assert_eq!(report.total_free_space, 0);
1059        assert_eq!(report.largest_free_region, 0);
1060
1061        // Free four random slots.
1062        allocator.free(allocations[243]);
1063        allocator.free(allocations[5]);
1064        allocator.free(allocations[123]);
1065        allocator.free(allocations[95]);
1066
1067        // Free four contiguous slots (the allocator must merge).
1068        allocator.free(allocations[151]);
1069        allocator.free(allocations[152]);
1070        allocator.free(allocations[153]);
1071        allocator.free(allocations[154]);
1072
1073        allocations[243] = allocator.allocate(1024 * 1024).unwrap();
1074        allocations[5] = allocator.allocate(1024 * 1024).unwrap();
1075        allocations[123] = allocator.allocate(1024 * 1024).unwrap();
1076        allocations[95] = allocator.allocate(1024 * 1024).unwrap();
1077        allocations[151] = allocator.allocate(1024 * 1024 * 4).unwrap(); // 4x larger
1078
1079        for (i, allocation) in allocations.iter().enumerate() {
1080            if !(152..155).contains(&i) {
1081                allocator.free(*allocation);
1082            }
1083        }
1084
1085        let report2 = allocator.storage_report();
1086        assert_eq!(report2.total_free_space, 1024 * 1024 * 256);
1087        assert_eq!(report2.largest_free_region, 1024 * 1024 * 256);
1088
1089        // End: Validate that allocator has no fragmentation left. Should be
1090        // 100% clean.
1091        let validate_all = allocator.allocate(1024 * 1024 * 256).unwrap();
1092        assert_eq!(validate_all.offset, 0);
1093        allocator.free(validate_all);
1094    }
1095
1096    #[test]
1097    fn ext_min_allocator_size() {
1098        // Randomly generated integers on a log distribution, σ = 10.
1099        static TEST_OBJECT_SIZES: [u32; 42] = [
1100            0, 1, 2, 3, 4, 5, 8, 17, 23, 36, 51, 68, 87, 151, 165, 167, 201, 223, 306, 346, 394,
1101            411, 806, 969, 1404, 1798, 2236, 4281, 4745, 13989, 21095, 26594, 27146, 29679, 144685,
1102            153878, 495127, 727999, 1377073, 9440387, 41994490, 68520116,
1103        ];
1104
1105        for needed_object_size in TEST_OBJECT_SIZES {
1106            let allocator_size = min_allocator_size(needed_object_size, NonZeroU32::MIN);
1107            let mut allocator = Allocator::new(allocator_size);
1108            assert!(allocator.allocate(needed_object_size).is_some());
1109        }
1110    }
1111
1112    #[test]
1113    fn aligned_allocations_are_contiguous_and_aligned() {
1114        let mut allocator =
1115            Allocator::with_max_nodes_and_alignment(1 << 20, DEFAULT_MAX_NODES, COPY_ALIGNMENT);
1116
1117        // Three units round up to four, so the second allocation starts right
1118        // after the padded first one, without a gap.
1119        let a = allocator.allocate(3).unwrap();
1120        assert_eq!(a.offset, 0);
1121        assert_eq!(allocator.allocation_size(a), 4);
1122
1123        let b = allocator.allocate(5).unwrap();
1124        assert_eq!(b.offset, 4);
1125        assert_eq!(allocator.allocation_size(b), 8);
1126
1127        let c = allocator.allocate(4).unwrap();
1128        assert_eq!(c.offset, 12);
1129        assert_eq!(allocator.allocation_size(c), 4);
1130
1131        for allocation in [a, b, c] {
1132            assert_eq!(allocation.offset % COPY_ALIGNMENT.get(), 0);
1133        }
1134
1135        // Freeing in a different order still merges everything back into one
1136        // aligned range.
1137        allocator.free(b);
1138        allocator.free(a);
1139        allocator.free(c);
1140
1141        let report = allocator.storage_report();
1142        assert_eq!(report.total_free_space, 1 << 20);
1143        assert_eq!(report.largest_free_region, 1 << 20);
1144    }
1145
1146    #[test]
1147    fn min_allocator_size_holds_aligned_objects() {
1148        // Randomly generated integers on a log distribution, σ = 10.
1149        static TEST_OBJECT_SIZES: [u32; 42] = [
1150            0, 1, 2, 3, 4, 5, 8, 17, 23, 36, 51, 68, 87, 151, 165, 167, 201, 223, 306, 346, 394,
1151            411, 806, 969, 1404, 1798, 2236, 4281, 4745, 13989, 21095, 26594, 27146, 29679, 144685,
1152            153878, 495127, 727999, 1377073, 9440387, 41994490, 68520116,
1153        ];
1154
1155        for needed_object_size in TEST_OBJECT_SIZES {
1156            let allocator_size = min_allocator_size(needed_object_size, COPY_ALIGNMENT);
1157            let mut allocator = Allocator::with_max_nodes_and_alignment(
1158                allocator_size,
1159                DEFAULT_MAX_NODES,
1160                COPY_ALIGNMENT,
1161            );
1162            let allocation = allocator.allocate(needed_object_size).unwrap_or_else(|| {
1163                panic!("{needed_object_size} units must fit in {allocator_size}")
1164            });
1165            assert_eq!(allocation.offset % COPY_ALIGNMENT.get(), 0);
1166            assert!(allocator.allocation_size(allocation) >= needed_object_size);
1167        }
1168    }
1169
1170    #[test]
1171    fn the_last_node_of_the_free_list_is_usable() {
1172        // Two nodes hold one one-unit allocation plus the free range left over.
1173        // (The port treated a free list with one node left as exhausted, which
1174        // wasted it.)
1175        let mut allocator = Allocator::with_max_nodes_and_alignment(2, 2, NonZeroU32::MIN);
1176        let allocation = allocator.allocate(1).unwrap();
1177        assert_eq!(allocation.offset, 0);
1178        assert!(allocator.allocate(1).is_none());
1179    }
1180
1181    #[test]
1182    fn every_offset_stays_aligned_under_churn() {
1183        // A small deterministic PRNG, so a failure reproduces exactly.
1184        let mut seed = 0x9e37_79b9u32;
1185        let mut next = move || {
1186            seed ^= seed << 13;
1187            seed ^= seed >> 17;
1188            seed ^= seed << 5;
1189            seed
1190        };
1191
1192        let alignment = COPY_ALIGNMENT;
1193        let mut allocator = Allocator::with_max_nodes_and_alignment(1 << 16, 4096, alignment);
1194
1195        // Live allocations, paired with the size charged for them, so overlap
1196        // and alignment can be checked against each other.
1197        let mut live: Vec<(Allocation, u32)> = Vec::new();
1198
1199        for _ in 0..10_000 {
1200            // Free roughly a third of the time, so the allocator churns.
1201            if !live.is_empty() && next() % 3 == 0 {
1202                let index = (next() as usize) % live.len();
1203                let (allocation, _) = live.swap_remove(index);
1204                allocator.free(allocation);
1205                continue;
1206            }
1207
1208            let size = next() % 2000;
1209            let Some(allocation) = allocator.allocate(size) else {
1210                continue;
1211            };
1212            let charged = allocator.allocation_size(allocation);
1213
1214            // The offset is aligned, and the charge covers the request.
1215            assert_eq!(allocation.offset % alignment.get(), 0);
1216            assert!(charged >= size);
1217            assert_eq!(charged % alignment.get(), 0);
1218
1219            // The new range overlaps no live one.
1220            let end = allocation.offset + charged;
1221            for &(other, other_charged) in &live {
1222                let other_end = other.offset + other_charged;
1223                assert!(
1224                    end <= other.offset || other_end <= allocation.offset,
1225                    "range {}..{} overlaps {}..{}",
1226                    allocation.offset,
1227                    end,
1228                    other.offset,
1229                    other_end
1230                );
1231            }
1232
1233            live.push((allocation, charged));
1234        }
1235
1236        // Freeing everything must return the allocator to one whole region.
1237        for (allocation, _) in live {
1238            allocator.free(allocation);
1239        }
1240        let report = allocator.storage_report();
1241        assert_eq!(report.total_free_space, 1 << 16);
1242        assert_eq!(report.largest_free_region, 1 << 16);
1243    }
1244
1245    #[test]
1246    #[should_panic(expected = "already freed")]
1247    fn double_free_panics() {
1248        let mut allocator = Allocator::new(1024);
1249        let allocation = allocator.allocate(64).unwrap();
1250        allocator.free(allocation);
1251        allocator.free(allocation);
1252    }
1253
1254    #[test]
1255    fn extend_keeps_existing_offsets_and_appends_at_the_end() {
1256        let mut allocator =
1257            Allocator::with_max_nodes_and_alignment(1024, DEFAULT_MAX_NODES, COPY_ALIGNMENT);
1258        assert_eq!(allocator.size(), 1024);
1259
1260        // Fill the chunk exactly, so the only way to satisfy the allocation
1261        // below is the appended space.
1262        let a = allocator.allocate(512).unwrap();
1263        let b = allocator.allocate(512).unwrap();
1264        assert_eq!((a.offset, b.offset), (0, 512));
1265
1266        assert!(allocator.extend(1024));
1267        assert_eq!(allocator.size(), 2048);
1268
1269        // The old allocations did not move.
1270        assert_eq!(allocator.allocation_size(a), 512);
1271        assert_eq!(allocator.allocation_size(b), 512);
1272
1273        let c = allocator.allocate(1024).unwrap();
1274        assert_eq!(
1275            c.offset, 1024,
1276            "the new range comes from the appended space"
1277        );
1278
1279        // The appended range is contiguous with the old chunk: freeing
1280        // everything must merge back into one region of the grown size.
1281        allocator.free(a);
1282        allocator.free(b);
1283        allocator.free(c);
1284        let report = allocator.storage_report();
1285        assert_eq!(report.total_free_space, 2048);
1286        assert_eq!(report.largest_free_region, 2048);
1287
1288        let whole = allocator.allocate(2048).unwrap();
1289        assert_eq!(whole.offset, 0);
1290    }
1291
1292    #[test]
1293    fn extend_merges_into_a_free_tail() {
1294        let mut allocator =
1295            Allocator::with_max_nodes_and_alignment(1024, DEFAULT_MAX_NODES, COPY_ALIGNMENT);
1296
1297        // Leave the end of the chunk free, then grow it: the appended space is
1298        // contiguous with that free tail, so the two must end up as one range
1299        // rather than as neighbours only a later `free` could merge.
1300        let a = allocator.allocate(256).unwrap();
1301        assert!(allocator.extend(1024));
1302
1303        let report = allocator.storage_report();
1304        assert_eq!(report.total_free_space, 2048 - 256);
1305        assert_eq!(report.largest_free_region, 2048 - 256);
1306
1307        let tail = allocator.allocate(2048 - 256).unwrap();
1308        assert_eq!(tail.offset, 256);
1309        allocator.free(a);
1310        allocator.free(tail);
1311
1312        let whole = allocator.allocate(2048).unwrap();
1313        assert_eq!(whole.offset, 0);
1314    }
1315
1316    #[test]
1317    fn extend_appends_after_an_allocated_tail() {
1318        let mut allocator = Allocator::with_max_nodes_and_alignment(2, 3, NonZeroU32::MIN);
1319
1320        // The chunk ends in an allocation, so the appended space becomes a new
1321        // free range.
1322        let a = allocator.allocate(2).unwrap();
1323        assert_eq!(a.offset, 0);
1324        assert!(allocator.extend(64));
1325        assert_eq!(allocator.size(), 66);
1326
1327        // The chunk now ends in free space, so a second extension grows that
1328        // node in place rather than appending a neighbour to it.
1329        assert!(allocator.extend(64));
1330        assert_eq!(allocator.size(), 130);
1331
1332        // The whole appended space is one range: an allocation may take all of
1333        // it, which a pair of adjacent ranges could not offer.
1334        let b = allocator.allocate(128).unwrap();
1335        assert_eq!(b.offset, 2);
1336
1337        allocator.free(a);
1338        allocator.free(b);
1339        assert_eq!(allocator.storage_report().total_free_space, 130);
1340
1341        // The two allocations merged back across the extension boundary. Had
1342        // they stayed separate, the free space would be the same 2 + 128 units
1343        // but the 128-unit range would sit at offset 2; taking it from offset 0
1344        // is only possible because `free` merged across the boundary.
1345        let whole = allocator.allocate(128).unwrap();
1346        assert_eq!(whole.offset, 0);
1347    }
1348}