use core::cmp;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use indexmap::IndexMap;
use quote::format_ident;
use syn::{Ident, Type};
use crate::{ast::App, Set};
pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
let mut late_resources = LateResources::new();
if !app.late_resources.is_empty() {
let mut resources = app.late_resources.keys().cloned().collect::<BTreeSet<_>>();
let mut rest = false;
if let Some(init) = &app.inits.first() {
if init.args.late.is_empty() {
rest = true;
} else {
let mut late_resources = Vec::new();
for name in &init.args.late {
late_resources.push(name.clone());
resources.remove(name);
}
}
}
if rest {
late_resources.push(resources);
}
}
let task_local: Vec<&Ident> = app
.resources
.iter()
.filter(|(_, r)| r.properties.task_local)
.map(|(i, _)| i)
.chain(
app.late_resources
.iter()
.filter(|(_, r)| r.properties.task_local)
.map(|(i, _)| i),
)
.collect();
let lock_free: Vec<&Ident> = app
.resources
.iter()
.filter(|(_, r)| r.properties.lock_free)
.map(|(i, _)| i)
.chain(
app.late_resources
.iter()
.filter(|(_, r)| r.properties.lock_free)
.map(|(i, _)| i),
)
.collect();
type TaskName = String;
type Priority = u8;
let task_list: Vec<(TaskName, Vec<&Ident>, Priority)> = app
.idles
.iter()
.map(|ht| {
(
"idle".to_string(),
ht.args.resources.iter().map(|(v, _)| v).collect::<Vec<_>>(),
0,
)
})
.chain(app.software_tasks.iter().map(|(name, ht)| {
(
name.to_string(),
ht.args.resources.iter().map(|(v, _)| v).collect::<Vec<_>>(),
ht.args.priority,
)
}))
.chain(app.hardware_tasks.iter().map(|(name, ht)| {
(
name.to_string(),
ht.args.resources.iter().map(|(v, _)| v).collect::<Vec<_>>(),
ht.args.priority,
)
}))
.collect();
let tasks = task_list.iter().map(|x| format_ident!("{}", x.0)).collect();
let mut error = vec![];
for task_local_id in task_local.iter() {
let mut used = vec![];
for (task, tr, priority) in task_list.iter() {
for r in tr {
if task_local_id == r {
used.push((task, r, priority));
}
}
}
if used.len() > 1 {
error.push(syn::Error::new(
task_local_id.span(),
format!(
"task local resource {:?} is used by multiple tasks",
task_local_id.to_string()
),
));
used.iter().for_each(|(task, resource, priority)| {
error.push(syn::Error::new(
resource.span(),
format!(
"task local resource {:?} is used by task {:?} with priority {:?}",
resource.to_string(),
task,
priority
),
))
});
}
}
let mut lf_res_with_error = vec![];
let mut lf_hash = HashMap::new();
for lf_res in lock_free.iter() {
for (task, tr, priority) in task_list.iter() {
for r in tr {
if lf_res == r {
if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) {
if priority != lf_res.2 {
lf_res_with_error.push(lf_res.1);
lf_res_with_error.push(r);
}
if lf_res_with_error.contains(&r) {
lf_res_with_error.push(lf_res.1);
lf_res_with_error.push(r);
}
}
}
}
}
}
for r in lock_free {
if lf_res_with_error.contains(&&r) {
error.push(syn::Error::new(
r.span(),
format!(
"Lock free resource {:?} is used by tasks at different priorities",
r.to_string(),
),
));
}
}
for resource in lf_res_with_error.clone() {
error.push(syn::Error::new(
resource.span(),
format!(
"Resource {:?} is declared lock free but used by tasks at different priorities",
resource.to_string(),
),
));
}
if !error.is_empty() {
let mut err = error.iter().next().unwrap().clone();
error.iter().for_each(|e| err.combine(e.clone()));
return Err(err);
}
let mut locations = IndexMap::new();
let mut ownerships = Ownerships::new();
let mut sync_types = SyncTypes::new();
for (prio, name, access) in app.resource_accesses() {
let res = app.resource(name).expect("UNREACHABLE").0;
locations.insert(name.clone(), Location::Owned);
if let Some(priority) = prio {
if let Some(ownership) = ownerships.get_mut(name) {
match *ownership {
Ownership::Owned { priority: ceiling }
| Ownership::CoOwned { priority: ceiling }
| Ownership::Contended { ceiling }
if priority != ceiling =>
{
*ownership = Ownership::Contended {
ceiling: cmp::max(ceiling, priority),
};
if access.is_shared() {
sync_types.insert(res.ty.clone());
}
}
Ownership::Owned { priority: ceil } if ceil == priority => {
*ownership = Ownership::CoOwned { priority };
}
_ => {}
}
} else {
ownerships.insert(name.clone(), Ownership::Owned { priority });
}
}
}
let mut send_types = SendTypes::new();
let owned_by_idle = Ownership::Owned { priority: 0 };
for (name, res) in app.late_resources.iter() {
if ownerships
.get(name)
.map(|ownership| *ownership != owned_by_idle)
.unwrap_or(false)
{
send_types.insert(res.ty.clone());
}
}
for name in app.inits.iter().flat_map(|init| init.args.resources.keys()) {
if let Some(ownership) = ownerships.get(name) {
if *ownership != owned_by_idle {
send_types.insert(app.resources[name].ty.clone());
}
}
}
let mut channels = Channels::new();
for (name, spawnee) in &app.software_tasks {
let spawnee_prio = spawnee.args.priority;
let channel = channels.entry(spawnee_prio).or_default();
channel.tasks.insert(name.clone());
spawnee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}
debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
for channel in channels.values_mut() {
channel.capacity = channel
.tasks
.iter()
.map(|name| app.software_tasks[name].args.capacity)
.sum();
}
Ok(Analysis {
channels,
late_resources,
locations,
tasks,
ownerships,
send_types,
sync_types,
})
}
pub type Ceiling = Option<u8>;
pub type Priority = u8;
pub type Resource = Ident;
pub type Task = Ident;
pub type Tasks = Vec<Ident>;
pub struct Analysis {
pub channels: Channels,
pub late_resources: LateResources,
pub locations: Locations,
pub tasks: Tasks,
pub ownerships: Ownerships,
pub send_types: SendTypes,
pub sync_types: SyncTypes,
}
pub type Channels = BTreeMap<Priority, Channel>;
pub type LateResources = Vec<BTreeSet<Resource>>;
pub type Locations = IndexMap<Resource, Location>;
pub type Ownerships = IndexMap<Resource, Ownership>;
pub type SendTypes = Set<Box<Type>>;
pub type SyncTypes = Set<Box<Type>>;
#[derive(Debug, Default)]
pub struct Channel {
pub capacity: u8,
pub tasks: BTreeSet<Task>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Ownership {
Owned {
priority: u8,
},
CoOwned {
priority: u8,
},
Contended {
ceiling: u8,
},
}
impl Ownership {
pub fn needs_lock(&self, priority: u8) -> bool {
match self {
Ownership::Owned { .. } | Ownership::CoOwned { .. } => false,
Ownership::Contended { ceiling } => {
debug_assert!(*ceiling >= priority);
priority < *ceiling
}
}
}
pub fn is_owned(&self) -> bool {
matches!(self, Ownership::Owned { .. })
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Location {
Owned,
}