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}