feat(tvix/eval): implement capture of self-recursive upvalues
With this change, it becomes possible for functions to call themselves as they are being defined in local bindings. Change-Id: Ib46a39ba17b1452b5673d96fa729d633d237241a Reviewed-on: https://cl.tvl.fyi/c/depot/+/6314 Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
This commit is contained in:
parent
06b07f5c47
commit
ae531a2245
4 changed files with 22 additions and 7 deletions
|
@ -709,9 +709,9 @@ impl Compiler {
|
||||||
self.chunk().push_op(OpCode::OpResolveWith)
|
self.chunk().push_op(OpCode::OpResolveWith)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalPosition::Recursive(_) => todo!("self-recursive upvalue"),
|
|
||||||
|
|
||||||
LocalPosition::Known(idx) => self.chunk().push_op(OpCode::OpGetLocal(idx)),
|
LocalPosition::Known(idx) => self.chunk().push_op(OpCode::OpGetLocal(idx)),
|
||||||
|
|
||||||
|
LocalPosition::Recursive(_) => panic!("TODO: unclear if this can happen"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -981,10 +981,14 @@ impl Compiler {
|
||||||
|
|
||||||
// Determine whether the upvalue is a local in the enclosing context.
|
// Determine whether the upvalue is a local in the enclosing context.
|
||||||
match self.contexts[ctx_idx - 1].scope.resolve_local(name) {
|
match self.contexts[ctx_idx - 1].scope.resolve_local(name) {
|
||||||
LocalPosition::Known(idx) => {
|
// recursive upvalues are dealt with the same way as
|
||||||
|
// standard known ones, as thunks and closures are
|
||||||
|
// guaranteed to be placed on the stack (i.e. in the right
|
||||||
|
// position) *during* their runtime construction
|
||||||
|
LocalPosition::Known(idx) | LocalPosition::Recursive(idx) => {
|
||||||
return Some(self.add_upvalue(ctx_idx, Upvalue::Stack(idx)))
|
return Some(self.add_upvalue(ctx_idx, Upvalue::Stack(idx)))
|
||||||
}
|
}
|
||||||
LocalPosition::Recursive(_) => todo!("self-recursive upvalue"),
|
|
||||||
LocalPosition::Unknown => { /* continue below */ }
|
LocalPosition::Unknown => { /* continue below */ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
"done"
|
|
@ -0,0 +1,4 @@
|
||||||
|
let
|
||||||
|
# self-recursive function should be able to close over itself
|
||||||
|
f = n: if n <= 0 then "done" else f (n - 1);
|
||||||
|
in f 10
|
|
@ -359,7 +359,15 @@ impl VM {
|
||||||
}
|
}
|
||||||
|
|
||||||
OpCode::OpClosure(idx) => {
|
OpCode::OpClosure(idx) => {
|
||||||
let closure = self.chunk().constant(idx).clone().to_closure()?;
|
let value = self.chunk().constant(idx).clone();
|
||||||
|
self.push(value.clone());
|
||||||
|
|
||||||
|
// This refers to the same Rc, and from this point
|
||||||
|
// on internally mutates the closure objects
|
||||||
|
// upvalues. The closure is already in its stack
|
||||||
|
// slot, which means that it can capture itself as
|
||||||
|
// an upvalue for self-recursion.
|
||||||
|
let closure = value.to_closure()?;
|
||||||
|
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
closure.upvalue_count() > 0,
|
closure.upvalue_count() > 0,
|
||||||
|
@ -387,8 +395,6 @@ impl VM {
|
||||||
_ => panic!("compiler error: missing closure operand"),
|
_ => panic!("compiler error: missing closure operand"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push(Value::Closure(closure));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data-carrying operands should never be executed,
|
// Data-carrying operands should never be executed,
|
||||||
|
|
Loading…
Reference in a new issue