use crate::Value;
use crate::clone::{deserialize, serialize};
use crate::types::{DelayState, LazySeqState};
use crate::value::{MapValue, SetValue};
use cljrs_gc::{GcPtr, Trace};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Scan {
Clean,
Dirty,
Opaque,
}
impl Scan {
fn join(self, other: Scan) -> Scan {
use Scan::*;
match (self, other) {
(Opaque, _) | (_, Opaque) => Opaque,
(Dirty, _) | (_, Dirty) => Dirty,
_ => Clean,
}
}
}
fn tag<T: Trace + 'static>(p: &GcPtr<T>) -> Scan {
if p.is_region_alloc() {
Scan::Dirty
} else {
Scan::Clean
}
}
fn tag_opaque<T: Trace + 'static>(p: &GcPtr<T>) -> Scan {
if p.is_region_alloc() {
Scan::Opaque
} else {
Scan::Clean
}
}
fn scan_map(m: &MapValue) -> Scan {
let mut acc = match m {
MapValue::Array(p) => tag(p),
MapValue::Hash(p) => tag(p),
MapValue::Sorted(p) => tag(p),
};
m.for_each(|k, v| {
acc = acc.join(scan(k)).join(scan(v));
});
acc
}
fn scan(v: &Value) -> Scan {
use Scan::*;
match v {
Value::Nil
| Value::Bool(_)
| Value::Long(_)
| Value::Double(_)
| Value::Char(_)
| Value::Uuid(_)
| Value::ByteBlob(_)
| Value::SharedAtom(_)
| Value::Resource(_) => Clean,
Value::BigInt(p) => tag(p),
Value::BigDecimal(p) => tag(p),
Value::Ratio(p) => tag(p),
Value::Str(p) => tag(p),
Value::Pattern(p) => tag(p),
Value::Symbol(p) => tag(p),
Value::Keyword(p) => tag(p),
Value::List(p) => p.get().iter().fold(tag(p), |acc, e| acc.join(scan(e))),
Value::Vector(p) => p.get().iter().fold(tag(p), |acc, e| acc.join(scan(e))),
Value::Queue(p) => p.get().iter().fold(tag(p), |acc, e| acc.join(scan(e))),
Value::Map(m) => scan_map(m),
Value::Set(s) => {
let t = match s {
SetValue::Hash(p) => tag(p),
SetValue::Sorted(p) => tag(p),
};
s.iter().fold(t, |acc, e| acc.join(scan(e)))
}
Value::Cons(p) => {
let c = p.get();
tag(p).join(scan(&c.head)).join(scan(&c.tail))
}
Value::IntArray(p) => tag(p),
Value::LongArray(p) => tag(p),
Value::ShortArray(p) => tag(p),
Value::ByteArray(p) => tag(p),
Value::FloatArray(p) => tag(p),
Value::DoubleArray(p) => tag(p),
Value::BooleanArray(p) => tag(p),
Value::CharArray(p) => tag(p),
Value::ObjectArray(p) => {
let guard = p.get().0.lock().unwrap();
guard.iter().fold(tag(p), |acc, e| acc.join(scan(e)))
}
Value::WithMeta(inner, meta) => scan(inner).join(scan(meta)),
Value::Reduced(inner) => scan(inner),
Value::TypeInstance(p) => tag(p).join(scan_map(&p.get().fields)),
Value::Error(p) => {
let mut acc = tag(p);
let e = p.get();
if let Some(data) = &e.data {
acc = acc.join(scan_map(data));
}
if let crate::error::ValueError::Thrown(t) = &e.error {
acc = acc.join(scan(t));
}
let mut cause = e.cause.clone();
while let Some(c) = cause {
acc = acc.join(tag(&c));
if let Some(data) = &c.get().data {
acc = acc.join(scan_map(data));
}
cause = c.get().cause.clone();
}
acc
}
Value::LazySeq(p) => {
let t = tag_opaque(p);
if t == Opaque {
return Opaque;
}
match &*p.get().state.lock().unwrap() {
LazySeqState::Forced(v) => t.join(scan(v)),
LazySeqState::Error(_) => t,
LazySeqState::Pending(_) => Opaque,
}
}
Value::Delay(p) => {
let t = tag_opaque(p);
if t == Opaque {
return Opaque;
}
match &*p.get().state.lock().unwrap() {
DelayState::Forced(v) => t.join(scan(v)),
DelayState::Pending(_) => Opaque,
}
}
Value::Atom(p) => tag_opaque(p),
Value::Volatile(p) => tag_opaque(p),
Value::Var(p) => tag_opaque(p),
Value::Promise(p) => tag_opaque(p),
Value::Future(p) => tag_opaque(p),
Value::Agent(p) => tag_opaque(p),
Value::Namespace(p) => tag_opaque(p),
Value::Protocol(p) => tag_opaque(p),
Value::Fn(_)
| Value::Macro(_)
| Value::BoundFn(_)
| Value::NativeFunction(_)
| Value::ProtocolFn(_)
| Value::MultiFn(_) => Opaque,
Value::TransientMap(_)
| Value::TransientSet(_)
| Value::TransientVector(_)
| Value::Matcher(_)
| Value::NativeObject(_) => Opaque,
}
}
pub fn publish_value(v: Value) -> Value {
if cljrs_gc::region::region_stack_depth() == 0 {
return v;
}
match scan(&v) {
Scan::Clean => v,
Scan::Dirty => match serialize(&v) {
Ok(wire) => deserialize(wire),
Err(_) => {
cljrs_gc::region::poison_active_regions();
v
}
},
Scan::Opaque => {
cljrs_gc::region::poison_active_regions();
v
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PersistentVector;
use cljrs_gc::region::{Region, RegionGuard};
fn region_vec(region: &mut Region, items: Vec<Value>) -> Value {
Value::Vector(region.alloc(PersistentVector::from_iter(items)))
}
#[test]
fn clean_value_passes_through_untouched() {
let mut region = Region::new();
let _guard = unsafe { RegionGuard::new(&mut region) };
let v = Value::Vector(GcPtr::new(PersistentVector::from_iter([Value::Long(1)])));
let Value::Vector(before) = &v else { panic!() };
let before = before.clone();
let out = publish_value(v);
let Value::Vector(after) = &out else { panic!() };
assert!(
GcPtr::ptr_eq(&before, after),
"heap value must not be copied"
);
}
#[test]
fn region_value_is_promoted_to_heap() {
let mut region = Region::new();
let inner = region_vec(&mut region, vec![Value::Long(1), Value::Long(2)]);
let map = Value::Map(crate::MapValue::from_pairs(vec![(Value::Long(0), inner)]));
let _guard = unsafe { RegionGuard::new(&mut region) };
let out = publish_value(map);
assert_eq!(scan(&out), Scan::Clean, "promoted copy must be heap-only");
let Value::Map(m) = &out else { panic!() };
let got = {
let mut got = None;
m.for_each(|_, v| got = Some(v.clone()));
got.unwrap()
};
assert_eq!(
got,
Value::Vector(GcPtr::new(PersistentVector::from_iter([
Value::Long(1),
Value::Long(2)
])))
);
}
#[test]
fn no_active_region_short_circuits() {
let mut region = Region::new();
let v = region_vec(&mut region, vec![Value::Long(5)]);
let out = publish_value(v.clone());
assert_eq!(out, v);
}
#[test]
fn opaque_value_poisons_active_regions() {
use std::sync::{Arc, Mutex};
#[derive(Debug)]
struct Tracked(Arc<Mutex<bool>>);
impl Drop for Tracked {
fn drop(&mut self) {
*self.0.lock().unwrap() = true;
}
}
impl Trace for Tracked {
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
}
let dropped = Arc::new(Mutex::new(false));
let mut region = Box::new(Region::new());
region.alloc(Tracked(dropped.clone()));
let lazy = {
struct T;
impl std::fmt::Debug for T {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "T")
}
}
impl Trace for T {
fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
}
impl crate::types::Thunk for T {
fn force(&self) -> Result<Value, String> {
Ok(Value::Nil)
}
}
Value::LazySeq(GcPtr::new(crate::types::LazySeq::new(Box::new(T))))
};
unsafe { cljrs_gc::region::push_region_raw(region.as_mut() as *mut Region) };
let _ = publish_value(lazy);
cljrs_gc::region::close_region(region);
assert!(
!*dropped.lock().unwrap(),
"publishing an opaque value must retire the active region"
);
}
}