malloc_rust/
lib.rs

1//! Malloc implementation using Rust allocator
2//!
3//! This crate MUST not have semver breaking changes
4
5#![no_std]
6#![allow(clippy::style)]
7
8extern crate alloc;
9
10pub mod align;
11
12use core::{mem, ptr};
13use core::ffi::c_void;
14use core::convert::TryInto;
15use alloc::alloc::Layout;
16
17///Default alignment.
18///
19///On most platforms default value is 8 with exception of Mac OS and windows 64bit which uses 16
20pub const DEFAULT_ALIGNMENT: align::Alignment = {
21    #[cfg(any(target_os = "macos", all(windows, target_pointer_width = "64")))]
22    {
23        align::Alignment::new(16)
24    }
25    #[cfg(not(any(target_os = "macos", all(windows, target_pointer_width = "64"))))]
26    {
27        align::Alignment::new(8)
28    }
29};
30
31//It is actually fine not to store original size as most system allocators do not require it
32//but we cannot trust Rust ecosystem not to screw us up
33const LAYOUT_OFFSET: usize = mem::size_of::<usize>();
34
35#[cold]
36#[inline(never)]
37fn unlikely_null() -> *mut c_void {
38    ptr::null_mut()
39}
40
41#[inline(always)]
42unsafe fn get_ptr_size(ptr: *mut c_void) -> usize {
43    let mem = (ptr as *mut u8).offset(-(LAYOUT_OFFSET as isize));
44    let size = ptr::read(mem as *const usize);
45    size.saturating_sub(LAYOUT_OFFSET)
46}
47
48#[inline]
49///Baseline `malloc` implementation with Rust allocator
50///
51///Returns NULL if size is 0 or overflows `isize::MAX`
52pub unsafe extern "C" fn rust_malloc(mut size: usize) -> *mut c_void {
53    if size != 0 {
54        size = LAYOUT_OFFSET.saturating_add(DEFAULT_ALIGNMENT.next(size));
55
56        if let Ok(layout) = Layout::from_size_align(size, DEFAULT_ALIGNMENT.into_raw()) {
57            let mem = alloc::alloc::alloc(layout);
58            if !mem.is_null() {
59                ptr::write(mem as *mut usize, size);
60                return mem.add(LAYOUT_OFFSET) as _;
61            }
62        }
63    }
64
65    unlikely_null()
66}
67
68#[inline]
69///Generic `malloc` implementation which requires size to be converted into `usize` without error
70pub unsafe extern "C" fn generic_rust_malloc<T: Into<usize>>(size: T) -> *mut c_void {
71    rust_malloc(size.into())
72}
73
74#[inline]
75///Generic `malloc` implementation which allows size to be optionally convertable.
76///
77///In case of invalid size, returns NULL pointer
78pub unsafe extern "C" fn generic_try_rust_malloc<T: TryInto<usize>>(size: T) -> *mut c_void {
79    if let Ok(size) = size.try_into() {
80        rust_malloc(size)
81    } else {
82        unlikely_null()
83    }
84}
85
86#[inline]
87///Baseline `realloc` implementation with Rust allocator
88///
89///Returns NULL if size is 0 or overflows `isize::MAX`
90pub unsafe extern "C" fn rust_realloc(mut old_ptr: *mut c_void, mut new_size: usize) -> *mut c_void {
91    if new_size != 0 {
92        new_size = LAYOUT_OFFSET.saturating_add(DEFAULT_ALIGNMENT.next(new_size));
93
94        old_ptr = (old_ptr as *mut u8).offset(-(LAYOUT_OFFSET as isize)) as _;
95        let size = ptr::read(old_ptr as *const usize);
96        let layout = Layout::from_size_align_unchecked(size, DEFAULT_ALIGNMENT.into_raw());
97        let new_ptr = alloc::alloc::realloc(old_ptr as _, layout, new_size);
98        if !new_ptr.is_null() {
99            ptr::write(new_ptr as *mut usize, new_size);
100            return new_ptr.add(LAYOUT_OFFSET) as _;
101        }
102    }
103
104    unlikely_null()
105}
106
107#[inline]
108///Generic `realloc` implementation which requires size to be converted into `usize` without error
109pub unsafe extern "C" fn generic_rust_realloc<T: Into<usize>>(mem: *mut c_void, size: T) -> *mut c_void {
110    rust_realloc(mem, size.into())
111}
112
113#[inline]
114///Generic `realloc` implementation which allows size to be optionally convertable.
115///
116///In case of invalid size, returns NULL pointer
117pub unsafe extern "C" fn generic_try_rust_realloc<T: TryInto<usize>>(mem: *mut c_void, size: T) -> *mut c_void {
118    if let Ok(size) = size.try_into() {
119        rust_realloc(mem, size)
120    } else {
121        unlikely_null()
122    }
123}
124
125#[inline]
126///Baseline `calloc` implementation with Rust allocator
127///
128///Returns NULL if size is 0 or overflows `isize::MAX`
129pub unsafe extern "C" fn rust_calloc(mut size: usize) -> *mut c_void {
130    let ptr = rust_malloc(size);
131    if !ptr.is_null() {
132        size = get_ptr_size(ptr);
133        ptr::write_bytes(ptr as *mut u8, 0, size);
134        ptr
135    } else {
136        unlikely_null()
137    }
138}
139
140#[inline]
141///Returns size of allocated memory in pointer
142///
143///Returns 0 for NULL
144pub unsafe extern "C" fn rust_size(mem: *mut c_void) -> usize {
145    if !mem.is_null() {
146        get_ptr_size(mem)
147    } else {
148        0
149    }
150}
151
152#[inline]
153///Baseline `free` implementation with Rust allocator
154///
155///Does nothing if `mem` is null
156pub unsafe extern "C" fn rust_free(mem: *mut c_void) {
157    if !mem.is_null() {
158        let mem = (mem as *mut u8).offset(-(LAYOUT_OFFSET as isize));
159        let size = ptr::read(mem as *const usize);
160        let layout = Layout::from_size_align_unchecked(size, DEFAULT_ALIGNMENT.into_raw());
161        alloc::alloc::dealloc(mem, layout);
162    }
163}