Skip to main content

ax_alloc/
tlsf_impl.rs

1//! TLSF memory allocator implementation using the `rlsf` crate.
2
3use core::{
4    alloc::{GlobalAlloc, Layout},
5    ptr::NonNull,
6};
7
8use ax_sync::SpinLock;
9use rlsf::Tlsf;
10
11use super::{AllocResult, AllocatorOps, UsageKind, Usages};
12
13/// The global allocator instance for TLSF mode.
14#[cfg_attr(
15    all(any(target_os = "none", feature = "global-allocator"), not(test)),
16    global_allocator
17)]
18static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new();
19
20const PAGE_SIZE: usize = 0x1000;
21
22/// The default byte allocator for TLSF mode.
23pub type DefaultByteAllocator = Tlsf<'static, u32, u32, 28, 32>;
24
25struct TlsfInfo {
26    tlsf: Tlsf<'static, u32, u32, 28, 32>,
27    total_bytes: usize,
28    used_bytes: usize,
29}
30
31impl TlsfInfo {
32    const fn new() -> Self {
33        Self {
34            tlsf: Tlsf::new(),
35            total_bytes: 0,
36            used_bytes: 0,
37        }
38    }
39}
40
41/// The global allocator used by ArceOS when TLSF is enabled.
42pub struct GlobalAllocator {
43    inner: SpinLock<TlsfInfo>,
44    usages: SpinLock<Usages>,
45}
46
47impl Default for GlobalAllocator {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl GlobalAllocator {
54    /// Creates an empty [`GlobalAllocator`].
55    pub const fn new() -> Self {
56        Self {
57            inner: SpinLock::new(TlsfInfo::new()),
58            usages: SpinLock::new(Usages::new()),
59        }
60    }
61
62    /// Returns the name of the allocator.
63    pub const fn name(&self) -> &'static str {
64        "TLSF"
65    }
66
67    /// Initializes the allocator with the given region.
68    pub fn init(&self, start_vaddr: usize, size: usize) -> AllocResult {
69        let mut inner = self.inner.lock_irqsave();
70        unsafe {
71            let pool = core::slice::from_raw_parts_mut(start_vaddr as *mut u8, size);
72            inner
73                .tlsf
74                .insert_free_block_ptr(NonNull::new(pool).unwrap())
75                .unwrap();
76        }
77        inner.total_bytes = size;
78        Ok(())
79    }
80
81    /// Add the given region to the allocator.
82    pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
83        let mut inner = self.inner.lock_irqsave();
84        unsafe {
85            let pool = core::slice::from_raw_parts_mut(start_vaddr as *mut u8, size);
86            inner
87                .tlsf
88                .insert_free_block_ptr(NonNull::new(pool).unwrap())
89                .ok_or(crate::AllocError::InvalidParam)?;
90        }
91        inner.total_bytes += size;
92        Ok(())
93    }
94
95    /// Allocate arbitrary number of bytes.
96    pub fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
97        let ptr =
98            crate::retry_after_registered_reclaim(crate::layout_reclaim_pages(layout), || {
99                self.inner
100                    .lock_irqsave()
101                    .tlsf
102                    .allocate(layout)
103                    .ok_or(crate::AllocError::NoMemory)
104            })?;
105        self.inner.lock_irqsave().used_bytes += layout.size();
106        self.usages
107            .lock_irqsave()
108            .alloc(UsageKind::RustHeap, layout.size());
109        Ok(ptr)
110    }
111
112    /// Gives back the allocated region.
113    pub fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
114        unsafe {
115            self.inner
116                .lock_irqsave()
117                .tlsf
118                .deallocate(pos, layout.align());
119        }
120        self.inner.lock_irqsave().used_bytes -= layout.size();
121        self.usages
122            .lock_irqsave()
123            .dealloc(UsageKind::RustHeap, layout.size());
124    }
125
126    /// Allocates contiguous pages by allocating page-aligned bytes from TLSF.
127    pub fn alloc_pages(
128        &self,
129        num_pages: usize,
130        alignment: usize,
131        kind: UsageKind,
132    ) -> AllocResult<usize> {
133        let size = num_pages
134            .checked_mul(PAGE_SIZE)
135            .ok_or(crate::AllocError::InvalidParam)?;
136        let align = alignment.max(PAGE_SIZE);
137        let layout =
138            Layout::from_size_align(size, align).map_err(|_| crate::AllocError::InvalidParam)?;
139        let ptr = crate::retry_after_registered_reclaim(num_pages, || {
140            self.inner
141                .lock_irqsave()
142                .tlsf
143                .allocate(layout)
144                .ok_or(crate::AllocError::NoMemory)
145        })?;
146        self.inner.lock_irqsave().used_bytes += size;
147        if !matches!(kind, UsageKind::RustHeap) {
148            self.usages.lock_irqsave().alloc(kind, size);
149        }
150        Ok(ptr.as_ptr() as usize)
151    }
152
153    /// Allocates contiguous low-memory pages (physical address < 4 GiB).
154    pub fn alloc_dma32_pages(
155        &self,
156        _num_pages: usize,
157        _alignment: usize,
158        _kind: UsageKind,
159    ) -> AllocResult<usize> {
160        unimplemented!("TLSF allocator does not support alloc_dma32_pages")
161    }
162
163    /// Allocates contiguous pages starting from the given address.
164    pub fn alloc_pages_at(
165        &self,
166        _start: usize,
167        _num_pages: usize,
168        _alignment: usize,
169        _kind: UsageKind,
170    ) -> AllocResult<usize> {
171        unimplemented!("TLSF allocator does not support alloc_pages_at")
172    }
173
174    /// Gives back the allocated pages.
175    pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
176        let size = num_pages * PAGE_SIZE;
177        let ptr = NonNull::new(pos as *mut u8).expect("dealloc_pages null ptr");
178        unsafe {
179            self.inner.lock_irqsave().tlsf.deallocate(ptr, PAGE_SIZE);
180        }
181        self.inner.lock_irqsave().used_bytes -= size;
182        self.usages.lock_irqsave().dealloc(kind, size);
183    }
184
185    /// Returns the number of allocated bytes.
186    pub fn used_bytes(&self) -> usize {
187        self.inner.lock_irqsave().used_bytes
188    }
189
190    /// Returns the number of available bytes.
191    pub fn available_bytes(&self) -> usize {
192        let inner = self.inner.lock_irqsave();
193        inner.total_bytes.saturating_sub(inner.used_bytes)
194    }
195
196    /// Returns the number of allocated pages.
197    pub fn used_pages(&self) -> usize {
198        self.used_bytes() / PAGE_SIZE
199    }
200
201    /// Returns the number of available pages.
202    pub fn available_pages(&self) -> usize {
203        self.available_bytes() / PAGE_SIZE
204    }
205
206    /// Returns the usage statistics.
207    pub fn usages(&self) -> Usages {
208        *self.usages.lock_irqsave()
209    }
210}
211
212impl AllocatorOps for GlobalAllocator {
213    fn name(&self) -> &'static str {
214        GlobalAllocator::name(self)
215    }
216
217    fn init(&self, start_vaddr: usize, size: usize) -> AllocResult {
218        GlobalAllocator::init(self, start_vaddr, size)
219    }
220
221    fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult {
222        GlobalAllocator::add_memory(self, start_vaddr, size)
223    }
224
225    fn alloc(&self, layout: Layout) -> AllocResult<NonNull<u8>> {
226        GlobalAllocator::alloc(self, layout)
227    }
228
229    fn dealloc(&self, pos: NonNull<u8>, layout: Layout) {
230        GlobalAllocator::dealloc(self, pos, layout)
231    }
232
233    fn alloc_pages(
234        &self,
235        num_pages: usize,
236        alignment: usize,
237        kind: UsageKind,
238    ) -> AllocResult<usize> {
239        GlobalAllocator::alloc_pages(self, num_pages, alignment, kind)
240    }
241
242    fn alloc_dma32_pages(
243        &self,
244        num_pages: usize,
245        alignment: usize,
246        kind: UsageKind,
247    ) -> AllocResult<usize> {
248        GlobalAllocator::alloc_dma32_pages(self, num_pages, alignment, kind)
249    }
250
251    fn alloc_pages_at(
252        &self,
253        start: usize,
254        num_pages: usize,
255        alignment: usize,
256        kind: UsageKind,
257    ) -> AllocResult<usize> {
258        GlobalAllocator::alloc_pages_at(self, start, num_pages, alignment, kind)
259    }
260
261    fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) {
262        GlobalAllocator::dealloc_pages(self, pos, num_pages, kind)
263    }
264
265    fn used_bytes(&self) -> usize {
266        GlobalAllocator::used_bytes(self)
267    }
268
269    fn available_bytes(&self) -> usize {
270        GlobalAllocator::available_bytes(self)
271    }
272
273    fn used_pages(&self) -> usize {
274        GlobalAllocator::used_pages(self)
275    }
276
277    fn available_pages(&self) -> usize {
278        GlobalAllocator::available_pages(self)
279    }
280
281    fn usages(&self) -> Usages {
282        GlobalAllocator::usages(self)
283    }
284}
285
286/// Returns the reference to the global allocator.
287pub fn global_allocator() -> &'static GlobalAllocator {
288    &GLOBAL_ALLOCATOR
289}
290
291/// Initializes per-CPU allocator state.
292///
293/// TLSF does not use per-CPU slabs, so this is intentionally a no-op.
294pub fn init_percpu_slab(_cpu_id: usize) {}
295
296/// Initializes the global allocator with the given memory region.
297pub fn global_init(start_vaddr: usize, size: usize) -> AllocResult {
298    debug!(
299        "initialize global allocator at: [{:#x}, {:#x})",
300        start_vaddr,
301        start_vaddr + size
302    );
303    GLOBAL_ALLOCATOR.init(start_vaddr, size)
304}
305
306/// Add the given memory region to the global allocator.
307pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult {
308    debug!(
309        "add a memory region to global allocator: [{:#x}, {:#x})",
310        start_vaddr,
311        start_vaddr + size
312    );
313    GLOBAL_ALLOCATOR.add_memory(start_vaddr, size)
314}
315
316unsafe impl GlobalAlloc for GlobalAllocator {
317    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
318        let inner = move || {
319            if let Ok(ptr) = GlobalAllocator::alloc(self, layout) {
320                ptr.as_ptr()
321            } else {
322                // Let fallible containers observe allocation failure. The
323                // standard library still calls its allocation-error handler
324                // for infallible Box/Vec/Arc construction after a null result.
325                core::ptr::null_mut()
326            }
327        };
328
329        #[cfg(feature = "tracking")]
330        {
331            crate::tracking::with_state(|state| match state {
332                None => inner(),
333                Some(state) => {
334                    let ptr = inner();
335                    if ptr.is_null() {
336                        return ptr;
337                    }
338                    let generation = state.generation;
339                    state.generation += 1;
340                    state.map.insert(
341                        ptr as usize,
342                        crate::tracking::AllocationInfo {
343                            layout,
344                            backtrace: axbacktrace::Backtrace::capture(),
345                            generation,
346                        },
347                    );
348                    ptr
349                }
350            })
351        }
352
353        #[cfg(not(feature = "tracking"))]
354        inner()
355    }
356
357    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
358        let ptr = NonNull::new(ptr).expect("dealloc null ptr");
359        let inner = || GlobalAllocator::dealloc(self, ptr, layout);
360
361        #[cfg(feature = "tracking")]
362        crate::tracking::with_state(|state| match state {
363            None => inner(),
364            Some(state) => {
365                let address = ptr.as_ptr() as usize;
366                state.map.remove(&address);
367                inner()
368            }
369        });
370
371        #[cfg(not(feature = "tracking"))]
372        inner();
373    }
374}