1use super::instruction::{CompiledProgram, ConstIdx, Constant, Op, Reg};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct DisasmLine {
12 pub pc: usize,
13 pub text: String,
14 pub targets: Vec<usize>,
17}
18
19pub fn disassemble(prog: &CompiledProgram) -> Vec<DisasmLine> {
22 prog.code
23 .iter()
24 .enumerate()
25 .map(|(pc, op)| DisasmLine { pc, text: format_op(op, prog), targets: op_targets(op) })
26 .collect()
27}
28
29pub fn format_op(op: &Op, prog: &CompiledProgram) -> String {
32 let r = |reg: Reg| format!("R{reg}");
33 let k = |idx: ConstIdx| format_const(prog.constants.get(idx as usize));
34 let g = |idx: u16| {
35 prog.globals
36 .get(idx as usize)
37 .cloned()
38 .unwrap_or_else(|| format!("global#{idx}"))
39 };
40 match *op {
41 Op::LoadConst { dst, idx } => format!("LoadConst {} = {}", r(dst), k(idx)),
42 Op::Move { dst, src } => format!("Move {} = {}", r(dst), r(src)),
43 Op::EnsureOwned { reg } => format!("EnsureOwned {}", r(reg)),
44 Op::Add { dst, lhs, rhs } => format!("Add {} = {} + {}", r(dst), r(lhs), r(rhs)),
45 Op::AddAssign { dst, src } => format!("AddAssign {} += {}", r(dst), r(src)),
46 Op::Sub { dst, lhs, rhs } => format!("Sub {} = {} - {}", r(dst), r(lhs), r(rhs)),
47 Op::Mul { dst, lhs, rhs } => format!("Mul {} = {} * {}", r(dst), r(lhs), r(rhs)),
48 Op::Div { dst, lhs, rhs } => format!("Div {} = {} / {}", r(dst), r(lhs), r(rhs)),
49 Op::FloorDiv { dst, lhs, rhs } => format!("FloorDiv {} = {} // {}", r(dst), r(lhs), r(rhs)),
50 Op::Mod { dst, lhs, rhs } => format!("Mod {} = {} % {}", r(dst), r(lhs), r(rhs)),
51 Op::Lt { dst, lhs, rhs } => format!("Lt {} = {} < {}", r(dst), r(lhs), r(rhs)),
52 Op::Gt { dst, lhs, rhs } => format!("Gt {} = {} > {}", r(dst), r(lhs), r(rhs)),
53 Op::LtEq { dst, lhs, rhs } => format!("LtEq {} = {} <= {}", r(dst), r(lhs), r(rhs)),
54 Op::GtEq { dst, lhs, rhs } => format!("GtEq {} = {} >= {}", r(dst), r(lhs), r(rhs)),
55 Op::Eq { dst, lhs, rhs } => format!("Eq {} = {} == {}", r(dst), r(lhs), r(rhs)),
56 Op::NotEq { dst, lhs, rhs } => format!("NotEq {} = {} != {}", r(dst), r(lhs), r(rhs)),
57 Op::Not { dst, src } => format!("Not {} = !{}", r(dst), r(src)),
58 Op::Concat { dst, lhs, rhs } => format!("Concat {} = {} ++ {}", r(dst), r(lhs), r(rhs)),
59 Op::Jump { target } => format!("Jump -> {target}"),
60 Op::JumpIfFalse { cond, target } => format!("JumpIfFalse {} -> {target}", r(cond)),
61 Op::JumpIfTrue { cond, target } => format!("JumpIfTrue {} -> {target}", r(cond)),
62 Op::Call { dst, func, args_start, arg_count } => {
63 format!("Call {} = fn#{func}({}..+{arg_count})", r(dst), r(args_start))
64 }
65 Op::Return { src } => format!("Return {}", r(src)),
66 Op::ReturnNothing => "ReturnNothing".to_string(),
67 Op::GlobalGet { dst, idx } => format!("GlobalGet {} = {}", r(dst), g(idx)),
68 Op::GlobalSet { idx, src } => format!("GlobalSet {} = {}", g(idx), r(src)),
69 Op::NewEmptyList { dst } => format!("NewEmptyList {}", r(dst)),
70 Op::NewList { dst, start, count } => format!("NewList {} = [{}..+{count}]", r(dst), r(start)),
71 Op::NewRange { dst, start, end } => format!("NewRange {} = {}..={}", r(dst), r(start), r(end)),
72 Op::ListPush { list, value } => format!("ListPush {} <- {}", r(list), r(value)),
73 Op::ListPop { list, dst } => format!("ListPop {} = {}.pop()", r(dst), r(list)),
74 Op::Index { dst, collection, index } => format!("Index {} = {}[{}]", r(dst), r(collection), r(index)),
75 Op::SetIndex { collection, index, value } => {
76 format!("SetIndex {}[{}] = {}", r(collection), r(index), r(value))
77 }
78 Op::Length { dst, collection } => format!("Length {} = len {}", r(dst), r(collection)),
79 Op::Contains { dst, collection, value } => {
80 format!("Contains {} = {} contains {}", r(dst), r(collection), r(value))
81 }
82 Op::IterPrepare { iterable } => format!("IterPrepare {}", r(iterable)),
83 Op::IterNext { dst, exit } => format!("IterNext {} (exhausted -> {exit})", r(dst)),
84 Op::IterPop => "IterPop".to_string(),
85 Op::Show { src } => format!("Show {}", r(src)),
86 Op::FailWith { msg } => format!("FailWith {}", k(msg)),
87 Op::Halt => "Halt".to_string(),
88 other => format!("{other:?}"),
91 }
92}
93
94pub fn op_targets(op: &Op) -> Vec<usize> {
96 match *op {
97 Op::Jump { target } => vec![target],
98 Op::JumpIfFalse { target, .. }
99 | Op::JumpIfTrue { target, .. } => vec![target],
100 Op::IterNext { exit, .. } => vec![exit],
101 _ => Vec::new(),
102 }
103}
104
105#[derive(Clone, Debug, Default, PartialEq, Eq)]
110pub struct OpIo {
111 pub writes: Option<Reg>,
112 pub reads: Vec<Reg>,
113}
114
115pub fn op_io(op: &Op) -> OpIo {
117 let rw = |dst: Reg, reads: Vec<Reg>| OpIo { writes: Some(dst), reads };
118 let ro = |reads: Vec<Reg>| OpIo { writes: None, reads };
119 match *op {
120 Op::LoadConst { dst, .. } => rw(dst, vec![]),
121 Op::Move { dst, src } => rw(dst, vec![src]),
122 Op::EnsureOwned { reg } => rw(reg, vec![reg]),
123 Op::Add { dst, lhs, rhs }
124 | Op::Sub { dst, lhs, rhs }
125 | Op::Mul { dst, lhs, rhs }
126 | Op::Div { dst, lhs, rhs }
127 | Op::ExactDiv { dst, lhs, rhs }
128 | Op::FloorDiv { dst, lhs, rhs }
129 | Op::Mod { dst, lhs, rhs }
130 | Op::Lt { dst, lhs, rhs }
131 | Op::Gt { dst, lhs, rhs }
132 | Op::LtEq { dst, lhs, rhs }
133 | Op::GtEq { dst, lhs, rhs }
134 | Op::Eq { dst, lhs, rhs }
135 | Op::NotEq { dst, lhs, rhs }
136 | Op::Concat { dst, lhs, rhs }
137 | Op::Pow { dst, lhs, rhs }
138 | Op::BitXor { dst, lhs, rhs }
139 | Op::BitAnd { dst, lhs, rhs }
140 | Op::BitOr { dst, lhs, rhs }
141 | Op::Shl { dst, lhs, rhs }
142 | Op::Shr { dst, lhs, rhs } => rw(dst, vec![lhs, rhs]),
143 Op::AddAssign { dst, src } => rw(dst, vec![dst, src]),
144 Op::Not { dst, src } => rw(dst, vec![src]),
145 Op::Show { src } => ro(vec![src]),
146 Op::Return { src } => ro(vec![src]),
147 Op::JumpIfFalse { cond, .. } | Op::JumpIfTrue { cond, .. } => {
148 ro(vec![cond])
149 }
150 Op::Index { dst, collection, index } | Op::IndexUnchecked { dst, collection, index } => {
151 rw(dst, vec![collection, index])
152 }
153 Op::SetIndex { collection, index, value } | Op::SetIndexUnchecked { collection, index, value } => {
154 rw(collection, vec![index, value])
155 }
156 Op::Length { dst, collection } => rw(dst, vec![collection]),
157 Op::Contains { dst, collection, value } => rw(dst, vec![collection, value]),
158 Op::ListPush { list, value } => rw(list, vec![value]),
159 _ => OpIo::default(),
160 }
161}
162
163pub fn format_constant(prog: &CompiledProgram, idx: ConstIdx) -> String {
165 format_const(prog.constants.get(idx as usize))
166}
167
168fn format_const(c: Option<&Constant>) -> String {
169 match c {
170 Some(Constant::Int(n)) => n.to_string(),
171 Some(Constant::Float(f)) => format!("{f}"),
172 Some(Constant::Bool(b)) => b.to_string(),
173 Some(Constant::Text(s)) => format!("{s:?}"),
174 Some(Constant::Char(ch)) => format!("'{ch}'"),
175 Some(Constant::Nothing) => "nothing".to_string(),
176 Some(other) => format!("{other:?}"),
177 None => "?".to_string(),
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 fn prog() -> CompiledProgram {
186 CompiledProgram {
187 constants: vec![Constant::Int(6), Constant::Int(7), Constant::Text("hi".into())],
188 code: vec![
189 Op::LoadConst { dst: 0, idx: 0 },
190 Op::LoadConst { dst: 1, idx: 1 },
191 Op::Mul { dst: 2, lhs: 0, rhs: 1 },
192 Op::JumpIfFalse { cond: 2, target: 7 },
193 Op::Show { src: 2 },
194 Op::Jump { target: 7 },
195 Op::LoadConst { dst: 3, idx: 2 },
196 Op::Halt,
197 ],
198 ..Default::default()
199 }
200 }
201
202 #[test]
203 fn renders_common_ops_with_resolved_operands() {
204 let d = disassemble(&prog());
205 assert_eq!(d[0].text, "LoadConst R0 = 6");
206 assert_eq!(d[1].text, "LoadConst R1 = 7");
207 assert_eq!(d[2].text, "Mul R2 = R0 * R1");
208 assert_eq!(d[3].text, "JumpIfFalse R2 -> 7");
209 assert_eq!(d[4].text, "Show R2");
210 assert_eq!(d[5].text, "Jump -> 7");
211 assert_eq!(d[6].text, "LoadConst R3 = \"hi\"");
212 assert_eq!(d[7].text, "Halt");
213 }
214
215 #[test]
216 fn extracts_jump_targets() {
217 let d = disassemble(&prog());
218 assert_eq!(d[3].targets, vec![7]); assert_eq!(d[5].targets, vec![7]); assert!(d[2].targets.is_empty()); }
222
223 #[test]
224 fn op_io_identifies_reads_and_writes() {
225 assert_eq!(op_io(&Op::Add { dst: 2, lhs: 0, rhs: 1 }), OpIo { writes: Some(2), reads: vec![0, 1] });
226 assert_eq!(op_io(&Op::LoadConst { dst: 0, idx: 0 }), OpIo { writes: Some(0), reads: vec![] });
227 assert_eq!(op_io(&Op::Show { src: 3 }), OpIo { writes: None, reads: vec![3] });
228 assert_eq!(op_io(&Op::Move { dst: 1, src: 0 }), OpIo { writes: Some(1), reads: vec![0] });
229 }
230
231 #[test]
232 fn pc_indices_are_sequential() {
233 let d = disassemble(&prog());
234 for (i, line) in d.iter().enumerate() {
235 assert_eq!(line.pc, i);
236 }
237 }
238}