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// ──────────────────────────────────────────────────────────────────────────