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 <sternenseemann@systemli.org>
This commit is contained in:
Vincent Ambo 2022-08-29 18:33:02 +03:00 committed by tazjin
parent db9cb70d5d
commit 9a783e50a4
4 changed files with 46 additions and 2 deletions

View file

@ -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,

View file

@ -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.

View file

@ -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 {

View file

@ -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