about summary refs log tree commit diff
path: root/tvix/eval/src/compiler/attrs.rs
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-09-23T13·05+0300
committertazjin <tazjin@tvl.su>2022-09-28T00·00+0000
commit71a8db108d94f68f6e411d36a36d4f3045987ac7 (patch)
treeb92b984af4c88067ddec6527e15cdc8af34ed33d /tvix/eval/src/compiler/attrs.rs
parent27b6f29550420072c68919c3511e1f221d7cad7c (diff)
refactor(tvix/eval): bye compiler::attrs, hello compiler::bindings r/4972
Changes the module structure of the compiler to have a module
dedicated to the logic of setting up bindings. This logic is in the
process of being merged between attribute sets and `let`-expressions,
and the structure of the modules makes more sense when ecapsulating
that specifically.

(Other bits of code related to e.g. attribute sets are pretty
straightforward and can just live in the main compiler module).

Change-Id: I9469b73a7034e5b5f3bb211694d97260c4c9ef54
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6766
Autosubmit: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
Reviewed-by: sterni <sternenseemann@systemli.org>
Diffstat (limited to 'tvix/eval/src/compiler/attrs.rs')
-rw-r--r--tvix/eval/src/compiler/attrs.rs365
1 files changed, 0 insertions, 365 deletions
diff --git a/tvix/eval/src/compiler/attrs.rs b/tvix/eval/src/compiler/attrs.rs
deleted file mode 100644
index 704bf211ffa7..000000000000
--- a/tvix/eval/src/compiler/attrs.rs
+++ /dev/null
@@ -1,365 +0,0 @@
-//! This module implements compiler logic related to attribute sets
-//! (construction, access operators, ...).
-
-use super::*;
-
-impl Compiler<'_> {
-    pub(super) fn compile_attr(&mut self, slot: LocalIdx, node: ast::Attr) {
-        match node {
-            ast::Attr::Dynamic(dynamic) => {
-                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);
-            }
-
-            ast::Attr::Ident(ident) => self.emit_literal_ident(&ident),
-        }
-    }
-
-    /// Compiles inherited values in an attribute set. Inherited
-    /// values are *always* inherited from the outer scope, even if
-    /// there is a matching name within a recursive attribute set.
-    fn compile_inherit_attrs(
-        &mut self,
-        slot: LocalIdx,
-        inherits: AstChildren<ast::Inherit>,
-    ) -> usize {
-        // Count the number of inherited values, so that the outer
-        // constructor can emit the correct number of pairs when
-        // constructing attribute sets.
-        let mut count = 0;
-
-        for inherit in inherits {
-            match inherit.from() {
-                Some(from) => {
-                    for attr in inherit.attrs() {
-                        count += 1;
-
-                        let name = match self.expr_static_attr_str(&attr) {
-                            Some(name) => name,
-                            None => {
-                                // TODO(tazjin): error variant for dynamic
-                                // key in *inherit* (or generalise it)
-                                self.emit_error(&attr, ErrorKind::DynamicKeyInLet);
-                                continue;
-                            }
-                        };
-
-                        let name_span = self.span_for(&attr);
-
-                        // First emit the identifier itself (this
-                        // becomes the new key).
-                        self.emit_constant(Value::String(SmolStr::new(&name).into()), &attr);
-                        self.scope_mut().declare_phantom(name_span, true);
-
-                        // Then emit the node that we're inheriting
-                        // from.
-                        //
-                        // TODO: Likely significant optimisation
-                        // potential in having a multi-select
-                        // instruction followed by a merge, rather
-                        // than pushing/popping the same attrs
-                        // potentially a lot of times.
-                        let val_idx = self.scope_mut().declare_phantom(name_span, false);
-                        self.compile(val_idx, from.expr().unwrap());
-                        self.emit_force(&from.expr().unwrap());
-                        self.emit_constant(Value::String(name.into()), &attr);
-                        self.push_op(OpCode::OpAttrsSelect, &attr);
-                        self.scope_mut().mark_initialised(val_idx);
-                    }
-                }
-
-                None => {
-                    for attr in inherit.attrs() {
-                        count += 1;
-
-                        // Emit the key to use for OpAttrs
-                        let name = match self.expr_static_attr_str(&attr) {
-                            Some(name) => name,
-                            None => {
-                                // TODO(tazjin): error variant for dynamic
-                                // key in *inherit* (or generalise it)
-                                self.emit_error(&attr, ErrorKind::DynamicKeyInLet);
-                                continue;
-                            }
-                        };
-
-                        let name_span = self.span_for(&attr);
-                        self.emit_constant(Value::String(SmolStr::new(&name).into()), &attr);
-                        self.scope_mut().declare_phantom(name_span, true);
-
-                        // Emit the value.
-                        self.compile_identifier_access(slot, &name, &attr);
-                        self.scope_mut().declare_phantom(name_span, true);
-                    }
-                }
-            }
-        }
-
-        count
-    }
-
-    /// Compile the statically known entries of an attribute set. Which
-    /// keys are which is not known from the iterator, so discovered
-    /// dynamic keys are returned from here.
-    fn compile_static_attr_entries(
-        &mut self,
-        count: &mut usize,
-        entries: AstChildren<ast::AttrpathValue>,
-    ) -> Vec<ast::AttrpathValue> {
-        let mut dynamic_attrs: Vec<ast::AttrpathValue> = vec![];
-
-        'entries: for kv in entries {
-            // Attempt to turn the attrpath into a list of static
-            // strings, but abort this process if any dynamic
-            // fragments are encountered.
-            let static_attrpath: Option<Vec<String>> = kv
-                .attrpath()
-                .unwrap()
-                .attrs()
-                .map(|a| self.expr_static_attr_str(&a))
-                .collect();
-
-            let fragments = match static_attrpath {
-                Some(fragments) => fragments,
-                None => {
-                    dynamic_attrs.push(kv);
-                    continue 'entries;
-                }
-            };
-
-            // At this point we can increase the counter because we
-            // know that this particular attribute is static and can
-            // thus be processed here.
-            *count += 1;
-
-            let key_count = fragments.len();
-            for fragment in fragments.into_iter() {
-                self.emit_constant(Value::String(fragment.into()), &kv.attrpath().unwrap());
-            }
-
-            // We're done with the key if there was only one fragment,
-            // otherwise we need to emit an instruction to construct
-            // the attribute path.
-            if key_count > 1 {
-                self.push_op(
-                    OpCode::OpAttrPath(Count(key_count)),
-                    &kv.attrpath().unwrap(),
-                );
-            }
-
-            // The value is just compiled as normal so that its
-            // resulting value is on the stack when the attribute set
-            // is constructed at runtime.
-            let value_span = self.span_for(&kv.value().unwrap());
-            let value_slot = self.scope_mut().declare_phantom(value_span, false);
-            self.compile(value_slot, kv.value().unwrap());
-            self.scope_mut().mark_initialised(value_slot);
-        }
-
-        dynamic_attrs
-    }
-
-    /// Compile the dynamic entries of an attribute set, where keys
-    /// are only known at runtime.
-    fn compile_dynamic_attr_entries(
-        &mut self,
-        count: &mut usize,
-        entries: Vec<ast::AttrpathValue>,
-    ) {
-        for entry in entries.into_iter() {
-            *count += 1;
-
-            let mut key_count = 0;
-            let key_span = self.span_for(&entry.attrpath().unwrap());
-            let key_idx = self.scope_mut().declare_phantom(key_span, false);
-
-            for fragment in entry.attrpath().unwrap().attrs() {
-                // Key fragments can contain dynamic expressions,
-                // which makes accounting for their stack slots very
-                // tricky.
-                //
-                // In order to ensure the locals are correctly cleaned
-                // up, we essentially treat any key fragment after the
-                // first one (which has a locals index that will
-                // become that of the final key itself) as being part
-                // of a separate scope, which results in the somewhat
-                // annoying setup logic below.
-                let fragment_slot = match key_count {
-                    0 => key_idx,
-                    1 => {
-                        self.scope_mut().begin_scope();
-                        self.scope_mut().declare_phantom(key_span, false)
-                    }
-                    _ => self.scope_mut().declare_phantom(key_span, false),
-                };
-
-                key_count += 1;
-                self.compile_attr(fragment_slot, fragment);
-                self.scope_mut().mark_initialised(fragment_slot);
-            }
-
-            // We're done with the key if there was only one fragment,
-            // otherwise we need to emit an instruction to construct
-            // the attribute path.
-            if key_count > 1 {
-                self.push_op(
-                    OpCode::OpAttrPath(Count(key_count)),
-                    &entry.attrpath().unwrap(),
-                );
-
-                // Close the temporary scope that was set up for the
-                // key fragments.
-                self.scope_mut().end_scope();
-            }
-
-            // The value is just compiled as normal so that its
-            // resulting value is on the stack when the attribute set
-            // is constructed at runtime.
-            let value_span = self.span_for(&entry.value().unwrap());
-            let value_slot = self.scope_mut().declare_phantom(value_span, false);
-            self.compile(value_slot, entry.value().unwrap());
-            self.scope_mut().mark_initialised(value_slot);
-        }
-    }
-
-    /// Compile attribute set literals into equivalent bytecode.
-    ///
-    /// This is complicated by a number of features specific to Nix
-    /// attribute sets, most importantly:
-    ///
-    /// 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) {
-        // Open a scope to track the positions of the temporaries used
-        // by the `OpAttrs` instruction.
-        self.scope_mut().begin_scope();
-
-        if node.rec_token().is_some() {
-            let count = self.compile_recursive_scope(slot, true, &node);
-            self.push_op(OpCode::OpAttrs(Count(count)), &node);
-        } else {
-            let mut count = self.compile_inherit_attrs(slot, node.inherits());
-
-            let dynamic_entries =
-                self.compile_static_attr_entries(&mut count, node.attrpath_values());
-
-            self.compile_dynamic_attr_entries(&mut count, dynamic_entries);
-
-            self.push_op(OpCode::OpAttrs(Count(count)), &node);
-        }
-
-        // Remove the temporary scope, but do not emit any additional
-        // cleanup (OpAttrs consumes all of these locals).
-        self.scope_mut().end_scope();
-    }
-
-    pub(super) 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);
-
-        // Push all path fragments with an operation for fetching the
-        // next nested element, for all fragments except the last one.
-        for (count, fragment) in node.attrpath().unwrap().attrs().enumerate() {
-            if count > 0 {
-                self.push_op(OpCode::OpAttrsTrySelect, &fragment);
-                self.emit_force(&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);
-    }
-
-    pub(super) fn compile_select(&mut self, slot: LocalIdx, node: ast::Select) {
-        let set = node.expr().unwrap();
-        let path = node.attrpath().unwrap();
-
-        if node.or_token().is_some() {
-            self.compile_select_or(slot, set, path, node.default_expr().unwrap());
-            return;
-        }
-
-        // Push the set onto the stack
-        self.compile(slot, set.clone());
-
-        // Compile each key fragment and emit access instructions.
-        //
-        // TODO: multi-select instruction to avoid re-pushing attrs on
-        // nested selects.
-        for fragment in path.attrs() {
-            // Force the current set value.
-            self.emit_force(&fragment);
-
-            self.compile_attr(slot, fragment.clone());
-            self.push_op(OpCode::OpAttrsSelect, &fragment);
-        }
-    }
-
-    /// Compile an `or` expression into a chunk of conditional jumps.
-    ///
-    /// If at any point during attribute set traversal a key is
-    /// missing, the `OpAttrOrNotFound` instruction will leave a
-    /// special sentinel value on the stack.
-    ///
-    /// After each access, a conditional jump evaluates the top of the
-    /// stack and short-circuits to the default value if it sees the
-    /// sentinel.
-    ///
-    /// Code like `{ a.b = 1; }.a.c or 42` yields this bytecode and
-    /// runtime stack:
-    ///
-    /// ```notrust
-    ///            Bytecode                     Runtime stack
-    ///  ┌────────────────────────────┐   ┌─────────────────────────┐
-    ///  │    ...                     │   │ ...                     │
-    ///  │ 5  OP_ATTRS(1)             │ → │ 5  [ { a.b = 1; }     ] │
-    ///  │ 6  OP_CONSTANT("a")        │ → │ 6  [ { a.b = 1; } "a" ] │
-    ///  │ 7  OP_ATTR_OR_NOT_FOUND    │ → │ 7  [ { b = 1; }       ] │
-    ///  │ 8  JUMP_IF_NOT_FOUND(13)   │ → │ 8  [ { b = 1; }       ] │
-    ///  │ 9  OP_CONSTANT("C")        │ → │ 9  [ { b = 1; } "c"   ] │
-    ///  │ 10 OP_ATTR_OR_NOT_FOUND    │ → │ 10 [ NOT_FOUND        ] │
-    ///  │ 11 JUMP_IF_NOT_FOUND(13)   │ → │ 11 [                  ] │
-    ///  │ 12 JUMP(14)                │   │ ..     jumped over      │
-    ///  │ 13 CONSTANT(42)            │ → │ 12 [ 42 ]               │
-    ///  │ 14 ...                     │   │ ..   ....               │
-    ///  └────────────────────────────┘   └─────────────────────────┘
-    /// ```
-    fn compile_select_or(
-        &mut self,
-        slot: LocalIdx,
-        set: ast::Expr,
-        path: ast::Attrpath,
-        default: ast::Expr,
-    ) {
-        self.compile(slot, set.clone());
-        let mut jumps = vec![];
-
-        for fragment in path.attrs() {
-            self.emit_force(&fragment);
-            self.compile_attr(slot, fragment.clone());
-            self.push_op(OpCode::OpAttrsTrySelect, &fragment);
-            jumps.push(self.push_op(OpCode::OpJumpIfNotFound(JumpOffset(0)), &fragment));
-        }
-
-        let final_jump = self.push_op(OpCode::OpJump(JumpOffset(0)), &path);
-
-        for jump in jumps {
-            self.patch_jump(jump);
-        }
-
-        // Compile the default value expression and patch the final
-        // jump to point *beyond* it.
-        self.compile(slot, default);
-        self.patch_jump(final_jump);
-    }
-}