Skip to main content

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        // RFVP/HCB's historical enum names are swapped for these two opcodes:
71        //   0x25 (Opcode::SetLE) executes a >= b
72        //   0x27 (Opcode::SetGE) executes a <= b
73        // OpKind names represent source-language semantics, so encode them by
74        // behavior rather than by the legacy enum spelling.
75        OpKind::SetLe => 0x27,
76        OpKind::SetL => 0x26,
77        OpKind::SetGe => 0x25,
78    }
79}
80
81fn op_size(meta: &Meta, k: &OpKind) -> Result<usize> {
82    let sz = match k {
83        OpKind::Nop
84        | OpKind::Ret
85        | OpKind::Retv
86        | OpKind::PushNil
87        | OpKind::PushTrue
88        | OpKind::PushTop
89        | OpKind::PushReturn
90        | OpKind::Neg
91        | OpKind::Add
92        | OpKind::Sub
93        | OpKind::Mul
94        | OpKind::Div
95        | OpKind::Mod
96        | OpKind::BitTest
97        | OpKind::And
98        | OpKind::Or
99        | OpKind::SetE
100        | OpKind::SetNe
101        | OpKind::SetG
102        | OpKind::SetLe
103        | OpKind::SetL
104        | OpKind::SetGe => 1,
105
106        OpKind::InitStack { .. } => 3,
107        OpKind::CallFn { .. } => 5,
108        OpKind::Syscall { .. } => 3,
109        OpKind::JmpAbs { .. }
110        | OpKind::JzAbs { .. }
111        | OpKind::JmpLabel { .. }
112        | OpKind::JzLabel { .. } => 5,
113
114        OpKind::PushI8(..) => 2,
115        OpKind::PushI16(..) => 3,
116        OpKind::PushI32(..) => 5,
117        OpKind::PushF32(..) => 5,
118        OpKind::PushString(s) => {
119            let b = enc_cstr(meta, s)?;
120            1 + 1 + b.len()
121        }
122
123        OpKind::PushGlobal(..)
124        | OpKind::PushGlobalTable(..)
125        | OpKind::PopGlobal(..)
126        | OpKind::PopGlobalTable(..) => 3,
127
128        OpKind::PushStack(..)
129        | OpKind::PopStack(..)
130        | OpKind::PushLocalTable(..)
131        | OpKind::PopLocalTable(..) => 2,
132    };
133    Ok(sz)
134}
135
136pub fn assemble(meta: &Meta, items: &[Item]) -> Result<(Vec<u8>, HashMap<String, u32>)> {
137    let base_addr: u32 = 4;
138
139    // First pass: compute label addresses.
140    let mut labels: HashMap<String, u32> = HashMap::new();
141    let mut addr: u32 = base_addr;
142    for it in items {
143        match it {
144            Item::Label(l) => {
145                labels.insert(l.name.clone(), addr);
146            }
147            Item::Op(op) => {
148                let sz = op_size(meta, op)? as u32;
149                addr = addr
150                    .checked_add(sz)
151                    .ok_or_else(|| anyhow!("address overflow"))?;
152            }
153        }
154    }
155
156    // Second pass: encode.
157    let mut out: Vec<u8> = Vec::new();
158    for it in items {
159        let op = match it {
160            Item::Label(_) => continue,
161            Item::Op(op) => op,
162        };
163
164        out.push(opcode(op));
165
166        match op {
167            OpKind::Nop
168            | OpKind::Ret
169            | OpKind::Retv
170            | OpKind::PushNil
171            | OpKind::PushTrue
172            | OpKind::PushTop
173            | OpKind::PushReturn
174            | OpKind::Neg
175            | OpKind::Add
176            | OpKind::Sub
177            | OpKind::Mul
178            | OpKind::Div
179            | OpKind::Mod
180            | OpKind::BitTest
181            | OpKind::And
182            | OpKind::Or
183            | OpKind::SetE
184            | OpKind::SetNe
185            | OpKind::SetG
186            | OpKind::SetLe
187            | OpKind::SetL
188            | OpKind::SetGe => {}
189
190            OpKind::InitStack { args, locals } => {
191                out.push(*args as u8);
192                out.push(*locals as u8);
193            }
194
195            OpKind::CallFn { name } => {
196                let lbl = format!("fn:{name}");
197                let tgt = labels
198                    .get(&lbl)
199                    .copied()
200                    .ok_or_else(|| anyhow!("unknown function label: {lbl}"))?;
201                out.extend_from_slice(&tgt.to_le_bytes());
202            }
203
204            OpKind::Syscall { id } => {
205                out.extend_from_slice(&id.to_le_bytes());
206            }
207
208            OpKind::JmpAbs { target } => {
209                out.extend_from_slice(&target.to_le_bytes());
210            }
211            OpKind::JzAbs { target } => {
212                out.extend_from_slice(&target.to_le_bytes());
213            }
214
215            OpKind::JmpLabel { label } => {
216                let tgt = labels
217                    .get(label)
218                    .copied()
219                    .ok_or_else(|| anyhow!("unknown label: {label}"))?;
220                out.extend_from_slice(&tgt.to_le_bytes());
221            }
222            OpKind::JzLabel { label } => {
223                let tgt = labels
224                    .get(label)
225                    .copied()
226                    .ok_or_else(|| anyhow!("unknown label: {label}"))?;
227                out.extend_from_slice(&tgt.to_le_bytes());
228            }
229
230            OpKind::PushI8(v) => out.push(*v as u8),
231            OpKind::PushI16(v) => out.extend_from_slice(&v.to_le_bytes()),
232            OpKind::PushI32(v) => out.extend_from_slice(&v.to_le_bytes()),
233            OpKind::PushF32(v) => out.extend_from_slice(&v.to_le_bytes()),
234            OpKind::PushString(s) => {
235                let b = enc_cstr(meta, s)?;
236                out.push(b.len() as u8);
237                out.extend_from_slice(&b);
238            }
239
240            OpKind::PushGlobal(idx)
241            | OpKind::PushGlobalTable(idx)
242            | OpKind::PopGlobal(idx)
243            | OpKind::PopGlobalTable(idx) => {
244                out.extend_from_slice(&idx.to_le_bytes());
245            }
246
247            OpKind::PushStack(idx)
248            | OpKind::PopStack(idx)
249            | OpKind::PushLocalTable(idx)
250            | OpKind::PopLocalTable(idx) => {
251                out.push(*idx as u8);
252            }
253        }
254    }
255
256    Ok((out, labels))
257}
258
259pub fn build_sysdesc(meta: &Meta, entry_point: u32) -> Result<Vec<u8>> {
260    let mut buf: Vec<u8> = Vec::new();
261
262    buf.extend_from_slice(&entry_point.to_le_bytes());
263    buf.extend_from_slice(&meta.non_volatile_global_count.to_le_bytes());
264    buf.extend_from_slice(&meta.volatile_global_count.to_le_bytes());
265    buf.push(meta.game_mode);
266    buf.push(meta.game_mode_reserved);
267
268    let title_b = enc_cstr(meta, &meta.game_title)?;
269    buf.push(title_b.len() as u8);
270    buf.extend_from_slice(&title_b);
271
272    let sc_count = meta.syscall_count();
273    buf.extend_from_slice(&sc_count.to_le_bytes());
274
275    for sc in &meta.syscalls {
276        let name_b = enc_cstr(meta, &sc.name)?;
277        buf.push(sc.args);
278        buf.push(name_b.len() as u8);
279        buf.extend_from_slice(&name_b);
280    }
281
282    buf.extend_from_slice(&meta.custom_syscall_count.to_le_bytes());
283
284    Ok(buf)
285}
286
287
288#[cfg(test)]
289mod tests {
290    use super::opcode;
291    use crate::ir::OpKind;
292
293    #[test]
294    fn comparison_opcodes_follow_rfvp_engine_semantics() {
295        assert_eq!(opcode(&OpKind::SetG), 0x24);
296        assert_eq!(opcode(&OpKind::SetGe), 0x25);
297        assert_eq!(opcode(&OpKind::SetL), 0x26);
298        assert_eq!(opcode(&OpKind::SetLe), 0x27);
299    }
300}