use crate::error::{Result, ZiporaError};
use crate::memory::simd_ops::{fast_fill, fast_compare, fast_prefetch};
use crate::memory::cache_layout::{CacheOptimizedAllocator, CacheLayoutConfig, align_to_cache_line, AccessPattern, HotColdSeparator, PrefetchHint};
use crate::memory::{get_optimal_numa_node, numa_alloc_aligned, numa_dealloc};
use crate::simd::adaptive::AdaptiveSimdSelector;
use crate::simd::Operation;
use super::CachePadded;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::alloc::{Layout, alloc, dealloc};
use std::cell::RefCell;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, Weak};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
const CHUNK_HEADER_MAGIC: u64 = 0xDEADBEEFCAFEBABE;
const CHUNK_FOOTER_MAGIC: u64 = 0xFEEDFACEDEADBEEF;
const POOL_MAGIC: u64 = 0xABCDEF0123456789;
const SIZE_CLASSES: &[usize] = &[
8, 16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048, 2560, 3072, 3584, 4096, 5120, 6144, 7168, 8192, ];
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SecurePoolConfig {
pub chunk_size: usize,
pub max_chunks: usize,
pub alignment: usize,
pub use_guard_pages: bool,
pub zero_on_free: bool,
pub zero_on_alloc: bool,
pub local_cache_size: usize,
pub batch_size: usize,
pub enable_simd_ops: bool,
pub simd_threshold: usize,
pub enable_cache_alignment: bool,
pub cache_config: Option<CacheLayoutConfig>,
pub enable_numa_awareness: bool,
pub enable_hot_cold_separation: bool,
pub hot_data_threshold: usize,
pub enable_huge_pages: bool,
pub huge_page_threshold: usize,
pub prefetch_distance: usize,
}
impl SecurePoolConfig {
pub fn new(chunk_size: usize, max_chunks: usize, alignment: usize) -> Self {
Self {
chunk_size,
max_chunks,
alignment,
use_guard_pages: false,
zero_on_free: true,
zero_on_alloc: false,
local_cache_size: 64,
batch_size: 16,
enable_simd_ops: true,
simd_threshold: 64,
enable_cache_alignment: true,
cache_config: Some(CacheLayoutConfig::new()),
enable_numa_awareness: true,
enable_hot_cold_separation: true,
hot_data_threshold: 1000,
enable_huge_pages: cfg!(target_os = "linux"),
huge_page_threshold: 2 * 1024 * 1024, prefetch_distance: 8, }
}
pub fn small_secure() -> Self {
Self {
chunk_size: 1024,
max_chunks: 100,
alignment: 8,
use_guard_pages: false,
zero_on_free: true,
zero_on_alloc: false,
local_cache_size: 64,
batch_size: 16,
enable_simd_ops: true,
simd_threshold: 64,
enable_cache_alignment: true,
cache_config: Some(CacheLayoutConfig::random()),
enable_numa_awareness: true,
enable_hot_cold_separation: true,
hot_data_threshold: 500, enable_huge_pages: false, huge_page_threshold: 2 * 1024 * 1024,
prefetch_distance: 8, }
}
pub fn medium_secure() -> Self {
Self {
chunk_size: 64 * 1024,
max_chunks: 50,
alignment: 16,
use_guard_pages: true,
zero_on_free: true,
zero_on_alloc: false,
local_cache_size: 32,
batch_size: 8,
enable_simd_ops: true,
simd_threshold: 64,
enable_cache_alignment: true,
cache_config: Some(CacheLayoutConfig::sequential()),
enable_numa_awareness: true,
enable_hot_cold_separation: true,
hot_data_threshold: 750, enable_huge_pages: false, huge_page_threshold: 2 * 1024 * 1024,
prefetch_distance: 8, }
}
pub fn large_secure() -> Self {
Self {
chunk_size: 1024 * 1024,
max_chunks: 10,
alignment: 32,
use_guard_pages: true,
zero_on_free: true,
zero_on_alloc: false,
local_cache_size: 16,
batch_size: 4,
enable_simd_ops: true,
simd_threshold: 64,
enable_cache_alignment: true,
cache_config: Some(CacheLayoutConfig::read_heavy()),
enable_numa_awareness: true,
enable_hot_cold_separation: true,
hot_data_threshold: 1500, enable_huge_pages: true, huge_page_threshold: 2 * 1024 * 1024,
prefetch_distance: 12, }
}
pub fn with_alignment(mut self, alignment: usize) -> Self {
self.alignment = alignment;
self
}
pub fn with_guard_pages(mut self, use_guard_pages: bool) -> Self {
self.use_guard_pages = use_guard_pages;
self
}
pub fn with_zero_on_free(mut self, zero_on_free: bool) -> Self {
self.zero_on_free = zero_on_free;
self
}
pub fn with_zero_on_alloc(mut self, zero_on_alloc: bool) -> Self {
self.zero_on_alloc = zero_on_alloc;
self
}
pub fn with_local_cache_size(mut self, size: usize) -> Self {
self.local_cache_size = size;
self
}
pub fn with_batch_size(mut self, size: usize) -> Self {
self.batch_size = size;
self
}
pub fn with_simd_ops(mut self, enable: bool) -> Self {
self.enable_simd_ops = enable;
self
}
pub fn with_simd_threshold(mut self, threshold: usize) -> Self {
self.simd_threshold = threshold;
self
}
pub fn with_cache_alignment(mut self, enable: bool) -> Self {
self.enable_cache_alignment = enable;
self
}
pub fn with_cache_config(mut self, config: Option<CacheLayoutConfig>) -> Self {
self.cache_config = config;
self
}
pub fn with_access_pattern(mut self, pattern: AccessPattern) -> Self {
let config = match pattern {
AccessPattern::Sequential => CacheLayoutConfig::sequential(),
AccessPattern::Random => CacheLayoutConfig::random(),
AccessPattern::WriteHeavy => CacheLayoutConfig::write_heavy(),
AccessPattern::ReadHeavy => CacheLayoutConfig::read_heavy(),
AccessPattern::Mixed => CacheLayoutConfig::new(),
};
self.cache_config = Some(config);
self
}
pub fn with_numa_awareness(mut self, enable: bool) -> Self {
self.enable_numa_awareness = enable;
self
}
pub fn with_hot_cold_separation(mut self, enable: bool) -> Self {
self.enable_hot_cold_separation = enable;
self
}
pub fn with_hot_data_threshold(mut self, threshold: usize) -> Self {
self.hot_data_threshold = threshold;
self
}
pub fn with_huge_pages(mut self, enable: bool) -> Self {
self.enable_huge_pages = enable;
self
}
pub fn with_huge_page_threshold(mut self, threshold: usize) -> Self {
self.huge_page_threshold = threshold;
self
}
pub fn with_prefetch_distance(mut self, distance: usize) -> Self {
self.prefetch_distance = distance;
self
}
}
#[derive(Debug, Clone)]
pub struct SecurePoolStats {
pub allocated: u64,
pub available: u64,
pub chunks: usize,
pub alloc_count: u64,
pub dealloc_count: u64,
pub pool_hits: u64,
pub pool_misses: u64,
pub corruption_detected: u64,
pub double_free_detected: u64,
pub local_cache_hits: u64,
pub cross_thread_steals: u64,
pub cache_aligned_allocs: u64,
pub numa_local_allocs: u64,
pub hot_data_allocs: u64,
pub cold_data_allocs: u64,
pub huge_page_allocs: u64,
pub cache_hit_ratio: f64,
}
impl Default for SecurePoolStats {
fn default() -> Self {
Self {
allocated: 0,
available: 0,
chunks: 0,
alloc_count: 0,
dealloc_count: 0,
pool_hits: 0,
pool_misses: 0,
corruption_detected: 0,
double_free_detected: 0,
local_cache_hits: 0,
cross_thread_steals: 0,
cache_aligned_allocs: 0,
numa_local_allocs: 0,
hot_data_allocs: 0,
cold_data_allocs: 0,
huge_page_allocs: 0,
cache_hit_ratio: 0.0,
}
}
}
#[repr(C)]
#[derive(Debug)]
struct ChunkHeader {
magic: u64,
size: usize,
generation: u32,
pool_id: u32,
allocation_time: u64,
canary: u32,
padding: u32,
}
#[repr(C)]
#[derive(Debug)]
struct ChunkFooter {
canary: u32,
generation: u32,
magic: u64,
}
pub struct SecureChunk {
ptr: NonNull<u8>,
size: usize,
generation: u32,
pool_id: u32,
canary: u32,
}
impl SecureChunk {
fn new(size: usize, generation: u32, pool_id: u32) -> Result<Self> {
let canary = fastrand::u32(..);
let header_size = std::mem::size_of::<ChunkHeader>();
let footer_size = std::mem::size_of::<ChunkFooter>();
let total_size = header_size + size + footer_size;
let layout = Layout::from_size_align(total_size, 8)
.map_err(|_| ZiporaError::invalid_data("Invalid layout for chunk allocation"))?;
let raw_ptr = unsafe { alloc(layout) };
if raw_ptr.is_null() {
return Err(ZiporaError::out_of_memory(size));
}
let header = raw_ptr as *mut ChunkHeader;
unsafe {
(*header) = ChunkHeader {
magic: CHUNK_HEADER_MAGIC,
size,
generation,
pool_id,
allocation_time: current_time_nanos(),
canary,
padding: 0,
};
}
let footer_ptr = unsafe { raw_ptr.add(header_size + size) as *mut ChunkFooter };
unsafe {
(*footer_ptr) = ChunkFooter {
canary,
generation,
magic: CHUNK_FOOTER_MAGIC,
};
}
let data_ptr = unsafe { raw_ptr.add(header_size) };
Ok(Self {
ptr: unsafe { NonNull::new_unchecked(data_ptr) },
size,
generation,
pool_id,
canary,
})
}
fn validate(&self) -> Result<()> {
let header_size = std::mem::size_of::<ChunkHeader>();
let header_ptr = unsafe { self.ptr.as_ptr().sub(header_size) as *const ChunkHeader };
let header = unsafe { &*header_ptr };
if header.magic != CHUNK_HEADER_MAGIC {
return Err(ZiporaError::invalid_data(&format!(
"Header corruption detected: magic={:#x}, expected={:#x}",
header.magic, CHUNK_HEADER_MAGIC
)));
}
if header.generation != self.generation {
return Err(ZiporaError::invalid_data(&format!(
"Generation mismatch: header={}, chunk={}",
header.generation, self.generation
)));
}
if header.pool_id != self.pool_id {
return Err(ZiporaError::invalid_data(&format!(
"Pool ID mismatch: header={}, chunk={}",
header.pool_id, self.pool_id
)));
}
if header.canary != self.canary {
return Err(ZiporaError::invalid_data(&format!(
"Header canary mismatch: header={:#x}, chunk={:#x}",
header.canary, self.canary
)));
}
let footer_ptr = unsafe { self.ptr.as_ptr().add(self.size) as *const ChunkFooter };
let footer = unsafe { &*footer_ptr };
if footer.magic != CHUNK_FOOTER_MAGIC {
return Err(ZiporaError::invalid_data(&format!(
"Footer corruption detected: magic={:#x}, expected={:#x}",
footer.magic, CHUNK_FOOTER_MAGIC
)));
}
if footer.canary != self.canary {
return Err(ZiporaError::invalid_data(&format!(
"Footer canary mismatch: footer={:#x}, chunk={:#x}",
footer.canary, self.canary
)));
}
if footer.generation != self.generation {
return Err(ZiporaError::invalid_data(&format!(
"Footer generation mismatch: footer={}, chunk={}",
footer.generation, self.generation
)));
}
Ok(())
}
#[inline]
pub fn as_ptr(&self) -> *mut u8 {
self.ptr.as_ptr()
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
pub fn generation(&self) -> u32 {
self.generation
}
fn deallocate(self, zero_on_free: bool, enable_simd_ops: bool, simd_threshold: usize) {
if zero_on_free {
unsafe {
if enable_simd_ops && self.size >= simd_threshold {
let slice = std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size);
fast_fill(slice, 0);
} else {
std::ptr::write_bytes(self.ptr.as_ptr(), 0, self.size);
}
}
}
let header_size = std::mem::size_of::<ChunkHeader>();
let footer_size = std::mem::size_of::<ChunkFooter>();
let total_size = header_size + self.size + footer_size;
let raw_ptr = unsafe { self.ptr.as_ptr().sub(header_size) };
let layout = Layout::from_size_align(total_size, 8)
.expect("layout creation: non-zero size, power-of-two alignment");
unsafe {
dealloc(raw_ptr, layout);
}
}
}
unsafe impl Send for SecureChunk {}
unsafe impl Sync for SecureChunk {}
use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared};
struct LockFreeStack<T> {
head: Atomic<Node<T>>,
}
struct Node<T> {
data: std::mem::ManuallyDrop<T>,
next: Atomic<Node<T>>,
}
impl<T> LockFreeStack<T> {
fn new() -> Self {
Self {
head: Atomic::null(),
}
}
fn push(&self, item: T) {
let mut node = Owned::new(Node {
data: std::mem::ManuallyDrop::new(item),
next: Atomic::null(),
});
let guard = epoch::pin();
loop {
let head = self.head.load(Ordering::Acquire, &guard);
node.next.store(head, Ordering::Relaxed);
match self.head.compare_exchange(head, node, Ordering::Release, Ordering::Relaxed, &guard) {
Ok(_) => break,
Err(e) => node = e.new,
}
}
}
fn pop(&self) -> Option<T> {
let guard = epoch::pin();
loop {
let head = self.head.load(Ordering::Acquire, &guard);
if head.is_null() {
return None;
}
let next = unsafe { head.deref() }.next.load(Ordering::Relaxed, &guard);
if self.head.compare_exchange(head, next, Ordering::AcqRel, Ordering::Acquire, &guard).is_ok() {
unsafe {
guard.defer_destroy(head);
return Some(std::mem::ManuallyDrop::into_inner(std::ptr::read(&head.deref().data)));
}
}
}
}
fn is_empty(&self) -> bool {
let guard = epoch::pin();
self.head.load(Ordering::Acquire, &guard).is_null()
}
}
impl<T> Drop for LockFreeStack<T> {
fn drop(&mut self) {
while self.pop().is_some() {}
}
}
#[derive(Default)]
struct LocalCache {
chunks: Vec<SecureChunk>,
max_size: usize,
}
impl LocalCache {
fn new(max_size: usize) -> Self {
Self {
chunks: Vec::with_capacity(max_size),
max_size,
}
}
fn try_pop(&mut self) -> Option<SecureChunk> {
self.chunks.pop()
}
fn try_push(&mut self, chunk: SecureChunk) -> std::result::Result<(), SecureChunk> {
if self.chunks.len() < self.max_size {
self.chunks.push(chunk);
Ok(())
} else {
Err(chunk)
}
}
fn is_empty(&self) -> bool {
self.chunks.is_empty()
}
fn len(&self) -> usize {
self.chunks.len()
}
fn clear(&mut self, zero_on_free: bool, enable_simd_ops: bool, simd_threshold: usize) {
for chunk in self.chunks.drain(..) {
chunk.deallocate(zero_on_free, enable_simd_ops, simd_threshold);
}
}
}
pub struct SecureMemoryPool {
config: SecurePoolConfig,
pool_id: u32,
global_stack: LockFreeStack<SecureChunk>,
next_generation: AtomicU32,
local_caches: thread_local::ThreadLocal<RefCell<LocalCache>>,
cache_allocator: Option<CacheOptimizedAllocator>,
hot_cold_separator: std::sync::Mutex<HotColdSeparator<usize>>,
alloc_count: CachePadded<AtomicU64>,
dealloc_count: CachePadded<AtomicU64>,
pool_hits: CachePadded<AtomicU64>,
pool_misses: CachePadded<AtomicU64>,
corruption_detected: CachePadded<AtomicU64>,
double_free_detected: CachePadded<AtomicU64>,
local_cache_hits: CachePadded<AtomicU64>,
cross_thread_steals: CachePadded<AtomicU64>,
cache_aligned_allocs: CachePadded<AtomicU64>,
numa_local_allocs: CachePadded<AtomicU64>,
hot_data_allocs: CachePadded<AtomicU64>,
cold_data_allocs: CachePadded<AtomicU64>,
huge_page_allocs: CachePadded<AtomicU64>,
active_allocations: std::sync::RwLock<HashMap<usize, (u32, Instant)>>, }
impl std::fmt::Debug for SecureMemoryPool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecureMemoryPool")
.field("config", &self.config)
.field("pool_id", &self.pool_id)
.field("next_generation", &self.next_generation)
.finish_non_exhaustive()
}
}
impl SecureMemoryPool {
pub fn new(config: SecurePoolConfig) -> Result<Arc<Self>> {
if config.chunk_size == 0 {
return Err(ZiporaError::invalid_data("chunk_size cannot be zero"));
}
if config.alignment == 0 || !config.alignment.is_power_of_two() {
return Err(ZiporaError::invalid_data(
"alignment must be a power of two",
));
}
static POOL_ID_COUNTER: AtomicU32 = AtomicU32::new(1);
let pool_id = POOL_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let cache_allocator = if config.enable_cache_alignment && config.cache_config.is_some() {
Some(CacheOptimizedAllocator::new(config.cache_config.clone().expect("cache_config present when cache_friendly enabled")))
} else {
None
};
let hot_cold_separator = if config.enable_hot_cold_separation && config.cache_config.is_some() {
HotColdSeparator::<usize>::new(config.cache_config.clone().expect("cache_config present when hot_cold_separation enabled"))
} else {
HotColdSeparator::<usize>::new(CacheLayoutConfig::default())
};
Ok(Arc::new(Self {
config,
pool_id,
global_stack: LockFreeStack::new(),
next_generation: AtomicU32::new(1),
local_caches: thread_local::ThreadLocal::new(),
cache_allocator,
hot_cold_separator: std::sync::Mutex::new(hot_cold_separator),
cache_aligned_allocs: CachePadded::new(AtomicU64::new(0)),
numa_local_allocs: CachePadded::new(AtomicU64::new(0)),
hot_data_allocs: CachePadded::new(AtomicU64::new(0)),
cold_data_allocs: CachePadded::new(AtomicU64::new(0)),
huge_page_allocs: CachePadded::new(AtomicU64::new(0)),
alloc_count: CachePadded::new(AtomicU64::new(0)),
dealloc_count: CachePadded::new(AtomicU64::new(0)),
pool_hits: CachePadded::new(AtomicU64::new(0)),
pool_misses: CachePadded::new(AtomicU64::new(0)),
corruption_detected: CachePadded::new(AtomicU64::new(0)),
double_free_detected: CachePadded::new(AtomicU64::new(0)),
local_cache_hits: CachePadded::new(AtomicU64::new(0)),
cross_thread_steals: CachePadded::new(AtomicU64::new(0)),
active_allocations: std::sync::RwLock::new(HashMap::new()),
}))
}
pub fn allocate(self: &Arc<Self>) -> Result<SecurePooledPtr> {
self.allocate_with_hint(false) }
pub fn allocate_with_hint(self: &Arc<Self>, is_hot: bool) -> Result<SecurePooledPtr> {
self.alloc_count.fetch_add(1, Ordering::Relaxed);
let local_cache = self
.local_caches
.get_or(|| RefCell::new(LocalCache::new(self.config.local_cache_size)));
if let Some(chunk) = local_cache.borrow_mut().try_pop() {
self.local_cache_hits.fetch_add(1, Ordering::Relaxed);
self.pool_hits.fetch_add(1, Ordering::Relaxed);
if let Err(e) = chunk.validate() {
self.corruption_detected.fetch_add(1, Ordering::Relaxed);
return Err(e);
}
if is_hot {
self.hot_data_allocs.fetch_add(1, Ordering::Relaxed);
} else {
self.cold_data_allocs.fetch_add(1, Ordering::Relaxed);
}
self.active_allocations.write().expect("active_allocations lock").insert(
chunk.as_ptr() as usize,
(chunk.generation(), Instant::now()),
);
return Ok(SecurePooledPtr {
chunk: Some(chunk),
pool: Arc::downgrade(self),
});
}
if let Some(chunk) = self.global_stack.pop() {
self.cross_thread_steals.fetch_add(1, Ordering::Relaxed);
self.pool_hits.fetch_add(1, Ordering::Relaxed);
if let Err(e) = chunk.validate() {
self.corruption_detected.fetch_add(1, Ordering::Relaxed);
return Err(e);
}
if is_hot {
self.hot_data_allocs.fetch_add(1, Ordering::Relaxed);
} else {
self.cold_data_allocs.fetch_add(1, Ordering::Relaxed);
}
self.active_allocations.write().expect("active_allocations lock").insert(
chunk.as_ptr() as usize,
(chunk.generation(), Instant::now()),
);
return Ok(SecurePooledPtr {
chunk: Some(chunk),
pool: Arc::downgrade(self),
});
}
self.pool_misses.fetch_add(1, Ordering::Relaxed);
let generation = self.next_generation.fetch_add(1, Ordering::AcqRel);
let chunk = self.allocate_new_chunk_optimized(generation, is_hot)?;
if is_hot {
self.hot_data_allocs.fetch_add(1, Ordering::Relaxed);
} else {
self.cold_data_allocs.fetch_add(1, Ordering::Relaxed);
}
self.active_allocations.write().expect("active_allocations lock").insert(
chunk.as_ptr() as usize,
(chunk.generation(), Instant::now()),
);
Ok(SecurePooledPtr {
chunk: Some(chunk),
pool: Arc::downgrade(self),
})
}
fn deallocate_internal(&self, chunk: SecureChunk) -> Result<()> {
self.dealloc_count.fetch_add(1, Ordering::Relaxed);
if let Err(e) = chunk.validate() {
self.corruption_detected.fetch_add(1, Ordering::Relaxed);
return Err(e);
}
if let Some(allocation_info) =
self.active_allocations.write().expect("active_allocations lock").remove(&(chunk.as_ptr() as usize))
{
let (original_generation, _): (u32, Instant) = allocation_info;
if original_generation != chunk.generation() {
self.double_free_detected.fetch_add(1, Ordering::Relaxed);
return Err(ZiporaError::invalid_data(
"Double-free detected: generation mismatch",
));
}
} else {
self.double_free_detected.fetch_add(1, Ordering::Relaxed);
return Err(ZiporaError::invalid_data(
"Double-free detected: pointer not allocated",
));
}
let local_cache = self
.local_caches
.get_or(|| RefCell::new(LocalCache::new(self.config.local_cache_size)));
if local_cache.borrow_mut().try_push(chunk).is_err() {
let chunk = local_cache.borrow_mut().try_pop().expect("local cache non-empty by count check");
self.global_stack.push(chunk);
}
Ok(())
}
fn allocate_new_chunk_optimized(&self, generation: u32, is_hot: bool) -> Result<SecureChunk> {
if let Some(ref cache_allocator) = self.cache_allocator {
if self.config.enable_cache_alignment {
self.cache_aligned_allocs.fetch_add(1, Ordering::Relaxed);
}
if self.config.enable_numa_awareness {
let optimal_node = -1; if optimal_node >= 0 {
self.numa_local_allocs.fetch_add(1, Ordering::Relaxed);
}
}
#[cfg(target_os = "linux")]
if self.config.enable_huge_pages && self.config.chunk_size >= self.config.huge_page_threshold {
self.huge_page_allocs.fetch_add(1, Ordering::Relaxed);
}
if is_hot && self.config.cache_config.as_ref().map(|c| c.enable_prefetch).unwrap_or(false) {
}
}
let mut chunk = SecureChunk::new(self.config.chunk_size, generation, self.pool_id)?;
if self.config.zero_on_alloc {
self.zero_chunk_simd(&mut chunk)?;
}
Ok(chunk)
}
fn zero_chunk_simd(&self, chunk: &mut SecureChunk) -> Result<()> {
if !self.config.enable_simd_ops || chunk.size() < self.config.simd_threshold {
unsafe {
std::ptr::write_bytes(chunk.as_ptr(), 0, chunk.size());
}
return Ok(());
}
let selector = AdaptiveSimdSelector::global();
let start = Instant::now();
let slice = unsafe { std::slice::from_raw_parts_mut(chunk.as_ptr(), chunk.size()) };
fast_fill(slice, 0);
selector.monitor_performance(
Operation::MemZero,
start.elapsed(),
chunk.size() as u64
);
Ok(())
}
#[inline]
pub fn verify_zeroed_simd(&self, data: &[u8]) -> Result<bool> {
let zero_buf = vec![0u8; data.len()];
Ok(fast_compare(data, &zero_buf) == 0)
}
#[inline]
fn prefetch_allocation_metadata(&self, _size: usize) {
if !self.config.enable_cache_alignment {
return;
}
#[cfg(target_arch = "x86_64")]
{
fast_prefetch(&self.global_stack, PrefetchHint::T0);
fast_prefetch(&self.alloc_count, PrefetchHint::T0);
}
}
pub fn allocate_bulk_with_prefetch(self: &Arc<Self>, sizes: &[usize]) -> Result<Vec<SecurePooledPtr>> {
let prefetch_distance = self.config.prefetch_distance;
let mut results = Vec::with_capacity(sizes.len());
for (i, &size) in sizes.iter().enumerate() {
if size != self.config.chunk_size {
return Err(ZiporaError::invalid_data(
format!("Size {} does not match chunk_size {}", size, self.config.chunk_size)
));
}
if i + prefetch_distance < sizes.len() {
self.prefetch_allocation_metadata(sizes[i + prefetch_distance]);
}
results.push(self.allocate()?);
}
Ok(results)
}
pub fn stats(&self) -> SecurePoolStats {
let total_allocs = self.alloc_count.load(Ordering::Relaxed);
let cache_hit_ratio = if total_allocs > 0 {
self.pool_hits.load(Ordering::Relaxed) as f64 / total_allocs as f64
} else {
0.0
};
SecurePoolStats {
allocated: 0, available: 0, chunks: 0, alloc_count: total_allocs,
dealloc_count: self.dealloc_count.load(Ordering::Relaxed),
pool_hits: self.pool_hits.load(Ordering::Relaxed),
pool_misses: self.pool_misses.load(Ordering::Relaxed),
corruption_detected: self.corruption_detected.load(Ordering::Relaxed),
double_free_detected: self.double_free_detected.load(Ordering::Relaxed),
local_cache_hits: self.local_cache_hits.load(Ordering::Relaxed),
cross_thread_steals: self.cross_thread_steals.load(Ordering::Relaxed),
cache_aligned_allocs: self.cache_aligned_allocs.load(Ordering::Relaxed),
numa_local_allocs: self.numa_local_allocs.load(Ordering::Relaxed),
hot_data_allocs: self.hot_data_allocs.load(Ordering::Relaxed),
cold_data_allocs: self.cold_data_allocs.load(Ordering::Relaxed),
huge_page_allocs: self.huge_page_allocs.load(Ordering::Relaxed),
cache_hit_ratio,
}
}
pub fn clear(&self) -> Result<()> {
while let Some(chunk) = self.global_stack.pop() {
chunk.deallocate(
self.config.zero_on_free,
self.config.enable_simd_ops,
self.config.simd_threshold
);
}
self.active_allocations.write().expect("active_allocations lock").clear();
Ok(())
}
pub fn config(&self) -> &SecurePoolConfig {
&self.config
}
pub fn validate(&self) -> Result<()> {
for (&ptr_addr, &(generation, _time)) in self.active_allocations.read().expect("active_allocations lock").iter() {
let data_ptr = ptr_addr as *mut u8;
let header_size = std::mem::size_of::<ChunkHeader>();
let header_ptr = unsafe { data_ptr.sub(header_size) as *const ChunkHeader };
let header = unsafe { &*header_ptr };
let chunk = SecureChunk {
ptr: unsafe { NonNull::new_unchecked(data_ptr) },
size: self.config.chunk_size,
generation,
pool_id: self.pool_id,
canary: header.canary,
};
if let Err(e) = chunk.validate() {
self.corruption_detected.fetch_add(1, Ordering::Relaxed);
return Err(e);
}
}
Ok(())
}
}
impl Drop for SecureMemoryPool {
fn drop(&mut self) {
let _ = self.clear();
}
}
pub struct SecurePooledPtr {
chunk: Option<SecureChunk>,
pool: Weak<SecureMemoryPool>,
}
impl SecurePooledPtr {
#[inline]
pub fn as_ptr(&self) -> *mut u8 {
self.chunk
.as_ref()
.map(|c| c.as_ptr())
.unwrap_or(std::ptr::null_mut())
}
pub fn as_non_null(&self) -> Option<NonNull<u8>> {
self.chunk.as_ref().and_then(|c| NonNull::new(c.as_ptr()))
}
#[inline]
pub fn size(&self) -> usize {
self.chunk.as_ref().map(|c| c.size()).unwrap_or(0)
}
pub fn generation(&self) -> u32 {
self.chunk.as_ref().map(|c| c.generation()).unwrap_or(0)
}
pub fn validate(&self) -> Result<()> {
if let Some(chunk) = &self.chunk {
chunk.validate()
} else {
Err(ZiporaError::invalid_data("Chunk already deallocated"))
}
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
if let Some(chunk) = &self.chunk {
unsafe { std::slice::from_raw_parts(chunk.as_ptr(), chunk.size()) }
} else {
&[]
}
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [u8] {
if let Some(chunk) = &self.chunk {
unsafe { std::slice::from_raw_parts_mut(chunk.as_ptr(), chunk.size()) }
} else {
&mut []
}
}
}
impl Drop for SecurePooledPtr {
fn drop(&mut self) {
if let Some(chunk) = self.chunk.take() {
if let Some(pool) = self.pool.upgrade() {
let _ = pool.deallocate_internal(chunk);
} else {
chunk.deallocate(true, true, 64); }
}
}
}
unsafe impl Send for SecurePooledPtr {}
unsafe impl Sync for SecurePooledPtr {}
fn current_time_nanos() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
pub fn size_to_class(size: usize) -> usize {
SIZE_CLASSES.binary_search(&size).unwrap_or_else(|i| {
if i < SIZE_CLASSES.len() {
i
} else {
SIZE_CLASSES.len() - 1
}
})
}
static GLOBAL_SECURE_POOLS: Lazy<Vec<Arc<SecureMemoryPool>>> = Lazy::new(|| {
vec![
SecureMemoryPool::new(SecurePoolConfig::small_secure()).expect("small secure pool creation"),
SecureMemoryPool::new(SecurePoolConfig::medium_secure()).expect("medium secure pool creation"),
SecureMemoryPool::new(SecurePoolConfig::large_secure()).expect("large secure pool creation"),
]
});
pub fn get_global_pool_for_size(size: usize) -> &'static Arc<SecureMemoryPool> {
if size <= 1024 {
&GLOBAL_SECURE_POOLS[0]
} else if size <= 64 * 1024 {
&GLOBAL_SECURE_POOLS[1]
} else {
&GLOBAL_SECURE_POOLS[2]
}
}
pub fn get_global_secure_pool_stats() -> SecurePoolStats {
let mut total_stats = SecurePoolStats::default();
for pool in GLOBAL_SECURE_POOLS.iter() {
let stats = pool.stats();
total_stats.allocated += stats.allocated;
total_stats.available += stats.available;
total_stats.chunks += stats.chunks;
total_stats.alloc_count += stats.alloc_count;
total_stats.dealloc_count += stats.dealloc_count;
total_stats.pool_hits += stats.pool_hits;
total_stats.pool_misses += stats.pool_misses;
total_stats.corruption_detected += stats.corruption_detected;
total_stats.double_free_detected += stats.double_free_detected;
total_stats.local_cache_hits += stats.local_cache_hits;
total_stats.cross_thread_steals += stats.cross_thread_steals;
}
total_stats
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicUsize;
use std::thread;
use std::time::Duration;
#[test]
fn test_secure_pool_creation() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
let stats = pool.stats();
assert_eq!(stats.alloc_count, 0);
assert_eq!(stats.dealloc_count, 0);
}
#[test]
fn test_secure_allocation_deallocation() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
let ptr1 = pool.allocate().unwrap();
assert!(!ptr1.as_ptr().is_null());
assert_eq!(ptr1.size(), 1024);
let ptr2 = pool.allocate().unwrap();
assert!(!ptr2.as_ptr().is_null());
assert_ne!(ptr1.as_ptr(), ptr2.as_ptr());
drop(ptr1);
drop(ptr2);
let stats = pool.stats();
assert_eq!(stats.alloc_count, 2);
assert_eq!(stats.dealloc_count, 2);
}
#[test]
fn test_chunk_validation() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
let ptr = pool.allocate().unwrap();
assert!(ptr.validate().is_ok());
assert!(ptr.validate().is_ok());
}
#[test]
fn test_double_free_detection() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
let ptr = pool.allocate().unwrap();
let _raw_ptr = ptr.as_ptr();
drop(ptr);
let stats = pool.stats();
assert_eq!(stats.double_free_detected, 0); }
#[test]
fn test_pool_reuse() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
let ptr1 = pool.allocate().unwrap();
let _addr1 = ptr1.as_ptr();
drop(ptr1);
let ptr2 = pool.allocate().unwrap();
let addr2 = ptr2.as_ptr();
assert!(!addr2.is_null());
drop(ptr2);
let stats = pool.stats();
assert!(stats.pool_hits > 0 || stats.pool_misses > 0);
}
#[test]
fn test_concurrent_allocation() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
let allocated_count = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let pool = pool.clone();
let count = allocated_count.clone();
thread::spawn(move || {
for _ in 0..100 {
let _ptr = pool.allocate().unwrap();
count.fetch_add(1, Ordering::Relaxed);
thread::sleep(Duration::from_micros(1));
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(allocated_count.load(Ordering::Relaxed), 1000);
let stats = pool.stats();
assert_eq!(stats.alloc_count, 1000);
assert_eq!(stats.dealloc_count, 1000);
assert_eq!(stats.corruption_detected, 0);
assert_eq!(stats.double_free_detected, 0);
}
#[test]
fn test_thread_local_caching() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
for _ in 0..10 {
let ptr = pool.allocate().unwrap();
drop(ptr);
}
let ptr = pool.allocate().unwrap();
drop(ptr);
let stats = pool.stats();
assert!(stats.local_cache_hits > 0);
}
#[test]
fn test_size_classes() {
assert_eq!(size_to_class(8), 0);
assert_eq!(size_to_class(16), 1);
assert_eq!(size_to_class(100), 7); assert_eq!(size_to_class(1000), 20); }
#[test]
fn test_global_pools() {
let small_pool = get_global_pool_for_size(100);
let medium_pool = get_global_pool_for_size(10000);
let large_pool = get_global_pool_for_size(100000);
assert_eq!(small_pool.config().chunk_size, 1024);
assert_eq!(medium_pool.config().chunk_size, 64 * 1024);
assert_eq!(large_pool.config().chunk_size, 1024 * 1024);
let ptr1 = small_pool.allocate().unwrap();
let ptr2 = medium_pool.allocate().unwrap();
let ptr3 = large_pool.allocate().unwrap();
assert!(!ptr1.as_ptr().is_null());
assert!(!ptr2.as_ptr().is_null());
assert!(!ptr3.as_ptr().is_null());
}
#[test]
fn test_memory_access() {
let config = SecurePoolConfig::small_secure();
let pool = SecureMemoryPool::new(config).unwrap();
let mut ptr = pool.allocate().unwrap();
let slice = ptr.as_mut_slice();
slice[0] = 42;
slice[1023] = 84;
let slice = ptr.as_slice();
assert_eq!(slice[0], 42);
assert_eq!(slice[1023], 84);
assert_eq!(slice.len(), 1024);
}
#[test]
fn test_simd_configuration() {
let config = SecurePoolConfig::small_secure();
assert_eq!(config.enable_simd_ops, true);
assert_eq!(config.simd_threshold, 64);
let config_disabled = SecurePoolConfig::small_secure()
.with_simd_ops(false)
.with_simd_threshold(128);
assert_eq!(config_disabled.enable_simd_ops, false);
assert_eq!(config_disabled.simd_threshold, 128);
let pool = SecureMemoryPool::new(config_disabled).unwrap();
assert_eq!(pool.config().enable_simd_ops, false);
assert_eq!(pool.config().simd_threshold, 128);
let ptr = pool.allocate().unwrap();
assert!(!ptr.as_ptr().is_null());
}
#[test]
fn test_simd_with_large_chunks() {
let config = SecurePoolConfig::large_secure()
.with_simd_ops(true)
.with_simd_threshold(64);
let pool = SecureMemoryPool::new(config).unwrap();
let ptr = pool.allocate().unwrap();
assert_eq!(ptr.size(), 1024 * 1024); assert!(!ptr.as_ptr().is_null());
assert!(pool.config().enable_simd_ops);
assert_eq!(pool.config().simd_threshold, 64);
}
#[test]
fn test_simd_memory_zeroing() {
let config = SecurePoolConfig::small_secure()
.with_zero_on_alloc(true)
.with_simd_ops(true)
.with_simd_threshold(64);
let pool = SecureMemoryPool::new(config).unwrap();
let ptr = pool.allocate().unwrap();
let slice = unsafe {
std::slice::from_raw_parts(ptr.as_ptr(), ptr.size())
};
assert!(slice.iter().all(|&b| b == 0));
assert!(pool.verify_zeroed_simd(ptr.as_slice()).unwrap());
}
#[test]
fn test_simd_verification() {
let config = SecurePoolConfig::medium_secure()
.with_simd_ops(true);
let pool = SecureMemoryPool::new(config).unwrap();
let ptr = pool.allocate().unwrap();
unsafe { std::ptr::write_bytes(ptr.as_ptr(), 0, ptr.size()); }
assert!(pool.verify_zeroed_simd(ptr.as_slice()).unwrap());
unsafe { *ptr.as_ptr() = 1; }
assert!(!pool.verify_zeroed_simd(ptr.as_slice()).unwrap());
}
#[test]
fn test_bulk_allocation_with_prefetch() {
let pool = SecureMemoryPool::new(SecurePoolConfig::small_secure()).unwrap();
let sizes = vec![1024; 20]; let ptrs = pool.allocate_bulk_with_prefetch(&sizes).unwrap();
assert_eq!(ptrs.len(), sizes.len());
for (ptr, &size) in ptrs.iter().zip(&sizes) {
assert!(!ptr.as_ptr().is_null());
assert_eq!(ptr.size(), size);
}
let mut addresses: std::collections::HashSet<usize> = std::collections::HashSet::new();
for ptr in &ptrs {
let addr = ptr.as_ptr() as usize;
assert!(addresses.insert(addr), "Duplicate pointer detected");
}
}
#[test]
fn test_bulk_allocation_size_mismatch() {
let pool = SecureMemoryPool::new(SecurePoolConfig::small_secure()).unwrap();
let sizes = vec![512, 1024, 2048]; let result = pool.allocate_bulk_with_prefetch(&sizes);
assert!(result.is_err());
}
#[test]
fn test_cache_optimized_operations() {
let config = SecurePoolConfig::large_secure()
.with_simd_ops(true)
.with_simd_threshold(64)
.with_cache_alignment(true)
.with_zero_on_alloc(true);
let pool = SecureMemoryPool::new(config).unwrap();
for _ in 0..10 {
let ptr = pool.allocate().unwrap();
assert!(!ptr.as_ptr().is_null());
let slice = unsafe {
std::slice::from_raw_parts(ptr.as_ptr(), ptr.size())
};
assert!(slice.iter().all(|&b| b == 0));
}
let stats = pool.stats();
assert!(stats.alloc_count >= 10);
}
#[test]
fn test_simd_with_small_threshold() {
let config = SecurePoolConfig::small_secure()
.with_simd_ops(true)
.with_simd_threshold(1024 * 1024) .with_zero_on_alloc(true);
let pool = SecureMemoryPool::new(config).unwrap();
let ptr = pool.allocate().unwrap();
let slice = unsafe {
std::slice::from_raw_parts(ptr.as_ptr(), ptr.size())
};
assert!(slice.iter().all(|&b| b == 0));
}
#[test]
fn test_verify_zeroed_empty_slice() {
let pool = SecureMemoryPool::new(SecurePoolConfig::small_secure()).unwrap();
let result = pool.verify_zeroed_simd(&[]);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_zero_on_alloc_disabled() {
let config = SecurePoolConfig::small_secure()
.with_zero_on_alloc(false)
.with_simd_ops(true);
let pool = SecureMemoryPool::new(config).unwrap();
let ptr = pool.allocate().unwrap();
assert!(!ptr.as_ptr().is_null());
assert_eq!(ptr.size(), 1024);
}
#[test]
fn test_adaptive_simd_integration() {
let config = SecurePoolConfig::large_secure()
.with_simd_ops(true)
.with_zero_on_alloc(true);
let pool = SecureMemoryPool::new(config).unwrap();
for _ in 0..100 {
let ptr = pool.allocate().unwrap();
assert!(!ptr.as_ptr().is_null());
assert!(pool.verify_zeroed_simd(ptr.as_slice()).unwrap());
}
let stats = pool.stats();
assert_eq!(stats.alloc_count, 100);
assert_eq!(stats.dealloc_count, 100);
}
#[test]
fn test_concurrent_simd_operations() {
use std::sync::atomic::AtomicUsize;
use std::thread;
let config = SecurePoolConfig::medium_secure()
.with_simd_ops(true)
.with_zero_on_alloc(true);
let pool = SecureMemoryPool::new(config).unwrap();
let verified_count = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..4)
.map(|_| {
let pool = pool.clone();
let count = verified_count.clone();
thread::spawn(move || {
for _ in 0..25 {
let ptr = pool.allocate().unwrap();
if pool.verify_zeroed_simd(ptr.as_slice()).unwrap() {
count.fetch_add(1, Ordering::Relaxed);
}
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(verified_count.load(Ordering::Relaxed), 100);
let stats = pool.stats();
assert_eq!(stats.alloc_count, 100);
assert_eq!(stats.dealloc_count, 100);
}
}