refactor(tvix/eval): remove unnecessary clones in compiler

There's basically nothing that needs *ownership* of an AST
node (which is just a little box full of references to other things
anyways), so we can thread this through as references all the way.

Change-Id: I35a1348a50c0e8e07d51dfc18847829379166fbf
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6853
Tested-by: BuildkiteCI
Reviewed-by: grfn <grfn@gws.fyi>
This commit is contained in:
Vincent Ambo 2022-10-03 17:08:39 +03:00 committed by tazjin
parent b69b50feb1
commit 2ff764ceb7
4 changed files with 147 additions and 151 deletions

View file

@ -254,7 +254,7 @@ fn eval(trace: bool, code: &str) -> Output {
let mut compilation_observer = DisassemblingObserver::new(codemap.clone(), &mut out.bytecode); let mut compilation_observer = DisassemblingObserver::new(codemap.clone(), &mut out.bytecode);
let result = tvix_eval::compile( let result = tvix_eval::compile(
root_expr, &root_expr,
Some("/nixbolt".into()), Some("/nixbolt".into()),
file.clone(), file.clone(),
tvix_eval::global_builtins(), tvix_eval::global_builtins(),

View file

@ -533,7 +533,7 @@ impl Compiler<'_> {
/// 1. Keys can be dynamically constructed through interpolation. /// 1. Keys can be dynamically constructed through interpolation.
/// 2. Keys can refer to nested attribute sets. /// 2. Keys can refer to nested attribute sets.
/// 3. Attribute sets can (optionally) be recursive. /// 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 // Open a scope to track the positions of the temporaries used by the
// `OpAttrs` instruction. // `OpAttrs` instruction.
self.scope_mut().begin_scope(); self.scope_mut().begin_scope();
@ -544,7 +544,7 @@ impl Compiler<'_> {
BindingsKind::Attrs 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 // Remove the temporary scope, but do not emit any additional cleanup
// (OpAttrs consumes all of these locals). // (OpAttrs consumes all of these locals).
@ -569,7 +569,7 @@ impl Compiler<'_> {
} }
KeySlot::Dynamic { slot, attr } => { KeySlot::Dynamic { slot, attr } => {
self.compile_attr(slot, attr); self.compile_attr(slot, &attr);
self.scope_mut().mark_initialised(slot); self.scope_mut().mark_initialised(slot);
} }
} }
@ -584,9 +584,9 @@ impl Compiler<'_> {
} => { } => {
// Create a thunk wrapping value (which may be one as well) // Create a thunk wrapping value (which may be one as well)
// to avoid forcing the from expr too early. // to avoid forcing the from expr too early.
self.thunk(binding.value_slot, &namespace, move |c, n, s| { self.thunk(binding.value_slot, &namespace, |c, s| {
c.compile(s, n.clone()); c.compile(s, &namespace);
c.emit_force(n); c.emit_force(&namespace);
c.emit_constant(Value::String(name.into()), &span); c.emit_constant(Value::String(name.into()), &span);
c.push_op(OpCode::OpAttrsSelect, &span); c.push_op(OpCode::OpAttrsSelect, &span);
@ -595,11 +595,11 @@ impl Compiler<'_> {
// Binding is "just" a plain expression that needs to be // Binding is "just" a plain expression that needs to be
// compiled. // 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 // Binding is a merged or nested attribute set, and needs to be
// recursively compiled as another binding. // 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.scope_mut().begin_scope();
c.compile_bindings(binding.value_slot, set.kind, &set); c.compile_bindings(binding.value_slot, set.kind, &set);
c.scope_mut().end_scope(); c.scope_mut().end_scope();
@ -647,20 +647,20 @@ impl Compiler<'_> {
/// ///
/// Unless in a non-standard scope, the encountered values are simply pushed /// Unless in a non-standard scope, the encountered values are simply pushed
/// on the stack and their indices noted in the entries vector. /// on the stack and their indices noted in the entries vector.
pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: ast::LetIn) { pub(super) fn compile_let_in(&mut self, slot: LocalIdx, node: &ast::LetIn) {
self.compile_bindings(slot, BindingsKind::LetIn, &node); self.compile_bindings(slot, BindingsKind::LetIn, node);
// Deal with the body, then clean up the locals afterwards. // Deal with the body, then clean up the locals afterwards.
self.compile(slot, node.body().unwrap()); self.compile(slot, &node.body().unwrap());
self.cleanup_scope(&node); self.cleanup_scope(node);
} }
pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: ast::LegacyLet) { pub(super) fn compile_legacy_let(&mut self, slot: LocalIdx, node: &ast::LegacyLet) {
self.emit_warning(&node, WarningKind::DeprecatedLegacyLet); self.emit_warning(node, WarningKind::DeprecatedLegacyLet);
self.scope_mut().begin_scope(); self.scope_mut().begin_scope();
self.compile_bindings(slot, BindingsKind::RecAttrs, &node); self.compile_bindings(slot, BindingsKind::RecAttrs, node);
self.emit_constant(Value::String(SmolStr::new_inline("body").into()), &node); self.emit_constant(Value::String(SmolStr::new_inline("body").into()), node);
self.push_op(OpCode::OpAttrsSelect, &node); self.push_op(OpCode::OpAttrsSelect, node);
} }
/// Resolve and compile access to an identifier in the scope. /// 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 // This identifier is referring to a value from the same scope which
// is not yet defined. This identifier access must be thunked. // 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( let upvalue_idx = compiler.add_upvalue(
compiler.contexts.len() - 1, compiler.contexts.len() - 1,
node, 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(); let ident = node.ident_token().unwrap();
self.compile_identifier_access(slot, ident.text(), &node); self.compile_identifier_access(slot, ident.text(), node);
} }
} }

View file

@ -177,7 +177,7 @@ impl Compiler<'_> {
// Actual code-emitting AST traversal methods. // Actual code-emitting AST traversal methods.
impl Compiler<'_> { impl Compiler<'_> {
fn compile(&mut self, slot: LocalIdx, expr: ast::Expr) { fn compile(&mut self, slot: LocalIdx, expr: &ast::Expr) {
match expr { match expr {
ast::Expr::Literal(literal) => self.compile_literal(literal), ast::Expr::Literal(literal) => self.compile_literal(literal),
ast::Expr::Path(path) => self.compile_path(path), 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::UnaryOp(op) => self.compile_unary_op(slot, op),
ast::Expr::BinOp(binop) => { 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::HasAttr(has_attr) => self.compile_has_attr(slot, has_attr),
ast::Expr::List(list) => { ast::Expr::List(list) => self.thunk(slot, list, move |c, s| c.compile_list(s, list)),
self.thunk(slot, &list, move |c, l, s| c.compile_list(s, l.clone()))
ast::Expr::AttrSet(attrs) => {
self.thunk(slot, attrs, move |c, s| c.compile_attr_set(s, attrs))
} }
ast::Expr::AttrSet(attrs) => self.thunk(slot, &attrs, move |c, a, s| { ast::Expr::Select(select) => {
c.compile_attr_set(s, a.clone()) self.thunk(slot, select, move |c, s| c.compile_select(s, select))
}), }
ast::Expr::Select(select) => self.thunk(slot, &select, move |c, sel, s| {
c.compile_select(s, sel.clone())
}),
ast::Expr::Assert(assert) => { 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::IfElse(if_else) => self.compile_if_else(slot, if_else),
ast::Expr::LetIn(let_in) => self.compile_let_in(slot, let_in), ast::Expr::LetIn(let_in) => self.compile_let_in(slot, let_in),
ast::Expr::Ident(ident) => self.compile_ident(slot, ident), ast::Expr::Ident(ident) => self.compile_ident(slot, ident),
ast::Expr::With(with) => { ast::Expr::With(with) => self.thunk(slot, with, |c, s| c.compile_with(s, with)),
self.thunk(slot, &with, |c, w, s| c.compile_with(s, w.clone()))
}
ast::Expr::Lambda(lambda) => self.compile_lambda(slot, lambda), ast::Expr::Lambda(lambda) => self.compile_lambda(slot, lambda),
ast::Expr::Apply(apply) => { 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 // Parenthesized expressions are simply unwrapped, leaving
// their value on the stack. // 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), 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() { let value = match node.kind() {
ast::LiteralKind::Float(f) => Value::Float(f.value().unwrap()), ast::LiteralKind::Float(f) => Value::Float(f.value().unwrap()),
ast::LiteralKind::Integer(i) => match i.value() { ast::LiteralKind::Integer(i) => match i.value() {
Ok(v) => Value::Integer(v), 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) => { ast::LiteralKind::Uri(u) => {
self.emit_warning(&node, WarningKind::DeprecatedLiteralURL); self.emit_warning(node, WarningKind::DeprecatedLiteralURL);
Value::String(u.syntax().text().into()) 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 // TODO(tazjin): placeholder implementation while waiting for
// https://github.com/nix-community/rnix-parser/pull/96 // https://github.com/nix-community/rnix-parser/pull/96
@ -257,7 +253,7 @@ impl Compiler<'_> {
Some(buf) => buf, Some(buf) => buf,
None => { None => {
self.emit_error( self.emit_error(
&node, node,
ErrorKind::PathResolution("failed to determine home directory".into()), ErrorKind::PathResolution("failed to determine home directory".into()),
); );
return; return;
@ -273,7 +269,7 @@ impl Compiler<'_> {
} else { } else {
// TODO: decide what to do with findFile // TODO: decide what to do with findFile
self.emit_error( self.emit_error(
&node, node,
ErrorKind::NotImplemented( ErrorKind::NotImplemented(
"other path types (e.g. <...> lookups) not yet implemented", "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 // TODO: Use https://github.com/rust-lang/rfcs/issues/2208
// once it is available // once it is available
let value = Value::Path(path.clean()); 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 /// 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, // the final string. We need to coerce them here,
// so OpInterpolate definitely has a string to consume. // so OpInterpolate definitely has a string to consume.
ast::InterpolPart::Interpolation(ipol) => { ast::InterpolPart::Interpolation(ipol) => {
self.compile(slot, ipol.expr().unwrap()); self.compile(slot, &ipol.expr().unwrap());
// implicitly forces as well // implicitly forces as well
self.push_op(OpCode::OpCoerceToString, ipol); 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(); let parts = node.normalized_parts();
// We need to thunk string expressions if they are the result of // 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 // 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. // value of the inner expression, so we need to wrap it in another thunk.
if parts.len() != 1 || matches!(&parts[0], ast::InterpolPart::Interpolation(_)) { if parts.len() != 1 || matches!(&parts[0], ast::InterpolPart::Interpolation(_)) {
self.thunk(slot, &node, move |c, n, s| { self.thunk(slot, node, move |c, s| {
c.compile_str_parts(s, n, parts); c.compile_str_parts(s, node, parts);
}); });
} else { } 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) { fn compile_unary_op(&mut self, slot: LocalIdx, op: &ast::UnaryOp) {
self.compile(slot, op.expr().unwrap()); self.compile(slot, &op.expr().unwrap());
self.emit_force(&op); self.emit_force(op);
let opcode = match op.operator().unwrap() { let opcode = match op.operator().unwrap() {
ast::UnaryOpKind::Invert => OpCode::OpInvert, ast::UnaryOpKind::Invert => OpCode::OpInvert,
ast::UnaryOpKind::Negate => OpCode::OpNegate, 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; use ast::BinOpKind;
// Short-circuiting and other strange operators, which are // 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 // For all other operators, the two values need to be left on
// the stack in the correct order before pushing the // the stack in the correct order before pushing the
// instruction for the operation itself. // instruction for the operation itself.
self.compile(slot, op.lhs().unwrap()); self.compile(slot, &op.lhs().unwrap());
self.emit_force(&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()); self.emit_force(&op.rhs().unwrap());
match op.operator().unwrap() { match op.operator().unwrap() {
BinOpKind::Add => self.push_op(OpCode::OpAdd, &op), BinOpKind::Add => self.push_op(OpCode::OpAdd, op),
BinOpKind::Sub => self.push_op(OpCode::OpSub, &op), BinOpKind::Sub => self.push_op(OpCode::OpSub, op),
BinOpKind::Mul => self.push_op(OpCode::OpMul, &op), BinOpKind::Mul => self.push_op(OpCode::OpMul, op),
BinOpKind::Div => self.push_op(OpCode::OpDiv, &op), BinOpKind::Div => self.push_op(OpCode::OpDiv, op),
BinOpKind::Update => self.push_op(OpCode::OpAttrsUpdate, &op), BinOpKind::Update => self.push_op(OpCode::OpAttrsUpdate, op),
BinOpKind::Equal => self.push_op(OpCode::OpEqual, &op), BinOpKind::Equal => self.push_op(OpCode::OpEqual, op),
BinOpKind::Less => self.push_op(OpCode::OpLess, &op), BinOpKind::Less => self.push_op(OpCode::OpLess, op),
BinOpKind::LessOrEq => self.push_op(OpCode::OpLessOrEq, &op), BinOpKind::LessOrEq => self.push_op(OpCode::OpLessOrEq, op),
BinOpKind::More => self.push_op(OpCode::OpMore, &op), BinOpKind::More => self.push_op(OpCode::OpMore, op),
BinOpKind::MoreOrEq => self.push_op(OpCode::OpMoreOrEq, &op), BinOpKind::MoreOrEq => self.push_op(OpCode::OpMoreOrEq, op),
BinOpKind::Concat => self.push_op(OpCode::OpConcat, &op), BinOpKind::Concat => self.push_op(OpCode::OpConcat, op),
BinOpKind::NotEqual => { BinOpKind::NotEqual => {
self.push_op(OpCode::OpEqual, &op); self.push_op(OpCode::OpEqual, op);
self.push_op(OpCode::OpInvert, &op) self.push_op(OpCode::OpInvert, op)
} }
// Handled by separate branch above. // 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!( debug_assert!(
matches!(node.operator(), Some(ast::BinOpKind::And)), matches!(node.operator(), Some(ast::BinOpKind::And)),
"compile_and called with wrong operator kind: {:?}", "compile_and called with wrong operator kind: {:?}",
@ -409,25 +405,25 @@ impl Compiler<'_> {
); );
// Leave left-hand side value on the stack. // 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()); self.emit_force(&node.lhs().unwrap());
// If this value is false, jump over the right-hand side - the // If this value is false, jump over the right-hand side - the
// whole expression is false. // 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 // Otherwise, remove the previous value and leave the
// right-hand side on the stack. Its result is now the value // right-hand side on the stack. Its result is now the value
// of the whole expression. // of the whole expression.
self.push_op(OpCode::OpPop, &node); self.push_op(OpCode::OpPop, node);
self.compile(slot, node.rhs().unwrap()); self.compile(slot, &node.rhs().unwrap());
self.emit_force(&node.rhs().unwrap()); self.emit_force(&node.rhs().unwrap());
self.patch_jump(end_idx); 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!( debug_assert!(
matches!(node.operator(), Some(ast::BinOpKind::Or)), matches!(node.operator(), Some(ast::BinOpKind::Or)),
"compile_or called with wrong operator kind: {:?}", "compile_or called with wrong operator kind: {:?}",
@ -435,21 +431,21 @@ impl Compiler<'_> {
); );
// Leave left-hand side value on the stack // 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()); self.emit_force(&node.lhs().unwrap());
// Opposite of above: If this value is **true**, we can // Opposite of above: If this value is **true**, we can
// short-circuit the right-hand side. // short-circuit the right-hand side.
let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), &node); let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), node);
self.push_op(OpCode::OpPop, &node); self.push_op(OpCode::OpPop, node);
self.compile(slot, node.rhs().unwrap()); self.compile(slot, &node.rhs().unwrap());
self.emit_force(&node.rhs().unwrap()); self.emit_force(&node.rhs().unwrap());
self.patch_jump(end_idx); 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!( debug_assert!(
matches!(node.operator(), Some(ast::BinOpKind::Implication)), matches!(node.operator(), Some(ast::BinOpKind::Implication)),
"compile_implication called with wrong operator kind: {:?}", "compile_implication called with wrong operator kind: {:?}",
@ -457,18 +453,18 @@ impl Compiler<'_> {
); );
// Leave left-hand side value on the stack and invert it. // 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.emit_force(&node.lhs().unwrap());
self.push_op(OpCode::OpInvert, &node); self.push_op(OpCode::OpInvert, node);
// Exactly as `||` (because `a -> b` = `!a || b`). // Exactly as `||` (because `a -> b` = `!a || b`).
let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), &node); let end_idx = self.push_op(OpCode::OpJumpIfTrue(JumpOffset(0)), node);
self.push_op(OpCode::OpPop, &node); self.push_op(OpCode::OpPop, node);
self.compile(slot, node.rhs().unwrap()); self.compile(slot, &node.rhs().unwrap());
self.emit_force(&node.rhs().unwrap()); self.emit_force(&node.rhs().unwrap());
self.patch_jump(end_idx); self.patch_jump(end_idx);
self.push_op(OpCode::OpAssertBool, &node); self.push_op(OpCode::OpAssertBool, node);
} }
/// Compile list literals into equivalent bytecode. List /// Compile list literals into equivalent bytecode. List
@ -478,7 +474,7 @@ impl Compiler<'_> {
/// ///
/// The VM, after evaluating the code for each element, simply /// The VM, after evaluating the code for each element, simply
/// constructs the list from the given number of elements. /// 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; let mut count = 0;
// Open a temporary scope to correctly account for stack items // Open a temporary scope to correctly account for stack items
@ -498,34 +494,34 @@ impl Compiler<'_> {
}; };
count += 1; count += 1;
self.compile(item_slot, item); self.compile(item_slot, &item);
self.scope_mut().mark_initialised(item_slot); 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(); 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 { match node {
ast::Attr::Dynamic(dynamic) => { ast::Attr::Dynamic(dynamic) => {
self.compile(slot, dynamic.expr().unwrap()); self.compile(slot, &dynamic.expr().unwrap());
self.emit_force(&dynamic.expr().unwrap()); self.emit_force(&dynamic.expr().unwrap());
} }
ast::Attr::Str(s) => { ast::Attr::Str(s) => {
self.compile_str(slot, s.clone()); self.compile_str(slot, s);
self.emit_force(&s); self.emit_force(s);
} }
ast::Attr::Ident(ident) => self.emit_literal_ident(&ident), 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. // Put the attribute set on the stack.
self.compile(slot, node.expr().unwrap()); self.compile(slot, &node.expr().unwrap());
self.emit_force(&node); self.emit_force(node);
// Push all path fragments with an operation for fetching the // Push all path fragments with an operation for fetching the
// next nested element, for all fragments except the last one. // next nested element, for all fragments except the last one.
@ -535,15 +531,15 @@ impl Compiler<'_> {
self.emit_force(&fragment); self.emit_force(&fragment);
} }
self.compile_attr(slot, fragment); self.compile_attr(slot, &fragment);
} }
// After the last fragment, emit the actual instruction that // After the last fragment, emit the actual instruction that
// leaves a boolean on the stack. // 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 set = node.expr().unwrap();
let path = node.attrpath().unwrap(); let path = node.attrpath().unwrap();
@ -553,7 +549,7 @@ impl Compiler<'_> {
} }
// Push the set onto the stack // Push the set onto the stack
self.compile(slot, set); self.compile(slot, &set);
// Compile each key fragment and emit access instructions. // Compile each key fragment and emit access instructions.
// //
@ -563,7 +559,7 @@ impl Compiler<'_> {
// Force the current set value. // Force the current set value.
self.emit_force(&fragment); self.emit_force(&fragment);
self.compile_attr(slot, fragment.clone()); self.compile_attr(slot, &fragment);
self.push_op(OpCode::OpAttrsSelect, &fragment); self.push_op(OpCode::OpAttrsSelect, &fragment);
} }
} }
@ -604,12 +600,12 @@ impl Compiler<'_> {
path: ast::Attrpath, path: ast::Attrpath,
default: ast::Expr, default: ast::Expr,
) { ) {
self.compile(slot, set); self.compile(slot, &set);
let mut jumps = vec![]; let mut jumps = vec![];
for fragment in path.attrs() { for fragment in path.attrs() {
self.emit_force(&fragment); self.emit_force(&fragment);
self.compile_attr(slot, fragment.clone()); self.compile_attr(slot, &fragment.clone());
self.push_op(OpCode::OpAttrsTrySelect, &fragment); self.push_op(OpCode::OpAttrsTrySelect, &fragment);
jumps.push(self.push_op(OpCode::OpJumpIfNotFound(JumpOffset(0)), &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 // Compile the default value expression and patch the final
// jump to point *beyond* it. // jump to point *beyond* it.
self.compile(slot, default); self.compile(slot, &default);
self.patch_jump(final_jump); 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. // 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.emit_force(&node.condition().unwrap());
self.push_op(OpCode::OpAssert, &node.condition().unwrap()); self.push_op(OpCode::OpAssert, &node.condition().unwrap());
// The runtime will abort evaluation at this point if the // The runtime will abort evaluation at this point if the
// assertion failed, if not the body simply continues on like // assertion failed, if not the body simply continues on like
// normal. // normal.
self.compile(slot, node.body().unwrap()); self.compile(slot, &node.body().unwrap());
} }
/// Compile conditional expressions using jumping instructions in the VM. /// Compile conditional expressions using jumping instructions in the VM.
@ -650,8 +646,8 @@ impl Compiler<'_> {
/// if condition is true.└┼─5─→ ... │ /// if condition is true.└┼─5─→ ... │
/// └────────────────────┘ /// └────────────────────┘
/// ``` /// ```
fn compile_if_else(&mut self, slot: LocalIdx, node: ast::IfElse) { fn compile_if_else(&mut self, slot: LocalIdx, node: &ast::IfElse) {
self.compile(slot, node.condition().unwrap()); self.compile(slot, &node.condition().unwrap());
self.emit_force(&node.condition().unwrap()); self.emit_force(&node.condition().unwrap());
let then_idx = self.push_op( let then_idx = self.push_op(
@ -659,14 +655,14 @@ impl Compiler<'_> {
&node.condition().unwrap(), &node.condition().unwrap(),
); );
self.push_op(OpCode::OpPop, &node); // discard condition value self.push_op(OpCode::OpPop, node); // discard condition value
self.compile(slot, node.body().unwrap()); 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.patch_jump(then_idx); // patch jump *to* else_body
self.push_op(OpCode::OpPop, &node); // discard condition value self.push_op(OpCode::OpPop, node); // discard condition value
self.compile(slot, node.else_body().unwrap()); self.compile(slot, &node.else_body().unwrap());
self.patch_jump(else_idx); // patch jump *over* else body self.patch_jump(else_idx); // patch jump *over* else body
} }
@ -674,12 +670,12 @@ impl Compiler<'_> {
/// Compile `with` expressions by emitting instructions that /// Compile `with` expressions by emitting instructions that
/// pop/remove the indices of attribute sets that are implicitly /// pop/remove the indices of attribute sets that are implicitly
/// in scope through `with` on the "with-stack". /// 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(); self.scope_mut().begin_scope();
// TODO: Detect if the namespace is just an identifier, and // TODO: Detect if the namespace is just an identifier, and
// resolve that directly (thus avoiding duplication on the // resolve that directly (thus avoiding duplication on the
// stack). // stack).
self.compile(slot, node.namespace().unwrap()); self.compile(slot, &node.namespace().unwrap());
let span = self.span_for(&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.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.scope_mut().pop_with();
self.cleanup_scope(&node); self.cleanup_scope(node);
} }
/// Compiles pattern function arguments, such as `{ a, b }: ...`. /// Compiles pattern function arguments, such as `{ a, b }: ...`.
@ -731,8 +727,8 @@ impl Compiler<'_> {
/// many arguments are provided. This is done by emitting a /// many arguments are provided. This is done by emitting a
/// special instruction that checks the set of keys from a /// special instruction that checks the set of keys from a
/// constant containing the expected keys. /// constant containing the expected keys.
fn compile_param_pattern(&mut self, pattern: ast::Pattern) { fn compile_param_pattern(&mut self, pattern: &ast::Pattern) {
let span = self.span_for(&pattern); let span = self.span_for(pattern);
let set_idx = match pattern.pat_bind() { let set_idx = match pattern.pat_bind() {
Some(name) => self.declare_local(&name, name.ident().unwrap().to_string()), Some(name) => self.declare_local(&name, name.ident().unwrap().to_string()),
None => self.scope_mut().declare_phantom(span, true), 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 // At call time, the attribute set is already at the top of
// the stack. // the stack.
self.scope_mut().mark_initialised(set_idx); 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 // Similar to `let ... in ...`, we now do multiple passes over
// the bindings to first declare them, then populate them, and // the bindings to first declare them, then populate them, and
@ -760,7 +756,7 @@ impl Compiler<'_> {
// attempt to select from it. // attempt to select from it.
let stack_idx = self.scope().stack_index(set_idx); let stack_idx = self.scope().stack_index(set_idx);
for (idx, entry) in entries.into_iter() { 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()); self.emit_literal_ident(&entry.ident().unwrap());
// Use the same mechanism as `compile_select_or` if a // 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); let jump_over_default = self.push_op(OpCode::OpJump(JumpOffset(0)), &default_expr);
self.patch_jump(jump_to_default); self.patch_jump(jump_to_default);
self.compile(idx, default_expr); self.compile(idx, &default_expr);
self.patch_jump(jump_over_default); self.patch_jump(jump_over_default);
} else { } else {
self.push_op(OpCode::OpAttrsSelect, &entry.ident().unwrap()); self.push_op(OpCode::OpAttrsSelect, &entry.ident().unwrap());
@ -786,26 +782,26 @@ impl Compiler<'_> {
for idx in indices { for idx in indices {
if self.scope()[idx].needs_finaliser { if self.scope()[idx].needs_finaliser {
let stack_idx = self.scope().stack_index(idx); 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 // TODO: strictly check if all keys have been consumed if
// there is no ellipsis. // there is no ellipsis.
if pattern.ellipsis_token().is_none() { 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(); self.new_context();
let span = self.span_for(&node); let span = self.span_for(node);
let slot = self.scope_mut().declare_phantom(span, false); let slot = self.scope_mut().declare_phantom(span, false);
self.scope_mut().begin_scope(); self.scope_mut().begin_scope();
// Compile the function itself // Compile the function itself
match node.param().unwrap() { 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) => { ast::Param::IdentParam(param) => {
let name = param let name = param
@ -821,8 +817,8 @@ impl Compiler<'_> {
} }
} }
self.compile(slot, node.body().unwrap()); self.compile(slot, &node.body().unwrap());
self.cleanup_scope(&node); self.cleanup_scope(node);
// TODO: determine and insert enclosing name, if available. // 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 // If the function is not a closure, just emit it directly and
// move on. // move on.
if lambda.upvalue_count == 0 { if lambda.upvalue_count == 0 {
self.emit_constant(Value::Closure(Closure::new(lambda)), &node); self.emit_constant(Value::Closure(Closure::new(lambda)), node);
return; return;
} }
@ -855,24 +851,24 @@ impl Compiler<'_> {
// which the runtime closure can be constructed. // which the runtime closure can be constructed.
let blueprint_idx = self.chunk().push_constant(Value::Blueprint(lambda)); 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( self.emit_upvalue_data(
outer_slot, outer_slot,
&node, node,
compiled.scope.upvalues, compiled.scope.upvalues,
compiled.captures_with_stack, 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, // To call a function, we leave its arguments on the stack,
// followed by the function expression itself, and then emit a // followed by the function expression itself, and then emit a
// call instruction. This way, the stack is perfectly laid out // call instruction. This way, the stack is perfectly laid out
// to enter the function call straight away. // to enter the function call straight away.
self.compile(slot, node.argument().unwrap()); self.compile(slot, &node.argument().unwrap());
self.compile(slot, node.lambda().unwrap()); self.compile(slot, &node.lambda().unwrap());
self.emit_force(&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 /// 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? // TODO: almost the same as Compiler::compile_lambda; unify?
fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F) fn thunk<N, F>(&mut self, outer_slot: LocalIdx, node: &N, content: F)
where where
N: ToSpan + Clone, N: ToSpan,
F: FnOnce(&mut Compiler, &N, LocalIdx), F: FnOnce(&mut Compiler, LocalIdx),
{ {
self.new_context(); self.new_context();
let span = self.span_for(node); let span = self.span_for(node);
let slot = self.scope_mut().declare_phantom(span, false); let slot = self.scope_mut().declare_phantom(span, false);
self.scope_mut().begin_scope(); self.scope_mut().begin_scope();
content(self, node, slot); content(self, slot);
self.cleanup_scope(node); self.cleanup_scope(node);
let mut thunk = self.contexts.pop().unwrap(); let mut thunk = self.contexts.pop().unwrap();
@ -1142,7 +1138,7 @@ fn prepare_globals(additional: HashMap<&'static str, Value>) -> GlobalsMap {
} }
pub fn compile( pub fn compile(
expr: ast::Expr, expr: &ast::Expr,
location: Option<PathBuf>, location: Option<PathBuf>,
file: Arc<codemap::File>, file: Arc<codemap::File>,
globals: HashMap<&'static str, Value>, globals: HashMap<&'static str, Value>,
@ -1150,15 +1146,15 @@ pub fn compile(
) -> EvalResult<CompilationOutput> { ) -> EvalResult<CompilationOutput> {
let mut c = Compiler::new(location, file, globals, observer)?; 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); 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 // The final operation of any top-level Nix program must always be
// `OpForce`. A thunk should not be returned to the user in an // `OpForce`. A thunk should not be returned to the user in an
// unevaluated state (though in practice, a value *containing* a // unevaluated state (though in practice, a value *containing* a
// thunk might be returned). // thunk might be returned).
c.emit_force(&expr); c.emit_force(expr);
let lambda = Rc::new(c.contexts.pop().unwrap().lambda); let lambda = Rc::new(c.contexts.pop().unwrap().lambda);
c.observer.observe_compiled_toplevel(&lambda); c.observer.observe_compiled_toplevel(&lambda);

View file

@ -60,7 +60,7 @@ pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> Eva
let result = if options.dump_bytecode { let result = if options.dump_bytecode {
crate::compiler::compile( crate::compiler::compile(
root_expr, &root_expr,
location, location,
file.clone(), file.clone(),
global_builtins(), global_builtins(),
@ -68,7 +68,7 @@ pub fn interpret(code: &str, location: Option<PathBuf>, options: Options) -> Eva
) )
} else { } else {
crate::compiler::compile( crate::compiler::compile(
root_expr, &root_expr,
location, location,
file.clone(), file.clone(),
global_builtins(), global_builtins(),