simplicity_sys/alloc.rs
1use std::alloc::{self, Layout};
2use std::mem;
3
4/// A type whose alignment is greater equal that of any C type.
5/// Allocations with this alignment are valid for all C types.
6/// Remake of `max_align_t` from `stddef.h`.
7/// [16 bytes is the greatest alignment of any architecture Rust currently supports](https://github.com/rust-lang/rust/blob/61223975d46f794466efa832bc7562b9707ecc46/library/std/src/sys/pal/common/alloc.rs).
8#[repr(align(16))]
9#[derive(Default, Copy, Clone)]
10#[allow(dead_code)]
11pub struct AlignedType([u8; 16]);
12
13/// Minimal alignment which is valid for all C types.
14pub const MIN_ALIGN: usize = mem::align_of::<AlignedType>();
15
16/// Signature of an allocator function.
17type AllocatorFn = unsafe fn(Layout) -> *mut u8;
18
19/// Allocate `size_bytes` many bytes using the `allocator` function
20/// and return a pointer.
21///
22/// # Panics
23///
24/// This function panics if `size_bytes` + [`MIN_ALIGN`],
25/// rounded up to next multiple of [`MIN_ALIGN`],
26/// is greater than [`isize::MAX`] (allocated too many bytes).
27///
28/// # Safety
29///
30/// - `allocator` must be [`alloc::alloc`] or [`alloc::alloc_zeroed`].
31/// - Allocated bytes must be freed using [`rust_0_6_free`].
32unsafe fn allocate(size_bytes: usize, allocator: AllocatorFn) -> *mut u8 {
33 assert!(mem::align_of::<usize>() <= MIN_ALIGN);
34 assert!(mem::align_of::<&usize>() <= MIN_ALIGN);
35 assert!(mem::size_of::<usize>() <= MIN_ALIGN);
36
37 // We allocate a sequence of N bytes (size_bytes) that will hold the actual data,
38 // prefixed by a sequence of MIN_ALIGN bytes that hold the number `MIN_ALIGN + N`
39 // (number of allocated bytes).
40 // The prefix needs to be offset by MIN_ALIGN to ensure correct alignment of the next bytes.
41 // Finally, we return a pointer after this prefix for the caller to use.
42 //
43 // MIN_ALIGN MIN_ALIGN + 2
44 // | |
45 // +---------------+-------+-------+-----+-------+
46 // | MIN_ALIGN + N | byte0 | byte1 | ... | byteN |
47 // +---------------+-------+-------+-----+-------+
48 // | ^ | |
49 // 0 | MIN_ALIGN + 1 MIN_ALIGN + N
50 // WE RETURN THIS POINTER
51 //
52 let size_prefixed_bytes = MIN_ALIGN.saturating_add(size_bytes);
53 // PANIC: Allocated too many bytes (documented above).
54 let layout =
55 Layout::from_size_align(size_prefixed_bytes, MIN_ALIGN).expect("allocated too many bytes");
56 // SAFETY: `layout` is nonzero.
57 let ptr_prefix = allocator(layout);
58 if ptr_prefix.is_null() {
59 // Abort execution if allocation failed.
60 alloc::handle_alloc_error(layout);
61 }
62 // Write number of allocated bytes into prefix.
63 //
64 // SAFETY: prefix is valid for writes and well-aligned.
65 (ptr_prefix as *mut usize).write(size_prefixed_bytes);
66 // Return pointer behind prefix.
67 //
68 // SAFETY: `ptr_prefix` and `ptr_prefix + MIN_ALIGN` are part of same allocated object.
69 ptr_prefix.add(MIN_ALIGN)
70}
71
72/// Allocate `size_bytes` many bytes and return a pointer.
73///
74/// # Safety
75///
76/// Allocated bytes must be freed using [`rust_0_6_free`].
77#[no_mangle]
78pub unsafe extern "C" fn rust_0_6_malloc(size_bytes: usize) -> *mut u8 {
79 // SAFETY: Allocator is `alloc::alloc`.
80 allocate(size_bytes, alloc::alloc)
81}
82
83/// Allocate `size_bytes` many zeroed bytes and return a pointer.
84///
85/// # Safety
86///
87/// Allocated bytes must be freed using [`rust_0_6_free`].
88#[no_mangle]
89pub unsafe extern "C" fn rust_0_6_calloc(num: usize, size: usize) -> *mut u8 {
90 let size_bytes = num * size;
91 // SAFETY: Allocator is `alloc_alloc_zeroed`.
92 allocate(size_bytes, alloc::alloc_zeroed)
93}
94
95/// Free allocated bytes at `ptr_bytes`.
96///
97/// # Safety
98///
99/// - `ptr_bytes` must have been allocated using [`rust_0_6_malloc`] or [`rust_0_6_calloc`].
100/// - If `ptr_bytes` is a `NULL` pointer, then this function is a NO-OP.
101#[no_mangle]
102pub unsafe extern "C" fn rust_0_6_free(ptr_bytes: *mut u8) {
103 if ptr_bytes.is_null() {
104 return;
105 }
106
107 // We got a pointer to an allocation from `rust_0_6_malloc` or `rust_0_6_calloc`,
108 // so the memory looks as follows.
109 // There is a prefix of `MIN_ALIGN` bytes in front of the pointer we got.
110 // This prefix holds the total number of allocated bytes.
111 // We free this number of bytes to free the entire sequence.
112 //
113 // MIN_ALIGN MIN_ALIGN + 2
114 // | |
115 // +---------------+-------+-------+-----+-------+
116 // | MIN_ALIGN + N | byte0 | byte1 | ... | byteN |
117 // +---------------+-------+-------+-----+-------+
118 // | ^ | |
119 // 0 | MIN_ALIGN + 1 MIN_ALIGN + N
120 // WE GOT THIS POINTER
121 //
122 // SAFETY: `ptr_bytes` and `ptr_bytes - MIN_ALIGN` are part of same allocated object.
123 let ptr_prefix = ptr_bytes.sub(MIN_ALIGN);
124 // SAFETY: prefix is valid for reads and well-aligned.
125 let size_prefixed_bytes = (ptr_prefix as *mut usize).read();
126 // INFALLIBLE: This layout was already allocated, so there is no overflow.
127 let layout = Layout::from_size_align(size_prefixed_bytes, MIN_ALIGN).unwrap();
128 // SAFETY: `ptr_prefix` was allocated via same allocator with same layout.
129 alloc::dealloc(ptr_prefix, layout)
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn aligned_type_size() {
138 unsafe {
139 assert!(MIN_ALIGN >= mem::size_of::<usize>());
140 assert!(MIN_ALIGN >= mem::size_of::<u128>());
141 assert!(MIN_ALIGN >= crate::ffi::c_sizeof_long_double);
142 }
143 }
144
145 #[test]
146 fn aligned_type_align() {
147 unsafe {
148 assert!(MIN_ALIGN.is_power_of_two());
149 assert!(MIN_ALIGN >= mem::align_of::<usize>());
150 assert!(MIN_ALIGN >= mem::align_of::<&usize>());
151 assert!(MIN_ALIGN >= mem::align_of::<u128>());
152 assert!(MIN_ALIGN >= crate::ffi::c_alignof_long_double);
153 }
154 }
155}