Skip to main content

oxilean_codegen/verilog_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use std::collections::{HashMap, HashSet, VecDeque};
7
8/// Pass registry for VerilogExt.
9#[allow(dead_code)]
10#[derive(Debug, Default)]
11pub struct VerilogExtPassRegistry {
12    pub(super) configs: Vec<VerilogExtPassConfig>,
13    pub(super) stats: Vec<VerilogExtPassStats>,
14}
15impl VerilogExtPassRegistry {
16    #[allow(dead_code)]
17    pub fn new() -> Self {
18        Self::default()
19    }
20    #[allow(dead_code)]
21    pub fn register(&mut self, c: VerilogExtPassConfig) {
22        self.stats.push(VerilogExtPassStats::new());
23        self.configs.push(c);
24    }
25    #[allow(dead_code)]
26    pub fn len(&self) -> usize {
27        self.configs.len()
28    }
29    #[allow(dead_code)]
30    pub fn is_empty(&self) -> bool {
31        self.configs.is_empty()
32    }
33    #[allow(dead_code)]
34    pub fn get(&self, i: usize) -> Option<&VerilogExtPassConfig> {
35        self.configs.get(i)
36    }
37    #[allow(dead_code)]
38    pub fn get_stats(&self, i: usize) -> Option<&VerilogExtPassStats> {
39        self.stats.get(i)
40    }
41    #[allow(dead_code)]
42    pub fn enabled_passes(&self) -> Vec<&VerilogExtPassConfig> {
43        self.configs.iter().filter(|c| c.enabled).collect()
44    }
45    #[allow(dead_code)]
46    pub fn passes_in_phase(&self, ph: &VerilogExtPassPhase) -> Vec<&VerilogExtPassConfig> {
47        self.configs
48            .iter()
49            .filter(|c| c.enabled && &c.phase == ph)
50            .collect()
51    }
52    #[allow(dead_code)]
53    pub fn total_nodes_visited(&self) -> usize {
54        self.stats.iter().map(|s| s.nodes_visited).sum()
55    }
56    #[allow(dead_code)]
57    pub fn any_changed(&self) -> bool {
58        self.stats.iter().any(|s| s.changed)
59    }
60}
61#[allow(dead_code)]
62#[derive(Debug, Clone)]
63pub struct VlogPassConfig {
64    pub phase: VlogPassPhase,
65    pub enabled: bool,
66    pub max_iterations: u32,
67    pub debug_output: bool,
68    pub pass_name: String,
69}
70impl VlogPassConfig {
71    #[allow(dead_code)]
72    pub fn new(name: impl Into<String>, phase: VlogPassPhase) -> Self {
73        VlogPassConfig {
74            phase,
75            enabled: true,
76            max_iterations: 10,
77            debug_output: false,
78            pass_name: name.into(),
79        }
80    }
81    #[allow(dead_code)]
82    pub fn disabled(mut self) -> Self {
83        self.enabled = false;
84        self
85    }
86    #[allow(dead_code)]
87    pub fn with_debug(mut self) -> Self {
88        self.debug_output = true;
89        self
90    }
91    #[allow(dead_code)]
92    pub fn max_iter(mut self, n: u32) -> Self {
93        self.max_iterations = n;
94        self
95    }
96}
97/// Dominator tree for VerilogX2.
98#[allow(dead_code)]
99#[derive(Debug, Clone)]
100pub struct VerilogX2DomTree {
101    pub(super) idom: Vec<Option<usize>>,
102    pub(super) children: Vec<Vec<usize>>,
103    pub(super) depth: Vec<usize>,
104}
105impl VerilogX2DomTree {
106    #[allow(dead_code)]
107    pub fn new(n: usize) -> Self {
108        Self {
109            idom: vec![None; n],
110            children: vec![Vec::new(); n],
111            depth: vec![0; n],
112        }
113    }
114    #[allow(dead_code)]
115    pub fn set_idom(&mut self, node: usize, dom: usize) {
116        if node < self.idom.len() {
117            self.idom[node] = Some(dom);
118            if dom < self.children.len() {
119                self.children[dom].push(node);
120            }
121            self.depth[node] = if dom < self.depth.len() {
122                self.depth[dom] + 1
123            } else {
124                1
125            };
126        }
127    }
128    #[allow(dead_code)]
129    pub fn dominates(&self, a: usize, mut b: usize) -> bool {
130        if a == b {
131            return true;
132        }
133        let n = self.idom.len();
134        for _ in 0..n {
135            match self.idom.get(b).copied().flatten() {
136                None => return false,
137                Some(p) if p == a => return true,
138                Some(p) if p == b => return false,
139                Some(p) => b = p,
140            }
141        }
142        false
143    }
144    #[allow(dead_code)]
145    pub fn children_of(&self, n: usize) -> &[usize] {
146        self.children.get(n).map(|v| v.as_slice()).unwrap_or(&[])
147    }
148    #[allow(dead_code)]
149    pub fn depth_of(&self, n: usize) -> usize {
150        self.depth.get(n).copied().unwrap_or(0)
151    }
152    #[allow(dead_code)]
153    pub fn lca(&self, mut a: usize, mut b: usize) -> usize {
154        let n = self.idom.len();
155        for _ in 0..(2 * n) {
156            if a == b {
157                return a;
158            }
159            if self.depth_of(a) > self.depth_of(b) {
160                a = self.idom.get(a).and_then(|x| *x).unwrap_or(a);
161            } else {
162                b = self.idom.get(b).and_then(|x| *x).unwrap_or(b);
163            }
164        }
165        0
166    }
167}
168/// Statistics for VerilogX2 passes.
169#[allow(dead_code)]
170#[derive(Debug, Clone, Default)]
171pub struct VerilogX2PassStats {
172    pub iterations: usize,
173    pub changed: bool,
174    pub nodes_visited: usize,
175    pub nodes_modified: usize,
176    pub time_ms: u64,
177    pub memory_bytes: usize,
178    pub errors: usize,
179}
180impl VerilogX2PassStats {
181    #[allow(dead_code)]
182    pub fn new() -> Self {
183        Self::default()
184    }
185    #[allow(dead_code)]
186    pub fn visit(&mut self) {
187        self.nodes_visited += 1;
188    }
189    #[allow(dead_code)]
190    pub fn modify(&mut self) {
191        self.nodes_modified += 1;
192        self.changed = true;
193    }
194    #[allow(dead_code)]
195    pub fn iterate(&mut self) {
196        self.iterations += 1;
197    }
198    #[allow(dead_code)]
199    pub fn error(&mut self) {
200        self.errors += 1;
201    }
202    #[allow(dead_code)]
203    pub fn efficiency(&self) -> f64 {
204        if self.nodes_visited == 0 {
205            0.0
206        } else {
207            self.nodes_modified as f64 / self.nodes_visited as f64
208        }
209    }
210    #[allow(dead_code)]
211    pub fn merge(&mut self, o: &VerilogX2PassStats) {
212        self.iterations += o.iterations;
213        self.changed |= o.changed;
214        self.nodes_visited += o.nodes_visited;
215        self.nodes_modified += o.nodes_modified;
216        self.time_ms += o.time_ms;
217        self.memory_bytes = self.memory_bytes.max(o.memory_bytes);
218        self.errors += o.errors;
219    }
220}
221#[allow(dead_code)]
222#[derive(Debug, Clone)]
223pub struct VlogLivenessInfo {
224    pub live_in: Vec<std::collections::HashSet<u32>>,
225    pub live_out: Vec<std::collections::HashSet<u32>>,
226    pub defs: Vec<std::collections::HashSet<u32>>,
227    pub uses: Vec<std::collections::HashSet<u32>>,
228}
229impl VlogLivenessInfo {
230    #[allow(dead_code)]
231    pub fn new(block_count: usize) -> Self {
232        VlogLivenessInfo {
233            live_in: vec![std::collections::HashSet::new(); block_count],
234            live_out: vec![std::collections::HashSet::new(); block_count],
235            defs: vec![std::collections::HashSet::new(); block_count],
236            uses: vec![std::collections::HashSet::new(); block_count],
237        }
238    }
239    #[allow(dead_code)]
240    pub fn add_def(&mut self, block: usize, var: u32) {
241        if block < self.defs.len() {
242            self.defs[block].insert(var);
243        }
244    }
245    #[allow(dead_code)]
246    pub fn add_use(&mut self, block: usize, var: u32) {
247        if block < self.uses.len() {
248            self.uses[block].insert(var);
249        }
250    }
251    #[allow(dead_code)]
252    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
253        self.live_in
254            .get(block)
255            .map(|s| s.contains(&var))
256            .unwrap_or(false)
257    }
258    #[allow(dead_code)]
259    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
260        self.live_out
261            .get(block)
262            .map(|s| s.contains(&var))
263            .unwrap_or(false)
264    }
265}
266/// Statistics for VerilogExt passes.
267#[allow(dead_code)]
268#[derive(Debug, Clone, Default)]
269pub struct VerilogExtPassStats {
270    pub iterations: usize,
271    pub changed: bool,
272    pub nodes_visited: usize,
273    pub nodes_modified: usize,
274    pub time_ms: u64,
275    pub memory_bytes: usize,
276    pub errors: usize,
277}
278impl VerilogExtPassStats {
279    #[allow(dead_code)]
280    pub fn new() -> Self {
281        Self::default()
282    }
283    #[allow(dead_code)]
284    pub fn visit(&mut self) {
285        self.nodes_visited += 1;
286    }
287    #[allow(dead_code)]
288    pub fn modify(&mut self) {
289        self.nodes_modified += 1;
290        self.changed = true;
291    }
292    #[allow(dead_code)]
293    pub fn iterate(&mut self) {
294        self.iterations += 1;
295    }
296    #[allow(dead_code)]
297    pub fn error(&mut self) {
298        self.errors += 1;
299    }
300    #[allow(dead_code)]
301    pub fn efficiency(&self) -> f64 {
302        if self.nodes_visited == 0 {
303            0.0
304        } else {
305            self.nodes_modified as f64 / self.nodes_visited as f64
306        }
307    }
308    #[allow(dead_code)]
309    pub fn merge(&mut self, o: &VerilogExtPassStats) {
310        self.iterations += o.iterations;
311        self.changed |= o.changed;
312        self.nodes_visited += o.nodes_visited;
313        self.nodes_modified += o.nodes_modified;
314        self.time_ms += o.time_ms;
315        self.memory_bytes = self.memory_bytes.max(o.memory_bytes);
316        self.errors += o.errors;
317    }
318}
319/// Dependency graph for VerilogExt.
320#[allow(dead_code)]
321#[derive(Debug, Clone)]
322pub struct VerilogExtDepGraph {
323    pub(super) n: usize,
324    pub(super) adj: Vec<Vec<usize>>,
325    pub(super) rev: Vec<Vec<usize>>,
326    pub(super) edge_count: usize,
327}
328impl VerilogExtDepGraph {
329    #[allow(dead_code)]
330    pub fn new(n: usize) -> Self {
331        Self {
332            n,
333            adj: vec![Vec::new(); n],
334            rev: vec![Vec::new(); n],
335            edge_count: 0,
336        }
337    }
338    #[allow(dead_code)]
339    pub fn add_edge(&mut self, from: usize, to: usize) {
340        if from < self.n && to < self.n {
341            if !self.adj[from].contains(&to) {
342                self.adj[from].push(to);
343                self.rev[to].push(from);
344                self.edge_count += 1;
345            }
346        }
347    }
348    #[allow(dead_code)]
349    pub fn succs(&self, n: usize) -> &[usize] {
350        self.adj.get(n).map(|v| v.as_slice()).unwrap_or(&[])
351    }
352    #[allow(dead_code)]
353    pub fn preds(&self, n: usize) -> &[usize] {
354        self.rev.get(n).map(|v| v.as_slice()).unwrap_or(&[])
355    }
356    #[allow(dead_code)]
357    pub fn topo_sort(&self) -> Option<Vec<usize>> {
358        let mut deg: Vec<usize> = (0..self.n).map(|i| self.rev[i].len()).collect();
359        let mut q: std::collections::VecDeque<usize> =
360            (0..self.n).filter(|&i| deg[i] == 0).collect();
361        let mut out = Vec::with_capacity(self.n);
362        while let Some(u) = q.pop_front() {
363            out.push(u);
364            for &v in &self.adj[u] {
365                deg[v] -= 1;
366                if deg[v] == 0 {
367                    q.push_back(v);
368                }
369            }
370        }
371        if out.len() == self.n {
372            Some(out)
373        } else {
374            None
375        }
376    }
377    #[allow(dead_code)]
378    pub fn has_cycle(&self) -> bool {
379        self.topo_sort().is_none()
380    }
381    #[allow(dead_code)]
382    pub fn reachable(&self, start: usize) -> Vec<usize> {
383        let mut vis = vec![false; self.n];
384        let mut stk = vec![start];
385        let mut out = Vec::new();
386        while let Some(u) = stk.pop() {
387            if u < self.n && !vis[u] {
388                vis[u] = true;
389                out.push(u);
390                for &v in &self.adj[u] {
391                    if !vis[v] {
392                        stk.push(v);
393                    }
394                }
395            }
396        }
397        out
398    }
399    #[allow(dead_code)]
400    pub fn scc(&self) -> Vec<Vec<usize>> {
401        let mut visited = vec![false; self.n];
402        let mut order = Vec::new();
403        for i in 0..self.n {
404            if !visited[i] {
405                let mut stk = vec![(i, 0usize)];
406                while let Some((u, idx)) = stk.last_mut() {
407                    if !visited[*u] {
408                        visited[*u] = true;
409                    }
410                    if *idx < self.adj[*u].len() {
411                        let v = self.adj[*u][*idx];
412                        *idx += 1;
413                        if !visited[v] {
414                            stk.push((v, 0));
415                        }
416                    } else {
417                        order.push(*u);
418                        stk.pop();
419                    }
420                }
421            }
422        }
423        let mut comp = vec![usize::MAX; self.n];
424        let mut components: Vec<Vec<usize>> = Vec::new();
425        for &start in order.iter().rev() {
426            if comp[start] == usize::MAX {
427                let cid = components.len();
428                let mut component = Vec::new();
429                let mut stk = vec![start];
430                while let Some(u) = stk.pop() {
431                    if comp[u] == usize::MAX {
432                        comp[u] = cid;
433                        component.push(u);
434                        for &v in &self.rev[u] {
435                            if comp[v] == usize::MAX {
436                                stk.push(v);
437                            }
438                        }
439                    }
440                }
441                components.push(component);
442            }
443        }
444        components
445    }
446    #[allow(dead_code)]
447    pub fn node_count(&self) -> usize {
448        self.n
449    }
450    #[allow(dead_code)]
451    pub fn edge_count(&self) -> usize {
452        self.edge_count
453    }
454}
455/// Pass execution phase for VerilogX2.
456#[allow(dead_code)]
457#[derive(Debug, Clone, PartialEq, Eq, Hash)]
458pub enum VerilogX2PassPhase {
459    Early,
460    Middle,
461    Late,
462    Finalize,
463}
464impl VerilogX2PassPhase {
465    #[allow(dead_code)]
466    pub fn is_early(&self) -> bool {
467        matches!(self, Self::Early)
468    }
469    #[allow(dead_code)]
470    pub fn is_middle(&self) -> bool {
471        matches!(self, Self::Middle)
472    }
473    #[allow(dead_code)]
474    pub fn is_late(&self) -> bool {
475        matches!(self, Self::Late)
476    }
477    #[allow(dead_code)]
478    pub fn is_finalize(&self) -> bool {
479        matches!(self, Self::Finalize)
480    }
481    #[allow(dead_code)]
482    pub fn order(&self) -> u32 {
483        match self {
484            Self::Early => 0,
485            Self::Middle => 1,
486            Self::Late => 2,
487            Self::Finalize => 3,
488        }
489    }
490    #[allow(dead_code)]
491    pub fn from_order(n: u32) -> Option<Self> {
492        match n {
493            0 => Some(Self::Early),
494            1 => Some(Self::Middle),
495            2 => Some(Self::Late),
496            3 => Some(Self::Finalize),
497            _ => None,
498        }
499    }
500}
501/// A complete Verilog / SystemVerilog module definition.
502#[derive(Debug, Clone)]
503pub struct VerilogModule {
504    /// Module name (identifier)
505    pub name: String,
506    /// Port list
507    pub ports: Vec<VerilogPort>,
508    /// `parameter` declarations: `(name, default_value)`
509    pub params: Vec<(String, u64)>,
510    /// Body statements (already-formatted strings)
511    pub body: Vec<String>,
512}
513impl VerilogModule {
514    /// Construct a new empty module.
515    pub fn new(name: impl Into<String>) -> Self {
516        VerilogModule {
517            name: name.into(),
518            ports: Vec::new(),
519            params: Vec::new(),
520            body: Vec::new(),
521        }
522    }
523    /// Add a port.
524    pub fn add_port(&mut self, port: VerilogPort) {
525        self.ports.push(port);
526    }
527    /// Add a parameter with a default value.
528    pub fn add_param(&mut self, name: impl Into<String>, default: u64) {
529        self.params.push((name.into(), default));
530    }
531    /// Append a body statement.
532    pub fn add_stmt(&mut self, stmt: impl Into<String>) {
533        self.body.push(stmt.into());
534    }
535}
536#[allow(dead_code)]
537#[derive(Debug, Clone, Default)]
538pub struct VlogPassStats {
539    pub total_runs: u32,
540    pub successful_runs: u32,
541    pub total_changes: u64,
542    pub time_ms: u64,
543    pub iterations_used: u32,
544}
545impl VlogPassStats {
546    #[allow(dead_code)]
547    pub fn new() -> Self {
548        Self::default()
549    }
550    #[allow(dead_code)]
551    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
552        self.total_runs += 1;
553        self.successful_runs += 1;
554        self.total_changes += changes;
555        self.time_ms += time_ms;
556        self.iterations_used = iterations;
557    }
558    #[allow(dead_code)]
559    pub fn average_changes_per_run(&self) -> f64 {
560        if self.total_runs == 0 {
561            return 0.0;
562        }
563        self.total_changes as f64 / self.total_runs as f64
564    }
565    #[allow(dead_code)]
566    pub fn success_rate(&self) -> f64 {
567        if self.total_runs == 0 {
568            return 0.0;
569        }
570        self.successful_runs as f64 / self.total_runs as f64
571    }
572    #[allow(dead_code)]
573    pub fn format_summary(&self) -> String {
574        format!(
575            "Runs: {}/{}, Changes: {}, Time: {}ms",
576            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
577        )
578    }
579}
580#[allow(dead_code)]
581pub struct VlogPassRegistry {
582    pub(super) configs: Vec<VlogPassConfig>,
583    pub(super) stats: std::collections::HashMap<String, VlogPassStats>,
584}
585impl VlogPassRegistry {
586    #[allow(dead_code)]
587    pub fn new() -> Self {
588        VlogPassRegistry {
589            configs: Vec::new(),
590            stats: std::collections::HashMap::new(),
591        }
592    }
593    #[allow(dead_code)]
594    pub fn register(&mut self, config: VlogPassConfig) {
595        self.stats
596            .insert(config.pass_name.clone(), VlogPassStats::new());
597        self.configs.push(config);
598    }
599    #[allow(dead_code)]
600    pub fn enabled_passes(&self) -> Vec<&VlogPassConfig> {
601        self.configs.iter().filter(|c| c.enabled).collect()
602    }
603    #[allow(dead_code)]
604    pub fn get_stats(&self, name: &str) -> Option<&VlogPassStats> {
605        self.stats.get(name)
606    }
607    #[allow(dead_code)]
608    pub fn total_passes(&self) -> usize {
609        self.configs.len()
610    }
611    #[allow(dead_code)]
612    pub fn enabled_count(&self) -> usize {
613        self.enabled_passes().len()
614    }
615    #[allow(dead_code)]
616    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
617        if let Some(stats) = self.stats.get_mut(name) {
618            stats.record_run(changes, time_ms, iter);
619        }
620    }
621}
622/// Pass execution phase for VerilogExt.
623#[allow(dead_code)]
624#[derive(Debug, Clone, PartialEq, Eq, Hash)]
625pub enum VerilogExtPassPhase {
626    Early,
627    Middle,
628    Late,
629    Finalize,
630}
631impl VerilogExtPassPhase {
632    #[allow(dead_code)]
633    pub fn is_early(&self) -> bool {
634        matches!(self, Self::Early)
635    }
636    #[allow(dead_code)]
637    pub fn is_middle(&self) -> bool {
638        matches!(self, Self::Middle)
639    }
640    #[allow(dead_code)]
641    pub fn is_late(&self) -> bool {
642        matches!(self, Self::Late)
643    }
644    #[allow(dead_code)]
645    pub fn is_finalize(&self) -> bool {
646        matches!(self, Self::Finalize)
647    }
648    #[allow(dead_code)]
649    pub fn order(&self) -> u32 {
650        match self {
651            Self::Early => 0,
652            Self::Middle => 1,
653            Self::Late => 2,
654            Self::Finalize => 3,
655        }
656    }
657    #[allow(dead_code)]
658    pub fn from_order(n: u32) -> Option<Self> {
659        match n {
660            0 => Some(Self::Early),
661            1 => Some(Self::Middle),
662            2 => Some(Self::Late),
663            3 => Some(Self::Finalize),
664            _ => None,
665        }
666    }
667}
668/// Configuration for VerilogExt passes.
669#[allow(dead_code)]
670#[derive(Debug, Clone)]
671pub struct VerilogExtPassConfig {
672    pub name: String,
673    pub phase: VerilogExtPassPhase,
674    pub enabled: bool,
675    pub max_iterations: usize,
676    pub debug: u32,
677    pub timeout_ms: Option<u64>,
678}
679impl VerilogExtPassConfig {
680    #[allow(dead_code)]
681    pub fn new(name: impl Into<String>) -> Self {
682        Self {
683            name: name.into(),
684            phase: VerilogExtPassPhase::Middle,
685            enabled: true,
686            max_iterations: 100,
687            debug: 0,
688            timeout_ms: None,
689        }
690    }
691    #[allow(dead_code)]
692    pub fn with_phase(mut self, phase: VerilogExtPassPhase) -> Self {
693        self.phase = phase;
694        self
695    }
696    #[allow(dead_code)]
697    pub fn with_max_iter(mut self, n: usize) -> Self {
698        self.max_iterations = n;
699        self
700    }
701    #[allow(dead_code)]
702    pub fn with_debug(mut self, d: u32) -> Self {
703        self.debug = d;
704        self
705    }
706    #[allow(dead_code)]
707    pub fn disabled(mut self) -> Self {
708        self.enabled = false;
709        self
710    }
711    #[allow(dead_code)]
712    pub fn with_timeout(mut self, ms: u64) -> Self {
713        self.timeout_ms = Some(ms);
714        self
715    }
716    #[allow(dead_code)]
717    pub fn is_debug_enabled(&self) -> bool {
718        self.debug > 0
719    }
720}
721#[allow(dead_code)]
722#[derive(Debug, Clone, PartialEq)]
723pub enum VlogPassPhase {
724    Analysis,
725    Transformation,
726    Verification,
727    Cleanup,
728}
729impl VlogPassPhase {
730    #[allow(dead_code)]
731    pub fn name(&self) -> &str {
732        match self {
733            VlogPassPhase::Analysis => "analysis",
734            VlogPassPhase::Transformation => "transformation",
735            VlogPassPhase::Verification => "verification",
736            VlogPassPhase::Cleanup => "cleanup",
737        }
738    }
739    #[allow(dead_code)]
740    pub fn is_modifying(&self) -> bool {
741        matches!(self, VlogPassPhase::Transformation | VlogPassPhase::Cleanup)
742    }
743}
744#[allow(dead_code)]
745#[derive(Debug, Clone)]
746pub struct VlogDepGraph {
747    pub(super) nodes: Vec<u32>,
748    pub(super) edges: Vec<(u32, u32)>,
749}
750impl VlogDepGraph {
751    #[allow(dead_code)]
752    pub fn new() -> Self {
753        VlogDepGraph {
754            nodes: Vec::new(),
755            edges: Vec::new(),
756        }
757    }
758    #[allow(dead_code)]
759    pub fn add_node(&mut self, id: u32) {
760        if !self.nodes.contains(&id) {
761            self.nodes.push(id);
762        }
763    }
764    #[allow(dead_code)]
765    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
766        self.add_node(dep);
767        self.add_node(dependent);
768        self.edges.push((dep, dependent));
769    }
770    #[allow(dead_code)]
771    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
772        self.edges
773            .iter()
774            .filter(|(d, _)| *d == node)
775            .map(|(_, dep)| *dep)
776            .collect()
777    }
778    #[allow(dead_code)]
779    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
780        self.edges
781            .iter()
782            .filter(|(_, dep)| *dep == node)
783            .map(|(d, _)| *d)
784            .collect()
785    }
786    #[allow(dead_code)]
787    pub fn topological_sort(&self) -> Vec<u32> {
788        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
789        for &n in &self.nodes {
790            in_degree.insert(n, 0);
791        }
792        for (_, dep) in &self.edges {
793            *in_degree.entry(*dep).or_insert(0) += 1;
794        }
795        let mut queue: std::collections::VecDeque<u32> = self
796            .nodes
797            .iter()
798            .filter(|&&n| in_degree[&n] == 0)
799            .copied()
800            .collect();
801        let mut result = Vec::new();
802        while let Some(node) = queue.pop_front() {
803            result.push(node);
804            for dep in self.dependents_of(node) {
805                let cnt = in_degree.entry(dep).or_insert(0);
806                *cnt = cnt.saturating_sub(1);
807                if *cnt == 0 {
808                    queue.push_back(dep);
809                }
810            }
811        }
812        result
813    }
814    #[allow(dead_code)]
815    pub fn has_cycle(&self) -> bool {
816        self.topological_sort().len() < self.nodes.len()
817    }
818}
819#[allow(dead_code)]
820#[derive(Debug, Clone)]
821pub struct VlogAnalysisCache {
822    pub(super) entries: std::collections::HashMap<String, VlogCacheEntry>,
823    pub(super) max_size: usize,
824    pub(super) hits: u64,
825    pub(super) misses: u64,
826}
827impl VlogAnalysisCache {
828    #[allow(dead_code)]
829    pub fn new(max_size: usize) -> Self {
830        VlogAnalysisCache {
831            entries: std::collections::HashMap::new(),
832            max_size,
833            hits: 0,
834            misses: 0,
835        }
836    }
837    #[allow(dead_code)]
838    pub fn get(&mut self, key: &str) -> Option<&VlogCacheEntry> {
839        if self.entries.contains_key(key) {
840            self.hits += 1;
841            self.entries.get(key)
842        } else {
843            self.misses += 1;
844            None
845        }
846    }
847    #[allow(dead_code)]
848    pub fn insert(&mut self, key: String, data: Vec<u8>) {
849        if self.entries.len() >= self.max_size {
850            if let Some(oldest) = self.entries.keys().next().cloned() {
851                self.entries.remove(&oldest);
852            }
853        }
854        self.entries.insert(
855            key.clone(),
856            VlogCacheEntry {
857                key,
858                data,
859                timestamp: 0,
860                valid: true,
861            },
862        );
863    }
864    #[allow(dead_code)]
865    pub fn invalidate(&mut self, key: &str) {
866        if let Some(entry) = self.entries.get_mut(key) {
867            entry.valid = false;
868        }
869    }
870    #[allow(dead_code)]
871    pub fn clear(&mut self) {
872        self.entries.clear();
873    }
874    #[allow(dead_code)]
875    pub fn hit_rate(&self) -> f64 {
876        let total = self.hits + self.misses;
877        if total == 0 {
878            return 0.0;
879        }
880        self.hits as f64 / total as f64
881    }
882    #[allow(dead_code)]
883    pub fn size(&self) -> usize {
884        self.entries.len()
885    }
886}
887/// Worklist for VerilogX2.
888#[allow(dead_code)]
889#[derive(Debug, Clone)]
890pub struct VerilogX2Worklist {
891    pub(super) items: std::collections::VecDeque<usize>,
892    pub(super) present: Vec<bool>,
893}
894impl VerilogX2Worklist {
895    #[allow(dead_code)]
896    pub fn new(capacity: usize) -> Self {
897        Self {
898            items: std::collections::VecDeque::new(),
899            present: vec![false; capacity],
900        }
901    }
902    #[allow(dead_code)]
903    pub fn push(&mut self, id: usize) {
904        if id < self.present.len() && !self.present[id] {
905            self.present[id] = true;
906            self.items.push_back(id);
907        }
908    }
909    #[allow(dead_code)]
910    pub fn push_front(&mut self, id: usize) {
911        if id < self.present.len() && !self.present[id] {
912            self.present[id] = true;
913            self.items.push_front(id);
914        }
915    }
916    #[allow(dead_code)]
917    pub fn pop(&mut self) -> Option<usize> {
918        let id = self.items.pop_front()?;
919        if id < self.present.len() {
920            self.present[id] = false;
921        }
922        Some(id)
923    }
924    #[allow(dead_code)]
925    pub fn is_empty(&self) -> bool {
926        self.items.is_empty()
927    }
928    #[allow(dead_code)]
929    pub fn len(&self) -> usize {
930        self.items.len()
931    }
932    #[allow(dead_code)]
933    pub fn contains(&self, id: usize) -> bool {
934        id < self.present.len() && self.present[id]
935    }
936    #[allow(dead_code)]
937    pub fn drain_all(&mut self) -> Vec<usize> {
938        let v: Vec<usize> = self.items.drain(..).collect();
939        for &id in &v {
940            if id < self.present.len() {
941                self.present[id] = false;
942            }
943        }
944        v
945    }
946}
947/// Dominator tree for VerilogExt.
948#[allow(dead_code)]
949#[derive(Debug, Clone)]
950pub struct VerilogExtDomTree {
951    pub(super) idom: Vec<Option<usize>>,
952    pub(super) children: Vec<Vec<usize>>,
953    pub(super) depth: Vec<usize>,
954}
955impl VerilogExtDomTree {
956    #[allow(dead_code)]
957    pub fn new(n: usize) -> Self {
958        Self {
959            idom: vec![None; n],
960            children: vec![Vec::new(); n],
961            depth: vec![0; n],
962        }
963    }
964    #[allow(dead_code)]
965    pub fn set_idom(&mut self, node: usize, dom: usize) {
966        if node < self.idom.len() {
967            self.idom[node] = Some(dom);
968            if dom < self.children.len() {
969                self.children[dom].push(node);
970            }
971            self.depth[node] = if dom < self.depth.len() {
972                self.depth[dom] + 1
973            } else {
974                1
975            };
976        }
977    }
978    #[allow(dead_code)]
979    pub fn dominates(&self, a: usize, mut b: usize) -> bool {
980        if a == b {
981            return true;
982        }
983        let n = self.idom.len();
984        for _ in 0..n {
985            match self.idom.get(b).copied().flatten() {
986                None => return false,
987                Some(p) if p == a => return true,
988                Some(p) if p == b => return false,
989                Some(p) => b = p,
990            }
991        }
992        false
993    }
994    #[allow(dead_code)]
995    pub fn children_of(&self, n: usize) -> &[usize] {
996        self.children.get(n).map(|v| v.as_slice()).unwrap_or(&[])
997    }
998    #[allow(dead_code)]
999    pub fn depth_of(&self, n: usize) -> usize {
1000        self.depth.get(n).copied().unwrap_or(0)
1001    }
1002    #[allow(dead_code)]
1003    pub fn lca(&self, mut a: usize, mut b: usize) -> usize {
1004        let n = self.idom.len();
1005        for _ in 0..(2 * n) {
1006            if a == b {
1007                return a;
1008            }
1009            if self.depth_of(a) > self.depth_of(b) {
1010                a = self.idom.get(a).and_then(|x| *x).unwrap_or(a);
1011            } else {
1012                b = self.idom.get(b).and_then(|x| *x).unwrap_or(b);
1013            }
1014        }
1015        0
1016    }
1017}
1018/// Configuration for VerilogX2 passes.
1019#[allow(dead_code)]
1020#[derive(Debug, Clone)]
1021pub struct VerilogX2PassConfig {
1022    pub name: String,
1023    pub phase: VerilogX2PassPhase,
1024    pub enabled: bool,
1025    pub max_iterations: usize,
1026    pub debug: u32,
1027    pub timeout_ms: Option<u64>,
1028}
1029impl VerilogX2PassConfig {
1030    #[allow(dead_code)]
1031    pub fn new(name: impl Into<String>) -> Self {
1032        Self {
1033            name: name.into(),
1034            phase: VerilogX2PassPhase::Middle,
1035            enabled: true,
1036            max_iterations: 100,
1037            debug: 0,
1038            timeout_ms: None,
1039        }
1040    }
1041    #[allow(dead_code)]
1042    pub fn with_phase(mut self, phase: VerilogX2PassPhase) -> Self {
1043        self.phase = phase;
1044        self
1045    }
1046    #[allow(dead_code)]
1047    pub fn with_max_iter(mut self, n: usize) -> Self {
1048        self.max_iterations = n;
1049        self
1050    }
1051    #[allow(dead_code)]
1052    pub fn with_debug(mut self, d: u32) -> Self {
1053        self.debug = d;
1054        self
1055    }
1056    #[allow(dead_code)]
1057    pub fn disabled(mut self) -> Self {
1058        self.enabled = false;
1059        self
1060    }
1061    #[allow(dead_code)]
1062    pub fn with_timeout(mut self, ms: u64) -> Self {
1063        self.timeout_ms = Some(ms);
1064        self
1065    }
1066    #[allow(dead_code)]
1067    pub fn is_debug_enabled(&self) -> bool {
1068        self.debug > 0
1069    }
1070}
1071/// Liveness analysis for VerilogExt.
1072#[allow(dead_code)]
1073#[derive(Debug, Clone, Default)]
1074pub struct VerilogExtLiveness {
1075    pub live_in: Vec<Vec<usize>>,
1076    pub live_out: Vec<Vec<usize>>,
1077    pub defs: Vec<Vec<usize>>,
1078    pub uses: Vec<Vec<usize>>,
1079}
1080impl VerilogExtLiveness {
1081    #[allow(dead_code)]
1082    pub fn new(n: usize) -> Self {
1083        Self {
1084            live_in: vec![Vec::new(); n],
1085            live_out: vec![Vec::new(); n],
1086            defs: vec![Vec::new(); n],
1087            uses: vec![Vec::new(); n],
1088        }
1089    }
1090    #[allow(dead_code)]
1091    pub fn live_in(&self, b: usize, v: usize) -> bool {
1092        self.live_in.get(b).map(|s| s.contains(&v)).unwrap_or(false)
1093    }
1094    #[allow(dead_code)]
1095    pub fn live_out(&self, b: usize, v: usize) -> bool {
1096        self.live_out
1097            .get(b)
1098            .map(|s| s.contains(&v))
1099            .unwrap_or(false)
1100    }
1101    #[allow(dead_code)]
1102    pub fn add_def(&mut self, b: usize, v: usize) {
1103        if let Some(s) = self.defs.get_mut(b) {
1104            if !s.contains(&v) {
1105                s.push(v);
1106            }
1107        }
1108    }
1109    #[allow(dead_code)]
1110    pub fn add_use(&mut self, b: usize, v: usize) {
1111        if let Some(s) = self.uses.get_mut(b) {
1112            if !s.contains(&v) {
1113                s.push(v);
1114            }
1115        }
1116    }
1117    #[allow(dead_code)]
1118    pub fn var_is_used_in_block(&self, b: usize, v: usize) -> bool {
1119        self.uses.get(b).map(|s| s.contains(&v)).unwrap_or(false)
1120    }
1121    #[allow(dead_code)]
1122    pub fn var_is_def_in_block(&self, b: usize, v: usize) -> bool {
1123        self.defs.get(b).map(|s| s.contains(&v)).unwrap_or(false)
1124    }
1125}
1126/// Dependency graph for VerilogX2.
1127#[allow(dead_code)]
1128#[derive(Debug, Clone)]
1129pub struct VerilogX2DepGraph {
1130    pub(super) n: usize,
1131    pub(super) adj: Vec<Vec<usize>>,
1132    pub(super) rev: Vec<Vec<usize>>,
1133    pub(super) edge_count: usize,
1134}
1135impl VerilogX2DepGraph {
1136    #[allow(dead_code)]
1137    pub fn new(n: usize) -> Self {
1138        Self {
1139            n,
1140            adj: vec![Vec::new(); n],
1141            rev: vec![Vec::new(); n],
1142            edge_count: 0,
1143        }
1144    }
1145    #[allow(dead_code)]
1146    pub fn add_edge(&mut self, from: usize, to: usize) {
1147        if from < self.n && to < self.n {
1148            if !self.adj[from].contains(&to) {
1149                self.adj[from].push(to);
1150                self.rev[to].push(from);
1151                self.edge_count += 1;
1152            }
1153        }
1154    }
1155    #[allow(dead_code)]
1156    pub fn succs(&self, n: usize) -> &[usize] {
1157        self.adj.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1158    }
1159    #[allow(dead_code)]
1160    pub fn preds(&self, n: usize) -> &[usize] {
1161        self.rev.get(n).map(|v| v.as_slice()).unwrap_or(&[])
1162    }
1163    #[allow(dead_code)]
1164    pub fn topo_sort(&self) -> Option<Vec<usize>> {
1165        let mut deg: Vec<usize> = (0..self.n).map(|i| self.rev[i].len()).collect();
1166        let mut q: std::collections::VecDeque<usize> =
1167            (0..self.n).filter(|&i| deg[i] == 0).collect();
1168        let mut out = Vec::with_capacity(self.n);
1169        while let Some(u) = q.pop_front() {
1170            out.push(u);
1171            for &v in &self.adj[u] {
1172                deg[v] -= 1;
1173                if deg[v] == 0 {
1174                    q.push_back(v);
1175                }
1176            }
1177        }
1178        if out.len() == self.n {
1179            Some(out)
1180        } else {
1181            None
1182        }
1183    }
1184    #[allow(dead_code)]
1185    pub fn has_cycle(&self) -> bool {
1186        self.topo_sort().is_none()
1187    }
1188    #[allow(dead_code)]
1189    pub fn reachable(&self, start: usize) -> Vec<usize> {
1190        let mut vis = vec![false; self.n];
1191        let mut stk = vec![start];
1192        let mut out = Vec::new();
1193        while let Some(u) = stk.pop() {
1194            if u < self.n && !vis[u] {
1195                vis[u] = true;
1196                out.push(u);
1197                for &v in &self.adj[u] {
1198                    if !vis[v] {
1199                        stk.push(v);
1200                    }
1201                }
1202            }
1203        }
1204        out
1205    }
1206    #[allow(dead_code)]
1207    pub fn scc(&self) -> Vec<Vec<usize>> {
1208        let mut visited = vec![false; self.n];
1209        let mut order = Vec::new();
1210        for i in 0..self.n {
1211            if !visited[i] {
1212                let mut stk = vec![(i, 0usize)];
1213                while let Some((u, idx)) = stk.last_mut() {
1214                    if !visited[*u] {
1215                        visited[*u] = true;
1216                    }
1217                    if *idx < self.adj[*u].len() {
1218                        let v = self.adj[*u][*idx];
1219                        *idx += 1;
1220                        if !visited[v] {
1221                            stk.push((v, 0));
1222                        }
1223                    } else {
1224                        order.push(*u);
1225                        stk.pop();
1226                    }
1227                }
1228            }
1229        }
1230        let mut comp = vec![usize::MAX; self.n];
1231        let mut components: Vec<Vec<usize>> = Vec::new();
1232        for &start in order.iter().rev() {
1233            if comp[start] == usize::MAX {
1234                let cid = components.len();
1235                let mut component = Vec::new();
1236                let mut stk = vec![start];
1237                while let Some(u) = stk.pop() {
1238                    if comp[u] == usize::MAX {
1239                        comp[u] = cid;
1240                        component.push(u);
1241                        for &v in &self.rev[u] {
1242                            if comp[v] == usize::MAX {
1243                                stk.push(v);
1244                            }
1245                        }
1246                    }
1247                }
1248                components.push(component);
1249            }
1250        }
1251        components
1252    }
1253    #[allow(dead_code)]
1254    pub fn node_count(&self) -> usize {
1255        self.n
1256    }
1257    #[allow(dead_code)]
1258    pub fn edge_count(&self) -> usize {
1259        self.edge_count
1260    }
1261}
1262/// Analysis cache for VerilogExt.
1263#[allow(dead_code)]
1264#[derive(Debug)]
1265pub struct VerilogExtCache {
1266    pub(super) entries: Vec<(u64, Vec<u8>, bool, u32)>,
1267    pub(super) cap: usize,
1268    pub(super) total_hits: u64,
1269    pub(super) total_misses: u64,
1270}
1271impl VerilogExtCache {
1272    #[allow(dead_code)]
1273    pub fn new(cap: usize) -> Self {
1274        Self {
1275            entries: Vec::new(),
1276            cap,
1277            total_hits: 0,
1278            total_misses: 0,
1279        }
1280    }
1281    #[allow(dead_code)]
1282    pub fn get(&mut self, key: u64) -> Option<&[u8]> {
1283        for e in self.entries.iter_mut() {
1284            if e.0 == key && e.2 {
1285                e.3 += 1;
1286                self.total_hits += 1;
1287                return Some(&e.1);
1288            }
1289        }
1290        self.total_misses += 1;
1291        None
1292    }
1293    #[allow(dead_code)]
1294    pub fn put(&mut self, key: u64, data: Vec<u8>) {
1295        if self.entries.len() >= self.cap {
1296            self.entries.retain(|e| e.2);
1297            if self.entries.len() >= self.cap {
1298                self.entries.remove(0);
1299            }
1300        }
1301        self.entries.push((key, data, true, 0));
1302    }
1303    #[allow(dead_code)]
1304    pub fn invalidate(&mut self) {
1305        for e in self.entries.iter_mut() {
1306            e.2 = false;
1307        }
1308    }
1309    #[allow(dead_code)]
1310    pub fn hit_rate(&self) -> f64 {
1311        let t = self.total_hits + self.total_misses;
1312        if t == 0 {
1313            0.0
1314        } else {
1315            self.total_hits as f64 / t as f64
1316        }
1317    }
1318    #[allow(dead_code)]
1319    pub fn live_count(&self) -> usize {
1320        self.entries.iter().filter(|e| e.2).count()
1321    }
1322}
1323/// Module port direction and name.
1324#[derive(Debug, Clone, PartialEq)]
1325pub enum VerilogPort {
1326    /// `input [width-1:0] name`
1327    Input(String, u32),
1328    /// `output [width-1:0] name`
1329    Output(String, u32),
1330    /// `inout [width-1:0] name`
1331    InOut(String, u32),
1332}
1333impl VerilogPort {
1334    /// Port signal name.
1335    pub fn name(&self) -> &str {
1336        match self {
1337            VerilogPort::Input(n, _) => n,
1338            VerilogPort::Output(n, _) => n,
1339            VerilogPort::InOut(n, _) => n,
1340        }
1341    }
1342    /// Port bit-width.
1343    pub fn width(&self) -> u32 {
1344        match self {
1345            VerilogPort::Input(_, w) => *w,
1346            VerilogPort::Output(_, w) => *w,
1347            VerilogPort::InOut(_, w) => *w,
1348        }
1349    }
1350    /// Port direction keyword.
1351    pub fn direction(&self) -> &'static str {
1352        match self {
1353            VerilogPort::Input(..) => "input",
1354            VerilogPort::Output(..) => "output",
1355            VerilogPort::InOut(..) => "inout",
1356        }
1357    }
1358}
1359/// Constant folding helper for VerilogExt.
1360#[allow(dead_code)]
1361#[derive(Debug, Clone, Default)]
1362pub struct VerilogExtConstFolder {
1363    pub(super) folds: usize,
1364    pub(super) failures: usize,
1365    pub(super) enabled: bool,
1366}
1367impl VerilogExtConstFolder {
1368    #[allow(dead_code)]
1369    pub fn new() -> Self {
1370        Self {
1371            folds: 0,
1372            failures: 0,
1373            enabled: true,
1374        }
1375    }
1376    #[allow(dead_code)]
1377    pub fn add_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1378        self.folds += 1;
1379        a.checked_add(b)
1380    }
1381    #[allow(dead_code)]
1382    pub fn sub_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1383        self.folds += 1;
1384        a.checked_sub(b)
1385    }
1386    #[allow(dead_code)]
1387    pub fn mul_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1388        self.folds += 1;
1389        a.checked_mul(b)
1390    }
1391    #[allow(dead_code)]
1392    pub fn div_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1393        if b == 0 {
1394            self.failures += 1;
1395            None
1396        } else {
1397            self.folds += 1;
1398            a.checked_div(b)
1399        }
1400    }
1401    #[allow(dead_code)]
1402    pub fn rem_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1403        if b == 0 {
1404            self.failures += 1;
1405            None
1406        } else {
1407            self.folds += 1;
1408            a.checked_rem(b)
1409        }
1410    }
1411    #[allow(dead_code)]
1412    pub fn neg_i64(&mut self, a: i64) -> Option<i64> {
1413        self.folds += 1;
1414        a.checked_neg()
1415    }
1416    #[allow(dead_code)]
1417    pub fn shl_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1418        if s >= 64 {
1419            self.failures += 1;
1420            None
1421        } else {
1422            self.folds += 1;
1423            a.checked_shl(s)
1424        }
1425    }
1426    #[allow(dead_code)]
1427    pub fn shr_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1428        if s >= 64 {
1429            self.failures += 1;
1430            None
1431        } else {
1432            self.folds += 1;
1433            a.checked_shr(s)
1434        }
1435    }
1436    #[allow(dead_code)]
1437    pub fn and_i64(&mut self, a: i64, b: i64) -> i64 {
1438        self.folds += 1;
1439        a & b
1440    }
1441    #[allow(dead_code)]
1442    pub fn or_i64(&mut self, a: i64, b: i64) -> i64 {
1443        self.folds += 1;
1444        a | b
1445    }
1446    #[allow(dead_code)]
1447    pub fn xor_i64(&mut self, a: i64, b: i64) -> i64 {
1448        self.folds += 1;
1449        a ^ b
1450    }
1451    #[allow(dead_code)]
1452    pub fn not_i64(&mut self, a: i64) -> i64 {
1453        self.folds += 1;
1454        !a
1455    }
1456    #[allow(dead_code)]
1457    pub fn fold_count(&self) -> usize {
1458        self.folds
1459    }
1460    #[allow(dead_code)]
1461    pub fn failure_count(&self) -> usize {
1462        self.failures
1463    }
1464    #[allow(dead_code)]
1465    pub fn enable(&mut self) {
1466        self.enabled = true;
1467    }
1468    #[allow(dead_code)]
1469    pub fn disable(&mut self) {
1470        self.enabled = false;
1471    }
1472    #[allow(dead_code)]
1473    pub fn is_enabled(&self) -> bool {
1474        self.enabled
1475    }
1476}
1477/// Analysis cache for VerilogX2.
1478#[allow(dead_code)]
1479#[derive(Debug)]
1480pub struct VerilogX2Cache {
1481    pub(super) entries: Vec<(u64, Vec<u8>, bool, u32)>,
1482    pub(super) cap: usize,
1483    pub(super) total_hits: u64,
1484    pub(super) total_misses: u64,
1485}
1486impl VerilogX2Cache {
1487    #[allow(dead_code)]
1488    pub fn new(cap: usize) -> Self {
1489        Self {
1490            entries: Vec::new(),
1491            cap,
1492            total_hits: 0,
1493            total_misses: 0,
1494        }
1495    }
1496    #[allow(dead_code)]
1497    pub fn get(&mut self, key: u64) -> Option<&[u8]> {
1498        for e in self.entries.iter_mut() {
1499            if e.0 == key && e.2 {
1500                e.3 += 1;
1501                self.total_hits += 1;
1502                return Some(&e.1);
1503            }
1504        }
1505        self.total_misses += 1;
1506        None
1507    }
1508    #[allow(dead_code)]
1509    pub fn put(&mut self, key: u64, data: Vec<u8>) {
1510        if self.entries.len() >= self.cap {
1511            self.entries.retain(|e| e.2);
1512            if self.entries.len() >= self.cap {
1513                self.entries.remove(0);
1514            }
1515        }
1516        self.entries.push((key, data, true, 0));
1517    }
1518    #[allow(dead_code)]
1519    pub fn invalidate(&mut self) {
1520        for e in self.entries.iter_mut() {
1521            e.2 = false;
1522        }
1523    }
1524    #[allow(dead_code)]
1525    pub fn hit_rate(&self) -> f64 {
1526        let t = self.total_hits + self.total_misses;
1527        if t == 0 {
1528            0.0
1529        } else {
1530            self.total_hits as f64 / t as f64
1531        }
1532    }
1533    #[allow(dead_code)]
1534    pub fn live_count(&self) -> usize {
1535        self.entries.iter().filter(|e| e.2).count()
1536    }
1537}
1538/// Constant folding helper for VerilogX2.
1539#[allow(dead_code)]
1540#[derive(Debug, Clone, Default)]
1541pub struct VerilogX2ConstFolder {
1542    pub(super) folds: usize,
1543    pub(super) failures: usize,
1544    pub(super) enabled: bool,
1545}
1546impl VerilogX2ConstFolder {
1547    #[allow(dead_code)]
1548    pub fn new() -> Self {
1549        Self {
1550            folds: 0,
1551            failures: 0,
1552            enabled: true,
1553        }
1554    }
1555    #[allow(dead_code)]
1556    pub fn add_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1557        self.folds += 1;
1558        a.checked_add(b)
1559    }
1560    #[allow(dead_code)]
1561    pub fn sub_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1562        self.folds += 1;
1563        a.checked_sub(b)
1564    }
1565    #[allow(dead_code)]
1566    pub fn mul_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1567        self.folds += 1;
1568        a.checked_mul(b)
1569    }
1570    #[allow(dead_code)]
1571    pub fn div_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1572        if b == 0 {
1573            self.failures += 1;
1574            None
1575        } else {
1576            self.folds += 1;
1577            a.checked_div(b)
1578        }
1579    }
1580    #[allow(dead_code)]
1581    pub fn rem_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1582        if b == 0 {
1583            self.failures += 1;
1584            None
1585        } else {
1586            self.folds += 1;
1587            a.checked_rem(b)
1588        }
1589    }
1590    #[allow(dead_code)]
1591    pub fn neg_i64(&mut self, a: i64) -> Option<i64> {
1592        self.folds += 1;
1593        a.checked_neg()
1594    }
1595    #[allow(dead_code)]
1596    pub fn shl_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1597        if s >= 64 {
1598            self.failures += 1;
1599            None
1600        } else {
1601            self.folds += 1;
1602            a.checked_shl(s)
1603        }
1604    }
1605    #[allow(dead_code)]
1606    pub fn shr_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1607        if s >= 64 {
1608            self.failures += 1;
1609            None
1610        } else {
1611            self.folds += 1;
1612            a.checked_shr(s)
1613        }
1614    }
1615    #[allow(dead_code)]
1616    pub fn and_i64(&mut self, a: i64, b: i64) -> i64 {
1617        self.folds += 1;
1618        a & b
1619    }
1620    #[allow(dead_code)]
1621    pub fn or_i64(&mut self, a: i64, b: i64) -> i64 {
1622        self.folds += 1;
1623        a | b
1624    }
1625    #[allow(dead_code)]
1626    pub fn xor_i64(&mut self, a: i64, b: i64) -> i64 {
1627        self.folds += 1;
1628        a ^ b
1629    }
1630    #[allow(dead_code)]
1631    pub fn not_i64(&mut self, a: i64) -> i64 {
1632        self.folds += 1;
1633        !a
1634    }
1635    #[allow(dead_code)]
1636    pub fn fold_count(&self) -> usize {
1637        self.folds
1638    }
1639    #[allow(dead_code)]
1640    pub fn failure_count(&self) -> usize {
1641        self.failures
1642    }
1643    #[allow(dead_code)]
1644    pub fn enable(&mut self) {
1645        self.enabled = true;
1646    }
1647    #[allow(dead_code)]
1648    pub fn disable(&mut self) {
1649        self.enabled = false;
1650    }
1651    #[allow(dead_code)]
1652    pub fn is_enabled(&self) -> bool {
1653        self.enabled
1654    }
1655}
1656#[allow(dead_code)]
1657#[derive(Debug, Clone)]
1658pub struct VlogCacheEntry {
1659    pub key: String,
1660    pub data: Vec<u8>,
1661    pub timestamp: u64,
1662    pub valid: bool,
1663}
1664#[allow(dead_code)]
1665#[derive(Debug, Clone)]
1666pub struct VlogDominatorTree {
1667    pub idom: Vec<Option<u32>>,
1668    pub dom_children: Vec<Vec<u32>>,
1669    pub dom_depth: Vec<u32>,
1670}
1671impl VlogDominatorTree {
1672    #[allow(dead_code)]
1673    pub fn new(size: usize) -> Self {
1674        VlogDominatorTree {
1675            idom: vec![None; size],
1676            dom_children: vec![Vec::new(); size],
1677            dom_depth: vec![0; size],
1678        }
1679    }
1680    #[allow(dead_code)]
1681    pub fn set_idom(&mut self, node: usize, idom: u32) {
1682        self.idom[node] = Some(idom);
1683    }
1684    #[allow(dead_code)]
1685    pub fn dominates(&self, a: usize, b: usize) -> bool {
1686        if a == b {
1687            return true;
1688        }
1689        let mut cur = b;
1690        loop {
1691            match self.idom[cur] {
1692                Some(parent) if parent as usize == a => return true,
1693                Some(parent) if parent as usize == cur => return false,
1694                Some(parent) => cur = parent as usize,
1695                None => return false,
1696            }
1697        }
1698    }
1699    #[allow(dead_code)]
1700    pub fn depth(&self, node: usize) -> u32 {
1701        self.dom_depth.get(node).copied().unwrap_or(0)
1702    }
1703}
1704#[allow(dead_code)]
1705pub struct VlogConstantFoldingHelper;
1706impl VlogConstantFoldingHelper {
1707    #[allow(dead_code)]
1708    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
1709        a.checked_add(b)
1710    }
1711    #[allow(dead_code)]
1712    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
1713        a.checked_sub(b)
1714    }
1715    #[allow(dead_code)]
1716    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
1717        a.checked_mul(b)
1718    }
1719    #[allow(dead_code)]
1720    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
1721        if b == 0 {
1722            None
1723        } else {
1724            a.checked_div(b)
1725        }
1726    }
1727    #[allow(dead_code)]
1728    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
1729        a + b
1730    }
1731    #[allow(dead_code)]
1732    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
1733        a * b
1734    }
1735    #[allow(dead_code)]
1736    pub fn fold_neg_i64(a: i64) -> Option<i64> {
1737        a.checked_neg()
1738    }
1739    #[allow(dead_code)]
1740    pub fn fold_not_bool(a: bool) -> bool {
1741        !a
1742    }
1743    #[allow(dead_code)]
1744    pub fn fold_and_bool(a: bool, b: bool) -> bool {
1745        a && b
1746    }
1747    #[allow(dead_code)]
1748    pub fn fold_or_bool(a: bool, b: bool) -> bool {
1749        a || b
1750    }
1751    #[allow(dead_code)]
1752    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
1753        a.checked_shl(b)
1754    }
1755    #[allow(dead_code)]
1756    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
1757        a.checked_shr(b)
1758    }
1759    #[allow(dead_code)]
1760    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
1761        if b == 0 {
1762            None
1763        } else {
1764            Some(a % b)
1765        }
1766    }
1767    #[allow(dead_code)]
1768    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
1769        a & b
1770    }
1771    #[allow(dead_code)]
1772    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
1773        a | b
1774    }
1775    #[allow(dead_code)]
1776    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
1777        a ^ b
1778    }
1779    #[allow(dead_code)]
1780    pub fn fold_bitnot_i64(a: i64) -> i64 {
1781        !a
1782    }
1783}
1784/// Verilog / SystemVerilog data types.
1785#[derive(Debug, Clone, PartialEq)]
1786pub enum VerilogType {
1787    /// `wire [width-1:0]` — combinational net
1788    Wire(u32),
1789    /// `reg [width-1:0]`  — clocked register (Verilog 2001)
1790    Reg(u32),
1791    /// `logic [width-1:0]` — SystemVerilog unified net/variable
1792    Logic(u32),
1793    /// `integer` — 32-bit signed integer
1794    Integer,
1795    /// `real`    — 64-bit IEEE-754 double
1796    Real,
1797}
1798#[allow(dead_code)]
1799#[derive(Debug, Clone)]
1800pub struct VlogWorklist {
1801    pub(super) items: std::collections::VecDeque<u32>,
1802    pub(super) in_worklist: std::collections::HashSet<u32>,
1803}
1804impl VlogWorklist {
1805    #[allow(dead_code)]
1806    pub fn new() -> Self {
1807        VlogWorklist {
1808            items: std::collections::VecDeque::new(),
1809            in_worklist: std::collections::HashSet::new(),
1810        }
1811    }
1812    #[allow(dead_code)]
1813    pub fn push(&mut self, item: u32) -> bool {
1814        if self.in_worklist.insert(item) {
1815            self.items.push_back(item);
1816            true
1817        } else {
1818            false
1819        }
1820    }
1821    #[allow(dead_code)]
1822    pub fn pop(&mut self) -> Option<u32> {
1823        let item = self.items.pop_front()?;
1824        self.in_worklist.remove(&item);
1825        Some(item)
1826    }
1827    #[allow(dead_code)]
1828    pub fn is_empty(&self) -> bool {
1829        self.items.is_empty()
1830    }
1831    #[allow(dead_code)]
1832    pub fn len(&self) -> usize {
1833        self.items.len()
1834    }
1835    #[allow(dead_code)]
1836    pub fn contains(&self, item: u32) -> bool {
1837        self.in_worklist.contains(&item)
1838    }
1839}
1840/// Code-generation backend for Verilog and SystemVerilog.
1841#[derive(Debug, Clone)]
1842pub struct VerilogBackend {
1843    /// When `true`, emit SystemVerilog (IEEE 1800) constructs.
1844    pub system_verilog: bool,
1845}
1846impl VerilogBackend {
1847    /// Create a new backend.
1848    ///
1849    /// * `system_verilog = false` → Verilog 2001
1850    /// * `system_verilog = true`  → SystemVerilog
1851    pub fn new(system_verilog: bool) -> Self {
1852        VerilogBackend { system_verilog }
1853    }
1854    /// Emit the keyword string for a `VerilogType`.
1855    ///
1856    /// In Verilog 2001 mode, `Logic` falls back to `reg`.
1857    pub fn emit_type(&self, ty: &VerilogType) -> String {
1858        match ty {
1859            VerilogType::Logic(w) if !self.system_verilog => VerilogType::Reg(*w).to_string(),
1860            other => other.to_string(),
1861        }
1862    }
1863    /// Emit a `VerilogExpr` as a Verilog source string.
1864    pub fn emit_expr(&self, expr: &VerilogExpr) -> String {
1865        expr.to_string()
1866    }
1867    /// Emit a complete Verilog / SystemVerilog module as a source string.
1868    pub fn emit_module(&self, module: &VerilogModule) -> String {
1869        let mut out = String::new();
1870        let ext = if self.system_verilog { "sv" } else { "v" };
1871        out.push_str(&format!(
1872            "// Generated by OxiLean VerilogBackend (.{ext})\n\n"
1873        ));
1874        out.push_str(&format!("module {}", module.name));
1875        if !module.params.is_empty() {
1876            out.push_str(" #(\n");
1877            for (i, (pname, default)) in module.params.iter().enumerate() {
1878                let comma = if i + 1 < module.params.len() { "," } else { "" };
1879                out.push_str(&format!("    parameter {pname} = {default}{comma}\n"));
1880            }
1881            out.push(')');
1882        }
1883        if !module.ports.is_empty() {
1884            out.push_str(" (\n");
1885            for (i, port) in module.ports.iter().enumerate() {
1886                let comma = if i + 1 < module.ports.len() { "," } else { "" };
1887                let width_str = range_suffix(port.width());
1888                let type_kw = if self.system_verilog
1889                    && matches!(port, VerilogPort::Input(..) | VerilogPort::Output(..))
1890                {
1891                    "logic"
1892                } else {
1893                    match port {
1894                        VerilogPort::Input(..) => "wire",
1895                        VerilogPort::Output(..) => "reg",
1896                        VerilogPort::InOut(..) => "wire",
1897                    }
1898                };
1899                out.push_str(&format!(
1900                    "    {dir} {type_kw}{width_str} {name}{comma}\n",
1901                    dir = port.direction(),
1902                    width_str = if width_str.is_empty() {
1903                        String::new()
1904                    } else {
1905                        format!(" {width_str}")
1906                    },
1907                    name = port.name(),
1908                ));
1909            }
1910            out.push(')');
1911        }
1912        out.push_str(";\n\n");
1913        for stmt in &module.body {
1914            out.push_str("    ");
1915            out.push_str(stmt);
1916            out.push('\n');
1917        }
1918        out.push_str("\nendmodule\n");
1919        out
1920    }
1921    /// Emit an `always` (Verilog 2001) or `always_ff`/`always_comb`
1922    /// (SystemVerilog) block.
1923    ///
1924    /// `sensitivity` examples:
1925    /// * `"posedge clk"` → clocked
1926    /// * `"*"`           → combinational (Verilog 2001)
1927    /// * `"comb"`        → `always_comb` in SV mode
1928    /// * `"ff posedge clk"` → `always_ff` in SV mode
1929    pub fn always_block(&self, sensitivity: &str, body: &str) -> String {
1930        if self.system_verilog {
1931            if sensitivity == "comb" {
1932                return format!("always_comb begin\n    {body}\nend");
1933            }
1934            if let Some(rest) = sensitivity.strip_prefix("ff ") {
1935                return format!("always_ff @({rest}) begin\n    {body}\nend");
1936            }
1937        }
1938        format!("always @({sensitivity}) begin\n    {body}\nend")
1939    }
1940    /// Emit a continuous assignment: `assign lhs = rhs;`
1941    pub fn assign_stmt(&self, lhs: &str, rhs: &str) -> String {
1942        format!("assign {lhs} = {rhs};")
1943    }
1944    /// Emit a non-blocking assignment: `lhs <= rhs;`
1945    pub fn nonblocking_assign(&self, lhs: &str, rhs: &str) -> String {
1946        format!("{lhs} <= {rhs};")
1947    }
1948    /// Emit a blocking assignment: `lhs = rhs;`
1949    pub fn blocking_assign(&self, lhs: &str, rhs: &str) -> String {
1950        format!("{lhs} = {rhs};")
1951    }
1952    /// Emit an `if`/`else` construct inside a procedural block.
1953    pub fn if_else(&self, cond: &str, then_body: &str, else_body: Option<&str>) -> String {
1954        match else_body {
1955            Some(eb) => {
1956                format!(
1957                    "if ({cond}) begin\n        {then_body}\n    end else begin\n        {eb}\n    end"
1958                )
1959            }
1960            None => format!("if ({cond}) begin\n        {then_body}\n    end"),
1961        }
1962    }
1963    /// Emit an `initial` block.
1964    pub fn initial_block(&self, body: &str) -> String {
1965        format!("initial begin\n    {body}\nend")
1966    }
1967    /// Emit a local wire/reg/logic declaration.
1968    pub fn declare_signal(&self, ty: &VerilogType, name: &str) -> String {
1969        format!("{} {name};", self.emit_type(ty))
1970    }
1971    /// Emit an instance of another module.
1972    ///
1973    /// `port_map`: `&[("formal_name", "actual_signal")]`
1974    pub fn instantiate(
1975        &self,
1976        module_name: &str,
1977        instance_name: &str,
1978        port_map: &[(&str, &str)],
1979    ) -> String {
1980        let mut s = format!("{module_name} {instance_name} (\n");
1981        for (i, (formal, actual)) in port_map.iter().enumerate() {
1982            let comma = if i + 1 < port_map.len() { "," } else { "" };
1983            s.push_str(&format!("    .{formal}({actual}){comma}\n"));
1984        }
1985        s.push_str(");");
1986        s
1987    }
1988    /// Emit a `$display` / `$monitor` system task call.
1989    pub fn sys_task(&self, task: &str, fmt_str: &str, args: &[&str]) -> String {
1990        if args.is_empty() {
1991            format!("{task}(\"{fmt_str}\");")
1992        } else {
1993            let arg_str = args.join(", ");
1994            format!("{task}(\"{fmt_str}\", {arg_str});")
1995        }
1996    }
1997}
1998/// Liveness analysis for VerilogX2.
1999#[allow(dead_code)]
2000#[derive(Debug, Clone, Default)]
2001pub struct VerilogX2Liveness {
2002    pub live_in: Vec<Vec<usize>>,
2003    pub live_out: Vec<Vec<usize>>,
2004    pub defs: Vec<Vec<usize>>,
2005    pub uses: Vec<Vec<usize>>,
2006}
2007impl VerilogX2Liveness {
2008    #[allow(dead_code)]
2009    pub fn new(n: usize) -> Self {
2010        Self {
2011            live_in: vec![Vec::new(); n],
2012            live_out: vec![Vec::new(); n],
2013            defs: vec![Vec::new(); n],
2014            uses: vec![Vec::new(); n],
2015        }
2016    }
2017    #[allow(dead_code)]
2018    pub fn live_in(&self, b: usize, v: usize) -> bool {
2019        self.live_in.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2020    }
2021    #[allow(dead_code)]
2022    pub fn live_out(&self, b: usize, v: usize) -> bool {
2023        self.live_out
2024            .get(b)
2025            .map(|s| s.contains(&v))
2026            .unwrap_or(false)
2027    }
2028    #[allow(dead_code)]
2029    pub fn add_def(&mut self, b: usize, v: usize) {
2030        if let Some(s) = self.defs.get_mut(b) {
2031            if !s.contains(&v) {
2032                s.push(v);
2033            }
2034        }
2035    }
2036    #[allow(dead_code)]
2037    pub fn add_use(&mut self, b: usize, v: usize) {
2038        if let Some(s) = self.uses.get_mut(b) {
2039            if !s.contains(&v) {
2040                s.push(v);
2041            }
2042        }
2043    }
2044    #[allow(dead_code)]
2045    pub fn var_is_used_in_block(&self, b: usize, v: usize) -> bool {
2046        self.uses.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2047    }
2048    #[allow(dead_code)]
2049    pub fn var_is_def_in_block(&self, b: usize, v: usize) -> bool {
2050        self.defs.get(b).map(|s| s.contains(&v)).unwrap_or(false)
2051    }
2052}
2053/// Verilog expressions.
2054#[derive(Debug, Clone, PartialEq)]
2055pub enum VerilogExpr {
2056    /// Integer literal: value and bit-width
2057    Lit(u64, u32),
2058    /// Signal / variable reference
2059    Var(String),
2060    /// Binary operation: `lhs op rhs`
2061    BinOp(Box<VerilogExpr>, String, Box<VerilogExpr>),
2062    /// Unary operation: `op operand`
2063    UnOp(String, Box<VerilogExpr>),
2064    /// Concatenation: `{a, b, c, ...}`
2065    Concat(Vec<VerilogExpr>),
2066    /// Replication: `{n{expr}}`
2067    Replicate(u32, Box<VerilogExpr>),
2068    /// Single-bit index: `expr[bit]`
2069    Index(Box<VerilogExpr>, u32),
2070    /// Part-select: `expr[hi:lo]`
2071    Slice(Box<VerilogExpr>, u32, u32),
2072    /// Conditional (ternary): `cond ? then_ : else_`
2073    Ternary(Box<VerilogExpr>, Box<VerilogExpr>, Box<VerilogExpr>),
2074    /// Function call: `func(args...)`
2075    Call(String, Vec<VerilogExpr>),
2076}
2077/// Worklist for VerilogExt.
2078#[allow(dead_code)]
2079#[derive(Debug, Clone)]
2080pub struct VerilogExtWorklist {
2081    pub(super) items: std::collections::VecDeque<usize>,
2082    pub(super) present: Vec<bool>,
2083}
2084impl VerilogExtWorklist {
2085    #[allow(dead_code)]
2086    pub fn new(capacity: usize) -> Self {
2087        Self {
2088            items: std::collections::VecDeque::new(),
2089            present: vec![false; capacity],
2090        }
2091    }
2092    #[allow(dead_code)]
2093    pub fn push(&mut self, id: usize) {
2094        if id < self.present.len() && !self.present[id] {
2095            self.present[id] = true;
2096            self.items.push_back(id);
2097        }
2098    }
2099    #[allow(dead_code)]
2100    pub fn push_front(&mut self, id: usize) {
2101        if id < self.present.len() && !self.present[id] {
2102            self.present[id] = true;
2103            self.items.push_front(id);
2104        }
2105    }
2106    #[allow(dead_code)]
2107    pub fn pop(&mut self) -> Option<usize> {
2108        let id = self.items.pop_front()?;
2109        if id < self.present.len() {
2110            self.present[id] = false;
2111        }
2112        Some(id)
2113    }
2114    #[allow(dead_code)]
2115    pub fn is_empty(&self) -> bool {
2116        self.items.is_empty()
2117    }
2118    #[allow(dead_code)]
2119    pub fn len(&self) -> usize {
2120        self.items.len()
2121    }
2122    #[allow(dead_code)]
2123    pub fn contains(&self, id: usize) -> bool {
2124        id < self.present.len() && self.present[id]
2125    }
2126    #[allow(dead_code)]
2127    pub fn drain_all(&mut self) -> Vec<usize> {
2128        let v: Vec<usize> = self.items.drain(..).collect();
2129        for &id in &v {
2130            if id < self.present.len() {
2131                self.present[id] = false;
2132            }
2133        }
2134        v
2135    }
2136}
2137/// Pass registry for VerilogX2.
2138#[allow(dead_code)]
2139#[derive(Debug, Default)]
2140pub struct VerilogX2PassRegistry {
2141    pub(super) configs: Vec<VerilogX2PassConfig>,
2142    pub(super) stats: Vec<VerilogX2PassStats>,
2143}
2144impl VerilogX2PassRegistry {
2145    #[allow(dead_code)]
2146    pub fn new() -> Self {
2147        Self::default()
2148    }
2149    #[allow(dead_code)]
2150    pub fn register(&mut self, c: VerilogX2PassConfig) {
2151        self.stats.push(VerilogX2PassStats::new());
2152        self.configs.push(c);
2153    }
2154    #[allow(dead_code)]
2155    pub fn len(&self) -> usize {
2156        self.configs.len()
2157    }
2158    #[allow(dead_code)]
2159    pub fn is_empty(&self) -> bool {
2160        self.configs.is_empty()
2161    }
2162    #[allow(dead_code)]
2163    pub fn get(&self, i: usize) -> Option<&VerilogX2PassConfig> {
2164        self.configs.get(i)
2165    }
2166    #[allow(dead_code)]
2167    pub fn get_stats(&self, i: usize) -> Option<&VerilogX2PassStats> {
2168        self.stats.get(i)
2169    }
2170    #[allow(dead_code)]
2171    pub fn enabled_passes(&self) -> Vec<&VerilogX2PassConfig> {
2172        self.configs.iter().filter(|c| c.enabled).collect()
2173    }
2174    #[allow(dead_code)]
2175    pub fn passes_in_phase(&self, ph: &VerilogX2PassPhase) -> Vec<&VerilogX2PassConfig> {
2176        self.configs
2177            .iter()
2178            .filter(|c| c.enabled && &c.phase == ph)
2179            .collect()
2180    }
2181    #[allow(dead_code)]
2182    pub fn total_nodes_visited(&self) -> usize {
2183        self.stats.iter().map(|s| s.nodes_visited).sum()
2184    }
2185    #[allow(dead_code)]
2186    pub fn any_changed(&self) -> bool {
2187        self.stats.iter().any(|s| s.changed)
2188    }
2189}