Skip to main content

oxilean_codegen/opt_cse/
types.rs

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