Skip to main content

rustdv_sim/
combinators.rs

1//! Ports of `First`/`Combine` (design-doc mapping row 16): future
2//! combinators with drop-based cancellation of the losers — the cleanup
3//! cocotb does manually with kill-on-completion tasks falls out of RAII.
4
5use std::fmt;
6use std::future::Future;
7use std::pin::Pin;
8use std::task::{Context, Poll};
9
10use crate::time::SimDuration;
11use crate::triggers::Timer;
12
13pub enum Either<A, B> {
14    First(A),
15    Second(B),
16}
17
18/// First of two futures; the loser is dropped (unsubscribing its trigger).
19///
20/// The futures are boxed behind lifetime `'a` rather than `'static` (D82), so
21/// a future that *borrows* — a component's `run` borrowing the tree, a
22/// sub-sequence borrowing its parent sequence — can be composed here. `'static`
23/// futures satisfy any `'a`, so every earlier caller is unaffected.
24pub struct First2<'a, A, B> {
25    a: Pin<Box<dyn Future<Output = A> + 'a>>,
26    b: Pin<Box<dyn Future<Output = B> + 'a>>,
27}
28
29pub fn first2<'a, FA, FB>(a: FA, b: FB) -> First2<'a, FA::Output, FB::Output>
30where
31    FA: Future + 'a,
32    FB: Future + 'a,
33{
34    First2 {
35        a: Box::pin(a),
36        b: Box::pin(b),
37    }
38}
39
40impl<A, B> Future for First2<'_, A, B> {
41    type Output = Either<A, B>;
42    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
43        if let Poll::Ready(v) = self.a.as_mut().poll(cx) {
44            return Poll::Ready(Either::First(v));
45        }
46        if let Poll::Ready(v) = self.b.as_mut().poll(cx) {
47            return Poll::Ready(Either::Second(v));
48        }
49        Poll::Pending
50    }
51}
52
53/// Join of two futures (port of `Combine`; SystemVerilog's `fork...join`).
54pub struct Join2<'a, A, B> {
55    a: Pin<Box<dyn Future<Output = A> + 'a>>,
56    b: Pin<Box<dyn Future<Output = B> + 'a>>,
57    ra: Option<A>,
58    rb: Option<B>,
59}
60
61pub fn join2<'a, FA, FB>(a: FA, b: FB) -> Join2<'a, FA::Output, FB::Output>
62where
63    FA: Future + 'a,
64    FB: Future + 'a,
65{
66    Join2 {
67        a: Box::pin(a),
68        b: Box::pin(b),
69        ra: None,
70        rb: None,
71    }
72}
73
74impl<A, B> Future for Join2<'_, A, B> {
75    type Output = (A, B);
76    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<(A, B)> {
77        // Sound: the inner futures are boxed (their pinning is their own),
78        // and ra/rb are plain values we intentionally move on completion.
79        let this = unsafe { self.get_unchecked_mut() };
80        if this.ra.is_none()
81            && let Poll::Ready(v) = this.a.as_mut().poll(cx)
82        {
83            this.ra = Some(v);
84        }
85        if this.rb.is_none()
86            && let Poll::Ready(v) = this.b.as_mut().poll(cx)
87        {
88            this.rb = Some(v);
89        }
90        if this.ra.is_some() && this.rb.is_some() {
91            Poll::Ready((this.ra.take().unwrap(), this.rb.take().unwrap()))
92        } else {
93            Poll::Pending
94        }
95    }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct TimeoutError;
100
101impl fmt::Display for TimeoutError {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "operation timed out")
104    }
105}
106impl std::error::Error for TimeoutError {}
107
108/// Run `fut` with a simulation-time timeout.
109pub async fn with_timeout<'a, F>(fut: F, d: SimDuration) -> Result<F::Output, TimeoutError>
110where
111    F: Future + 'a,
112{
113    match first2(fut, Timer::new(d)).await {
114        Either::First(v) => Ok(v),
115        Either::Second(()) => Err(TimeoutError),
116    }
117}
118
119/// Join *N* futures, where N is known only at run time (D82).
120///
121/// `join2` covers the fixed-arity case the `join!` macro expands to; this
122/// covers a `Vec` built at run time — a parent joining however many children
123/// it has, or a virtual sequence joining a list of sub-sequences. Every future
124/// is polled on each wake until all have completed; results come back in the
125/// original order. Like `Join2`, the futures may borrow (`'a`).
126pub struct JoinAll<'a, T> {
127    futs: Vec<Option<Pin<Box<dyn Future<Output = T> + 'a>>>>,
128    out: Vec<Option<T>>,
129}
130
131pub fn join_all<'a, T>(futs: Vec<Pin<Box<dyn Future<Output = T> + 'a>>>) -> JoinAll<'a, T> {
132    let n = futs.len();
133    JoinAll {
134        futs: futs.into_iter().map(Some).collect(),
135        out: (0..n).map(|_| None).collect(),
136    }
137}
138
139impl<T> Future for JoinAll<'_, T> {
140    type Output = Vec<T>;
141    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Vec<T>> {
142        // Sound: the inner futures are boxed (they own their pinning), and
143        // `out` holds plain values we move out once every future is done.
144        let this = unsafe { self.get_unchecked_mut() };
145        let mut all_done = true;
146        for (i, slot) in this.futs.iter_mut().enumerate() {
147            if let Some(f) = slot {
148                match f.as_mut().poll(cx) {
149                    Poll::Ready(v) => {
150                        this.out[i] = Some(v);
151                        *slot = None; // drop the finished future
152                    }
153                    Poll::Pending => all_done = false,
154                }
155            }
156        }
157        if all_done {
158            Poll::Ready(this.out.iter_mut().map(|o| o.take().unwrap()).collect())
159        } else {
160            Poll::Pending
161        }
162    }
163}
164
165/// `first!(a, b, ...)` — first completed future wins; losers are dropped.
166#[macro_export]
167macro_rules! first {
168    ($a:expr, $b:expr $(,)?) => {
169        $crate::combinators::first2($a, $b)
170    };
171    ($a:expr, $b:expr, $($rest:expr),+ $(,)?) => {
172        $crate::combinators::first2($a, $crate::first!($b, $($rest),+))
173    };
174}
175
176/// `join!(a, b, ...)` — wait for all.
177#[macro_export]
178macro_rules! join {
179    ($a:expr, $b:expr $(,)?) => {
180        $crate::combinators::join2($a, $b)
181    };
182    ($a:expr, $b:expr, $($rest:expr),+ $(,)?) => {
183        $crate::combinators::join2($a, $crate::join!($b, $($rest),+))
184    };
185}
186
187// ===========================================================================
188// Tests — no simulator.
189// ===========================================================================
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use crate::testing::block_on;
195    use std::cell::RefCell;
196    use std::rc::Rc;
197
198    #[test]
199    fn join2_yields_both() {
200        block_on(async {
201            let (a, b) = join2(async { 1u8 }, async { "two" }).await;
202            assert_eq!(a, 1);
203            assert_eq!(b, "two");
204        });
205    }
206
207    #[test]
208    fn join_all_preserves_input_order() {
209        block_on(async {
210            let futs: Vec<Pin<Box<dyn Future<Output = u8>>>> = vec![
211                Box::pin(async { 1 }),
212                Box::pin(async { 2 }),
213                Box::pin(async { 3 }),
214            ];
215            assert_eq!(join_all(futs).await, vec![1, 2, 3]);
216        });
217    }
218
219    #[test]
220    fn first2_returns_the_winner() {
221        block_on(async {
222            let ev = crate::sync::Event::new();
223            let waiter = ev.clone();
224            crate::executor::spawn(async move {
225                ev.set();
226            });
227            match first2(async { 7u8 }, async move { waiter.wait().await }).await {
228                Either::First(v) => assert_eq!(v, 7),
229                Either::Second(()) => panic!("the ready future should have won"),
230            }
231        });
232    }
233
234    /// D82c in miniature: **losing a race means being dropped.** Racing the
235    /// whole run tree instead of each component dropped the tree mid-phase
236    /// and a test passed with its scoreboard never running.
237    #[test]
238    fn first2_drops_the_loser() {
239        struct Tattle(Rc<RefCell<bool>>);
240        impl Drop for Tattle {
241            fn drop(&mut self) {
242                *self.0.borrow_mut() = true;
243            }
244        }
245
246        let dropped = Rc::new(RefCell::new(false));
247        let flag = dropped.clone();
248        block_on(async move {
249            let never = crate::sync::Event::new();
250            // The tattle is moved *into* the future from outside, so it is
251            // dropped when the future is dropped — whether or not the future
252            // ever got polled.
253            let tattle = Tattle(flag);
254            let loser = async move {
255                let _t = tattle;
256                never.wait().await;
257            };
258            let _ = first2(async { 1u8 }, loser).await;
259        });
260        assert!(
261            *dropped.borrow(),
262            "the losing future was dropped, not left running"
263        );
264    }
265
266    /// The whole point of D82: a joined future may **borrow**, so a
267    /// sub-sequence can use its parent's state. If this stops compiling, the
268    /// combinators have regained a `'static` bound.
269    #[test]
270    fn joined_futures_may_borrow() {
271        block_on(async {
272            let owned = [1u8, 2, 3];
273            let borrow_a = async { owned.len() };
274            let borrow_b = async { owned[0] as usize };
275            let (a, b) = join2(borrow_a, borrow_b).await;
276            assert_eq!((a, b), (3, 1));
277        });
278    }
279}