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    types_list(f, "regs", "r", &func.regs)?;
70    types_list(f, "captures", "u", &func.captures)?;
71    if !func.names.is_empty() {
72        f.write_str("  names")?;
73        for (i, s) in func.names.iter().enumerate() {
74            write!(f, " n{i}={s}")?;
75        }
76        writeln!(f)?;
77    }
78    if !func.type_refs.is_empty() {
79        f.write_str("  type_refs")?;
80        for (i, t) in func.type_refs.iter().enumerate() {
81            write!(f, " ty{i}={t}")?;
82        }
83        writeln!(f)?;
84    }
85    let labels = labels(func);
86    let cx = Cx {
87        labels: &labels,
88        module: Some(m),
89        func: Some(func),
90    };
91    let target = |f: &mut fmt::Formatter<'_>, pc: u32| -> fmt::Result {
92        match labels.binary_search(&pc) {
93            Ok(i) => write!(f, "L{i}"),
94            Err(_) => write!(f, "@{pc}"),
95        }
96    };
97    for (i, table) in func.tables.iter().enumerate() {
98        write!(f, "  table jt{i} [")?;
99        for (j, t) in table.targets.iter().enumerate() {
100            if j > 0 {
101                f.write_str(", ")?;
102            }
103            target(f, t.0)?;
104        }
105        f.write_str("] default ")?;
106        target(f, table.default.0)?;
107        writeln!(f)?;
108    }
109    for h in &func.handlers {
110        write!(f, "  try @{}..@{} -> ", h.start, h.end)?;
111        target(f, h.target.0)?;
112        writeln!(f, " catch {}", h.catch)?;
113    }
114    for local in &func.locals {
115        write!(f, "  local {} ", local.reg)?;
116        named(f, m, local.name)?;
117        writeln!(f, " @{}..@{}", local.start, local.end)?;
118    }
119    for row in &func.lines {
120        writeln!(
121            f,
122            "  line @{} {}:{}:{}",
123            row.pc, row.file, row.line, row.column
124        )?;
125    }
126    let mut next_label = 0usize;
127    for (pc, inst) in func.code.iter().enumerate() {
128        if labels.get(next_label).is_some_and(|&l| l as usize == pc) {
129            writeln!(f, "L{next_label}:")?;
130            next_label += 1;
131        }
132        write!(f, "  {pc:04} ")?;
133        inst.render(f, &cx)?;
134        writeln!(f)?;
135    }
136    Ok(())
137}
138
139/// The disassembly listing; see [`crate::disassemble`].
140impl fmt::Display for Module {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        let m = self;
143        writeln!(f, "lsb {FORMAT_VERSION}")?;
144        if let Some(name) = m.name {
145            f.write_str("module ")?;
146            named(f, m, name)?;
147            writeln!(f)?;
148        }
149        if let Some(start) = m.start {
150            writeln!(f, "start {start}")?;
151        }
152
153        writeln!(f, "strings {}", m.strings.len())?;
154        for (i, s) in m.strings.iter().enumerate() {
155            write!(f, "  s{i} ")?;
156            write_quoted(f, s)?;
157            writeln!(f)?;
158        }
159        writeln!(f, "types {}", m.types.len())?;
160        for (i, t) in m.types.iter().enumerate() {
161            writeln!(f, "  t{i} {t}")?;
162        }
163        writeln!(f, "consts {}", m.consts.len())?;
164        for (i, c) in m.consts.iter().enumerate() {
165            write!(f, "  k{i} {c}")?;
166            if let crate::Const::Str(_) = c {
167                f.write_str("  ; ")?;
168                write_const_preview(f, m, c)?;
169            }
170            writeln!(f)?;
171        }
172        writeln!(f, "imports {}", m.imports.len())?;
173        for (i, imp) in m.imports.iter().enumerate() {
174            write!(f, "  imp{i} ")?;
175            named(f, m, imp.module)?;
176            f.write_str(" . ")?;
177            named(f, m, imp.name)?;
178            writeln!(f, " : {}", imp.sig)?;
179        }
180        writeln!(f, "globals {}", m.globals.len())?;
181        for (i, g) in m.globals.iter().enumerate() {
182            write!(f, "  g{i} ")?;
183            named(f, m, g.name)?;
184            write!(f, " : {}", g.ty)?;
185            if g.mutable {
186                f.write_str(" mut")?;
187            }
188            if let Some(init) = g.init {
189                write!(f, " = {init}")?;
190            }
191            writeln!(f)?;
192        }
193        writeln!(f, "exports {}", m.exports.len())?;
194        for e in &m.exports {
195            f.write_str("  ")?;
196            named(f, m, e.name)?;
197            writeln!(f, " = {}", e.item)?;
198        }
199        writeln!(f, "hooks {}", m.hooks.len())?;
200        for b in &m.hooks {
201            writeln!(f, "  {} = {}", b.hook, b.callee)?;
202        }
203        writeln!(f, "functions {}", m.functions.len())?;
204        for (i, func) in m.functions.iter().enumerate() {
205            function(f, m, i, func)?;
206        }
207        Ok(())
208    }
209}