revert: reject marginal numeric VM slot reuse
This commit is contained in:
@@ -168,40 +168,36 @@ 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 value = if scratch.variable_generations[index] == scratch.generation {
|
let cached = scratch.variables[index];
|
||||||
scratch.variables[index]
|
let value = match cached {
|
||||||
} else {
|
Some(value) => value,
|
||||||
let expected_type = self.variable_types[index];
|
None => {
|
||||||
let value = resolve(index, &self.variables[index], expected_type)?;
|
let expected_type = self.variable_types[index];
|
||||||
if value.value_type() != expected_type {
|
let value = resolve(index, &self.variables[index], expected_type)?;
|
||||||
return Err(EvalError::new(format!(
|
if value.value_type() != expected_type {
|
||||||
"variable {} expected {:?}, got {:?}",
|
return Err(EvalError::new(format!(
|
||||||
self.variables[index],
|
"variable {} expected {:?}, got {:?}",
|
||||||
expected_type,
|
self.variables[index],
|
||||||
value.value_type()
|
expected_type,
|
||||||
)));
|
value.value_type()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
scratch.variables[index] = Some(value);
|
||||||
|
value
|
||||||
}
|
}
|
||||||
scratch.variables[index] = value;
|
|
||||||
scratch.variable_generations[index] = scratch.generation;
|
|
||||||
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);
|
||||||
if scratch.local_generations[index] != scratch.generation {
|
let value = scratch.locals[index].ok_or_else(|| {
|
||||||
return Err(EvalError::new(format!(
|
EvalError::new(format!("local slot {index} was not initialized"))
|
||||||
"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[index] = value;
|
scratch.locals[usize::from(index)] = Some(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)?;
|
||||||
@@ -264,11 +260,8 @@ impl Program {
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Scratch {
|
pub(crate) struct Scratch {
|
||||||
stack: Vec<Value>,
|
stack: Vec<Value>,
|
||||||
variables: Vec<Value>,
|
variables: Vec<Option<Value>>,
|
||||||
variable_generations: Vec<u32>,
|
locals: Vec<Option<Value>>,
|
||||||
locals: Vec<Value>,
|
|
||||||
local_generations: Vec<u32>,
|
|
||||||
generation: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scratch {
|
impl Scratch {
|
||||||
@@ -280,21 +273,10 @@ impl Scratch {
|
|||||||
.len()
|
.len()
|
||||||
.saturating_sub(self.stack.capacity()),
|
.saturating_sub(self.stack.capacity()),
|
||||||
);
|
);
|
||||||
self.generation = self.generation.wrapping_add(1);
|
self.variables.clear();
|
||||||
if self.generation == 0 {
|
self.variables.resize(program.variables.len(), None);
|
||||||
self.variable_generations.fill(0);
|
self.locals.clear();
|
||||||
self.local_generations.fill(0);
|
self.locals.resize(program.local_count, None);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1380,27 +1362,6 @@ 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#"
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": "fidc-numeric-vm-binding-generation-rejection/v1",
|
||||||
|
"measuredAt": "2026-09-06T05:36:00+08:00",
|
||||||
|
"host": "192.168.31.177",
|
||||||
|
"baseline": {
|
||||||
|
"engineCommit": "5f08978",
|
||||||
|
"primaryFiveYearHotMedianEngineSeconds": 2.629,
|
||||||
|
"primaryFiveYearTotalReturn": 0.9219861819172002,
|
||||||
|
"primaryFiveYearTradeCount": 26088,
|
||||||
|
"primaryCanonicalSha256": "b42fea66237d06eadb24f6b8c9e2760e7319fe3699f315b99e01f433ef2aa234",
|
||||||
|
"primaryResultStoreSha256": "9ccf0c0fc6f5d72e974381ad4cd09a80241d7de99f2649f2b01736f7c80dc2c7",
|
||||||
|
"profileInstructions": 16600366059,
|
||||||
|
"profileBranches": 2592214949
|
||||||
|
},
|
||||||
|
"compileTimeIdentifierBinding": {
|
||||||
|
"engineCommit": "2135a5b",
|
||||||
|
"implementation": "map every numeric VM identifier to a typed runtime enum during expression plan compilation",
|
||||||
|
"primaryFiveYearHotEngineSeconds": [2.608, 2.624, 2.651, 2.619, 2.781],
|
||||||
|
"primaryFiveYearHotMedianEngineSeconds": 2.624,
|
||||||
|
"observedMedianImprovementPercent": 0.190186,
|
||||||
|
"profileInstructions": 16360894880,
|
||||||
|
"instructionReductionPercent": 1.442566,
|
||||||
|
"secondaryFiveYearResultConsistent": true,
|
||||||
|
"secondaryFiveYearPerformanceExcluded": true,
|
||||||
|
"secondaryFiveYearExclusionReason": "the symbolic campaign entered a high-memory-bandwidth phase between the primary and secondary batches",
|
||||||
|
"netCodeLinesAdded": 562,
|
||||||
|
"retained": false
|
||||||
|
},
|
||||||
|
"generationStampedScratchSlots": {
|
||||||
|
"engineCommit": "5122c73",
|
||||||
|
"implementation": "invalidate numeric VM variable and local slots with a generation counter instead of clearing Option arrays for each evaluation",
|
||||||
|
"localReleaseBenchmark": {
|
||||||
|
"baselineVmNanosecondsPerEvaluation": 86.992,
|
||||||
|
"candidateSamples": [83.284, 94.166, 86.275, 84.138, 85.453],
|
||||||
|
"candidateMedianNanosecondsPerEvaluation": 85.453,
|
||||||
|
"componentImprovementPercent": 1.769136,
|
||||||
|
"comparisonStrength": "weak because the baseline contains one sample"
|
||||||
|
},
|
||||||
|
"primaryFiveYearHotEngineSeconds": [2.634, 2.636, 2.608, 2.642, 2.710],
|
||||||
|
"primaryFiveYearHotMedianEngineSeconds": 2.636,
|
||||||
|
"observedMedianRegressionPercent": 0.266261,
|
||||||
|
"profileInstructions": 16521146700,
|
||||||
|
"instructionReductionPercent": 0.477215,
|
||||||
|
"netCodeLinesAdded": 39,
|
||||||
|
"retained": false
|
||||||
|
},
|
||||||
|
"hostLoad": {
|
||||||
|
"symbolicWorkersObserved": 3,
|
||||||
|
"symbolicWorkerCpuPercentApproximate": [720, 718, 698],
|
||||||
|
"symbolicWorkerRssBytesApproximate": [54479982592, 53353455616, 53941170176],
|
||||||
|
"wallTimeComparisonsAcrossPhasesExcluded": true
|
||||||
|
},
|
||||||
|
"testGate": {
|
||||||
|
"workspacePassedBeforeFirstCandidateRejection": 539,
|
||||||
|
"workspacePassedForGenerationCandidate": 540,
|
||||||
|
"failed": 0,
|
||||||
|
"ignoredManualBenchmarks": 8
|
||||||
|
},
|
||||||
|
"remoteArtifacts": [
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/goal-primary-five-year-20260906.json",
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/identifier-binding-primary-five-year-20260906.json",
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/identifier-binding-secondary-five-year-20260906.json",
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/identifier-binding-candidate-perf-stat-20260906.csv",
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/identifier-binding-rollback-primary-five-year-20260906.json",
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/identifier-binding-rollback-perf-stat-20260906.csv",
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/vm-generation-primary-five-year-20260906.json",
|
||||||
|
"/srv/fidc/canonical/run/fidc-private/evidence/vm-generation-candidate-perf-stat-20260906.csv"
|
||||||
|
],
|
||||||
|
"decision": {
|
||||||
|
"status": "rejected_and_removed",
|
||||||
|
"reason": "both candidates preserved exact business results but failed to produce a material, stable end-to-end improvement; the typed binding added disproportionate code and the generation slots slightly regressed the five-year median",
|
||||||
|
"nextTarget": "profile and specialize the numeric VM instruction dispatch or runtime helper execution without changing expression, PIT, or lazy short-circuit semantics"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user