nstd_sys/
alloc.rs

1//! Low level memory allocation.
2extern crate alloc;
3#[cfg(windows)]
4use crate::os::windows::alloc::{
5    nstd_os_windows_alloc_allocate, nstd_os_windows_alloc_allocate_zeroed,
6    nstd_os_windows_alloc_deallocate,
7};
8use crate::{
9    core::{
10        alloc::{
11            nstd_core_alloc_layout_align, nstd_core_alloc_layout_new,
12            nstd_core_alloc_layout_new_unchecked, nstd_core_alloc_layout_size, NSTDAllocError,
13            NSTDAllocLayout, NSTDAllocator,
14        },
15        mem::{nstd_core_mem_copy, nstd_core_mem_dangling_mut},
16        optional::NSTDOptional,
17    },
18    NSTDAny, NSTDAnyMut, NSTD_NULL,
19};
20use cfg_if::cfg_if;
21use core::{
22    alloc::Layout,
23    marker::PhantomData,
24    ops::{Deref, DerefMut},
25    ptr::addr_of,
26};
27use nstdapi::nstdapi;
28
29/// An FFI safe [Box] variant for `nstd`.
30#[repr(transparent)]
31#[allow(dead_code)]
32pub(crate) struct CBox<T>(NSTDAnyMut, PhantomData<T>);
33#[allow(dead_code)]
34impl<T> CBox<T> {
35    /// Creates a new heap allocated [`CBox`] object.
36    pub(crate) fn new(value: T) -> Option<Self> {
37        match core::mem::size_of::<T>() {
38            #[allow(unused_unsafe)]
39            // SAFETY: This operation is safe.
40            0 => unsafe { Some(Self(nstd_core_mem_dangling_mut(), PhantomData)) },
41            size => {
42                #[allow(unused_unsafe)]
43                // SAFETY: This operation is safe.
44                match unsafe { nstd_core_alloc_layout_new(size, core::mem::align_of::<T>()) } {
45                    // SAFETY: `size` is greater than 0.
46                    NSTDOptional::Some(layout) => match unsafe { nstd_alloc_allocate(layout) } {
47                        NSTD_NULL => None,
48                        mem => {
49                            // SAFETY: `mem` is a non-null pointer to `size` uninitialized bytes.
50                            unsafe { nstd_core_mem_copy(mem.cast(), addr_of!(value).cast(), size) };
51                            core::mem::forget(value);
52                            Some(Self(mem, PhantomData))
53                        }
54                    },
55                    NSTDOptional::None => None,
56                }
57            }
58        }
59    }
60
61    /// Moves a [`CBox`] value onto the stack.
62    pub(crate) fn into_inner(self) -> T {
63        // SAFETY: `self.0` points to a valid object of type `T`.
64        let value = unsafe { (self.0 as *const T).read() };
65        let size = core::mem::size_of::<T>();
66        if size > 0 {
67            let align = core::mem::align_of::<T>();
68            // SAFETY:
69            // - `size` is never greater than `NSTDInt`'s max value.
70            // - `align` is a nonzero power of two.
71            let layout = unsafe { nstd_core_alloc_layout_new_unchecked(size, align) };
72            // SAFETY:
73            // - `self.0` points to a valid object of type `T`.
74            // - `size` is greater than 0.
75            unsafe { nstd_alloc_deallocate(self.0, layout) };
76        }
77        core::mem::forget(self);
78        value
79    }
80}
81impl<T> Deref for CBox<T> {
82    /// [`CBox`]'s dereference target.
83    type Target = T;
84
85    /// Immutably dereferences a [`CBox`].
86    #[inline]
87    fn deref(&self) -> &Self::Target {
88        // SAFETY: `self.0` points to a valid object of type `T`.
89        unsafe { &*(self.0 as *const _) }
90    }
91}
92impl<T> DerefMut for CBox<T> {
93    /// Mutably dereferences a [`CBox`].
94    #[inline]
95    fn deref_mut(&mut self) -> &mut Self::Target {
96        // SAFETY: `self.0` points to a valid object of type `T`.
97        unsafe { &mut *self.0.cast() }
98    }
99}
100impl<T> Drop for CBox<T> {
101    /// [`CBox`]'s destructor.
102    fn drop(&mut self) {
103        // SAFETY:
104        // - `self.0` points to a valid object of type `T`.
105        // - `size` is greater than 0.
106        unsafe {
107            drop(self.0.cast::<T>().read());
108            let size = core::mem::size_of::<T>();
109            if size > 0 {
110                let align = core::mem::align_of::<T>();
111                let layout = nstd_core_alloc_layout_new_unchecked(size, align);
112                nstd_alloc_deallocate(self.0, layout);
113            }
114        }
115    }
116}
117
118/// Forwards an `NSTD_ALLOCATOR`'s `allocate` call to `nstd_alloc_allocate`.
119#[inline]
120unsafe extern "C" fn allocate(_: NSTDAny, layout: NSTDAllocLayout) -> NSTDAnyMut {
121    nstd_alloc_allocate(layout)
122}
123
124/// Forwards an `NSTD_ALLOCATOR`'s `allocate_zeroed` call to `nstd_alloc_allocate_zeroed`.
125#[inline]
126unsafe extern "C" fn allocate_zeroed(_: NSTDAny, layout: NSTDAllocLayout) -> NSTDAnyMut {
127    nstd_alloc_allocate_zeroed(layout)
128}
129
130/// Forwards an `NSTD_ALLOCATOR`'s `reallocate` call to `nstd_alloc_reallocate`.
131#[inline]
132unsafe extern "C" fn reallocate(
133    _: NSTDAny,
134    ptr: &mut NSTDAnyMut,
135    old_layout: NSTDAllocLayout,
136    new_layout: NSTDAllocLayout,
137) -> NSTDAllocError {
138    nstd_alloc_reallocate(ptr, old_layout, new_layout)
139}
140
141/// Forwards an `NSTD_ALLOCATOR`'s `deallocate` call to `nstd_alloc_deallocate`.
142#[inline]
143unsafe extern "C" fn deallocate(
144    _: NSTDAny,
145    ptr: NSTDAnyMut,
146    layout: NSTDAllocLayout,
147) -> NSTDAllocError {
148    nstd_alloc_deallocate(ptr, layout)
149}
150
151/// `nstd`'s default allocator.
152#[nstdapi]
153pub static NSTD_ALLOCATOR: NSTDAllocator = NSTDAllocator {
154    state: NSTD_NULL,
155    allocate,
156    allocate_zeroed,
157    reallocate,
158    deallocate,
159};
160
161/// The `NSTDAllocator`'s `allocate` function.
162#[inline]
163unsafe extern "C" fn rust_allocate(_: NSTDAny, layout: NSTDAllocLayout) -> NSTDAnyMut {
164    let size = nstd_core_alloc_layout_size(layout);
165    let align = nstd_core_alloc_layout_align(layout);
166    if let Ok(layout) = Layout::from_size_align(size, align) {
167        return alloc::alloc::alloc(layout).cast();
168    }
169    NSTD_NULL
170}
171
172/// The `NSTDAllocator`'s `allocate_zeroed` function.
173#[inline]
174unsafe extern "C" fn rust_allocate_zeroed(_: NSTDAny, layout: NSTDAllocLayout) -> NSTDAnyMut {
175    let size = nstd_core_alloc_layout_size(layout);
176    let align = nstd_core_alloc_layout_align(layout);
177    if let Ok(layout) = Layout::from_size_align(size, align) {
178        return alloc::alloc::alloc_zeroed(layout).cast();
179    }
180    NSTD_NULL
181}
182
183/// The `NSTDAllocator`'s `reallocate` function.
184unsafe extern "C" fn rust_reallocate(
185    this: NSTDAny,
186    ptr: &mut NSTDAnyMut,
187    old_layout: NSTDAllocLayout,
188    new_layout: NSTDAllocLayout,
189) -> NSTDAllocError {
190    if old_layout != new_layout {
191        let new_mem = rust_allocate(this, new_layout);
192        if new_mem.is_null() {
193            return NSTDAllocError::NSTD_ALLOC_ERROR_OUT_OF_MEMORY;
194        }
195        let old_size = nstd_core_alloc_layout_size(old_layout);
196        let new_size = nstd_core_alloc_layout_size(new_layout);
197        nstd_core_mem_copy(new_mem.cast(), (*ptr).cast(), old_size.min(new_size));
198        rust_deallocate(this, *ptr, old_layout);
199        *ptr = new_mem;
200    }
201    NSTDAllocError::NSTD_ALLOC_ERROR_NONE
202}
203
204/// The `NSTDAllocator`'s `deallocate` function.
205unsafe extern "C" fn rust_deallocate(
206    _: NSTDAny,
207    ptr: NSTDAnyMut,
208    layout: NSTDAllocLayout,
209) -> NSTDAllocError {
210    let size = nstd_core_alloc_layout_size(layout);
211    let align = nstd_core_alloc_layout_align(layout);
212    if let Ok(layout) = Layout::from_size_align(size, align) {
213        alloc::alloc::dealloc(ptr.cast(), layout);
214        return NSTDAllocError::NSTD_ALLOC_ERROR_NONE;
215    }
216    NSTDAllocError::NSTD_ALLOC_ERROR_INVALID_LAYOUT
217}
218
219/// Rust's [Global] [`NSTDAllocator`].
220#[allow(dead_code)]
221pub(crate) static GLOBAL_ALLOCATOR: NSTDAllocator = NSTDAllocator {
222    state: NSTD_NULL,
223    allocate: rust_allocate,
224    allocate_zeroed: rust_allocate_zeroed,
225    reallocate: rust_reallocate,
226    deallocate: rust_deallocate,
227};
228
229/// Allocates a new block of memory.
230///
231/// If allocation fails, a null pointer is returned.
232///
233/// If allocation succeeds, this returns a pointer to the new memory that is suitably aligned for
234/// `layout`'s alignment and the number of bytes allocated is at least equal to `layout`'s size.
235///
236/// # Parameters:
237///
238/// - `NSTDAllocLayout layout` - Describes the memory layout to allocate for.
239///
240/// # Returns
241///
242/// `NSTDAnyMut ptr` - A pointer to the allocated memory, null on error.
243///
244/// # Safety
245///
246/// - Behavior is undefined if `layout`'s size is zero.
247///
248/// - The new memory buffer should be considered uninitialized.
249///
250/// # Example
251///
252/// ```
253/// use nstd_sys::{
254///     alloc::{nstd_alloc_allocate, nstd_alloc_deallocate},
255///     core::alloc::{nstd_core_alloc_layout_new, NSTDAllocError::NSTD_ALLOC_ERROR_NONE},
256/// };
257///
258/// unsafe {
259///     let layout = nstd_core_alloc_layout_new(32, 1).unwrap();
260///     let mem = nstd_alloc_allocate(layout);
261///     assert!(!mem.is_null());
262///     assert!(nstd_alloc_deallocate(mem, layout) == NSTD_ALLOC_ERROR_NONE);
263/// }
264/// ```
265#[inline]
266#[nstdapi]
267pub unsafe fn nstd_alloc_allocate(layout: NSTDAllocLayout) -> NSTDAnyMut {
268    cfg_if! {
269        if #[cfg(any(
270            all(
271                target_os = "linux",
272                target_env = "gnu",
273                any(
274                    target_arch = "arm",
275                    target_arch = "aarch64",
276                    target_arch = "csky",
277                    target_arch = "loongarch64",
278                    target_arch = "m68k",
279                    target_arch = "mips",
280                    target_arch = "mips32r6",
281                    target_arch = "mips64",
282                    target_arch = "mips64r6",
283                    target_arch = "powerpc64",
284                    target_arch = "sparc",
285                    target_arch = "sparc64",
286                    target_arch = "x86",
287                    target_arch = "x86_64"
288                )
289            ),
290            all(
291                target_os = "linux",
292                any(target_env = "musl", target_env = "ohos"),
293                any(
294                    target_arch = "arm",
295                    target_arch = "aarch64",
296                    target_arch = "mips",
297                    target_arch = "riscv32",
298                    target_arch = "x86",
299                    target_arch = "x86_64"
300                )
301            ),
302            all(
303                target_os = "android",
304                any(
305                    target_arch = "aarch64",
306                    target_arch = "riscv64",
307                    target_arch = "x86",
308                    target_arch = "x86_64"
309                )
310            ),
311            all(
312                any(
313                    target_os = "macos",
314                    target_os = "ios",
315                    target_os = "tvos",
316                    target_os = "watchos"
317                ),
318                any(target_pointer_width = "32", target_arch = "aarch64", target_arch = "x86_64")
319            ),
320            all(target_os = "freebsd", target_arch = "x86_64"),
321            target_env = "wasi",
322            target_os = "wasi",
323            target_os = "emscripten"
324        ))] {
325            let align = nstd_core_alloc_layout_align(layout);
326            if align <= core::mem::align_of::<libc::max_align_t>() {
327                libc::malloc(nstd_core_alloc_layout_size(layout))
328            } else {
329                let size = nstd_core_alloc_layout_size(layout);
330                let min_align = core::mem::size_of::<NSTDAnyMut>();
331                let mut ptr = NSTD_NULL;
332                libc::posix_memalign(&mut ptr, align.max(min_align), size);
333                ptr
334            }
335        } else if #[cfg(any(unix, target_os = "teeos"))] {
336            let size = nstd_core_alloc_layout_size(layout);
337            let min_align = core::mem::size_of::<NSTDAnyMut>();
338            let align = nstd_core_alloc_layout_align(layout).max(min_align);
339            let mut ptr = NSTD_NULL;
340            libc::posix_memalign(&mut ptr, align, size);
341            ptr
342        } else if #[cfg(target_os = "solid_asp3")] {
343            use crate::NSTD_INT_MAX;
344            let mut size = nstd_core_alloc_layout_size(layout);
345            let align = nstd_core_alloc_layout_align(layout);
346            #[allow(clippy::arithmetic_side_effects)]
347            let off = size % align;
348            #[allow(clippy::arithmetic_side_effects)]
349            if off != 0 {
350                size = match size.checked_add(align - off) {
351                    Some(size) if size <= NSTD_INT_MAX => size,
352                    _ => return NSTD_NULL,
353                };
354            }
355            libc::aligned_alloc(align, size)
356        } else if #[cfg(windows)] {
357            nstd_os_windows_alloc_allocate(layout)
358        } else {
359            let size = nstd_core_alloc_layout_size(layout);
360            let align = nstd_core_alloc_layout_align(layout);
361            if let Ok(layout) = Layout::from_size_align(size, align) {
362                return alloc::alloc::alloc(layout).cast();
363            }
364            NSTD_NULL
365        }
366    }
367}
368
369/// Allocates a new block of zero-initialized memory.
370///
371/// If allocation fails, a null pointer is returned.
372///
373/// If allocation succeeds, this returns a pointer to the new memory that is suitably aligned
374/// for `layout`'s alignment and the number of bytes allocated is at least equal to `layout`'s
375/// size.
376///
377/// # Parameters:
378///
379/// - `NSTDAllocLayout layout` - Describes the memory layout to allocate for.
380///
381/// # Returns
382///
383/// `NSTDAnyMut ptr` - A pointer to the allocated memory, null on error.
384///
385/// # Safety
386///
387/// Behavior is undefined if `layout`'s size is zero.
388///
389/// # Example
390///
391/// ```
392/// use nstd_sys::{
393///     alloc::{nstd_alloc_allocate_zeroed, nstd_alloc_deallocate},
394///     core::alloc::{nstd_core_alloc_layout_new, NSTDAllocError::NSTD_ALLOC_ERROR_NONE},
395/// };
396///
397/// unsafe {
398///     let size = core::mem::size_of::<[i16; 16]>();
399///     let align = core::mem::align_of::<[i16; 16]>();
400///     let layout = nstd_core_alloc_layout_new(size, align).unwrap();
401///     let mem = nstd_alloc_allocate_zeroed(layout);
402///     assert!(!mem.is_null());
403///     assert!(*mem.cast::<[i16; 16]>() == [0i16; 16]);
404///     assert!(nstd_alloc_deallocate(mem, layout) == NSTD_ALLOC_ERROR_NONE);
405/// }
406/// ```
407#[inline]
408#[nstdapi]
409pub unsafe fn nstd_alloc_allocate_zeroed(layout: NSTDAllocLayout) -> NSTDAnyMut {
410    cfg_if! {
411        if #[cfg(any(
412            unix,
413            any(target_env = "wasi", target_os = "wasi"),
414            target_os = "solid_asp3",
415            target_os = "teeos"
416        ))] {
417            use crate::core::mem::nstd_core_mem_zero;
418            let ptr = nstd_alloc_allocate(layout);
419            if !ptr.is_null() {
420                nstd_core_mem_zero(ptr.cast(), nstd_core_alloc_layout_size(layout));
421            }
422            ptr
423        } else if #[cfg(windows)] {
424            nstd_os_windows_alloc_allocate_zeroed(layout)
425        } else {
426            let size = nstd_core_alloc_layout_size(layout);
427            let align = nstd_core_alloc_layout_align(layout);
428            if let Ok(layout) = Layout::from_size_align(size, align) {
429                return alloc::alloc::alloc_zeroed(layout).cast();
430            }
431            NSTD_NULL
432        }
433    }
434}
435
436/// Reallocates memory that was previously allocated by this allocator.
437///
438/// On successful reallocation, `ptr` will point to the new memory location and
439/// `NSTD_ALLOC_ERROR_NONE` will be returned. If this is not the case and reallocation fails,
440/// the pointer will remain untouched and the appropriate error is returned.
441///
442/// # Parameters:
443///
444/// - `NSTDAnyMut *ptr` - A pointer to the allocated memory.
445///
446/// - `NSTDAllocLayout old_layout` - Describes the previous memory layout.
447///
448/// - `NSTDAllocLayout new_layout` - Describes the new memory layout to allocate for.
449///
450/// # Returns
451///
452/// `NSTDAllocError errc` - The allocation operation error code.
453///
454/// # Safety
455///
456/// - Behavior is undefined if `new_layout`'s size is zero.
457///
458/// - Behavior is undefined if `ptr` is not a pointer to memory allocated by this allocator.
459///
460/// - `old_layout` must be the same value that was used to allocate the memory buffer.
461///
462/// # Example
463///
464/// ```
465/// use nstd_sys::{
466///     alloc::{nstd_alloc_allocate_zeroed, nstd_alloc_deallocate, nstd_alloc_reallocate},
467///     core::alloc::{nstd_core_alloc_layout_new, NSTDAllocError::NSTD_ALLOC_ERROR_NONE},
468/// };
469///
470///
471/// unsafe {
472///     let mut size = core::mem::size_of::<[u64; 64]>();
473///     let mut align = core::mem::align_of::<[u64; 64]>();
474///     let layout = nstd_core_alloc_layout_new(size, align).unwrap();
475///     let mut mem = nstd_alloc_allocate_zeroed(layout);
476///     assert!(!mem.is_null());
477///     assert!(*mem.cast::<[u64; 64]>() == [0u64; 64]);
478///
479///     size = core::mem::size_of::<[u64; 32]>();
480///     align = core::mem::align_of::<[u64; 32]>();
481///     let new_layout = nstd_core_alloc_layout_new(size, align).unwrap();
482///     assert!(nstd_alloc_reallocate(&mut mem, layout, new_layout) == NSTD_ALLOC_ERROR_NONE);
483///     assert!(*mem.cast::<[u64; 32]>() == [0u64; 32]);
484///
485///     assert!(nstd_alloc_deallocate(mem, new_layout) == NSTD_ALLOC_ERROR_NONE);
486/// }
487/// ```
488#[inline]
489#[nstdapi]
490pub unsafe fn nstd_alloc_reallocate(
491    ptr: &mut NSTDAnyMut,
492    old_layout: NSTDAllocLayout,
493    new_layout: NSTDAllocLayout,
494) -> NSTDAllocError {
495    if old_layout != new_layout {
496        let new_mem = nstd_alloc_allocate(new_layout);
497        if new_mem.is_null() {
498            return NSTDAllocError::NSTD_ALLOC_ERROR_OUT_OF_MEMORY;
499        }
500        let old_size = nstd_core_alloc_layout_size(old_layout);
501        let new_size = nstd_core_alloc_layout_size(new_layout);
502        nstd_core_mem_copy(new_mem.cast(), (*ptr).cast(), old_size.min(new_size));
503        nstd_alloc_deallocate(*ptr, old_layout);
504        *ptr = new_mem;
505    }
506    NSTDAllocError::NSTD_ALLOC_ERROR_NONE
507}
508
509/// Deallocates memory that was previously allocated by this allocator.
510///
511/// # Parameters:
512///
513/// - `NSTDAnyMut ptr` - A pointer to the allocated memory.
514///
515/// - `NSTDAllocLayout layout` - Describes the layout of memory that `ptr` points to.
516///
517/// # Returns
518///
519/// `NSTDAllocError errc` - The allocation operation error code.
520///
521/// # Safety
522///
523/// - Behavior is undefined if `ptr` is not a pointer to memory allocated by this allocator.
524///
525/// - `layout` must be the same value that was used to allocate the memory buffer.
526///
527/// # Example
528///
529/// ```
530/// use nstd_sys::{
531///     alloc::{nstd_alloc_allocate, nstd_alloc_deallocate},
532///     core::alloc::{nstd_core_alloc_layout_new, NSTDAllocError::NSTD_ALLOC_ERROR_NONE},
533/// };
534///
535/// unsafe {
536///     let layout = nstd_core_alloc_layout_new(24, 1).unwrap();
537///     let mem = nstd_alloc_allocate(layout);
538///     assert!(!mem.is_null());
539///     assert!(nstd_alloc_deallocate(mem, layout) == NSTD_ALLOC_ERROR_NONE);
540/// }
541/// ```
542#[inline]
543#[nstdapi]
544#[allow(unused_variables)]
545pub unsafe fn nstd_alloc_deallocate(ptr: NSTDAnyMut, layout: NSTDAllocLayout) -> NSTDAllocError {
546    cfg_if! {
547        if #[cfg(any(
548            unix,
549            any(target_env = "wasi", target_os = "wasi"),
550            target_os = "solid_asp3",
551            target_os = "teeos"
552        ))] {
553            libc::free(ptr);
554            NSTDAllocError::NSTD_ALLOC_ERROR_NONE
555        } else if #[cfg(windows)] {
556            nstd_os_windows_alloc_deallocate(ptr);
557            NSTDAllocError::NSTD_ALLOC_ERROR_NONE
558        } else {
559            let size = nstd_core_alloc_layout_size(layout);
560            let align = nstd_core_alloc_layout_align(layout);
561            if let Ok(layout) = Layout::from_size_align(size, align) {
562                alloc::alloc::dealloc(ptr.cast(), layout);
563                return NSTDAllocError::NSTD_ALLOC_ERROR_NONE;
564            }
565            NSTDAllocError::NSTD_ALLOC_ERROR_INVALID_LAYOUT
566        }
567    }
568}