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

@ -20,6 +20,7 @@
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashSet,
fmt::Display,
rc::Rc,
};
@ -86,13 +87,12 @@ impl Thunk {
})))
}
/// Evaluate the content of a thunk, potentially repeatedly, until
/// a non-thunk value is returned.
/// Evaluate the content of a thunk, potentially repeatedly, until a
/// non-thunk value is returned.
///
/// This will change the existing thunk (and thus all references
/// to it, providing memoization) through interior mutability. In
/// case of nested thunks, the intermediate thunk representations
/// are replaced.
/// This will change the existing thunk (and thus all references to it,
/// providing memoization) through interior mutability. In case of nested
/// thunks, the intermediate thunk representations are replaced.
pub fn force(&self, vm: &mut VM) -> Result<(), ErrorKind> {
loop {
let mut thunk_mut = self.0.borrow_mut();
@ -200,3 +200,20 @@ impl Display for Thunk {
}
}
}
/// A wrapper type for tracking which thunks have already been seen in a
/// context. This is necessary for cycle detection.
///
/// The inner `HashSet` is not available on the outside, as it would be
/// potentially unsafe to interact with the pointers in the set.
#[derive(Default)]
pub struct ThunkSet(HashSet<*mut ThunkRepr>);
impl ThunkSet {
/// Check whether the given thunk has already been seen. Will mark the thunk
/// as seen otherwise.
pub fn insert(&mut self, thunk: &Thunk) -> bool {
let ptr: *mut ThunkRepr = thunk.0.as_ptr();
self.0.insert(ptr)
}
}