rquickjs_core/
allocator.rs

1//! Tools for using different allocators with QuickJS.
2
3use crate::qjs;
4
5mod rust;
6
7use alloc::boxed::Box;
8pub use rust::RustAllocator;
9
10/// The allocator interface
11///
12/// # Safety
13/// Failure to implement this trait correctly will result in undefined behavior.
14/// - `alloc` must return a either a null pointer or a pointer to an available region of memory
15/// atleast `size` bytes and aligned to the size of `usize`.
16/// - `realloc` must either return a null pointer or return a pointer to an available region of
17/// memory atleast `new_size` bytes and aligned to the size of `usize`.
18/// - `usable_size` must return the amount of available memory for any allocation allocated with
19/// this allocator.
20pub unsafe trait Allocator {
21    /// Allocate new memory
22    ///
23    ///
24    fn alloc(&mut self, size: usize) -> *mut u8;
25
26    /// Allocates memory for an array of num objects of size and initializes all bytes in the allocated storage to zero.
27    ///
28    ///
29    fn calloc(&mut self, count: usize, size: usize) -> *mut u8;
30
31    /// De-allocate previously allocated memory
32    ///
33    /// # Safety
34    /// Caller must ensure that the pointer that is being deallocated was allocated by the same
35    /// Allocator instance.
36    unsafe fn dealloc(&mut self, ptr: *mut u8);
37
38    /// Re-allocate previously allocated memory
39    ///
40    /// # Safety
41    /// Caller must ensure that the pointer points to an allocation that was allocated by the same
42    /// Allocator instance.
43    unsafe fn realloc(&mut self, ptr: *mut u8, new_size: usize) -> *mut u8;
44
45    /// Get usable size of allocated memory region
46    ///
47    /// # Safety
48    /// Caller must ensure that the pointer handed to this function points to an allocation
49    /// allocated by the same allocator instance.
50    unsafe fn usable_size(ptr: *mut u8) -> usize
51    where
52        Self: Sized;
53}
54
55type DynAllocator = Box<dyn Allocator>;
56
57#[derive(Debug)]
58pub(crate) struct AllocatorHolder(*mut DynAllocator);
59
60impl Drop for AllocatorHolder {
61    fn drop(&mut self) {
62        let _ = unsafe { Box::from_raw(self.0) };
63    }
64}
65
66#[allow(clippy::extra_unused_type_parameters)]
67impl AllocatorHolder {
68    pub(crate) fn functions<A>() -> qjs::JSMallocFunctions
69    where
70        A: Allocator,
71    {
72        qjs::JSMallocFunctions {
73            js_calloc: Some(Self::calloc::<A>),
74            js_malloc: Some(Self::malloc::<A>),
75            js_free: Some(Self::free::<A>),
76            js_realloc: Some(Self::realloc::<A>),
77            js_malloc_usable_size: Some(Self::malloc_usable_size::<A>),
78        }
79    }
80
81    pub(crate) fn new<A>(allocator: A) -> Self
82    where
83        A: Allocator + 'static,
84    {
85        Self(Box::into_raw(Box::new(Box::new(allocator))))
86    }
87
88    pub(crate) fn opaque_ptr(&self) -> *mut DynAllocator {
89        self.0
90    }
91
92    unsafe extern "C" fn calloc<A>(
93        opaque: *mut qjs::c_void,
94        count: qjs::size_t,
95        size: qjs::size_t,
96    ) -> *mut qjs::c_void
97    where
98        A: Allocator,
99    {
100        let allocator = &mut *(opaque as *mut DynAllocator);
101        let rust_size: usize = size.try_into().expect(qjs::SIZE_T_ERROR);
102        let rust_count: usize = count.try_into().expect(qjs::SIZE_T_ERROR);
103        allocator.calloc(rust_count, rust_size) as *mut qjs::c_void
104    }
105
106    unsafe extern "C" fn malloc<A>(opaque: *mut qjs::c_void, size: qjs::size_t) -> *mut qjs::c_void
107    where
108        A: Allocator,
109    {
110        let allocator = &mut *(opaque as *mut DynAllocator);
111        let rust_size: usize = size.try_into().expect(qjs::SIZE_T_ERROR);
112        allocator.alloc(rust_size) as *mut qjs::c_void
113    }
114
115    unsafe extern "C" fn free<A>(opaque: *mut qjs::c_void, ptr: *mut qjs::c_void)
116    where
117        A: Allocator,
118    {
119        // simulate the default behavior of libc::free
120        if ptr.is_null() {
121            // nothing to do
122            return;
123        }
124
125        let allocator = &mut *(opaque as *mut DynAllocator);
126        allocator.dealloc(ptr as _);
127    }
128
129    unsafe extern "C" fn realloc<A>(
130        opaque: *mut qjs::c_void,
131        ptr: *mut qjs::c_void,
132        size: qjs::size_t,
133    ) -> *mut qjs::c_void
134    where
135        A: Allocator,
136    {
137        let rust_size: usize = size.try_into().expect(qjs::SIZE_T_ERROR);
138        let allocator = &mut *(opaque as *mut DynAllocator);
139        allocator.realloc(ptr as _, rust_size) as *mut qjs::c_void
140    }
141
142    unsafe extern "C" fn malloc_usable_size<A>(ptr: *const qjs::c_void) -> qjs::size_t
143    where
144        A: Allocator,
145    {
146        // simulate the default behavior of libc::malloc_usable_size
147        if ptr.is_null() {
148            return 0;
149        }
150        A::usable_size(ptr as _).try_into().unwrap()
151    }
152}