Skip to main content

libghostty_vt/
alloc.rs

1//! Adapting custom allocators to work with libghostty.
2use std::{
3    borrow::Borrow,
4    ffi::c_void,
5    marker::PhantomData,
6    ops::{Deref, DerefMut},
7    ptr::NonNull,
8};
9
10#[cfg(feature = "allocator_api")]
11use allocator_api2::alloc;
12
13use crate::{
14    error::{Error, Result},
15    ffi::{self, GhosttyAllocator, GhosttyAllocatorVtable},
16};
17
18/// A custom allocator that libghostty uses for its memory allocations.
19///
20/// The allocator may depend on some external state `Ctx` for the
21/// duration of lifetime `'ctx`. This is useful for adapting external,
22/// stateful allocators that may not have a `'static` lifetime.
23///
24/// One example of a custom allocator that *does* have a `'static`
25/// lifetime is Rust's own default allocator, which can also be used
26/// within libghostty as [`Allocator::GLOBAL`].
27#[derive(Debug)]
28pub struct Allocator<'ctx, Ctx: 'ctx = ()> {
29    pub(crate) inner: GhosttyAllocator,
30    _phan: PhantomData<&'ctx Ctx>,
31}
32
33impl<Ctx> Allocator<'_, Ctx> {
34    pub(crate) fn to_raw(&self) -> *const GhosttyAllocator {
35        std::ptr::from_ref(&self.inner)
36    }
37}
38
39/// An internal helper struct for dealing with the common allocation
40/// pattern of allowing custom allocators for libghostty's opaque objects.
41#[derive(Debug)]
42pub(crate) struct Object<'alloc, T> {
43    pub(crate) ptr: NonNull<T>,
44    _phan: PhantomData<&'alloc GhosttyAllocator>,
45}
46
47impl<T> Object<'_, T> {
48    pub(crate) fn new(raw: *mut T) -> Result<Self> {
49        let ptr = NonNull::new(raw).ok_or(Error::OutOfMemory)?;
50        Ok(Self {
51            ptr,
52            _phan: PhantomData,
53        })
54    }
55    pub(crate) fn as_raw(&self) -> *mut T {
56        self.ptr.as_ptr()
57    }
58}
59
60/// Bytes allocated by libghostty, possibly using a custom allocator.
61#[derive(Debug)]
62pub struct Bytes<'alloc> {
63    ptr: NonNull<u8>,
64    len: usize,
65    alloc: *const GhosttyAllocator,
66    _phan: PhantomData<&'alloc GhosttyAllocator>,
67}
68impl<'alloc> Bytes<'alloc> {
69    /// Allocate `len` bytes with libghostty's default allocator.
70    ///
71    /// Not really useful except in very niche cases.
72    pub fn new(len: usize) -> Result<Self> {
73        // SAFETY: A NULL allocator is always valid
74        unsafe { Self::new_inner(std::ptr::null(), len) }
75    }
76
77    /// Allocate `len` bytes with a custom allocator.
78    ///
79    /// Not really useful except in very niche cases.
80    pub fn new_with_alloc<'ctx: 'alloc, Ctx>(
81        alloc: &'alloc Allocator<'ctx, Ctx>,
82        len: usize,
83    ) -> Result<Self> {
84        // SAFETY: Borrow checking should forbid invalid allocators
85        unsafe { Self::new_inner(alloc.to_raw(), len) }
86    }
87
88    unsafe fn new_inner(alloc: *const ffi::GhosttyAllocator, len: usize) -> Result<Self> {
89        let raw = unsafe { ffi::ghostty_alloc(alloc, len) };
90        let ptr = NonNull::new(raw).ok_or(Error::OutOfMemory)?;
91        Ok(unsafe { Self::from_raw_parts(ptr, len, alloc) })
92    }
93
94    pub(crate) unsafe fn from_raw_parts(
95        ptr: NonNull<u8>,
96        len: usize,
97        alloc: *const GhosttyAllocator,
98    ) -> Self {
99        Self {
100            ptr,
101            len,
102            alloc,
103            _phan: PhantomData,
104        }
105    }
106}
107impl Drop for Bytes<'_> {
108    fn drop(&mut self) {
109        // SAFETY: The lifetime dictates that the allocator must
110        // remain valid through here. We retain ownership of the bytes
111        // memory itself so it should not be freed beforehand.
112        unsafe { ffi::ghostty_free(self.alloc, self.ptr.as_ptr(), self.len) };
113    }
114}
115impl Deref for Bytes<'_> {
116    type Target = [u8];
117
118    #[inline]
119    fn deref(&self) -> &Self::Target {
120        // SAFETY: See Drop
121        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
122    }
123}
124impl DerefMut for Bytes<'_> {
125    #[inline]
126    fn deref_mut(&mut self) -> &mut Self::Target {
127        // SAFETY: See Drop
128        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
129    }
130}
131impl AsRef<[u8]> for Bytes<'_> {
132    fn as_ref(&self) -> &[u8] {
133        self
134    }
135}
136impl AsMut<[u8]> for Bytes<'_> {
137    fn as_mut(&mut self) -> &mut [u8] {
138        self
139    }
140}
141impl Borrow<[u8]> for Bytes<'_> {
142    fn borrow(&self) -> &[u8] {
143        self
144    }
145}
146impl<'a> IntoIterator for &'a Bytes<'_> {
147    type Item = &'a u8;
148    type IntoIter = std::slice::Iter<'a, u8>;
149
150    fn into_iter(self) -> Self::IntoIter {
151        self.deref().iter()
152    }
153}
154
155//------------------------------------
156// GlobalAlloc
157//------------------------------------
158
159impl Allocator<'static> {
160    /// A custom allocator based on Rust's built-in
161    /// [global allocator](std::alloc::GlobalAlloc).
162    pub const GLOBAL: Self = Self {
163        inner: GhosttyAllocator {
164            ctx: std::ptr::null_mut(),
165            vtable: &GhosttyAllocatorVtable {
166                alloc: Some(_global_alloc),
167                free: Some(_global_free),
168                resize: Some(_global_resize),
169                remap: Some(_global_remap),
170            },
171        },
172        _phan: PhantomData,
173    };
174}
175
176unsafe extern "C" fn _global_alloc(
177    _allocator: *mut c_void,
178    len: usize,
179    alignment: u8,
180    _ret_addr: usize,
181) -> *mut c_void {
182    let Ok(layout) = std::alloc::Layout::from_size_align(len, 1 << alignment) else {
183        return std::ptr::null_mut();
184    };
185    unsafe { std::alloc::alloc(layout).cast::<c_void>() }
186}
187
188unsafe extern "C" fn _global_free(
189    _allocator: *mut c_void,
190    mem: *mut c_void,
191    len: usize,
192    alignment: u8,
193    _ret_addr: usize,
194) {
195    let Ok(layout) = std::alloc::Layout::from_size_align(len, 1 << alignment) else {
196        return;
197    };
198    unsafe { std::alloc::dealloc(mem.cast::<u8>(), layout) }
199}
200unsafe extern "C" fn _global_resize(
201    _allocator: *mut c_void,
202    _mem: *mut c_void,
203    _old_len: usize,
204    _alignment: u8,
205    _new_len: usize,
206    _ret_addr: usize,
207) -> bool {
208    false
209}
210unsafe extern "C" fn _global_remap(
211    _allocator: *mut c_void,
212    mem: *mut c_void,
213    old_len: usize,
214    alignment: u8,
215    new_len: usize,
216    _ret_addr: usize,
217) -> *mut c_void {
218    let Ok(layout) = std::alloc::Layout::from_size_align(old_len, 1 << alignment) else {
219        return std::ptr::null_mut();
220    };
221    unsafe { std::alloc::realloc(mem.cast::<u8>(), layout, new_len).cast::<c_void>() }
222}
223
224//------------------------------------
225// Allocator API
226//------------------------------------
227
228/// Adapt a Rust Allocator into a libghostty Allocator.
229#[cfg(feature = "allocator_api")]
230impl<'ctx, A: alloc::Allocator + 'ctx> From<A> for Allocator<'ctx, A> {
231    fn from(value: A) -> Self {
232        Self {
233            inner: GhosttyAllocator {
234                ctx: std::ptr::from_ref(value.by_ref()) as *mut std::ffi::c_void,
235                vtable: &GhosttyAllocatorVtable {
236                    alloc: Some(_alloc::<A>),
237                    free: Some(_free::<A>),
238                    resize: Some(_resize),
239                    remap: Some(_remap::<A>),
240                },
241            },
242            _phan: PhantomData,
243        }
244    }
245}
246
247#[cfg(feature = "allocator_api")]
248unsafe extern "C" fn _alloc<A: alloc::Allocator>(
249    allocator: *mut c_void,
250    len: usize,
251    alignment: u8,
252    _ret_addr: usize,
253) -> *mut c_void {
254    let layout = alloc::Layout::from_size_align(len, 1 << alignment).ok();
255
256    unsafe { get_allocator::<A>(allocator) }
257        .and_then(|alloc| alloc.allocate(layout?).ok())
258        .map(|p| p.as_ptr().cast::<c_void>())
259        .unwrap_or(std::ptr::null_mut())
260}
261
262#[cfg(feature = "allocator_api")]
263unsafe extern "C" fn _free<A: alloc::Allocator>(
264    allocator: *mut c_void,
265    mem: *mut c_void,
266    len: usize,
267    alignment: u8,
268    _ret_addr: usize,
269) {
270    let Some(mem) = NonNull::new(mem.cast::<u8>()) else {
271        return;
272    };
273    let Some(layout) = alloc::Layout::from_size_align(len, 1 << alignment).ok() else {
274        return;
275    };
276    if let Some(alloc) = unsafe { get_allocator::<A>(allocator) } {
277        unsafe { alloc.deallocate(mem, layout) };
278    }
279}
280
281/// Resize (grow or shrink) an allocation *in-place*.
282///
283/// Rather unfortunately, Rust's Allocator API does not guarantee that
284/// growing or shrinking an allocation would necessarily be in-place.
285/// Therefore, we have to assume rather pessimistically that every
286/// resizing operation might relocate the memory block, so in-place
287/// resizes are always impossible.
288#[cfg(feature = "allocator_api")]
289unsafe extern "C" fn _resize(
290    _allocator: *mut c_void,
291    _mem: *mut c_void,
292    _old_len: usize,
293    _alignment: u8,
294    _new_len: usize,
295    _ret_addr: usize,
296) -> bool {
297    false
298}
299
300/// Resize (grow or shrink) an allocation, *allowing relocation if necessary*,
301/// returning `null` if resizing requires reallocation.
302#[cfg(feature = "allocator_api")]
303unsafe extern "C" fn _remap<A: alloc::Allocator>(
304    allocator: *mut c_void,
305    mem: *mut c_void,
306    old_len: usize,
307    alignment: u8,
308    new_len: usize,
309    _ret_addr: usize,
310) -> *mut c_void {
311    let mem = NonNull::new(mem.cast::<u8>());
312    let old_layout = alloc::Layout::from_size_align(old_len, 1 << alignment).ok();
313    let new_layout = alloc::Layout::from_size_align(new_len, 1 << alignment).ok();
314
315    unsafe { get_allocator::<A>(allocator) }
316        .and_then(|alloc| {
317            if new_len < old_len {
318                unsafe { alloc.shrink(mem?, old_layout?, new_layout?) }.ok()
319            } else {
320                unsafe { alloc.grow(mem?, old_layout?, new_layout?) }.ok()
321            }
322        })
323        .map(|p| p.as_ptr().cast::<c_void>())
324        .unwrap_or(std::ptr::null_mut())
325}
326
327/// Get the allocator back from a vtable function.
328///
329/// # Safety
330///
331/// This function only behaves correctly if called by one of the vtable functions.
332/// In particular, it expects the vtable function to be used correctly, which means
333/// libghostty must have received a valid allocator object from elsewhere in this
334/// crate. If any of these preconditions are unmet, this will definitely cause
335/// Undefined Behavior.
336///
337/// The returned allocator must **never** be smuggled outside the lifetime of the caller.
338#[inline(always)]
339#[cfg(feature = "allocator_api")]
340unsafe fn get_allocator<'a, A: alloc::Allocator>(ptr: *mut c_void) -> Option<&'a A> {
341    unsafe { ptr.cast::<A>().as_ref() }
342}