luna_core/vm/isa.rs
1//! Instruction set: u32 instructions with the PUC 5.5 field layout
2//! (op 7 | A 8 | k 1 | B 8 | C 8, plus Bx/sBx/Ax/sJ variants). The opcode
3//! set follows lopcodes.h (v5.5.0) with deliberate v1 trims recorded in the
4//! P03 plan: no K-/immediate-arith variants and no MMBIN* (metamethod
5//! fallback is handled inline by the Rust dispatch loop); they return in the
6//! P10 ceiling pass if profiles ask for them.
7
8/// Opcode kinds for the luna bytecode. Layout follows PUC `lopcodes.h`
9/// (5.5.0); semantics may differ where noted in the dispatcher.
10#[derive(Clone, Copy, PartialEq, Eq, Debug)]
11#[repr(u8)]
12pub enum Op {
13 /// `R[A] := R[B]` register move.
14 Move,
15 /// `R[A] := sBx` load immediate integer.
16 LoadI,
17 /// `R[A] := (lua_Number)sBx` load immediate float.
18 LoadF,
19 /// `R[A] := K[Bx]` load constant.
20 LoadK,
21 /// `R[A] := K[extra_arg]` load constant with extended index (next op
22 /// must be `ExtraArg`).
23 LoadKx,
24 /// `R[A] := false`.
25 LoadFalse,
26 /// `R[A] := false; pc++` load false and skip next instruction.
27 LFalseSkip,
28 /// `R[A] := true`.
29 LoadTrue,
30 /// `R[A..A+B] := nil` clear a register range.
31 LoadNil,
32 /// `R[A] := Upvalues[B]`.
33 GetUpval,
34 /// `Upvalues[B] := R[A]`.
35 SetUpval,
36 /// `R[A] := Upvalues[B][K[C]:string]` global-style table read on an
37 /// upvalue.
38 GetTabUp,
39 /// `R[A] := R[B][R[C]]`.
40 GetTable,
41 /// `R[A] := R[B][C:int]` integer-indexed read.
42 GetI,
43 /// `R[A] := R[B][K[C]:string]` field read with constant key.
44 GetField,
45 /// `Upvalues[A][K[B]:string] := R[C]/K[C]`.
46 SetTabUp,
47 /// `R[A][R[B]] := R[C]/K[C]`.
48 SetTable,
49 /// `R[A][B:int] := R[C]/K[C]` integer-indexed write.
50 SetI,
51 /// `R[A][K[B]:string] := R[C]/K[C]` field write.
52 SetField,
53 /// `R[A] := {}` allocate a new table; B/C carry size hints.
54 NewTable,
55 /// `R[A+1] := R[B]; R[A] := R[B][K[C]:string]` self-method prep for
56 /// `obj:m(...)`.
57 SelfOp,
58 /// `R[A] := R[B] + R[C]/K[C]`.
59 Add,
60 /// `R[A] := R[B] - R[C]/K[C]`.
61 Sub,
62 /// `R[A] := R[B] * R[C]/K[C]`.
63 Mul,
64 /// `R[A] := R[B] % R[C]/K[C]`.
65 Mod,
66 /// `R[A] := R[B] ^ R[C]/K[C]`.
67 Pow,
68 /// `R[A] := R[B] / R[C]/K[C]`.
69 Div,
70 /// `R[A] := R[B] // R[C]/K[C]`.
71 IDiv,
72 /// `R[A] := R[B] & R[C]/K[C]`.
73 BAnd,
74 /// `R[A] := R[B] | R[C]/K[C]`.
75 BOr,
76 /// `R[A] := R[B] ~ R[C]/K[C]`.
77 BXor,
78 /// `R[A] := R[B] << R[C]/K[C]`.
79 Shl,
80 /// `R[A] := R[B] >> R[C]/K[C]`.
81 Shr,
82 /// `R[A] := -R[B]` arithmetic negation.
83 Unm,
84 /// `R[A] := ~R[B]` bitwise NOT.
85 BNot,
86 /// `R[A] := not R[B]`.
87 Not,
88 /// `R[A] := #R[B]` length operator.
89 Len,
90 /// `R[A] := R[A] .. ... .. R[A+B-1]` string concatenation chain.
91 Concat,
92 /// Close upvalues in scope `A` (closes pending `<close>` and upvalues).
93 Close,
94 /// Mark to-be-closed slot `A` (5.4).
95 Tbc,
96 /// `pc += sJ` unconditional jump.
97 Jmp,
98 /// Equality comparison with optional skip.
99 Eq,
100 /// Less-than comparison with optional skip.
101 Lt,
102 /// Less-or-equal comparison with optional skip.
103 Le,
104 /// Equality against a constant.
105 EqK,
106 /// `if (not R[A]) == k then pc++`.
107 Test,
108 /// `if (not R[B]) == k then pc++ else R[A] := R[B]`.
109 TestSet,
110 /// `R[A], ..., R[A+C-2] := R[A](R[A+1], ..., R[A+B-1])`.
111 Call,
112 /// Tail call (same register/return contract as `Call`).
113 TailCall,
114 /// `return R[A], ..., R[A+B-2]`.
115 Return,
116 /// `return` with no values.
117 Return0,
118 /// `return R[A]` single-value return.
119 Return1,
120 /// Numeric-for iteration step.
121 ForLoop,
122 /// Numeric-for prepare (validates types, normalizes step).
123 ForPrep,
124 /// Generic-for prepare.
125 TForPrep,
126 /// Generic-for call: invoke iterator once.
127 TForCall,
128 /// Generic-for loop tail (branch back if iterator returned non-nil).
129 TForLoop,
130 /// Bulk-store a sequence into a table (table constructor).
131 SetList,
132 /// `R[A] := closure(KPROTO[Bx])`.
133 Closure,
134 /// `R[A], R[A+1], ..., R[A+C-2] := vararg`.
135 Vararg,
136 /// 5.5: materialize the vararg table into `R[A]` (named vararg that is
137 /// written / escapes / is `_ENV`). Builds it from the stack varargs.
138 GetVarg,
139 /// 5.5: `R[A] := vararg[R[C]]` — index the *virtual* named vararg without
140 /// allocating a table. Integer key in `[1,n]` → that vararg, key `"n"` →
141 /// the count, else nil (PUC OP_GETVARG on an unmaterialized vararg).
142 VargIdx,
143 /// 5.5: error if `R[A]` is not nil — a defining `global` write whose target
144 /// already exists. Bx is the name constant index + 1 (0 ⇒ unknown name).
145 ErrNNil,
146 /// Extended-immediate payload for the preceding instruction (see
147 /// `LoadKx`).
148 ExtraArg,
149}
150
151/// Total number of opcodes defined in [`Op`].
152pub const NUM_OPS: usize = Op::ExtraArg as usize + 1;
153
154/// One encoded instruction.
155#[derive(Clone, Copy, PartialEq, Eq)]
156pub struct Inst(
157 /// The 32-bit packed instruction word; layout depends on the
158 /// instruction format (iABC / iABx / iAsBx / iAx / isJ).
159 pub u32,
160);
161
162const POS_A: u32 = 7;
163const POS_K: u32 = 15;
164const POS_B: u32 = 16;
165const POS_C: u32 = 24;
166const POS_BX: u32 = 15;
167
168/// Maximum value encodable in the `A` field.
169pub const MAX_A: u32 = 0xFF;
170/// Maximum value encodable in the `B` field.
171pub const MAX_B: u32 = 0xFF;
172/// Maximum value encodable in the `C` field.
173pub const MAX_C: u32 = 0xFF;
174/// Maximum value encodable in the `Bx` field.
175pub const MAX_BX: u32 = (1 << 17) - 1;
176/// Maximum value encodable in the signed `sBx` field (bias = MAX_BX/2).
177pub const MAX_SBX: i32 = (MAX_BX >> 1) as i32; // 65535
178/// Maximum value encodable in the `Ax` field.
179pub const MAX_AX: u32 = (1 << 25) - 1;
180/// Maximum magnitude encodable in the signed `sJ` (jump offset) field.
181pub const MAX_SJ: i32 = ((1u32 << 24) - 1) as i32; // sJ stored with this offset
182
183impl Inst {
184 /// Build an iABC-format instruction (`A`, `B`, `C`, `k` flag).
185 pub fn iabc(op: Op, a: u32, b: u32, c: u32, k: bool) -> Inst {
186 debug_assert!(a <= MAX_A && b <= MAX_B && c <= MAX_C);
187 Inst(op as u32 | (a << POS_A) | ((k as u32) << POS_K) | (b << POS_B) | (c << POS_C))
188 }
189
190 /// Build an iABx-format instruction (`A`, unsigned `Bx`).
191 pub fn iabx(op: Op, a: u32, bx: u32) -> Inst {
192 debug_assert!(a <= MAX_A && bx <= MAX_BX);
193 Inst(op as u32 | (a << POS_A) | (bx << POS_BX))
194 }
195
196 /// Build an iAsBx-format instruction (`A`, signed `sBx`).
197 pub fn iasbx(op: Op, a: u32, sbx: i32) -> Inst {
198 debug_assert!((-MAX_SBX..=MAX_SBX).contains(&sbx));
199 Inst::iabx(op, a, (sbx + MAX_SBX) as u32)
200 }
201
202 /// Build an iAx-format instruction (unsigned 25-bit `Ax`).
203 pub fn iax(op: Op, ax: u32) -> Inst {
204 debug_assert!(ax <= MAX_AX);
205 Inst(op as u32 | (ax << POS_A))
206 }
207
208 /// Build an isJ-format instruction (signed jump offset `sJ`).
209 pub fn isj(op: Op, sj: i32) -> Inst {
210 debug_assert!((-MAX_SJ..=MAX_SJ).contains(&sj));
211 Inst::iax(op, (sj + MAX_SJ) as u32)
212 }
213
214 /// Decode the opcode field.
215 #[inline(always)]
216 pub fn op(self) -> Op {
217 let raw = (self.0 & 0x7F) as u8;
218 debug_assert!((raw as usize) < NUM_OPS, "corrupt opcode {raw}");
219 // SAFETY: instructions are only built via the constructors above with
220 // a valid Op; Op is repr(u8) and dense from 0..NUM_OPS.
221 unsafe { std::mem::transmute::<u8, Op>(raw) }
222 }
223
224 /// Decode the `A` field.
225 #[inline(always)]
226 pub fn a(self) -> u32 {
227 (self.0 >> POS_A) & 0xFF
228 }
229
230 /// Decode the `k` flag (constant-vs-register selector for some ops).
231 #[inline(always)]
232 pub fn k(self) -> bool {
233 (self.0 >> POS_K) & 1 != 0
234 }
235
236 /// Decode the `B` field.
237 #[inline(always)]
238 pub fn b(self) -> u32 {
239 (self.0 >> POS_B) & 0xFF
240 }
241
242 /// Decode the `C` field.
243 #[inline(always)]
244 pub fn c(self) -> u32 {
245 self.0 >> POS_C
246 }
247
248 /// Decode the unsigned `Bx` field.
249 #[inline(always)]
250 pub fn bx(self) -> u32 {
251 self.0 >> POS_BX
252 }
253
254 /// Decode the signed `sBx` field.
255 #[inline(always)]
256 pub fn sbx(self) -> i32 {
257 self.bx() as i32 - MAX_SBX
258 }
259
260 /// Decode the unsigned `Ax` field.
261 #[inline(always)]
262 pub fn ax(self) -> u32 {
263 self.0 >> POS_A
264 }
265
266 /// Decode the signed jump offset `sJ`.
267 #[inline(always)]
268 pub fn sj(self) -> i32 {
269 self.ax() as i32 - MAX_SJ
270 }
271
272 /// Patch the sJ field of a jump (forward-jump backfill).
273 pub fn set_sj(&mut self, sj: i32) {
274 debug_assert!((-MAX_SJ..=MAX_SJ).contains(&sj));
275 self.0 = (self.0 & 0x7F) | (((sj + MAX_SJ) as u32) << POS_A);
276 }
277}
278
279impl std::fmt::Debug for Inst {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 write!(
282 f,
283 "{:?} a={} b={} c={} k={}",
284 self.op(),
285 self.a(),
286 self.b(),
287 self.c(),
288 self.k()
289 )
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn roundtrip_fields() {
299 let i = Inst::iabc(Op::GetField, 200, 17, 255, true);
300 assert_eq!(i.op(), Op::GetField);
301 assert_eq!(i.a(), 200);
302 assert_eq!(i.b(), 17);
303 assert_eq!(i.c(), 255);
304 assert!(i.k());
305
306 let j = Inst::iasbx(Op::LoadI, 3, -42);
307 assert_eq!(j.op(), Op::LoadI);
308 assert_eq!(j.a(), 3);
309 assert_eq!(j.sbx(), -42);
310
311 let mut k = Inst::isj(Op::Jmp, -1);
312 assert_eq!(k.sj(), -1);
313 k.set_sj(12345);
314 assert_eq!(k.op(), Op::Jmp);
315 assert_eq!(k.sj(), 12345);
316
317 let x = Inst::iax(Op::ExtraArg, MAX_AX);
318 assert_eq!(x.ax(), MAX_AX);
319 }
320}