lite_alloc/single_threaded/segregated_bump.rs
1use crate::{PAGE_SIZE, grow_memory};
2
3/// Safety Warning:
4/// Allocators in this module are designed for [Single Threaded] environments.
5/// `Sync` is implemented only to satisfy `GlobalAlloc` trait requirements.
6/// Using this allocator in a multi-threaded environment will lead to Undefined Behavior (UB).
7/// Please ensure it is used only in single-threaded environments (e.g., WASM or single-threaded embedded).
8///
9/// 安全性警示 (Safety Warning):
10/// 本模块中的分配器均为【单线程】设计。
11/// 实现了 `Sync` 仅为了满足 `GlobalAlloc` trait 的要求。
12/// 在多线程环境中使用此分配器会导致未定义行为 (UB)。
13/// 请确保只在单线程环境(如 WASM 或单线程嵌入式环境)中使用。
14unsafe impl Sync for SegregatedBumpAllocator {}
15use core::{
16 alloc::{GlobalAlloc, Layout},
17 ptr::null_mut,
18};
19
20/// Minimal Allocator with Fixed Bins + Bump Pointer Fallback.
21///
22/// 极简固定分箱 + Bump Pointer 回退分配器。
23///
24/// # Design Philosophy
25/// - **Extreme Size**: Abandon Coalescing and Splitting, remove all complex list traversals and metadata headers.
26/// - **Speed First**: Small object allocation/deallocation are strictly O(1).
27/// - **Use Cases**: Wasm Serverless functions, short-lived scripts, or scenarios with ample memory but sensitive to startup speed/size.
28///
29/// # 设计哲学
30/// - **极简体积**:放弃合并(Coalescing)与拆分(Splitting),移除所有复杂的链表遍历和元数据头。
31/// - **速度优先**:小对象分配/释放均为严格的 O(1)。
32/// - **场景定位**:Wasm Serverless 函数、短生命周期脚本、或内存充裕但对启动速度/体积敏感的场景。
33///
34/// # Memory Layout
35/// - **Bin 0**: 16 Bytes (for Box<u8>, small structs)
36/// - **Bin 1**: 32 Bytes
37/// - **Bin 2**: 64 Bytes
38/// - **Bin 3**: 128 Bytes
39/// - **Large**: > 128 Bytes, allocated directly using Bump Pointer, not reused.
40///
41/// # 内存布局
42/// - **Bin 0**: 16 Bytes (用于 Box<u8>, small structs)
43/// - **Bin 1**: 32 Bytes
44/// - **Bin 2**: 64 Bytes
45/// - **Bin 3**: 128 Bytes
46/// - **Large**: > 128 Bytes,直接使用 Bump Pointer 分配,不复用。
47pub struct SegregatedBumpAllocator;
48
49impl SegregatedBumpAllocator {
50 pub const fn new() -> Self {
51 SegregatedBumpAllocator
52 }
53
54 /// ⚠️ Test/Bench only: Reset global state
55 ///
56 /// ⚠️ 仅用于测试/Bench:重置全局状态
57 pub unsafe fn reset() {
58 unsafe {
59 BINS = [null_mut(); 4];
60 HEAP_TOP = 0;
61 HEAP_END = 0;
62 }
63 }
64}
65
66// Singly linked list node, embedded in free memory blocks
67// 单链表节点,嵌入在空闲内存块中
68struct Node {
69 next: *mut Node,
70}
71
72// --------------------------------------------------------------------------
73// Global Static State (Safe in single-threaded Wasm)
74// 全局静态状态 (在单线程 Wasm 中是安全的)
75// --------------------------------------------------------------------------
76
77// 4 bins head pointers. BINS[0] -> 16B, [1] -> 32B, [2] -> 64B, [3] -> 128B
78// 4个桶的头指针。BINS[0] -> 16B, [1] -> 32B, [2] -> 64B, [3] -> 128B
79static mut BINS: [*mut Node; 4] = [null_mut(); 4];
80
81// Bump Pointer (Heap Top Pointer)
82// Bump Pointer (堆顶指针)
83static mut HEAP_TOP: usize = 0;
84// Current Wasm memory boundary
85// 当前已申请的 Wasm 内存边界
86static mut HEAP_END: usize = 0;
87
88unsafe impl GlobalAlloc for SegregatedBumpAllocator {
89 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
90 // 1. Large alignment handling
91 // Fixed Bins default guarantee 16-byte alignment.
92 // If user requests > 16-byte alignment (very rare), handle directly via Bump allocation.
93 // 1. 大对齐处理
94 // 固定 Bins 默认保证 16 字节对齐。
95 // 如果用户请求 > 16 字节对齐(非常罕见),直接通过 Bump 分配来处理对齐。
96 if layout.align() > 16 {
97 return unsafe { self.bump_alloc(layout.size(), layout.align()) };
98 }
99
100 // 2. Calculate category
101 // 2. 计算分类
102 let size = layout.size().max(16);
103
104 // 3. Try lookup reuse (Small Alloc)
105 // 3. 尝试查表复用 (Small Alloc)
106 if let Some(index) = get_index(size) {
107 unsafe {
108 let head = BINS[index];
109 if !head.is_null() {
110 // Hit: Pop from list head (LIFO)
111 // Hit: 弹出链表头 (LIFO)
112 let next = (*head).next;
113 BINS[index] = next;
114 return head as *mut u8;
115 }
116 }
117
118 // Miss: Bin is empty, fallback to Bump allocation
119 // Allocate block of corresponding Bin size directly, instead of layout.size(), so it can be returned correctly later
120 // Miss: Bin 为空,回退到 Bump 分配
121 // 直接分配对应 Bin 大小的块,而不是 layout.size(),以便将来 dealloc 能正确归位
122 let block_size = 16 << index;
123 return unsafe { self.bump_alloc(block_size, 16) };
124 }
125
126 // 4. Large object handling (> 128 Bytes)
127 // Alloc via Bump directly, skip Bins
128 // 4. 大对象处理 (> 128 Bytes)
129 // 直接 Bump 分配,不走 Bin
130 unsafe { self.bump_alloc(size, 16) }
131 }
132
133 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
134 // 1. If block has high alignment requirement, it must not be from Bins,
135 // and because we don't have metadata for its size, just discard it (leak).
136 // 1. 如果是对齐要求很高的块,它一定不是来自 Bins,
137 // 且我们没有元数据记录它的大小,所以直接丢弃(泄漏)。
138 if layout.align() > 16 {
139 return;
140 }
141
142 let size = layout.size().max(16);
143
144 // 2. Try to return to Bins
145 // 2. 尝试归还到 Bins
146 if let Some(index) = get_index(size) {
147 let node = ptr as *mut Node;
148 // Insert at head (O(1))
149 // 头插法 (O(1))
150 unsafe {
151 (*node).next = BINS[index];
152 BINS[index] = node;
153 }
154 return;
155 }
156
157 // 3. Large Object (> 128 Bytes)
158 // Strategy choice: Abandon large object reuse for minimal size.
159 // They will be reclaimed when Wasm instance is destroyed.
160 // 3. 大对象 (> 128 Bytes)
161 // 策略选择:为了极简体积,放弃大对象复用。
162 // 它们会随 Wasm 实例销毁而回收。
163 }
164
165 #[cfg(feature = "realloc")]
166 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
167 // 1. Determine actual capacity of old block
168 // 1. 确定旧块的实际容量
169 let old_size = layout.size().max(16);
170 let old_capacity = if layout.align() > 16 {
171 old_size
172 } else if let Some(index) = get_index(old_size) {
173 16 << index
174 } else {
175 old_size
176 };
177
178 // 2. If new size <= old capacity, reuse directly (In-place shrink)
179 // 2. 如果新大小 <= 旧容量,直接复用 (In-place shrink)
180 if new_size <= old_capacity {
181 return ptr;
182 }
183
184 // 3. Try to grow in place (In-place grow at HEAP_TOP)
185 // Only possible if ptr is exactly at heap top.
186 // 3. 尝试原地扩容 (In-place grow at HEAP_TOP)
187 // 只有当 ptr 恰好在堆顶时才可能。
188 let heap_top = unsafe { HEAP_TOP };
189 if ptr as usize + old_capacity == heap_top {
190 let diff = new_size - old_capacity;
191 let heap_end = unsafe { HEAP_END };
192
193 // Check if there is enough remaining space or grow memory
194 // 检查是否有足够的剩余空间或扩容
195 unsafe {
196 if HEAP_TOP + diff <= heap_end {
197 HEAP_TOP += diff;
198 return ptr;
199 }
200
201 let pages_needed =
202 ((HEAP_TOP + diff - heap_end + PAGE_SIZE - 1) / PAGE_SIZE).max(1);
203 if grow_memory(pages_needed) != usize::MAX {
204 HEAP_END += pages_needed * PAGE_SIZE;
205 HEAP_TOP += diff;
206 return ptr;
207 }
208 }
209 }
210
211 // 4. Default fallback: Alloc + Copy + Dealloc
212 // 4. 默认回退:Alloc + Copy + Dealloc
213 unsafe {
214 let new_ptr = self.alloc(Layout::from_size_align_unchecked(new_size, layout.align()));
215 if !new_ptr.is_null() {
216 // copy old_size, not old_capacity, because data is only valid up to old_size
217 core::ptr::copy_nonoverlapping(ptr, new_ptr, layout.size());
218 self.dealloc(ptr, layout);
219 }
220 new_ptr
221 }
222 }
223}
224
225impl SegregatedBumpAllocator {
226 /// Core Bump Pointer allocation logic
227 /// 核心 Bump Pointer 分配逻辑
228 unsafe fn bump_alloc(&self, size: usize, align: usize) -> *mut u8 {
229 unsafe {
230 let mut ptr = HEAP_TOP;
231
232 // Handle alignment: (ptr + align - 1) & !(align - 1)
233 // For align=16, it means (ptr + 15) & !15
234 // 处理对齐: (ptr + align - 1) & !(align - 1)
235 // 对于 align=16,即 (ptr + 15) & !15
236 ptr = (ptr + align - 1) & !(align - 1);
237
238 // Check for overflow or insufficient capacity
239 // 检查溢出或容量不足
240 if ptr + size > HEAP_END || ptr < HEAP_TOP {
241 // How many pages needed?
242 // 需要多少页?
243 let bytes_needed = (ptr + size).saturating_sub(HEAP_END);
244 let pages_needed = ((bytes_needed + PAGE_SIZE - 1) / PAGE_SIZE).max(1);
245
246 let prev_page = grow_memory(pages_needed);
247 if prev_page == usize::MAX {
248 return null_mut(); // OOM
249 }
250
251 // If initial allocation (HEAP_END == 0), need to initialize ptr
252 // 如果是初次分配 (HEAP_END == 0),需要初始化 ptr
253 if HEAP_END == 0 {
254 // prev_page should be 0 (or existing memory size)
255 // Wasm memory_grow returns old page count
256 // prev_page 应该是 0 (或者现有内存大小)
257 // Wasm memory_grow 返回旧的页数
258 let memory_start = prev_page * PAGE_SIZE;
259 ptr = memory_start;
260 // Re-align
261 // 再次对齐
262 ptr = (ptr + align - 1) & !(align - 1);
263
264 HEAP_END = memory_start + pages_needed * PAGE_SIZE;
265 } else {
266 HEAP_END += pages_needed * PAGE_SIZE;
267 }
268 }
269
270 HEAP_TOP = ptr + size;
271 ptr as *mut u8
272 }
273 }
274}
275
276// --------------------------------------------------------------------------
277// Helper Functions
278// 辅助函数
279// --------------------------------------------------------------------------
280
281/// Get Bin index based on size.
282/// 0 -> 16B, 1 -> 32B, 2 -> 64B, 3 -> 128B
283/// Returns None indicating large object.
284///
285/// 根据大小获取 Bin 索引。
286/// 0 -> 16B, 1 -> 32B, 2 -> 64B, 3 -> 128B
287/// 返回 None 表示是大对象。
288#[inline(always)]
289fn get_index(size: usize) -> Option<usize> {
290 if size > 128 {
291 return None;
292 }
293
294 // Use CLZ (Count Leading Zeros) instruction to quickly calculate log2
295 // size 16 (10000) -> index 0
296 // size 17..32 -> index 1
297 // ...
298 // 利用 CLZ (Count Leading Zeros) 指令快速计算 log2
299 // size 16 (10000) -> index 0
300 // size 17..32 -> index 1
301 // ...
302 let size_val = size as usize;
303
304 // next_power_of_two ensures 17 becomes 32
305 // next_power_of_two 确保 17 变成 32
306 let power_of_two = size_val.next_power_of_two();
307 let zeros = power_of_two.leading_zeros();
308
309 // Calculate base offset.
310 // u32: leading_zeros(16) = 27. Target index = 0. => 27 - 27 = 0
311 // u64: leading_zeros(16) = 59. Target index = 0. => 59 - 59 = 0
312 // 计算基准偏移。
313 // u32: leading_zeros(16) = 27. Target index = 0. => 27 - 27 = 0
314 // u64: leading_zeros(16) = 59. Target index = 0. => 59 - 59 = 0
315 #[cfg(target_pointer_width = "32")]
316 const BASE: u32 = 27;
317
318 #[cfg(target_pointer_width = "64")]
319 const BASE: u32 = 59;
320
321 Some((BASE - zeros) as usize)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use std::sync::Mutex;
328
329 // Use a global lock to serialize tests because SegregatedBumpAllocator uses global `static mut` state.
330 static TEST_LOCK: Mutex<()> = Mutex::new(());
331
332 fn with_clean_allocator(f: impl FnOnce()) {
333 let _guard = TEST_LOCK.lock().unwrap();
334 unsafe {
335 // 1. Reset internal global state
336 SegregatedBumpAllocator::reset();
337
338 // 2. Reset host memory simulation (lib.rs)
339 crate::reset_heap();
340 }
341 f();
342 }
343
344 #[test]
345 fn test_small_alloc_reuse() {
346 with_clean_allocator(|| {
347 let allocator = SegregatedBumpAllocator::new();
348 let layout = Layout::from_size_align(16, 8).unwrap(); // Bin 0: 16B
349
350 unsafe {
351 // 1. Alloc (Miss -> Bump)
352 let ptr1 = allocator.alloc(layout);
353 assert!(!ptr1.is_null());
354 *ptr1 = 0xAA;
355
356 // 2. Dealloc (Push to Bin 0)
357 allocator.dealloc(ptr1, layout);
358
359 // 3. Alloc (Hit -> Reuse)
360 let ptr2 = allocator.alloc(layout);
361 assert_eq!(ptr1, ptr2, "Should reuse the same pointer from bin");
362 // Intrusive list overwrites the first bytes with 'next' pointer, so data is not preserved.
363 }
364 });
365 }
366
367 #[test]
368 fn test_cross_bin_isolation() {
369 with_clean_allocator(|| {
370 let allocator = SegregatedBumpAllocator::new();
371
372 unsafe {
373 // Bin 0 (16B)
374 let l0 = Layout::from_size_align(10, 1).unwrap();
375 let p0 = allocator.alloc(l0);
376
377 // Bin 1 (32B)
378 let l1 = Layout::from_size_align(20, 1).unwrap();
379 let p1 = allocator.alloc(l1);
380
381 assert_ne!(p0, p1);
382
383 // Dealloc 16B block
384 allocator.dealloc(p0, l0);
385
386 // Alloc 32B block - should NOT reuse 16B block
387 let p2 = allocator.alloc(l1);
388 assert_ne!(p0, p2);
389
390 // Alloc 16B block - should reuse p0
391 let p3 = allocator.alloc(l0);
392 assert_eq!(p0, p3);
393 }
394 });
395 }
396
397 #[test]
398 fn test_large_alloc_bypass() {
399 with_clean_allocator(|| {
400 let allocator = SegregatedBumpAllocator::new();
401 // > 128 Bytes -> Large alloc (Bump directly)
402 let layout = Layout::from_size_align(200, 16).unwrap();
403
404 unsafe {
405 let p1 = allocator.alloc(layout);
406 assert!(!p1.is_null());
407
408 // Dealloc (No-op usually for large allocs in this simple allocator)
409 allocator.dealloc(p1, layout);
410
411 // Alloc again. Since dealloc is no-op, it allocates new space.
412 let p2 = allocator.alloc(layout);
413 assert_ne!(
414 p1, p2,
415 "Large objects should not be implicitly reused in this implementation"
416 );
417 }
418 });
419 }
420
421 #[test]
422 fn test_memory_growth() {
423 with_clean_allocator(|| {
424 let allocator = SegregatedBumpAllocator::new();
425 // Alloc enough to force page growth. PAGE_SIZE is 64KB.
426 // Let's alloc 40KB twice.
427 let layout = Layout::from_size_align(40 * 1024, 16).unwrap();
428
429 unsafe {
430 let p1 = allocator.alloc(layout);
431 assert!(!p1.is_null());
432
433 let p2 = allocator.alloc(layout);
434 assert!(!p2.is_null());
435 assert_ne!(p1, p2);
436
437 // Verify they don't overlap
438 let dist = (p2 as usize).wrapping_sub(p1 as usize);
439 assert!(dist >= 40 * 1024);
440 }
441 });
442 }
443
444 #[test]
445 fn test_high_alignment() {
446 with_clean_allocator(|| {
447 let allocator = SegregatedBumpAllocator::new();
448 // 32B size, but 128B alignment.
449 // Should bypass bins (max align 16) and use bump with align pad.
450 let layout = Layout::from_size_align(32, 128).unwrap();
451
452 unsafe {
453 let p1 = allocator.alloc(layout);
454 assert!(!p1.is_null());
455 assert_eq!(p1 as usize % 128, 0);
456
457 allocator.dealloc(p1, layout); // Should be no-op or handled gracefully
458 }
459 });
460 }
461
462 #[test]
463 fn test_realloc_shrink() {
464 with_clean_allocator(|| {
465 let allocator = SegregatedBumpAllocator::new();
466 // Bin 3: 128B
467 let layout = Layout::from_size_align(100, 16).unwrap();
468 // capacity will be 128
469
470 unsafe {
471 let ptr = allocator.alloc(layout);
472 ptr.write_bytes(0xCC, 100);
473
474 // Shrink to 80. Still Bin 3 (128B). Should be inplace.
475 let new_size = 80;
476 let new_ptr = allocator.realloc(ptr, layout, new_size);
477
478 #[cfg(feature = "realloc")]
479 assert_eq!(ptr, new_ptr);
480 #[cfg(not(feature = "realloc"))]
481 assert_ne!(ptr, new_ptr); // Default moves
482
483 assert_eq!(*new_ptr, 0xCC);
484 }
485 });
486 }
487
488 #[test]
489 fn test_realloc_grow_large_at_top() {
490 with_clean_allocator(|| {
491 let allocator = SegregatedBumpAllocator::new();
492 // Large alloc > 128B.
493 let layout = Layout::from_size_align(256, 16).unwrap();
494
495 unsafe {
496 let ptr = allocator.alloc(layout);
497 ptr.write_bytes(0xAA, 256);
498
499 // This should be at HEAP_TOP.
500
501 let new_size = 512;
502 let new_ptr = allocator.realloc(ptr, layout, new_size);
503
504 #[cfg(feature = "realloc")]
505 assert_eq!(ptr, new_ptr); // Should grow in place
506 #[cfg(not(feature = "realloc"))]
507 assert_ne!(ptr, new_ptr);
508
509 assert_eq!(*new_ptr, 0xAA);
510 assert_eq!(*new_ptr.add(255), 0xAA);
511 }
512 });
513 }
514
515 #[test]
516 fn test_realloc_move() {
517 with_clean_allocator(|| {
518 let allocator = SegregatedBumpAllocator::new();
519 // 1. Alloc something to block bottom
520 let l1 = Layout::from_size_align(64, 16).unwrap();
521 let _ = unsafe { allocator.alloc(l1) };
522
523 // 2. Alloc target block (Large)
524 let layout = Layout::from_size_align(256, 16).unwrap();
525 let ptr = unsafe { allocator.alloc(layout) };
526
527 // 3. Alloc something else to block top (so ptr is NOT at HEAP_TOP)
528 let l3 = Layout::from_size_align(64, 16).unwrap();
529 let _p3 = unsafe { allocator.alloc(l3) };
530
531 unsafe {
532 ptr.write_bytes(0xBB, 256);
533
534 let new_size = 512;
535 let new_ptr = allocator.realloc(ptr, layout, new_size);
536
537 assert_ne!(ptr, new_ptr); // Must move
538 assert_eq!(*new_ptr, 0xBB);
539 }
540 });
541 }
542}