diff --git a/crates/jrsonnet-evaluator/Cargo.toml b/crates/jrsonnet-evaluator/Cargo.toml index 2217e8a5..63b1cd14 100644 --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -30,13 +30,12 @@ jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.7" } jrsonnet-types = { path = "../jrsonnet-types", version = "0.3.7" } pathdiff = "0.2.0" -closure = "0.3.0" - md5 = "0.7.0" base64 = "0.13.0" rustc-hash = "1.1.0" thiserror = "1.0" +gc = { version = "0.4.1", features = ["derive"] } [dependencies.anyhow] version = "1.0" diff --git a/crates/jrsonnet-evaluator/src/builtin/format.rs b/crates/jrsonnet-evaluator/src/builtin/format.rs index f931805f..c326e616 100644 --- a/crates/jrsonnet-evaluator/src/builtin/format.rs +++ b/crates/jrsonnet-evaluator/src/builtin/format.rs @@ -2,11 +2,12 @@ #![allow(clippy::too_many_arguments)] use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val}; +use gc::{Finalize, Trace}; use jrsonnet_interner::IStr; use jrsonnet_types::ValType; use thiserror::Error; -#[derive(Debug, Clone, Error)] +#[derive(Debug, Clone, Error, Trace, Finalize)] pub enum FormatError { #[error("truncated format code")] TruncatedFormatCode, diff --git a/crates/jrsonnet-evaluator/src/builtin/manifest.rs b/crates/jrsonnet-evaluator/src/builtin/manifest.rs index 1ca262d9..0353c58a 100644 --- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs +++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs @@ -126,6 +126,7 @@ fn manifest_json_ex_buf( buf.push('}'); } Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())), + Val::DebugGcTraceValue(v) => manifest_json_ex_buf(&v.value, buf, cur_padding, options)?, }; Ok(()) } diff --git a/crates/jrsonnet-evaluator/src/builtin/mod.rs b/crates/jrsonnet-evaluator/src/builtin/mod.rs index 8112bceb..bf809b6a 100644 --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -1,10 +1,11 @@ use crate::{ equals, error::{Error::*, Result}, - parse_args, primitive_equals, push, throw, with_state, ArrValue, Context, EvaluationState, - FuncVal, LazyVal, Val, + parse_args, primitive_equals, push, throw, with_state, ArrValue, Context, DebugGcTraceValue, + EvaluationState, FuncVal, LazyVal, Val, }; use format::{format_arr, format_obj}; +use gc::Gc; use jrsonnet_interner::IStr; use jrsonnet_parser::{ArgsDesc, BinaryOpType, ExprLocation}; use jrsonnet_types::ty; @@ -68,6 +69,8 @@ thread_local! { ("md5".into(), builtin_md5), ("base64".into(), builtin_base64), ("trace".into(), builtin_trace), + ("gc".into(), builtin_gc), + ("gcTrace".into(), builtin_gc_trace), ("join".into(), builtin_join), ("escapeStringJson".into(), builtin_escape_string_json), ("manifestJsonEx".into(), builtin_manifest_json_ex), @@ -301,7 +304,7 @@ fn builtin_native(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc parse_args!(context, "native", args, 1, [ 0, x: ty!(string) => Val::Str; ], { - Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?) + Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Gc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?) }) } @@ -446,6 +449,28 @@ fn builtin_trace(context: Context, loc: Option<&ExprLocation>, args: &ArgsDesc) }) } +fn builtin_gc(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "gc", args, 1, [ + 0, rest: ty!(any); + ], { + println!("GC start"); + gc::force_collect(); + println!("GC done"); + + Ok(rest) + }) +} + +fn builtin_gc_trace(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { + parse_args!(context, "gcTrace", args, 2, [ + 0, name: ty!(string) => Val::Str; + 1, rest: ty!(any); + ], { + + Ok(DebugGcTraceValue::new(name, rest)) + }) +} + fn builtin_base64(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result { parse_args!(context, "base64", args, 1, [ 0, input: ty!((string | (Array))); diff --git a/crates/jrsonnet-evaluator/src/builtin/sort.rs b/crates/jrsonnet-evaluator/src/builtin/sort.rs index dbe68902..af68772a 100644 --- a/crates/jrsonnet-evaluator/src/builtin/sort.rs +++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs @@ -2,9 +2,9 @@ use crate::{ error::{Error, LocError, Result}, throw, Context, FuncVal, Val, }; -use std::rc::Rc; +use gc::{Finalize, Gc, Trace}; -#[derive(Debug, Clone, thiserror::Error)] +#[derive(Debug, Clone, thiserror::Error, Trace, Finalize)] pub enum SortError { #[error("sort key should be string or number")] SortKeyShouldBeStringOrNumber, @@ -59,13 +59,13 @@ fn get_sort_type( Ok(sort_type) } -pub fn sort(ctx: Context, mut values: Rc>, key_getter: &FuncVal) -> Result>> { +pub fn sort(ctx: Context, values: Gc>, key_getter: &FuncVal) -> Result>> { if values.len() <= 1 { return Ok(values); } if key_getter.is_ident() { - let mvalues = Rc::make_mut(&mut values); - let sort_type = get_sort_type(mvalues, |k| k)?; + let mut mvalues = (*values).clone(); + let sort_type = get_sort_type(&mut mvalues, |k| k)?; match sort_type { SortKeyType::Number => mvalues.sort_by_key(|v| match v { Val::Num(n) => NonNaNf64(*n), @@ -77,7 +77,7 @@ pub fn sort(ctx: Context, mut values: Rc>, key_getter: &FuncVal) -> Res }), SortKeyType::Unknown => unreachable!(), }; - Ok(values) + Ok(Gc::new(mvalues)) } else { let mut vk = Vec::with_capacity(values.len()); for value in values.iter() { @@ -98,6 +98,6 @@ pub fn sort(ctx: Context, mut values: Rc>, key_getter: &FuncVal) -> Res }), SortKeyType::Unknown => unreachable!(), }; - Ok(Rc::new(vk.into_iter().map(|v| v.0).collect())) + Ok(Gc::new(vk.into_iter().map(|v| v.0).collect())) } } diff --git a/crates/jrsonnet-evaluator/src/ctx.rs b/crates/jrsonnet-evaluator/src/ctx.rs index 16e27c2b..46313cdf 100644 --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -1,13 +1,14 @@ use crate::{ - error::Error::*, map::LayeredHashMap, resolved_lazy_val, FutureWrapper, LazyBinding, LazyVal, - ObjValue, Result, Val, + error::Error::*, map::LayeredHashMap, FutureWrapper, LazyBinding, LazyVal, ObjValue, Result, + Val, }; +use gc::{Finalize, Gc, Trace}; use jrsonnet_interner::IStr; use rustc_hash::FxHashMap; +use std::fmt::Debug; use std::hash::BuildHasherDefault; -use std::{fmt::Debug, rc::Rc}; -#[derive(Clone)] +#[derive(Clone, Trace, Finalize)] pub struct ContextCreator(pub Context, pub FutureWrapper>); impl ContextCreator { pub fn create(&self, this: Option, super_obj: Option) -> Result { @@ -20,6 +21,7 @@ impl ContextCreator { } } +#[derive(Trace, Finalize)] struct ContextInternals { dollar: Option, this: Option, @@ -28,15 +30,12 @@ struct ContextInternals { } impl Debug for ContextInternals { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Context") - .field("this", &self.this.as_ref().map(|e| Rc::as_ptr(&e.0))) - .field("bindings", &self.bindings) - .finish() + f.debug_struct("Context").finish() } } -#[derive(Debug, Clone)] -pub struct Context(Rc); +#[derive(Debug, Clone, Trace, Finalize)] +pub struct Context(Gc); impl Context { pub fn new_future() -> FutureWrapper { FutureWrapper::new() @@ -55,7 +54,7 @@ impl Context { } pub fn new() -> Self { - Self(Rc::new(ContextInternals { + Self(Gc::new(ContextInternals { dollar: None, this: None, super_obj: None, @@ -81,7 +80,7 @@ impl Context { pub fn with_var(self, name: IStr, value: Val) -> Self { let mut new_bindings = FxHashMap::with_capacity_and_hasher(1, BuildHasherDefault::default()); - new_bindings.insert(name, resolved_lazy_val!(value)); + new_bindings.insert(name, LazyVal::new_resolved(value)); self.extend(new_bindings, None, None, None) } @@ -96,40 +95,21 @@ impl Context { new_this: Option, new_super_obj: Option, ) -> Self { - match Rc::try_unwrap(self.0) { - Ok(mut ctx) => { - // Extended context aren't used by anything else, we can freely mutate it without cloning - if let Some(dollar) = new_dollar { - ctx.dollar = Some(dollar); - } - if let Some(this) = new_this { - ctx.this = Some(this); - } - if let Some(super_obj) = new_super_obj { - ctx.super_obj = Some(super_obj); - } - if !new_bindings.is_empty() { - ctx.bindings = ctx.bindings.extend(new_bindings); - } - Self(Rc::new(ctx)) - } - Err(ctx) => { - let dollar = new_dollar.or_else(|| ctx.dollar.clone()); - let this = new_this.or_else(|| ctx.this.clone()); - let super_obj = new_super_obj.or_else(|| ctx.super_obj.clone()); - let bindings = if new_bindings.is_empty() { - ctx.bindings.clone() - } else { - ctx.bindings.clone().extend(new_bindings) - }; - Self(Rc::new(ContextInternals { - dollar, - this, - super_obj, - bindings, - })) - } - } + let ctx = &self.0; + let dollar = new_dollar.or_else(|| ctx.dollar.clone()); + let this = new_this.or_else(|| ctx.this.clone()); + let super_obj = new_super_obj.or_else(|| ctx.super_obj.clone()); + let bindings = if new_bindings.is_empty() { + ctx.bindings.clone() + } else { + ctx.bindings.clone().extend(new_bindings) + }; + Self(Gc::new(ContextInternals { + dollar, + this, + super_obj, + bindings, + })) } pub fn extend_bound(self, new_bindings: FxHashMap) -> Self { let new_this = self.0.this.clone(); @@ -166,22 +146,6 @@ impl Default for Context { impl PartialEq for Context { fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) - } -} - -#[cfg(feature = "unstable")] -#[derive(Debug, Clone)] -pub struct WeakContext(std::rc::Weak); -#[cfg(feature = "unstable")] -impl WeakContext { - pub fn upgrade(&self) -> Context { - Context(self.0.upgrade().expect("context is removed")) - } -} -#[cfg(feature = "unstable")] -impl PartialEq for WeakContext { - fn eq(&self, other: &Self) -> bool { - self.0.ptr_eq(&other.0) + Gc::ptr_eq(&self.0, &other.0) } } diff --git a/crates/jrsonnet-evaluator/src/dynamic.rs b/crates/jrsonnet-evaluator/src/dynamic.rs index d3722b63..91fa6339 100644 --- a/crates/jrsonnet-evaluator/src/dynamic.rs +++ b/crates/jrsonnet-evaluator/src/dynamic.rs @@ -1,23 +1,23 @@ -use std::{cell::RefCell, rc::Rc}; +use gc::{Finalize, Gc, GcCell, Trace}; -#[derive(Clone)] -pub struct FutureWrapper(pub Rc>>); -impl FutureWrapper { +#[derive(Clone, Trace, Finalize)] +pub struct FutureWrapper(pub Gc>>); +impl FutureWrapper { pub fn new() -> Self { - Self(Rc::new(RefCell::new(None))) + Self(Gc::new(GcCell::new(None))) } pub fn fill(self, value: T) { assert!(self.0.borrow().is_none(), "wrapper is filled already"); self.0.borrow_mut().replace(value); } } -impl FutureWrapper { +impl FutureWrapper { pub fn unwrap(&self) -> T { self.0.borrow().as_ref().cloned().unwrap() } } -impl Default for FutureWrapper { +impl Default for FutureWrapper { fn default() -> Self { Self::new() } diff --git a/crates/jrsonnet-evaluator/src/error.rs b/crates/jrsonnet-evaluator/src/error.rs index 99d808e1..7804a4dc 100644 --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -2,13 +2,14 @@ use crate::{ builtin::{format::FormatError, sort::SortError}, typed::TypeLocError, }; +use gc::{Finalize, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType}; use jrsonnet_types::ValType; use std::{path::PathBuf, rc::Rc}; use thiserror::Error; -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, Trace, Finalize)] pub enum Error { #[error("intrinsic not found: {0}")] IntrinsicNotFound(IStr), @@ -88,6 +89,7 @@ pub enum Error { ImportSyntaxError { path: Rc, source_code: IStr, + #[unsafe_ignore_trace] error: Box, }, @@ -95,6 +97,8 @@ pub enum Error { RuntimeError(IStr), #[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")] StackOverflow, + #[error("infinite recursion detected")] + RecursiveLazyValueEvaluation, #[error("tried to index by fractional value")] FractionalIndex, #[error("attempted to divide by zero")] @@ -142,15 +146,15 @@ impl From for LocError { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Trace, Finalize)] pub struct StackTraceElement { pub location: Option, pub desc: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize)] pub struct StackTrace(pub Vec); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize)] pub struct LocError(Box<(Error, StackTrace)>); impl LocError { pub fn new(e: Error) -> Self { diff --git a/crates/jrsonnet-evaluator/src/evaluate.rs b/crates/jrsonnet-evaluator/src/evaluate.rs index 755c5c23..511795f5 100644 --- a/crates/jrsonnet-evaluator/src/evaluate.rs +++ b/crates/jrsonnet-evaluator/src/evaluate.rs @@ -1,8 +1,9 @@ use crate::{ - equals, error::Error::*, lazy_val, push, throw, with_state, ArrValue, Context, ContextCreator, - FuncDesc, FuncVal, FutureWrapper, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val, + equals, error::Error::*, push, throw, with_state, ArrValue, Bindable, Context, ContextCreator, + FuncDesc, FuncVal, FutureWrapper, LazyBinding, LazyVal, LazyValValue, ObjMember, ObjValue, + ObjectAssertion, Result, Val, }; -use closure::closure; +use gc::{custom_trace, Finalize, Gc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{ ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprLocation, FieldMember, @@ -20,17 +21,62 @@ pub fn evaluate_binding_in_future( let b = b.clone(); if let Some(params) = &b.params { let params = params.clone(); - LazyVal::new(Box::new(move || { - Ok(evaluate_method( - context_creator.unwrap(), - b.name.clone(), - params.clone(), - b.value.clone(), - )) + + struct LazyMethodBinding { + context_creator: FutureWrapper, + name: IStr, + params: ParamsDesc, + value: LocExpr, + } + impl Finalize for LazyMethodBinding {} + unsafe impl Trace for LazyMethodBinding { + custom_trace!(this, { + mark(&this.context_creator); + mark(&this.name); + mark(&this.params); + mark(&this.value); + }); + } + impl LazyValValue for LazyMethodBinding { + fn get(self: Box) -> Result { + Ok(evaluate_method( + self.context_creator.unwrap(), + self.name, + self.params, + self.value, + )) + } + } + + LazyVal::new(Box::new(LazyMethodBinding { + context_creator, + name: b.name.clone(), + params, + value: b.value.clone(), })) } else { - LazyVal::new(Box::new(move || { - evaluate_named(context_creator.unwrap(), &b.value, b.name.clone()) + struct LazyNamedBinding { + context_creator: FutureWrapper, + name: IStr, + value: LocExpr, + } + impl Finalize for LazyNamedBinding {} + unsafe impl Trace for LazyNamedBinding { + custom_trace!(this, { + mark(&this.context_creator); + mark(&this.name); + mark(&this.value); + }); + } + impl LazyValValue for LazyNamedBinding { + fn get(self: Box) -> Result { + evaluate_named(self.context_creator.unwrap(), &self.value, self.name) + } + } + LazyVal::new(Box::new(LazyNamedBinding { + context_creator, + name: b.name.clone(), + value: b.value, })) } } @@ -39,37 +85,129 @@ pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (IStr, let b = b.clone(); if let Some(params) = &b.params { let params = params.clone(); + + struct BindableMethodLazyVal { + this: Option, + super_obj: Option, + + context_creator: ContextCreator, + name: IStr, + params: ParamsDesc, + value: LocExpr, + } + impl Finalize for BindableMethodLazyVal {} + unsafe impl Trace for BindableMethodLazyVal { + custom_trace!(this, { + mark(&this.this); + mark(&this.super_obj); + mark(&this.context_creator); + mark(&this.name); + mark(&this.params); + mark(&this.value); + }); + } + impl LazyValValue for BindableMethodLazyVal { + fn get(self: Box) -> Result { + Ok(evaluate_method( + self.context_creator.create(self.this, self.super_obj)?, + self.name, + self.params, + self.value, + )) + } + } + + #[derive(Trace, Finalize)] + struct BindableMethod { + context_creator: ContextCreator, + name: IStr, + params: ParamsDesc, + value: LocExpr, + } + impl Bindable for BindableMethod { + fn bind(&self, this: Option, super_obj: Option) -> Result { + Ok(LazyVal::new(Box::new(BindableMethodLazyVal { + this: this.clone(), + super_obj: super_obj.clone(), + + context_creator: self.context_creator.clone(), + name: self.name.clone(), + params: self.params.clone(), + value: self.value.clone(), + }))) + } + } + ( b.name.clone(), - LazyBinding::Bindable(Rc::new(move |this, super_obj| { - Ok(lazy_val!( - closure!(clone b, clone params, clone context_creator, || Ok(evaluate_method( - context_creator.create(this.clone(), super_obj.clone())?, - b.name.clone(), - params.clone(), - b.value.clone(), - ))) - )) - })), + LazyBinding::Bindable(Gc::new(Box::new(BindableMethod { + context_creator, + name: b.name.clone(), + params, + value: b.value.clone(), + }))), ) } else { + struct BindableNamedLazyVal { + this: Option, + super_obj: Option, + + context_creator: ContextCreator, + name: IStr, + value: LocExpr, + } + impl Finalize for BindableNamedLazyVal {} + unsafe impl Trace for BindableNamedLazyVal { + custom_trace!(this, { + mark(&this.this); + mark(&this.super_obj); + mark(&this.context_creator); + mark(&this.name); + mark(&this.value); + }); + } + impl LazyValValue for BindableNamedLazyVal { + fn get(self: Box) -> Result { + evaluate_named( + self.context_creator.create(self.this, self.super_obj)?, + &self.value, + self.name, + ) + } + } + + #[derive(Trace, Finalize)] + struct BindableNamed { + context_creator: ContextCreator, + name: IStr, + value: LocExpr, + } + impl Bindable for BindableNamed { + fn bind(&self, this: Option, super_obj: Option) -> Result { + Ok(LazyVal::new(Box::new(BindableNamedLazyVal { + this, + super_obj, + + context_creator: self.context_creator.clone(), + name: self.name.clone(), + value: self.value.clone(), + }))) + } + } + ( b.name.clone(), - LazyBinding::Bindable(Rc::new(move |this, super_obj| { - Ok(lazy_val!(closure!(clone context_creator, clone b, || - evaluate_named( - context_creator.create(this.clone(), super_obj.clone())?, - &b.value, - b.name.clone() - ) - ))) - })), + LazyBinding::Bindable(Gc::new(Box::new(BindableNamed { + context_creator, + name: b.name.clone(), + value: b.value.clone(), + }))), ) } } pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val { - Val::Func(Rc::new(FuncVal::Normal(FuncDesc { + Val::Func(Gc::new(FuncVal::Normal(FuncDesc { name, ctx, params, @@ -105,6 +243,9 @@ pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result { pub fn evaluate_add_op(a: &Val, b: &Val) -> Result { Ok(match (a, b) { + (Val::DebugGcTraceValue(v1), Val::DebugGcTraceValue(v2)) => { + evaluate_add_op(&v1.value, &v2.value)? + } (Val::Str(v1), Val::Str(v2)) => Val::Str(((**v1).to_owned() + v2).into()), // Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890) @@ -257,7 +398,7 @@ pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Resu } let mut new_members = FxHashMap::default(); - let mut assertions = Vec::new(); + let mut assertions: Vec> = Vec::new(); for member in members.iter() { match member { Member::Field(FieldMember { @@ -272,20 +413,36 @@ pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Resu continue; } let name = name.unwrap(); + + #[derive(Trace, Finalize)] + struct ObjMemberBinding { + context_creator: ContextCreator, + value: LocExpr, + name: IStr, + } + impl Bindable for ObjMemberBinding { + fn bind( + &self, + this: Option, + super_obj: Option, + ) -> Result { + Ok(LazyVal::new_resolved(evaluate_named( + self.context_creator.create(this, super_obj)?, + &self.value, + self.name.clone(), + )?)) + } + } new_members.insert( name.clone(), ObjMember { add: *plus, visibility: *visibility, - invoke: LazyBinding::Bindable(Rc::new( - closure!(clone name, clone value, clone context_creator, |this, super_obj| { - Ok(LazyVal::new_resolved(evaluate_named( - context_creator.create(this, super_obj)?, - &value, - name.clone(), - )?)) - }), - )), + invoke: LazyBinding::Bindable(Gc::new(Box::new(ObjMemberBinding { + context_creator: context_creator.clone(), + value: value.clone(), + name, + }))), location: value.1.clone(), }, ); @@ -301,33 +458,73 @@ pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Resu continue; } let name = name.unwrap(); + #[derive(Trace, Finalize)] + struct ObjMemberBinding { + context_creator: ContextCreator, + value: LocExpr, + params: ParamsDesc, + name: IStr, + } + impl Bindable for ObjMemberBinding { + fn bind( + &self, + this: Option, + super_obj: Option, + ) -> Result { + Ok(LazyVal::new_resolved(evaluate_method( + self.context_creator.create(this, super_obj)?, + self.name.clone(), + self.params.clone(), + self.value.clone(), + ))) + } + } new_members.insert( name.clone(), ObjMember { add: false, visibility: Visibility::Hidden, - invoke: LazyBinding::Bindable(Rc::new( - closure!(clone value, clone context_creator, clone params, clone name, |this, super_obj| { - // TODO: Assert - Ok(LazyVal::new_resolved(evaluate_method( - context_creator.create(this, super_obj)?, - name.clone(), - params.clone(), - value.clone(), - ))) - }), - )), + invoke: LazyBinding::Bindable(Gc::new(Box::new(ObjMemberBinding { + context_creator: context_creator.clone(), + value: value.clone(), + params: params.clone(), + name, + }))), location: value.1.clone(), }, ); } Member::BindStmt(_) => {} Member::AssertStmt(stmt) => { - assertions.push(stmt.clone()); + struct ObjectAssert { + context_creator: ContextCreator, + assert: AssertStmt, + } + impl Finalize for ObjectAssert {} + unsafe impl Trace for ObjectAssert { + custom_trace!(this, { + mark(&this.context_creator); + mark(&this.assert); + }); + } + impl ObjectAssertion for ObjectAssert { + fn run( + &self, + this: Option, + super_obj: Option, + ) -> Result<()> { + let ctx = self.context_creator.create(this, super_obj)?; + evaluate_assert(ctx, &self.assert) + } + } + assertions.push(Box::new(ObjectAssert { + context_creator: context_creator.clone(), + assert: stmt.clone(), + })); } } } - let this = ObjValue::new(context, None, Rc::new(new_members), Rc::new(assertions)); + let this = ObjValue::new(None, Gc::new(new_members), Gc::new(assertions)); future_this.fill(this.clone()); Ok(this) } @@ -361,16 +558,37 @@ pub fn evaluate_object(context: Context, object: &ObjBody) -> Result { match key { Val::Null => {} Val::Str(n) => { + #[derive(Trace, Finalize)] + struct ObjCompBinding { + context: Context, + value: LocExpr, + } + impl Bindable for ObjCompBinding { + fn bind( + &self, + this: Option, + _super_obj: Option, + ) -> Result { + Ok(LazyVal::new_resolved(evaluate( + self.context.clone().extend( + FxHashMap::default(), + None, + this, + None, + ), + &self.value, + )?)) + } + } new_members.insert( n, ObjMember { add: false, visibility: Visibility::Normal, - invoke: LazyBinding::Bindable(Rc::new( - closure!(clone ctx, clone obj.value, |this, _super_obj| { - Ok(LazyVal::new_resolved(evaluate(ctx.clone().extend(FxHashMap::default(), None, this, None), &value)?)) - }), - )), + invoke: LazyBinding::Bindable(Gc::new(Box::new(ObjCompBinding { + context: ctx.clone(), + value: obj.value.clone(), + }))), location: obj.value.1.clone(), }, ); @@ -381,7 +599,7 @@ pub fn evaluate_object(context: Context, object: &ObjBody) -> Result { Ok(()) })?; - let this = ObjValue::new(context, None, Rc::new(new_members), Rc::new(Vec::new())); + let this = ObjValue::new(None, Gc::new(new_members), Gc::new(Vec::new())); future_this.fill(this.clone()); this } @@ -486,7 +704,7 @@ pub fn evaluate(context: Context, expr: &LocExpr) -> Result { if let Some(v) = v.get(s.clone())? { Ok(v) } else if v.get("__intrinsic_namespace__".into())?.is_some() { - Ok(Val::Func(Rc::new(FuncVal::Intrinsic(s)))) + Ok(Val::Func(Gc::new(FuncVal::Intrinsic(s)))) } else { throw!(NoSuchField(s)) } @@ -549,11 +767,27 @@ pub fn evaluate(context: Context, expr: &LocExpr) -> Result { Arr(items) => { let mut out = Vec::with_capacity(items.len()); for item in items { - out.push(LazyVal::new(Box::new( - closure!(clone context, clone item, || { - evaluate(context.clone(), &item) - }), - ))); + // TODO: Implement ArrValue::Lazy with same context for every element? + struct ArrayElement { + context: Context, + item: LocExpr, + } + impl Finalize for ArrayElement {} + unsafe impl Trace for ArrayElement { + custom_trace!(this, { + mark(&this.context); + mark(&this.item); + }); + } + impl LazyValValue for ArrayElement { + fn get(self: Box) -> Result { + evaluate(self.context, &self.item) + } + } + out.push(LazyVal::new(Box::new(ArrayElement { + context: context.clone(), + item: item.clone(), + }))); } Val::Arr(out.into()) } @@ -563,7 +797,7 @@ pub fn evaluate(context: Context, expr: &LocExpr) -> Result { out.push(evaluate(ctx, expr)?); Ok(()) })?; - Val::Arr(ArrValue::Eager(Rc::new(out))) + Val::Arr(ArrValue::Eager(Gc::new(out))) } Obj(body) => Val::Obj(evaluate_object(context, body)?), ObjExtend(s, t) => evaluate_add_op( @@ -576,7 +810,7 @@ pub fn evaluate(context: Context, expr: &LocExpr) -> Result { Function(params, body) => { evaluate_method(context, "anonymous".into(), params.clone(), body.clone()) } - Intrinsic(name) => Val::Func(Rc::new(FuncVal::Intrinsic(name.clone()))), + Intrinsic(name) => Val::Func(Gc::new(FuncVal::Intrinsic(name.clone()))), AssertExpr(assert, returned) => { evaluate_assert(context.clone(), assert)?; evaluate(context, returned)? diff --git a/crates/jrsonnet-evaluator/src/function.rs b/crates/jrsonnet-evaluator/src/function.rs index 1c3f317e..065835ec 100644 --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -1,7 +1,7 @@ -use crate::{error::Error::*, evaluate, lazy_val, resolved_lazy_val, throw, Context, Result, Val}; -use closure::closure; +use crate::{error::Error::*, evaluate, throw, Context, LazyVal, LazyValValue, Result, Val}; +use gc::{custom_trace, Finalize, Trace}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ArgsDesc, ParamsDesc}; +use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc}; use rustc_hash::FxHashMap; use std::{collections::HashMap, hash::BuildHasherDefault}; @@ -53,9 +53,29 @@ pub fn parse_function_call( throw!(FunctionParameterNotBoundInCall(p.0.clone())); }; let val = if tailstrict { - resolved_lazy_val!(evaluate(ctx, expr)?) + LazyVal::new_resolved(evaluate(ctx, expr)?) } else { - lazy_val!(closure!(clone ctx, clone expr, ||evaluate(ctx.clone(), &expr))) + struct EvaluateLazyVal { + context: Context, + expr: LocExpr, + } + impl Finalize for EvaluateLazyVal {} + unsafe impl Trace for EvaluateLazyVal { + custom_trace!(this, { + mark(&this.context); + mark(&this.expr); + }); + } + impl LazyValValue for EvaluateLazyVal { + fn get(self: Box) -> Result { + evaluate(self.context, &self.expr) + } + } + + LazyVal::new(Box::new(EvaluateLazyVal { + context: ctx.clone(), + expr: expr.clone(), + })) }; out.insert(p.0.clone(), val); } @@ -89,19 +109,30 @@ pub fn parse_function_call_map( // Fill defaults for (id, p) in params.iter().enumerate() { let val = if let Some(arg) = positioned_args[id].take() { - resolved_lazy_val!(arg) + LazyVal::new_resolved(arg) } else if let Some(default) = &p.1 { if tailstrict { - resolved_lazy_val!(evaluate( + LazyVal::new_resolved(evaluate( body_ctx.clone().expect(NO_DEFAULT_CONTEXT), - default + default, )?) } else { let body_ctx = body_ctx.clone(); let default = default.clone(); - lazy_val!(move || { - evaluate(body_ctx.clone().expect(NO_DEFAULT_CONTEXT), &default) - }) + #[derive(Trace, Finalize)] + struct EvaluateLazyVal { + body_ctx: Option, + default: LocExpr, + } + impl LazyValValue for EvaluateLazyVal { + fn get(self: Box) -> Result { + evaluate( + self.body_ctx.clone().expect(NO_DEFAULT_CONTEXT), + &self.default, + ) + } + } + LazyVal::new(Box::new(EvaluateLazyVal { body_ctx, default })) } } else { throw!(FunctionParameterNotBoundInCall(p.0.clone())); @@ -135,7 +166,7 @@ pub fn place_args( } else { throw!(FunctionParameterNotBoundInCall(p.0.clone())); }; - out.insert(p.0.clone(), resolved_lazy_val!(val)); + out.insert(p.0.clone(), LazyVal::new_resolved(val)); } Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)) diff --git a/crates/jrsonnet-evaluator/src/integrations/serde.rs b/crates/jrsonnet-evaluator/src/integrations/serde.rs index c4d9ca0b..179389cb 100644 --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -2,6 +2,7 @@ use crate::{ error::{Error::*, LocError, Result}, throw, Context, LazyBinding, LazyVal, ObjMember, ObjValue, Val, }; +use gc::Gc; use jrsonnet_parser::Visibility; use rustc_hash::FxHasher; use serde_json::{Map, Number, Value}; @@ -9,7 +10,6 @@ use std::{ collections::HashMap, convert::{TryFrom, TryInto}, hash::BuildHasherDefault, - rc::Rc, }; impl TryFrom<&Val> for Value { @@ -42,6 +42,7 @@ impl TryFrom<&Val> for Value { Self::Object(out) } Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())), + Val::DebugGcTraceValue(v) => Value::try_from(&*v.value as &Val)?, }) } } @@ -76,12 +77,7 @@ impl From<&Value> for Val { }, ); } - Self::Obj(ObjValue::new( - Context::new(), - None, - Rc::new(entries), - Rc::new(Vec::new()), - )) + Self::Obj(ObjValue::new(None, Gc::new(entries), Gc::new(Vec::new()))) } } } diff --git a/crates/jrsonnet-evaluator/src/lib.rs b/crates/jrsonnet-evaluator/src/lib.rs index 0a55f319..9f9caa23 100644 --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -25,6 +25,7 @@ pub use dynamic::*; use error::{Error::*, LocError, Result, StackTraceElement}; pub use evaluate::*; pub use function::parse_function_call; +use gc::{Finalize, Gc, Trace}; pub use import::*; use jrsonnet_interner::IStr; use jrsonnet_parser::*; @@ -42,10 +43,12 @@ use std::{ use trace::{offset_to_location, CodeLocation, CompactFormat, TraceFormat}; pub use val::*; -type BindableFn = dyn Fn(Option, Option) -> Result; -#[derive(Clone)] +pub trait Bindable: Trace { + fn bind(&self, this: Option, super_obj: Option) -> Result; +} +#[derive(Trace, Finalize, Clone)] pub enum LazyBinding { - Bindable(Rc), + Bindable(Gc>), Bound(LazyVal), } @@ -57,7 +60,7 @@ impl Debug for LazyBinding { impl LazyBinding { pub fn evaluate(&self, this: Option, super_obj: Option) -> Result { match self { - Self::Bindable(v) => v(this, super_obj), + Self::Bindable(v) => v.bind(this, super_obj), Self::Bound(v) => Ok(v.clone()), } } @@ -71,7 +74,7 @@ pub struct EvaluationSettings { /// Used for s`td.extVar` pub ext_vars: HashMap, /// Used for ext.native - pub ext_natives: HashMap>, + pub ext_natives: HashMap>, /// TLA vars pub tla_vars: HashMap, /// Global variables are inserted in default context @@ -270,7 +273,7 @@ impl EvaluationState { let mut new_bindings: FxHashMap = FxHashMap::with_capacity_and_hasher(globals.len(), BuildHasherDefault::default()); for (name, value) in globals.iter() { - new_bindings.insert(name.clone(), resolved_lazy_val!(value.clone())); + new_bindings.insert(name.clone(), LazyVal::new_resolved(value.clone())); } Context::new().extend_bound(new_bindings) } @@ -449,7 +452,7 @@ impl EvaluationState { self.settings_mut().import_resolver = resolver; } - pub fn add_native(&self, name: IStr, cb: Rc) { + pub fn add_native(&self, name: IStr, cb: Gc) { self.settings_mut().ext_natives.insert(name, cb); } diff --git a/crates/jrsonnet-evaluator/src/map.rs b/crates/jrsonnet-evaluator/src/map.rs index d026ff4e..43b76386 100644 --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -1,28 +1,29 @@ +use gc::{Finalize, Gc, Trace}; use jrsonnet_interner::IStr; use rustc_hash::FxHashMap; -use std::rc::Rc; -#[derive(Default, Debug)] -struct LayeredHashMapInternals { +pub struct LayeredHashMapInternals { parent: Option>, current: FxHashMap, } -#[derive(Debug)] -pub struct LayeredHashMap(Rc>); +unsafe impl Trace for LayeredHashMapInternals { + gc::custom_trace!(this, { + mark(&this.parent); + mark(&this.current); + }); +} +impl Finalize for LayeredHashMapInternals {} + +#[derive(Trace, Finalize)] +pub struct LayeredHashMap(Gc>); -impl LayeredHashMap { +impl LayeredHashMap { pub fn extend(self, new_layer: FxHashMap) -> Self { - match Rc::try_unwrap(self.0) { - Ok(mut map) => { - map.current.extend(new_layer); - Self(Rc::new(map)) - } - Err(this) => Self(Rc::new(LayeredHashMapInternals { - parent: Some(Self(this)), - current: new_layer, - })), - } + Self(Gc::new(LayeredHashMapInternals { + parent: Some(self), + current: new_layer, + })) } pub fn get(&self, key: &IStr) -> Option<&V> { @@ -33,15 +34,15 @@ impl LayeredHashMap { } } -impl Clone for LayeredHashMap { +impl Clone for LayeredHashMap { fn clone(&self) -> Self { Self(self.0.clone()) } } -impl Default for LayeredHashMap { +impl Default for LayeredHashMap { fn default() -> Self { - Self(Rc::new(LayeredHashMapInternals { + Self(Gc::new(LayeredHashMapInternals { parent: None, current: FxHashMap::default(), })) diff --git a/crates/jrsonnet-evaluator/src/native.rs b/crates/jrsonnet-evaluator/src/native.rs index a6673c48..4a215ec5 100644 --- a/crates/jrsonnet-evaluator/src/native.rs +++ b/crates/jrsonnet-evaluator/src/native.rs @@ -1,27 +1,27 @@ #![allow(clippy::type_complexity)] use crate::{error::Result, Val}; +use gc::{Finalize, Trace}; use jrsonnet_parser::ParamsDesc; use std::fmt::Debug; use std::path::PathBuf; use std::rc::Rc; +pub trait NativeCallbackHandler: Trace { + fn call(&self, from: Option>, args: &[Val]) -> Result; +} + +#[derive(Trace, Finalize)] pub struct NativeCallback { pub params: ParamsDesc, - handler: Box>, &[Val]) -> Result>, + handler: Box, } impl NativeCallback { - pub fn new( - params: ParamsDesc, - handler: impl Fn(Option>, &[Val]) -> Result + 'static, - ) -> Self { - Self { - params, - handler: Box::new(handler), - } + pub fn new(params: ParamsDesc, handler: Box) -> Self { + Self { params, handler } } pub fn call(&self, caller: Option>, args: &[Val]) -> Result { - (self.handler)(caller, args) + self.handler.call(caller, args) } } impl Debug for NativeCallback { diff --git a/crates/jrsonnet-evaluator/src/obj.rs b/crates/jrsonnet-evaluator/src/obj.rs index 0b3a8c14..a4267e47 100644 --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -1,11 +1,12 @@ use crate::{evaluate_add_op, evaluate_assert, Context, LazyBinding, Result, Val}; +use gc::{Finalize, Gc, GcCell, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{AssertStmt, ExprLocation, Visibility}; use rustc_hash::{FxHashMap, FxHashSet}; use std::hash::{Hash, Hasher}; -use std::{cell::RefCell, fmt::Debug, hash::BuildHasherDefault, rc::Rc}; +use std::{fmt::Debug, hash::BuildHasherDefault}; -#[derive(Debug)] +#[derive(Debug, Trace, Finalize)] pub struct ObjMember { pub add: bool, pub visibility: Visibility, @@ -13,21 +14,24 @@ pub struct ObjMember { pub location: Option, } +pub trait ObjectAssertion: Trace { + fn run(&self, this: Option, super_obj: Option) -> Result<()>; +} + // Field => This type CacheKey = (IStr, ObjValue); -#[derive(Debug)] +#[derive(Trace, Finalize)] pub struct ObjValueInternals { - context: Context, super_obj: Option, - assertions: Rc>, - assertions_ran: RefCell>, + assertions: Gc>>, + assertions_ran: GcCell>, this_obj: Option, - this_entries: Rc>, - value_cache: RefCell>>, + this_entries: Gc>, + value_cache: GcCell>>, } -#[derive(Clone)] -pub struct ObjValue(pub(crate) Rc); +#[derive(Clone, Trace, Finalize)] +pub struct ObjValue(pub(crate) Gc); impl Debug for ObjValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(super_obj) = self.0.super_obj.as_ref() { @@ -55,39 +59,30 @@ impl Debug for ObjValue { impl ObjValue { pub fn new( - context: Context, super_obj: Option, - this_entries: Rc>, - assertions: Rc>, + this_entries: Gc>, + assertions: Gc>>, ) -> Self { - Self(Rc::new(ObjValueInternals { - context, + Self(Gc::new(ObjValueInternals { super_obj, assertions, - assertions_ran: RefCell::new(FxHashSet::default()), + assertions_ran: GcCell::new(FxHashSet::default()), this_obj: None, this_entries, - value_cache: RefCell::new(FxHashMap::default()), + value_cache: GcCell::new(FxHashMap::default()), })) } pub fn new_empty() -> Self { - Self::new( - Context::new(), - None, - Rc::new(FxHashMap::default()), - Rc::new(Vec::new()), - ) + Self::new(None, Gc::new(FxHashMap::default()), Gc::new(Vec::new())) } pub fn extend_from(&self, super_obj: Self) -> Self { match &self.0.super_obj { None => Self::new( - self.0.context.clone(), Some(super_obj), self.0.this_entries.clone(), self.0.assertions.clone(), ), Some(v) => Self::new( - self.0.context.clone(), Some(v.extend_from(super_obj)), self.0.this_entries.clone(), self.0.assertions.clone(), @@ -95,14 +90,13 @@ impl ObjValue { } } pub fn with_this(&self, this_obj: Self) -> Self { - Self(Rc::new(ObjValueInternals { - context: self.0.context.clone(), + Self(Gc::new(ObjValueInternals { super_obj: self.0.super_obj.clone(), assertions: self.0.assertions.clone(), - assertions_ran: RefCell::new(FxHashSet::default()), + assertions_ran: GcCell::new(FxHashSet::default()), this_obj: Some(this_obj), this_entries: self.0.this_entries.clone(), - value_cache: RefCell::new(FxHashMap::default()), + value_cache: GcCell::new(FxHashMap::default()), })) } @@ -203,12 +197,7 @@ impl ObjValue { pub fn extend_with_field(self, key: IStr, value: ObjMember) -> Self { let mut new = FxHashMap::with_capacity_and_hasher(1, BuildHasherDefault::default()); new.insert(key, value); - Self::new( - Context::new(), - Some(self), - Rc::new(new), - Rc::new(Vec::new()), - ) + Self::new(Some(self), Gc::new(new), Gc::new(Vec::new())) } fn get_raw(&self, key: IStr, real_this: Option<&Self>) -> Result> { @@ -249,13 +238,7 @@ impl ObjValue { fn run_assertions_raw(&self, real_this: &Self) -> Result<()> { if self.0.assertions_ran.borrow_mut().insert(real_this.clone()) { for assertion in self.0.assertions.iter() { - if let Err(e) = evaluate_assert( - self.0 - .context - .clone() - .with_this_super(real_this.clone(), self.0.super_obj.clone()), - assertion, - ) { + if let Err(e) = assertion.run(Some(real_this.clone()), self.0.super_obj.clone()) { self.0.assertions_ran.borrow_mut().remove(real_this); return Err(e); } @@ -271,19 +254,19 @@ impl ObjValue { } pub fn ptr_eq(a: &Self, b: &Self) -> bool { - Rc::ptr_eq(&a.0, &b.0) + Gc::ptr_eq(&a.0, &b.0) } } impl PartialEq for ObjValue { fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) + Gc::ptr_eq(&self.0, &other.0) } } impl Eq for ObjValue {} impl Hash for ObjValue { - fn hash(&self, state: &mut H) { - state.write_usize(Rc::as_ptr(&self.0) as usize) + fn hash(&self, hasher: &mut H) { + hasher.write_usize(&*self.0 as *const _ as usize) } } diff --git a/crates/jrsonnet-evaluator/src/typed.rs b/crates/jrsonnet-evaluator/src/typed.rs index 4f67b7e2..78a4b0e5 100644 --- a/crates/jrsonnet-evaluator/src/typed.rs +++ b/crates/jrsonnet-evaluator/src/typed.rs @@ -4,6 +4,7 @@ use crate::{ error::{Error, LocError, Result}, push, Val, }; +use gc::{Finalize, Trace}; use jrsonnet_parser::ExprLocation; use jrsonnet_types::{ComplexValType, ValType}; use thiserror::Error; @@ -20,7 +21,7 @@ macro_rules! unwrap_type { }}; } -#[derive(Debug, Error, Clone)] +#[derive(Debug, Error, Clone, Trace, Finalize)] pub enum TypeError { #[error("expected {0}, got {1}")] ExpectedGot(ComplexValType, ValType), @@ -37,7 +38,7 @@ impl From for LocError { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize)] pub struct TypeLocError(Box, ValuePathStack); impl From for TypeLocError { fn from(e: TypeError) -> Self { @@ -59,7 +60,7 @@ impl Display for TypeLocError { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize)] pub struct TypeLocErrorList(Vec); impl Display for TypeLocErrorList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -122,7 +123,7 @@ impl CheckType for ValType { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Trace, Finalize)] enum ValuePathItem { Field(Rc), Index(u64), @@ -137,7 +138,7 @@ impl Display for ValuePathItem { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Trace, Finalize)] struct ValuePathStack(Vec); impl Display for ValuePathStack { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/jrsonnet-evaluator/src/val.rs b/crates/jrsonnet-evaluator/src/val.rs index 44fb3fb8..631ce74c 100644 --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -3,52 +3,75 @@ use crate::{ call_builtin, manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType}, }, - error::Error::*, + error::{Error::*, LocError}, evaluate, function::{parse_function_call, parse_function_call_map, place_args}, native::NativeCallback, throw, with_state, Context, ObjValue, Result, }; +use gc::{custom_trace, Finalize, Gc, GcCell, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc}; use jrsonnet_types::ValType; -use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; +use std::{collections::HashMap, fmt::Debug, rc::Rc}; + +pub trait LazyValValue: Trace { + fn get(self: Box) -> Result; +} enum LazyValInternals { Computed(Val), - Waiting(Box Result>), + Errored(LocError), + Waiting(Box), + Pending, } -#[derive(Clone)] -pub struct LazyVal(Rc>); +impl Finalize for LazyValInternals {} +unsafe impl Trace for LazyValInternals { + custom_trace!(this, { + match &this { + LazyValInternals::Computed(v) => mark(v), + LazyValInternals::Errored(e) => mark(e), + LazyValInternals::Waiting(w) => mark(w), + LazyValInternals::Pending => {} + } + }); +} + +#[derive(Clone, Trace, Finalize)] +pub struct LazyVal(Gc>); impl LazyVal { - pub fn new(f: Box Result>) -> Self { - Self(Rc::new(RefCell::new(LazyValInternals::Waiting(f)))) + pub fn new(f: Box) -> Self { + Self(Gc::new(GcCell::new(LazyValInternals::Waiting(f)))) } pub fn new_resolved(val: Val) -> Self { - Self(Rc::new(RefCell::new(LazyValInternals::Computed(val)))) + Self(Gc::new(GcCell::new(LazyValInternals::Computed(val)))) } pub fn evaluate(&self) -> Result { - let new_value = match &*self.0.borrow() { + match &*self.0.borrow() { LazyValInternals::Computed(v) => return Ok(v.clone()), - LazyValInternals::Waiting(f) => f()?, + LazyValInternals::Errored(e) => return Err(e.clone().into()), + LazyValInternals::Pending => return Err(RecursiveLazyValueEvaluation.into()), + _ => (), + }; + let value = if let LazyValInternals::Waiting(value) = + std::mem::replace(&mut *self.0.borrow_mut(), LazyValInternals::Pending) + { + value + } else { + unreachable!() + }; + let new_value = match value.get() { + Ok(v) => v, + Err(e) => { + *self.0.borrow_mut() = LazyValInternals::Errored(e.clone()); + return Err(e); + } }; *self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone()); Ok(new_value) } } -#[macro_export] -macro_rules! lazy_val { - ($f: expr) => { - $crate::LazyVal::new(Box::new($f)) - }; -} -#[macro_export] -macro_rules! resolved_lazy_val { - ($f: expr) => { - $crate::LazyVal::new_resolved($f) - }; -} impl Debug for LazyVal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Lazy") @@ -56,11 +79,11 @@ impl Debug for LazyVal { } impl PartialEq for LazyVal { fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) + Gc::ptr_eq(&self.0, &other.0) } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace, Finalize)] pub struct FuncDesc { pub name: IStr, pub ctx: Context, @@ -68,14 +91,14 @@ pub struct FuncDesc { pub body: LocExpr, } -#[derive(Debug)] +#[derive(Debug, Trace, Finalize)] pub enum FuncVal { /// Plain function implemented in jsonnet Normal(FuncDesc), /// Standard library function Intrinsic(IStr), /// Library functions implemented in native - NativeExt(IStr, Rc), + NativeExt(IStr, Gc), } impl PartialEq for FuncVal { @@ -174,13 +197,23 @@ pub enum ManifestFormat { #[derive(Debug, Clone)] pub enum ArrValue { - Lazy(Rc>), - Eager(Rc>), + Lazy(Gc>), + Eager(Gc>), Extended(Box<(Self, Self)>), } +impl Finalize for ArrValue {} +unsafe impl Trace for ArrValue { + custom_trace!(this, { + match &this { + ArrValue::Lazy(l) => mark(l), + ArrValue::Eager(e) => mark(e), + ArrValue::Extended(x) => mark(x), + } + }); +} impl ArrValue { pub fn new_eager() -> Self { - Self::Eager(Rc::new(Vec::new())) + Self::Eager(Gc::new(Vec::new())) } pub fn len(&self) -> usize { @@ -231,14 +264,14 @@ impl ArrValue { } } - pub fn evaluated(&self) -> Result>> { + pub fn evaluated(&self) -> Result>> { Ok(match self { Self::Lazy(vec) => { let mut out = Vec::with_capacity(vec.len()); for item in vec.iter() { out.push(item.evaluate()?); } - Rc::new(out) + Gc::new(out) } Self::Eager(vec) => vec.clone(), Self::Extended(_v) => { @@ -246,7 +279,7 @@ impl ArrValue { for item in self.iter() { out.push(item?); } - Rc::new(out) + Gc::new(out) } }) } @@ -272,12 +305,12 @@ impl ArrValue { Self::Lazy(vec) => { let mut out = (&vec as &Vec<_>).clone(); out.reverse(); - Self::Lazy(Rc::new(out)) + Self::Lazy(Gc::new(out)) } Self::Eager(vec) => { let mut out = (&vec as &Vec<_>).clone(); out.reverse(); - Self::Eager(Rc::new(out)) + Self::Eager(Gc::new(out)) } Self::Extended(b) => Self::Extended(Box::new((b.1.reversed(), b.0.reversed()))), } @@ -290,7 +323,7 @@ impl ArrValue { out.push(mapper(value?)?); } - Ok(Self::Eager(Rc::new(out))) + Ok(Self::Eager(Gc::new(out))) } pub fn filter(self, filter: impl Fn(&Val) -> Result) -> Result { @@ -303,13 +336,13 @@ impl ArrValue { } } - Ok(Self::Eager(Rc::new(out))) + Ok(Self::Eager(Gc::new(out))) } pub fn ptr_eq(a: &Self, b: &Self) -> bool { match (a, b) { - (Self::Lazy(a), Self::Lazy(b)) => Rc::ptr_eq(a, b), - (Self::Eager(a), Self::Eager(b)) => Rc::ptr_eq(a, b), + (Self::Lazy(a), Self::Lazy(b)) => Gc::ptr_eq(a, b), + (Self::Eager(a), Self::Eager(b)) => Gc::ptr_eq(a, b), _ => false, } } @@ -317,13 +350,72 @@ impl ArrValue { impl From> for ArrValue { fn from(v: Vec) -> Self { - Self::Lazy(Rc::new(v)) + Self::Lazy(Gc::new(v)) } } impl From> for ArrValue { fn from(v: Vec) -> Self { - Self::Eager(Rc::new(v)) + Self::Eager(Gc::new(v)) + } +} + +#[derive(Debug)] +pub struct DebugGcTraceValue { + name: IStr, + pub value: Box, +} +impl DebugGcTraceValue { + fn print(&self, action: &str) { + println!("{} {}#{:?}", action, self.name, &*self.value as *const _) + } +} +impl Finalize for DebugGcTraceValue { + fn finalize(&self) { + self.print("Garbage-collecting") + } +} +impl Drop for DebugGcTraceValue { + fn drop(&mut self) { + self.print("Garbage-collected") + } +} +unsafe impl Trace for DebugGcTraceValue { + unsafe fn trace(&self) { + self.print("Traced"); + self.value.trace() + } + unsafe fn root(&self) { + self.print("Rooted"); + self.value.root() + } + unsafe fn unroot(&self) { + self.print("Unrooted"); + self.value.unroot() + } + fn finalize_glue(&self) { + Finalize::finalize(self) + } +} +impl Clone for DebugGcTraceValue { + fn clone(&self) -> Self { + self.print("Cloned"); + let value = DebugGcTraceValue { + name: self.name.clone(), + value: self.value.clone(), + }; + value.print("I'm clone"); + value + } +} +impl DebugGcTraceValue { + pub fn new(name: IStr, value: Val) -> Val { + let value = Self { + name, + value: Box::new(value), + }; + value.print("Constructed"); + Val::DebugGcTraceValue(value) } } @@ -335,7 +427,23 @@ pub enum Val { Num(f64), Arr(ArrValue), Obj(ObjValue), - Func(Rc), + Func(Gc), + DebugGcTraceValue(DebugGcTraceValue), +} +impl Finalize for Val {} +unsafe impl Trace for Val { + custom_trace!(this, { + match &this { + Val::Bool(_) => {} + Val::Null => {} + Val::Str(_) => {} + Val::Num(_) => {} + Val::Arr(a) => mark(a), + Val::Obj(o) => mark(o), + Val::Func(f) => mark(f), + Val::DebugGcTraceValue(v) => mark(v), + } + }); } macro_rules! matches_unwrap { @@ -368,7 +476,7 @@ impl Val { pub fn unwrap_num(self) -> Result { Ok(matches_unwrap!(self, Self::Num(v), v)) } - pub fn unwrap_func(self) -> Result> { + pub fn unwrap_func(self) -> Result> { Ok(matches_unwrap!(self, Self::Func(v), v)) } pub fn try_cast_bool(self, context: &'static str) -> Result { @@ -392,6 +500,7 @@ impl Val { Self::Bool(_) => ValType::Bool, Self::Null => ValType::Null, Self::Func(..) => ValType::Func, + Self::DebugGcTraceValue(v) => v.value.value_type(), } }