Skip to main content

lua_code/
opcodes.rs

1//! Opcode definitions and instruction encoding/decoding for the Lua 5.4 VM.
2//!
3//! Ports `src/lopcodes.c` (the `luaP_opmodes` table) and `src/lopcodes.h`
4//! (the `OpCode`/`OpMode` enums, field-size constants, and instruction
5//! accessor macros). Per PORTING.md §1, headers merge into their consuming
6//! `.rs`.
7//!
8//! C source preserved inline as `// C:` comments for diff-time review.
9
10// C: /* $Id: lopcodes.c $ */
11// C: /* $Id: lopcodes.h $ */
12
13// ─── Instruction format diagram ──────────────────────────────────────────────
14//
15// C: /*
16// C:   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
17// C:   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
18// C: iABC    C(8)     |      B(8)     |k|     A(8)      |   Op(7)     |
19// C: iABx          Bx(17)               |     A(8)      |   Op(7)     |
20// C: iAsBx        sBx (signed)(17)      |     A(8)      |   Op(7)     |
21// C: iAx                     Ax(25)                     |   Op(7)     |
22// C: isJ                     sJ (signed)(25)            |   Op(7)     |
23// C: */
24
25// ─── OpMode ──────────────────────────────────────────────────────────────────
26
27/// Instruction addressing mode.
28///
29/// C: `enum OpMode { iABC, iABx, iAsBx, iAx, isJ };`
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[repr(u8)]
32pub enum OpMode {
33    /// C: `iABC`  — A(8), B(8), C(8), k(1)
34    Abc = 0,
35    /// C: `iABx`  — A(8), Bx(17)
36    ABx = 1,
37    /// C: `iAsBx` — A(8), sBx signed(17)
38    AsBx = 2,
39    /// C: `iAx`   — Ax(25)
40    Ax = 3,
41    /// C: `isJ`   — sJ signed(25)
42    SJ = 4,
43}
44
45// ─── Field size constants ─────────────────────────────────────────────────────
46//
47// C: #define SIZE_C   8
48// C: #define SIZE_B   8
49// C: #define SIZE_Bx  (SIZE_C + SIZE_B + 1)
50// C: #define SIZE_A   8
51// C: #define SIZE_Ax  (SIZE_Bx + SIZE_A)
52// C: #define SIZE_sJ  (SIZE_Bx + SIZE_A)
53// C: #define SIZE_OP  7
54
55pub const SIZE_C: u32 = 8;
56pub const SIZE_B: u32 = 8;
57pub const SIZE_BX: u32 = SIZE_C + SIZE_B + 1;
58pub const SIZE_A: u32 = 8;
59pub const SIZE_AX: u32 = SIZE_BX + SIZE_A;
60pub const SIZE_S_J: u32 = SIZE_BX + SIZE_A;
61pub const SIZE_OP: u32 = 7;
62
63// ─── Field position constants ─────────────────────────────────────────────────
64//
65// C: #define POS_OP  0
66// C: #define POS_A   (POS_OP + SIZE_OP)
67// C: #define POS_k   (POS_A + SIZE_A)
68// C: #define POS_B   (POS_k + 1)
69// C: #define POS_C   (POS_B + SIZE_B)
70// C: #define POS_Bx  POS_k
71// C: #define POS_Ax  POS_A
72// C: #define POS_sJ  POS_A
73
74pub const POS_OP: u32 = 0;
75pub const POS_A: u32 = POS_OP + SIZE_OP;
76pub const POS_K: u32 = POS_A + SIZE_A;
77pub const POS_B: u32 = POS_K + 1;
78pub const POS_C: u32 = POS_B + SIZE_B;
79pub const POS_BX: u32 = POS_K;
80pub const POS_AX: u32 = POS_A;
81pub const POS_S_J: u32 = POS_A;
82
83// ─── Argument limit constants ─────────────────────────────────────────────────
84//
85// C: #define MAXARG_Bx     ((1<<SIZE_Bx)-1)
86// C: #define OFFSET_sBx    (MAXARG_Bx>>1)
87// C: #define MAXARG_Ax     ((1<<SIZE_Ax)-1)
88// C: #define MAXARG_sJ     ((1<<SIZE_sJ)-1)
89// C: #define OFFSET_sJ     (MAXARG_sJ>>1)
90// C: #define MAXARG_A      ((1<<SIZE_A)-1)
91// C: #define MAXARG_B      ((1<<SIZE_B)-1)
92// C: #define MAXARG_C      ((1<<SIZE_C)-1)
93// C: #define OFFSET_sC     (MAXARG_C>>1)
94
95pub const MAXARG_BX: u32 = (1u32 << SIZE_BX) - 1;
96pub const OFFSET_S_BX: i32 = (MAXARG_BX >> 1) as i32;
97pub const MAXARG_AX: u32 = (1u32 << SIZE_AX) - 1;
98pub const MAXARG_S_J: u32 = (1u32 << SIZE_S_J) - 1;
99pub const OFFSET_S_J: i32 = (MAXARG_S_J >> 1) as i32;
100pub const MAXARG_A: u32 = (1u32 << SIZE_A) - 1;
101pub const MAXARG_B: u32 = (1u32 << SIZE_B) - 1;
102pub const MAXARG_C: u32 = (1u32 << SIZE_C) - 1;
103pub const OFFSET_S_C: i32 = (MAXARG_C >> 1) as i32;
104
105/// Sentinel "no register" value that fits in 8 bits.
106///
107/// C: `#define NO_REG  MAXARG_A`
108pub const NO_REG: u32 = MAXARG_A;
109
110/// Maximum RK index (for debugging only).
111///
112/// C: `#define MAXINDEXRK  MAXARG_B`
113pub const MAXINDEXRK: u32 = MAXARG_B;
114
115/// Number of list items to accumulate before a SETLIST instruction.
116///
117/// C: `#define LFIELDS_PER_FLUSH  50`
118pub const LFIELDS_PER_FLUSH: u32 = 50;
119
120// ─── OpCode enum ─────────────────────────────────────────────────────────────
121//
122// C: /* Grep "ORDER OP" if you change these enums. */
123// C: typedef enum { OP_MOVE, ..., OP_EXTRAARG } OpCode;
124//
125// ORDER OP — variant discriminants must match `lopcodes.h` exactly.
126// The VM casts the raw opcode field directly to this enum.
127
128/// All opcodes for the Lua 5.4 virtual machine.
129///
130/// ORDER OP — must match `lopcodes.h` exactly.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
132#[repr(u8)]
133pub enum OpCode {
134    // C: OP_MOVE,/*  A B    R[A] := R[B]  */
135    Move = 0,
136    // C: OP_LOADI,/* A sBx  R[A] := sBx  */
137    LoadI,
138    // C: OP_LOADF,/* A sBx  R[A] := (lua_Number)sBx  */
139    LoadF,
140    // C: OP_LOADK,/* A Bx   R[A] := K[Bx]  */
141    LoadK,
142    // C: OP_LOADKX,/* A     R[A] := K[extra arg]  */
143    LoadKX,
144    // C: OP_LOADFALSE,/* A  R[A] := false  */
145    LoadFalse,
146    // C: OP_LFALSESKIP,/* A R[A] := false; pc++  */
147    LFalseSkip,
148    // C: OP_LOADTRUE,/* A   R[A] := true  */
149    LoadTrue,
150    // C: OP_LOADNIL,/* A B  R[A], R[A+1], ..., R[A+B] := nil  */
151    LoadNil,
152    // C: OP_GETUPVAL,/* A B R[A] := UpValue[B]  */
153    GetUpVal,
154    // C: OP_SETUPVAL,/* A B UpValue[B] := R[A]  */
155    SetUpVal,
156
157    // C: OP_GETTABUP,/* A B C  R[A] := UpValue[B][K[C]:shortstring]  */
158    GetTabUp,
159    // C: OP_GETTABLE,/* A B C  R[A] := R[B][R[C]]  */
160    GetTable,
161    // C: OP_GETI,/*    A B C  R[A] := R[B][C]  */
162    GetI,
163    // C: OP_GETFIELD,/* A B C  R[A] := R[B][K[C]:shortstring]  */
164    GetField,
165
166    // C: OP_SETTABUP,/* A B C  UpValue[A][K[B]:shortstring] := RK(C)  */
167    SetTabUp,
168    // C: OP_SETTABLE,/* A B C  R[A][R[B]] := RK(C)  */
169    SetTable,
170    // C: OP_SETI,/*    A B C  R[A][B] := RK(C)  */
171    SetI,
172    // C: OP_SETFIELD,/* A B C  R[A][K[B]:shortstring] := RK(C)  */
173    SetField,
174
175    // C: OP_NEWTABLE,/* A B C k  R[A] := {}  */
176    NewTable,
177
178    // C: OP_SELF,/* A B C  R[A+1] := R[B]; R[A] := R[B][RK(C):string]  */
179    // PORT NOTE: `self` is a Rust keyword; renamed to `Self_`.
180    Self_,
181
182    // C: OP_ADDI,/* A B sC  R[A] := R[B] + sC  */
183    AddI,
184
185    // C: OP_ADDK,/* A B C  R[A] := R[B] + K[C]:number  */
186    AddK,
187    // C: OP_SUBK,/* A B C  R[A] := R[B] - K[C]:number  */
188    SubK,
189    // C: OP_MULK,/* A B C  R[A] := R[B] * K[C]:number  */
190    MulK,
191    // C: OP_MODK,/* A B C  R[A] := R[B] % K[C]:number  */
192    ModK,
193    // C: OP_POWK,/* A B C  R[A] := R[B] ^ K[C]:number  */
194    PowK,
195    // C: OP_DIVK,/* A B C  R[A] := R[B] / K[C]:number  */
196    DivK,
197    // C: OP_IDIVK,/* A B C  R[A] := R[B] // K[C]:number  */
198    IDivK,
199
200    // C: OP_BANDK,/* A B C  R[A] := R[B] & K[C]:integer  */
201    BAndK,
202    // C: OP_BORK,/*  A B C  R[A] := R[B] | K[C]:integer  */
203    BOrK,
204    // C: OP_BXORK,/* A B C  R[A] := R[B] ~ K[C]:integer  */
205    BXorK,
206
207    // C: OP_SHRI,/* A B sC  R[A] := R[B] >> sC  */
208    ShrI,
209    // C: OP_SHLI,/* A B sC  R[A] := sC << R[B]  */
210    ShlI,
211
212    // C: OP_ADD,/*  A B C  R[A] := R[B] + R[C]  */
213    Add,
214    // C: OP_SUB,/*  A B C  R[A] := R[B] - R[C]  */
215    Sub,
216    // C: OP_MUL,/*  A B C  R[A] := R[B] * R[C]  */
217    Mul,
218    // C: OP_MOD,/*  A B C  R[A] := R[B] % R[C]  */
219    Mod,
220    // C: OP_POW,/*  A B C  R[A] := R[B] ^ R[C]  */
221    Pow,
222    // C: OP_DIV,/*  A B C  R[A] := R[B] / R[C]  */
223    Div,
224    // C: OP_IDIV,/* A B C  R[A] := R[B] // R[C]  */
225    IDiv,
226
227    // C: OP_BAND,/* A B C  R[A] := R[B] & R[C]  */
228    BAnd,
229    // C: OP_BOR,/*  A B C  R[A] := R[B] | R[C]  */
230    BOr,
231    // C: OP_BXOR,/* A B C  R[A] := R[B] ~ R[C]  */
232    BXor,
233    // C: OP_SHL,/*  A B C  R[A] := R[B] << R[C]  */
234    Shl,
235    // C: OP_SHR,/*  A B C  R[A] := R[B] >> R[C]  */
236    Shr,
237
238    // C: OP_MMBIN,/*  A B C    call C metamethod over R[A] and R[B]  */
239    MmBin,
240    // C: OP_MMBINI,/* A sB C k call C metamethod over R[A] and sB  */
241    MmBinI,
242    // C: OP_MMBINK,/* A B C k  call C metamethod over R[A] and K[B]  */
243    MmBinK,
244
245    // C: OP_UNM,/*    A B  R[A] := -R[B]  */
246    Unm,
247    // C: OP_BNOT,/*   A B  R[A] := ~R[B]  */
248    BNot,
249    // C: OP_NOT,/*    A B  R[A] := not R[B]  */
250    Not,
251    // C: OP_LEN,/*    A B  R[A] := #R[B]  */
252    Len,
253
254    // C: OP_CONCAT,/* A B  R[A] := R[A].. ... ..R[A + B - 1]  */
255    Concat,
256
257    // C: OP_CLOSE,/* A  close all upvalues >= R[A]  */
258    Close,
259    // C: OP_TBC,/*   A  mark variable A "to be closed"  */
260    Tbc,
261    // C: OP_JMP,/*   sJ  pc += sJ  */
262    Jmp,
263
264    // C: OP_EQ,/* A B k  if ((R[A] == R[B]) ~= k) then pc++  */
265    Eq,
266    // C: OP_LT,/* A B k  if ((R[A] <  R[B]) ~= k) then pc++  */
267    Lt,
268    // C: OP_LE,/* A B k  if ((R[A] <= R[B]) ~= k) then pc++  */
269    Le,
270
271    // C: OP_EQK,/* A B k   if ((R[A] == K[B]) ~= k) then pc++  */
272    EqK,
273    // C: OP_EQI,/* A sB k  if ((R[A] == sB) ~= k) then pc++  */
274    EqI,
275    // C: OP_LTI,/* A sB k  if ((R[A] < sB) ~= k) then pc++  */
276    LtI,
277    // C: OP_LEI,/* A sB k  if ((R[A] <= sB) ~= k) then pc++  */
278    LeI,
279    // C: OP_GTI,/* A sB k  if ((R[A] > sB) ~= k) then pc++  */
280    GtI,
281    // C: OP_GEI,/* A sB k  if ((R[A] >= sB) ~= k) then pc++  */
282    GeI,
283
284    // C: OP_TEST,/*    A k    if (not R[A] == k) then pc++  */
285    Test,
286    // C: OP_TESTSET,/* A B k  if (not R[B] == k) then pc++ else R[A] := R[B]  */
287    TestSet,
288
289    // C: OP_CALL,/*     A B C    R[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1])  */
290    Call,
291    // C: OP_TAILCALL,/* A B C k  return R[A](R[A+1], ... ,R[A+B-1])  */
292    TailCall,
293
294    // C: OP_RETURN,/*  A B C k  return R[A], ... ,R[A+B-2]  */
295    Return,
296    // C: OP_RETURN0,/*           return  */
297    Return0,
298    // C: OP_RETURN1,/* A         return R[A]  */
299    Return1,
300
301    // C: OP_FORLOOP,/* A Bx  update counters; if loop continues then pc-=Bx  */
302    ForLoop,
303    // C: OP_FORPREP,/* A Bx  check values and prepare; if not to run then pc+=Bx+1  */
304    ForPrep,
305
306    // C: OP_TFORPREP,/* A Bx  create upvalue for R[A+3]; pc+=Bx  */
307    TForPrep,
308    // C: OP_TFORCALL,/* A C   R[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2])  */
309    TForCall,
310    // C: OP_TFORLOOP,/* A Bx  if R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx }  */
311    TForLoop,
312
313    // C: OP_SETLIST,/* A B C k  R[A][C+i] := R[A+i], 1 <= i <= B  */
314    SetList,
315
316    // C: OP_CLOSURE,/* A Bx  R[A] := closure(KPROTO[Bx])  */
317    Closure,
318
319    // C: OP_VARARG,/* A C  R[A], R[A+1], ..., R[A+C-2] = vararg  */
320    VarArg,
321
322    // C: OP_VARARGPREP,/* A  (adjust vararg parameters)  */
323    VarArgPrep,
324
325    // C: OP_EXTRAARG/* Ax  extra (larger) argument for previous opcode  */
326    ExtraArg,
327}
328
329/// Total number of opcodes.
330///
331/// C: `#define NUM_OPCODES ((int)(OP_EXTRAARG) + 1)`
332pub const NUM_OPCODES: usize = OpCode::ExtraArg as usize + 1;
333
334impl OpCode {
335    /// Convert a raw `u32` opcode field value to an `OpCode`.
336    ///
337    /// Returns `None` if `v >= NUM_OPCODES`.
338    ///
339    /// C: `GET_OPCODE(i)` — `cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))`
340    ///
341    /// TODO(port): replace explicit match with a safe transmute or `num_enum`
342    /// crate derive once Phase B settles the dependency policy. The match is
343    /// correct but mechanical; 83 arms is noise at compile-time and runtime.
344    pub fn from_u32(v: u32) -> Option<Self> {
345        match v {
346            0 => Some(Self::Move),
347            1 => Some(Self::LoadI),
348            2 => Some(Self::LoadF),
349            3 => Some(Self::LoadK),
350            4 => Some(Self::LoadKX),
351            5 => Some(Self::LoadFalse),
352            6 => Some(Self::LFalseSkip),
353            7 => Some(Self::LoadTrue),
354            8 => Some(Self::LoadNil),
355            9 => Some(Self::GetUpVal),
356            10 => Some(Self::SetUpVal),
357            11 => Some(Self::GetTabUp),
358            12 => Some(Self::GetTable),
359            13 => Some(Self::GetI),
360            14 => Some(Self::GetField),
361            15 => Some(Self::SetTabUp),
362            16 => Some(Self::SetTable),
363            17 => Some(Self::SetI),
364            18 => Some(Self::SetField),
365            19 => Some(Self::NewTable),
366            20 => Some(Self::Self_),
367            21 => Some(Self::AddI),
368            22 => Some(Self::AddK),
369            23 => Some(Self::SubK),
370            24 => Some(Self::MulK),
371            25 => Some(Self::ModK),
372            26 => Some(Self::PowK),
373            27 => Some(Self::DivK),
374            28 => Some(Self::IDivK),
375            29 => Some(Self::BAndK),
376            30 => Some(Self::BOrK),
377            31 => Some(Self::BXorK),
378            32 => Some(Self::ShrI),
379            33 => Some(Self::ShlI),
380            34 => Some(Self::Add),
381            35 => Some(Self::Sub),
382            36 => Some(Self::Mul),
383            37 => Some(Self::Mod),
384            38 => Some(Self::Pow),
385            39 => Some(Self::Div),
386            40 => Some(Self::IDiv),
387            41 => Some(Self::BAnd),
388            42 => Some(Self::BOr),
389            43 => Some(Self::BXor),
390            44 => Some(Self::Shl),
391            45 => Some(Self::Shr),
392            46 => Some(Self::MmBin),
393            47 => Some(Self::MmBinI),
394            48 => Some(Self::MmBinK),
395            49 => Some(Self::Unm),
396            50 => Some(Self::BNot),
397            51 => Some(Self::Not),
398            52 => Some(Self::Len),
399            53 => Some(Self::Concat),
400            54 => Some(Self::Close),
401            55 => Some(Self::Tbc),
402            56 => Some(Self::Jmp),
403            57 => Some(Self::Eq),
404            58 => Some(Self::Lt),
405            59 => Some(Self::Le),
406            60 => Some(Self::EqK),
407            61 => Some(Self::EqI),
408            62 => Some(Self::LtI),
409            63 => Some(Self::LeI),
410            64 => Some(Self::GtI),
411            65 => Some(Self::GeI),
412            66 => Some(Self::Test),
413            67 => Some(Self::TestSet),
414            68 => Some(Self::Call),
415            69 => Some(Self::TailCall),
416            70 => Some(Self::Return),
417            71 => Some(Self::Return0),
418            72 => Some(Self::Return1),
419            73 => Some(Self::ForLoop),
420            74 => Some(Self::ForPrep),
421            75 => Some(Self::TForPrep),
422            76 => Some(Self::TForCall),
423            77 => Some(Self::TForLoop),
424            78 => Some(Self::SetList),
425            79 => Some(Self::Closure),
426            80 => Some(Self::VarArg),
427            81 => Some(Self::VarArgPrep),
428            82 => Some(Self::ExtraArg),
429            _ => None,
430        }
431    }
432}
433
434// ─── opmode_byte helper ───────────────────────────────────────────────────────
435//
436// C: #define opmode(mm,ot,it,t,a,m)
437//        (((mm)<<7) | ((ot)<<6) | ((it)<<5) | ((t)<<4) | ((a)<<3) | (m))
438//
439// Bit layout for each entry in OP_MODES:
440//   bits 0-2: OpMode value (Abc=0 ABx=1 AsBx=2 Ax=3 SJ=4)
441//   bit 3:    instruction sets register A
442//   bit 4:    is a test (next instruction must be a jump)
443//   bit 5:    instruction uses L->top from previous (IT mode)
444//   bit 6:    instruction sets L->top for next (OT mode)
445//   bit 7:    is a metamethod instruction (MM)
446
447const fn opmode_byte(mm: u8, ot: u8, it: u8, t: u8, a: u8, m: u8) -> u8 {
448    (mm << 7) | (ot << 6) | (it << 5) | (t << 4) | (a << 3) | m
449}
450
451// Shorthand mode constants for the OP_MODES table below.
452const M_ABC: u8 = OpMode::Abc as u8;
453const M_ABX: u8 = OpMode::ABx as u8;
454const M_ASBX: u8 = OpMode::AsBx as u8;
455const M_AX: u8 = OpMode::Ax as u8;
456const M_SJ: u8 = OpMode::SJ as u8;
457
458// ─── OP_MODES table ───────────────────────────────────────────────────────────
459//
460// C: /* ORDER OP */
461// C: LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
462// C:   opmode(mm, ot, it, t, a, mode)  /* OP_XXX */
463// C:   ...
464// C: };
465//
466// Per macros.tsv: LUAI_DDEF → drop (definition site, no modifier needed in Rust).
467// Per macros.tsv: LUAI_DDEC → `pub(crate) static` at the declaration site.
468
469/// Opcode properties table, indexed by `OpCode as usize`.
470///
471/// C: `const lu_byte luaP_opmodes[NUM_OPCODES]`
472///
473/// Use `get_op_mode`, `test_a_mode`, etc. to query individual properties
474/// rather than indexing this array directly.
475pub(crate) const OP_MODES: [u8; NUM_OPCODES] = [
476    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_MOVE */
477    opmode_byte(0, 0, 0, 0, 1, M_ABC),
478    // C: opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADI */
479    opmode_byte(0, 0, 0, 0, 1, M_ASBX),
480    // C: opmode(0, 0, 0, 0, 1, iAsBx) /* OP_LOADF */
481    opmode_byte(0, 0, 0, 0, 1, M_ASBX),
482    // C: opmode(0, 0, 0, 0, 1, iABx)  /* OP_LOADK */
483    opmode_byte(0, 0, 0, 0, 1, M_ABX),
484    // C: opmode(0, 0, 0, 0, 1, iABx)  /* OP_LOADKX */
485    opmode_byte(0, 0, 0, 0, 1, M_ABX),
486    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_LOADFALSE */
487    opmode_byte(0, 0, 0, 0, 1, M_ABC),
488    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_LFALSESKIP */
489    opmode_byte(0, 0, 0, 0, 1, M_ABC),
490    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_LOADTRUE */
491    opmode_byte(0, 0, 0, 0, 1, M_ABC),
492    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_LOADNIL */
493    opmode_byte(0, 0, 0, 0, 1, M_ABC),
494    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_GETUPVAL */
495    opmode_byte(0, 0, 0, 0, 1, M_ABC),
496    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_SETUPVAL */
497    opmode_byte(0, 0, 0, 0, 0, M_ABC),
498    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_GETTABUP */
499    opmode_byte(0, 0, 0, 0, 1, M_ABC),
500    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_GETTABLE */
501    opmode_byte(0, 0, 0, 0, 1, M_ABC),
502    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_GETI */
503    opmode_byte(0, 0, 0, 0, 1, M_ABC),
504    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_GETFIELD */
505    opmode_byte(0, 0, 0, 0, 1, M_ABC),
506    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_SETTABUP */
507    opmode_byte(0, 0, 0, 0, 0, M_ABC),
508    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_SETTABLE */
509    opmode_byte(0, 0, 0, 0, 0, M_ABC),
510    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_SETI */
511    opmode_byte(0, 0, 0, 0, 0, M_ABC),
512    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_SETFIELD */
513    opmode_byte(0, 0, 0, 0, 0, M_ABC),
514    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_NEWTABLE */
515    opmode_byte(0, 0, 0, 0, 1, M_ABC),
516    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_SELF */
517    opmode_byte(0, 0, 0, 0, 1, M_ABC),
518    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_ADDI */
519    opmode_byte(0, 0, 0, 0, 1, M_ABC),
520    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_ADDK */
521    opmode_byte(0, 0, 0, 0, 1, M_ABC),
522    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_SUBK */
523    opmode_byte(0, 0, 0, 0, 1, M_ABC),
524    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_MULK */
525    opmode_byte(0, 0, 0, 0, 1, M_ABC),
526    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_MODK */
527    opmode_byte(0, 0, 0, 0, 1, M_ABC),
528    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_POWK */
529    opmode_byte(0, 0, 0, 0, 1, M_ABC),
530    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_DIVK */
531    opmode_byte(0, 0, 0, 0, 1, M_ABC),
532    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_IDIVK */
533    opmode_byte(0, 0, 0, 0, 1, M_ABC),
534    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_BANDK */
535    opmode_byte(0, 0, 0, 0, 1, M_ABC),
536    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_BORK */
537    opmode_byte(0, 0, 0, 0, 1, M_ABC),
538    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_BXORK */
539    opmode_byte(0, 0, 0, 0, 1, M_ABC),
540    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_SHRI */
541    opmode_byte(0, 0, 0, 0, 1, M_ABC),
542    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_SHLI */
543    opmode_byte(0, 0, 0, 0, 1, M_ABC),
544    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_ADD */
545    opmode_byte(0, 0, 0, 0, 1, M_ABC),
546    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_SUB */
547    opmode_byte(0, 0, 0, 0, 1, M_ABC),
548    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_MUL */
549    opmode_byte(0, 0, 0, 0, 1, M_ABC),
550    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_MOD */
551    opmode_byte(0, 0, 0, 0, 1, M_ABC),
552    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_POW */
553    opmode_byte(0, 0, 0, 0, 1, M_ABC),
554    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_DIV */
555    opmode_byte(0, 0, 0, 0, 1, M_ABC),
556    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_IDIV */
557    opmode_byte(0, 0, 0, 0, 1, M_ABC),
558    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_BAND */
559    opmode_byte(0, 0, 0, 0, 1, M_ABC),
560    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_BOR */
561    opmode_byte(0, 0, 0, 0, 1, M_ABC),
562    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_BXOR */
563    opmode_byte(0, 0, 0, 0, 1, M_ABC),
564    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_SHL */
565    opmode_byte(0, 0, 0, 0, 1, M_ABC),
566    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_SHR */
567    opmode_byte(0, 0, 0, 0, 1, M_ABC),
568    // C: opmode(1, 0, 0, 0, 0, iABC)  /* OP_MMBIN */
569    opmode_byte(1, 0, 0, 0, 0, M_ABC),
570    // C: opmode(1, 0, 0, 0, 0, iABC)  /* OP_MMBINI */
571    opmode_byte(1, 0, 0, 0, 0, M_ABC),
572    // C: opmode(1, 0, 0, 0, 0, iABC)  /* OP_MMBINK */
573    opmode_byte(1, 0, 0, 0, 0, M_ABC),
574    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_UNM */
575    opmode_byte(0, 0, 0, 0, 1, M_ABC),
576    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_BNOT */
577    opmode_byte(0, 0, 0, 0, 1, M_ABC),
578    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_NOT */
579    opmode_byte(0, 0, 0, 0, 1, M_ABC),
580    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_LEN */
581    opmode_byte(0, 0, 0, 0, 1, M_ABC),
582    // C: opmode(0, 0, 0, 0, 1, iABC)  /* OP_CONCAT */
583    opmode_byte(0, 0, 0, 0, 1, M_ABC),
584    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_CLOSE */
585    opmode_byte(0, 0, 0, 0, 0, M_ABC),
586    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_TBC */
587    opmode_byte(0, 0, 0, 0, 0, M_ABC),
588    // C: opmode(0, 0, 0, 0, 0, isJ)   /* OP_JMP */
589    opmode_byte(0, 0, 0, 0, 0, M_SJ),
590    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_EQ */
591    opmode_byte(0, 0, 0, 1, 0, M_ABC),
592    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_LT */
593    opmode_byte(0, 0, 0, 1, 0, M_ABC),
594    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_LE */
595    opmode_byte(0, 0, 0, 1, 0, M_ABC),
596    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_EQK */
597    opmode_byte(0, 0, 0, 1, 0, M_ABC),
598    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_EQI */
599    opmode_byte(0, 0, 0, 1, 0, M_ABC),
600    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_LTI */
601    opmode_byte(0, 0, 0, 1, 0, M_ABC),
602    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_LEI */
603    opmode_byte(0, 0, 0, 1, 0, M_ABC),
604    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_GTI */
605    opmode_byte(0, 0, 0, 1, 0, M_ABC),
606    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_GEI */
607    opmode_byte(0, 0, 0, 1, 0, M_ABC),
608    // C: opmode(0, 0, 0, 1, 0, iABC)  /* OP_TEST */
609    opmode_byte(0, 0, 0, 1, 0, M_ABC),
610    // C: opmode(0, 0, 0, 1, 1, iABC)  /* OP_TESTSET */
611    opmode_byte(0, 0, 0, 1, 1, M_ABC),
612    // C: opmode(0, 1, 1, 0, 1, iABC)  /* OP_CALL */
613    opmode_byte(0, 1, 1, 0, 1, M_ABC),
614    // C: opmode(0, 1, 1, 0, 1, iABC)  /* OP_TAILCALL */
615    opmode_byte(0, 1, 1, 0, 1, M_ABC),
616    // C: opmode(0, 0, 1, 0, 0, iABC)  /* OP_RETURN */
617    opmode_byte(0, 0, 1, 0, 0, M_ABC),
618    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_RETURN0 */
619    opmode_byte(0, 0, 0, 0, 0, M_ABC),
620    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_RETURN1 */
621    opmode_byte(0, 0, 0, 0, 0, M_ABC),
622    // C: opmode(0, 0, 0, 0, 1, iABx)  /* OP_FORLOOP */
623    opmode_byte(0, 0, 0, 0, 1, M_ABX),
624    // C: opmode(0, 0, 0, 0, 1, iABx)  /* OP_FORPREP */
625    opmode_byte(0, 0, 0, 0, 1, M_ABX),
626    // C: opmode(0, 0, 0, 0, 0, iABx)  /* OP_TFORPREP */
627    opmode_byte(0, 0, 0, 0, 0, M_ABX),
628    // C: opmode(0, 0, 0, 0, 0, iABC)  /* OP_TFORCALL */
629    opmode_byte(0, 0, 0, 0, 0, M_ABC),
630    // C: opmode(0, 0, 0, 0, 1, iABx)  /* OP_TFORLOOP */
631    opmode_byte(0, 0, 0, 0, 1, M_ABX),
632    // C: opmode(0, 0, 1, 0, 0, iABC)  /* OP_SETLIST */
633    opmode_byte(0, 0, 1, 0, 0, M_ABC),
634    // C: opmode(0, 0, 0, 0, 1, iABx)  /* OP_CLOSURE */
635    opmode_byte(0, 0, 0, 0, 1, M_ABX),
636    // C: opmode(0, 1, 0, 0, 1, iABC)  /* OP_VARARG */
637    opmode_byte(0, 1, 0, 0, 1, M_ABC),
638    // C: opmode(0, 0, 1, 0, 1, iABC)  /* OP_VARARGPREP */
639    opmode_byte(0, 0, 1, 0, 1, M_ABC),
640    // C: opmode(0, 0, 0, 0, 0, iAx)   /* OP_EXTRAARG */
641    opmode_byte(0, 0, 0, 0, 0, M_AX),
642];
643
644// ─── OP_MODES accessors ───────────────────────────────────────────────────────
645//
646// C: #define getOpMode(m)    (cast(enum OpMode, luaP_opmodes[m] & 7))
647// C: #define testAMode(m)    (luaP_opmodes[m] & (1 << 3))
648// C: #define testTMode(m)    (luaP_opmodes[m] & (1 << 4))
649// C: #define testITMode(m)   (luaP_opmodes[m] & (1 << 5))
650// C: #define testOTMode(m)   (luaP_opmodes[m] & (1 << 6))
651// C: #define testMMMode(m)   (luaP_opmodes[m] & (1 << 7))
652
653/// Extract the `OpMode` for an opcode.
654///
655/// C: `getOpMode(m)` — `cast(enum OpMode, luaP_opmodes[m] & 7)`
656pub fn get_op_mode(op: OpCode) -> OpMode {
657    match OP_MODES[op as usize] & 7 {
658        0 => OpMode::Abc,
659        1 => OpMode::ABx,
660        2 => OpMode::AsBx,
661        3 => OpMode::Ax,
662        4 => OpMode::SJ,
663        // PERF(port): unreachable branch — values 5-7 are unused; profile in Phase B
664        _ => OpMode::Abc,
665    }
666}
667
668/// True if this opcode writes to register A.
669///
670/// C: `testAMode(m)` — `luaP_opmodes[m] & (1 << 3)`
671#[inline]
672pub fn test_a_mode(op: OpCode) -> bool {
673    (OP_MODES[op as usize] & (1 << 3)) != 0
674}
675
676/// True if this opcode is a test (the next instruction must be a jump).
677///
678/// C: `testTMode(m)` — `luaP_opmodes[m] & (1 << 4)`
679#[inline]
680pub fn test_t_mode(op: OpCode) -> bool {
681    (OP_MODES[op as usize] & (1 << 4)) != 0
682}
683
684/// True if this opcode uses `L->top` as set by the previous instruction (B == 0 case).
685///
686/// C: `testITMode(m)` — `luaP_opmodes[m] & (1 << 5)`
687#[inline]
688pub fn test_it_mode(op: OpCode) -> bool {
689    (OP_MODES[op as usize] & (1 << 5)) != 0
690}
691
692/// True if this opcode sets `L->top` for the next instruction (C == 0 case).
693///
694/// C: `testOTMode(m)` — `luaP_opmodes[m] & (1 << 6)`
695#[inline]
696pub fn test_ot_mode(op: OpCode) -> bool {
697    (OP_MODES[op as usize] & (1 << 6)) != 0
698}
699
700/// True if this opcode is a metamethod call.
701///
702/// C: `testMMMode(m)` — `luaP_opmodes[m] & (1 << 7)`
703#[inline]
704pub fn test_mm_mode(op: OpCode) -> bool {
705    (OP_MODES[op as usize] & (1 << 7)) != 0
706}
707
708// ─── Instruction newtype ──────────────────────────────────────────────────────
709//
710// Per types.tsv: `Instruction` is a `u32` newtype; bytecode word.
711// All accessor/builder macros from lopcodes.h become methods here.
712
713/// A single Lua bytecode instruction (unsigned 32-bit word).
714///
715/// C: `typedef unsigned int Instruction;` (see llimits.h)
716#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
717#[repr(transparent)]
718pub struct Instruction(pub u32);
719
720impl Instruction {
721    // ── Low-level field accessors ─────────────────────────────────────────
722
723    /// Extract a bit-field of `size` bits at position `pos`.
724    ///
725    /// C: `getarg(i, pos, size)` — `cast_int(((i)>>(pos)) & MASK1(size,0))`
726    #[inline]
727    pub const fn get_arg(self, pos: u32, size: u32) -> u32 {
728        (self.0 >> pos) & ((1u32 << size) - 1)
729    }
730
731    /// Set a bit-field of `size` bits at position `pos` to `v`.
732    ///
733    /// C: `setarg(i, v, pos, size)`
734    #[inline]
735    pub fn set_arg(&mut self, v: u32, pos: u32, size: u32) {
736        let mask = ((1u32 << size) - 1) << pos;
737        self.0 = (self.0 & !mask) | ((v << pos) & mask);
738    }
739
740    // ── Opcode field ──────────────────────────────────────────────────────
741
742    /// Extract the opcode.
743    ///
744    /// C: `GET_OPCODE(i)` — `cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))`
745    #[inline]
746    pub fn opcode(self) -> Option<OpCode> {
747        OpCode::from_u32(self.get_arg(POS_OP, SIZE_OP))
748    }
749
750    /// Replace the opcode field.
751    ///
752    /// C: `SET_OPCODE(i, o)`
753    #[inline]
754    pub fn set_opcode(&mut self, op: OpCode) {
755        self.set_arg(op as u32, POS_OP, SIZE_OP);
756    }
757
758    // ── A field ───────────────────────────────────────────────────────────
759
760    /// C: `GETARG_A(i)`
761    #[inline]
762    pub const fn arg_a(self) -> u32 {
763        self.get_arg(POS_A, SIZE_A)
764    }
765
766    /// C: `SETARG_A(i, v)`
767    #[inline]
768    pub fn set_arg_a(&mut self, v: u32) {
769        self.set_arg(v, POS_A, SIZE_A);
770    }
771
772    // ── k bit ─────────────────────────────────────────────────────────────
773
774    /// C: `GETARG_k(i)` — returns 0 or 1.
775    #[inline]
776    pub const fn arg_k(self) -> u32 {
777        self.get_arg(POS_K, 1)
778    }
779
780    /// C: `TESTARG_k(i)` — boolean form of `GETARG_k`.
781    #[inline]
782    pub const fn test_k(self) -> bool {
783        self.arg_k() != 0
784    }
785
786    /// C: `SETARG_k(i, v)`
787    #[inline]
788    pub fn set_arg_k(&mut self, v: u32) {
789        self.set_arg(v, POS_K, 1);
790    }
791
792    // ── B field (iABC only) ───────────────────────────────────────────────
793
794    /// C: `GETARG_B(i)` — debug-asserts iABC mode in C; here we trust the caller.
795    #[inline]
796    pub const fn arg_b(self) -> u32 {
797        self.get_arg(POS_B, SIZE_B)
798    }
799
800    /// C: `GETARG_sB(i)` — signed B (subtracts `OFFSET_S_C`).
801    #[inline]
802    pub const fn arg_s_b(self) -> i32 {
803        self.arg_b() as i32 - OFFSET_S_C
804    }
805
806    /// C: `SETARG_B(i, v)`
807    #[inline]
808    pub fn set_arg_b(&mut self, v: u32) {
809        self.set_arg(v, POS_B, SIZE_B);
810    }
811
812    // ── C field (iABC only) ───────────────────────────────────────────────
813
814    /// C: `GETARG_C(i)`
815    #[inline]
816    pub const fn arg_c(self) -> u32 {
817        self.get_arg(POS_C, SIZE_C)
818    }
819
820    /// C: `GETARG_sC(i)` — signed C (subtracts `OFFSET_S_C`).
821    #[inline]
822    pub const fn arg_s_c(self) -> i32 {
823        self.arg_c() as i32 - OFFSET_S_C
824    }
825
826    /// C: `SETARG_C(i, v)`
827    #[inline]
828    pub fn set_arg_c(&mut self, v: u32) {
829        self.set_arg(v, POS_C, SIZE_C);
830    }
831
832    // ── Bx field (iABx / iAsBx) ──────────────────────────────────────────
833
834    /// C: `GETARG_Bx(i)` — unsigned Bx.
835    #[inline]
836    pub const fn arg_bx(self) -> u32 {
837        self.get_arg(POS_BX, SIZE_BX)
838    }
839
840    /// C: `SETARG_Bx(i, v)`
841    #[inline]
842    pub fn set_arg_bx(&mut self, v: u32) {
843        self.set_arg(v, POS_BX, SIZE_BX);
844    }
845
846    /// C: `GETARG_sBx(i)` — signed Bx (subtracts `OFFSET_S_BX`).
847    #[inline]
848    pub const fn arg_s_bx(self) -> i32 {
849        self.arg_bx() as i32 - OFFSET_S_BX
850    }
851
852    /// C: `SETARG_sBx(i, b)` — stores `b + OFFSET_S_BX` in the Bx field.
853    #[inline]
854    pub fn set_arg_s_bx(&mut self, b: i32) {
855        self.set_arg_bx((b + OFFSET_S_BX) as u32);
856    }
857
858    // ── Ax field (iAx) ────────────────────────────────────────────────────
859
860    /// C: `GETARG_Ax(i)`
861    #[inline]
862    pub const fn arg_ax(self) -> u32 {
863        self.get_arg(POS_AX, SIZE_AX)
864    }
865
866    /// C: `SETARG_Ax(i, v)`
867    #[inline]
868    pub fn set_arg_ax(&mut self, v: u32) {
869        self.set_arg(v, POS_AX, SIZE_AX);
870    }
871
872    // ── sJ field (isJ) ────────────────────────────────────────────────────
873
874    /// C: `GETARG_sJ(i)` — signed J (subtracts `OFFSET_S_J`).
875    #[inline]
876    pub const fn arg_s_j(self) -> i32 {
877        self.get_arg(POS_S_J, SIZE_S_J) as i32 - OFFSET_S_J
878    }
879
880    /// C: `SETARG_sJ(i, j)` — stores `j + OFFSET_S_J` in the sJ field.
881    #[inline]
882    pub fn set_arg_s_j(&mut self, j: i32) {
883        self.set_arg((j + OFFSET_S_J) as u32, POS_S_J, SIZE_S_J);
884    }
885
886    // ── Instruction builders ──────────────────────────────────────────────
887
888    /// Build an `iABC` instruction.
889    ///
890    /// C: `CREATE_ABCk(o, a, b, c, k)`
891    #[inline]
892    pub fn abck(op: OpCode, a: u32, b: u32, c: u32, k: u32) -> Self {
893        Self(
894            ((op as u32) << POS_OP)
895                | (a << POS_A)
896                | (b << POS_B)
897                | (c << POS_C)
898                | (k << POS_K),
899        )
900    }
901
902    /// Build an `iABx` instruction.
903    ///
904    /// C: `CREATE_ABx(o, a, bc)`
905    #[inline]
906    pub fn abx(op: OpCode, a: u32, bc: u32) -> Self {
907        Self(((op as u32) << POS_OP) | (a << POS_A) | (bc << POS_BX))
908    }
909
910    /// Build an `iAx` instruction.
911    ///
912    /// C: `CREATE_Ax(o, a)`
913    #[inline]
914    pub fn ax(op: OpCode, a: u32) -> Self {
915        Self(((op as u32) << POS_OP) | (a << POS_AX))
916    }
917
918    /// Build an `isJ` instruction.
919    ///
920    /// C: `CREATE_sJ(o, j, k)`
921    #[inline]
922    pub fn sj(op: OpCode, j: u32, k: u32) -> Self {
923        Self(((op as u32) << POS_OP) | (j << POS_S_J) | (k << POS_K))
924    }
925
926    // ── Mode query helpers (isOT / isIT) ──────────────────────────────────
927
928    /// True if this instruction sets `L->top` for the next instruction.
929    ///
930    /// C: `isOT(i)` —
931    /// `(testOTMode(GET_OPCODE(i)) && GETARG_C(i) == 0) || GET_OPCODE(i) == OP_TAILCALL`
932    pub fn is_out_top(self) -> bool {
933        match self.opcode() {
934            Some(op) => (test_ot_mode(op) && self.arg_c() == 0) || op == OpCode::TailCall,
935            None => false,
936        }
937    }
938
939    /// True if this instruction uses `L->top` from the previous instruction.
940    ///
941    /// C: `isIT(i)` — `testITMode(GET_OPCODE(i)) && GETARG_B(i) == 0`
942    pub fn is_in_top(self) -> bool {
943        match self.opcode() {
944            Some(op) => test_it_mode(op) && self.arg_b() == 0,
945            None => false,
946        }
947    }
948
949    /// Return the `OpMode` for this instruction.
950    ///
951    /// C: `getOpMode(GET_OPCODE(i))`
952    pub fn op_mode(self) -> Option<OpMode> {
953        self.opcode().map(get_op_mode)
954    }
955}
956
957// ─── Signed-argument encode/decode helpers ────────────────────────────────────
958//
959// C: #define int2sC(i)  ((i) + OFFSET_sC)
960// C: #define sC2int(i)  ((i) - OFFSET_sC)
961//
962// These are inline helpers used at call sites; the Instruction methods above
963// incorporate them, but standalone functions are provided for codegen use.
964
965/// Encode a signed integer into an unsigned C-field value.
966///
967/// C: `int2sC(i)`
968#[inline]
969pub const fn int_to_s_c(i: i32) -> u32 {
970    (i + OFFSET_S_C) as u32
971}
972
973/// Decode a C-field unsigned value to a signed integer.
974///
975/// C: `sC2int(i)`
976#[inline]
977pub const fn s_c_to_int(i: u32) -> i32 {
978    i as i32 - OFFSET_S_C
979}
980
981// ─── Tests ────────────────────────────────────────────────────────────────────
982
983#[cfg(test)]
984mod tests {
985    use super::*;
986
987    #[test]
988    fn num_opcodes_matches_enum() {
989        assert_eq!(NUM_OPCODES, 83);
990        assert_eq!(OpCode::ExtraArg as usize, 82);
991    }
992
993    #[test]
994    fn op_modes_table_length() {
995        assert_eq!(OP_MODES.len(), NUM_OPCODES);
996    }
997
998    #[test]
999    fn opmode_byte_values() {
1000        assert_eq!(OP_MODES[OpCode::Move as usize], 0b00001000); // a=1, mode=iABC=0 → 8
1001        assert_eq!(OP_MODES[OpCode::LoadI as usize], 0b00001010); // a=1, mode=iAsBx=2 → 10
1002        assert_eq!(OP_MODES[OpCode::Jmp as usize], 0b00000100); // a=0, mode=isJ=4 → 4
1003        assert_eq!(OP_MODES[OpCode::MmBin as usize], 0b10000000); // mm=1, a=0, mode=iABC=0 → 128
1004        assert_eq!(OP_MODES[OpCode::Call as usize], 0b01101000); // ot=1,it=1,a=1,mode=0 → 104
1005        assert_eq!(OP_MODES[OpCode::ExtraArg as usize], 0b00000011); // mode=iAx=3 → 3
1006    }
1007
1008    #[test]
1009    fn from_u32_round_trip() {
1010        for i in 0..NUM_OPCODES {
1011            let op = OpCode::from_u32(i as u32).expect("valid opcode");
1012            assert_eq!(op as usize, i);
1013        }
1014        assert!(OpCode::from_u32(83).is_none());
1015    }
1016
1017    #[test]
1018    fn instruction_arg_a() {
1019        let i = Instruction::abck(OpCode::Move, 5, 3, 0, 0);
1020        assert_eq!(i.arg_a(), 5);
1021        assert_eq!(i.arg_b(), 3);
1022    }
1023
1024    #[test]
1025    fn instruction_s_bx_round_trip() {
1026        let mut i = Instruction::abx(OpCode::LoadK, 0, 0);
1027        i.set_arg_s_bx(-10);
1028        assert_eq!(i.arg_s_bx(), -10);
1029        i.set_arg_s_bx(0);
1030        assert_eq!(i.arg_s_bx(), 0);
1031        i.set_arg_s_bx(100);
1032        assert_eq!(i.arg_s_bx(), 100);
1033    }
1034
1035    #[test]
1036    fn instruction_s_j_round_trip() {
1037        let mut i = Instruction::sj(OpCode::Jmp, (OFFSET_S_J) as u32, 0);
1038        assert_eq!(i.arg_s_j(), 0);
1039        i.set_arg_s_j(42);
1040        assert_eq!(i.arg_s_j(), 42);
1041        i.set_arg_s_j(-1);
1042        assert_eq!(i.arg_s_j(), -1);
1043    }
1044
1045    #[test]
1046    fn get_op_mode_smoke() {
1047        assert_eq!(get_op_mode(OpCode::Move), OpMode::Abc);
1048        assert_eq!(get_op_mode(OpCode::LoadI), OpMode::AsBx);
1049        assert_eq!(get_op_mode(OpCode::LoadK), OpMode::ABx);
1050        assert_eq!(get_op_mode(OpCode::Jmp), OpMode::SJ);
1051        assert_eq!(get_op_mode(OpCode::ExtraArg), OpMode::Ax);
1052    }
1053
1054    #[test]
1055    fn int_to_s_c_round_trip() {
1056        assert_eq!(s_c_to_int(int_to_s_c(0)), 0);
1057        assert_eq!(s_c_to_int(int_to_s_c(100)), 100);
1058        assert_eq!(s_c_to_int(int_to_s_c(-50)), -50);
1059    }
1060}
1061
1062// ──────────────────────────────────────────────────────────────────────────
1063// PORT STATUS
1064//   source:        src/lopcodes.c  (104 lines, 0 functions — data only)
1065//                  src/lopcodes.h  (406 lines, merged per PORTING.md §1)
1066//   target_crate:  lua-code
1067//   confidence:    high
1068//   todos:         1
1069//   port_notes:    1
1070//   unsafe_blocks: 0
1071//   notes:         Pure data/encoding translation; OpCode::from_u32 needs
1072//                  Phase B review for transmute vs. num_enum. Self_ rename
1073//                  is permanent (Rust keyword conflict).
1074// ──────────────────────────────────────────────────────────────────────────