use std::{
slice::from_raw_parts,
sync::atomic::{AtomicU64, Ordering},
};
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use wram::AlignedBuf;
use crate::{
config::{HybridLogConfig, SECTOR_ALIGNMENT},
error::Result,
};
const INVALID_PAGE_ID: u64 = u64::MAX;
const CLAIMED_PAGE_ID: u64 = u64::MAX - 1;
pub struct CircularPageBuffer {
pub pages: Box<[RwLock<AlignedBuf>]>,
raw_pages: Box<[*mut u8]>,
page_ids: Box<[AtomicU64]>,
pub page_size: usize,
pub page_bits: u32,
pub page_mask: u64,
pub num_pages: usize,
pub num_pages_mask: usize,
}
unsafe impl Send for CircularPageBuffer {}
unsafe impl Sync for CircularPageBuffer {}
impl CircularPageBuffer {
pub fn new(config: &HybridLogConfig) -> Result<Self> {
let mut pages_vec = Vec::with_capacity(config.num_pages);
let mut raw_pages_vec = Vec::with_capacity(config.num_pages);
let mut page_ids_vec = Vec::with_capacity(config.num_pages);
for _ in 0..config.num_pages {
let mut buf = AlignedBuf::zeroed(config.page_size, SECTOR_ALIGNMENT)?;
raw_pages_vec.push(buf.as_mut_buf_ptr());
pages_vec.push(RwLock::new(buf));
page_ids_vec.push(AtomicU64::new(INVALID_PAGE_ID));
}
Ok(Self {
pages: pages_vec.into_boxed_slice(),
raw_pages: raw_pages_vec.into_boxed_slice(),
page_ids: page_ids_vec.into_boxed_slice(),
page_size: config.page_size,
page_bits: config.page_bits(),
page_mask: config.page_mask(),
num_pages: config.num_pages,
num_pages_mask: config.num_pages_mask(),
})
}
#[inline]
pub const fn page_idx(&self, page_id: u64) -> usize {
(page_id as usize) & self.num_pages_mask
}
#[inline]
pub const fn slot_for_address(&self, addr: u64) -> usize {
((addr >> self.page_bits) as usize) & self.num_pages_mask
}
#[inline]
pub const fn offset_in_page(&self, addr: u64) -> usize {
(addr & self.page_mask) as usize
}
#[inline]
pub fn read_page(&self, page_id: u64) -> RwLockReadGuard<'_, AlignedBuf> {
let slot = self.page_idx(page_id);
unsafe { self.pages.get_unchecked(slot).read() }
}
#[inline]
pub fn write_page(&self, page_id: u64) -> RwLockWriteGuard<'_, AlignedBuf> {
let slot = self.page_idx(page_id);
unsafe { self.pages.get_unchecked(slot).write() }
}
#[inline]
pub fn clear_page(&self, page_id: u64) {
self.clear_page_from_offset(page_id, 0);
}
pub fn load_page(&self, page_id: u64, data: &[u8]) {
let slot = self.page_idx(page_id);
unsafe {
self
.page_ids
.get_unchecked(slot)
.store(INVALID_PAGE_ID, Ordering::Release);
let mut guard = self.pages.get_unchecked(slot).write();
let copy_len = data.len().min(self.page_size);
guard[..copy_len].copy_from_slice(&data[..copy_len]);
if copy_len < self.page_size {
guard[copy_len..].fill(0);
}
self
.page_ids
.get_unchecked(slot)
.store(page_id, Ordering::Release);
}
}
pub fn clear_page_from_offset(&self, page_id: u64, offset: usize) {
let slot = self.page_idx(page_id);
unsafe {
self
.page_ids
.get_unchecked(slot)
.store(INVALID_PAGE_ID, Ordering::Release);
let mut guard = self.pages.get_unchecked(slot).write();
if offset < self.page_size {
guard[offset..].fill(0);
}
self
.page_ids
.get_unchecked(slot)
.store(page_id, Ordering::Release);
}
}
pub fn preclear_page(&self, page_id: u64) {
let slot = self.page_idx(page_id);
unsafe {
let mut guard = self.pages.get_unchecked(slot).write();
let cur = self.page_ids.get_unchecked(slot).load(Ordering::Relaxed);
if cur == INVALID_PAGE_ID || cur == page_id.wrapping_sub(self.num_pages as u64) {
guard.fill(0);
self
.page_ids
.get_unchecked(slot)
.store(CLAIMED_PAGE_ID, Ordering::Release);
}
}
}
pub fn seal_page(&self, page_id: u64) {
let slot = self.page_idx(page_id);
unsafe {
let mut guard = self.pages.get_unchecked(slot).write();
let cur = self.page_ids.get_unchecked(slot).load(Ordering::Relaxed);
if cur != page_id {
if cur != CLAIMED_PAGE_ID {
guard.fill(0);
}
self
.page_ids
.get_unchecked(slot)
.store(page_id, Ordering::Release);
}
}
}
#[inline]
pub fn is_page_loaded(&self, page_id: u64) -> bool {
let slot = self.page_idx(page_id);
unsafe { self.page_ids.get_unchecked(slot).load(Ordering::Acquire) == page_id }
}
#[inline]
pub fn set_page_id(&self, page_id: u64) {
let slot = self.page_idx(page_id);
unsafe {
self
.page_ids
.get_unchecked(slot)
.store(page_id, Ordering::Release)
};
}
#[inline(always)]
pub unsafe fn page_slice_unchecked(&self, slot: usize) -> &[u8] {
debug_assert!(slot < self.num_pages);
unsafe {
let ptr = *self.raw_pages.get_unchecked(slot);
from_raw_parts(ptr, self.page_size)
}
}
#[inline]
pub unsafe fn try_read_page_unlocked(&self, page_id: u64) -> Option<&[u8]> {
let slot = self.page_idx(page_id);
let page_id_atomic = unsafe { self.page_ids.get_unchecked(slot) };
if page_id_atomic.load(Ordering::Acquire) != page_id {
return None;
}
let slice = unsafe { self.page_slice_unchecked(slot) };
if page_id_atomic.load(Ordering::Acquire) != page_id {
return None;
}
Some(slice)
}
#[inline(always)]
pub unsafe fn get_physical_address(&self, addr: u64) -> *const u8 {
let slot = self.slot_for_address(addr);
let offset = self.offset_in_page(addr);
debug_assert!(slot < self.num_pages);
unsafe {
let ptr = *self.raw_pages.get_unchecked(slot);
ptr.add(offset)
}
}
#[inline(always)]
pub unsafe fn raw_page_ptr_mut(&self, slot: usize) -> *mut u8 {
debug_assert!(slot < self.num_pages);
unsafe { *self.raw_pages.get_unchecked(slot) }
}
}