1use anyhow::Result;
2use std::collections::{HashMap, HashSet};
3use std::fmt::Write as _;
4
5use crate::model::{Tjs2File, Tjs2Object};
6
7use super::cfg::Cfg;
8use super::expr::Expr;
9use super::expr_build::{ExprBlock, ExprProgram, Stmt, Terminator};
10use super::ssa::{Var, VarId};
11
12pub struct SrcgenOptions {
13 pub inline: bool,
14}
15
16impl Default for SrcgenOptions {
17 fn default() -> Self {
18 SrcgenOptions { inline: true }
19 }
20}
21
22pub fn dump_src_file(file: &Tjs2File, _opt: SrcgenOptions) -> Result<String> {
23 let mut out = String::new();
24
25 writeln!(
26 out,
27 "// Decompiled from TJS2 bytecode (decompile_low)\n// objects: {}\n",
28 file.objects.len()
29 )?;
30 writeln!(out, "var __tjs2dec_objs = [];\n")?;
31
32 for obj in &file.objects {
33 if obj.code.is_empty() {
34 continue;
35 }
36
37 writeln!(
38 out,
39 "// == object {}: {} ==",
40 obj.index,
41 obj.name.as_deref().unwrap_or("<anonymous>")
42 )?;
43
44 let lhs = obj_lhs(obj.index, obj.name.as_deref());
45 writeln!(out, "{} = function() {{", lhs)?;
46
47 let cfg = Cfg::build(obj)?;
48 let ssa = super::ssa::SsaProgram::from_cfg(&cfg)?;
49 let prog = ExprProgram::from_ssa(file, obj, &ssa)?;
50
51 emit_function(&mut out, &prog, &cfg, obj)?;
52
53 writeln!(out, "}};\n")?;
54 }
55
56 Ok(out)
57}
58
59fn obj_lhs(idx: usize, name: Option<&str>) -> String {
60 if let Some(n) = name {
61 let n = n.trim();
62 if !n.is_empty() {
63 return n.to_string();
64 }
65 }
66 format!("__tjs2dec_objs[{}]", idx)
67}
68
69fn fmt_vid(vid: VarId) -> String {
70 match vid.var {
71 Var::Reg(r) => format!("__r{}_{}", r, vid.ver),
72 Var::Flag => format!("__flag_{}", vid.ver),
73 Var::Exception => format!("__exc_{}", vid.ver),
74 }
75}
76
77fn emit_function(out: &mut String, prog: &ExprProgram, cfg: &Cfg, obj: &Tjs2Object) -> Result<()> {
78 writeln!(out, " function __tjs2dec_opaque(op) {{ return void; }}")?;
80
81 let fmt_var = |v: VarId| fmt_vid(v);
82
83 let vars = collect_all_vars(prog);
85 if !vars.is_empty() {
86 for name in vars {
87 writeln!(out, " var {} = void;", name)?;
88 }
89 }
90
91 writeln!(out, " var __rv = void;")?;
92 writeln!(out, " var __bb = {};", prog.entry_block)?;
93 writeln!(out, " var __exobj = void;")?;
94 writeln!(out, " while(true) {{")?;
95 writeln!(out, " switch(__bb) {{")?;
96
97 let phi_moves = build_phi_edge_moves(prog);
99
100 for b in &prog.blocks {
101 writeln!(out, " case {}: {{", b.id)?;
102
103 let exc_succ = exceptional_succ(b);
107
108 if let Some(ex) = exc_succ {
109 writeln!(out, " try {{")?;
110 emit_block_body(out, b, &fmt_var, &phi_moves, obj, ex)?;
111 writeln!(out, " }} catch(__e) {{")?;
112 writeln!(out, " __exobj = __e;")?;
113 emit_phi_parallel_copies(out, &fmt_var, &phi_moves, b.id, ex, " ")?;
114 writeln!(out, " __bb = {};", ex)?;
115 writeln!(out, " continue;")?;
116 writeln!(out, " }}")?;
117 } else {
118 emit_block_body(out, b, &fmt_var, &phi_moves, obj, usize::MAX)?;
119 }
120
121 writeln!(out, " }}")?;
122 }
123
124 writeln!(out, " default: return __rv;")?;
126 writeln!(out, " }}")?;
127 writeln!(out, " }}")?;
128
129 Ok(())
130}
131
132fn emit_block_body(
133 out: &mut String,
134 b: &ExprBlock,
135 fmt_var: &dyn Fn(VarId) -> String,
136 phi_moves: &HashMap<(usize, usize), Vec<(VarId, VarId)>>,
137 _obj: &Tjs2Object,
138 _exc_succ: usize,
139) -> Result<()> {
140 for st in &b.stmts {
141 emit_stmt(out, st, fmt_var)?;
142 }
143
144 emit_terminator(out, b, fmt_var, phi_moves)?;
145
146 Ok(())
147}
148
149fn emit_stmt(out: &mut String, st: &Stmt, fmt_var: &dyn Fn(VarId) -> String) -> Result<()> {
150 match st {
151 Stmt::Assign { dst, expr } => {
152 writeln!(
153 out,
154 " {} = {};",
155 fmt_var(*dst),
156 expr.to_tjs_with(fmt_var)
157 )?;
158 }
159 Stmt::Store { target, value } => {
160 writeln!(
161 out,
162 " {} = {};",
163 target.to_tjs_with(fmt_var),
164 value.to_tjs_with(fmt_var)
165 )?;
166 }
167 Stmt::Update {
168 dst,
169 target,
170 op,
171 rhs,
172 } => {
173 let tmp = "__tjs2dec_u";
176 writeln!(
177 out,
178 " var {} = ({} {} {});",
179 tmp,
180 target.to_tjs_with(fmt_var),
181 op.op_str(),
182 rhs.to_tjs_with(fmt_var)
183 )?;
184 writeln!(out, " {} = {};", target.to_tjs_with(fmt_var), tmp)?;
185 if let Some(d) = dst {
186 writeln!(out, " {} = {};", fmt_var(*d), tmp)?;
187 }
188 }
189 Stmt::Expr(e) => {
190 writeln!(out, " {};", e.to_tjs_with(fmt_var))?;
191 }
192 Stmt::Opaque { op, args, defs } => {
193 let mut call = String::new();
194 write!(&mut call, "__tjs2dec_opaque(\"{}\"", op)?;
195 for a in args {
196 write!(&mut call, ", {}", a.to_tjs_with(fmt_var))?;
197 }
198 call.push(')');
199
200 if defs.is_empty() {
201 writeln!(out, " {};", call)?;
202 } else if defs.len() == 1 {
203 writeln!(out, " {} = {};", fmt_var(defs[0]), call)?;
204 } else {
205 writeln!(out, " var __tjs2dec_o = {};", call)?;
207 for d in defs {
208 writeln!(out, " {} = __tjs2dec_o;", fmt_var(*d))?;
209 }
210 }
211 }
212 }
213 Ok(())
214}
215
216fn emit_terminator(
217 out: &mut String,
218 b: &ExprBlock,
219 fmt_var: &dyn Fn(VarId) -> String,
220 phi_moves: &HashMap<(usize, usize), Vec<(VarId, VarId)>>,
221) -> Result<()> {
222 match &b.term {
223 Terminator::Jmp(t) => {
224 emit_phi_parallel_copies(out, fmt_var, phi_moves, b.id, *t, " ")?;
225 writeln!(out, " __bb = {};", t)?;
226 writeln!(out, " continue;")?;
227 }
228 Terminator::Br {
229 cond,
230 if_true,
231 if_false,
232 } => {
233 writeln!(out, " if ({}) {{", cond.to_tjs_with(fmt_var))?;
234 emit_phi_parallel_copies(out, fmt_var, phi_moves, b.id, *if_true, " ")?;
235 writeln!(out, " __bb = {};", if_true)?;
236 writeln!(out, " continue;")?;
237 writeln!(out, " }} else {{")?;
238 emit_phi_parallel_copies(out, fmt_var, phi_moves, b.id, *if_false, " ")?;
239 writeln!(out, " __bb = {};", if_false)?;
240 writeln!(out, " continue;")?;
241 writeln!(out, " }}")?;
242 }
243 Terminator::Ret => {
244 writeln!(out, " return __rv;")?;
245 }
246 Terminator::Throw(e) => {
247 writeln!(out, " throw {};", e.to_tjs_with(fmt_var))?;
248 }
249 Terminator::Exit => {
250 writeln!(out, " return __rv;")?;
251 }
252 Terminator::Fallthrough => {
253 if let Some(t) = b.succ.first().copied() {
254 emit_phi_parallel_copies(out, fmt_var, phi_moves, b.id, t, " ")?;
255 writeln!(out, " __bb = {};", t)?;
256 writeln!(out, " continue;")?;
257 } else {
258 writeln!(out, " return __rv;")?;
259 }
260 }
261 }
262 Ok(())
263}
264
265fn exceptional_succ(b: &ExprBlock) -> Option<usize> {
266 let normal = match b.term {
267 Terminator::Jmp(_) => 1,
268 Terminator::Br { .. } => 2,
269 Terminator::Fallthrough => {
270 if b.succ.is_empty() {
271 0
272 } else {
273 1
274 }
275 }
276 Terminator::Ret | Terminator::Throw(_) | Terminator::Exit => 0,
277 };
278 if b.succ.len() > normal {
279 b.succ.last().copied()
281 } else {
282 None
283 }
284}
285
286fn build_phi_edge_moves(prog: &ExprProgram) -> HashMap<(usize, usize), Vec<(VarId, VarId)>> {
287 let mut m: HashMap<(usize, usize), Vec<(VarId, VarId)>> = HashMap::new();
288 for blk in &prog.blocks {
289 let succ = blk.id;
290 for p in &blk.phi {
291 let dst = p.result;
292 for (pred, src) in &p.args {
293 m.entry((*pred, succ)).or_default().push((dst, *src));
294 }
295 }
296 }
297 m
298}
299
300fn emit_phi_parallel_copies(
301 out: &mut String,
302 fmt_var: &dyn Fn(VarId) -> String,
303 phi_moves: &HashMap<(usize, usize), Vec<(VarId, VarId)>>,
304 pred: usize,
305 succ: usize,
306 indent: &str,
307) -> Result<()> {
308 let Some(moves) = phi_moves.get(&(pred, succ)) else {
309 return Ok(());
310 };
311
312 let mut mv: Vec<(VarId, VarId)> = moves.iter().copied().filter(|(d, s)| d != s).collect();
314 if mv.is_empty() {
315 return Ok(());
316 }
317
318 let dst_set: HashSet<VarId> = mv.iter().map(|(d, _)| *d).collect();
320 let mut saved: HashMap<VarId, String> = HashMap::new();
321 let mut tmp_i = 0usize;
322
323 for (_, s) in &mv {
324 if dst_set.contains(s) && !saved.contains_key(s) {
325 let tname = format!("__tjs2dec_phi{}", tmp_i);
326 tmp_i += 1;
327 writeln!(out, "{}var {} = {};", indent, tname, fmt_var(*s))?;
328 saved.insert(*s, tname);
329 }
330 }
331
332 for (d, s) in mv.drain(..) {
333 if let Some(tn) = saved.get(&s) {
334 writeln!(out, "{}{} = {};", indent, fmt_var(d), tn)?;
335 } else {
336 writeln!(out, "{}{} = {};", indent, fmt_var(d), fmt_var(s))?;
337 }
338 }
339
340 Ok(())
341}
342
343fn collect_all_vars(prog: &ExprProgram) -> Vec<String> {
344 let mut set: HashSet<VarId> = HashSet::new();
345
346 for b in &prog.blocks {
347 for p in &b.phi {
348 set.insert(p.result);
349 for (_, v) in &p.args {
350 set.insert(*v);
351 }
352 }
353
354 for st in &b.stmts {
355 match st {
356 Stmt::Assign { dst, expr } => {
357 set.insert(*dst);
358 collect_expr_vars(expr, &mut set);
359 }
360 Stmt::Store { target, value } => {
361 collect_expr_vars(target, &mut set);
362 collect_expr_vars(value, &mut set);
363 }
364 Stmt::Update {
365 dst, target, rhs, ..
366 } => {
367 if let Some(d) = dst {
368 set.insert(*d);
369 }
370 collect_expr_vars(target, &mut set);
371 collect_expr_vars(rhs, &mut set);
372 }
373 Stmt::Expr(e) => collect_expr_vars(e, &mut set),
374 Stmt::Opaque { args, defs, .. } => {
375 for d in defs {
376 set.insert(*d);
377 }
378 for a in args {
379 collect_expr_vars(a, &mut set);
380 }
381 }
382 }
383 }
384
385 match &b.term {
386 Terminator::Br { cond, .. } => collect_expr_vars(cond, &mut set),
387 Terminator::Throw(e) => collect_expr_vars(e, &mut set),
388 _ => {}
389 }
390 }
391
392 let mut names: Vec<String> = set.into_iter().map(fmt_vid).collect();
393 names.sort();
394 names.dedup();
395 names
396}
397
398fn collect_expr_vars(e: &Expr, out: &mut HashSet<VarId>) {
399 match e {
400 Expr::SsaVar(v) => {
401 out.insert(*v);
402 }
403 Expr::Unary(_, x) | Expr::Deref(x) => {
404 collect_expr_vars(x, out);
405 }
406 Expr::Binary(_, a, b) => {
407 collect_expr_vars(a, out);
408 collect_expr_vars(b, out);
409 }
410 Expr::Call(f, args) | Expr::New(f, args) => {
411 collect_expr_vars(f, out);
412 for a in args {
413 collect_expr_vars(a, out);
414 }
415 }
416 Expr::Index(a, b) => {
417 collect_expr_vars(a, out);
418 collect_expr_vars(b, out);
419 }
420 Expr::Member(base, _) => {
421 collect_expr_vars(base, out);
422 }
423 Expr::MethodCall { base, args, .. } => {
424 collect_expr_vars(base, out);
425 for a in args {
426 collect_expr_vars(a, out);
427 }
428 }
429 Expr::Opaque(_, args) => {
430 for a in args {
431 collect_expr_vars(a, out);
432 }
433 }
434 _ => {}
435 }
436}