Skip to main content

oxilean_codegen/elixir_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6
7use super::types::{
8    ElixirBackend, ElixirExpr, ElixirExtCache, ElixirExtConstFolder, ElixirExtDepGraph,
9    ElixirExtDomTree, ElixirExtLiveness, ElixirExtPassConfig, ElixirExtPassPhase,
10    ElixirExtPassRegistry, ElixirExtPassStats, ElixirExtWorklist, ElixirFunction, ElixirModule,
11    ElixirX2Cache, ElixirX2ConstFolder, ElixirX2DepGraph, ElixirX2DomTree, ElixirX2Liveness,
12    ElixirX2PassConfig, ElixirX2PassPhase, ElixirX2PassRegistry, ElixirX2PassStats,
13    ElixirX2Worklist, ElxAnalysisCache, ElxConstantFoldingHelper, ElxDepGraph, ElxDominatorTree,
14    ElxLivenessInfo, ElxPassConfig, ElxPassPhase, ElxPassRegistry, ElxPassStats, ElxWorklist,
15};
16
17/// Convert a `CamelCase` or `mixedCase` identifier to `snake_case`.
18pub(super) fn to_snake_case(s: &str) -> String {
19    let mut out = String::new();
20    let mut prev_upper = false;
21    for (i, c) in s.char_indices() {
22        if c.is_uppercase() {
23            if i > 0 && !prev_upper {
24                out.push('_');
25            }
26            out.extend(c.to_lowercase());
27            prev_upper = true;
28        } else {
29            out.push(c);
30            prev_upper = false;
31        }
32    }
33    out
34}
35/// Return `true` if `name` is an Elixir reserved word.
36pub(super) fn is_elixir_reserved(name: &str) -> bool {
37    matches!(
38        name,
39        "do" | "end"
40            | "fn"
41            | "in"
42            | "nil"
43            | "true"
44            | "false"
45            | "when"
46            | "and"
47            | "or"
48            | "not"
49            | "if"
50            | "else"
51            | "cond"
52            | "case"
53            | "receive"
54            | "after"
55            | "for"
56            | "try"
57            | "catch"
58            | "rescue"
59            | "with"
60            | "import"
61            | "use"
62            | "require"
63            | "alias"
64            | "defmodule"
65            | "def"
66            | "defp"
67            | "defmacro"
68            | "defmacrop"
69            | "defstruct"
70            | "defprotocol"
71            | "defimpl"
72            | "defdelegate"
73    )
74}
75/// Escape special characters inside an Elixir double-quoted string.
76pub(super) fn escape_elixir_string(s: &str) -> String {
77    s.replace('\\', "\\\\")
78        .replace('"', "\\\"")
79        .replace('\n', "\\n")
80        .replace('\r', "\\r")
81        .replace('\t', "\\t")
82}
83pub const ELIXIR_RUNTIME: &str = r#"defmodule OxiLean.Runtime do
84  @moduledoc """
85  OxiLean runtime support for Elixir-compiled code.
86
87  Provides:
88  - Algebraic-data-type helpers (tagged tuples)
89  - Basic numeric utilities
90  - Functional combinators
91  """
92
93  # ---- ADT constructors -------------------------------------------------
94
95  @doc "Wrap a value in a tagged tuple for algebraic data types."
96  def adt(tag, fields) when is_atom(tag) do
97    List.to_tuple([tag | fields])
98  end
99
100  @doc "Extract the tag from an ADT tagged tuple."
101  def adt_tag(t) when is_tuple(t), do: elem(t, 0)
102
103  # ---- Numeric utilities ------------------------------------------------
104
105  @doc "Integer power: base^exp (exp >= 0)."
106  def ipow(_base, 0), do: 1
107  def ipow(base, exp) when rem(exp, 2) == 0 do
108    half = ipow(base, div(exp, 2))
109    half * half
110  end
111  def ipow(base, exp), do: base * ipow(base, exp - 1)
112
113  @doc "Clamp a value to [lo, hi]."
114  def clamp(v, lo, hi), do: max(lo, min(hi, v))
115
116  # ---- Functional combinators -------------------------------------------
117
118  @doc "Function identity."
119  def id(x), do: x
120
121  @doc "Constant function: always returns `a`."
122  def const(a, _b), do: a
123
124  @doc "Flip argument order of a two-argument function."
125  def flip(f, a, b), do: f.(b, a)
126
127  @doc "Compose two functions: `compose(f, g).(x) == f.(g.(x))`."
128  def compose(f, g), do: fn x -> f.(g.(x)) end
129
130  # ---- Option/Maybe helpers ---------------------------------------------
131
132  @doc "Wrap in `{:some, v}` or return `:none`."
133  def some(v), do: {:some, v}
134  def none, do: :none
135
136  @doc "Unwrap an option, returning `default` on `:none`."
137  def option_get({:some, v}, _default), do: v
138  def option_get(:none, default), do: default
139
140  # ---- List helpers -----------------------------------------------------
141
142  @doc "Safe head: returns `{:some, head}` or `:none`."
143  def list_head([h | _]), do: {:some, h}
144  def list_head([]), do: :none
145
146  @doc "Safe tail: returns `{:some, tail}` or `:none`."
147  def list_tail([_ | t]), do: {:some, t}
148  def list_tail([]), do: :none
149
150  @doc "Zip two lists into a list of 2-tuples."
151  def zip([], _), do: []
152  def zip(_, []), do: []
153  def zip([h1 | t1], [h2 | t2]), do: [{h1, h2} | zip(t1, t2)]
154end
155"#;
156#[cfg(test)]
157mod tests {
158    use super::*;
159    pub(super) fn backend() -> ElixirBackend {
160        ElixirBackend::new()
161    }
162    #[test]
163    pub(super) fn test_emit_atom() {
164        assert_eq!(
165            backend().emit_expr(&ElixirExpr::Atom("ok".to_string())),
166            ":ok"
167        );
168    }
169    #[test]
170    pub(super) fn test_emit_integer() {
171        assert_eq!(backend().emit_expr(&ElixirExpr::Integer(42)), "42");
172        assert_eq!(backend().emit_expr(&ElixirExpr::Integer(-7)), "-7");
173    }
174    #[test]
175    pub(super) fn test_emit_float() {
176        let s = backend().emit_expr(&ElixirExpr::Float(3.14));
177        assert!(s.contains("3.14"), "got: {}", s);
178    }
179    #[test]
180    pub(super) fn test_emit_binary() {
181        assert_eq!(
182            backend().emit_expr(&ElixirExpr::Binary("hello".to_string())),
183            "\"hello\""
184        );
185    }
186    #[test]
187    pub(super) fn test_emit_bool() {
188        assert_eq!(backend().emit_expr(&ElixirExpr::Bool(true)), "true");
189        assert_eq!(backend().emit_expr(&ElixirExpr::Bool(false)), "false");
190    }
191    #[test]
192    pub(super) fn test_emit_nil() {
193        assert_eq!(backend().emit_expr(&ElixirExpr::Nil), "nil");
194    }
195    #[test]
196    pub(super) fn test_emit_list() {
197        let e = ElixirExpr::List(vec![
198            ElixirExpr::Integer(1),
199            ElixirExpr::Integer(2),
200            ElixirExpr::Integer(3),
201        ]);
202        assert_eq!(backend().emit_expr(&e), "[1, 2, 3]");
203        let empty = ElixirExpr::List(vec![]);
204        assert_eq!(backend().emit_expr(&empty), "[]");
205    }
206    #[test]
207    pub(super) fn test_emit_tuple() {
208        let e = ElixirExpr::Tuple(vec![
209            ElixirExpr::Atom("ok".to_string()),
210            ElixirExpr::Integer(0),
211        ]);
212        assert_eq!(backend().emit_expr(&e), "{:ok, 0}");
213    }
214    #[test]
215    pub(super) fn test_emit_map() {
216        let e = ElixirExpr::Map(vec![(
217            ElixirExpr::Atom("key".to_string()),
218            ElixirExpr::Integer(1),
219        )]);
220        assert_eq!(backend().emit_expr(&e), "%{:key => 1}");
221        let empty = ElixirExpr::Map(vec![]);
222        assert_eq!(backend().emit_expr(&empty), "%{}");
223    }
224    #[test]
225    pub(super) fn test_emit_func_call() {
226        let e = ElixirExpr::FuncCall(
227            "Enum.map".to_string(),
228            vec![
229                ElixirExpr::Var("list".to_string()),
230                ElixirExpr::Var("f".to_string()),
231            ],
232        );
233        assert_eq!(backend().emit_expr(&e), "Enum.map(list, f)");
234    }
235    #[test]
236    pub(super) fn test_emit_binop() {
237        let e = ElixirExpr::BinOp(
238            "+".to_string(),
239            Box::new(ElixirExpr::Integer(1)),
240            Box::new(ElixirExpr::Integer(2)),
241        );
242        assert_eq!(backend().emit_expr(&e), "1 + 2");
243    }
244    #[test]
245    pub(super) fn test_emit_pipe() {
246        let e = ElixirExpr::Pipe(
247            Box::new(ElixirExpr::Var("list".to_string())),
248            Box::new(ElixirExpr::FuncCall("Enum.sort".to_string(), vec![])),
249        );
250        let s = backend().emit_expr(&e);
251        assert!(s.contains("|>"), "pipe missing: {}", s);
252        assert!(s.contains("list"), "lhs missing: {}", s);
253    }
254    #[test]
255    pub(super) fn test_mangle_name() {
256        let b = backend();
257        assert_eq!(b.mangle_name("fooBar"), "foo_bar");
258        assert_eq!(b.mangle_name("do"), "do_");
259        assert_eq!(b.mangle_name("myFunc"), "my_func");
260    }
261    #[test]
262    pub(super) fn test_emit_function() {
263        let func = ElixirFunction {
264            name: "add".to_string(),
265            arity: 2,
266            clauses: vec![(
267                vec![
268                    ElixirExpr::Var("a".to_string()),
269                    ElixirExpr::Var("b".to_string()),
270                ],
271                None,
272                ElixirExpr::BinOp(
273                    "+".to_string(),
274                    Box::new(ElixirExpr::Var("a".to_string())),
275                    Box::new(ElixirExpr::Var("b".to_string())),
276                ),
277            )],
278            is_private: false,
279            doc: None,
280        };
281        let s = backend().emit_function(&func);
282        assert!(s.contains("def add(a, b) do"), "got: {}", s);
283        assert!(s.contains("a + b"), "got: {}", s);
284    }
285    #[test]
286    pub(super) fn test_emit_module() {
287        let module = ElixirModule {
288            name: "MyApp.Math".to_string(),
289            functions: vec![ElixirFunction {
290                name: "square".to_string(),
291                arity: 1,
292                clauses: vec![(
293                    vec![ElixirExpr::Var("x".to_string())],
294                    None,
295                    ElixirExpr::BinOp(
296                        "*".to_string(),
297                        Box::new(ElixirExpr::Var("x".to_string())),
298                        Box::new(ElixirExpr::Var("x".to_string())),
299                    ),
300                )],
301                is_private: false,
302                doc: None,
303            }],
304            use_modules: vec![],
305            imports: vec![],
306            attributes: HashMap::new(),
307        };
308        let s = backend().emit_module(&module);
309        assert!(s.contains("defmodule MyApp.Math do"), "got: {}", s);
310        assert!(s.contains("def square(x) do"), "got: {}", s);
311        assert!(s.contains("x * x"), "got: {}", s);
312        assert!(s.ends_with("end\n"), "got: {}", s);
313    }
314    #[test]
315    pub(super) fn test_runtime_content() {
316        let rt = backend().emit_runtime();
317        assert!(rt.contains("OxiLean.Runtime"));
318        assert!(rt.contains("defmodule"));
319        assert!(rt.contains("def adt(tag, fields)"));
320        assert!(rt.contains("def ipow("));
321        assert!(rt.contains("def compose(f, g)"));
322    }
323    #[test]
324    pub(super) fn test_emit_case() {
325        let e = ElixirExpr::Case(
326            Box::new(ElixirExpr::Var("x".to_string())),
327            vec![
328                (ElixirExpr::Integer(0), ElixirExpr::Atom("zero".to_string())),
329                (
330                    ElixirExpr::Var("_".to_string()),
331                    ElixirExpr::Atom("nonzero".to_string()),
332                ),
333            ],
334        );
335        let s = backend().emit_expr(&e);
336        assert!(s.contains("case x do"), "got: {}", s);
337        assert!(s.contains(":zero"), "got: {}", s);
338        assert!(s.contains(":nonzero"), "got: {}", s);
339    }
340    #[test]
341    pub(super) fn test_emit_lambda() {
342        let e = ElixirExpr::Lambda(
343            vec!["x".to_string(), "y".to_string()],
344            Box::new(ElixirExpr::BinOp(
345                "+".to_string(),
346                Box::new(ElixirExpr::Var("x".to_string())),
347                Box::new(ElixirExpr::Var("y".to_string())),
348            )),
349        );
350        let s = backend().emit_expr(&e);
351        assert!(s.contains("fn x, y ->"), "got: {}", s);
352        assert!(s.contains("x + y"), "got: {}", s);
353    }
354    #[test]
355    pub(super) fn test_escape_string() {
356        assert_eq!(
357            backend().emit_expr(&ElixirExpr::Binary("say \"hi\"".to_string())),
358            r#""say \"hi\"""#
359        );
360    }
361    #[test]
362    pub(super) fn test_private_function() {
363        let func = ElixirFunction {
364            name: "helper".to_string(),
365            arity: 0,
366            clauses: vec![(vec![], None, ElixirExpr::Nil)],
367            is_private: true,
368            doc: None,
369        };
370        let s = backend().emit_function(&func);
371        assert!(s.contains("defp helper()"), "got: {}", s);
372    }
373    #[test]
374    pub(super) fn test_emit_if() {
375        let e = ElixirExpr::If(
376            Box::new(ElixirExpr::Bool(true)),
377            Box::new(ElixirExpr::Integer(1)),
378            Box::new(ElixirExpr::Integer(0)),
379        );
380        let s = backend().emit_expr(&e);
381        assert!(s.contains("if true do"), "got: {}", s);
382        assert!(s.contains("else"), "got: {}", s);
383    }
384}
385#[cfg(test)]
386mod Elx_infra_tests {
387    use super::*;
388    #[test]
389    pub(super) fn test_pass_config() {
390        let config = ElxPassConfig::new("test_pass", ElxPassPhase::Transformation);
391        assert!(config.enabled);
392        assert!(config.phase.is_modifying());
393        assert_eq!(config.phase.name(), "transformation");
394    }
395    #[test]
396    pub(super) fn test_pass_stats() {
397        let mut stats = ElxPassStats::new();
398        stats.record_run(10, 100, 3);
399        stats.record_run(20, 200, 5);
400        assert_eq!(stats.total_runs, 2);
401        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
402        assert!((stats.success_rate() - 1.0).abs() < 0.01);
403        let s = stats.format_summary();
404        assert!(s.contains("Runs: 2/2"));
405    }
406    #[test]
407    pub(super) fn test_pass_registry() {
408        let mut reg = ElxPassRegistry::new();
409        reg.register(ElxPassConfig::new("pass_a", ElxPassPhase::Analysis));
410        reg.register(ElxPassConfig::new("pass_b", ElxPassPhase::Transformation).disabled());
411        assert_eq!(reg.total_passes(), 2);
412        assert_eq!(reg.enabled_count(), 1);
413        reg.update_stats("pass_a", 5, 50, 2);
414        let stats = reg.get_stats("pass_a").expect("stats should exist");
415        assert_eq!(stats.total_changes, 5);
416    }
417    #[test]
418    pub(super) fn test_analysis_cache() {
419        let mut cache = ElxAnalysisCache::new(10);
420        cache.insert("key1".to_string(), vec![1, 2, 3]);
421        assert!(cache.get("key1").is_some());
422        assert!(cache.get("key2").is_none());
423        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
424        cache.invalidate("key1");
425        assert!(!cache.entries["key1"].valid);
426        assert_eq!(cache.size(), 1);
427    }
428    #[test]
429    pub(super) fn test_worklist() {
430        let mut wl = ElxWorklist::new();
431        assert!(wl.push(1));
432        assert!(wl.push(2));
433        assert!(!wl.push(1));
434        assert_eq!(wl.len(), 2);
435        assert_eq!(wl.pop(), Some(1));
436        assert!(!wl.contains(1));
437        assert!(wl.contains(2));
438    }
439    #[test]
440    pub(super) fn test_dominator_tree() {
441        let mut dt = ElxDominatorTree::new(5);
442        dt.set_idom(1, 0);
443        dt.set_idom(2, 0);
444        dt.set_idom(3, 1);
445        assert!(dt.dominates(0, 3));
446        assert!(dt.dominates(1, 3));
447        assert!(!dt.dominates(2, 3));
448        assert!(dt.dominates(3, 3));
449    }
450    #[test]
451    pub(super) fn test_liveness() {
452        let mut liveness = ElxLivenessInfo::new(3);
453        liveness.add_def(0, 1);
454        liveness.add_use(1, 1);
455        assert!(liveness.defs[0].contains(&1));
456        assert!(liveness.uses[1].contains(&1));
457    }
458    #[test]
459    pub(super) fn test_constant_folding() {
460        assert_eq!(ElxConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
461        assert_eq!(ElxConstantFoldingHelper::fold_div_i64(10, 0), None);
462        assert_eq!(ElxConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
463        assert_eq!(
464            ElxConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
465            0b1000
466        );
467        assert_eq!(ElxConstantFoldingHelper::fold_bitnot_i64(0), -1);
468    }
469    #[test]
470    pub(super) fn test_dep_graph() {
471        let mut g = ElxDepGraph::new();
472        g.add_dep(1, 2);
473        g.add_dep(2, 3);
474        g.add_dep(1, 3);
475        assert_eq!(g.dependencies_of(2), vec![1]);
476        let topo = g.topological_sort();
477        assert_eq!(topo.len(), 3);
478        assert!(!g.has_cycle());
479        let pos: std::collections::HashMap<u32, usize> =
480            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
481        assert!(pos[&1] < pos[&2]);
482        assert!(pos[&1] < pos[&3]);
483        assert!(pos[&2] < pos[&3]);
484    }
485}
486#[cfg(test)]
487mod elixirext_pass_tests {
488    use super::*;
489    #[test]
490    pub(super) fn test_elixirext_phase_order() {
491        assert_eq!(ElixirExtPassPhase::Early.order(), 0);
492        assert_eq!(ElixirExtPassPhase::Middle.order(), 1);
493        assert_eq!(ElixirExtPassPhase::Late.order(), 2);
494        assert_eq!(ElixirExtPassPhase::Finalize.order(), 3);
495        assert!(ElixirExtPassPhase::Early.is_early());
496        assert!(!ElixirExtPassPhase::Early.is_late());
497    }
498    #[test]
499    pub(super) fn test_elixirext_config_builder() {
500        let c = ElixirExtPassConfig::new("p")
501            .with_phase(ElixirExtPassPhase::Late)
502            .with_max_iter(50)
503            .with_debug(1);
504        assert_eq!(c.name, "p");
505        assert_eq!(c.max_iterations, 50);
506        assert!(c.is_debug_enabled());
507        assert!(c.enabled);
508        let c2 = c.disabled();
509        assert!(!c2.enabled);
510    }
511    #[test]
512    pub(super) fn test_elixirext_stats() {
513        let mut s = ElixirExtPassStats::new();
514        s.visit();
515        s.visit();
516        s.modify();
517        s.iterate();
518        assert_eq!(s.nodes_visited, 2);
519        assert_eq!(s.nodes_modified, 1);
520        assert!(s.changed);
521        assert_eq!(s.iterations, 1);
522        let e = s.efficiency();
523        assert!((e - 0.5).abs() < 1e-9);
524    }
525    #[test]
526    pub(super) fn test_elixirext_registry() {
527        let mut r = ElixirExtPassRegistry::new();
528        r.register(ElixirExtPassConfig::new("a").with_phase(ElixirExtPassPhase::Early));
529        r.register(ElixirExtPassConfig::new("b").disabled());
530        assert_eq!(r.len(), 2);
531        assert_eq!(r.enabled_passes().len(), 1);
532        assert_eq!(r.passes_in_phase(&ElixirExtPassPhase::Early).len(), 1);
533    }
534    #[test]
535    pub(super) fn test_elixirext_cache() {
536        let mut c = ElixirExtCache::new(4);
537        assert!(c.get(99).is_none());
538        c.put(99, vec![1, 2, 3]);
539        let v = c.get(99).expect("v should be present in map");
540        assert_eq!(v, &[1u8, 2, 3]);
541        assert!(c.hit_rate() > 0.0);
542        assert_eq!(c.live_count(), 1);
543    }
544    #[test]
545    pub(super) fn test_elixirext_worklist() {
546        let mut w = ElixirExtWorklist::new(10);
547        w.push(5);
548        w.push(3);
549        w.push(5);
550        assert_eq!(w.len(), 2);
551        assert!(w.contains(5));
552        let first = w.pop().expect("first should be available to pop");
553        assert!(!w.contains(first));
554    }
555    #[test]
556    pub(super) fn test_elixirext_dom_tree() {
557        let mut dt = ElixirExtDomTree::new(5);
558        dt.set_idom(1, 0);
559        dt.set_idom(2, 0);
560        dt.set_idom(3, 1);
561        dt.set_idom(4, 1);
562        assert!(dt.dominates(0, 3));
563        assert!(dt.dominates(1, 4));
564        assert!(!dt.dominates(2, 3));
565        assert_eq!(dt.depth_of(3), 2);
566    }
567    #[test]
568    pub(super) fn test_elixirext_liveness() {
569        let mut lv = ElixirExtLiveness::new(3);
570        lv.add_def(0, 1);
571        lv.add_use(1, 1);
572        assert!(lv.var_is_def_in_block(0, 1));
573        assert!(lv.var_is_used_in_block(1, 1));
574        assert!(!lv.var_is_def_in_block(1, 1));
575    }
576    #[test]
577    pub(super) fn test_elixirext_const_folder() {
578        let mut cf = ElixirExtConstFolder::new();
579        assert_eq!(cf.add_i64(3, 4), Some(7));
580        assert_eq!(cf.div_i64(10, 0), None);
581        assert_eq!(cf.mul_i64(6, 7), Some(42));
582        assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
583        assert_eq!(cf.fold_count(), 3);
584        assert_eq!(cf.failure_count(), 1);
585    }
586    #[test]
587    pub(super) fn test_elixirext_dep_graph() {
588        let mut g = ElixirExtDepGraph::new(4);
589        g.add_edge(0, 1);
590        g.add_edge(1, 2);
591        g.add_edge(2, 3);
592        assert!(!g.has_cycle());
593        assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
594        assert_eq!(g.reachable(0).len(), 4);
595        let sccs = g.scc();
596        assert_eq!(sccs.len(), 4);
597    }
598}
599#[cfg(test)]
600mod elixirx2_pass_tests {
601    use super::*;
602    #[test]
603    pub(super) fn test_elixirx2_phase_order() {
604        assert_eq!(ElixirX2PassPhase::Early.order(), 0);
605        assert_eq!(ElixirX2PassPhase::Middle.order(), 1);
606        assert_eq!(ElixirX2PassPhase::Late.order(), 2);
607        assert_eq!(ElixirX2PassPhase::Finalize.order(), 3);
608        assert!(ElixirX2PassPhase::Early.is_early());
609        assert!(!ElixirX2PassPhase::Early.is_late());
610    }
611    #[test]
612    pub(super) fn test_elixirx2_config_builder() {
613        let c = ElixirX2PassConfig::new("p")
614            .with_phase(ElixirX2PassPhase::Late)
615            .with_max_iter(50)
616            .with_debug(1);
617        assert_eq!(c.name, "p");
618        assert_eq!(c.max_iterations, 50);
619        assert!(c.is_debug_enabled());
620        assert!(c.enabled);
621        let c2 = c.disabled();
622        assert!(!c2.enabled);
623    }
624    #[test]
625    pub(super) fn test_elixirx2_stats() {
626        let mut s = ElixirX2PassStats::new();
627        s.visit();
628        s.visit();
629        s.modify();
630        s.iterate();
631        assert_eq!(s.nodes_visited, 2);
632        assert_eq!(s.nodes_modified, 1);
633        assert!(s.changed);
634        assert_eq!(s.iterations, 1);
635        let e = s.efficiency();
636        assert!((e - 0.5).abs() < 1e-9);
637    }
638    #[test]
639    pub(super) fn test_elixirx2_registry() {
640        let mut r = ElixirX2PassRegistry::new();
641        r.register(ElixirX2PassConfig::new("a").with_phase(ElixirX2PassPhase::Early));
642        r.register(ElixirX2PassConfig::new("b").disabled());
643        assert_eq!(r.len(), 2);
644        assert_eq!(r.enabled_passes().len(), 1);
645        assert_eq!(r.passes_in_phase(&ElixirX2PassPhase::Early).len(), 1);
646    }
647    #[test]
648    pub(super) fn test_elixirx2_cache() {
649        let mut c = ElixirX2Cache::new(4);
650        assert!(c.get(99).is_none());
651        c.put(99, vec![1, 2, 3]);
652        let v = c.get(99).expect("v should be present in map");
653        assert_eq!(v, &[1u8, 2, 3]);
654        assert!(c.hit_rate() > 0.0);
655        assert_eq!(c.live_count(), 1);
656    }
657    #[test]
658    pub(super) fn test_elixirx2_worklist() {
659        let mut w = ElixirX2Worklist::new(10);
660        w.push(5);
661        w.push(3);
662        w.push(5);
663        assert_eq!(w.len(), 2);
664        assert!(w.contains(5));
665        let first = w.pop().expect("first should be available to pop");
666        assert!(!w.contains(first));
667    }
668    #[test]
669    pub(super) fn test_elixirx2_dom_tree() {
670        let mut dt = ElixirX2DomTree::new(5);
671        dt.set_idom(1, 0);
672        dt.set_idom(2, 0);
673        dt.set_idom(3, 1);
674        dt.set_idom(4, 1);
675        assert!(dt.dominates(0, 3));
676        assert!(dt.dominates(1, 4));
677        assert!(!dt.dominates(2, 3));
678        assert_eq!(dt.depth_of(3), 2);
679    }
680    #[test]
681    pub(super) fn test_elixirx2_liveness() {
682        let mut lv = ElixirX2Liveness::new(3);
683        lv.add_def(0, 1);
684        lv.add_use(1, 1);
685        assert!(lv.var_is_def_in_block(0, 1));
686        assert!(lv.var_is_used_in_block(1, 1));
687        assert!(!lv.var_is_def_in_block(1, 1));
688    }
689    #[test]
690    pub(super) fn test_elixirx2_const_folder() {
691        let mut cf = ElixirX2ConstFolder::new();
692        assert_eq!(cf.add_i64(3, 4), Some(7));
693        assert_eq!(cf.div_i64(10, 0), None);
694        assert_eq!(cf.mul_i64(6, 7), Some(42));
695        assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
696        assert_eq!(cf.fold_count(), 3);
697        assert_eq!(cf.failure_count(), 1);
698    }
699    #[test]
700    pub(super) fn test_elixirx2_dep_graph() {
701        let mut g = ElixirX2DepGraph::new(4);
702        g.add_edge(0, 1);
703        g.add_edge(1, 2);
704        g.add_edge(2, 3);
705        assert!(!g.has_cycle());
706        assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
707        assert_eq!(g.reachable(0).len(), 4);
708        let sccs = g.scc();
709        assert_eq!(sccs.len(), 4);
710    }
711}