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