Skip to main content

memapi2/ffi/
c_alloc.rs

1use core::{
2    ffi::c_void,
3    ptr::{self, null_mut}
4};
5
6const NULL: *mut c_void = null_mut();
7
8/// Copies `size` bytes from `old_ptr` to `ptr` when `ptr` is non-null, then deallocates `old_ptr`.
9///
10/// If `ptr` is `NULL`, this is a no-op and `old_ptr` is not freed.
11///
12/// # Safety
13///
14/// - `old_ptr` must point to a C allocation of at least `size` bytes.
15/// - `ptr` must point to an allocation of at least `size` bytes.
16pub unsafe fn try_move(ptr: *mut c_void, old_ptr: *mut c_void, size: usize) {
17    if ptr != NULL {
18        // SAFETY: `ptr` validated nonnull, caller guarantees `old_ptr` is valid. caller guarantees
19        // `size` is <= size of allocation at `ptr` and <= size of allocation at `old_ptr`,
20        // so copying that many bytes is safe.
21        unsafe {
22            memcpy(ptr, old_ptr, size);
23        }
24        // SAFETY: caller guarantees that `old_ptr` is valid
25        unsafe {
26            c_dealloc(old_ptr);
27        }
28    }
29}
30
31/// Allocates `size` bytes with at least `align` alignment.
32///
33/// The closest Rust equivalent is [`alloc`](stdalloc::alloc::alloc).
34///
35/// On non-Windows platforms this forwards to `aligned_alloc`, which requires `align` to be a
36/// power of two and a multiple of `size_of::<*mut c_void>()`, and `size` to be a multiple of
37/// `align`.
38///
39/// # Returns
40///
41/// - On success returns a nonnull pointer to the allocated memory.
42/// - On allocation failure returns `NULL`.
43#[must_use = "this function allocates memory on success, and dropping the returned pointer will \
44              leak memory"]
45pub fn c_alloc(align: usize, size: usize) -> *mut c_void {
46    #[cfg(windows)]
47    // SAFETY: this function is safe to call
48    unsafe {
49        _aligned_malloc(size, align)
50    }
51    #[cfg(not(windows))]
52    // SAFETY: this function is safe to call
53    unsafe {
54        aligned_alloc(align, size)
55    }
56}
57
58/// Frees memory previously returned by the primary C allocator.
59///
60/// The closest Rust equivalent is [`dealloc`](stdalloc::alloc::dealloc).
61///
62/// # Safety
63///
64/// The caller must ensure:
65/// - `ptr` points to the start of a valid allocation returned by this allocator, or is `NULL`.
66/// - `ptr` has not yet been deallocated.
67pub unsafe fn c_dealloc(ptr: *mut c_void) {
68    #[cfg(windows)]
69    {
70        _aligned_free(ptr);
71    }
72    #[cfg(not(windows))]
73    {
74        free(ptr);
75    }
76}
77
78/// Allocates `size` bytes with at least `align` alignment and zeroes the allocation.
79///
80/// # Returns
81///
82/// - On success returns a nonnull pointer to `size` bytes of memory which are guaranteed to be
83///   zeroed.
84/// - On allocation failure returns `NULL`.
85///
86/// # Safety
87///
88/// The caller must ensure:
89/// - `align` is a power of two and a multiple of <code>[size_of]::<*mut [c_void]>()</code>.
90/// - `size` is a multiple of `align`.
91#[must_use = "this function allocates memory on success, and dropping the returned pointer will \
92              leak memory"]
93pub unsafe fn c_zalloc(align: usize, size: usize) -> *mut c_void {
94    // allocate
95    // SAFETY: requirements are passed on to the caller.
96    let ptr = unsafe { c_alloc(align, size) };
97
98    // zero memory if allocation was successful
99    if ptr != NULL {
100        // SAFETY: `ptr` is nonnull, and at least size bytes in length. `0i32` fits in a `u8`.
101        unsafe {
102            ptr::write_bytes(ptr, 0, size);
103        }
104    }
105
106    ptr
107}
108
109/// Grows an existing allocation.
110///
111/// Allocates a new block of `size` bytes with at least `align` alignment, copies `old_size`
112/// bytes from `old_ptr` into the new block, frees the old block, and returns the new pointer.
113/// New bytes are set by `alloc` and may be uninitialized depending on what allocation function
114/// is passed.
115///
116/// # Returns
117///
118/// - On success returns a nonnull pointer to the new allocation.
119/// - On allocation failure returns `NULL` and does **not** free the original allocation.
120///
121/// # Safety
122///
123/// The caller must ensure:
124/// - `old_ptr` was allocated by this allocator and is valid for reads of `old_size` bytes.
125/// - `old_size` equals the size of the allocation requested at `old_ptr`.
126/// - `align` is a power of two and a multiple of <code>[size_of]::<*mut [c_void]>()</code>.
127/// - `size` is greater than or equal to `old_size` and a multiple of `align`.
128#[cfg_attr(miri, track_caller)]
129pub unsafe fn grow_aligned(
130    old_ptr: *mut c_void,
131    old_size: usize,
132    align: usize,
133    size: usize,
134    alloc: unsafe fn(usize, usize) -> *mut c_void
135) -> *mut c_void {
136    // allocate new aligned memory
137    // SAFETY: requirements are passed on to the caller
138    let ptr = unsafe { alloc(align, size) };
139
140    // if successful, move data to new pointer
141    // SAFETY: requirements are passed on to the caller
142    unsafe {
143        try_move(ptr, old_ptr, old_size);
144    }
145
146    ptr
147}
148
149/// Shrinks an existing allocation.
150///
151/// Allocates a new block of `size` bytes with at least `align` alignment, copies `size` bytes
152/// from `old_ptr` into the new block, frees the old block, and returns the new pointer.
153///
154/// # Returns
155///
156/// - On success returns a nonnull pointer to the new allocation.
157/// - If `size == 0`, the old allocation is freed and a [`dangling`](ptr::dangling) pointer is
158///   returned.
159/// - On allocation failure returns `NULL` and does __not__ free the original allocation.
160///
161/// # Safety
162///
163/// The caller must ensure:
164/// - `old_ptr` was allocated by this allocator and is valid for reads of at least `size` bytes.
165/// - `align` is a power of two and a multiple of <code>[size_of]::<*mut [c_void]>()</code>.
166/// - `size` is less than or equal to the size of the allocation at `old_ptr` and a multiple of
167///   `align`.
168#[cfg_attr(miri, track_caller)]
169pub unsafe fn shrink_aligned(
170    old_ptr: *mut c_void,
171    align: usize,
172    size: usize // a memset-ing alloc here is useless, as it will just be overwritten anyway.
173) -> *mut c_void {
174    // allocate new aligned memory
175    // SAFETY: requirements are passed on to the caller
176    let ptr = unsafe { c_alloc(align, size) };
177
178    // if successful, move data to new pointer
179    // SAFETY: requirements are passed on to the caller
180    unsafe {
181        try_move(ptr, old_ptr, size);
182    }
183
184    ptr
185}
186
187// public in case the user wants them for some reason
188extern "C" {
189    /// Allocates `size` bytes.
190    ///
191    /// The closest Rust equivalent is [`alloc`](stdalloc::alloc::alloc) with the `layout`
192    /// parameter's alignment being <code>[align_of]::\<usize\>()</code>
193    ///
194    /// # Safety
195    ///
196    /// This function is safe to call but may return `NULL` if allocation fails, or `size` is 0.
197    pub fn malloc(size: usize) -> *mut c_void;
198
199    #[cfg(not(windows))]
200    /// Allocates `size` bytes with at least `align` alignment.
201    ///
202    /// The closest Rust equivalent is [`alloc`](stdalloc::alloc::alloc).
203    ///
204    /// # Returns
205    ///
206    /// - On success returns a nonnull pointer to the allocated memory.
207    /// - On allocation failure returns `NULL`.
208    ///
209    /// # Safety
210    ///
211    /// This function is safe to call but may return `NULL` if:
212    /// - `align` is not a power of two and a multiple of `size_of::<*mut c_void>()`.
213    /// - `size` is not a multiple of `align`.
214    pub fn aligned_alloc(align: usize, size: usize) -> *mut c_void;
215
216    #[cfg(not(windows))]
217    /// Frees memory previously returned by the primary C allocator.
218    ///
219    /// The closest Rust equivalent is [`dealloc`](stdalloc::alloc::dealloc).
220    ///
221    /// # Safety
222    ///
223    /// The caller must ensure:
224    /// - `ptr` points to the start of a valid allocation returned by this allocator _or_ is `NULL`.
225    /// - `ptr` has not yet been deallocated.
226    pub fn free(ptr: *mut c_void);
227
228    #[cfg(windows)]
229    /// Windows version of [`aligned_alloc`].
230    pub fn _aligned_malloc(size: usize, alignment: usize) -> *mut c_void;
231    #[cfg(windows)]
232    /// Windows version of [`free`] specifically for memory returned by [`_aligned_malloc`].
233    pub fn _aligned_free(ptr: *mut c_void);
234
235    /// Sets `count` bytes at `ptr` to `val`. The returned pointer is a copy of `ptr`.
236    ///
237    /// The closest Rust equivalent is [`write_bytes`](ptr::write_bytes).
238    ///
239    /// # Safety
240    ///
241    /// The caller must ensure:
242    /// - `ptr` points to `count` valid bytes.
243    /// - `val` contains a value less than [`u8::MAX`].
244    pub fn memset(ptr: *mut c_void, val: i32, count: usize) -> *mut c_void;
245
246    /// Copies `count` bytes from `src` to `dest`. The returned pointer is a copy of `dest`.
247    ///
248    /// `src` and `dest` must not overlap, or the result stored in `dest` may be unexpected.
249    ///
250    /// The closest Rust equivalent is [`copy_nonoverlapping`](ptr::copy_nonoverlapping)
251    ///
252    /// # Safety
253    ///
254    /// The caller must ensure:
255    /// - `src` points to a valid block of memory of at least `count` bytes.
256    /// - `dest` points to a valid block of memory of at least `count` bytes.
257    /// - `src` and `dest` do not overlap.
258    pub fn memcpy(dest: *mut c_void, src: *const c_void, count: usize) -> *mut c_void;
259
260    /// Copies `count` bytes from `src` to `dest`. The returned pointer is a copy of `dest`.
261    ///
262    /// Unlike [`memcpy`], `src` and `dest` may overlap.
263    ///
264    /// The closest Rust equivalent is [`copy`](ptr::copy)
265    ///
266    /// # Safety
267    ///
268    /// The caller must ensure:
269    /// - `src` points to a valid block of memory of at least `count` bytes.
270    /// - `dest` points to a valid block of memory of at least `count` bytes.
271    pub fn memmove(dest: *mut c_void, src: *const c_void, count: usize) -> *mut c_void;
272}