lua2hcb_compiler/
asm.rs

1use crate::ir::{Item, OpKind};
2use crate::meta::{Meta, Nls};
3use anyhow::{anyhow, bail, Result};
4use encoding_rs::{GB18030, SHIFT_JIS, UTF_8};
5use std::collections::HashMap;
6
7fn enc_text(meta: &Meta, s: &str) -> Result<Vec<u8>> {
8    let (cow, _, had_errors) = match meta.nls {
9        Nls::Utf8 => UTF_8.encode(s),
10        Nls::ShiftJis => SHIFT_JIS.encode(s),
11        Nls::Gb18030 => GB18030.encode(s),
12    };
13    if had_errors {
14        bail!("text encoding error for string: {s}");
15    }
16    Ok(cow.into_owned())
17}
18
19fn enc_cstr(meta: &Meta, s: &str) -> Result<Vec<u8>> {
20    let mut b = enc_text(meta, s)?;
21    b.push(0);
22    if b.len() > 255 {
23        bail!(
24            "string too long for u8 length (including NUL): len={} (max=255)",
25            b.len()
26        );
27    }
28    Ok(b)
29}
30
31fn opcode(k: &OpKind) -> u8 {
32    match k {
33        OpKind::Nop => 0x00,
34        OpKind::InitStack { .. } => 0x01,
35        OpKind::CallFn { .. } => 0x02,
36        OpKind::Syscall { .. } => 0x03,
37        OpKind::Ret => 0x04,
38        OpKind::Retv => 0x05,
39        OpKind::JmpAbs { .. } | OpKind::JmpLabel { .. } => 0x06,
40        OpKind::JzAbs { .. } | OpKind::JzLabel { .. } => 0x07,
41        OpKind::PushNil => 0x08,
42        OpKind::PushTrue => 0x09,
43        OpKind::PushI32(..) => 0x0A,
44        OpKind::PushI16(..) => 0x0B,
45        OpKind::PushI8(..) => 0x0C,
46        OpKind::PushF32(..) => 0x0D,
47        OpKind::PushString(..) => 0x0E,
48        OpKind::PushGlobal(..) => 0x0F,
49        OpKind::PushStack(..) => 0x10,
50        OpKind::PushGlobalTable(..) => 0x11,
51        OpKind::PushLocalTable(..) => 0x12,
52        OpKind::PushTop => 0x13,
53        OpKind::PushReturn => 0x14,
54        OpKind::PopGlobal(..) => 0x15,
55        OpKind::PopStack(..) => 0x16,
56        OpKind::PopGlobalTable(..) => 0x17,
57        OpKind::PopLocalTable(..) => 0x18,
58        OpKind::Neg => 0x19,
59        OpKind::Add => 0x1A,
60        OpKind::Sub => 0x1B,
61        OpKind::Mul => 0x1C,
62        OpKind::Div => 0x1D,
63        OpKind::Mod => 0x1E,
64        OpKind::BitTest => 0x1F,
65        OpKind::And => 0x20,
66        OpKind::Or => 0x21,
67        OpKind::SetE => 0x22,
68        OpKind::SetNe => 0x23,
69        OpKind::SetG => 0x24,
70        OpKind::SetLe => 0x25,
71        OpKind::SetL => 0x26,
72        OpKind::SetGe => 0x27,
73    }
74}
75
76fn op_size(meta: &Meta, k: &OpKind) -> Result<usize> {
77    let sz = match k {
78        OpKind::Nop
79        | OpKind::Ret
80        | OpKind::Retv
81        | OpKind::PushNil
82        | OpKind::PushTrue
83        | OpKind::PushTop
84        | OpKind::PushReturn
85        | OpKind::Neg
86        | OpKind::Add
87        | OpKind::Sub
88        | OpKind::Mul
89        | OpKind::Div
90        | OpKind::Mod
91        | OpKind::BitTest
92        | OpKind::And
93        | OpKind::Or
94        | OpKind::SetE
95        | OpKind::SetNe
96        | OpKind::SetG
97        | OpKind::SetLe
98        | OpKind::SetL
99        | OpKind::SetGe => 1,
100
101        OpKind::InitStack { .. } => 3,
102        OpKind::CallFn { .. } => 5,
103        OpKind::Syscall { .. } => 3,
104        OpKind::JmpAbs { .. }
105        | OpKind::JzAbs { .. }
106        | OpKind::JmpLabel { .. }
107        | OpKind::JzLabel { .. } => 5,
108
109        OpKind::PushI8(..) => 2,
110        OpKind::PushI16(..) => 3,
111        OpKind::PushI32(..) => 5,
112        OpKind::PushF32(..) => 5,
113        OpKind::PushString(s) => {
114            let b = enc_cstr(meta, s)?;
115            1 + 1 + b.len()
116        }
117
118        OpKind::PushGlobal(..)
119        | OpKind::PushGlobalTable(..)
120        | OpKind::PopGlobal(..)
121        | OpKind::PopGlobalTable(..) => 3,
122
123        OpKind::PushStack(..)
124        | OpKind::PopStack(..)
125        | OpKind::PushLocalTable(..)
126        | OpKind::PopLocalTable(..) => 2,
127    };
128    Ok(sz)
129}
130
131pub fn assemble(meta: &Meta, items: &[Item]) -> Result<(Vec<u8>, HashMap<String, u32>)> {
132    let base_addr: u32 = 4;
133
134    // First pass: compute label addresses.
135    let mut labels: HashMap<String, u32> = HashMap::new();
136    let mut addr: u32 = base_addr;
137    for it in items {
138        match it {
139            Item::Label(l) => {
140                labels.insert(l.name.clone(), addr);
141            }
142            Item::Op(op) => {
143                let sz = op_size(meta, op)? as u32;
144                addr = addr
145                    .checked_add(sz)
146                    .ok_or_else(|| anyhow!("address overflow"))?;
147            }
148        }
149    }
150
151    // Second pass: encode.
152    let mut out: Vec<u8> = Vec::new();
153    for it in items {
154        let op = match it {
155            Item::Label(_) => continue,
156            Item::Op(op) => op,
157        };
158
159        out.push(opcode(op));
160
161        match op {
162            OpKind::Nop
163            | OpKind::Ret
164            | OpKind::Retv
165            | OpKind::PushNil
166            | OpKind::PushTrue
167            | OpKind::PushTop
168            | OpKind::PushReturn
169            | OpKind::Neg
170            | OpKind::Add
171            | OpKind::Sub
172            | OpKind::Mul
173            | OpKind::Div
174            | OpKind::Mod
175            | OpKind::BitTest
176            | OpKind::And
177            | OpKind::Or
178            | OpKind::SetE
179            | OpKind::SetNe
180            | OpKind::SetG
181            | OpKind::SetLe
182            | OpKind::SetL
183            | OpKind::SetGe => {}
184
185            OpKind::InitStack { args, locals } => {
186                out.push(*args as u8);
187                out.push(*locals as u8);
188            }
189
190            OpKind::CallFn { name } => {
191                let lbl = format!("fn:{name}");
192                let tgt = labels
193                    .get(&lbl)
194                    .copied()
195                    .ok_or_else(|| anyhow!("unknown function label: {lbl}"))?;
196                out.extend_from_slice(&tgt.to_le_bytes());
197            }
198
199            OpKind::Syscall { id } => {
200                out.extend_from_slice(&id.to_le_bytes());
201            }
202
203            OpKind::JmpAbs { target } => {
204                out.extend_from_slice(&target.to_le_bytes());
205            }
206            OpKind::JzAbs { target } => {
207                out.extend_from_slice(&target.to_le_bytes());
208            }
209
210            OpKind::JmpLabel { label } => {
211                let tgt = labels
212                    .get(label)
213                    .copied()
214                    .ok_or_else(|| anyhow!("unknown label: {label}"))?;
215                out.extend_from_slice(&tgt.to_le_bytes());
216            }
217            OpKind::JzLabel { label } => {
218                let tgt = labels
219                    .get(label)
220                    .copied()
221                    .ok_or_else(|| anyhow!("unknown label: {label}"))?;
222                out.extend_from_slice(&tgt.to_le_bytes());
223            }
224
225            OpKind::PushI8(v) => out.push(*v as u8),
226            OpKind::PushI16(v) => out.extend_from_slice(&v.to_le_bytes()),
227            OpKind::PushI32(v) => out.extend_from_slice(&v.to_le_bytes()),
228            OpKind::PushF32(v) => out.extend_from_slice(&v.to_le_bytes()),
229            OpKind::PushString(s) => {
230                let b = enc_cstr(meta, s)?;
231                out.push(b.len() as u8);
232                out.extend_from_slice(&b);
233            }
234
235            OpKind::PushGlobal(idx)
236            | OpKind::PushGlobalTable(idx)
237            | OpKind::PopGlobal(idx)
238            | OpKind::PopGlobalTable(idx) => {
239                out.extend_from_slice(&idx.to_le_bytes());
240            }
241
242            OpKind::PushStack(idx) | OpKind::PopStack(idx) | OpKind::PushLocalTable(idx) | OpKind::PopLocalTable(idx) => {
243                out.push(*idx as u8);
244            }
245        }
246    }
247
248    Ok((out, labels))
249}
250
251pub fn build_sysdesc(meta: &Meta, entry_point: u32) -> Result<Vec<u8>> {
252    let mut buf: Vec<u8> = Vec::new();
253
254    buf.extend_from_slice(&entry_point.to_le_bytes());
255    buf.extend_from_slice(&meta.non_volatile_global_count.to_le_bytes());
256    buf.extend_from_slice(&meta.volatile_global_count.to_le_bytes());
257    buf.push(meta.game_mode);
258    buf.push(meta.game_mode_reserved);
259
260    let title_b = enc_cstr(meta, &meta.game_title)?;
261    buf.push(title_b.len() as u8);
262    buf.extend_from_slice(&title_b);
263
264    let sc_count = meta.syscall_count();
265    buf.extend_from_slice(&sc_count.to_le_bytes());
266
267    for sc in &meta.syscalls {
268        let name_b = enc_cstr(meta, &sc.name)?;
269        buf.push(sc.args);
270        buf.push(name_b.len() as u8);
271        buf.extend_from_slice(&name_b);
272    }
273
274    buf.extend_from_slice(&meta.custom_syscall_count.to_le_bytes());
275
276    Ok(buf)
277}