refactor(tvix/eval): always pass slot to compiler methods
The slot is now always known (at the root of the file it is simply stack slot 0 once the scope drops back down to 0), so it does not need to be wrapped in an `Option` and accessed in cumbersome ways anymore. Change-Id: I46bf67a4cf5cb96e4874dffd0e3fb07c551d44f0 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6420 Reviewed-by: sterni <sternenseemann@systemli.org> Tested-by: BuildkiteCI
This commit is contained in:
parent
a303ea3ff5
commit
7bc6e5984d
2 changed files with 32 additions and 33 deletions
|
@ -149,7 +149,7 @@ impl Compiler<'_> {
|
|||
|
||||
// Actual code-emitting AST traversal methods.
|
||||
impl Compiler<'_> {
|
||||
fn compile(&mut self, slot: Option<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),
|
||||
|
@ -234,7 +234,7 @@ impl Compiler<'_> {
|
|||
self.emit_constant(value, &node);
|
||||
}
|
||||
|
||||
fn compile_str(&mut self, slot: Option<LocalIdx>, node: ast::Str) {
|
||||
fn compile_str(&mut self, slot: LocalIdx, node: ast::Str) {
|
||||
let mut count = 0;
|
||||
|
||||
// The string parts are produced in literal order, however
|
||||
|
@ -261,7 +261,7 @@ impl Compiler<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn compile_unary_op(&mut self, slot: Option<LocalIdx>, op: ast::UnaryOp) {
|
||||
fn compile_unary_op(&mut self, slot: LocalIdx, op: ast::UnaryOp) {
|
||||
self.compile(slot, op.expr().unwrap());
|
||||
self.emit_force(&op);
|
||||
|
||||
|
@ -273,7 +273,7 @@ impl Compiler<'_> {
|
|||
self.push_op(opcode, &op);
|
||||
}
|
||||
|
||||
fn compile_binop(&mut self, slot: Option<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
|
||||
|
@ -322,7 +322,7 @@ impl Compiler<'_> {
|
|||
};
|
||||
}
|
||||
|
||||
fn compile_and(&mut self, slot: Option<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: {:?}",
|
||||
|
@ -348,7 +348,7 @@ impl Compiler<'_> {
|
|||
self.push_op(OpCode::OpAssertBool, &node);
|
||||
}
|
||||
|
||||
fn compile_or(&mut self, slot: Option<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: {:?}",
|
||||
|
@ -370,7 +370,7 @@ impl Compiler<'_> {
|
|||
self.push_op(OpCode::OpAssertBool, &node);
|
||||
}
|
||||
|
||||
fn compile_implication(&mut self, slot: Option<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: {:?}",
|
||||
|
@ -392,7 +392,7 @@ impl Compiler<'_> {
|
|||
self.push_op(OpCode::OpAssertBool, &node);
|
||||
}
|
||||
|
||||
fn compile_has_attr(&mut self, slot: Option<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());
|
||||
|
||||
|
@ -411,7 +411,7 @@ impl Compiler<'_> {
|
|||
self.push_op(OpCode::OpAttrsIsSet, &node);
|
||||
}
|
||||
|
||||
fn compile_attr(&mut self, slot: Option<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());
|
||||
|
@ -433,7 +433,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: Option<LocalIdx>, node: ast::List) {
|
||||
fn compile_list(&mut self, slot: LocalIdx, node: ast::List) {
|
||||
let mut count = 0;
|
||||
|
||||
for item in node.items() {
|
||||
|
@ -452,7 +452,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.
|
||||
fn compile_attr_set(&mut self, slot: Option<LocalIdx>, node: ast::AttrSet) {
|
||||
fn compile_attr_set(&mut self, slot: LocalIdx, node: ast::AttrSet) {
|
||||
if node.rec_token().is_some() {
|
||||
todo!("recursive attribute sets are not yet implemented")
|
||||
}
|
||||
|
@ -534,7 +534,7 @@ impl Compiler<'_> {
|
|||
self.push_op(OpCode::OpAttrs(Count(count)), &node);
|
||||
}
|
||||
|
||||
fn compile_select(&mut self, slot: Option<LocalIdx>, node: ast::Select) {
|
||||
fn compile_select(&mut self, slot: LocalIdx, node: ast::Select) {
|
||||
let set = node.expr().unwrap();
|
||||
let path = node.attrpath().unwrap();
|
||||
|
||||
|
@ -588,7 +588,7 @@ impl Compiler<'_> {
|
|||
/// ```
|
||||
fn compile_select_or(
|
||||
&mut self,
|
||||
slot: Option<LocalIdx>,
|
||||
slot: LocalIdx,
|
||||
set: ast::Expr,
|
||||
path: ast::Attrpath,
|
||||
default: ast::Expr,
|
||||
|
@ -615,7 +615,7 @@ impl Compiler<'_> {
|
|||
self.patch_jump(final_jump);
|
||||
}
|
||||
|
||||
fn compile_assert(&mut self, slot: Option<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.push_op(OpCode::OpAssert, &node);
|
||||
|
@ -636,7 +636,7 @@ impl Compiler<'_> {
|
|||
// Jump over else body ││ 4 [ else body ]←┼─┘
|
||||
// if condition is true.└┼─5─→ ... │
|
||||
// └────────────────────┘
|
||||
fn compile_if_else(&mut self, slot: Option<LocalIdx>, node: ast::IfElse) {
|
||||
fn compile_if_else(&mut self, slot: LocalIdx, node: ast::IfElse) {
|
||||
self.compile(slot, node.condition().unwrap());
|
||||
|
||||
let then_idx = self.push_op(
|
||||
|
@ -659,7 +659,7 @@ impl Compiler<'_> {
|
|||
// Compile an `inherit` node of a `let`-expression.
|
||||
fn compile_let_inherit<I: Iterator<Item = ast::Inherit>>(
|
||||
&mut self,
|
||||
slot: Option<LocalIdx>,
|
||||
slot: LocalIdx,
|
||||
inherits: I,
|
||||
) {
|
||||
for inherit in inherits {
|
||||
|
@ -712,7 +712,7 @@ 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.
|
||||
fn compile_let_in(&mut self, slot: Option<LocalIdx>, node: ast::LetIn) {
|
||||
fn compile_let_in(&mut self, slot: LocalIdx, node: ast::LetIn) {
|
||||
self.begin_scope();
|
||||
|
||||
self.compile_let_inherit(slot, node.inherits());
|
||||
|
@ -741,7 +741,7 @@ impl Compiler<'_> {
|
|||
// Second pass to place the values in the correct stack slots.
|
||||
let indices: Vec<LocalIdx> = entries.iter().map(|(idx, _)| *idx).collect();
|
||||
for (idx, value) in entries.into_iter() {
|
||||
self.compile(Some(idx), value);
|
||||
self.compile(idx, value);
|
||||
|
||||
// Any code after this point will observe the value in the
|
||||
// right stack slot, so mark it as initialised.
|
||||
|
@ -761,7 +761,7 @@ impl Compiler<'_> {
|
|||
self.end_scope(&node);
|
||||
}
|
||||
|
||||
fn compile_ident(&mut self, slot: Option<LocalIdx>, node: ast::Ident) {
|
||||
fn compile_ident(&mut self, slot: LocalIdx, node: ast::Ident) {
|
||||
let ident = node.ident_token().unwrap();
|
||||
|
||||
// If the identifier is a global, and it is not poisoned, emit
|
||||
|
@ -833,7 +833,7 @@ 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: Option<LocalIdx>, node: ast::With) {
|
||||
fn compile_with(&mut self, slot: LocalIdx, node: ast::With) {
|
||||
self.begin_scope();
|
||||
// TODO: Detect if the namespace is just an identifier, and
|
||||
// resolve that directly (thus avoiding duplication on the
|
||||
|
@ -856,7 +856,7 @@ impl Compiler<'_> {
|
|||
self.end_scope(&node);
|
||||
}
|
||||
|
||||
fn compile_lambda(&mut self, slot: Option<LocalIdx>, node: ast::Lambda) {
|
||||
fn compile_lambda(&mut self, slot: LocalIdx, node: ast::Lambda) {
|
||||
self.new_context();
|
||||
self.begin_scope();
|
||||
|
||||
|
@ -913,7 +913,7 @@ impl Compiler<'_> {
|
|||
self.emit_upvalue_data(slot, compiled.scope.upvalues);
|
||||
}
|
||||
|
||||
fn compile_apply(&mut self, slot: Option<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
|
||||
|
@ -926,10 +926,10 @@ impl Compiler<'_> {
|
|||
/// Compile an expression into a runtime thunk which should be
|
||||
/// lazily evaluated when accessed.
|
||||
// TODO: almost the same as Compiler::compile_lambda; unify?
|
||||
fn thunk<N, F>(&mut self, slot: Option<LocalIdx>, node: &N, content: F)
|
||||
fn thunk<N, F>(&mut self, slot: LocalIdx, node: &N, content: F)
|
||||
where
|
||||
N: AstNode + Clone,
|
||||
F: FnOnce(&mut Compiler, &N, Option<LocalIdx>),
|
||||
F: FnOnce(&mut Compiler, &N, LocalIdx),
|
||||
{
|
||||
self.new_context();
|
||||
self.begin_scope();
|
||||
|
@ -961,14 +961,9 @@ impl Compiler<'_> {
|
|||
|
||||
/// Emit the data instructions that the runtime needs to correctly
|
||||
/// assemble the provided upvalues array.
|
||||
fn emit_upvalue_data(&mut self, slot: Option<LocalIdx>, upvalues: Vec<Upvalue>) {
|
||||
fn emit_upvalue_data(&mut self, slot: LocalIdx, upvalues: Vec<Upvalue>) {
|
||||
for upvalue in upvalues {
|
||||
match upvalue.kind {
|
||||
UpvalueKind::Local(idx) if slot.is_none() => {
|
||||
let stack_idx = self.scope().stack_index(idx);
|
||||
self.push_op(OpCode::DataLocalIdx(stack_idx), &upvalue.node);
|
||||
}
|
||||
|
||||
UpvalueKind::Local(idx) => {
|
||||
let stack_idx = self.scope().stack_index(idx);
|
||||
|
||||
|
@ -976,9 +971,9 @@ impl Compiler<'_> {
|
|||
// closure, the upvalue resolution must be
|
||||
// deferred until the scope is fully initialised
|
||||
// and can be finalised.
|
||||
if slot.unwrap() < idx {
|
||||
if slot < idx {
|
||||
self.push_op(OpCode::DataDeferredLocal(stack_idx), &upvalue.node);
|
||||
self.scope_mut().mark_needs_finaliser(slot.unwrap());
|
||||
self.scope_mut().mark_needs_finaliser(slot);
|
||||
} else {
|
||||
self.push_op(OpCode::DataLocalIdx(stack_idx), &upvalue.node);
|
||||
}
|
||||
|
@ -1382,7 +1377,7 @@ pub fn compile<'code>(
|
|||
c.context_mut().lambda.chunk.codemap = c.codemap.clone();
|
||||
}
|
||||
|
||||
c.compile(None, expr.clone());
|
||||
c.compile(LocalIdx::ZERO, expr.clone());
|
||||
|
||||
// The final operation of any top-level Nix program must always be
|
||||
// `OpForce`. A thunk should not be returned to the user in an
|
||||
|
|
|
@ -104,6 +104,10 @@ pub struct Upvalue {
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
|
||||
pub struct LocalIdx(usize);
|
||||
|
||||
impl LocalIdx {
|
||||
pub const ZERO: LocalIdx = LocalIdx(0);
|
||||
}
|
||||
|
||||
/// Represents a scope known during compilation, which can be resolved
|
||||
/// directly to stack indices.
|
||||
///
|
||||
|
|
Loading…
Reference in a new issue