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::LoadVariable(index) => {
|
||||
let index = usize::from(index);
|
||||
let value = if scratch.variable_generations[index] == scratch.generation {
|
||||
scratch.variables[index]
|
||||
} else {
|
||||
let expected_type = self.variable_types[index];
|
||||
let value = resolve(index, &self.variables[index], expected_type)?;
|
||||
if value.value_type() != expected_type {
|
||||
return Err(EvalError::new(format!(
|
||||
"variable {} expected {:?}, got {:?}",
|
||||
self.variables[index],
|
||||
expected_type,
|
||||
value.value_type()
|
||||
)));
|
||||
let cached = scratch.variables[index];
|
||||
let value = match cached {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
let expected_type = self.variable_types[index];
|
||||
let value = resolve(index, &self.variables[index], expected_type)?;
|
||||
if value.value_type() != expected_type {
|
||||
return Err(EvalError::new(format!(
|
||||
"variable {} expected {:?}, got {:?}",
|
||||
self.variables[index],
|
||||
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);
|
||||
}
|
||||
Instruction::LoadLocal(index) => {
|
||||
let index = usize::from(index);
|
||||
if scratch.local_generations[index] != scratch.generation {
|
||||
return Err(EvalError::new(format!(
|
||||
"local slot {index} was not initialized"
|
||||
)));
|
||||
}
|
||||
let value = scratch.locals[index];
|
||||
let value = scratch.locals[index].ok_or_else(|| {
|
||||
EvalError::new(format!("local slot {index} was not initialized"))
|
||||
})?;
|
||||
scratch.stack.push(value);
|
||||
}
|
||||
Instruction::StoreLocal(index) => {
|
||||
let index = usize::from(index);
|
||||
let value = pop(&mut scratch.stack)?;
|
||||
scratch.locals[index] = value;
|
||||
scratch.local_generations[index] = scratch.generation;
|
||||
scratch.locals[usize::from(index)] = Some(value);
|
||||
}
|
||||
Instruction::Unary(operator) => {
|
||||
let value = pop(&mut scratch.stack)?;
|
||||
@@ -264,11 +260,8 @@ impl Program {
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Scratch {
|
||||
stack: Vec<Value>,
|
||||
variables: Vec<Value>,
|
||||
variable_generations: Vec<u32>,
|
||||
locals: Vec<Value>,
|
||||
local_generations: Vec<u32>,
|
||||
generation: u32,
|
||||
variables: Vec<Option<Value>>,
|
||||
locals: Vec<Option<Value>>,
|
||||
}
|
||||
|
||||
impl Scratch {
|
||||
@@ -280,21 +273,10 @@ impl Scratch {
|
||||
.len()
|
||||
.saturating_sub(self.stack.capacity()),
|
||||
);
|
||||
self.generation = self.generation.wrapping_add(1);
|
||||
if self.generation == 0 {
|
||||
self.variable_generations.fill(0);
|
||||
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);
|
||||
}
|
||||
self.variables.clear();
|
||||
self.variables.resize(program.variables.len(), None);
|
||||
self.locals.clear();
|
||||
self.locals.resize(program.local_count, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
fn matches_rhai_for_representative_numeric_boolean_corpus() {
|
||||
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