rquickjs_core/
allocator.rs

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