1use core::alloc::Layout;
7use core::ffi::c_void;
8use core::ptr::NonNull;
9use core::sync::atomic::{AtomicUsize, Ordering};
10
11use iree_embedded_sys as sys;
12use spin::Mutex;
13use talc::{ClaimOnOom, Span, Talc};
14
15pub static LAST_ALLOC_FAIL_SIZE: AtomicUsize = AtomicUsize::new(0);
18
19const HEADER: usize = 16;
22const ALIGN: usize = 16;
24
25pub struct Arena {
30 talc: Mutex<Talc<ClaimOnOom>>,
31}
32
33impl Arena {
34 pub fn new(buffer: &'static mut [u8]) -> Self {
37 let span = Span::from_base_size(buffer.as_mut_ptr(), buffer.len());
38 let talc = Talc::new(unsafe { ClaimOnOom::new(span) });
40 Arena {
41 talc: Mutex::new(talc),
42 }
43 }
44
45 pub fn as_iree_allocator(&self) -> sys::iree_allocator_t {
47 sys::iree_allocator_t {
48 self_: self as *const Arena as *mut c_void,
49 ctl: Some(arena_ctl),
50 }
51 }
52
53 fn alloc(&self, byte_length: usize, zero: bool) -> *mut c_void {
54 let Ok(layout) = Layout::from_size_align(byte_length + HEADER, ALIGN) else {
55 return core::ptr::null_mut();
56 };
57 let mut talc = self.talc.lock();
58 let base = match unsafe { talc.malloc(layout) } {
60 Ok(p) => p.as_ptr(),
61 Err(_) => {
62 LAST_ALLOC_FAIL_SIZE.store(byte_length, Ordering::Relaxed);
63 return core::ptr::null_mut();
64 }
65 };
66 unsafe {
68 (base as *mut usize).write(byte_length);
69 let user = base.add(HEADER);
70 if zero {
71 core::ptr::write_bytes(user, 0, byte_length);
72 }
73 user as *mut c_void
74 }
75 }
76
77 unsafe fn free(&self, user: *mut c_void) {
79 if user.is_null() {
80 return;
81 }
82 unsafe {
85 let base = (user as *mut u8).sub(HEADER);
86 let byte_length = (base as *const usize).read();
87 let layout = Layout::from_size_align_unchecked(byte_length + HEADER, ALIGN);
88 let mut talc = self.talc.lock();
89 talc.free(NonNull::new_unchecked(base), layout);
90 }
91 }
92
93 unsafe fn realloc(&self, user: *mut c_void, new_len: usize) -> *mut c_void {
95 if user.is_null() {
96 return self.alloc(new_len, false);
97 }
98 unsafe {
102 let base = (user as *mut u8).sub(HEADER);
103 let old_len = (base as *const usize).read();
104 let new_ptr = self.alloc(new_len, false);
105 if new_ptr.is_null() {
106 return core::ptr::null_mut();
107 }
108 core::ptr::copy_nonoverlapping(
109 user as *const u8,
110 new_ptr as *mut u8,
111 old_len.min(new_len),
112 );
113 self.free(user);
114 new_ptr
115 }
116 }
117}
118
119unsafe impl Send for Arena {}
121unsafe impl Sync for Arena {}
122
123unsafe extern "C" fn arena_ctl(
126 self_: *mut c_void,
127 command: sys::iree_allocator_command_t,
128 params: *const c_void,
129 inout_ptr: *mut *mut c_void,
130) -> sys::iree_status_t {
131 unsafe {
135 let arena = &*(self_ as *const Arena);
136 let cmd = command;
137
138 if cmd == sys::IREE_ALLOCATOR_COMMAND_FREE {
139 arena.free(*inout_ptr);
140 *inout_ptr = core::ptr::null_mut();
141 return ok();
142 }
143 if cmd == sys::IREE_ALLOCATOR_COMMAND_MALLOC || cmd == sys::IREE_ALLOCATOR_COMMAND_CALLOC {
144 let byte_length = (*(params as *const sys::iree_allocator_alloc_params_t)).byte_length;
145 let zero = cmd == sys::IREE_ALLOCATOR_COMMAND_CALLOC;
146 let p = arena.alloc(byte_length, zero);
147 if p.is_null() {
148 return oom();
149 }
150 *inout_ptr = p;
151 return ok();
152 }
153 if cmd == sys::IREE_ALLOCATOR_COMMAND_REALLOC {
154 let new_len = (*(params as *const sys::iree_allocator_alloc_params_t)).byte_length;
155 let p = arena.realloc(*inout_ptr, new_len);
156 if p.is_null() {
157 return oom();
158 }
159 *inout_ptr = p;
160 return ok();
161 }
162 unimplemented_status()
163 }
164}
165
166#[inline]
167fn ok() -> sys::iree_status_t {
168 core::ptr::null_mut() }
170#[inline]
171fn oom() -> sys::iree_status_t {
172 sys::IREE_STATUS_RESOURCE_EXHAUSTED as usize as sys::iree_status_t
173}
174#[inline]
175fn unimplemented_status() -> sys::iree_status_t {
176 sys::IREE_STATUS_UNIMPLEMENTED as usize as sys::iree_status_t
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn alloc_and_free_roundtrip() {
185 static mut BUF: [u8; 64 * 1024] = [0; 64 * 1024];
186 let arena = unsafe { Arena::new(&mut *core::ptr::addr_of_mut!(BUF)) };
188 let allocator = arena.as_iree_allocator();
189 unsafe {
190 let mut p: *mut c_void = core::ptr::null_mut();
191 let st = sys::iree_allocator_malloc(allocator, 128, &mut p);
192 assert!(st.is_null(), "malloc returned non-OK status");
193 assert!(!p.is_null());
194 core::ptr::write_bytes(p as *mut u8, 0xAB, 128);
196 sys::iree_allocator_free(allocator, p);
197 }
198 }
199
200 #[test]
201 fn many_allocs_do_not_leak_across_reuse() {
202 static mut BUF: [u8; 64 * 1024] = [0; 64 * 1024];
203 let arena = unsafe { Arena::new(&mut *core::ptr::addr_of_mut!(BUF)) };
205 let allocator = arena.as_iree_allocator();
206 for _ in 0..10_000 {
209 unsafe {
210 let mut p: *mut c_void = core::ptr::null_mut();
211 let st = sys::iree_allocator_malloc(allocator, 256, &mut p);
212 assert!(st.is_null());
213 assert!(!p.is_null());
214 sys::iree_allocator_free(allocator, p);
215 }
216 }
217 }
218}