about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/compiler/mod.rs22
1 files changed, 20 insertions, 2 deletions
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
index 448999fb38df..496f0aaf3207 100644
--- a/tvix/eval/src/compiler/mod.rs
+++ b/tvix/eval/src/compiler/mod.rs
@@ -900,7 +900,11 @@ impl Compiler<'_, '_> {
 
         // Pop the lambda context back off, and emit the finished
         // lambda as a constant.
-        let compiled = self.contexts.pop().unwrap();
+        let mut compiled = self.contexts.pop().unwrap();
+
+        // Check if tail-call optimisation is possible and perform it.
+        optimise_tail_call(&mut compiled.lambda.chunk);
+
         let lambda = Rc::new(compiled.lambda);
         self.observer.observe_compiled_lambda(&lambda);
 
@@ -947,7 +951,8 @@ impl Compiler<'_, '_> {
         content(self, node, slot);
         self.end_scope(node);
 
-        let thunk = self.contexts.pop().unwrap();
+        let mut thunk = self.contexts.pop().unwrap();
+        optimise_tail_call(&mut thunk.lambda.chunk);
         let lambda = Rc::new(thunk.lambda);
         self.observer.observe_compiled_thunk(&lambda);
 
@@ -1304,6 +1309,19 @@ impl Compiler<'_, '_> {
     }
 }
 
+/// Perform tail-call optimisation if the last call within a
+/// compiled chunk is another call.
+fn optimise_tail_call(chunk: &mut Chunk) {
+    let last_op = chunk
+        .code
+        .last_mut()
+        .expect("compiler bug: chunk should never be empty");
+
+    if matches!(last_op, OpCode::OpCall) {
+        *last_op = OpCode::OpTailCall;
+    }
+}
+
 /// Prepare the full set of globals from additional globals supplied
 /// by the caller of the compiler, as well as the built-in globals
 /// that are always part of the language.