Skip to main content

bytecode_lang/
disasm.rs

1//! The disassembler: a deterministic text listing of a module.
2//!
3//! The listing depends only on the module (there is no map iteration and no
4//! address or pointer anywhere), so the same module always prints the same
5//! text, byte for byte. It never panics on a module whose indices are out of
6//! range (a decoded module is not yet verified): such references print as
7//! `<invalid>`.
8
9use alloc::vec::Vec;
10use core::fmt;
11
12use crate::FORMAT_VERSION;
13use crate::inst::{Cx, write_const_preview, write_quoted};
14use crate::module::{Function, Module};
15use crate::types::ValType;
16
17/// Writes `id` followed by its string in quotes, or `<invalid>`.
18fn named(f: &mut fmt::Formatter<'_>, m: &Module, id: crate::StrId) -> fmt::Result {
19    write!(f, "{id} ")?;
20    match m.string(id) {
21        Some(s) => write_quoted(f, s),
22        None => f.write_str("<invalid>"),
23    }
24}
25
26fn types_list(
27    f: &mut fmt::Formatter<'_>,
28    label: &str,
29    prefix: &str,
30    types: &[ValType],
31) -> fmt::Result {
32    if types.is_empty() {
33        return Ok(());
34    }
35    write!(f, "  {label}")?;
36    for (i, ty) in types.iter().enumerate() {
37        write!(f, " {prefix}{i}:{ty}")?;
38    }
39    writeln!(f)
40}
41
42/// The sorted, deduplicated in-range pcs some branch, table, or handler
43/// jumps to.
44fn labels(func: &Function) -> Vec<u32> {
45    let len = func.code.len();
46    let mut out: Vec<u32> = func
47        .code
48        .iter()
49        .filter_map(crate::Inst::branch_target)
50        .map(|t| t.0)
51        .chain(func.tables.iter().flat_map(|t| {
52            t.targets
53                .iter()
54                .map(|t| t.0)
55                .chain(core::iter::once(t.default.0))
56        }))
57        .chain(func.handlers.iter().map(|h| h.target.0))
58        .filter(|&pc| (pc as usize) < len)
59        .collect();
60    out.sort_unstable();
61    out.dedup();
62    out
63}
64
65fn function(f: &mut fmt::Formatter<'_>, m: &Module, id: usize, func: &Function) -> fmt::Result {
66    write!(f, "\nfunc f{id} ")?;
67    named(f, m, func.name)?;
68    writeln!(f, " : {}", func.sig)?;
69    if let Some(params) = &func.params {
70        writeln!(f, "  params {params}")?;
71    }
72    types_list(f, "regs", "r", &func.regs)?;
73    types_list(f, "captures", "u", &func.captures)?;
74    if !func.names.is_empty() {
75        f.write_str("  names")?;
76        for (i, s) in func.names.iter().enumerate() {
77            write!(f, " n{i}={s}")?;
78        }
79        writeln!(f)?;
80    }
81    if !func.type_refs.is_empty() {
82        f.write_str("  type_refs")?;
83        for (i, t) in func.type_refs.iter().enumerate() {
84            write!(f, " ty{i}={t}")?;
85        }
86        writeln!(f)?;
87    }
88    let labels = labels(func);
89    let cx = Cx {
90        labels: &labels,
91        module: Some(m),
92        func: Some(func),
93    };
94    let target = |f: &mut fmt::Formatter<'_>, pc: u32| -> fmt::Result {
95        match labels.binary_search(&pc) {
96            Ok(i) => write!(f, "L{i}"),
97            Err(_) => write!(f, "@{pc}"),
98        }
99    };
100    for (i, table) in func.tables.iter().enumerate() {
101        write!(f, "  table jt{i} [")?;
102        for (j, t) in table.targets.iter().enumerate() {
103            if j > 0 {
104                f.write_str(", ")?;
105            }
106            target(f, t.0)?;
107        }
108        f.write_str("] default ")?;
109        target(f, table.default.0)?;
110        writeln!(f)?;
111    }
112    for (i, shape) in func.shapes.iter().enumerate() {
113        writeln!(f, "  shape cs{i} {shape}")?;
114    }
115    for h in &func.handlers {
116        write!(f, "  try @{}..@{} -> ", h.start, h.end)?;
117        target(f, h.target.0)?;
118        writeln!(f, " catch {}", h.catch)?;
119    }
120    for local in &func.locals {
121        write!(f, "  local {} ", local.reg)?;
122        named(f, m, local.name)?;
123        writeln!(f, " @{}..@{}", local.start, local.end)?;
124    }
125    for row in &func.lines {
126        writeln!(
127            f,
128            "  line @{} {}:{}:{}",
129            row.pc, row.file, row.line, row.column
130        )?;
131    }
132    let mut next_label = 0usize;
133    for (pc, inst) in func.code.iter().enumerate() {
134        if labels.get(next_label).is_some_and(|&l| l as usize == pc) {
135            writeln!(f, "L{next_label}:")?;
136            next_label += 1;
137        }
138        write!(f, "  {pc:04} ")?;
139        inst.render(f, &cx)?;
140        writeln!(f)?;
141    }
142    Ok(())
143}
144
145/// The disassembly listing; see [`crate::disassemble`].
146impl fmt::Display for Module {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        let m = self;
149        writeln!(f, "lsb {FORMAT_VERSION}")?;
150        if let Some(name) = m.name {
151            f.write_str("module ")?;
152            named(f, m, name)?;
153            writeln!(f)?;
154        }
155        if let Some(start) = m.start {
156            writeln!(f, "start {start}")?;
157        }
158
159        writeln!(f, "strings {}", m.strings.len())?;
160        for (i, s) in m.strings.iter().enumerate() {
161            write!(f, "  s{i} ")?;
162            write_quoted(f, s)?;
163            writeln!(f)?;
164        }
165        writeln!(f, "types {}", m.types.len())?;
166        for (i, t) in m.types.iter().enumerate() {
167            writeln!(f, "  t{i} {t}")?;
168        }
169        writeln!(f, "consts {}", m.consts.len())?;
170        for (i, c) in m.consts.iter().enumerate() {
171            write!(f, "  k{i} {c}")?;
172            if let crate::Const::Str(_) = c {
173                f.write_str("  ; ")?;
174                write_const_preview(f, m, c)?;
175            }
176            writeln!(f)?;
177        }
178        writeln!(f, "imports {}", m.imports.len())?;
179        for (i, imp) in m.imports.iter().enumerate() {
180            write!(f, "  imp{i} ")?;
181            named(f, m, imp.module)?;
182            f.write_str(" . ")?;
183            named(f, m, imp.name)?;
184            write!(f, " : {}", imp.sig)?;
185            if let Some(params) = &imp.params {
186                write!(f, " params {params}")?;
187            }
188            writeln!(f)?;
189        }
190        writeln!(f, "globals {}", m.globals.len())?;
191        for (i, g) in m.globals.iter().enumerate() {
192            write!(f, "  g{i} ")?;
193            named(f, m, g.name)?;
194            write!(f, " : {}", g.ty)?;
195            if g.mutable {
196                f.write_str(" mut")?;
197            }
198            if let Some(init) = g.init {
199                write!(f, " = {init}")?;
200            }
201            writeln!(f)?;
202        }
203        writeln!(f, "exports {}", m.exports.len())?;
204        for e in &m.exports {
205            f.write_str("  ")?;
206            named(f, m, e.name)?;
207            writeln!(f, " = {}", e.item)?;
208        }
209        writeln!(f, "hooks {}", m.hooks.len())?;
210        for b in &m.hooks {
211            writeln!(f, "  {} = {}", b.hook, b.callee)?;
212        }
213        writeln!(f, "functions {}", m.functions.len())?;
214        for (i, func) in m.functions.iter().enumerate() {
215            function(f, m, i, func)?;
216        }
217        Ok(())
218    }
219}