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