libbz2_rs_sys/
allocator.rs

1//! # allocator infrastructure
2//!
3//! The public interface allows setting a custom allocator, but we need to configure a default
4//! allocator if the user did not configure one. We have two choices, configured by feature flags:
5//!
6//! - `"rust-allocator"` uses the rust global allocator
7//! - `"c-allocator"` uses an allocator based on `malloc` and `free`
8//!
9//! When both configured, `"rust-allocator"` is preferred.
10//!
11//! The interface for the allocator is not a great fit for rust. In particular, rust always needs
12//! the layout of an allocation to deallocate it, and C interfaces don't usually provide this
13//! information. Luckily in the library we know in all cases how big the allocation was at the
14//! point where we deallocate it.
15
16#[cfg(feature = "rust-allocator")]
17extern crate alloc;
18
19use core::ffi::{c_int, c_void};
20
21use crate::bzlib::{BzStream, StreamState};
22
23type AllocFunc = unsafe extern "C" fn(*mut c_void, c_int, c_int) -> *mut c_void;
24type FreeFunc = unsafe extern "C" fn(*mut c_void, *mut c_void) -> ();
25
26pub(crate) enum Allocator {
27    #[cfg(feature = "rust-allocator")]
28    Rust,
29    #[cfg(feature = "c-allocator")]
30    C,
31    Custom {
32        allocate: AllocFunc,
33        deallocate: FreeFunc,
34        opaque: *mut c_void,
35    },
36}
37
38impl Allocator {
39    #[allow(unreachable_code)]
40    pub(crate) const DEFAULT: Option<Self> = 'blk: {
41        #[cfg(feature = "rust-allocator")]
42        break 'blk Some(Self::Rust);
43
44        #[cfg(feature = "c-allocator")]
45        break 'blk Some(Self::C);
46
47        None
48    };
49
50    #[allow(unreachable_code)]
51    pub(crate) fn default_function_pointers() -> Option<(AllocFunc, FreeFunc)> {
52        #[cfg(feature = "rust-allocator")]
53        return Some(rust_allocator::ALLOCATOR);
54
55        #[cfg(feature = "c-allocator")]
56        return Some(c_allocator::ALLOCATOR);
57
58        None
59    }
60
61    /// # Safety
62    ///
63    /// - `strm.bzalloc` and `strm.opaque` must form a valid allocator, meaning `strm.bzalloc` returns either
64    ///     * a `NULL` pointer
65    ///     * a valid pointer to an allocation of `len * size_of::<T>()` bytes aligned to at least `align_of::<usize>()`
66    /// - `strm.bzfree` frees memory allocated by `strm.bzalloc`
67    pub(crate) unsafe fn from_bz_stream<S: StreamState>(strm: &BzStream<S>) -> Option<Self> {
68        let bzalloc = strm.bzalloc?;
69        let bzfree = strm.bzfree?;
70
71        #[cfg(feature = "rust-allocator")]
72        if (bzalloc, bzfree) == rust_allocator::ALLOCATOR {
73            return Some(Self::Rust);
74        }
75
76        #[cfg(feature = "c-allocator")]
77        if (bzalloc, bzfree) == c_allocator::ALLOCATOR {
78            return Some(Self::C);
79        }
80
81        Some(Self::custom(bzalloc, bzfree, strm.opaque))
82    }
83
84    /// # Safety
85    ///
86    /// - `allocate` and `opaque` must form a valid allocator, meaning `allocate` returns either
87    ///     * a `NULL` pointer
88    ///     * a valid pointer to an allocation of `len * size_of::<T>()` bytes aligned to at least `align_of::<usize>()`
89    /// - `deallocate` frees memory allocated by `allocate`
90    pub(crate) fn custom(allocate: AllocFunc, deallocate: FreeFunc, opaque: *mut c_void) -> Self {
91        Self::Custom {
92            allocate,
93            deallocate,
94            opaque,
95        }
96    }
97}
98
99#[cfg(feature = "c-allocator")]
100pub(crate) mod c_allocator {
101    use super::*;
102
103    // make sure that the only way these function pointers leave this module is via this constant
104    // that way the function pointer address is a reliable way to know that the default C allocator
105    // is used.
106    pub(crate) static ALLOCATOR: (AllocFunc, FreeFunc) = (self::allocate, self::deallocate);
107
108    unsafe extern "C" fn allocate(_opaque: *mut c_void, count: c_int, size: c_int) -> *mut c_void {
109        unsafe { libc::malloc((count * size) as usize) }
110    }
111
112    unsafe extern "C" fn deallocate(_opaque: *mut c_void, ptr: *mut c_void) {
113        if !ptr.is_null() {
114            unsafe {
115                libc::free(ptr);
116            }
117        }
118    }
119}
120
121#[cfg(feature = "rust-allocator")]
122mod rust_allocator {
123    use super::*;
124
125    // make sure that the only way these function pointers leave this module is via this constant
126    // that way the function pointer address is a reliable way to know that the default C allocator
127    // is used.
128    pub(crate) static ALLOCATOR: (AllocFunc, FreeFunc) = (self::allocate, self::deallocate);
129
130    unsafe extern "C" fn allocate(
131        _opaque: *mut c_void,
132        _count: c_int,
133        _size: c_int,
134    ) -> *mut c_void {
135        unreachable!("the default rust allocation function should never be called directly");
136    }
137
138    unsafe extern "C" fn deallocate(_opaque: *mut c_void, _ptr: *mut c_void) {
139        unreachable!("the default rust deallocation function should never be called directly");
140    }
141}
142
143impl Allocator {
144    /// Allocates `count` contiguous values of type `T`, and zeros out all elements.
145    pub(crate) fn allocate_zeroed<T>(&self, count: usize) -> Option<*mut T> {
146        const {
147            assert!(size_of::<T>() != 0);
148        }
149        assert_ne!(count, 0);
150
151        match self {
152            #[cfg(feature = "rust-allocator")]
153            Allocator::Rust => {
154                let layout = core::alloc::Layout::array::<T>(count).unwrap();
155                let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
156                (!ptr.is_null()).then_some(ptr.cast())
157            }
158            #[cfg(feature = "c-allocator")]
159            Allocator::C => {
160                let ptr = unsafe { libc::calloc(count, core::mem::size_of::<T>()) };
161                (!ptr.is_null()).then_some(ptr.cast())
162            }
163            Allocator::Custom {
164                allocate, opaque, ..
165            } => unsafe {
166                let ptr = (allocate)(*opaque, count as i32, core::mem::size_of::<T>() as i32);
167                let ptr = ptr.cast::<T>();
168
169                if ptr.is_null() {
170                    return None;
171                }
172
173                core::ptr::write_bytes(ptr, 0, count);
174
175                Some(ptr)
176            },
177        }
178    }
179
180    pub(crate) unsafe fn deallocate<T>(&self, ptr: *mut T, count: usize) {
181        if ptr.is_null() || count == 0 {
182            return;
183        }
184
185        match self {
186            #[cfg(feature = "rust-allocator")]
187            Allocator::Rust => {
188                let layout = core::alloc::Layout::array::<T>(count).unwrap();
189                unsafe { alloc::alloc::dealloc(ptr.cast(), layout) }
190            }
191            #[cfg(feature = "c-allocator")]
192            Allocator::C => {
193                unsafe { libc::free(ptr.cast()) };
194            }
195            Allocator::Custom {
196                deallocate, opaque, ..
197            } => {
198                unsafe { deallocate(*opaque, ptr.cast()) };
199            }
200        }
201    }
202}