vmo_fifo/
ring_allocator.rs1#[derive(Debug, PartialEq, Eq)]
7pub struct AllocationToken {
8 offset: u32,
10
11 bytes_added: u64,
14}
15
16impl AllocationToken {
17 pub fn offset(&self) -> u32 {
19 self.offset
20 }
21
22 pub(crate) fn bytes_added(&self) -> u64 {
24 self.bytes_added
25 }
26}
27
28pub(crate) struct RingAllocator {
30 payload_capacity: u64,
32
33 queue_capacity: u64,
35
36 alignment: u64,
41
42 allocated_bytes: u64,
44
45 freed_bytes: u64,
47
48 last_reclaimed_read_index: u64,
50
51 reclaim_targets: Box<[Option<u64>]>,
54}
55
56impl RingAllocator {
57 pub(crate) fn new(payload_capacity: usize, queue_capacity: usize, alignment: usize) -> Self {
63 assert!(
64 alignment > 0 && alignment.is_power_of_two(),
65 "Alignment must be a power of two to support bitwise alignment arithmetic."
66 );
67 Self {
68 payload_capacity: payload_capacity as u64,
69 queue_capacity: queue_capacity as u64,
70 alignment: alignment as u64,
71 allocated_bytes: 0,
72 freed_bytes: 0,
73 last_reclaimed_read_index: 0,
74 reclaim_targets: vec![None; queue_capacity].into_boxed_slice(),
75 }
76 }
77
78 pub(crate) fn allocate(&mut self, size: usize) -> Option<AllocationToken> {
81 let align = self.alignment;
82
83 let aligned_size = (size as u64 + align - 1) & !(align - 1);
85 if aligned_size > self.payload_capacity {
86 return None;
87 }
88
89 let physical_head = self.allocated_bytes % self.payload_capacity;
90 let padding = (align - (physical_head % align)) % align;
92 let padded_size = aligned_size + padding;
93
94 let space_to_end = self.payload_capacity - physical_head;
95 let (bytes_added, offset) = if padded_size <= space_to_end {
96 (padded_size, (physical_head + padding) as u32)
98 } else {
99 (space_to_end + aligned_size, 0)
102 };
103
104 let active_bytes = self.allocated_bytes - self.freed_bytes;
105 if active_bytes + bytes_added <= self.payload_capacity {
106 self.allocated_bytes += bytes_added;
107 Some(AllocationToken { offset, bytes_added })
108 } else {
109 None
110 }
111 }
112
113 pub(crate) fn commit_allocation_to_slot(&mut self, slot_index: u64, _token: AllocationToken) {
116 let index = (slot_index % self.queue_capacity) as usize;
117 self.reclaim_targets[index] = Some(self.allocated_bytes);
118 }
119
120 pub(crate) fn cancel_allocation(&mut self, token: AllocationToken) {
122 self.allocated_bytes -= token.bytes_added();
123 }
124
125 pub(crate) fn reclaim_consumed_slots(&mut self, new_read_index: u64) {
129 for i in self.last_reclaimed_read_index..new_read_index {
130 let slot = (i % self.queue_capacity) as usize;
131
132 if let Some(target) = self.reclaim_targets[slot].take() {
133 if target > self.freed_bytes {
134 self.freed_bytes = target;
135 }
136 }
137 }
138 self.last_reclaimed_read_index = new_read_index;
139
140 if self.allocated_bytes == self.freed_bytes {
143 self.allocated_bytes = 0;
144 self.freed_bytes = 0;
145 }
146 }
147
148 pub(crate) fn is_within_capacity(&self, size: usize) -> bool {
151 let align = self.alignment;
152 let aligned_size = (size as u64 + align - 1) & !(align - 1);
153 aligned_size <= self.payload_capacity
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 fn active_bytes(allocator: &RingAllocator) -> u64 {
162 allocator.allocated_bytes - allocator.freed_bytes
163 }
164
165 #[test]
166 fn test_allocate_sequential() {
167 let mut allocator = RingAllocator::new(100, 10, 8);
168
169 let t1 = allocator.allocate(20).unwrap();
170 assert_eq!(t1.offset(), 0);
171 assert_eq!(t1.bytes_added(), 24); allocator.commit_allocation_to_slot(0, t1);
173
174 let t2 = allocator.allocate(32).unwrap();
175 assert_eq!(t2.offset(), 24);
176 assert_eq!(t2.bytes_added(), 32);
177 allocator.commit_allocation_to_slot(1, t2);
178
179 assert_eq!(active_bytes(&allocator), 56);
180 }
181
182 #[test]
183 fn test_cancel_allocation() {
184 let mut allocator = RingAllocator::new(100, 10, 8);
185
186 let t1 = allocator.allocate(50).unwrap();
187 assert_eq!(active_bytes(&allocator), 56);
188
189 allocator.cancel_allocation(t1);
190
191 assert_eq!(active_bytes(&allocator), 0);
193 assert_eq!(allocator.allocated_bytes, 0);
194 }
195
196 #[test]
197 fn test_empty_vs_full() {
198 let mut allocator = RingAllocator::new(128, 10, 64);
199
200 let t1 = allocator.allocate(64).unwrap();
201 assert_eq!(active_bytes(&allocator), 64);
202 allocator.commit_allocation_to_slot(0, t1);
203
204 let t2 = allocator.allocate(64).unwrap();
205 assert_eq!(active_bytes(&allocator), 128);
206 allocator.commit_allocation_to_slot(1, t2);
207
208 assert!(allocator.allocate(10).is_none());
210
211 allocator.reclaim_consumed_slots(1);
212 assert_eq!(active_bytes(&allocator), 64);
213
214 allocator.reclaim_consumed_slots(2);
215 assert_eq!(active_bytes(&allocator), 0);
216 }
217}