Skip to main content

vj_sim/constraints/
walk.rs

1//! The object traversals behind `pre_randomize()` / `post_randomize()` (Task
2//! C18; IEEE 1800-2023 18.6.2).
3//!
4//! "Every class contains pre_randomize() and post_randomize() methods, which
5//! are automatically called by randomize() before and after computing new
6//! random values." And, for object members: "If the class is [a] random
7//! object member of another class ... pre_randomize() and post_randomize()
8//! are called on the object and on each of its enabled `rand` object
9//! members" (18.5.8 gives the active set; this is the same set
10//! [`joint::closure`](super::joint::closure) solves).
11//!
12//! The hooks are compiled subroutines, which this crate cannot call, so a
13//! `randomize()` call site drives the traversal itself: [`begin`] opens a walk
14//! over the objects rooted at the receiver, [`next`] hands back the next
15//! object whose hook the call site runs (it dispatches on the object's
16//! dynamic class), and [`end`] closes it.
17//!
18//! * [`Phase::Pre`] is lazy pre-order: an object's `rand` members are
19//!   discovered only after its own `pre_randomize()` ran, so a hook that turns
20//!   a member off with `rand_mode(0)` (or replaces the member) is honoured, as
21//!   it would be for the solve that follows.
22//! * [`Phase::Post`] is a post-order snapshot taken after a successful solve:
23//!   members before their container. The LRM leaves the sibling order
24//!   unspecified; here it is the declaration order of the `rand` handles.
25//!
26//! An object graph may be cyclic (a doubly linked list), so every object is
27//! visited exactly once.
28
29use std::collections::{HashMap, HashSet};
30use std::sync::{Mutex, OnceLock};
31
32use super::joint::read_handle;
33use super::registry::{owner, program};
34use crate::objects;
35
36/// Which hook a walk visits objects for.
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum Phase {
39    /// `pre_randomize()`: container before members.
40    Pre,
41    /// `post_randomize()`: members before container.
42    Post,
43}
44
45impl Phase {
46    /// The phase of ABI code `code` (`0` = pre, `1` = post).
47    pub fn from_code(code: u64) -> Option<Self> {
48        match code {
49            0 => Some(Phase::Pre),
50            1 => Some(Phase::Post),
51            _ => None,
52        }
53    }
54
55    pub fn code(self) -> u64 {
56        match self {
57            Phase::Pre => 0,
58            Phase::Post => 1,
59        }
60    }
61}
62
63struct Walk {
64    /// Pre-order: objects still to visit, the next one last.
65    stack: Vec<u64>,
66    /// Pre-order: the object last handed out, whose members are discovered on
67    /// the following [`next`].
68    last: Option<u64>,
69    visited: HashSet<u64>,
70    /// Post-order: the remaining snapshot, the next object first.
71    queue: std::collections::VecDeque<u64>,
72    phase: Phase,
73    statics: Vec<(u32, u64)>,
74}
75
76struct Walks {
77    next_token: u64,
78    open: HashMap<u64, Walk>,
79}
80
81fn walks() -> &'static Mutex<Walks> {
82    static W: OnceLock<Mutex<Walks>> = OnceLock::new();
83    W.get_or_init(|| {
84        Mutex::new(Walks {
85            next_token: 1,
86            open: HashMap::new(),
87        })
88    })
89}
90
91/// The live objects `me` holds in its active, non-null `rand` class-handle
92/// members, in declaration order. A class without a usable program has none
93/// (its `randomize()` is rejected on its own).
94fn members(me: u64, statics: &[(u32, u64)]) -> Vec<u64> {
95    let Some(template) = objects::dynamic_template(me) else {
96        return Vec::new();
97    };
98    let Some(entry) = program(template) else {
99        return Vec::new();
100    };
101    let Ok(prog) = entry.as_ref() else {
102        return Vec::new();
103    };
104    let Some(meta) = objects::rand_meta(me) else {
105        return Vec::new();
106    };
107    let mut out = Vec::new();
108    for ph in &prog.rand_handles {
109        let active = match ph.prop_index {
110            Some(i) => meta.rand_mode.get(i).copied().unwrap_or(true),
111            None => true,
112        };
113        if !active {
114            continue;
115        }
116        let Ok(o) = owner(ph.home, me, statics) else {
117            continue;
118        };
119        if let Ok(Some(child)) = read_handle(o, ph.slot, ph.bit)
120            && objects::dynamic_template(child).is_some()
121        {
122            out.push(child);
123        }
124    }
125    out
126}
127
128/// The post-order of the objects reachable from `root` (iterative: an object
129/// graph can be a long linked list).
130fn post_order(root: u64, statics: &[(u32, u64)]) -> std::collections::VecDeque<u64> {
131    let mut out = std::collections::VecDeque::new();
132    let mut visited = HashSet::new();
133    visited.insert(root);
134    // (object, its members, index of the next member to enter)
135    let mut stack = vec![(root, members(root, statics), 0usize)];
136    while let Some(top) = stack.last_mut() {
137        if top.2 < top.1.len() {
138            let child = top.1[top.2];
139            top.2 += 1;
140            if visited.insert(child) {
141                stack.push((child, members(child, statics), 0));
142            }
143        } else {
144            out.push_back(top.0);
145            stack.pop();
146        }
147    }
148    out
149}
150
151/// Opens a walk of `phase` rooted at `root`; `statics` are the call site's
152/// `(id, handle)` static-property objects (as for
153/// [`registry::randomize`](super::registry::randomize)). Returns the token the
154/// other calls take. A dead `root` gives a walk that visits nothing.
155pub fn begin(root: u64, phase: Phase, statics: &[(u32, u64)]) -> u64 {
156    let live = objects::dynamic_template(root).is_some();
157    let mut walk = Walk {
158        stack: Vec::new(),
159        last: None,
160        visited: HashSet::new(),
161        queue: std::collections::VecDeque::new(),
162        phase,
163        statics: statics.to_vec(),
164    };
165    if live {
166        match phase {
167            Phase::Pre => walk.stack.push(root),
168            Phase::Post => walk.queue = post_order(root, statics),
169        }
170    }
171    let mut w = walks().lock().unwrap_or_else(|e| e.into_inner());
172    let token = w.next_token;
173    w.next_token += 1;
174    w.open.insert(token, walk);
175    token
176}
177
178/// The next object of the walk `token` to run its hook on; `0` when the walk
179/// is over (or `token` is not an open walk).
180pub fn next(token: u64) -> u64 {
181    let mut w = walks().lock().unwrap_or_else(|e| e.into_inner());
182    let Some(walk) = w.open.get_mut(&token) else {
183        return 0;
184    };
185    match walk.phase {
186        Phase::Post => walk.queue.pop_front().unwrap_or(0),
187        Phase::Pre => {
188            if let Some(done) = walk.last.take() {
189                // Its hook has run: now its members are known. Pushed in
190                // reverse so the first-declared one is visited first.
191                for child in members(done, &walk.statics).into_iter().rev() {
192                    walk.stack.push(child);
193                }
194            }
195            while let Some(h) = walk.stack.pop() {
196                if objects::dynamic_template(h).is_some() && walk.visited.insert(h) {
197                    walk.last = Some(h);
198                    return h;
199                }
200            }
201            0
202        }
203    }
204}
205
206/// Closes the walk `token` (a no-op for an unknown token).
207pub fn end(token: u64) {
208    walks()
209        .lock()
210        .unwrap_or_else(|e| e.into_inner())
211        .open
212        .remove(&token);
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::constraints::registry::{self, Home, ProgHandle, Program};
219    use crate::netlist::ClassTemplateId;
220    use crate::objects::{FieldSlot, RandKind, RandProp, alloc, register_rand_props};
221
222    const LEFT: FieldSlot = FieldSlot {
223        offset: 0,
224        words: 2,
225    };
226    const RIGHT: FieldSlot = FieldSlot {
227        offset: 2,
228        words: 2,
229    };
230
231    /// A class `{ rand Node left; rand Node right; }` (rand props 0 and 1).
232    fn node_class(id: u32) -> ClassTemplateId {
233        let t = ClassTemplateId(id);
234        register_rand_props(
235            t,
236            vec![
237                RandProp {
238                    slot: LEFT,
239                    kind: RandKind::Rand,
240                },
241                RandProp {
242                    slot: RIGHT,
243                    kind: RandKind::Rand,
244                },
245            ],
246        );
247        registry::register_program(
248            t,
249            Ok(Program {
250                rand_handles: vec![
251                    ProgHandle {
252                        slot: LEFT,
253                        home: Home::Instance,
254                        prop_index: Some(0),
255                        bit: 0,
256                    },
257                    ProgHandle {
258                        slot: RIGHT,
259                        home: Home::Instance,
260                        prop_index: Some(1),
261                        bit: 0,
262                    },
263                ],
264                ..Program::default()
265            }),
266        );
267        t
268    }
269
270    fn link(parent: u64, left: u64, right: u64) {
271        assert!(objects::field_set(parent, LEFT, &[left, 0]));
272        assert!(objects::field_set(parent, RIGHT, &[right, 0]));
273    }
274
275    fn drain(root: u64, phase: Phase) -> Vec<u64> {
276        let token = begin(root, phase, &[]);
277        let mut out = Vec::new();
278        loop {
279            let h = next(token);
280            if h == 0 {
281                break;
282            }
283            out.push(h);
284            assert!(out.len() < 100, "a walk must terminate");
285        }
286        end(token);
287        out
288    }
289
290    /// `root(a(c, d), b)`.
291    fn tree(id: u32) -> [u64; 5] {
292        let t = node_class(id);
293        let [root, a, b, c, d] = [(); 5].map(|()| alloc(t, 4));
294        link(root, a, b);
295        link(a, c, d);
296        [root, a, b, c, d]
297    }
298
299    #[test]
300    fn pre_order_visits_a_container_before_its_members_in_declaration_order() {
301        let [root, a, b, c, d] = tree(0x7c00_0001);
302        assert_eq!(drain(root, Phase::Pre), vec![root, a, c, d, b]);
303    }
304
305    #[test]
306    fn post_order_visits_members_before_their_container() {
307        let [root, a, b, c, d] = tree(0x7c00_0002);
308        assert_eq!(drain(root, Phase::Post), vec![c, d, a, b, root]);
309    }
310
311    #[test]
312    fn a_cycle_and_a_shared_member_are_visited_once() {
313        let t = node_class(0x7c00_0003);
314        let [p, q, r] = [(); 3].map(|()| alloc(t, 4));
315        link(p, q, r);
316        link(q, p, r); // back edge to p, and r reached twice
317        assert_eq!(drain(p, Phase::Pre), vec![p, q, r]);
318        assert_eq!(drain(p, Phase::Post), vec![r, q, p]);
319    }
320
321    #[test]
322    fn a_member_turned_off_by_rand_mode_is_skipped() {
323        let [root, a, b, _, _] = tree(0x7c00_0004);
324        let mut meta = objects::rand_meta(root).expect("meta");
325        meta.rand_mode[0] = false; // `left` off
326        objects::set_rand_meta(root, meta);
327        assert_eq!(drain(root, Phase::Pre), vec![root, b]);
328        assert_eq!(drain(root, Phase::Post), vec![b, root]);
329        // `a` itself is still a perfectly good root
330        assert_eq!(drain(a, Phase::Pre).len(), 3);
331    }
332
333    #[test]
334    fn pre_order_sees_a_member_change_made_by_the_container_hook() {
335        // The container's hook runs between `next` calls: a rand_mode flip it
336        // makes before the walk resumes decides which members are visited.
337        let [root, a, b, c, d] = tree(0x7c00_0005);
338        let token = begin(root, Phase::Pre, &[]);
339        assert_eq!(next(token), root);
340        let mut meta = objects::rand_meta(root).expect("meta");
341        meta.rand_mode[1] = false; // the hook turns `right` off
342        objects::set_rand_meta(root, meta);
343        assert_eq!(next(token), a);
344        assert_eq!(next(token), c);
345        assert_eq!(next(token), d);
346        assert_eq!(
347            next(token),
348            0,
349            "b was switched off before it was discovered"
350        );
351        end(token);
352        let _ = b;
353    }
354
355    #[test]
356    fn null_members_and_a_dead_root_visit_nothing_extra() {
357        let t = node_class(0x7c00_0006);
358        let lone = alloc(t, 4);
359        assert_eq!(drain(lone, Phase::Pre), vec![lone]);
360        assert_eq!(drain(lone, Phase::Post), vec![lone]);
361        assert_eq!(drain(0, Phase::Pre), Vec::<u64>::new());
362        assert_eq!(drain(0, Phase::Post), Vec::<u64>::new());
363    }
364
365    #[test]
366    fn an_unknown_token_is_over_and_end_is_idempotent() {
367        assert_eq!(next(u64::MAX), 0);
368        end(u64::MAX);
369        let t = node_class(0x7c00_0007);
370        let h = alloc(t, 4);
371        let token = begin(h, Phase::Pre, &[]);
372        end(token);
373        assert_eq!(next(token), 0, "a closed walk yields nothing");
374    }
375
376    #[test]
377    fn phase_codes_round_trip() {
378        for p in [Phase::Pre, Phase::Post] {
379            assert_eq!(Phase::from_code(p.code()), Some(p));
380        }
381        assert_eq!(Phase::from_code(2), None);
382    }
383}