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 result = tvix_eval::compile(
root_expr,
&root_expr,
Some("/nixbolt".into()),
file.clone(),
tvix_eval::global_builtins(),

View file

@ -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);
}
}

View file

@ -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, s| c.compile_attr_set(s, attrs))
}
ast::Expr::AttrSet(attrs) => self.thunk(slot, &attrs, move |c, a, s| {
c.compile_attr_set(s, a.clone())
}),
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);

View file

@ -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(),