perf: reuse numeric VM slots by generation
This commit is contained in:
@@ -168,10 +168,9 @@ impl Program {
|
|||||||
Instruction::Push(value) => scratch.stack.push(value),
|
Instruction::Push(value) => scratch.stack.push(value),
|
||||||
Instruction::LoadVariable(index) => {
|
Instruction::LoadVariable(index) => {
|
||||||
let index = usize::from(index);
|
let index = usize::from(index);
|
||||||
let cached = scratch.variables[index];
|
let value = if scratch.variable_generations[index] == scratch.generation {
|
||||||
let value = match cached {
|
scratch.variables[index]
|
||||||
Some(value) => value,
|
} else {
|
||||||
None => {
|
|
||||||
let expected_type = self.variable_types[index];
|
let expected_type = self.variable_types[index];
|
||||||
let value = resolve(index, &self.variables[index], expected_type)?;
|
let value = resolve(index, &self.variables[index], expected_type)?;
|
||||||
if value.value_type() != expected_type {
|
if value.value_type() != expected_type {
|
||||||
@@ -182,22 +181,27 @@ impl Program {
|
|||||||
value.value_type()
|
value.value_type()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
scratch.variables[index] = Some(value);
|
scratch.variables[index] = value;
|
||||||
|
scratch.variable_generations[index] = scratch.generation;
|
||||||
value
|
value
|
||||||
}
|
|
||||||
};
|
};
|
||||||
scratch.stack.push(value);
|
scratch.stack.push(value);
|
||||||
}
|
}
|
||||||
Instruction::LoadLocal(index) => {
|
Instruction::LoadLocal(index) => {
|
||||||
let index = usize::from(index);
|
let index = usize::from(index);
|
||||||
let value = scratch.locals[index].ok_or_else(|| {
|
if scratch.local_generations[index] != scratch.generation {
|
||||||
EvalError::new(format!("local slot {index} was not initialized"))
|
return Err(EvalError::new(format!(
|
||||||
})?;
|
"local slot {index} was not initialized"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let value = scratch.locals[index];
|
||||||
scratch.stack.push(value);
|
scratch.stack.push(value);
|
||||||
}
|
}
|
||||||
Instruction::StoreLocal(index) => {
|
Instruction::StoreLocal(index) => {
|
||||||
|
let index = usize::from(index);
|
||||||
let value = pop(&mut scratch.stack)?;
|
let value = pop(&mut scratch.stack)?;
|
||||||
scratch.locals[usize::from(index)] = Some(value);
|
scratch.locals[index] = value;
|
||||||
|
scratch.local_generations[index] = scratch.generation;
|
||||||
}
|
}
|
||||||
Instruction::Unary(operator) => {
|
Instruction::Unary(operator) => {
|
||||||
let value = pop(&mut scratch.stack)?;
|
let value = pop(&mut scratch.stack)?;
|
||||||
@@ -260,8 +264,11 @@ impl Program {
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Scratch {
|
pub(crate) struct Scratch {
|
||||||
stack: Vec<Value>,
|
stack: Vec<Value>,
|
||||||
variables: Vec<Option<Value>>,
|
variables: Vec<Value>,
|
||||||
locals: Vec<Option<Value>>,
|
variable_generations: Vec<u32>,
|
||||||
|
locals: Vec<Value>,
|
||||||
|
local_generations: Vec<u32>,
|
||||||
|
generation: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scratch {
|
impl Scratch {
|
||||||
@@ -273,10 +280,21 @@ impl Scratch {
|
|||||||
.len()
|
.len()
|
||||||
.saturating_sub(self.stack.capacity()),
|
.saturating_sub(self.stack.capacity()),
|
||||||
);
|
);
|
||||||
self.variables.clear();
|
self.generation = self.generation.wrapping_add(1);
|
||||||
self.variables.resize(program.variables.len(), None);
|
if self.generation == 0 {
|
||||||
self.locals.clear();
|
self.variable_generations.fill(0);
|
||||||
self.locals.resize(program.local_count, None);
|
self.local_generations.fill(0);
|
||||||
|
self.generation = 1;
|
||||||
|
}
|
||||||
|
if self.variables.len() < program.variables.len() {
|
||||||
|
self.variables
|
||||||
|
.resize(program.variables.len(), Value::Number(0.0));
|
||||||
|
self.variable_generations.resize(program.variables.len(), 0);
|
||||||
|
}
|
||||||
|
if self.locals.len() < program.local_count {
|
||||||
|
self.locals.resize(program.local_count, Value::Number(0.0));
|
||||||
|
self.local_generations.resize(program.local_count, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1362,6 +1380,27 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scratch_generation_rollover_invalidates_cached_slots() {
|
||||||
|
let program = compile("left + right", |_| Some(ValueType::Number)).expect("compile");
|
||||||
|
let mut scratch = Scratch::default();
|
||||||
|
let first = program
|
||||||
|
.evaluate(&mut scratch, |_index, name, _expected| {
|
||||||
|
Ok(Value::Number(if name == "left" { 1.0 } else { 2.0 }))
|
||||||
|
})
|
||||||
|
.expect("first evaluation");
|
||||||
|
assert_eq!(first, Value::Number(3.0));
|
||||||
|
|
||||||
|
scratch.generation = u32::MAX;
|
||||||
|
let second = program
|
||||||
|
.evaluate(&mut scratch, |_index, name, _expected| {
|
||||||
|
Ok(Value::Number(if name == "left" { 10.0 } else { 20.0 }))
|
||||||
|
})
|
||||||
|
.expect("evaluation after generation rollover");
|
||||||
|
assert_eq!(second, Value::Number(30.0));
|
||||||
|
assert_eq!(scratch.generation, 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn matches_rhai_for_representative_numeric_boolean_corpus() {
|
fn matches_rhai_for_representative_numeric_boolean_corpus() {
|
||||||
let source = r#"
|
let source = r#"
|
||||||
|
|||||||
Reference in New Issue
Block a user