Skip to main content

lift_core/
printer.rs

1use crate::attributes::Attribute;
2use crate::blocks::BlockKey;
3use crate::context::Context;
4use crate::operations::OpKey;
5use crate::regions::RegionKey;
6use crate::values::ValueKey;
7use std::fmt::Write;
8
9pub struct Printer<'a> {
10    ctx: &'a Context,
11    indent: usize,
12    output: String,
13    value_names: std::collections::HashMap<ValueKey, String>,
14    next_value_id: u32,
15    next_block_id: u32,
16    block_names: std::collections::HashMap<BlockKey, String>,
17}
18
19impl<'a> Printer<'a> {
20    pub fn new(ctx: &'a Context) -> Self {
21        Self {
22            ctx,
23            indent: 0,
24            output: String::new(),
25            value_names: std::collections::HashMap::new(),
26            next_value_id: 0,
27            next_block_id: 0,
28            block_names: std::collections::HashMap::new(),
29        }
30    }
31
32    pub fn print_all(&mut self) -> &str {
33        for module in &self.ctx.modules {
34            let name = self.ctx.strings.resolve(module.name);
35            self.write_line(&format!("module @{} {{", name));
36            self.indent += 1;
37
38            for func in &module.functions {
39                self.print_function(func);
40            }
41
42            self.indent -= 1;
43            self.write_line("}");
44            self.write_line("");
45        }
46
47        // Print any standalone blocks/ops not in modules
48        for (block_key, _block) in &self.ctx.blocks {
49            if self.ctx.blocks[block_key].parent_region.is_none() {
50                self.print_block(block_key);
51            }
52        }
53
54        &self.output
55    }
56
57    fn print_function(&mut self, func: &crate::functions::FunctionData) {
58        let name = self.ctx.strings.resolve(func.name);
59        let mut sig = format!("func @{}(", name);
60
61        for (i, &param_ty) in func.params.iter().enumerate() {
62            if i > 0 {
63                sig.push_str(", ");
64            }
65            let pname = self.fresh_value_name();
66            let _ = write!(sig, "%{}: {}", pname, self.format_type(param_ty));
67        }
68
69        sig.push_str(") -> ");
70        if func.returns.len() == 1 {
71            let _ = write!(sig, "{}", self.format_type(func.returns[0]));
72        } else {
73            sig.push('(');
74            for (i, &ret_ty) in func.returns.iter().enumerate() {
75                if i > 0 {
76                    sig.push_str(", ");
77                }
78                let _ = write!(sig, "{}", self.format_type(ret_ty));
79            }
80            sig.push(')');
81        }
82
83        if let Some(body) = func.body {
84            sig.push_str(" {");
85            self.write_line(&sig);
86            self.indent += 1;
87            self.print_region(body);
88            self.indent -= 1;
89            self.write_line("}");
90        } else {
91            self.write_line(&sig);
92        }
93        self.write_line("");
94    }
95
96    fn print_region(&mut self, region_key: RegionKey) {
97        if let Some(region) = self.ctx.get_region(region_key) {
98            for &block_key in &region.blocks {
99                self.print_block(block_key);
100            }
101        }
102    }
103
104    fn print_block(&mut self, block_key: BlockKey) {
105        let block_name = self.get_block_name(block_key);
106        if let Some(block) = self.ctx.get_block(block_key) {
107            if !block.args.is_empty() {
108                let mut args = String::new();
109                for (i, &arg) in block.args.iter().enumerate() {
110                    if i > 0 {
111                        args.push_str(", ");
112                    }
113                    let vname = self.get_value_name(arg);
114                    if let Some(val) = self.ctx.get_value(arg) {
115                        let _ = write!(args, "%{}: {}", vname, self.format_type(val.ty));
116                    }
117                }
118                self.write_line(&format!("^{}({}):", block_name, args));
119            } else {
120                self.write_line(&format!("^{}:", block_name));
121            }
122
123            self.indent += 1;
124            for &op_key in &block.ops {
125                self.print_op(op_key);
126            }
127            self.indent -= 1;
128        }
129    }
130
131    fn print_op(&mut self, op_key: OpKey) {
132        if let Some(op) = self.ctx.get_op(op_key) {
133            let op_name = self.ctx.strings.resolve(op.name).to_string();
134            let mut line = String::new();
135
136            // Result values
137            if !op.results.is_empty() {
138                for (i, &result) in op.results.iter().enumerate() {
139                    if i > 0 {
140                        line.push_str(", ");
141                    }
142                    let vname = self.get_value_name(result);
143                    let _ = write!(line, "%{}", vname);
144                }
145                line.push_str(" = ");
146            }
147
148            // Operation name
149            let _ = write!(line, "\"{}\"", op_name);
150
151            // Inputs
152            line.push('(');
153            for (i, &input) in op.inputs.iter().enumerate() {
154                if i > 0 {
155                    line.push_str(", ");
156                }
157                let vname = self.get_value_name(input);
158                let _ = write!(line, "%{}", vname);
159            }
160            line.push(')');
161
162            // Attributes
163            if !op.attrs.is_empty() {
164                line.push_str(" {");
165                for (i, (key, val)) in op.attrs.iter().enumerate() {
166                    if i > 0 {
167                        line.push_str(", ");
168                    }
169                    let _ = write!(line, "{} = {}", key, self.format_attr(val));
170                }
171                line.push('}');
172            }
173
174            // Type signature
175            line.push_str(" : (");
176            for (i, &input) in op.inputs.iter().enumerate() {
177                if i > 0 {
178                    line.push_str(", ");
179                }
180                if let Some(val) = self.ctx.get_value(input) {
181                    let _ = write!(line, "{}", self.format_type(val.ty));
182                }
183            }
184            line.push_str(") -> ");
185
186            if op.results.len() == 1 {
187                if let Some(val) = self.ctx.get_value(op.results[0]) {
188                    let _ = write!(line, "{}", self.format_type(val.ty));
189                }
190            } else if op.results.is_empty() {
191                line.push_str("()");
192            } else {
193                line.push('(');
194                for (i, &result) in op.results.iter().enumerate() {
195                    if i > 0 {
196                        line.push_str(", ");
197                    }
198                    if let Some(val) = self.ctx.get_value(result) {
199                        let _ = write!(line, "{}", self.format_type(val.ty));
200                    }
201                }
202                line.push(')');
203            }
204
205            self.write_line(&line);
206
207            // Print nested regions
208            if !op.regions.is_empty() {
209                for &region in &op.regions {
210                    self.indent += 1;
211                    self.print_region(region);
212                    self.indent -= 1;
213                }
214            }
215        }
216    }
217
218    fn format_type(&self, ty_id: crate::types::TypeId) -> String {
219        let ty = self.ctx.resolve_type(ty_id);
220        format!("{}", ty)
221    }
222
223    fn format_attr(&self, attr: &Attribute) -> String {
224        match attr {
225            Attribute::Integer(v) => format!("{}", v),
226            Attribute::Float(v) => format!("{:.6}", v),
227            Attribute::String(s) => {
228                let resolved = self.ctx.strings.resolve(*s);
229                format!("\"{}\"", resolved)
230            }
231            Attribute::Bool(b) => format!("{}", b),
232            Attribute::Type(_) => "type".to_string(),
233            Attribute::Array(arr) => {
234                let inner: Vec<String> = arr.iter().map(|a| self.format_attr(a)).collect();
235                format!("[{}]", inner.join(", "))
236            }
237            Attribute::Dict(map) => {
238                let inner: Vec<String> = map
239                    .iter()
240                    .map(|(k, v)| format!("{}: {}", k, self.format_attr(v)))
241                    .collect();
242                format!("{{{}}}", inner.join(", "))
243            }
244        }
245    }
246
247    fn get_value_name(&mut self, key: ValueKey) -> String {
248        if let Some(name) = self.value_names.get(&key) {
249            return name.clone();
250        }
251
252        // Try to use the debug name if available
253        let name = if let Some(val) = self.ctx.get_value(key) {
254            if let Some(name_id) = val.name {
255                self.ctx.strings.resolve(name_id).to_string()
256            } else {
257                self.fresh_value_name()
258            }
259        } else {
260            self.fresh_value_name()
261        };
262
263        self.value_names.insert(key, name.clone());
264        name
265    }
266
267    fn fresh_value_name(&mut self) -> String {
268        let name = format!("v{}", self.next_value_id);
269        self.next_value_id += 1;
270        name
271    }
272
273    fn get_block_name(&mut self, key: BlockKey) -> String {
274        if let Some(name) = self.block_names.get(&key) {
275            return name.clone();
276        }
277        let name = format!("bb{}", self.next_block_id);
278        self.next_block_id += 1;
279        self.block_names.insert(key, name.clone());
280        name
281    }
282
283    fn write_line(&mut self, text: &str) {
284        for _ in 0..self.indent {
285            self.output.push_str("    ");
286        }
287        self.output.push_str(text);
288        self.output.push('\n');
289    }
290
291    pub fn into_string(self) -> String {
292        self.output
293    }
294}
295
296pub fn print_ir(ctx: &Context) -> String {
297    let mut printer = Printer::new(ctx);
298    printer.print_all();
299    printer.into_string()
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_print_empty_context() {
308        let ctx = Context::new();
309        let output = print_ir(&ctx);
310        assert!(output.is_empty() || output.trim().is_empty());
311    }
312}