use std::alloc::{Layout, dealloc};
use std::marker::PhantomData;
use std::mem;
use std::num::NonZero;
use std::ptr::{copy_nonoverlapping, NonNull};
use std::sync::atomic::{AtomicUsize, Ordering};
mod error;
pub use self::error::{Error, Result};
pub struct Bitena<'a> {
buf: NonNull<u8>,
end_byte_idx: AtomicUsize, layout: Layout, _marker: PhantomData<&'a ()>,
}
impl<'a> Bitena<'a> {
pub fn new(byte_capacity: usize) -> Result<Self> {
assert!(byte_capacity > 0, "Capacity must be greater than zero.");
let layout = Layout::from_size_align(byte_capacity, mem::align_of::<u8>())?;
let buf = unsafe {
let ptr = std::alloc::alloc(layout);
if ptr.is_null() {
return Err(Error::OutOfMemory);
}
ptr as *mut u8
};
Ok(Self {
buf: NonNull::new(buf).ok_or(Error::PointerUnderflow)?,
end_byte_idx: AtomicUsize::new(byte_capacity),
layout,
_marker: PhantomData,
})
}
#[inline]
pub fn alloc<T>(&self, val: T) -> &mut T {
self.try_alloc(val)
.unwrap_or_else(|e| panic!("Bitena Failed: {}", e))
}
pub fn try_alloc<T>(&self, val: T) -> Result<&mut T> {
let sizet = std::mem::size_of::<T>();
let align = std::mem::align_of::<T>();
debug_assert!(sizet > 0, "Can't alloc 0 bytes");
debug_assert!(align.is_power_of_two(), "Alignment must be a power of two");
unsafe {
loop {
let end_byte_idx = self.end_byte_idx.load(Ordering::Relaxed);
let ptr_num = (self.buf.as_ptr().add(end_byte_idx as usize) as usize)
.checked_sub(sizet)
.ok_or(Error::PointerUnderflow)?;
let ptr = self
.buf
.with_addr(NonZero::new(ptr_num & !(align - 1)).ok_or(Error::PointerUnderflow)?)
.as_ptr() as *mut u8;
if (ptr as usize) < self.buf.as_ptr() as usize {
return Err(Error::OutOfMemory);
}
let new_end_byte_idx =
(ptr as usize).saturating_sub(self.buf.as_ptr() as usize) as usize;
if let Ok(_) = self.end_byte_idx.compare_exchange_weak(
end_byte_idx, new_end_byte_idx, Ordering::Relaxed,
Ordering::Relaxed,
) {
std::ptr::write(ptr as *mut T, val);
return Ok(&mut *(ptr as *mut T));
}
}
}
}
#[inline]
pub fn alloc_slice<T>(&self, initial_value: T, len: usize) -> &mut [T] {
self.try_alloc_slice(initial_value, len)
.unwrap_or_else(|e| panic!("Bitena Failed: {}", e))
}
pub fn try_alloc_slice<T>(&self, initial_value: T, len: usize) -> Result<&mut [T]> {
let sizet = std::mem::size_of::<T>();
let align = std::mem::align_of::<T>();
debug_assert!(sizet > 0, "Can't alloc 0 bytes");
debug_assert!(align.is_power_of_two(), "Alignment must be a power of two");
unsafe {
loop {
let end_byte_idx = self.end_byte_idx.load(Ordering::Relaxed);
let ptr_num = (self.buf.as_ptr().add(end_byte_idx) as usize)
.checked_sub(len * sizet)
.ok_or(Error::PointerUnderflow)?;
let ptr = self
.buf
.with_addr(NonZero::new(ptr_num & !(align - 1)).ok_or(Error::PointerUnderflow)?)
.as_ptr() as *mut u8;
if (ptr as *mut u8 as usize) < self.buf.as_ptr() as usize {
return Err(Error::OutOfMemory);
}
let new_end_byte_idx =
(ptr as usize).saturating_sub(self.buf.as_ptr() as usize) as usize;
if let Ok(_) = self.end_byte_idx.compare_exchange_weak(
end_byte_idx, new_end_byte_idx, Ordering::Relaxed,
Ordering::Relaxed,
) {
if sizet == 1 {
let byte_ptr = &initial_value as *const T as *const u8;
std::ptr::write_bytes(ptr, *byte_ptr, len * sizet);
} else if is_all_zeros(&initial_value) {
std::ptr::write_bytes(ptr, 0, len * sizet);
} else {
let initial_value_ptr = &initial_value as *const T as *const u8;
for i in 0..len {
copy_nonoverlapping(
initial_value_ptr,
(ptr as *mut u8).add(i * sizet),
sizet,
);
}
}
return Ok(std::slice::from_raw_parts_mut(ptr as *mut T, len));
}
}
}
}
#[inline]
pub fn alloc_str(&self, st: &str) -> &str {
self.try_alloc_str(st)
.unwrap_or_else(|e| panic!("Bitena Failed: {}", e))
}
pub fn try_alloc_str(&self, st: &str) -> Result<&str> {
let sizet = st.len();
let align = std::mem::align_of::<u8>();
if sizet == 0 {
return Ok::<&str, Error>("");
}
debug_assert!(align.is_power_of_two(), "Alignment must be a power of two");
unsafe {
loop {
let end_byte_idx = self.end_byte_idx.load(Ordering::Relaxed);
let ptr_num = (self.buf.as_ptr().add(end_byte_idx as usize) as usize)
.checked_sub(sizet)
.ok_or(Error::PointerUnderflow)?;
let ptr = self
.buf
.with_addr(NonZero::new(ptr_num & !(align - 1)).ok_or(Error::PointerUnderflow)?)
.as_ptr() as *mut u8;
if (ptr as usize) < self.buf.as_ptr() as usize {
return Err(Error::OutOfMemory);
}
let new_end_byte_idx =
(ptr as usize).saturating_sub(self.buf.as_ptr() as usize) as usize;
if let Ok(_) = self.end_byte_idx.compare_exchange_weak(
end_byte_idx, new_end_byte_idx, Ordering::Relaxed,
Ordering::Relaxed,
) {
copy_nonoverlapping(st.as_ptr(), ptr, sizet);
return Ok(std::str::from_utf8_unchecked(std::slice::from_raw_parts(
ptr, sizet,
)));
}
}
}
}
#[inline]
pub fn remaining(&self) -> usize {
self.end_byte_idx.load(Ordering::Relaxed)
}
pub fn reset(&mut self) {
loop {
let end_byte_idx = self.end_byte_idx.load(Ordering::Relaxed);
if let Ok(_) = self.end_byte_idx.compare_exchange_weak(
end_byte_idx, self.layout.size(), Ordering::Relaxed,
Ordering::Relaxed,
) {
return ();
}
}
}
}
impl Drop for Bitena<'_> {
#[inline]
fn drop(&mut self) {
unsafe {
dealloc(self.buf.as_ptr(), self.layout);
}
}
}
unsafe impl Send for Bitena<'_> {}
unsafe impl Sync for Bitena<'_> {}
#[inline]
fn is_all_zeros<T>(value: &T) -> bool {
let num_bytes = std::mem::size_of::<T>();
unsafe {
let ptr = value as *const T as *const u8;
for i in 0..num_bytes {
if *ptr.add(i) != 0 {
return false;
}
}
}
true
}
#[cfg(test)]
mod test {
use super::*;
use sysinfo::{Pid, System};
#[test]
fn test_try_alignment() -> Result<()> {
let bitena = Bitena::new(1024)?;
let _a = bitena.alloc_slice(0i8, 1); let _a = bitena.alloc_slice(0i8, 1); let a = bitena.alloc_slice(0i8, 1); assert_eq!(a.as_ptr() as usize % 1, 0);
let b = bitena.alloc(0u16); assert_eq!(b as *const u16 as usize % 2, 0);
let c = bitena.alloc_slice(0i32, 1); assert_eq!(c.as_ptr() as usize % 4, 0);
let d = bitena.alloc_slice(0i64, 1); assert_eq!(d.as_ptr() as usize % 8, 0);
let a = bitena.try_alloc_slice(0i8, 1)?; assert_eq!(a.as_ptr() as usize % 1, 0);
let b = bitena.try_alloc(0u16)?; assert_eq!(b as *const u16 as usize % 2, 0);
let c = bitena.try_alloc_slice(0i32, 1)?; assert_eq!(c.as_ptr() as usize % 4, 0);
let d = bitena.try_alloc_slice(0i64, 1)?; assert_eq!(d.as_ptr() as usize % 8, 0);
Ok(())
}
#[test]
fn test_try_bitena() -> Result<()> {
let mut bitena = Bitena::new(1024)?;
assert_eq!(bitena.remaining(), 1024, "Bitena should report 1024 bytes");
let u8_ptr: &mut u8 = bitena.alloc(41u8);
assert_eq!(bitena.remaining(), 1023, "Bitena should report 1023 bytes");
let u32_ptr: &mut u32 = bitena.alloc(42u32);
assert_eq!(
u32_ptr as *mut u32 as usize % 4,
0,
"Pointer should be aligned"
);
*u32_ptr += *u8_ptr as u32;
let u8_ptr: &mut [u8] = bitena.alloc_slice(43u8, 5);
assert_eq!(u8_ptr, vec![43u8, 43, 43, 43, 43]);
bitena.reset();
assert_eq!(bitena.remaining(), 1024, "Bitena should report 1024 bytes");
let _u8_ptr: &mut u8 = bitena.alloc(44u8);
assert_eq!(bitena.remaining(), 1023, "Bitena should report 1023 bytes");
let u64_ptr = bitena.alloc_slice(0u64, 4);
assert_eq!(
u64_ptr.as_ptr() as usize % 8,
0,
"u64 pointer should be 8-byte aligned"
);
let _u8_ptr: &mut u8 = bitena.alloc(46u8);
let u128_ptr = bitena.alloc_slice(0u128, 5);
assert_eq!(
u128_ptr.as_ptr() as usize % 16,
0,
"u128 pointer should be 8-byte aligned"
);
bitena.reset();
let u8_ptr: &mut u8 = bitena.try_alloc(41u8)?;
assert_eq!(bitena.remaining(), 1023, "Bitena should report 1023 bytes");
let u32_ptr: &mut u32 = bitena.try_alloc(42u32)?;
assert_eq!(
u32_ptr as *mut u32 as usize % 4,
0,
"Pointer should be aligned"
);
*u32_ptr += *u8_ptr as u32;
let u8_ptr: &mut [u8] = bitena.try_alloc_slice(43u8, 5)?;
assert_eq!(u8_ptr, vec![43u8, 43, 43, 43, 43]);
bitena.reset();
assert_eq!(bitena.remaining(), 1024, "Bitena should report 1024 bytes");
let _u8_ptr: &mut u8 = bitena.try_alloc(44u8)?;
assert_eq!(bitena.remaining(), 1023, "Bitena should report 1023 bytes");
let st = bitena.try_alloc_str("Test")?;
assert_eq!(bitena.remaining(), 1019, "Bitena should report 1023 bytes");
assert_eq!(st, "Test");
let st = bitena.try_alloc_str("")?;
assert_eq!(bitena.remaining(), 1019, "Bitena should report 1023 bytes");
assert_eq!(st, "");
let u64_ptr = bitena.try_alloc_slice(0u64, 4)?;
assert_eq!(
u64_ptr.as_ptr() as usize % 8,
0,
"u64 pointer should be 8-byte aligned"
);
let _u8_ptr: &mut u8 = bitena.try_alloc(46u8)?;
let u128_ptr = bitena.try_alloc_slice(0u128, 5)?;
assert_eq!(
u128_ptr.as_ptr() as usize % 16,
0,
"u128 pointer should be 8-byte aligned"
);
Ok(())
}
#[test]
#[should_panic(expected = "Layout Error: invalid parameters to Layout::from_size_align")]
fn test_failed_to_allocate_panic() {
let _bitena = Bitena::new(usize::MAX).unwrap_or_else(|e| panic!("Bitena Failed: {}", e));
}
#[test]
fn test_try_failed_to_allocate() -> Result<()> {
assert!(matches!(Bitena::new(usize::MAX), Err(Error::Layout(_))));
Ok(())
}
#[test]
#[should_panic(expected = "Bitena Failed: Out of Memory")]
fn test_out_of_memory_panic() {
let bitena = Bitena::new(1024).unwrap_or_else(|e| panic!("Should work Arena Failed: {}", e));
let _large_slice: &mut [u64] = bitena.alloc_slice(0u64, 150);
}
#[test]
fn test_try_out_of_memory() -> Result<()> {
let bitena = Bitena::new(1024)?;
assert!(matches!(
bitena.try_alloc_slice(0u64, 150),
Err(Error::OutOfMemory)
));
Ok(())
}
fn format_number(n: u64) -> String {
let s = n.to_string();
let mut result = String::new();
let mut count = 0;
for c in s.chars().rev() {
if count == 3 {
result.push(',');
count = 0;
}
result.push(c);
count += 1;
}
result.chars().rev().collect()
}
fn get_system_available_memory() -> u64 {
let sys = System::new_all();
sys.available_memory()
}
fn get_process_memory_usage() -> u64 {
let sys = System::new_all();
let pid = Pid::from(std::process::id() as usize); sys.process(pid)
.map(|process: &sysinfo::Process| process.memory())
.unwrap_or(0)
}
fn test_lg_alloc(size: usize) -> Result<()> {
let bitena = Bitena::new(size)?;
let j = bitena.try_alloc_slice(0u8, size)?;
j.fill(15u8);
Ok(())
}
#[cfg_attr(miri, cfg(miri_skip))]
#[test]
fn test_try_large_allocation() -> Result<()> {
const TARGET_SIZE: usize = 2 * 1024 * 1024 * 1024; const NUM_ALLOCS: usize = 1000;
const ALLOC_SIZE: usize = TARGET_SIZE / NUM_ALLOCS;
let start_sys = get_system_available_memory();
let start_proc = get_process_memory_usage();
println!(
"Available memory: {} bytes",
format_number(get_system_available_memory())
);
println!(
"Process memory usage: {} bytes",
format_number(get_process_memory_usage())
);
for _ in 0..NUM_ALLOCS {
test_lg_alloc(ALLOC_SIZE).unwrap();
}
let used_sys = start_sys.saturating_sub(get_system_available_memory());
let used_proc = get_process_memory_usage().saturating_sub(start_proc);
println!(
"Available memory: {} bytes, used: {}",
format_number(get_system_available_memory()),
format_number(used_sys)
);
println!(
"Process memory usage: {} bytes, Additional proc mem used: {}",
format_number(get_process_memory_usage()),
format_number(used_proc)
);
assert!(used_sys < 50_000_000); assert!(used_proc < 10_000_000); Ok(())
}
}