about summary refs log tree commit diff
path: root/tvix/eval/src/value/thunk.rs
diff options
context:
space:
mode:
authorGriffin Smith <grfn@gws.fyi>2022-10-13T02·47-0400
committergrfn <grfn@gws.fyi>2022-10-22T18·11+0000
commitd4fa3152e92ef72d9ee050000b1fd4952203e383 (patch)
tree288cd7bd6f47169aec08e402edd0cf5fb90560fa /tvix/eval/src/value/thunk.rs
parent8724d2fff871827dc66503f9b3dfa1d29149ddc7 (diff)
feat(tvix/eval): Implement builtins.deepSeq r/5175
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>
Diffstat (limited to 'tvix/eval/src/value/thunk.rs')
-rw-r--r--tvix/eval/src/value/thunk.rs29
1 files changed, 23 insertions, 6 deletions
diff --git a/tvix/eval/src/value/thunk.rs b/tvix/eval/src/value/thunk.rs
index 818ec0f58aec..7bad7e8777ba 100644
--- a/tvix/eval/src/value/thunk.rs
+++ b/tvix/eval/src/value/thunk.rs
@@ -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)
+    }
+}