about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-09-29T20·58+0300
committerclbot <clbot@tvl.fyi>2022-10-03T07·50+0000
commit8336577c411004d27c86703c4a95e26d3cd762cc (patch)
treea4a7bcd242a2937cce549f117081d959f891afdb
parentc1d8cee215a3e5787d4bd5de91ce029307ac718a (diff)
feat(tvix/eval): implement tail-calling of __functor attributes r/5019
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 <tazjin@tvl.su>
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-functor-call.nix1
-rw-r--r--tvix/eval/src/vm.rs56
3 files changed, 39 insertions, 19 deletions
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 000000000000..d81cc0710eb6
--- /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 000000000000..80ae345d836b
--- /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 c5b892d78d6f..72d672688217 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) => {