1use anyhow::Result;
2
3use crate::model::{ConstPools, Tjs2File, Tjs2Object, Variant};
4use crate::vmcodes::vm;
5
6use super::expr::{BinOp, Expr, UnOp};
7use super::ssa::{Phi, SsaBlock, SsaInsn, SsaProgram, Var, VarId};
8
9#[derive(Debug, Clone)]
10pub enum Stmt {
11 Assign {
12 dst: VarId,
13 expr: Expr,
14 },
15 Store {
16 target: Expr,
17 value: Expr,
18 },
19 Update {
21 dst: Option<VarId>,
22 target: Expr,
23 op: BinOp,
24 rhs: Expr,
25 },
26 Expr(Expr),
27 Opaque {
28 op: &'static str,
29 args: Vec<Expr>,
30 defs: Vec<VarId>,
31 },
32}
33
34#[derive(Debug, Clone)]
35pub enum Terminator {
36 Jmp(usize),
37 Br {
38 cond: Expr,
39 if_true: usize,
40 if_false: usize,
41 },
42 Ret,
43 Throw(Expr),
44 Exit,
45 Fallthrough,
46}
47
48#[derive(Debug, Clone)]
49pub struct ExprBlock {
50 pub id: usize,
51 pub start_pc: usize,
52 pub pred: Vec<usize>,
53 pub succ: Vec<usize>,
54 pub phi: Vec<Phi>,
55 pub stmts: Vec<Stmt>,
56 pub term: Terminator,
57}
58
59#[derive(Debug, Clone)]
60pub struct ExprProgram {
61 pub obj_index: usize,
62 pub entry_block: usize,
63 pub blocks: Vec<ExprBlock>,
64}
65
66impl ExprProgram {
67 pub fn from_ssa(file: &Tjs2File, obj: &Tjs2Object, ssa: &SsaProgram) -> Result<Self> {
68 let mut blocks = Vec::new();
69 for b in &ssa.blocks {
70 blocks.push(lower_block(file, obj, b));
71 }
72 Ok(Self {
73 obj_index: ssa.obj_index,
74 entry_block: ssa.entry_block,
75 blocks,
76 })
77 }
78
79 pub fn dump(&self) -> String {
80 let mut out = String::new();
81 for b in &self.blocks {
82 out.push_str(&format!(
83 "-- bb{} @{} (pred={:?}, succ={:?})\n",
84 b.id, b.start_pc, b.pred, b.succ
85 ));
86 for p in &b.phi {
87 out.push_str(&format!(" {} = phi [", fmt_vid(p.result)));
88 for (i, (pred, v)) in p.args.iter().enumerate() {
89 if i != 0 {
90 out.push_str(", ");
91 }
92 out.push_str(&format!("bb{}: {}", pred, fmt_vid(*v)));
93 }
94 out.push_str("]\n");
95 }
96 for st in &b.stmts {
97 match st {
98 Stmt::Assign { dst, expr } => {
99 out.push_str(&format!(" {} = {};\n", fmt_vid(*dst), expr));
100 }
101 Stmt::Store { target, value } => {
102 out.push_str(&format!(" {} = {};\n", target, value));
103 }
104 Stmt::Update {
105 dst,
106 target,
107 op,
108 rhs,
109 } => {
110 if let Some(d) = dst {
111 out.push_str(&format!(
112 " {} = ({} {} {}); {} = {};\n",
113 fmt_vid(*d),
114 target,
115 op.op_str(),
116 rhs,
117 target,
118 fmt_vid(*d)
119 ));
120 } else {
121 out.push_str(&format!(
122 " {} = ({} {} {});\n",
123 target,
124 target,
125 op.op_str(),
126 rhs
127 ));
128 }
129 }
130 Stmt::Expr(e) => out.push_str(&format!(" {};\n", e)),
131 Stmt::Opaque { op, args, defs } => {
132 if !defs.is_empty() {
133 out.push_str(" ");
134 for (i, d) in defs.iter().enumerate() {
135 if i != 0 {
136 out.push_str(", ");
137 }
138 out.push_str(&fmt_vid(*d));
139 }
140 out.push_str(" = ");
141 } else {
142 out.push_str(" ");
143 }
144 out.push_str(op);
145 if !args.is_empty() {
146 out.push('(');
147 for (i, a) in args.iter().enumerate() {
148 if i != 0 {
149 out.push_str(", ");
150 }
151 out.push_str(&a.to_string());
152 }
153 out.push(')');
154 }
155 out.push('\n');
156 }
157 }
158 }
159 out.push_str(&format!(" ;; term = {}\n\n", fmt_term(&b.term)));
160 }
161 out
162 }
163}
164
165fn lower_block(file: &Tjs2File, obj: &Tjs2Object, b: &SsaBlock) -> ExprBlock {
166 let mut stmts = Vec::new();
167
168 let phi = b.phi.clone();
170
171 for insn in &b.insns {
173 lower_insn(file, obj, insn, &mut stmts);
174 }
175
176 let term = derive_terminator(b, b.insns.last());
178
179 ExprBlock {
180 id: b.id,
181 start_pc: b.start_pc,
182 pred: b.pred.clone(),
183 succ: b.succ.clone(),
184 phi,
185 stmts,
186 term,
187 }
188}
189
190fn derive_terminator(b: &SsaBlock, last: Option<&SsaInsn>) -> Terminator {
191 let Some(insn) = last else {
192 return Terminator::Fallthrough;
193 };
194
195 match insn.op {
196 vm::VM_JMP => {
197 let tgt = b.succ.get(0).copied().unwrap_or(b.id);
198 Terminator::Jmp(tgt)
199 }
200 vm::VM_JF | vm::VM_JNF => {
201 let tgt = b.succ.get(0).copied().unwrap_or(b.id);
202 let fall = b.succ.get(1).copied().unwrap_or(b.id);
203 let flag = insn
204 .uses
205 .get(0)
206 .copied()
207 .map(Expr::SsaVar)
208 .unwrap_or(Expr::Flag);
209 let cond = if insn.op == vm::VM_JF {
211 Expr::Unary(UnOp::Not, Box::new(flag))
212 } else {
213 flag
214 };
215 Terminator::Br {
216 cond,
217 if_true: tgt,
218 if_false: fall,
219 }
220 }
221 vm::VM_RET => Terminator::Ret,
222 vm::VM_THROW => {
223 let e = insn
224 .uses
225 .get(0)
226 .copied()
227 .map(Expr::SsaVar)
228 .unwrap_or(Expr::Opaque("throw".into(), vec![]));
229 Terminator::Throw(e)
230 }
231 vm::VM_EXTRY => Terminator::Exit,
232 _ => Terminator::Fallthrough,
233 }
234}
235
236fn lower_insn(file: &Tjs2File, obj: &Tjs2Object, insn: &SsaInsn, out: &mut Vec<Stmt>) {
237 let use_e = |i: usize| insn.uses.get(i).copied().map(Expr::SsaVar);
239 let def0 = || insn.defs.get(0).copied();
240
241 match insn.op {
242 vm::VM_CP => {
243 if let (Some(dst), Some(src)) = (def0(), use_e(0)) {
244 out.push(Stmt::Assign { dst, expr: src });
245 } else {
246 out.push(Stmt::Opaque {
247 op: insn.mnemonic,
248 args: vec![],
249 defs: insn.defs.clone(),
250 });
251 }
252 }
253
254 vm::VM_CONST => {
255 if let Some(dst) = def0() {
257 let data_idx = insn.raw_ops.get(1).copied().unwrap_or(0);
258 out.push(Stmt::Assign {
259 dst,
260 expr: data_to_expr(file, obj, data_idx),
261 });
262 }
263 }
264
265 vm::VM_CL => {
266 if let Some(dst) = def0() {
267 out.push(Stmt::Assign {
268 dst,
269 expr: Expr::Void,
270 });
271 }
272 }
273
274 vm::VM_CCL => {
275 for d in &insn.defs {
277 out.push(Stmt::Assign {
278 dst: *d,
279 expr: Expr::Void,
280 });
281 }
282 }
283
284 vm::VM_GLOBAL => {
285 if let Some(dst) = def0() {
286 out.push(Stmt::Assign {
287 dst,
288 expr: Expr::Opaque("global".into(), vec![]),
289 });
290 }
291 }
292
293 vm::VM_SETF => {
294 if let Some(dst) = def0() {
295 out.push(Stmt::Assign {
296 dst,
297 expr: Expr::Bool(true),
298 });
299 }
300 }
301 vm::VM_SETNF => {
302 if let Some(dst) = def0() {
303 out.push(Stmt::Assign {
304 dst,
305 expr: Expr::Bool(false),
306 });
307 }
308 }
309
310 vm::VM_CEQ | vm::VM_CDEQ | vm::VM_CLT | vm::VM_CGT | vm::VM_CHKINS => {
312 if let Some(dst) = def0() {
313 let a = use_e(0).unwrap_or(Expr::Opaque("a".into(), vec![]));
314 let b = use_e(1).unwrap_or(Expr::Opaque("b".into(), vec![]));
315 let op = match insn.op {
316 vm::VM_CEQ => BinOp::Eq,
317 vm::VM_CDEQ => BinOp::StrictEq,
318 vm::VM_CLT => BinOp::Lt,
319 vm::VM_CGT => BinOp::Gt,
320 vm::VM_CHKINS => BinOp::In,
321 _ => BinOp::Eq,
322 };
323 out.push(Stmt::Assign {
324 dst,
325 expr: Expr::Binary(op, Box::new(a), Box::new(b)),
326 });
327 }
328 }
329 vm::VM_TT => {
330 if let Some(dst) = def0() {
331 let a = use_e(0).unwrap_or(Expr::Opaque("a".into(), vec![]));
332 let e = Expr::Unary(UnOp::Not, Box::new(Expr::Unary(UnOp::Not, Box::new(a))));
334 out.push(Stmt::Assign { dst, expr: e });
335 }
336 }
337 vm::VM_TF => {
338 if let Some(dst) = def0() {
339 let a = use_e(0).unwrap_or(Expr::Opaque("a".into(), vec![]));
340 out.push(Stmt::Assign {
341 dst,
342 expr: Expr::Unary(UnOp::Not, Box::new(a)),
343 });
344 }
345 }
346 vm::VM_NF => {
347 if let Some(dst) = def0() {
348 let a = use_e(0).unwrap_or(Expr::Opaque("flag".into(), vec![]));
349 out.push(Stmt::Assign {
350 dst,
351 expr: Expr::Unary(UnOp::Not, Box::new(a)),
352 });
353 }
354 }
355
356 vm::VM_GPD | vm::VM_GPDS => {
358 if let Some(dst) = def0() {
359 let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
360 let data_idx = insn.raw_ops.get(2).copied().unwrap_or(0);
361 let key_e = data_to_expr(file, obj, data_idx);
362 let expr = prop_read(obj_e, key_e);
363 out.push(Stmt::Assign { dst, expr });
364 }
365 }
366 vm::VM_GPI | vm::VM_GPIS => {
367 if let Some(dst) = def0() {
368 let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
369 let key_e = use_e(1).unwrap_or(Expr::Opaque("key".into(), vec![]));
370 let expr = Expr::Index(Box::new(obj_e), Box::new(key_e));
371 out.push(Stmt::Assign { dst, expr });
372 }
373 }
374 vm::VM_GETP => {
375 if let Some(dst) = def0() {
376 let prop = use_e(0).unwrap_or(Expr::Opaque("prop".into(), vec![]));
377 out.push(Stmt::Assign {
378 dst,
379 expr: Expr::Deref(Box::new(prop)),
380 });
381 }
382 }
383
384 vm::VM_SPD | vm::VM_SPDE | vm::VM_SPDEH | vm::VM_SPDS => {
386 let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
387 let val_e = use_e(1).unwrap_or(Expr::Opaque("val".into(), vec![]));
388 let data_idx = insn.raw_ops.get(1).copied().unwrap_or(0);
389 let key_e = data_to_expr(file, obj, data_idx);
390 let target = prop_lvalue(obj_e, key_e);
391 out.push(Stmt::Store {
392 target,
393 value: val_e,
394 });
395 }
396 vm::VM_SPI | vm::VM_SPIE | vm::VM_SPIS => {
397 let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
398 let key_e = use_e(1).unwrap_or(Expr::Opaque("key".into(), vec![]));
399 let val_e = use_e(2).unwrap_or(Expr::Opaque("val".into(), vec![]));
400 let target = Expr::Index(Box::new(obj_e), Box::new(key_e));
401 out.push(Stmt::Store {
402 target,
403 value: val_e,
404 });
405 }
406 vm::VM_SETP => {
407 let prop = use_e(0).unwrap_or(Expr::Opaque("prop".into(), vec![]));
408 let val = use_e(1).unwrap_or(Expr::Opaque("val".into(), vec![]));
409 out.push(Stmt::Store {
410 target: Expr::Deref(Box::new(prop)),
411 value: val,
412 });
413 }
414
415 vm::VM_CALL => {
417 let dst = def0();
418 let func = use_e(0).unwrap_or(Expr::Opaque("func".into(), vec![]));
419 let args = insn
420 .uses
421 .iter()
422 .skip(1)
423 .map(|v| Expr::SsaVar(*v))
424 .collect::<Vec<_>>();
425 let call = Expr::Call(Box::new(func), args);
426 if let Some(d) = dst {
427 out.push(Stmt::Assign { dst: d, expr: call });
428 } else {
429 out.push(Stmt::Expr(call));
430 }
431 }
432 vm::VM_NEW => {
433 let dst = def0();
434 let ctor = use_e(0).unwrap_or(Expr::Opaque("ctor".into(), vec![]));
435 let args = insn
436 .uses
437 .iter()
438 .skip(1)
439 .map(|v| Expr::SsaVar(*v))
440 .collect::<Vec<_>>();
441 let e = Expr::New(Box::new(ctor), args);
442 if let Some(d) = dst {
443 out.push(Stmt::Assign { dst: d, expr: e });
444 } else {
445 out.push(Stmt::Expr(e));
446 }
447 }
448 vm::VM_CALLD => {
449 let dst = def0();
450 let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
451 let data_idx = insn.raw_ops.get(2).copied().unwrap_or(0);
452 let key_e = data_to_expr(file, obj, data_idx);
453 let args = insn
454 .uses
455 .iter()
456 .skip(1)
457 .map(|v| Expr::SsaVar(*v))
458 .collect::<Vec<_>>();
459 let call = method_call_or_index_call(obj_e, key_e, args);
460 if let Some(d) = dst {
461 out.push(Stmt::Assign { dst: d, expr: call });
462 } else {
463 out.push(Stmt::Expr(call));
464 }
465 }
466 vm::VM_CALLI => {
467 let dst = def0();
468 let obj_e = use_e(0).unwrap_or(Expr::Opaque("obj".into(), vec![]));
469 let key_e = use_e(1).unwrap_or(Expr::Opaque("key".into(), vec![]));
470 let args = insn
471 .uses
472 .iter()
473 .skip(2)
474 .map(|v| Expr::SsaVar(*v))
475 .collect::<Vec<_>>();
476 let func = Expr::Index(Box::new(obj_e), Box::new(key_e));
477 let call = Expr::Call(Box::new(func), args);
478 if let Some(d) = dst {
479 out.push(Stmt::Assign { dst: d, expr: call });
480 } else {
481 out.push(Stmt::Expr(call));
482 }
483 }
484
485 _ => {
487 let args = insn
488 .uses
489 .iter()
490 .map(|v| Expr::SsaVar(*v))
491 .collect::<Vec<_>>();
492 out.push(Stmt::Opaque {
493 op: insn.mnemonic,
494 args,
495 defs: insn.defs.clone(),
496 });
497 }
498 }
499}
500
501fn data_to_expr(file: &Tjs2File, obj: &Tjs2Object, data_idx: i32) -> Expr {
502 let v = obj.data.get(data_idx as usize).unwrap_or(&Variant::Unknown);
503 variant_to_expr(v, &file.const_pools)
504}
505
506fn variant_to_expr(v: &Variant, pools: &ConstPools) -> Expr {
507 match *v {
508 Variant::Void => Expr::Void,
509 Variant::NullObject => Expr::Null,
510 Variant::String(i) => Expr::Str(pools.strings.get(i as usize).cloned().unwrap_or_default()),
511 Variant::Octet(i) => Expr::Octet(pools.octets.get(i as usize).cloned().unwrap_or_default()),
512 Variant::Real(i) => Expr::Real(*pools.doubles.get(i as usize).unwrap_or(&f64::NAN)),
513 Variant::Byte(i) => Expr::Int(*pools.bytes.get(i as usize).unwrap_or(&0) as i64),
514 Variant::Short(i) => Expr::Int(*pools.shorts.get(i as usize).unwrap_or(&0) as i64),
515 Variant::Integer(i) => Expr::Int(*pools.ints.get(i as usize).unwrap_or(&0) as i64),
516 Variant::Long(i) => Expr::Int(*pools.longs.get(i as usize).unwrap_or(&0)),
517 Variant::InterObject(idx) => Expr::Opaque(format!("#InterObject({})", idx), vec![]),
518 Variant::InterGenerator(idx) => Expr::Opaque(format!("#InterGenerator({})", idx), vec![]),
519 Variant::Unknown => Expr::Opaque("#Unknown".into(), vec![]),
520 }
521}
522
523fn prop_read(obj: Expr, key: Expr) -> Expr {
524 if let Expr::Str(s) = &key {
526 if is_ident_ascii(s) {
527 return Expr::Member(Box::new(obj), s.clone());
528 }
529 }
530 Expr::Index(Box::new(obj), Box::new(key))
531}
532
533fn prop_lvalue(obj: Expr, key: Expr) -> Expr {
534 prop_read(obj, key)
536}
537
538fn method_call_or_index_call(base: Expr, key: Expr, args: Vec<Expr>) -> Expr {
539 if let Expr::Str(s) = &key {
540 if is_ident_ascii(s) {
541 return Expr::MethodCall {
542 base: Box::new(base),
543 member: s.clone(),
544 args,
545 };
546 }
547 }
548 let f = Expr::Index(Box::new(base), Box::new(key));
549 Expr::Call(Box::new(f), args)
550}
551
552pub fn is_ident_ascii(s: &str) -> bool {
553 let mut it = s.chars();
554 let Some(first) = it.next() else {
555 return false;
556 };
557 let ok_first = first == '_' || first.is_ascii_alphabetic();
558 if !ok_first {
559 return false;
560 }
561 it.all(|c| c == '_' || c.is_ascii_alphanumeric())
562}
563
564fn fmt_vid(v: VarId) -> String {
565 match v.var {
566 Var::Reg(r) => format!("r{}#{}", r, v.ver),
567 Var::Flag => format!("flag#{}", v.ver),
568 Var::Exception => format!("exc#{}", v.ver),
569 }
570}
571
572fn fmt_term(t: &Terminator) -> String {
573 match t {
574 Terminator::Jmp(b) => format!("jmp bb{}", b),
575 Terminator::Br {
576 cond,
577 if_true,
578 if_false,
579 } => format!("br ({}) ? bb{} : bb{}", cond, if_true, if_false),
580 Terminator::Ret => "ret".into(),
581 Terminator::Throw(e) => format!("throw {}", e),
582 Terminator::Exit => "exit".into(),
583 Terminator::Fallthrough => "fallthrough".into(),
584 }
585}