feat(tvix/eval): add Evaluation::strict to toggle top-level deepseq

This makes it possible for callers to control whether they can receive
partially evaluated values from an evaluation or not.

We're actually flipping the default behaviour to non-strict top-level
evaluation, which means that callers have to set `strict = true` on
the Evaluation to get the previous behaviour.

Change-Id: Ic048e9ba09c88866d4c3177d5fa07db11c4eb20e
Reviewed-on: https://cl.tvl.fyi/c/depot/+/8325
Autosubmit: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
Reviewed-by: sterni <sternenseemann@systemli.org>
This commit is contained in:
Vincent Ambo 2023-03-18 00:10:29 +03:00 committed by clbot
parent a5f28eea94
commit ba138712e4
5 changed files with 26 additions and 5 deletions

View file

@ -104,6 +104,11 @@ pub struct Evaluation<'code, 'co, 'ro> {
/// able to read the files specified as arguments to `import`. /// able to read the files specified as arguments to `import`.
pub enable_import: bool, pub enable_import: bool,
/// Determines whether the returned value should be strictly
/// evaluated, that is whether its list and attribute set elements
/// should be forced recursively.
pub strict: bool,
/// (optional) Nix search path, e.g. the value of `NIX_PATH` used /// (optional) Nix search path, e.g. the value of `NIX_PATH` used
/// for resolving items on the search path (such as `<nixpkgs>`). /// for resolving items on the search path (such as `<nixpkgs>`).
pub nix_path: Option<String>, pub nix_path: Option<String>,
@ -161,6 +166,7 @@ impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> {
src_builtins: vec![], src_builtins: vec![],
io_handle: Box::new(DummyIO {}), io_handle: Box::new(DummyIO {}),
enable_import: false, enable_import: false,
strict: false,
nix_path: None, nix_path: None,
compiler_observer: None, compiler_observer: None,
runtime_observer: None, runtime_observer: None,
@ -256,7 +262,15 @@ impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> {
.unwrap_or_default(); .unwrap_or_default();
let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer); let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer);
let vm_result = run_lambda(nix_path, self.io_handle, runtime_observer, globals, lambda);
let vm_result = run_lambda(
nix_path,
self.io_handle,
runtime_observer,
globals,
lambda,
self.strict,
);
match vm_result { match vm_result {
Ok(mut runtime_result) => { Ok(mut runtime_result) => {

View file

@ -52,6 +52,7 @@ fn eval_test(code_path: &str, expect_success: bool) {
} }
let mut eval = crate::Evaluation::new_impure(&code, Some(code_path.into())); let mut eval = crate::Evaluation::new_impure(&code, Some(code_path.into()));
eval.strict = true;
eval.builtins.extend(mock_builtins::builtins()); eval.builtins.extend(mock_builtins::builtins());
let result = eval.evaluate(); let result = eval.evaluate();
@ -100,6 +101,7 @@ fn identity(code_path: &str) {
let code = std::fs::read_to_string(code_path).expect("should be able to read test code"); let code = std::fs::read_to_string(code_path).expect("should be able to read test code");
let mut eval = crate::Evaluation::new(&code, None); let mut eval = crate::Evaluation::new(&code, None);
eval.strict = true;
eval.io_handle = Box::new(crate::StdIO); eval.io_handle = Box::new(crate::StdIO);
let result = eval.evaluate(); let result = eval.evaluate();

View file

@ -1190,6 +1190,7 @@ pub fn run_lambda(
observer: &mut dyn RuntimeObserver, observer: &mut dyn RuntimeObserver,
globals: Rc<GlobalsMap>, globals: Rc<GlobalsMap>,
lambda: Rc<Lambda>, lambda: Rc<Lambda>,
strict: bool,
) -> EvalResult<RuntimeResult> { ) -> EvalResult<RuntimeResult> {
// Retain the top-level span of the expression in this lambda, as // Retain the top-level span of the expression in this lambda, as
// synthetic "calls" in deep_force will otherwise not have a span // synthetic "calls" in deep_force will otherwise not have a span
@ -1207,9 +1208,11 @@ pub fn run_lambda(
root_span.into(), root_span.into(),
); );
// Synthesise a frame that will instruct the VM to deep-force the final // When evaluating strictly, synthesise a frame that will instruct
// value before returning it. // the VM to deep-force the final value before returning it.
if strict {
vm.enqueue_generator("final_deep_force", root_span.into(), final_deep_force); vm.enqueue_generator("final_deep_force", root_span.into(), final_deep_force);
}
vm.frames.push(Frame::CallFrame { vm.frames.push(Frame::CallFrame {
span: root_span.into(), span: root_span.into(),

View file

@ -41,6 +41,7 @@ fn nix_eval(expr: &str) -> String {
fn compare_eval(expr: &str) { fn compare_eval(expr: &str) {
let nix_result = nix_eval(expr); let nix_result = nix_eval(expr);
let mut eval = tvix_eval::Evaluation::new(expr, None); let mut eval = tvix_eval::Evaluation::new(expr, None);
eval.strict = true;
eval.io_handle = Box::new(tvix_eval::StdIO); eval.io_handle = Box::new(tvix_eval::StdIO);
let tvix_result = eval let tvix_result = eval

View file

@ -33,7 +33,8 @@ where
T: serde::Deserialize<'code>, T: serde::Deserialize<'code>,
{ {
// First step is to evaluate the Nix code ... // First step is to evaluate the Nix code ...
let eval = tvix_eval::Evaluation::new(src, None); let mut eval = tvix_eval::Evaluation::new(src, None);
eval.strict = true;
let source = eval.source_map(); let source = eval.source_map();
let result = eval.evaluate(); let result = eval.evaluate();