use core::cell::UnsafeCell;
use core::ptr;
use super::raw_mem::{alloc_pages, free_pages};
use super::table;
use super::walk::Frames;
pub(crate) const ENTRIES_PER_ARENA: usize = 512;
const ARENA_BYTES: usize = 64 * 1024;
#[repr(C)]
#[derive(Clone, Copy)]
struct Entry {
frames: [u64; 8],
size: u64,
frame_count: u64,
}
struct ArenaState {
base: *mut Entry,
cursor: usize,
}
impl ArenaState {
const fn new() -> Self {
Self {
base: ptr::null_mut(),
cursor: 0,
}
}
fn ensure_init(&mut self) -> bool {
if !self.base.is_null() {
return true;
}
let pages = unsafe { alloc_pages(ARENA_BYTES) };
if pages.is_null() {
return false;
}
self.base = pages as *mut Entry;
self.cursor = 0;
true
}
fn append(&mut self, frames: &Frames, size: u64) {
if self.cursor >= ENTRIES_PER_ARENA {
self.flush();
}
unsafe {
let slot = self.base.add(self.cursor);
(*slot).frames = frames.frames;
(*slot).size = size;
(*slot).frame_count = frames.count as u64;
}
self.cursor += 1;
}
fn flush(&mut self) {
if self.base.is_null() || self.cursor == 0 {
self.cursor = 0;
return;
}
let count = self.cursor;
unsafe {
for i in 0..count {
let e = &*self.base.add(i);
let mut fr = Frames {
frames: e.frames,
count: e.frame_count as u8,
};
if (fr.count as usize) > fr.frames.len() {
fr.count = fr.frames.len() as u8;
}
table::record(&fr, e.size);
}
}
self.cursor = 0;
}
fn release(&mut self) {
if self.base.is_null() {
return;
}
self.flush();
unsafe {
free_pages(self.base as *mut u8, ARENA_BYTES);
}
self.base = ptr::null_mut();
self.cursor = 0;
}
}
struct ArenaSlot {
state: UnsafeCell<ArenaState>,
}
impl ArenaSlot {
const fn new() -> Self {
Self {
state: UnsafeCell::new(ArenaState::new()),
}
}
}
impl Drop for ArenaSlot {
fn drop(&mut self) {
unsafe {
let state = &mut *self.state.get();
state.release();
}
}
}
thread_local! {
static ARENA: ArenaSlot = const { ArenaSlot::new() };
}
pub(crate) fn record_event(frames: &Frames, size: u64) {
let _ = ARENA.try_with(|slot| {
unsafe {
let state = &mut *slot.state.get();
if !state.ensure_init() {
return;
}
state.append(frames, size);
}
});
}
pub(crate) fn flush_current_thread() {
let _ = ARENA.try_with(|slot| {
unsafe {
let state = &mut *slot.state.get();
state.flush();
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_and_flush_round_trip() {
let frames = Frames {
frames: [0x1000, 0x2000, 0x3000, 0, 0, 0, 0, 0],
count: 3,
};
record_event(&frames, 64);
flush_current_thread();
}
}