Skip to main content

ir_lang/
print.rs

1//! The deterministic textual form (see `specs/IR.md` ยง11 for the grammar).
2
3use core::fmt::{self, Write};
4
5use crate::entity::{FuncId, GlobalId, Value, ValueRange};
6use crate::function::Function;
7use crate::inst::{Attrs, BlockCall, Call, CallTarget, InstData, Terminator};
8use crate::module::{Init, Module, SymbolRef};
9use crate::types::{Aggregate, PointerWidth, Type};
10
11/// Writes `s` as a double-quoted string with `\\`, `\"`, `\n`, `\t`, `\r`, and
12/// `\u{..}` escapes for other control characters.
13pub(crate) fn write_quoted(f: &mut impl Write, s: &str) -> fmt::Result {
14    f.write_char('"')?;
15    for c in s.chars() {
16        match c {
17            '\\' => f.write_str("\\\\")?,
18            '"' => f.write_str("\\\"")?,
19            '\n' => f.write_str("\\n")?,
20            '\t' => f.write_str("\\t")?,
21            '\r' => f.write_str("\\r")?,
22            c if c.is_control() => write!(f, "\\u{{{:x}}}", c as u32)?,
23            c => f.write_char(c)?,
24        }
25    }
26    f.write_char('"')
27}
28
29/// Whether a symbol name can be printed without quotes.
30pub(crate) fn is_bare_symbol(name: &str) -> bool {
31    let mut chars = name.chars();
32    match chars.next() {
33        Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
34        _ => return false,
35    }
36    chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.' || c == '$')
37}
38
39fn write_symbol(f: &mut impl Write, name: &str) -> fmt::Result {
40    f.write_char('@')?;
41    if is_bare_symbol(name) {
42        f.write_str(name)
43    } else {
44        write_quoted(f, name)
45    }
46}
47
48fn func_name(m: &Module, func: FuncId, f: &mut impl Write) -> fmt::Result {
49    match m.decls.funcs.get(func.index()) {
50        Some(d) => write_symbol(f, &d.name),
51        None => write!(f, "@\"?{func}\""),
52    }
53}
54
55fn global_name(m: &Module, global: GlobalId, f: &mut impl Write) -> fmt::Result {
56    match m.decls.globals.get(global.index()) {
57        Some(g) => write_symbol(f, &g.name),
58        None => write!(f, "@\"?{global}\""),
59    }
60}
61
62/// Writes a constant's bits in the form the parser reads back for `ty`.
63pub(crate) fn write_const(
64    f: &mut impl Write,
65    ty: Type,
66    bits: u64,
67    ptr: PointerWidth,
68) -> fmt::Result {
69    match ty {
70        Type::Bool if bits <= 1 => f.write_str(if bits == 1 { "true" } else { "false" }),
71        Type::Ptr | Type::Ref if bits == 0 => f.write_str("null"),
72        Type::F64 => write_f64(f, f64::from_bits(bits), bits),
73        Type::F32 if bits >> 32 == 0 => write_f32(f, f32::from_bits(bits as u32), bits),
74        t if t.is_int() => {
75            let width = t.bits(ptr);
76            if width < 64 && bits >> width != 0 {
77                return write!(f, "{bits:#x}");
78            }
79            if t.is_signed_int() {
80                let shift = 64 - width;
81                write!(f, "{}", ((bits << shift) as i64) >> shift)
82            } else {
83                write!(f, "{bits}")
84            }
85        }
86        _ => write!(f, "{bits:#x}"),
87    }
88}
89
90fn write_f64(f: &mut impl Write, x: f64, bits: u64) -> fmt::Result {
91    if x.is_nan() {
92        write!(f, "nan:{bits:#x}")
93    } else if x.is_infinite() {
94        f.write_str(if x > 0.0 { "inf" } else { "-inf" })
95    } else {
96        write!(f, "{x:?}")
97    }
98}
99
100fn write_f32(f: &mut impl Write, x: f32, bits: u64) -> fmt::Result {
101    if x.is_nan() {
102        write!(f, "nan:{bits:#x}")
103    } else if x.is_infinite() {
104        f.write_str(if x > 0.0 { "inf" } else { "-inf" })
105    } else {
106        write!(f, "{x:?}")
107    }
108}
109
110fn write_values(f: &mut impl Write, func: &Function, values: &[Value]) -> fmt::Result {
111    for (i, &v) in values.iter().enumerate() {
112        if i > 0 {
113            f.write_str(", ")?;
114        }
115        write!(f, "{}", func.resolve(v))?;
116    }
117    Ok(())
118}
119
120fn write_dest(f: &mut impl Write, func: &Function, dest: &BlockCall) -> fmt::Result {
121    write!(f, "{}", dest.block)?;
122    if !dest.args.is_empty() {
123        f.write_char('(')?;
124        for (i, a) in dest.args.iter().enumerate() {
125            if i > 0 {
126                f.write_str(", ")?;
127            }
128            match a {
129                crate::inst::BlockArg::Value(v) => write!(f, "{}", func.resolve(*v))?,
130                other => write!(f, "{other}")?,
131            }
132        }
133        f.write_char(')')?;
134    }
135    Ok(())
136}
137
138fn write_call(f: &mut impl Write, m: &Module, func: &Function, call: &Call) -> fmt::Result {
139    match call.target {
140        CallTarget::Direct(callee) => func_name(m, callee, f)?,
141        CallTarget::Indirect { sig, callee } => write!(f, "{sig} {}", func.resolve(callee))?,
142    }
143    f.write_char('(')?;
144    write_values(f, func, &call.args)?;
145    f.write_char(')')?;
146    if !call.gc.is_empty() {
147        f.write_str(" gc(")?;
148        write_values(f, func, &call.gc)?;
149        f.write_char(')')?;
150    }
151    write_attrs(f, call.attrs)
152}
153
154fn write_attrs(f: &mut impl Write, attrs: Attrs) -> fmt::Result {
155    if attrs.is_empty() {
156        Ok(())
157    } else {
158        write!(f, " {attrs}")
159    }
160}
161
162/// Writes an instruction's operation (everything after `results =`). `conv_to`
163/// prints a conversion's target type, which a `check` needs (it has no result
164/// value to carry the type).
165fn write_op(
166    f: &mut impl Write,
167    m: &Module,
168    func: &Function,
169    data: &InstData,
170    conv_to: bool,
171) -> fmt::Result {
172    let r = |v: Value| func.resolve(v);
173    f.write_str(data.opcode())?;
174    match data {
175        InstData::Const { ty, bits } => {
176            f.write_char(' ')?;
177            write_const(f, *ty, *bits, m.decls.ptr)
178        }
179        InstData::Unary { policy, arg, .. } => write!(f, "{policy} {}", r(*arg)),
180        InstData::Binary { policy, args, .. } => {
181            write!(f, "{policy} {}, {}", r(args[0]), r(args[1]))
182        }
183        InstData::Fma { args } => write!(f, " {}, {}, {}", r(args[0]), r(args[1]), r(args[2])),
184        InstData::Compare { args, .. } => write!(f, " {}, {}", r(args[0]), r(args[1])),
185        InstData::Convert {
186            policy, to, arg, ..
187        } => {
188            write!(f, "{policy} {}", r(*arg))?;
189            if conv_to {
190                write!(f, " as {to}")?;
191            }
192            Ok(())
193        }
194        InstData::Select { cond, args } => {
195            write!(f, " {}, {}, {}", r(*cond), r(args[0]), r(args[1]))
196        }
197        InstData::StackAddr { slot } => write!(f, " {slot}"),
198        InstData::GlobalAddr { global } => {
199            f.write_char(' ')?;
200            global_name(m, *global, f)
201        }
202        InstData::FuncAddr { func: callee } => {
203            f.write_char(' ')?;
204            func_name(m, *callee, f)
205        }
206        InstData::FieldAddr { base, agg, field } => write!(f, " {}, {agg}, {field}", r(*base)),
207        InstData::ElemAddr { base, elem, index } => {
208            write!(f, " {}, {elem}, {}", r(*base), r(*index))
209        }
210        InstData::PtrOffset { base, offset } => write!(f, " {}, {}", r(*base), r(*offset)),
211        InstData::Load { addr, flags, .. } => write!(f, " {} {flags}", r(*addr)),
212        InstData::Store { value, addr, flags } => write!(f, " {}, {} {flags}", r(*value), r(*addr)),
213        InstData::AtomicLoad { addr, order, .. } => write!(f, " {} {order}", r(*addr)),
214        InstData::AtomicStore { value, addr, order } => {
215            write!(f, " {}, {} {order}", r(*value), r(*addr))
216        }
217        InstData::AtomicRmw {
218            op,
219            addr,
220            value,
221            order,
222        } => write!(f, " {} {}, {} {order}", op.name(), r(*addr), r(*value)),
223        InstData::CmpXchg {
224            addr,
225            expected,
226            new,
227            success,
228            failure,
229        } => write!(
230            f,
231            " {}, {}, {} {success} {failure}",
232            r(*addr),
233            r(*expected),
234            r(*new)
235        ),
236        InstData::Fence { order } => write!(f, " {order}"),
237        InstData::MemCopy {
238            dst,
239            src,
240            len,
241            flags,
242        }
243        | InstData::MemMove {
244            dst,
245            src,
246            len,
247            flags,
248        } => {
249            write!(f, " {}, {}, {} {flags}", r(*dst), r(*src), r(*len))
250        }
251        InstData::MemSet {
252            dst,
253            byte,
254            len,
255            flags,
256        } => {
257            write!(f, " {}, {}, {} {flags}", r(*dst), r(*byte), r(*len))
258        }
259        InstData::Call(call) => {
260            f.write_char(' ')?;
261            write_call(f, m, func, call)
262        }
263        InstData::Safepoint { gc } => {
264            f.write_str(" gc(")?;
265            write_values(f, func, gc)?;
266            f.write_char(')')
267        }
268    }
269}
270
271fn write_results(f: &mut impl Write, func: &Function, results: ValueRange) -> fmt::Result {
272    for (i, v) in results.iter().enumerate() {
273        if i > 0 {
274            f.write_str(", ")?;
275        }
276        match func.value_type(v) {
277            Some(ty) => write!(f, "{v}: {ty}")?,
278            None => write!(f, "{v}: ?")?,
279        }
280    }
281    if !results.is_empty() {
282        f.write_str(" = ")?;
283    }
284    Ok(())
285}
286
287fn write_terminator(
288    f: &mut impl Write,
289    m: &Module,
290    func: &Function,
291    term: &Terminator,
292) -> fmt::Result {
293    f.write_str(term.opcode())?;
294    match term {
295        Terminator::Jump(dest) => {
296            f.write_char(' ')?;
297            write_dest(f, func, dest)
298        }
299        Terminator::Branch {
300            cond,
301            then_dest,
302            else_dest,
303        } => {
304            write!(f, " {}, ", func.resolve(*cond))?;
305            write_dest(f, func, then_dest)?;
306            f.write_str(", ")?;
307            write_dest(f, func, else_dest)
308        }
309        Terminator::Switch {
310            value,
311            cases,
312            default,
313        } => {
314            write!(f, " {} default ", func.resolve(*value))?;
315            write_dest(f, func, default)?;
316            f.write_str(" [")?;
317            // Case values print as their raw bits (unsigned decimal): the parser
318            // may meet a switch before the scrutinee's definition.
319            for (i, case) in cases.iter().enumerate() {
320                if i > 0 {
321                    f.write_str(", ")?;
322                }
323                write!(f, "{}: ", case.value)?;
324                write_dest(f, func, &case.dest)?;
325            }
326            f.write_char(']')
327        }
328        Terminator::Return { values } => {
329            if !values.is_empty() {
330                f.write_char(' ')?;
331                write_values(f, func, values)?;
332            }
333            Ok(())
334        }
335        Terminator::Unreachable => Ok(()),
336        Terminator::Trap { code } => write!(f, " {code}"),
337        Terminator::TailCall(call) => {
338            f.write_char(' ')?;
339            write_call(f, m, func, call)
340        }
341        Terminator::Invoke {
342            call,
343            normal,
344            unwind,
345        } => {
346            f.write_char(' ')?;
347            write_call(f, m, func, call)?;
348            f.write_str(" to ")?;
349            write_dest(f, func, normal)?;
350            f.write_str(" unwind ")?;
351            write_dest(f, func, unwind)
352        }
353        Terminator::Check { op, normal, error } => {
354            f.write_char(' ')?;
355            write_op(f, m, func, op, true)?;
356            f.write_str(" to ")?;
357            write_dest(f, func, normal)?;
358            f.write_str(" error ")?;
359            write_dest(f, func, error)
360        }
361        Terminator::Resume { payload } => write!(f, " {}", func.resolve(*payload)),
362    }
363}
364
365fn write_body(f: &mut impl Write, m: &Module, func: &Function) -> fmt::Result {
366    for (i, s) in func.slots.iter().enumerate() {
367        writeln!(f, "    ss{i} = slot {} align {}", s.size, s.align)?;
368    }
369    let entry = func.entry();
370    let order = core::iter::once(entry)
371        .filter(|&b| func.is_live_block(b))
372        .chain(func.blocks().filter(|&b| b != entry));
373    for b in order {
374        write!(f, "{b}")?;
375        let params = func.block_params(b);
376        if !params.is_empty() {
377            f.write_char('(')?;
378            for (i, &p) in params.iter().enumerate() {
379                if i > 0 {
380                    f.write_str(", ")?;
381                }
382                match func.value_type(p) {
383                    Some(ty) => write!(f, "{p}: {ty}")?,
384                    None => write!(f, "{p}: ?")?,
385                }
386            }
387            f.write_char(')')?;
388        }
389        f.write_str(":\n")?;
390        for inst in func.insts(b) {
391            f.write_str("    ")?;
392            write_results(f, func, func.results(inst))?;
393            if let Some(data) = func.inst(inst) {
394                write_op(f, m, func, data, false)?;
395            }
396            f.write_char('\n')?;
397        }
398        if let Some(term) = func.terminator(b) {
399            f.write_str("    ")?;
400            write_terminator(f, m, func, term)?;
401            f.write_char('\n')?;
402        }
403    }
404    Ok(())
405}
406
407fn write_function(f: &mut impl Write, m: &Module, id: FuncId) -> fmt::Result {
408    let Some(decl) = m.decls.funcs.get(id.index()) else {
409        return Ok(());
410    };
411    f.write_str("func ")?;
412    write_symbol(f, &decl.name)?;
413    write!(f, " {} {}", decl.sig, decl.linkage.name())?;
414    write_attrs(f, decl.attrs)?;
415    match m.function(id) {
416        Some(body) => {
417            f.write_str(" {\n")?;
418            write_body(f, m, body)?;
419            f.write_str("}\n")
420        }
421        None => f.write_char('\n'),
422    }
423}
424
425/// Prints one function; see [`Module::display_function`].
426pub(crate) struct FunctionDisplay<'m> {
427    pub(crate) module: &'m Module,
428    pub(crate) func: FuncId,
429}
430
431impl fmt::Display for FunctionDisplay<'_> {
432    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433        write_function(f, self.module, self.func)
434    }
435}
436
437impl fmt::Display for Module {
438    /// Prints the module in the textual form. The output depends only on the module
439    /// and is the same on every platform; [`parse`](crate::parse) reads it back.
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        f.write_str("module ")?;
442        write_quoted(f, &self.name)?;
443        let ptr = match self.decls.ptr {
444            PointerWidth::P32 => "ptr32",
445            PointerWidth::P64 => "ptr64",
446        };
447        writeln!(f, " {ptr}")?;
448        for (i, a) in self.decls.aggs.iter().enumerate() {
449            write!(f, "type t{i} = ")?;
450            match &a.agg {
451                Aggregate::Struct { fields, packed } => {
452                    if *packed {
453                        f.write_str("packed ")?;
454                    }
455                    f.write_str("struct {")?;
456                    for (k, field) in fields.iter().enumerate() {
457                        f.write_str(if k == 0 { " " } else { ", " })?;
458                        write!(f, "{field}")?;
459                    }
460                    f.write_str(" }\n")?;
461                }
462                Aggregate::Array { elem, len } => writeln!(f, "[{elem} x {len}]")?,
463            }
464        }
465        for (i, s) in self.decls.sigs.iter().enumerate() {
466            writeln!(f, "sig s{i} = {s}")?;
467        }
468        for g in &self.decls.globals {
469            let gl = &g.global;
470            f.write_str("global ")?;
471            write_symbol(f, &g.name)?;
472            write!(
473                f,
474                " {} {} size {} align {}",
475                gl.linkage.name(),
476                if gl.mutable { "mutable" } else { "const" },
477                gl.size,
478                gl.align
479            )?;
480            match &gl.init {
481                Init::Zeroed => f.write_str(" zeroed")?,
482                Init::Bytes(bytes) => {
483                    f.write_str(" bytes \"")?;
484                    for b in bytes {
485                        write!(f, "{b:02x}")?;
486                    }
487                    f.write_char('"')?;
488                }
489            }
490            for r in &gl.relocs {
491                write!(f, " reloc {} ", r.offset)?;
492                match r.target {
493                    SymbolRef::Func(func) => func_name(self, func, f)?,
494                    SymbolRef::Global(global) => global_name(self, global, f)?,
495                }
496                write!(f, " {:+}", r.addend)?;
497            }
498            f.write_char('\n')?;
499        }
500        for id in self.functions() {
501            if self.function(id).is_some() {
502                f.write_char('\n')?;
503            }
504            write_function(f, self, id)?;
505        }
506        Ok(())
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513    use alloc::string::String;
514
515    #[test]
516    fn test_quoting_escapes_specials() {
517        let mut s = String::new();
518        let _ = write_quoted(&mut s, "a\"b\\c\n\u{1}");
519        assert_eq!(s, "\"a\\\"b\\\\c\\n\\u{1}\"");
520    }
521
522    #[test]
523    fn test_bare_symbols() {
524        assert!(is_bare_symbol("main"));
525        assert!(is_bare_symbol("_start.x$1"));
526        assert!(!is_bare_symbol("1x"));
527        assert!(!is_bare_symbol(""));
528        assert!(!is_bare_symbol("a b"));
529    }
530
531    #[test]
532    fn test_const_forms() {
533        let mut s = String::new();
534        let p = PointerWidth::P64;
535        let _ = write_const(&mut s, Type::I8, 0xff, p);
536        s.push(' ');
537        let _ = write_const(&mut s, Type::U8, 0xff, p);
538        s.push(' ');
539        let _ = write_const(&mut s, Type::F64, (-0.0f64).to_bits(), p);
540        s.push(' ');
541        let _ = write_const(&mut s, Type::F64, f64::NAN.to_bits(), p);
542        s.push(' ');
543        let _ = write_const(&mut s, Type::I8, 0x1ff, p);
544        s.push(' ');
545        let _ = write_const(&mut s, Type::Ref, 0, p);
546        assert_eq!(s, "-1 255 -0.0 nan:0x7ff8000000000000 0x1ff null");
547    }
548}