about summary refs log tree commit diff
path: root/tvix/eval/src
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src')
-rw-r--r--tvix/eval/src/compiler/bindings.rs40
-rw-r--r--tvix/eval/src/compiler/mod.rs252
-rw-r--r--tvix/eval/src/eval.rs4
3 files changed, 146 insertions, 150 deletions
diff --git a/tvix/eval/src/compiler/bindings.rs b/tvix/eval/src/compiler/bindings.rs
index a9532cc36b14..ca437e90e996 100644
--- a/tvix/eval/src/compiler/bindings.rs
+++ b/tvix/eval/src/compiler/bindings.rs
@@ -533,7 +533,7 @@ impl Compiler<'_> {
     /// 1. Keys can be dynamically constructed through interpolation.
     /// 2. Keys can refer to nested attribute sets.
     /// 3. Attribute sets can (optionally) be recursive.
-    pub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: ast::AttrSet) {
+    pub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: &ast::AttrSet) {
         // Open a scope to track the positions of the temporaries used by the
         // `OpAttrs` instruction.
         self.scope_mut().begin_scope();
@@ -544,7 +544,7 @@ impl Compiler<'_> {
             BindingsKind::Attrs
         };
 
-        self.compile_bindings(slot, kind, &node);
+        self.compile_bindings(slot, kind, node);
 
         // Remove the temporary scope, but do not emit any additional cleanup
         // (OpAttrs consumes all of these locals).
@@ -569,7 +569,7 @@ impl Compiler<'_> {
                 }
 
                 KeySlot::Dynamic { slot, attr } => {
-                    self.compile_attr(slot, attr);
+                    self.compile_attr(slot, &attr);
                     self.scope_mut().mark_initialised(slot);
                 }
             }
@@ -584,9 +584,9 @@ impl Compiler<'_> {
                 } => {
                     // Create a thunk wrapping value (which may be one as well)
                     // to avoid forcing the from expr too early.
-                    self.thunk(binding.value_slot, &namespace, move |c, n, s| {
-                        c.compile(s, n.clone());
-                        c.emit_force(n);
+                    self.thunk(binding.value_slot, &namespace, |c, s| {
+                        c.compile(s, &namespace);
+                        c.emit_force(&namespace);
 
                         c.emit_constant(Value::String(name.into()), &span);
                         c.push_op(OpCode::OpAttrsSelect, &span);
@@ -595,11 +595,11 @@ impl Compiler<'_> {
 
                 // Binding is "just" a plain expression that needs to be
                 // compiled.
-                Binding::Plain { expr } => self.compile(binding.value_slot, expr),
+                Binding::Plain { expr } => self.compile(binding.value_slot, &expr),
 
                 // Binding is a merged or nested attribute set, and needs to be
                 // recursively compiled as another binding.
-                Binding::Set(set) => self.thunk(binding.value_slot, &set, |c, _, _| {
+                Binding::Set(set) => self.thunk(binding.value_slot, &set, |c, _| {
                     c.scope_mut().begin_scope();
                     c.compile_bindings(binding.value_slot, set.kind, &set);
                     c.scope_mut().end_scope();
@@ -647,20 +647,20 @@ impl Compiler<'_> {
     ///
     /// Unless in a non-standard scope, the encountered values are simply pushed
     /// on the stack and their indices noted in the entries vector.
-    pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: ast::LetIn) {
-        self.compile_bindings(slot, BindingsKind::LetIn, &node);
+    pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: &ast::LetIn) {
+        self.compile_bindings(slot, BindingsKind::LetIn, node);
 
         // Deal with the body, then clean up the locals afterwards.
-        self.compile(slot, node.body().unwrap());
-        self.cleanup_scope(&node);
+        self.compile(slot, &node.body().unwrap());
+        self.cleanup_scope(node);
     }
 
-    pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: ast::LegacyLet) {
-        self.emit_warning(&node, WarningKind::DeprecatedLegacyLet);
+    pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: &ast::LegacyLet) {
+        self.emit_warning(node, WarningKind::DeprecatedLegacyLet);
         self.scope_mut().begin_scope();
-        self.compile_bindings(slot, BindingsKind::RecAttrs, &node);
-        self.emit_constant(Value::String(SmolStr::new_inline("body").into()), &node);
-        self.push_op(OpCode::OpAttrsSelect, &node);
+        self.compile_bindings(slot, BindingsKind::RecAttrs, node);
+        self.emit_constant(Value::String(SmolStr::new_inline("body").into()), node);
+        self.push_op(OpCode::OpAttrsSelect, node);
     }
 
     /// Resolve and compile access to an identifier in the scope.
@@ -706,7 +706,7 @@ impl Compiler<'_> {
 
             // This identifier is referring to a value from the same scope which
             // is not yet defined. This identifier access must be thunked.
-            LocalPosition::Recursive(idx) => self.thunk(slot, node, move |compiler, node, _| {
+            LocalPosition::Recursive(idx) => self.thunk(slot, node, move |compiler, _| {
                 let upvalue_idx = compiler.add_upvalue(
                     compiler.contexts.len() - 1,
                     node,
@@ -717,9 +717,9 @@ impl Compiler<'_> {
         };
     }
 
-    pub(super) fn compile_ident(&mut self, slot: LocalIdx, node: ast::Ident) {
+    pub(super) fn compile_ident(&mut self, slot: LocalIdx, node: &ast::Ident) {
         let ident = node.ident_token().unwrap();
-        self.compile_identifier_access(slot, ident.text(), &node);
+        self.compile_identifier_access(slot, ident.text(), node);
     }
 }
 
diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs
index d69566f070d2..eb617b227351 100644
--- a/tvix/eval/src/compiler/mod.rs
+++ b/tvix/eval/src/compiler/mod.rs
@@ -177,7 +177,7 @@ impl Compiler<'_> {
 
 // Actual code-emitting AST traversal methods.
 impl Compiler<'_> {
-    fn compile(&mut self, slot: LocalIdx, expr: ast::Expr) {
+    fn compile(&mut self, slot: LocalIdx, expr: &ast::Expr) {
         match expr {
             ast::Expr::Literal(literal) => self.compile_literal(literal),
             ast::Expr::Path(path) => self.compile_path(path),
@@ -186,40 +186,36 @@ impl Compiler<'_> {
             ast::Expr::UnaryOp(op) => self.compile_unary_op(slot, op),
 
             ast::Expr::BinOp(binop) => {
-                self.thunk(slot, &binop, move |c, o, s| c.compile_binop(s, o.clone()))
+                self.thunk(slot, binop, move |c, s| c.compile_binop(s, binop))
             }
 
             ast::Expr::HasAttr(has_attr) => self.compile_has_attr(slot, has_attr),
 
-            ast::Expr::List(list) => {
-                self.thunk(slot, &list, move |c, l, s| c.compile_list(s, l.clone()))
-            }
+            ast::Expr::List(list) => self.thunk(slot, list, move |c, s| c.compile_list(s, list)),
 
-            ast::Expr::AttrSet(attrs) => self.thunk(slot, &attrs, move |c, a, s| {
-                c.compile_attr_set(s, a.clone())
-            }),
+            ast::Expr::AttrSet(attrs) => {
+                self.thunk(slot, attrs, move |c, s| c.compile_attr_set(s, attrs))
+            }
 
-            ast::Expr::Select(select) => self.thunk(slot, &select, move |c, sel, s| {
-                c.compile_select(s, sel.clone())
-            }),
+            ast::Expr::Select(select) => {
+                self.thunk(slot, select, move |c, s| c.compile_select(s, select))
+            }
 
             ast::Expr::Assert(assert) => {
-                self.thunk(slot, &assert, move |c, a, s| c.compile_assert(s, a.clone()))
+                self.thunk(slot, assert, move |c, s| c.compile_assert(s, assert))
             }
             ast::Expr::IfElse(if_else) => self.compile_if_else(slot, if_else),
             ast::Expr::LetIn(let_in) => self.compile_let_in(slot, let_in),
             ast::Expr::Ident(ident) => self.compile_ident(slot, ident),
-            ast::Expr::With(with) => {
-                self.thunk(slot, &with, |c, w, s| c.compile_with(s, w.clone()))
-            }
+            ast::Expr::With(with) => self.thunk(slot, with, |c, s| c.compile_with(s, with)),
             ast::Expr::Lambda(lambda) => self.compile_lambda(slot, lambda),
             ast::Expr::Apply(apply) => {
-                self.thunk(slot, &apply, move |c, a, s| c.compile_apply(s, a.clone()))
+                self.thunk(slot, apply, move |c, s| c.compile_apply(s, apply))
             }
 
             // Parenthesized expressions are simply unwrapped, leaving
             // their value on the stack.
-            ast::Expr::Paren(paren) => self.compile(slot, paren.expr().unwrap()),
+            ast::Expr::Paren(paren) => self.compile(slot, &paren.expr().unwrap()),
 
             ast::Expr::LegacyLet(legacy_let) => self.compile_legacy_let(slot, legacy_let),
 
@@ -228,24 +224,24 @@ impl Compiler<'_> {
         }
     }
 
-    fn compile_literal(&mut self, node: ast::Literal) {
+    fn compile_literal(&mut self, node: &ast::Literal) {
         let value = match node.kind() {
             ast::LiteralKind::Float(f) => Value::Float(f.value().unwrap()),
             ast::LiteralKind::Integer(i) => match i.value() {
                 Ok(v) => Value::Integer(v),
-                Err(err) => return self.emit_error(&node, err.into()),
+                Err(err) => return self.emit_error(node, err.into()),
             },
 
             ast::LiteralKind::Uri(u) => {
-                self.emit_warning(&node, WarningKind::DeprecatedLiteralURL);
+                self.emit_warning(node, WarningKind::DeprecatedLiteralURL);
                 Value::String(u.syntax().text().into())
             }
         };
 
-        self.emit_constant(value, &node);
+        self.emit_constant(value, node);
     }
 
-    fn compile_path(&mut self, node: ast::Path) {
+    fn compile_path(&mut self, node: &ast::Path) {
         // TODO(tazjin): placeholder implementation while waiting for
         // https://github.com/nix-community/rnix-parser/pull/96
 
@@ -257,7 +253,7 @@ impl Compiler<'_> {
                 Some(buf) => buf,
                 None => {
                     self.emit_error(
-                        &node,
+                        node,
                         ErrorKind::PathResolution("failed to determine home directory".into()),
                     );
                     return;
@@ -273,7 +269,7 @@ impl Compiler<'_> {
         } else {
             // TODO: decide what to do with findFile
             self.emit_error(
-                &node,
+                node,
                 ErrorKind::NotImplemented(
                     "other path types (e.g. <...> lookups) not yet implemented",
                 ),
@@ -284,7 +280,7 @@ impl Compiler<'_> {
         // TODO: Use https://github.com/rust-lang/rfcs/issues/2208
         // once it is available
         let value = Value::Path(path.clean());
-        self.emit_constant(value, &node);
+        self.emit_constant(value, node);
     }
 
     /// Helper that compiles the given string parts strictly. The caller
@@ -307,7 +303,7 @@ impl Compiler<'_> {
                 // the final string. We need to coerce them here,
                 // so OpInterpolate definitely has a string to consume.
                 ast::InterpolPart::Interpolation(ipol) => {
-                    self.compile(slot, ipol.expr().unwrap());
+                    self.compile(slot, &ipol.expr().unwrap());
                     // implicitly forces as well
                     self.push_op(OpCode::OpCoerceToString, ipol);
                 }
@@ -323,7 +319,7 @@ impl Compiler<'_> {
         }
     }
 
-    fn compile_str(&mut self, slot: LocalIdx, node: ast::Str) {
+    fn compile_str(&mut self, slot: LocalIdx, node: &ast::Str) {
         let parts = node.normalized_parts();
 
         // We need to thunk string expressions if they are the result of
@@ -332,27 +328,27 @@ impl Compiler<'_> {
         // coerce the result to a string value. This would require forcing the
         // value of the inner expression, so we need to wrap it in another thunk.
         if parts.len() != 1 || matches!(&parts[0], ast::InterpolPart::Interpolation(_)) {
-            self.thunk(slot, &node, move |c, n, s| {
-                c.compile_str_parts(s, n, parts);
+            self.thunk(slot, node, move |c, s| {
+                c.compile_str_parts(s, node, parts);
             });
         } else {
-            self.compile_str_parts(slot, &node, parts);
+            self.compile_str_parts(slot, node, parts);
         }
     }
 
-    fn compile_unary_op(&mut self, slot: LocalIdx, op: ast::UnaryOp) {
-        self.compile(slot, op.expr().unwrap());
-        self.emit_force(&op);
+    fn compile_unary_op(&mut self, slot: LocalIdx, op: &ast::UnaryOp) {
+        self.compile(slot, &op.expr().unwrap());
+        self.emit_force(op);
 
         let opcode = match op.operator().unwrap() {
             ast::UnaryOpKind::Invert => OpCode::OpInvert,
             ast::UnaryOpKind::Negate => OpCode::OpNegate,
         };
 
-        self.push_op(opcode, &op);
+        self.push_op(opcode, op);
     }
 
-    fn compile_binop(&mut self, slot: LocalIdx, op: ast::BinOp) {
+    fn compile_binop(&mut self, slot: LocalIdx, op: &ast::BinOp) {
         use ast::BinOpKind;
 
         // Short-circuiting and other strange operators, which are
@@ -370,28 +366,28 @@ impl Compiler<'_> {
         // For all other operators, the two values need to be left on
         // the stack in the correct order before pushing the
         // instruction for the operation itself.
-        self.compile(slot, op.lhs().unwrap());
+        self.compile(slot, &op.lhs().unwrap());
         self.emit_force(&op.lhs().unwrap());
 
-        self.compile(slot, op.rhs().unwrap());
+        self.compile(slot, &op.rhs().unwrap());
         self.emit_force(&op.rhs().unwrap());
 
         match op.operator().unwrap() {
-            BinOpKind::Add => self.push_op(OpCode::OpAdd, &op),
-            BinOpKind::Sub => self.push_op(OpCode::OpSub, &op),
-            BinOpKind::Mul => self.push_op(OpCode::OpMul, &op),
-            BinOpKind::Div => self.push_op(OpCode::OpDiv, &op),
-            BinOpKind::Update => self.push_op(OpCode::OpAttrsUpdate, &op),
-            BinOpKind::Equal => self.push_op(OpCode::OpEqual, &op),
-            BinOpKind::Less => self.push_op(OpCode::OpLess, &op),
-            BinOpKind::LessOrEq => self.push_op(OpCode::OpLessOrEq, &op),
-            BinOpKind::More => self.push_op(OpCode::OpMore, &op),
-            BinOpKind::MoreOrEq => self.push_op(OpCode::OpMoreOrEq, &op),
-            BinOpKind::Concat => self.push_op(OpCode::OpConcat, &op),
+            BinOpKind::Add => self.push_op(OpCode::OpAdd, op),
+            BinOpKind::Sub => self.push_op(OpCode::OpSub, op),
+            BinOpKind::Mul => self.push_op(OpCode::OpMul, op),
+            BinOpKind::Div => self.push_op(OpCode::OpDiv, op),
+            BinOpKind::Update => self.push_op(OpCode::OpAttrsUpdate, op),
+            BinOpKind::Equal => self.push_op(OpCode::OpEqual, op),
+            BinOpKind::Less => self.push_op(OpCode::OpLess, op),
+            BinOpKind::LessOrEq => self.push_op(OpCode::OpLessOrEq, op),
+            BinOpKind::More => self.push_op(OpCode::OpMore, op),
+            BinOpKind::MoreOrEq => self.push_op(OpCode::OpMoreOrEq, op),
+            BinOpKind::Concat => self.push_op(OpCode::OpConcat, op),
 
             BinOpKind::NotEqual => {
-                self.push_op(OpCode::OpEqual, &op);
-                self.push_op(OpCode::OpInvert, &op)
+                self.push_op(OpCode::OpEqual, op);
+                self.push_op(OpCode::OpInvert, op)
             }
 
             // Handled by separate branch above.
@@ -401,7 +397,7 @@ impl Compiler<'_> {
         };
     }
 
-    fn compile_and(&mut self, slot: LocalIdx, node: ast::BinOp) {
+    fn compile_and(&mut self, slot: LocalIdx, node: &ast::BinOp) {
         debug_assert!(
             matches!(node.operator(), Some(ast::BinOpKind::And)),
             "compile_and called with wrong operator kind: {:?}",
@@ -409,25 +405,25 @@ impl Compiler<'_> {
         );
 
         // Leave left-hand side value on the stack.
-        self.compile(slot, node.lhs().unwrap());
+        self.compile(slot, &node.lhs().unwrap());
         self.emit_force(&node.lhs().unwrap());
 
         // If this value is false, jump over the right-hand side - the
         // whole expression is false.
-        let end_idx = self.push_op(OpCode::OpJumpIfFalse(JumpOffset(0)), &node);
+        let end_idx = self.push_op(OpCode::OpJumpIfFalse(JumpOffset(0)), node);
 
         // Otherwise, remove the previous value and leave the
         // right-hand side on the stack. Its result is now the value
         // of the whole expression.
-        self.push_op(OpCode::OpPop, &node);
-        self.compile(slot, node.rhs().unwrap());
+        self.push_op(OpCode::OpPop, node);
+        self.compile(slot, &node.rhs().unwrap());
         self.emit_force(&node.rhs().unwrap());
 
         self.patch_jump(end_idx);
-        self.push_op(OpCode::OpAssertBool, &node);
+        self.push_op(OpCode::OpAssertBool, node);
     }
 
-    fn compile_or(&mut self, slot: LocalIdx, node: ast::BinOp) {
+    fn compile_or(&mut self, slot: LocalIdx, node: &ast::BinOp) {
         debug_assert!(
             matches!(node.operator(), Some(ast::BinOpKind::Or)),
             "compile_or called with wrong operator kind: {:?}",
@@ -435,21 +431,21 @@ impl Compiler<'_> {
         );
 
         // Leave left-hand side value on the stack
-        self.compile(slot, node.lhs().unwrap());
+        self.compile(slot, &node.lhs().unwrap());
         self.emit_force(&node.lhs().unwrap());
 
         // Opposite of above: If this value is **true**, we can
         // short-circuit the right-hand side.
-        let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), &node);
-        self.push_op(OpCode::OpPop, &node);
-        self.compile(slot, node.rhs().unwrap());
+        let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), node);
+        self.push_op(OpCode::OpPop, node);
+        self.compile(slot, &node.rhs().unwrap());
         self.emit_force(&node.rhs().unwrap());
 
         self.patch_jump(end_idx);
-        self.push_op(OpCode::OpAssertBool, &node);
+        self.push_op(OpCode::OpAssertBool, node);
     }
 
-    fn compile_implication(&mut self, slot: LocalIdx, node: ast::BinOp) {
+    fn compile_implication(&mut self, slot: LocalIdx, node: &ast::BinOp) {
         debug_assert!(
             matches!(node.operator(), Some(ast::BinOpKind::Implication)),
             "compile_implication called with wrong operator kind: {:?}",
@@ -457,18 +453,18 @@ impl Compiler<'_> {
         );
 
         // Leave left-hand side value on the stack and invert it.
-        self.compile(slot, node.lhs().unwrap());
+        self.compile(slot, &node.lhs().unwrap());
         self.emit_force(&node.lhs().unwrap());
-        self.push_op(OpCode::OpInvert, &node);
+        self.push_op(OpCode::OpInvert, node);
 
         // Exactly as `||` (because `a -> b` = `!a || b`).
-        let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), &node);
-        self.push_op(OpCode::OpPop, &node);
-        self.compile(slot, node.rhs().unwrap());
+        let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), node);
+        self.push_op(OpCode::OpPop, node);
+        self.compile(slot, &node.rhs().unwrap());
         self.emit_force(&node.rhs().unwrap());
 
         self.patch_jump(end_idx);
-        self.push_op(OpCode::OpAssertBool, &node);
+        self.push_op(OpCode::OpAssertBool, node);
     }
 
     /// Compile list literals into equivalent bytecode. List
@@ -478,7 +474,7 @@ impl Compiler<'_> {
     ///
     /// The VM, after evaluating the code for each element, simply
     /// constructs the list from the given number of elements.
-    fn compile_list(&mut self, slot: LocalIdx, node: ast::List) {
+    fn compile_list(&mut self, slot: LocalIdx, node: &ast::List) {
         let mut count = 0;
 
         // Open a temporary scope to correctly account for stack items
@@ -498,34 +494,34 @@ impl Compiler<'_> {
             };
 
             count += 1;
-            self.compile(item_slot, item);
+            self.compile(item_slot, &item);
             self.scope_mut().mark_initialised(item_slot);
         }
 
-        self.push_op(OpCode::OpList(Count(count)), &node);
+        self.push_op(OpCode::OpList(Count(count)), node);
         self.scope_mut().end_scope();
     }
 
-    fn compile_attr(&mut self, slot: LocalIdx, node: ast::Attr) {
+    fn compile_attr(&mut self, slot: LocalIdx, node: &ast::Attr) {
         match node {
             ast::Attr::Dynamic(dynamic) => {
-                self.compile(slot, dynamic.expr().unwrap());
+                self.compile(slot, &dynamic.expr().unwrap());
                 self.emit_force(&dynamic.expr().unwrap());
             }
 
             ast::Attr::Str(s) => {
-                self.compile_str(slot, s.clone());
-                self.emit_force(&s);
+                self.compile_str(slot, s);
+                self.emit_force(s);
             }
 
             ast::Attr::Ident(ident) => self.emit_literal_ident(&ident),
         }
     }
 
-    fn compile_has_attr(&mut self, slot: LocalIdx, node: ast::HasAttr) {
+    fn compile_has_attr(&mut self, slot: LocalIdx, node: &ast::HasAttr) {
         // Put the attribute set on the stack.
-        self.compile(slot, node.expr().unwrap());
-        self.emit_force(&node);
+        self.compile(slot, &node.expr().unwrap());
+        self.emit_force(node);
 
         // Push all path fragments with an operation for fetching the
         // next nested element, for all fragments except the last one.
@@ -535,15 +531,15 @@ impl Compiler<'_> {
                 self.emit_force(&fragment);
             }
 
-            self.compile_attr(slot, fragment);
+            self.compile_attr(slot, &fragment);
         }
 
         // After the last fragment, emit the actual instruction that
         // leaves a boolean on the stack.
-        self.push_op(OpCode::OpHasAttr, &node);
+        self.push_op(OpCode::OpHasAttr, node);
     }
 
-    fn compile_select(&mut self, slot: LocalIdx, node: ast::Select) {
+    fn compile_select(&mut self, slot: LocalIdx, node: &ast::Select) {
         let set = node.expr().unwrap();
         let path = node.attrpath().unwrap();
 
@@ -553,7 +549,7 @@ impl Compiler<'_> {
         }
 
         // Push the set onto the stack
-        self.compile(slot, set);
+        self.compile(slot, &set);
 
         // Compile each key fragment and emit access instructions.
         //
@@ -563,7 +559,7 @@ impl Compiler<'_> {
             // Force the current set value.
             self.emit_force(&fragment);
 
-            self.compile_attr(slot, fragment.clone());
+            self.compile_attr(slot, &fragment);
             self.push_op(OpCode::OpAttrsSelect, &fragment);
         }
     }
@@ -604,12 +600,12 @@ impl Compiler<'_> {
         path: ast::Attrpath,
         default: ast::Expr,
     ) {
-        self.compile(slot, set);
+        self.compile(slot, &set);
         let mut jumps = vec![];
 
         for fragment in path.attrs() {
             self.emit_force(&fragment);
-            self.compile_attr(slot, fragment.clone());
+            self.compile_attr(slot, &fragment.clone());
             self.push_op(OpCode::OpAttrsTrySelect, &fragment);
             jumps.push(self.push_op(OpCode::OpJumpIfNotFound(JumpOffset(0)), &fragment));
         }
@@ -622,20 +618,20 @@ impl Compiler<'_> {
 
         // Compile the default value expression and patch the final
         // jump to point *beyond* it.
-        self.compile(slot, default);
+        self.compile(slot, &default);
         self.patch_jump(final_jump);
     }
 
-    fn compile_assert(&mut self, slot: LocalIdx, node: ast::Assert) {
+    fn compile_assert(&mut self, slot: LocalIdx, node: &ast::Assert) {
         // Compile the assertion condition to leave its value on the stack.
-        self.compile(slot, node.condition().unwrap());
+        self.compile(slot, &node.condition().unwrap());
         self.emit_force(&node.condition().unwrap());
         self.push_op(OpCode::OpAssert, &node.condition().unwrap());
 
         // The runtime will abort evaluation at this point if the
         // assertion failed, if not the body simply continues on like
         // normal.
-        self.compile(slot, node.body().unwrap());
+        self.compile(slot, &node.body().unwrap());
     }
 
     /// Compile conditional expressions using jumping instructions in the VM.
@@ -650,8 +646,8 @@ impl Compiler<'_> {
     ///  if condition is true.└┼─5─→     ...        │
     ///                        └────────────────────┘
     /// ```
-    fn compile_if_else(&mut self, slot: LocalIdx, node: ast::IfElse) {
-        self.compile(slot, node.condition().unwrap());
+    fn compile_if_else(&mut self, slot: LocalIdx, node: &ast::IfElse) {
+        self.compile(slot, &node.condition().unwrap());
         self.emit_force(&node.condition().unwrap());
 
         let then_idx = self.push_op(
@@ -659,14 +655,14 @@ impl Compiler<'_> {
             &node.condition().unwrap(),
         );
 
-        self.push_op(OpCode::OpPop, &node); // discard condition value
-        self.compile(slot, node.body().unwrap());
+        self.push_op(OpCode::OpPop, node); // discard condition value
+        self.compile(slot, &node.body().unwrap());
 
-        let else_idx = self.push_op(OpCode::OpJump(JumpOffset(0)), &node);
+        let else_idx = self.push_op(OpCode::OpJump(JumpOffset(0)), node);
 
         self.patch_jump(then_idx); // patch jump *to* else_body
-        self.push_op(OpCode::OpPop, &node); // discard condition value
-        self.compile(slot, node.else_body().unwrap());
+        self.push_op(OpCode::OpPop, node); // discard condition value
+        self.compile(slot, &node.else_body().unwrap());
 
         self.patch_jump(else_idx); // patch jump *over* else body
     }
@@ -674,12 +670,12 @@ impl Compiler<'_> {
     /// Compile `with` expressions by emitting instructions that
     /// pop/remove the indices of attribute sets that are implicitly
     /// in scope through `with` on the "with-stack".
-    fn compile_with(&mut self, slot: LocalIdx, node: ast::With) {
+    fn compile_with(&mut self, slot: LocalIdx, node: &ast::With) {
         self.scope_mut().begin_scope();
         // TODO: Detect if the namespace is just an identifier, and
         // resolve that directly (thus avoiding duplication on the
         // stack).
-        self.compile(slot, node.namespace().unwrap());
+        self.compile(slot, &node.namespace().unwrap());
 
         let span = self.span_for(&node.namespace().unwrap());
 
@@ -695,11 +691,11 @@ impl Compiler<'_> {
 
         self.push_op(OpCode::OpPushWith(with_idx), &node.namespace().unwrap());
 
-        self.compile(slot, node.body().unwrap());
+        self.compile(slot, &node.body().unwrap());
 
-        self.push_op(OpCode::OpPopWith, &node);
+        self.push_op(OpCode::OpPopWith, node);
         self.scope_mut().pop_with();
-        self.cleanup_scope(&node);
+        self.cleanup_scope(node);
     }
 
     /// Compiles pattern function arguments, such as `{ a, b }: ...`.
@@ -731,8 +727,8 @@ impl Compiler<'_> {
     /// many arguments are provided. This is done by emitting a
     /// special instruction that checks the set of keys from a
     /// constant containing the expected keys.
-    fn compile_param_pattern(&mut self, pattern: ast::Pattern) {
-        let span = self.span_for(&pattern);
+    fn compile_param_pattern(&mut self, pattern: &ast::Pattern) {
+        let span = self.span_for(pattern);
         let set_idx = match pattern.pat_bind() {
             Some(name) => self.declare_local(&name, name.ident().unwrap().to_string()),
             None => self.scope_mut().declare_phantom(span, true),
@@ -741,7 +737,7 @@ impl Compiler<'_> {
         // At call time, the attribute set is already at the top of
         // the stack.
         self.scope_mut().mark_initialised(set_idx);
-        self.emit_force(&pattern);
+        self.emit_force(pattern);
 
         // Similar to `let ... in ...`, we now do multiple passes over
         // the bindings to first declare them, then populate them, and
@@ -760,7 +756,7 @@ impl Compiler<'_> {
         // attempt to select from it.
         let stack_idx = self.scope().stack_index(set_idx);
         for (idx, entry) in entries.into_iter() {
-            self.push_op(OpCode::OpGetLocal(stack_idx), &pattern);
+            self.push_op(OpCode::OpGetLocal(stack_idx), pattern);
             self.emit_literal_ident(&entry.ident().unwrap());
 
             // Use the same mechanism as `compile_select_or` if a
@@ -774,7 +770,7 @@ impl Compiler<'_> {
                 let jump_over_default = self.push_op(OpCode::OpJump(JumpOffset(0)), &default_expr);
 
                 self.patch_jump(jump_to_default);
-                self.compile(idx, default_expr);
+                self.compile(idx, &default_expr);
                 self.patch_jump(jump_over_default);
             } else {
                 self.push_op(OpCode::OpAttrsSelect, &entry.ident().unwrap());
@@ -786,26 +782,26 @@ impl Compiler<'_> {
         for idx in indices {
             if self.scope()[idx].needs_finaliser {
                 let stack_idx = self.scope().stack_index(idx);
-                self.push_op(OpCode::OpFinalise(stack_idx), &pattern);
+                self.push_op(OpCode::OpFinalise(stack_idx), pattern);
             }
         }
 
         // TODO: strictly check if all keys have been consumed if
         // there is no ellipsis.
         if pattern.ellipsis_token().is_none() {
-            self.emit_warning(&pattern, WarningKind::NotImplemented("closed formals"));
+            self.emit_warning(pattern, WarningKind::NotImplemented("closed formals"));
         }
     }
 
-    fn compile_lambda(&mut self, outer_slot: LocalIdx, node: ast::Lambda) {
+    fn compile_lambda(&mut self, outer_slot: LocalIdx, node: &ast::Lambda) {
         self.new_context();
-        let span = self.span_for(&node);
+        let span = self.span_for(node);
         let slot = self.scope_mut().declare_phantom(span, false);
         self.scope_mut().begin_scope();
 
         // Compile the function itself
         match node.param().unwrap() {
-            ast::Param::Pattern(pat) => self.compile_param_pattern(pat),
+            ast::Param::Pattern(pat) => self.compile_param_pattern(&pat),
 
             ast::Param::IdentParam(param) => {
                 let name = param
@@ -821,8 +817,8 @@ impl Compiler<'_> {
             }
         }
 
-        self.compile(slot, node.body().unwrap());
-        self.cleanup_scope(&node);
+        self.compile(slot, &node.body().unwrap());
+        self.cleanup_scope(node);
 
         // TODO: determine and insert enclosing name, if available.
 
@@ -845,7 +841,7 @@ impl Compiler<'_> {
         // If the function is not a closure, just emit it directly and
         // move on.
         if lambda.upvalue_count == 0 {
-            self.emit_constant(Value::Closure(Closure::new(lambda)), &node);
+            self.emit_constant(Value::Closure(Closure::new(lambda)), node);
             return;
         }
 
@@ -855,24 +851,24 @@ impl Compiler<'_> {
         // which the runtime closure can be constructed.
         let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda));
 
-        self.push_op(OpCode::OpClosure(blueprint_idx), &node);
+        self.push_op(OpCode::OpClosure(blueprint_idx), node);
         self.emit_upvalue_data(
             outer_slot,
-            &node,
+            node,
             compiled.scope.upvalues,
             compiled.captures_with_stack,
         );
     }
 
-    fn compile_apply(&mut self, slot: LocalIdx, node: ast::Apply) {
+    fn compile_apply(&mut self, slot: LocalIdx, node: &ast::Apply) {
         // To call a function, we leave its arguments on the stack,
         // followed by the function expression itself, and then emit a
         // call instruction. This way, the stack is perfectly laid out
         // to enter the function call straight away.
-        self.compile(slot, node.argument().unwrap());
-        self.compile(slot, node.lambda().unwrap());
+        self.compile(slot, &node.argument().unwrap());
+        self.compile(slot, &node.lambda().unwrap());
         self.emit_force(&node.lambda().unwrap());
-        self.push_op(OpCode::OpCall, &node);
+        self.push_op(OpCode::OpCall, node);
     }
 
     /// Compile an expression into a runtime thunk which should be
@@ -880,14 +876,14 @@ impl Compiler<'_> {
     // TODO: almost the same as Compiler::compile_lambda; unify?
     fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
     where
-        N: ToSpan + Clone,
-        F: FnOnce(&mut Compiler, &N, LocalIdx),
+        N: ToSpan,
+        F: FnOnce(&mut Compiler, LocalIdx),
     {
         self.new_context();
         let span = self.span_for(node);
         let slot = self.scope_mut().declare_phantom(span, false);
         self.scope_mut().begin_scope();
-        content(self, node, slot);
+        content(self, slot);
         self.cleanup_scope(node);
 
         let mut thunk = self.contexts.pop().unwrap();
@@ -1142,7 +1138,7 @@ fn prepare_globals(additional: HashMap<&'static str, Value>) -> GlobalsMap {
 }
 
 pub fn compile(
-    expr: ast::Expr,
+    expr: &ast::Expr,
     location: Option<PathBuf>,
     file: Arc<codemap::File>,
     globals: HashMap<&'static str, Value>,
@@ -1150,15 +1146,15 @@ pub fn compile(
 ) -> EvalResult<CompilationOutput> {
     let mut c = Compiler::new(location, file, globals, observer)?;
 
-    let root_span = c.span_for(&expr);
+    let root_span = c.span_for(expr);
     let root_slot = c.scope_mut().declare_phantom(root_span, false);
-    c.compile(root_slot, expr.clone());
+    c.compile(root_slot, &expr);
 
     // The final operation of any top-level Nix program must always be
     // `OpForce`. A thunk should not be returned to the user in an
     // unevaluated state (though in practice, a value *containing* a
     // thunk might be returned).
-    c.emit_force(&expr);
+    c.emit_force(expr);
 
     let lambda = Rc::new(c.contexts.pop().unwrap().lambda);
     c.observer.observe_compiled_toplevel(&lambda);
diff --git a/tvix/eval/src/eval.rs b/tvix/eval/src/eval.rs
index e6199e00b0e2..bc430e58043f 100644
--- a/tvix/eval/src/eval.rs
+++ b/tvix/eval/src/eval.rs
@@ -60,7 +60,7 @@ pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> Eva
 
     let result = if options.dump_bytecode {
         crate::compiler::compile(
-            root_expr,
+            &root_expr,
             location,
             file.clone(),
             global_builtins(),
@@ -68,7 +68,7 @@ pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> Eva
         )
     } else {
         crate::compiler::compile(
-            root_expr,
+            &root_expr,
             location,
             file.clone(),
             global_builtins(),