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};
7use safa_abi::mem::MemMapFlags;
8
9use crate::sync::locks::Mutex;
10
11use super::syscalls;
12use core::{alloc::GlobalAlloc, ptr::NonNull};
13
14#[derive(Debug, Default)]
15struct Block {
16    free: bool,
17    next: Option<NonNull<Block>>,
18    data_len: usize,
19}
20
21fn sys_allocate(size_hint: usize) -> Option<(*mut u8, usize)> {
22    let page_count = size_hint.next_multiple_of(4096) / 4096;
23    let (_, s) = syscalls::mem::map(
24        core::ptr::null(),
25        page_count,
26        0,
27        None,
28        None,
29        MemMapFlags::WRITE,
30    )
31    .ok()?;
32
33    Some((s.as_ptr() as *mut u8, s.len()))
34}
35
36impl Block {
37    /// Asks the system for a new memory Block with a size big enough to hold `data_len` bytes
38    pub fn create(data_len: usize) -> Option<(NonNull<Self>, Option<NonNull<Block>>)> {
39        let data_len = data_len.next_multiple_of(size_of::<Block>());
40        let size = data_len + size_of::<Block>();
41        let size = size.next_multiple_of(align_of::<Block>());
42        assert!(size <= isize::MAX as usize);
43
44        let (alloc_ptr, alloc_size) = sys_allocate(size)?;
45        assert!(alloc_size >= size);
46
47        let ptr = alloc_ptr as *mut Block;
48
49        unsafe {
50            *ptr = Self {
51                free: true,
52                data_len: size - size_of::<Block>(),
53                ..Default::default()
54            };
55
56            if alloc_size > size {
57                let extra_block_size = alloc_size - size;
58                let extra_block_ptr = alloc_ptr.add(size) as *mut Block;
59                *extra_block_ptr = Self {
60                    free: true,
61                    data_len: extra_block_size - size_of::<Block>(),
62                    ..Default::default()
63                };
64
65                (*ptr).next = Some(NonNull::new_unchecked(extra_block_ptr));
66            }
67            Some((NonNull::new_unchecked(ptr), (*ptr).next))
68        }
69    }
70
71    #[inline(always)]
72    /// Gets the Block metadata of a data ptr,
73    /// unsafe because the pointer had to be made by calling `[Block::data_from_ptr]` on a valid pointer, otherwise the returned value is invalid
74    pub unsafe fn block_from_data_ptr(data: NonNull<u8>) -> NonNull<Self> {
75        unsafe { NonNull::new_unchecked((data.as_ptr() as *mut Block).offset(-1)) }
76    }
77
78    #[inline(always)]
79    /// Gets the data ptr from a pointer to Block
80    pub unsafe fn data_from_ptr(ptr: *const Self) -> NonNull<[u8]> {
81        unsafe {
82            let length = (*ptr).data_len;
83            let ptr_to_data = ptr.offset(1) as *const u8 as *mut u8;
84            NonNull::new_unchecked(core::slice::from_raw_parts_mut(ptr_to_data, length) as *mut [u8])
85        }
86    }
87}
88
89pub struct SystemAllocator {
90    head: Option<NonNull<Block>>,
91}
92
93impl SystemAllocator {
94    const fn new() -> Self {
95        Self { head: None }
96    }
97
98    /// tries to find a block with enough space for `data_len` bytes
99    #[inline]
100    fn try_find_block(&self, data_len: usize) -> Option<NonNull<Block>> {
101        // To optimize the search for exact size we have to manipulate the data_len a bit
102        let size = data_len + size_of::<Block>();
103        let size = size.next_multiple_of(align_of::<Block>());
104        let data_len = size - size_of::<Block>();
105
106        let mut current = self.head;
107        let mut best_block: Option<(NonNull<Block>, usize)> = None;
108
109        while let Some(block_ptr) = current {
110            let block = unsafe { &*block_ptr.as_ptr() };
111            if !block.free {
112                current = block.next;
113                continue;
114            }
115
116            if block.data_len == data_len {
117                return Some(block_ptr);
118            }
119
120            if block.data_len > data_len
121                && best_block.is_none_or(|(_, bb_len)| bb_len > block.data_len)
122            {
123                best_block = Some((block_ptr, block.data_len));
124            }
125
126            current = block.next;
127        }
128
129        best_block.map(|(ptr, _)| ptr)
130    }
131
132    /// finds a block with enough space for `data_len` bytes
133    /// or creates a new one if there is no enough space
134    #[inline]
135    fn find_block(&mut self, data_len: usize) -> Option<NonNull<Block>> {
136        let data_len = data_len.next_multiple_of(size_of::<Block>());
137
138        if let Some(block) = self.try_find_block(data_len) {
139            let block_ptr = block.as_ptr();
140
141            unsafe {
142                let block_len = (*block_ptr).data_len;
143
144                // Spilt the Block
145                if block_len > data_len && (block_len - data_len) > size_of::<Block>() {
146                    let left_over = block_len - data_len;
147                    let new_block_len = left_over - size_of::<Block>();
148
149                    let new_block = block_ptr.add(1).byte_add(data_len);
150                    *new_block = Block {
151                        free: true,
152                        data_len: new_block_len,
153                        next: (*block_ptr).next.take(),
154                    };
155
156                    (*block_ptr).next = Some(NonNull::new_unchecked(new_block));
157                    (*block_ptr).data_len = data_len;
158                }
159            }
160            Some(block)
161        } else {
162            unsafe {
163                let (new_block, new_allocation_tail) = Block::create(data_len)?;
164                let set_next_of = new_allocation_tail.unwrap_or(new_block);
165                let stolen_head = self.head.take();
166
167                (*set_next_of.as_ptr()).next = stolen_head;
168                self.head = Some(new_block);
169
170                Some(new_block)
171            }
172        }
173    }
174
175    fn merge_blocks(&mut self) {
176        let mut current = self.head;
177        while let Some(block_ptr) = current {
178            unsafe {
179                let block = block_ptr.as_ptr();
180                if !(*block).free {
181                    current = (*block).next;
182                    continue;
183                }
184
185                let Some(next) = (*block).next else {
186                    return;
187                };
188
189                let next_ptr = next.as_ptr();
190                if !(*next_ptr).free {
191                    current = (*block).next;
192                    continue;
193                }
194
195                if block.add(1).byte_add((*block).data_len) == next_ptr {
196                    // consume the next block
197                    (*block).next = (*next_ptr).next;
198                    (*block).data_len += (*next_ptr).data_len + size_of::<Block>();
199                }
200
201                current = (*block).next;
202            }
203        }
204    }
205
206    fn allocate(&mut self, size: usize) -> Option<NonNull<[u8]>> {
207        let block = self.find_block(size)?;
208        unsafe {
209            let ptr = block.as_ptr();
210            (*ptr).free = false;
211            Some(Block::data_from_ptr(ptr))
212        }
213    }
214
215    unsafe fn deallocate(&mut self, block_data: NonNull<u8>) {
216        unsafe {
217            let block_ptr = Block::block_from_data_ptr(block_data).as_ptr();
218            let block = &mut *block_ptr;
219            block.free = true;
220
221            self.merge_blocks();
222        }
223    }
224}
225
226unsafe impl Send for SystemAllocator {}
227unsafe impl Sync for SystemAllocator {}
228
229// FIXME: implement locks before multithreading
230pub struct GlobalSystemAllocator {
231    inner: Mutex<SystemAllocator>,
232}
233
234impl GlobalSystemAllocator {
235    const fn new() -> Self {
236        Self {
237            inner: Mutex::new(SystemAllocator::new()),
238        }
239    }
240
241    #[inline]
242    pub fn allocate(&self, size: usize) -> Option<NonNull<[u8]>> {
243        self.inner.lock().allocate(size)
244    }
245
246    #[inline]
247    pub unsafe fn deallocate(&self, ptr: NonNull<u8>) {
248        self.inner.lock().deallocate(ptr)
249    }
250
251    // TODO: implement grow and shrink
252}
253
254unsafe impl Sync for GlobalSystemAllocator {}
255unsafe impl Send for GlobalSystemAllocator {}
256
257unsafe impl GlobalAlloc for GlobalSystemAllocator {
258    unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
259        self.allocate(layout.size())
260            .map(|x| x.as_ptr() as *mut u8)
261            .unwrap_or(core::ptr::null_mut())
262    }
263
264    unsafe fn dealloc(&self, ptr: *mut u8, _: core::alloc::Layout) {
265        self.deallocate(NonNull::new_unchecked(ptr));
266    }
267}
268
269#[cfg_attr(
270    not(any(feature = "std", feature = "rustc-dep-of-std")),
271    global_allocator
272)]
273/// A high-level userspace allocator that internally uses the [`crate::syscalls::syssbrk`] syscall
274/// (rust wrapper)
275pub static GLOBAL_SYSTEM_ALLOCATOR: GlobalSystemAllocator = GlobalSystemAllocator::new();
276
277#[cfg(not(any(feature = "std", feature = "rustc-dep-of-std")))]
278#[unsafe(no_mangle)]
279/// Allocates an object sized `object_size` using [`GLOBAL_SYSTEM_ALLOCATOR`]
280pub extern "C" fn syscreate(object_size: usize) -> OptZero<Slice<u8>> {
281    GLOBAL_SYSTEM_ALLOCATOR
282        .allocate(object_size)
283        .map(|mut x| unsafe {
284            let x = x.as_mut();
285            Slice::from_raw_parts(x.as_mut_ptr(), x.len())
286        })
287        .into()
288}
289
290#[cfg(not(any(feature = "std", feature = "rustc-dep-of-std")))]
291#[unsafe(no_mangle)]
292/// Deallocates an object sized `object_size` using [`GLOBAL_SYSTEM_ALLOCATOR`]
293/// # Safety
294/// `object_ptr` must be a pointer to a valid object allocated by [`GLOBAL_SYSTEM_ALLOCATOR`]
295pub unsafe extern "C" fn sysdestroy(object_ptr: *mut u8) {
296    unsafe {
297        match NonNull::new(object_ptr) {
298            Some(ptr) => GLOBAL_SYSTEM_ALLOCATOR.deallocate(ptr),
299            None => (),
300        }
301    }
302}