rquickjs_core/
allocator.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Tools for using different allocators with QuickJS.

use crate::qjs;

mod rust;

pub use rust::RustAllocator;

/// The allocator interface
///
/// # Safety
/// Failure to implement this trait correctly will result in undefined behavior.
/// - `alloc` must return a either a null pointer or a pointer to an available region of memory
/// atleast `size` bytes and aligned to the size of `usize`.
/// - `realloc` must either return a null pointer or return a pointer to an available region of
/// memory atleast `new_size` bytes and aligned to the size of `usize`.
/// - `usable_size` must return the amount of available memory for any allocation allocated with
/// this allocator.
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "allocator")))]
pub unsafe trait Allocator {
    /// Allocate new memory
    ///
    ///
    fn alloc(&mut self, size: usize) -> *mut u8;

    /// Allocates memory for an array of num objects of size and initializes all bytes in the allocated storage to zero.
    ///
    ///
    fn calloc(&mut self, count: usize, size: usize) -> *mut u8;

    /// De-allocate previously allocated memory
    ///
    /// # Safety
    /// Caller must ensure that the pointer that is being deallocated was allocated by the same
    /// Allocator instance.
    unsafe fn dealloc(&mut self, ptr: *mut u8);

    /// Re-allocate previously allocated memory
    ///
    /// # Safety
    /// Caller must ensure that the pointer points to an allocation that was allocated by the same
    /// Allocator instance.
    unsafe fn realloc(&mut self, ptr: *mut u8, new_size: usize) -> *mut u8;

    /// Get usable size of allocated memory region
    ///
    /// # Safety
    /// Caller must ensure that the pointer handed to this function points to an allocation
    /// allocated by the same allocator instance.
    unsafe fn usable_size(ptr: *mut u8) -> usize
    where
        Self: Sized;
}

type DynAllocator = Box<dyn Allocator>;

#[derive(Debug)]
pub(crate) struct AllocatorHolder(*mut DynAllocator);

impl Drop for AllocatorHolder {
    fn drop(&mut self) {
        let _ = unsafe { Box::from_raw(self.0) };
    }
}

impl AllocatorHolder {
    pub(crate) fn functions<A>() -> qjs::JSMallocFunctions
    where
        A: Allocator,
    {
        qjs::JSMallocFunctions {
            js_calloc: Some(Self::calloc::<A>),
            js_malloc: Some(Self::malloc::<A>),
            js_free: Some(Self::free::<A>),
            js_realloc: Some(Self::realloc::<A>),
            js_malloc_usable_size: Some(Self::malloc_usable_size::<A>),
        }
    }

    pub(crate) fn new<A>(allocator: A) -> Self
    where
        A: Allocator + 'static,
    {
        Self(Box::into_raw(Box::new(Box::new(allocator))))
    }

    pub(crate) fn opaque_ptr(&self) -> *mut DynAllocator {
        self.0
    }

    unsafe extern "C" fn calloc<A>(
        opaque: *mut qjs::c_void,
        count: qjs::size_t,
        size: qjs::size_t,
    ) -> *mut qjs::c_void
    where
        A: Allocator,
    {
        let allocator = &mut *(opaque as *mut DynAllocator);
        let rust_size: usize = size.try_into().expect(qjs::SIZE_T_ERROR);
        let rust_count: usize = count.try_into().expect(qjs::SIZE_T_ERROR);
        allocator.calloc(rust_count, rust_size) as *mut qjs::c_void
    }

    unsafe extern "C" fn malloc<A>(opaque: *mut qjs::c_void, size: qjs::size_t) -> *mut qjs::c_void
    where
        A: Allocator,
    {
        let allocator = &mut *(opaque as *mut DynAllocator);
        let rust_size: usize = size.try_into().expect(qjs::SIZE_T_ERROR);
        allocator.alloc(rust_size) as *mut qjs::c_void
    }

    unsafe extern "C" fn free<A>(opaque: *mut qjs::c_void, ptr: *mut qjs::c_void)
    where
        A: Allocator,
    {
        // simulate the default behavior of libc::free
        if ptr.is_null() {
            // nothing to do
            return;
        }

        let allocator = &mut *(opaque as *mut DynAllocator);
        allocator.dealloc(ptr as _);
    }

    unsafe extern "C" fn realloc<A>(
        opaque: *mut qjs::c_void,
        ptr: *mut qjs::c_void,
        size: qjs::size_t,
    ) -> *mut qjs::c_void
    where
        A: Allocator,
    {
        let rust_size: usize = size.try_into().expect(qjs::SIZE_T_ERROR);
        let allocator = &mut *(opaque as *mut DynAllocator);
        allocator.realloc(ptr as _, rust_size) as *mut qjs::c_void
    }

    unsafe extern "C" fn malloc_usable_size<A>(ptr: *const qjs::c_void) -> qjs::size_t
    where
        A: Allocator,
    {
        // simulate the default behavior of libc::malloc_usable_size
        if ptr.is_null() {
            return 0;
        }
        A::usable_size(ptr as _).try_into().unwrap()
    }
}