hydro_lang/sim/
flow.rs

1//! Entrypoint for compiling and running Hydro simulations.
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::panic::RefUnwindSafe;
6use std::rc::Rc;
7
8use dfir_lang::graph::FlatGraphBuilder;
9use libloading::Library;
10
11use super::builder::SimBuilder;
12use super::compiled::{CompiledSim, CompiledSimInstance};
13use super::graph::{SimDeploy, SimExternal, SimNode, compile_sim, create_sim_graph_trybuild};
14use crate::compile::ir::HydroRoot;
15use crate::staging_util::Invariant;
16
17/// A not-yet-compiled simulator for a Hydro program.
18pub struct SimFlow<'a> {
19    pub(crate) ir: Vec<HydroRoot>,
20
21    pub(crate) external_ports: Rc<RefCell<(Vec<usize>, usize)>>,
22
23    pub(crate) processes: HashMap<usize, SimNode>,
24    pub(crate) clusters: HashMap<usize, SimNode>,
25    pub(crate) externals: HashMap<usize, SimExternal>,
26
27    /// Lists all the processes that were created in the flow, same ID as `processes`
28    /// but with the type name of the tag.
29    pub(crate) _process_id_name: Vec<(usize, String)>,
30    pub(crate) _external_id_name: Vec<(usize, String)>,
31    pub(crate) _cluster_id_name: Vec<(usize, String)>,
32
33    pub(crate) _phantom: Invariant<'a>,
34}
35
36impl<'a> SimFlow<'a> {
37    /// Executes the given closure with a single instance of the compiled simulation.
38    pub fn with_instance<T>(self, thunk: impl FnOnce(CompiledSimInstance) -> T) -> T {
39        self.compiled().with_instance(thunk)
40    }
41
42    /// Uses a fuzzing strategy to explore possible executions of the simulation. The provided
43    /// closure will be repeatedly executed with instances of the Hydro program where the
44    /// batching boundaries, order of messages, and retries are varied.
45    ///
46    /// During development, you should run the test that invokes this function with the `cargo sim`
47    /// command, which will use `libfuzzer` to intelligently explore the execution space. If a
48    /// failure is found, a minimized test case will be produced in a `sim-failures` directory.
49    /// When running the test with `cargo test` (such as in CI), if a reproducer is found it will
50    /// be executed, and if no reproducer is found a small number of random executions will be
51    /// performed.
52    pub fn fuzz(self, thunk: impl AsyncFn(CompiledSimInstance) + RefUnwindSafe) {
53        self.compiled().fuzz(thunk)
54    }
55
56    /// Exhaustively searches all possible executions of the simulation. The provided
57    /// closure will be repeatedly executed with instances of the Hydro program where the
58    /// batching boundaries, order of messages, and retries are varied.
59    ///
60    /// Exhaustive searching is feasible when the inputs to the Hydro program are finite and there
61    /// are no dataflow loops that generate infinite messages. Exhaustive searching provides a
62    /// stronger guarantee of correctness than fuzzing, but may take a long time to complete.
63    /// Because no fuzzer is involved, you can run exhaustive tests with `cargo test`.
64    ///
65    /// Returns the number of distinct executions explored.
66    pub fn exhaustive(self, thunk: impl AsyncFn(CompiledSimInstance) + RefUnwindSafe) -> usize {
67        self.compiled().exhaustive(thunk)
68    }
69
70    /// Compiles the simulation into a dynamically loadable library, and returns a handle to it.
71    pub fn compiled(mut self) -> CompiledSim {
72        use std::collections::BTreeMap;
73
74        use dfir_lang::graph::{eliminate_extra_unions_tees, partition_graph};
75
76        let mut sim_emit = SimBuilder {
77            async_level: FlatGraphBuilder::new(),
78            tick_dfirs: BTreeMap::new(),
79            extra_stmts: vec![],
80            next_hoff_id: 0,
81        };
82
83        let mut seen_tees_instantiate: HashMap<_, _> = HashMap::new();
84        self.ir.iter_mut().for_each(|leaf| {
85            leaf.compile_network::<SimDeploy>(
86                &(),
87                &mut BTreeMap::new(),
88                &mut seen_tees_instantiate,
89                &self.processes,
90                &self.clusters,
91                &self.externals,
92            );
93        });
94
95        let mut built_tees = HashMap::new();
96        let mut next_stmt_id = 0;
97        for leaf in &mut self.ir {
98            leaf.emit(&mut sim_emit, &mut built_tees, &mut next_stmt_id);
99        }
100
101        let (mut async_level_flat_graph, _, _) = sim_emit.async_level.build();
102        eliminate_extra_unions_tees(&mut async_level_flat_graph);
103        let async_level_graph =
104            partition_graph(async_level_flat_graph).expect("Failed to partition (cycle detected).");
105
106        let tick_graphs = sim_emit
107            .tick_dfirs
108            .into_iter()
109            .map(|(l, g)| {
110                let (mut flat_graph, _, _) = g.build();
111                eliminate_extra_unions_tees(&mut flat_graph);
112                (
113                    l,
114                    partition_graph(flat_graph).expect("Failed to partition (cycle detected)."),
115                )
116            })
117            .collect::<BTreeMap<_, _>>();
118
119        let (bin, trybuild) =
120            create_sim_graph_trybuild(async_level_graph, tick_graphs, sim_emit.extra_stmts);
121
122        let out = compile_sim(bin, trybuild).unwrap();
123        let lib = unsafe { Library::new(&out).unwrap() };
124
125        let external_ports = self.external_ports.take().0;
126        CompiledSim {
127            _path: out,
128            lib,
129            external_ports,
130        }
131    }
132}