Grackle: Write array length before array (#1326)

This gives the Execution Plan virtual machine the information it needs to look up indices of arrays at runtime.
This commit is contained in:
Adam Chalmers
2024-01-26 19:07:29 +11:00
committed by GitHub
parent 98f7a564ea
commit 390cb2d51d
3 changed files with 83 additions and 23 deletions

View File

@ -17,7 +17,10 @@ pub enum EpBinding {
/// A KCL value which gets stored in a particular address in KCEP memory. /// A KCL value which gets stored in a particular address in KCEP memory.
Single(Address), Single(Address),
/// A sequence of KCL values, indexed by their position in the sequence. /// A sequence of KCL values, indexed by their position in the sequence.
Sequence { elements: Vec<EpBinding> }, Sequence {
length_at: Address,
elements: Vec<EpBinding>,
},
/// A sequence of KCL values, indexed by their identifier. /// A sequence of KCL values, indexed by their identifier.
Map(HashMap<String, EpBinding>), Map(HashMap<String, EpBinding>),
/// Not associated with a KCEP address. /// Not associated with a KCEP address.
@ -38,7 +41,7 @@ impl EpBinding {
LiteralIdentifier::Literal(litval) => match litval.value { LiteralIdentifier::Literal(litval) => match litval.value {
// Arrays can be indexed by integers. // Arrays can be indexed by integers.
LiteralValue::IInteger(i) => match self { LiteralValue::IInteger(i) => match self {
EpBinding::Sequence { elements } => { EpBinding::Sequence { elements, length_at: _ } => {
let i = usize::try_from(i).map_err(|_| CompileError::InvalidIndex(i.to_string()))?; let i = usize::try_from(i).map_err(|_| CompileError::InvalidIndex(i.to_string()))?;
elements elements
.get(i) .get(i)

View File

@ -456,6 +456,11 @@ impl Planner {
// each element of that array. Collect their bindings, and bind them all // each element of that array. Collect their bindings, and bind them all
// under one property of the parent object. // under one property of the parent object.
let n = expr.elements.len(); let n = expr.elements.len();
let length_at = self.next_addr.offset_by(1);
acc_instrs.push(Instruction::SetPrimitive {
address: length_at,
value: expr.elements.len().into(),
});
let binding = expr let binding = expr
.elements .elements
.into_iter() .into_iter()
@ -465,7 +470,7 @@ impl Planner {
acc_instrs.extend(instructions); acc_instrs.extend(instructions);
Ok(seq) Ok(seq)
}) })
.map(|elements| EpBinding::Sequence { elements })?; .map(|elements| EpBinding::Sequence { length_at, elements })?;
acc_bindings.insert(key.name, binding); acc_bindings.insert(key.name, binding);
} }
KclValueGroup::ObjectExpression(expr) => { KclValueGroup::ObjectExpression(expr) => {
@ -503,7 +508,12 @@ impl Planner {
) -> Result<EvalPlan, CompileError> { ) -> Result<EvalPlan, CompileError> {
// First, emit a plan to compute each element of the array. // First, emit a plan to compute each element of the array.
// Collect all the bindings from each element too. // Collect all the bindings from each element too.
let (instructions, bindings) = expr.elements.into_iter().try_fold( let length_at = self.next_addr.offset_by(1);
let mut instructions = vec![Instruction::SetPrimitive {
address: length_at,
value: expr.elements.len().into(),
}];
let (instrs, bindings) = expr.elements.into_iter().try_fold(
(Vec::new(), Vec::new()), (Vec::new(), Vec::new()),
|(mut acc_instrs, mut acc_bindings), element| { |(mut acc_instrs, mut acc_bindings), element| {
match KclValueGroup::from(element) { match KclValueGroup::from(element) {
@ -519,6 +529,11 @@ impl Planner {
// emit a plan to calculate each element of this child array. // emit a plan to calculate each element of this child array.
// Then we collect the child array's bindings, and bind them to one // Then we collect the child array's bindings, and bind them to one
// element of the parent array. // element of the parent array.
let sublength_at = self.next_addr.offset_by(1);
acc_instrs.push(Instruction::SetPrimitive {
address: sublength_at,
value: expr.elements.len().into(),
});
let binding = expr let binding = expr
.elements .elements
.into_iter() .into_iter()
@ -528,7 +543,10 @@ impl Planner {
seq.push(binding); seq.push(binding);
Ok(seq) Ok(seq)
}) })
.map(|elements| EpBinding::Sequence { elements })?; .map(|elements| EpBinding::Sequence {
length_at: sublength_at,
elements,
})?;
acc_bindings.push(binding); acc_bindings.push(binding);
} }
KclValueGroup::ObjectExpression(expr) => { KclValueGroup::ObjectExpression(expr) => {
@ -553,9 +571,13 @@ impl Planner {
Ok((acc_instrs, acc_bindings)) Ok((acc_instrs, acc_bindings))
}, },
)?; )?;
instructions.extend(instrs);
Ok(EvalPlan { Ok(EvalPlan {
instructions, instructions,
binding: EpBinding::Sequence { elements: bindings }, binding: EpBinding::Sequence {
length_at,
elements: bindings,
},
}) })
} }
} }

View File

@ -48,16 +48,22 @@ fn bind_array() {
assert_eq!( assert_eq!(
plan, plan,
vec![ vec![
// Array length
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO, address: Address::ZERO,
value: 3usize.into()
},
// Array contents
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 44i64.into(), value: 44i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(1), address: Address::ZERO + 2,
value: 55i64.into(), value: 55i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(2), address: Address::ZERO + 3,
value: "sixty-six".to_owned().into(), value: "sixty-six".to_owned().into(),
} }
] ]
@ -71,16 +77,28 @@ fn bind_nested_array() {
assert_eq!( assert_eq!(
plan, plan,
vec![ vec![
// Outer array length
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO, address: Address::ZERO,
value: 2usize.into(),
},
// Outer array contents
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 44i64.into(), value: 44i64.into(),
}, },
// Inner array length
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(1), address: Address::ZERO + 2,
value: 2usize.into(),
},
// Inner array length
Instruction::SetPrimitive {
address: Address::ZERO + 3,
value: 55i64.into(), value: 55i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(2), address: Address::ZERO + 4,
value: "sixty-six".to_owned().into(), value: "sixty-six".to_owned().into(),
} }
] ]
@ -94,16 +112,22 @@ fn bind_arrays_with_objects_elements() {
assert_eq!( assert_eq!(
plan, plan,
vec![ vec![
// Array length
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO, address: Address::ZERO,
value: 2usize.into()
},
// Array contents
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 44i64.into(), value: 44i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(1), address: Address::ZERO + 2,
value: 55i64.into(), value: 55i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(2), address: Address::ZERO + 3,
value: "sixty-six".to_owned().into(), value: "sixty-six".to_owned().into(),
} }
] ]
@ -265,7 +289,7 @@ fn member_expressions_array() {
let (_plan, scope) = must_plan(program); let (_plan, scope) = must_plan(program);
match scope.get("first").unwrap() { match scope.get("first").unwrap() {
EpBinding::Single(addr) => { EpBinding::Single(addr) => {
assert_eq!(*addr, Address::ZERO); assert_eq!(*addr, Address::ZERO + 2);
} }
other => { other => {
panic!("expected 'number' bound to 0x0 but it was bound to {other:?}"); panic!("expected 'number' bound to 0x0 but it was bound to {other:?}");
@ -273,7 +297,7 @@ fn member_expressions_array() {
} }
match scope.get("last").unwrap() { match scope.get("last").unwrap() {
EpBinding::Single(addr) => { EpBinding::Single(addr) => {
assert_eq!(*addr, Address::ZERO + 3); assert_eq!(*addr, Address::ZERO + 6);
} }
other => { other => {
panic!("expected 'number' bound to 0x3 but it was bound to {other:?}"); panic!("expected 'number' bound to 0x3 but it was bound to {other:?}");
@ -704,17 +728,21 @@ fn store_object_with_array_property() {
address: Address::ZERO, address: Address::ZERO,
value: 1i64.into(), value: 1i64.into(),
}, },
// Array length
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(1), address: Address::ZERO + 1,
value: 2usize.into(),
},
Instruction::SetPrimitive {
address: Address::ZERO + 2,
value: 2i64.into(), value: 2i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO.offset(2), address: Address::ZERO + 3,
value: 3i64.into(), value: 3i64.into(),
}, },
]; ];
assert_eq!(actual, expected); assert_eq!(actual, expected);
eprintln!("{bindings:#?}");
assert_eq!( assert_eq!(
bindings.get("x0").unwrap(), bindings.get("x0").unwrap(),
&EpBinding::Map(HashMap::from([ &EpBinding::Map(HashMap::from([
@ -722,9 +750,10 @@ fn store_object_with_array_property() {
( (
"b".to_owned(), "b".to_owned(),
EpBinding::Sequence { EpBinding::Sequence {
length_at: Address::ZERO + 1,
elements: vec![ elements: vec![
EpBinding::Single(Address::ZERO.offset(1)), EpBinding::Single(Address::ZERO + 2),
EpBinding::Single(Address::ZERO.offset(2)), EpBinding::Single(Address::ZERO + 3),
] ]
} }
), ),
@ -771,17 +800,22 @@ fn arrays_as_parameters() {
let array = identity([1,2,3])"; let array = identity([1,2,3])";
let (plan, scope) = must_plan(program); let (plan, scope) = must_plan(program);
let expected_plan = vec![ let expected_plan = vec![
// Array contents // Array length
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO, address: Address::ZERO,
value: 3usize.into(),
},
// Array contents
Instruction::SetPrimitive {
address: Address::ZERO + 1,
value: 1i64.into(), value: 1i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO + 1, address: Address::ZERO + 2,
value: 2i64.into(), value: 2i64.into(),
}, },
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: Address::ZERO + 2, address: Address::ZERO + 3,
value: 3i64.into(), value: 3i64.into(),
}, },
]; ];
@ -789,10 +823,11 @@ fn arrays_as_parameters() {
assert_eq!( assert_eq!(
scope.get("array").unwrap(), scope.get("array").unwrap(),
&EpBinding::Sequence { &EpBinding::Sequence {
length_at: Address::ZERO,
elements: vec![ elements: vec![
EpBinding::Single(Address::ZERO),
EpBinding::Single(Address::ZERO + 1), EpBinding::Single(Address::ZERO + 1),
EpBinding::Single(Address::ZERO + 2), EpBinding::Single(Address::ZERO + 2),
EpBinding::Single(Address::ZERO + 3),
] ]
} }
) )