Skip to main content

oxilean_codegen/pipeline/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::c_backend;
6use crate::lcnf::*;
7use crate::native_backend;
8use crate::opt_join::{self, JoinPointConfig};
9use crate::opt_reuse::{self, ReuseConfig};
10use crate::opt_specialize::{self, SpecializationConfig};
11use crate::CodegenTarget;
12use oxilean_kernel::expr::Expr;
13use oxilean_kernel::Name;
14
15use super::types::{
16    CompilerPipeline, OptLevel, PassId, PassStats, PipeAnalysisCache, PipeConstantFoldingHelper,
17    PipeDepGraph, PipeDominatorTree, PipeExtCache, PipeExtConstFolder, PipeExtDepGraph,
18    PipeExtDomTree, PipeExtLiveness, PipeExtPassConfig, PipeExtPassPhase, PipeExtPassRegistry,
19    PipeExtPassStats, PipeExtWorklist, PipeLivenessInfo, PipePassConfig, PipePassPhase,
20    PipePassRegistry, PipePassStats, PipeWorklist, PipeX2Cache, PipeX2ConstFolder, PipeX2DepGraph,
21    PipeX2DomTree, PipeX2Liveness, PipeX2PassConfig, PipeX2PassPhase, PipeX2PassRegistry,
22    PipeX2PassStats, PipeX2Worklist, PipelineBuilder, PipelineChangeSummary, PipelineConfig,
23    PipelineResult, PipelineStats,
24};
25
26/// Type alias for the raw kernel input to `exprs_to_lcnf`: (name, params, body).
27pub type LcnfDeclInput = (Name, Vec<(Name, Expr)>, Expr);
28/// Peel leading `Lam` binders from a kernel expression, collecting the
29/// parameter names and types.
30///
31/// Returns `(params, body)` where `params` is a list of `(Name, Expr)` pairs
32/// (one per peeled lambda) and `body` is the residual expression after all
33/// leading lambdas have been removed.
34pub(super) fn peel_lam_params(expr: &Expr) -> (Vec<(Name, Expr)>, Expr) {
35    let mut params = Vec::new();
36    let mut cur = expr.clone();
37    loop {
38        match cur {
39            Expr::Lam(_, name, ty, body) => {
40                params.push((name, *ty));
41                cur = *body;
42            }
43            other => {
44                return (params, other);
45            }
46        }
47    }
48}
49/// Run the join point optimization pass.
50///
51/// Identifies expressions shared across case branches and hoists them into
52/// join-point let-bindings, reducing code duplication and enabling further
53/// optimisations such as tail-call elimination.
54pub(super) fn run_join_point_pass(module: &LcnfModule) -> LcnfModule {
55    let config = JoinPointConfig::default();
56    let mut result = module.clone();
57    opt_join::optimize_join_points(&mut result, &config);
58    result
59}
60/// Run the specialization pass.
61///
62/// Creates monomorphised copies of polymorphic functions for the concrete
63/// argument types observed at call sites (e.g. `Nat → u64`), allowing the
64/// back-end to emit more efficient code without runtime dispatch overhead.
65pub(super) fn run_specialize_pass(module: &LcnfModule) -> LcnfModule {
66    let config = SpecializationConfig::default();
67    let mut result = module.clone();
68    opt_specialize::specialize_module(&mut result, &config);
69    result
70}
71/// Run the reuse optimization pass.
72///
73/// Performs reset–reuse analysis (inspired by "Counting Immutable Beans"):
74/// when a heap-allocated value is the last use of a uniquely-owned cell the
75/// cell's memory can be recycled for the result, avoiding a fresh allocation.
76/// Also infers borrow annotations to reduce reference-counting traffic.
77pub(super) fn run_reuse_pass(module: &LcnfModule) -> LcnfModule {
78    let config = ReuseConfig::default();
79    let mut result = module.clone();
80    opt_reuse::optimize_reuse(&mut result, &config);
81    result
82}
83/// Count the total number of let-bindings in a module.
84pub(super) fn count_module_lets(module: &LcnfModule) -> usize {
85    module
86        .fun_decls
87        .iter()
88        .map(|d| count_expr_lets(&d.body))
89        .sum()
90}
91/// Count let-bindings in an expression.
92pub(super) fn count_expr_lets(expr: &LcnfExpr) -> usize {
93    match expr {
94        LcnfExpr::Let { body, .. } => 1 + count_expr_lets(body),
95        LcnfExpr::Case { alts, default, .. } => {
96            let alt_count: usize = alts.iter().map(|a| count_expr_lets(&a.body)).sum();
97            let def_count = default.as_ref().map(|d| count_expr_lets(d)).unwrap_or(0);
98            alt_count + def_count
99        }
100        _ => 0,
101    }
102}
103/// Compile an LCNF module with the given optimization level and target.
104pub fn compile_module(
105    module: &LcnfModule,
106    opt_level: OptLevel,
107    target: CodegenTarget,
108) -> PipelineResult {
109    let config = PipelineConfig {
110        opt_level,
111        target,
112        ..Default::default()
113    };
114    let pipeline = CompilerPipeline::new(config.clone());
115    let mut stats = PipelineStats {
116        input_decls: module.fun_decls.len(),
117        ..Default::default()
118    };
119    let passes = config.effective_passes();
120    let max_iter = config.effective_max_iterations();
121    let mut optimized = module.clone();
122    if !passes.is_empty() {
123        optimized = pipeline.iterate_to_fixpoint(optimized, &passes, max_iter, &mut stats);
124    }
125    stats.output_decls = optimized.fun_decls.len();
126    let mut result = PipelineResult {
127        c_output: None,
128        native_output: None,
129        lcnf_module: optimized.clone(),
130        stats,
131    };
132    match target {
133        CodegenTarget::C => {
134            let c_output = c_backend::compile_to_c_default(&optimized);
135            result.c_output = Some(c_output);
136        }
137        CodegenTarget::LlvmIr | CodegenTarget::Rust => {
138            let native_module = native_backend::compile_to_native(&optimized);
139            result.native_output = Some(native_module);
140        }
141        CodegenTarget::Interpreter => {}
142    }
143    result
144}
145/// Quick compilation: O0, C target, no optimization.
146pub fn compile_module_o0(module: &LcnfModule) -> PipelineResult {
147    compile_module(module, OptLevel::O0, CodegenTarget::C)
148}
149/// Standard compilation: O2, C target.
150pub fn compile_module_o2(module: &LcnfModule) -> PipelineResult {
151    compile_module(module, OptLevel::O2, CodegenTarget::C)
152}
153#[cfg(test)]
154mod tests {
155    use super::*;
156    pub(super) fn vid(n: u64) -> LcnfVarId {
157        LcnfVarId(n)
158    }
159    pub(super) fn mk_param(n: u64, name: &str) -> LcnfParam {
160        LcnfParam {
161            id: vid(n),
162            name: name.to_string(),
163            ty: LcnfType::Nat,
164            erased: false,
165            borrowed: false,
166        }
167    }
168    pub(super) fn mk_fun_decl(name: &str, body: LcnfExpr) -> LcnfFunDecl {
169        LcnfFunDecl {
170            name: name.to_string(),
171            original_name: None,
172            params: vec![mk_param(0, "x")],
173            ret_type: LcnfType::Nat,
174            body,
175            is_recursive: false,
176            is_lifted: false,
177            inline_cost: 1,
178        }
179    }
180    pub(super) fn mk_let(id: u64, value: LcnfLetValue, body: LcnfExpr) -> LcnfExpr {
181        LcnfExpr::Let {
182            id: vid(id),
183            name: format!("x{}", id),
184            ty: LcnfType::Nat,
185            value,
186            body: Box::new(body),
187        }
188    }
189    pub(super) fn mk_module(decls: Vec<LcnfFunDecl>) -> LcnfModule {
190        LcnfModule {
191            fun_decls: decls,
192            extern_decls: vec![],
193            name: "test_mod".to_string(),
194            metadata: LcnfModuleMetadata::default(),
195        }
196    }
197    #[test]
198    pub(super) fn test_opt_level_display() {
199        assert_eq!(OptLevel::O0.to_string(), "O0");
200        assert_eq!(OptLevel::O1.to_string(), "O1");
201        assert_eq!(OptLevel::O2.to_string(), "O2");
202        assert_eq!(OptLevel::O3.to_string(), "O3");
203    }
204    #[test]
205    pub(super) fn test_opt_level_to_u8() {
206        assert_eq!(OptLevel::O0.to_u8(), 0);
207        assert_eq!(OptLevel::O1.to_u8(), 1);
208        assert_eq!(OptLevel::O2.to_u8(), 2);
209        assert_eq!(OptLevel::O3.to_u8(), 3);
210    }
211    #[test]
212    pub(super) fn test_opt_level_default_passes() {
213        assert!(OptLevel::O0.default_passes().is_empty());
214        assert!(!OptLevel::O1.default_passes().is_empty());
215        assert!(OptLevel::O2.default_passes().len() > OptLevel::O1.default_passes().len());
216        assert!(OptLevel::O3.default_passes().len() >= OptLevel::O2.default_passes().len());
217    }
218    #[test]
219    pub(super) fn test_pass_id_display() {
220        assert_eq!(PassId::Dce.to_string(), "dce");
221        assert_eq!(PassId::JoinPoints.to_string(), "join-points");
222        assert_eq!(PassId::Specialize.to_string(), "specialize");
223        assert_eq!(PassId::Reuse.to_string(), "reuse");
224        assert_eq!(PassId::ClosureConvert.to_string(), "closure-convert");
225        assert_eq!(
226            PassId::Custom("my-pass".to_string()).to_string(),
227            "custom:my-pass"
228        );
229    }
230    #[test]
231    pub(super) fn test_pipeline_config_default() {
232        let cfg = PipelineConfig::default();
233        assert_eq!(cfg.opt_level, OptLevel::O1);
234        assert_eq!(cfg.target, CodegenTarget::C);
235        assert!(!cfg.debug);
236        assert!(!cfg.emit_ir);
237    }
238    #[test]
239    pub(super) fn test_pipeline_config_effective_passes() {
240        let mut cfg = PipelineConfig::default();
241        assert_eq!(cfg.effective_passes(), OptLevel::O1.default_passes());
242        cfg.passes = vec![PassId::Dce, PassId::JoinPoints];
243        assert_eq!(cfg.effective_passes().len(), 2);
244    }
245    #[test]
246    pub(super) fn test_full_pipeline_o0() {
247        let module = mk_module(vec![mk_fun_decl(
248            "test",
249            LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(42))),
250        )]);
251        let result = compile_module_o0(&module);
252        assert!(result.c_output.is_some());
253        assert!(result.native_output.is_none());
254    }
255    #[test]
256    pub(super) fn test_pipeline_with_single_pass() {
257        let body = mk_let(
258            1,
259            LcnfLetValue::Lit(LcnfLit::Nat(42)),
260            mk_let(
261                2,
262                LcnfLetValue::Lit(LcnfLit::Nat(99)),
263                LcnfExpr::Return(LcnfArg::Var(vid(1))),
264            ),
265        );
266        let _module = mk_module(vec![mk_fun_decl("test", body)]);
267        let config = PipelineConfig {
268            opt_level: OptLevel::O0,
269            target: CodegenTarget::C,
270            passes: vec![PassId::Dce],
271            max_iterations: 3,
272            ..Default::default()
273        };
274        let pipeline = CompilerPipeline::new(config.clone());
275        let result = pipeline.run_pipeline(vec![], &config);
276        let _ = result.stats.iterations;
277    }
278    #[test]
279    pub(super) fn test_iterate_to_fixpoint() {
280        let body = mk_let(
281            1,
282            LcnfLetValue::Lit(LcnfLit::Nat(42)),
283            mk_let(
284                2,
285                LcnfLetValue::Lit(LcnfLit::Nat(99)),
286                LcnfExpr::Return(LcnfArg::Var(vid(1))),
287            ),
288        );
289        let module = mk_module(vec![mk_fun_decl("test", body)]);
290        let pipeline = CompilerPipeline::default_pipeline();
291        let mut stats = PipelineStats::default();
292        let result = pipeline.iterate_to_fixpoint(module, &[PassId::Dce], 5, &mut stats);
293        assert_eq!(result.fun_decls.len(), 1);
294        let let_count = count_expr_lets(&result.fun_decls[0].body);
295        assert!(let_count <= 1, "expected at most 1 let, got {}", let_count);
296    }
297    #[test]
298    pub(super) fn test_run_pass_dce() {
299        let body = mk_let(
300            1,
301            LcnfLetValue::Lit(LcnfLit::Nat(42)),
302            LcnfExpr::Return(LcnfArg::Var(vid(0))),
303        );
304        let module = mk_module(vec![mk_fun_decl("test", body)]);
305        let pipeline = CompilerPipeline::default_pipeline();
306        let result = pipeline.run_pass(&module, &PassId::Dce);
307        assert!(result.changed);
308    }
309    #[test]
310    pub(super) fn test_run_pass_custom() {
311        let module = mk_module(vec![mk_fun_decl(
312            "test",
313            LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0))),
314        )]);
315        let pipeline = CompilerPipeline::default_pipeline();
316        let result = pipeline.run_pass(&module, &PassId::Custom("noop".to_string()));
317        assert!(!result.changed);
318    }
319    #[test]
320    pub(super) fn test_compile_module_c() {
321        let module = mk_module(vec![mk_fun_decl(
322            "main",
323            LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0))),
324        )]);
325        let result = compile_module(&module, OptLevel::O1, CodegenTarget::C);
326        assert!(result.c_output.is_some());
327        assert!(result.native_output.is_none());
328    }
329    #[test]
330    pub(super) fn test_compile_module_native() {
331        let module = mk_module(vec![mk_fun_decl(
332            "main",
333            LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0))),
334        )]);
335        let result = compile_module(&module, OptLevel::O0, CodegenTarget::LlvmIr);
336        assert!(result.c_output.is_none());
337        assert!(result.native_output.is_some());
338    }
339    #[test]
340    pub(super) fn test_pipeline_stats_display() {
341        let stats = PipelineStats {
342            total_time_us: 1234,
343            iterations: 3,
344            input_decls: 5,
345            output_decls: 4,
346            per_pass: vec![(
347                PassId::Dce,
348                PassStats {
349                    decls_processed: 5,
350                    transformations: 2,
351                    time_us: 500,
352                },
353            )],
354        };
355        let s = stats.to_string();
356        assert!(s.contains("total_time=1234us"));
357        assert!(s.contains("iterations=3"));
358        assert!(s.contains("dce"));
359    }
360    #[test]
361    pub(super) fn test_pass_stats_display() {
362        let stats = PassStats {
363            decls_processed: 10,
364            transformations: 3,
365            time_us: 100,
366        };
367        let s = stats.to_string();
368        assert!(s.contains("decls=10"));
369        assert!(s.contains("transforms=3"));
370    }
371    #[test]
372    pub(super) fn test_count_module_lets() {
373        let body = mk_let(
374            1,
375            LcnfLetValue::Lit(LcnfLit::Nat(1)),
376            mk_let(
377                2,
378                LcnfLetValue::Lit(LcnfLit::Nat(2)),
379                LcnfExpr::Return(LcnfArg::Var(vid(2))),
380            ),
381        );
382        let module = mk_module(vec![mk_fun_decl("test", body)]);
383        assert_eq!(count_module_lets(&module), 2);
384    }
385    #[test]
386    pub(super) fn test_empty_module_pipeline() {
387        let module = mk_module(vec![]);
388        let result = compile_module_o0(&module);
389        assert_eq!(result.lcnf_module.fun_decls.len(), 0);
390    }
391    #[test]
392    pub(super) fn test_compile_module_o2() {
393        let body = mk_let(
394            1,
395            LcnfLetValue::Lit(LcnfLit::Nat(42)),
396            mk_let(
397                2,
398                LcnfLetValue::FVar(vid(1)),
399                mk_let(
400                    3,
401                    LcnfLetValue::Lit(LcnfLit::Nat(99)),
402                    LcnfExpr::Return(LcnfArg::Var(vid(2))),
403                ),
404            ),
405        );
406        let module = mk_module(vec![mk_fun_decl("opt_test", body)]);
407        let result = compile_module_o2(&module);
408        assert!(result.c_output.is_some());
409    }
410}
411#[cfg(test)]
412mod extra_pipeline_tests {
413    use super::*;
414    pub(super) fn mk_simple_module() -> LcnfModule {
415        LcnfModule {
416            fun_decls: vec![LcnfFunDecl {
417                name: "test".to_string(),
418                original_name: None,
419                params: vec![],
420                ret_type: LcnfType::Nat,
421                body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0))),
422                is_recursive: false,
423                is_lifted: false,
424                inline_cost: 1,
425            }],
426            extern_decls: vec![],
427            name: "mod".to_string(),
428            metadata: LcnfModuleMetadata::default(),
429        }
430    }
431    #[test]
432    pub(super) fn test_pipeline_builder_defaults() {
433        let cfg = PipelineBuilder::new().build();
434        assert_eq!(cfg.opt_level, OptLevel::O1);
435        assert!(!cfg.debug);
436    }
437    #[test]
438    pub(super) fn test_pipeline_builder_opt_level() {
439        let cfg = PipelineBuilder::new().opt_level(OptLevel::O3).build();
440        assert_eq!(cfg.opt_level, OptLevel::O3);
441    }
442    #[test]
443    pub(super) fn test_pipeline_builder_target() {
444        let cfg = PipelineBuilder::new().target(CodegenTarget::LlvmIr).build();
445        assert_eq!(cfg.target, CodegenTarget::LlvmIr);
446    }
447    #[test]
448    pub(super) fn test_pipeline_builder_debug() {
449        let cfg = PipelineBuilder::new().debug().build();
450        assert!(cfg.debug);
451    }
452    #[test]
453    pub(super) fn test_pipeline_builder_emit_ir() {
454        let cfg = PipelineBuilder::new().emit_ir().build();
455        assert!(cfg.emit_ir);
456    }
457    #[test]
458    pub(super) fn test_pipeline_builder_with_passes() {
459        let cfg = PipelineBuilder::new()
460            .with_passes(vec![PassId::Dce, PassId::JoinPoints])
461            .build();
462        assert_eq!(cfg.passes.len(), 2);
463    }
464    #[test]
465    pub(super) fn test_pipeline_builder_max_iterations() {
466        let cfg = PipelineBuilder::new().max_iterations(7).build();
467        assert_eq!(cfg.max_iterations, 7);
468    }
469    #[test]
470    pub(super) fn test_change_summary_mark_active() {
471        let mut s = PipelineChangeSummary::new();
472        s.mark_active("dce");
473        assert!(s.any_changed());
474    }
475    #[test]
476    pub(super) fn test_change_summary_mark_converged() {
477        let mut s = PipelineChangeSummary::new();
478        s.mark_converged("join-points");
479        assert!(!s.any_changed());
480        assert_eq!(s.converged_passes.len(), 1);
481    }
482    #[test]
483    pub(super) fn test_change_summary_display() {
484        let mut s = PipelineChangeSummary::new();
485        s.mark_active("dce");
486        let text = format!("{}", s);
487        assert!(text.contains("dce"));
488    }
489    #[test]
490    pub(super) fn test_run_pipeline_with_builder() {
491        let cfg = PipelineBuilder::new()
492            .opt_level(OptLevel::O1)
493            .target(CodegenTarget::C)
494            .build();
495        let pipeline = CompilerPipeline::new(cfg.clone());
496        let result = pipeline.run_pipeline(vec![], &cfg);
497        assert!(result.c_output.is_some());
498    }
499    #[test]
500    pub(super) fn test_opt_level_max_iterations_ordering() {
501        assert!(OptLevel::O0.max_iterations() <= OptLevel::O1.max_iterations());
502        assert!(OptLevel::O1.max_iterations() <= OptLevel::O2.max_iterations());
503        assert!(OptLevel::O2.max_iterations() <= OptLevel::O3.max_iterations());
504    }
505}
506#[cfg(test)]
507mod Pipe_infra_tests {
508    use super::*;
509    #[test]
510    pub(super) fn test_pass_config() {
511        let config = PipePassConfig::new("test_pass", PipePassPhase::Transformation);
512        assert!(config.enabled);
513        assert!(config.phase.is_modifying());
514        assert_eq!(config.phase.name(), "transformation");
515    }
516    #[test]
517    pub(super) fn test_pass_stats() {
518        let mut stats = PipePassStats::new();
519        stats.record_run(10, 100, 3);
520        stats.record_run(20, 200, 5);
521        assert_eq!(stats.total_runs, 2);
522        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
523        assert!((stats.success_rate() - 1.0).abs() < 0.01);
524        let s = stats.format_summary();
525        assert!(s.contains("Runs: 2/2"));
526    }
527    #[test]
528    pub(super) fn test_pass_registry() {
529        let mut reg = PipePassRegistry::new();
530        reg.register(PipePassConfig::new("pass_a", PipePassPhase::Analysis));
531        reg.register(PipePassConfig::new("pass_b", PipePassPhase::Transformation).disabled());
532        assert_eq!(reg.total_passes(), 2);
533        assert_eq!(reg.enabled_count(), 1);
534        reg.update_stats("pass_a", 5, 50, 2);
535        let stats = reg.get_stats("pass_a").expect("stats should exist");
536        assert_eq!(stats.total_changes, 5);
537    }
538    #[test]
539    pub(super) fn test_analysis_cache() {
540        let mut cache = PipeAnalysisCache::new(10);
541        cache.insert("key1".to_string(), vec![1, 2, 3]);
542        assert!(cache.get("key1").is_some());
543        assert!(cache.get("key2").is_none());
544        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
545        cache.invalidate("key1");
546        assert!(!cache.entries["key1"].valid);
547        assert_eq!(cache.size(), 1);
548    }
549    #[test]
550    pub(super) fn test_worklist() {
551        let mut wl = PipeWorklist::new();
552        assert!(wl.push(1));
553        assert!(wl.push(2));
554        assert!(!wl.push(1));
555        assert_eq!(wl.len(), 2);
556        assert_eq!(wl.pop(), Some(1));
557        assert!(!wl.contains(1));
558        assert!(wl.contains(2));
559    }
560    #[test]
561    pub(super) fn test_dominator_tree() {
562        let mut dt = PipeDominatorTree::new(5);
563        dt.set_idom(1, 0);
564        dt.set_idom(2, 0);
565        dt.set_idom(3, 1);
566        assert!(dt.dominates(0, 3));
567        assert!(dt.dominates(1, 3));
568        assert!(!dt.dominates(2, 3));
569        assert!(dt.dominates(3, 3));
570    }
571    #[test]
572    pub(super) fn test_liveness() {
573        let mut liveness = PipeLivenessInfo::new(3);
574        liveness.add_def(0, 1);
575        liveness.add_use(1, 1);
576        assert!(liveness.defs[0].contains(&1));
577        assert!(liveness.uses[1].contains(&1));
578    }
579    #[test]
580    pub(super) fn test_constant_folding() {
581        assert_eq!(PipeConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
582        assert_eq!(PipeConstantFoldingHelper::fold_div_i64(10, 0), None);
583        assert_eq!(PipeConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
584        assert_eq!(
585            PipeConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
586            0b1000
587        );
588        assert_eq!(PipeConstantFoldingHelper::fold_bitnot_i64(0), -1);
589    }
590    #[test]
591    pub(super) fn test_dep_graph() {
592        let mut g = PipeDepGraph::new();
593        g.add_dep(1, 2);
594        g.add_dep(2, 3);
595        g.add_dep(1, 3);
596        assert_eq!(g.dependencies_of(2), vec![1]);
597        let topo = g.topological_sort();
598        assert_eq!(topo.len(), 3);
599        assert!(!g.has_cycle());
600        let pos: std::collections::HashMap<u32, usize> =
601            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
602        assert!(pos[&1] < pos[&2]);
603        assert!(pos[&1] < pos[&3]);
604        assert!(pos[&2] < pos[&3]);
605    }
606}
607#[cfg(test)]
608mod pipeext_pass_tests {
609    use super::*;
610    #[test]
611    pub(super) fn test_pipeext_phase_order() {
612        assert_eq!(PipeExtPassPhase::Early.order(), 0);
613        assert_eq!(PipeExtPassPhase::Middle.order(), 1);
614        assert_eq!(PipeExtPassPhase::Late.order(), 2);
615        assert_eq!(PipeExtPassPhase::Finalize.order(), 3);
616        assert!(PipeExtPassPhase::Early.is_early());
617        assert!(!PipeExtPassPhase::Early.is_late());
618    }
619    #[test]
620    pub(super) fn test_pipeext_config_builder() {
621        let c = PipeExtPassConfig::new("p")
622            .with_phase(PipeExtPassPhase::Late)
623            .with_max_iter(50)
624            .with_debug(1);
625        assert_eq!(c.name, "p");
626        assert_eq!(c.max_iterations, 50);
627        assert!(c.is_debug_enabled());
628        assert!(c.enabled);
629        let c2 = c.disabled();
630        assert!(!c2.enabled);
631    }
632    #[test]
633    pub(super) fn test_pipeext_stats() {
634        let mut s = PipeExtPassStats::new();
635        s.visit();
636        s.visit();
637        s.modify();
638        s.iterate();
639        assert_eq!(s.nodes_visited, 2);
640        assert_eq!(s.nodes_modified, 1);
641        assert!(s.changed);
642        assert_eq!(s.iterations, 1);
643        let e = s.efficiency();
644        assert!((e - 0.5).abs() < 1e-9);
645    }
646    #[test]
647    pub(super) fn test_pipeext_registry() {
648        let mut r = PipeExtPassRegistry::new();
649        r.register(PipeExtPassConfig::new("a").with_phase(PipeExtPassPhase::Early));
650        r.register(PipeExtPassConfig::new("b").disabled());
651        assert_eq!(r.len(), 2);
652        assert_eq!(r.enabled_passes().len(), 1);
653        assert_eq!(r.passes_in_phase(&PipeExtPassPhase::Early).len(), 1);
654    }
655    #[test]
656    pub(super) fn test_pipeext_cache() {
657        let mut c = PipeExtCache::new(4);
658        assert!(c.get(99).is_none());
659        c.put(99, vec![1, 2, 3]);
660        let v = c.get(99).expect("v should be present in map");
661        assert_eq!(v, &[1u8, 2, 3]);
662        assert!(c.hit_rate() > 0.0);
663        assert_eq!(c.live_count(), 1);
664    }
665    #[test]
666    pub(super) fn test_pipeext_worklist() {
667        let mut w = PipeExtWorklist::new(10);
668        w.push(5);
669        w.push(3);
670        w.push(5);
671        assert_eq!(w.len(), 2);
672        assert!(w.contains(5));
673        let first = w.pop().expect("first should be available to pop");
674        assert!(!w.contains(first));
675    }
676    #[test]
677    pub(super) fn test_pipeext_dom_tree() {
678        let mut dt = PipeExtDomTree::new(5);
679        dt.set_idom(1, 0);
680        dt.set_idom(2, 0);
681        dt.set_idom(3, 1);
682        dt.set_idom(4, 1);
683        assert!(dt.dominates(0, 3));
684        assert!(dt.dominates(1, 4));
685        assert!(!dt.dominates(2, 3));
686        assert_eq!(dt.depth_of(3), 2);
687    }
688    #[test]
689    pub(super) fn test_pipeext_liveness() {
690        let mut lv = PipeExtLiveness::new(3);
691        lv.add_def(0, 1);
692        lv.add_use(1, 1);
693        assert!(lv.var_is_def_in_block(0, 1));
694        assert!(lv.var_is_used_in_block(1, 1));
695        assert!(!lv.var_is_def_in_block(1, 1));
696    }
697    #[test]
698    pub(super) fn test_pipeext_const_folder() {
699        let mut cf = PipeExtConstFolder::new();
700        assert_eq!(cf.add_i64(3, 4), Some(7));
701        assert_eq!(cf.div_i64(10, 0), None);
702        assert_eq!(cf.mul_i64(6, 7), Some(42));
703        assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
704        assert_eq!(cf.fold_count(), 3);
705        assert_eq!(cf.failure_count(), 1);
706    }
707    #[test]
708    pub(super) fn test_pipeext_dep_graph() {
709        let mut g = PipeExtDepGraph::new(4);
710        g.add_edge(0, 1);
711        g.add_edge(1, 2);
712        g.add_edge(2, 3);
713        assert!(!g.has_cycle());
714        assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
715        assert_eq!(g.reachable(0).len(), 4);
716        let sccs = g.scc();
717        assert_eq!(sccs.len(), 4);
718    }
719}
720#[cfg(test)]
721mod pipex2_pass_tests {
722    use super::*;
723    #[test]
724    pub(super) fn test_pipex2_phase_order() {
725        assert_eq!(PipeX2PassPhase::Early.order(), 0);
726        assert_eq!(PipeX2PassPhase::Middle.order(), 1);
727        assert_eq!(PipeX2PassPhase::Late.order(), 2);
728        assert_eq!(PipeX2PassPhase::Finalize.order(), 3);
729        assert!(PipeX2PassPhase::Early.is_early());
730        assert!(!PipeX2PassPhase::Early.is_late());
731    }
732    #[test]
733    pub(super) fn test_pipex2_config_builder() {
734        let c = PipeX2PassConfig::new("p")
735            .with_phase(PipeX2PassPhase::Late)
736            .with_max_iter(50)
737            .with_debug(1);
738        assert_eq!(c.name, "p");
739        assert_eq!(c.max_iterations, 50);
740        assert!(c.is_debug_enabled());
741        assert!(c.enabled);
742        let c2 = c.disabled();
743        assert!(!c2.enabled);
744    }
745    #[test]
746    pub(super) fn test_pipex2_stats() {
747        let mut s = PipeX2PassStats::new();
748        s.visit();
749        s.visit();
750        s.modify();
751        s.iterate();
752        assert_eq!(s.nodes_visited, 2);
753        assert_eq!(s.nodes_modified, 1);
754        assert!(s.changed);
755        assert_eq!(s.iterations, 1);
756        let e = s.efficiency();
757        assert!((e - 0.5).abs() < 1e-9);
758    }
759    #[test]
760    pub(super) fn test_pipex2_registry() {
761        let mut r = PipeX2PassRegistry::new();
762        r.register(PipeX2PassConfig::new("a").with_phase(PipeX2PassPhase::Early));
763        r.register(PipeX2PassConfig::new("b").disabled());
764        assert_eq!(r.len(), 2);
765        assert_eq!(r.enabled_passes().len(), 1);
766        assert_eq!(r.passes_in_phase(&PipeX2PassPhase::Early).len(), 1);
767    }
768    #[test]
769    pub(super) fn test_pipex2_cache() {
770        let mut c = PipeX2Cache::new(4);
771        assert!(c.get(99).is_none());
772        c.put(99, vec![1, 2, 3]);
773        let v = c.get(99).expect("v should be present in map");
774        assert_eq!(v, &[1u8, 2, 3]);
775        assert!(c.hit_rate() > 0.0);
776        assert_eq!(c.live_count(), 1);
777    }
778    #[test]
779    pub(super) fn test_pipex2_worklist() {
780        let mut w = PipeX2Worklist::new(10);
781        w.push(5);
782        w.push(3);
783        w.push(5);
784        assert_eq!(w.len(), 2);
785        assert!(w.contains(5));
786        let first = w.pop().expect("first should be available to pop");
787        assert!(!w.contains(first));
788    }
789    #[test]
790    pub(super) fn test_pipex2_dom_tree() {
791        let mut dt = PipeX2DomTree::new(5);
792        dt.set_idom(1, 0);
793        dt.set_idom(2, 0);
794        dt.set_idom(3, 1);
795        dt.set_idom(4, 1);
796        assert!(dt.dominates(0, 3));
797        assert!(dt.dominates(1, 4));
798        assert!(!dt.dominates(2, 3));
799        assert_eq!(dt.depth_of(3), 2);
800    }
801    #[test]
802    pub(super) fn test_pipex2_liveness() {
803        let mut lv = PipeX2Liveness::new(3);
804        lv.add_def(0, 1);
805        lv.add_use(1, 1);
806        assert!(lv.var_is_def_in_block(0, 1));
807        assert!(lv.var_is_used_in_block(1, 1));
808        assert!(!lv.var_is_def_in_block(1, 1));
809    }
810    #[test]
811    pub(super) fn test_pipex2_const_folder() {
812        let mut cf = PipeX2ConstFolder::new();
813        assert_eq!(cf.add_i64(3, 4), Some(7));
814        assert_eq!(cf.div_i64(10, 0), None);
815        assert_eq!(cf.mul_i64(6, 7), Some(42));
816        assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
817        assert_eq!(cf.fold_count(), 3);
818        assert_eq!(cf.failure_count(), 1);
819    }
820    #[test]
821    pub(super) fn test_pipex2_dep_graph() {
822        let mut g = PipeX2DepGraph::new(4);
823        g.add_edge(0, 1);
824        g.add_edge(1, 2);
825        g.add_edge(2, 3);
826        assert!(!g.has_cycle());
827        assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
828        assert_eq!(g.reachable(0).len(), 4);
829        let sccs = g.scc();
830        assert_eq!(sccs.len(), 4);
831    }
832}