feat(tvix/vm): implement construction of optimised KV attrsets

For name/value pairs (which occur extremely often in Nix and make up a
significant chunk of the runtime cost of evaluating nixpkgs) we
substitute an optimised representation.

For now this will only be used if the name/value pair keys were
specified as literal identifiers or strings (i.e. if chunks are
encountered as keys they are not forced and a normal attribute set
backed by a map will be constructed).

Change-Id: Ic79746c323e627528bd58b1a6024ee8d0aff7858
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6102
Reviewed-by: grfn <grfn@gws.fyi>
Tested-by: BuildkiteCI
This commit is contained in:
Vincent Ambo 2022-08-09 18:23:32 +03:00 committed by tazjin
parent e876c3a41c
commit ec1770f95a

View file

@ -25,6 +25,10 @@ impl VM {
self.stack.pop().expect("TODO") self.stack.pop().expect("TODO")
} }
fn peek(&self, at: usize) -> &Value {
&self.stack[self.stack.len() - 1 - at]
}
fn pop_number_pair(&mut self) -> EvalResult<NumberPair> { fn pop_number_pair(&mut self) -> EvalResult<NumberPair> {
let v2 = self.pop(); let v2 = self.pop();
let v1 = self.pop(); let v1 = self.pop();
@ -128,6 +132,79 @@ impl VM {
} }
fn run_attrset(&mut self, count: usize) -> EvalResult<()> { fn run_attrset(&mut self, count: usize) -> EvalResult<()> {
// If the attribute count happens to be 2, we might be able to
// create the optimised name/value struct instead.
if count == 2 {
// When determining whether we are dealing with a
// name/value pair, we return the stack locations of name
// and value, using `0` as a sentinel value (i.e. if
// either is 0, we are dealing with some other attrset).
let is_pair = {
// The keys are located 1 & 3 values back in the
// stack.
let k1 = self.peek(1);
let k2 = self.peek(3);
match (k1, k2) {
(Value::String(NixString(s1)), Value::String(NixString(s2)))
if (s1 == "name" && s2 == "value") =>
{
(1, 2)
}
(Value::String(NixString(s1)), Value::String(NixString(s2)))
if (s1 == "value" && s2 == "name") =>
{
(2, 1)
}
// Technically this branch lets type errors pass,
// but they will be caught during normal attribute
// set construction instead.
_ => (0, 0),
}
};
match is_pair {
(1, 2) => {
// The value of 'name' is at stack slot 0, the
// value of 'value' is at stack slot 2.
let pair = Value::Attrs(Rc::new(NixAttrs::KV {
name: self.pop(),
value: {
self.pop(); // ignore the key
self.pop()
},
}));
// Clean up the last key fragment.
self.pop();
self.push(pair);
return Ok(());
}
(2, 1) => {
// The value of 'name' is at stack slot 2, the
// value of 'value' is at stack slot 0.
let pair = Value::Attrs(Rc::new(NixAttrs::KV {
value: self.pop(),
name: {
self.pop(); // ignore the key
self.pop()
},
}));
// Clean up the last key fragment.
self.pop();
self.push(pair);
return Ok(());
}
_ => {}
}
}
let mut attrs: BTreeMap<NixString, Value> = BTreeMap::new(); let mut attrs: BTreeMap<NixString, Value> = BTreeMap::new();
for _ in 0..count { for _ in 0..count {