diff options
author | Adam Joseph <adam@westernsemico.com> | 2022-10-15T23·10-0700 |
---|---|---|
committer | tazjin <tazjin@tvl.su> | 2022-10-19T10·38+0000 |
commit | d978b556e6d3189760e36fbc0fefe683618cf6ad (patch) | |
tree | 9eea21dfac3510b805ef202dd764143c1e03092e /tvix/eval/src/vm.rs | |
parent | c91d86ee5ccada2c3868170f263be2b3d396d759 (diff) |
feat(tvix/eval): deduplicate overlap between Closure and Thunk r/5159
This commit deduplicates the Thunk-like functionality from Closure and unifies it with Thunk. Specifically, we now have one and only one way of breaking reference cycles in the Value-graph: Thunk. No other variant contains a RefCell. This should make it easier to reason about the behavior of the VM. InnerClosure and UpvaluesCarrier are no longer necessary. This refactoring allowed an improvement in code generation: `Rc<RefCell<>>`s are now created only for closures which do not have self-references or deferred upvalues, instead of for all closures. OpClosure has been split into two separate opcodes: - OpClosure creates non-recursive closures with no deferred upvalues. The VM will not create an `Rc<RefCell<>>` when executing this instruction. - OpThunkClosure is used for closures with self-references or deferred upvalues. The VM will create a Thunk when executing this opcode, but the Thunk will start out already in the `ThunkRepr::Evaluated` state, rather than in the `ThunkRepr::Suspeneded` state. To avoid confusion, OpThunk has been renamed OpThunkSuspended. Thanks to @sterni for suggesting that all this could be done without adding an additional variant to ThunkRepr. This does however mean that there will be mutating accesses to `ThunkRepr::Evaluated`, which was not previously the case. The field `is_finalised:bool` has been added to `Closure` to ensure that these mutating accesses are performed only on finalised Closures. Both the check and the field are present only if `#[cfg(debug_assertions)]`. Change-Id: I04131501029772f30e28da8281d864427685097f Signed-off-by: Adam Joseph <adam@westernsemico.com> Reviewed-on: https://cl.tvl.fyi/c/depot/+/7019 Tested-by: BuildkiteCI Reviewed-by: tazjin <tazjin@tvl.su>
Diffstat (limited to 'tvix/eval/src/vm.rs')
-rw-r--r-- | tvix/eval/src/vm.rs | 59 |
1 files changed, 32 insertions, 27 deletions
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index 65662ed555d3..ff39cf129572 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -1,7 +1,7 @@ //! This module implements the virtual (or abstract) machine that runs //! Tvix bytecode. -use std::{cell::RefMut, path::PathBuf, rc::Rc}; +use std::{ops::DerefMut, path::PathBuf, rc::Rc}; use crate::{ chunk::Chunk, @@ -9,7 +9,7 @@ use crate::{ nix_search_path::NixSearchPath, observer::RuntimeObserver, opcode::{CodeIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx}, - upvalues::{UpvalueCarrier, Upvalues}, + upvalues::Upvalues, value::{Builtin, Closure, CoercionKind, Lambda, NixAttrs, NixList, Thunk, Value}, warnings::{EvalWarning, WarningKind}, }; @@ -675,30 +675,37 @@ impl<'o> VM<'o> { upvalue_count > 0, "OpClosure should not be called for plain lambdas" ); - - let closure = Closure::new(blueprint); - let upvalues = closure.upvalues_mut(); - self.push(Value::Closure(closure.clone())); - - // From this point on we internally mutate the - // closure object's upvalues. The closure is - // already in its stack slot, which means that it - // can capture itself as an upvalue for - // self-recursion. - self.populate_upvalues(upvalue_count, upvalues)?; + let mut upvalues = Upvalues::with_capacity(blueprint.upvalue_count); + self.populate_upvalues(upvalue_count, &mut upvalues)?; + self.push(Value::Closure(Closure::new_with_upvalues( + upvalues, blueprint, + ))); } - OpCode::OpThunk(idx) => { + OpCode::OpThunkSuspended(idx) | OpCode::OpThunkClosure(idx) => { let blueprint = match &self.chunk()[idx] { Value::Blueprint(lambda) => lambda.clone(), _ => panic!("compiler bug: non-blueprint in blueprint slot"), }; let upvalue_count = blueprint.upvalue_count; - let thunk = Thunk::new(blueprint, self.current_span()); + let thunk = if matches!(op, OpCode::OpThunkClosure(_)) { + debug_assert!( + upvalue_count > 0, + "OpThunkClosure should not be called for plain lambdas" + ); + Thunk::new_closure(blueprint) + } else { + Thunk::new_suspended(blueprint, self.current_span()) + }; let upvalues = thunk.upvalues_mut(); - self.push(Value::Thunk(thunk.clone())); + + // From this point on we internally mutate the + // upvalues. The closure (if `is_closure`) is + // already in its stack slot, which means that it + // can capture itself as an upvalue for + // self-recursion. self.populate_upvalues(upvalue_count, upvalues)?; } @@ -715,13 +722,9 @@ impl<'o> VM<'o> { OpCode::OpFinalise(StackIdx(idx)) => { match &self.stack[self.frame().stack_offset + idx] { - Value::Closure(closure) => { - closure.resolve_deferred_upvalues(&self.stack[self.frame().stack_offset..]) - } + Value::Closure(_) => panic!("attempted to finalise a closure"), - Value::Thunk(thunk) => { - thunk.resolve_deferred_upvalues(&self.stack[self.frame().stack_offset..]) - } + Value::Thunk(thunk) => thunk.finalise(&self.stack[self.frame().stack_offset..]), // In functions with "formals" attributes, it is // possible for `OpFinalise` to be called on a @@ -809,21 +812,23 @@ impl<'o> VM<'o> { fn populate_upvalues( &mut self, count: usize, - mut upvalues: RefMut<'_, Upvalues>, + mut upvalues: impl DerefMut<Target = Upvalues>, ) -> EvalResult<()> { for _ in 0..count { match self.inc_ip() { OpCode::DataLocalIdx(StackIdx(local_idx)) => { let idx = self.frame().stack_offset + local_idx; - upvalues.push(self.stack[idx].clone()); + upvalues.deref_mut().push(self.stack[idx].clone()); } OpCode::DataUpvalueIdx(upv_idx) => { - upvalues.push(self.frame().upvalue(upv_idx).clone()); + upvalues + .deref_mut() + .push(self.frame().upvalue(upv_idx).clone()); } OpCode::DataDeferredLocal(idx) => { - upvalues.push(Value::DeferredUpvalue(idx)); + upvalues.deref_mut().push(Value::DeferredUpvalue(idx)); } OpCode::DataCaptureWith => { @@ -841,7 +846,7 @@ impl<'o> VM<'o> { captured_with_stack.push(self.stack[*idx].clone()); } - upvalues.set_with_stack(captured_with_stack); + upvalues.deref_mut().set_with_stack(captured_with_stack); } _ => panic!("compiler error: missing closure operand"), |