Skip to content

Commit

Permalink
feat(jrsonnet-evaluator): implement gc
Browse files Browse the repository at this point in the history
Some manual Trace/Finalize implementations can be replaced with derives
with Manishearth/rust-gc#116 getting merged

Signed-off-by: Yaroslav Bolyukin <[email protected]>
  • Loading branch information
CertainLach committed Jun 5, 2021
1 parent 1dda2e4 commit b28a33f
Show file tree
Hide file tree
Showing 17 changed files with 656 additions and 304 deletions.
3 changes: 1 addition & 2 deletions crates/jrsonnet-evaluator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion crates/jrsonnet-evaluator/src/builtin/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions crates/jrsonnet-evaluator/src/builtin/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
31 changes: 28 additions & 3 deletions crates/jrsonnet-evaluator/src/builtin/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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))?)
})
}

Expand Down Expand Up @@ -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<Val> {
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<Val> {
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<Val> {
parse_args!(context, "base64", args, 1, [
0, input: ty!((string | (Array<number>)));
Expand Down
14 changes: 7 additions & 7 deletions crates/jrsonnet-evaluator/src/builtin/sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -59,13 +59,13 @@ fn get_sort_type<T>(
Ok(sort_type)
}

pub fn sort(ctx: Context, mut values: Rc<Vec<Val>>, key_getter: &FuncVal) -> Result<Rc<Vec<Val>>> {
pub fn sort(ctx: Context, values: Gc<Vec<Val>>, key_getter: &FuncVal) -> Result<Gc<Vec<Val>>> {
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),
Expand All @@ -77,7 +77,7 @@ pub fn sort(ctx: Context, mut values: Rc<Vec<Val>>, 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() {
Expand All @@ -98,6 +98,6 @@ pub fn sort(ctx: Context, mut values: Rc<Vec<Val>>, 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()))
}
}
90 changes: 27 additions & 63 deletions crates/jrsonnet-evaluator/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -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<FxHashMap<IStr, LazyBinding>>);
impl ContextCreator {
pub fn create(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<Context> {
Expand All @@ -20,6 +21,7 @@ impl ContextCreator {
}
}

#[derive(Trace, Finalize)]
struct ContextInternals {
dollar: Option<ObjValue>,
this: Option<ObjValue>,
Expand All @@ -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<ContextInternals>);
#[derive(Debug, Clone, Trace, Finalize)]
pub struct Context(Gc<ContextInternals>);
impl Context {
pub fn new_future() -> FutureWrapper<Self> {
FutureWrapper::new()
Expand All @@ -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,
Expand All @@ -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)
}

Expand All @@ -96,40 +95,21 @@ impl Context {
new_this: Option<ObjValue>,
new_super_obj: Option<ObjValue>,
) -> 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<IStr, LazyVal>) -> Self {
let new_this = self.0.this.clone();
Expand Down Expand Up @@ -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<ContextInternals>);
#[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)
}
}
14 changes: 7 additions & 7 deletions crates/jrsonnet-evaluator/src/dynamic.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
use std::{cell::RefCell, rc::Rc};
use gc::{Finalize, Gc, GcCell, Trace};

#[derive(Clone)]
pub struct FutureWrapper<V>(pub Rc<RefCell<Option<V>>>);
impl<T> FutureWrapper<T> {
#[derive(Clone, Trace, Finalize)]
pub struct FutureWrapper<V: Trace + 'static>(pub Gc<GcCell<Option<V>>>);
impl<T: Trace + 'static> FutureWrapper<T> {
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<T: Clone> FutureWrapper<T> {
impl<T: Clone + Trace + 'static> FutureWrapper<T> {
pub fn unwrap(&self) -> T {
self.0.borrow().as_ref().cloned().unwrap()
}
}

impl<T> Default for FutureWrapper<T> {
impl<T: Trace + 'static> Default for FutureWrapper<T> {
fn default() -> Self {
Self::new()
}
Expand Down
12 changes: 8 additions & 4 deletions crates/jrsonnet-evaluator/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -88,13 +89,16 @@ pub enum Error {
ImportSyntaxError {
path: Rc<PathBuf>,
source_code: IStr,
#[unsafe_ignore_trace]
error: Box<jrsonnet_parser::ParseError>,
},

#[error("runtime error: {0}")]
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")]
Expand Down Expand Up @@ -142,15 +146,15 @@ impl From<Error> for LocError {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Trace, Finalize)]
pub struct StackTraceElement {
pub location: Option<ExprLocation>,
pub desc: String,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Trace, Finalize)]
pub struct StackTrace(pub Vec<StackTraceElement>);

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Trace, Finalize)]
pub struct LocError(Box<(Error, StackTrace)>);
impl LocError {
pub fn new(e: Error) -> Self {
Expand Down
Loading

0 comments on commit b28a33f

Please sign in to comment.