rtic_syntax/
analyze.rs

1//! RTIC application analysis
2
3use 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    // Collect all tasks into a vector
16    type TaskName = String;
17    type Priority = u8;
18
19    // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority)
20    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    // Collect lock free resources
67    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    // Check that lock_free resources are correct
75    for lf_res in lock_free.iter() {
76        for (task, tr, _, priority) in task_resources_list.iter() {
77            for r in tr {
78                // Get all uses of resources annotated lock_free
79                if lf_res == r {
80                    // HashMap returns the previous existing object if old.key == new.key
81                    if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) {
82                        // Check if priority differ, if it does, append to
83                        // list of resources which will be annotated with errors
84                        if priority != lf_res.2 {
85                            lf_res_with_error.push(lf_res.1);
86                            lf_res_with_error.push(r);
87                        }
88                        // If the resource already violates lock free properties
89                        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    // Add error message in the resource struct
100    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    // Add error message for each use of the shared resource
113    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    // Collect local resources
124    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    // Check that local resources are not shared
130    for lr in local {
131        for (task, _, local_resources, _) in task_resources_list.iter() {
132            for (name, res) in local_resources.iter() {
133                // Get all uses of resources annotated lock_free
134                if lr == name {
135                    match res {
136                        TaskLocal::External => {
137                            // HashMap returns the previous existing object if old.key == new.key
138                            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                        // If a declared local has the same name as the `#[local]` struct, it's an
144                        // direct error
145                        TaskLocal::Declared(_) => {
146                            lr_with_error.push(lr);
147                            lr_with_error.push(name);
148                        }
149                    }
150                }
151            }
152        }
153    }
154
155    // Add error message for each use of the local resource
156    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    // Collect errors if any and return/halt
167    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    // e. Location of resources
174    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        // (e)
181        // This shared resource is used
182        used_shared_resource.insert(name.clone());
183
184        // (c)
185        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    // Create the list of used local resource Idents
215    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    // Most shared resources need to be `Send`
224    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        // Handle not owned by idle
228        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    // Most local resources need to be `Send` as well
238    for (name, res) in app.local_resources.iter() {
239        if let Some(idle) = &app.idle {
240            // Only Send if not in idle
241            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        // All inputs are now send as we do not know from where they may be spawned.
258        spawnee.inputs.iter().for_each(|input| {
259            send_types.insert(input.ty.clone());
260        });
261    }
262
263    // No channel should ever be empty
264    debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty()));
265
266    // Compute channel capacities
267    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
285/// Priority ceiling
286pub type Ceiling = Option<u8>;
287
288/// Task priority
289pub type Priority = u8;
290
291/// Resource name
292pub type Resource = Ident;
293
294/// Task name
295pub type Task = Ident;
296
297/// The result of analyzing an RTIC application
298pub struct Analysis {
299    /// SPSC message channels
300    pub channels: Channels,
301
302    /// Shared resources
303    ///
304    /// If a resource is not listed here it means that's a "dead" (never
305    /// accessed) resource and the backend should not generate code for it
306    pub shared_resources: UsedSharedResource,
307
308    /// Local resources
309    ///
310    /// If a resource is not listed here it means that's a "dead" (never
311    /// accessed) resource and the backend should not generate code for it
312    pub local_resources: UsedLocalResource,
313
314    /// Resource ownership
315    pub ownerships: Ownerships,
316
317    /// These types must implement the `Send` trait
318    pub send_types: SendTypes,
319
320    /// These types must implement the `Sync` trait
321    pub sync_types: SyncTypes,
322}
323
324/// All channels, keyed by dispatch priority
325pub type Channels = BTreeMap<Priority, Channel>;
326
327/// Location of all *used* shared resources
328pub type UsedSharedResource = IndexSet<Resource>;
329
330/// Location of all *used* local resources
331pub type UsedLocalResource = IndexSet<Resource>;
332
333/// Resource ownership
334pub type Ownerships = IndexMap<Resource, Ownership>;
335
336/// These types must implement the `Send` trait
337pub type SendTypes = Set<Box<Type>>;
338
339/// These types must implement the `Sync` trait
340pub type SyncTypes = Set<Box<Type>>;
341
342/// A channel used to send messages
343#[derive(Debug, Default)]
344pub struct Channel {
345    /// The channel capacity
346    pub capacity: u8,
347
348    /// Tasks that can be spawned on this channel
349    pub tasks: BTreeSet<Task>,
350}
351
352/// Resource ownership
353#[derive(Clone, Copy, Debug, Eq, PartialEq)]
354pub enum Ownership {
355    /// Owned by a single task
356    Owned {
357        /// Priority of the task that owns this resource
358        priority: u8,
359    },
360
361    /// "Co-owned" by more than one task; all of them have the same priority
362    CoOwned {
363        /// Priority of the tasks that co-own this resource
364        priority: u8,
365    },
366
367    /// Contended by more than one task; the tasks have different priorities
368    Contended {
369        /// Priority ceiling
370        ceiling: u8,
371    },
372}
373
374impl Ownership {
375    /// Whether this resource needs to a lock at this priority level
376    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    /// Whether this resource is exclusively owned
389    pub fn is_owned(&self) -> bool {
390        matches!(self, Ownership::Owned { .. })
391    }
392}