use std::ops::Deref;
use std::sync::{Arc, Mutex};
use arc_swap::ArcSwap;
use cljrs_gc::{GcPtr, MemoryCharge, MemoryClass, MemoryLimitExceeded, StaticGcPtr, governor};
use crate::intern::{intern_keyword, intern_symbol};
use crate::keyword::Keyword;
use crate::symbol::Symbol;
use crate::value::Value;
#[derive(Debug, Clone)]
pub struct PromoteError {
pub type_name: &'static str,
pub memory: Option<MemoryLimitExceeded>,
}
impl PromoteError {
pub fn not_promotable(type_name: &'static str) -> Self {
Self {
type_name,
memory: None,
}
}
pub fn memory_limit(type_name: &'static str, error: MemoryLimitExceeded) -> Self {
Self {
type_name,
memory: Some(error),
}
}
}
impl PromoteError {
pub fn into_value_error(self) -> crate::error::ValueError {
match self.memory {
Some(limit) => crate::error::ValueError::MemoryLimitExceeded(limit),
None => crate::error::ValueError::Other(self.to_string()),
}
}
}
impl std::fmt::Display for PromoteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(limit) = &self.memory {
return limit.fmt(f);
}
write!(
f,
"value of type '{}' cannot be promoted to a shared-atom: \
only scalars, strings, keywords, symbols, and byte-blobs are supported",
self.type_name
)
}
}
impl std::error::Error for PromoteError {}
#[derive(Debug, Clone)]
pub enum SharedValue {
Nil,
Bool(bool),
Long(i64),
Double(f64),
Char(char),
Uuid(u128),
Str(Arc<str>),
Keyword(StaticGcPtr<Keyword>),
Symbol(StaticGcPtr<Symbol>),
ByteBlob(Arc<ByteBlob>),
}
unsafe impl Send for SharedValue {}
unsafe impl Sync for SharedValue {}
impl SharedValue {
pub fn type_name(&self) -> &'static str {
match self {
SharedValue::Nil => "nil",
SharedValue::Bool(_) => "boolean",
SharedValue::Long(_) => "long",
SharedValue::Double(_) => "double",
SharedValue::Char(_) => "char",
SharedValue::Uuid(_) => "uuid",
SharedValue::Str(_) => "string",
SharedValue::Keyword(_) => "keyword",
SharedValue::Symbol(_) => "symbol",
SharedValue::ByteBlob(_) => "byte-blob",
}
}
}
const fn arc_bytes<T>() -> usize {
2 * std::mem::size_of::<usize>() + std::mem::size_of::<T>()
}
fn reserve(bytes: usize) -> MemoryCharge {
governor().reserve_infallible(MemoryClass::SharedValue, bytes)
}
pub struct ByteBlob {
bytes: Box<[u8]>,
charge: MemoryCharge,
}
impl ByteBlob {
pub fn new(bytes: impl Into<Box<[u8]>>) -> Self {
let bytes = bytes.into();
let charge = reserve(arc_bytes::<ByteBlob>() + bytes.len());
Self { bytes, charge }
}
pub fn try_new(bytes: impl Into<Box<[u8]>>) -> Result<Self, MemoryLimitExceeded> {
let bytes = bytes.into();
let charge = governor().reserve_shared(
MemoryClass::SharedValue,
arc_bytes::<ByteBlob>() + bytes.len(),
)?;
Ok(Self { bytes, charge })
}
pub fn try_with(
len: usize,
fill: impl FnOnce() -> Box<[u8]>,
) -> Result<Self, MemoryLimitExceeded> {
let charge =
governor().reserve_shared(MemoryClass::SharedValue, arc_bytes::<ByteBlob>() + len)?;
Ok(Self {
bytes: fill(),
charge,
})
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn charged_bytes(&self) -> usize {
self.charge.bytes()
}
}
impl Deref for ByteBlob {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.bytes
}
}
impl AsRef<[u8]> for ByteBlob {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
impl From<Vec<u8>> for ByteBlob {
fn from(bytes: Vec<u8>) -> Self {
Self::new(bytes)
}
}
impl From<&[u8]> for ByteBlob {
fn from(bytes: &[u8]) -> Self {
Self::new(bytes)
}
}
impl std::fmt::Debug for ByteBlob {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ByteBlob({} bytes)", self.bytes.len())
}
}
pub struct SharedSlot {
value: SharedValue,
charge: MemoryCharge,
}
impl SharedSlot {
pub fn new(value: SharedValue) -> Self {
let charge = reserve(Self::bytes_for(&value));
Self { value, charge }
}
pub fn try_new(value: SharedValue) -> Result<Self, MemoryLimitExceeded> {
let charge =
governor().reserve_shared(MemoryClass::SharedValue, Self::bytes_for(&value))?;
Ok(Self { value, charge })
}
fn bytes_for(value: &SharedValue) -> usize {
let payload = match value {
SharedValue::Str(s) => s.len(),
_ => 0,
};
arc_bytes::<SharedSlot>() + payload
}
pub fn value(&self) -> &SharedValue {
&self.value
}
pub fn charged_bytes(&self) -> usize {
self.charge.bytes()
}
}
impl Deref for SharedSlot {
type Target = SharedValue;
fn deref(&self) -> &SharedValue {
&self.value
}
}
impl AsRef<SharedValue> for SharedSlot {
fn as_ref(&self) -> &SharedValue {
&self.value
}
}
impl std::fmt::Debug for SharedSlot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
pub fn promote(value: &Value) -> Result<SharedValue, PromoteError> {
match value {
Value::Nil => Ok(SharedValue::Nil),
Value::Bool(b) => Ok(SharedValue::Bool(*b)),
Value::Long(n) => Ok(SharedValue::Long(*n)),
Value::Double(d) => Ok(SharedValue::Double(*d)),
Value::Char(c) => Ok(SharedValue::Char(*c)),
Value::Uuid(u) => Ok(SharedValue::Uuid(u.get().0)),
Value::Str(s) => Ok(SharedValue::Str(Arc::from(s.get().as_str()))),
Value::Keyword(kw) => {
let kw = kw.get();
let ptr = intern_keyword(kw.namespace.as_deref(), &kw.name);
Ok(SharedValue::Keyword(ptr))
}
Value::Symbol(sym) => {
let sym = sym.get();
let ptr = intern_symbol(sym.namespace.as_deref(), &sym.name, sym.version.as_deref());
Ok(SharedValue::Symbol(ptr))
}
Value::ByteArray(arr) => {
let bytes = arr.get().lock().unwrap();
let blob = ByteBlob::try_with(bytes.len(), || bytes.iter().map(|&b| b as u8).collect())
.map_err(|e| PromoteError::memory_limit("byte-array", e))?;
Ok(SharedValue::ByteBlob(Arc::new(blob)))
}
Value::ByteBlob(blob) => Ok(SharedValue::ByteBlob(blob.clone())),
other => Err(PromoteError::not_promotable(other.type_name())),
}
}
pub fn demote(sv: &SharedValue) -> Value {
match sv {
SharedValue::Nil => Value::Nil,
SharedValue::Bool(b) => Value::Bool(*b),
SharedValue::Long(n) => Value::Long(*n),
SharedValue::Double(d) => Value::Double(*d),
SharedValue::Char(c) => Value::Char(*c),
SharedValue::Uuid(u) => Value::uuid(*u),
SharedValue::Str(s) => Value::Str(GcPtr::new(s.as_ref().to_owned())),
SharedValue::Keyword(kw) => Value::Keyword(GcPtr::new(kw.get().clone())),
SharedValue::Symbol(sym) => Value::Symbol(GcPtr::new(sym.get().clone())),
SharedValue::ByteBlob(blob) => Value::ByteBlob(blob.clone()),
}
}
#[derive(Debug)]
pub struct SharedAtom {
pub cell: Arc<ArcSwap<SharedSlot>>,
pub meta: Mutex<Option<SharedValue>>,
}
impl SharedAtom {
pub fn new(val: SharedValue) -> Self {
Self {
cell: Arc::new(ArcSwap::new(Arc::new(SharedSlot::new(val)))),
meta: Mutex::new(None),
}
}
pub fn try_new(val: SharedValue) -> Result<Self, MemoryLimitExceeded> {
Ok(Self {
cell: Arc::new(ArcSwap::new(Arc::new(SharedSlot::try_new(val)?))),
meta: Mutex::new(None),
})
}
pub fn deref_val(&self) -> Arc<SharedSlot> {
self.cell.load_full()
}
pub fn reset(&self, val: SharedValue) -> Arc<SharedSlot> {
let arc = Arc::new(SharedSlot::new(val));
self.cell.store(arc.clone());
arc
}
pub fn try_reset(&self, val: SharedValue) -> Result<Arc<SharedSlot>, MemoryLimitExceeded> {
let arc = Arc::new(SharedSlot::try_new(val)?);
self.cell.store(arc.clone());
Ok(arc)
}
pub fn swap<F>(&self, mut f: F) -> Arc<SharedSlot>
where
F: FnMut(&SharedValue) -> SharedValue,
{
self.cell.rcu(|old| Arc::new(SharedSlot::new(f(old))))
}
pub fn compare_and_set(&self, current: &Arc<SharedSlot>, new: SharedValue) -> bool {
let prev = self
.cell
.compare_and_swap(current, Arc::new(SharedSlot::new(new)));
std::ptr::eq(Arc::as_ptr(current), Arc::as_ptr(&prev))
}
pub fn try_compare_and_set(
&self,
current: &Arc<SharedSlot>,
new: SharedValue,
) -> Result<bool, MemoryLimitExceeded> {
let slot = Arc::new(SharedSlot::try_new(new)?);
let prev = self.cell.compare_and_swap(current, slot);
Ok(std::ptr::eq(Arc::as_ptr(current), Arc::as_ptr(&prev)))
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::keyword::Keyword;
use crate::value::Value;
use cljrs_gc::GcPtr;
use super::*;
fn kw(name: &str) -> Value {
Value::Keyword(GcPtr::new(Keyword::simple(name)))
}
#[test]
fn promote_scalars() {
assert!(matches!(promote(&Value::Nil), Ok(SharedValue::Nil)));
assert!(matches!(
promote(&Value::Bool(true)),
Ok(SharedValue::Bool(true))
));
assert!(matches!(
promote(&Value::Long(42)),
Ok(SharedValue::Long(42))
));
}
#[test]
fn promote_keyword_interns() {
let v = kw("foo");
let sv = promote(&v).unwrap();
let sv2 = promote(&kw("foo")).unwrap();
if let (SharedValue::Keyword(a), SharedValue::Keyword(b)) = (sv, sv2) {
assert!(
cljrs_gc::StaticGcPtr::ptr_eq(&a, &b),
"same keyword should intern to same StaticGcPtr"
);
} else {
panic!("expected SharedValue::Keyword");
}
}
#[test]
fn demote_roundtrip_long() {
let sv = SharedValue::Long(99);
assert!(matches!(demote(&sv), Value::Long(99)));
}
#[test]
fn demote_roundtrip_keyword() {
let sv = promote(&kw("test")).unwrap();
let v = demote(&sv);
if let Value::Keyword(kw_ptr) = v {
assert_eq!(kw_ptr.get().name.as_ref(), "test");
} else {
panic!("expected Value::Keyword");
}
}
#[test]
fn promote_non_promotable_returns_err() {
let atom = Value::Atom(GcPtr::new(crate::types::Atom::new(Value::Nil)));
assert!(promote(&atom).is_err());
}
#[test]
fn promote_byte_blob() {
let arr: Vec<i8> = vec![1, 2, 3];
let v = Value::ByteArray(GcPtr::new(std::sync::Mutex::new(arr)));
let sv = promote(&v).unwrap();
assert!(matches!(sv, SharedValue::ByteBlob(_)));
}
#[test]
fn shared_atom_reset_and_deref() {
let atom = SharedAtom::new(SharedValue::Long(0));
atom.reset(SharedValue::Long(42));
let val = atom.deref_val();
assert!(matches!(val.value(), SharedValue::Long(42)));
}
#[test]
fn shared_atom_swap() {
let atom = SharedAtom::new(SharedValue::Long(1));
atom.swap(|old| {
if let SharedValue::Long(n) = old {
SharedValue::Long(n + 1)
} else {
SharedValue::Long(0)
}
});
let val = atom.deref_val();
assert!(matches!(val.value(), SharedValue::Long(2)));
}
#[test]
fn shared_atom_compare_and_set() {
let atom = SharedAtom::new(SharedValue::Long(1));
let cur = atom.deref_val();
assert!(atom.compare_and_set(&cur, SharedValue::Long(2)));
assert!(matches!(atom.deref_val().value(), SharedValue::Long(2)));
assert!(!atom.compare_and_set(&cur, SharedValue::Long(99)));
assert!(matches!(atom.deref_val().value(), SharedValue::Long(2)));
}
#[test]
fn shared_atom_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<SharedAtom>();
assert_send_sync::<Arc<SharedAtom>>();
}
#[test]
fn byte_blob_shared_across_clone() {
let blob = Arc::new(ByteBlob::new(vec![10u8, 20, 30]));
let v1 = Value::ByteBlob(blob.clone());
let v2 = Value::ByteBlob(blob.clone());
if let (Value::ByteBlob(a), Value::ByteBlob(b)) = (&v1, &v2) {
assert!(Arc::ptr_eq(a, b));
}
}
#[test]
fn byte_blob_holds_one_charge() {
let blob = Arc::new(ByteBlob::new(vec![0u8; 4096]));
assert!(blob.charged_bytes() >= 4096);
let clone = blob.clone();
assert_eq!(clone.charged_bytes(), blob.charged_bytes());
assert_eq!(&clone[..3], &[0, 0, 0]);
}
#[test]
fn shared_slot_charges_string_payload() {
let short = SharedSlot::new(SharedValue::Long(1));
let long = SharedSlot::new(SharedValue::Str(Arc::from("x".repeat(1000))));
assert!(short.charged_bytes() > 0);
assert_eq!(long.charged_bytes(), short.charged_bytes() + 1000);
}
#[test]
fn shared_value_class_reports_live_charges() {
let blob = ByteBlob::new(vec![1u8; 1 << 20]);
assert!(governor().class_bytes(MemoryClass::SharedValue) >= blob.charged_bytes());
}
}