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    #[cfg(feature = "native-accel")]
34    pub fusion_plan: Option<Arc<FusionPlan>>,
35    #[cfg(feature = "native-accel")]
36    pub fusion_accel_graph: Option<AccelGraph>,
37}
38
39impl InterpreterState {
40    pub fn new(
41        bytecode: Bytecode,
42        initial_vars: &mut [Value],
43        current_function_name: Option<&str>,
44        call_counts: Vec<(usize, usize)>,
45    ) -> Self {
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            current_function_name: current_function_name
97                .map(|s| s.to_string())
98                .unwrap_or_else(|| "<main>".to_string()),
99            #[cfg(feature = "native-accel")]
100            fusion_plan,
101            #[cfg(feature = "native-accel")]
102            fusion_accel_graph,
103            bytecode,
104        }
105    }
106}
107
108#[cfg(all(test, feature = "native-accel"))]
109mod tests {
110    use super::InterpreterState;
111    use crate::bytecode::{Bytecode, FusionInstructionKind, FusionInstructionWindow};
112    use runmat_accelerate::graph::{AccelNodeLabel, InstrSpan, PrimitiveOp};
113
114    #[test]
115    fn runtime_materialized_graph_is_retained_for_fusion_execution() {
116        let mut bytecode = Bytecode::empty();
117        bytecode.instructions = vec![
118            crate::Instr::LoadVar(0),
119            crate::Instr::LoadVar(1),
120            crate::Instr::Add,
121        ];
122        bytecode.var_types = vec![
123            runmat_builtins::Type::Num,
124            runmat_builtins::Type::Num,
125            runmat_builtins::Type::Num,
126        ];
127        bytecode.fusion_metadata.mir_fusion_candidate_group_count = 1;
128        bytecode.fusion_metadata.instruction_windows = vec![FusionInstructionWindow {
129            span: InstrSpan { start: 2, end: 2 },
130            kind: FusionInstructionKind::Elementwise,
131        }];
132
133        let mut initial_vars = Vec::new();
134        let state = InterpreterState::new(bytecode, &mut initial_vars, Some("<main>"), Vec::new());
135        assert!(
136            state.fusion_plan.is_some(),
137            "expected runtime fusion plan when semantic windows exist"
138        );
139        assert!(
140            state.fusion_accel_graph.is_some(),
141            "expected runtime accel graph to be retained for fusion execution"
142        );
143    }
144
145    #[test]
146    fn runtime_state_ignores_stale_compile_graph_metadata() {
147        let mut bytecode = Bytecode::empty();
148        bytecode.instructions = vec![
149            crate::Instr::LoadVar(0),
150            crate::Instr::LoadVar(1),
151            crate::Instr::Add,
152        ];
153        bytecode.var_types = vec![
154            runmat_builtins::Type::Num,
155            runmat_builtins::Type::Num,
156            runmat_builtins::Type::Num,
157        ];
158        let stale_graph = crate::accel::graph::build_accel_graph(
159            &[
160                crate::Instr::LoadVar(0),
161                crate::Instr::LoadVar(1),
162                crate::Instr::Mul,
163            ],
164            &bytecode.var_types,
165        );
166        bytecode.accel_graph = Some(stale_graph);
167        bytecode.fusion_metadata.mir_fusion_candidate_group_count = 1;
168        bytecode.fusion_metadata.instruction_windows = vec![FusionInstructionWindow {
169            span: InstrSpan { start: 2, end: 2 },
170            kind: FusionInstructionKind::Elementwise,
171        }];
172
173        let mut initial_vars = Vec::new();
174        let state = InterpreterState::new(bytecode, &mut initial_vars, Some("<main>"), Vec::new());
175        let graph = state
176            .fusion_accel_graph
177            .as_ref()
178            .expect("expected runtime accel graph to be retained");
179        assert!(
180            graph
181                .nodes
182                .iter()
183                .any(|node| matches!(node.label, AccelNodeLabel::Primitive(PrimitiveOp::Add))),
184            "runtime retained graph should reflect active bytecode instructions"
185        );
186        assert!(
187            !graph
188                .nodes
189                .iter()
190                .any(|node| matches!(node.label, AccelNodeLabel::Primitive(PrimitiveOp::Mul))),
191            "stale compile graph metadata should not be retained in runtime state"
192        );
193    }
194}