malloc_rust/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Malloc implementation using Rust allocator
//!
//! This crate MUST not have semver breaking changes

#![no_std]
#![allow(clippy::style)]

extern crate alloc;

pub mod align;

use core::{mem, ptr};
use core::ffi::c_void;
use core::convert::TryInto;
use alloc::alloc::Layout;

///Default alignment.
///
///On most platforms default value is 8 with exception of Mac OS and windows 64bit which uses 16
pub const DEFAULT_ALIGNMENT: align::Alignment = {
    #[cfg(any(target_os = "macos", all(windows, target_pointer_width = "64")))]
    {
        align::Alignment::new(16)
    }
    #[cfg(not(any(target_os = "macos", all(windows, target_pointer_width = "64"))))]
    {
        align::Alignment::new(8)
    }
};

//It is actually fine not to store original size as most system allocators do not require it
//but we cannot trust Rust ecosystem not to screw us up
const LAYOUT_OFFSET: usize = mem::size_of::<usize>();

#[cold]
#[inline(never)]
fn unlikely_null() -> *mut c_void {
    ptr::null_mut()
}

#[inline(always)]
unsafe fn get_ptr_size(ptr: *mut c_void) -> usize {
    let mem = (ptr as *mut u8).offset(-(LAYOUT_OFFSET as isize));
    let size = ptr::read(mem as *const usize);
    size.saturating_sub(LAYOUT_OFFSET)
}

#[inline]
///Baseline `malloc` implementation with Rust allocator
///
///Returns NULL if size is 0 or overflows `isize::MAX`
pub unsafe extern "C" fn rust_malloc(mut size: usize) -> *mut c_void {
    if size != 0 {
        size = LAYOUT_OFFSET.saturating_add(DEFAULT_ALIGNMENT.next(size));

        if let Ok(layout) = Layout::from_size_align(size, DEFAULT_ALIGNMENT.into_raw()) {
            let mem = alloc::alloc::alloc(layout);
            if !mem.is_null() {
                ptr::write(mem as *mut usize, size);
                return mem.add(LAYOUT_OFFSET) as _;
            }
        }
    }

    unlikely_null()
}

#[inline]
///Generic `malloc` implementation which requires size to be converted into `usize` without error
pub unsafe extern "C" fn generic_rust_malloc<T: Into<usize>>(size: T) -> *mut c_void {
    rust_malloc(size.into())
}

#[inline]
///Generic `malloc` implementation which allows size to be optionally convertable.
///
///In case of invalid size, returns NULL pointer
pub unsafe extern "C" fn generic_try_rust_malloc<T: TryInto<usize>>(size: T) -> *mut c_void {
    if let Ok(size) = size.try_into() {
        rust_malloc(size)
    } else {
        unlikely_null()
    }
}

#[inline]
///Baseline `realloc` implementation with Rust allocator
///
///Returns NULL if size is 0 or overflows `isize::MAX`
pub unsafe extern "C" fn rust_realloc(mut old_ptr: *mut c_void, mut new_size: usize) -> *mut c_void {
    if new_size != 0 {
        new_size = LAYOUT_OFFSET.saturating_add(DEFAULT_ALIGNMENT.next(new_size));

        old_ptr = (old_ptr as *mut u8).offset(-(LAYOUT_OFFSET as isize)) as _;
        let size = ptr::read(old_ptr as *const usize);
        let layout = Layout::from_size_align_unchecked(size, DEFAULT_ALIGNMENT.into_raw());
        let new_ptr = alloc::alloc::realloc(old_ptr as _, layout, new_size);
        if !new_ptr.is_null() {
            ptr::write(new_ptr as *mut usize, new_size);
            return new_ptr.add(LAYOUT_OFFSET) as _;
        }
    }

    unlikely_null()
}

#[inline]
///Generic `realloc` implementation which requires size to be converted into `usize` without error
pub unsafe extern "C" fn generic_rust_realloc<T: Into<usize>>(mem: *mut c_void, size: T) -> *mut c_void {
    rust_realloc(mem, size.into())
}

#[inline]
///Generic `realloc` implementation which allows size to be optionally convertable.
///
///In case of invalid size, returns NULL pointer
pub unsafe extern "C" fn generic_try_rust_realloc<T: TryInto<usize>>(mem: *mut c_void, size: T) -> *mut c_void {
    if let Ok(size) = size.try_into() {
        rust_realloc(mem, size)
    } else {
        unlikely_null()
    }
}

#[inline]
///Baseline `calloc` implementation with Rust allocator
///
///Returns NULL if size is 0 or overflows `isize::MAX`
pub unsafe extern "C" fn rust_calloc(mut size: usize) -> *mut c_void {
    let ptr = rust_malloc(size);
    if !ptr.is_null() {
        size = get_ptr_size(ptr);
        ptr::write_bytes(ptr as *mut u8, 0, size);
        ptr
    } else {
        unlikely_null()
    }
}

#[inline]
///Returns size of allocated memory in pointer
///
///Returns 0 for NULL
pub unsafe extern "C" fn rust_size(mem: *mut c_void) -> usize {
    if !mem.is_null() {
        get_ptr_size(mem)
    } else {
        0
    }
}

#[inline]
///Baseline `free` implementation with Rust allocator
///
///Does nothing if `mem` is null
pub unsafe extern "C" fn rust_free(mem: *mut c_void) {
    if !mem.is_null() {
        let mem = (mem as *mut u8).offset(-(LAYOUT_OFFSET as isize));
        let size = ptr::read(mem as *const usize);
        let layout = Layout::from_size_align_unchecked(size, DEFAULT_ALIGNMENT.into_raw());
        alloc::alloc::dealloc(mem, layout);
    }
}