From c3c4d752c91f64eff8e7f7f7b21fbcc1209d27a6 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 12 Dec 2022 17:38:28 +0300 Subject: feat(tvix/eval): add EvalIO to public crate API This lets users set the `io_handle` field on an `Evaluation`, which is then propagated to the VM. Change-Id: I616d7140724fb2b4db47c2ebf95451d5303a487a Reviewed-on: https://cl.tvl.fyi/c/depot/+/7566 Reviewed-by: grfn Tested-by: BuildkiteCI --- tvix/eval/src/io.rs | 4 ++-- tvix/eval/src/lib.rs | 15 ++++++++++++++- tvix/eval/src/tests/mod.rs | 10 ++++++++-- tvix/eval/src/value/attrs/tests.rs | 6 +++--- tvix/eval/src/value/mod.rs | 8 ++++---- tvix/eval/src/vm.rs | 13 +++++++++++-- 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/tvix/eval/src/io.rs b/tvix/eval/src/io.rs index 8003746c4853..cf9f6304305f 100644 --- a/tvix/eval/src/io.rs +++ b/tvix/eval/src/io.rs @@ -27,7 +27,7 @@ pub trait EvalIO { /// Implementation of [`EvalIO`] that simply uses the equivalent /// standard library functions, i.e. does local file-IO. -struct StdIO; +pub struct StdIO; impl EvalIO for StdIO { fn read_to_string(&self, path: PathBuf) -> Result { @@ -41,7 +41,7 @@ impl EvalIO for StdIO { /// Dummy implementation of [`EvalIO`], can be used in contexts where /// IO is not available but code should "pretend" that it is. -struct DummyIO; +pub struct DummyIO; impl EvalIO for DummyIO { fn read_to_string(&self, _: PathBuf) -> Result { diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs index c9a6ac103eba..8cf9fe6e1bba 100644 --- a/tvix/eval/src/lib.rs +++ b/tvix/eval/src/lib.rs @@ -45,6 +45,7 @@ use std::sync::Arc; pub use crate::builtins::global_builtins; pub use crate::compiler::{compile, prepare_globals}; pub use crate::errors::{Error, ErrorKind, EvalResult}; +pub use crate::io::{DummyIO, EvalIO, StdIO}; use crate::observer::{CompilerObserver, RuntimeObserver}; pub use crate::pretty_ast::pretty_print_expr; pub use crate::source::SourceCode; @@ -86,6 +87,12 @@ pub struct Evaluation<'code, 'co, 'ro> { /// Top-level file reference for this code inside the source map. file: Arc, + /// Implementation of file-IO to use during evaluation, e.g. for + /// impure builtins. + /// + /// Defaults to [`DummyIO`] if not set explicitly. + pub io_handle: Box, + /// (optional) Nix search path, e.g. the value of `NIX_PATH` used /// for resolving items on the search path (such as ``). pub nix_path: Option, @@ -137,6 +144,7 @@ impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> { location, source_map, file, + io_handle: Box::new(DummyIO {}), nix_path: None, compiler_observer: None, runtime_observer: None, @@ -216,7 +224,12 @@ impl<'code, 'co, 'ro> Evaluation<'code, 'co, 'ro> { .unwrap_or_else(|| Default::default()); let runtime_observer = self.runtime_observer.take().unwrap_or(&mut noop_observer); - let vm_result = run_lambda(nix_path, runtime_observer, compiler_result.lambda); + let vm_result = run_lambda( + nix_path, + self.io_handle, + runtime_observer, + compiler_result.lambda, + ); match vm_result { Ok(mut runtime_result) => { diff --git a/tvix/eval/src/tests/mod.rs b/tvix/eval/src/tests/mod.rs index 65aa12e0a01b..1c872f67d2b0 100644 --- a/tvix/eval/src/tests/mod.rs +++ b/tvix/eval/src/tests/mod.rs @@ -17,7 +17,10 @@ fn eval_test(code_path: &str, expect_success: bool) { return; } - let result = crate::Evaluation::new(&code, Some(code_path.into())).evaluate(); + let mut eval = crate::Evaluation::new(&code, Some(code_path.into())); + eval.io_handle = Box::new(crate::StdIO); + + let result = eval.evaluate(); if expect_success && !result.errors.is_empty() { panic!( @@ -64,7 +67,10 @@ fn eval_test(code_path: &str, expect_success: bool) { fn identity(code_path: &str) { let code = std::fs::read_to_string(code_path).expect("should be able to read test code"); - let result = crate::Evaluation::new(&code, None).evaluate(); + let mut eval = crate::Evaluation::new(&code, None); + eval.io_handle = Box::new(crate::StdIO); + + let result = eval.evaluate(); assert!( result.errors.is_empty(), "evaluation of identity test failed: {:?}", diff --git a/tvix/eval/src/value/attrs/tests.rs b/tvix/eval/src/value/attrs/tests.rs index 65d3c8d7ca0e..2e80fa6f2308 100644 --- a/tvix/eval/src/value/attrs/tests.rs +++ b/tvix/eval/src/value/attrs/tests.rs @@ -10,7 +10,7 @@ mod nix_eq { #[proptest(ProptestConfig { cases: 2, ..Default::default() })] fn reflexive(x: NixAttrs) { let mut observer = NoOpObserver {}; - let mut vm = VM::new(Default::default(), &mut observer); + let mut vm = VM::new(Default::default(), Box::new(crate::DummyIO), &mut observer); assert!(x.nix_eq(&x, &mut vm).unwrap()) } @@ -18,7 +18,7 @@ mod nix_eq { #[proptest(ProptestConfig { cases: 2, ..Default::default() })] fn symmetric(x: NixAttrs, y: NixAttrs) { let mut observer = NoOpObserver {}; - let mut vm = VM::new(Default::default(), &mut observer); + let mut vm = VM::new(Default::default(), Box::new(crate::DummyIO), &mut observer); assert_eq!( x.nix_eq(&y, &mut vm).unwrap(), @@ -29,7 +29,7 @@ mod nix_eq { #[proptest(ProptestConfig { cases: 2, ..Default::default() })] fn transitive(x: NixAttrs, y: NixAttrs, z: NixAttrs) { let mut observer = NoOpObserver {}; - let mut vm = VM::new(Default::default(), &mut observer); + let mut vm = VM::new(Default::default(), Box::new(crate::DummyIO), &mut observer); if x.nix_eq(&y, &mut vm).unwrap() && y.nix_eq(&z, &mut vm).unwrap() { assert!(x.nix_eq(&z, &mut vm).unwrap()) diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs index af75bc9a7346..583d0c38e46e 100644 --- a/tvix/eval/src/value/mod.rs +++ b/tvix/eval/src/value/mod.rs @@ -565,7 +565,7 @@ mod tests { #[proptest(ProptestConfig { cases: 5, ..Default::default() })] fn reflexive(x: Value) { let mut observer = NoOpObserver {}; - let mut vm = VM::new(Default::default(), &mut observer); + let mut vm = VM::new(Default::default(), Box::new(crate::DummyIO), &mut observer); assert!(x.nix_eq(&x, &mut vm).unwrap()) } @@ -573,7 +573,7 @@ mod tests { #[proptest(ProptestConfig { cases: 5, ..Default::default() })] fn symmetric(x: Value, y: Value) { let mut observer = NoOpObserver {}; - let mut vm = VM::new(Default::default(), &mut observer); + let mut vm = VM::new(Default::default(), Box::new(crate::DummyIO), &mut observer); assert_eq!( x.nix_eq(&y, &mut vm).unwrap(), @@ -584,7 +584,7 @@ mod tests { #[proptest(ProptestConfig { cases: 5, ..Default::default() })] fn transitive(x: Value, y: Value, z: Value) { let mut observer = NoOpObserver {}; - let mut vm = VM::new(Default::default(), &mut observer); + let mut vm = VM::new(Default::default(), Box::new(crate::DummyIO), &mut observer); if x.nix_eq(&y, &mut vm).unwrap() && y.nix_eq(&z, &mut vm).unwrap() { assert!(x.nix_eq(&z, &mut vm).unwrap()) @@ -594,7 +594,7 @@ mod tests { #[test] fn list_int_float_fungibility() { let mut observer = NoOpObserver {}; - let mut vm = VM::new(Default::default(), &mut observer); + let mut vm = VM::new(Default::default(), Box::new(crate::DummyIO), &mut observer); let v1 = Value::List(NixList::from(vec![Value::Integer(1)])); let v2 = Value::List(NixList::from(vec![Value::Float(1.0)])); diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index 37d30a6c0178..baf594f9368c 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -7,6 +7,7 @@ use std::{cmp::Ordering, collections::BTreeMap, ops::DerefMut, path::PathBuf, rc use crate::{ chunk::Chunk, errors::{Error, ErrorKind, EvalResult}, + io::EvalIO, nix_search_path::NixSearchPath, observer::RuntimeObserver, opcode::{CodeIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx}, @@ -65,6 +66,8 @@ pub struct VM<'o> { nix_search_path: NixSearchPath, + io_handle: Box, + observer: &'o mut dyn RuntimeObserver, } @@ -150,7 +153,11 @@ macro_rules! cmp_op { } impl<'o> VM<'o> { - pub fn new(nix_search_path: NixSearchPath, observer: &'o mut dyn RuntimeObserver) -> Self { + pub fn new( + nix_search_path: NixSearchPath, + io_handle: Box, + observer: &'o mut dyn RuntimeObserver, + ) -> Self { // Backtrace-on-stack-overflow is some seriously weird voodoo and // very unsafe. This double-guard prevents it from accidentally // being enabled on release builds. @@ -162,6 +169,7 @@ impl<'o> VM<'o> { Self { nix_search_path, + io_handle, observer, frames: vec![], stack: vec![], @@ -1084,10 +1092,11 @@ impl<'o> VM<'o> { pub fn run_lambda( nix_search_path: NixSearchPath, + io_handle: Box, observer: &mut dyn RuntimeObserver, lambda: Rc, ) -> EvalResult { - let mut vm = VM::new(nix_search_path, observer); + let mut vm = VM::new(nix_search_path, io_handle, observer); // Retain the top-level span of the expression in this lambda, as // synthetic "calls" in deep_force will otherwise not have a span -- cgit 1.4.1