Skip to main content

aver/ir/
dump.rs

1//! Textual IR dump — turns a `Vec<TopLevel>` into a stable, human-readable
2//! representation. Used by `aver compile --emit-ir` and `--emit-ir-after=PASS`
3//! to give compiler engineers a verifiable diff between passes.
4//!
5//! The format is Aver-like surface syntax with explicit "this came from
6//! a pass" markers in the expression body:
7//!
8//!   `<tail-call:fn>(args)`  — Expr::TailCall (TCO output)
9//!   `<resolved>`            — Expr::Resolved (resolver output)
10//!   `__buf_*` / `__to_str`  — buffer-build / interp_lower intrinsics
11//!
12//! When an `AnalysisResult` is available (the pipeline's `Analyze` stage
13//! ran), each FnDef line also carries the per-fn facts:
14//!
15//!   `[no_alloc]`      — proven not to allocate under the configured policy
16//!   `[locals=N]`      — resolver's `local_count`
17//!   `[body=kind]`     — body shape from the thin-body classifier
18//!
19//! Expression rendering reuses `checker::verify::expr_to_str`. Top-level
20//! scaffolding (fn signatures, stmt list, type defs, module headers) lives
21//! here. The analysis itself lives in `ir::analyze` — dump is read-only
22//! over its result, which keeps compute-once / read-many honest.
23
24use std::fmt::Write;
25
26use crate::ast::{FnBody, FnDef, Stmt, TopLevel, TypeDef};
27use crate::checker::expr_to_str;
28use crate::ir::{AnalysisResult, BodyShape, FnAnalysis, ThinKind};
29
30/// Render every top-level item in `items`, separated by blank lines.
31/// Pass `analysis: None` for dumps before the `Analyze` stage has run
32/// (or when running passes individually); FnDef lines will then omit
33/// the `[...]` annotation block but the IR itself still renders.
34pub fn dump_items(items: &[TopLevel], analysis: Option<&AnalysisResult>) -> String {
35    let mut out = String::new();
36    let mut first = true;
37    for item in items {
38        if !first {
39            out.push('\n');
40        }
41        first = false;
42        dump_top_level(item, &mut out, analysis);
43    }
44    out
45}
46
47fn dump_top_level(item: &TopLevel, out: &mut String, analysis: Option<&AnalysisResult>) {
48    match item {
49        TopLevel::Module(m) => {
50            writeln!(out, "module {}", m.name).ok();
51            if !m.depends.is_empty() {
52                writeln!(out, "  depends [{}]", m.depends.join(", ")).ok();
53            }
54            if !m.exposes.is_empty() {
55                writeln!(out, "  exposes [{}]", m.exposes.join(", ")).ok();
56            }
57            if let Some(effects) = &m.effects {
58                writeln!(out, "  effects [{}]", effects.join(", ")).ok();
59            }
60        }
61        TopLevel::TypeDef(td) => dump_typedef(td, out),
62        TopLevel::FnDef(fd) => {
63            let fn_analysis = analysis.and_then(|a| a.fn_analyses.get(&fd.name));
64            dump_fndef(fd, out, fn_analysis);
65        }
66        TopLevel::Stmt(s) => dump_stmt(s, 0, out),
67        TopLevel::Verify(vb) => {
68            writeln!(out, "verify {} <{} case(s)>", vb.fn_name, vb.cases.len()).ok();
69        }
70        TopLevel::Decision(_) => {
71            writeln!(out, "decision <block>").ok();
72        }
73    }
74}
75
76fn dump_typedef(td: &TypeDef, out: &mut String) {
77    match td {
78        TypeDef::Product { name, fields, .. } => {
79            let parts: Vec<String> = fields
80                .iter()
81                .map(|(n, t)| format!("{}: {}", n, t))
82                .collect();
83            writeln!(out, "type {} = {{ {} }}", name, parts.join(", ")).ok();
84        }
85        TypeDef::Sum { name, variants, .. } => {
86            let parts: Vec<String> = variants
87                .iter()
88                .map(|v| {
89                    if v.fields.is_empty() {
90                        v.name.clone()
91                    } else {
92                        format!("{}({})", v.name, v.fields.join(", "))
93                    }
94                })
95                .collect();
96            writeln!(out, "type {} = {}", name, parts.join(" | ")).ok();
97        }
98    }
99}
100
101fn dump_fndef(fd: &FnDef, out: &mut String, fn_analysis: Option<&FnAnalysis>) {
102    let params: Vec<String> = fd
103        .params
104        .iter()
105        .map(|(n, t)| format!("{}: {}", n, t))
106        .collect();
107    let effects = if fd.effects.is_empty() {
108        String::new()
109    } else {
110        let names: Vec<String> = fd.effects.iter().map(|e| e.node.clone()).collect();
111        format!(" ! [{}]", names.join(", "))
112    };
113
114    let tag_str = match fn_analysis {
115        None => String::new(),
116        Some(a) => {
117            let mut tags: Vec<String> = Vec::new();
118            if matches!(a.allocates, Some(false)) {
119                tags.push("no_alloc".to_string());
120            }
121            if let Some(n) = a.local_count {
122                tags.push(format!("locals={}", n));
123            }
124            if a.mutual_tco_member {
125                tags.push("mutual-tco".to_string());
126            } else if a.recursive {
127                if a.recursive_call_count >= 2 {
128                    tags.push(format!("recursive×{}", a.recursive_call_count));
129                } else {
130                    tags.push("recursive".to_string());
131                }
132            }
133            tags.push(body_shape_tag(a.body_shape, a.thin_kind));
134            format!("  [{}]", tags.join(", "))
135        }
136    };
137
138    writeln!(
139        out,
140        "fn {}({}) -> {}{}{}",
141        fd.name,
142        params.join(", "),
143        fd.return_type,
144        effects,
145        tag_str
146    )
147    .ok();
148
149    let FnBody::Block(stmts) = fd.body.as_ref();
150    for stmt in stmts {
151        dump_stmt(stmt, 1, out);
152    }
153}
154
155fn body_shape_tag(shape: BodyShape, thin_kind: Option<ThinKind>) -> String {
156    let kind_suffix = thin_kind.map(thin_kind_label).unwrap_or("");
157    let kind_part = if kind_suffix.is_empty() {
158        String::new()
159    } else {
160        format!(" ({})", kind_suffix)
161    };
162    match shape {
163        BodyShape::LeafExpr => format!("body=leaf-expr{}", kind_part),
164        BodyShape::SingleExpr => format!("body=single-expr{}", kind_part),
165        BodyShape::Block(n) => format!("body=block:{}{}", n, kind_part),
166        BodyShape::Unclassified(1) => "body=single-expr".to_string(),
167        BodyShape::Unclassified(n) => format!("body=block:{}", n),
168    }
169}
170
171fn thin_kind_label(kind: ThinKind) -> &'static str {
172    match kind {
173        ThinKind::Leaf => "leaf",
174        ThinKind::Direct => "direct",
175        ThinKind::Forward => "forward",
176        ThinKind::Dispatch => "dispatch",
177        ThinKind::Tail => "tail",
178    }
179}
180
181fn dump_stmt(stmt: &Stmt, indent: usize, out: &mut String) {
182    let pad = "  ".repeat(indent);
183    match stmt {
184        Stmt::Binding(name, ty, expr) => {
185            let ty_part = ty
186                .as_deref()
187                .map(|t| format!(": {}", t))
188                .unwrap_or_default();
189            writeln!(out, "{}{}{} = {}", pad, name, ty_part, expr_to_str(expr)).ok();
190        }
191        Stmt::Expr(expr) => {
192            writeln!(out, "{}{}", pad, expr_to_str(expr)).ok();
193        }
194    }
195}