feat(tvix/eval): Implement builtins.deepSeq

This is done via a new `deepForce` function on Value. Since values can
be cyclical (for example, see the test-case), we need to do some extra
work to avoid RefCell borrow errors if we ever hit a graph cycle:

While deep-forcing values, we keep a set of thunks that we have
already seen and avoid doing any work on the same thunk twice. The set
is encapsulated in a separate type to stop potentially invalid
pointers from leaking out.

Finally, since deep_force is conceptually similar to
`VM::force_for_output` (but more suited to usage in eval since it
doesn't clone the values) this removes the latter, replacing it with
the former.

Co-Authored-By: Vincent Ambo <tazjin@tvl.su>
Change-Id: Iefddefcf09fae3b6a4d161a5873febcff54b9157
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7000
Tested-by: BuildkiteCI
Reviewed-by: grfn <grfn@gws.fyi>
Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
Griffin Smith 2022-10-12 22:47:23 -04:00 committed by grfn
parent 8724d2fff8
commit d4fa3152e9
10 changed files with 100 additions and 62 deletions

View file

@ -27,6 +27,8 @@ pub use path::canon_path;
pub use string::NixString;
pub use thunk::Thunk;
use self::thunk::ThunkSet;
#[warn(variant_size_differences)]
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
@ -341,6 +343,49 @@ impl Value {
_ => Ok(ForceResult::Immediate(self)),
}
}
/// Ensure `self` is *deeply* forced, including all recursive sub-values
pub(crate) fn deep_force(
&self,
vm: &mut VM,
thunk_set: &mut ThunkSet,
) -> Result<(), ErrorKind> {
match self {
Value::Null
| Value::Bool(_)
| Value::Integer(_)
| Value::Float(_)
| Value::String(_)
| Value::Path(_)
| Value::Closure(_)
| Value::Builtin(_)
| Value::AttrNotFound
| Value::Blueprint(_)
| Value::DeferredUpvalue(_)
| Value::UnresolvedPath(_) => Ok(()),
Value::Attrs(a) => {
for (_, v) in a.iter() {
v.deep_force(vm, thunk_set)?;
}
Ok(())
}
Value::List(l) => {
for val in l {
val.deep_force(vm, thunk_set)?;
}
Ok(())
}
Value::Thunk(thunk) => {
if !thunk_set.insert(thunk) {
return Ok(());
}
thunk.force(vm)?;
let value = thunk.value().clone();
value.deep_force(vm, thunk_set)
}
}
}
}
impl Display for Value {