From ec1770f95ac33e3cfdb57c84f2b94fa63fbc0fb2 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Tue, 9 Aug 2022 18:23:32 +0300 Subject: 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 Tested-by: BuildkiteCI --- tvix/eval/src/vm.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index e4597807ce..d6d3de154c 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -25,6 +25,10 @@ impl VM { 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 { let v2 = self.pop(); let v1 = self.pop(); @@ -128,6 +132,79 @@ impl VM { } 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 = BTreeMap::new(); for _ in 0..count { -- cgit 1.4.1