1use anyhow::Result;
2use std::collections::{HashMap, HashSet};
3use std::fmt::Write as _;
4
5use crate::{Tjs2File, Tjs2Object};
6
7use super::cfg::Cfg;
8use super::expr::{BinOp, Expr, UnOp};
9use super::expr_build::{ExprProgram, Stmt, Terminator};
10use super::ssa::{SsaProgram, Var, VarId};
11
12fn vm_binop(op: &str) -> Option<BinOp> {
13 match op {
14 "VM_ADD" | "ADD" => Some(BinOp::Add),
15 "VM_SUB" | "SUB" => Some(BinOp::Sub),
16 "VM_MUL" | "MUL" => Some(BinOp::Mul),
17 "VM_DIV" | "DIV" => Some(BinOp::Div),
18 "VM_MOD" | "MOD" => Some(BinOp::Mod),
19
20 "VM_SAL" | "SHL" => Some(BinOp::Shl),
21 "VM_SAR" | "SHR" => Some(BinOp::Shr),
22 "VM_SR" | "USHR" => Some(BinOp::UShr),
23
24 "VM_BAND" | "BAND" => Some(BinOp::BitAnd),
25 "VM_BXOR" | "BXOR" => Some(BinOp::BitXor),
26 "VM_BOR" | "BOR" => Some(BinOp::BitOr),
27
28 "VM_LAND" | "LAND" => Some(BinOp::LogAnd),
29 "VM_LOR" | "LOR" => Some(BinOp::LogOr),
30
31 "VM_EQ" | "EQ" => Some(BinOp::Eq),
32 "VM_NE" | "NE" => Some(BinOp::Ne),
33 "VM_DEQ" | "DEQ" => Some(BinOp::StrictEq),
34 "VM_DNE" | "DNE" => Some(BinOp::StrictNe),
35
36 "VM_LT" | "LT" => Some(BinOp::Lt),
37 "VM_LE" | "LE" => Some(BinOp::Le),
38 "VM_GT" | "GT" => Some(BinOp::Gt),
39 "VM_GE" | "GE" => Some(BinOp::Ge),
40
41 "VM_IN" | "CHKINS" => Some(BinOp::In),
42
43 _ => None,
44 }
45}
46
47fn vm_unop(op: &str) -> Option<UnOp> {
48 match op {
49 "VM_CHS" | "CHS" => Some(UnOp::Neg),
50 "VM_LNOT" | "LNOT" => Some(UnOp::Not),
51 "VM_BNOT" | "BNOT" => Some(UnOp::BitNot),
52 "VM_TYPEOF" | "TYPEOF" => Some(UnOp::Typeof),
53 "VM_DELETE" | "DELETE" => Some(UnOp::Delete),
54 _ => None,
55 }
56}
57
58fn fmt_octet_literal(bytes: &[u8]) -> String {
59 let mut s = String::new();
61 s.push_str("octet([");
62 for (k, b) in bytes.iter().enumerate() {
63 if k != 0 {
64 s.push_str(", ");
65 }
66 s.push_str(&format!("0x{:02X}", b));
67 }
68 s.push_str("])");
69 s
70}
71
72fn escape_tjs_string_min(s: &str) -> String {
73 let mut out = String::new();
74 for ch in s.chars() {
75 match ch {
76 '\\' => out.push_str("\\\\"),
77 '"' => out.push_str("\\\""),
78 '\n' => out.push_str("\\n"),
79 '\r' => out.push_str("\\r"),
80 '\t' => out.push_str("\\t"),
81 '\0' => out.push_str("\\0"),
82 _ => out.push(ch),
83 }
84 }
85 out
86}
87
88pub struct SrcgenOptions {
90 pub inline: bool,
91}
92
93impl Default for SrcgenOptions {
94 fn default() -> Self {
95 SrcgenOptions { inline: true }
96 }
97}
98
99fn scope_is_class(file: &Tjs2File, obj: &Tjs2Object) -> bool {
102 let mut cur = obj.parent;
103 loop {
104 if cur < 0 || cur as usize >= file.objects.len() {
105 return false;
106 }
107 let p = &file.objects[cur as usize];
108 match p.context_type {
109 0 => return false,
110 6 => return true,
111 _ => cur = p.parent,
112 }
113 }
114}
115
116fn make_fmt_var(in_class: bool, arg_count: usize) -> impl Fn(VarId) -> String {
123 move |vid: VarId| -> String {
124 match vid.var {
125 Var::Reg(r) if r >= 0 => format!("r{}_{}", r, vid.ver),
126 Var::Reg(-1) => "this".to_string(),
127 Var::Reg(-2) => {
128 if in_class {
129 "this".to_string()
130 } else {
131 "global".to_string()
132 }
133 }
134 Var::Reg(r) => {
135 let frame_idx = (-3 - r) as usize;
136 if frame_idx < arg_count {
137 format!("a{}", frame_idx)
138 } else {
139 format!("_fr{}", frame_idx - arg_count)
140 }
141 }
142 Var::Flag => format!("flag_{}", vid.ver),
143 Var::Exception => format!("exc_{}", vid.ver),
144 }
145 }
146}
147
148fn const_propagate_intrablock(prog: &mut ExprProgram) {
203 let initial_void: HashMap<VarId, Expr> = {
206 let mut m = HashMap::new();
207 m.insert(
208 VarId {
209 var: Var::Reg(0),
210 ver: 0,
211 },
212 Expr::Void,
213 );
214 m
215 };
216
217 for b in &mut prog.blocks {
218 let mut env: HashMap<VarId, Expr> = initial_void.clone();
219
220 for st in &mut b.stmts {
221 rewrite_stmt(st, &env);
223
224 match st {
226 Stmt::Assign { dst, expr } => {
227 if let Some(v) = prop_value(expr, &env) {
228 env.insert(*dst, v);
229 }
230 }
231 Stmt::Opaque { defs, op, args } => {
233 let _ = (defs, op, args);
235 }
236 _ => {}
237 }
238 }
239 }
240}
241
242fn rewrite_stmt(st: &mut Stmt, env: &HashMap<VarId, Expr>) {
243 match st {
244 Stmt::Assign { expr, .. } => rewrite_expr(expr, env),
245 Stmt::Store { target, value } => {
246 rewrite_expr(target, env);
247 rewrite_expr(value, env);
248 }
249 Stmt::Update { target, rhs, .. } => {
250 rewrite_expr(target, env);
251 rewrite_expr(rhs, env);
252 }
253 Stmt::Expr(e) => rewrite_expr(e, env),
254 Stmt::Opaque { args, .. } => {
255 for a in args {
256 rewrite_expr(a, env);
257 }
258 }
259 }
260}
261
262fn rewrite_expr(e: &mut Expr, env: &HashMap<VarId, Expr>) {
263 match e {
264 Expr::SsaVar(v) => {
265 if let Some(rep) = env.get(v).cloned() {
266 *e = rep;
267 }
268 }
269 Expr::Unary(_, expr) => rewrite_expr(expr, env),
270 Expr::Binary(_, lhs, rhs) => {
271 rewrite_expr(lhs, env);
272 rewrite_expr(rhs, env);
273 }
274 Expr::Member(base, _) | Expr::Deref(base) => rewrite_expr(base, env),
275 Expr::Index(base, index) => {
276 rewrite_expr(base, env);
277 rewrite_expr(index, env);
278 }
279 Expr::Call(callee, args) => {
280 rewrite_expr(callee, env);
281 for a in args {
282 rewrite_expr(a, env);
283 }
284 }
285 Expr::New(ctor, args) => {
286 rewrite_expr(ctor, env);
287 for a in args {
288 rewrite_expr(a, env);
289 }
290 }
291 Expr::MethodCall { base, args, .. } => {
292 rewrite_expr(base, env);
293 for a in args {
294 rewrite_expr(a, env);
295 }
296 }
297 Expr::Opaque(_, args) => {
298 for a in args {
299 rewrite_expr(a, env);
300 }
301 }
302 _ => {}
303 }
304}
305
306fn prop_value(rhs: &Expr, env: &HashMap<VarId, Expr>) -> Option<Expr> {
309 let mut v = rhs.clone();
310 rewrite_expr(&mut v, env);
311
312 match &v {
313 Expr::Void
315 | Expr::Null
316 | Expr::Bool(_)
317 | Expr::Int(_)
318 | Expr::Real(_)
319 | Expr::Str(_)
320 | Expr::Octet(_) => Some(v),
321
322 Expr::SsaVar(_) => Some(v),
324
325 Expr::Member(base, name) => {
327 let ok = matches!(
328 **base,
329 Expr::SsaVar(VarId {
330 var: Var::Reg(-2),
331 ..
332 }) | Expr::SsaVar(VarId {
333 var: Var::Reg(-1),
334 ..
335 }) ) || is_identifier(name);
337 if ok { Some(v) } else { None }
338 }
339
340 _ => None,
341 }
342}
343
344fn emit_object_body(obj: &Tjs2Object, file: &Tjs2File, indent: usize) -> Result<(String, String)> {
345 let mut out = String::new();
346
347 let cfg = Cfg::build(obj)?;
348 let ssa = SsaProgram::from_cfg(&cfg)?;
349 let mut prog = ExprProgram::from_ssa(file, obj, &ssa)?;
350 const_propagate_intrablock(&mut prog);
351
352 let params = build_params(obj);
353 let arg_count = obj.func_decl_arg_count.max(0) as usize;
354
355 let in_class = scope_is_class(file, obj);
356 let fmt_var = make_fmt_var(in_class, arg_count);
357
358 let mut ret_expr: Vec<Option<Expr>> = vec![None; prog.blocks.len()];
361 for b in &ssa.blocks {
362 let srv = b.insns.iter().rev().find(|i| {
364 i.mnemonic.eq_ignore_ascii_case("SRV") || i.mnemonic.eq_ignore_ascii_case("VM_SRV")
365 });
366 if let Some(si) = srv {
367 if let Some(v) = si.uses.get(0).copied() {
368 ret_expr[b.id] = Some(Expr::SsaVar(v));
369 }
370 }
371 }
372 propagate_into_ret_expr(&mut ret_expr, &prog);
373 remove_dead_phis(&mut prog, &ret_expr);
374 remove_dead_assigns(&mut prog, &ret_expr);
375
376 emit_var_decls(&mut out, &prog, &fmt_var, arg_count, indent)?;
377
378 let mut s = Structurer::new(&cfg, &prog, &fmt_var, ret_expr);
379 let lines = s.emit_function_body(prog.entry_block, indent);
380
381 for l in lines {
382 writeln!(out, "{}", l)?;
383 }
384 Ok((out, params))
385}
386
387fn build_params(obj: &Tjs2Object) -> String {
389 let n = obj.func_decl_arg_count.max(0) as usize;
390 (0..n)
391 .map(|i| format!("a{}", i))
392 .collect::<Vec<_>>()
393 .join(", ")
394}
395
396fn propagate_into_ret_expr(ret_expr: &mut Vec<Option<Expr>>, prog: &ExprProgram) {
398 for b in &prog.blocks {
399 let Some(re) = ret_expr.get_mut(b.id) else {
400 continue;
401 };
402 let Some(e) = re else {
403 continue;
404 };
405 let mut env: HashMap<VarId, Expr> = HashMap::new();
406 for st in &b.stmts {
407 match st {
408 Stmt::Assign { dst, expr } => {
409 let mut v = expr.clone();
410 rewrite_expr(&mut v, &env);
411 if let Some(pv) = prop_value(&v, &env) {
412 env.insert(*dst, pv);
413 }
414 }
415 Stmt::Opaque { op, args, defs }
416 if (op.eq_ignore_ascii_case("VM_SRV") || op.eq_ignore_ascii_case("SRV"))
417 && !args.is_empty()
418 && !defs.is_empty() =>
419 {
420 let mut v = args[0].clone();
421 rewrite_expr(&mut v, &env);
422 if let Some(pv) = prop_value(&v, &env) {
423 env.insert(defs[0], pv);
424 }
425 }
426 _ => {}
427 }
428 }
429 rewrite_expr(e, &env);
430 }
431}
432
433fn remove_dead_phis(prog: &mut ExprProgram, ret_expr: &[Option<Expr>]) {
438 let mut live: HashSet<VarId> = HashSet::new();
440 for b in &prog.blocks {
441 for st in &b.stmts {
442 collect_uses_stmt(st, &mut live);
443 }
444 collect_vars_term(&b.term, &mut live);
445 }
446 for re in ret_expr {
447 if let Some(e) = re {
448 collect_vars_expr(e, &mut live);
449 }
450 }
451
452 let mut changed = true;
454 while changed {
455 changed = false;
456 for b in &prog.blocks {
457 for phi in &b.phi {
458 if live.contains(&phi.result) {
459 for (_, v) in &phi.args {
460 if live.insert(*v) {
461 changed = true;
462 }
463 }
464 }
465 }
466 }
467 }
468
469 for b in &mut prog.blocks {
471 b.phi.retain(|phi| live.contains(&phi.result));
472 }
473}
474
475fn remove_dead_assigns(prog: &mut ExprProgram, ret_expr: &[Option<Expr>]) {
477 let mut used: HashSet<VarId> = HashSet::new();
478 for b in &prog.blocks {
479 for phi in &b.phi {
480 for (_, v) in &phi.args {
481 used.insert(*v);
482 }
483 }
484 for st in &b.stmts {
485 collect_uses_stmt(st, &mut used); }
487 collect_vars_term(&b.term, &mut used);
488 }
489 for re in ret_expr {
490 if let Some(e) = re {
491 collect_vars_expr(e, &mut used);
492 }
493 }
494
495 let mut dead_dsts: HashSet<VarId> = HashSet::new();
496 for b in &prog.blocks {
497 for st in &b.stmts {
498 if let Stmt::Assign { dst, .. } = st {
499 if !used.contains(dst) {
500 dead_dsts.insert(*dst);
501 }
502 }
503 }
504 }
505 for b in &mut prog.blocks {
506 b.stmts
507 .retain(|st| !matches!(st, Stmt::Assign { dst, .. } if dead_dsts.contains(dst)));
508 }
509}
510
511fn collect_uses_stmt(st: &Stmt, s: &mut HashSet<VarId>) {
513 match st {
514 Stmt::Assign { expr, .. } => collect_vars_expr(expr, s), Stmt::Store { target, value } => {
516 collect_vars_expr(target, s);
517 collect_vars_expr(value, s);
518 }
519 Stmt::Update {
520 dst, target, rhs, ..
521 } => {
522 if let Some(d) = dst {
523 s.insert(*d); }
525 collect_vars_expr(target, s);
526 collect_vars_expr(rhs, s);
527 }
528 Stmt::Expr(e) => collect_vars_expr(e, s),
529 Stmt::Opaque { args, defs, .. } => {
530 for d in defs {
531 s.insert(*d);
532 }
533 for a in args {
534 collect_vars_expr(a, s);
535 }
536 }
537 }
538}
539
540fn infer_single_return_expr(file: &Tjs2File, getter_obj: &Tjs2Object) -> Option<String> {
541 let cfg = Cfg::build(getter_obj).ok()?;
542 let ssa = SsaProgram::from_cfg(&cfg).ok()?;
543 let mut prog = ExprProgram::from_ssa(file, getter_obj, &ssa).ok()?;
544 const_propagate_intrablock(&mut prog);
545
546 let in_class = scope_is_class(file, getter_obj);
547 let arg_count = getter_obj.func_decl_arg_count.max(0) as usize;
548 let fmt_var = make_fmt_var(in_class, arg_count);
549
550 let mut ret_expr: Vec<Option<Expr>> = vec![None; prog.blocks.len()];
552 for b in &ssa.blocks {
553 let srv = b.insns.iter().rev().find(|i| {
554 i.mnemonic.eq_ignore_ascii_case("SRV") || i.mnemonic.eq_ignore_ascii_case("VM_SRV")
555 });
556 if let Some(si) = srv {
557 if let Some(v) = si.uses.get(0).copied() {
558 ret_expr[b.id] = Some(Expr::SsaVar(v));
559 }
560 }
561 }
562 propagate_into_ret_expr(&mut ret_expr, &prog);
563
564 let mut unique_ret: Option<String> = None;
565 for re in &ret_expr {
566 if let Some(e) = re {
567 let s = e.to_tjs_with(&fmt_var);
568 if let Some(prev) = &unique_ret {
569 if *prev != s {
570 return None;
571 }
572 } else {
573 unique_ret = Some(s);
574 }
575 }
576 }
577 unique_ret
578}
579
580pub fn dump_src_file(file: &Tjs2File) -> Result<String> {
581 let mut out = String::new();
582 writeln!(out, "// Decompiled by tjs2Decompiler (high)")?;
583 writeln!(
584 out,
585 "// objects={}, toplevel={}",
586 file.objects.len(),
587 file.toplevel
588 )?;
589 writeln!(out)?;
590
591 let toplevel = file.toplevel.max(0) as usize;
592
593 let mut children_of: HashMap<usize, Vec<usize>> = HashMap::new();
595 for obj in &file.objects {
596 if obj.parent >= 0 {
597 children_of
598 .entry(obj.parent as usize)
599 .or_default()
600 .push(obj.index);
601 }
602 }
603
604 let mut emitted: HashSet<usize> = HashSet::new();
606 emitted.insert(toplevel);
607
608 let classes: Vec<usize> = file
610 .objects
611 .iter()
612 .filter(|o| o.context_type == 6 && o.parent == toplevel as i32)
613 .map(|o| o.index)
614 .collect();
615
616 for cls_idx in classes {
617 let cls_obj = &file.objects[cls_idx];
618 let cls_name = match cls_obj.name.as_deref() {
619 Some(n) if is_identifier(n) => n.to_string(),
620 _ => format!("__class_{}", cls_idx),
621 };
622
623 emitted.insert(cls_idx);
624
625 let extends = if cls_obj.super_class_getter >= 0 {
627 let sg = cls_obj.super_class_getter as usize;
628 if sg < file.objects.len() {
629 emitted.insert(sg);
630 infer_single_return_expr(file, &file.objects[sg])
631 } else {
632 None
633 }
634 } else {
635 children_of
637 .get(&cls_idx)
638 .and_then(|ch| ch.iter().find(|&&ci| file.objects[ci].context_type == 7))
639 .and_then(|&ci| {
640 emitted.insert(ci);
641 infer_single_return_expr(file, &file.objects[ci])
642 })
643 };
644
645 match &extends {
646 Some(e) => writeln!(out, "class {} extends {} {{", cls_name, e)?,
647 None => writeln!(out, "class {} {{", cls_name)?,
648 }
649
650 let empty_children: Vec<usize> = Vec::new();
651 let children = children_of.get(&cls_idx).unwrap_or(&empty_children).clone();
652
653 let mut first_member = true;
654 for ci in &children {
655 if emitted.contains(ci) {
656 continue;
657 }
658 let mobj = &file.objects[*ci];
659 match mobj.context_type {
660 7 => {
661 emitted.insert(*ci);
662 } 2 => {
664 emitted.insert(*ci);
665 } 3 => {
667 emitted.insert(*ci);
669 let prop_name = match mobj.name.as_deref() {
670 Some(n) if n != "(anonymous)" && is_identifier(n) => n.to_string(),
671 _ => format!("__prop_{}", ci),
672 };
673 if !first_member {
674 writeln!(out)?;
675 }
676 first_member = false;
677 writeln!(out, " property {} {{", prop_name)?;
678
679 if mobj.prop_getter >= 0 {
680 let gi = mobj.prop_getter as usize;
681 if gi < file.objects.len() && !emitted.contains(&gi) {
682 let (body, _params) = emit_object_body(&file.objects[gi], file, 6)?;
684 writeln!(out, " getter() {{")?;
685 write!(out, "{body}")?;
686 writeln!(out, " }}")?;
687 emitted.insert(gi);
688 }
689 }
690 if mobj.prop_setter >= 0 {
691 let si = mobj.prop_setter as usize;
692 if si < file.objects.len() && !emitted.contains(&si) {
693 let (body, params) = emit_object_body(&file.objects[si], file, 6)?;
695 writeln!(out, " setter({}) {{", params)?;
696 write!(out, "{body}")?;
697 writeln!(out, " }}")?;
698 emitted.insert(si);
699 }
700 }
701
702 if let Some(prop_children) = children_of.get(ci) {
704 for &pci in prop_children {
705 emitted.insert(pci);
706 }
707 }
708
709 writeln!(out, " }}")?;
710 }
711 1 => {
712 let mname = match mobj.name.as_deref() {
714 Some(n) if n != "(anonymous)" && is_identifier(n) => n.to_string(),
715 _ => format!("__method_{}", ci),
716 };
717 emitted.insert(*ci);
718 if let Some(mch) = children_of.get(ci) {
720 for &mci in mch {
721 if file.objects[mci].context_type == 2 {
722 emitted.insert(mci);
723 }
724 }
725 }
726
727 if !first_member {
728 writeln!(out)?;
729 }
730 first_member = false;
731
732 if mobj.code.is_empty() {
733 writeln!(out, " function {}() {{}}", mname)?;
734 } else {
735 let (body, params) = emit_object_body(mobj, file, 4)?;
737 writeln!(out, " function {}({}) {{", mname, params)?;
738 write!(out, "{body}")?;
739 writeln!(out, " }}")?;
740 }
741 }
742 _ => {
743 emitted.insert(*ci);
744 }
745 }
746 }
747
748 writeln!(out, "}}")?;
749 writeln!(out)?;
750 }
751
752 for obj in &file.objects {
754 if emitted.contains(&obj.index) {
755 continue;
756 }
757 if obj.parent != toplevel as i32 {
758 continue;
759 }
760 if obj.context_type != 1 {
761 continue;
762 }
763 if obj.code.is_empty() {
764 continue;
765 }
766
767 let name = match obj.name.as_deref() {
768 Some(n) if n != "(anonymous)" && is_identifier(n) => n.to_string(),
769 _ => format!("__func_{}", obj.index),
770 };
771 emitted.insert(obj.index);
772
773 if let Some(ch) = children_of.get(&obj.index) {
775 for &ci in ch {
776 if file.objects[ci].context_type == 2 {
777 emitted.insert(ci);
778 }
779 }
780 }
781
782 let (body, params) = emit_object_body(obj, file, 2)?;
784 writeln!(out, "function {}({}) {{", name, params)?;
785 write!(out, "{body}")?;
786 writeln!(out, "}}")?;
787 writeln!(out)?;
788 }
789
790 Ok(out)
791}
792
793#[derive(Clone)]
796struct LoopCtx {
797 header: usize,
798 exit: Option<usize>,
799}
800
801#[derive(Clone, Copy)]
802struct RegionOutcome {
803 falls_through: bool,
804}
805
806struct Structurer<'a> {
807 cfg: &'a Cfg,
808 prog: &'a ExprProgram,
809 fmt_var: &'a dyn Fn(VarId) -> String,
810
811 edge_copies: HashMap<(usize, usize), Vec<(VarId, VarId)>>,
813
814 dom: Vec<HashSet<usize>>,
816 pdom: Vec<HashSet<usize>>,
817 ipdom: Vec<Option<usize>>,
818
819 loops: HashMap<usize, HashSet<usize>>,
821
822 emitted: HashSet<usize>,
823
824 ret_expr: Vec<Option<Expr>>,
826 uses_rv: bool,
827}
828
829impl<'a> Structurer<'a> {
830 fn new(
831 cfg: &'a Cfg,
832 prog: &'a ExprProgram,
833 fmt_var: &'a dyn Fn(VarId) -> String,
834 ret_expr: Vec<Option<Expr>>,
835 ) -> Self {
836 let edge_copies = build_edge_copies(prog);
837 let reachable = compute_reachable(prog, prog.entry_block);
838
839 let dom = compute_dominators(prog, prog.entry_block, &reachable);
840 let pdom = compute_postdominators(prog, &reachable);
841 let ipdom = compute_ipdom(&pdom);
842
843 let loops = compute_natural_loops(prog, &dom, &reachable);
844
845 let uses_rv = prog.blocks.iter().any(|b| {
846 b.stmts.iter().any(|st| {
847 matches!(
848 st,
849 Stmt::Opaque { op, .. }
850 if op.eq_ignore_ascii_case("SRV") || op.eq_ignore_ascii_case("VM_SRV")
851 )
852 })
853 });
854
855 Self {
856 cfg,
857 prog,
858 fmt_var,
859 edge_copies,
860 dom,
861 pdom,
862 ipdom,
863 loops,
864 emitted: HashSet::new(),
865 ret_expr,
866 uses_rv,
867 }
868 }
869
870 fn emit_function_body(&mut self, entry: usize, indent: usize) -> Vec<String> {
871 let mut lines = Vec::new();
872 let _ = self.emit_seq(entry, None, indent, None, &mut lines);
873 simplify_empty_if_then(&mut lines);
875 lines
876 }
877
878 fn emit_seq(
879 &mut self,
880 mut cur: usize,
881 stop: Option<usize>,
882 indent: usize,
883 loop_ctx: Option<LoopCtx>,
884 out: &mut Vec<String>,
885 ) -> RegionOutcome {
886 while Some(cur) != stop {
887 let loop_ctx = loop_ctx.clone();
888 if self.emitted.contains(&cur) {
889 return RegionOutcome {
890 falls_through: true,
891 };
892 }
893
894 if self.is_loop_header(cur) && stop != Some(cur) {
895 let oc = self.emit_loop(cur, indent, out);
896 if let Some(n) = self.loop_exit(cur) {
897 cur = n;
898 continue;
899 }
900 return oc;
901 }
902
903 self.emitted.insert(cur);
904
905 self.emit_block_stmts(cur, indent, out);
906
907 let blk = &self.prog.blocks[cur];
908 match blk.term.clone() {
909 Terminator::Ret => {
910 if let Some(e) = self.ret_expr.get(cur).and_then(|x| x.clone()) {
911 let s = self.expr_to_tjs(&e);
912 if s == "void" || s == "r0_0" {
913 out.push(format!("{}return;", " ".repeat(indent)));
914 } else {
915 out.push(format!("{}return {};", " ".repeat(indent), s));
916 }
917 } else {
918 out.push(format!("{}return;", " ".repeat(indent)));
919 }
920 return RegionOutcome {
921 falls_through: false,
922 };
923 }
924 Terminator::Throw(e) => {
925 out.push(format!(
926 "{}throw {};",
927 " ".repeat(indent),
928 self.expr_to_tjs(&e)
929 ));
930 return RegionOutcome {
931 falls_through: false,
932 };
933 }
934 Terminator::Exit | Terminator::Fallthrough => {
935 if let Some(n) = blk.succ.get(0).copied() {
936 self.emit_edge_copies(cur, n, indent, out);
937 cur = n;
938 continue;
939 }
940 out.push(format!("{}return;", " ".repeat(indent)));
941 return RegionOutcome {
942 falls_through: false,
943 };
944 }
945 Terminator::Jmp(t) => {
946 if let Some(ctx) = loop_ctx.clone() {
947 if t == ctx.header {
948 self.emit_edge_copies(cur, t, indent, out);
949 out.push(format!("{}continue;", " ".repeat(indent)));
950 return RegionOutcome {
951 falls_through: false,
952 };
953 }
954 if ctx.exit == Some(t) {
955 self.emit_edge_copies(cur, t, indent, out);
956 out.push(format!("{}break;", " ".repeat(indent)));
957 return RegionOutcome {
958 falls_through: false,
959 };
960 }
961 }
962 if stop == Some(t) {
963 self.emit_edge_copies(cur, t, indent, out);
964 return RegionOutcome {
965 falls_through: true,
966 };
967 }
968 self.emit_edge_copies(cur, t, indent, out);
969 cur = t;
970 continue;
971 }
972 Terminator::Br {
973 cond,
974 if_true,
975 if_false,
976 } => {
977 if let Some(ctx) = loop_ctx.clone() {
979 if if_true == ctx.header
980 || if_false == ctx.header
981 || ctx.exit == Some(if_true)
982 || ctx.exit == Some(if_false)
983 {
984 let oc = self.emit_branch_in_loop(
985 cur, &cond, if_true, if_false, indent, ctx, out,
986 );
987 return oc;
988 }
989 }
990
991 let join = self.ipdom.get(cur).and_then(|x| *x).or(stop);
992
993 let (cond_emitted, first_succ, second_succ) = if self
996 .branch_is_trivially_empty(cur, if_true, join)
997 && !self.branch_is_trivially_empty(cur, if_false, join)
998 {
999 (Expr::Unary(UnOp::Not, Box::new(cond)), if_false, if_true)
1000 } else {
1001 (cond, if_true, if_false)
1002 };
1003
1004 out.push(format!(
1005 "{}if ({}) {{",
1006 " ".repeat(indent),
1007 self.expr_to_tjs(&cond_emitted)
1008 ));
1009
1010 self.emit_edge_copies(cur, first_succ, indent + 2, out);
1012 let then_oc =
1013 self.emit_seq(first_succ, join, indent + 2, loop_ctx.clone(), out);
1014 out.push(format!("{}}}", " ".repeat(indent)));
1015
1016 let second_is_empty = self.branch_is_trivially_empty(cur, second_succ, join);
1018 let else_oc = if second_is_empty {
1019 self.mark_chain_emitted(second_succ, join);
1020 RegionOutcome {
1021 falls_through: true,
1022 }
1023 } else {
1024 out.push(format!("{}else {{", " ".repeat(indent)));
1025 self.emit_edge_copies(cur, second_succ, indent + 2, out);
1026 let oc = self.emit_seq(second_succ, join, indent + 2, loop_ctx, out);
1027 out.push(format!("{}}}", " ".repeat(indent)));
1028 oc
1029 };
1030
1031 if let Some(j) = join {
1032 if then_oc.falls_through || else_oc.falls_through {
1033 cur = j;
1034 continue;
1035 }
1036 return RegionOutcome {
1037 falls_through: false,
1038 };
1039 }
1040 return RegionOutcome {
1041 falls_through: then_oc.falls_through || else_oc.falls_through,
1042 };
1043 }
1044 }
1045 }
1046
1047 RegionOutcome {
1048 falls_through: true,
1049 }
1050 }
1051
1052 fn emit_branch_in_loop(
1053 &mut self,
1054 cur: usize,
1055 cond: &Expr,
1056 t: usize,
1057 f: usize,
1058 indent: usize,
1059 ctx: LoopCtx,
1060 out: &mut Vec<String>,
1061 ) -> RegionOutcome {
1062 out.push(format!(
1066 "{}if ({}) {{",
1067 " ".repeat(indent),
1068 self.expr_to_tjs(cond)
1069 ));
1070
1071 self.emit_edge_copies(cur, t, indent + 2, out);
1072 let then_oc = self.emit_seq(t, None, indent + 2, Some(ctx.clone()), out);
1073 out.push(format!("{}}}", " ".repeat(indent)));
1074
1075 out.push(format!("{}else {{", " ".repeat(indent)));
1076 self.emit_edge_copies(cur, f, indent + 2, out);
1077 let else_oc = self.emit_seq(f, None, indent + 2, Some(ctx), out);
1078 out.push(format!("{}}}", " ".repeat(indent)));
1079
1080 RegionOutcome {
1081 falls_through: then_oc.falls_through || else_oc.falls_through,
1082 }
1083 }
1084
1085 fn is_loop_header(&self, h: usize) -> bool {
1086 self.loops.contains_key(&h)
1087 }
1088
1089 fn loop_exit(&self, h: usize) -> Option<usize> {
1090 let body = self.loops.get(&h)?;
1091 let blk = &self.prog.blocks[h];
1092 for &s in &blk.succ {
1093 if !body.contains(&s) {
1094 return Some(s);
1095 }
1096 }
1097 None
1098 }
1099
1100 fn emit_loop(&mut self, header: usize, indent: usize, out: &mut Vec<String>) -> RegionOutcome {
1101 let body_nodes = match self.loops.get(&header) {
1102 Some(s) => s.clone(),
1103 None => {
1104 return RegionOutcome {
1105 falls_through: true,
1106 };
1107 }
1108 };
1109
1110 let exit = self.loop_exit(header);
1112
1113 out.push(format!("{}while (true) {{", " ".repeat(indent)));
1114
1115 self.emit_block_stmts(header, indent + 2, out);
1117
1118 let blk = &self.prog.blocks[header];
1120 match blk.term.clone() {
1121 Terminator::Br {
1122 cond,
1123 if_true,
1124 if_false,
1125 } => {
1126 let t_in = body_nodes.contains(&if_true);
1128 let f_in = body_nodes.contains(&if_false);
1129
1130 if exit.is_some() && (t_in ^ f_in) {
1131 let (body_succ, exit_succ, break_on_true) = if t_in {
1132 (if_true, if_false, false)
1133 } else {
1134 (if_false, if_true, true)
1135 };
1136
1137 if break_on_true {
1138 out.push(format!(
1140 "{}if ({}) {{",
1141 " ".repeat(indent + 2),
1142 self.expr_to_tjs(&cond)
1143 ));
1144 self.emit_edge_copies(header, exit_succ, indent + 4, out);
1145 out.push(format!("{}break;", " ".repeat(indent + 4)));
1146 out.push(format!("{}}}", " ".repeat(indent + 2)));
1147 } else {
1148 let ncond = Expr::Unary(UnOp::Not, Box::new(cond));
1150 out.push(format!(
1151 "{}if ({}) {{",
1152 " ".repeat(indent + 2),
1153 self.expr_to_tjs(&ncond)
1154 ));
1155 self.emit_edge_copies(header, exit_succ, indent + 4, out);
1156 out.push(format!("{}break;", " ".repeat(indent + 4)));
1157 out.push(format!("{}}}", " ".repeat(indent + 2)));
1158 }
1159
1160 self.emit_edge_copies(header, body_succ, indent + 2, out);
1162 let _ = self.emit_seq(
1163 body_succ,
1164 Some(header),
1165 indent + 2,
1166 Some(LoopCtx { header, exit }),
1167 out,
1168 );
1169 } else {
1170 out.push(format!(
1172 "{}if ({}) {{",
1173 " ".repeat(indent + 2),
1174 self.expr_to_tjs(&cond)
1175 ));
1176 self.emit_edge_copies(header, if_true, indent + 4, out);
1177 let _ = self.emit_seq(
1178 if_true,
1179 Some(header),
1180 indent + 4,
1181 Some(LoopCtx { header, exit }),
1182 out,
1183 );
1184 out.push(format!("{}}}", " ".repeat(indent + 2)));
1185 out.push(format!("{}else {{", " ".repeat(indent + 2)));
1186 self.emit_edge_copies(header, if_false, indent + 4, out);
1187 let _ = self.emit_seq(
1188 if_false,
1189 Some(header),
1190 indent + 4,
1191 Some(LoopCtx { header, exit }),
1192 out,
1193 );
1194 out.push(format!("{}}}", " ".repeat(indent + 2)));
1195 }
1196 }
1197 Terminator::Jmp(t) => {
1198 if t == header {
1199 out.push(format!("{}continue;", " ".repeat(indent + 2)));
1200 } else {
1201 self.emit_edge_copies(header, t, indent + 2, out);
1202 let _ = self.emit_seq(
1203 t,
1204 Some(header),
1205 indent + 2,
1206 Some(LoopCtx { header, exit }),
1207 out,
1208 );
1209 }
1210 }
1211 Terminator::Ret => {
1212 if let Some(e) = self.ret_expr.get(header).and_then(|x| x.clone()) {
1213 let s = self.expr_to_tjs(&e);
1214 if s == "void" || s == "r0_0" {
1215 out.push(format!("{}return;", " ".repeat(indent + 2)));
1216 } else {
1217 out.push(format!("{}return {};", " ".repeat(indent + 2), s));
1218 }
1219 } else {
1220 out.push(format!("{}return;", " ".repeat(indent + 2)));
1221 }
1222 }
1223 Terminator::Throw(e) => {
1224 out.push(format!(
1225 "{}throw {};",
1226 " ".repeat(indent + 2),
1227 self.expr_to_tjs(&e)
1228 ));
1229 }
1230 Terminator::Exit | Terminator::Fallthrough => {
1231 if let Some(n) = blk.succ.get(0).copied() {
1232 self.emit_edge_copies(header, n, indent + 2, out);
1233 let _ = self.emit_seq(
1234 n,
1235 Some(header),
1236 indent + 2,
1237 Some(LoopCtx { header, exit }),
1238 out,
1239 );
1240 } else {
1241 out.push(format!("{}return;", " ".repeat(indent + 2)));
1242 }
1243 }
1244 }
1245
1246 out.push(format!("{}}}", " ".repeat(indent)));
1247
1248 for n in body_nodes {
1250 self.emitted.insert(n);
1251 }
1252 self.emitted.insert(header);
1253
1254 RegionOutcome {
1255 falls_through: exit.is_some(),
1256 }
1257 }
1258
1259 fn branch_is_trivially_empty(&self, pred: usize, succ: usize, stop: Option<usize>) -> bool {
1264 let mut visited = HashSet::new();
1265 self.chain_is_empty(pred, succ, stop, &mut visited, 16)
1266 }
1267
1268 fn chain_is_empty(
1269 &self,
1270 pred: usize,
1271 succ: usize,
1272 stop: Option<usize>,
1273 visited: &mut HashSet<usize>,
1274 depth: usize,
1275 ) -> bool {
1276 if let Some(xs) = self.edge_copies.get(&(pred, succ)) {
1279 for (d, s) in xs {
1280 if (self.fmt_var)(*d) != (self.fmt_var)(*s) {
1281 return false;
1282 }
1283 }
1284 }
1285 if Some(succ) == stop {
1286 return true;
1287 }
1288 if depth == 0 || !visited.insert(succ) {
1289 return false;
1290 }
1291 let blk = &self.prog.blocks[succ];
1292 for st in &blk.stmts {
1294 if !matches!(st, Stmt::Opaque { op, .. } if is_control_op(op)) {
1295 return false;
1296 }
1297 }
1298 match &blk.term {
1300 Terminator::Jmp(t) => self.chain_is_empty(succ, *t, stop, visited, depth - 1),
1301 Terminator::Fallthrough | Terminator::Exit => match blk.succ.get(0).copied() {
1302 Some(t) => self.chain_is_empty(succ, t, stop, visited, depth - 1),
1303 None => stop.is_none(),
1304 },
1305 _ => false,
1306 }
1307 }
1308
1309 fn mark_chain_emitted(&mut self, succ: usize, stop: Option<usize>) {
1312 let mut cur = succ;
1313 loop {
1314 if Some(cur) == stop || !self.emitted.insert(cur) {
1315 break;
1316 }
1317 let blk = &self.prog.blocks[cur];
1318 match &blk.term {
1319 Terminator::Jmp(t) => cur = *t,
1320 Terminator::Fallthrough | Terminator::Exit => match blk.succ.get(0).copied() {
1321 Some(t) => cur = t,
1322 None => break,
1323 },
1324 _ => break,
1325 }
1326 }
1327 }
1328
1329 fn emit_block_stmts(&self, bid: usize, indent: usize, out: &mut Vec<String>) {
1330 let blk = &self.prog.blocks[bid];
1331 for st in &blk.stmts {
1332 if let Stmt::Opaque { op, .. } = st {
1333 if is_control_op(op) {
1334 continue;
1335 }
1336 }
1337 let s = self.stmt_to_tjs(st);
1338 if s.is_empty() || s == "// (control op omitted)" {
1339 continue;
1340 }
1341 out.push(format!("{}{}", " ".repeat(indent), s));
1342 }
1343 }
1344
1345 fn emit_edge_copies(&self, pred: usize, succ: usize, indent: usize, out: &mut Vec<String>) {
1346 if let Some(xs) = self.edge_copies.get(&(pred, succ)) {
1347 for (dst, src) in xs {
1348 let d = (self.fmt_var)(*dst);
1349 let s = if src.var == Var::Reg(0) && src.ver == 0 {
1351 "void".to_string()
1352 } else {
1353 (self.fmt_var)(*src)
1354 };
1355 if d == s {
1356 continue; }
1358 out.push(format!("{}{} = {};", " ".repeat(indent), d, s));
1359 }
1360 }
1361 }
1362
1363 fn stmt_to_tjs(&self, st: &Stmt) -> String {
1364 match st {
1365 Stmt::Assign { dst, expr } => {
1366 format!("{} = {};", (self.fmt_var)(*dst), self.expr_to_tjs(expr))
1367 }
1368 Stmt::Store { target, value } => {
1369 format!(
1370 "{} = {};",
1371 self.expr_to_tjs(target),
1372 self.expr_to_tjs(value)
1373 )
1374 }
1375 Stmt::Update {
1376 dst,
1377 target,
1378 op,
1379 rhs,
1380 } => {
1381 if let Some(comp) = to_compound_assign(*op) {
1382 if let Some(d) = dst {
1383 format!(
1384 "{} = ({} {} {});",
1385 (self.fmt_var)(*d),
1386 self.expr_to_tjs(target),
1387 comp.op_str(),
1388 self.expr_to_tjs(rhs)
1389 )
1390 } else {
1391 format!(
1392 "{} {} {};",
1393 self.expr_to_tjs(target),
1394 comp.op_str(),
1395 self.expr_to_tjs(rhs)
1396 )
1397 }
1398 } else {
1399 if let Some(d) = dst {
1400 format!(
1401 "{} = ({} = ({} {} {}));",
1402 (self.fmt_var)(*d),
1403 self.expr_to_tjs(target),
1404 self.expr_to_tjs(target),
1405 op.op_str(),
1406 self.expr_to_tjs(rhs)
1407 )
1408 } else {
1409 format!(
1410 "{} = ({} {} {});",
1411 self.expr_to_tjs(target),
1412 self.expr_to_tjs(target),
1413 op.op_str(),
1414 self.expr_to_tjs(rhs)
1415 )
1416 }
1417 }
1418 }
1419 Stmt::Expr(e) => format!("{};", self.expr_to_tjs(e)),
1420 Stmt::Opaque { op, args, defs } => {
1421 match op.to_string().as_str() {
1422 "JF" | "JNF" | "JMP" | "RET" | "THROW" | "ENTRY" | "EXTRY" | "VM_JF"
1423 | "VM_JNF" | "VM_JMP" | "VM_RET" | "VM_THROW" | "VM_ENTRY" | "VM_EXTRY" => {
1424 return "// (control op omitted)".to_string();
1425 }
1426 _ => {}
1427 }
1428 let op_name = op.to_string();
1429 if op_name == "VM_CHGTHIS" || op_name == "CHGTHIS" {
1430 return "// (this-change op omitted)".to_string();
1431 }
1432
1433 if (op_name == "VM_TYPEOFD"
1434 || op_name == "TYPEOFD"
1435 || op_name == "VM_TYPEOF"
1436 || op_name == "TYPEOF")
1437 && args.len() == 1
1438 {
1439 let x = args[0].to_tjs_with(self.fmt_var);
1440 let expr = format!("(typeof {})", x);
1441
1442 if defs.is_empty() {
1443 return format!("{};", expr);
1444 } else if defs.len() == 1 {
1445 return format!("{} = {};", (self.fmt_var)(defs[0]), expr);
1446 } else {
1447 let mut s = String::new();
1448 let _ = write!(&mut s, "{{ var __t = {}; ", expr);
1449 for (i, d) in defs.iter().enumerate() {
1450 let _ = write!(&mut s, "{} = __t[{}]; ", (self.fmt_var)(*d), i);
1451 }
1452 let _ = write!(&mut s, "}}");
1453 return s;
1454 }
1455 }
1456
1457 if op_name == "VM_SRV" || op_name == "SRV" {
1458 return String::new();
1460 }
1461
1462 if (op_name == "VM_NUM" || op_name == "NUM") && args.len() == 1 {
1463 let x = args[0].to_tjs_with(self.fmt_var);
1464
1465 let expr = format!("real({})", x);
1466
1467 if defs.len() == 1 {
1468 return format!("{} = {};", (self.fmt_var)(defs[0]), expr);
1469 } else {
1470 return format!("{};", expr);
1471 }
1472 }
1473
1474 if (op_name.starts_with("VM_STR") || op_name == "STR") && args.len() == 1 {
1475 let x = args[0].to_tjs_with(self.fmt_var);
1476 let expr = format!("string({})", x);
1477
1478 if defs.len() == 1 {
1479 return format!("{} = {};", (self.fmt_var)(defs[0]), expr);
1480 } else {
1481 return format!("{};", expr);
1482 }
1483 }
1484
1485 if op_name == "VM_CHGTHIS" || op_name == "CHGTHIS" {
1486 if args.len() == 2 {
1487 return format!(
1488 "chgthis({}, {});",
1489 args[0].to_tjs_with(self.fmt_var),
1490 args[1].to_tjs_with(self.fmt_var),
1491 );
1492 }
1493 return "// chgthis();".to_string();
1494 }
1495
1496 if op_name.starts_with("VM_REGMEMBER") && args.len() == 3 {
1497 return format!(
1498 "{}.{} = {};",
1499 args[0].to_tjs_with(self.fmt_var),
1500 args[1].to_tjs_with(self.fmt_var),
1501 args[2].to_tjs_with(self.fmt_var)
1502 );
1503 }
1504
1505 if op_name.starts_with("VM_INV") && args.len() >= 2 {
1506 let recv = args[0].to_tjs_with(self.fmt_var);
1507 let method = args[1].to_tjs_with(self.fmt_var);
1508 let call_args = args
1509 .iter()
1510 .skip(2)
1511 .map(|x| x.to_tjs_with(self.fmt_var))
1512 .collect::<Vec<_>>()
1513 .join(", ");
1514 let call = format!("{}.{}({})", recv, method, call_args);
1515 if defs.len() == 1 {
1516 return format!("{} = {};", (self.fmt_var)(defs[0]), call);
1517 } else {
1518 return format!("{};", call);
1519 }
1520 }
1521
1522 let call = if args.is_empty() {
1523 format!("{}()", op)
1524 } else {
1525 let a0 = args.get(0).map(|x| x.to_tjs_with(self.fmt_var));
1537 let a1 = args.get(1).map(|x| x.to_tjs_with(self.fmt_var));
1538
1539 let opname = op;
1540
1541 let call = if let (Some(x), Some(y)) = (a0.as_deref(), a1.as_deref()) {
1542 if opname.starts_with("VM_ADD") {
1544 format!("({} + {})", x, y)
1545 } else if opname.starts_with("VM_SUB") {
1546 format!("({} - {})", x, y)
1547 } else if opname.starts_with("VM_MUL") {
1548 format!("({} * {})", x, y)
1549 } else if opname.starts_with("VM_DIV") {
1550 format!("({} / {})", x, y)
1551 } else if opname.starts_with("VM_IDIV") {
1552 format!("({} \\ {})", x, y)
1553 } else if opname.starts_with("VM_MOD") {
1554 format!("({} % {})", x, y)
1555 } else if opname.starts_with("VM_SAL") {
1556 format!("({} << {})", x, y)
1557 } else if opname.starts_with("VM_SAR") {
1558 format!("({} >> {})", x, y)
1559 } else if opname.starts_with("VM_SR") {
1560 format!("({} >>> {})", x, y)
1561 } else if opname.starts_with("VM_BAND") {
1562 format!("({} & {})", x, y)
1563 } else if opname.starts_with("VM_BXOR") {
1564 format!("({} ^ {})", x, y)
1565 } else if opname.starts_with("VM_BOR") {
1566 format!("({} | {})", x, y)
1567 } else if opname.starts_with("VM_LAND") {
1568 format!("({} && {})", x, y)
1569 } else if opname.starts_with("VM_LOR") {
1570 format!("({} || {})", x, y)
1571 } else if opname.starts_with("VM_EQ") {
1572 format!("({} == {})", x, y)
1573 } else if opname.starts_with("VM_NE") {
1574 format!("({} != {})", x, y)
1575 } else if opname.starts_with("VM_DEQ") {
1576 format!("({} === {})", x, y)
1577 } else if opname.starts_with("VM_DNE") {
1578 format!("({} !== {})", x, y)
1579 } else if opname.starts_with("VM_LT") {
1580 format!("({} < {})", x, y)
1581 } else if opname.starts_with("VM_LE") {
1582 format!("({} <= {})", x, y)
1583 } else if opname.starts_with("VM_GT") {
1584 format!("({} > {})", x, y)
1585 } else if opname.starts_with("VM_GE") {
1586 format!("({} >= {})", x, y)
1587 } else if opname.to_string() == "CHKINS" || opname.starts_with("VM_IN") {
1588 format!("({} in {})", x, y)
1589 } else {
1590 let mut s = String::new();
1592 s.push_str(op);
1593 s.push('(');
1594 for (i, a) in args.iter().enumerate() {
1595 if i != 0 {
1596 s.push_str(", ");
1597 }
1598 s.push_str(&a.to_tjs_with(self.fmt_var));
1599 }
1600 s.push(')');
1601 s
1602 }
1603 } else if let Some(x) = a0.as_deref() {
1604 if opname.starts_with("VM_CHS") {
1606 format!("(-{})", x)
1607 } else if opname.starts_with("VM_LNOT") {
1608 format!("(!{})", x)
1609 } else if opname.starts_with("VM_BNOT") {
1610 format!("(~{})", x)
1611 } else if opname.starts_with("VM_TYPEOF") {
1612 format!("(typeof {})", x)
1613 } else if opname.starts_with("VM_DELETE") {
1614 format!("(delete {})", x)
1615 } else if opname.starts_with("VM_INC") {
1616 format!("({} + 1)", x)
1617 } else if opname.starts_with("VM_DEC") {
1618 format!("({} - 1)", x)
1619 } else {
1620 let mut s = String::new();
1622 s.push_str(op);
1623 s.push('(');
1624 for (i, a) in args.iter().enumerate() {
1625 if i != 0 {
1626 s.push_str(", ");
1627 }
1628 s.push_str(&a.to_tjs_with(self.fmt_var));
1629 }
1630 s.push(')');
1631 s
1632 }
1633 } else {
1634 format!("{}()", op)
1635 };
1636
1637 call
1638 };
1639
1640 if defs.is_empty() {
1641 format!("{};", call)
1642 } else if defs.len() == 1 {
1643 format!("{} = {};", (self.fmt_var)(defs[0]), call)
1644 } else {
1645 let mut s = String::new();
1648 s.push_str("{ ");
1649 s.push_str("var __t = ");
1650 s.push_str(&call);
1651 s.push_str("; ");
1652 for (i, d) in defs.iter().enumerate() {
1653 let _ = write!(&mut s, "{} = __t[{}]; ", (self.fmt_var)(*d), i);
1654 }
1655 s.push_str("}");
1656 s
1657 }
1658 }
1659 }
1660 }
1661
1662 fn expr_to_tjs(&self, e: &Expr) -> String {
1663 e.to_tjs_with(self.fmt_var)
1664 }
1665}
1666
1667fn build_edge_copies(prog: &ExprProgram) -> HashMap<(usize, usize), Vec<(VarId, VarId)>> {
1670 let mut m: HashMap<(usize, usize), Vec<(VarId, VarId)>> = HashMap::new();
1671 for b in &prog.blocks {
1672 for phi in &b.phi {
1673 for (pred, v) in &phi.args {
1674 m.entry((*pred, b.id)).or_default().push((phi.result, *v));
1675 }
1676 }
1677 }
1678 m
1679}
1680
1681fn compute_reachable(prog: &ExprProgram, entry: usize) -> HashSet<usize> {
1682 let mut seen = HashSet::new();
1683 let mut stack = vec![entry];
1684 while let Some(n) = stack.pop() {
1685 if !seen.insert(n) {
1686 continue;
1687 }
1688 for &s in &prog.blocks[n].succ {
1689 stack.push(s);
1690 }
1691 }
1692 seen
1693}
1694
1695fn compute_dominators(
1696 prog: &ExprProgram,
1697 entry: usize,
1698 reachable: &HashSet<usize>,
1699) -> Vec<HashSet<usize>> {
1700 let n = prog.blocks.len();
1701 let all: HashSet<usize> = (0..n).filter(|x| reachable.contains(x)).collect();
1702
1703 let mut dom = vec![HashSet::new(); n];
1704 for b in 0..n {
1705 if !reachable.contains(&b) {
1706 continue;
1707 }
1708 if b == entry {
1709 dom[b].insert(entry);
1710 } else {
1711 dom[b] = all.clone();
1712 }
1713 }
1714
1715 let mut changed = true;
1716 while changed {
1717 changed = false;
1718 for b in 0..n {
1719 if !reachable.contains(&b) || b == entry {
1720 continue;
1721 }
1722 let preds = &prog.blocks[b].pred;
1723 if preds.is_empty() {
1724 continue;
1725 }
1726 let mut nd = all.clone();
1727 for &p in preds {
1728 if !reachable.contains(&p) {
1729 continue;
1730 }
1731 nd = nd
1732 .intersection(&dom[p])
1733 .copied()
1734 .collect::<HashSet<usize>>();
1735 }
1736 nd.insert(b);
1737 if nd != dom[b] {
1738 dom[b] = nd;
1739 changed = true;
1740 }
1741 }
1742 }
1743 dom
1744}
1745
1746fn compute_postdominators(prog: &ExprProgram, reachable: &HashSet<usize>) -> Vec<HashSet<usize>> {
1747 let n = prog.blocks.len();
1748 let all: HashSet<usize> = (0..n).filter(|x| reachable.contains(x)).collect();
1749
1750 let exits: HashSet<usize> = (0..n)
1751 .filter(|b| {
1752 if !reachable.contains(b) {
1753 return false;
1754 }
1755 matches!(
1756 prog.blocks[*b].term,
1757 Terminator::Ret | Terminator::Throw(_) ) || prog.blocks[*b].succ.is_empty()
1759 })
1760 .collect();
1761
1762 let mut pdom = vec![HashSet::new(); n];
1763 for b in 0..n {
1764 if !reachable.contains(&b) {
1765 continue;
1766 }
1767 if exits.contains(&b) {
1768 pdom[b].insert(b);
1769 } else {
1770 pdom[b] = all.clone();
1771 }
1772 }
1773
1774 let mut changed = true;
1775 while changed {
1776 changed = false;
1777 for b in 0..n {
1778 if !reachable.contains(&b) || exits.contains(&b) {
1779 continue;
1780 }
1781 let succs = &prog.blocks[b].succ;
1782 if succs.is_empty() {
1783 continue;
1784 }
1785 let mut nd = all.clone();
1786 for &s in succs {
1787 if !reachable.contains(&s) {
1788 continue;
1789 }
1790 nd = nd
1791 .intersection(&pdom[s])
1792 .copied()
1793 .collect::<HashSet<usize>>();
1794 }
1795 nd.insert(b);
1796 if nd != pdom[b] {
1797 pdom[b] = nd;
1798 changed = true;
1799 }
1800 }
1801 }
1802 pdom
1803}
1804
1805fn compute_ipdom(pdom: &[HashSet<usize>]) -> Vec<Option<usize>> {
1806 let n = pdom.len();
1807 let mut ip = vec![None; n];
1808 for b in 0..n {
1809 let mut cand: Vec<usize> = pdom[b].iter().copied().collect();
1810 cand.retain(|x| *x != b);
1811 if cand.is_empty() {
1812 continue;
1813 }
1814 let mut picked = None;
1816 'outer: for &c in &cand {
1817 for &d in &cand {
1818 if d == c {
1819 continue;
1820 }
1821 if pdom[d].contains(&c) {
1822 continue 'outer;
1823 }
1824 }
1825 picked = Some(c);
1826 break;
1827 }
1828 ip[b] = picked;
1829 }
1830 ip
1831}
1832
1833fn compute_natural_loops(
1834 prog: &ExprProgram,
1835 dom: &[HashSet<usize>],
1836 reachable: &HashSet<usize>,
1837) -> HashMap<usize, HashSet<usize>> {
1838 let mut loops: HashMap<usize, HashSet<usize>> = HashMap::new();
1839 for u in 0..prog.blocks.len() {
1840 if !reachable.contains(&u) {
1841 continue;
1842 }
1843 for &v in &prog.blocks[u].succ {
1844 if !reachable.contains(&v) {
1845 continue;
1846 }
1847 if dom[u].contains(&v) {
1849 let mut set = HashSet::new();
1850 set.insert(v);
1851 set.insert(u);
1852 let mut stack = vec![u];
1853 while let Some(x) = stack.pop() {
1854 for &p in &prog.blocks[x].pred {
1855 if !reachable.contains(&p) {
1856 continue;
1857 }
1858 if set.insert(p) {
1859 stack.push(p);
1860 }
1861 }
1862 }
1863 loops
1864 .entry(v)
1865 .and_modify(|s| {
1866 for n in &set {
1867 s.insert(*n);
1868 }
1869 })
1870 .or_insert(set);
1871 }
1872 }
1873 }
1874 loops
1875}
1876
1877fn to_compound_assign(op: BinOp) -> Option<BinOp> {
1878 Some(match op {
1879 BinOp::Add => BinOp::AddAssign,
1880 BinOp::Sub => BinOp::SubAssign,
1881 BinOp::Mul => BinOp::MulAssign,
1882 BinOp::Div => BinOp::DivAssign,
1883 BinOp::Mod => BinOp::ModAssign,
1884 BinOp::Shl => BinOp::ShlAssign,
1885 BinOp::Shr => BinOp::ShrAssign,
1886 BinOp::UShr => BinOp::UShrAssign,
1887 BinOp::BitAnd => BinOp::AndAssign,
1888 BinOp::BitOr => BinOp::OrAssign,
1889 BinOp::BitXor => BinOp::XorAssign,
1890 _ => return None,
1891 })
1892}
1893
1894fn is_control_op(op: &str) -> bool {
1895 let bare = op.strip_prefix("VM_").unwrap_or(op);
1896 bare.eq_ignore_ascii_case("JMP")
1897 || bare.eq_ignore_ascii_case("JF")
1898 || bare.eq_ignore_ascii_case("JNF")
1899 || bare.eq_ignore_ascii_case("RET")
1900 || bare.eq_ignore_ascii_case("THROW")
1901 || bare.eq_ignore_ascii_case("ENTRY")
1902 || bare.eq_ignore_ascii_case("EXTRY")
1903}
1904
1905fn emit_var_decls(
1906 out: &mut String,
1907 prog: &ExprProgram,
1908 fmt_var: &dyn Fn(VarId) -> String,
1909 arg_count: usize,
1910 indent: usize,
1911) -> Result<()> {
1912 let mut vars: Vec<VarId> = collect_vars(prog);
1913 vars.sort_by_key(|v| (var_key(v), v.ver));
1914 vars.retain(|v| match v.var {
1918 Var::Reg(r) if r >= 0 => !(r == 0 && v.ver == 0), Var::Reg(r) if r <= -3 => (-3 - r) as usize >= arg_count, Var::Flag | Var::Exception => true,
1921 _ => false,
1922 });
1923 vars.dedup_by_key(|v| fmt_var(*v));
1924 if vars.is_empty() {
1925 return Ok(());
1926 }
1927
1928 let pad = " ".repeat(indent);
1929 let mut i = 0usize;
1930 while i < vars.len() {
1931 let end = (i + 12).min(vars.len());
1932 write!(out, "{}var ", pad)?;
1933 for j in i..end {
1934 if j != i {
1935 write!(out, ", ")?;
1936 }
1937 write!(out, "{}", fmt_var(vars[j]))?;
1938 }
1939 writeln!(out, ";")?;
1940 i = end;
1941 }
1942 Ok(())
1943}
1944
1945fn collect_vars(prog: &ExprProgram) -> Vec<VarId> {
1946 let mut s: HashSet<VarId> = HashSet::new();
1947
1948 for b in &prog.blocks {
1949 for p in &b.phi {
1950 s.insert(p.result);
1951 for (_pred, v) in &p.args {
1952 s.insert(*v);
1953 }
1954 }
1955 for st in &b.stmts {
1956 collect_vars_stmt(st, &mut s);
1957 }
1958 collect_vars_term(&b.term, &mut s);
1959 }
1960
1961 s.into_iter().collect()
1962}
1963
1964fn collect_vars_stmt(st: &Stmt, s: &mut HashSet<VarId>) {
1965 match st {
1966 Stmt::Assign { dst, expr } => {
1967 s.insert(*dst);
1968 collect_vars_expr(expr, s);
1969 }
1970 Stmt::Store { target, value } => {
1971 collect_vars_expr(target, s);
1972 collect_vars_expr(value, s);
1973 }
1974 Stmt::Update {
1975 dst, target, rhs, ..
1976 } => {
1977 if let Some(d) = dst {
1978 s.insert(*d);
1979 }
1980 collect_vars_expr(target, s);
1981 collect_vars_expr(rhs, s);
1982 }
1983 Stmt::Expr(e) => collect_vars_expr(e, s),
1984 Stmt::Opaque { args, defs, .. } => {
1985 for d in defs {
1986 s.insert(*d);
1987 }
1988 for a in args {
1989 collect_vars_expr(a, s);
1990 }
1991 }
1992 }
1993}
1994
1995fn collect_vars_term(t: &Terminator, s: &mut HashSet<VarId>) {
1996 match t {
1997 Terminator::Br { cond, .. } => collect_vars_expr(cond, s),
1998 Terminator::Throw(e) => collect_vars_expr(e, s),
1999 _ => {}
2000 }
2001}
2002
2003fn collect_vars_expr(e: &Expr, s: &mut HashSet<VarId>) {
2004 match e {
2005 Expr::SsaVar(v) => {
2006 s.insert(*v);
2007 }
2008 Expr::Unary(_, a) => collect_vars_expr(a, s),
2009 Expr::Deref(a) => collect_vars_expr(a, s),
2010 Expr::Binary(_, a, b) => {
2011 collect_vars_expr(a, s);
2012 collect_vars_expr(b, s);
2013 }
2014 Expr::Call(f, args) | Expr::New(f, args) => {
2015 collect_vars_expr(f, s);
2016 for a in args {
2017 collect_vars_expr(a, s);
2018 }
2019 }
2020 Expr::Index(a, b) => {
2021 collect_vars_expr(a, s);
2022 collect_vars_expr(b, s);
2023 }
2024 Expr::Member(a, _) => collect_vars_expr(a, s),
2025 Expr::MethodCall { base, args, .. } => {
2026 collect_vars_expr(base, s);
2027 for a in args {
2028 collect_vars_expr(a, s);
2029 }
2030 }
2031 Expr::Opaque(_, args) => {
2032 for a in args {
2033 collect_vars_expr(a, s);
2034 }
2035 }
2036 _ => {}
2037 }
2038}
2039
2040fn var_key(v: &VarId) -> (u8, i32) {
2041 match v.var {
2042 Var::Reg(r) => (0, r),
2043 Var::Flag => (1, 0),
2044 Var::Exception => (2, 0),
2045 }
2046}
2047
2048fn fmt_vid_tjs(vid: VarId) -> String {
2049 match vid.var {
2050 Var::Reg(r) if r >= 0 => format!("r{}_{}", r, vid.ver),
2051 Var::Reg(-1) => "this".to_string(),
2052 Var::Reg(-2) => "global".to_string(),
2053 Var::Reg(r) => format!("a{}", (-3 - r) as usize),
2054 Var::Flag => format!("flag_{}", vid.ver),
2055 Var::Exception => format!("exc_{}", vid.ver),
2056 }
2057}
2058
2059fn obj_lhs(index: usize, name: Option<&str>) -> String {
2060 if let Some(n) = name {
2061 let parts: Vec<&str> = n.split('.').collect();
2062 if !parts.is_empty() && parts.iter().all(|p| is_identifier(p)) {
2063 return parts.join(".");
2064 }
2065 }
2066 format!("obj{}", index)
2067}
2068
2069fn simplify_empty_if_then(lines: &mut Vec<String>) {
2072 let mut i = 0;
2073 while i + 2 < lines.len() {
2074 let ind0 = leading_spaces(&lines[i]);
2075 let ind1 = leading_spaces(&lines[i + 1]);
2076 let ind2 = leading_spaces(&lines[i + 2]);
2077 let ln0 = lines[i][ind0..].trim_end();
2078 let ln1 = lines[i + 1][ind1..].trim_end();
2079 let ln2 = lines[i + 2][ind2..].trim_end();
2080
2081 if ind0 == ind1
2082 && ind0 == ind2
2083 && ln0.starts_with("if (")
2084 && ln0.ends_with(") {")
2085 && ln1 == "}"
2086 && ln2 == "else {"
2087 {
2088 let cond = &ln0["if (".len()..ln0.len() - ") {".len()];
2089 let ncond = negate_str_cond(cond);
2090 let spaces = " ".repeat(ind0);
2091 lines[i] = format!("{}if ({}) {{", spaces, ncond);
2092 lines.remove(i + 2); lines.remove(i + 1); } else {
2096 i += 1;
2097 }
2098 }
2099}
2100
2101fn leading_spaces(s: &str) -> usize {
2102 s.len() - s.trim_start().len()
2103}
2104
2105fn negate_str_cond(cond: &str) -> String {
2110 if let Some(rest) = cond.strip_prefix('!') {
2111 if rest.starts_with('(') && rest.ends_with(')') {
2112 rest[1..rest.len() - 1].to_string()
2113 } else {
2114 rest.to_string()
2115 }
2116 } else if cond.chars().all(|c| c.is_alphanumeric() || c == '_') {
2117 format!("!{}", cond)
2118 } else {
2119 format!("!({})", cond)
2120 }
2121}
2122
2123fn is_identifier(s: &str) -> bool {
2124 let mut it = s.chars();
2125 let Some(c0) = it.next() else {
2126 return false;
2127 };
2128 if !(c0 == '_' || c0.is_ascii_alphabetic()) {
2129 return false;
2130 }
2131 it.all(|c| c == '_' || c.is_ascii_alphanumeric())
2132}