safa_api/
alloc.rs

1//! This module implements a high-level userspace allocator
2//! which internally uses the [`crate::syscalls::syssbrk`] syscall
3//! to allocate memory
4
5#[cfg(not(any(feature = "std", feature = "rustc-dep-of-std")))]
6use safa_abi::ffi::{option::OptZero, slice::Slice};
7
8use crate::sync::locks::Mutex;
9
10use super::syscalls;
11use core::{alloc::GlobalAlloc, ptr::NonNull};
12
13#[derive(Debug, Default)]
14struct Block {
15    free: bool,
16    next: Option<NonNull<Block>>,
17    data_len: usize,
18}
19
20impl Block {
21    /// Asks the system for a new memory Block with a size big enough to hold `data_len` bytes
22    pub fn create(data_len: usize) -> Option<NonNull<Self>> {
23        let size = data_len + size_of::<Block>();
24        let size = size.next_multiple_of(align_of::<Block>());
25        assert!(size <= isize::MAX as usize);
26
27        let ptr = get_data_break() as *mut Block;
28        syscalls::process_misc::sbrk(size as isize).ok()?;
29        unsafe {
30            *ptr = Self {
31                free: true,
32                data_len: size - size_of::<Block>(),
33                ..Default::default()
34            };
35            Some(NonNull::new_unchecked(ptr))
36        }
37    }
38
39    #[inline(always)]
40    /// Gets the Block metadata of a data ptr,
41    /// unsafe because the pointer had to be made by calling `[Block::data_from_ptr]` on a valid pointer, otherwise the returned value is invalid
42    pub unsafe fn block_from_data_ptr(data: NonNull<u8>) -> NonNull<Self> {
43        unsafe { NonNull::new_unchecked((data.as_ptr() as *mut Block).offset(-1)) }
44    }
45
46    #[inline(always)]
47    /// Gets the data ptr from a pointer to Block
48    pub unsafe fn data_from_ptr(ptr: *const Self) -> NonNull<[u8]> {
49        unsafe {
50            let length = (*ptr).data_len;
51            let ptr_to_data = ptr.offset(1) as *const u8 as *mut u8;
52            NonNull::new_unchecked(core::slice::from_raw_parts_mut(ptr_to_data, length) as *mut [u8])
53        }
54    }
55}
56
57pub struct SystemAllocator {
58    head: Option<NonNull<Block>>,
59}
60
61fn get_data_break() -> *mut u8 {
62    // Should never fail
63    unsafe { syscalls::process_misc::sbrk(0).unwrap_unchecked() }
64}
65
66impl SystemAllocator {
67    const fn new() -> Self {
68        Self { head: None }
69    }
70
71    /// tries to find a block with enough space for `data_len` bytes
72    #[inline]
73    fn try_find_block(&self, data_len: usize) -> Option<NonNull<Block>> {
74        // To optimize the search for exact size we have to manipulate the data_len a bit
75        let size = data_len + size_of::<Block>();
76        let size = size.next_multiple_of(align_of::<Block>());
77        let data_len = size - size_of::<Block>();
78
79        let mut current = self.head;
80        let mut best_block: Option<(NonNull<Block>, usize)> = None;
81
82        while let Some(block_ptr) = current {
83            let block = unsafe { &*block_ptr.as_ptr() };
84            if !block.free {
85                current = block.next;
86                continue;
87            }
88
89            if block.data_len == data_len {
90                return Some(block_ptr);
91            }
92
93            if block.data_len > data_len
94                && best_block.is_none_or(|(_, bb_len)| bb_len > block.data_len)
95            {
96                best_block = Some((block_ptr, block.data_len));
97            }
98
99            current = block.next;
100        }
101
102        best_block.map(|(ptr, _)| ptr)
103    }
104
105    /// finds a block with enough space for `data_len` bytes
106    /// or creates a new one if there is no enough space
107    #[inline]
108    fn find_block(&mut self, data_len: usize) -> Option<NonNull<Block>> {
109        if let Some(block) = self.try_find_block(data_len) {
110            Some(block)
111        } else {
112            unsafe {
113                let new_block = Block::create(data_len)?;
114                let stolen_head = self.head.take();
115
116                (*new_block.as_ptr()).next = stolen_head;
117                self.head = Some(new_block);
118
119                Some(new_block)
120            }
121        }
122    }
123
124    fn allocate(&mut self, size: usize) -> Option<NonNull<[u8]>> {
125        let block = self.find_block(size)?;
126        unsafe {
127            let ptr = block.as_ptr();
128            (*ptr).free = false;
129            Some(Block::data_from_ptr(ptr))
130        }
131    }
132
133    unsafe fn deallocate(&mut self, block_data: NonNull<u8>) {
134        unsafe {
135            let block_ptr = Block::block_from_data_ptr(block_data).as_ptr();
136            let block = &mut *block_ptr;
137            block.free = true;
138        }
139    }
140}
141
142unsafe impl Send for SystemAllocator {}
143unsafe impl Sync for SystemAllocator {}
144
145// FIXME: implement locks before multithreading
146pub struct GlobalSystemAllocator {
147    inner: Mutex<SystemAllocator>,
148}
149
150impl GlobalSystemAllocator {
151    const fn new() -> Self {
152        Self {
153            inner: Mutex::new(SystemAllocator::new()),
154        }
155    }
156
157    #[inline]
158    pub fn allocate(&self, size: usize) -> Option<NonNull<[u8]>> {
159        self.inner.lock().allocate(size)
160    }
161
162    #[inline]
163    pub unsafe fn deallocate(&self, ptr: NonNull<u8>) {
164        self.inner.lock().deallocate(ptr)
165    }
166
167    // TODO: implement grow and shrink
168}
169
170unsafe impl Sync for GlobalSystemAllocator {}
171unsafe impl Send for GlobalSystemAllocator {}
172
173unsafe impl GlobalAlloc for GlobalSystemAllocator {
174    unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
175        self.allocate(layout.size())
176            .map(|x| x.as_ptr() as *mut u8)
177            .unwrap_or(core::ptr::null_mut())
178    }
179
180    unsafe fn dealloc(&self, ptr: *mut u8, _: core::alloc::Layout) {
181        self.deallocate(NonNull::new_unchecked(ptr));
182    }
183}
184
185#[cfg_attr(
186    not(any(feature = "std", feature = "rustc-dep-of-std")),
187    global_allocator
188)]
189/// A high-level userspace allocator that internally uses the [`crate::syscalls::syssbrk`] syscall
190/// (rust wrapper)
191pub static GLOBAL_SYSTEM_ALLOCATOR: GlobalSystemAllocator = GlobalSystemAllocator::new();
192
193#[cfg(not(any(feature = "std", feature = "rustc-dep-of-std")))]
194#[unsafe(no_mangle)]
195/// Allocates an object sized `object_size` using [`GLOBAL_SYSTEM_ALLOCATOR`]
196pub extern "C" fn syscreate(object_size: usize) -> OptZero<Slice<u8>> {
197    GLOBAL_SYSTEM_ALLOCATOR
198        .allocate(object_size)
199        .map(|mut x| unsafe {
200            let x = x.as_mut();
201            Slice::from_raw_parts(x.as_mut_ptr(), x.len())
202        })
203        .into()
204}
205
206#[cfg(not(any(feature = "std", feature = "rustc-dep-of-std")))]
207#[unsafe(no_mangle)]
208/// Deallocates an object sized `object_size` using [`GLOBAL_SYSTEM_ALLOCATOR`]
209/// # Safety
210/// `object_ptr` must be a pointer to a valid object allocated by [`GLOBAL_SYSTEM_ALLOCATOR`]
211pub unsafe extern "C" fn sysdestroy(object_ptr: *mut u8) {
212    unsafe {
213        match NonNull::new(object_ptr) {
214            Some(ptr) => GLOBAL_SYSTEM_ALLOCATOR.deallocate(ptr),
215            None => (),
216        }
217    }
218}