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:
parent
8724d2fff8
commit
d4fa3152e9
10 changed files with 100 additions and 62 deletions
|
|
@ -102,3 +102,13 @@ impl IntoIterator for NixList {
|
|||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a NixList {
|
||||
type Item = &'a Value;
|
||||
|
||||
type IntoIter = std::slice::Iter<'a, Value>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue