diff options
-rw-r--r-- | tvix/eval/src/compiler/mod.rs | 24 | ||||
-rw-r--r-- | tvix/eval/src/opcode.rs | 12 | ||||
-rw-r--r-- | tvix/eval/src/value/function.rs | 12 | ||||
-rw-r--r-- | tvix/eval/src/vm.rs | 9 |
4 files changed, 51 insertions, 6 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs index 3ade6831c286..0c41f06cbde1 100644 --- a/tvix/eval/src/compiler/mod.rs +++ b/tvix/eval/src/compiler/mod.rs @@ -856,9 +856,27 @@ impl Compiler { crate::disassembler::disassemble_chunk(&compiled.lambda.chunk); } - self.emit_constant(Value::Closure(Closure { - lambda: compiled.lambda, - })); + // If the function is not a closure, just emit it directly and + // move on. + if compiled.lambda.upvalue_count == 0 { + self.emit_constant(Value::Closure(Closure::new(compiled.lambda))); + return; + } + + // If the function is a closure, we need to emit the variable + // number of operands that allow the runtime to close over the + // upvalues. + let closure_idx = self + .chunk() + .push_constant(Value::Closure(Closure::new(compiled.lambda))); + + self.chunk().push_op(OpCode::OpClosure(closure_idx)); + for upvalue in compiled.scope.upvalues { + match upvalue { + Upvalue::Stack(idx) => self.chunk().push_op(OpCode::DataLocalIdx(idx)), + Upvalue::Upvalue(idx) => self.chunk().push_op(OpCode::DataUpvalueIdx(idx)), + }; + } } fn compile_apply(&mut self, node: ast::Apply) { diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs index 6cce7ea9b176..ca99602862d5 100644 --- a/tvix/eval/src/opcode.rs +++ b/tvix/eval/src/opcode.rs @@ -102,7 +102,17 @@ pub enum OpCode { // Asserts stack top is a boolean, and true. OpAssert, - // Lambdas + // Lambdas & closures OpCall, OpGetUpvalue(UpvalueIdx), + OpClosure(ConstantIdx), + + // The closure and thunk creation instructions have a variable + // number of arguments to the instruction, which is represented + // here by making their data part of the opcodes. + // + // The VM skips over these by advancing the instruction pointer + // according to the count. + DataLocalIdx(StackIdx), + DataUpvalueIdx(UpvalueIdx), } diff --git a/tvix/eval/src/value/function.rs b/tvix/eval/src/value/function.rs index 5d7247416ee0..2b5fcf6c9819 100644 --- a/tvix/eval/src/value/function.rs +++ b/tvix/eval/src/value/function.rs @@ -1,7 +1,7 @@ //! This module implements the runtime representation of functions. use std::rc::Rc; -use crate::chunk::Chunk; +use crate::{chunk::Chunk, Value}; #[derive(Clone, Debug)] pub struct Lambda { @@ -27,4 +27,14 @@ impl Lambda { #[derive(Clone, Debug)] pub struct Closure { pub lambda: Lambda, + pub upvalues: Vec<Value>, +} + +impl Closure { + pub fn new(lambda: Lambda) -> Self { + Closure { + upvalues: Vec::with_capacity(lambda.upvalue_count), + lambda, + } + } } diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index 1c7772f2a7e0..5ebc95e7f15e 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -357,7 +357,7 @@ impl VM { OpCode::OpCall => { let callable = self.pop(); match callable { - Value::Closure(Closure { lambda }) => self.call(lambda, 1), + Value::Closure(Closure { lambda, .. }) => self.call(lambda, 1), Value::Builtin(builtin) => { let arg = self.pop(); let result = builtin.apply(arg)?; @@ -368,6 +368,13 @@ impl VM { } OpCode::OpGetUpvalue(_) => todo!("getting upvalues"), + OpCode::OpClosure(_) => todo!("creating closure objects"), + + // Data-carrying operands should never be executed, + // that is a critical error in the VM. + OpCode::DataLocalIdx(_) | OpCode::DataUpvalueIdx(_) => { + panic!("VM bug: attempted to execute data-carrying operand") + } } #[cfg(feature = "disassembler")] |