use std::mem::size_of;
use std::sync::atomic::{AtomicU8, AtomicU16, AtomicU32, AtomicU64, Ordering};
use crate::StreamSpec;
pub(crate) const MAGIC: u32 = 0x53_54_52_4D; pub(crate) const VERSION: u16 = 2;
pub(crate) const SLOT_EMPTY: u8 = 0;
pub(crate) const SLOT_LIVE: u8 = 1;
pub(crate) const SLOT_EXHAUSTED: u8 = 2;
pub(crate) const SIDE_FREE: u8 = 0;
pub(crate) const SIDE_CLAIMED: u8 = 1;
pub(crate) const SIDE_RELEASED: u8 = 2;
pub(crate) const SIDE_DEAD: u8 = 3;
pub(crate) const FLAG_FIN: u8 = 1;
pub(crate) const FLAG_RESET: u8 = 2;
pub(crate) const FLAG_READER_GONE: u8 = 4;
pub(crate) const SLOT_BITS: u32 = 16;
pub(crate) const SLOT_MASK: u64 = (1 << SLOT_BITS) - 1;
pub(crate) const GENERATION_BITS: u32 = 40 - SLOT_BITS;
pub(crate) const GENERATION_MASK: u32 = (1 << GENERATION_BITS) - 1;
pub(crate) const GENERATION_LIMIT: u32 = if cfg!(test) { 4 } else { GENERATION_MASK };
#[repr(C, align(64))]
pub(crate) struct Header {
pub(crate) magic: u32,
pub(crate) version: u16,
pub(crate) header_size: u16,
pub(crate) lane_capacity: u32,
pub(crate) slot_size: u32,
pub(crate) buffer_bytes: u32,
pub(crate) fleet_capacity: u16,
_reserved: [u8; 2],
pub(crate) epoch: AtomicU64,
_reserved2: [u8; 32]
}
impl Header {
pub(crate) fn new(
fleet_capacity: u16,
geometry: &Geometry,
epoch: u64
) -> Self {
Self {
magic: MAGIC,
version: VERSION,
header_size: size_of::<Self>() as u16,
lane_capacity: geometry.lane_capacity as u32,
slot_size: size_of::<Slot>() as u32,
buffer_bytes: geometry.buffer_bytes as u32,
fleet_capacity,
_reserved: [0; 2],
epoch: AtomicU64::new(epoch),
_reserved2: [0; 32]
}
}
pub(crate) fn compatible(
&self,
fleet_capacity: u16,
geometry: &Geometry
) -> bool {
self.magic == MAGIC
&& self.version == VERSION
&& usize::from(self.header_size) == size_of::<Self>()
&& self.lane_capacity as usize == geometry.lane_capacity
&& self.slot_size as usize == size_of::<Slot>()
&& self.buffer_bytes as usize == geometry.buffer_bytes
&& self.fleet_capacity == fleet_capacity
}
}
#[repr(C, align(64))]
pub(crate) struct Doorbell {
pub(crate) generation: AtomicU32,
pub(crate) listening: AtomicU32,
pub(crate) incarnation: AtomicU64,
_padding: [u8; 48]
}
#[repr(C, align(64))]
pub(crate) struct Direction {
pub(crate) head: AtomicU64,
pub(crate) flags: AtomicU8,
_reserved: [u8; 3],
_writer_padding: [u8; 52],
pub(crate) tail: AtomicU64,
_reader_padding: [u8; 56],
pub(crate) changes: AtomicU32,
pub(crate) waiters: AtomicU32,
_wake_padding: [u8; 56]
}
impl Direction {
pub(crate) fn clear(&self) {
self.head.store(0, Ordering::Relaxed);
self.tail.store(0, Ordering::Relaxed);
self.flags.store(0, Ordering::Relaxed);
self.changes.store(0, Ordering::Relaxed);
self.waiters.store(0, Ordering::Relaxed);
}
pub(crate) fn flags(&self) -> u8 {
self.flags.load(Ordering::Acquire)
}
}
#[repr(C, align(64))]
pub(crate) struct Slot {
pub(crate) state: AtomicU8,
_reserved: [u8; 3],
pub(crate) generation: AtomicU32,
pub(crate) claimed: [AtomicU8; 2],
_reserved2: [u8; 2],
pub(crate) node: [AtomicU16; 2],
pub(crate) incarnation: [AtomicU64; 2],
_padding: [u8; 32],
pub(crate) directions: [Direction; 2]
}
impl Slot {
pub(crate) fn is(
&self,
generation: u32
) -> bool {
self.state.load(Ordering::Acquire) == SLOT_LIVE
&& self.generation.load(Ordering::Relaxed) == generation
}
pub(crate) fn install(
&self,
creator: u16,
incarnation: u64
) -> Option<u32> {
let generation = self.generation.load(Ordering::Relaxed);
if generation >= GENERATION_LIMIT {
self.state.store(SLOT_EXHAUSTED, Ordering::Release);
return None;
}
let generation = generation + 1;
self.claimed[0].store(SIDE_CLAIMED, Ordering::Relaxed);
self.claimed[1].store(SIDE_FREE, Ordering::Relaxed);
self.node[0].store(creator, Ordering::Relaxed);
self.node[1].store(u16::MAX, Ordering::Relaxed);
self.incarnation[0].store(incarnation, Ordering::Relaxed);
self.incarnation[1].store(0, Ordering::Relaxed);
for direction in &self.directions {
direction.clear();
}
self.generation.store(generation, Ordering::Relaxed);
self.state.store(SLOT_LIVE, Ordering::Release);
Some(generation)
}
}
const _: () = assert!(size_of::<Header>() == 64);
const _: () = assert!(size_of::<Doorbell>() == 64);
const _: () = assert!(size_of::<Direction>() == 192);
const _: () = assert!(size_of::<Slot>() == 448);
#[derive(Clone, Copy, Debug)]
pub(crate) struct Geometry {
pub(crate) lane_capacity: usize,
pub(crate) buffer_bytes: usize,
pub(crate) fleet_capacity: usize,
pub(crate) total_slots: usize,
pub(crate) bitmap_words: usize,
pub(crate) doorbells_offset: usize,
pub(crate) pending_offset: usize,
pub(crate) offers_offset: usize,
pub(crate) slots_offset: usize,
pub(crate) buffers_offset: usize,
pub(crate) segment_size: usize
}
impl Geometry {
pub(crate) fn new(
fleet_capacity: u16,
spec: StreamSpec
) -> Self {
let StreamSpec { lane_capacity, buffer_bytes, .. } = spec;
let fleet_capacity = usize::from(fleet_capacity);
let total_slots = fleet_capacity * lane_capacity;
let bitmap_words = total_slots.div_ceil(64);
let bitmap_bytes = fleet_capacity * bitmap_words * size_of::<AtomicU64>();
let doorbells_offset = size_of::<Header>();
let pending_offset = doorbells_offset + fleet_capacity * size_of::<Doorbell>();
let offers_offset = pending_offset + bitmap_bytes;
let slots_offset = (offers_offset + bitmap_bytes).next_multiple_of(64);
let buffers_offset = slots_offset + total_slots * size_of::<Slot>();
let segment_size = buffers_offset + total_slots * 2 * buffer_bytes;
Self {
lane_capacity,
buffer_bytes,
fleet_capacity,
total_slots,
bitmap_words,
doorbells_offset,
pending_offset,
offers_offset,
slots_offset,
buffers_offset,
segment_size
}
}
pub(crate) fn buffer_offset(
&self,
slot: usize,
direction: usize
) -> usize {
self.buffers_offset + (slot * 2 + direction) * self.buffer_bytes
}
}