1use crate::cache_aligned::{CacheAligned, CacheAlignedU64};
2use crate::size_classes::NUM_SIZE_CLASSES;
3use crate::sync::{AtomicU32, AtomicU64, AtomicU8, AtomicUsize};
4
5pub const MAGIC: u64 = u64::from_be_bytes(*b"\0rtsaloc");
7pub const VERSION_MAJOR: u16 = 4;
8pub const VERSION_PATCH: u16 = 0;
9pub const VERSION: u32 = (VERSION_MAJOR as u32) << 16 | VERSION_PATCH as u32;
11
12#[repr(C)]
13pub struct WorkerLocalListPartialFullHeads {
14 pub partial: AtomicU32,
15 pub full: AtomicU32,
16}
17
18#[repr(C, align(64))]
19pub struct WorkerLocalListHeads {
20 pub claimed: AtomicU8,
21 pub outstanding_allocation_bytes: AtomicU64,
22 pub heads: [WorkerLocalListPartialFullHeads; NUM_SIZE_CLASSES],
23 pub remote_free_head: CacheAligned<AtomicUsize>,
24}
25
26#[repr(C)]
27pub struct Header {
28 pub magic: AtomicU64,
29 pub version: u32,
30 pub num_workers: u32,
32 pub num_slabs: u32,
34 pub slab_size: u32,
36
37 pub free_list_elements_offset: u32,
39 pub slab_shared_meta_offset: u32,
41 pub slab_free_stacks_offset: u32,
43 pub slabs_offset: u32,
45
46 pub global_free_list_head: CacheAlignedU64,
50 pub worker_local_list_heads: [WorkerLocalListHeads; 0],
52}
53
54pub mod layout {
96 use crate::{
97 align::round_to_next_alignment_of,
98 free_stack::FreeStack,
99 header::{Header, WorkerLocalListHeads},
100 linked_list_node::LinkedListNode,
101 size_classes::MIN_SIZE,
102 slab_meta::SlabMeta,
103 };
104 #[derive(Debug)]
105 pub struct AllocatorLayout {
106 pub num_slabs: u32,
108 pub free_list_elements_offset: u32,
110 pub slab_shared_meta_offset: u32,
112 pub slab_free_stacks_offset: u32,
114 pub slabs_offset: u32,
116 }
117
118 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
119 pub struct WorkerLimits {
120 pub meta_slabs: u32,
121 pub usable_slabs: u32,
122 pub max_workers: u32,
123 }
124
125 pub fn max_workers(file_size: usize, slab_size: u32, min_workers: u32) -> Option<WorkerLimits> {
126 if slab_size == 0 {
127 return None;
128 }
129 let slab_size_usize = slab_size as usize;
130 let total_slabs = (file_size / slab_size_usize) as u32;
131 if total_slabs == 0 {
132 return None;
133 }
134
135 let mut meta_slabs: u32 = 1;
136 loop {
137 let usable_slabs = total_slabs.checked_sub(meta_slabs)?;
138 if usable_slabs == 0 {
139 return None;
140 }
141 let base_layout = layout_for(0, slab_size, usable_slabs);
142 let base_meta_bytes = base_layout.slabs_offset as usize;
143
144 let needed_meta_slabs = base_meta_bytes.div_ceil(slab_size_usize) as u32;
145 if needed_meta_slabs > meta_slabs {
146 meta_slabs = needed_meta_slabs;
147 continue;
148 }
149
150 let slack_bytes = meta_slabs as usize * slab_size_usize - base_meta_bytes;
151 let per_worker_bytes = core::mem::size_of::<WorkerLocalListHeads>();
152 let max_workers = (slack_bytes / per_worker_bytes) as u32;
153 if max_workers < min_workers {
154 meta_slabs = meta_slabs.saturating_add(1);
155 continue;
156 }
157
158 return Some(WorkerLimits {
159 meta_slabs,
160 usable_slabs,
161 max_workers,
162 });
163 }
164 }
165
166 fn layout_for(num_workers: u32, slab_size: u32, num_slabs: u32) -> AllocatorLayout {
167 let mut offset = header_size();
168 offset += worker_local_list_heads_size(num_workers);
169 offset = pad_for_free_list_elements(offset);
170 let free_list_elements_offset = offset as u32;
171 offset += free_list_elements_size(num_slabs);
172 offset = pad_for_slab_meta(offset);
173 let slab_shared_meta_offset = offset as u32;
174 offset += slab_meta_size(num_slabs);
175 offset = pad_for_slab_free_stacks(offset);
176 let slab_free_stacks_offset = offset as u32;
177 offset += free_stacks_size(num_slabs, slab_size);
178 let slabs_offset = pad_for_slabs(offset, slab_size) as u32;
179
180 AllocatorLayout {
181 num_slabs,
182 free_list_elements_offset,
183 slab_shared_meta_offset,
184 slab_free_stacks_offset,
185 slabs_offset,
186 }
187 }
188
189 pub fn layout_for_num_slabs(
190 num_workers: u32,
191 slab_size: u32,
192 num_slabs: u32,
193 ) -> AllocatorLayout {
194 layout_for(num_workers, slab_size, num_slabs)
195 }
196
197 pub const fn header_size() -> usize {
199 core::mem::size_of::<Header>()
200 }
201
202 pub const fn worker_local_list_heads_size(num_workers: u32) -> usize {
204 core::mem::size_of::<WorkerLocalListHeads>() * num_workers as usize
205 }
206
207 pub const fn pad_for_free_list_elements(offset: usize) -> usize {
209 const FREE_LIST_ELEMENT_ALIGNMENT: usize = core::mem::align_of::<LinkedListNode>();
210 round_to_next_alignment_of::<FREE_LIST_ELEMENT_ALIGNMENT>(offset)
211 }
212
213 pub const fn free_list_elements_size(num_slabs: u32) -> usize {
215 core::mem::size_of::<LinkedListNode>() * num_slabs as usize
216 }
217
218 pub const fn pad_for_slab_meta(offset: usize) -> usize {
220 const SLAB_META_ALIGNMENT: usize = core::mem::align_of::<SlabMeta>();
221 round_to_next_alignment_of::<SLAB_META_ALIGNMENT>(offset)
222 }
223
224 pub const fn slab_meta_size(num_slabs: u32) -> usize {
226 core::mem::size_of::<SlabMeta>() * num_slabs as usize
227 }
228
229 pub const fn pad_for_slab_free_stacks(offset: usize) -> usize {
231 const FREE_STACK_ALIGNMENT: usize = core::mem::align_of::<FreeStack>();
232 round_to_next_alignment_of::<FREE_STACK_ALIGNMENT>(offset)
233 }
234
235 pub const fn single_free_stack_size(slab_size: u32) -> usize {
237 let max_capacity = slab_size / MIN_SIZE;
238 FreeStack::byte_size(max_capacity as u16)
239 }
240
241 pub const fn free_stacks_size(num_slabs: u32, slab_size: u32) -> usize {
243 single_free_stack_size(slab_size) * num_slabs as usize
244 }
245
246 pub const fn pad_for_slabs(offset: usize, slab_size: u32) -> usize {
248 debug_assert!(slab_size.is_power_of_two());
249 let slab_size = slab_size as usize;
250 (offset + slab_size - 1) & !(slab_size - 1)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_worker_local_list_heads_layout() {
260 assert_eq!(core::mem::align_of::<WorkerLocalListHeads>(), 64);
261 assert_eq!(core::mem::size_of::<WorkerLocalListHeads>(), 128);
262 assert_eq!(core::mem::align_of::<CacheAligned<AtomicUsize>>(), 64);
263 assert_eq!(
264 core::mem::offset_of!(WorkerLocalListHeads, remote_free_head),
265 64
266 );
267 }
268
269 #[test]
270 fn test_layout() {
271 let num_workers = 4;
272 let num_slabs = 8;
273 let slab_size = 4096;
274
275 let mut offset = layout::header_size();
276 assert_eq!(offset, core::mem::size_of::<Header>());
277 assert_eq!(
278 offset,
279 core::mem::offset_of!(Header, worker_local_list_heads)
280 );
281 assert_eq!(offset, 128);
282
283 offset += layout::worker_local_list_heads_size(num_workers);
284 assert_eq!(offset, 640);
285
286 offset = layout::pad_for_free_list_elements(offset);
287 assert_eq!(offset, 640);
288
289 offset += layout::free_list_elements_size(num_slabs);
290 assert_eq!(offset, 736);
291
292 offset = layout::pad_for_slab_meta(offset);
293 assert_eq!(offset, 736);
294
295 offset += layout::slab_meta_size(num_slabs);
296 assert_eq!(offset, 864);
297
298 offset = layout::pad_for_slab_free_stacks(offset);
299 assert_eq!(offset, 864);
300
301 offset += layout::free_stacks_size(num_slabs, slab_size);
302 assert_eq!(offset, 1152);
303
304 offset = layout::pad_for_slabs(offset, slab_size);
305 assert_eq!(offset, 4096);
306 }
307}