janetrs/
allocator.rs

1//! This module provides a allocator that uses the Janet scratch memory API to create
2//! objects tracked by the Janet Garbage Collector.
3//!
4//! For more in depth information, you can look at the [Janet memory model documentation]
5//!
6//! [Janet memory model documentation]: https://janet-lang.org/capi/memory-model.html
7use core::{
8    alloc::Layout,
9    ptr::{self, NonNull},
10};
11
12#[cfg(feature = "nightly")]
13use core::alloc::{AllocError, Allocator};
14
15#[cfg(any(
16    target_arch = "x86",
17    target_arch = "arm",
18    target_arch = "mips",
19    target_arch = "powerpc",
20    target_arch = "powerpc64",
21    target_arch = "sparc",
22    target_arch = "wasm32",
23    target_arch = "hexagon",
24    target_arch = "riscv32"
25))]
26const MIN_ALIGN: usize = 8;
27#[cfg(any(
28    target_arch = "x86_64",
29    target_arch = "aarch64",
30    target_arch = "mips64",
31    target_arch = "s390x",
32    target_arch = "sparc64",
33    target_arch = "riscv64"
34))]
35const MIN_ALIGN: usize = 16;
36
37/// Memory allocator that will certainly be cleaned up in the next Janet Garbage
38/// Collection cycle.
39///
40/// If this crate are build with the `nightly` feature enabled, this type also implements
41/// the [`Allocator`](core::alloc::Allocator) trait. That means that with the `nightly`
42/// feature set it's possible to use this allocator with Rust types that uses allocator as
43/// parameter, like [`Box`].
44#[derive(Copy, Clone, Default, Debug)]
45pub struct Scratch;
46
47impl Scratch {
48    /// Attempts to allocate a block of memory.
49    ///
50    /// On success, returns a [`NonNull<[u8]>`][NonNull] meeting the size and alignment
51    /// guarantees of `layout`.
52    ///
53    /// The returned block may have a larger size than specified by `layout.size()`, and
54    /// may or may not have its contents initialized.
55    #[inline]
56    pub fn malloc(&self, layout: Layout) -> Option<NonNull<[u8]>> {
57        // Allocate size if it fits in the type size and has a alignment smaller than the
58        // minimum alignment of the architecture. Over allocate otherwise
59        let (raw_ptr, alloc_mem_size) =
60            if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() {
61                let size = layout.size();
62
63                unsafe { (evil_janet::janet_smalloc(size) as *mut u8, size) }
64            } else {
65                // MacOS alloc_system is buggy on huge alignments (e.g. an align of `1 << 32`)
66                #[cfg(target_os = "macos")]
67                if layout.align() > (1 << 31) {
68                    return None;
69                }
70
71                let size = layout.size() + layout.align();
72                unsafe { (evil_janet::janet_smalloc(size) as *mut u8, size) }
73            };
74        NonNull::new(ptr::slice_from_raw_parts_mut(raw_ptr, alloc_mem_size))
75    }
76
77    /// Behaves like `allocate`, but also ensures that the returned memory is
78    /// zero-initialized.
79    #[inline]
80    pub fn calloc(&self, layout: Layout) -> Option<NonNull<[u8]>> {
81        // Allocate size if it fits in the type size and has a alignment smaller than the
82        // minimum alignment of the architecture. Over allocate otherwise
83        let (raw_ptr, alloc_mem_size) =
84            if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() {
85                let size = layout.size();
86
87                unsafe { (evil_janet::janet_scalloc(1, size) as *mut u8, size) }
88            } else {
89                // MacOS alloc_system is buggy on huge alignments (e.g. an align of `1 << 32`)
90                #[cfg(target_os = "macos")]
91                if layout.align() > (1 << 31) {
92                    return None;
93                }
94
95                let size = layout.size() + layout.align();
96                unsafe { (evil_janet::janet_scalloc(1, size) as *mut u8, size) }
97            };
98        NonNull::new(ptr::slice_from_raw_parts_mut(raw_ptr, alloc_mem_size))
99    }
100
101    /// Shrink or grow a block of memory to the given `new_size`.
102    /// The block is described by the given `ptr` pointer and `layout`.
103    ///
104    /// # Safety
105    ///
106    /// This function is unsafe because undefined behavior can result
107    /// if the caller does not ensure all of the following:
108    ///
109    /// * `ptr` must be currently allocated via this allocator,
110    /// * `layout` must be the same layout that was used to allocate that block of memory,
111    /// * `new_size` must be greater than zero.
112    /// * `new_size`, when rounded up to the nearest multiple of `layout.align()`, must
113    ///   not overflow (i.e., the rounded value must be less than `usize::MAX`).
114    #[inline]
115    pub unsafe fn realloc(
116        &self, ptr: NonNull<[u8]>, layout: Layout, new_size: usize,
117    ) -> Option<NonNull<[u8]>> {
118        let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
119
120        // Allocate size if it fits in the type size and has a alignment smaller than the
121        // minimum alignment of the architecture. Over allocate otherwise
122        let (raw_ptr, alloc_mem_size) =
123            if layout.align() <= MIN_ALIGN && layout.align() <= new_layout.size() {
124                let size = new_layout.size();
125
126                (
127                    evil_janet::janet_srealloc(ptr.as_ptr() as *mut _, size) as *mut u8,
128                    size,
129                )
130            } else {
131                // MacOS alloc_system is buggy on huge alignments (e.g. an align of `1 << 32`)
132                #[cfg(target_os = "macos")]
133                if layout.align() > (1 << 31) {
134                    return None;
135                }
136
137                let size = layout.size() + layout.align();
138                (
139                    evil_janet::janet_srealloc(ptr.as_ptr() as *mut _, size) as *mut u8,
140                    size,
141                )
142            };
143        NonNull::new(ptr::slice_from_raw_parts_mut(raw_ptr, alloc_mem_size))
144    }
145
146    /// Deallocates the memory referenced by `ptr`.
147    ///
148    /// # Safety
149    /// `ptr` must denote a block of memory currently allocated via this allocator.
150    #[inline]
151    pub unsafe fn free(&self, ptr: NonNull<[u8]>) {
152        evil_janet::janet_sfree(ptr.as_ptr() as *mut _)
153    }
154}
155
156#[cfg(feature = "nightly")]
157#[cfg_attr(docsrs, doc(cfg(feature = "nightly")))]
158unsafe impl Allocator for Scratch {
159    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
160        self.malloc(layout).ok_or(AllocError)
161    }
162
163    unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
164}