rtlola2rust/
lib.rs

1//! Provides a formatter for the StreamIR to generate Rust code
2//! Requires the streamir-lib to parse a specification into StreamIR.
3
4#![forbid(unused_must_use)]
5#![warn(
6    missing_docs,
7    missing_debug_implementations,
8    missing_copy_implementations,
9    trivial_casts,
10    trivial_numeric_casts,
11    unsafe_code,
12    unstable_features,
13    unused_import_braces,
14    unused_qualifications
15)]
16
17mod api;
18mod constructs;
19mod error;
20mod expressions;
21mod guards;
22mod io;
23mod main_function;
24mod memory;
25mod names;
26mod schedule;
27mod statements;
28mod types;
29mod windows;
30
31use std::{collections::HashMap, path::PathBuf, sync::Mutex, time::Duration};
32
33use api::{AcceptEventFunction, MonitorConstructor};
34use constructs::{
35    EnumDefinition, FunctionDefinition, FunctionVisibility, RequirementKey, RustType,
36    StructDefinition,
37};
38use include_dir::{include_dir, Dir, DirEntry};
39use itertools::Itertools;
40pub use main_function::MainFunction;
41use memory::StreamMemoryStruct;
42use rtlola_streamir::{
43    formatter::{
44        files::{ConstructStore, ConstructWriteError, FilesFormatter},
45        StreamIrFormatter,
46    },
47    ir::{
48        expressions::Expr,
49        memory::{Memory, Parameter, StreamMemory},
50        windows::Window,
51        LocalFreq, LocalFreqRef, OutputReference, StreamIr, StreamReference, Type, WindowReference,
52    },
53};
54use schedule::{DeadlineEnum, QueueStruct, StreamReferenceEnum};
55use statements::CycleFunction;
56use tera::Tera;
57use windows::WindowMemory;
58
59#[derive(Debug, Clone)]
60/// Allows to specify the memory bounds when compiling to embedded Rust.
61pub struct NoStdInfo {
62    /// The maximal number of instances accomodated for each stream
63    pub max_instances: HashMap<OutputReference, usize>,
64    /// The maximal number of stream instances that can be spawned during a single evaluation cycle
65    /// (can never be larger than the number of parameterized outputs)
66    pub max_spawned: usize,
67    /// The maximal number of stream instances that can be closed during a single evaluation cycle
68    pub max_closed: usize,
69    /// The maximal number of periodic cycles that can be evaluated before evaluating the event-based cycle
70    pub max_verdict_periodic: usize,
71    /// The maximal number of streams that are due at a single dynamic deadline
72    pub max_dynamic_deadlines: usize,
73    /// The maximal number of stream instances of a single stream that can be part of a dynamic deadline
74    pub max_dynamic_instances: usize,
75    /// The maximal number of deadline that can be part of the queue
76    pub max_queue_size: usize,
77}
78
79#[derive(Debug)]
80/// The main struct holding the required information for generating Rust code
81pub struct RustFormatter {
82    sr2name: HashMap<StreamReference, String>,
83    sr2ty: HashMap<StreamReference, Type>,
84    sr2parameters: HashMap<StreamReference, Vec<Parameter>>,
85    sr2memory: HashMap<StreamReference, StreamMemory>,
86    lfreq2lfreq: HashMap<LocalFreqRef, LocalFreq>,
87    wref2window: HashMap<WindowReference, Window>,
88    static_deadlines: Vec<Duration>,
89    dynamic_deadlines: Vec<Duration>,
90    construct_store: ConstructStore<Self>,
91    output_folder: PathBuf,
92    // Whether to overwrite existing files
93    overwrite: bool,
94    // for each expression we generate a separate function
95    // this counter is incremented for each expression that is generated
96    expr_counter: Mutex<HashMap<(Expr, Option<StreamReference>), usize>>,
97    num_exprs: Mutex<usize>,
98    tera: Tera,
99    main: MainFunction,
100    verdict_streams: Vec<StreamReference>,
101    no_std_info: Option<NoStdInfo>,
102}
103
104impl StreamIrFormatter for RustFormatter {
105    type Return = Result<(), ConstructWriteError>;
106
107    fn id(&self) -> String {
108        "rust-formatter".into()
109    }
110
111    fn format(self, ir: StreamIr) -> Self::Return {
112        let StreamIr { stmt, .. } = ir;
113        let _ = self.call_self_function::<_, String>(CycleFunction(stmt), &[]);
114        let _ = self.call_self_function::<_, String>(AcceptEventFunction, &[]);
115        self.require_struct(MonitorStruct);
116        self.main.insert_requirement(&self);
117        if self.no_std_info.is_some() {
118            self.add_requirement_string_all(RequirementKey::NoStd, "#![no_std]".into());
119        }
120        self.generate_files()
121    }
122}
123
124impl RustFormatter {
125    /// Construct a new RustFormatter for the given StreamIR, writing the files to `output_folder`, optionally overwriting existing files.
126    ///
127    /// The `main` arguments specifies the kind of main function to generate, while `verdict_streams` contains a list of (unparameterized) stream references
128    /// that are included in the verdict.
129    pub fn new(
130        ir: &StreamIr,
131        output_folder: PathBuf,
132        overwrite: bool,
133        main: MainFunction,
134        verdict_streams: Vec<StreamReference>,
135        no_std_info: Option<NoStdInfo>,
136    ) -> Self {
137        let (sr2name, sr2ty, sr2parameters, sr2memory) = ir
138            .sr2memory
139            .iter()
140            .map(|(sr, m)| {
141                let Memory { buffer, ty, name } = m;
142                (
143                    (*sr, name.clone()),
144                    (*sr, ty.clone()),
145                    (*sr, m.parameters().unwrap_or(&[]).to_owned()),
146                    (*sr, buffer.clone()),
147                )
148            })
149            .multiunzip();
150        let mut tera = Tera::default();
151        static TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates");
152
153        for entry in TEMPLATE_DIR.find("**/*").unwrap() {
154            if let DirEntry::File(template) = entry {
155                tera.add_raw_template(
156                    template.path().to_str().unwrap(),
157                    template.contents_utf8().unwrap(),
158                )
159                .unwrap();
160            }
161        }
162
163        let (static_deadlines, dynamic_deadlines) = ir.all_periodic_pacings();
164        let dynamic_deadlines = dynamic_deadlines
165            .into_iter()
166            .map(|d| d.dur)
167            .unique()
168            .collect();
169
170        let lfreq2lfreq = ir.lref2lfreq.clone();
171        let wref2window = ir.wref2window.clone();
172
173        Self {
174            sr2name,
175            sr2ty,
176            sr2parameters,
177            sr2memory,
178            static_deadlines,
179            dynamic_deadlines,
180            lfreq2lfreq,
181            wref2window,
182            construct_store: ConstructStore::default(),
183            output_folder,
184            expr_counter: Mutex::new(HashMap::new()),
185            num_exprs: Mutex::new(0),
186            tera,
187            overwrite,
188            main,
189            verdict_streams,
190            no_std_info,
191        }
192    }
193
194    pub(crate) fn streams(&self) -> impl Iterator<Item = StreamReference> + '_ {
195        self.sr2name.keys().sorted().copied()
196    }
197
198    pub(crate) fn inputs(&self) -> impl Iterator<Item = StreamReference> + '_ {
199        self.sr2name
200            .keys()
201            .filter(|o| matches!(o, StreamReference::In(_)))
202            .sorted()
203            .copied()
204    }
205
206    pub(crate) fn outputs(&self) -> impl Iterator<Item = StreamReference> + '_ {
207        self.sr2name
208            .keys()
209            .filter(|o| matches!(o, StreamReference::Out(_)))
210            .sorted()
211            .copied()
212    }
213
214    pub(crate) fn stream_type(&self, sr: StreamReference) -> RustType {
215        self.lola_stream_type(sr).clone().into()
216    }
217
218    pub(crate) fn lola_stream_type(&self, sr: StreamReference) -> &Type {
219        &self.sr2ty[&sr]
220    }
221
222    pub(crate) fn stream_memory(&self, sr: StreamReference) -> &StreamMemory {
223        &self.sr2memory[&sr]
224    }
225
226    pub(crate) fn stream_parameter(&self, sr: StreamReference) -> &[Parameter] {
227        &self.sr2parameters[&sr]
228    }
229
230    pub(crate) fn parameter_ty(&self, sr: StreamReference) -> Option<RustType> {
231        if let Some(parameters) = self.stream_memory(sr).parameters() {
232            let rust_tys = parameters
233                .iter()
234                .map(|p| RustType::from(p.ty.clone()))
235                .collect::<Vec<_>>();
236            Some(match rust_tys.len() {
237                0 => unreachable!(),
238                1 => rust_tys.into_iter().next().unwrap(),
239                2.. => RustType::Tuple(rust_tys),
240            })
241        } else {
242            None
243        }
244    }
245
246    pub(crate) fn windows(&self) -> impl Iterator<Item = WindowReference> + '_ {
247        self.wref2window.keys().sorted().copied()
248    }
249
250    pub(crate) fn sliding_windows(&self) -> impl Iterator<Item = usize> + '_ {
251        self.windows().filter_map(|w| match w {
252            WindowReference::Sliding(i) => Some(i),
253            _ => None,
254        })
255    }
256
257    pub(crate) fn no_std_num_instances(&self, sr: OutputReference) -> Option<usize> {
258        self.no_std_info.as_ref().map(|m| m.max_instances[&sr])
259    }
260}
261
262struct MonitorStruct;
263
264impl StructDefinition for MonitorStruct {
265    fn key(&self) -> RequirementKey {
266        RequirementKey::MonitorStruct
267    }
268
269    fn struct_name(&self, f: &RustFormatter) -> String {
270        f.monitor_struct_name()
271    }
272
273    fn fields(&self, f: &RustFormatter) -> Vec<(String, RustType)> {
274        f.require_struct(StreamMemoryStruct);
275        f.require_enum(DeadlineEnum);
276        if f.no_std_info.is_some() {
277            f.import("heapless::Vec", self.file(f));
278        }
279        f.call_function::<_, String>(MonitorConstructor, &[]);
280        [
281            Some(StreamMemoryStruct.as_argument(f)),
282            (!(f.dynamic_deadlines.is_empty() && f.static_deadlines.is_empty()))
283                .then(|| QueueStruct.as_argument(f)),
284            (!f.wref2window.is_empty()).then(|| WindowMemory.as_argument(f)),
285            Some(f.time_argument()),
286            Some((
287                f.spawned_argument_name(),
288                RustType::Vec(
289                    Box::new(DeadlineEnum.as_ty(f)),
290                    f.no_std_info.as_ref().map(|i| i.max_spawned),
291                ),
292            )),
293            Some((
294                f.closed_argument_name(),
295                RustType::Vec(
296                    Box::new(StreamReferenceEnum.as_ty(f)),
297                    f.no_std_info.as_ref().map(|i| i.max_closed),
298                ),
299            )),
300        ]
301        .into_iter()
302        .flatten()
303        .collect()
304    }
305
306    fn visibility(&self) -> FunctionVisibility {
307        FunctionVisibility::Public
308    }
309
310    fn file(&self, _f: &RustFormatter) -> PathBuf {
311        _f.main_file()
312    }
313}