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 slot_allocations: Box<[u64]>,
54}
55
56impl RingAllocator {
57 pub(crate) fn new(payload_capacity: usize, queue_capacity: usize, alignment: usize) -> Self {
65 assert!(
66 alignment > 0 && alignment.is_power_of_two(),
67 "Alignment must be a power of two to support bitwise alignment arithmetic."
68 );
69 Self {
70 payload_capacity: payload_capacity as u64,
71 queue_capacity: queue_capacity as u64,
72 alignment: alignment as u64,
73 allocated_bytes: 0,
74 freed_bytes: 0,
75 last_reclaimed_read_index: 0,
76 slot_allocations: vec![0; queue_capacity].into_boxed_slice(),
77 }
78 }
79
80 pub(crate) fn allocate(&mut self, size: usize) -> Option<AllocationToken> {
83 let align = self.alignment;
84
85 let aligned_size = (size as u64 + align - 1) & !(align - 1);
87 if aligned_size > self.payload_capacity {
88 return None;
89 }
90
91 let physical_head = self.allocated_bytes % self.payload_capacity;
92 let padding = (align - (physical_head % align)) % align;
94 let padded_size = aligned_size + padding;
95
96 let space_to_end = self.payload_capacity - physical_head;
97 let (bytes_added, offset) = if padded_size <= space_to_end {
98 (padded_size, (physical_head + padding) as u32)
100 } else {
101 (space_to_end + aligned_size, 0)
104 };
105
106 let active_bytes = self.allocated_bytes - self.freed_bytes;
107 if active_bytes + bytes_added <= self.payload_capacity {
108 self.allocated_bytes += bytes_added;
109 Some(AllocationToken { offset, bytes_added })
110 } else {
111 None
112 }
113 }
114
115 pub(crate) fn commit_allocation_to_slot(&mut self, slot_index: u64, token: AllocationToken) {
119 let index = (slot_index % self.queue_capacity) as usize;
120 self.slot_allocations[index] = token.bytes_added();
121 }
122
123 pub(crate) fn cancel_allocation(&mut self, token: AllocationToken) {
125 self.allocated_bytes -= token.bytes_added();
126 if self.allocated_bytes == self.freed_bytes {
130 self.allocated_bytes = 0;
131 self.freed_bytes = 0;
132 }
133 }
134
135 pub(crate) fn reclaim_consumed_slots(&mut self, new_read_index: u64) {
139 for i in self.last_reclaimed_read_index..new_read_index {
140 let slot = (i % self.queue_capacity) as usize;
141 self.freed_bytes += std::mem::take(&mut self.slot_allocations[slot]);
142 }
143 self.last_reclaimed_read_index = new_read_index;
144
145 if self.allocated_bytes == self.freed_bytes {
148 self.allocated_bytes = 0;
149 self.freed_bytes = 0;
150 }
151 }
152
153 pub(crate) fn is_within_capacity(&self, size: usize) -> bool {
156 let align = self.alignment;
157 let aligned_size = (size as u64 + align - 1) & !(align - 1);
158 aligned_size <= self.payload_capacity
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 fn active_bytes(allocator: &RingAllocator) -> u64 {
167 allocator.allocated_bytes - allocator.freed_bytes
168 }
169
170 #[test]
171 fn test_allocate_sequential() {
172 let mut allocator = RingAllocator::new(100, 10, 8);
173
174 let t1 = allocator.allocate(20).unwrap();
175 assert_eq!(t1.offset(), 0);
176 assert_eq!(t1.bytes_added(), 24); allocator.commit_allocation_to_slot(0, t1);
178
179 let t2 = allocator.allocate(32).unwrap();
180 assert_eq!(t2.offset(), 24);
181 assert_eq!(t2.bytes_added(), 32);
182 allocator.commit_allocation_to_slot(1, t2);
183
184 assert_eq!(active_bytes(&allocator), 56);
185 }
186
187 #[test]
188 fn test_cancel_allocation() {
189 let mut allocator = RingAllocator::new(100, 10, 8);
190
191 let t1 = allocator.allocate(50).unwrap();
192 assert_eq!(active_bytes(&allocator), 56);
193
194 allocator.cancel_allocation(t1);
195
196 assert_eq!(active_bytes(&allocator), 0);
198 assert_eq!(allocator.allocated_bytes, 0);
199 }
200
201 #[test]
202 fn test_empty_vs_full() {
203 let mut allocator = RingAllocator::new(128, 10, 64);
204
205 let t1 = allocator.allocate(64).unwrap();
206 assert_eq!(active_bytes(&allocator), 64);
207 allocator.commit_allocation_to_slot(0, t1);
208
209 let t2 = allocator.allocate(64).unwrap();
210 assert_eq!(active_bytes(&allocator), 128);
211 allocator.commit_allocation_to_slot(1, t2);
212
213 assert!(allocator.allocate(10).is_none());
215
216 allocator.reclaim_consumed_slots(1);
217 assert_eq!(active_bytes(&allocator), 64);
218
219 allocator.reclaim_consumed_slots(2);
220 assert_eq!(active_bytes(&allocator), 0);
221 }
222
223 #[test]
224 fn test_zero_size_allocation_after_nonzero_reclaim() {
225 let mut allocator = RingAllocator::new(65536, 16, 4096);
226 let mut read_index = 0;
227
228 let t0 = allocator.allocate(8192).unwrap();
230 assert_eq!(t0.bytes_added(), 8192);
231 allocator.commit_allocation_to_slot(0, t0);
232
233 let t1 = allocator.allocate(0).unwrap();
235 assert_eq!(t1.bytes_added(), 0);
236 allocator.commit_allocation_to_slot(1, t1);
237
238 read_index += 1;
240 allocator.reclaim_consumed_slots(read_index);
241 assert_eq!(active_bytes(&allocator), 0);
242
243 let t2 = allocator.allocate(4096).unwrap();
245 assert_eq!(t2.bytes_added(), 4096);
246 allocator.commit_allocation_to_slot(2, t2);
247
248 read_index += 1;
250 allocator.reclaim_consumed_slots(read_index);
251 assert_eq!(active_bytes(&allocator), 4096);
252
253 let t3 = allocator.allocate(4096).unwrap();
255 allocator.commit_allocation_to_slot(3, t3);
256 assert_eq!(active_bytes(&allocator), 8192);
257
258 read_index += 2;
260 allocator.reclaim_consumed_slots(read_index);
261 assert_eq!(active_bytes(&allocator), 0);
262 }
263}