Skip to main content

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}