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