Skip to main content

buddy_slab_allocator/buddy/
pooled_list.rs

1//! Pooled linked list implementation using global node pool
2//!
3//! Provides linked lists that draw nodes from a shared global pool,
4//! allowing efficient use of memory across all zones and orders.
5
6#[cfg(feature = "log")]
7use log::{error, warn};
8
9use super::{buddy_block::BuddyBlock, global_node_pool::GlobalNodePool};
10
11/// Pooled linked list - uses nodes from global pool
12///
13/// This maintains only the list structure (head/tail/len), while
14/// all nodes are allocated from the global pool.
15pub struct PooledLinkedList {
16    head: Option<usize>,
17    tail: Option<usize>,
18    len: usize,
19}
20
21impl PooledLinkedList {
22    /// Create a new empty pooled linked list
23    pub const fn new() -> Self {
24        Self {
25            head: None,
26            tail: None,
27            len: 0,
28        }
29    }
30
31    /// Insert element in sorted order (ascending by address)
32    /// This is used for buddy free lists to enable efficient contiguity checking
33    pub fn insert_sorted(&mut self, pool: &mut GlobalNodePool, data: BuddyBlock) -> bool {
34        let new_node_idx = match pool.alloc_node() {
35            Some(idx) => idx,
36            None => {
37                error!("Global node pool exhausted");
38                return false;
39            }
40        };
41
42        // Find insertion position
43        let mut prev_idx = None;
44        let mut current_idx = self.head;
45        let mut visited = 0;
46
47        while let Some(idx) = current_idx {
48            if visited > self.len {
49                error!("Potential cycle detected during insert");
50                // Deallocate the node
51                pool.dealloc_node(new_node_idx);
52                return false;
53            }
54
55            if let Some(node) = pool.get_node(idx) {
56                if node.data.addr == data.addr {
57                    // Block already in free list - this is a success (no-op)
58                    // The caller tried to free something that's already free
59                    pool.dealloc_node(new_node_idx);
60                    return true;
61                }
62                if node.data.addr > data.addr {
63                    break; // Found position
64                }
65                prev_idx = current_idx;
66                current_idx = node.next;
67            } else {
68                error!("Invalid node reference in list");
69                pool.dealloc_node(new_node_idx);
70                return false;
71            }
72            visited += 1;
73        }
74
75        // Initialize the new node
76        if let Some(node) = pool.get_node_mut(new_node_idx) {
77            node.data = data;
78            node.next = current_idx;
79        } else {
80            error!("Failed to get mutable reference to newly allocated node");
81            pool.dealloc_node(new_node_idx);
82            return false;
83        }
84
85        // Update links
86        if let Some(prev) = prev_idx {
87            if let Some(prev_node) = pool.get_node_mut(prev) {
88                prev_node.next = Some(new_node_idx);
89            }
90        } else {
91            self.head = Some(new_node_idx);
92        }
93
94        // Update tail if needed
95        if current_idx.is_none() {
96            self.tail = Some(new_node_idx);
97        }
98
99        self.len += 1;
100        true
101    }
102
103    /// Check if any block in the list falls within the given address range [start, end)
104    pub fn has_block_in_range(&self, pool: &GlobalNodePool, start: usize, end: usize) -> bool {
105        let mut current_idx = self.head;
106        let mut visited = 0;
107
108        while let Some(idx) = current_idx {
109            if visited > self.len {
110                break;
111            }
112            if let Some(node) = pool.get_node(idx) {
113                // Early termination: list is sorted by address
114                if node.data.addr >= end {
115                    break;
116                }
117                // Check if block starts within the range
118                // For i < initial_order, any block in the range is a conflict.
119                // Since blocks are aligned to their size, a block starting before 'start'
120                // cannot overlap with [start, end) if its size is smaller than start's alignment.
121                if node.data.addr >= start {
122                    return true;
123                }
124                current_idx = node.next;
125            } else {
126                break;
127            }
128            visited += 1;
129        }
130        false
131    }
132
133    /// Pop an element from the front of the list
134    pub fn pop_front(&mut self, pool: &mut GlobalNodePool) -> Option<BuddyBlock> {
135        self.head?;
136
137        let head_idx = self.head?;
138
139        if let Some(head_node) = pool.get_node_mut(head_idx) {
140            self.head = head_node.next;
141            if self.head.is_none() {
142                self.tail = None;
143            }
144
145            let data = head_node.data;
146
147            // Return node to pool
148            pool.dealloc_node(head_idx);
149            self.len -= 1;
150
151            Some(data)
152        } else {
153            error!("Head node {head_idx} is corrupted");
154            None
155        }
156    }
157
158    /// Check if the list is empty
159    pub fn is_empty(&self) -> bool {
160        self.len == 0
161    }
162
163    /// Get the length of the list
164    pub fn len(&self) -> usize {
165        self.len
166    }
167
168    /// Find a node by address (for buddy system)
169    ///
170    /// Returns (node_idx, prev_idx) where prev_idx is the node before it (or None if head)
171    pub fn find_by_addr(
172        &self,
173        pool: &GlobalNodePool,
174        addr: usize,
175    ) -> Option<(usize, Option<usize>)> {
176        let mut prev_idx = None;
177        let mut current_idx = self.head;
178        let mut visited = 0;
179
180        while let Some(idx) = current_idx {
181            if visited > self.len {
182                error!("Potential cycle detected during search");
183                return None;
184            }
185
186            if let Some(node) = pool.get_node(idx) {
187                // Early termination: list is sorted by address
188                if node.data.addr > addr {
189                    break;
190                }
191                if node.data.addr == addr {
192                    return Some((idx, prev_idx));
193                }
194                prev_idx = current_idx;
195                current_idx = node.next;
196            } else {
197                break;
198            }
199            visited += 1;
200        }
201
202        None
203    }
204
205    /// Remove a node at the given index
206    pub fn remove(&mut self, pool: &mut GlobalNodePool, node_idx: usize) -> bool {
207        // Find the node
208        let mut prev_idx = None;
209        let mut current_idx = self.head;
210        let mut visited = 0;
211
212        while let Some(idx) = current_idx {
213            if visited > self.len {
214                warn!("Potential cycle detected during remove");
215                return false;
216            }
217
218            if idx == node_idx {
219                break;
220            }
221            prev_idx = current_idx;
222            if let Some(node) = pool.get_node(idx) {
223                current_idx = node.next;
224            } else {
225                break;
226            }
227            visited += 1;
228        }
229
230        if current_idx != Some(node_idx) {
231            return false;
232        }
233
234        self.remove_with_prev_impl(pool, node_idx, prev_idx)
235    }
236
237    /// Remove a node using known prev_idx (O(1) operation)
238    ///
239    /// This is used when we already know the previous node index from find_by_addr(),
240    /// avoiding a second traversal of the list.
241    pub fn remove_with_prev(
242        &mut self,
243        pool: &mut GlobalNodePool,
244        node_idx: usize,
245        prev_idx: Option<usize>,
246    ) -> bool {
247        // Verify the node exists
248        if pool.get_node(node_idx).is_none() {
249            warn!("Invalid node index {node_idx} for remove_with_prev");
250            return false;
251        }
252
253        // Verify prev_idx leads to node_idx if provided
254        if let Some(prev) = prev_idx {
255            if let Some(prev_node) = pool.get_node(prev) {
256                if prev_node.next != Some(node_idx) {
257                    warn!("prev_idx {prev} does not point to node_idx {node_idx}");
258                    return false;
259                }
260            } else {
261                warn!("Invalid prev_idx {prev}");
262                return false;
263            }
264        } else if self.head != Some(node_idx) {
265            // prev_idx is None means node should be head
266            warn!("prev_idx is None but node_idx {node_idx} is not head");
267            return false;
268        }
269
270        self.remove_with_prev_impl(pool, node_idx, prev_idx)
271    }
272
273    /// Internal implementation of remove with known prev_idx
274    fn remove_with_prev_impl(
275        &mut self,
276        pool: &mut GlobalNodePool,
277        node_idx: usize,
278        prev_idx: Option<usize>,
279    ) -> bool {
280        // Get the node's next pointer before deallocating
281        let next_idx = pool.get_node(node_idx).and_then(|n| n.next);
282
283        // Update links
284        if let Some(prev) = prev_idx {
285            if let Some(prev_node) = pool.get_node_mut(prev) {
286                prev_node.next = next_idx;
287            }
288        } else {
289            self.head = next_idx;
290        }
291
292        // Update tail if needed
293        if self.tail == Some(node_idx) {
294            if next_idx.is_none() {
295                self.tail = prev_idx;
296            }
297        } else if self.head.is_none() {
298            self.tail = None;
299        }
300
301        // Return node to pool
302        pool.dealloc_node(node_idx);
303        self.len -= 1;
304        true
305    }
306
307    /// Get iterator over elements
308    pub fn iter<'a>(&'a self, pool: &'a GlobalNodePool) -> PooledListIter<'a> {
309        PooledListIter {
310            pool,
311            current: self.head,
312        }
313    }
314
315    /// Clear all nodes from the list
316    ///
317    /// Returns all nodes to the global pool
318    pub fn clear(&mut self, pool: &mut GlobalNodePool) {
319        while self.pop_front(pool).is_some() {
320            // Just pop all elements
321        }
322    }
323}
324
325impl Default for PooledLinkedList {
326    fn default() -> Self {
327        Self::new()
328    }
329}
330
331/// Iterator for PooledLinkedList
332pub struct PooledListIter<'a> {
333    pool: &'a GlobalNodePool,
334    current: Option<usize>,
335}
336
337impl<'a> Iterator for PooledListIter<'a> {
338    type Item = &'a BuddyBlock;
339
340    fn next(&mut self) -> Option<Self::Item> {
341        self.current.and_then(|idx| {
342            if let Some(node) = self.pool.get_node(idx) {
343                self.current = node.next;
344                Some(&node.data)
345            } else {
346                self.current = None;
347                None
348            }
349        })
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356    use crate::buddy::ListNode;
357
358    const TEST_NODE_COUNT: usize = 512;
359
360    #[test]
361    fn test_pooled_list_basic() {
362        let mut pool: GlobalNodePool = GlobalNodePool::new();
363        let mut backing = [ListNode {
364            data: BuddyBlock { order: 0, addr: 0 },
365            next: None,
366        }; TEST_NODE_COUNT];
367
368        let region_start = backing.as_mut_ptr() as usize;
369        let region_size = core::mem::size_of_val(&backing);
370        pool.init(region_start, region_size);
371
372        let mut list: PooledLinkedList = PooledLinkedList::new();
373
374        assert!(list.is_empty());
375        assert_eq!(list.len(), 0);
376        assert_eq!(pool.free_node_count(), TEST_NODE_COUNT);
377
378        list.insert_sorted(
379            &mut pool,
380            BuddyBlock {
381                order: 0,
382                addr: 0x1000,
383            },
384        );
385        list.insert_sorted(
386            &mut pool,
387            BuddyBlock {
388                order: 0,
389                addr: 0x2000,
390            },
391        );
392        list.insert_sorted(
393            &mut pool,
394            BuddyBlock {
395                order: 0,
396                addr: 0x3000,
397            },
398        );
399
400        assert_eq!(list.len(), 3);
401        assert_eq!(pool.free_node_count(), TEST_NODE_COUNT - 3);
402
403        assert_eq!(
404            list.pop_front(&mut pool),
405            Some(BuddyBlock {
406                order: 0,
407                addr: 0x1000
408            })
409        );
410        assert_eq!(
411            list.pop_front(&mut pool),
412            Some(BuddyBlock {
413                order: 0,
414                addr: 0x2000
415            })
416        );
417        assert_eq!(list.len(), 1);
418        assert_eq!(pool.free_node_count(), TEST_NODE_COUNT - 1);
419
420        // Clear remaining
421        list.clear(&mut pool);
422        assert!(list.is_empty());
423        assert_eq!(pool.free_node_count(), TEST_NODE_COUNT);
424    }
425
426    #[test]
427    fn test_insert_sorted() {
428        let mut pool: GlobalNodePool = GlobalNodePool::new();
429        let mut backing = [ListNode {
430            data: BuddyBlock { order: 0, addr: 0 },
431            next: None,
432        }; TEST_NODE_COUNT];
433
434        let region_start = backing.as_mut_ptr() as usize;
435        let region_size = core::mem::size_of_val(&backing);
436        pool.init(region_start, region_size);
437
438        let mut list: PooledLinkedList = PooledLinkedList::new();
439
440        list.insert_sorted(
441            &mut pool,
442            BuddyBlock {
443                order: 0,
444                addr: 0x5000,
445            },
446        );
447        list.insert_sorted(
448            &mut pool,
449            BuddyBlock {
450                order: 0,
451                addr: 0x3000,
452            },
453        );
454        list.insert_sorted(
455            &mut pool,
456            BuddyBlock {
457                order: 0,
458                addr: 0x7000,
459            },
460        );
461        list.insert_sorted(
462            &mut pool,
463            BuddyBlock {
464                order: 0,
465                addr: 0x1000,
466            },
467        );
468
469        let items: alloc::vec::Vec<_> = list.iter(&pool).collect();
470        assert_eq!(items.len(), 4);
471        assert_eq!(items[0].addr, 0x1000);
472        assert_eq!(items[1].addr, 0x3000);
473        assert_eq!(items[2].addr, 0x5000);
474        assert_eq!(items[3].addr, 0x7000);
475    }
476
477    #[test]
478    fn test_find_and_remove() {
479        let mut pool: GlobalNodePool = GlobalNodePool::new();
480        let mut backing = [ListNode {
481            data: BuddyBlock { order: 0, addr: 0 },
482            next: None,
483        }; TEST_NODE_COUNT];
484
485        let region_start = backing.as_mut_ptr() as usize;
486        let region_size = core::mem::size_of_val(&backing);
487        pool.init(region_start, region_size);
488
489        let mut list: PooledLinkedList = PooledLinkedList::new();
490
491        list.insert_sorted(
492            &mut pool,
493            BuddyBlock {
494                order: 0,
495                addr: 0x1000,
496            },
497        );
498        list.insert_sorted(
499            &mut pool,
500            BuddyBlock {
501                order: 0,
502                addr: 0x2000,
503            },
504        );
505        list.insert_sorted(
506            &mut pool,
507            BuddyBlock {
508                order: 0,
509                addr: 0x3000,
510            },
511        );
512
513        let (node_idx, _) = list.find_by_addr(&pool, 0x2000).unwrap();
514        assert!(list.remove(&mut pool, node_idx));
515
516        assert_eq!(list.len(), 2);
517        assert_eq!(pool.free_node_count(), TEST_NODE_COUNT - 2);
518
519        let items: alloc::vec::Vec<_> = list.iter(&pool).collect();
520        assert_eq!(items[0].addr, 0x1000);
521        assert_eq!(items[1].addr, 0x3000);
522    }
523}