use core::cmp;
use std::collections::{btree_map::Entry, BTreeMap, BTreeSet, HashMap};
use indexmap::IndexMap;
use syn::{Ident, Type};
use crate::{ast::App, Core, Set};
pub(crate) fn app(app: &App) -> Analysis {
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 = None;
for (&core, init) in &app.inits {
if init.args.late.is_empty() {
debug_assert!(rest.is_none());
rest = Some(core);
} else {
let late_resources = late_resources.entry(core).or_default();
for name in &init.args.late {
late_resources.insert(name.clone());
resources.remove(name);
}
}
}
if let Some(rest) = rest {
late_resources.insert(rest, resources);
}
}
let mut initialization_barriers = InitializationBarriers::new();
let mut locations = app
.late_resources
.iter()
.chain(app.resources.iter().map(|(name, res)| (name, &res.late)))
.filter_map(|(name, lr)| {
if lr.shared {
Some((
name.clone(),
Location::Shared {
cores: BTreeSet::new(),
},
))
} else {
None
}
})
.collect::<Locations>();
let mut ownerships = Ownerships::new();
let mut shared_accesses = HashMap::new();
let mut sync_types = SyncTypes::new();
for (core, prio, name, access) in app.resource_accesses() {
let res = app.resource(name).expect("UNREACHABLE").0;
if access.is_shared() {
if let Some(&other_core) = shared_accesses.get(name) {
if other_core != core {
sync_types.entry(core).or_default().insert(res.ty.clone());
sync_types
.entry(other_core)
.or_default()
.insert(res.ty.clone());
}
} else {
shared_accesses.insert(name, core);
}
}
if let Some(loc) = locations.get_mut(name) {
match loc {
Location::Owned {
core: other_core, ..
} => {
if core != *other_core {
let mut cores = BTreeSet::new();
cores.insert(core);
cores.insert(*other_core);
*loc = Location::Shared { cores };
}
}
Location::Shared { cores } => {
cores.insert(core);
}
}
} else {
locations.insert(
name.clone(),
Location::Owned {
core,
cross_initialized: false,
},
);
}
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.entry(core).or_default().insert(res.ty.clone());
}
}
Ownership::Owned { priority: ceil } if ceil == priority => {
*ownership = Ownership::CoOwned { priority };
}
_ => {}
}
} else {
ownerships.insert(name.clone(), Ownership::Owned { priority });
}
}
let receiver = core;
for (&sender, resources) in &late_resources {
if sender == receiver {
continue;
}
if resources.contains(name) {
initialization_barriers
.entry(receiver)
.or_default()
.insert(sender);
}
}
}
for (name, loc) in &mut locations {
if let Location::Owned {
core,
cross_initialized,
} = loc
{
for (&initializer, resources) in &late_resources {
if resources.contains(name) && *core != initializer {
*cross_initialized = true;
}
}
}
}
let mut send_types = SendTypes::new();
let owned_by_idle = Ownership::Owned { priority: 0 };
for (name, res) in app.late_resources.iter() {
if locations
.get(name)
.map(|loc| loc.cross_initialized())
.unwrap_or(false)
|| ownerships
.get(name)
.map(|ownership| *ownership != owned_by_idle)
.unwrap_or(false)
{
if let Some(loc) = locations.get(name) {
match loc {
Location::Owned { core, .. } => {
send_types.entry(*core).or_default().insert(res.ty.clone());
}
Location::Shared { cores } => cores.iter().for_each(|&core| {
send_types.entry(core).or_default().insert(res.ty.clone());
}),
}
}
}
}
for name in app
.inits
.values()
.flat_map(|init| init.args.resources.keys())
{
if let Some(ownership) = ownerships.get(name) {
if *ownership != owned_by_idle {
if let Some(loc) = locations.get(name) {
match loc {
Location::Owned { core, .. } => {
send_types
.entry(*core)
.or_default()
.insert(app.resources[name].ty.clone());
}
Location::Shared { cores } => cores.iter().for_each(|&core| {
send_types
.entry(core)
.or_default()
.insert(app.resources[name].ty.clone());
}),
}
}
}
}
}
let mut timer_queues = TimerQueues::new();
for (scheduler_core, _scheduler_prio, name) in app.schedule_calls() {
let schedulee = &app.software_tasks[name];
let schedulee_core = schedulee.args.core;
let schedulee_prio = schedulee.args.priority;
let tq = timer_queues.entry(scheduler_core).or_default();
tq.tasks.insert(name.clone());
if scheduler_core == schedulee_core {
tq.priority = cmp::max(tq.priority, schedulee_prio);
tq.ceiling = tq.priority;
} else {
tq.priority = app
.hardware_tasks
.values()
.filter_map(|task| {
if task.args.core == scheduler_core {
Some(task.args.priority)
} else {
None
}
})
.chain(app.software_tasks.values().filter_map(|task| {
if task.args.core == scheduler_core {
Some(task.args.priority)
} else {
None
}
}))
.max()
.map(|prio| prio + 1)
.unwrap_or(tq.priority);
tq.ceiling = tq.priority;
}
}
let mut channels = Channels::new();
let mut free_queues = FreeQueues::new();
let mut spawn_barriers = SpawnBarriers::new();
for (spawner_core, spawner_prio, name) in app.spawn_calls() {
let spawnee = &app.software_tasks[name];
let spawnee_core = spawnee.args.core;
let spawnee_prio = spawnee.args.priority;
let mut must_be_send = false;
if spawner_core != spawnee_core {
let spawned_from_init = spawner_prio.is_none();
spawn_barriers
.entry(spawnee_core)
.or_default()
.insert(spawner_core, spawned_from_init);
must_be_send = true;
}
let channel = channels
.entry(spawnee_core)
.or_default()
.entry(spawnee_prio)
.or_default()
.entry(spawner_core)
.or_default();
channel.tasks.insert(name.clone());
let fq = free_queues
.entry(name.clone())
.or_default()
.entry(spawner_core)
.or_default();
if let Some(prio) = spawner_prio {
match channel.ceiling {
None => channel.ceiling = Some(prio),
Some(ceil) => channel.ceiling = Some(cmp::max(prio, ceil)),
}
match *fq {
None => *fq = Some(prio),
Some(ceil) => *fq = Some(cmp::max(ceil, prio)),
}
if spawner_core == spawnee_core && spawnee_prio != prio {
must_be_send = true;
}
} else if spawner_core == spawnee_core {
must_be_send = true;
}
if must_be_send {
{
let send_types = send_types.entry(spawner_core).or_default();
spawnee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}
let send_types = send_types.entry(spawnee_core).or_default();
spawnee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}
}
for (scheduler_core, scheduler_prio, name) in app.schedule_calls() {
let schedulee = &app.software_tasks[name];
let schedulee_core = schedulee.args.core;
let schedulee_prio = schedulee.args.priority;
let mut must_be_send = false;
if scheduler_core != schedulee_core {
match spawn_barriers
.entry(schedulee_core)
.or_default()
.entry(scheduler_core)
{
Entry::Vacant(entry) => {
entry.insert(false);
}
Entry::Occupied(..) => {}
}
must_be_send = true;
}
let tq = timer_queues.get_mut(&scheduler_core).expect("UNREACHABLE");
let channel = channels
.entry(schedulee_core)
.or_default()
.entry(schedulee_prio)
.or_default()
.entry(scheduler_core)
.or_default();
channel.tasks.insert(name.clone());
let fq = free_queues
.entry(name.clone())
.or_default()
.entry(scheduler_core)
.or_default();
match channel.ceiling {
None => channel.ceiling = Some(tq.priority),
Some(ceil) => channel.ceiling = Some(cmp::max(ceil, tq.priority)),
}
if let Some(prio) = scheduler_prio {
match *fq {
None => *fq = Some(prio),
Some(ceil) => *fq = Some(cmp::max(ceil, prio)),
}
tq.ceiling = cmp::max(tq.ceiling, prio);
if scheduler_core == schedulee_core && schedulee_prio != prio {
must_be_send = true;
}
} else if scheduler_core == schedulee_core {
must_be_send = true;
}
if must_be_send {
{
let send_types = send_types.entry(scheduler_core).or_default();
schedulee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}
let send_types = send_types.entry(schedulee_core).or_default();
schedulee.inputs.iter().for_each(|input| {
send_types.insert(input.ty.clone());
});
}
}
debug_assert!(channels.values().all(|dispatchers| dispatchers
.values()
.all(|channels| channels.values().all(|channel| !channel.tasks.is_empty()))));
for channel in channels
.values_mut()
.flat_map(|dispatchers| dispatchers.values_mut())
.flat_map(|dispatcher| dispatcher.values_mut())
{
channel.capacity = channel
.tasks
.iter()
.map(|name| app.software_tasks[name].args.capacity)
.sum();
}
for tq in timer_queues.values_mut() {
tq.capacity = tq
.tasks
.iter()
.map(|name| app.software_tasks[name].args.capacity)
.sum();
}
let used_cores = app
.inits
.keys()
.cloned()
.chain(app.idles.keys().cloned())
.chain(app.hardware_tasks.values().map(|task| task.args.core))
.chain(app.software_tasks.values().map(|task| task.args.core))
.collect();
Analysis {
used_cores,
channels,
free_queues,
initialization_barriers,
late_resources,
locations,
ownerships,
send_types,
spawn_barriers,
sync_types,
timer_queues,
}
}
pub type Ceiling = Option<u8>;
pub type Priority = u8;
pub type Receiver = Core;
pub type Resource = Ident;
pub type Sender = Core;
pub type Task = Ident;
pub struct Analysis {
pub used_cores: BTreeSet<Core>,
pub channels: Channels,
pub free_queues: FreeQueues,
pub late_resources: LateResources,
pub locations: Locations,
pub ownerships: Ownerships,
pub send_types: SendTypes,
pub sync_types: SyncTypes,
pub initialization_barriers: InitializationBarriers,
pub spawn_barriers: SpawnBarriers,
pub timer_queues: TimerQueues,
}
pub type Channels = BTreeMap<Receiver, BTreeMap<Priority, BTreeMap<Sender, Channel>>>;
pub type FreeQueues = IndexMap<Task, BTreeMap<Sender, Ceiling>>;
pub type LateResources = BTreeMap<Core, BTreeSet<Resource>>;
pub type Locations = IndexMap<Resource, Location>;
pub type Ownerships = IndexMap<Resource, Ownership>;
pub type SendTypes = BTreeMap<Core, Set<Box<Type>>>;
pub type SyncTypes = BTreeMap<Core, Set<Box<Type>>>;
pub type InitializationBarriers =
BTreeMap< Receiver, BTreeSet< Sender>>;
pub type SpawnBarriers =
BTreeMap< Receiver, BTreeMap< Sender, bool>>;
pub type TimerQueues = BTreeMap<Core, TimerQueue>;
#[derive(Debug)]
pub struct TimerQueue {
pub capacity: u8,
pub ceiling: u8,
pub priority: u8,
pub tasks: BTreeSet<Task>,
}
impl Default for TimerQueue {
fn default() -> Self {
Self {
capacity: 0,
ceiling: 1,
priority: 1,
tasks: BTreeSet::new(),
}
}
}
#[derive(Debug, Default)]
pub struct Channel {
pub capacity: u8,
pub ceiling: Ceiling,
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 {
match self {
Ownership::Owned { .. } => true,
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Location {
Owned {
core: u8,
cross_initialized: bool,
},
Shared {
cores: BTreeSet<Core>,
},
}
impl Location {
pub fn core(&self) -> Option<u8> {
match *self {
Location::Owned { core, .. } => Some(core),
Location::Shared { .. } => None,
}
}
fn cross_initialized(&self) -> bool {
match *self {
Location::Owned {
cross_initialized, ..
} => cross_initialized,
Location::Shared { .. } => false,
}
}
}