reloaded_memory_buffers/structs/internal/
locator_header.rs1extern crate alloc;
2use crate::internal::buffer_allocator::allocate;
3use crate::structs::errors::ItemAllocationError;
4use crate::structs::internal::LocatorItem;
5use crate::structs::params::BufferAllocatorSettings;
6use crate::structs::SafeLocatorItem;
7use crate::utilities::cached::get_sys_info;
8use crate::utilities::wrappers::Unaligned;
9use core::alloc::Layout;
10use core::cell::Cell;
11use core::cmp::min;
12use core::mem::size_of;
13use core::ptr::null_mut;
14use core::sync::atomic::{AtomicI32, Ordering};
15
16pub(crate) const LENGTH: usize = 4096;
18
19pub(crate) const LENGTH_OF_PREALLOCATED_CHUNKS: u32 = 16384;
30
31pub(crate) const MAX_ITEM_COUNT: u32 =
33 ((LENGTH - size_of::<LocatorHeader>()) / size_of::<LocatorItem>()) as u32;
34
35#[repr(C, align(1))]
37pub struct LocatorHeader {
38 pub this_address: Unaligned<*mut LocatorHeader>,
39 pub next_locator_ptr: Unaligned<*mut LocatorHeader>,
40 pub is_locked: AtomicI32,
41 pub flags: u8,
42 pub num_items: u8,
43 padding: [u8; 2],
44}
45
46impl LocatorHeader {
47 #[allow(clippy::new_without_default)]
49 #[cfg(test)]
50 pub fn new() -> Self {
51 extern crate std;
52 LocatorHeader {
53 this_address: Unaligned::new(std::ptr::null_mut()),
54 next_locator_ptr: Unaligned::new(std::ptr::null_mut::<LocatorHeader>()),
55 is_locked: AtomicI32::new(0),
56 flags: 0,
57 num_items: 0,
58 padding: [0; 2],
59 }
60 }
61
62 pub(crate) fn initialize(&mut self, length: usize) {
67 self.set_default_values();
68 let remaining_bytes = (length - LENGTH) as u32;
69
70 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
77 Self::initialize_remaining_space_as_headers(self as *mut LocatorHeader, remaining_bytes);
78
79 #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
80 self.initialize_remaining_space_as_buffers(remaining_bytes);
81 }
82
83 fn set_default_values(&mut self) {
84 self.this_address = Unaligned::new(self as *mut LocatorHeader);
85 self.next_locator_ptr = Unaligned::new(null_mut());
86 self.is_locked = AtomicI32::new(0);
87 self.flags = 0;
88 self.num_items = 0;
89 }
90
91 fn initialize_remaining_space_as_buffers(&mut self, mut remaining_bytes: u32) {
92 let mut num_items = 0u8;
93 unsafe {
94 let buffer_address = (self.this_address.value as *mut u8).add(LENGTH);
95 let mut current_item = self.get_first_item();
96
97 while remaining_bytes > 0 {
98 let this_length = min(LENGTH_OF_PREALLOCATED_CHUNKS, remaining_bytes);
99 *current_item = LocatorItem::new(buffer_address as usize, this_length);
100 current_item = current_item.offset(1);
101 remaining_bytes -= this_length;
102 num_items += 1;
103 }
104 }
105
106 self.num_items = num_items;
107 }
108
109 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
110 fn initialize_remaining_space_as_headers(header: *mut LocatorHeader, mut remaining_bytes: u32) {
111 unsafe {
112 let mut current_header = header;
113 while remaining_bytes >= LENGTH as u32 {
114 let next_header = (current_header as *mut u8).add(LENGTH) as *mut LocatorHeader;
115 (*next_header).set_default_values();
116 (*current_header).next_locator_ptr = Unaligned::new(next_header);
117 current_header = next_header;
118 remaining_bytes -= LENGTH as u32;
119 }
120 }
121 }
122
123 #[allow(dead_code)]
125 pub fn version(&self) -> u8 {
126 self.flags & 0x07
127 }
128
129 #[allow(dead_code)]
131 pub fn set_version(&mut self, value: u8) {
132 self.flags = (self.flags & 0xF8) | (value & 0x07);
133 }
134
135 pub fn has_next_locator(&self) -> bool {
137 !self.next_locator_ptr.value.is_null()
138 }
139
140 pub fn is_full(&self) -> bool {
142 self.num_items as usize >= MAX_ITEM_COUNT as usize
143 }
144
145 pub fn try_lock(&mut self) -> bool {
149 self.is_locked
152 .compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire)
153 .is_ok()
154 }
155
156 pub fn lock(&mut self) {
158 while !self.try_lock() {
159 #[cfg(all(feature = "std", not(unix), not(windows)))]
160 {
161 std::thread::yield_now();
162 }
163
164 #[cfg(unix)]
165 unsafe {
166 libc::sched_yield();
167 }
168
169 #[cfg(windows)]
170 unsafe {
171 windows_sys::Win32::System::Threading::SwitchToThread();
172 }
173 }
174 }
175
176 pub fn unlock(&mut self) {
183 let original = self.is_locked.swap(0, Ordering::AcqRel);
185
186 debug_assert_ne!(
188 original, 0,
189 "Attempted to unlock a LocatorHeader that wasn't locked"
190 );
191 }
192
193 pub fn get_first_item(&self) -> *mut LocatorItem {
195 unsafe { (self as *const LocatorHeader).add(1) as *mut LocatorItem }
197 }
198
199 pub fn get_item(&self, index: usize) -> *mut LocatorItem {
203 unsafe { self.get_first_item().add(index) }
204 }
205
206 pub unsafe fn get_first_available_item_locked(
223 &self,
224 size: u32,
225 min_address: usize,
226 max_address: usize,
227 ) -> Option<SafeLocatorItem> {
228 let mut current_item = self.get_first_item();
229 let final_item = current_item.add(self.num_items as usize);
230 while current_item < final_item {
231 let item_ref = &mut *current_item;
232 if item_ref.can_use(size, min_address, max_address) && item_ref.try_lock() {
233 return Some({
234 let item: *mut LocatorItem = item_ref;
235 SafeLocatorItem {
236 item: Cell::new(item),
237 }
238 });
239 }
240
241 current_item = current_item.offset(1);
242 }
243
244 None
245 }
246
247 pub fn try_allocate_item(
272 &mut self,
273 size: u32,
274 min_address: usize,
275 max_address: usize,
276 ) -> Result<SafeLocatorItem, ItemAllocationError> {
277 if self.is_full() {
278 return Err(ItemAllocationError::NoSpaceInHeader);
279 }
280
281 self.lock();
285
286 if self.is_full() {
287 self.unlock();
288 return Err(ItemAllocationError::NoSpaceInHeader);
289 }
290
291 let mut settings = BufferAllocatorSettings::new();
292 settings.min_address = min_address;
293 settings.max_address = max_address;
294 settings.size = size;
295 let result = allocate(&mut settings);
296
297 match result {
298 Ok(mut allocated_memory) => {
299 allocated_memory.lock();
300
301 unsafe {
302 let target = self.get_item(self.num_items as usize);
303 *target = allocated_memory;
304 let item = SafeLocatorItem {
305 item: Cell::new(target),
306 };
307
308 self.num_items += 1;
309 self.unlock();
310 Ok(item)
311 }
312 }
313 Err(_) => {
314 self.unlock();
315 Err(ItemAllocationError::CannotAllocateMemory)
316 }
317 }
318 }
319
320 pub fn get_next_locator(&mut self) -> Result<*mut LocatorHeader, &'static str> {
327 if self.has_next_locator() {
329 return Ok(self.next_locator_ptr.value);
330 }
331
332 self.lock();
333
334 if self.has_next_locator() {
336 self.unlock();
337 return Ok(self.next_locator_ptr.value);
338 }
339
340 let sys_info = get_sys_info();
342 let alloc_size = sys_info.allocation_granularity;
343 unsafe {
344 let addr = alloc::alloc::alloc(
345 Layout::from_size_align(alloc_size as usize, sys_info.page_size as usize).unwrap(),
346 );
347 if addr.is_null() {
348 self.unlock();
349 return Err(
350 "Failed to allocate memory for LocatorHeader. Is this process out of memory?",
351 );
352 }
353
354 self.next_locator_ptr.value = addr as *mut LocatorHeader;
355 (*self.next_locator_ptr.value).initialize(alloc_size as usize);
356 self.unlock();
357
358 Ok(self.next_locator_ptr.value)
359 }
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 extern crate std;
366 use crate::structs::internal::locator_header::{Unaligned, LENGTH, MAX_ITEM_COUNT};
367 use crate::structs::internal::LocatorHeader;
368 use crate::utilities::cached::get_sys_info;
369 use memoffset::offset_of;
370 use std::alloc::{alloc, Layout};
371 use std::mem::{align_of, size_of};
372 use std::sync::atomic::Ordering;
373
374 macro_rules! expected_offset {
376 ($true_value:expr, $false_value:expr) => {
377 if size_of::<usize>() == 8 {
378 $true_value
379 } else {
380 $false_value
381 }
382 };
383 }
384
385 #[test]
386 fn is_correct_size() {
387 let expected = if size_of::<usize>() == 4 { 16 } else { 24 };
388 assert_eq!(size_of::<LocatorHeader>(), expected);
389
390 assert_eq!(
391 expected_offset!(0, 0),
392 offset_of!(LocatorHeader, this_address)
393 );
394 assert_eq!(
395 expected_offset!(8, 4),
396 offset_of!(LocatorHeader, next_locator_ptr)
397 );
398 assert_eq!(
399 expected_offset!(16, 8),
400 offset_of!(LocatorHeader, is_locked)
401 );
402 assert_eq!(expected_offset!(20, 12), offset_of!(LocatorHeader, flags));
403 assert_eq!(
404 expected_offset!(21, 13),
405 offset_of!(LocatorHeader, num_items)
406 );
407 }
408
409 #[test]
410 fn has_correct_max_item_count() {
411 let expected = if size_of::<usize>() == 4 { 255 } else { 203 };
412 assert_eq!(MAX_ITEM_COUNT, expected);
413 }
414
415 #[test]
416 fn try_lock_should_lock_header_when_lock_is_available() {
417 let mut header = LocatorHeader::new();
419
420 let result = header.try_lock();
422
423 assert!(result);
425 assert_eq!(1, header.is_locked.load(Ordering::Acquire));
426 }
427
428 #[test]
429 fn try_lock_should_not_lock_header_when_lock_is_already_acquired() {
430 let mut header = LocatorHeader::new();
432 header.try_lock();
433
434 let result = header.try_lock();
436
437 assert!(!result);
439 assert_eq!(1, header.is_locked.load(Ordering::Acquire));
440 }
441
442 #[test]
443 fn lock_should_acquire_lock_when_lock_is_available() {
444 let mut header = LocatorHeader::new();
446
447 header.lock();
449
450 assert_eq!(1, header.is_locked.load(Ordering::Acquire));
452 }
453
454 #[test]
455 fn unlock_should_release_lock_when_header_is_locked() {
456 let mut header = LocatorHeader::new();
458 header.lock();
459
460 header.unlock();
462
463 assert_eq!(0, header.is_locked.load(Ordering::Acquire));
465 }
466
467 #[test]
468 fn version_should_be_3_bits() {
469 let mut header = LocatorHeader::new();
470
471 for value in 0..8 {
472 header.set_version(value);
474 assert_eq!(header.version(), value);
475 }
476
477 header.set_version(8);
479 assert_eq!(header.version(), 0);
480 }
481
482 #[cfg(debug_assertions)] #[test]
484 #[should_panic(expected = "Attempted to unlock a LocatorHeader that wasn't locked")]
485 fn unlock_should_throw_exception_when_header_is_not_locked() {
486 let mut header = LocatorHeader::new();
487 header.unlock();
488 }
489
490 #[test]
491 fn get_first_available_item_locked_should_return_expected_result() {
492 unsafe {
493 let mut header_buf: [u8; LENGTH] = [0; LENGTH];
495 let header: *mut LocatorHeader = header_buf.as_mut_ptr() as *mut LocatorHeader;
496
497 (*header).this_address = Unaligned::new(header);
498 (*header).num_items = 2;
499
500 let first_item = (*header).get_first_item();
501 (*first_item).base_address = Unaligned::new(100);
502 (*first_item).size = 50;
503 (*first_item).position = 25;
504
505 let second_item = (*header).get_item(1);
506 (*second_item).base_address = Unaligned::new(200);
507 (*second_item).size = 50;
508 (*second_item).position = 25;
509
510 let result = (*header).get_first_available_item_locked(25, 100, 300);
512
513 assert!(result.is_some());
515 let result = result.unwrap();
516 let locator_item = result.item.get();
517 let base_address = (*locator_item).base_address.value;
518 assert_eq!(base_address, 100);
519 assert!((*locator_item).is_taken());
520 }
521 }
522
523 #[test]
524 fn get_first_available_item_locked_should_return_null_if_no_available_item_because_size_is_insufficient(
525 ) {
526 unsafe {
527 let mut header_buf: [u8; LENGTH] = [0; LENGTH];
529 let header: *mut LocatorHeader = header_buf.as_mut_ptr() as *mut LocatorHeader;
530
531 (*header).this_address = Unaligned::new(header);
532 (*header).num_items = 2;
533
534 let first_item = (*header).get_first_item();
535 (*first_item).base_address = Unaligned::new(100);
536 (*first_item).size = 50;
537 (*first_item).position = 30;
538
539 let second_item = (*header).get_item(1);
540 (*second_item).base_address = Unaligned::new(200);
541 (*second_item).size = 50;
542 (*second_item).position = 30;
543
544 let result = (*header).get_first_available_item_locked(25, 100, 300);
546
547 assert!(result.is_none());
549 }
550 }
551
552 #[test]
553 fn get_first_available_item_locked_should_return_null_if_no_available_item_because_no_buffer_fits_range(
554 ) {
555 unsafe {
556 let mut header_buf: [u8; LENGTH] = [0; LENGTH];
558 let header: *mut LocatorHeader = header_buf.as_mut_ptr() as *mut LocatorHeader;
559
560 (*header).this_address = Unaligned::new(header);
561 (*header).num_items = 2;
562
563 let first_item = (*header).get_first_item();
564 (*first_item).base_address = Unaligned::new(100);
565 (*first_item).size = 50;
566 (*first_item).position = 0;
567
568 let second_item = (*header).get_item(1);
569 (*second_item).base_address = Unaligned::new(200);
570 (*second_item).size = 50;
571 (*second_item).position = 0;
572
573 let result = (*header).get_first_available_item_locked(25, 0, 100);
575
576 assert!(result.is_none());
578 }
579 }
580
581 #[test]
582 fn try_allocate_item_should_allocate_item_when_header_is_not_full_and_within_address_limits() {
583 let ptr =
585 unsafe { alloc(Layout::from_size_align(LENGTH, align_of::<LocatorHeader>()).unwrap()) };
586 let header_ptr = ptr as *mut LocatorHeader;
587 let header = unsafe { &mut *header_ptr };
588 header.initialize(LENGTH);
589
590 let size = 100;
591 let min_address = get_sys_info().max_address / 2;
592 let max_address = get_sys_info().max_address;
593
594 let item_count = header.num_items;
596 let result = header.try_allocate_item(size, min_address, max_address);
597 assert_eq!(item_count + 1, header.num_items);
598
599 assert!(result.is_ok());
601 unsafe {
602 let item = result.unwrap_unchecked();
603 let address = (*item.item.get()).base_address.value;
604 assert!(address >= min_address);
605 assert!(address <= max_address);
606 }
607 }
608
609 #[test]
610 fn try_allocate_item_should_not_allocate_item_when_header_is_full() {
611 let ptr =
613 unsafe { alloc(Layout::from_size_align(LENGTH, align_of::<LocatorHeader>()).unwrap()) };
614 let header_ptr = ptr as *mut LocatorHeader;
615 let header = unsafe { &mut *header_ptr };
616 header.initialize(LENGTH);
617 header.num_items = MAX_ITEM_COUNT as u8;
618
619 let size = 100;
620 let min_address = get_sys_info().max_address / 2;
621 let max_address = get_sys_info().max_address;
622
623 let item_count = header.num_items;
625 let result = header.try_allocate_item(size, min_address, max_address);
626 assert_eq!(item_count, header.num_items);
627
628 assert!(result.is_err());
630 }
631
632 #[test]
633 fn try_allocate_item_should_not_allocate_item_when_outside_address_limits() {
634 let ptr =
636 unsafe { alloc(Layout::from_size_align(LENGTH, align_of::<LocatorHeader>()).unwrap()) };
637 let header_ptr = ptr as *mut LocatorHeader;
638 let header = unsafe { &mut *header_ptr };
639 header.initialize(LENGTH);
640
641 let size = 100;
642 let min_address = 0;
643 let max_address = 10; assert!(header
647 .try_allocate_item(size, min_address, max_address)
648 .is_err());
649 }
650
651 #[test]
652 fn get_next_locator_should_allocate_when_newly_created() {
653 let ptr =
655 unsafe { alloc(Layout::from_size_align(LENGTH, align_of::<LocatorHeader>()).unwrap()) };
656 let header_ptr = ptr as *mut LocatorHeader;
657 let header = unsafe { &mut *header_ptr };
658 header.initialize(LENGTH);
659 header.num_items = MAX_ITEM_COUNT as u8;
660
661 let next = header.get_next_locator().unwrap();
663 let next_cached = header.get_next_locator().unwrap();
664
665 assert_eq!(next as usize, next_cached as usize);
667 assert_ne!(next as usize, 0);
668 }
669}