1use core::cmp;
4use std::collections::{BTreeMap, BTreeSet, HashMap};
5
6use indexmap::{IndexMap, IndexSet};
7use syn::{Ident, Type};
8
9use crate::syntax::{
10 ast::{App, LocalResources, TaskLocal},
11 Set,
12};
13
14pub(crate) fn app(app: &App) -> Result<Analysis, syn::Error> {
15 type TaskName = Ident;
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| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0))
24 .chain(app.idle.iter().map(|ht| {
25 (
26 ht.name.clone(),
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.clone(),
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.clone(),
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 app.software_tasks.get(task).is_some() {
82 error.push(syn::Error::new(
83 r.span(),
84 format!(
85 "Lock free shared resource {:?} is used by an async tasks, which is forbidden",
86 r.to_string(),
87 ),
88 ));
89 }
90
91 if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) {
93 if priority != lf_res.2 {
96 lf_res_with_error.push(lf_res.1);
97 lf_res_with_error.push(r);
98 }
99
100 if lf_res_with_error.contains(&r) {
102 lf_res_with_error.push(lf_res.1);
103 lf_res_with_error.push(r);
104 }
105 }
106 }
107 }
108 }
109 }
110
111 for r in lock_free {
113 if lf_res_with_error.contains(&&r) {
114 error.push(syn::Error::new(
115 r.span(),
116 format!(
117 "Lock free shared resource {:?} is used by tasks at different priorities",
118 r.to_string(),
119 ),
120 ));
121 }
122 }
123
124 for resource in lf_res_with_error.clone() {
126 error.push(syn::Error::new(
127 resource.span(),
128 format!(
129 "Shared resource {:?} is declared lock free but used by tasks at different priorities",
130 resource.to_string(),
131 ),
132 ));
133 }
134
135 let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect();
137
138 let mut lr_with_error = vec![];
139 let mut lr_hash = HashMap::new();
140
141 for lr in local {
143 for (task, _, local_resources, _) in task_resources_list.iter() {
144 for (name, res) in local_resources.iter() {
145 if lr == name {
147 match res {
148 TaskLocal::External => {
149 if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) {
151 lr_with_error.push(lr.1);
152 lr_with_error.push(name);
153 }
154 }
155 TaskLocal::Declared(_) => {
158 lr_with_error.push(lr);
159 lr_with_error.push(name);
160 }
161 }
162 }
163 }
164 }
165 }
166
167 for resource in lr_with_error.clone() {
169 error.push(syn::Error::new(
170 resource.span(),
171 format!(
172 "Local resource {:?} is used by multiple tasks or collides with multiple definitions",
173 resource.to_string(),
174 ),
175 ));
176 }
177
178 for (name, task) in &app.software_tasks {
180 if task.args.priority == 0 {
181 if app.idle.is_some() {
183 error.push(syn::Error::new(
184 name.span(),
185 format!(
186 "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.",
187 name.to_string(),
188 )
189 ));
190 }
191 }
192 }
193
194 if !error.is_empty() {
196 let mut err = error.first().unwrap().clone();
197 error.iter().for_each(|e| err.combine(e.clone()));
198 return Err(err);
199 }
200
201 let mut used_shared_resource = IndexSet::new();
203 let mut ownerships = Ownerships::new();
204 let mut sync_types = SyncTypes::new();
205 for (prio, name, access) in app.shared_resource_accesses() {
206 let res = app.shared_resources.get(name).expect("UNREACHABLE");
207
208 used_shared_resource.insert(name.clone());
211
212 if let Some(priority) = prio {
214 if let Some(ownership) = ownerships.get_mut(name) {
215 match *ownership {
216 Ownership::Owned { priority: ceiling }
217 | Ownership::CoOwned { priority: ceiling }
218 | Ownership::Contended { ceiling }
219 if priority != ceiling =>
220 {
221 *ownership = Ownership::Contended {
222 ceiling: cmp::max(ceiling, priority),
223 };
224
225 if access.is_shared() {
226 sync_types.insert(res.ty.clone());
227 }
228 }
229
230 Ownership::Owned { priority: ceil } if ceil == priority => {
231 *ownership = Ownership::CoOwned { priority };
232 }
233
234 _ => {}
235 }
236 } else {
237 ownerships.insert(name.clone(), Ownership::Owned { priority });
238 }
239 }
240 }
241
242 let mut used_local_resource = IndexSet::new();
244
245 for (_, _, locals, _) in task_resources_list {
246 for (local, _) in locals {
247 used_local_resource.insert(local.clone());
248 }
249 }
250
251 let mut send_types = SendTypes::new();
253
254 for (name, res) in app.shared_resources.iter() {
255 if ownerships
256 .get(name)
257 .map(|ownership| match *ownership {
258 Ownership::Owned { priority: ceiling }
259 | Ownership::CoOwned { priority: ceiling }
260 | Ownership::Contended { ceiling } => ceiling != 0,
261 })
262 .unwrap_or(false)
263 {
264 send_types.insert(res.ty.clone());
265 }
266 }
267
268 for (name, res) in app.local_resources.iter() {
270 if ownerships
271 .get(name)
272 .map(|ownership| match *ownership {
273 Ownership::Owned { priority: ceiling }
274 | Ownership::CoOwned { priority: ceiling }
275 | Ownership::Contended { ceiling } => ceiling != 0,
276 })
277 .unwrap_or(false)
278 {
279 send_types.insert(res.ty.clone());
280 }
281 }
282
283 let mut channels = Channels::new();
284
285 for (name, spawnee) in &app.software_tasks {
286 let spawnee_prio = spawnee.args.priority;
287
288 let channel = channels.entry(spawnee_prio).or_default();
289 channel.tasks.insert(name.clone());
290
291 spawnee.inputs.iter().for_each(|input| {
293 send_types.insert(input.ty.clone());
294 });
295 }
296
297 debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
299
300 Ok(Analysis {
301 channels,
302 shared_resources: used_shared_resource,
303 local_resources: used_local_resource,
304 ownerships,
305 send_types,
306 sync_types,
307 })
308}
309
310pub type Priority = u8;
315
316pub type Resource = Ident;
318
319pub type Task = Ident;
321
322pub struct Analysis {
324 pub channels: Channels,
326
327 pub shared_resources: UsedSharedResource,
332
333 pub local_resources: UsedLocalResource,
338
339 pub ownerships: Ownerships,
341
342 pub send_types: SendTypes,
344
345 pub sync_types: SyncTypes,
347}
348
349pub type Channels = BTreeMap<Priority, Channel>;
351
352pub type UsedSharedResource = IndexSet<Resource>;
354
355pub type UsedLocalResource = IndexSet<Resource>;
357
358pub type Ownerships = IndexMap<Resource, Ownership>;
360
361pub type SendTypes = Set<Box<Type>>;
363
364pub type SyncTypes = Set<Box<Type>>;
366
367#[derive(Debug, Default)]
369pub struct Channel {
370 pub tasks: BTreeSet<Task>,
372}
373
374#[derive(Clone, Copy, Debug, PartialEq)]
376pub enum Ownership {
377 Owned {
379 priority: u8,
381 },
382
383 CoOwned {
385 priority: u8,
387 },
388
389 Contended {
391 ceiling: u8,
393 },
394}
395
396