diff --git a/Cargo.lock b/Cargo.lock index 459a98ce..e31f694f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,12 +99,6 @@ dependencies = [ "clap", ] -[[package]] -name = "closure" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6173fd61b610d15a7566dd7b7620775627441c4ab9dac8906e17cb93a24b782" - [[package]] name = "hashbrown" version = "0.9.1" @@ -164,6 +158,7 @@ version = "0.3.8" dependencies = [ "clap", "jrsonnet-evaluator", + "jrsonnet-gc", "jrsonnet-parser", ] @@ -175,7 +170,7 @@ dependencies = [ "anyhow", "base64", "bincode", - "closure", + "jrsonnet-gc", "jrsonnet-interner", "jrsonnet-parser", "jrsonnet-stdlib", @@ -188,10 +183,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "jrsonnet-gc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68da8bc2f00117b1373bb8877af03b1d391e4c4800e6585d7279e5b99c919dde" +dependencies = [ + "jrsonnet-gc-derive", +] + +[[package]] +name = "jrsonnet-gc-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcba9c387b64b054f06cc4d724905296e21edeeb7506847f3299117a2d92d12" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "jrsonnet-interner" version = "0.3.8" dependencies = [ + "jrsonnet-gc", "rustc-hash", "serde", ] @@ -200,6 +217,7 @@ dependencies = [ name = "jrsonnet-parser" version = "0.3.8" dependencies = [ + "jrsonnet-gc", "jrsonnet-interner", "jrsonnet-stdlib", "peg", @@ -215,6 +233,7 @@ version = "0.3.8" name = "jrsonnet-types" version = "0.3.8" dependencies = [ + "jrsonnet-gc", "peg", ] @@ -223,6 +242,7 @@ name = "jsonnet" version = "0.3.8" dependencies = [ "jrsonnet-evaluator", + "jrsonnet-gc", "jrsonnet-parser", ] @@ -404,6 +424,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "termcolor" version = "1.1.2" diff --git a/bindings/jsonnet/Cargo.toml b/bindings/jsonnet/Cargo.toml index 3c12c9a0..52e64505 100644 --- a/bindings/jsonnet/Cargo.toml +++ b/bindings/jsonnet/Cargo.toml @@ -10,6 +10,7 @@ publish = false [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.3.8" } jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.3.8" } +jrsonnet-gc = { version = "0.4.2", features = ["derive"] } [lib] crate-type = ["cdylib"] diff --git a/bindings/jsonnet/src/native.rs b/bindings/jsonnet/src/native.rs index 99d4d9a0..fdb19aa9 100644 --- a/bindings/jsonnet/src/native.rs +++ b/bindings/jsonnet/src/native.rs @@ -1,8 +1,14 @@ -use jrsonnet_evaluator::{error::Error, native::NativeCallback, EvaluationState, Val}; +use jrsonnet_evaluator::{ + error::{Error, LocError}, + native::{NativeCallback, NativeCallbackHandler}, + EvaluationState, Val, +}; +use jrsonnet_gc::{unsafe_empty_trace, Finalize, Gc, Trace}; use jrsonnet_parser::{Param, ParamsDesc}; use std::{ ffi::{c_void, CStr}, os::raw::{c_char, c_int}, + path::Path, rc::Rc, }; @@ -12,6 +18,39 @@ type JsonnetNativeCallback = unsafe extern "C" fn( success: *mut c_int, ) -> *mut Val; +struct JsonnetNativeCallbackHandler { + ctx: *const c_void, + cb: JsonnetNativeCallback, +} +impl Finalize for JsonnetNativeCallbackHandler {} +unsafe impl Trace for JsonnetNativeCallbackHandler { + unsafe_empty_trace!(); +} +impl NativeCallbackHandler for JsonnetNativeCallbackHandler { + fn call(&self, _from: Option>, args: &[Val]) -> Result { + let mut n_args = Vec::new(); + for a in args { + n_args.push(Some(Box::new(a.clone()))); + } + n_args.push(None); + let mut success = 1; + let v = unsafe { + (self.cb)( + self.ctx, + &n_args as *const _ as *const *const Val, + &mut success, + ) + }; + let v = unsafe { *Box::from_raw(v) }; + if success == 1 { + Ok(v) + } else { + let e = v.try_cast_str("native error").expect("error msg"); + Err(Error::RuntimeError(e).into()) + } + } +} + /// # Safety #[no_mangle] pub unsafe extern "C" fn jsonnet_native_callback( @@ -35,21 +74,9 @@ pub unsafe extern "C" fn jsonnet_native_callback( vm.add_native( name, - Rc::new(NativeCallback::new(params, move |_caller, args| { - let mut n_args = Vec::new(); - for a in args { - n_args.push(Some(Box::new(a.clone()))); - } - n_args.push(None); - let mut success = 1; - let v = cb(ctx, &n_args as *const _ as *const *const Val, &mut success); - let v = *Box::from_raw(v); - if success == 1 { - Ok(v) - } else { - let e = v.try_cast_str("native error").expect("error msg"); - Err(Error::RuntimeError(e).into()) - } - })), + Gc::new(NativeCallback::new( + params, + Box::new(JsonnetNativeCallbackHandler { ctx, cb }), + )), ) } diff --git a/bindings/jsonnet/src/val_make.rs b/bindings/jsonnet/src/val_make.rs index 7f5c809d..90ae53a1 100644 --- a/bindings/jsonnet/src/val_make.rs +++ b/bindings/jsonnet/src/val_make.rs @@ -1,10 +1,10 @@ //! Create values in VM use jrsonnet_evaluator::{ArrValue, EvaluationState, ObjValue, Val}; +use jrsonnet_gc::Gc; use std::{ ffi::CStr, os::raw::{c_char, c_double, c_int}, - rc::Rc, }; /// # Safety @@ -38,7 +38,7 @@ pub extern "C" fn jsonnet_json_make_null(_vm: &EvaluationState) -> *mut Val { #[no_mangle] pub extern "C" fn jsonnet_json_make_array(_vm: &EvaluationState) -> *mut Val { - Box::into_raw(Box::new(Val::Arr(ArrValue::Eager(Rc::new(Vec::new()))))) + Box::into_raw(Box::new(Val::Arr(ArrValue::Eager(Gc::new(Vec::new()))))) } #[no_mangle] diff --git a/bindings/jsonnet/src/val_modify.rs b/bindings/jsonnet/src/val_modify.rs index 93d1cb27..52ec7e14 100644 --- a/bindings/jsonnet/src/val_modify.rs +++ b/bindings/jsonnet/src/val_modify.rs @@ -3,8 +3,9 @@ //! In jrsonnet every value is immutable, and this code is probally broken use jrsonnet_evaluator::{ArrValue, EvaluationState, LazyBinding, LazyVal, ObjMember, Val}; +use jrsonnet_gc::Gc; use jrsonnet_parser::Visibility; -use std::{ffi::CStr, os::raw::c_char, rc::Rc}; +use std::{ffi::CStr, os::raw::c_char}; /// # Safety /// @@ -22,7 +23,7 @@ pub unsafe extern "C" fn jsonnet_json_array_append( new.push(item); } new.push(LazyVal::new_resolved(val.clone())); - *arr = Val::Arr(ArrValue::Lazy(Rc::new(new))); + *arr = Val::Arr(ArrValue::Lazy(Gc::new(new))); } _ => panic!("should receive array"), } diff --git a/cmds/jrsonnet/Cargo.toml b/cmds/jrsonnet/Cargo.toml index 0dc45817..329e831a 100644 --- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -8,15 +8,14 @@ edition = "2018" publish = false [features] -default = [] +default = ["mimalloc"] # Use mimalloc as allocator -mimalloc = [] +mimalloc = ["mimallocator"] [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.3.8" } jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.3.8" } jrsonnet-cli = { path = "../../crates/jrsonnet-cli", version = "0.3.8" } -# TODO: Fix mimalloc compile errors, and use them mimallocator = { version = "0.1.3", optional = true } thiserror = "1.0" diff --git a/cmds/jrsonnet/src/main.rs b/cmds/jrsonnet/src/main.rs index 47047b26..8f8107e9 100644 --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -1,5 +1,5 @@ use clap::{AppSettings, Clap, IntoApp}; -use jrsonnet_cli::{ConfigureState, GeneralOpts, InputOpts, ManifestOpts, OutputOpts}; +use jrsonnet_cli::{ConfigureState, GcOpts, GeneralOpts, InputOpts, ManifestOpts, OutputOpts}; use jrsonnet_evaluator::{error::LocError, EvaluationState, ManifestFormat}; use std::{ fs::{create_dir_all, File}, @@ -61,6 +61,8 @@ struct Opts { output: OutputOpts, #[clap(flatten)] debug: DebugOpts, + #[clap(flatten)] + gc: GcOpts, } fn main() { @@ -114,6 +116,7 @@ impl From for Error { } fn main_catch(opts: Opts) -> bool { + let _printer = opts.gc.stats_printer(); let state = EvaluationState::default(); if let Err(e) = main_real(&state, opts) { if let Error::Evaluation(e) = e { @@ -127,6 +130,7 @@ fn main_catch(opts: Opts) -> bool { } fn main_real(state: &EvaluationState, opts: Opts) -> Result<(), Error> { + opts.gc.configure_global(); opts.general.configure(&state)?; opts.manifest.configure(&state)?; diff --git a/crates/jrsonnet-cli/Cargo.toml b/crates/jrsonnet-cli/Cargo.toml index a4531bda..cdc4b841 100644 --- a/crates/jrsonnet-cli/Cargo.toml +++ b/crates/jrsonnet-cli/Cargo.toml @@ -10,6 +10,7 @@ publish = false [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.3.6", features = ["explaining-traces"] } jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.3.6" } +jrsonnet-gc = { version = "0.4.2", features = ["derive", "unstable-config", "unstable-stats"] } [dependencies.clap] git = "https://github.com/clap-rs/clap" diff --git a/crates/jrsonnet-cli/src/lib.rs b/crates/jrsonnet-cli/src/lib.rs index e69c64b8..4bb7945e 100644 --- a/crates/jrsonnet-cli/src/lib.rs +++ b/crates/jrsonnet-cli/src/lib.rs @@ -95,3 +95,55 @@ impl ConfigureState for GeneralOpts { Ok(()) } } + +#[derive(Clap)] +#[clap(help_heading = "GARBAGE COLLECTION")] +pub struct GcOpts { + /// Min bytes allocated to start garbage collection + #[clap(long, default_value = "20000000")] + gc_initial_threshold: usize, + /// How much heap should grow after unsuccessful garbage collection + #[clap(long)] + gc_used_space_ratio: Option, + /// Do not skip gc on exit + #[clap(long)] + gc_collect_on_exit: bool, + /// Print gc stats before exit + #[clap(long)] + gc_print_stats: bool, + /// Force garbage collection before printing stats + /// Useful for checking for memory leaks + /// Does nothing useless --gc-print-stats is specified + #[clap(long)] + gc_collect_before_printing_stats: bool, +} +impl GcOpts { + pub fn stats_printer(&self) -> Option { + self.gc_print_stats + .then(|| GcStatsPrinter(self.gc_collect_before_printing_stats)) + } + pub fn configure_global(&self) { + jrsonnet_gc::configure(|config| { + config.leak_on_drop = !self.gc_collect_on_exit; + config.threshold = self.gc_initial_threshold; + if let Some(used_space_ratio) = self.gc_used_space_ratio { + config.used_space_ratio = used_space_ratio; + } + }); + } +} +pub struct GcStatsPrinter(bool); +impl Drop for GcStatsPrinter { + fn drop(&mut self) { + if self.0 { + jrsonnet_gc::force_collect() + } + eprintln!("=== GC STATS ==="); + jrsonnet_gc::configure(|c| { + eprintln!("Final threshold: {:?}", c.threshold); + }); + let stats = jrsonnet_gc::stats(); + eprintln!("Collections performed: {}", stats.collections_performed); + eprintln!("Bytes still allocated: {}", stats.bytes_allocated); + } +} diff --git a/crates/jrsonnet-evaluator/Cargo.toml b/crates/jrsonnet-evaluator/Cargo.toml index cce3704a..6fee466e 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.8" } jrsonnet-types = { path = "../jrsonnet-types", version = "0.3.8" } pathdiff = "0.2.0" -closure = "0.3.0" - md5 = "0.7.0" base64 = "0.13.0" rustc-hash = "1.1.0" thiserror = "1.0" +jrsonnet-gc = { version = "0.4.2", features = ["derive"] } [dependencies.anyhow] version = "1.0" diff --git a/crates/jrsonnet-evaluator/README.md b/crates/jrsonnet-evaluator/README.md index 48e66504..a97bc7fa 100644 --- a/crates/jrsonnet-evaluator/README.md +++ b/crates/jrsonnet-evaluator/README.md @@ -6,9 +6,6 @@ Interpreter for parsed jsonnet tree jsonnet stdlib is embedded into evaluator, but there is different modes for this: -- `codegenerated-stdlib` - - generates source code for reproducing stdlib AST ([Example](https://gist.githubusercontent.com/CertainLach/7b3149df556f3406f5e9368aaa9f32ec/raw/0c80d8ab9aa7b9288c6219a2779cb2ab37287669/a.rs)) - - fastest on interpretation, slowest on compilation (it takes more than 5 minutes to optimize them by llvm) - `serialized-stdlib` - serializes standard library AST using serde - slower than `codegenerated-stdlib` at runtime, but have no compilation speed penality @@ -23,8 +20,6 @@ Because of `codegenerated-stdlib` compilation slowdown, `serialized-stdlib` is u Can also be run via `cargo bench` ```markdown -# codegenerated-stdlib -test tests::bench_codegen ... bench: 401,696 ns/iter (+/- 38,521) # serialized-stdlib test tests::bench_serialize ... bench: 1,763,999 ns/iter (+/- 76,211) # none @@ -34,7 +29,3 @@ test tests::bench_parse ... bench: 7,206,164 ns/iter (+/- 1,067,418) ## Intrinsics Some functions from stdlib are implemented as intrinsics - -### Intrinsic handling - -If indexed jsonnet object has field '__intrinsic_namespace__' of type 'string', then any not found field/method is resolved as `Val::Intrinsic(__intrinsic_namespace__, name)` diff --git a/crates/jrsonnet-evaluator/src/builtin/format.rs b/crates/jrsonnet-evaluator/src/builtin/format.rs index f931805f..4a69f30b 100644 --- a/crates/jrsonnet-evaluator/src/builtin/format.rs +++ b/crates/jrsonnet-evaluator/src/builtin/format.rs @@ -2,11 +2,13 @@ #![allow(clippy::too_many_arguments)] use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val}; +use jrsonnet_gc::Trace; use jrsonnet_interner::IStr; use jrsonnet_types::ValType; use thiserror::Error; -#[derive(Debug, Clone, Error)] +#[derive(Debug, Clone, Error, Trace)] +#[trivially_drop] 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 393f31f8..dbc8d6f8 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 jrsonnet_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,27 @@ 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"); + jrsonnet_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::create(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..09a4e1bf 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 jrsonnet_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..30c98ce1 100644 --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -1,13 +1,15 @@ 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 jrsonnet_gc::{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)] +#[trivially_drop] pub struct ContextCreator(pub Context, pub FutureWrapper>); impl ContextCreator { pub fn create(&self, this: Option, super_obj: Option) -> Result { @@ -20,23 +22,23 @@ impl ContextCreator { } } +#[derive(Trace)] +#[trivially_drop] struct ContextInternals { dollar: Option, this: Option, super_obj: Option, - bindings: LayeredHashMap, + bindings: LayeredHashMap, } 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)] +#[trivially_drop] +pub struct Context(Gc); impl Context { pub fn new_future() -> FutureWrapper { FutureWrapper::new() @@ -55,7 +57,7 @@ impl Context { } pub fn new() -> Self { - Self(Rc::new(ContextInternals { + Self(Gc::new(ContextInternals { dollar: None, this: None, super_obj: None, @@ -81,7 +83,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 +98,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 +149,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..d9092c24 100644 --- a/crates/jrsonnet-evaluator/src/dynamic.rs +++ b/crates/jrsonnet-evaluator/src/dynamic.rs @@ -1,23 +1,24 @@ -use std::{cell::RefCell, rc::Rc}; +use jrsonnet_gc::{Gc, GcCell, Trace}; -#[derive(Clone)] -pub struct FutureWrapper(pub Rc>>); -impl FutureWrapper { +#[derive(Clone, Trace)] +#[trivially_drop] +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 09c60c46..befb5c8f 100644 --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -2,6 +2,7 @@ use crate::{ builtin::{format::FormatError, sort::SortError}, typed::TypeLocError, }; +use jrsonnet_gc::Trace; use jrsonnet_interner::IStr; use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType}; use jrsonnet_types::ValType; @@ -11,7 +12,8 @@ use std::{ }; use thiserror::Error; -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug, Clone, Trace)] +#[trivially_drop] pub enum Error { #[error("intrinsic not found: {0}")] IntrinsicNotFound(IStr), @@ -91,6 +93,7 @@ pub enum Error { ImportSyntaxError { path: Rc, source_code: IStr, + #[unsafe_ignore_trace] error: Box, }, @@ -98,6 +101,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")] @@ -145,15 +150,18 @@ impl From for LocError { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Trace)] +#[trivially_drop] pub struct StackTraceElement { pub location: Option, pub desc: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace)] +#[trivially_drop] pub struct StackTrace(pub Vec); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace)] +#[trivially_drop] 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 3040b5a1..795d3865 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 jrsonnet_gc::{Gc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{ ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprLocation, FieldMember, @@ -11,7 +12,7 @@ use jrsonnet_parser::{ }; use jrsonnet_types::ValType; use rustc_hash::{FxHashMap, FxHasher}; -use std::{collections::HashMap, hash::BuildHasherDefault, rc::Rc}; +use std::{collections::HashMap, hash::BuildHasherDefault}; pub fn evaluate_binding_in_future( b: &BindSpec, @@ -20,17 +21,49 @@ 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(), - )) + + #[derive(Trace)] + #[trivially_drop] + struct LazyMethodBinding { + context_creator: FutureWrapper, + name: IStr, + params: ParamsDesc, + value: LocExpr, + } + 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()) + #[derive(Trace)] + #[trivially_drop] + struct LazyNamedBinding { + context_creator: FutureWrapper, + name: IStr, + value: LocExpr, + } + 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 +72,114 @@ pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (IStr, let b = b.clone(); if let Some(params) = &b.params { let params = params.clone(); + + #[derive(Trace)] + #[trivially_drop] + struct BindableMethodLazyVal { + this: Option, + super_obj: Option, + + context_creator: ContextCreator, + name: IStr, + params: ParamsDesc, + value: LocExpr, + } + 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)] + #[trivially_drop] + 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, + super_obj, + + 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 { + #[derive(Trace)] + #[trivially_drop] + struct BindableNamedLazyVal { + this: Option, + super_obj: Option, + + context_creator: ContextCreator, + name: IStr, + value: LocExpr, + } + 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)] + #[trivially_drop] + 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 +215,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 +370,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 +385,37 @@ pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Resu continue; } let name = name.unwrap(); + + #[derive(Trace)] + #[trivially_drop] + 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 +431,69 @@ pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Resu continue; } let name = name.unwrap(); + #[derive(Trace)] + #[trivially_drop] + 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()); + #[derive(Trace)] + #[trivially_drop] + struct ObjectAssert { + context_creator: ContextCreator, + assert: AssertStmt, + } + 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 +527,38 @@ pub fn evaluate_object(context: Context, object: &ObjBody) -> Result { match key { Val::Null => {} Val::Str(n) => { + #[derive(Trace)] + #[trivially_drop] + 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, + value: obj.value.clone(), + }))), location: obj.value.1.clone(), }, ); @@ -381,7 +569,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 +674,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 +737,22 @@ 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? + #[derive(Trace)] + #[trivially_drop] + struct ArrayElement { + context: Context, + item: LocExpr, + } + 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 +762,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 +775,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..c83b5b5a 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 jrsonnet_gc::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,24 @@ 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))) + #[derive(Trace)] + #[trivially_drop] + struct EvaluateLazyVal { + context: Context, + expr: LocExpr, + } + 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 +104,31 @@ 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)] + #[trivially_drop] + 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 +162,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..8721b169 100644 --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -1,7 +1,8 @@ use crate::{ error::{Error::*, LocError, Result}, - throw, Context, LazyBinding, LazyVal, ObjMember, ObjValue, Val, + throw, LazyBinding, LazyVal, ObjMember, ObjValue, Val, }; +use jrsonnet_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) => Self::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 7dc53eb9..5ce0cb98 100644 --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -26,6 +26,8 @@ use error::{Error::*, LocError, Result, StackTraceElement}; pub use evaluate::*; pub use function::parse_function_call; pub use import::*; +use jrsonnet_gc::{Finalize, Gc, Trace}; +pub use jrsonnet_interner::IStr; use jrsonnet_parser::*; use native::NativeCallback; pub use obj::*; @@ -41,13 +43,12 @@ use std::{ use trace::{offset_to_location, CodeLocation, CompactFormat, TraceFormat}; pub use val::*; -// Re-exports -pub use jrsonnet_interner::IStr; - -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), } @@ -59,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()), } } @@ -73,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 @@ -272,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) } @@ -451,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); } @@ -487,7 +488,10 @@ impl EvaluationState { #[cfg(test)] pub mod tests { use super::Val; - use crate::{error::Error::*, primitive_equals, EvaluationState}; + use crate::{ + error::Error::*, native::NativeCallbackHandler, primitive_equals, EvaluationState, + }; + use jrsonnet_gc::{Finalize, Gc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::*; use std::{ @@ -919,23 +923,29 @@ pub mod tests { let evaluator = EvaluationState::default(); evaluator.with_stdlib(); + + #[derive(Trace, Finalize)] + struct NativeAdd; + impl NativeCallbackHandler for NativeAdd { + fn call(&self, from: Option>, args: &[Val]) -> crate::error::Result { + assert_eq!( + &from.unwrap() as &Path, + &PathBuf::from("native_caller.jsonnet") + ); + match (&args[0], &args[1]) { + (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)), + (_, _) => unreachable!(), + } + } + } evaluator.settings_mut().ext_natives.insert( "native_add".into(), - Rc::new(NativeCallback::new( + Gc::new(NativeCallback::new( ParamsDesc(Rc::new(vec![ Param("a".into(), None), Param("b".into(), None), ])), - |caller, args| { - assert_eq!( - &caller.unwrap() as &Path, - &PathBuf::from("native_caller.jsonnet") - ); - match (&args[0], &args[1]) { - (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)), - (_, _) => unreachable!(), - } - }, + Box::new(NativeAdd), )), ); evaluator.evaluate_snippet_raw( diff --git a/crates/jrsonnet-evaluator/src/map.rs b/crates/jrsonnet-evaluator/src/map.rs index d026ff4e..0b1a7f75 100644 --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -1,31 +1,29 @@ +use jrsonnet_gc::{Gc, Trace}; use jrsonnet_interner::IStr; use rustc_hash::FxHashMap; -use std::rc::Rc; -#[derive(Default, Debug)] -struct LayeredHashMapInternals { - parent: Option>, - current: FxHashMap, +use crate::LazyVal; + +#[derive(Trace)] +#[trivially_drop] +pub struct LayeredHashMapInternals { + parent: Option, + current: FxHashMap, } -#[derive(Debug)] -pub struct LayeredHashMap(Rc>); +#[derive(Trace)] +#[trivially_drop] +pub struct LayeredHashMap(Gc); -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, - })), - } +impl LayeredHashMap { + pub fn extend(self, new_layer: FxHashMap) -> Self { + Self(Gc::new(LayeredHashMapInternals { + parent: Some(self), + current: new_layer, + })) } - pub fn get(&self, key: &IStr) -> Option<&V> { + pub fn get(&self, key: &IStr) -> Option<&LazyVal> { (self.0) .current .get(key) @@ -33,15 +31,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 580a9537..dbca7204 100644 --- a/crates/jrsonnet-evaluator/src/native.rs +++ b/crates/jrsonnet-evaluator/src/native.rs @@ -1,27 +1,28 @@ #![allow(clippy::type_complexity)] use crate::{error::Result, Val}; +use jrsonnet_gc::Trace; use jrsonnet_parser::ParamsDesc; use std::fmt::Debug; use std::path::Path; use std::rc::Rc; +pub trait NativeCallbackHandler: Trace { + fn call(&self, from: Option>, args: &[Val]) -> Result; +} + +#[derive(Trace)] +#[trivially_drop] 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..ec2e86a6 100644 --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -1,11 +1,13 @@ -use crate::{evaluate_add_op, evaluate_assert, Context, LazyBinding, Result, Val}; +use crate::{evaluate_add_op, LazyBinding, Result, Val}; +use jrsonnet_gc::{Gc, GcCell, Trace}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{AssertStmt, ExprLocation, Visibility}; +use jrsonnet_parser::{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)] +#[trivially_drop] pub struct ObjMember { pub add: bool, pub visibility: Visibility, @@ -13,21 +15,26 @@ 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)] +#[trivially_drop] 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)] +#[trivially_drop] +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 +62,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 +93,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 +200,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 +241,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 +257,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..5d139033 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 jrsonnet_gc::Trace; use jrsonnet_parser::ExprLocation; use jrsonnet_types::{ComplexValType, ValType}; use thiserror::Error; @@ -20,7 +21,8 @@ macro_rules! unwrap_type { }}; } -#[derive(Debug, Error, Clone)] +#[derive(Debug, Error, Clone, Trace)] +#[trivially_drop] pub enum TypeError { #[error("expected {0}, got {1}")] ExpectedGot(ComplexValType, ValType), @@ -37,7 +39,8 @@ impl From for LocError { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace)] +#[trivially_drop] pub struct TypeLocError(Box, ValuePathStack); impl From for TypeLocError { fn from(e: TypeError) -> Self { @@ -59,7 +62,8 @@ impl Display for TypeLocError { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace)] +#[trivially_drop] pub struct TypeLocErrorList(Vec); impl Display for TypeLocErrorList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -122,7 +126,8 @@ impl CheckType for ValType { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Trace)] +#[trivially_drop] enum ValuePathItem { Field(Rc), Index(u64), @@ -137,7 +142,8 @@ impl Display for ValuePathItem { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Trace)] +#[trivially_drop] 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..dc9423b5 100644 --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -3,52 +3,67 @@ 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 jrsonnet_gc::{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; +} + +#[derive(Trace)] +#[trivially_drop] enum LazyValInternals { Computed(Val), - Waiting(Box Result>), + Errored(LocError), + Waiting(Box), + Pending, } -#[derive(Clone)] -pub struct LazyVal(Rc>); + +#[derive(Clone, Trace)] +#[trivially_drop] +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()), + 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 +71,12 @@ 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)] +#[trivially_drop] pub struct FuncDesc { pub name: IStr, pub ctx: Context, @@ -68,14 +84,15 @@ pub struct FuncDesc { pub body: LocExpr, } -#[derive(Debug)] +#[derive(Debug, Trace)] +#[trivially_drop] 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 { @@ -172,15 +189,16 @@ pub enum ManifestFormat { String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace)] +#[trivially_drop] pub enum ArrValue { - Lazy(Rc>), - Eager(Rc>), + Lazy(Gc>), + Eager(Gc>), Extended(Box<(Self, Self)>), } 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 +249,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 +264,7 @@ impl ArrValue { for item in self.iter() { out.push(item?); } - Rc::new(out) + Gc::new(out) } }) } @@ -272,12 +290,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 +308,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 +321,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,17 +335,77 @@ 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 = Self { + name: self.name.clone(), + value: self.value.clone(), + }; + value.print("I'm clone"); + value + } +} +impl DebugGcTraceValue { + pub fn create(name: IStr, value: Val) -> Val { + let value = Self { + name, + value: Box::new(value), + }; + value.print("Constructed"); + Val::DebugGcTraceValue(value) } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace)] +#[trivially_drop] pub enum Val { Bool(bool), Null, @@ -335,7 +413,8 @@ pub enum Val { Num(f64), Arr(ArrValue), Obj(ObjValue), - Func(Rc), + Func(Gc), + DebugGcTraceValue(DebugGcTraceValue), } macro_rules! matches_unwrap { @@ -368,7 +447,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 +471,7 @@ impl Val { Self::Bool(_) => ValType::Bool, Self::Null => ValType::Null, Self::Func(..) => ValType::Func, + Self::DebugGcTraceValue(v) => v.value.value_type(), } } diff --git a/crates/jrsonnet-interner/Cargo.toml b/crates/jrsonnet-interner/Cargo.toml index 6518db0f..3e056f17 100644 --- a/crates/jrsonnet-interner/Cargo.toml +++ b/crates/jrsonnet-interner/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" [dependencies] serde = { version = "1.0" } rustc-hash = "1.1.0" +jrsonnet-gc = { version = "0.4.2", features = ["derive"] } diff --git a/crates/jrsonnet-interner/src/lib.rs b/crates/jrsonnet-interner/src/lib.rs index 51a0a0d2..88dbf185 100644 --- a/crates/jrsonnet-interner/src/lib.rs +++ b/crates/jrsonnet-interner/src/lib.rs @@ -1,3 +1,4 @@ +use jrsonnet_gc::{unsafe_empty_trace, Finalize, Trace}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use std::{ @@ -10,6 +11,10 @@ use std::{ #[derive(Clone, PartialOrd, Ord, Eq)] pub struct IStr(Rc); +impl Finalize for IStr {} +unsafe impl Trace for IStr { + unsafe_empty_trace!(); +} impl Deref for IStr { type Target = str; diff --git a/crates/jrsonnet-parser/Cargo.toml b/crates/jrsonnet-parser/Cargo.toml index 4ae5a288..96f4551d 100644 --- a/crates/jrsonnet-parser/Cargo.toml +++ b/crates/jrsonnet-parser/Cargo.toml @@ -18,6 +18,7 @@ peg = "0.7.0" unescape = "0.1.0" serde = { version = "1.0", features = ["derive", "rc"], optional = true } +jrsonnet-gc = { version = "0.4.2", features = ["derive"] } [dev-dependencies] jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.8" } diff --git a/crates/jrsonnet-parser/src/expr.rs b/crates/jrsonnet-parser/src/expr.rs index d42a2d4d..5f970765 100644 --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -1,3 +1,4 @@ +use jrsonnet_gc::{unsafe_empty_trace, Finalize, Trace}; use jrsonnet_interner::IStr; #[cfg(feature = "deserialize")] use serde::Deserialize; @@ -12,7 +13,8 @@ use std::{ #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub enum FieldName { /// {fixed: 2} Fixed(IStr), @@ -22,7 +24,8 @@ pub enum FieldName { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Trace)] +#[trivially_drop] pub enum Visibility { /// : Normal, @@ -40,12 +43,14 @@ impl Visibility { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Trace)] +#[trivially_drop] pub struct AssertStmt(pub LocExpr, pub Option); #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct FieldMember { pub name: FieldName, pub plus: bool, @@ -56,7 +61,8 @@ pub struct FieldMember { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub enum Member { Field(FieldMember), BindStmt(BindSpec), @@ -65,13 +71,15 @@ pub enum Member { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Trace)] +#[trivially_drop] pub enum UnaryOpType { Plus, Minus, BitNot, Not, } + impl Display for UnaryOpType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use UnaryOpType::*; @@ -90,7 +98,8 @@ impl Display for UnaryOpType { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Trace)] +#[trivially_drop] pub enum BinaryOpType { Mul, Div, @@ -119,6 +128,7 @@ pub enum BinaryOpType { And, Or, } + impl Display for BinaryOpType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use BinaryOpType::*; @@ -152,7 +162,8 @@ impl Display for BinaryOpType { /// name, default value #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct Param(pub IStr, pub Option); /// Defined function parameters @@ -160,6 +171,14 @@ pub struct Param(pub IStr, pub Option); #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Debug, Clone, PartialEq)] pub struct ParamsDesc(pub Rc>); + +/// Safety: +/// AST is acyclic, and there should be no gc pointers +unsafe impl Trace for ParamsDesc { + unsafe_empty_trace!(); +} +impl Finalize for ParamsDesc {} + impl Deref for ParamsDesc { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -169,13 +188,16 @@ impl Deref for ParamsDesc { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct Arg(pub Option, pub LocExpr); #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct ArgsDesc(pub Vec); + impl Deref for ArgsDesc { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -185,7 +207,8 @@ impl Deref for ArgsDesc { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Trace)] +#[trivially_drop] pub struct BindSpec { pub name: IStr, pub params: Option, @@ -194,17 +217,20 @@ pub struct BindSpec { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct IfSpecData(pub LocExpr); #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct ForSpecData(pub IStr, pub LocExpr); #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub enum CompSpec { IfSpec(IfSpecData), ForSpec(ForSpecData), @@ -212,7 +238,8 @@ pub enum CompSpec { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct ObjComp { pub pre_locals: Vec, pub key: LocExpr, @@ -223,7 +250,8 @@ pub struct ObjComp { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub enum ObjBody { MemberList(Vec), ObjComp(ObjComp), @@ -231,7 +259,8 @@ pub enum ObjBody { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone, Copy, Trace)] +#[trivially_drop] pub enum LiteralType { This, Super, @@ -241,7 +270,8 @@ pub enum LiteralType { False, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub struct SliceDesc { pub start: Option, pub end: Option, @@ -251,7 +281,8 @@ pub struct SliceDesc { /// Syntax base #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Trace)] +#[trivially_drop] pub enum Expr { Literal(LiteralType), @@ -319,8 +350,10 @@ pub enum Expr { /// file, begin offset, end offset #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Trace)] +#[trivially_drop] pub struct ExprLocation(pub Rc, pub usize, pub usize); + impl Debug for ExprLocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2) @@ -332,6 +365,13 @@ impl Debug for ExprLocation { #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[derive(Clone, PartialEq)] pub struct LocExpr(pub Rc, pub Option); +/// Safety: +/// AST is acyclic, and there should be no gc pointers +unsafe impl Trace for LocExpr { + unsafe_empty_trace!(); +} +impl Finalize for LocExpr {} + impl Debug for LocExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if f.alternate() { diff --git a/crates/jrsonnet-types/Cargo.toml b/crates/jrsonnet-types/Cargo.toml index e05a8083..660ff8a8 100644 --- a/crates/jrsonnet-types/Cargo.toml +++ b/crates/jrsonnet-types/Cargo.toml @@ -8,3 +8,4 @@ edition = "2018" [dependencies] peg = "0.7.0" +jrsonnet-gc = { version = "0.4.2", features = ["derive"] } diff --git a/crates/jrsonnet-types/src/lib.rs b/crates/jrsonnet-types/src/lib.rs index d3efe63a..cd35a8f8 100644 --- a/crates/jrsonnet-types/src/lib.rs +++ b/crates/jrsonnet-types/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::redundant_closure_call)] +use jrsonnet_gc::Trace; use std::fmt::Display; #[macro_export] @@ -77,7 +78,8 @@ fn test() { ); } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)] +#[trivially_drop] pub enum ValType { Bool, Null, @@ -109,7 +111,8 @@ impl Display for ValType { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Trace)] +#[trivially_drop] pub enum ComplexValType { Any, Char, @@ -123,6 +126,7 @@ pub enum ComplexValType { Sum(Vec), SumRef(&'static [ComplexValType]), } + impl From for ComplexValType { fn from(s: ValType) -> Self { Self::Simple(s) diff --git a/flake.lock b/flake.lock index 3297a68d..0519043e 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1614513358, - "narHash": "sha256-LakhOx3S1dRjnh0b5Dg3mbZyH0ToC9I8Y2wKSkBaTzU=", + "lastModified": 1623875721, + "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=", "owner": "numtide", "repo": "flake-utils", - "rev": "5466c5bbece17adaab2d82fae80b46e807611bf3", + "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772", "type": "github" }, "original": { @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1615532953, - "narHash": "sha256-SWpaGjrp/INzorEqMz3HLi6Uuk9I0KAn4YS8B4n3q5g=", + "lastModified": 1625281901, + "narHash": "sha256-DkZDtTIPzhXATqIps2ifNFpnI+PTcfMYdcrx/oFm00Q=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "916ee862e87ac5ee2439f2fb7856386b4dc906ae", + "rev": "09c38c29f2c719cd76ca17a596c2fdac9e186ceb", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 7203887d..aafe525d 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ pname = "jrsonnet"; version = "0.1.0"; src = self; - cargoSha256 = "sha256-6VhaQi3L2LWzR0cq7oRG81MDbrKJbzSNPcvYSoQ5ISo="; + cargoSha256 = "sha256-cez8pJ/uwj+PHAPQwpSB4CKaxcP8Uvv8xguOrVXR2xE="; }; in { defaultPackage = jrsonnet;