rtlola_streamir/
ir.rs

1//! The internal representation of the StreamIR.
2
3use std::{
4    collections::{HashMap, HashSet},
5    hash::Hash,
6    time::Duration,
7};
8
9use expressions::Expr;
10pub use lowering::livetime_equivalences::LivetimeEquivalences;
11use memory::{Memory, StreamMemory};
12use rtlola_frontend::mir::{self};
13use windows::Window;
14mod display;
15pub mod expressions;
16mod lowering;
17pub mod memory;
18#[cfg(test)]
19pub(crate) mod parse;
20mod print;
21mod schedule;
22pub mod windows;
23pub use lowering::LoweringError;
24pub use print::DebugFormatter;
25pub use schedule::{Deadline, StaticSchedule, Task};
26
27#[derive(Debug, Clone)]
28/// The internal representation of the StreamIR.
29pub struct StreamIr {
30    /// The outermost statement of the StreamIR program.
31    pub stmt: Stmt,
32    /// The memory representation of each stream.
33    pub sr2memory: HashMap<StreamReference, Memory>,
34    /// Information on windows in the specification.
35    pub wref2window: HashMap<WindowReference, Window>,
36    /// A mapping from references to information of local frequencies.
37    pub lref2lfreq: HashMap<LocalFreqRef, LocalFreq>,
38    /// Equivalence classes for the livetime information of streams.
39    pub livetime_equivalences: LivetimeEquivalences,
40    /// The precomputed schedule for global periodic output streams
41    pub static_schedule: Option<StaticSchedule>,
42    /// The mapping of all output references that represent triggers to the corresponding trigger reference
43    pub triggers: HashMap<OutputReference, usize>,
44    /// The collection of streams this stream accesses non-transitively.  Includes this stream's spawn, evaluation condition, and close expressions.
45    pub accesses: HashMap<StreamReference, Accesses>,
46    /// The collection of streams that access the current stream non-transitively
47    pub accessed_by: HashMap<StreamReference, Accesses>,
48}
49
50impl StreamIr {
51    /// Returns the memory representation for the given stream.
52    pub fn stream_memory(&self, sr: StreamReference) -> &Memory {
53        &self.sr2memory[&sr]
54    }
55
56    /// Returns the name of the given stream.
57    pub fn name(&self, sr: StreamReference) -> &str {
58        &self.stream_memory(sr).name
59    }
60
61    /// Returns the stream reference of the stream with the given name
62    pub fn stream_by_name(&self, name: &str) -> Option<StreamReference> {
63        self.sr2memory
64            .iter()
65            .find(|(_, m)| m.name == name)
66            .map(|(sr, _)| sr)
67            .copied()
68    }
69
70    /// Returns an iterator over all streams in the specification UNSORTED
71    pub fn streams(&self) -> impl Iterator<Item = StreamReference> + '_ {
72        self.sr2memory.keys().copied()
73    }
74
75    /// Returns an iterator over all inputs in the specification UNSORTED
76    pub fn inputs(&self) -> impl Iterator<Item = InputReference> + '_ {
77        self.sr2memory
78            .keys()
79            .filter(|sr| matches!(sr, StreamReference::In(_)))
80            .map(|sr| sr.in_idx())
81    }
82
83    /// Returns the number of input streams in the specification
84    pub fn num_inputs(&self) -> usize {
85        self.inputs().count()
86    }
87
88    /// Returns an iterator over all output streams in the specification UNSORTED
89    pub fn outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
90        self.sr2memory
91            .keys()
92            .filter(|sr| matches!(sr, StreamReference::Out(_)))
93            .map(|sr| sr.out_idx())
94    }
95
96    /// Returns an iterator over all the output streams that represent triggers UNSORTED
97    pub fn triggers(&self) -> impl Iterator<Item = OutputReference> + '_ {
98        self.outputs().filter(|o| self.triggers.contains_key(o))
99    }
100
101    /// Returns the total number of output streams in the specification
102    pub fn num_outputs(&self) -> usize {
103        self.outputs().count()
104    }
105
106    /// Returns an iterator over all static output streams in the specification
107    pub fn static_outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
108        self.outputs().filter(|sr| {
109            matches!(
110                self.stream_memory(sr.sr()).buffer,
111                StreamMemory::Static(_) | StreamMemory::NoMemory
112            )
113        })
114    }
115
116    /// Returns an iterator over of dynamic output streams in the specification
117    pub fn dynamic_outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
118        self.outputs().filter(|sr| {
119            matches!(
120                self.stream_memory(sr.sr()).buffer,
121                StreamMemory::Dynamic { .. }
122            )
123        })
124    }
125
126    /// Returns the number of parameterized output streams in the specification
127    pub fn parameterized_outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
128        self.outputs().filter(|sr| {
129            matches!(
130                self.stream_memory(sr.sr()).buffer,
131                StreamMemory::Instances { .. }
132            )
133        })
134    }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138/// A statement of the StreamIR.
139pub enum Stmt {
140    /// A no-op operation
141    Skip,
142    /// A sequence of statements
143    Seq(Vec<Stmt>),
144    /// A set of statements that can be executed in parallel
145    Parallel(Vec<Stmt>),
146    /// A statement shifting a stream
147    Shift(StreamReference),
148    /// A statement updating the memory of an input stream with a new value
149    Input(InputReference),
150    /// A statement spawning a new instance of an output stream
151    Spawn {
152        /// The reference of the output stream
153        sr: OutputReference,
154        /// The condition to calculate the new value (if None, the stream is not parameterized)
155        with: Option<Vec<Expr>>,
156        /// A list of (local) clocks that need to start with the spawn of the instance
157        local_frequencies: Vec<LocalFreqRef>,
158        /// A list of windows that need to start with the spawn of the instance
159        windows: Vec<WindowReference>,
160    },
161    /// A statement computing the new value of an output stream and writing the value to memory
162    Eval {
163        /// The reference of the output stream
164        sr: OutputReference,
165        /// The stream expression of the eval-with clause
166        with: Expr,
167        /// The index of the eval clause this statment originated from
168        idx: usize,
169    },
170    /// A statement closing an instance of an output stream
171    /// To close a specific instance, the statement has to be inside an iterate/assign statement
172    Close {
173        /// The reference of the output stream
174        sr: OutputReference,
175        /// A list of (local) clocks that need to stop with the close of the instance
176        local_frequencies: Vec<LocalFreqRef>,
177        /// A list of windows that need to be closed
178        windows: Vec<WindowReference>,
179    },
180    /// A conditional statement
181    If(IfStmt),
182    /// A statement iterating over all currently alive instances of an output stream
183    Iterate {
184        /// The references of the output streams
185        sr: Vec<OutputReference>,
186        /// A statement that is executed for every instance
187        stmt: Box<Stmt>,
188    },
189    /// A statement assigning the value for the current instance by a stream expression
190    Assign {
191        /// A list of expressions calculating the parameters
192        parameter_expr: Vec<Expr>,
193        /// The streams this assign statement originated from
194        sr: Vec<OutputReference>,
195        /// The inner statement that is executed once with the calculated instance
196        stmt: Box<Stmt>,
197    },
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201/// An conditional statement in the StreamIR.
202pub struct IfStmt {
203    /// The condition
204    pub(crate) guard: Guard,
205    /// The consequence statement
206    pub(crate) cons: Box<Stmt>,
207    /// The alternative statement (is often Stmt::Skip)
208    pub(crate) alt: Box<Stmt>,
209}
210
211impl IfStmt {
212    /// Return the guard of the conditional
213    pub fn guard(&self) -> &Guard {
214        &self.guard
215    }
216
217    /// Return the consequence of the conditional
218    pub fn cons(&self) -> &Stmt {
219        &self.cons
220    }
221
222    /// Return the alternative of the conditional (or None if the alternative is Stmt::Skip)
223    pub fn alt(&self) -> Option<&Stmt> {
224        (!matches!(*self.alt, Stmt::Skip)).then_some(self.alt.as_ref())
225    }
226
227    /// Destructure the conditional into the guard, the consequence and (if not Skip) the alternative.
228    pub fn destruct(self) -> (Guard, Stmt, Option<Stmt>) {
229        let IfStmt { guard, cons, alt } = self;
230        (guard, *cons, (!matches!(*alt, Stmt::Skip)).then_some(*alt))
231    }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
235/// The condition of an conditional statement
236pub enum Guard {
237    /// Is true when the given stream received a new value in the current evaluation cycle
238    Stream(StreamReference),
239    /// Is true when the given stream is currently alive
240    Alive(StreamReference),
241    /// Is true when the given stream expression evaluates to true under the current monitoring state
242    Dynamic(Expr),
243    /// Is true if the given global frequency is currently due
244    GlobalFreq(Duration),
245    /// Is true if the given local frequency is currently due
246    LocalFreq(LocalFreqRef),
247    /// The conjunction of guards
248    And {
249        /// the left hand side of the conjunction
250        lhs: Box<Guard>,
251        /// the right hand side of the conjunction
252        rhs: Box<Guard>,
253    },
254    /// The disjunction of guards
255    Or {
256        /// the left hand side of the disjunction
257        lhs: Box<Guard>,
258        /// the right hand side of the disjunction
259        rhs: Box<Guard>,
260    },
261    /// A guard constantly evaluating to true/false
262    Constant(bool),
263    /// Shortcuts for conjunction of Guard::Stream's
264    FastAnd(Vec<StreamReference>),
265    /// Shortcuts for disjunction of Guard::Stream's
266    FastOr(Vec<StreamReference>),
267}
268
269impl Guard {
270    pub(crate) fn eq_liveness(
271        &self,
272        other: &Self,
273        livetime_equivalences: &LivetimeEquivalences,
274    ) -> bool {
275        match (self, other) {
276            (Self::Stream(l0), Self::Stream(r0)) => l0 == r0,
277            (Self::Alive(l0), Self::Alive(r0)) => livetime_equivalences.is_equivalent(*l0, *r0),
278            (Self::Dynamic(l0), Self::Dynamic(r0)) => l0 == r0,
279            (Self::GlobalFreq(l0), Self::GlobalFreq(r0)) => l0 == r0,
280            (Self::LocalFreq(l0), Self::LocalFreq(r0)) => l0 == r0,
281            (
282                Self::And {
283                    lhs: l_lhs,
284                    rhs: l_rhs,
285                },
286                Self::And {
287                    lhs: r_lhs,
288                    rhs: r_rhs,
289                },
290            ) => {
291                l_lhs.eq_liveness(r_lhs, livetime_equivalences)
292                    && l_rhs.eq_liveness(r_rhs, livetime_equivalences)
293            }
294            (
295                Self::Or {
296                    lhs: l_lhs,
297                    rhs: l_rhs,
298                },
299                Self::Or {
300                    lhs: r_lhs,
301                    rhs: r_rhs,
302                },
303            ) => {
304                l_lhs.eq_liveness(r_lhs, livetime_equivalences)
305                    && l_rhs.eq_liveness(r_rhs, livetime_equivalences)
306            }
307            (Self::Constant(l0), Self::Constant(r0)) => l0 == r0,
308            _ => false,
309        }
310    }
311}
312
313impl Hash for Guard {
314    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
315        match self {
316            Guard::Stream(sr) => {
317                state.write_u8(1);
318                sr.hash(state);
319            }
320            Guard::Alive(sr) => {
321                state.write_u8(2);
322                sr.hash(state);
323            }
324            Guard::Dynamic(_) => {
325                state.write_u8(3);
326            }
327            Guard::GlobalFreq(duration) => {
328                state.write_u8(4);
329                duration.hash(state);
330            }
331            Guard::LocalFreq(f) => {
332                state.write_u8(5);
333                f.hash(state);
334            }
335            Guard::And { lhs, rhs } => {
336                state.write_u8(6);
337                lhs.hash(state);
338                rhs.hash(state);
339            }
340            Guard::Or { lhs, rhs } => {
341                state.write_u8(7);
342                lhs.hash(state);
343                rhs.hash(state);
344            }
345            Guard::Constant(c) => {
346                state.write_u8(8);
347                c.hash(state);
348            }
349            Guard::FastAnd(sr) => {
350                state.write_u8(9);
351                sr.hash(state);
352            }
353            Guard::FastOr(sr) => {
354                state.write_u8(10);
355                sr.hash(state);
356            }
357        }
358    }
359}
360
361/// A reference to a local frequency
362pub type LocalFreqRef = usize;
363
364#[derive(Debug, Clone, Copy)]
365/// The information of a local frequency
366pub struct LocalFreq {
367    /// The frequency
368    pub dur: Duration,
369    /// The stream this frequency belongs to
370    pub sr: OutputReference,
371    /// The reference of the frequency
372    pub reference: LocalFreqRef,
373}
374
375impl PartialEq for LocalFreq {
376    fn eq(&self, other: &Self) -> bool {
377        self.dur == other.dur && self.sr == other.sr
378    }
379}
380
381impl Eq for LocalFreq {}
382
383/// Allows for referencing an input stream within the specification.
384pub type InputReference = usize;
385
386/// Allows for referencing an output stream within the specification.
387#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
388pub enum OutputReference {
389    /// Un unparameterized stream
390    Unparameterized(usize),
391    /// A parameterized stream
392    Parameterized(usize),
393}
394
395impl OutputReference {
396    /// The index of an unparameterized stream (panics if is is parameterized)
397    pub fn unparameterized_idx(self) -> usize {
398        match self {
399            OutputReference::Parameterized(_) => unreachable!(),
400            OutputReference::Unparameterized(i) => i,
401        }
402    }
403
404    /// The index of an parameterized stream (panics if is is unparameterized)
405    pub fn parameterized_idx(self) -> usize {
406        match self {
407            OutputReference::Unparameterized(_) => unreachable!(),
408            OutputReference::Parameterized(i) => i,
409        }
410    }
411
412    /// Returns the stream reference to that output reference
413    pub fn sr(self) -> StreamReference {
414        StreamReference::Out(self)
415    }
416}
417
418#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
419/// A reference of an input or output stream
420pub enum StreamReference {
421    /// The reference is for an input stream
422    In(InputReference),
423    /// The reference is for an output stream
424    Out(OutputReference),
425}
426
427impl StreamReference {
428    /// Returns the [InputReference] from a Streamreference
429    pub fn in_idx(self) -> InputReference {
430        match self {
431            StreamReference::In(i) => i,
432            StreamReference::Out(_) => unreachable!("Called in_idx on an Outputstream"),
433        }
434    }
435
436    /// Returns the [OutputReference] from a Streamreference
437    pub fn out_idx(self) -> OutputReference {
438        match self {
439            StreamReference::In(_) => unreachable!("Called out_idx on an Inputstream"),
440            StreamReference::Out(o) => o,
441        }
442    }
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
446/// The origin of a stream expression (the clauses of an output stream)
447pub enum Origin {
448    /// The expression was in the spawn clause
449    Spawn,
450    /// The expression was in the eval when clause
451    EvalWhen(usize),
452    /// The expression was in the eval with clause
453    EvalWith(usize),
454    /// The expression was in the close condition
455    Close,
456}
457
458impl From<mir::Origin> for Origin {
459    fn from(value: mir::Origin) -> Self {
460        match value {
461            mir::Origin::Spawn => Origin::Spawn,
462            mir::Origin::Filter(clause) => Origin::EvalWhen(clause),
463            mir::Origin::Eval(clause) => Origin::EvalWith(clause),
464            mir::Origin::Close => Origin::Close,
465        }
466    }
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
470/// A reference of a window in the specification
471pub enum WindowReference {
472    /// The window is a sliding window
473    Sliding(usize),
474    /// The window is a discrete window
475    Discrete(usize),
476    /// The window is a instance aggregation
477    Instance(usize),
478}
479
480impl WindowReference {
481    /// The index of the window reference
482    pub fn idx(self) -> usize {
483        match self {
484            WindowReference::Sliding(i)
485            | WindowReference::Discrete(i)
486            | WindowReference::Instance(i) => i,
487        }
488    }
489}
490
491/// Represents the type of a stream
492#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
493pub enum Type {
494    /// An signed integer with a specific number of bits
495    Int(u16),
496    /// An unsigned integer with a specific number of bits
497    UInt(u16),
498    /// A boolean
499    Bool,
500    /// A string
501    String,
502    /// A 32 bit floating point number
503    Float32,
504    /// A 64 bit floating point number
505    Float64,
506    /// A signed fixed point number
507    Fixed(u16),
508    /// An unsigned fixed point number
509    UFixed(u16),
510    /// An optional type
511    Option(Box<Type>),
512    /// A tuple type
513    Tuple(Vec<Type>),
514    /// A bytestring
515    Bytes,
516}
517
518impl Type {
519    /// Returns the type inside an Option, or the type itself, if not an option
520    pub fn inner_ty(&self) -> &Type {
521        if let Type::Option(inner) = self {
522            inner
523        } else {
524            self
525        }
526    }
527}
528
529impl StreamIr {
530    /// Returns all periodic pacings that are used in guards in the StreamIR
531    ///
532    /// Returns a tuple, the first element holds the durations of global frequencies,
533    /// the second element the reference of local frequencies
534    pub fn all_periodic_pacings(&self) -> (Vec<Duration>, Vec<&LocalFreq>) {
535        let global_freqs = self.stmt.all_global_freqs().into_iter().collect();
536        let local_freqs = self.lref2lfreq.values().collect();
537        (global_freqs, local_freqs)
538    }
539}
540
541impl Stmt {
542    fn all_global_freqs(&self) -> HashSet<Duration> {
543        match self {
544            Stmt::Skip
545            | Stmt::Shift(_)
546            | Stmt::Input(_)
547            | Stmt::Spawn { .. }
548            | Stmt::Eval { .. }
549            | Stmt::Close { .. } => HashSet::new(),
550            Stmt::Parallel(stmts) | Stmt::Seq(stmts) => {
551                stmts.iter().flat_map(|s| s.all_global_freqs()).collect()
552            }
553            Stmt::Iterate { stmt, .. } | Stmt::Assign { stmt, .. } => stmt.all_global_freqs(),
554            Stmt::If(IfStmt { guard, cons, alt }) => cons
555                .all_global_freqs()
556                .into_iter()
557                .chain(alt.all_global_freqs())
558                .chain(guard.all_global_freqs())
559                .collect(),
560        }
561    }
562}
563
564impl Guard {
565    fn all_global_freqs(&self) -> HashSet<Duration> {
566        match self {
567            Guard::GlobalFreq(duration) => vec![*duration].into_iter().collect(),
568            Guard::And { lhs, rhs } | Guard::Or { lhs, rhs } => lhs
569                .all_global_freqs()
570                .into_iter()
571                .chain(rhs.all_global_freqs())
572                .collect(),
573            Guard::Constant(_)
574            | Guard::Stream(_)
575            | Guard::LocalFreq(_)
576            | Guard::Alive(_)
577            | Guard::Dynamic(_)
578            | Guard::FastAnd(_)
579            | Guard::FastOr(_) => HashSet::new(),
580        }
581    }
582}
583
584/// Represent the accesses to other streams
585pub type Accesses = Vec<(StreamReference, Vec<(Origin, StreamAccessKind)>)>;
586
587/// Representation of the different stream accesses
588#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
589pub enum StreamAccessKind {
590    /// Represents the synchronous access
591    Sync,
592    /// Represents the access to a (discrete window)[DiscreteWindow]
593    ///
594    /// The argument contains the reference to the (discrete window)[DiscreteWindow] whose value is used in the [Expression].
595    DiscreteWindow(WindowReference),
596    /// Represents the access to a (sliding window)[SlidingWindow]
597    ///
598    /// The argument contains the reference to the (sliding window)[SlidingWindow] whose value is used in the [Expression].
599    SlidingWindow(WindowReference),
600    /// Represents the access to a (instance aggregation)[InstanceAggregation]
601    ///
602    /// The argument contains the reference to the (instance aggregation)[InstanceAggregation] whose value is used in the [Expression].
603    InstanceAggregation(WindowReference),
604    /// Representation of sample and hold accesses
605    Hold,
606    /// Representation of offset accesses
607    ///
608    /// The argument contains the [Offset] of the stream access.
609    Offset(Offset),
610    /// Represents the optional `get` access.
611    Get,
612    /// Represents the update check of a stream, if the target received a new value at this timestamp.
613    Fresh,
614}
615
616/// Offset used in the lookup expression
617#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
618pub enum Offset {
619    /// A strictly positive discrete offset, e.g., `4`, or `42`
620    Future(u32),
621    /// A non-negative discrete offset, e.g., `0`, `-4`, or `-42`
622    Past(u32),
623}
624
625impl PartialOrd for Offset {
626    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
627        Some(self.cmp(other))
628    }
629}
630
631impl Ord for Offset {
632    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
633        use std::cmp::Ordering;
634
635        use Offset::*;
636        match (self, other) {
637            (Past(_), Future(_)) => Ordering::Less,
638            (Future(_), Past(_)) => Ordering::Greater,
639            (Future(a), Future(b)) => a.cmp(b),
640            (Past(a), Past(b)) => b.cmp(a),
641        }
642    }
643}
644
645impl Stmt {
646    /// Returns whether the statment contains iteration over the given stream reference
647    pub fn contains_interate(&self, sr: OutputReference) -> bool {
648        match self {
649            Stmt::Skip
650            | Stmt::Shift(_)
651            | Stmt::Input(_)
652            | Stmt::Spawn { .. }
653            | Stmt::Close { .. }
654            | Stmt::Eval { .. } => false,
655            Stmt::Parallel(stmts) | Stmt::Seq(stmts) => {
656                stmts.iter().any(|s| s.contains_interate(sr))
657            }
658            Stmt::Iterate { sr: srs, stmt } => srs.contains(&sr) || stmt.contains_interate(sr),
659            Stmt::If(IfStmt {
660                guard: _,
661                cons,
662                alt,
663            }) => cons.contains_interate(sr) || alt.contains_interate(sr),
664            Stmt::Assign { stmt, .. } => stmt.contains_interate(sr),
665        }
666    }
667}