Skip to main content

talc/source/
global_alloc.rs

1use core::{
2    alloc::{GlobalAlloc, Layout},
3    fmt::Debug,
4    mem::{align_of, size_of},
5    ptr::{NonNull, addr_of_mut},
6};
7
8use crate::{
9    base::binning::Binning,
10    base::{CHUNK_UNIT, Talc},
11    node::Node,
12    ptr_utils,
13};
14
15use super::Source;
16
17/// Source memory from a backing allocator on-demand.
18///
19/// This will also release memory back to the allocator when memory blocks are freed up.
20///
21/// # Heap management
22///
23/// This [`Source`] places metadata around heaps to manage them.
24///
25/// Therefore manual heap management (i.e. using [`Talc::claim`], [`Talc::resize`], etc.)
26/// directly is not allowed, and will cause UB.
27///
28/// # Example
29///
30/// ```
31/// # extern crate talc;
32/// use allocator_api2::alloc::{Allocator, System, Layout};
33///
34/// let talc = talc::TalcCell::new(unsafe { talc::source::GlobalAllocSource::new(System) });
35/// let allocation = talc.allocate(Layout::new::<[usize; 500]>());
36/// ```
37#[derive(Debug)]
38pub struct GlobalAllocSource<G: GlobalAlloc> {
39    block_size: usize,
40    allocator: G,
41    allocation_chain: Option<NonNull<Option<NonNull<Node>>>>,
42}
43
44// SAFETY: GlobalAllocSource has ownership semantics over the `allocation_chain` pointee.
45unsafe impl<G: GlobalAlloc + Send> Send for GlobalAllocSource<G> {}
46
47/// 1 MiB, chosen pretty arbitrarily.
48const DEFAULT_BLOCK_SIZE: usize = 1 << 20;
49
50impl<G: GlobalAlloc> GlobalAllocSource<G> {
51    /// Create a new [`GlobalAllocSource`] with the given allocator.
52    ///
53    /// A default minimum block size per allocation is used.
54    /// This is subject to change. If you need a specific value,
55    /// use [`GlobalAllocSource::with_block_size`] instead.
56    pub const fn new(allocator: G) -> Self {
57        Self { block_size: DEFAULT_BLOCK_SIZE, allocator, allocation_chain: None }
58    }
59
60    /// Create a new [`GlobalAllocSource`] with the given allocator and power-of-two block size.
61    ///
62    /// # Panics
63    ///
64    /// Panics if `block_size` is not a power of two. This might be relaxed in the future.
65    pub const fn with_block_size(allocator: G, block_size: usize) -> Self {
66        assert!(block_size.is_power_of_two());
67
68        Self { block_size, allocator, allocation_chain: None }
69    }
70}
71
72unsafe impl<G: GlobalAlloc + Debug> Source for GlobalAllocSource<G> {
73    fn acquire<B: Binning>(talc: &mut Talc<Self, B>, layout: Layout) -> Result<(), ()> {
74        // Account for the size and potential overhead from alignment.
75        // Allocating extra space isn't a big deal; more space for future
76        // allocations to make use of.
77        let mut required_size = layout.size() + layout.align();
78
79        // Extra space for Talc's internal heap alignment on either side.
80        // I believe this is 1 byte more than absolutely necessary.
81        // (TODO document the memory layout well and confirm this.)
82        required_size += CHUNK_UNIT + CHUNK_UNIT;
83        // Extra space for the footer.
84        required_size += size_of::<Footer>();
85
86        if !talc.is_metadata_established() {
87            //
88            required_size += crate::min_first_heap_layout::<B>().size();
89            // Ensure there's additional space to establish the in-heap chain pointer too.
90            required_size += size_of::<Option<NonNull<Node>>>();
91        }
92
93        let required_blocks =
94            (required_size + talc.source.block_size - 1) & !(talc.source.block_size - 1);
95
96        debug_assert!(CHUNK_UNIT > align_of::<Footer>());
97        let layout = unsafe { Layout::from_size_align_unchecked(required_blocks, BLOCK_ALIGN) };
98        let allocation = unsafe { talc.source.allocator.alloc(layout) };
99
100        if allocation.is_null() {
101            return Err(());
102        }
103
104        let mut base_offset = 0;
105
106        let meta = if let Some(meta) = talc.source.allocation_chain {
107            meta.as_ptr()
108        } else {
109            let meta = ptr_utils::align_up_by(allocation, align_of::<Option<NonNull<Node>>>())
110                .cast::<Option<NonNull<Node>>>();
111
112            unsafe {
113                *meta = None;
114            }
115
116            base_offset = size_of::<Option<NonNull<Node>>>() + meta as usize - allocation as usize;
117
118            let allocation_chain = NonNull::new(meta);
119            debug_assert!(allocation_chain.is_some());
120            talc.source.allocation_chain = allocation_chain;
121
122            meta
123        };
124
125        let heap_end = unsafe {
126            talc.claim(
127                allocation.wrapping_add(base_offset),
128                required_blocks - base_offset - size_of::<Footer>(),
129            )
130            .unwrap_unchecked()
131        };
132
133        unsafe {
134            let footer = heap_end.as_ptr().cast::<Footer>();
135            Node::link_at(addr_of_mut!((*footer).node), Node { next: *meta, next_of_prev: meta });
136            (*footer).base = allocation;
137            (*footer).size = required_blocks;
138        }
139
140        Ok(())
141    }
142
143    const TRACK_HEAP_END: bool = true;
144
145    unsafe fn resize(
146        &mut self,
147        chunk_base: *mut u8,
148        heap_end: *mut u8,
149        is_heap_base: bool,
150    ) -> *mut u8 {
151        if is_heap_base {
152            let footer = heap_end.cast::<Footer>();
153            Node::unlink((*footer).node);
154
155            let layout = Layout::from_size_align_unchecked((*footer).size, BLOCK_ALIGN);
156            self.allocator.dealloc((*footer).base, layout);
157
158            chunk_base
159        } else {
160            heap_end
161        }
162    }
163}
164
165impl<G: GlobalAlloc> Drop for GlobalAllocSource<G> {
166    fn drop(&mut self) {
167        if let Some(chain) = self.allocation_chain {
168            unsafe {
169                for node_ptr in Node::iter_mut(chain.as_ptr().read()) {
170                    let footer = node_ptr.cast::<Footer>().as_ptr();
171                    let layout = Layout::from_size_align_unchecked((*footer).size, CHUNK_UNIT);
172                    self.allocator.dealloc((*footer).base, layout);
173                }
174            }
175        }
176    }
177}
178
179#[repr(C)] // ensure the node ptr is the same as the footer ptr
180struct Footer {
181    node: Node,
182    base: *mut u8,
183    size: usize,
184}
185
186const BLOCK_ALIGN: usize = 1;