Skip to main content

oxilean_codegen/opt_regalloc/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use crate::lcnf::{LcnfArg, LcnfExpr, LcnfFunDecl, LcnfLetValue, LcnfType, LcnfVarId};
7use std::collections::{HashMap, HashSet, VecDeque};
8
9#[allow(dead_code)]
10#[derive(Debug, Clone)]
11pub struct RADominatorTree {
12    pub idom: Vec<Option<u32>>,
13    pub dom_children: Vec<Vec<u32>>,
14    pub dom_depth: Vec<u32>,
15}
16impl RADominatorTree {
17    #[allow(dead_code)]
18    pub fn new(size: usize) -> Self {
19        RADominatorTree {
20            idom: vec![None; size],
21            dom_children: vec![Vec::new(); size],
22            dom_depth: vec![0; size],
23        }
24    }
25    #[allow(dead_code)]
26    pub fn set_idom(&mut self, node: usize, idom: u32) {
27        self.idom[node] = Some(idom);
28    }
29    #[allow(dead_code)]
30    pub fn dominates(&self, a: usize, b: usize) -> bool {
31        if a == b {
32            return true;
33        }
34        let mut cur = b;
35        loop {
36            match self.idom[cur] {
37                Some(parent) if parent as usize == a => return true,
38                Some(parent) if parent as usize == cur => return false,
39                Some(parent) => cur = parent as usize,
40                None => return false,
41            }
42        }
43    }
44    #[allow(dead_code)]
45    pub fn depth(&self, node: usize) -> u32 {
46        self.dom_depth.get(node).copied().unwrap_or(0)
47    }
48}
49/// Pass execution phase for RAExt.
50#[allow(dead_code)]
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub enum RAExtPassPhase {
53    Early,
54    Middle,
55    Late,
56    Finalize,
57}
58impl RAExtPassPhase {
59    #[allow(dead_code)]
60    pub fn is_early(&self) -> bool {
61        matches!(self, Self::Early)
62    }
63    #[allow(dead_code)]
64    pub fn is_middle(&self) -> bool {
65        matches!(self, Self::Middle)
66    }
67    #[allow(dead_code)]
68    pub fn is_late(&self) -> bool {
69        matches!(self, Self::Late)
70    }
71    #[allow(dead_code)]
72    pub fn is_finalize(&self) -> bool {
73        matches!(self, Self::Finalize)
74    }
75    #[allow(dead_code)]
76    pub fn order(&self) -> u32 {
77        match self {
78            Self::Early => 0,
79            Self::Middle => 1,
80            Self::Late => 2,
81            Self::Finalize => 3,
82        }
83    }
84    #[allow(dead_code)]
85    pub fn from_order(n: u32) -> Option<Self> {
86        match n {
87            0 => Some(Self::Early),
88            1 => Some(Self::Middle),
89            2 => Some(Self::Late),
90            3 => Some(Self::Finalize),
91            _ => None,
92        }
93    }
94}
95/// Chaitin-Briggs graph coloring register allocator.
96///
97/// Phases:
98/// 1. Build interference graph from live intervals.
99/// 2. Simplify: repeatedly remove nodes with degree < k (available colors).
100/// 3. Coalesce: merge copy-related nodes that don't interfere.
101/// 4. Spill: if no simplifiable node, mark highest-cost node for potential spill.
102/// 5. Select: pop nodes off the stack and assign colors.
103#[derive(Debug)]
104pub struct GraphColoringAllocator {
105    /// Number of available colors (physical registers).
106    pub num_colors: usize,
107    /// Physical registers available.
108    pub phys_regs: Vec<PhysReg>,
109    /// Current interference graph (mutated during simplification).
110    pub graph: InterferenceGraph,
111    /// Stack of removed nodes (in simplification order).
112    pub(super) simplify_stack: Vec<LcnfVarId>,
113    /// Nodes marked as potential spills.
114    pub(super) potential_spills: Vec<LcnfVarId>,
115    /// Number of coalescing merges performed.
116    pub coalesced_count: usize,
117    /// Number of simplification iterations.
118    pub iterations: usize,
119}
120impl GraphColoringAllocator {
121    /// Create a new Chaitin-Briggs allocator.
122    pub fn new(phys_regs: Vec<PhysReg>) -> Self {
123        let k = phys_regs.len();
124        GraphColoringAllocator {
125            num_colors: k,
126            phys_regs,
127            graph: InterferenceGraph::new(),
128            simplify_stack: Vec::new(),
129            potential_spills: Vec::new(),
130            coalesced_count: 0,
131            iterations: 0,
132        }
133    }
134    /// Build the interference graph from live intervals.
135    pub fn build_interference_graph(&mut self, intervals: &[LiveInterval]) -> InterferenceGraph {
136        self.graph = InterferenceGraph::build_from_intervals(intervals);
137        self.graph.clone()
138    }
139    /// Simplify phase: push low-degree (<k) nodes onto the stack.
140    ///
141    /// Returns the number of nodes removed in this pass.
142    pub fn simplify(&mut self) -> usize {
143        self.iterations += 1;
144        let k = self.num_colors;
145        let low_degree: Vec<LcnfVarId> = self
146            .graph
147            .nodes
148            .iter()
149            .copied()
150            .filter(|&v| self.graph.degree(v) < k)
151            .collect();
152        let removed = low_degree.len();
153        for v in low_degree {
154            self.graph.remove_node(v);
155            self.simplify_stack.push(v);
156        }
157        removed
158    }
159    /// Coalesce phase: merge copy-related vregs that don't interfere.
160    ///
161    /// Uses the George / Briggs conservative coalescing heuristic.
162    pub fn coalesce(&mut self, vreg_map: &mut HashMap<LcnfVarId, LcnfVarId>) {
163        let pairs = self.graph.move_pairs.clone();
164        for (u, v) in pairs {
165            let ru = *vreg_map.get(&u).unwrap_or(&u);
166            let rv = *vreg_map.get(&v).unwrap_or(&v);
167            if ru == rv {
168                continue;
169            }
170            if !self.graph.interferes(ru, rv) {
171                let neighbors: Vec<LcnfVarId> = self
172                    .graph
173                    .edges
174                    .get(&rv)
175                    .cloned()
176                    .unwrap_or_default()
177                    .into_iter()
178                    .collect();
179                for n in neighbors {
180                    self.graph.add_edge(ru, n);
181                }
182                self.graph.remove_node(rv);
183                vreg_map.insert(rv, ru);
184                self.coalesced_count += 1;
185            }
186        }
187    }
188    /// Spill selection: pick the node with the lowest spill weight to spill.
189    pub fn spill_select(&mut self, intervals: &[LiveInterval]) -> Option<LcnfVarId> {
190        if self.graph.nodes.is_empty() {
191            return None;
192        }
193        let candidate = self.graph.nodes.iter().copied().min_by(|&a, &b| {
194            let weight_a = intervals
195                .iter()
196                .find(|iv| iv.vreg == a)
197                .map(|iv| iv.spill_weight)
198                .unwrap_or(1.0);
199            let weight_b = intervals
200                .iter()
201                .find(|iv| iv.vreg == b)
202                .map(|iv| iv.spill_weight)
203                .unwrap_or(1.0);
204            weight_a
205                .partial_cmp(&weight_b)
206                .unwrap_or(std::cmp::Ordering::Equal)
207        });
208        if let Some(v) = candidate {
209            self.graph.remove_node(v);
210            self.simplify_stack.push(v);
211            self.potential_spills.push(v);
212        }
213        candidate
214    }
215    /// Color phase: pop nodes from the stack and assign colors.
216    ///
217    /// Returns the coloring (vreg → phys reg) and the set of actual spills.
218    #[allow(clippy::too_many_arguments)]
219    pub fn color(
220        &self,
221        k: usize,
222        original_graph: &InterferenceGraph,
223    ) -> HashMap<LcnfVarId, PhysReg> {
224        let mut coloring: HashMap<LcnfVarId, PhysReg> = HashMap::new();
225        for &vreg in self.simplify_stack.iter().rev() {
226            let used_colors: HashSet<u32> = original_graph
227                .edges
228                .get(&vreg)
229                .iter()
230                .flat_map(|neighbors| neighbors.iter())
231                .filter_map(|n| coloring.get(n))
232                .map(|pr| pr.id)
233                .collect();
234            let assigned = (0..k).find(|&c| !used_colors.contains(&(c as u32)));
235            if let Some(c) = assigned {
236                coloring.insert(vreg, self.phys_regs[c].clone());
237            }
238        }
239        coloring
240    }
241    /// Run the full Chaitin-Briggs allocation for a set of intervals.
242    ///
243    /// Returns an `Allocation`.
244    pub fn allocate(&mut self, intervals: &[LiveInterval]) -> Allocation {
245        let original_graph = self.build_interference_graph(intervals);
246        let mut vreg_map: HashMap<LcnfVarId, LcnfVarId> = HashMap::new();
247        loop {
248            let removed = self.simplify();
249            if removed == 0 {
250                if self.graph.nodes.is_empty() {
251                    break;
252                }
253                self.coalesce(&mut vreg_map);
254                if self.spill_select(intervals).is_none() {
255                    break;
256                }
257            }
258            if self.graph.nodes.is_empty() {
259                break;
260            }
261        }
262        let coloring = self.color(self.num_colors, &original_graph);
263        let mut alloc = Allocation::new();
264        for iv in intervals {
265            let canonical = *vreg_map.get(&iv.vreg).unwrap_or(&iv.vreg);
266            if let Some(preg) = coloring.get(&canonical).or_else(|| coloring.get(&iv.vreg)) {
267                alloc.assign(iv.vreg, preg.clone());
268            } else {
269                alloc.spill(iv.vreg, 8);
270            }
271        }
272        alloc
273    }
274}
275/// A feature flag set for RegAlloc capabilities.
276#[derive(Debug, Clone, Default)]
277pub struct RegAllocFeatures {
278    pub(super) flags: std::collections::HashSet<String>,
279}
280impl RegAllocFeatures {
281    pub fn new() -> Self {
282        RegAllocFeatures::default()
283    }
284    pub fn enable(&mut self, flag: impl Into<String>) {
285        self.flags.insert(flag.into());
286    }
287    pub fn disable(&mut self, flag: &str) {
288        self.flags.remove(flag);
289    }
290    pub fn is_enabled(&self, flag: &str) -> bool {
291        self.flags.contains(flag)
292    }
293    pub fn len(&self) -> usize {
294        self.flags.len()
295    }
296    pub fn is_empty(&self) -> bool {
297        self.flags.is_empty()
298    }
299    pub fn union(&self, other: &RegAllocFeatures) -> RegAllocFeatures {
300        RegAllocFeatures {
301            flags: self.flags.union(&other.flags).cloned().collect(),
302        }
303    }
304    pub fn intersection(&self, other: &RegAllocFeatures) -> RegAllocFeatures {
305        RegAllocFeatures {
306            flags: self.flags.intersection(&other.flags).cloned().collect(),
307        }
308    }
309}
310/// A candidate for spilling, ranked by cost.
311#[derive(Debug, Clone)]
312pub struct SpillCandidate {
313    /// The virtual register to spill.
314    pub vreg: LcnfVarId,
315    /// Estimated spill cost (frequency * size).
316    pub cost: f64,
317}
318impl SpillCandidate {
319    /// Create a new spill candidate.
320    pub fn new(vreg: LcnfVarId, cost: f64) -> Self {
321        SpillCandidate { vreg, cost }
322    }
323}
324#[allow(dead_code)]
325#[derive(Debug, Clone)]
326pub struct RAAnalysisCache {
327    pub(super) entries: std::collections::HashMap<String, RACacheEntry>,
328    pub(super) max_size: usize,
329    pub(super) hits: u64,
330    pub(super) misses: u64,
331}
332impl RAAnalysisCache {
333    #[allow(dead_code)]
334    pub fn new(max_size: usize) -> Self {
335        RAAnalysisCache {
336            entries: std::collections::HashMap::new(),
337            max_size,
338            hits: 0,
339            misses: 0,
340        }
341    }
342    #[allow(dead_code)]
343    pub fn get(&mut self, key: &str) -> Option<&RACacheEntry> {
344        if self.entries.contains_key(key) {
345            self.hits += 1;
346            self.entries.get(key)
347        } else {
348            self.misses += 1;
349            None
350        }
351    }
352    #[allow(dead_code)]
353    pub fn insert(&mut self, key: String, data: Vec<u8>) {
354        if self.entries.len() >= self.max_size {
355            if let Some(oldest) = self.entries.keys().next().cloned() {
356                self.entries.remove(&oldest);
357            }
358        }
359        self.entries.insert(
360            key.clone(),
361            RACacheEntry {
362                key,
363                data,
364                timestamp: 0,
365                valid: true,
366            },
367        );
368    }
369    #[allow(dead_code)]
370    pub fn invalidate(&mut self, key: &str) {
371        if let Some(entry) = self.entries.get_mut(key) {
372            entry.valid = false;
373        }
374    }
375    #[allow(dead_code)]
376    pub fn clear(&mut self) {
377        self.entries.clear();
378    }
379    #[allow(dead_code)]
380    pub fn hit_rate(&self) -> f64 {
381        let total = self.hits + self.misses;
382        if total == 0 {
383            return 0.0;
384        }
385        self.hits as f64 / total as f64
386    }
387    #[allow(dead_code)]
388    pub fn size(&self) -> usize {
389        self.entries.len()
390    }
391}
392/// Summary statistics from register allocation.
393#[derive(Debug, Clone, Default)]
394pub struct RegAllocReport {
395    /// Number of virtual registers successfully allocated to physical registers.
396    pub vregs_allocated: usize,
397    /// Number of virtual registers that were spilled to the stack.
398    pub spills: usize,
399    /// Number of copy pairs coalesced (eliminates move instructions).
400    pub copies_coalesced: usize,
401    /// Number of coloring / simplification iterations performed.
402    pub coloring_iterations: usize,
403    /// Total stack frame size used for spill slots (bytes).
404    pub stack_frame_bytes: u32,
405    /// Number of functions processed.
406    pub functions_processed: usize,
407}
408impl RegAllocReport {
409    /// Spill ratio: fraction of vregs that were spilled.
410    pub fn spill_ratio(&self) -> f64 {
411        let total = self.vregs_allocated + self.spills;
412        if total == 0 {
413            0.0
414        } else {
415            self.spills as f64 / total as f64
416        }
417    }
418}
419/// Collects RegAlloc diagnostics.
420#[derive(Debug, Default)]
421pub struct RegAllocDiagCollector {
422    pub(super) msgs: Vec<RegAllocDiagMsg>,
423}
424impl RegAllocDiagCollector {
425    pub fn new() -> Self {
426        RegAllocDiagCollector::default()
427    }
428    pub fn emit(&mut self, d: RegAllocDiagMsg) {
429        self.msgs.push(d);
430    }
431    pub fn has_errors(&self) -> bool {
432        self.msgs
433            .iter()
434            .any(|d| d.severity == RegAllocDiagSeverity::Error)
435    }
436    pub fn errors(&self) -> Vec<&RegAllocDiagMsg> {
437        self.msgs
438            .iter()
439            .filter(|d| d.severity == RegAllocDiagSeverity::Error)
440            .collect()
441    }
442    pub fn warnings(&self) -> Vec<&RegAllocDiagMsg> {
443        self.msgs
444            .iter()
445            .filter(|d| d.severity == RegAllocDiagSeverity::Warning)
446            .collect()
447    }
448    pub fn len(&self) -> usize {
449        self.msgs.len()
450    }
451    pub fn is_empty(&self) -> bool {
452        self.msgs.is_empty()
453    }
454    pub fn clear(&mut self) {
455        self.msgs.clear();
456    }
457}
458/// Register allocation pass.
459///
460/// Supports both linear scan and graph coloring algorithms.
461#[derive(Debug)]
462pub struct RegAllocPass {
463    /// Physical register bank.
464    pub phys_regs: Vec<PhysReg>,
465    /// Whether to use graph coloring (Chaitin-Briggs) instead of linear scan.
466    pub use_graph_coloring: bool,
467    /// Accumulated report.
468    pub(super) report: RegAllocReport,
469    /// Allocation results per function (function name → allocation).
470    pub allocations: HashMap<String, Allocation>,
471}
472impl RegAllocPass {
473    /// Create a new pass with `num_regs` integer registers using linear scan.
474    pub fn new(num_regs: u32) -> Self {
475        RegAllocPass {
476            phys_regs: PhysReg::integer_bank(num_regs),
477            use_graph_coloring: false,
478            report: RegAllocReport::default(),
479            allocations: HashMap::new(),
480        }
481    }
482    /// Create a pass that uses Chaitin-Briggs graph coloring.
483    pub fn graph_coloring(num_regs: u32) -> Self {
484        RegAllocPass {
485            phys_regs: PhysReg::integer_bank(num_regs),
486            use_graph_coloring: true,
487            report: RegAllocReport::default(),
488            allocations: HashMap::new(),
489        }
490    }
491    /// Create a pass with an explicit physical register set.
492    pub fn with_regs(phys_regs: Vec<PhysReg>, use_graph_coloring: bool) -> Self {
493        RegAllocPass {
494            phys_regs,
495            use_graph_coloring,
496            report: RegAllocReport::default(),
497            allocations: HashMap::new(),
498        }
499    }
500    /// Run register allocation on all function declarations.
501    pub fn run(&mut self, decls: &mut [LcnfFunDecl]) {
502        for decl in decls.iter() {
503            self.report.functions_processed += 1;
504            let alloc = if self.use_graph_coloring {
505                self.run_graph_coloring(decl)
506            } else {
507                self.run_linear_scan(decl)
508            };
509            self.report.vregs_allocated += alloc.reg_map.len();
510            self.report.spills += alloc.spills.len();
511            self.report.stack_frame_bytes += alloc.stack_frame_size();
512            self.allocations.insert(decl.name.clone(), alloc);
513        }
514    }
515    /// Run linear scan allocation for a single declaration.
516    pub(super) fn run_linear_scan(&self, decl: &LcnfFunDecl) -> Allocation {
517        let mut lsa = LinearScanAllocator::new(self.phys_regs.clone());
518        let intervals = lsa.build_live_intervals(decl);
519        let n = self.phys_regs.len();
520        lsa.linear_scan(intervals, n)
521    }
522    /// Run graph coloring allocation for a single declaration.
523    pub(super) fn run_graph_coloring(&self, decl: &LcnfFunDecl) -> Allocation {
524        let lsa = LinearScanAllocator::new(self.phys_regs.clone());
525        let intervals = lsa.build_live_intervals(decl);
526        let mut gca = GraphColoringAllocator::new(self.phys_regs.clone());
527        let alloc = gca.allocate(&intervals);
528        let _ = self.report.clone();
529        alloc
530    }
531    /// Return the accumulated allocation report.
532    pub fn report(&self) -> RegAllocReport {
533        self.report.clone()
534    }
535    /// Lookup the allocation for a function by name.
536    pub fn allocation_for(&self, func: &str) -> Option<&Allocation> {
537        self.allocations.get(func)
538    }
539}
540/// The register class (hardware register file) of a physical register.
541#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
542pub enum RegClass {
543    /// General-purpose integer registers (e.g. rax, rbx, …).
544    Integer,
545    /// Floating-point / SSE / AVX registers (e.g. xmm0, …).
546    Float,
547    /// SIMD / vector registers (e.g. ymm0, zmm0, …).
548    Vector,
549    /// Predicate / condition registers (e.g. ARM p0, x86 k0, …).
550    Predicate,
551}
552impl RegClass {
553    /// A short name for this class used in physical register names.
554    pub fn prefix(&self) -> &'static str {
555        match self {
556            RegClass::Integer => "r",
557            RegClass::Float => "f",
558            RegClass::Vector => "v",
559            RegClass::Predicate => "p",
560        }
561    }
562}
563/// The result of register allocation.
564#[derive(Debug, Clone, Default)]
565pub struct Allocation {
566    /// Maps virtual register IDs to physical registers.
567    pub reg_map: HashMap<LcnfVarId, PhysReg>,
568    /// Virtual registers that were spilled to the stack.
569    pub spills: Vec<SpillSlot>,
570    /// Next available stack offset.
571    pub(super) next_stack_offset: u32,
572}
573impl Allocation {
574    /// Create an empty allocation.
575    pub fn new() -> Self {
576        Allocation::default()
577    }
578    /// Assign a physical register to a virtual register.
579    pub fn assign(&mut self, vreg: LcnfVarId, preg: PhysReg) {
580        self.reg_map.insert(vreg, preg);
581    }
582    /// Spill a virtual register, returning the slot.
583    pub fn spill(&mut self, vreg: LcnfVarId, size: u32) -> SpillSlot {
584        let slot = SpillSlot::new(vreg, self.next_stack_offset, size);
585        self.next_stack_offset += size;
586        self.spills.push(slot.clone());
587        slot
588    }
589    /// Look up the physical register for `vreg`.
590    pub fn lookup(&self, vreg: LcnfVarId) -> Option<&PhysReg> {
591        self.reg_map.get(&vreg)
592    }
593    /// Returns `true` if `vreg` was spilled.
594    pub fn is_spilled(&self, vreg: LcnfVarId) -> bool {
595        self.spills.iter().any(|s| s.vreg == vreg)
596    }
597    /// Total stack frame size needed for spills.
598    pub fn stack_frame_size(&self) -> u32 {
599        self.next_stack_offset
600    }
601}
602/// A diagnostic message from a RegAlloc pass.
603#[derive(Debug, Clone)]
604pub struct RegAllocDiagMsg {
605    pub severity: RegAllocDiagSeverity,
606    pub pass: String,
607    pub message: String,
608}
609impl RegAllocDiagMsg {
610    pub fn error(pass: impl Into<String>, msg: impl Into<String>) -> Self {
611        RegAllocDiagMsg {
612            severity: RegAllocDiagSeverity::Error,
613            pass: pass.into(),
614            message: msg.into(),
615        }
616    }
617    pub fn warning(pass: impl Into<String>, msg: impl Into<String>) -> Self {
618        RegAllocDiagMsg {
619            severity: RegAllocDiagSeverity::Warning,
620            pass: pass.into(),
621            message: msg.into(),
622        }
623    }
624    pub fn note(pass: impl Into<String>, msg: impl Into<String>) -> Self {
625        RegAllocDiagMsg {
626            severity: RegAllocDiagSeverity::Note,
627            pass: pass.into(),
628            message: msg.into(),
629        }
630    }
631}
632#[allow(dead_code)]
633#[derive(Debug, Clone)]
634pub struct RACacheEntry {
635    pub key: String,
636    pub data: Vec<u8>,
637    pub timestamp: u64,
638    pub valid: bool,
639}
640/// Dependency graph for RAExt.
641#[allow(dead_code)]
642#[derive(Debug, Clone)]
643pub struct RAExtDepGraph {
644    pub(super) n: usize,
645    pub(super) adj: Vec<Vec<usize>>,
646    pub(super) rev: Vec<Vec<usize>>,
647    pub(super) edge_count: usize,
648}
649impl RAExtDepGraph {
650    #[allow(dead_code)]
651    pub fn new(n: usize) -> Self {
652        Self {
653            n,
654            adj: vec![Vec::new(); n],
655            rev: vec![Vec::new(); n],
656            edge_count: 0,
657        }
658    }
659    #[allow(dead_code)]
660    pub fn add_edge(&mut self, from: usize, to: usize) {
661        if from < self.n && to < self.n {
662            if !self.adj[from].contains(&to) {
663                self.adj[from].push(to);
664                self.rev[to].push(from);
665                self.edge_count += 1;
666            }
667        }
668    }
669    #[allow(dead_code)]
670    pub fn succs(&self, n: usize) -> &[usize] {
671        self.adj.get(n).map(|v| v.as_slice()).unwrap_or(&[])
672    }
673    #[allow(dead_code)]
674    pub fn preds(&self, n: usize) -> &[usize] {
675        self.rev.get(n).map(|v| v.as_slice()).unwrap_or(&[])
676    }
677    #[allow(dead_code)]
678    pub fn topo_sort(&self) -> Option<Vec<usize>> {
679        let mut deg: Vec<usize> = (0..self.n).map(|i| self.rev[i].len()).collect();
680        let mut q: std::collections::VecDeque<usize> =
681            (0..self.n).filter(|&i| deg[i] == 0).collect();
682        let mut out = Vec::with_capacity(self.n);
683        while let Some(u) = q.pop_front() {
684            out.push(u);
685            for &v in &self.adj[u] {
686                deg[v] -= 1;
687                if deg[v] == 0 {
688                    q.push_back(v);
689                }
690            }
691        }
692        if out.len() == self.n {
693            Some(out)
694        } else {
695            None
696        }
697    }
698    #[allow(dead_code)]
699    pub fn has_cycle(&self) -> bool {
700        self.topo_sort().is_none()
701    }
702    #[allow(dead_code)]
703    pub fn reachable(&self, start: usize) -> Vec<usize> {
704        let mut vis = vec![false; self.n];
705        let mut stk = vec![start];
706        let mut out = Vec::new();
707        while let Some(u) = stk.pop() {
708            if u < self.n && !vis[u] {
709                vis[u] = true;
710                out.push(u);
711                for &v in &self.adj[u] {
712                    if !vis[v] {
713                        stk.push(v);
714                    }
715                }
716            }
717        }
718        out
719    }
720    #[allow(dead_code)]
721    pub fn scc(&self) -> Vec<Vec<usize>> {
722        let mut visited = vec![false; self.n];
723        let mut order = Vec::new();
724        for i in 0..self.n {
725            if !visited[i] {
726                let mut stk = vec![(i, 0usize)];
727                while let Some((u, idx)) = stk.last_mut() {
728                    if !visited[*u] {
729                        visited[*u] = true;
730                    }
731                    if *idx < self.adj[*u].len() {
732                        let v = self.adj[*u][*idx];
733                        *idx += 1;
734                        if !visited[v] {
735                            stk.push((v, 0));
736                        }
737                    } else {
738                        order.push(*u);
739                        stk.pop();
740                    }
741                }
742            }
743        }
744        let mut comp = vec![usize::MAX; self.n];
745        let mut components: Vec<Vec<usize>> = Vec::new();
746        for &start in order.iter().rev() {
747            if comp[start] == usize::MAX {
748                let cid = components.len();
749                let mut component = Vec::new();
750                let mut stk = vec![start];
751                while let Some(u) = stk.pop() {
752                    if comp[u] == usize::MAX {
753                        comp[u] = cid;
754                        component.push(u);
755                        for &v in &self.rev[u] {
756                            if comp[v] == usize::MAX {
757                                stk.push(v);
758                            }
759                        }
760                    }
761                }
762                components.push(component);
763            }
764        }
765        components
766    }
767    #[allow(dead_code)]
768    pub fn node_count(&self) -> usize {
769        self.n
770    }
771    #[allow(dead_code)]
772    pub fn edge_count(&self) -> usize {
773        self.edge_count
774    }
775}
776/// Pipeline profiler for RegAlloc.
777#[derive(Debug, Default)]
778pub struct RegAllocProfiler {
779    pub(super) timings: Vec<RegAllocPassTiming>,
780}
781impl RegAllocProfiler {
782    pub fn new() -> Self {
783        RegAllocProfiler::default()
784    }
785    pub fn record(&mut self, t: RegAllocPassTiming) {
786        self.timings.push(t);
787    }
788    pub fn total_elapsed_us(&self) -> u64 {
789        self.timings.iter().map(|t| t.elapsed_us).sum()
790    }
791    pub fn slowest_pass(&self) -> Option<&RegAllocPassTiming> {
792        self.timings.iter().max_by_key(|t| t.elapsed_us)
793    }
794    pub fn num_passes(&self) -> usize {
795        self.timings.len()
796    }
797    pub fn profitable_passes(&self) -> Vec<&RegAllocPassTiming> {
798        self.timings.iter().filter(|t| t.is_profitable()).collect()
799    }
800}
801/// Emission statistics for RegAlloc.
802#[derive(Debug, Clone, Default)]
803pub struct RegAllocEmitStats {
804    pub bytes_emitted: usize,
805    pub items_emitted: usize,
806    pub errors: usize,
807    pub warnings: usize,
808    pub elapsed_ms: u64,
809}
810impl RegAllocEmitStats {
811    pub fn new() -> Self {
812        RegAllocEmitStats::default()
813    }
814    pub fn throughput_bps(&self) -> f64 {
815        if self.elapsed_ms == 0 {
816            0.0
817        } else {
818            self.bytes_emitted as f64 / (self.elapsed_ms as f64 / 1000.0)
819        }
820    }
821    pub fn is_clean(&self) -> bool {
822        self.errors == 0
823    }
824}
825/// An undirected interference graph for register allocation.
826///
827/// Nodes are virtual registers; an edge (u, v) means u and v have
828/// overlapping live ranges and therefore cannot share a physical register.
829#[derive(Debug, Clone, Default)]
830pub struct InterferenceGraph {
831    /// Adjacency lists.
832    pub edges: HashMap<LcnfVarId, HashSet<LcnfVarId>>,
833    /// All nodes (vregs) in the graph.
834    pub nodes: HashSet<LcnfVarId>,
835    /// Move-related pairs: pairs of vregs connected by copy instructions
836    /// that are candidates for coalescing.
837    pub move_pairs: Vec<(LcnfVarId, LcnfVarId)>,
838}
839impl InterferenceGraph {
840    /// Create an empty interference graph.
841    pub fn new() -> Self {
842        InterferenceGraph::default()
843    }
844    /// Add a node to the graph.
845    pub fn add_node(&mut self, vreg: LcnfVarId) {
846        self.nodes.insert(vreg);
847        self.edges.entry(vreg).or_default();
848    }
849    /// Add an interference edge between `u` and `v`.
850    pub fn add_edge(&mut self, u: LcnfVarId, v: LcnfVarId) {
851        if u == v {
852            return;
853        }
854        self.nodes.insert(u);
855        self.nodes.insert(v);
856        self.edges.entry(u).or_default().insert(v);
857        self.edges.entry(v).or_default().insert(u);
858    }
859    /// Returns `true` if `u` and `v` interfere.
860    pub fn interferes(&self, u: LcnfVarId, v: LcnfVarId) -> bool {
861        self.edges.get(&u).map(|s| s.contains(&v)).unwrap_or(false)
862    }
863    /// Returns the degree of node `vreg`.
864    pub fn degree(&self, vreg: LcnfVarId) -> usize {
865        self.edges.get(&vreg).map(|s| s.len()).unwrap_or(0)
866    }
867    /// Remove a node and all its edges.
868    pub fn remove_node(&mut self, vreg: LcnfVarId) {
869        if let Some(neighbors) = self.edges.remove(&vreg) {
870            for n in neighbors {
871                if let Some(s) = self.edges.get_mut(&n) {
872                    s.remove(&vreg);
873                }
874            }
875        }
876        self.nodes.remove(&vreg);
877    }
878    /// Record a move-related pair (candidate for coalescing).
879    pub fn add_move_pair(&mut self, u: LcnfVarId, v: LcnfVarId) {
880        if u != v {
881            self.move_pairs.push((u, v));
882        }
883    }
884    /// Build an interference graph from a set of live intervals.
885    pub fn build_from_intervals(intervals: &[LiveInterval]) -> Self {
886        let mut g = InterferenceGraph::new();
887        for iv in intervals {
888            g.add_node(iv.vreg);
889        }
890        for i in 0..intervals.len() {
891            for j in (i + 1)..intervals.len() {
892                if intervals[i].overlaps(&intervals[j]) {
893                    g.add_edge(intervals[i].vreg, intervals[j].vreg);
894                }
895            }
896        }
897        g
898    }
899}
900/// Heuristic freshness key for RegAlloc incremental compilation.
901#[derive(Debug, Clone, PartialEq, Eq, Hash)]
902pub struct RegAllocIncrKey {
903    pub content_hash: u64,
904    pub config_hash: u64,
905}
906impl RegAllocIncrKey {
907    pub fn new(content: u64, config: u64) -> Self {
908        RegAllocIncrKey {
909            content_hash: content,
910            config_hash: config,
911        }
912    }
913    pub fn combined_hash(&self) -> u64 {
914        self.content_hash.wrapping_mul(0x9e3779b97f4a7c15) ^ self.config_hash
915    }
916    pub fn matches(&self, other: &RegAllocIncrKey) -> bool {
917        self.content_hash == other.content_hash && self.config_hash == other.config_hash
918    }
919}
920#[allow(dead_code)]
921#[derive(Debug, Clone)]
922pub struct RALivenessInfo {
923    pub live_in: Vec<std::collections::HashSet<u32>>,
924    pub live_out: Vec<std::collections::HashSet<u32>>,
925    pub defs: Vec<std::collections::HashSet<u32>>,
926    pub uses: Vec<std::collections::HashSet<u32>>,
927}
928impl RALivenessInfo {
929    #[allow(dead_code)]
930    pub fn new(block_count: usize) -> Self {
931        RALivenessInfo {
932            live_in: vec![std::collections::HashSet::new(); block_count],
933            live_out: vec![std::collections::HashSet::new(); block_count],
934            defs: vec![std::collections::HashSet::new(); block_count],
935            uses: vec![std::collections::HashSet::new(); block_count],
936        }
937    }
938    #[allow(dead_code)]
939    pub fn add_def(&mut self, block: usize, var: u32) {
940        if block < self.defs.len() {
941            self.defs[block].insert(var);
942        }
943    }
944    #[allow(dead_code)]
945    pub fn add_use(&mut self, block: usize, var: u32) {
946        if block < self.uses.len() {
947            self.uses[block].insert(var);
948        }
949    }
950    #[allow(dead_code)]
951    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
952        self.live_in
953            .get(block)
954            .map(|s| s.contains(&var))
955            .unwrap_or(false)
956    }
957    #[allow(dead_code)]
958    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
959        self.live_out
960            .get(block)
961            .map(|s| s.contains(&var))
962            .unwrap_or(false)
963    }
964}
965/// Liveness analysis for RAExt.
966#[allow(dead_code)]
967#[derive(Debug, Clone, Default)]
968pub struct RAExtLiveness {
969    pub live_in: Vec<Vec<usize>>,
970    pub live_out: Vec<Vec<usize>>,
971    pub defs: Vec<Vec<usize>>,
972    pub uses: Vec<Vec<usize>>,
973}
974impl RAExtLiveness {
975    #[allow(dead_code)]
976    pub fn new(n: usize) -> Self {
977        Self {
978            live_in: vec![Vec::new(); n],
979            live_out: vec![Vec::new(); n],
980            defs: vec![Vec::new(); n],
981            uses: vec![Vec::new(); n],
982        }
983    }
984    #[allow(dead_code)]
985    pub fn live_in(&self, b: usize, v: usize) -> bool {
986        self.live_in.get(b).map(|s| s.contains(&v)).unwrap_or(false)
987    }
988    #[allow(dead_code)]
989    pub fn live_out(&self, b: usize, v: usize) -> bool {
990        self.live_out
991            .get(b)
992            .map(|s| s.contains(&v))
993            .unwrap_or(false)
994    }
995    #[allow(dead_code)]
996    pub fn add_def(&mut self, b: usize, v: usize) {
997        if let Some(s) = self.defs.get_mut(b) {
998            if !s.contains(&v) {
999                s.push(v);
1000            }
1001        }
1002    }
1003    #[allow(dead_code)]
1004    pub fn add_use(&mut self, b: usize, v: usize) {
1005        if let Some(s) = self.uses.get_mut(b) {
1006            if !s.contains(&v) {
1007                s.push(v);
1008            }
1009        }
1010    }
1011    #[allow(dead_code)]
1012    pub fn var_is_used_in_block(&self, b: usize, v: usize) -> bool {
1013        self.uses.get(b).map(|s| s.contains(&v)).unwrap_or(false)
1014    }
1015    #[allow(dead_code)]
1016    pub fn var_is_def_in_block(&self, b: usize, v: usize) -> bool {
1017        self.defs.get(b).map(|s| s.contains(&v)).unwrap_or(false)
1018    }
1019}
1020/// A generic key-value configuration store for RegAlloc.
1021#[derive(Debug, Clone, Default)]
1022pub struct RegAllocConfig {
1023    pub(super) entries: std::collections::HashMap<String, String>,
1024}
1025impl RegAllocConfig {
1026    pub fn new() -> Self {
1027        RegAllocConfig::default()
1028    }
1029    pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
1030        self.entries.insert(key.into(), value.into());
1031    }
1032    pub fn get(&self, key: &str) -> Option<&str> {
1033        self.entries.get(key).map(|s| s.as_str())
1034    }
1035    pub fn get_bool(&self, key: &str) -> bool {
1036        matches!(self.get(key), Some("true") | Some("1") | Some("yes"))
1037    }
1038    pub fn get_int(&self, key: &str) -> Option<i64> {
1039        self.get(key)?.parse().ok()
1040    }
1041    pub fn len(&self) -> usize {
1042        self.entries.len()
1043    }
1044    pub fn is_empty(&self) -> bool {
1045        self.entries.is_empty()
1046    }
1047}
1048/// Severity of a RegAlloc diagnostic.
1049#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1050pub enum RegAllocDiagSeverity {
1051    Note,
1052    Warning,
1053    Error,
1054}
1055#[allow(dead_code)]
1056#[derive(Debug, Clone)]
1057pub struct RAWorklist {
1058    pub(super) items: std::collections::VecDeque<u32>,
1059    pub(super) in_worklist: std::collections::HashSet<u32>,
1060}
1061impl RAWorklist {
1062    #[allow(dead_code)]
1063    pub fn new() -> Self {
1064        RAWorklist {
1065            items: std::collections::VecDeque::new(),
1066            in_worklist: std::collections::HashSet::new(),
1067        }
1068    }
1069    #[allow(dead_code)]
1070    pub fn push(&mut self, item: u32) -> bool {
1071        if self.in_worklist.insert(item) {
1072            self.items.push_back(item);
1073            true
1074        } else {
1075            false
1076        }
1077    }
1078    #[allow(dead_code)]
1079    pub fn pop(&mut self) -> Option<u32> {
1080        let item = self.items.pop_front()?;
1081        self.in_worklist.remove(&item);
1082        Some(item)
1083    }
1084    #[allow(dead_code)]
1085    pub fn is_empty(&self) -> bool {
1086        self.items.is_empty()
1087    }
1088    #[allow(dead_code)]
1089    pub fn len(&self) -> usize {
1090        self.items.len()
1091    }
1092    #[allow(dead_code)]
1093    pub fn contains(&self, item: u32) -> bool {
1094        self.in_worklist.contains(&item)
1095    }
1096}
1097/// A physical register on the target architecture.
1098#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1099pub struct PhysReg {
1100    /// Unique numeric ID of this register.
1101    pub id: u32,
1102    /// Human-readable name (e.g. "rax", "f0", "xmm3").
1103    pub name: String,
1104    /// The register class.
1105    pub class: RegClass,
1106}
1107impl PhysReg {
1108    /// Create a new physical register.
1109    pub fn new(id: u32, name: impl Into<String>, class: RegClass) -> Self {
1110        PhysReg {
1111            id,
1112            name: name.into(),
1113            class,
1114        }
1115    }
1116    /// Create a set of `n` integer physical registers named r0..r{n-1}.
1117    pub fn integer_bank(n: u32) -> Vec<PhysReg> {
1118        (0..n)
1119            .map(|i| PhysReg::new(i, format!("r{}", i), RegClass::Integer))
1120            .collect()
1121    }
1122    /// Create a set of `n` float physical registers named f0..f{n-1}.
1123    pub fn float_bank(n: u32) -> Vec<PhysReg> {
1124        (0..n)
1125            .map(|i| PhysReg::new(i, format!("f{}", i), RegClass::Float))
1126            .collect()
1127    }
1128}
1129/// A monotonically increasing ID generator for RegAlloc.
1130#[derive(Debug, Default)]
1131pub struct RegAllocIdGen {
1132    pub(super) next: u32,
1133}
1134impl RegAllocIdGen {
1135    pub fn new() -> Self {
1136        RegAllocIdGen::default()
1137    }
1138    pub fn next_id(&mut self) -> u32 {
1139        let id = self.next;
1140        self.next += 1;
1141        id
1142    }
1143    pub fn peek_next(&self) -> u32 {
1144        self.next
1145    }
1146    pub fn reset(&mut self) {
1147        self.next = 0;
1148    }
1149    pub fn skip(&mut self, n: u32) {
1150        self.next += n;
1151    }
1152}
1153/// A stack spill slot for a virtual register.
1154#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1155pub struct SpillSlot {
1156    /// The spilled virtual register.
1157    pub vreg: LcnfVarId,
1158    /// Byte offset on the stack frame.
1159    pub offset: u32,
1160    /// Size of the slot in bytes.
1161    pub size: u32,
1162}
1163impl SpillSlot {
1164    /// Create a new spill slot.
1165    pub fn new(vreg: LcnfVarId, offset: u32, size: u32) -> Self {
1166        SpillSlot { vreg, offset, size }
1167    }
1168}
1169#[allow(dead_code)]
1170#[derive(Debug, Clone)]
1171pub struct RAPassConfig {
1172    pub phase: RAPassPhase,
1173    pub enabled: bool,
1174    pub max_iterations: u32,
1175    pub debug_output: bool,
1176    pub pass_name: String,
1177}
1178impl RAPassConfig {
1179    #[allow(dead_code)]
1180    pub fn new(name: impl Into<String>, phase: RAPassPhase) -> Self {
1181        RAPassConfig {
1182            phase,
1183            enabled: true,
1184            max_iterations: 10,
1185            debug_output: false,
1186            pass_name: name.into(),
1187        }
1188    }
1189    #[allow(dead_code)]
1190    pub fn disabled(mut self) -> Self {
1191        self.enabled = false;
1192        self
1193    }
1194    #[allow(dead_code)]
1195    pub fn with_debug(mut self) -> Self {
1196        self.debug_output = true;
1197        self
1198    }
1199    #[allow(dead_code)]
1200    pub fn max_iter(mut self, n: u32) -> Self {
1201        self.max_iterations = n;
1202        self
1203    }
1204}
1205/// A text buffer for building RegAlloc output source code.
1206#[derive(Debug, Default)]
1207pub struct RegAllocSourceBuffer {
1208    pub(super) buf: String,
1209    pub(super) indent_level: usize,
1210    pub(super) indent_str: String,
1211}
1212impl RegAllocSourceBuffer {
1213    pub fn new() -> Self {
1214        RegAllocSourceBuffer {
1215            buf: String::new(),
1216            indent_level: 0,
1217            indent_str: "    ".to_string(),
1218        }
1219    }
1220    pub fn with_indent(mut self, indent: impl Into<String>) -> Self {
1221        self.indent_str = indent.into();
1222        self
1223    }
1224    pub fn push_line(&mut self, line: &str) {
1225        for _ in 0..self.indent_level {
1226            self.buf.push_str(&self.indent_str);
1227        }
1228        self.buf.push_str(line);
1229        self.buf.push('\n');
1230    }
1231    pub fn push_raw(&mut self, s: &str) {
1232        self.buf.push_str(s);
1233    }
1234    pub fn indent(&mut self) {
1235        self.indent_level += 1;
1236    }
1237    pub fn dedent(&mut self) {
1238        self.indent_level = self.indent_level.saturating_sub(1);
1239    }
1240    pub fn as_str(&self) -> &str {
1241        &self.buf
1242    }
1243    pub fn len(&self) -> usize {
1244        self.buf.len()
1245    }
1246    pub fn is_empty(&self) -> bool {
1247        self.buf.is_empty()
1248    }
1249    pub fn line_count(&self) -> usize {
1250        self.buf.lines().count()
1251    }
1252    pub fn into_string(self) -> String {
1253        self.buf
1254    }
1255    pub fn reset(&mut self) {
1256        self.buf.clear();
1257        self.indent_level = 0;
1258    }
1259}
1260/// Tracks declared names for RegAlloc scope analysis.
1261#[derive(Debug, Default)]
1262pub struct RegAllocNameScope {
1263    pub(super) declared: std::collections::HashSet<String>,
1264    pub(super) depth: usize,
1265    pub(super) parent: Option<Box<RegAllocNameScope>>,
1266}
1267impl RegAllocNameScope {
1268    pub fn new() -> Self {
1269        RegAllocNameScope::default()
1270    }
1271    pub fn declare(&mut self, name: impl Into<String>) -> bool {
1272        self.declared.insert(name.into())
1273    }
1274    pub fn is_declared(&self, name: &str) -> bool {
1275        self.declared.contains(name)
1276    }
1277    pub fn push_scope(self) -> Self {
1278        RegAllocNameScope {
1279            declared: std::collections::HashSet::new(),
1280            depth: self.depth + 1,
1281            parent: Some(Box::new(self)),
1282        }
1283    }
1284    pub fn pop_scope(self) -> Self {
1285        *self.parent.unwrap_or_default()
1286    }
1287    pub fn depth(&self) -> usize {
1288        self.depth
1289    }
1290    pub fn len(&self) -> usize {
1291        self.declared.len()
1292    }
1293}
1294/// Pass-timing record for RegAlloc profiler.
1295#[derive(Debug, Clone)]
1296pub struct RegAllocPassTiming {
1297    pub pass_name: String,
1298    pub elapsed_us: u64,
1299    pub items_processed: usize,
1300    pub bytes_before: usize,
1301    pub bytes_after: usize,
1302}
1303impl RegAllocPassTiming {
1304    pub fn new(
1305        pass_name: impl Into<String>,
1306        elapsed_us: u64,
1307        items: usize,
1308        before: usize,
1309        after: usize,
1310    ) -> Self {
1311        RegAllocPassTiming {
1312            pass_name: pass_name.into(),
1313            elapsed_us,
1314            items_processed: items,
1315            bytes_before: before,
1316            bytes_after: after,
1317        }
1318    }
1319    pub fn throughput_mps(&self) -> f64 {
1320        if self.elapsed_us == 0 {
1321            0.0
1322        } else {
1323            self.items_processed as f64 / (self.elapsed_us as f64 / 1_000_000.0)
1324        }
1325    }
1326    pub fn size_ratio(&self) -> f64 {
1327        if self.bytes_before == 0 {
1328            1.0
1329        } else {
1330            self.bytes_after as f64 / self.bytes_before as f64
1331        }
1332    }
1333    pub fn is_profitable(&self) -> bool {
1334        self.size_ratio() <= 1.05
1335    }
1336}
1337#[allow(dead_code)]
1338#[derive(Debug, Clone, Default)]
1339pub struct RAPassStats {
1340    pub total_runs: u32,
1341    pub successful_runs: u32,
1342    pub total_changes: u64,
1343    pub time_ms: u64,
1344    pub iterations_used: u32,
1345}
1346impl RAPassStats {
1347    #[allow(dead_code)]
1348    pub fn new() -> Self {
1349        Self::default()
1350    }
1351    #[allow(dead_code)]
1352    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
1353        self.total_runs += 1;
1354        self.successful_runs += 1;
1355        self.total_changes += changes;
1356        self.time_ms += time_ms;
1357        self.iterations_used = iterations;
1358    }
1359    #[allow(dead_code)]
1360    pub fn average_changes_per_run(&self) -> f64 {
1361        if self.total_runs == 0 {
1362            return 0.0;
1363        }
1364        self.total_changes as f64 / self.total_runs as f64
1365    }
1366    #[allow(dead_code)]
1367    pub fn success_rate(&self) -> f64 {
1368        if self.total_runs == 0 {
1369            return 0.0;
1370        }
1371        self.successful_runs as f64 / self.total_runs as f64
1372    }
1373    #[allow(dead_code)]
1374    pub fn format_summary(&self) -> String {
1375        format!(
1376            "Runs: {}/{}, Changes: {}, Time: {}ms",
1377            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
1378        )
1379    }
1380}
1381#[allow(dead_code)]
1382pub struct RAConstantFoldingHelper;
1383impl RAConstantFoldingHelper {
1384    #[allow(dead_code)]
1385    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
1386        a.checked_add(b)
1387    }
1388    #[allow(dead_code)]
1389    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
1390        a.checked_sub(b)
1391    }
1392    #[allow(dead_code)]
1393    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
1394        a.checked_mul(b)
1395    }
1396    #[allow(dead_code)]
1397    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
1398        if b == 0 {
1399            None
1400        } else {
1401            a.checked_div(b)
1402        }
1403    }
1404    #[allow(dead_code)]
1405    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
1406        a + b
1407    }
1408    #[allow(dead_code)]
1409    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
1410        a * b
1411    }
1412    #[allow(dead_code)]
1413    pub fn fold_neg_i64(a: i64) -> Option<i64> {
1414        a.checked_neg()
1415    }
1416    #[allow(dead_code)]
1417    pub fn fold_not_bool(a: bool) -> bool {
1418        !a
1419    }
1420    #[allow(dead_code)]
1421    pub fn fold_and_bool(a: bool, b: bool) -> bool {
1422        a && b
1423    }
1424    #[allow(dead_code)]
1425    pub fn fold_or_bool(a: bool, b: bool) -> bool {
1426        a || b
1427    }
1428    #[allow(dead_code)]
1429    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
1430        a.checked_shl(b)
1431    }
1432    #[allow(dead_code)]
1433    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
1434        a.checked_shr(b)
1435    }
1436    #[allow(dead_code)]
1437    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
1438        if b == 0 {
1439            None
1440        } else {
1441            Some(a % b)
1442        }
1443    }
1444    #[allow(dead_code)]
1445    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
1446        a & b
1447    }
1448    #[allow(dead_code)]
1449    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
1450        a | b
1451    }
1452    #[allow(dead_code)]
1453    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
1454        a ^ b
1455    }
1456    #[allow(dead_code)]
1457    pub fn fold_bitnot_i64(a: i64) -> i64 {
1458        !a
1459    }
1460}
1461/// A virtual register (pre-allocation).
1462#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1463pub struct VirtualReg {
1464    /// Unique ID matching the corresponding `LcnfVarId`.
1465    pub id: u32,
1466    /// The LCNF type of this register's value.
1467    pub ty: LcnfType,
1468    /// An optional hint towards a preferred physical register.
1469    pub hint: Option<PhysReg>,
1470}
1471impl VirtualReg {
1472    /// Create a new virtual register without a hint.
1473    pub fn new(id: u32, ty: LcnfType) -> Self {
1474        VirtualReg { id, ty, hint: None }
1475    }
1476    /// Create a virtual register with a physical-register hint.
1477    pub fn with_hint(id: u32, ty: LcnfType, hint: PhysReg) -> Self {
1478        VirtualReg {
1479            id,
1480            ty,
1481            hint: Some(hint),
1482        }
1483    }
1484    /// Determine which register class this vreg belongs to.
1485    pub fn reg_class(&self) -> RegClass {
1486        match &self.ty {
1487            LcnfType::Fun(_, _) => RegClass::Integer,
1488            LcnfType::Nat => RegClass::Integer,
1489            LcnfType::LcnfString => RegClass::Integer,
1490            LcnfType::Object => RegClass::Integer,
1491            LcnfType::Ctor(_, _) => RegClass::Integer,
1492            LcnfType::Var(_) => RegClass::Integer,
1493            LcnfType::Erased | LcnfType::Unit | LcnfType::Irrelevant => RegClass::Integer,
1494        }
1495    }
1496}
1497/// The live interval [start, end) of a virtual register.
1498///
1499/// `start` is the instruction index where the vreg is first defined,
1500/// `end` is one past the last use.
1501#[derive(Debug, Clone, PartialEq)]
1502pub struct LiveInterval {
1503    /// The virtual register this interval belongs to.
1504    pub vreg: LcnfVarId,
1505    /// Inclusive start (def point).
1506    pub start: u32,
1507    /// Exclusive end (last use + 1).
1508    pub end: u32,
1509    /// All use points (instruction indices where this vreg is read).
1510    pub uses: Vec<u32>,
1511    /// All def points (instruction indices where this vreg is written).
1512    pub defs: Vec<u32>,
1513    /// Spill weight: higher = more costly to spill.
1514    pub spill_weight: f64,
1515}
1516impl LiveInterval {
1517    /// Create a new live interval.
1518    pub fn new(vreg: LcnfVarId, start: u32, end: u32) -> Self {
1519        LiveInterval {
1520            vreg,
1521            start,
1522            end,
1523            uses: vec![],
1524            defs: vec![],
1525            spill_weight: 1.0,
1526        }
1527    }
1528    /// Returns `true` if this interval overlaps with `other`.
1529    pub fn overlaps(&self, other: &LiveInterval) -> bool {
1530        self.start < other.end && other.start < self.end
1531    }
1532    /// Length of the live interval in instructions.
1533    pub fn length(&self) -> u32 {
1534        self.end.saturating_sub(self.start)
1535    }
1536    /// Add a use point.
1537    pub fn add_use(&mut self, pos: u32) {
1538        if !self.uses.contains(&pos) {
1539            self.uses.push(pos);
1540            if pos >= self.end {
1541                self.end = pos + 1;
1542            }
1543        }
1544    }
1545    /// Add a def point.
1546    pub fn add_def(&mut self, pos: u32) {
1547        if !self.defs.contains(&pos) {
1548            self.defs.push(pos);
1549            if pos < self.start {
1550                self.start = pos;
1551            }
1552        }
1553    }
1554    /// Compute spill weight = total uses / length (more uses → costlier to spill).
1555    pub fn compute_spill_weight(&mut self) {
1556        let len = self.length().max(1) as f64;
1557        self.spill_weight = self.uses.len() as f64 / len;
1558    }
1559}
1560#[allow(dead_code)]
1561pub struct RAPassRegistry {
1562    pub(super) configs: Vec<RAPassConfig>,
1563    pub(super) stats: std::collections::HashMap<String, RAPassStats>,
1564}
1565impl RAPassRegistry {
1566    #[allow(dead_code)]
1567    pub fn new() -> Self {
1568        RAPassRegistry {
1569            configs: Vec::new(),
1570            stats: std::collections::HashMap::new(),
1571        }
1572    }
1573    #[allow(dead_code)]
1574    pub fn register(&mut self, config: RAPassConfig) {
1575        self.stats
1576            .insert(config.pass_name.clone(), RAPassStats::new());
1577        self.configs.push(config);
1578    }
1579    #[allow(dead_code)]
1580    pub fn enabled_passes(&self) -> Vec<&RAPassConfig> {
1581        self.configs.iter().filter(|c| c.enabled).collect()
1582    }
1583    #[allow(dead_code)]
1584    pub fn get_stats(&self, name: &str) -> Option<&RAPassStats> {
1585        self.stats.get(name)
1586    }
1587    #[allow(dead_code)]
1588    pub fn total_passes(&self) -> usize {
1589        self.configs.len()
1590    }
1591    #[allow(dead_code)]
1592    pub fn enabled_count(&self) -> usize {
1593        self.enabled_passes().len()
1594    }
1595    #[allow(dead_code)]
1596    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
1597        if let Some(stats) = self.stats.get_mut(name) {
1598            stats.record_run(changes, time_ms, iter);
1599        }
1600    }
1601}
1602/// Configuration for RAExt passes.
1603#[allow(dead_code)]
1604#[derive(Debug, Clone)]
1605pub struct RAExtPassConfig {
1606    pub name: String,
1607    pub phase: RAExtPassPhase,
1608    pub enabled: bool,
1609    pub max_iterations: usize,
1610    pub debug: u32,
1611    pub timeout_ms: Option<u64>,
1612}
1613impl RAExtPassConfig {
1614    #[allow(dead_code)]
1615    pub fn new(name: impl Into<String>) -> Self {
1616        Self {
1617            name: name.into(),
1618            phase: RAExtPassPhase::Middle,
1619            enabled: true,
1620            max_iterations: 100,
1621            debug: 0,
1622            timeout_ms: None,
1623        }
1624    }
1625    #[allow(dead_code)]
1626    pub fn with_phase(mut self, phase: RAExtPassPhase) -> Self {
1627        self.phase = phase;
1628        self
1629    }
1630    #[allow(dead_code)]
1631    pub fn with_max_iter(mut self, n: usize) -> Self {
1632        self.max_iterations = n;
1633        self
1634    }
1635    #[allow(dead_code)]
1636    pub fn with_debug(mut self, d: u32) -> Self {
1637        self.debug = d;
1638        self
1639    }
1640    #[allow(dead_code)]
1641    pub fn disabled(mut self) -> Self {
1642        self.enabled = false;
1643        self
1644    }
1645    #[allow(dead_code)]
1646    pub fn with_timeout(mut self, ms: u64) -> Self {
1647        self.timeout_ms = Some(ms);
1648        self
1649    }
1650    #[allow(dead_code)]
1651    pub fn is_debug_enabled(&self) -> bool {
1652        self.debug > 0
1653    }
1654}
1655/// Worklist for RAExt.
1656#[allow(dead_code)]
1657#[derive(Debug, Clone)]
1658pub struct RAExtWorklist {
1659    pub(super) items: std::collections::VecDeque<usize>,
1660    pub(super) present: Vec<bool>,
1661}
1662impl RAExtWorklist {
1663    #[allow(dead_code)]
1664    pub fn new(capacity: usize) -> Self {
1665        Self {
1666            items: std::collections::VecDeque::new(),
1667            present: vec![false; capacity],
1668        }
1669    }
1670    #[allow(dead_code)]
1671    pub fn push(&mut self, id: usize) {
1672        if id < self.present.len() && !self.present[id] {
1673            self.present[id] = true;
1674            self.items.push_back(id);
1675        }
1676    }
1677    #[allow(dead_code)]
1678    pub fn push_front(&mut self, id: usize) {
1679        if id < self.present.len() && !self.present[id] {
1680            self.present[id] = true;
1681            self.items.push_front(id);
1682        }
1683    }
1684    #[allow(dead_code)]
1685    pub fn pop(&mut self) -> Option<usize> {
1686        let id = self.items.pop_front()?;
1687        if id < self.present.len() {
1688            self.present[id] = false;
1689        }
1690        Some(id)
1691    }
1692    #[allow(dead_code)]
1693    pub fn is_empty(&self) -> bool {
1694        self.items.is_empty()
1695    }
1696    #[allow(dead_code)]
1697    pub fn len(&self) -> usize {
1698        self.items.len()
1699    }
1700    #[allow(dead_code)]
1701    pub fn contains(&self, id: usize) -> bool {
1702        id < self.present.len() && self.present[id]
1703    }
1704    #[allow(dead_code)]
1705    pub fn drain_all(&mut self) -> Vec<usize> {
1706        let v: Vec<usize> = self.items.drain(..).collect();
1707        for &id in &v {
1708            if id < self.present.len() {
1709                self.present[id] = false;
1710            }
1711        }
1712        v
1713    }
1714}
1715/// A version tag for RegAlloc output artifacts.
1716#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1717pub struct RegAllocVersion {
1718    pub major: u32,
1719    pub minor: u32,
1720    pub patch: u32,
1721    pub pre: Option<String>,
1722}
1723impl RegAllocVersion {
1724    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
1725        RegAllocVersion {
1726            major,
1727            minor,
1728            patch,
1729            pre: None,
1730        }
1731    }
1732    pub fn with_pre(mut self, pre: impl Into<String>) -> Self {
1733        self.pre = Some(pre.into());
1734        self
1735    }
1736    pub fn is_stable(&self) -> bool {
1737        self.pre.is_none()
1738    }
1739    pub fn is_compatible_with(&self, other: &RegAllocVersion) -> bool {
1740        self.major == other.major && self.minor >= other.minor
1741    }
1742}
1743#[allow(dead_code)]
1744#[derive(Debug, Clone, PartialEq)]
1745pub enum RAPassPhase {
1746    Analysis,
1747    Transformation,
1748    Verification,
1749    Cleanup,
1750}
1751impl RAPassPhase {
1752    #[allow(dead_code)]
1753    pub fn name(&self) -> &str {
1754        match self {
1755            RAPassPhase::Analysis => "analysis",
1756            RAPassPhase::Transformation => "transformation",
1757            RAPassPhase::Verification => "verification",
1758            RAPassPhase::Cleanup => "cleanup",
1759        }
1760    }
1761    #[allow(dead_code)]
1762    pub fn is_modifying(&self) -> bool {
1763        matches!(self, RAPassPhase::Transformation | RAPassPhase::Cleanup)
1764    }
1765}
1766/// Statistics for RAExt passes.
1767#[allow(dead_code)]
1768#[derive(Debug, Clone, Default)]
1769pub struct RAExtPassStats {
1770    pub iterations: usize,
1771    pub changed: bool,
1772    pub nodes_visited: usize,
1773    pub nodes_modified: usize,
1774    pub time_ms: u64,
1775    pub memory_bytes: usize,
1776    pub errors: usize,
1777}
1778impl RAExtPassStats {
1779    #[allow(dead_code)]
1780    pub fn new() -> Self {
1781        Self::default()
1782    }
1783    #[allow(dead_code)]
1784    pub fn visit(&mut self) {
1785        self.nodes_visited += 1;
1786    }
1787    #[allow(dead_code)]
1788    pub fn modify(&mut self) {
1789        self.nodes_modified += 1;
1790        self.changed = true;
1791    }
1792    #[allow(dead_code)]
1793    pub fn iterate(&mut self) {
1794        self.iterations += 1;
1795    }
1796    #[allow(dead_code)]
1797    pub fn error(&mut self) {
1798        self.errors += 1;
1799    }
1800    #[allow(dead_code)]
1801    pub fn efficiency(&self) -> f64 {
1802        if self.nodes_visited == 0 {
1803            0.0
1804        } else {
1805            self.nodes_modified as f64 / self.nodes_visited as f64
1806        }
1807    }
1808    #[allow(dead_code)]
1809    pub fn merge(&mut self, o: &RAExtPassStats) {
1810        self.iterations += o.iterations;
1811        self.changed |= o.changed;
1812        self.nodes_visited += o.nodes_visited;
1813        self.nodes_modified += o.nodes_modified;
1814        self.time_ms += o.time_ms;
1815        self.memory_bytes = self.memory_bytes.max(o.memory_bytes);
1816        self.errors += o.errors;
1817    }
1818}
1819/// Pass registry for RAExt.
1820#[allow(dead_code)]
1821#[derive(Debug, Default)]
1822pub struct RAExtPassRegistry {
1823    pub(super) configs: Vec<RAExtPassConfig>,
1824    pub(super) stats: Vec<RAExtPassStats>,
1825}
1826impl RAExtPassRegistry {
1827    #[allow(dead_code)]
1828    pub fn new() -> Self {
1829        Self::default()
1830    }
1831    #[allow(dead_code)]
1832    pub fn register(&mut self, c: RAExtPassConfig) {
1833        self.stats.push(RAExtPassStats::new());
1834        self.configs.push(c);
1835    }
1836    #[allow(dead_code)]
1837    pub fn len(&self) -> usize {
1838        self.configs.len()
1839    }
1840    #[allow(dead_code)]
1841    pub fn is_empty(&self) -> bool {
1842        self.configs.is_empty()
1843    }
1844    #[allow(dead_code)]
1845    pub fn get(&self, i: usize) -> Option<&RAExtPassConfig> {
1846        self.configs.get(i)
1847    }
1848    #[allow(dead_code)]
1849    pub fn get_stats(&self, i: usize) -> Option<&RAExtPassStats> {
1850        self.stats.get(i)
1851    }
1852    #[allow(dead_code)]
1853    pub fn enabled_passes(&self) -> Vec<&RAExtPassConfig> {
1854        self.configs.iter().filter(|c| c.enabled).collect()
1855    }
1856    #[allow(dead_code)]
1857    pub fn passes_in_phase(&self, ph: &RAExtPassPhase) -> Vec<&RAExtPassConfig> {
1858        self.configs
1859            .iter()
1860            .filter(|c| c.enabled && &c.phase == ph)
1861            .collect()
1862    }
1863    #[allow(dead_code)]
1864    pub fn total_nodes_visited(&self) -> usize {
1865        self.stats.iter().map(|s| s.nodes_visited).sum()
1866    }
1867    #[allow(dead_code)]
1868    pub fn any_changed(&self) -> bool {
1869        self.stats.iter().any(|s| s.changed)
1870    }
1871}
1872/// Analysis cache for RAExt.
1873#[allow(dead_code)]
1874#[derive(Debug)]
1875pub struct RAExtCache {
1876    pub(super) entries: Vec<(u64, Vec<u8>, bool, u32)>,
1877    pub(super) cap: usize,
1878    pub(super) total_hits: u64,
1879    pub(super) total_misses: u64,
1880}
1881impl RAExtCache {
1882    #[allow(dead_code)]
1883    pub fn new(cap: usize) -> Self {
1884        Self {
1885            entries: Vec::new(),
1886            cap,
1887            total_hits: 0,
1888            total_misses: 0,
1889        }
1890    }
1891    #[allow(dead_code)]
1892    pub fn get(&mut self, key: u64) -> Option<&[u8]> {
1893        for e in self.entries.iter_mut() {
1894            if e.0 == key && e.2 {
1895                e.3 += 1;
1896                self.total_hits += 1;
1897                return Some(&e.1);
1898            }
1899        }
1900        self.total_misses += 1;
1901        None
1902    }
1903    #[allow(dead_code)]
1904    pub fn put(&mut self, key: u64, data: Vec<u8>) {
1905        if self.entries.len() >= self.cap {
1906            self.entries.retain(|e| e.2);
1907            if self.entries.len() >= self.cap {
1908                self.entries.remove(0);
1909            }
1910        }
1911        self.entries.push((key, data, true, 0));
1912    }
1913    #[allow(dead_code)]
1914    pub fn invalidate(&mut self) {
1915        for e in self.entries.iter_mut() {
1916            e.2 = false;
1917        }
1918    }
1919    #[allow(dead_code)]
1920    pub fn hit_rate(&self) -> f64 {
1921        let t = self.total_hits + self.total_misses;
1922        if t == 0 {
1923            0.0
1924        } else {
1925            self.total_hits as f64 / t as f64
1926        }
1927    }
1928    #[allow(dead_code)]
1929    pub fn live_count(&self) -> usize {
1930        self.entries.iter().filter(|e| e.2).count()
1931    }
1932}
1933/// Constant folding helper for RAExt.
1934#[allow(dead_code)]
1935#[derive(Debug, Clone, Default)]
1936pub struct RAExtConstFolder {
1937    pub(super) folds: usize,
1938    pub(super) failures: usize,
1939    pub(super) enabled: bool,
1940}
1941impl RAExtConstFolder {
1942    #[allow(dead_code)]
1943    pub fn new() -> Self {
1944        Self {
1945            folds: 0,
1946            failures: 0,
1947            enabled: true,
1948        }
1949    }
1950    #[allow(dead_code)]
1951    pub fn add_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1952        self.folds += 1;
1953        a.checked_add(b)
1954    }
1955    #[allow(dead_code)]
1956    pub fn sub_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1957        self.folds += 1;
1958        a.checked_sub(b)
1959    }
1960    #[allow(dead_code)]
1961    pub fn mul_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1962        self.folds += 1;
1963        a.checked_mul(b)
1964    }
1965    #[allow(dead_code)]
1966    pub fn div_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1967        if b == 0 {
1968            self.failures += 1;
1969            None
1970        } else {
1971            self.folds += 1;
1972            a.checked_div(b)
1973        }
1974    }
1975    #[allow(dead_code)]
1976    pub fn rem_i64(&mut self, a: i64, b: i64) -> Option<i64> {
1977        if b == 0 {
1978            self.failures += 1;
1979            None
1980        } else {
1981            self.folds += 1;
1982            a.checked_rem(b)
1983        }
1984    }
1985    #[allow(dead_code)]
1986    pub fn neg_i64(&mut self, a: i64) -> Option<i64> {
1987        self.folds += 1;
1988        a.checked_neg()
1989    }
1990    #[allow(dead_code)]
1991    pub fn shl_i64(&mut self, a: i64, s: u32) -> Option<i64> {
1992        if s >= 64 {
1993            self.failures += 1;
1994            None
1995        } else {
1996            self.folds += 1;
1997            a.checked_shl(s)
1998        }
1999    }
2000    #[allow(dead_code)]
2001    pub fn shr_i64(&mut self, a: i64, s: u32) -> Option<i64> {
2002        if s >= 64 {
2003            self.failures += 1;
2004            None
2005        } else {
2006            self.folds += 1;
2007            a.checked_shr(s)
2008        }
2009    }
2010    #[allow(dead_code)]
2011    pub fn and_i64(&mut self, a: i64, b: i64) -> i64 {
2012        self.folds += 1;
2013        a & b
2014    }
2015    #[allow(dead_code)]
2016    pub fn or_i64(&mut self, a: i64, b: i64) -> i64 {
2017        self.folds += 1;
2018        a | b
2019    }
2020    #[allow(dead_code)]
2021    pub fn xor_i64(&mut self, a: i64, b: i64) -> i64 {
2022        self.folds += 1;
2023        a ^ b
2024    }
2025    #[allow(dead_code)]
2026    pub fn not_i64(&mut self, a: i64) -> i64 {
2027        self.folds += 1;
2028        !a
2029    }
2030    #[allow(dead_code)]
2031    pub fn fold_count(&self) -> usize {
2032        self.folds
2033    }
2034    #[allow(dead_code)]
2035    pub fn failure_count(&self) -> usize {
2036        self.failures
2037    }
2038    #[allow(dead_code)]
2039    pub fn enable(&mut self) {
2040        self.enabled = true;
2041    }
2042    #[allow(dead_code)]
2043    pub fn disable(&mut self) {
2044        self.enabled = false;
2045    }
2046    #[allow(dead_code)]
2047    pub fn is_enabled(&self) -> bool {
2048        self.enabled
2049    }
2050}
2051/// Linear scan register allocator.
2052///
2053/// Algorithm (Poletto & Sarkar, 1999):
2054/// 1. Sort live intervals by start point.
2055/// 2. For each interval:
2056///    a. Expire all intervals that end before this one starts → free their registers.
2057///    b. If a physical register is free, assign it.
2058///    c. Otherwise spill the interval with the furthest end point (or this interval).
2059#[derive(Debug)]
2060pub struct LinearScanAllocator {
2061    /// Available physical registers.
2062    pub phys_regs: Vec<PhysReg>,
2063    /// Number of spills performed.
2064    pub spill_count: usize,
2065}
2066impl LinearScanAllocator {
2067    /// Create a new allocator with the given physical register bank.
2068    pub fn new(phys_regs: Vec<PhysReg>) -> Self {
2069        LinearScanAllocator {
2070            phys_regs,
2071            spill_count: 0,
2072        }
2073    }
2074    /// Build live intervals for all variables in a function declaration.
2075    ///
2076    /// Uses a simple linear numbering: each let-binding gets a consecutive index.
2077    pub fn build_live_intervals(&self, decl: &LcnfFunDecl) -> Vec<LiveInterval> {
2078        let mut counter = 0u32;
2079        let mut intervals: HashMap<LcnfVarId, LiveInterval> = HashMap::new();
2080        for param in &decl.params {
2081            let mut iv = LiveInterval::new(param.id, 0, 1);
2082            iv.add_def(0);
2083            intervals.insert(param.id, iv);
2084        }
2085        collect_intervals_from_expr(&decl.body, &mut counter, &mut intervals);
2086        let mut result: Vec<LiveInterval> = intervals.into_values().collect();
2087        for iv in &mut result {
2088            iv.compute_spill_weight();
2089        }
2090        result.sort_by_key(|iv| iv.start);
2091        result
2092    }
2093    /// Run linear scan allocation.
2094    ///
2095    /// Returns an `Allocation` mapping vregs to pregs (or spill slots).
2096    pub fn linear_scan(&mut self, intervals: Vec<LiveInterval>, num_phys: usize) -> Allocation {
2097        let mut alloc = Allocation::new();
2098        let mut free_regs: Vec<usize> = (0..num_phys.min(self.phys_regs.len())).collect();
2099        let mut active: Vec<(u32, LcnfVarId, usize)> = Vec::new();
2100        for iv in &intervals {
2101            active.retain(|&(end, old_vreg, preg_idx)| {
2102                if end <= iv.start {
2103                    free_regs.push(preg_idx);
2104                    let _ = old_vreg;
2105                    false
2106                } else {
2107                    true
2108                }
2109            });
2110            if let Some(preg_idx) = free_regs.pop() {
2111                alloc.assign(iv.vreg, self.phys_regs[preg_idx].clone());
2112                active.push((iv.end, iv.vreg, preg_idx));
2113                active.sort_by_key(|&(end, _, _)| end);
2114            } else {
2115                if let Some(&(far_end, far_vreg, preg_idx)) = active.last() {
2116                    if far_end > iv.end {
2117                        alloc.spill(far_vreg, 8);
2118                        self.spill_count += 1;
2119                        active.pop();
2120                        alloc.assign(iv.vreg, self.phys_regs[preg_idx].clone());
2121                        active.push((iv.end, iv.vreg, preg_idx));
2122                        active.sort_by_key(|&(end, _, _)| end);
2123                    } else {
2124                        alloc.spill(iv.vreg, 8);
2125                        self.spill_count += 1;
2126                    }
2127                } else {
2128                    alloc.spill(iv.vreg, 8);
2129                    self.spill_count += 1;
2130                }
2131            }
2132        }
2133        alloc
2134    }
2135    /// Spill a specific virtual register (creates a spill slot).
2136    pub fn handle_spill(&mut self, vreg: LcnfVarId, alloc: &mut Allocation) -> SpillSlot {
2137        self.spill_count += 1;
2138        alloc.spill(vreg, 8)
2139    }
2140}
2141/// A fixed-capacity ring buffer of strings (for recent-event logging in RegAlloc).
2142#[derive(Debug)]
2143pub struct RegAllocEventLog {
2144    pub(super) entries: std::collections::VecDeque<String>,
2145    pub(super) capacity: usize,
2146}
2147impl RegAllocEventLog {
2148    pub fn new(capacity: usize) -> Self {
2149        RegAllocEventLog {
2150            entries: std::collections::VecDeque::with_capacity(capacity),
2151            capacity,
2152        }
2153    }
2154    pub fn push(&mut self, event: impl Into<String>) {
2155        if self.entries.len() >= self.capacity {
2156            self.entries.pop_front();
2157        }
2158        self.entries.push_back(event.into());
2159    }
2160    pub fn iter(&self) -> impl Iterator<Item = &String> {
2161        self.entries.iter()
2162    }
2163    pub fn len(&self) -> usize {
2164        self.entries.len()
2165    }
2166    pub fn is_empty(&self) -> bool {
2167        self.entries.is_empty()
2168    }
2169    pub fn capacity(&self) -> usize {
2170        self.capacity
2171    }
2172    pub fn clear(&mut self) {
2173        self.entries.clear();
2174    }
2175}
2176#[allow(dead_code)]
2177#[derive(Debug, Clone)]
2178pub struct RADepGraph {
2179    pub(super) nodes: Vec<u32>,
2180    pub(super) edges: Vec<(u32, u32)>,
2181}
2182impl RADepGraph {
2183    #[allow(dead_code)]
2184    pub fn new() -> Self {
2185        RADepGraph {
2186            nodes: Vec::new(),
2187            edges: Vec::new(),
2188        }
2189    }
2190    #[allow(dead_code)]
2191    pub fn add_node(&mut self, id: u32) {
2192        if !self.nodes.contains(&id) {
2193            self.nodes.push(id);
2194        }
2195    }
2196    #[allow(dead_code)]
2197    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
2198        self.add_node(dep);
2199        self.add_node(dependent);
2200        self.edges.push((dep, dependent));
2201    }
2202    #[allow(dead_code)]
2203    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
2204        self.edges
2205            .iter()
2206            .filter(|(d, _)| *d == node)
2207            .map(|(_, dep)| *dep)
2208            .collect()
2209    }
2210    #[allow(dead_code)]
2211    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
2212        self.edges
2213            .iter()
2214            .filter(|(_, dep)| *dep == node)
2215            .map(|(d, _)| *d)
2216            .collect()
2217    }
2218    #[allow(dead_code)]
2219    pub fn topological_sort(&self) -> Vec<u32> {
2220        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
2221        for &n in &self.nodes {
2222            in_degree.insert(n, 0);
2223        }
2224        for (_, dep) in &self.edges {
2225            *in_degree.entry(*dep).or_insert(0) += 1;
2226        }
2227        let mut queue: std::collections::VecDeque<u32> = self
2228            .nodes
2229            .iter()
2230            .filter(|&&n| in_degree[&n] == 0)
2231            .copied()
2232            .collect();
2233        let mut result = Vec::new();
2234        while let Some(node) = queue.pop_front() {
2235            result.push(node);
2236            for dep in self.dependents_of(node) {
2237                let cnt = in_degree.entry(dep).or_insert(0);
2238                *cnt = cnt.saturating_sub(1);
2239                if *cnt == 0 {
2240                    queue.push_back(dep);
2241                }
2242            }
2243        }
2244        result
2245    }
2246    #[allow(dead_code)]
2247    pub fn has_cycle(&self) -> bool {
2248        self.topological_sort().len() < self.nodes.len()
2249    }
2250}
2251/// Dominator tree for RAExt.
2252#[allow(dead_code)]
2253#[derive(Debug, Clone)]
2254pub struct RAExtDomTree {
2255    pub(super) idom: Vec<Option<usize>>,
2256    pub(super) children: Vec<Vec<usize>>,
2257    pub(super) depth: Vec<usize>,
2258}
2259impl RAExtDomTree {
2260    #[allow(dead_code)]
2261    pub fn new(n: usize) -> Self {
2262        Self {
2263            idom: vec![None; n],
2264            children: vec![Vec::new(); n],
2265            depth: vec![0; n],
2266        }
2267    }
2268    #[allow(dead_code)]
2269    pub fn set_idom(&mut self, node: usize, dom: usize) {
2270        if node < self.idom.len() {
2271            self.idom[node] = Some(dom);
2272            if dom < self.children.len() {
2273                self.children[dom].push(node);
2274            }
2275            self.depth[node] = if dom < self.depth.len() {
2276                self.depth[dom] + 1
2277            } else {
2278                1
2279            };
2280        }
2281    }
2282    #[allow(dead_code)]
2283    pub fn dominates(&self, a: usize, mut b: usize) -> bool {
2284        if a == b {
2285            return true;
2286        }
2287        let n = self.idom.len();
2288        for _ in 0..n {
2289            match self.idom.get(b).copied().flatten() {
2290                None => return false,
2291                Some(p) if p == a => return true,
2292                Some(p) if p == b => return false,
2293                Some(p) => b = p,
2294            }
2295        }
2296        false
2297    }
2298    #[allow(dead_code)]
2299    pub fn children_of(&self, n: usize) -> &[usize] {
2300        self.children.get(n).map(|v| v.as_slice()).unwrap_or(&[])
2301    }
2302    #[allow(dead_code)]
2303    pub fn depth_of(&self, n: usize) -> usize {
2304        self.depth.get(n).copied().unwrap_or(0)
2305    }
2306    #[allow(dead_code)]
2307    pub fn lca(&self, mut a: usize, mut b: usize) -> usize {
2308        let n = self.idom.len();
2309        for _ in 0..(2 * n) {
2310            if a == b {
2311                return a;
2312            }
2313            if self.depth_of(a) > self.depth_of(b) {
2314                a = self.idom.get(a).and_then(|x| *x).unwrap_or(a);
2315            } else {
2316                b = self.idom.get(b).and_then(|x| *x).unwrap_or(b);
2317            }
2318        }
2319        0
2320    }
2321}