1use core::cmp;
4use std::collections::{BTreeMap, BTreeSet, HashMap};
5
6use indexmap::{IndexMap, IndexSet};
7use syn::{Ident, Type};
8
9use crate::{
10 ast::{App, LocalResources, TaskLocal},
11 Set,
12};
13
14pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
15 type TaskName = String;
17 type Priority = u8;
18
19 let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> =
21 Some(&app.init)
22 .iter()
23 .map(|ht| ("init".to_string(), Vec::new(), &ht.args.local_resources, 0))
24 .chain(app.idle.iter().map(|ht| {
25 (
26 "idle".to_string(),
27 ht.args
28 .shared_resources
29 .iter()
30 .map(|(v, _)| v)
31 .collect::<Vec<_>>(),
32 &ht.args.local_resources,
33 0,
34 )
35 }))
36 .chain(app.software_tasks.iter().map(|(name, ht)| {
37 (
38 name.to_string(),
39 ht.args
40 .shared_resources
41 .iter()
42 .map(|(v, _)| v)
43 .collect::<Vec<_>>(),
44 &ht.args.local_resources,
45 ht.args.priority,
46 )
47 }))
48 .chain(app.hardware_tasks.iter().map(|(name, ht)| {
49 (
50 name.to_string(),
51 ht.args
52 .shared_resources
53 .iter()
54 .map(|(v, _)| v)
55 .collect::<Vec<_>>(),
56 &ht.args.local_resources,
57 ht.args.priority,
58 )
59 }))
60 .collect();
61
62 let mut error = vec![];
63 let mut lf_res_with_error = vec![];
64 let mut lf_hash = HashMap::new();
65
66 let lock_free: Vec<&Ident> = app
68 .shared_resources
69 .iter()
70 .filter(|(_, r)| r.properties.lock_free)
71 .map(|(i, _)| i)
72 .collect();
73
74 for lf_res in lock_free.iter() {
76 for (task, tr, _, priority) in task_resources_list.iter() {
77 for r in tr {
78 if lf_res == r {
80 if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) {
82 if priority != lf_res.2 {
85 lf_res_with_error.push(lf_res.1);
86 lf_res_with_error.push(r);
87 }
88 if lf_res_with_error.contains(&r) {
90 lf_res_with_error.push(lf_res.1);
91 lf_res_with_error.push(r);
92 }
93 }
94 }
95 }
96 }
97 }
98
99 for r in lock_free {
101 if lf_res_with_error.contains(&&r) {
102 error.push(syn::Error::new(
103 r.span(),
104 format!(
105 "Lock free shared resource {:?} is used by tasks at different priorities",
106 r.to_string(),
107 ),
108 ));
109 }
110 }
111
112 for resource in lf_res_with_error.clone() {
114 error.push(syn::Error::new(
115 resource.span(),
116 format!(
117 "Shared resource {:?} is declared lock free but used by tasks at different priorities",
118 resource.to_string(),
119 ),
120 ));
121 }
122
123 let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect();
125
126 let mut lr_with_error = vec![];
127 let mut lr_hash = HashMap::new();
128
129 for lr in local {
131 for (task, _, local_resources, _) in task_resources_list.iter() {
132 for (name, res) in local_resources.iter() {
133 if lr == name {
135 match res {
136 TaskLocal::External => {
137 if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) {
139 lr_with_error.push(lr.1);
140 lr_with_error.push(name);
141 }
142 }
143 TaskLocal::Declared(_) => {
146 lr_with_error.push(lr);
147 lr_with_error.push(name);
148 }
149 }
150 }
151 }
152 }
153 }
154
155 for resource in lr_with_error.clone() {
157 error.push(syn::Error::new(
158 resource.span(),
159 format!(
160 "Local resource {:?} is used by multiple tasks or collides with multiple definitions",
161 resource.to_string(),
162 ),
163 ));
164 }
165
166 if !error.is_empty() {
168 let mut err = error.get(0).unwrap().clone();
169 error.iter().for_each(|e| err.combine(e.clone()));
170 return Err(err);
171 }
172
173 let mut used_shared_resource = IndexSet::new();
175 let mut ownerships = Ownerships::new();
176 let mut sync_types = SyncTypes::new();
177 for (prio, name, access) in app.shared_resource_accesses() {
178 let res = app.shared_resources.get(name).expect("UNREACHABLE");
179
180 used_shared_resource.insert(name.clone());
183
184 if let Some(priority) = prio {
186 if let Some(ownership) = ownerships.get_mut(name) {
187 match *ownership {
188 Ownership::Owned { priority: ceiling }
189 | Ownership::CoOwned { priority: ceiling }
190 | Ownership::Contended { ceiling }
191 if priority != ceiling =>
192 {
193 *ownership = Ownership::Contended {
194 ceiling: cmp::max(ceiling, priority),
195 };
196
197 if access.is_shared() {
198 sync_types.insert(res.ty.clone());
199 }
200 }
201
202 Ownership::Owned { priority: ceil } if ceil == priority => {
203 *ownership = Ownership::CoOwned { priority };
204 }
205
206 _ => {}
207 }
208 } else {
209 ownerships.insert(name.clone(), Ownership::Owned { priority });
210 }
211 }
212 }
213
214 let mut used_local_resource = IndexSet::new();
216
217 for (_, _, locals, _) in task_resources_list {
218 for (local, _) in locals {
219 used_local_resource.insert(local.clone());
220 }
221 }
222
223 let mut send_types = SendTypes::new();
225 let owned_by_idle = Ownership::Owned { priority: 0 };
226 for (name, res) in app.shared_resources.iter() {
227 if ownerships
229 .get(name)
230 .map(|ownership| *ownership != owned_by_idle)
231 .unwrap_or(false)
232 {
233 send_types.insert(res.ty.clone());
234 }
235 }
236
237 for (name, res) in app.local_resources.iter() {
239 if let Some(idle) = &app.idle {
240 if idle.args.local_resources.get(name).is_none() {
242 send_types.insert(res.ty.clone());
243 }
244 } else {
245 send_types.insert(res.ty.clone());
246 }
247 }
248
249 let mut channels = Channels::new();
250
251 for (name, spawnee) in &app.software_tasks {
252 let spawnee_prio = spawnee.args.priority;
253
254 let channel = channels.entry(spawnee_prio).or_default();
255 channel.tasks.insert(name.clone());
256
257 spawnee.inputs.iter().for_each(|input| {
259 send_types.insert(input.ty.clone());
260 });
261 }
262
263 debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
265
266 for channel in channels.values_mut() {
268 channel.capacity = channel
269 .tasks
270 .iter()
271 .map(|name| app.software_tasks[name].args.capacity)
272 .sum();
273 }
274
275 Ok(Analysis {
276 channels,
277 shared_resources: used_shared_resource,
278 local_resources: used_local_resource,
279 ownerships,
280 send_types,
281 sync_types,
282 })
283}
284
285pub type Ceiling = Option<u8>;
287
288pub type Priority = u8;
290
291pub type Resource = Ident;
293
294pub type Task = Ident;
296
297pub struct Analysis {
299 pub channels: Channels,
301
302 pub shared_resources: UsedSharedResource,
307
308 pub local_resources: UsedLocalResource,
313
314 pub ownerships: Ownerships,
316
317 pub send_types: SendTypes,
319
320 pub sync_types: SyncTypes,
322}
323
324pub type Channels = BTreeMap<Priority, Channel>;
326
327pub type UsedSharedResource = IndexSet<Resource>;
329
330pub type UsedLocalResource = IndexSet<Resource>;
332
333pub type Ownerships = IndexMap<Resource, Ownership>;
335
336pub type SendTypes = Set<Box<Type>>;
338
339pub type SyncTypes = Set<Box<Type>>;
341
342#[derive(Debug, Default)]
344pub struct Channel {
345 pub capacity: u8,
347
348 pub tasks: BTreeSet<Task>,
350}
351
352#[derive(Clone, Copy, Debug, Eq, PartialEq)]
354pub enum Ownership {
355 Owned {
357 priority: u8,
359 },
360
361 CoOwned {
363 priority: u8,
365 },
366
367 Contended {
369 ceiling: u8,
371 },
372}
373
374impl Ownership {
375 pub fn needs_lock(&self, priority: u8) -> bool {
377 match self {
378 Ownership::Owned { .. } | Ownership::CoOwned { .. } => false,
379
380 Ownership::Contended { ceiling } => {
381 debug_assert!(*ceiling >= priority);
382
383 priority < *ceiling
384 }
385 }
386 }
387
388 pub fn is_owned(&self) -> bool {
390 matches!(self, Ownership::Owned { .. })
391 }
392}