use std::{
collections::{HashMap, HashSet},
hash::Hash,
time::Duration,
};
use expressions::Expr;
pub use lowering::livetime_equivalences::LivetimeEquivalences;
use memory::{Memory, StreamMemory};
use rtlola_frontend::mir::{self};
use windows::Window;
mod display;
pub mod expressions;
mod lowering;
pub mod memory;
#[cfg(test)]
pub(crate) mod parse;
mod print;
mod schedule;
pub mod windows;
pub use lowering::LoweringError;
pub use print::DebugFormatter;
pub use schedule::{Deadline, StaticSchedule, Task};
#[derive(Debug, Clone)]
pub struct StreamIr {
pub stmt: Stmt,
pub sr2memory: HashMap<StreamReference, Memory>,
pub wref2window: HashMap<WindowReference, Window>,
pub lref2lfreq: HashMap<LocalFreqRef, LocalFreq>,
pub livetime_equivalences: LivetimeEquivalences,
pub static_schedule: Option<StaticSchedule>,
pub triggers: HashMap<OutputReference, usize>,
pub accesses: HashMap<StreamReference, Accesses>,
pub accessed_by: HashMap<StreamReference, Accesses>,
}
impl StreamIr {
pub fn stream_memory(&self, sr: StreamReference) -> &Memory {
&self.sr2memory[&sr]
}
pub fn name(&self, sr: StreamReference) -> &str {
&self.stream_memory(sr).name
}
pub fn stream_by_name(&self, name: &str) -> Option<StreamReference> {
self.sr2memory
.iter()
.find(|(_, m)| m.name == name)
.map(|(sr, _)| sr)
.copied()
}
pub fn streams(&self) -> impl Iterator<Item = StreamReference> + '_ {
self.sr2memory.keys().copied()
}
pub fn inputs(&self) -> impl Iterator<Item = InputReference> + '_ {
self.sr2memory
.keys()
.filter(|sr| matches!(sr, StreamReference::In(_)))
.map(|sr| sr.in_idx())
}
pub fn num_inputs(&self) -> usize {
self.inputs().count()
}
pub fn outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
self.sr2memory
.keys()
.filter(|sr| matches!(sr, StreamReference::Out(_)))
.map(|sr| sr.out_idx())
}
pub fn triggers(&self) -> impl Iterator<Item = OutputReference> + '_ {
self.outputs().filter(|o| self.triggers.contains_key(o))
}
pub fn num_outputs(&self) -> usize {
self.outputs().count()
}
pub fn static_outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
self.outputs().filter(|sr| {
matches!(
self.stream_memory(sr.sr()).buffer,
StreamMemory::Static(_) | StreamMemory::NoMemory
)
})
}
pub fn dynamic_outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
self.outputs().filter(|sr| {
matches!(
self.stream_memory(sr.sr()).buffer,
StreamMemory::Dynamic { .. }
)
})
}
pub fn parameterized_outputs(&self) -> impl Iterator<Item = OutputReference> + '_ {
self.outputs().filter(|sr| {
matches!(
self.stream_memory(sr.sr()).buffer,
StreamMemory::Instances { .. }
)
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stmt {
Skip,
Seq(Vec<Stmt>),
Parallel(Vec<Stmt>),
Shift(StreamReference),
Input(InputReference),
Spawn {
sr: OutputReference,
with: Option<Vec<Expr>>,
local_frequencies: Vec<LocalFreqRef>,
windows: Vec<WindowReference>,
},
Eval {
sr: OutputReference,
with: Expr,
idx: usize,
},
Close {
sr: OutputReference,
local_frequencies: Vec<LocalFreqRef>,
windows: Vec<WindowReference>,
},
If(IfStmt),
Iterate {
sr: Vec<OutputReference>,
stmt: Box<Stmt>,
},
Assign {
parameter_expr: Vec<Expr>,
sr: Vec<OutputReference>,
stmt: Box<Stmt>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfStmt {
pub(crate) guard: Guard,
pub(crate) cons: Box<Stmt>,
pub(crate) alt: Box<Stmt>,
}
impl IfStmt {
pub fn guard(&self) -> &Guard {
&self.guard
}
pub fn cons(&self) -> &Stmt {
&self.cons
}
pub fn alt(&self) -> Option<&Stmt> {
(!matches!(*self.alt, Stmt::Skip)).then_some(self.alt.as_ref())
}
pub fn destruct(self) -> (Guard, Stmt, Option<Stmt>) {
let IfStmt { guard, cons, alt } = self;
(guard, *cons, (!matches!(*alt, Stmt::Skip)).then_some(*alt))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Guard {
Stream(StreamReference),
Alive(StreamReference),
Dynamic(Expr),
GlobalFreq(Duration),
LocalFreq(LocalFreqRef),
And {
lhs: Box<Guard>,
rhs: Box<Guard>,
},
Or {
lhs: Box<Guard>,
rhs: Box<Guard>,
},
Constant(bool),
FastAnd(Vec<StreamReference>),
FastOr(Vec<StreamReference>),
}
impl Guard {
pub(crate) fn eq_liveness(
&self,
other: &Self,
livetime_equivalences: &LivetimeEquivalences,
) -> bool {
match (self, other) {
(Self::Stream(l0), Self::Stream(r0)) => l0 == r0,
(Self::Alive(l0), Self::Alive(r0)) => livetime_equivalences.is_equivalent(*l0, *r0),
(Self::Dynamic(l0), Self::Dynamic(r0)) => l0 == r0,
(Self::GlobalFreq(l0), Self::GlobalFreq(r0)) => l0 == r0,
(Self::LocalFreq(l0), Self::LocalFreq(r0)) => l0 == r0,
(
Self::And {
lhs: l_lhs,
rhs: l_rhs,
},
Self::And {
lhs: r_lhs,
rhs: r_rhs,
},
) => {
l_lhs.eq_liveness(r_lhs, livetime_equivalences)
&& l_rhs.eq_liveness(r_rhs, livetime_equivalences)
}
(
Self::Or {
lhs: l_lhs,
rhs: l_rhs,
},
Self::Or {
lhs: r_lhs,
rhs: r_rhs,
},
) => {
l_lhs.eq_liveness(r_lhs, livetime_equivalences)
&& l_rhs.eq_liveness(r_rhs, livetime_equivalences)
}
(Self::Constant(l0), Self::Constant(r0)) => l0 == r0,
_ => false,
}
}
}
impl Hash for Guard {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Guard::Stream(sr) => {
state.write_u8(1);
sr.hash(state);
}
Guard::Alive(sr) => {
state.write_u8(2);
sr.hash(state);
}
Guard::Dynamic(_) => {
state.write_u8(3);
}
Guard::GlobalFreq(duration) => {
state.write_u8(4);
duration.hash(state);
}
Guard::LocalFreq(f) => {
state.write_u8(5);
f.hash(state);
}
Guard::And { lhs, rhs } => {
state.write_u8(6);
lhs.hash(state);
rhs.hash(state);
}
Guard::Or { lhs, rhs } => {
state.write_u8(7);
lhs.hash(state);
rhs.hash(state);
}
Guard::Constant(c) => {
state.write_u8(8);
c.hash(state);
}
Guard::FastAnd(sr) => {
state.write_u8(9);
sr.hash(state);
}
Guard::FastOr(sr) => {
state.write_u8(10);
sr.hash(state);
}
}
}
}
pub type LocalFreqRef = usize;
#[derive(Debug, Clone, Copy)]
pub struct LocalFreq {
pub dur: Duration,
pub sr: OutputReference,
pub reference: LocalFreqRef,
}
impl PartialEq for LocalFreq {
fn eq(&self, other: &Self) -> bool {
self.dur == other.dur && self.sr == other.sr
}
}
impl Eq for LocalFreq {}
pub type InputReference = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum OutputReference {
Unparameterized(usize),
Parameterized(usize),
}
impl OutputReference {
pub fn unparameterized_idx(self) -> usize {
match self {
OutputReference::Parameterized(_) => unreachable!(),
OutputReference::Unparameterized(i) => i,
}
}
pub fn parameterized_idx(self) -> usize {
match self {
OutputReference::Unparameterized(_) => unreachable!(),
OutputReference::Parameterized(i) => i,
}
}
pub fn sr(self) -> StreamReference {
StreamReference::Out(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum StreamReference {
In(InputReference),
Out(OutputReference),
}
impl StreamReference {
pub fn in_idx(self) -> InputReference {
match self {
StreamReference::In(i) => i,
StreamReference::Out(_) => unreachable!("Called in_idx on an Outputstream"),
}
}
pub fn out_idx(self) -> OutputReference {
match self {
StreamReference::In(_) => unreachable!("Called out_idx on an Inputstream"),
StreamReference::Out(o) => o,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Origin {
Spawn,
EvalWhen(usize),
EvalWith(usize),
Close,
}
impl From<mir::Origin> for Origin {
fn from(value: mir::Origin) -> Self {
match value {
mir::Origin::Spawn => Origin::Spawn,
mir::Origin::Filter(clause) => Origin::EvalWhen(clause),
mir::Origin::Eval(clause) => Origin::EvalWith(clause),
mir::Origin::Close => Origin::Close,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum WindowReference {
Sliding(usize),
Discrete(usize),
Instance(usize),
}
impl WindowReference {
pub fn idx(self) -> usize {
match self {
WindowReference::Sliding(i)
| WindowReference::Discrete(i)
| WindowReference::Instance(i) => i,
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum Type {
Int(u16),
UInt(u16),
Bool,
String,
Float32,
Float64,
Fixed(u16),
UFixed(u16),
Option(Box<Type>),
Tuple(Vec<Type>),
Bytes,
}
impl Type {
pub fn inner_ty(&self) -> &Type {
if let Type::Option(inner) = self {
inner
} else {
self
}
}
}
impl StreamIr {
pub fn all_periodic_pacings(&self) -> (Vec<Duration>, Vec<&LocalFreq>) {
let global_freqs = self.stmt.all_global_freqs().into_iter().collect();
let local_freqs = self.lref2lfreq.values().collect();
(global_freqs, local_freqs)
}
}
impl Stmt {
fn all_global_freqs(&self) -> HashSet<Duration> {
match self {
Stmt::Skip
| Stmt::Shift(_)
| Stmt::Input(_)
| Stmt::Spawn { .. }
| Stmt::Eval { .. }
| Stmt::Close { .. } => HashSet::new(),
Stmt::Parallel(stmts) | Stmt::Seq(stmts) => {
stmts.iter().flat_map(|s| s.all_global_freqs()).collect()
}
Stmt::Iterate { stmt, .. } | Stmt::Assign { stmt, .. } => stmt.all_global_freqs(),
Stmt::If(IfStmt { guard, cons, alt }) => cons
.all_global_freqs()
.into_iter()
.chain(alt.all_global_freqs())
.chain(guard.all_global_freqs())
.collect(),
}
}
}
impl Guard {
fn all_global_freqs(&self) -> HashSet<Duration> {
match self {
Guard::GlobalFreq(duration) => vec![*duration].into_iter().collect(),
Guard::And { lhs, rhs } | Guard::Or { lhs, rhs } => lhs
.all_global_freqs()
.into_iter()
.chain(rhs.all_global_freqs())
.collect(),
Guard::Constant(_)
| Guard::Stream(_)
| Guard::LocalFreq(_)
| Guard::Alive(_)
| Guard::Dynamic(_)
| Guard::FastAnd(_)
| Guard::FastOr(_) => HashSet::new(),
}
}
}
pub type Accesses = Vec<(StreamReference, Vec<(Origin, StreamAccessKind)>)>;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum StreamAccessKind {
Sync,
DiscreteWindow(WindowReference),
SlidingWindow(WindowReference),
InstanceAggregation(WindowReference),
Hold,
Offset(Offset),
Get,
Fresh,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Offset {
Future(u32),
Past(u32),
}
impl PartialOrd for Offset {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Offset {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
use Offset::*;
match (self, other) {
(Past(_), Future(_)) => Ordering::Less,
(Future(_), Past(_)) => Ordering::Greater,
(Future(a), Future(b)) => a.cmp(b),
(Past(a), Past(b)) => b.cmp(a),
}
}
}
impl Stmt {
pub fn contains_interate(&self, sr: OutputReference) -> bool {
match self {
Stmt::Skip
| Stmt::Shift(_)
| Stmt::Input(_)
| Stmt::Spawn { .. }
| Stmt::Close { .. }
| Stmt::Eval { .. } => false,
Stmt::Parallel(stmts) | Stmt::Seq(stmts) => {
stmts.iter().any(|s| s.contains_interate(sr))
}
Stmt::Iterate { sr: srs, stmt } => srs.contains(&sr) || stmt.contains_interate(sr),
Stmt::If(IfStmt {
guard: _,
cons,
alt,
}) => cons.contains_interate(sr) || alt.contains_interate(sr),
Stmt::Assign { stmt, .. } => stmt.contains_interate(sr),
}
}
}