midenc_hir/ir/
print.rs

1use alloc::format;
2use core::fmt;
3
4use super::{Context, Operation};
5use crate::{
6    AttributeValue, EntityWithId, SuccessorOperands, Value,
7    formatter::{Document, PrettyPrint},
8    matchers::Matcher,
9    traits::BranchOpInterface,
10};
11
12#[derive(Debug)]
13pub struct OpPrintingFlags {
14    pub print_entry_block_headers: bool,
15    pub print_intrinsic_attributes: bool,
16    pub print_source_locations: bool,
17}
18
19impl Default for OpPrintingFlags {
20    fn default() -> Self {
21        Self {
22            print_entry_block_headers: true,
23            print_intrinsic_attributes: false,
24            print_source_locations: false,
25        }
26    }
27}
28
29/// The `OpPrinter` trait is expected to be implemented by all [Op] impls as a prequisite.
30///
31/// The actual implementation is typically generated as part of deriving [Op].
32pub trait OpPrinter {
33    fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document;
34}
35
36impl OpPrinter for Operation {
37    #[inline]
38    fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document {
39        if let Some(op_printer) = self.as_trait::<dyn OpPrinter>() {
40            op_printer.print(flags, context)
41        } else {
42            let printer = OperationPrinter {
43                op: self,
44                flags,
45                context,
46            };
47            printer.render()
48        }
49    }
50}
51
52impl fmt::Display for Operation {
53    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        let flags = OpPrintingFlags::default();
55        let context = self.context();
56        let doc = self.print(&flags, context);
57        write!(f, "{doc}")
58    }
59}
60
61pub trait AttrPrinter {
62    fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document;
63}
64
65impl<T: PrettyPrint + AttributeValue> AttrPrinter for T {
66    default fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> Document {
67        PrettyPrint::render(self)
68    }
69}
70
71impl AttrPrinter for crate::Attribute {
72    fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document {
73        use crate::formatter::*;
74
75        match self.value() {
76            None => text(format!("#[{}]", self.name.as_str())),
77            Some(value) => {
78                const_text("#[")
79                    + text(self.name.as_str())
80                    + const_text(" = ")
81                    + value.print(flags, context)
82                    + const_text("]")
83            }
84        }
85    }
86}
87
88impl AttrPrinter for crate::OpFoldResult {
89    fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document {
90        use crate::formatter::*;
91
92        match self {
93            Self::Attribute(attr) => attr.print(flags, context),
94            Self::Value(value) => display(value.borrow().id()),
95        }
96    }
97}
98
99impl<T: AttrPrinter> AttrPrinter for [T] {
100    fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document {
101        use crate::formatter::*;
102
103        let mut doc = Document::Empty;
104        for (i, item) in self.iter().enumerate() {
105            if i == 0 {
106                doc += const_text(", ");
107            }
108
109            doc += item.print(flags, context);
110        }
111        doc
112    }
113}
114
115pub fn render_operation_results(op: &Operation) -> crate::formatter::Document {
116    use crate::formatter::*;
117
118    let results = op.results();
119    let doc = results.iter().fold(Document::Empty, |acc, result| {
120        if acc.is_empty() {
121            display(result.borrow().id())
122        } else {
123            acc + const_text(", ") + display(result.borrow().id())
124        }
125    });
126    if doc.is_empty() {
127        doc
128    } else {
129        doc + const_text(" = ")
130    }
131}
132
133pub fn render_operation_operands(op: &Operation) -> crate::formatter::Document {
134    use crate::formatter::*;
135
136    let operands = op.operands();
137    operands.iter().fold(Document::Empty, |acc, operand| {
138        let operand = operand.borrow();
139        let value = operand.value();
140        if acc.is_empty() {
141            display(value.id())
142        } else {
143            acc + const_text(", ") + display(value.id())
144        }
145    })
146}
147
148pub fn render_operation_result_types(op: &Operation) -> crate::formatter::Document {
149    use crate::formatter::*;
150
151    let results = op.results();
152    let result_types = results.iter().fold(Document::Empty, |acc, result| {
153        if acc.is_empty() {
154            text(format!("{}", result.borrow().ty()))
155        } else {
156            acc + const_text(", ") + text(format!("{}", result.borrow().ty()))
157        }
158    });
159    if result_types.is_empty() {
160        result_types
161    } else {
162        const_text(" : ") + result_types
163    }
164}
165
166pub fn render_regions(op: &Operation, flags: &OpPrintingFlags) -> crate::formatter::Document {
167    use crate::formatter::*;
168    const_text(" ")
169        + op.regions.iter().fold(Document::Empty, |acc, region| {
170            let doc = region.print(flags);
171            if acc.is_empty() {
172                doc
173            } else {
174                acc + const_text(" ") + doc
175            }
176        })
177        + const_text(";")
178}
179
180pub fn render_source_location(op: &Operation, context: &Context) -> crate::formatter::Document {
181    use crate::formatter::*;
182
183    // Check if the span is valid (not default/empty)
184    if op.span.is_unknown() {
185        return Document::Empty;
186    }
187
188    // Try to resolve the source location
189    let session = context.session();
190    if let Ok(source_file) = session.source_manager.get(op.span.source_id()) {
191        let location = source_file.location(op.span);
192        // Format: #loc("filename":line:col)
193        let filename = source_file.uri().as_str();
194        let loc_str = format!(
195            " #loc(\"{}\":{}:{})",
196            filename,
197            location.line.to_u32(),
198            location.column.to_u32()
199        );
200        return text(loc_str);
201    }
202
203    Document::Empty
204}
205
206struct OperationPrinter<'a> {
207    op: &'a Operation,
208    flags: &'a OpPrintingFlags,
209    context: &'a Context,
210}
211
212/// The generic format for printed operations is:
213///
214/// <%result..> = <dialect>.<op>(%operand : <operand_ty>, ..) : <result_ty..> #<attr>.. {
215///     // Region
216/// ^<block_id>(<%block_argument...>):
217///     // Block
218/// };
219///
220/// Special handling is provided for SingleRegionSingleBlock and CallableOpInterface ops:
221///
222/// * SingleRegionSingleBlock ops with no operands will have the block header elided
223impl PrettyPrint for OperationPrinter<'_> {
224    fn render(&self) -> crate::formatter::Document {
225        use crate::formatter::*;
226
227        let doc = render_operation_results(self.op) + display(self.op.name()) + const_text(" ");
228        let doc = if let Some(value) = crate::matchers::constant().matches(self.op) {
229            doc + value.print(self.flags, self.context)
230        } else if let Some(branch) = self.op.as_trait::<dyn BranchOpInterface>() {
231            // Print non-successor operands
232            let operands = branch.operands().group(0);
233            let doc = if !operands.is_empty() {
234                operands.iter().enumerate().fold(doc, |doc, (i, operand)| {
235                    let operand = operand.borrow();
236                    let value = operand.value();
237                    if i > 0 {
238                        doc + const_text(", ") + display(value.id())
239                    } else {
240                        doc + display(value.id())
241                    }
242                }) + const_text(" ")
243            } else {
244                doc
245            };
246            // Print successors
247            branch.successors().iter().enumerate().fold(doc, |doc, (succ_index, succ)| {
248                let doc = if succ_index > 0 {
249                    doc + const_text(", ") + display(succ.block.borrow().successor())
250                } else {
251                    doc + display(succ.block.borrow().successor())
252                };
253
254                let operands = branch.get_successor_operands(succ_index);
255                if !operands.is_empty() {
256                    let doc = doc + const_text("(");
257                    operands.forwarded().iter().enumerate().fold(doc, |doc, (i, operand)| {
258                        if !operand.is_linked() {
259                            if i > 0 {
260                                doc + const_text(", ") + const_text("<unlinked>")
261                            } else {
262                                doc + const_text("<unlinked>")
263                            }
264                        } else {
265                            let operand = operand.borrow();
266                            let value = operand.value();
267                            if i > 0 {
268                                doc + const_text(", ") + display(value.id())
269                            } else {
270                                doc + display(value.id())
271                            }
272                        }
273                    }) + const_text(")")
274                } else {
275                    doc
276                }
277            })
278        } else {
279            doc + render_operation_operands(self.op)
280        };
281
282        let doc = doc + render_operation_result_types(self.op);
283
284        let attrs = self.op.attrs.iter().fold(Document::Empty, |acc, attr| {
285            // Do not print intrinsic attributes unless explicitly configured
286            if !self.flags.print_intrinsic_attributes && attr.intrinsic {
287                return acc;
288            }
289            let doc = if let Some(value) = attr.value() {
290                const_text("#[")
291                    + display(attr.name)
292                    + const_text(" = ")
293                    + value.print(self.flags, self.context)
294                    + const_text("]")
295            } else {
296                text(format!("#[{}]", &attr.name))
297            };
298            if acc.is_empty() {
299                doc
300            } else {
301                acc + const_text(" ") + doc
302            }
303        });
304
305        let doc = if attrs.is_empty() {
306            doc
307        } else {
308            doc + const_text(" ") + attrs
309        };
310
311        // Add source location if requested
312        let doc = if self.flags.print_source_locations {
313            doc + render_source_location(self.op, self.context)
314        } else {
315            doc
316        };
317
318        if self.op.has_regions() {
319            doc + render_regions(self.op, self.flags)
320        } else {
321            doc + const_text(";")
322        }
323    }
324}