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 `
9
10
11// ─── Instruction format diagram ──────────────────────────────────────────────
12//
13
14// ─── OpMode ──────────────────────────────────────────────────────────────────
15
16/// Instruction addressing mode.
17///
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u8)]
20pub enum OpMode {
21    Abc = 0,
22    ABx = 1,
23    AsBx = 2,
24    Ax = 3,
25    SJ = 4,
26}
27
28// ─── Field size constants ─────────────────────────────────────────────────────
29//
30
31pub const SIZE_C: u32 = 8;
32pub const SIZE_B: u32 = 8;
33pub const SIZE_BX: u32 = SIZE_C + SIZE_B + 1;
34pub const SIZE_A: u32 = 8;
35pub const SIZE_AX: u32 = SIZE_BX + SIZE_A;
36pub const SIZE_S_J: u32 = SIZE_BX + SIZE_A;
37pub const SIZE_OP: u32 = 7;
38
39// ─── Field position constants ─────────────────────────────────────────────────
40//
41
42pub const POS_OP: u32 = 0;
43pub const POS_A: u32 = POS_OP + SIZE_OP;
44pub const POS_K: u32 = POS_A + SIZE_A;
45pub const POS_B: u32 = POS_K + 1;
46pub const POS_C: u32 = POS_B + SIZE_B;
47pub const POS_BX: u32 = POS_K;
48pub const POS_AX: u32 = POS_A;
49pub const POS_S_J: u32 = POS_A;
50
51// ─── Argument limit constants ─────────────────────────────────────────────────
52//
53
54pub const MAXARG_BX: u32 = (1u32 << SIZE_BX) - 1;
55pub const OFFSET_S_BX: i32 = (MAXARG_BX >> 1) as i32;
56pub const MAXARG_AX: u32 = (1u32 << SIZE_AX) - 1;
57pub const MAXARG_S_J: u32 = (1u32 << SIZE_S_J) - 1;
58pub const OFFSET_S_J: i32 = (MAXARG_S_J >> 1) as i32;
59pub const MAXARG_A: u32 = (1u32 << SIZE_A) - 1;
60pub const MAXARG_B: u32 = (1u32 << SIZE_B) - 1;
61pub const MAXARG_C: u32 = (1u32 << SIZE_C) - 1;
62pub const OFFSET_S_C: i32 = (MAXARG_C >> 1) as i32;
63
64/// Sentinel "no register" value that fits in 8 bits.
65///
66pub const NO_REG: u32 = MAXARG_A;
67
68/// Maximum RK index (for debugging only).
69///
70pub const MAXINDEXRK: u32 = MAXARG_B;
71
72/// Number of list items to accumulate before a SETLIST instruction.
73///
74pub const LFIELDS_PER_FLUSH: u32 = 50;
75
76// ─── OpCode enum ─────────────────────────────────────────────────────────────
77//
78//
79// ORDER OP — variant discriminants must match `lopcodes.h` exactly.
80// The VM casts the raw opcode field directly to this enum.
81
82/// All opcodes for the Lua 5.4 virtual machine.
83///
84/// ORDER OP — must match `lopcodes.h` exactly.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
86#[repr(u8)]
87pub enum OpCode {
88    Move = 0,
89    LoadI,
90    LoadF,
91    LoadK,
92    LoadKX,
93    LoadFalse,
94    LFalseSkip,
95    LoadTrue,
96    LoadNil,
97    GetUpVal,
98    SetUpVal,
99
100    GetTabUp,
101    GetTable,
102    GetI,
103    GetField,
104
105    SetTabUp,
106    SetTable,
107    SetI,
108    SetField,
109
110    NewTable,
111
112    // PORT NOTE: `self` is a Rust keyword; renamed to `Self_`.
113    Self_,
114
115    AddI,
116
117    AddK,
118    SubK,
119    MulK,
120    ModK,
121    PowK,
122    DivK,
123    IDivK,
124
125    BAndK,
126    BOrK,
127    BXorK,
128
129    ShrI,
130    ShlI,
131
132    Add,
133    Sub,
134    Mul,
135    Mod,
136    Pow,
137    Div,
138    IDiv,
139
140    BAnd,
141    BOr,
142    BXor,
143    Shl,
144    Shr,
145
146    MmBin,
147    MmBinI,
148    MmBinK,
149
150    Unm,
151    BNot,
152    Not,
153    Len,
154
155    Concat,
156
157    Close,
158    Tbc,
159    Jmp,
160
161    Eq,
162    Lt,
163    Le,
164
165    EqK,
166    EqI,
167    LtI,
168    LeI,
169    GtI,
170    GeI,
171
172    Test,
173    TestSet,
174
175    Call,
176    TailCall,
177
178    Return,
179    Return0,
180    Return1,
181
182    ForLoop,
183    ForPrep,
184
185    TForPrep,
186    TForCall,
187    TForLoop,
188
189    SetList,
190
191    Closure,
192
193    VarArg,
194
195    VarArgPrep,
196
197    ExtraArg,
198}
199
200/// Total number of opcodes.
201///
202pub const NUM_OPCODES: usize = OpCode::ExtraArg as usize + 1;
203
204impl OpCode {
205    /// Convert a raw `u32` opcode field value to an `OpCode`.
206    ///
207    /// Returns `None` if `v >= NUM_OPCODES`.
208    ///
209    ///
210    /// TODO(port): replace explicit match with a safe transmute or `num_enum`
211    /// crate derive once Phase B settles the dependency policy. The match is
212    /// correct but mechanical; 83 arms is noise at compile-time and runtime.
213    pub fn from_u32(v: u32) -> Option<Self> {
214        match v {
215            0 => Some(Self::Move),
216            1 => Some(Self::LoadI),
217            2 => Some(Self::LoadF),
218            3 => Some(Self::LoadK),
219            4 => Some(Self::LoadKX),
220            5 => Some(Self::LoadFalse),
221            6 => Some(Self::LFalseSkip),
222            7 => Some(Self::LoadTrue),
223            8 => Some(Self::LoadNil),
224            9 => Some(Self::GetUpVal),
225            10 => Some(Self::SetUpVal),
226            11 => Some(Self::GetTabUp),
227            12 => Some(Self::GetTable),
228            13 => Some(Self::GetI),
229            14 => Some(Self::GetField),
230            15 => Some(Self::SetTabUp),
231            16 => Some(Self::SetTable),
232            17 => Some(Self::SetI),
233            18 => Some(Self::SetField),
234            19 => Some(Self::NewTable),
235            20 => Some(Self::Self_),
236            21 => Some(Self::AddI),
237            22 => Some(Self::AddK),
238            23 => Some(Self::SubK),
239            24 => Some(Self::MulK),
240            25 => Some(Self::ModK),
241            26 => Some(Self::PowK),
242            27 => Some(Self::DivK),
243            28 => Some(Self::IDivK),
244            29 => Some(Self::BAndK),
245            30 => Some(Self::BOrK),
246            31 => Some(Self::BXorK),
247            32 => Some(Self::ShrI),
248            33 => Some(Self::ShlI),
249            34 => Some(Self::Add),
250            35 => Some(Self::Sub),
251            36 => Some(Self::Mul),
252            37 => Some(Self::Mod),
253            38 => Some(Self::Pow),
254            39 => Some(Self::Div),
255            40 => Some(Self::IDiv),
256            41 => Some(Self::BAnd),
257            42 => Some(Self::BOr),
258            43 => Some(Self::BXor),
259            44 => Some(Self::Shl),
260            45 => Some(Self::Shr),
261            46 => Some(Self::MmBin),
262            47 => Some(Self::MmBinI),
263            48 => Some(Self::MmBinK),
264            49 => Some(Self::Unm),
265            50 => Some(Self::BNot),
266            51 => Some(Self::Not),
267            52 => Some(Self::Len),
268            53 => Some(Self::Concat),
269            54 => Some(Self::Close),
270            55 => Some(Self::Tbc),
271            56 => Some(Self::Jmp),
272            57 => Some(Self::Eq),
273            58 => Some(Self::Lt),
274            59 => Some(Self::Le),
275            60 => Some(Self::EqK),
276            61 => Some(Self::EqI),
277            62 => Some(Self::LtI),
278            63 => Some(Self::LeI),
279            64 => Some(Self::GtI),
280            65 => Some(Self::GeI),
281            66 => Some(Self::Test),
282            67 => Some(Self::TestSet),
283            68 => Some(Self::Call),
284            69 => Some(Self::TailCall),
285            70 => Some(Self::Return),
286            71 => Some(Self::Return0),
287            72 => Some(Self::Return1),
288            73 => Some(Self::ForLoop),
289            74 => Some(Self::ForPrep),
290            75 => Some(Self::TForPrep),
291            76 => Some(Self::TForCall),
292            77 => Some(Self::TForLoop),
293            78 => Some(Self::SetList),
294            79 => Some(Self::Closure),
295            80 => Some(Self::VarArg),
296            81 => Some(Self::VarArgPrep),
297            82 => Some(Self::ExtraArg),
298            _ => None,
299        }
300    }
301}
302
303// ─── opmode_byte helper ───────────────────────────────────────────────────────
304//
305//        (((mm)<<7) | ((ot)<<6) | ((it)<<5) | ((t)<<4) | ((a)<<3) | (m))
306//
307// Bit layout for each entry in OP_MODES:
308//   bits 0-2: OpMode value (Abc=0 ABx=1 AsBx=2 Ax=3 SJ=4)
309//   bit 3:    instruction sets register A
310//   bit 4:    is a test (next instruction must be a jump)
311//   bit 5:    instruction uses L->top from previous (IT mode)
312//   bit 6:    instruction sets L->top for next (OT mode)
313//   bit 7:    is a metamethod instruction (MM)
314
315const fn opmode_byte(mm: u8, ot: u8, it: u8, t: u8, a: u8, m: u8) -> u8 {
316    (mm << 7) | (ot << 6) | (it << 5) | (t << 4) | (a << 3) | m
317}
318
319// Shorthand mode constants for the OP_MODES table below.
320const M_ABC: u8 = OpMode::Abc as u8;
321const M_ABX: u8 = OpMode::ABx as u8;
322const M_ASBX: u8 = OpMode::AsBx as u8;
323const M_AX: u8 = OpMode::Ax as u8;
324const M_SJ: u8 = OpMode::SJ as u8;
325
326// ─── OP_MODES table ───────────────────────────────────────────────────────────
327//
328//
329// Per macros.tsv: LUAI_DDEF → drop (definition site, no modifier needed in Rust).
330// Per macros.tsv: LUAI_DDEC → `pub(crate) static` at the declaration site.
331
332/// Opcode properties table, indexed by `OpCode as usize`.
333///
334///
335/// Use `get_op_mode`, `test_a_mode`, etc. to query individual properties
336/// rather than indexing this array directly.
337pub(crate) const OP_MODES: [u8; NUM_OPCODES] = [
338    opmode_byte(0, 0, 0, 0, 1, M_ABC),
339    opmode_byte(0, 0, 0, 0, 1, M_ASBX),
340    opmode_byte(0, 0, 0, 0, 1, M_ASBX),
341    opmode_byte(0, 0, 0, 0, 1, M_ABX),
342    opmode_byte(0, 0, 0, 0, 1, M_ABX),
343    opmode_byte(0, 0, 0, 0, 1, M_ABC),
344    opmode_byte(0, 0, 0, 0, 1, M_ABC),
345    opmode_byte(0, 0, 0, 0, 1, M_ABC),
346    opmode_byte(0, 0, 0, 0, 1, M_ABC),
347    opmode_byte(0, 0, 0, 0, 1, M_ABC),
348    opmode_byte(0, 0, 0, 0, 0, M_ABC),
349    opmode_byte(0, 0, 0, 0, 1, M_ABC),
350    opmode_byte(0, 0, 0, 0, 1, M_ABC),
351    opmode_byte(0, 0, 0, 0, 1, M_ABC),
352    opmode_byte(0, 0, 0, 0, 1, M_ABC),
353    opmode_byte(0, 0, 0, 0, 0, M_ABC),
354    opmode_byte(0, 0, 0, 0, 0, M_ABC),
355    opmode_byte(0, 0, 0, 0, 0, M_ABC),
356    opmode_byte(0, 0, 0, 0, 0, M_ABC),
357    opmode_byte(0, 0, 0, 0, 1, M_ABC),
358    opmode_byte(0, 0, 0, 0, 1, M_ABC),
359    opmode_byte(0, 0, 0, 0, 1, M_ABC),
360    opmode_byte(0, 0, 0, 0, 1, M_ABC),
361    opmode_byte(0, 0, 0, 0, 1, M_ABC),
362    opmode_byte(0, 0, 0, 0, 1, M_ABC),
363    opmode_byte(0, 0, 0, 0, 1, M_ABC),
364    opmode_byte(0, 0, 0, 0, 1, M_ABC),
365    opmode_byte(0, 0, 0, 0, 1, M_ABC),
366    opmode_byte(0, 0, 0, 0, 1, M_ABC),
367    opmode_byte(0, 0, 0, 0, 1, M_ABC),
368    opmode_byte(0, 0, 0, 0, 1, M_ABC),
369    opmode_byte(0, 0, 0, 0, 1, M_ABC),
370    opmode_byte(0, 0, 0, 0, 1, M_ABC),
371    opmode_byte(0, 0, 0, 0, 1, M_ABC),
372    opmode_byte(0, 0, 0, 0, 1, M_ABC),
373    opmode_byte(0, 0, 0, 0, 1, M_ABC),
374    opmode_byte(0, 0, 0, 0, 1, M_ABC),
375    opmode_byte(0, 0, 0, 0, 1, M_ABC),
376    opmode_byte(0, 0, 0, 0, 1, M_ABC),
377    opmode_byte(0, 0, 0, 0, 1, M_ABC),
378    opmode_byte(0, 0, 0, 0, 1, M_ABC),
379    opmode_byte(0, 0, 0, 0, 1, M_ABC),
380    opmode_byte(0, 0, 0, 0, 1, M_ABC),
381    opmode_byte(0, 0, 0, 0, 1, M_ABC),
382    opmode_byte(0, 0, 0, 0, 1, M_ABC),
383    opmode_byte(0, 0, 0, 0, 1, M_ABC),
384    opmode_byte(1, 0, 0, 0, 0, M_ABC),
385    opmode_byte(1, 0, 0, 0, 0, M_ABC),
386    opmode_byte(1, 0, 0, 0, 0, M_ABC),
387    opmode_byte(0, 0, 0, 0, 1, M_ABC),
388    opmode_byte(0, 0, 0, 0, 1, M_ABC),
389    opmode_byte(0, 0, 0, 0, 1, M_ABC),
390    opmode_byte(0, 0, 0, 0, 1, M_ABC),
391    opmode_byte(0, 0, 0, 0, 1, M_ABC),
392    opmode_byte(0, 0, 0, 0, 0, M_ABC),
393    opmode_byte(0, 0, 0, 0, 0, M_ABC),
394    opmode_byte(0, 0, 0, 0, 0, M_SJ),
395    opmode_byte(0, 0, 0, 1, 0, M_ABC),
396    opmode_byte(0, 0, 0, 1, 0, M_ABC),
397    opmode_byte(0, 0, 0, 1, 0, M_ABC),
398    opmode_byte(0, 0, 0, 1, 0, M_ABC),
399    opmode_byte(0, 0, 0, 1, 0, M_ABC),
400    opmode_byte(0, 0, 0, 1, 0, M_ABC),
401    opmode_byte(0, 0, 0, 1, 0, M_ABC),
402    opmode_byte(0, 0, 0, 1, 0, M_ABC),
403    opmode_byte(0, 0, 0, 1, 0, M_ABC),
404    opmode_byte(0, 0, 0, 1, 0, M_ABC),
405    opmode_byte(0, 0, 0, 1, 1, M_ABC),
406    opmode_byte(0, 1, 1, 0, 1, M_ABC),
407    opmode_byte(0, 1, 1, 0, 1, M_ABC),
408    opmode_byte(0, 0, 1, 0, 0, M_ABC),
409    opmode_byte(0, 0, 0, 0, 0, M_ABC),
410    opmode_byte(0, 0, 0, 0, 0, M_ABC),
411    opmode_byte(0, 0, 0, 0, 1, M_ABX),
412    opmode_byte(0, 0, 0, 0, 1, M_ABX),
413    opmode_byte(0, 0, 0, 0, 0, M_ABX),
414    opmode_byte(0, 0, 0, 0, 0, M_ABC),
415    opmode_byte(0, 0, 0, 0, 1, M_ABX),
416    opmode_byte(0, 0, 1, 0, 0, M_ABC),
417    opmode_byte(0, 0, 0, 0, 1, M_ABX),
418    opmode_byte(0, 1, 0, 0, 1, M_ABC),
419    opmode_byte(0, 0, 1, 0, 1, M_ABC),
420    opmode_byte(0, 0, 0, 0, 0, M_AX),
421];
422
423// ─── OP_MODES accessors ───────────────────────────────────────────────────────
424//
425
426/// Extract the `OpMode` for an opcode.
427///
428pub fn get_op_mode(op: OpCode) -> OpMode {
429    match OP_MODES[op as usize] & 7 {
430        0 => OpMode::Abc,
431        1 => OpMode::ABx,
432        2 => OpMode::AsBx,
433        3 => OpMode::Ax,
434        4 => OpMode::SJ,
435        // PERF(port): unreachable branch — values 5-7 are unused; profile in Phase B
436        _ => OpMode::Abc,
437    }
438}
439
440/// True if this opcode writes to register A.
441///
442#[inline]
443pub fn test_a_mode(op: OpCode) -> bool {
444    (OP_MODES[op as usize] & (1 << 3)) != 0
445}
446
447/// True if this opcode is a test (the next instruction must be a jump).
448///
449#[inline]
450pub fn test_t_mode(op: OpCode) -> bool {
451    (OP_MODES[op as usize] & (1 << 4)) != 0
452}
453
454/// True if this opcode uses `L->top` as set by the previous instruction (B == 0 case).
455///
456#[inline]
457pub fn test_it_mode(op: OpCode) -> bool {
458    (OP_MODES[op as usize] & (1 << 5)) != 0
459}
460
461/// True if this opcode sets `L->top` for the next instruction (C == 0 case).
462///
463#[inline]
464pub fn test_ot_mode(op: OpCode) -> bool {
465    (OP_MODES[op as usize] & (1 << 6)) != 0
466}
467
468/// True if this opcode is a metamethod call.
469///
470#[inline]
471pub fn test_mm_mode(op: OpCode) -> bool {
472    (OP_MODES[op as usize] & (1 << 7)) != 0
473}
474
475// ─── Instruction newtype ──────────────────────────────────────────────────────
476//
477// Per types.tsv: `Instruction` is a `u32` newtype; bytecode word.
478// All accessor/builder macros from lopcodes.h become methods here.
479
480/// A single Lua bytecode instruction (unsigned 32-bit word).
481///
482#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
483#[repr(transparent)]
484pub struct Instruction(pub u32);
485
486impl Instruction {
487    // ── Low-level field accessors ─────────────────────────────────────────
488
489    /// Extract a bit-field of `size` bits at position `pos`.
490    ///
491    #[inline]
492    pub const fn get_arg(self, pos: u32, size: u32) -> u32 {
493        (self.0 >> pos) & ((1u32 << size) - 1)
494    }
495
496    /// Set a bit-field of `size` bits at position `pos` to `v`.
497    ///
498    #[inline]
499    pub fn set_arg(&mut self, v: u32, pos: u32, size: u32) {
500        let mask = ((1u32 << size) - 1) << pos;
501        self.0 = (self.0 & !mask) | ((v << pos) & mask);
502    }
503
504    // ── Opcode field ──────────────────────────────────────────────────────
505
506    /// Extract the opcode.
507    ///
508    #[inline]
509    pub fn opcode(self) -> Option<OpCode> {
510        OpCode::from_u32(self.get_arg(POS_OP, SIZE_OP))
511    }
512
513    /// Replace the opcode field.
514    ///
515    #[inline]
516    pub fn set_opcode(&mut self, op: OpCode) {
517        self.set_arg(op as u32, POS_OP, SIZE_OP);
518    }
519
520    // ── A field ───────────────────────────────────────────────────────────
521
522    #[inline]
523    pub const fn arg_a(self) -> u32 {
524        self.get_arg(POS_A, SIZE_A)
525    }
526
527    #[inline]
528    pub fn set_arg_a(&mut self, v: u32) {
529        self.set_arg(v, POS_A, SIZE_A);
530    }
531
532    // ── k bit ─────────────────────────────────────────────────────────────
533
534    #[inline]
535    pub const fn arg_k(self) -> u32 {
536        self.get_arg(POS_K, 1)
537    }
538
539    #[inline]
540    pub const fn test_k(self) -> bool {
541        self.arg_k() != 0
542    }
543
544    #[inline]
545    pub fn set_arg_k(&mut self, v: u32) {
546        self.set_arg(v, POS_K, 1);
547    }
548
549    // ── B field (iABC only) ───────────────────────────────────────────────
550
551    #[inline]
552    pub const fn arg_b(self) -> u32 {
553        self.get_arg(POS_B, SIZE_B)
554    }
555
556    #[inline]
557    pub const fn arg_s_b(self) -> i32 {
558        self.arg_b() as i32 - OFFSET_S_C
559    }
560
561    #[inline]
562    pub fn set_arg_b(&mut self, v: u32) {
563        self.set_arg(v, POS_B, SIZE_B);
564    }
565
566    // ── C field (iABC only) ───────────────────────────────────────────────
567
568    #[inline]
569    pub const fn arg_c(self) -> u32 {
570        self.get_arg(POS_C, SIZE_C)
571    }
572
573    #[inline]
574    pub const fn arg_s_c(self) -> i32 {
575        self.arg_c() as i32 - OFFSET_S_C
576    }
577
578    #[inline]
579    pub fn set_arg_c(&mut self, v: u32) {
580        self.set_arg(v, POS_C, SIZE_C);
581    }
582
583    // ── Bx field (iABx / iAsBx) ──────────────────────────────────────────
584
585    #[inline]
586    pub const fn arg_bx(self) -> u32 {
587        self.get_arg(POS_BX, SIZE_BX)
588    }
589
590    #[inline]
591    pub fn set_arg_bx(&mut self, v: u32) {
592        self.set_arg(v, POS_BX, SIZE_BX);
593    }
594
595    #[inline]
596    pub const fn arg_s_bx(self) -> i32 {
597        self.arg_bx() as i32 - OFFSET_S_BX
598    }
599
600    #[inline]
601    pub fn set_arg_s_bx(&mut self, b: i32) {
602        self.set_arg_bx((b + OFFSET_S_BX) as u32);
603    }
604
605    // ── Ax field (iAx) ────────────────────────────────────────────────────
606
607    #[inline]
608    pub const fn arg_ax(self) -> u32 {
609        self.get_arg(POS_AX, SIZE_AX)
610    }
611
612    #[inline]
613    pub fn set_arg_ax(&mut self, v: u32) {
614        self.set_arg(v, POS_AX, SIZE_AX);
615    }
616
617    // ── sJ field (isJ) ────────────────────────────────────────────────────
618
619    #[inline]
620    pub const fn arg_s_j(self) -> i32 {
621        self.get_arg(POS_S_J, SIZE_S_J) as i32 - OFFSET_S_J
622    }
623
624    #[inline]
625    pub fn set_arg_s_j(&mut self, j: i32) {
626        self.set_arg((j + OFFSET_S_J) as u32, POS_S_J, SIZE_S_J);
627    }
628
629    // ── Instruction builders ──────────────────────────────────────────────
630
631    /// Build an `iABC` instruction.
632    ///
633    #[inline]
634    pub fn abck(op: OpCode, a: u32, b: u32, c: u32, k: u32) -> Self {
635        Self(
636            ((op as u32) << POS_OP)
637                | (a << POS_A)
638                | (b << POS_B)
639                | (c << POS_C)
640                | (k << POS_K),
641        )
642    }
643
644    /// Build an `iABx` instruction.
645    ///
646    #[inline]
647    pub fn abx(op: OpCode, a: u32, bc: u32) -> Self {
648        Self(((op as u32) << POS_OP) | (a << POS_A) | (bc << POS_BX))
649    }
650
651    /// Build an `iAx` instruction.
652    ///
653    #[inline]
654    pub fn ax(op: OpCode, a: u32) -> Self {
655        Self(((op as u32) << POS_OP) | (a << POS_AX))
656    }
657
658    /// Build an `isJ` instruction.
659    ///
660    #[inline]
661    pub fn sj(op: OpCode, j: u32, k: u32) -> Self {
662        Self(((op as u32) << POS_OP) | (j << POS_S_J) | (k << POS_K))
663    }
664
665    // ── Mode query helpers (isOT / isIT) ──────────────────────────────────
666
667    /// True if this instruction sets `L->top` for the next instruction.
668    ///
669    /// `(testOTMode(GET_OPCODE(i)) && GETARG_C(i) == 0) || GET_OPCODE(i) == OP_TAILCALL`
670    pub fn is_out_top(self) -> bool {
671        match self.opcode() {
672            Some(op) => (test_ot_mode(op) && self.arg_c() == 0) || op == OpCode::TailCall,
673            None => false,
674        }
675    }
676
677    /// True if this instruction uses `L->top` from the previous instruction.
678    ///
679    pub fn is_in_top(self) -> bool {
680        match self.opcode() {
681            Some(op) => test_it_mode(op) && self.arg_b() == 0,
682            None => false,
683        }
684    }
685
686    /// Return the `OpMode` for this instruction.
687    ///
688    pub fn op_mode(self) -> Option<OpMode> {
689        self.opcode().map(get_op_mode)
690    }
691}
692
693// ─── Signed-argument encode/decode helpers ────────────────────────────────────
694//
695//
696// These are inline helpers used at call sites; the Instruction methods above
697// incorporate them, but standalone functions are provided for codegen use.
698
699/// Encode a signed integer into an unsigned C-field value.
700///
701#[inline]
702pub const fn int_to_s_c(i: i32) -> u32 {
703    (i + OFFSET_S_C) as u32
704}
705
706/// Decode a C-field unsigned value to a signed integer.
707///
708#[inline]
709pub const fn s_c_to_int(i: u32) -> i32 {
710    i as i32 - OFFSET_S_C
711}
712
713// ─── Tests ────────────────────────────────────────────────────────────────────
714
715#[cfg(test)]
716mod tests {
717    use super::*;
718
719    #[test]
720    fn num_opcodes_matches_enum() {
721        assert_eq!(NUM_OPCODES, 83);
722        assert_eq!(OpCode::ExtraArg as usize, 82);
723    }
724
725    #[test]
726    fn op_modes_table_length() {
727        assert_eq!(OP_MODES.len(), NUM_OPCODES);
728    }
729
730    #[test]
731    fn opmode_byte_values() {
732        assert_eq!(OP_MODES[OpCode::Move as usize], 0b00001000); // a=1, mode=iABC=0 → 8
733        assert_eq!(OP_MODES[OpCode::LoadI as usize], 0b00001010); // a=1, mode=iAsBx=2 → 10
734        assert_eq!(OP_MODES[OpCode::Jmp as usize], 0b00000100); // a=0, mode=isJ=4 → 4
735        assert_eq!(OP_MODES[OpCode::MmBin as usize], 0b10000000); // mm=1, a=0, mode=iABC=0 → 128
736        assert_eq!(OP_MODES[OpCode::Call as usize], 0b01101000); // ot=1,it=1,a=1,mode=0 → 104
737        assert_eq!(OP_MODES[OpCode::ExtraArg as usize], 0b00000011); // mode=iAx=3 → 3
738    }
739
740    #[test]
741    fn from_u32_round_trip() {
742        for i in 0..NUM_OPCODES {
743            let op = OpCode::from_u32(i as u32).expect("valid opcode");
744            assert_eq!(op as usize, i);
745        }
746        assert!(OpCode::from_u32(83).is_none());
747    }
748
749    #[test]
750    fn instruction_arg_a() {
751        let i = Instruction::abck(OpCode::Move, 5, 3, 0, 0);
752        assert_eq!(i.arg_a(), 5);
753        assert_eq!(i.arg_b(), 3);
754    }
755
756    #[test]
757    fn instruction_s_bx_round_trip() {
758        let mut i = Instruction::abx(OpCode::LoadK, 0, 0);
759        i.set_arg_s_bx(-10);
760        assert_eq!(i.arg_s_bx(), -10);
761        i.set_arg_s_bx(0);
762        assert_eq!(i.arg_s_bx(), 0);
763        i.set_arg_s_bx(100);
764        assert_eq!(i.arg_s_bx(), 100);
765    }
766
767    #[test]
768    fn instruction_s_j_round_trip() {
769        let mut i = Instruction::sj(OpCode::Jmp, (OFFSET_S_J) as u32, 0);
770        assert_eq!(i.arg_s_j(), 0);
771        i.set_arg_s_j(42);
772        assert_eq!(i.arg_s_j(), 42);
773        i.set_arg_s_j(-1);
774        assert_eq!(i.arg_s_j(), -1);
775    }
776
777    #[test]
778    fn get_op_mode_smoke() {
779        assert_eq!(get_op_mode(OpCode::Move), OpMode::Abc);
780        assert_eq!(get_op_mode(OpCode::LoadI), OpMode::AsBx);
781        assert_eq!(get_op_mode(OpCode::LoadK), OpMode::ABx);
782        assert_eq!(get_op_mode(OpCode::Jmp), OpMode::SJ);
783        assert_eq!(get_op_mode(OpCode::ExtraArg), OpMode::Ax);
784    }
785
786    #[test]
787    fn int_to_s_c_round_trip() {
788        assert_eq!(s_c_to_int(int_to_s_c(0)), 0);
789        assert_eq!(s_c_to_int(int_to_s_c(100)), 100);
790        assert_eq!(s_c_to_int(int_to_s_c(-50)), -50);
791    }
792}
793
794// ──────────────────────────────────────────────────────────────────────────
795// PORT STATUS
796//   source:        src/lopcodes.c  (104 lines, 0 functions — data only)
797//                  src/lopcodes.h  (406 lines, merged per PORTING.md §1)
798//   target_crate:  lua-code
799//   confidence:    high
800//   todos:         1
801//   port_notes:    1
802//   unsafe_blocks: 0
803//   notes:         Pure data/encoding translation; OpCode::from_u32 needs
804//                  Phase B review for transmute vs. num_enum. Self_ rename
805//                  is permanent (Rust keyword conflict).
806// ──────────────────────────────────────────────────────────────────────────