use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::ptr;
use core::sync::atomic::{AtomicBool, Ordering};
const WORD: usize = core::mem::size_of::<usize>();
const MIN_BLOCK: usize = 4 * WORD;
const FREE_BIT: usize = 1;
const PREV_FREE_BIT: usize = 2;
const FLAGS: usize = FREE_BIT | PREV_FREE_BIT;
const SL_BITS: u32 = 2;
const SL_COUNT: usize = 1 << SL_BITS;
const FL_COUNT: usize = 8;
const BINS: usize = FL_COUNT * SL_COUNT;
const fn log2_floor(x: usize) -> u32 {
(usize::BITS - 1) - (x.leading_zeros())
}
const FL_MIN: u32 = log2_floor(MIN_BLOCK);
#[inline(always)]
fn round_up(x: usize, align: usize) -> usize {
(x + align - 1) & !(align - 1)
}
#[inline(always)]
unsafe fn read_word(p: *const u8) -> usize {
ptr::read_unaligned(p as *const usize)
}
#[inline(always)]
unsafe fn write_word(p: *mut u8, v: usize) {
ptr::write_unaligned(p as *mut usize, v);
}
unsafe fn block_size(b: *const u8) -> usize {
read_word(b) & !FLAGS
}
unsafe fn block_is_free(b: *const u8) -> bool {
read_word(b) & FREE_BIT != 0
}
unsafe fn block_is_prev_free(b: *const u8) -> bool {
read_word(b) & PREV_FREE_BIT != 0
}
unsafe fn block_set_header(b: *mut u8, size: usize, flags: usize) {
write_word(b, size | (flags & FLAGS));
}
unsafe fn block_body(b: *mut u8) -> *mut u8 {
b.add(WORD)
}
unsafe fn body_to_block(body: *mut u8) -> *mut u8 {
body.sub(WORD)
}
unsafe fn block_next(b: *const u8) -> *mut u8 {
(b as *mut u8).add(block_size(b))
}
unsafe fn block_prev(b: *const u8) -> *mut u8 {
let prev_size = read_word((b as *const u8).sub(WORD)) & !FLAGS;
(b as *mut u8).sub(prev_size)
}
unsafe fn free_next(b: *const u8) -> *mut u8 {
read_word((b as *const u8).add(WORD)) as *mut u8
}
unsafe fn free_set_next(b: *mut u8, n: *mut u8) {
write_word(b.add(WORD), n as usize);
}
unsafe fn free_prev(b: *const u8) -> *mut u8 {
read_word((b as *const u8).add(2 * WORD)) as *mut u8
}
unsafe fn free_set_prev(b: *mut u8, p: *mut u8) {
write_word(b.add(2 * WORD), p as usize);
}
unsafe fn block_write_footer(b: *mut u8) {
let sz = block_size(b);
write_word(b.add(sz - WORD), read_word(b));
}
fn bin_index(size: usize) -> usize {
debug_assert!(size >= MIN_BLOCK);
let fl = log2_floor(size);
let sl = (size >> (fl.saturating_sub(SL_BITS) as usize)) & (SL_COUNT - 1);
let fi = (fl - FL_MIN) as usize;
(fi * SL_COUNT + sl).min(BINS - 1)
}
fn search_index(size: usize) -> usize {
bin_index(size.max(MIN_BLOCK))
}
struct TlsfInner {
bitmap: u32,
bins: [*mut u8; BINS],
heap_end: *mut u8,
used: usize,
peak: usize,
total: usize,
ready: bool,
}
unsafe impl Send for TlsfInner {}
impl TlsfInner {
const fn new() -> Self {
Self {
bitmap: 0,
bins: [ptr::null_mut(); BINS],
heap_end: ptr::null_mut(),
used: 0,
peak: 0,
total: 0,
ready: false,
}
}
unsafe fn init(&mut self, base: *mut u8, size: usize) {
let start = round_up(base as usize, WORD) as *mut u8;
let end = ((base as usize + size) & !(WORD - 1)) as *mut u8;
let usable = end as usize - start as usize;
assert!(
usable >= MIN_BLOCK + WORD,
"memory: heap too small (need >= {} bytes, got {})",
MIN_BLOCK + WORD,
usable,
);
self.heap_end = end;
self.total = usable;
self.bitmap = 0;
self.bins = [ptr::null_mut(); BINS];
self.used = 0;
self.peak = 0;
let blk_size = usable - WORD;
block_set_header(start, blk_size, FREE_BIT);
block_write_footer(start);
let sentinel = start.add(blk_size);
block_set_header(sentinel, 0, PREV_FREE_BIT);
self.insert(start);
self.ready = true;
}
unsafe fn insert(&mut self, block: *mut u8) {
let idx = bin_index(block_size(block));
let head = self.bins[idx];
free_set_next(block, head);
free_set_prev(block, ptr::null_mut());
if !head.is_null() {
free_set_prev(head, block);
}
self.bins[idx] = block;
self.bitmap |= 1u32 << idx;
}
unsafe fn remove(&mut self, block: *mut u8) {
let nx = free_next(block);
let pv = free_prev(block);
if !nx.is_null() {
free_set_prev(nx, pv);
}
if !pv.is_null() {
free_set_next(pv, nx);
} else {
let idx = bin_index(block_size(block));
self.bins[idx] = nx;
if nx.is_null() {
self.bitmap &= !(1u32 << idx);
}
}
}
unsafe fn find(&self, size: usize) -> *mut u8 {
let idx = search_index(size);
let mut mask = self.bitmap & !((1u32 << idx) - 1);
while mask != 0 {
let found = mask.trailing_zeros() as usize;
let block = self.bins[found];
if block_size(block) >= size {
return block;
}
mask &= !(1u32 << found);
}
ptr::null_mut()
}
unsafe fn alloc(&mut self, layout: Layout) -> *mut u8 {
if !self.ready {
return ptr::null_mut();
}
let needed = Self::block_size_for(layout);
let block = self.find(needed);
if block.is_null() {
return ptr::null_mut();
}
self.remove(block);
let bsz = block_size(block);
let prev_flag = read_word(block) & PREV_FREE_BIT;
let remain = bsz - needed;
if remain >= MIN_BLOCK {
block_set_header(block, needed, prev_flag);
let split = block.add(needed);
block_set_header(split, remain, FREE_BIT); block_write_footer(split);
let after = split.add(remain);
if (after as usize) < self.heap_end as usize {
let h = read_word(after);
write_word(after, h | PREV_FREE_BIT);
}
self.insert(split);
} else {
block_set_header(block, bsz, prev_flag);
let next = block.add(bsz);
if (next as usize) < self.heap_end as usize {
let h = read_word(next);
write_word(next, h & !PREV_FREE_BIT);
}
}
let alloc_sz = block_size(block);
self.used += alloc_sz;
if self.used > self.peak {
self.peak = self.used;
}
let body = block_body(block);
if layout.align() <= WORD {
body
} else {
let aligned = round_up(body as usize + WORD, layout.align()) as *mut u8;
write_word(aligned.sub(WORD), block as usize);
aligned
}
}
unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) {
if ptr.is_null() {
return;
}
let block = if layout.align() <= WORD {
body_to_block(ptr)
} else {
read_word(ptr.sub(WORD)) as *mut u8
};
let mut current = block;
let mut total = block_size(block);
self.used -= total;
let next = block.add(total);
if (next as usize) < self.heap_end as usize && block_is_free(next) {
self.remove(next);
total += block_size(next);
}
if block_is_prev_free(block) {
let prev = block_prev(block);
self.remove(prev);
total += block_size(prev);
current = prev;
}
let pf = if current == block {
0 } else {
read_word(current) & PREV_FREE_BIT
};
block_set_header(current, total, FREE_BIT | pf);
block_write_footer(current);
let after = current.add(total);
if (after as usize) < self.heap_end as usize {
let h = read_word(after);
write_word(after, h | PREV_FREE_BIT);
}
self.insert(current);
}
fn block_size_for(layout: Layout) -> usize {
let align = layout.align().max(WORD);
let extra = if align > WORD { WORD + align - 1 } else { 0 };
let body = (layout.size() + extra).max(MIN_BLOCK - WORD);
let body = round_up(body, WORD);
body + WORD
}
}
struct SpinMutex<T> {
locked: AtomicBool,
data: UnsafeCell<T>,
}
unsafe impl<T: Send> Sync for SpinMutex<T> {}
impl<T> SpinMutex<T> {
const fn new(data: T) -> Self {
Self {
locked: AtomicBool::new(false),
data: UnsafeCell::new(data),
}
}
fn lock(&self) -> SpinGuard<'_, T> {
while self
.locked
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
SpinGuard { mutex: self }
}
}
struct SpinGuard<'a, T> {
mutex: &'a SpinMutex<T>,
}
impl<T> core::ops::Deref for SpinGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.mutex.data.get() }
}
}
impl<T> core::ops::DerefMut for SpinGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.mutex.data.get() }
}
}
impl<T> Drop for SpinGuard<'_, T> {
fn drop(&mut self) {
self.mutex.locked.store(false, Ordering::Release);
}
}
pub struct KrypteiaAlloc {
inner: SpinMutex<TlsfInner>,
}
impl KrypteiaAlloc {
pub const fn new() -> Self {
Self {
inner: SpinMutex::new(TlsfInner::new()),
}
}
pub unsafe fn init(&self, base: *mut u8, size: usize) {
self.inner.lock().init(base, size);
}
}
unsafe impl GlobalAlloc for KrypteiaAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.inner.lock().alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.inner.lock().dealloc(ptr, layout);
}
}
#[cfg(feature = "global-alloc")]
#[global_allocator]
static ALLOCATOR: KrypteiaAlloc = KrypteiaAlloc::new();
#[cfg(not(feature = "global-alloc"))]
static ALLOCATOR: KrypteiaAlloc = KrypteiaAlloc::new();
#[derive(Clone, Copy, Debug, Default)]
pub struct MemStats {
pub used: usize,
pub free: usize,
pub peak: usize,
}
pub unsafe fn krypteia_init(base: *mut u8, size: usize) -> i32 {
if size < MIN_BLOCK + WORD {
return -1;
}
ALLOCATOR.init(base, size);
0
}
pub fn memory_stats() -> MemStats {
let inner = ALLOCATOR.inner.lock();
MemStats {
used: inner.used,
free: inner.total.saturating_sub(inner.used),
peak: inner.peak,
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use core::alloc::Layout;
use std::vec;
use std::vec::Vec;
fn with_heap<F: FnOnce(&mut TlsfInner)>(size: usize, f: F) {
let mut buf = vec![0u8; size];
let mut tlsf = TlsfInner::new();
unsafe { tlsf.init(buf.as_mut_ptr(), size) };
f(&mut tlsf);
}
#[test]
fn init_creates_one_free_block() {
with_heap(1024, |tlsf| {
assert!(tlsf.bitmap != 0, "bitmap should have at least one bit set");
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn single_alloc_dealloc() {
with_heap(1024, |tlsf| {
let layout = Layout::from_size_align(64, 8).unwrap();
let ptr = unsafe { tlsf.alloc(layout) };
assert!(!ptr.is_null());
assert!(tlsf.used > 0);
unsafe { tlsf.dealloc(ptr, layout) };
assert_eq!(tlsf.used, 0, "used should be 0 after freeing everything");
});
}
#[test]
fn multiple_alloc_dealloc() {
with_heap(4096, |tlsf| {
let layout = Layout::from_size_align(128, 8).unwrap();
let mut ptrs = Vec::new();
for _ in 0..8 {
let p = unsafe { tlsf.alloc(layout) };
assert!(!p.is_null(), "allocation should succeed");
ptrs.push(p);
}
for p in ptrs.into_iter().rev() {
unsafe { tlsf.dealloc(p, layout) };
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn interleaved_alloc_free() {
with_heap(4096, |tlsf| {
let la = Layout::from_size_align(32, 8).unwrap();
let lb = Layout::from_size_align(256, 8).unwrap();
let a = unsafe { tlsf.alloc(la) };
let b = unsafe { tlsf.alloc(lb) };
let c = unsafe { tlsf.alloc(la) };
assert!(!a.is_null() && !b.is_null() && !c.is_null());
unsafe { tlsf.dealloc(b, lb) };
let d = unsafe { tlsf.alloc(lb) };
assert!(!d.is_null());
unsafe {
tlsf.dealloc(a, la);
tlsf.dealloc(c, la);
tlsf.dealloc(d, lb);
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn coalescing_restores_full_heap() {
with_heap(2048, |tlsf| {
let la = Layout::from_size_align(64, 8).unwrap();
let a = unsafe { tlsf.alloc(la) };
let b = unsafe { tlsf.alloc(la) };
let c = unsafe { tlsf.alloc(la) };
assert!(!a.is_null() && !b.is_null() && !c.is_null());
unsafe {
tlsf.dealloc(a, la);
tlsf.dealloc(b, la);
tlsf.dealloc(c, la);
}
assert_eq!(tlsf.used, 0);
let big = Layout::from_size_align(1800, 8).unwrap();
let p = unsafe { tlsf.alloc(big) };
assert!(!p.is_null(), "coalescing should restore the full heap");
unsafe { tlsf.dealloc(p, big) };
});
}
#[test]
fn alloc_too_large_returns_null() {
with_heap(256, |tlsf| {
let big = Layout::from_size_align(1024, 8).unwrap();
let p = unsafe { tlsf.alloc(big) };
assert!(p.is_null());
});
}
#[test]
fn peak_tracking() {
with_heap(4096, |tlsf| {
let la = Layout::from_size_align(128, 8).unwrap();
let a = unsafe { tlsf.alloc(la) };
let b = unsafe { tlsf.alloc(la) };
let peak_after_two = tlsf.peak;
unsafe { tlsf.dealloc(b, la) };
assert_eq!(tlsf.peak, peak_after_two, "peak should not decrease");
unsafe { tlsf.dealloc(a, la) };
assert_eq!(tlsf.peak, peak_after_two);
});
}
#[test]
fn write_then_read_back() {
with_heap(4096, |tlsf| {
let layout = Layout::from_size_align(256, 8).unwrap();
let ptr = unsafe { tlsf.alloc(layout) };
assert!(!ptr.is_null());
unsafe {
for i in 0..256 {
*ptr.add(i) = (i & 0xFF) as u8;
}
}
unsafe {
for i in 0..256 {
assert_eq!(*ptr.add(i), (i & 0xFF) as u8);
}
}
unsafe { tlsf.dealloc(ptr, layout) };
});
}
#[test]
fn stress_random_sizes() {
with_heap(8192, |tlsf| {
let mut ptrs: Vec<(*mut u8, Layout)> = Vec::new();
let sizes = [16, 32, 48, 64, 100, 128, 200, 256, 512];
for &sz in &sizes {
let la = Layout::from_size_align(sz, 8).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null(), "failed to alloc {} bytes", sz);
ptrs.push((p, la));
}
let mut kept = Vec::new();
for (i, (p, la)) in ptrs.into_iter().enumerate() {
if i % 2 == 0 {
unsafe { tlsf.dealloc(p, la) };
} else {
kept.push((p, la));
}
}
for &sz in &[16, 48, 100, 200, 512] {
let la = Layout::from_size_align(sz, 8).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null(), "realloc of {} bytes failed", sz);
kept.push((p, la));
}
for (p, la) in kept {
unsafe { tlsf.dealloc(p, la) };
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn alloc_respects_word_alignment() {
with_heap(4096, |tlsf| {
for &sz in &[1usize, 3, 7, 13, 31, 64] {
let la = Layout::from_size_align(sz, WORD).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null());
assert_eq!(
p as usize % WORD,
0,
"pointer for size {} is not {}-byte aligned",
sz,
WORD,
);
unsafe { tlsf.dealloc(p, la) };
}
});
}
#[test]
fn alloc_respects_large_alignment() {
with_heap(4096, |tlsf| {
for &align in &[16usize, 32] {
let la = Layout::from_size_align(64, align).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null());
assert_eq!(p as usize % align, 0, "pointer not {}-byte aligned", align,);
unsafe {
for i in 0..64 {
*p.add(i) = 0xAA;
}
for i in 0..64 {
assert_eq!(*p.add(i), 0xAA);
}
tlsf.dealloc(p, la);
}
}
});
}
#[test]
fn coalesce_forward_only() {
with_heap(2048, |tlsf| {
let la = Layout::from_size_align(64, 8).unwrap();
let a = unsafe { tlsf.alloc(la) };
let b = unsafe { tlsf.alloc(la) };
let c = unsafe { tlsf.alloc(la) };
assert!(!a.is_null() && !b.is_null() && !c.is_null());
unsafe { tlsf.dealloc(b, la) }; unsafe { tlsf.dealloc(a, la) };
let big = Layout::from_size_align(140, 8).unwrap();
let p = unsafe { tlsf.alloc(big) };
assert!(!p.is_null(), "forward coalescing should create a larger block");
unsafe {
tlsf.dealloc(p, big);
tlsf.dealloc(c, la);
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn coalesce_backward_only() {
with_heap(2048, |tlsf| {
let la = Layout::from_size_align(64, 8).unwrap();
let a = unsafe { tlsf.alloc(la) };
let b = unsafe { tlsf.alloc(la) };
let c = unsafe { tlsf.alloc(la) };
assert!(!a.is_null() && !b.is_null() && !c.is_null());
unsafe { tlsf.dealloc(a, la) }; unsafe { tlsf.dealloc(b, la) };
let big = Layout::from_size_align(140, 8).unwrap();
let p = unsafe { tlsf.alloc(big) };
assert!(!p.is_null(), "backward coalescing should create a larger block");
unsafe {
tlsf.dealloc(p, big);
tlsf.dealloc(c, la);
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn coalesce_both_directions() {
with_heap(4096, |tlsf| {
let la = Layout::from_size_align(64, 8).unwrap();
let a = unsafe { tlsf.alloc(la) };
let b = unsafe { tlsf.alloc(la) };
let c = unsafe { tlsf.alloc(la) };
let d = unsafe { tlsf.alloc(la) };
assert!(!a.is_null() && !b.is_null() && !c.is_null() && !d.is_null());
unsafe { tlsf.dealloc(a, la) };
unsafe { tlsf.dealloc(c, la) };
unsafe { tlsf.dealloc(b, la) };
let big = Layout::from_size_align(210, 8).unwrap();
let p = unsafe { tlsf.alloc(big) };
assert!(!p.is_null(), "both-direction coalescing should work");
unsafe {
tlsf.dealloc(p, big);
tlsf.dealloc(d, la);
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn alloc_1_byte() {
with_heap(1024, |tlsf| {
let la = Layout::from_size_align(1, 1).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null());
unsafe {
*p = 0x42;
assert_eq!(*p, 0x42);
tlsf.dealloc(p, la);
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn alloc_exact_heap_minus_overhead() {
let heap_size = 512;
with_heap(heap_size, |tlsf| {
let max_user = heap_size - 2 * WORD;
let la = Layout::from_size_align(max_user, WORD).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null(), "should be able to allocate max_user={}", max_user);
unsafe { tlsf.dealloc(p, la) };
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn exhaust_then_free_then_reuse() {
with_heap(512, |tlsf| {
let la = Layout::from_size_align(64, 8).unwrap();
let mut ptrs = Vec::new();
loop {
let p = unsafe { tlsf.alloc(la) };
if p.is_null() {
break;
}
ptrs.push(p);
}
let count = ptrs.len();
assert!(count >= 3, "should fit at least 3 × 64-byte blocks in 512");
for p in ptrs.drain(..) {
unsafe { tlsf.dealloc(p, la) };
}
assert_eq!(tlsf.used, 0);
for _ in 0..count {
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null(), "reallocation after full free should succeed");
ptrs.push(p);
}
for p in ptrs {
unsafe { tlsf.dealloc(p, la) };
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn no_overlap_between_allocations() {
with_heap(4096, |tlsf| {
let sizes = [32usize, 64, 128, 48, 96, 200];
let mut regions: Vec<(*mut u8, usize)> = Vec::new();
for &sz in &sizes {
let la = Layout::from_size_align(sz, 8).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null());
regions.push((p, sz));
}
for i in 0..regions.len() {
let (pi, si) = regions[i];
let ri = pi as usize..pi as usize + si;
for j in (i + 1)..regions.len() {
let (pj, sj) = regions[j];
let rj = pj as usize..pj as usize + sj;
assert!(
ri.end <= rj.start || rj.end <= ri.start,
"allocations {} ([{:#x}..{:#x}]) and {} ([{:#x}..{:#x}]) overlap",
i,
ri.start,
ri.end,
j,
rj.start,
rj.end,
);
}
}
for (p, sz) in regions {
let la = Layout::from_size_align(sz, 8).unwrap();
unsafe { tlsf.dealloc(p, la) };
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn data_survives_adjacent_alloc_free() {
with_heap(4096, |tlsf| {
let la = Layout::from_size_align(128, 8).unwrap();
let a = unsafe { tlsf.alloc(la) };
assert!(!a.is_null());
unsafe {
for i in 0..128 {
*a.add(i) = 0xAA;
}
}
let b = unsafe { tlsf.alloc(la) };
assert!(!b.is_null());
unsafe {
for i in 0..128 {
*b.add(i) = 0xBB;
}
tlsf.dealloc(b, la);
}
unsafe {
for i in 0..128 {
assert_eq!(*a.add(i), 0xAA, "A corrupted at byte {} after adjacent alloc/free", i,);
}
tlsf.dealloc(a, la);
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn simulated_crypto_session() {
with_heap(8192, |tlsf| {
let cipher_layout = Layout::from_size_align(488, 8).unwrap();
let mac_layout = Layout::from_size_align(752, 8).unwrap();
let tmp_layout = Layout::from_size_align(256, 8).unwrap();
let cipher = unsafe { tlsf.alloc(cipher_layout) };
let mac = unsafe { tlsf.alloc(mac_layout) };
assert!(!cipher.is_null() && !mac.is_null());
for _ in 0..5 {
let tmp = unsafe { tlsf.alloc(tmp_layout) };
assert!(!tmp.is_null(), "temp buffer alloc failed");
unsafe {
for i in 0..256 {
*tmp.add(i) = 0xCC;
}
tlsf.dealloc(tmp, tmp_layout);
}
}
unsafe {
tlsf.dealloc(mac, mac_layout);
tlsf.dealloc(cipher, cipher_layout);
}
assert_eq!(tlsf.used, 0);
let cipher2 = unsafe { tlsf.alloc(cipher_layout) };
let mac2 = unsafe { tlsf.alloc(mac_layout) };
assert!(!cipher2.is_null() && !mac2.is_null());
unsafe {
tlsf.dealloc(cipher2, cipher_layout);
tlsf.dealloc(mac2, mac_layout);
}
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn repeated_init_resets_state() {
let mut buf = vec![0u8; 2048];
let mut tlsf = TlsfInner::new();
unsafe { tlsf.init(buf.as_mut_ptr(), 2048) };
let la = Layout::from_size_align(128, 8).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null());
unsafe { tlsf.init(buf.as_mut_ptr(), 2048) };
assert_eq!(tlsf.used, 0);
assert_eq!(tlsf.peak, 0);
let p2 = unsafe { tlsf.alloc(la) };
assert!(!p2.is_null());
unsafe { tlsf.dealloc(p2, la) };
}
#[test]
fn minimum_heap_size() {
let min = MIN_BLOCK + WORD;
with_heap(min, |tlsf| {
let la = Layout::from_size_align(1, 1).unwrap();
let p = unsafe { tlsf.alloc(la) };
assert!(!p.is_null(), "1-byte alloc on minimum heap should work");
unsafe { tlsf.dealloc(p, la) };
assert_eq!(tlsf.used, 0);
});
}
#[test]
fn dealloc_null_is_noop() {
with_heap(1024, |tlsf| {
let la = Layout::from_size_align(8, 8).unwrap();
unsafe { tlsf.dealloc(ptr::null_mut(), la) };
assert_eq!(tlsf.used, 0);
});
}
}