about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/errors.rs3
-rw-r--r--tvix/eval/src/opcode.rs1
-rw-r--r--tvix/eval/src/value/thunk.rs32
-rw-r--r--tvix/eval/src/vm.rs12
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<rnix::parser::ParseError>),
 
     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<Ref<'_, Value>> {
+        // 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<Value> {
+    pub fn run(&mut self) -> EvalResult<Value> {
         #[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