1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum Phase {
39 Pre,
41 Post,
43}
44
45impl Phase {
46 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 stack: Vec<u64>,
66 last: Option<u64>,
69 visited: HashSet<u64>,
70 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
91fn 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
128fn 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 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
151pub 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
178pub 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 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
206pub 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 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 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); 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; 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 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 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; 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}