Skip to main content

runmat_vm/interpreter/
state.rs

1use crate::bytecode::program::ExecutionContext;
2use crate::bytecode::Bytecode;
3use log::debug;
4#[cfg(feature = "native-accel")]
5use runmat_accelerate::graph::AccelGraph;
6#[cfg(feature = "native-accel")]
7use runmat_accelerate::{prepare_fusion_plan, FusionPlan};
8use runmat_builtins::Value;
9use std::collections::{HashMap, HashSet};
10#[cfg(feature = "native-accel")]
11use std::sync::Arc;
12
13#[derive(Debug)]
14pub enum InterpreterOutcome {
15    Completed(Vec<Value>),
16}
17
18#[derive(Debug)]
19pub struct InterpreterState {
20    pub bytecode: Bytecode,
21    pub stack: Vec<Value>,
22    pub vars: Vec<Value>,
23    pub pc: usize,
24    pub context: ExecutionContext,
25    pub try_stack: Vec<(usize, Option<usize>)>,
26    pub last_exception: Option<runmat_builtins::MException>,
27    pub imports: Vec<(Vec<String>, bool)>,
28    pub global_aliases: HashMap<usize, String>,
29    pub persistent_aliases: HashMap<usize, String>,
30    pub missing_input_slots: HashSet<usize>,
31    pub current_function_name: String,
32    pub call_counts: Vec<(usize, usize)>,
33    pub initial_assigned_var_count: usize,
34    #[cfg(feature = "native-accel")]
35    pub fusion_plan: Option<Arc<FusionPlan>>,
36    #[cfg(feature = "native-accel")]
37    pub fusion_accel_graph: Option<AccelGraph>,
38}
39
40impl InterpreterState {
41    pub fn new(
42        bytecode: Bytecode,
43        initial_vars: &mut [Value],
44        current_function_name: Option<&str>,
45        call_counts: Vec<(usize, usize)>,
46    ) -> Self {
47        let initial_assigned_var_count = initial_vars.len();
48        let mut vars = initial_vars.to_vec();
49        if vars.len() < bytecode.var_count {
50            vars.resize(bytecode.var_count, Value::Num(0.0));
51        }
52        if bytecode.async_metadata.mir_spawn_site_count > 0
53            || bytecode.async_metadata.mir_await_site_count > 0
54        {
55            debug!(
56                "async semantics: compiled bytecode carries {} MIR spawn site(s) and {} MIR await site(s); runtime model={} with explicit spawn/await bytecode boundaries",
57                bytecode.async_metadata.mir_spawn_site_count,
58                bytecode.async_metadata.mir_await_site_count,
59                bytecode.async_metadata.runtime_model.as_str()
60            );
61        }
62        #[cfg(feature = "native-accel")]
63        let (fusion_plan, fusion_accel_graph) = {
64            // Runtime planning/execution owns accel-graph realization from the active
65            // bytecode instruction stream whenever semantic fusion scaffolds exist.
66            let runtime_groups = bytecode.runtime_fusion_groups();
67            let runtime_graph = bytecode.runtime_accel_graph_for_fusion(&runtime_groups);
68            let runtime_groups = if let Some(graph) = runtime_graph.as_ref() {
69                bytecode.runtime_fusion_groups_for_graph(graph)
70            } else {
71                runtime_groups
72            };
73            let fusion_plan = prepare_fusion_plan(
74                runtime_graph.as_ref(),
75                &runtime_groups,
76                bytecode.fusion_metadata.mir_fusion_candidate_group_count,
77            );
78            (fusion_plan, runtime_graph)
79        };
80        Self {
81            stack: Vec::new(),
82            context: ExecutionContext {
83                call_stack: Vec::new(),
84                locals: Vec::new(),
85                instruction_pointer: 0,
86                spawned_task_ids: std::collections::HashSet::new(),
87                next_spawn_task_id: 0,
88            },
89            try_stack: Vec::new(),
90            last_exception: None,
91            imports: Vec::new(),
92            global_aliases: HashMap::new(),
93            persistent_aliases: HashMap::new(),
94            missing_input_slots: HashSet::new(),
95            vars,
96            pc: 0,
97            call_counts,
98            initial_assigned_var_count,
99            current_function_name: current_function_name
100                .map(|s| s.to_string())
101                .unwrap_or_else(|| "<main>".to_string()),
102            #[cfg(feature = "native-accel")]
103            fusion_plan,
104            #[cfg(feature = "native-accel")]
105            fusion_accel_graph,
106            bytecode,
107        }
108    }
109}
110
111#[cfg(all(test, feature = "native-accel"))]
112mod tests {
113    use super::InterpreterState;
114    use crate::bytecode::{Bytecode, FusionInstructionKind, FusionInstructionWindow};
115    use runmat_accelerate::graph::{AccelNodeLabel, InstrSpan, PrimitiveOp};
116
117    #[test]
118    fn runtime_materialized_graph_is_retained_for_fusion_execution() {
119        let mut bytecode = Bytecode::empty();
120        bytecode.instructions = vec![
121            crate::Instr::LoadVar(0),
122            crate::Instr::LoadVar(1),
123            crate::Instr::Add,
124        ];
125        bytecode.var_types = vec![
126            runmat_builtins::Type::Num,
127            runmat_builtins::Type::Num,
128            runmat_builtins::Type::Num,
129        ];
130        bytecode.fusion_metadata.mir_fusion_candidate_group_count = 1;
131        bytecode.fusion_metadata.instruction_windows = vec![FusionInstructionWindow {
132            span: InstrSpan { start: 2, end: 2 },
133            kind: FusionInstructionKind::Elementwise,
134        }];
135
136        let mut initial_vars = Vec::new();
137        let state = InterpreterState::new(bytecode, &mut initial_vars, Some("<main>"), Vec::new());
138        assert!(
139            state.fusion_plan.is_some(),
140            "expected runtime fusion plan when semantic windows exist"
141        );
142        assert!(
143            state.fusion_accel_graph.is_some(),
144            "expected runtime accel graph to be retained for fusion execution"
145        );
146    }
147
148    #[test]
149    fn runtime_state_ignores_stale_compile_graph_metadata() {
150        let mut bytecode = Bytecode::empty();
151        bytecode.instructions = vec![
152            crate::Instr::LoadVar(0),
153            crate::Instr::LoadVar(1),
154            crate::Instr::Add,
155        ];
156        bytecode.var_types = vec![
157            runmat_builtins::Type::Num,
158            runmat_builtins::Type::Num,
159            runmat_builtins::Type::Num,
160        ];
161        let stale_graph = crate::accel::graph::build_accel_graph(
162            &[
163                crate::Instr::LoadVar(0),
164                crate::Instr::LoadVar(1),
165                crate::Instr::Mul,
166            ],
167            &bytecode.var_types,
168        );
169        bytecode.accel_graph = Some(stale_graph);
170        bytecode.fusion_metadata.mir_fusion_candidate_group_count = 1;
171        bytecode.fusion_metadata.instruction_windows = vec![FusionInstructionWindow {
172            span: InstrSpan { start: 2, end: 2 },
173            kind: FusionInstructionKind::Elementwise,
174        }];
175
176        let mut initial_vars = Vec::new();
177        let state = InterpreterState::new(bytecode, &mut initial_vars, Some("<main>"), Vec::new());
178        let graph = state
179            .fusion_accel_graph
180            .as_ref()
181            .expect("expected runtime accel graph to be retained");
182        assert!(
183            graph
184                .nodes
185                .iter()
186                .any(|node| matches!(node.label, AccelNodeLabel::Primitive(PrimitiveOp::Add))),
187            "runtime retained graph should reflect active bytecode instructions"
188        );
189        assert!(
190            !graph
191                .nodes
192                .iter()
193                .any(|node| matches!(node.label, AccelNodeLabel::Primitive(PrimitiveOp::Mul))),
194            "stale compile graph metadata should not be retained in runtime state"
195        );
196    }
197}