cu29_runtime/
copperlist.rs

1//! CopperList is the main data structure used by Copper to communicate between tasks.
2//! It is a queue that can be used to store preallocated messages between tasks in memory order.
3extern crate alloc;
4
5use bincode::{Decode, Encode};
6use std::fmt;
7
8use cu29_traits::{CopperListTuple, ErasedCuStampedData, ErasedCuStampedDataSet};
9use serde_derive::Serialize;
10use std::fmt::Display;
11use std::iter::{Chain, Rev};
12use std::slice::{Iter as SliceIter, IterMut as SliceIterMut};
13
14const MAX_TASKS: usize = 512;
15
16/// Not implemented yet.
17/// This mask will be used to for example filter out necessary regions of a copper list between remote systems.
18#[derive(Debug, Encode, Decode, PartialEq, Clone, Copy)]
19pub struct CopperLiskMask {
20    #[allow(dead_code)]
21    mask: [u128; MAX_TASKS / 128 + 1],
22}
23
24/// Those are the possible states along the lifetime of a CopperList.
25#[derive(Debug, Encode, Decode, Serialize, PartialEq, Copy, Clone)]
26pub enum CopperListState {
27    Free,
28    Initialized,
29    Processing,
30    DoneProcessing,
31    BeingSerialized,
32}
33
34impl Display for CopperListState {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            CopperListState::Free => write!(f, "Free"),
38            CopperListState::Initialized => write!(f, "Initialized"),
39            CopperListState::Processing => write!(f, "Processing"),
40            CopperListState::DoneProcessing => write!(f, "DoneProcessing"),
41            CopperListState::BeingSerialized => write!(f, "BeingSerialized"),
42        }
43    }
44}
45
46#[derive(Debug, Encode, Decode, Serialize)]
47pub struct CopperList<P: CopperListTuple> {
48    pub id: u32,
49    state: CopperListState,
50    pub msgs: P, // This is generated from the runtime.
51}
52
53impl<P: CopperListTuple> Default for CopperList<P> {
54    fn default() -> Self {
55        CopperList {
56            id: 0,
57            state: CopperListState::Free,
58            msgs: P::default(),
59        }
60    }
61}
62
63impl<P: CopperListTuple> CopperList<P> {
64    // This is not the usual way to create a CopperList, this is just for testing.
65    pub fn new(id: u32, msgs: P) -> Self {
66        CopperList {
67            id,
68            state: CopperListState::Initialized,
69            msgs,
70        }
71    }
72
73    pub fn change_state(&mut self, new_state: CopperListState) {
74        self.state = new_state; // TODO: probably wise here to enforce a state machine.
75    }
76
77    pub fn get_state(&self) -> CopperListState {
78        self.state
79    }
80}
81
82impl<P: CopperListTuple> ErasedCuStampedDataSet for CopperList<P> {
83    fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
84        self.msgs.cumsgs()
85    }
86}
87
88/// This structure maintains the entire memory needed by Copper for one loop for the inter tasks communication within a process.
89/// P or Payload is typically a Tuple of various types of messages that are exchanged between tasks.
90/// N is the maximum number of in flight Copper List the runtime can support.
91pub struct CuListsManager<P: CopperListTuple, const N: usize> {
92    data: Box<[CopperList<P>; N]>,
93    length: usize,
94    insertion_index: usize,
95    current_cl_id: u32,
96}
97
98impl<P: CopperListTuple + fmt::Debug, const N: usize> fmt::Debug for CuListsManager<P, N> {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.debug_struct("CuListsManager")
101            .field("data", &self.data)
102            .field("length", &self.length)
103            .field("insertion_index", &self.insertion_index)
104            // Do not include on_drop field
105            .finish()
106    }
107}
108
109pub type Iter<'a, T> = Chain<Rev<SliceIter<'a, T>>, Rev<SliceIter<'a, T>>>;
110pub type IterMut<'a, T> = Chain<Rev<SliceIterMut<'a, T>>, Rev<SliceIterMut<'a, T>>>;
111pub type AscIter<'a, T> = Chain<SliceIter<'a, T>, SliceIter<'a, T>>;
112pub type AscIterMut<'a, T> = Chain<SliceIterMut<'a, T>, SliceIterMut<'a, T>>;
113
114/// Initializes fields that cannot be zeroed after allocating a zeroed
115/// [`CopperList`].
116pub trait CuListZeroedInit: CopperListTuple {
117    /// Fixes up a zero-initialized copper list so that all internal fields are
118    /// in a valid state.
119    fn init_zeroed(&mut self);
120}
121
122impl<P: CopperListTuple + CuListZeroedInit, const N: usize> Default for CuListsManager<P, N> {
123    fn default() -> Self {
124        Self::new()
125    }
126}
127
128impl<P: CopperListTuple, const N: usize> CuListsManager<P, N> {
129    pub fn new() -> Self
130    where
131        P: CuListZeroedInit,
132    {
133        let data = unsafe {
134            let layout = std::alloc::Layout::new::<[CopperList<P>; N]>();
135            let ptr = std::alloc::alloc_zeroed(layout) as *mut [CopperList<P>; N];
136            if ptr.is_null() {
137                std::alloc::handle_alloc_error(layout);
138            }
139            Box::from_raw(ptr)
140        };
141        let mut manager = CuListsManager {
142            data,
143            length: 0,
144            insertion_index: 0,
145            current_cl_id: 0,
146        };
147
148        for cl in manager.data.iter_mut() {
149            cl.msgs.init_zeroed();
150        }
151
152        manager
153    }
154
155    /// Returns the current number of elements in the queue.
156    ///
157    #[inline]
158    pub fn len(&self) -> usize {
159        self.length
160    }
161
162    /// Returns `true` if the queue contains no elements.
163    ///
164    #[inline]
165    pub fn is_empty(&self) -> bool {
166        self.length == 0
167    }
168
169    /// Returns `true` if the queue is full.
170    ///
171    #[inline]
172    pub fn is_full(&self) -> bool {
173        N == self.len()
174    }
175
176    /// Clears the queue.
177    ///
178    #[inline]
179    pub fn clear(&mut self) {
180        self.insertion_index = 0;
181        self.length = 0;
182    }
183
184    #[inline]
185    pub fn create(&mut self) -> Option<&mut CopperList<P>> {
186        if self.is_full() {
187            return None;
188        }
189        let result = &mut self.data[self.insertion_index];
190        self.insertion_index = (self.insertion_index + 1) % N;
191        self.length += 1;
192
193        // We assign a unique id to each CopperList to be able to track them across their lifetime.
194        result.id = self.current_cl_id;
195        self.current_cl_id += 1;
196
197        Some(result)
198    }
199
200    /// Peeks at the last element in the queue.
201    #[inline]
202    pub fn peek(&self) -> Option<&CopperList<P>> {
203        if self.length == 0 {
204            return None;
205        }
206        let index = if self.insertion_index == 0 {
207            N - 1
208        } else {
209            self.insertion_index - 1
210        };
211        Some(&self.data[index])
212    }
213
214    #[inline]
215    #[allow(dead_code)]
216    fn drop_last(&mut self) {
217        if self.length == 0 {
218            return;
219        }
220        if self.insertion_index == 0 {
221            self.insertion_index = N - 1;
222        } else {
223            self.insertion_index -= 1;
224        }
225        self.length -= 1;
226    }
227
228    #[inline]
229    pub fn pop(&mut self) -> Option<&mut CopperList<P>> {
230        if self.length == 0 {
231            return None;
232        }
233        if self.insertion_index == 0 {
234            self.insertion_index = N - 1;
235        } else {
236            self.insertion_index -= 1;
237        }
238        self.length -= 1;
239        Some(&mut self.data[self.insertion_index])
240    }
241
242    /// Returns an iterator over the queue's contents.
243    ///
244    /// The iterator goes from the most recently pushed items to the oldest ones.
245    ///
246    #[inline]
247    pub fn iter(&self) -> Iter<'_, CopperList<P>> {
248        let (a, b) = self.data[0..self.length].split_at(self.insertion_index);
249        a.iter().rev().chain(b.iter().rev())
250    }
251
252    /// Returns a mutable iterator over the queue's contents.
253    ///
254    /// The iterator goes from the most recently pushed items to the oldest ones.
255    ///
256    #[inline]
257    pub fn iter_mut(&mut self) -> IterMut<'_, CopperList<P>> {
258        let (a, b) = self.data.split_at_mut(self.insertion_index);
259        a.iter_mut().rev().chain(b.iter_mut().rev())
260    }
261
262    /// Returns an ascending iterator over the queue's contents.
263    ///
264    /// The iterator goes from the least recently pushed items to the newest ones.
265    ///
266    #[inline]
267    pub fn asc_iter(&self) -> AscIter<'_, CopperList<P>> {
268        let (a, b) = self.data.split_at(self.insertion_index);
269        b.iter().chain(a.iter())
270    }
271
272    /// Returns a mutable ascending iterator over the queue's contents.
273    ///
274    /// The iterator goes from the least recently pushed items to the newest ones.
275    ///
276    #[inline]
277    pub fn asc_iter_mut(&mut self) -> AscIterMut<'_, CopperList<P>> {
278        let (a, b) = self.data.split_at_mut(self.insertion_index);
279        b.iter_mut().chain(a.iter_mut())
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286    use cu29_traits::{ErasedCuStampedData, ErasedCuStampedDataSet, MatchingTasks};
287    use serde::{Serialize, Serializer};
288
289    #[derive(Debug, Encode, Decode, PartialEq, Clone, Copy, Serialize, Default)]
290    struct CuStampedDataSet(i32);
291
292    impl ErasedCuStampedDataSet for CuStampedDataSet {
293        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
294            Vec::new()
295        }
296    }
297
298    impl MatchingTasks for CuStampedDataSet {
299        fn get_all_task_ids() -> &'static [&'static str] {
300            &[]
301        }
302    }
303
304    impl CuListZeroedInit for CuStampedDataSet {
305        fn init_zeroed(&mut self) {}
306    }
307
308    #[test]
309    fn empty_queue() {
310        let q = CuListsManager::<CuStampedDataSet, 5>::new();
311
312        assert!(q.is_empty());
313        assert!(q.iter().next().is_none());
314    }
315
316    #[test]
317    fn partially_full_queue() {
318        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
319        q.create().unwrap().msgs.0 = 1;
320        q.create().unwrap().msgs.0 = 2;
321        q.create().unwrap().msgs.0 = 3;
322
323        assert!(!q.is_empty());
324        assert_eq!(q.len(), 3);
325
326        let res: Vec<i32> = q.iter().map(|x| x.msgs.0).collect();
327        assert_eq!(res, [3, 2, 1]);
328    }
329
330    #[test]
331    fn full_queue() {
332        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
333        q.create().unwrap().msgs.0 = 1;
334        q.create().unwrap().msgs.0 = 2;
335        q.create().unwrap().msgs.0 = 3;
336        q.create().unwrap().msgs.0 = 4;
337        q.create().unwrap().msgs.0 = 5;
338        assert_eq!(q.len(), 5);
339
340        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
341        assert_eq!(res, [5, 4, 3, 2, 1]);
342    }
343
344    #[test]
345    fn over_full_queue() {
346        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
347        q.create().unwrap().msgs.0 = 1;
348        q.create().unwrap().msgs.0 = 2;
349        q.create().unwrap().msgs.0 = 3;
350        q.create().unwrap().msgs.0 = 4;
351        q.create().unwrap().msgs.0 = 5;
352        assert!(q.create().is_none());
353        assert_eq!(q.len(), 5);
354
355        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
356        assert_eq!(res, [5, 4, 3, 2, 1]);
357    }
358
359    #[test]
360    fn clear() {
361        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
362        q.create().unwrap().msgs.0 = 1;
363        q.create().unwrap().msgs.0 = 2;
364        q.create().unwrap().msgs.0 = 3;
365        q.create().unwrap().msgs.0 = 4;
366        q.create().unwrap().msgs.0 = 5;
367        assert!(q.create().is_none());
368        assert_eq!(q.len(), 5);
369
370        q.clear();
371
372        assert_eq!(q.len(), 0);
373        assert!(q.iter().next().is_none());
374
375        q.create().unwrap().msgs.0 = 1;
376        q.create().unwrap().msgs.0 = 2;
377        q.create().unwrap().msgs.0 = 3;
378
379        assert_eq!(q.len(), 3);
380
381        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
382        assert_eq!(res, [3, 2, 1]);
383    }
384
385    #[test]
386    fn mutable_iterator() {
387        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
388        q.create().unwrap().msgs.0 = 1;
389        q.create().unwrap().msgs.0 = 2;
390        q.create().unwrap().msgs.0 = 3;
391        q.create().unwrap().msgs.0 = 4;
392        q.create().unwrap().msgs.0 = 5;
393
394        for x in q.iter_mut() {
395            x.msgs.0 *= 2;
396        }
397
398        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
399        assert_eq!(res, [10, 8, 6, 4, 2]);
400    }
401
402    #[test]
403    fn test_drop_last() {
404        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
405        q.create().unwrap().msgs.0 = 1;
406        q.create().unwrap().msgs.0 = 2;
407        q.create().unwrap().msgs.0 = 3;
408        q.create().unwrap().msgs.0 = 4;
409        q.create().unwrap().msgs.0 = 5;
410        assert_eq!(q.len(), 5);
411
412        q.drop_last();
413        assert_eq!(q.len(), 4);
414
415        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
416        assert_eq!(res, [4, 3, 2, 1]);
417    }
418
419    #[test]
420    fn test_pop() {
421        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
422        q.create().unwrap().msgs.0 = 1;
423        q.create().unwrap().msgs.0 = 2;
424        q.create().unwrap().msgs.0 = 3;
425        q.create().unwrap().msgs.0 = 4;
426        q.create().unwrap().msgs.0 = 5;
427        assert_eq!(q.len(), 5);
428
429        let last = q.pop().unwrap();
430        assert_eq!(last.msgs.0, 5);
431        assert_eq!(q.len(), 4);
432
433        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
434        assert_eq!(res, [4, 3, 2, 1]);
435    }
436
437    #[test]
438    fn test_peek() {
439        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
440        q.create().unwrap().msgs.0 = 1;
441        q.create().unwrap().msgs.0 = 2;
442        q.create().unwrap().msgs.0 = 3;
443        q.create().unwrap().msgs.0 = 4;
444        q.create().unwrap().msgs.0 = 5;
445        assert_eq!(q.len(), 5);
446
447        let last = q.peek().unwrap();
448        assert_eq!(last.msgs.0, 5);
449        assert_eq!(q.len(), 5);
450
451        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
452        assert_eq!(res, [5, 4, 3, 2, 1]);
453    }
454
455    #[derive(Decode, Encode, Debug, PartialEq, Clone, Copy)]
456    struct TestStruct {
457        content: [u8; 10_000_000],
458    }
459
460    impl Default for TestStruct {
461        fn default() -> Self {
462            TestStruct {
463                content: [0; 10_000_000],
464            }
465        }
466    }
467
468    impl ErasedCuStampedDataSet for TestStruct {
469        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
470            Vec::new()
471        }
472    }
473
474    impl Serialize for TestStruct {
475        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
476        where
477            S: Serializer,
478        {
479            serializer.serialize_i8(0)
480        }
481    }
482
483    impl MatchingTasks for TestStruct {
484        fn get_all_task_ids() -> &'static [&'static str] {
485            &[]
486        }
487    }
488
489    impl CuListZeroedInit for TestStruct {
490        fn init_zeroed(&mut self) {}
491    }
492
493    #[test]
494    fn be_sure_we_wont_stackoverflow_at_init() {
495        let _ = CuListsManager::<TestStruct, 3>::new();
496    }
497}