Skip to main content

oxide_core/bytecode/
disasm.rs

1//! The disassembler: one instruction per line, registers by role (`this`,
2//! `p0`, `l3`, `t2`, or `r300` above a frame's temporaries).
3
4use std::fmt::Write;
5
6use super::{Capture, CodeUnit, Format, Insn};
7use crate::heap::atom::{KeyKind, PropertyKey};
8
9/// Names register `r` of `unit` by its role in the frame layout.
10pub fn register_name(unit: &CodeUnit, r: u32) -> String {
11    let parameters = unit.parameter_count as u32;
12    let locals = unit.temp_base() - 1 - parameters;
13    if unit.spill_register != super::NO_SPILL && r == unit.spill_register as u32 {
14        "spill".to_owned()
15    } else if r == 0 {
16        "this".to_owned()
17    } else if r <= parameters {
18        format!("p{}", r - 1)
19    } else if r <= parameters + locals {
20        format!("l{}", r - 1 - parameters)
21    } else if r < unit.register_count as u32 {
22        format!("t{}", r - unit.temp_base())
23    } else {
24        format!("r{r}")
25    }
26}
27
28/// A key without the heap: `[7]` for an index, `atom#12`, `sym#3`. The
29/// runtime passes a namer that prints atom text instead.
30pub fn key_placeholder(key: PropertyKey) -> String {
31    match key.kind() {
32        KeyKind::Index(index) => format!("[{index}]"),
33        KeyKind::String(id) => format!("atom#{}", id.index()),
34        KeyKind::Symbol(id) => format!("sym#{}", id.index()),
35    }
36}
37
38/// The operands of one instruction, without the opcode.
39pub fn operands(
40    unit: &CodeUnit,
41    insn: Insn,
42    key_name: &mut dyn FnMut(PropertyKey) -> String,
43) -> String {
44    let r = |r: u8| register_name(unit, r as u32);
45    let target = |t: u32| format!("@{t}");
46    let text = match insn.op.format() {
47        Format::None => String::new(),
48        Format::A => r(insn.a),
49        Format::AB => format!("{}, {}", r(insn.a), r(insn.b)),
50        Format::ABC => format!("{}, {}, {}", r(insn.a), r(insn.b), r(insn.c)),
51        Format::AInt => format!("{}, {}", r(insn.a), insn.int()),
52        Format::ABInt => format!("{}, {}, {}", r(insn.a), r(insn.b), insn.int()),
53        Format::AConst => format!("{}, k{}", r(insn.a), insn.imm),
54        Format::Key => key_name(insn.key()),
55        Format::AKey => format!("{}, {}", r(insn.a), key_name(insn.key())),
56        Format::ABKey => format!("{}, {}, {}", r(insn.a), r(insn.b), key_name(insn.key())),
57        Format::ACapture => format!("{}, c{}", r(insn.a), insn.imm),
58        Format::ACount => format!("{}, {}", r(insn.a), insn.imm),
59        Format::ABRange => {
60            if insn.imm == 0 {
61                format!("{}, []", r(insn.a))
62            } else {
63                let last = insn.b as u32 + insn.imm - 1;
64                format!(
65                    "{}, [{}..{}]",
66                    r(insn.a),
67                    r(insn.b),
68                    register_name(unit, last)
69                )
70            }
71        }
72        Format::AChild => format!("{}, fn{}", r(insn.a), insn.imm),
73        Format::Call => format!("{}, {}, {}", r(insn.a), r(insn.b), insn.c),
74        Format::Jump => target(insn.imm),
75        Format::AJump => format!("{}, {}", r(insn.a), target(insn.imm)),
76        Format::ABJump => format!("{}, {}, {}", r(insn.a), r(insn.b), target(insn.imm)),
77        Format::AShortJump => format!("{}, {}, {}", r(insn.a), insn.short(), target(insn.imm)),
78        Format::AWide => format!("{}, {}", r(insn.a), register_name(unit, insn.imm)),
79    };
80    match insn.site() {
81        Some(site) => format!("{text}, ic{site}"),
82        None => text,
83    }
84}
85
86/// The whole unit: a header line, the instructions, the handler table.
87pub fn disassemble(unit: &CodeUnit, key_name: &mut dyn FnMut(PropertyKey) -> String) -> String {
88    let mut out = String::new();
89    let name = if unit.debug.name.is_empty() {
90        "<anonymous>"
91    } else {
92        &unit.debug.name
93    };
94    let _ = writeln!(
95        out,
96        "{name}: params {}, locals {}, registers {}, constants {}, children {}, this slots {}, cache sites {}",
97        unit.parameter_count,
98        unit.local_count,
99        unit.register_count,
100        unit.constant_count,
101        unit.children.len(),
102        unit.this_slots,
103        unit.ic_count
104    );
105    for (pc, &insn) in unit.code.iter().enumerate() {
106        let operands = operands(unit, insn, key_name);
107        let line = format!("  {pc:04}  {:<20}{operands}", insn.op.name());
108        let _ = writeln!(out, "{}", line.trim_end());
109    }
110    for (index, capture) in unit.captures.iter().enumerate() {
111        let source = match *capture {
112            Capture::None => "none".to_owned(),
113            Capture::Register(r) => format!("parent {}", r),
114            Capture::Outer(i) => format!("parent c{i}"),
115            Capture::Slot(i) => format!("parent slot {i}"),
116        };
117        let _ = writeln!(out, "  capture c{index} <- {source}");
118    }
119    for handler in &unit.handlers {
120        let _ = writeln!(
121            out,
122            "  handler @{}..@{} -> @{}, {}",
123            handler.start,
124            handler.end,
125            handler.target,
126            register_name(unit, handler.register as u32)
127        );
128    }
129    out
130}