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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Tools for using different allocators with QuickJS.

use crate::qjs;
use std::ptr;

mod rust;

pub use rust::RustAllocator;

/// Raw memory pointer
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "allocator")))]
pub type RawMemPtr = *mut u8;

/// 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) -> RawMemPtr;

    /// 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: RawMemPtr);

    /// 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: RawMemPtr, new_size: usize) -> RawMemPtr;

    /// 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: RawMemPtr) -> 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_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
    }

    fn size_t(size: usize) -> qjs::size_t {
        size.try_into().expect(qjs::SIZE_T_ERROR)
    }

    unsafe extern "C" fn malloc<A>(
        state: *mut qjs::JSMallocState,
        size: qjs::size_t,
    ) -> *mut qjs::c_void
    where
        A: Allocator,
    {
        if size == 0 {
            return ptr::null_mut();
        }

        let state = &mut *state;

        if state.malloc_size + size > state.malloc_limit {
            return ptr::null_mut();
        }

        let rust_size: usize = size.try_into().expect(qjs::SIZE_T_ERROR);
        // simulate the default behavior of libc::malloc

        let allocator = &mut *(state.opaque as *mut DynAllocator);

        let res = allocator.alloc(rust_size as _);

        if res.is_null() {
            return ptr::null_mut();
        }

        let size = A::usable_size(res);

        state.malloc_count += 1;
        state.malloc_size += Self::size_t(size);

        res as *mut qjs::c_void
    }

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

        let state = &mut *state;
        state.malloc_count -= 1;

        let size = A::usable_size(ptr as RawMemPtr);

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

        state.malloc_size -= Self::size_t(size);
    }

    unsafe extern "C" fn realloc<A>(
        state: *mut qjs::JSMallocState,
        ptr: *mut qjs::c_void,
        size: qjs::size_t,
    ) -> *mut qjs::c_void
    where
        A: Allocator,
    {
        let state_ref = &mut *state;
        let allocator = &mut *(state_ref.opaque as *mut DynAllocator);

        // simulate the default behavior of libc::realloc
        if ptr.is_null() {
            return Self::malloc::<A>(state, size);
        } else if size == 0 {
            Self::free::<A>(state, ptr);
            return ptr::null_mut();
        }

        let old_size = Self::size_t(A::usable_size(ptr as RawMemPtr));

        let new_malloc_size = state_ref.malloc_size - old_size + size;
        if new_malloc_size > state_ref.malloc_limit {
            return ptr::null_mut();
        }

        let ptr = allocator.realloc(ptr as _, size.try_into().expect(qjs::SIZE_T_ERROR))
            as *mut qjs::c_void;

        if ptr.is_null() {
            return ptr::null_mut();
        }

        let actual_size = Self::size_t(A::usable_size(ptr as RawMemPtr));

        state_ref.malloc_size -= old_size;
        state_ref.malloc_size += actual_size;

        ptr
    }

    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()
    }
}