feat(tvix/eval): deduplicate overlap between Closure and Thunk
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>
This commit is contained in:
parent
c91d86ee5c
commit
d978b556e6
9 changed files with 206 additions and 152 deletions
|
|
@ -927,7 +927,7 @@ impl Compiler<'_> {
|
|||
if lambda.upvalue_count == 0 {
|
||||
self.emit_constant(
|
||||
if is_thunk {
|
||||
Value::Thunk(Thunk::new(lambda, span))
|
||||
Value::Thunk(Thunk::new_suspended(lambda, span))
|
||||
} else {
|
||||
Value::Closure(Closure::new(lambda))
|
||||
},
|
||||
|
|
@ -942,11 +942,11 @@ impl Compiler<'_> {
|
|||
// which the result can be constructed.
|
||||
let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda));
|
||||
|
||||
self.push_op(
|
||||
let code_idx = self.push_op(
|
||||
if is_thunk {
|
||||
OpCode::OpThunk(blueprint_idx)
|
||||
OpCode::OpThunkSuspended(blueprint_idx)
|
||||
} else {
|
||||
OpCode::OpClosure(blueprint_idx)
|
||||
OpCode::OpThunkClosure(blueprint_idx)
|
||||
},
|
||||
node,
|
||||
);
|
||||
|
|
@ -957,6 +957,23 @@ impl Compiler<'_> {
|
|||
compiled.scope.upvalues,
|
||||
compiled.captures_with_stack,
|
||||
);
|
||||
|
||||
if !is_thunk && !self.scope()[outer_slot].needs_finaliser {
|
||||
if !self.scope()[outer_slot].must_thunk {
|
||||
// The closure has upvalues, but is not recursive. Therefore no thunk is required,
|
||||
// which saves us the overhead of Rc<RefCell<>>
|
||||
self.chunk()[code_idx] = OpCode::OpClosure(blueprint_idx);
|
||||
} else {
|
||||
// This case occurs when a closure has upvalue-references to itself but does not need a
|
||||
// finaliser. Since no OpFinalise will be emitted later on we synthesize one here.
|
||||
// It is needed here only to set [`Closure::is_finalised`] which is used for sanity checks.
|
||||
#[cfg(debug_assertions)]
|
||||
self.push_op(
|
||||
OpCode::OpFinalise(self.scope().stack_index(outer_slot)),
|
||||
&self.span_for(node),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_apply(&mut self, slot: LocalIdx, node: &ast::Apply) {
|
||||
|
|
@ -996,6 +1013,10 @@ impl Compiler<'_> {
|
|||
self.push_op(OpCode::DataDeferredLocal(stack_idx), &upvalue.span);
|
||||
self.scope_mut().mark_needs_finaliser(slot);
|
||||
} else {
|
||||
// a self-reference
|
||||
if this_depth == target_depth && this_stack_slot == stack_idx {
|
||||
self.scope_mut().mark_must_thunk(slot);
|
||||
}
|
||||
self.push_op(OpCode::DataLocalIdx(stack_idx), &upvalue.span);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue