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 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}