use std::{cell::RefCell, collections::BTreeMap, fmt, ops::Deref, ptr::NonNull, rc::Rc};
#[cfg(feature = "types")]
use crate::types::Type;
use crate::{
mem::TARGET,
value::{ByteVector, Pair, SmartString, Value, Vector},
};
use super::{Arena, Pointer, Root, Slot, Stats, Trace};
#[allow(clippy::module_name_repetitions)]
#[derive(Clone, Debug, PartialEq)]
pub struct MutatorRef<'a>(Rc<RefCell<Mutator<'a>>>);
impl<'a> Deref for MutatorRef<'a> {
type Target = RefCell<Mutator<'a>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub const BUCKET_DEFAULT_SIZE: usize = 1024;
pub const BUCKET_LARGE_SIZE: usize = 2048;
pub const BUCKET_SMALL_SIZE: usize = 512;
pub const MIN_ALLOCATION_PRESSURE: usize = 1024;
pub const MAX_ALLOCATION_PRESSURE: usize = 1024 * 1024;
pub struct Mutator<'a> {
bytevecs: Arena<'a, ByteVector, BUCKET_SMALL_SIZE>,
pairs: Arena<'a, Pair<'a>, BUCKET_LARGE_SIZE>,
strings: Arena<'a, SmartString, BUCKET_DEFAULT_SIZE>,
symbol_map: BTreeMap<&'a str, NonNull<Slot<Box<str>>>>,
symbols: Arena<'a, Box<str>, BUCKET_DEFAULT_SIZE>,
vectors: Arena<'a, Vector<'a>, BUCKET_SMALL_SIZE>,
#[cfg(feature = "types")]
types: Arena<'a, Type, BUCKET_SMALL_SIZE>,
allocations: usize,
allocation_pressure: usize,
garbage_collections: usize,
}
impl<'a> Mutator<'a> {
#[must_use]
pub fn new() -> Self {
log::debug!(target: TARGET, "Mutator: new mutator");
let mut mutator = Self {
bytevecs: Arena::new(),
pairs: Arena::new(),
strings: Arena::new(),
symbols: Arena::new(),
vectors: Arena::new(),
#[cfg(feature = "types")]
types: Arena::new(),
symbol_map: BTreeMap::new(),
allocations: 0,
allocation_pressure: 0,
garbage_collections: 0,
};
mutator.reset_pressure();
mutator
}
#[must_use]
pub fn new_ref() -> MutatorRef<'a> {
MutatorRef(Rc::new(RefCell::new(Self::new())))
}
#[allow(clippy::let_and_return)]
#[must_use]
pub fn len(&self) -> usize {
let len = self.bytevecs.len()
+ self.pairs.len()
+ self.strings.len()
+ self.symbols.len()
+ self.vectors.len();
#[cfg(feature = "types")]
let len = len + self.types.len();
len
}
#[allow(clippy::let_and_return)]
#[must_use]
pub fn capacity(&self) -> usize {
let capacity = self.bytevecs.capacity()
+ self.pairs.capacity()
+ self.strings.capacity()
+ self.symbols.capacity()
+ self.vectors.capacity();
#[cfg(feature = "types")]
let capacity = capacity + self.types.capacity();
capacity
}
#[allow(clippy::let_and_return)]
#[must_use]
pub fn available(&self) -> usize {
let available = self.bytevecs.available()
+ self.pairs.available()
+ self.strings.available()
+ self.symbols.available()
+ self.vectors.available();
#[cfg(feature = "types")]
let available = available + self.types.available();
available
}
#[inline]
#[must_use]
pub fn stats(&self) -> Stats {
Stats {
allocated: self.len(),
allocated_bytevecs: self.bytevecs.len(),
allocated_strings: self.strings.len(),
allocated_symbols: self.symbols.len(),
allocated_pairs: self.pairs.len(),
allocated_vectors: self.vectors.len(),
#[cfg(feature = "types")]
allocated_types: self.types.len(),
allocations: self.allocations,
allocation_pressure: self.allocation_pressure,
garbage_collections: self.garbage_collections,
}
}
#[allow(clippy::let_and_return)]
#[must_use]
pub fn is_empty(&self) -> bool {
let empty = self.bytevecs.is_empty()
&& self.pairs.is_empty()
&& self.strings.is_empty()
&& self.symbols.is_empty()
&& self.vectors.is_empty();
#[cfg(feature = "types")]
let empty = empty && self.types.is_empty();
empty
}
#[must_use]
pub fn new_bytevec(&mut self, value: impl AsRef<[u8]>) -> Pointer<'a, ByteVector> {
self.on_allocate();
let value = self.bytevecs.alloc(value.as_ref().into());
log::debug!(target: TARGET, "Mutator: new bytevec {:p}", value.as_ptr());
value
}
#[must_use]
pub fn new_pair(&mut self, head: Value<'a>, tail: Value<'a>) -> Pointer<'a, Pair<'a>> {
self.on_allocate();
let value = self.pairs.alloc(Pair::new(head, tail));
log::debug!(target: TARGET, "Mutator: new pair {:p}", value.as_ptr());
value
}
#[must_use]
pub fn new_string(&mut self, value: impl AsRef<str>) -> Pointer<'a, SmartString> {
self.on_allocate();
let value = self.strings.alloc(value.as_ref().into());
log::debug!(target: TARGET, "Mutator: new string {:p}", value.as_ptr());
value
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn new_symbol(&mut self, value: impl AsRef<str>) -> Pointer<'a, Box<str>> {
let value = value.as_ref();
if let Some(value) = self.symbol_map.get(value).copied() {
log::debug!(target: TARGET, "Mutator: using symbol {:p}", value.as_ptr());
Pointer::new(value)
} else {
self.on_allocate();
let value: Box<str> = value.into();
let key = std::ptr::addr_of!(*value);
let key = unsafe { key.as_ref() }.unwrap();
let value = self.symbols.alloc(value);
self.symbol_map.insert(key, unsafe { value.as_inner() });
log::debug!(target: TARGET, "Mutator: new symbol {:p}", value.as_ptr());
value
}
}
pub fn new_vector(&mut self, values: impl Into<Vec<Value<'a>>>) -> Pointer<'a, Vector<'a>> {
self.on_allocate();
let values = values.into();
let len = values.len();
let value = self.vectors.alloc(Vector::new(values));
log::debug!(target: TARGET, "Mutator: new vector[{}] {:p}", len, value.as_ptr());
value
}
#[cfg(feature = "types")]
#[cfg_attr(docsrs, doc(cfg(feature = "types")))]
pub fn new_type(&mut self, value: Type) -> Pointer<'a, Type> {
self.on_allocate();
let value = self.types.alloc(value);
log::debug!(target: TARGET, "Mutator: new type {:p}", value.as_ptr());
value
}
#[inline]
pub fn into_bytevec(
&mut self,
raw: NonNull<Slot<ByteVector>>,
) -> Option<Pointer<'a, ByteVector>> {
self.bytevecs
.is_valid_pointer(raw)
.then(|| Pointer::new(raw))
}
#[inline]
pub fn into_pair(&mut self, raw: NonNull<Slot<Pair<'a>>>) -> Option<Pointer<'a, Pair<'a>>> {
self.pairs.is_valid_pointer(raw).then(|| Pointer::new(raw))
}
#[inline]
pub fn into_string(
&mut self,
raw: NonNull<Slot<SmartString>>,
) -> Option<Pointer<'a, SmartString>> {
self.strings
.is_valid_pointer(raw)
.then(|| Pointer::new(raw))
}
#[inline]
pub fn into_symbol(&mut self, raw: NonNull<Slot<Box<str>>>) -> Option<Pointer<'a, Box<str>>> {
self.symbols
.is_valid_pointer(raw)
.then(|| Pointer::new(raw))
}
#[inline]
pub fn into_vector(
&mut self,
raw: NonNull<Slot<Vector<'a>>>,
) -> Option<Pointer<'a, Vector<'a>>> {
self.vectors
.is_valid_pointer(raw)
.then(|| Pointer::new(raw))
}
#[cfg(feature = "types")]
#[cfg_attr(docsrs, doc(cfg(feature = "types")))]
#[inline]
pub fn into_type(&mut self, raw: NonNull<Slot<Type>>) -> Option<Pointer<'a, Type>> {
self.types.is_valid_pointer(raw).then(|| Pointer::new(raw))
}
pub fn collect_garbage(&mut self) -> usize {
self.on_collect();
let mut collected = 0;
let mut pending = vec![];
log::debug!(target: TARGET, "Mutator: marking arenas");
self.bytevecs.mark(&mut pending);
self.pairs.mark(&mut pending);
self.strings.mark(&mut pending);
self.symbols.mark(&mut pending);
self.vectors.mark(&mut pending);
#[cfg(feature = "types")]
self.types.mark(&mut pending);
log::debug!(target: TARGET, "Mutator: marking pending values");
Self::mark_pending(pending);
log::debug!(target: TARGET, "Mutator: sweeping arenas");
#[cfg(feature = "types")]
{
collected += self.types.sweep();
}
collected += self.vectors.sweep();
collected += self.symbols.sweep_and(|key| {
let key = key.value().expect("not occupied").as_ref();
self.symbol_map.remove(key);
});
collected += self.strings.sweep();
collected += self.pairs.sweep();
collected += self.bytevecs.sweep();
log::debug!(target: TARGET, "Mutator: collected {} values", collected);
collected
}
fn mark_pending(mut pending: Vec<Root>) {
while let Some(root) = pending.pop() {
match root {
Root::Bytevec(mut value) => unsafe { value.as_mut() }.mark(),
Root::String(mut value) => unsafe { value.as_mut() }.mark(),
Root::Symbol(mut value) => unsafe { value.as_mut() }.mark(),
#[cfg(feature = "types")]
Root::Type(mut value) => unsafe { value.as_mut() }.mark(),
Root::Pair(mut value) => {
let entry = unsafe { value.as_mut() };
if !entry.is_marked() {
entry.mark();
entry.trace(&mut pending);
}
}
Root::Vector(mut value) => {
let entry = unsafe { value.as_mut() };
if !entry.is_marked() {
entry.mark();
entry.trace(&mut pending);
}
}
}
}
}
#[inline]
fn on_allocate(&mut self) {
self.allocations += 1;
log::debug!(target: TARGET,
"Mutator: started allocating value, {}. allocation",
self.allocations);
if self.allocations > self.allocation_pressure {
self.collect_garbage();
self.allocations = 1;
}
}
#[inline]
fn on_collect(&mut self) {
self.garbage_collections += 1;
log::info!(target: TARGET,
"Mutator: started garbage collection for {}. time",
self.garbage_collections);
self.reset_pressure();
}
#[inline]
fn reset_pressure(&mut self) {
let pressure = (self.bytevecs.capacity() / 2)
+ (self.pairs.capacity() / 2)
+ (self.strings.capacity() / 2)
+ (self.symbols.capacity() / 2)
+ (self.vectors.capacity() / 2);
#[cfg(feature = "types")]
let pressure = pressure + (self.types.capacity() / 2);
self.allocations = 0;
self.allocation_pressure = pressure.clamp(MIN_ALLOCATION_PRESSURE, MAX_ALLOCATION_PRESSURE);
log::info!(target: TARGET,
"Mutator: allocation pressure is set to {}",
self.allocation_pressure);
}
}
impl<'a> Default for Mutator<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> PartialEq for Mutator<'a> {
fn eq(&self, other: &Self) -> bool {
let lhs = self as *const Self;
let rhs = other as *const Self;
std::ptr::eq(lhs, rhs)
}
}
impl<'a> fmt::Debug for Mutator<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = f.debug_struct("Mutator");
let out = out
.field("bytevecs", &self.bytevecs)
.field("pairs", &self.pairs)
.field("strings", &self.strings)
.field("symbols", &self.symbols)
.field("vectors", &self.vectors);
#[cfg(feature = "types")]
let out = out.field("types", &self.types);
out.field("symbol_map", &self.symbol_map)
.field("allocations", &self.allocations)
.field("allocation_pressure", &self.allocation_pressure)
.field("garbage_collections", &self.garbage_collections)
.finish()
}
}
impl<'a> Drop for Mutator<'a> {
fn drop(&mut self) {
log::debug!(target: TARGET, "Mutator: dropping mutator");
self.symbol_map.clear();
self.bytevecs.drop_all();
self.pairs.drop_all();
self.strings.drop_all();
self.symbols.drop_all();
self.vectors.drop_all();
#[cfg(feature = "types")]
self.types.drop_all();
}
}