1const ALLOC_STEP: usize = 16;
9const ALLOC_MIN_CLASS: usize = 32;
10const ALLOC_MAX_CLASS: usize = 32768;
11const ALLOC_CLASSES: usize = ALLOC_MAX_CLASS / ALLOC_STEP + 1;
12const ALLOC_HDR: usize = core::mem::size_of::<AllocBlock>();
13const ALLOC_LARGE: usize = 1usize << 63;
14const ALLOC_ALIGNED: usize = 1usize << 62;
15const ALLOC_PAGES_MASK: usize = ALLOC_LARGE - 1;
16
17#[repr(C)]
18struct AllocBlock {
19 next: *mut AllocBlock,
20 tag: usize,
21}
22
23static ALLOC_LOCK: AtomicUsize = AtomicUsize::new(0);
24static mut ALLOC_FREE: [*mut AllocBlock; ALLOC_CLASSES] =
25 [null_mut(); ALLOC_CLASSES];
26
27#[inline]
28unsafe fn alloc_lock() {
29 while ALLOC_LOCK.swap(1, Ordering::Acquire) != 0 {
30 core::hint::spin_loop();
31 }
32}
33
34#[inline]
35unsafe fn alloc_unlock() {
36 ALLOC_LOCK.store(0, Ordering::Release);
37}
38
39pub(crate) unsafe fn allocator_after_fork() {
40 ALLOC_LOCK.store(0, Ordering::Release);
41}
42
43#[inline]
44fn alloc_class_for(size: usize) -> Option<usize> {
45 let total = size.checked_add(ALLOC_HDR)?;
46 let class = if total <= ALLOC_MIN_CLASS {
47 ALLOC_MIN_CLASS
48 } else {
49 match total.checked_add(ALLOC_STEP - 1) {
50 Some(v) => v & !(ALLOC_STEP - 1),
51 None => return None,
52 }
53 };
54 if class <= ALLOC_MAX_CLASS { Some(class) } else { None }
55}
56
57unsafe fn alloc_mmap(size: usize) -> *mut u8 {
58 let pages = (size + PAGE - 1) & !(PAGE - 1);
59 let p = sys_mmap(
60 null_mut(),
61 pages,
62 PROT_READ | PROT_WRITE,
63 MAP_PRIVATE | MAP_ANONYMOUS,
64 -1,
65 0,
66 );
67 if p == MMAP_FAILED { null_mut() } else { p }
68}
69
70unsafe fn alloc_large(size: usize) -> *mut u8 {
71 let total = match size.checked_add(ALLOC_HDR) {
72 Some(v) => v,
73 None => {
74 ERRNO!() = ENOMEM;
75 return null_mut();
76 }
77 };
78 if total > usize::MAX - (PAGE - 1) {
79 ERRNO!() = ENOMEM;
80 return null_mut();
81 }
82 let pages = (total + PAGE - 1) / PAGE;
83 let map_size = pages * PAGE;
84 let base = alloc_mmap(map_size);
85 if base.is_null() {
86 return null_mut();
87 }
88 let block = base as *mut AllocBlock;
89 (*block).next = block;
90 (*block).tag = ALLOC_LARGE | pages;
91 base.add(ALLOC_HDR)
92}
93
94unsafe fn alloc_aligned_impl(alignment: usize, size: usize) -> *mut u8 {
95 if alignment == 0 || !alignment.is_power_of_two() {
96 ERRNO!() = EINVAL;
97 return null_mut();
98 }
99 if alignment <= ALLOC_STEP {
100 return malloc(size) as *mut u8;
101 }
102 let total = match size.checked_add(alignment).and_then(|v| v.checked_add(ALLOC_HDR)) {
103 Some(v) => v,
104 None => {
105 ERRNO!() = ENOMEM;
106 return null_mut();
107 }
108 };
109 if total > usize::MAX - (PAGE - 1) {
110 ERRNO!() = ENOMEM;
111 return null_mut();
112 }
113 let pages = (total + PAGE - 1) / PAGE;
114 let map_size = pages * PAGE;
115 let base = alloc_mmap(map_size);
116 if base.is_null() {
117 return null_mut();
118 }
119 let data_addr = (base as usize + ALLOC_HDR + alignment - 1) & !(alignment - 1);
120 let block = (data_addr - ALLOC_HDR) as *mut AllocBlock;
121 (*block).next = base as *mut AllocBlock;
122 (*block).tag = ALLOC_ALIGNED | pages;
123 data_addr as *mut u8
124}
125
126pub(crate) unsafe fn alloc_usable_size(ptr: *mut u8) -> usize {
127 if ptr.is_null() {
128 return 0;
129 }
130 let block = ptr.sub(ALLOC_HDR) as *mut AllocBlock;
131 let tag = (*block).tag;
132 if tag & ALLOC_ALIGNED != 0 {
133 let base = (*block).next as usize;
134 let pages = tag & ALLOC_PAGES_MASK;
135 pages * PAGE - (ptr as usize - base)
136 } else if tag & ALLOC_LARGE != 0 {
137 let pages = tag & ALLOC_PAGES_MASK;
138 pages * PAGE - ALLOC_HDR
139 } else if tag >= ALLOC_MIN_CLASS && tag <= ALLOC_MAX_CLASS {
140 tag - ALLOC_HDR
141 } else {
142 0
143 }
144}
145
146#[no_mangle]
147pub unsafe extern "C" fn malloc(size: SizeT) -> *mut c_void {
148 let class = match alloc_class_for(size) {
149 Some(c) => c,
150 None => return alloc_large(size) as *mut c_void,
151 };
152 let idx = class / ALLOC_STEP;
153
154 alloc_lock();
155 let head = ALLOC_FREE[idx];
156 if !head.is_null() {
157 ALLOC_FREE[idx] = (*head).next;
158 alloc_unlock();
159 return (head as *mut u8).add(ALLOC_HDR) as *mut c_void;
160 }
161
162 let chunk_size = if class < 4096 { 65536 } else { class * 2 };
164 let map_size = (chunk_size + PAGE - 1) & !(PAGE - 1);
165 let base = alloc_mmap(map_size);
166 if base.is_null() {
167 alloc_unlock();
168 return null_mut();
169 }
170 let count = map_size / class;
171 let mut i = count;
172 while i > 1 {
173 i -= 1;
174 let block = base.add(i * class) as *mut AllocBlock;
175 (*block).tag = class;
176 (*block).next = ALLOC_FREE[idx];
177 ALLOC_FREE[idx] = block;
178 }
179 let first = base as *mut AllocBlock;
180 (*first).tag = class;
181 alloc_unlock();
182 base.add(ALLOC_HDR) as *mut c_void
183}
184
185#[no_mangle]
186pub unsafe extern "C" fn free(ptr: *mut c_void) {
187 if ptr.is_null() {
188 return;
189 }
190 let block = (ptr as *mut u8).sub(ALLOC_HDR) as *mut AllocBlock;
191 let tag = (*block).tag;
192 if tag & ALLOC_ALIGNED != 0 {
193 let base = (*block).next as *mut u8;
194 let pages = tag & ALLOC_PAGES_MASK;
195 sys_munmap(base, pages * PAGE);
196 return;
197 }
198 if tag & ALLOC_LARGE != 0 {
199 let pages = tag & ALLOC_PAGES_MASK;
200 sys_munmap(block as *mut u8, pages * PAGE);
201 return;
202 }
203 if tag < ALLOC_MIN_CLASS || tag > ALLOC_MAX_CLASS {
204 return;
205 }
206 let idx = tag / ALLOC_STEP;
207 alloc_lock();
208 (*block).next = ALLOC_FREE[idx];
209 ALLOC_FREE[idx] = block;
210 alloc_unlock();
211}
212
213#[no_mangle]
214pub unsafe extern "C" fn calloc(count: SizeT, size: SizeT) -> *mut c_void {
215 let total = match count.checked_mul(size) {
216 Some(v) => v,
217 None => {
218 ERRNO!() = ENOMEM;
219 return null_mut();
220 }
221 };
222 let ptr = malloc(total);
223 if !ptr.is_null() {
224 core::ptr::write_bytes(ptr as *mut u8, 0, total);
225 }
226 ptr
227}
228
229#[no_mangle]
230pub unsafe extern "C" fn realloc(ptr: *mut c_void, new_size: SizeT) -> *mut c_void {
231 if ptr.is_null() {
232 return malloc(new_size);
233 }
234 if new_size == 0 {
235 free(ptr);
236 return null_mut();
237 }
238 let old_size = alloc_usable_size(ptr as *mut u8);
239 if new_size <= old_size {
240 return ptr;
241 }
242 let new_ptr = malloc(new_size);
243 if new_ptr.is_null() {
244 return null_mut();
245 }
246 core::ptr::copy_nonoverlapping(ptr as *const u8, new_ptr as *mut u8, old_size);
247 free(ptr);
248 new_ptr
249}