use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::cell::Cell;
use std::sync::atomic::{AtomicU32, Ordering};
use crate::buckets::{EACH_BUCKET_MEM_SIZE, EachBucket, MAX_EACH_BUCKETS};
use crate::slots::{ASSERTION_TABLE_MEM_SIZE, AssertionSlot, MAX_ASSERTION_SLOTS};
const REGION_ALIGN: usize = 8;
thread_local! {
static ASSERTION_TABLE: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
static EACH_BUCKET_PTR: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) };
static HEAP_OWNED: Cell<bool> = const { Cell::new(false) };
}
fn table_layout() -> Layout {
Layout::from_size_align(ASSERTION_TABLE_MEM_SIZE, REGION_ALIGN)
.expect("assertion table layout: const size, power-of-two align")
}
fn bucket_layout() -> Layout {
Layout::from_size_align(EACH_BUCKET_MEM_SIZE, REGION_ALIGN)
.expect("each-bucket layout: const size, power-of-two align")
}
#[must_use]
pub fn assertion_table_ptr() -> *mut u8 {
ASSERTION_TABLE.with(Cell::get)
}
#[must_use]
pub fn each_bucket_ptr() -> *mut u8 {
EACH_BUCKET_PTR.with(Cell::get)
}
pub fn init() {
if !assertion_table_ptr().is_null() {
return;
}
let table = unsafe { alloc_zeroed(table_layout()) };
if table.is_null() {
std::alloc::handle_alloc_error(table_layout());
}
let buckets = unsafe { alloc_zeroed(bucket_layout()) };
if buckets.is_null() {
std::alloc::handle_alloc_error(bucket_layout());
}
ASSERTION_TABLE.with(|c| c.set(table));
EACH_BUCKET_PTR.with(|c| c.set(buckets));
HEAP_OWNED.with(|c| c.set(true));
}
pub fn install_region(table: *mut u8, buckets: *mut u8) {
free_heap_regions();
ASSERTION_TABLE.with(|c| c.set(table));
EACH_BUCKET_PTR.with(|c| c.set(buckets));
HEAP_OWNED.with(|c| c.set(false));
}
pub fn clear() {
free_heap_regions();
ASSERTION_TABLE.with(|c| c.set(std::ptr::null_mut()));
EACH_BUCKET_PTR.with(|c| c.set(std::ptr::null_mut()));
}
fn free_heap_regions() {
if !HEAP_OWNED.with(Cell::get) {
return;
}
let table = assertion_table_ptr();
if !table.is_null() {
unsafe { dealloc(table, table_layout()) };
}
let buckets = each_bucket_ptr();
if !buckets.is_null() {
unsafe { dealloc(buckets, bucket_layout()) };
}
HEAP_OWNED.with(|c| c.set(false));
}
pub fn reset() {
let table = assertion_table_ptr();
if !table.is_null() {
unsafe { std::ptr::write_bytes(table, 0, ASSERTION_TABLE_MEM_SIZE) };
}
let buckets = each_bucket_ptr();
if !buckets.is_null() {
unsafe { std::ptr::write_bytes(buckets, 0, EACH_BUCKET_MEM_SIZE) };
}
}
pub fn prepare_next_seed_reset() {
let table = assertion_table_ptr();
if !table.is_null() {
unsafe {
let count_ptr = table.cast::<()>().cast::<AtomicU32>();
let max_slots = u32::try_from(MAX_ASSERTION_SLOTS).unwrap_or(u32::MAX);
let count = (*count_ptr).load(Ordering::Relaxed).min(max_slots) as usize;
let base = table.add(8).cast::<()>().cast::<AssertionSlot>();
for i in 0..count {
let slot = &mut *base.add(i);
if slot.msg_hash == 0 {
continue;
}
slot.split_triggered = 0;
}
}
}
let buckets = each_bucket_ptr();
if !buckets.is_null() {
unsafe {
let count_ptr = buckets.cast::<()>().cast::<AtomicU32>();
let max_buckets = u32::try_from(MAX_EACH_BUCKETS).unwrap_or(u32::MAX);
let count = (*count_ptr).load(Ordering::Relaxed).min(max_buckets) as usize;
let base = buckets.add(8).cast::<()>().cast::<EachBucket>();
for i in 0..count {
(*base.add(i)).split_triggered = 0;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::slots::{AssertCmp, AssertKind, assertion_bool, assertion_read_all};
#[test]
fn heap_init_enables_accounting_and_reset() {
assert!(assertion_table_ptr().is_null());
init();
assert!(!assertion_table_ptr().is_null());
assertion_bool(AssertKind::Sometimes, true, true, "site_a");
let slots = assertion_read_all();
assert_eq!(slots.len(), 1);
assert_eq!(slots[0].pass_count, 1);
reset();
assert!(assertion_read_all().is_empty());
clear();
assert!(assertion_table_ptr().is_null());
}
#[test]
fn prepare_next_seed_preserves_counts_resets_triggers() {
init();
crate::slots::assertion_numeric(
AssertKind::NumericSometimes,
AssertCmp::Gt,
true,
5,
0,
"n",
);
assertion_bool(AssertKind::Sometimes, true, true, "s");
let before = assertion_read_all();
let pass_total: u64 = before.iter().map(|s| s.pass_count).sum();
assert!(pass_total >= 2);
prepare_next_seed_reset();
let after = assertion_read_all();
let pass_after: u64 = after.iter().map(|s| s.pass_count).sum();
assert_eq!(pass_total, pass_after);
clear();
}
}