use std::alloc::Layout;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::ownership::{Access, PROMOTE_ON_ACCESS};
#[repr(C)]
pub struct WordBuf {
cap: usize,
slots: [AtomicU64; 0],
}
#[repr(C)]
struct Prefixed {
written: AtomicU64,
buf: WordBuf,
}
const PREFIX: usize = 1;
const WRITTEN_ALL: u64 = u32::MAX as u64;
const WRITTEN_NONE: u64 = (u32::MAX as u64) << 32;
static EMPTY: Prefixed = Prefixed { written: AtomicU64::new(WRITTEN_ALL), buf: WordBuf { cap: 0, slots: [] } };
impl WordBuf {
fn layout(cap: usize) -> Layout {
Layout::from_size_align(8 * (cap + 1 + PREFIX), 8).expect("container too large")
}
pub fn empty() -> *mut WordBuf {
unsafe { (&EMPTY as *const Prefixed as *mut u8).add(8 * PREFIX) as *mut WordBuf }
}
pub fn alloc(cap: usize, fill: u64) -> *mut WordBuf {
Self::alloc_from(cap, std::iter::empty(), fill)
}
pub fn alloc_from(cap: usize, prefix: impl IntoIterator<Item = u64>, fill: u64) -> *mut WordBuf {
Self::try_alloc_from(cap, prefix, fill).unwrap_or_else(|layout| std::alloc::handle_alloc_error(layout))
}
pub fn try_alloc(cap: usize, fill: u64) -> Result<*mut WordBuf, Layout> {
Self::try_alloc_from(cap, std::iter::empty(), fill)
}
pub fn try_alloc_from(
cap: usize,
prefix: impl IntoIterator<Item = u64>,
fill: u64,
) -> Result<*mut WordBuf, Layout> {
if cap == 0 {
return Ok(Self::empty());
}
let layout = cap
.checked_add(1 + PREFIX)
.and_then(|n| Layout::array::<u64>(n).ok())
.ok_or_else(|| Layout::from_size_align(isize::MAX as usize & !7, 8).unwrap())?;
unsafe {
let base = std::alloc::alloc(layout) as *mut Prefixed;
if base.is_null() {
return Err(layout);
}
std::ptr::addr_of_mut!((*base).written).write(AtomicU64::new(WRITTEN_ALL));
let p = std::ptr::addr_of_mut!((*base).buf);
Self::init_in_place(p, cap, prefix, fill);
crate::note_external(cap * 8);
Ok(p)
}
}
pub unsafe fn init_in_place(p: *mut WordBuf, cap: usize, prefix: impl IntoIterator<Item = u64>, fill: u64) {
std::ptr::addr_of_mut!((*p).cap).write(cap);
let slots = Self::slots_ptr(p);
let mut n = 0;
for w in prefix.into_iter().take(cap) {
slots.add(n).write(AtomicU64::new(w));
n += 1;
}
for i in n..cap {
slots.add(i).write(AtomicU64::new(fill));
}
}
pub fn from_vec(items: Vec<u64>, fill: u64) -> *mut WordBuf {
Self::try_from_vec(items, fill).unwrap_or_else(|layout| std::alloc::handle_alloc_error(layout))
}
pub fn try_from_vec(items: Vec<u64>, fill: u64) -> Result<*mut WordBuf, Layout> {
let mut items = std::mem::ManuallyDrop::new(items);
let (ptr, len, cap) = (items.as_mut_ptr(), items.len(), items.capacity());
if cap == 0 {
return Ok(Self::empty());
}
unsafe {
let old = Layout::array::<u64>(cap).expect("container too large");
let Some(new) = cap.checked_add(1 + PREFIX).and_then(|n| Layout::array::<u64>(n).ok()) else {
std::alloc::dealloc(ptr as *mut u8, old);
return Err(old);
};
let base = std::alloc::realloc(ptr as *mut u8, old, new.size()) as *mut Prefixed;
if base.is_null() {
std::alloc::dealloc(ptr as *mut u8, old);
return Err(new);
}
let words = base as *mut u64;
std::ptr::copy(words, words.add(1 + PREFIX), len);
std::ptr::addr_of_mut!((*base).written).write(AtomicU64::new(WRITTEN_ALL));
let p = std::ptr::addr_of_mut!((*base).buf);
std::ptr::addr_of_mut!((*p).cap).write(cap);
let slots = Self::slots_ptr(p);
for i in len..cap {
slots.add(i).write(AtomicU64::new(fill));
}
crate::note_external(cap * 8);
Ok(p)
}
}
pub unsafe fn free(p: *mut u8) {
let b = p as *mut WordBuf;
let cap = (*b).cap;
if cap > 0 {
std::alloc::dealloc(p.sub(8 * PREFIX), Self::layout(cap));
}
}
#[inline(always)]
fn slots_ptr(p: *const WordBuf) -> *mut AtomicU64 {
unsafe { (p as *mut u8).add(8) as *mut AtomicU64 }
}
#[inline(always)]
pub unsafe fn slots<'a>(p: *const WordBuf) -> &'a [AtomicU64] {
std::slice::from_raw_parts(Self::slots_ptr(p), (*p).cap)
}
#[inline(always)]
pub unsafe fn cap(p: *const WordBuf) -> usize {
(*p).cap
}
#[inline(always)]
unsafe fn written_word<'a>(p: *const WordBuf) -> &'a AtomicU64 {
&*(p as *const AtomicU64).sub(PREFIX)
}
#[inline(always)]
pub unsafe fn range_word(p: *const WordBuf) -> u64 {
Self::written_word(p).load(Ordering::Relaxed)
}
#[inline(always)]
pub fn range_covers(r: u64, lo: usize, hi: usize) -> bool {
lo >= (r >> 32) as usize && hi <= (r as u32) as usize
}
#[inline(always)]
pub unsafe fn note_written(p: *const WordBuf, lo: usize, hi: usize) {
if !Self::range_covers(Self::range_word(p), lo, hi) {
Self::widen_written(p, lo, hi);
}
}
#[cold]
#[inline(never)]
pub unsafe fn widen_written(p: *const WordBuf, lo: usize, hi: usize) {
let w = Self::written_word(p);
let r = w.load(Ordering::Relaxed);
let (lo, hi) = (lo.min((r >> 32) as usize), hi.max((r as u32) as usize));
let new = if hi >= u32::MAX as usize { WRITTEN_ALL } else { ((lo as u64) << 32) | hi as u64 };
w.store(new, Ordering::Relaxed);
}
#[inline]
pub unsafe fn copy_written(from: *const WordBuf, to: *const WordBuf) {
if (*to).cap > 0 {
Self::written_word(to).store(Self::written_word(from).load(Ordering::Relaxed), Ordering::Relaxed);
}
}
#[inline]
pub unsafe fn take_written(p: *const WordBuf) -> std::ops::Range<usize> {
let r = Self::written(p);
if (*p).cap > 0 {
Self::written_word(p).store(WRITTEN_NONE, Ordering::Relaxed);
}
r
}
#[inline]
pub unsafe fn written(p: *const WordBuf) -> std::ops::Range<usize> {
let cap = (*p).cap;
if cap == 0 {
return 0..0;
}
let r = Self::range_word(p);
let hi = ((r as u32) as usize).min(cap);
((r >> 32) as usize).min(hi)..hi
}
}
pub unsafe fn release(old: *mut WordBuf, access: Access) {
if WordBuf::cap(old) == 0 {
return;
}
if access == Access::Local && !PROMOTE_ON_ACCESS.load(Ordering::Relaxed) {
WordBuf::free(old as *mut u8);
} else {
catallaxy_gc::retire(old as *mut u8, WordBuf::free);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn refused_buffers_are_reported() {
for cap in [usize::MAX / 8, usize::MAX] {
assert!(WordBuf::try_alloc(cap, 0).is_err(), "{cap}");
}
let b = WordBuf::try_alloc_from(4, [1, 2], 9).unwrap();
unsafe {
let words: Vec<u64> = WordBuf::slots(b).iter().map(|s| s.load(Ordering::Relaxed)).collect();
assert_eq!(words, [1, 2, 9, 9]);
WordBuf::free(b as *mut u8);
}
}
}