1use std::cell::RefCell;
4use std::collections::{HashMap, HashSet};
5use std::panic::RefUnwindSafe;
6use std::rc::Rc;
7
8use dfir_lang::graph::{DfirGraph, FlatGraphBuilder, FlatGraphBuilderOutput};
9use libloading::Library;
10use slotmap::SparseSecondaryMap;
11
12use super::builder::SimBuilder;
13use super::compiled::{CompiledSim, CompiledSimInstance};
14use super::graph::{SimDeploy, SimExternal, SimNode, compile_sim, create_sim_graph_trybuild};
15use crate::compile::builder::{HandoffId, StmtId};
16use crate::compile::ir::HydroRoot;
17use crate::location::LocationKey;
18use crate::location::dynamic::LocationId;
19use crate::prelude::Cluster;
20use crate::sim::graph::SimExternalPortRegistry;
21use crate::staging_util::Invariant;
22
23pub struct SimFlow<'a> {
25 pub(crate) ir: Vec<HydroRoot>,
26
27 pub(crate) processes: SparseSecondaryMap<LocationKey, SimNode>,
29 pub(crate) clusters: SparseSecondaryMap<LocationKey, SimNode>,
31 pub(crate) externals: SparseSecondaryMap<LocationKey, SimExternal>,
33
34 pub(crate) cluster_max_sizes: SparseSecondaryMap<LocationKey, usize>,
36 pub(crate) externals_port_registry: Rc<RefCell<SimExternalPortRegistry>>,
38
39 pub(crate) test_safety_only: bool,
41
42 pub(crate) skip_consistency_assertions: bool,
46
47 pub(crate) unit_test_fuzz_iterations: usize,
49
50 pub(crate) _phantom: Invariant<'a>,
51}
52
53impl<'a> SimFlow<'a> {
54 pub fn with_cluster_size<C>(mut self, cluster: &Cluster<'a, C>, max_size: usize) -> Self {
56 self.cluster_max_sizes.insert(cluster.key, max_size);
57 self
58 }
59
60 pub fn test_safety_only(mut self) -> Self {
69 self.test_safety_only = true;
70 self
71 }
72
73 pub fn skip_consistency_assertions(mut self) -> Self {
78 self.skip_consistency_assertions = true;
79 self
80 }
81
82 pub fn unit_test_fuzz_iterations(mut self, iterations: usize) -> Self {
85 self.unit_test_fuzz_iterations = iterations;
86 self
87 }
88
89 pub fn with_instance<T>(self, thunk: impl FnOnce(CompiledSimInstance) -> T) -> T {
91 self.compiled().with_instance(thunk)
92 }
93
94 pub fn fuzz(self, thunk: impl AsyncFn() + RefUnwindSafe) {
105 self.compiled().fuzz(thunk)
106 }
107
108 pub fn exhaustive(self, thunk: impl AsyncFnMut() + RefUnwindSafe) -> usize {
119 self.compiled().exhaustive(thunk)
120 }
121
122 pub fn compiled(mut self) -> CompiledSim {
124 use std::collections::BTreeMap;
125
126 use dfir_lang::graph::{eliminate_extra_unions_tees, partition_graph};
127
128 let mut sim_emit = SimBuilder {
129 process_graphs: BTreeMap::new(),
130 cluster_graphs: BTreeMap::new(),
131 process_tick_dfirs: BTreeMap::new(),
132 cluster_tick_dfirs: BTreeMap::new(),
133 extra_stmts_global: vec![],
134 extra_stmts_cluster: BTreeMap::new(),
135 next_hoff_id: HandoffId::default(),
136 test_safety_only: self.test_safety_only,
137 skip_consistency_assertions: self.skip_consistency_assertions,
138 };
139
140 self.externals.insert(
142 LocationKey::FIRST,
143 SimExternal {
144 shared_inner: self.externals_port_registry.clone(),
145 },
146 );
147
148 let mut seen_tees_instantiate: HashMap<_, _> = HashMap::new();
149 let mut seen_cluster_members = HashSet::new();
150 self.ir.iter_mut().for_each(|leaf| {
151 leaf.compile_network::<SimDeploy>(
152 &mut SparseSecondaryMap::new(),
153 &mut seen_tees_instantiate,
154 &mut seen_cluster_members,
155 &self.processes,
156 &self.clusters,
157 &self.externals,
158 &mut (),
159 );
160 });
161
162 let mut seen_tees = HashMap::new();
163 let mut built_tees = HashMap::new();
164 let mut next_stmt_id = StmtId::default();
165 let mut fold_hooked_idents = HashSet::new();
166 for leaf in &mut self.ir {
167 leaf.emit(
168 &mut sim_emit,
169 &mut seen_tees,
170 &mut built_tees,
171 &mut next_stmt_id,
172 &mut fold_hooked_idents,
173 );
174 }
175
176 fn build_graphs(
177 graphs: BTreeMap<LocationId, FlatGraphBuilder>,
178 ) -> BTreeMap<LocationId, DfirGraph> {
179 graphs
180 .into_iter()
181 .map(|(l, g)| {
182 let FlatGraphBuilderOutput { mut flat_graph, .. } =
183 g.build().expect("Failed to build DFIR flat graph.");
184 eliminate_extra_unions_tees(&mut flat_graph);
185 (
186 l,
187 partition_graph(flat_graph).expect("Failed to partition (cycle detected)."),
188 )
189 })
190 .collect()
191 }
192
193 let process_graphs = build_graphs(sim_emit.process_graphs);
194 let cluster_graphs = build_graphs(sim_emit.cluster_graphs);
195 let process_tick_graphs = build_graphs(sim_emit.process_tick_dfirs);
196 let cluster_tick_graphs = build_graphs(sim_emit.cluster_tick_dfirs);
197
198 #[expect(
199 clippy::disallowed_methods,
200 reason = "nondeterministic iteration order, fine for checks"
201 )]
202 for c in self.clusters.keys() {
203 assert!(
204 self.cluster_max_sizes.contains_key(c),
205 "Cluster {:?} missing max size; call with_cluster_size() before compiled()",
206 c
207 );
208 }
209
210 let (bin, trybuild) = create_sim_graph_trybuild(
211 process_graphs,
212 cluster_graphs,
213 self.cluster_max_sizes,
214 process_tick_graphs,
215 cluster_tick_graphs,
216 sim_emit.extra_stmts_global,
217 sim_emit.extra_stmts_cluster,
218 );
219
220 let out = compile_sim(bin, trybuild).unwrap();
221 let lib = unsafe { Library::new(&out).unwrap() };
222
223 CompiledSim {
224 _path: out,
225 lib,
226 externals_port_registry: self.externals_port_registry.take(),
227 unit_test_fuzz_iterations: self.unit_test_fuzz_iterations,
228 }
229 }
230}