use std::alloc::{Layout, alloc, dealloc};
pub const TLAB_SIZE: usize = 32 * 1024;
const TLAB_ALIGN: usize = 8;
pub struct Tlab {
base: *mut u8,
cursor: usize,
limit: usize,
capacity: usize,
}
unsafe impl Send for Tlab {}
impl Tlab {
pub fn new() -> Self {
Self::with_capacity(TLAB_SIZE)
}
pub fn with_capacity(capacity: usize) -> Self {
assert!(capacity > 0, "TLAB capacity must be non-zero");
let layout = Layout::from_size_align(capacity, TLAB_ALIGN).expect("valid TLAB layout");
let base = unsafe { alloc(layout) };
assert!(!base.is_null(), "TLAB allocation failed");
let base_addr = base as usize;
Self {
base,
cursor: base_addr,
limit: base_addr + capacity,
capacity,
}
}
#[inline(always)]
pub fn bump_alloc(&mut self, size: usize) -> Option<*mut u8> {
let aligned_cursor = (self.cursor + (TLAB_ALIGN - 1)) & !(TLAB_ALIGN - 1);
let new_cursor = aligned_cursor + size;
if new_cursor > self.limit {
return None; }
let ptr = aligned_cursor as *mut u8;
self.cursor = new_cursor;
Some(ptr)
}
pub fn bump_alloc_slow(&mut self, size: usize) -> Option<*mut u8> {
let needed = size + (TLAB_ALIGN - 1); if needed > self.capacity {
return None;
}
self.refill();
self.bump_alloc(size)
}
#[inline]
pub fn allocate(&mut self, size: usize) -> Option<*mut u8> {
self.bump_alloc(size).or_else(|| self.bump_alloc_slow(size))
}
pub fn remaining(&self) -> usize {
self.limit.saturating_sub(self.cursor)
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn used(&self) -> usize {
self.cursor - (self.base as usize)
}
fn refill(&mut self) {
if !self.base.is_null() {
let layout =
Layout::from_size_align(self.capacity, TLAB_ALIGN).expect("valid TLAB layout");
unsafe { dealloc(self.base, layout) };
}
let layout = Layout::from_size_align(self.capacity, TLAB_ALIGN).expect("valid TLAB layout");
let base = unsafe { alloc(layout) };
assert!(!base.is_null(), "TLAB refill allocation failed");
let base_addr = base as usize;
self.base = base;
self.cursor = base_addr;
self.limit = base_addr + self.capacity;
}
}
impl Default for Tlab {
fn default() -> Self {
Self::new()
}
}
impl Drop for Tlab {
fn drop(&mut self) {
if !self.base.is_null() {
let layout =
Layout::from_size_align(self.capacity, TLAB_ALIGN).expect("valid TLAB layout");
unsafe { dealloc(self.base, layout) };
self.base = std::ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tlab_new_has_full_capacity() {
let tlab = Tlab::new();
assert_eq!(tlab.capacity(), TLAB_SIZE);
assert_eq!(tlab.used(), 0);
assert_eq!(tlab.remaining(), TLAB_SIZE);
}
#[test]
fn test_bump_alloc_fast_path() {
let mut tlab = Tlab::with_capacity(256);
let p1 = tlab.bump_alloc(16).expect("first alloc");
assert!(!p1.is_null());
assert_eq!(tlab.used(), 16);
assert_eq!(tlab.remaining(), 256 - 16);
let p2 = tlab.bump_alloc(32).expect("second alloc");
assert!(!p2.is_null());
assert_ne!(p1, p2);
}
#[test]
fn test_bump_alloc_returns_none_on_exhaustion() {
let mut tlab = Tlab::with_capacity(64);
assert!(tlab.bump_alloc(64).is_some());
assert!(tlab.bump_alloc(1).is_none());
}
#[test]
fn test_slow_path_refills_and_succeeds() {
let mut tlab = Tlab::with_capacity(64);
assert!(tlab.bump_alloc(64).is_some());
let p = tlab.bump_alloc_slow(16).expect("slow path must succeed");
assert!(!p.is_null());
}
#[test]
fn test_slow_path_rejects_oversized_allocation() {
let mut tlab = Tlab::with_capacity(64);
assert!(tlab.bump_alloc_slow(128).is_none());
}
#[test]
fn test_allocate_combines_fast_and_slow() {
let mut tlab = Tlab::with_capacity(64);
assert!(tlab.allocate(32).is_some());
assert!(tlab.allocate(32).is_some());
assert!(tlab.allocate(16).is_some());
}
#[test]
fn test_allocations_are_aligned() {
let mut tlab = Tlab::with_capacity(256);
for _ in 0..10 {
let p = tlab.allocate(7).expect("alloc");
assert_eq!(p as usize % TLAB_ALIGN, 0, "pointer must be aligned");
}
}
#[test]
fn test_default_tlab_size_is_32kb() {
assert_eq!(TLAB_SIZE, 32 * 1024);
}
}