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 => 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 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 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}