1use core::alloc::{GlobalAlloc, Layout};
18use core::ffi::c_void;
19use core::sync::atomic::{AtomicBool, Ordering};
20
21use crate::os;
22
23const CLASSES: usize = 8; const CHUNK: usize = 64 * 1024;
25const PAGE: usize = 4096;
26const MID: usize = 9; const CACHE_MAX: usize = 32 << 20; const SHARDS: usize = 64; struct Heap {
31 free: [*mut u8; CLASSES],
32 chunk: *mut u8,
33 chunk_left: usize,
34}
35
36#[repr(align(64))]
38struct Shard {
39 lock: AtomicBool,
40 heap: core::cell::UnsafeCell<Heap>,
41}
42
43struct Runs {
44 mid: [*mut u8; MID], mid_bytes: usize,
46}
47
48pub struct Alloc {
49 shards: [Shard; SHARDS],
50 runs_lock: AtomicBool,
51 runs: core::cell::UnsafeCell<Runs>,
52}
53
54unsafe impl Sync for Alloc {}
55
56#[cfg_attr(not(feature = "std"), global_allocator)]
57static ALLOC: Alloc = Alloc::new();
58
59impl Alloc {
60 pub const fn new() -> Alloc {
62 Alloc {
63 shards: [const {
64 Shard {
65 lock: AtomicBool::new(false),
66 heap: core::cell::UnsafeCell::new(Heap { free: [core::ptr::null_mut(); CLASSES], chunk: core::ptr::null_mut(), chunk_left: 0 }),
67 }
68 }; SHARDS],
69 runs_lock: AtomicBool::new(false),
70 runs: core::cell::UnsafeCell::new(Runs { mid: [core::ptr::null_mut(); MID], mid_bytes: 0 }),
71 }
72 }
73}
74
75impl Default for Alloc {
76 fn default() -> Alloc {
77 Alloc::new()
78 }
79}
80
81fn mid_of(total: usize) -> Option<usize> {
83 let pages = total.div_ceil(PAGE);
84 let k = (usize::BITS - (pages - 1).leading_zeros()) as usize; if k < MID { Some(k) } else { None }
86}
87
88fn class_of(size: usize) -> Option<usize> {
89 let mut c = 0;
90 let mut cap = 16;
91 while c < CLASSES {
92 if size <= cap {
93 return Some(c);
94 }
95 c += 1;
96 cap <<= 1;
97 }
98 None
99}
100
101fn lock(l: &AtomicBool) {
102 while l.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
103 core::hint::spin_loop();
104 }
105}
106
107fn unlock(l: &AtomicBool) {
108 l.store(false, Ordering::Release);
109}
110
111impl Alloc {
112 #[inline]
115 fn shard(&self) -> usize {
116 let probe = 0u8;
117 let a = core::ptr::addr_of!(probe) as usize >> 20;
118 a.wrapping_mul(0x9E37_79B9_7F4A_7C15_u64 as usize) >> (usize::BITS - SHARDS.trailing_zeros())
119 }
120
121 fn steal(&self, me: usize, c: usize) -> *mut u8 {
124 for (i, s) in self.shards.iter().enumerate() {
125 if i == me || s.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
126 continue;
127 }
128 let list = unsafe { core::mem::replace(&mut (*s.heap.get()).free[c], core::ptr::null_mut()) };
130 unlock(&s.lock);
131 if !list.is_null() {
132 return list;
133 }
134 }
135 core::ptr::null_mut()
136 }
137}
138
139impl Alloc {
140 unsafe fn alloc_inner(&self, layout: Layout) -> (*mut u8, bool) {
142 let size = layout.size().max(layout.align());
143 match class_of(size) {
144 Some(c) if layout.align() <= 16 => {
145 let me = self.shard();
146 let s = &self.shards[me];
147 lock(&s.lock);
148 let h = unsafe { &mut *s.heap.get() };
150 let bytes = 16usize << c;
151 let mut p = h.free[c];
152 if p.is_null() && h.chunk_left < bytes {
154 p = self.steal(me, c);
155 }
156 let r = if !p.is_null() {
157 h.free[c] = unsafe { *(p as *mut *mut u8) };
159 p
160 } else {
161 if h.chunk_left < bytes {
162 while h.chunk_left >= 16 {
164 let mut k = CLASSES - 1;
165 while 16usize << k > h.chunk_left {
166 k -= 1;
167 }
168 unsafe { *(h.chunk as *mut *mut u8) = h.free[k] };
170 h.free[k] = h.chunk;
171 h.chunk = unsafe { h.chunk.add(16 << k) };
173 h.chunk_left -= 16 << k;
174 }
175 h.chunk = unsafe { os::pages_alloc(CHUNK) };
177 h.chunk_left = if h.chunk.is_null() { 0 } else { CHUNK };
178 }
179 if h.chunk_left < bytes {
180 core::ptr::null_mut()
181 } else {
182 let q = h.chunk;
183 h.chunk = unsafe { h.chunk.add(bytes) };
185 h.chunk_left -= bytes;
186 q
187 }
188 };
189 unlock(&s.lock);
190 (r, true)
191 }
192 _ if layout.align() > 64 => {
193 let Some(total) = size
196 .checked_add(layout.align() + 64 + PAGE - 1)
197 .map(|t| t & !(PAGE - 1))
198 else {
199 return (core::ptr::null_mut(), false);
200 };
201 let base = unsafe { os::pages_alloc(total) };
203 if base.is_null() {
204 return (base, false);
205 }
206 let user = (base as usize + 64).next_multiple_of(layout.align()) as *mut u8;
207 unsafe {
209 *(user as *mut usize).sub(1) = base as usize;
210 *(user as *mut usize).sub(2) = total;
211 }
212 (user, false)
213 }
214 _ => {
215 let mut total = (size + 64 + PAGE - 1) & !(PAGE - 1);
217 if let Some(k) = mid_of(total) {
218 total = PAGE << k;
219 lock(&self.runs_lock);
220 let p = unsafe {
223 let h = &mut *self.runs.get();
224 let p = h.mid[k];
225 if !p.is_null() {
226 h.mid[k] = *(p as *mut *mut u8).add(1);
227 h.mid_bytes -= total;
228 }
229 p
230 };
231 unlock(&self.runs_lock);
232 if !p.is_null() {
233 return (unsafe { p.add(64) }, true);
235 }
236 }
237 let p = unsafe { os::pages_alloc(total) };
239 if p.is_null() {
240 return (p, false);
241 }
242 unsafe {
244 *(p as *mut usize) = total;
245 (p.add(64), false)
246 }
247 }
248 }
249 }
250}
251
252unsafe impl GlobalAlloc for Alloc {
253 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
254 unsafe { self.alloc_inner(layout).0 }
256 }
257
258 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
259 let size = layout.size().max(layout.align());
260 match class_of(size) {
261 Some(c) if layout.align() <= 16 => {
262 let s = &self.shards[self.shard()];
263 lock(&s.lock);
264 unsafe {
266 let h = &mut *s.heap.get();
267 *(ptr as *mut *mut u8) = h.free[c];
268 h.free[c] = ptr;
269 }
270 unlock(&s.lock);
271 }
272 _ if layout.align() > 64 => unsafe {
273 let base = *(ptr as *mut usize).sub(1) as *mut u8;
275 let total = *(ptr as *mut usize).sub(2);
276 os::pages_free(base, total);
277 },
278 _ => unsafe {
279 let base = ptr.sub(64);
281 let total = *(base as *mut usize);
282 if let Some(k) = mid_of(total).filter(|&k| PAGE << k == total) {
283 lock(&self.runs_lock);
284 let h = &mut *self.runs.get();
285 let keep = h.mid_bytes + total <= CACHE_MAX;
286 if keep {
287 *(base as *mut *mut u8).add(1) = h.mid[k];
289 h.mid[k] = base;
290 h.mid_bytes += total;
291 }
292 unlock(&self.runs_lock);
293 if keep {
294 return;
295 }
296 }
297 os::pages_free(base, total);
298 },
299 }
300 }
301
302 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
303 let (p, reused) = unsafe { self.alloc_inner(layout) };
307 if !p.is_null() && reused {
308 unsafe { memset(p as *mut c_void, 0, layout.size()) };
309 }
310 p
311 }
312}
313
314#[cfg_attr(not(feature = "std"), unsafe(no_mangle))]
317pub unsafe extern "C" fn memcpy(dst: *mut c_void, src: *const c_void, n: usize) -> *mut c_void {
318 unsafe {
320 core::arch::asm!("rep movsb", inout("rcx") n => _, inout("rdi") dst => _,
321 inout("rsi") src => _, options(nostack, preserves_flags));
322 }
323 dst
324}
325
326#[cfg_attr(not(feature = "std"), unsafe(no_mangle))]
327pub unsafe extern "C" fn memmove(dst: *mut c_void, src: *const c_void, n: usize) -> *mut c_void {
328 let (d, s) = (dst as *mut u8, src as *const u8);
329 if n == 0 || (d as usize) <= (s as usize) || (d as usize) >= (s as usize) + n {
330 unsafe { memcpy(dst, src, n) };
332 } else {
333 unsafe {
335 core::arch::asm!("std", "rep movsb", "cld",
336 inout("rcx") n => _, inout("rdi") d.add(n - 1) => _,
337 inout("rsi") s.add(n - 1) => _, options(nostack));
338 }
339 }
340 dst
341}
342
343#[cfg_attr(not(feature = "std"), unsafe(no_mangle))]
344pub unsafe extern "C" fn memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
345 unsafe {
347 core::arch::asm!("rep stosb", inout("rcx") n => _, inout("rdi") dst => _,
348 in("al") c as u8, options(nostack, preserves_flags));
349 }
350 dst
351}
352
353#[inline(always)]
356unsafe fn load8(p: *const u8) -> u64 {
357 let v: u64;
358 unsafe {
360 core::arch::asm!("mov {v}, qword ptr [{p}]", v = out(reg) v, p = in(reg) p,
361 options(pure, readonly, nostack, preserves_flags));
362 }
363 v
364}
365
366#[cfg_attr(not(feature = "std"), unsafe(no_mangle))]
367pub unsafe extern "C" fn memcmp(a: *const c_void, b: *const c_void, n: usize) -> i32 {
368 let (a, b) = (a as *const u8, b as *const u8);
369 let mut i = 0;
370 while i + 8 <= n {
372 let (x, y) = unsafe { (load8(a.add(i)), load8(b.add(i))) };
374 if x != y {
375 let sh = (x ^ y).trailing_zeros() & !7;
376 return ((x >> sh) as u8) as i32 - ((y >> sh) as u8) as i32;
377 }
378 i += 8;
379 }
380 while i < n {
381 let (x, y) = unsafe { (a.add(i).read_volatile(), b.add(i).read_volatile()) };
384 if x != y {
385 return x as i32 - y as i32;
386 }
387 i += 1;
388 }
389 0
390}
391
392#[cfg_attr(not(feature = "std"), unsafe(no_mangle))]
393pub unsafe extern "C" fn bcmp(a: *const c_void, b: *const c_void, n: usize) -> i32 {
394 unsafe { memcmp(a, b, n) }
396}
397
398#[cfg_attr(not(feature = "std"), unsafe(no_mangle))]
399pub unsafe extern "C" fn strlen(s: *const core::ffi::c_char) -> usize {
400 let s = s as *const u8;
401 let mut n = 0;
402 while unsafe { s.add(n).read_volatile() } != 0 {
404 n += 1;
405 }
406 n
407}