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