diff options
author | Griffin Smith <grfn@gws.fyi> | 2022-10-13T02·47-0400 |
---|---|---|
committer | grfn <grfn@gws.fyi> | 2022-10-22T18·11+0000 |
commit | d4fa3152e92ef72d9ee050000b1fd4952203e383 (patch) | |
tree | 288cd7bd6f47169aec08e402edd0cf5fb90560fa /tvix/eval/src/vm.rs | |
parent | 8724d2fff871827dc66503f9b3dfa1d29149ddc7 (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/vm.rs')
-rw-r--r-- | tvix/eval/src/vm.rs | 65 |
1 files changed, 9 insertions, 56 deletions
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index 9e25da7d23ae..75fe8b32acda 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -3,8 +3,6 @@ use std::{ops::DerefMut, path::PathBuf, rc::Rc}; -use codemap::Span; - use crate::{ chunk::Chunk, errors::{Error, ErrorKind, EvalResult}, @@ -858,57 +856,6 @@ impl<'o> VM<'o> { Ok(()) } - /// Strictly evaluate the supplied value for outputting it. This - /// will ensure that lists and attribute sets do not contain - /// chunks which, for users, are displayed in a strange and often - /// unexpected way. - fn force_for_output(&mut self, value: &Value, root_span: Span) -> EvalResult<()> { - match value { - Value::Attrs(attrs) => { - for (_, value) in attrs.iter() { - self.force_for_output(value, root_span)?; - } - Ok(()) - } - - Value::List(list) => list - .iter() - .try_for_each(|elem| self.force_for_output(elem, root_span)), - - Value::Thunk(thunk) => { - // This force is "synthetic", in that there is no - // outer expression from which a top-level span for - // the call can be derived, so we need to set this - // span manually. - thunk.force(self).map_err(|kind| Error { - kind, - span: root_span, - })?; - - let value = thunk.value().clone(); - self.force_for_output(&value, root_span) - } - - // If any of these internal values are encountered here a - // critical error has happened (likely a compiler bug). - Value::AttrNotFound - | Value::Blueprint(_) - | Value::DeferredUpvalue(_) - | Value::UnresolvedPath(_) => { - panic!("tvix bug: internal value left on stack: {:?}", value) - } - - Value::Null - | Value::Bool(_) - | Value::Integer(_) - | Value::Float(_) - | Value::String(_) - | Value::Path(_) - | Value::Closure(_) - | Value::Builtin(_) => Ok(()), - } - } - pub fn call_builtin(&mut self, builtin: Builtin) -> EvalResult<()> { let builtin_name = builtin.name(); self.observer.observe_enter_builtin(builtin_name); @@ -939,8 +886,8 @@ pub fn run_lambda( let mut vm = VM::new(nix_search_path, observer); // Retain the top-level span of the expression in this lambda, as - // synthetic "calls" in force_for_output will otherwise not have a - // span to fall back to. + // synthetic "calls" in deep_force will otherwise not have a span + // to fall back to. // // We exploit the fact that the compiler emits a final instruction // with the span of the entire file for top-level expressions. @@ -948,7 +895,13 @@ pub fn run_lambda( vm.enter_frame(lambda, Upvalues::with_capacity(0), 0)?; let value = vm.pop(); - vm.force_for_output(&value, root_span)?; + + value + .deep_force(&mut vm, &mut Default::default()) + .map_err(|kind| Error { + kind, + span: root_span, + })?; Ok(RuntimeResult { value, |