From 8336577c411004d27c86703c4a95e26d3cd762cc Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 29 Sep 2022 23:58:53 +0300 Subject: [PATCH] feat(tvix/eval): implement tail-calling of __functor attributes This implements __functor calling in situations where `OpTailCall` is used, but not yet for `OpCall`. For some reason I have not yet figured out, this same implementation does not work in call_value, which means that it also doesn't yet work in builtins that apply functions. Change-Id: I378f9065ac53d4c05166a7d0151acb1f55c91579 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6826 Autosubmit: tazjin Reviewed-by: sterni Tested-by: BuildkiteCI --- .../tvix_tests/eval-okay-functor-call.exp | 1 + .../tvix_tests/eval-okay-functor-call.nix | 1 + tvix/eval/src/vm.rs | 56 ++++++++++++------- 3 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp new file mode 100644 index 000000000..d81cc0710 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp @@ -0,0 +1 @@ +42 diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix new file mode 100644 index 000000000..80ae345d8 --- /dev/null +++ b/tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix @@ -0,0 +1 @@ +{ x = 21; __functor = self: y: self.x * y; } 2 diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index c5b892d78..72d672688 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -204,6 +204,42 @@ impl<'o> VM<'o> { } } + fn tail_call_value(&mut self, callable: Value) -> EvalResult<()> { + match callable { + Value::Builtin(builtin) => self.call_builtin(builtin), + Value::Thunk(thunk) => self.tail_call_value(thunk.value().clone()), + + Value::Closure(closure) => { + let lambda = closure.lambda(); + self.observer.observe_tail_call(self.frames.len(), &lambda); + + // Replace the current call frames internals with + // that of the tail-called closure. + let mut frame = self.frame_mut(); + frame.lambda = lambda; + frame.upvalues = closure.upvalues().clone(); + frame.ip = CodeIdx(0); // reset instruction pointer to beginning + Ok(()) + } + + // Attribute sets with a __functor attribute are callable. + Value::Attrs(ref attrs) => match attrs.select("__functor") { + None => Err(self.error(ErrorKind::NotCallable(callable.type_of()))), + Some(functor) => { + // The functor receives the set itself as its first argument + // and needs to be called with it. However, this call is + // synthetic (i.e. there is no corresponding OpCall for the + // first call in the bytecode.) + self.push(callable.clone()); + let primed = self.call_value(functor)?; + self.tail_call_value(primed) + } + }, + + _ => Err(self.error(ErrorKind::NotCallable(callable.type_of()))), + } + } + /// Execute the given lambda in this VM's context, returning its /// value after its stack frame completes. pub fn call( @@ -488,25 +524,7 @@ impl<'o> VM<'o> { OpCode::OpTailCall => { let callable = self.pop(); - - match callable { - Value::Builtin(builtin) => self.call_builtin(builtin)?, - - Value::Closure(closure) => { - let lambda = closure.lambda(); - self.observer.observe_tail_call(self.frames.len(), &lambda); - - // Replace the current call frames - // internals with that of the tail-called - // closure. - let mut frame = self.frame_mut(); - frame.lambda = lambda; - frame.upvalues = closure.upvalues().clone(); - frame.ip = CodeIdx(0); // reset instruction pointer to beginning - } - - _ => return Err(self.error(ErrorKind::NotCallable(callable.type_of()))), - } + self.tail_call_value(callable)?; } OpCode::OpGetUpvalue(upv_idx) => {