refactor(tvix/eval): statically resolve select from constant attrs

When resolving a select expression (`attrs.name` or `attrs.name or
default`), if the set compiles to a constant attribute set (as is most
notably the case with `builtins`) we can backtrack and replace that
attribute set directly with the compiled value.

For something like `builtins.length`, this will directly emit an
`OpConstant` that leaves the `length` builtin on the stack.

Change-Id: I639654e065a06e8cfcbcacb528c6da7ec9e513ee
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7957
Tested-by: BuildkiteCI
Reviewed-by: flokli <flokli@flokli.de>
This commit is contained in:
Vincent Ambo 2023-01-29 23:40:57 +03:00 committed by tazjin
parent f2afd38f2d
commit 32698766ef
3 changed files with 94 additions and 50 deletions

View file

@ -226,7 +226,7 @@ impl TrackedBindings {
// If the first element of the path is not statically known, the entry
// can not be merged.
let name = match c.expr_static_attr_str(name) {
let name = match expr_static_attr_str(name) {
Some(name) => name,
None => return false,
};
@ -336,7 +336,7 @@ impl Compiler<'_> {
None => {
for attr in inherit.attrs() {
let name = match self.expr_static_attr_str(&attr) {
let name = match expr_static_attr_str(&attr) {
Some(name) => name,
None => {
self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
@ -387,7 +387,7 @@ impl Compiler<'_> {
Some(from) => {
for attr in inherit.attrs() {
let name = match self.expr_static_attr_str(&attr) {
let name = match expr_static_attr_str(&attr) {
Some(name) => name,
None => {
self.emit_error(&attr, ErrorKind::DynamicKeyInScope("inherit"));
@ -476,7 +476,7 @@ impl Compiler<'_> {
*count += 1;
let key_span = self.span_for(&key);
let key_slot = match self.expr_static_attr_str(&key) {
let key_slot = match expr_static_attr_str(&key) {
Some(name) if kind.is_attrs() => KeySlot::Static {
name,
slot: self.scope_mut().declare_phantom(key_span, false),
@ -819,37 +819,4 @@ impl Compiler<'_> {
self.contexts[ctx_idx].lambda.upvalue_count += 1;
idx
}
/// Convert a non-dynamic string expression to a string if possible.
fn expr_static_str(&self, node: &ast::Str) -> Option<SmolStr> {
let mut parts = node.normalized_parts();
if parts.len() != 1 {
return None;
}
if let Some(ast::InterpolPart::Literal(lit)) = parts.pop() {
return Some(SmolStr::new(lit));
}
None
}
/// Convert the provided `ast::Attr` into a statically known string if
/// possible.
fn expr_static_attr_str(&self, node: &ast::Attr) -> Option<SmolStr> {
match node {
ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()),
ast::Attr::Str(s) => self.expr_static_str(s),
// The dynamic node type is just a wrapper. C++ Nix does not care
// about the dynamic wrapper when determining whether the node
// itself is dynamic, it depends solely on the expression inside
// (i.e. `let ${"a"} = 1; in a` is valid).
ast::Attr::Dynamic(ref dynamic) => match dynamic.expr().unwrap() {
ast::Expr::Str(s) => self.expr_static_str(&s),
_ => None,
},
}
}
}