1use anyhow::{Result, bail};
2
3use crate::model::{ConstPools, Tjs2File, Tjs2Object};
4use crate::vmcodes::vm;
5
6const FAT_NORMAL: i32 = 0;
7const FAT_EXPAND: i32 = 1;
8const FAT_UNNAMED_EXPAND: i32 = 2;
9
10pub fn disassemble_file(file: &Tjs2File) -> Result<String> {
11 let mut out = String::new();
12 out.push_str(&format!("toplevel: {}\n", file.toplevel));
13 out.push_str(&format!("objects: {}\n\n", file.objects.len()));
14
15 for obj in &file.objects {
16 out.push_str(&format_object_header(obj));
17 out.push('\n');
18 out.push_str(&disassemble_object(obj, &file.const_pools)?);
19 out.push('\n');
20 }
21
22 Ok(out)
23}
24
25fn format_object_header(obj: &Tjs2Object) -> String {
26 let name = obj.name.as_deref().unwrap_or("<anonymous>");
27 format!(
28 "== object {}: name={} context_type={} parent={} ==",
29 obj.index,
30 quote(name),
31 obj.context_type,
32 obj.parent
33 )
34}
35
36pub fn disassemble_object(obj: &Tjs2Object, pools: &ConstPools) -> Result<String> {
37 let mut out = String::new();
38 let code = &obj.code;
39
40 let mut i: usize = 0;
41 while i < code.len() {
42 let (msg, comment, size) = format_insn(code, i, obj, pools)?;
43 out.push_str(&format!("{:08} {}", i, msg));
44 if !comment.is_empty() {
45 out.push_str("\t// ");
46 out.push_str(&comment);
47 }
48 out.push('\n');
49 i += size;
50 }
51 Ok(out)
52}
53
54fn format_insn(
55 code: &[i32],
56 i: usize,
57 obj: &Tjs2Object,
58 pools: &ConstPools,
59) -> Result<(String, String, usize)> {
60 let op = code[i];
61 let mut msg = String::new();
62 let mut com = String::new();
63 let mut size: usize = 1;
64
65 let reg = |x: i32| -> i32 { x };
67 let data_idx = |x: i32| -> i32 { x };
68 let target = |rel: i32| -> i32 { rel + i as i32 };
69
70 if op == vm::VM_CONST {
72 ensure(code, i, 3)?;
73 let dst = reg(code[i + 1]);
74 let src = data_idx(code[i + 2]);
75 msg = format!("const %{}, *{}", dst, src);
76 if let Some(v) = obj.data.get(src as usize) {
77 com = format!("*{} = {}", src, v.to_readable(pools));
78 }
79 size = 3;
80 return Ok((msg, com, size));
81 }
82
83 for (base, name) in [
85 (vm::VM_CP, "cp"),
86 (vm::VM_CEQ, "ceq"),
87 (vm::VM_CDEQ, "cdeq"),
88 (vm::VM_CLT, "clt"),
89 (vm::VM_CGT, "cgt"),
90 (vm::VM_CHKINS, "chkins"),
91 (vm::VM_ADDCI, "addci"),
92 (vm::VM_CHGTHIS, "chgthis"),
93 ] {
94 if op == base {
95 ensure(code, i, 3)?;
96 msg = format!("{} %{}, %{}", name, reg(code[i + 1]), reg(code[i + 2]));
97 size = 3;
98 return Ok((msg, com, size));
99 }
100 }
101
102 for (base, name) in [
104 (vm::VM_CL, "cl"),
105 (vm::VM_SRV, "srv"),
106 (vm::VM_GLOBAL, "global"),
107 (vm::VM_THROW, "throw"),
108 (vm::VM_TT, "tt"),
109 (vm::VM_TF, "tf"),
110 (vm::VM_SETF, "setf"),
111 (vm::VM_SETNF, "setnf"),
112 (vm::VM_LNOT, "lnot"),
113 (vm::VM_BNOT, "bnot"),
114 (vm::VM_ASC, "asc"),
115 (vm::VM_CHR, "chr"),
116 (vm::VM_NUM, "num"),
117 (vm::VM_CHS, "chs"),
118 (vm::VM_INV, "inv"),
119 (vm::VM_CHKINV, "chkinv"),
120 (vm::VM_TYPEOF, "typeof"),
121 (vm::VM_EVAL, "eval"),
122 (vm::VM_EEXP, "eexp"),
123 (vm::VM_INT, "int"),
124 (vm::VM_REAL, "real"),
125 (vm::VM_STR, "str"),
126 (vm::VM_OCTET, "octet"),
127 ] {
128 if op == base {
129 ensure(code, i, 2)?;
130 msg = format!("{} %{}", name, reg(code[i + 1]));
131 size = 2;
132 return Ok((msg, com, size));
133 }
134 }
135
136 if op == vm::VM_CCL {
137 ensure(code, i, 3)?;
138 let r0 = reg(code[i + 1]);
139 let cnt = code[i + 2];
140 msg = format!("ccl %{}-%{}", r0, r0 + cnt - 1);
141 size = 3;
142 return Ok((msg, com, size));
143 }
144
145 for (base, name) in [(vm::VM_JF, "jf"), (vm::VM_JNF, "jnf"), (vm::VM_JMP, "jmp")] {
147 if op == base {
148 ensure(code, i, 2)?;
149 msg = format!("{} {:09}", name, target(code[i + 1]));
150 size = 2;
151 return Ok((msg, com, size));
152 }
153 }
154
155 if op == vm::VM_ENTRY {
157 ensure(code, i, 3)?;
158 msg = format!("entry {:09}, %{}", target(code[i + 1]), reg(code[i + 2]));
159 size = 3;
160 return Ok((msg, com, size));
161 }
162
163 for (base, name) in [
165 (vm::VM_RET, "ret"),
166 (vm::VM_NOP, "nop"),
167 (vm::VM_NF, "nf"),
168 (vm::VM_EXTRY, "extry"),
169 (vm::VM_REGMEMBER, "regmember"),
170 (vm::VM_DEBUGGER, "debugger"),
171 ] {
172 if op == base {
173 msg = name.to_string();
174 size = 1;
175 return Ok((msg, com, size));
176 }
177 }
178
179 if op == vm::VM_SETP || op == vm::VM_GETP {
181 ensure(code, i, 3)?;
182 let n = if op == vm::VM_SETP { "setp" } else { "getp" };
183 msg = format!("{} %{}, %{}", n, reg(code[i + 1]), reg(code[i + 2]));
184 size = 3;
185 return Ok((msg, com, size));
186 }
187
188 if op == vm::VM_DELD || op == vm::VM_TYPEOFD {
190 ensure(code, i, 4)?;
191 let n = if op == vm::VM_DELD { "deld" } else { "typeofd" };
192 let a = reg(code[i + 1]);
193 let b = reg(code[i + 2]);
194 let c = data_idx(code[i + 3]);
195 msg = format!("{} %{}, %{}.*{}", n, a, b, c);
196 if let Some(v) = obj.data.get(c as usize) {
197 com = format!("*{} = {}", c, v.to_readable(pools));
198 }
199 size = 4;
200 return Ok((msg, com, size));
201 }
202
203 if op == vm::VM_DELI || op == vm::VM_TYPEOFI {
205 ensure(code, i, 4)?;
206 let n = if op == vm::VM_DELI { "deli" } else { "typeofi" };
207 msg = format!(
208 "{} %{}, %{}.%{}",
209 n,
210 reg(code[i + 1]),
211 reg(code[i + 2]),
212 reg(code[i + 3])
213 );
214 size = 4;
215 return Ok((msg, com, size));
216 }
217
218 if op == vm::VM_GPD || op == vm::VM_GPDS {
220 ensure(code, i, 4)?;
221 let n = if op == vm::VM_GPD { "gpd" } else { "gpds" };
222 let a = reg(code[i + 1]);
223 let b = reg(code[i + 2]);
224 let c = data_idx(code[i + 3]);
225 msg = format!("{} %{}, %{}.*{}", n, a, b, c);
226 if let Some(v) = obj.data.get(c as usize) {
227 com = format!("*{} = {}", c, v.to_readable(pools));
228 }
229 size = 4;
230 return Ok((msg, com, size));
231 }
232
233 if op == vm::VM_SPD || op == vm::VM_SPDE || op == vm::VM_SPDEH || op == vm::VM_SPDS {
235 ensure(code, i, 4)?;
236 let n = match op {
237 x if x == vm::VM_SPD => "spd",
238 x if x == vm::VM_SPDE => "spde",
239 x if x == vm::VM_SPDEH => "spdeh",
240 _ => "spds",
241 };
242 let a = reg(code[i + 1]);
243 let b = data_idx(code[i + 2]);
244 let c = reg(code[i + 3]);
245 msg = format!("{} %{}.*{}, %{}", n, a, b, c);
246 if let Some(v) = obj.data.get(b as usize) {
247 com = format!("*{} = {}", b, v.to_readable(pools));
248 }
249 size = 4;
250 return Ok((msg, com, size));
251 }
252
253 if op == vm::VM_GPI || op == vm::VM_GPIS {
255 ensure(code, i, 4)?;
256 let n = if op == vm::VM_GPI { "gpi" } else { "gpis" };
257 msg = format!(
258 "{} %{}, %{}.%{}",
259 n,
260 reg(code[i + 1]),
261 reg(code[i + 2]),
262 reg(code[i + 3])
263 );
264 size = 4;
265 return Ok((msg, com, size));
266 }
267
268 if op == vm::VM_SPI || op == vm::VM_SPIE || op == vm::VM_SPIS {
270 ensure(code, i, 4)?;
271 let n = match op {
272 x if x == vm::VM_SPI => "spi",
273 x if x == vm::VM_SPIE => "spie",
274 _ => "spis",
275 };
276 msg = format!(
277 "{} %{}.%{}, %{}",
278 n,
279 reg(code[i + 1]),
280 reg(code[i + 2]),
281 reg(code[i + 3])
282 );
283 size = 4;
284 return Ok((msg, com, size));
285 }
286
287 if let Some((m, c, s)) = op1_prop(code, i, obj, pools, vm::VM_INC, "inc")? {
289 return Ok((m, c, s));
290 }
291 if let Some((m, c, s)) = op1_prop(code, i, obj, pools, vm::VM_DEC, "dec")? {
292 return Ok((m, c, s));
293 }
294
295 for (base, name) in [
297 (vm::VM_LOR, "lor"),
298 (vm::VM_LAND, "land"),
299 (vm::VM_BOR, "bor"),
300 (vm::VM_BXOR, "bxor"),
301 (vm::VM_BAND, "band"),
302 (vm::VM_SAR, "sar"),
303 (vm::VM_SAL, "sal"),
304 (vm::VM_SR, "sr"),
305 (vm::VM_ADD, "add"),
306 (vm::VM_SUB, "sub"),
307 (vm::VM_MOD, "mod"),
308 (vm::VM_DIV, "div"),
309 (vm::VM_IDIV, "idiv"),
310 (vm::VM_MUL, "mul"),
311 ] {
312 if let Some((m, c, s)) = op2_prop(code, i, obj, pools, base, name)? {
313 return Ok((m, c, s));
314 }
315 }
316
317 if op == vm::VM_CALL || op == vm::VM_CALLD || op == vm::VM_CALLI || op == vm::VM_NEW {
319 return format_call(code, i, obj, pools);
320 }
321
322 if op == vm::VM_REGMEMBER || op == vm::VM_DEBUGGER {
324 msg = vm::name(op).to_lowercase();
325 size = 1;
326 return Ok((msg, com, size));
327 }
328
329 msg = format!("unknown instruction {}", op);
331 Ok((msg, com, 1))
332}
333
334fn op2_prop(
335 code: &[i32],
336 i: usize,
337 obj: &Tjs2Object,
338 pools: &ConstPools,
339 base: i32,
340 name: &str,
341) -> Result<Option<(String, String, usize)>> {
342 let op = code[i];
343 if op < base || op > base + 3 {
344 return Ok(None);
345 }
346 match op - base {
347 0 => {
348 ensure(code, i, 3)?;
349 Ok(Some((
350 format!("{} %{}, %{}", name, code[i + 1], code[i + 2]),
351 String::new(),
352 3,
353 )))
354 }
355 1 => {
356 ensure(code, i, 5)?;
357 let a = code[i + 1];
358 let b = code[i + 2];
359 let c = code[i + 3];
360 let d = code[i + 4];
361 let mut com = String::new();
362 if let Some(v) = obj.data.get(c as usize) {
363 com = format!("*{} = {}", c, v.to_readable(pools));
364 }
365 Ok(Some((
366 format!("{}pd %{}, %{}.*{}, %{}", name, a, b, c, d),
367 com,
368 5,
369 )))
370 }
371 2 => {
372 ensure(code, i, 5)?;
373 Ok(Some((
374 format!(
375 "{}pi %{}, %{}.%{}, %{}",
376 name,
377 code[i + 1],
378 code[i + 2],
379 code[i + 3],
380 code[i + 4]
381 ),
382 String::new(),
383 5,
384 )))
385 }
386 3 => {
387 ensure(code, i, 4)?;
388 Ok(Some((
389 format!(
390 "{}p %{}, %{}, %{}",
391 name,
392 code[i + 1],
393 code[i + 2],
394 code[i + 3]
395 ),
396 String::new(),
397 4,
398 )))
399 }
400 _ => Ok(None),
401 }
402}
403
404fn op1_prop(
405 code: &[i32],
406 i: usize,
407 obj: &Tjs2Object,
408 pools: &ConstPools,
409 base: i32,
410 name: &str,
411) -> Result<Option<(String, String, usize)>> {
412 let op = code[i];
413 if op < base || op > base + 3 {
414 return Ok(None);
415 }
416 match op - base {
417 0 => {
418 ensure(code, i, 2)?;
419 Ok(Some((
420 format!("{} %{}", name, code[i + 1]),
421 String::new(),
422 2,
423 )))
424 }
425 1 => {
426 ensure(code, i, 4)?;
427 let a = code[i + 1];
428 let b = code[i + 2];
429 let c = code[i + 3];
430 let mut com = String::new();
431 if let Some(v) = obj.data.get(c as usize) {
432 com = format!("*{} = {}", c, v.to_readable(pools));
433 }
434 Ok(Some((format!("{}pd %{}, %{}.*{}", name, a, b, c), com, 4)))
435 }
436 2 => {
437 ensure(code, i, 4)?;
438 Ok(Some((
439 format!(
440 "{}pi %{}, %{}.%{}",
441 name,
442 code[i + 1],
443 code[i + 2],
444 code[i + 3]
445 ),
446 String::new(),
447 4,
448 )))
449 }
450 3 => {
451 ensure(code, i, 3)?;
452 Ok(Some((
453 format!("{}p %{}, %{}", name, code[i + 1], code[i + 2]),
454 String::new(),
455 3,
456 )))
457 }
458 _ => Ok(None),
459 }
460}
461
462fn format_call(
463 code: &[i32],
464 i: usize,
465 obj: &Tjs2Object,
466 pools: &ConstPools,
467) -> Result<(String, String, usize)> {
468 let op = code[i];
469 let (prefix, st, member_data_index_opt): (&str, usize, Option<i32>) = match op {
475 x if x == vm::VM_CALL => ("call", 4, None),
476 x if x == vm::VM_NEW => ("new", 4, None),
477 x if x == vm::VM_CALLD => ("calld", 5, Some(code[i + 3])),
478 _ => ("calli", 5, None),
479 };
480
481 let mut msg = String::new();
483 match op {
484 x if x == vm::VM_CALL => {
485 ensure(code, i, 4)?;
486 msg = format!("call %{}, %{}(", code[i + 1], code[i + 2]);
487 }
488 x if x == vm::VM_NEW => {
489 ensure(code, i, 4)?;
490 msg = format!("new %{}, %{}(", code[i + 1], code[i + 2]);
491 }
492 x if x == vm::VM_CALLD => {
493 ensure(code, i, 5)?;
494 msg = format!("calld %{}, %{}.*{}(", code[i + 1], code[i + 2], code[i + 3]);
495 }
496 _ => {
497 ensure(code, i, 5)?;
498 msg = format!("calli %{}, %{}.%{}(", code[i + 1], code[i + 2], code[i + 3]);
499 }
500 }
501
502 let argc = code[i + st - 1];
503 let mut size: usize;
504
505 if argc == -1 {
506 size = st;
508 msg.push_str("...");
509 } else if argc == -2 {
510 ensure(code, i, st + 1)?;
512 let num = code[i + st];
513 let num = num as usize;
514 size = st + 1 + num * 2;
515 for j in 0..num {
516 if j != 0 {
517 msg.push_str(", ");
518 }
519 let ty = code[i + st + 1 + j * 2];
520 let v = code[i + st + 1 + j * 2 + 1];
521 match ty {
522 FAT_NORMAL => msg.push_str(&format!("%{}", v)),
523 FAT_EXPAND => msg.push_str(&format!("%{}*", v)),
524 FAT_UNNAMED_EXPAND => msg.push('*'),
525 _ => msg.push_str(&format!("<bad-fat:{}>", ty)),
526 }
527 }
528 } else {
529 let num = argc as usize;
531 size = st + num;
532 for j in 0..num {
533 if j != 0 {
534 msg.push_str(", ");
535 }
536 msg.push_str(&format!("%{}", code[i + st + j]));
537 }
538 }
539
540 msg.push(')');
541
542 let mut com = String::new();
543 if op == vm::VM_CALLD {
544 let c = code[i + 3];
546 if let Some(v) = obj.data.get(c as usize) {
547 com = format!("*{} = {}", c, v.to_readable(pools));
548 }
549 }
550
551 Ok((msg, com, size))
552}
553
554fn ensure(code: &[i32], i: usize, need: usize) -> Result<()> {
555 if i + need > code.len() {
556 bail!(
557 "truncated instruction at {}: need {}, code_len {}",
558 i,
559 need,
560 code.len()
561 );
562 }
563 Ok(())
564}
565
566fn quote(s: &str) -> String {
567 let mut out = String::new();
568 out.push('"');
569 for ch in s.chars() {
570 match ch {
571 '\\' => out.push_str("\\\\"),
572 '"' => out.push_str("\\\""),
573 '\n' => out.push_str("\\n"),
574 '\r' => out.push_str("\\r"),
575 '\t' => out.push_str("\\t"),
576 _ => out.push(ch),
577 }
578 }
579 out.push('"');
580 out
581}