1use std::collections::BTreeSet;
2use std::fmt::Write as _;
3use std::io::{Result as IoResult, Write};
4
5use crate::cfg::{build_cfg, BlockTerm, FunctionCfg};
6use crate::decode::{Function, Instruction, Op};
7use crate::lua_opt::BlockEmitter;
8use crate::parser::Parser;
9
10fn func_name(addr: u32) -> String {
11 format!("f_{:08X}", addr)
12}
13
14fn emit_stack_slot_get(args_count: u8, idx: i8) -> String {
15 if idx < 0 {
16 let abs = (-idx) as u8 - 2;
17 if abs <= args_count {
18 let a = (args_count - abs) as usize;
19 return format!("a{}", a);
20 }
21 return format!("a_{}", idx);
22 }
23
24 let u = idx as u8;
25 if u < args_count {
26 format!("a{}", u as usize)
27 } else {
28 let l = (u - args_count) as usize;
29 format!("l{}", l)
30 }
31}
32
33fn emit_stack_slot_set(args_count: u8, idx: i8, rhs: &str) -> String {
34 let lhs = emit_stack_slot_get(args_count, idx);
35 format!("{} = {}", lhs, rhs)
36}
37
38fn emit_global(idx: u16, non_volatile_count: u16, volatile_count: u16) -> String {
39 if idx < non_volatile_count {
40 return format!("g{}", idx);
41 }
42
43 let vbase = non_volatile_count;
44 let vlimit = non_volatile_count.saturating_add(volatile_count);
45 if idx >= vbase && idx < vlimit {
46 return format!("vg{}", idx - vbase);
47 }
48
49 format!("G[{}]", idx)
50}
51
52fn emit_global_table(idx: u16) -> String {
53 format!("GT[{}]", idx)
54}
55
56fn emit_local_table(idx: i8) -> String {
57 format!("LT[{}]", idx)
58}
59
60fn escape_lua_string(s: &str) -> String {
61 let mut out = String::new();
62 for ch in s.chars() {
63 match ch {
64 '\\' => out.push_str("\\\\"),
65 '"' => out.push_str("\\\""),
66 '\n' => out.push_str("\\n"),
67 '\r' => out.push_str("\\r"),
68 '\t' => out.push_str("\\t"),
69 _ => out.push(ch),
70 }
71 }
72 out
73}
74
75fn collect_used_frame_locals(func: &Function) -> BTreeSet<usize> {
76 let mut used_l = BTreeSet::new();
77 for inst in &func.insts {
78 match inst.op {
79 Op::PushStack(idx) | Op::PopStack(idx) => {
80 if idx >= 0 {
81 let u = idx as u8;
82 if u >= func.args {
83 used_l.insert((u - func.args) as usize);
84 }
85 }
86 }
87 _ => {}
88 }
89 }
90 used_l
91}
92
93fn scan_runtime_tables(functions: &[Function]) -> (bool, bool, bool) {
94 let mut need_g = false;
95 let mut need_gt = false;
96 let mut need_lt = false;
97
98 for func in functions {
99 for inst in &func.insts {
100 match inst.op {
101 Op::PushGlobalTable(_) | Op::PopGlobalTable(_) => need_gt = true,
102 Op::PushLocalTable(_) | Op::PopLocalTable(_) => need_lt = true,
103 _ => {}
104 }
105 }
106 }
107
108 (need_g, need_gt, need_lt)
109}
110
111fn uses_fallback_global(functions: &[Function], non_volatile_count: u16, volatile_count: u16) -> bool {
112 let limit = non_volatile_count.saturating_add(volatile_count);
113 for func in functions {
114 for inst in &func.insts {
115 match inst.op {
116 Op::PushGlobal(idx) | Op::PopGlobal(idx) if idx >= limit => return true,
117 _ => {}
118 }
119 }
120 }
121 false
122}
123
124fn emit_name_decls<W: Write>(w: &mut W, keyword: &str, prefix: &str, count: u16) -> IoResult<()> {
125 if count == 0 {
126 return Ok(());
127 }
128
129 let mut start = 0u16;
130 while start < count {
131 let end = count.min(start + 16);
132 write!(w, "{} ", keyword)?;
133 for i in start..end {
134 if i > start {
135 write!(w, ", ")?;
136 }
137 write!(w, "{}{}", prefix, i)?;
138 }
139 writeln!(w)?;
140 start = end;
141 }
142 Ok(())
143}
144
145fn emit_push_value<W: Write>(w: &mut W, indent: &str, value: &str) -> IoResult<()> {
146 writeln!(w, "{}__sp = __sp + 1", indent)?;
147 writeln!(w, "{}__stk[__sp] = {}", indent, value)
148}
149
150fn emit_binary_reduce<W: Write>(w: &mut W, indent: &str, expr: &str, op_name: &str) -> IoResult<()> {
151 writeln!(w, "{}if __sp < 2 then", indent)?;
152 writeln!(w, "{} -- {} on short stack", indent, op_name)?;
153 writeln!(w, "{}else", indent)?;
154 writeln!(w, "{} local __rhs = __stk[__sp]", indent)?;
155 writeln!(w, "{} local __lhs = __stk[__sp - 1]", indent)?;
156 writeln!(w, "{} __stk[__sp] = nil", indent)?;
157 writeln!(w, "{} __sp = __sp - 1", indent)?;
158 writeln!(w, "{} __stk[__sp] = {}", indent, expr)?;
159 writeln!(w, "{}end", indent)
160}
161
162fn emit_runtime_inst<W: Write>(
163 w: &mut W,
164 indent: &str,
165 func: &Function,
166 inst: &Instruction,
167 callee_args: &std::collections::BTreeMap<u32, u8>,
168 non_volatile_count: u16,
169 volatile_count: u16,
170) -> IoResult<()> {
171 match &inst.op {
172 Op::Nop | Op::InitStack { .. } => {}
173 Op::PushNil => emit_push_value(w, indent, "nil")?,
174 Op::PushTrue => emit_push_value(w, indent, "true")?,
175 Op::PushI8(v) => emit_push_value(w, indent, &format!("{}", v))?,
176 Op::PushI16(v) => emit_push_value(w, indent, &format!("{}", v))?,
177 Op::PushI32(v) => emit_push_value(w, indent, &format!("{}", v))?,
178 Op::PushF32(v) => emit_push_value(w, indent, &format!("{}", v))?,
179 Op::PushString(s0) => emit_push_value(w, indent, &format!("\"{}\"", escape_lua_string(s0)))?,
180 Op::PushTop => {
181 writeln!(w, "{}if __sp == 0 then", indent)?;
182 writeln!(w, "{} -- push_top on empty stack", indent)?;
183 writeln!(w, "{} __sp = 1", indent)?;
184 writeln!(w, "{} __stk[1] = nil", indent)?;
185 writeln!(w, "{}else", indent)?;
186 writeln!(w, "{} __sp = __sp + 1", indent)?;
187 writeln!(w, "{} __stk[__sp] = __stk[__sp - 1]", indent)?;
188 writeln!(w, "{}end", indent)?;
189 }
190 Op::PushReturn => emit_push_value(w, indent, "__ret")?,
191 Op::PushGlobal(idx) => emit_push_value(w, indent, &emit_global(*idx, non_volatile_count, volatile_count))?,
192 Op::PopGlobal(idx) => {
193 writeln!(w, "{}if __sp == 0 then", indent)?;
194 writeln!(w, "{} -- pop_global with empty stack", indent)?;
195 writeln!(w, "{}else", indent)?;
196 writeln!(w, "{} {} = __stk[__sp]", indent, emit_global(*idx, non_volatile_count, volatile_count))?;
197 writeln!(w, "{} __stk[__sp] = nil", indent)?;
198 writeln!(w, "{} __sp = __sp - 1", indent)?;
199 writeln!(w, "{}end", indent)?;
200 }
201 Op::PushGlobalTable(idx) => {
202 writeln!(w, "{}if __sp == 0 then", indent)?;
203 writeln!(w, "{} -- push_global_table on empty stack", indent)?;
204 writeln!(w, "{}else", indent)?;
205 writeln!(w, "{} __stk[__sp] = {}[__stk[__sp]]", indent, emit_global_table(*idx))?;
206 writeln!(w, "{}end", indent)?;
207 }
208 Op::PopGlobalTable(idx) => {
209 writeln!(w, "{}if __sp < 2 then", indent)?;
210 writeln!(w, "{} -- pop_global_table on short stack", indent)?;
211 writeln!(w, "{} __sp = 0", indent)?;
212 writeln!(w, "{}else", indent)?;
213 writeln!(w, "{} local __value = __stk[__sp]", indent)?;
214 writeln!(w, "{} __stk[__sp] = nil", indent)?;
215 writeln!(w, "{} __sp = __sp - 1", indent)?;
216 writeln!(w, "{} local __key = __stk[__sp]", indent)?;
217 writeln!(w, "{} {}[__key] = __value", indent, emit_global_table(*idx))?;
218 writeln!(w, "{} __stk[__sp] = nil", indent)?;
219 writeln!(w, "{} __sp = __sp - 1", indent)?;
220 writeln!(w, "{}end", indent)?;
221 }
222 Op::PushLocalTable(idx) => {
223 writeln!(w, "{}if __sp == 0 then", indent)?;
224 writeln!(w, "{} -- push_local_table on empty stack", indent)?;
225 writeln!(w, "{}else", indent)?;
226 writeln!(w, "{} __stk[__sp] = {}[__stk[__sp]]", indent, emit_local_table(*idx))?;
227 writeln!(w, "{}end", indent)?;
228 }
229 Op::PopLocalTable(idx) => {
230 writeln!(w, "{}if __sp < 2 then", indent)?;
231 writeln!(w, "{} -- pop_local_table on short stack", indent)?;
232 writeln!(w, "{} __sp = 0", indent)?;
233 writeln!(w, "{}else", indent)?;
234 writeln!(w, "{} local __value = __stk[__sp]", indent)?;
235 writeln!(w, "{} __stk[__sp] = nil", indent)?;
236 writeln!(w, "{} __sp = __sp - 1", indent)?;
237 writeln!(w, "{} local __key = __stk[__sp]", indent)?;
238 writeln!(w, "{} {}[__key] = __value", indent, emit_local_table(*idx))?;
239 writeln!(w, "{} __stk[__sp] = nil", indent)?;
240 writeln!(w, "{} __sp = __sp - 1", indent)?;
241 writeln!(w, "{}end", indent)?;
242 }
243 Op::PushStack(idx) => emit_push_value(w, indent, &emit_stack_slot_get(func.args, *idx))?,
244 Op::PopStack(idx) => {
245 writeln!(w, "{}if __sp == 0 then", indent)?;
246 writeln!(w, "{} -- pop_stack with empty stack", indent)?;
247 writeln!(w, "{}else", indent)?;
248 writeln!(w, "{} {}", indent, emit_stack_slot_set(func.args, *idx, "__stk[__sp]"))?;
249 writeln!(w, "{} __stk[__sp] = nil", indent)?;
250 writeln!(w, "{} __sp = __sp - 1", indent)?;
251 writeln!(w, "{}end", indent)?;
252 }
253 Op::Neg => {
254 writeln!(w, "{}if __sp == 0 then", indent)?;
255 writeln!(w, "{} -- neg on empty stack", indent)?;
256 writeln!(w, "{}else", indent)?;
257 writeln!(w, "{} __stk[__sp] = -__stk[__sp]", indent)?;
258 writeln!(w, "{}end", indent)?;
259 }
260 Op::Add => emit_binary_reduce(w, indent, "__lhs + __rhs", "add")?,
261 Op::Sub => emit_binary_reduce(w, indent, "__lhs - __rhs", "sub")?,
262 Op::Mul => emit_binary_reduce(w, indent, "__lhs * __rhs", "mul")?,
263 Op::Div => emit_binary_reduce(w, indent, "__lhs / __rhs", "div")?,
264 Op::Mod => emit_binary_reduce(w, indent, "__lhs % __rhs", "mod")?,
265 Op::BitTest => emit_binary_reduce(w, indent, "(__lhs & __rhs) ~= 0", "bittest")?,
266 Op::And => emit_binary_reduce(w, indent, "(__lhs ~= nil) and (__rhs ~= nil)", "and")?,
267 Op::Or => emit_binary_reduce(w, indent, "(__lhs ~= nil) or (__rhs ~= nil)", "or")?,
268 Op::SetE => emit_binary_reduce(w, indent, "(__lhs == __rhs)", "sete")?,
269 Op::SetNE => emit_binary_reduce(w, indent, "(__lhs ~= __rhs)", "setne")?,
270 Op::SetG => emit_binary_reduce(w, indent, "(__lhs > __rhs)", "setg")?,
271 Op::SetGE => emit_binary_reduce(w, indent, "(__lhs >= __rhs)", "setge")?,
272 Op::SetL => emit_binary_reduce(w, indent, "(__lhs < __rhs)", "setl")?,
273 Op::SetLE => emit_binary_reduce(w, indent, "(__lhs <= __rhs)", "setle")?,
274 Op::Call { target } => {
275 let argc = callee_args.get(target).copied().unwrap_or(0) as usize;
276 writeln!(w, "{}if __sp < {} then", indent, argc)?;
277 writeln!(w, "{} -- call {} with argc={} on short stack", indent, func_name(*target), argc)?;
278 writeln!(w, "{} __ret = {}()", indent, func_name(*target))?;
279 writeln!(w, "{} __sp = 0", indent)?;
280 writeln!(w, "{}else", indent)?;
281 let base_expr = if argc == 0 { "__sp + 1".to_string() } else { format!("__sp - {} + 1", argc) };
282 writeln!(w, "{} local __base = {}", indent, base_expr)?;
283 let mut args_s = String::new();
284 for i in 0..argc {
285 if i > 0 { args_s.push_str(", "); }
286 write!(&mut args_s, "__stk[__base + {}]", i).ok();
287 }
288 writeln!(w, "{} __ret = {}({})", indent, func_name(*target), args_s)?;
289 writeln!(w, "{} for __i = __base, __sp do", indent)?;
290 writeln!(w, "{} __stk[__i] = nil", indent)?;
291 writeln!(w, "{} end", indent)?;
292 writeln!(w, "{} __sp = __base - 1", indent)?;
293 writeln!(w, "{}end", indent)?;
294 }
295 Op::Syscall { id, name, args } => {
296 let argc = *args as usize;
297 writeln!(w, "{}if __sp < {} then", indent, argc)?;
298 writeln!(w, "{} -- syscall {} (id={}) argc={} on short stack", indent, name, id, argc)?;
299 writeln!(w, "{} __ret = {}()", indent, name)?;
300 writeln!(w, "{} __sp = 0", indent)?;
301 writeln!(w, "{}else", indent)?;
302 let base_expr = if argc == 0 { "__sp + 1".to_string() } else { format!("__sp - {} + 1", argc) };
303 writeln!(w, "{} local __base = {}", indent, base_expr)?;
304 let mut args_s = String::new();
305 for i in 0..argc {
306 if i > 0 { args_s.push_str(", "); }
307 write!(&mut args_s, "__stk[__base + {}]", i).ok();
308 }
309 writeln!(w, "{} __ret = {}({})", indent, name, args_s)?;
310 writeln!(w, "{} for __i = __base, __sp do", indent)?;
311 writeln!(w, "{} __stk[__i] = nil", indent)?;
312 writeln!(w, "{} end", indent)?;
313 writeln!(w, "{} __sp = __base - 1", indent)?;
314 writeln!(w, "{}end", indent)?;
315 }
316 Op::Jmp { .. } | Op::Jz { .. } | Op::Ret | Op::RetV => {}
317 Op::Unknown(opcode) => writeln!(w, "{}-- unknown opcode 0x{:02X}", indent, opcode)?,
318 }
319 Ok(())
320}
321
322fn emit_runtime_terminator<W: Write>(w: &mut W, indent: &str, term: BlockTerm, succs: &[usize]) -> IoResult<()> {
323 match term {
324 BlockTerm::Jmp | BlockTerm::Fallthrough => {
325 if let Some(&t) = succs.get(0) {
326 writeln!(w, "{}__pc = {}", indent, t)?;
327 } else {
328 writeln!(w, "{}return", indent)?;
329 }
330 }
331 BlockTerm::Jz => {
332 writeln!(w, "{}if __sp == 0 then", indent)?;
333 writeln!(w, "{} -- jz on empty stack", indent)?;
334 if let Some(&t) = succs.get(0) {
335 writeln!(w, "{} __pc = {}", indent, t)?;
336 } else {
337 writeln!(w, "{} return", indent)?;
338 }
339 writeln!(w, "{}else", indent)?;
340 writeln!(w, "{} local __cond = __stk[__sp]", indent)?;
341 writeln!(w, "{} __stk[__sp] = nil", indent)?;
342 writeln!(w, "{} __sp = __sp - 1", indent)?;
343 writeln!(w, "{} if __cond == 0 then", indent)?;
344 match succs.get(0).copied() {
345 Some(tid) => writeln!(w, "{} __pc = {}", indent, tid)?,
346 None => writeln!(w, "{} return", indent)?,
347 }
348 writeln!(w, "{} else", indent)?;
349 match succs.get(1).copied() {
350 Some(fid) => writeln!(w, "{} __pc = {}", indent, fid)?,
351 None => writeln!(w, "{} return", indent)?,
352 }
353 writeln!(w, "{} end", indent)?;
354 writeln!(w, "{}end", indent)?;
355 }
356 BlockTerm::Ret => writeln!(w, "{}return", indent)?,
357 BlockTerm::RetV => {
358 writeln!(w, "{}if __sp == 0 then", indent)?;
359 writeln!(w, "{} return nil", indent)?;
360 writeln!(w, "{}else", indent)?;
361 writeln!(w, "{} return __stk[__sp]", indent)?;
362 writeln!(w, "{}end", indent)?;
363 }
364 }
365 Ok(())
366}
367
368fn is_linear_cfg(cfg: &FunctionCfg) -> bool {
369 if cfg.blocks.is_empty() {
370 return true;
371 }
372
373 for (i, b) in cfg.blocks.iter().enumerate() {
374 match b.term {
375 BlockTerm::Jz => return false,
376 BlockTerm::Ret | BlockTerm::RetV => {
377 if i + 1 != cfg.blocks.len() {
378 return false;
379 }
380 }
381 BlockTerm::Jmp | BlockTerm::Fallthrough => {
382 if i + 1 == cfg.blocks.len() {
383 if !b.succs.is_empty() {
384 return false;
385 }
386 } else if b.succs.as_slice() != [i + 1] {
387 return false;
388 }
389 }
390 }
391 }
392
393 true
394}
395
396fn can_chain_to(cfg: &FunctionCfg, bid: usize) -> Option<usize> {
397 let b = &cfg.blocks[bid];
398 if !matches!(b.term, BlockTerm::Jmp | BlockTerm::Fallthrough) {
399 return None;
400 }
401 if b.succs.len() != 1 {
402 return None;
403 }
404 let sid = b.succs[0];
405 if sid <= bid {
406 return None;
407 }
408 let succ = &cfg.blocks[sid];
409 if succ.preds.as_slice() != [bid] {
410 return None;
411 }
412 Some(sid)
413}
414
415fn build_block_chains(cfg: &FunctionCfg) -> Vec<Vec<usize>> {
416 let n = cfg.blocks.len();
417 let mut is_cont = vec![false; n];
418 for bid in 0..n {
419 if let Some(sid) = can_chain_to(cfg, bid) {
420 is_cont[sid] = true;
421 }
422 }
423
424 let mut seen = vec![false; n];
425 let mut chains = Vec::new();
426
427 for head in 0..n {
428 if is_cont[head] || seen[head] {
429 continue;
430 }
431 let mut chain = vec![head];
432 seen[head] = true;
433 let mut cur = head;
434 while let Some(next) = can_chain_to(cfg, cur) {
435 if seen[next] {
436 break;
437 }
438 chain.push(next);
439 seen[next] = true;
440 cur = next;
441 }
442 chains.push(chain);
443 }
444
445 for bid in 0..n {
446 if !seen[bid] {
447 chains.push(vec![bid]);
448 }
449 }
450
451 chains.sort_by_key(|chain| chain[0]);
452 chains
453}
454
455fn emit_optimized_chain(
456 out: &mut Vec<u8>,
457 func: &Function,
458 cfg: &FunctionCfg,
459 chain: &[usize],
460 callee_args: &std::collections::BTreeMap<u32, u8>,
461 used_s: &mut BTreeSet<usize>,
462 non_volatile_count: u16,
463 volatile_count: u16,
464 indent: &str,
465) {
466 if chain.is_empty() {
467 return;
468 }
469
470 let first = &cfg.blocks[chain[0]];
471 let mut be = BlockEmitter::new(
472 indent,
473 func.args,
474 callee_args,
475 used_s,
476 non_volatile_count,
477 volatile_count,
478 );
479 be.init_stack(first.in_depth);
480
481 for (pos, &bid) in chain.iter().enumerate() {
482 let b = &cfg.blocks[bid];
483 let term_idx = if matches!(b.term, BlockTerm::Jmp | BlockTerm::Jz | BlockTerm::Ret | BlockTerm::RetV) {
484 b.inst_indices.clone().last()
485 } else {
486 None
487 };
488
489 for ii in b.inst_indices.clone() {
490 if Some(ii) == term_idx {
491 continue;
492 }
493 be.emit_inst(&func.insts[ii]);
494 }
495
496 if pos + 1 == chain.len() {
497 be.emit_terminator(b.term, &b.succs);
498 }
499 }
500
501 out.extend_from_slice(be.take_output().as_bytes());
502}
503
504fn emit_optimized_linear_body<W: Write>(
505 w: &mut W,
506 func: &Function,
507 cfg: &FunctionCfg,
508 callee_args: &std::collections::BTreeMap<u32, u8>,
509 used_s: &mut BTreeSet<usize>,
510 non_volatile_count: u16,
511 volatile_count: u16,
512) -> IoResult<()> {
513 let chain = build_block_chains(cfg);
514 let mut body = Vec::new();
515 for part in &chain {
516 emit_optimized_chain(
517 &mut body,
518 func,
519 cfg,
520 part,
521 callee_args,
522 used_s,
523 non_volatile_count,
524 volatile_count,
525 " ",
526 );
527 }
528 w.write_all(&body)?;
529 writeln!(w, "end")?;
530 writeln!(w)?;
531 Ok(())
532}
533
534fn emit_optimized_dispatcher_body<W: Write>(
535 w: &mut W,
536 func: &Function,
537 cfg: &FunctionCfg,
538 callee_args: &std::collections::BTreeMap<u32, u8>,
539 used_s: &mut BTreeSet<usize>,
540 non_volatile_count: u16,
541 volatile_count: u16,
542) -> IoResult<()> {
543 let entry_pc = cfg.blocks.first().map(|b| b.id).unwrap_or(0);
544 let chains = build_block_chains(cfg);
545 let mut body: Vec<u8> = Vec::new();
546
547 writeln!(&mut body, " local __pc = {}", entry_pc)?;
548 writeln!(&mut body, " while true do")?;
549
550 for (i, chain) in chains.iter().enumerate() {
551 let head = chain[0];
552 if i == 0 {
553 writeln!(&mut body, " if __pc == {} then", head)?;
554 } else {
555 writeln!(&mut body, " elseif __pc == {} then", head)?;
556 }
557 emit_optimized_chain(
558 &mut body,
559 func,
560 cfg,
561 chain,
562 callee_args,
563 used_s,
564 non_volatile_count,
565 volatile_count,
566 " ",
567 );
568 }
569
570 writeln!(&mut body, " else")?;
571 writeln!(&mut body, " return")?;
572 writeln!(&mut body, " end")?;
573 writeln!(&mut body, " end")?;
574 writeln!(&mut body, "end")?;
575 writeln!(&mut body)?;
576 w.write_all(&body)?;
577 Ok(())
578}
579
580fn emit_runtime_chain<W: Write>(
581 w: &mut W,
582 func: &Function,
583 cfg: &FunctionCfg,
584 chain: &[usize],
585 callee_args: &std::collections::BTreeMap<u32, u8>,
586 non_volatile_count: u16,
587 volatile_count: u16,
588 indent: &str,
589) -> IoResult<()> {
590 for (pos, &bid) in chain.iter().enumerate() {
591 let b = &cfg.blocks[bid];
592 let term_idx = if matches!(b.term, BlockTerm::Jmp | BlockTerm::Jz | BlockTerm::Ret | BlockTerm::RetV) {
593 b.inst_indices.clone().last()
594 } else {
595 None
596 };
597
598 for ii in b.inst_indices.clone() {
599 if Some(ii) == term_idx {
600 continue;
601 }
602 emit_runtime_inst(
603 w,
604 indent,
605 func,
606 &func.insts[ii],
607 callee_args,
608 non_volatile_count,
609 volatile_count,
610 )?;
611 }
612
613 if pos + 1 == chain.len() {
614 emit_runtime_terminator(w, indent, b.term, &b.succs)?;
615 }
616 }
617 Ok(())
618}
619
620fn emit_runtime_linear_body<W: Write>(
621 w: &mut W,
622 func: &Function,
623 cfg: &FunctionCfg,
624 callee_args: &std::collections::BTreeMap<u32, u8>,
625 non_volatile_count: u16,
626 volatile_count: u16,
627) -> IoResult<()> {
628 let chains = build_block_chains(cfg);
629 for chain in &chains {
630 emit_runtime_chain(
631 w,
632 func,
633 cfg,
634 chain,
635 callee_args,
636 non_volatile_count,
637 volatile_count,
638 " ",
639 )?;
640 }
641 writeln!(w, "end")?;
642 writeln!(w)?;
643 Ok(())
644}
645
646fn emit_runtime_dispatcher_body<W: Write>(
647 w: &mut W,
648 func: &Function,
649 cfg: &FunctionCfg,
650 callee_args: &std::collections::BTreeMap<u32, u8>,
651 non_volatile_count: u16,
652 volatile_count: u16,
653) -> IoResult<()> {
654 let entry_pc = cfg.blocks.first().map(|b| b.id).unwrap_or(0);
655 let chains = build_block_chains(cfg);
656 writeln!(w, " local __pc = {}", entry_pc)?;
657 writeln!(w, " while true do")?;
658
659 for (i, chain) in chains.iter().enumerate() {
660 let head = chain[0];
661 if i == 0 {
662 writeln!(w, " if __pc == {} then", head)?;
663 } else {
664 writeln!(w, " elseif __pc == {} then", head)?;
665 }
666 emit_runtime_chain(
667 w,
668 func,
669 cfg,
670 chain,
671 callee_args,
672 non_volatile_count,
673 volatile_count,
674 " ",
675 )?;
676 }
677
678 writeln!(w, " else")?;
679 writeln!(w, " return")?;
680 writeln!(w, " end")?;
681 writeln!(w, " end")?;
682 writeln!(w, "end")?;
683 writeln!(w)?;
684 Ok(())
685}
686
687fn emit_function<W: Write>(
688 w: &mut W,
689 func: &Function,
690 callee_args: &std::collections::BTreeMap<u32, u8>,
691 is_entry: bool,
692 non_volatile_count: u16,
693 volatile_count: u16,
694) -> IoResult<()> {
695 let cfg = build_cfg(func, callee_args);
696 let used_l = collect_used_frame_locals(func);
697
698 let mut sig = String::new();
699 if is_entry {
700 write!(&mut sig, "function main(").ok();
701 } else {
702 write!(&mut sig, "function {}(", func_name(func.start_addr)).ok();
703 }
704 for i in 0..(func.args as usize) {
705 if i > 0 {
706 sig.push_str(", ");
707 }
708 sig.push_str(&format!("a{}", i));
709 }
710 sig.push(')');
711 writeln!(w, "{}", sig)?;
712
713 if !used_l.is_empty() {
714 write!(w, " local ")?;
715 for (i, lidx) in used_l.iter().enumerate() {
716 if i > 0 {
717 write!(w, ", ")?;
718 }
719 write!(w, "l{}", lidx)?;
720 }
721 writeln!(w)?;
722 }
723
724 writeln!(w, " local __ret = nil")?;
725
726 if cfg.stack_consistent {
727 let mut used_s: BTreeSet<usize> = BTreeSet::new();
728 let mut body = Vec::new();
729 if is_linear_cfg(&cfg) {
730 emit_optimized_linear_body(
731 &mut body,
732 func,
733 &cfg,
734 callee_args,
735 &mut used_s,
736 non_volatile_count,
737 volatile_count,
738 )?;
739 } else {
740 emit_optimized_dispatcher_body(
741 &mut body,
742 func,
743 &cfg,
744 callee_args,
745 &mut used_s,
746 non_volatile_count,
747 volatile_count,
748 )?;
749 }
750
751 if !used_s.is_empty() {
752 write!(w, " local ")?;
753 for (i, sidx) in used_s.iter().enumerate() {
754 if i > 0 {
755 write!(w, ", ")?;
756 }
757 write!(w, "S{}", sidx)?;
758 }
759 writeln!(w)?;
760 }
761
762 w.write_all(&body)?;
763 } else {
764 writeln!(w, " local __stk = {{}}")?;
765 writeln!(w, " local __sp = 0")?;
766 if is_linear_cfg(&cfg) {
767 emit_runtime_linear_body(
768 w,
769 func,
770 &cfg,
771 callee_args,
772 non_volatile_count,
773 volatile_count,
774 )?;
775 } else {
776 emit_runtime_dispatcher_body(
777 w,
778 func,
779 &cfg,
780 callee_args,
781 non_volatile_count,
782 volatile_count,
783 )?;
784 }
785 }
786
787 Ok(())
788}
789
790pub fn emit_lua_script<W: Write>(w: &mut W, parser: &Parser, functions: &[Function]) -> IoResult<()> {
791 writeln!(w, "-- Decompiled from HCB bytecode")?;
792 writeln!(w, "-- Title: {}", parser.get_title())?;
793 let (sw, sh) = parser.get_screen_size();
794 writeln!(w, "-- Screen: {}x{}", sw, sh)?;
795 writeln!(w)?;
796
797 let non_volatile_count = parser.get_non_volatile_global_count();
798 let volatile_count = parser.get_volatile_global_count();
799 emit_name_decls(w, "global", "g", non_volatile_count)?;
800 emit_name_decls(w, "volatile global", "vg", volatile_count)?;
801
802 let (mut need_g, need_gt, need_lt) = scan_runtime_tables(functions);
803 need_g |= uses_fallback_global(functions, non_volatile_count, volatile_count);
804 if need_g {
805 writeln!(w, "G = G or {{}}")?;
806 }
807 if need_gt {
808 writeln!(w, "GT = GT or {{}}")?;
809 }
810 if need_lt {
811 writeln!(w, "LT = LT or {{}}")?;
812 }
813 if non_volatile_count > 0 || volatile_count > 0 || need_g || need_gt || need_lt {
814 writeln!(w)?;
815 }
816
817 let mut callee_args = std::collections::BTreeMap::<u32, u8>::new();
818 for f in functions {
819 callee_args.insert(f.start_addr, f.args);
820 }
821
822 for func in functions {
823 emit_function(
824 w,
825 func,
826 &callee_args,
827 parser.get_entry_point() == func.start_addr,
828 non_volatile_count,
829 volatile_count,
830 )?;
831 }
832
833 Ok(())
834}