From 9a783e50a490a873f157ec298165b3f38973e4b5 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 29 Aug 2022 18:33:02 +0300 Subject: feat(tvix/eval): implement OpForce in VM This operation forces the evaluation of a thunk. There is some potential here for making an implementation that avoids some copies, but the thunk machinery is tricky to get right so the first priority is to make sure it is correct by keeping the implementation simple. Change-Id: Ib381455b02f42ded717faff63f55afed4c8fb7e3 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6352 Tested-by: BuildkiteCI Reviewed-by: sterni --- tvix/eval/src/errors.rs | 3 +++ tvix/eval/src/opcode.rs | 1 + tvix/eval/src/value/thunk.rs | 32 +++++++++++++++++++++++++++++++- tvix/eval/src/vm.rs | 12 +++++++++++- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs index 49a051c16eee..32d37d78156d 100644 --- a/tvix/eval/src/errors.rs +++ b/tvix/eval/src/errors.rs @@ -38,6 +38,9 @@ pub enum ErrorKind { // Attempt to call something that is not callable. NotCallable, + // Infinite recursion encountered while forcing thunks. + InfiniteRecursion, + ParseErrors(Vec), AssertionFailed, diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs index a8802e8c0a2b..18f0fce3cb0b 100644 --- a/tvix/eval/src/opcode.rs +++ b/tvix/eval/src/opcode.rs @@ -109,6 +109,7 @@ pub enum OpCode { // Thunks OpThunk(ConstantIdx), + OpForce, /// Finalise initialisation of the upvalues of the value in the /// given stack index after the scope is fully bound. diff --git a/tvix/eval/src/value/thunk.rs b/tvix/eval/src/value/thunk.rs index 9d52ded3c43c..892868d0f660 100644 --- a/tvix/eval/src/value/thunk.rs +++ b/tvix/eval/src/value/thunk.rs @@ -23,7 +23,7 @@ use std::{ rc::Rc, }; -use crate::{upvalues::UpvalueCarrier, Value}; +use crate::{errors::ErrorKind, upvalues::UpvalueCarrier, vm::VM, EvalResult, Value}; use super::Lambda; @@ -55,6 +55,36 @@ impl Thunk { lambda, }))) } + + pub fn force(&self, vm: &mut VM) -> EvalResult> { + // Due to mutable borrowing rules, the following code can't + // easily use a match statement or something like that; it + // requires a bit of manual fiddling. + let mut thunk_mut = self.0.borrow_mut(); + + if let ThunkRepr::Blackhole = *thunk_mut { + return Err(ErrorKind::InfiniteRecursion.into()); + } + + if matches!(*thunk_mut, ThunkRepr::Suspended { .. }) { + if let ThunkRepr::Suspended { lambda, upvalues } = + std::mem::replace(&mut *thunk_mut, ThunkRepr::Blackhole) + { + vm.call(lambda, upvalues, 0); + *thunk_mut = ThunkRepr::Evaluated(vm.run()?); + } + } + + drop(thunk_mut); + + // Otherwise it's already ThunkRepr::Evaluated and we do not + // need another branch. + + Ok(Ref::map(self.0.borrow(), |t| match t { + ThunkRepr::Evaluated(value) => value, + _ => unreachable!("already evaluated"), + })) + } } impl UpvalueCarrier for Thunk { diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index e32c0d7f6b62..c55bc81777f1 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -136,7 +136,7 @@ impl VM { self.frames.push(frame); } - fn run(&mut self) -> EvalResult { + pub fn run(&mut self) -> EvalResult { #[cfg(feature = "disassembler")] let mut tracer = Tracer::new(); @@ -441,6 +441,16 @@ impl VM { self.populate_upvalues(upvalue_count, upvalues)?; } + OpCode::OpForce => { + let mut value = self.pop(); + + while let Value::Thunk(thunk) = value { + value = thunk.force(self)?.clone(); + } + + self.push(value); + } + OpCode::OpFinalise(StackIdx(idx)) => { match &self.stack[self.frame().stack_offset + idx] { Value::Closure(closure) => closure -- cgit 1.4.1