Skip to main content

lua_vm/
vm.rs

1//! Lua virtual machine — port of `src/lvm.c` (1899 lines, 32 functions).
2//!
3//! This module implements:
4//! - Number coercion helpers (tonumber_, flttointeger, tointegerns, tointeger)
5//! - Numeric `for`-loop preparation and stepping (forlimit, forprep, floatforloop)
6//! - Table get/set with metamethod chaining (finishget, finishset)
7//! - String comparison respecting embedded NULs (l_strcmp)
8//! - Relational operators: lessthan, lessequal, equalobj (with metamethods)
9//! - String concatenation (concat)
10//! - Object length operator (objlen)
11//! - Integer arithmetic: idiv, mod, modf, shiftl
12//! - Closure creation (pushclosure)
13//! - Yield-resume bridge (finishOp)
14//! - Main interpreter loop (execute) — the Lua bytecode dispatch engine.
15//!
16//! # Control flow note
17//! The C source uses `goto startfunc` / `goto returning` / `goto ret` across
18//! labelled points in `luaV_execute`. These are modelled with Rust's labelled
19//! loops (`'startfunc`, `'returning`, `'dispatch`) and `continue`/`break`
20//! on those labels.  See inline `PORT NOTE` comments.
21
22#[allow(unused_imports)]
23use crate::prelude::*;
24use crate::state::LuaState;
25use lua_types::opcode::Instruction;
26use lua_types::tagmethod::TagMethod;
27use lua_types::{CallInfoIdx, GcRef, LuaError, LuaString, LuaValue, StackIdx};
28
29/// TODO(multiversion, Step 0 deferred): this `OpCode` is a DUPLICATE of the
30/// canonical one in `lua-code/src/opcodes.rs:87`. The Step-0 plan wanted them
31/// consolidated to one owner (`lua-code`) with `lua-vm` depending on it, but
32/// that creates a DEPENDENCY CYCLE: `lua-code/Cargo.toml` already depends on
33/// `lua-vm`, so `lua-vm` cannot depend back on `lua-code`. Consolidating
34/// therefore requires moving the canonical `OpCode`/`OP_MODES`/`Instruction`
35/// definitions DOWN into `lua-types` (which `lua-types/src/opcode.rs` already
36/// reserves) and pointing both `lua-vm` and `lua-code` at it — plus reconciling
37/// variant-name skew between the two copies (`lua-vm` uses `BXOrK`/`BXOr`,
38/// `lua-code` uses `BXorK`/`BXor`; `lua-vm` also has `LoadKx`/`GetUpval`
39/// aliases) and the `InstructionExt` decode trait that lives here. That is a
40/// larger refactor than the Step-0 scaffold; deferred to keep 5.4 green.
41/// Duplicate sites: `lua-vm/src/vm.rs:45` (this enum) vs
42/// `lua-code/src/opcodes.rs:87` (canonical).
43///
44/// Original note: Stubbed locally with all 5.4 opcodes so call sites in
45/// vm.rs/debug.rs resolve; the real numeric values and per-opcode mode flags
46/// live in `lua-types/src/opcode.rs` once translated.
47///
48/// `#[repr(u8)]` with explicit discriminants matching C-Lua's `lopcodes.h`
49/// numbering (0=OP_MOVE, 1=OP_LOADI, ..., 82=OP_EXTRAARG). The ordered, dense
50/// 0..=82 layout lets LLVM compile `opcode()` to a bounds-checked cast on the
51/// low 7 bits of the instruction word and fuse it with the dispatch `match`
52/// downstream. Discriminant order intentionally matches the integer keys in
53/// `InstructionExt::opcode`, not the prior compile-order grouping.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
55#[allow(non_camel_case_types)]
56#[repr(u8)]
57pub enum OpCode {
58    Move = 0,
59    LoadI = 1,
60    LoadF = 2,
61    LoadK = 3,
62    LoadKX = 4,
63    LoadFalse = 5,
64    LFalseSkip = 6,
65    LoadTrue = 7,
66    LoadNil = 8,
67    GetUpVal = 9,
68    SetUpVal = 10,
69    GetTabUp = 11,
70    GetTable = 12,
71    GetI = 13,
72    GetField = 14,
73    SetTabUp = 15,
74    SetTable = 16,
75    SetI = 17,
76    SetField = 18,
77    NewTable = 19,
78    Self_ = 20,
79    AddI = 21,
80    AddK = 22,
81    SubK = 23,
82    MulK = 24,
83    ModK = 25,
84    PowK = 26,
85    DivK = 27,
86    IDivK = 28,
87    BAndK = 29,
88    BOrK = 30,
89    BXOrK = 31,
90    ShrI = 32,
91    ShlI = 33,
92    Add = 34,
93    Sub = 35,
94    Mul = 36,
95    Mod = 37,
96    Pow = 38,
97    Div = 39,
98    IDiv = 40,
99    BAnd = 41,
100    BOr = 42,
101    BXOr = 43,
102    Shl = 44,
103    Shr = 45,
104    MmBin = 46,
105    MmBinI = 47,
106    MmBinK = 48,
107    Unm = 49,
108    BNot = 50,
109    Not = 51,
110    Len = 52,
111    Concat = 53,
112    Close = 54,
113    Tbc = 55,
114    Jmp = 56,
115    Eq = 57,
116    Lt = 58,
117    Le = 59,
118    EqK = 60,
119    EqI = 61,
120    LtI = 62,
121    LeI = 63,
122    GtI = 64,
123    GeI = 65,
124    Test = 66,
125    TestSet = 67,
126    Call = 68,
127    TailCall = 69,
128    Return = 70,
129    Return0 = 71,
130    Return1 = 72,
131    ForLoop = 73,
132    ForPrep = 74,
133    TForPrep = 75,
134    TForCall = 76,
135    TForLoop = 77,
136    SetList = 78,
137    Closure = 79,
138    VarArg = 80,
139    VarArgPrep = 81,
140    ExtraArg = 82,
141    /// Lua 5.5 `global name = expr` guard. Reads register A (the current value
142    /// of the global), and if it is non-nil raises `global '<name>' already
143    /// defined`. Bx encodes the name: 0 means "?", otherwise Bx-1 is the index
144    /// into the constant table of the name string. Mirrors upstream
145    /// `OP_ERRNNIL` (`ldebug.c:luaG_errnnil`). 5.5-only; no other version emits
146    /// it. Appended after `ExtraArg` so existing opcode indices are unchanged.
147    ErrNNil = 83,
148    /// Lua 5.5 named-varargs (`function f(...t)`) support. Packs all extra
149    /// varargs of the current frame into a fresh table stored in register A,
150    /// with `table.pack` semantics: a 1-based sequence of all extra args plus
151    /// an integer `.n` field counting them (including nil holes). Emitted once
152    /// at function entry (right after `VarArgPrep`) when a vararg name is bound.
153    /// 5.5-only; no other version's parser emits it. Appended after `ErrNNil`
154    /// so existing opcode indices are unchanged.
155    VarArgPack = 84,
156    /// Lua 5.5 virtual named-vararg indexed read. Reads key register C from the
157    /// named vararg parameter in register B without materializing its table.
158    GetVArg = 85,
159}
160
161/// Number of distinct opcodes (matches C-Lua's `NUM_OPCODES`). Held for
162/// downstream debug/dump callers that count opcodes by name; the dispatch
163/// hot path in `InstructionExt::opcode` does its own per-arm match.
164#[allow(dead_code)]
165const NUM_OPCODES: u8 = 86;
166
167impl OpCode {
168    /// Legacy alias retained because the prior duplicate enum variant
169    /// `LoadKx` (case-typo of `LoadKX`) is still referenced from
170    /// `crates/lua-vm/src/debug.rs`. Both names denote the same C
171    /// `OP_LOADKX` opcode. Kept as an associated `const` so existing call
172    /// sites compile unchanged while the enum remains a clean 0..=82 dense
173    /// discriminant set required by `#[repr(u8)]`.
174    #[allow(non_upper_case_globals)]
175    pub const LoadKx: OpCode = OpCode::LoadKX;
176
177    /// Legacy alias for `GetUpVal` retained for the same reason as `LoadKx`.
178    #[allow(non_upper_case_globals)]
179    pub const GetUpval: OpCode = OpCode::GetUpVal;
180
181    /// Decode a raw opcode field value to an `OpCode`, or `None` if out of
182    /// range (`v >= 83`). This is the canonical decoder; `lua-code` re-exports
183    /// `OpCode` and uses this rather than carrying its own duplicate enum.
184    pub fn from_u32(v: u32) -> Option<Self> {
185        match v {
186            0 => Some(Self::Move),
187            1 => Some(Self::LoadI),
188            2 => Some(Self::LoadF),
189            3 => Some(Self::LoadK),
190            4 => Some(Self::LoadKX),
191            5 => Some(Self::LoadFalse),
192            6 => Some(Self::LFalseSkip),
193            7 => Some(Self::LoadTrue),
194            8 => Some(Self::LoadNil),
195            9 => Some(Self::GetUpVal),
196            10 => Some(Self::SetUpVal),
197            11 => Some(Self::GetTabUp),
198            12 => Some(Self::GetTable),
199            13 => Some(Self::GetI),
200            14 => Some(Self::GetField),
201            15 => Some(Self::SetTabUp),
202            16 => Some(Self::SetTable),
203            17 => Some(Self::SetI),
204            18 => Some(Self::SetField),
205            19 => Some(Self::NewTable),
206            20 => Some(Self::Self_),
207            21 => Some(Self::AddI),
208            22 => Some(Self::AddK),
209            23 => Some(Self::SubK),
210            24 => Some(Self::MulK),
211            25 => Some(Self::ModK),
212            26 => Some(Self::PowK),
213            27 => Some(Self::DivK),
214            28 => Some(Self::IDivK),
215            29 => Some(Self::BAndK),
216            30 => Some(Self::BOrK),
217            31 => Some(Self::BXOrK),
218            32 => Some(Self::ShrI),
219            33 => Some(Self::ShlI),
220            34 => Some(Self::Add),
221            35 => Some(Self::Sub),
222            36 => Some(Self::Mul),
223            37 => Some(Self::Mod),
224            38 => Some(Self::Pow),
225            39 => Some(Self::Div),
226            40 => Some(Self::IDiv),
227            41 => Some(Self::BAnd),
228            42 => Some(Self::BOr),
229            43 => Some(Self::BXOr),
230            44 => Some(Self::Shl),
231            45 => Some(Self::Shr),
232            46 => Some(Self::MmBin),
233            47 => Some(Self::MmBinI),
234            48 => Some(Self::MmBinK),
235            49 => Some(Self::Unm),
236            50 => Some(Self::BNot),
237            51 => Some(Self::Not),
238            52 => Some(Self::Len),
239            53 => Some(Self::Concat),
240            54 => Some(Self::Close),
241            55 => Some(Self::Tbc),
242            56 => Some(Self::Jmp),
243            57 => Some(Self::Eq),
244            58 => Some(Self::Lt),
245            59 => Some(Self::Le),
246            60 => Some(Self::EqK),
247            61 => Some(Self::EqI),
248            62 => Some(Self::LtI),
249            63 => Some(Self::LeI),
250            64 => Some(Self::GtI),
251            65 => Some(Self::GeI),
252            66 => Some(Self::Test),
253            67 => Some(Self::TestSet),
254            68 => Some(Self::Call),
255            69 => Some(Self::TailCall),
256            70 => Some(Self::Return),
257            71 => Some(Self::Return0),
258            72 => Some(Self::Return1),
259            73 => Some(Self::ForLoop),
260            74 => Some(Self::ForPrep),
261            75 => Some(Self::TForPrep),
262            76 => Some(Self::TForCall),
263            77 => Some(Self::TForLoop),
264            78 => Some(Self::SetList),
265            79 => Some(Self::Closure),
266            80 => Some(Self::VarArg),
267            81 => Some(Self::VarArgPrep),
268            82 => Some(Self::ExtraArg),
269            83 => Some(Self::ErrNNil),
270            84 => Some(Self::VarArgPack),
271            85 => Some(Self::GetVArg),
272            _ => None,
273        }
274    }
275}
276
277/// TODO(phase-b): Instruction accessor extension trait. The real per-mode
278/// decode helpers live in `lua-types::opcode` once translated. Stubbed locally
279/// so call sites resolve; bodies are inferred from `lopcodes.h` macro shapes.
280pub trait InstructionExt {
281    fn opcode(&self) -> OpCode;
282    fn arg_a(&self) -> i32;
283    fn arg_b(&self) -> i32;
284    fn arg_c(&self) -> i32;
285    fn arg_k(&self) -> i32;
286    fn arg_ax(&self) -> i32;
287    fn arg_bx(&self) -> i32;
288    fn arg_s_b(&self) -> i32;
289    fn arg_s_c(&self) -> i32;
290    fn arg_s_j(&self) -> i32;
291    fn arg_s_bx(&self) -> i32;
292    fn test_k(&self) -> bool;
293    fn test_a_mode(&self) -> bool;
294    fn is_mm_mode(&self) -> bool;
295    fn is_vararg_prep(&self) -> bool;
296    fn is_in_top(&self) -> bool;
297}
298
299impl InstructionExt for Instruction {
300    ///
301    /// The 83-arm match looks expensive, but because `OpCode` is
302    /// `#[repr(u8)]` with explicit discriminants 0..=82 matching each match
303    /// arm's integer key exactly, LLVM compiles this to a single bounds
304    /// check + identity cast — no jump table, no memory indirection. The
305    /// previous array-lookup form forced an extra `OPCODE_TABLE` byte load
306    /// per dispatch tick that LLVM could not see through.
307    #[inline(always)]
308    fn opcode(&self) -> OpCode {
309        match (self.raw() & 0x7F) as u8 {
310            0 => OpCode::Move,
311            1 => OpCode::LoadI,
312            2 => OpCode::LoadF,
313            3 => OpCode::LoadK,
314            4 => OpCode::LoadKX,
315            5 => OpCode::LoadFalse,
316            6 => OpCode::LFalseSkip,
317            7 => OpCode::LoadTrue,
318            8 => OpCode::LoadNil,
319            9 => OpCode::GetUpVal,
320            10 => OpCode::SetUpVal,
321            11 => OpCode::GetTabUp,
322            12 => OpCode::GetTable,
323            13 => OpCode::GetI,
324            14 => OpCode::GetField,
325            15 => OpCode::SetTabUp,
326            16 => OpCode::SetTable,
327            17 => OpCode::SetI,
328            18 => OpCode::SetField,
329            19 => OpCode::NewTable,
330            20 => OpCode::Self_,
331            21 => OpCode::AddI,
332            22 => OpCode::AddK,
333            23 => OpCode::SubK,
334            24 => OpCode::MulK,
335            25 => OpCode::ModK,
336            26 => OpCode::PowK,
337            27 => OpCode::DivK,
338            28 => OpCode::IDivK,
339            29 => OpCode::BAndK,
340            30 => OpCode::BOrK,
341            31 => OpCode::BXOrK,
342            32 => OpCode::ShrI,
343            33 => OpCode::ShlI,
344            34 => OpCode::Add,
345            35 => OpCode::Sub,
346            36 => OpCode::Mul,
347            37 => OpCode::Mod,
348            38 => OpCode::Pow,
349            39 => OpCode::Div,
350            40 => OpCode::IDiv,
351            41 => OpCode::BAnd,
352            42 => OpCode::BOr,
353            43 => OpCode::BXOr,
354            44 => OpCode::Shl,
355            45 => OpCode::Shr,
356            46 => OpCode::MmBin,
357            47 => OpCode::MmBinI,
358            48 => OpCode::MmBinK,
359            49 => OpCode::Unm,
360            50 => OpCode::BNot,
361            51 => OpCode::Not,
362            52 => OpCode::Len,
363            53 => OpCode::Concat,
364            54 => OpCode::Close,
365            55 => OpCode::Tbc,
366            56 => OpCode::Jmp,
367            57 => OpCode::Eq,
368            58 => OpCode::Lt,
369            59 => OpCode::Le,
370            60 => OpCode::EqK,
371            61 => OpCode::EqI,
372            62 => OpCode::LtI,
373            63 => OpCode::LeI,
374            64 => OpCode::GtI,
375            65 => OpCode::GeI,
376            66 => OpCode::Test,
377            67 => OpCode::TestSet,
378            68 => OpCode::Call,
379            69 => OpCode::TailCall,
380            70 => OpCode::Return,
381            71 => OpCode::Return0,
382            72 => OpCode::Return1,
383            73 => OpCode::ForLoop,
384            74 => OpCode::ForPrep,
385            75 => OpCode::TForPrep,
386            76 => OpCode::TForCall,
387            77 => OpCode::TForLoop,
388            78 => OpCode::SetList,
389            79 => OpCode::Closure,
390            80 => OpCode::VarArg,
391            81 => OpCode::VarArgPrep,
392            82 => OpCode::ExtraArg,
393            83 => OpCode::ErrNNil,
394            84 => OpCode::VarArgPack,
395            85 => OpCode::GetVArg,
396            _ => OpCode::ExtraArg,
397        }
398    }
399    #[inline]
400    fn arg_a(&self) -> i32 {
401        ((self.raw() >> 7) & 0xFF) as i32
402    }
403    #[inline]
404    fn arg_b(&self) -> i32 {
405        ((self.raw() >> 16) & 0xFF) as i32
406    }
407    #[inline]
408    fn arg_c(&self) -> i32 {
409        ((self.raw() >> 24) & 0xFF) as i32
410    }
411    #[inline]
412    fn arg_k(&self) -> i32 {
413        ((self.raw() >> 15) & 0x1) as i32
414    }
415    #[inline]
416    fn arg_ax(&self) -> i32 {
417        (self.raw() >> 7) as i32
418    }
419    #[inline]
420    fn arg_bx(&self) -> i32 {
421        (self.raw() >> 15) as i32
422    }
423    #[inline]
424    fn arg_s_b(&self) -> i32 {
425        self.arg_b() - 0x7F
426    }
427    #[inline]
428    fn arg_s_c(&self) -> i32 {
429        self.arg_c() - 0x7F
430    }
431    #[inline]
432    fn arg_s_j(&self) -> i32 {
433        self.arg_ax() - 0xFFFFFF
434    }
435    #[inline]
436    fn arg_s_bx(&self) -> i32 {
437        self.arg_bx() - 0xFFFF
438    }
439    #[inline]
440    fn test_k(&self) -> bool {
441        (self.raw() & (1 << 15)) != 0
442    }
443    #[inline]
444    fn test_a_mode(&self) -> bool {
445        (op_mode_byte(self.opcode()) & (1 << 3)) != 0
446    }
447    #[inline]
448    fn is_mm_mode(&self) -> bool {
449        (op_mode_byte(self.opcode()) & (1 << 7)) != 0
450    }
451    #[inline]
452    fn is_vararg_prep(&self) -> bool {
453        matches!(self.opcode(), OpCode::VarArgPrep)
454    }
455    #[inline]
456    fn is_in_top(&self) -> bool {
457        (op_mode_byte(self.opcode()) & (1 << 5)) != 0 && self.arg_b() == 0
458    }
459}
460
461///
462/// Layout (from lopcodes.h `opmode` macro):
463///   bit 7: MM (metamethod call)
464///   bit 6: OT (instruction sets `L->top` for next when C == 0)
465///   bit 5: IT (instruction reads `L->top` from prev when B == 0)
466///   bit 4: T  (test; next instruction must be a jump)
467///   bit 3: A  (instruction writes register A)
468///   bits 0-2: op format mode (iABC, iABx, iAsBx, iAx, isJ)
469///
470/// PORT NOTE: lua-types does not yet expose the canonical `OP_MODES` table; this
471/// is a local stand-in keyed off the vm.rs `OpCode` stub so the four mode
472/// predicates above can answer correctly until the real table lands.
473const OP_MODE_BYTES: [u8; NUM_OPCODES as usize] = [
474    0x08, // Move
475    0x0a, // LoadI
476    0x0a, // LoadF
477    0x09, // LoadK
478    0x09, // LoadKX
479    0x08, // LoadFalse
480    0x08, // LFalseSkip
481    0x08, // LoadTrue
482    0x08, // LoadNil
483    0x08, // GetUpVal
484    0x00, // SetUpVal
485    0x08, // GetTabUp
486    0x08, // GetTable
487    0x08, // GetI
488    0x08, // GetField
489    0x00, // SetTabUp
490    0x00, // SetTable
491    0x00, // SetI
492    0x00, // SetField
493    0x08, // NewTable
494    0x08, // Self_
495    0x08, // AddI
496    0x08, // AddK
497    0x08, // SubK
498    0x08, // MulK
499    0x08, // ModK
500    0x08, // PowK
501    0x08, // DivK
502    0x08, // IDivK
503    0x08, // BAndK
504    0x08, // BOrK
505    0x08, // BXOrK
506    0x08, // ShrI
507    0x08, // ShlI
508    0x08, // Add
509    0x08, // Sub
510    0x08, // Mul
511    0x08, // Mod
512    0x08, // Pow
513    0x08, // Div
514    0x08, // IDiv
515    0x08, // BAnd
516    0x08, // BOr
517    0x08, // BXOr
518    0x08, // Shl
519    0x08, // Shr
520    0x80, // MmBin
521    0x80, // MmBinI
522    0x80, // MmBinK
523    0x08, // Unm
524    0x08, // BNot
525    0x08, // Not
526    0x08, // Len
527    0x08, // Concat
528    0x00, // Close
529    0x00, // Tbc
530    0x04, // Jmp
531    0x10, // Eq
532    0x10, // Lt
533    0x10, // Le
534    0x10, // EqK
535    0x10, // EqI
536    0x10, // LtI
537    0x10, // LeI
538    0x10, // GtI
539    0x10, // GeI
540    0x10, // Test
541    0x18, // TestSet
542    0x68, // Call
543    0x68, // TailCall
544    0x20, // Return
545    0x00, // Return0
546    0x00, // Return1
547    0x09, // ForLoop
548    0x09, // ForPrep
549    0x01, // TForPrep
550    0x00, // TForCall
551    0x09, // TForLoop
552    0x20, // SetList
553    0x09, // Closure
554    0x48, // VarArg
555    0x28, // VarArgPrep
556    0x03, // ExtraArg
557    0x01, // ErrNNil (iABx, no A-write, no test)
558    0x08, // VarArgPack (iABC, sets register A)
559    0x08, // GetVArg (iABC, sets register A)
560];
561
562#[inline(always)]
563fn op_mode_byte(op: OpCode) -> u8 {
564    OP_MODE_BYTES[op as usize]
565}
566
567// ─── Constants ───────────────────────────────────────────────────────────────
568
569/// Limit for tag-method chains to avoid infinite loops.
570const MAX_TAG_LOOP: i32 = 2000;
571
572const NBITS: u32 = 64;
573
574// ─── F2Imod — float-to-integer rounding mode ────────────────────────────────
575
576/// Rounding mode for float→integer coercions.
577#[derive(Debug, Clone, Copy, PartialEq, Eq)]
578pub(crate) enum F2Imod {
579    /// Accept only exact integral values (no rounding).
580    Eq,
581    /// Round toward negative infinity.
582    Floor,
583    /// Round toward positive infinity.
584    Ceil,
585}
586
587// ─── Integer-overflow-safe helpers ──────────────────────────────────────────
588
589#[inline]
590fn intop_add(a: i64, b: i64) -> i64 {
591    (a as u64).wrapping_add(b as u64) as i64
592}
593
594#[inline]
595fn intop_sub(a: i64, b: i64) -> i64 {
596    (a as u64).wrapping_sub(b as u64) as i64
597}
598
599#[inline]
600fn intop_mul(a: i64, b: i64) -> i64 {
601    (a as u64).wrapping_mul(b as u64) as i64
602}
603
604/// Shifts via unsigned intermediate to get logical (not arithmetic) semantics.
605#[inline]
606fn intop_shr(x: i64, n: u32) -> i64 {
607    // PERF(port): logical right shift via unsigned; matches C unsigned semantics
608    (x as u64 >> n) as i64
609}
610
611#[inline]
612fn intop_shl(x: i64, n: u32) -> i64 {
613    (x as u64).wrapping_shl(n) as i64
614}
615
616#[inline]
617fn intop_band(a: i64, b: i64) -> i64 {
618    ((a as u64) & (b as u64)) as i64
619}
620#[inline]
621fn intop_bor(a: i64, b: i64) -> i64 {
622    ((a as u64) | (b as u64)) as i64
623}
624#[inline]
625fn intop_bxor(a: i64, b: i64) -> i64 {
626    ((a as u64) ^ (b as u64)) as i64
627}
628
629// ─── l_intfitsf ─────────────────────────────────────────────────────────────
630
631/// f64 has 53 bits of mantissa (including implicit leading 1).
632/// All i64 values with |i| <= 2^53 are exactly representable.
633#[inline]
634fn int_fits_float(i: i64) -> bool {
635    const MAXINTFITSF: u64 = 1u64 << f64::MANTISSA_DIGITS;
636    (MAXINTFITSF.wrapping_add(i as u64)) <= 2 * MAXINTFITSF
637}
638
639// ─── Private helper: string-to-number coercion ──────────────────────────────
640
641/// Attempt to convert a string value to a number in-place.
642/// Returns `Some(LuaValue)` with the numeric result, or `None` if the
643/// value is not a string or cannot be parsed as a numeral.
644fn str_to_number(obj: &LuaValue) -> Option<LuaValue> {
645    // cvt2num(o) = matches!(o, LuaValue::Str(_))
646    let s = match obj {
647        LuaValue::Str(ts) => ts.as_bytes().to_vec(),
648        _ => return None,
649    };
650    // Trim whitespace as Lua allows spaces around numerals in coercions.
651    let trimmed = trim_whitespace(&s);
652    if trimmed.is_empty() {
653        return None;
654    }
655    let mut result = LuaValue::Nil;
656    if crate::object::str2num(trimmed, &mut result) != 0 {
657        return Some(result);
658    }
659    None
660}
661
662fn trim_whitespace(s: &[u8]) -> &[u8] {
663    let start = s
664        .iter()
665        .position(|&b| !b.is_ascii_whitespace())
666        .unwrap_or(s.len());
667    let end = s
668        .iter()
669        .rposition(|&b| !b.is_ascii_whitespace())
670        .map(|i| i + 1)
671        .unwrap_or(0);
672    if start <= end {
673        &s[start..end]
674    } else {
675        &s[0..0]
676    }
677}
678
679// ─── Number coercion (public API matching lvm.h exports) ────────────────────
680
681/// Convert `obj` to f64, with string coercion.  Returns `Some(f64)` on
682/// success.  The fast path (already float) is handled by the caller's
683/// `tonumber` macro (inlined at call sites).
684pub(crate) fn tonumber_(obj: &LuaValue) -> Option<f64> {
685    if let LuaValue::Int(i) = obj {
686        return Some(*i as f64);
687    }
688    if let Some(v) = str_to_number(obj) {
689        return match v {
690            LuaValue::Float(f) => Some(f),
691            LuaValue::Int(i) => Some(i as f64),
692            _ => None,
693        };
694    }
695    None
696}
697
698/// Full numeric coercion including the float fast-path that `tonumber_` omits.
699fn tonumber(obj: &LuaValue) -> Option<f64> {
700    if let LuaValue::Float(f) = obj {
701        return Some(*f);
702    }
703    tonumber_(obj)
704}
705
706/// Convert float `n` to an integer according to `mode`.
707/// Returns `Some(i64)` on success.
708pub(crate) fn flt_to_integer(n: f64, mode: F2Imod) -> Option<i64> {
709    let f = n.floor();
710    if n != f {
711        match mode {
712            F2Imod::Eq => return None,
713            F2Imod::Ceil => {
714                // f = floor(n) + 1 = ceil(n) since n is not integral
715                let f = f + 1.0;
716                // lua_numbertointeger checks i64::MIN <= f <= i64::MAX
717                if f >= i64::MIN as f64 && f < (i64::MAX as f64 + 1.0) {
718                    return Some(f as i64);
719                }
720                return None;
721            }
722            F2Imod::Floor => { /* f is already floor(n) */ }
723        }
724    }
725    if f >= i64::MIN as f64 && f < (i64::MAX as f64 + 1.0) {
726        Some(f as i64)
727    } else {
728        None
729    }
730}
731
732/// Convert a value to integer without string coercion.
733pub(crate) fn to_integer_ns(obj: &LuaValue, mode: F2Imod) -> Option<i64> {
734    if let LuaValue::Float(f) = obj {
735        return flt_to_integer(*f, mode);
736    }
737    if let LuaValue::Int(i) = obj {
738        return Some(*i);
739    }
740    None
741}
742
743/// Convert a value to integer, with string coercion.
744pub(crate) fn to_integer(obj: &LuaValue, mode: F2Imod) -> Option<i64> {
745    let coerced;
746    let obj = if let Some(v) = str_to_number(obj) {
747        coerced = v;
748        &coerced
749    } else {
750        obj
751    };
752    to_integer_ns(obj, mode)
753}
754
755// ─── for-loop helpers ────────────────────────────────────────────────────────
756
757/// lua_Integer *p, lua_Integer step)`
758/// Compute the integer loop limit.  Returns `Ok(true)` to skip the loop,
759/// `Ok(false)` with `*p` set to the limit, or `Err` if the limit is not a
760/// number at all.
761fn forlimit(
762    state: &mut LuaState,
763    init: i64,
764    lim: &LuaValue,
765    step: i64,
766) -> Result<(bool, i64), LuaError> {
767    let round = if step < 0 {
768        F2Imod::Ceil
769    } else {
770        F2Imod::Floor
771    };
772    if let Some(p) = to_integer(lim, round) {
773        let skip = if step > 0 { init > p } else { init < p };
774        return Ok((skip, p));
775    }
776    let flim = match tonumber(lim) {
777        Some(f) => f,
778        None => return Err(crate::debug::for_error(state, lim, b"limit")),
779    };
780    if 0.0_f64 < flim {
781        // positive → too large
782        if step < 0 {
783            return Ok((true, 0));
784        }
785        Ok((false, i64::MAX))
786    } else {
787        // negative → less than min integer
788        if step > 0 {
789            return Ok((true, 0));
790        }
791        Ok((false, i64::MIN))
792    }
793}
794
795/// Prepare a numeric `for` loop (OP_FORPREP).
796/// Stack layout at `ra`:
797///   ra+0: init, ra+1: limit, ra+2: step, ra+3: control variable (written here)
798/// Returns `Ok(true)` to skip the loop body entirely.
799pub(crate) fn forprep(state: &mut LuaState, ra: StackIdx) -> Result<bool, LuaError> {
800    let pinit = state.get_at(ra);
801    let plimit = state.get_at(ra + 1);
802    let pstep = state.get_at(ra + 2);
803
804    if let (LuaValue::Int(init), LuaValue::Int(step)) = (&pinit, &pstep) {
805        let init = *init;
806        let step = *step;
807        if step == 0 {
808            return Err(LuaError::runtime(format_args!("'for' step is zero")));
809        }
810        state.set_at(ra + 3, LuaValue::Int(init));
811
812        let (skip, limit) = forlimit(state, init, &plimit, step)?;
813        if skip {
814            return Ok(true);
815        }
816        let count: u64 = if step > 0 {
817            let c = (limit as u64).wrapping_sub(init as u64);
818            if step != 1 {
819                c / (step as u64)
820            } else {
821                c
822            }
823        } else {
824            let c = (init as u64).wrapping_sub(limit as u64);
825            c / (((-(step + 1)) as u64).wrapping_add(1))
826        };
827        state.set_at(ra + 1, LuaValue::Int(count as i64));
828        Ok(false)
829    } else {
830        let limit_f = match tonumber(&plimit) {
831            Some(f) => f,
832            None => return Err(crate::debug::for_error(state, &plimit, b"limit")),
833        };
834        let step_f = match tonumber(&pstep) {
835            Some(f) => f,
836            None => return Err(crate::debug::for_error(state, &pstep, b"step")),
837        };
838        let init_f = match tonumber(&pinit) {
839            Some(f) => f,
840            None => return Err(crate::debug::for_error(state, &pinit, b"initial value")),
841        };
842        if step_f == 0.0 {
843            return Err(LuaError::runtime(format_args!("'for' step is zero")));
844        }
845        let skip = if step_f > 0.0 {
846            limit_f < init_f
847        } else {
848            init_f < limit_f
849        };
850        if skip {
851            return Ok(true);
852        }
853        //    setfltvalue(s2v(ra), init); setfltvalue(s2v(ra+3), init);
854        state.set_at(ra + 1, LuaValue::Float(limit_f));
855        state.set_at(ra + 2, LuaValue::Float(step_f));
856        state.set_at(ra, LuaValue::Float(init_f));
857        state.set_at(ra + 3, LuaValue::Float(init_f));
858        Ok(false)
859    }
860}
861
862/// `forlimit` for the legacy (<=5.3) numeric `for`. Mirrors 5.3.6 `forlimit`:
863/// returns `Some((clamped_limit, stopnow))` when `obj` is a number — clamping an
864/// out-of-integer-range float limit to `i64::MAX`/`MIN` and flagging `stopnow`
865/// when that means the loop must not run — or `None` when `obj` is not a number
866/// (the caller then falls through to the float path / error).
867fn forlimit_legacy(obj: &LuaValue, step: i64) -> Option<(i64, bool)> {
868    let round = if step < 0 {
869        F2Imod::Ceil
870    } else {
871        F2Imod::Floor
872    };
873    if let Some(p) = to_integer(obj, round) {
874        return Some((p, false));
875    }
876    let n = tonumber(obj)?;
877    if 0.0 < n {
878        Some((i64::MAX, step < 0))
879    } else {
880        Some((i64::MIN, step >= 0))
881    }
882}
883
884/// Prepare a legacy (<=5.3) numeric `for` (OP_FORPREP). Mirrors 5.3.6
885/// `OP_FORPREP`: subtract the step from the initial value and let the caller
886/// always jump forward to OP_FORLOOP (which performs the first test). This is
887/// what makes iteration 1 enter the body via a backward jump — the source of
888/// the extra per-iteration line-hook event on <=5.3 (issue #92). Note there is
889/// deliberately **no** "'for' step is zero" check (that was added in 5.4): on
890/// 5.3 a zero step simply fails FORLOOP's test and the loop runs zero times.
891pub(crate) fn forprep_legacy(state: &mut LuaState, ra: StackIdx) -> Result<(), LuaError> {
892    let init = state.get_at(ra);
893    let plimit = state.get_at(ra + 1);
894    let pstep = state.get_at(ra + 2);
895
896    // 5.1/5.2 `OP_FORPREP` coerce in source order init → limit → step, so the
897    // *initial value* is the first reported when several operands are
898    // non-numeric (`for i='a','b'` blames the initial value, not the limit).
899    // 5.3 reordered the checks to limit → step → init (it clamps the limit
900    // first via `forlimit`), which the shared path below already mirrors.
901    if matches!(
902        state.global().lua_version,
903        lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52
904    ) && !matches!(init, LuaValue::Int(_))
905        && tonumber(&init).is_none()
906    {
907        return Err(crate::debug::for_error(state, &init, b"initial value"));
908    }
909
910    if let (LuaValue::Int(initv), LuaValue::Int(stepv)) = (&init, &pstep) {
911        let (initv, stepv) = (*initv, *stepv);
912        if let Some((ilimit, stopnow)) = forlimit_legacy(&plimit, stepv) {
913            let base = if stopnow { 0 } else { initv };
914            state.set_at(ra + 1, LuaValue::Int(ilimit));
915            state.set_at(ra, LuaValue::Int(intop_sub(base, stepv)));
916            return Ok(());
917        }
918        // limit is not a number: fall through so the float path raises
919        // "'for' limit must be a number" in upstream source order.
920    }
921
922    let nlimit = match tonumber(&plimit) {
923        Some(f) => f,
924        None => return Err(crate::debug::for_error(state, &plimit, b"limit")),
925    };
926    let nstep = match tonumber(&pstep) {
927        Some(f) => f,
928        None => return Err(crate::debug::for_error(state, &pstep, b"step")),
929    };
930    let ninit = match tonumber(&init) {
931        Some(f) => f,
932        None => return Err(crate::debug::for_error(state, &init, b"initial value")),
933    };
934    state.set_at(ra + 1, LuaValue::Float(nlimit));
935    state.set_at(ra + 2, LuaValue::Float(nstep));
936    state.set_at(ra, LuaValue::Float(ninit - nstep));
937    Ok(())
938}
939
940/// One iteration of a legacy (<=5.3) numeric `for` (OP_FORLOOP). Adds the step
941/// to the index and tests against the already-clamped limit; returns `true`
942/// when the loop continues (the caller jumps back to the body). Mirrors 5.3.6
943/// `OP_FORLOOP` — compare-based, no precomputed count.
944fn forloop_legacy(state: &mut LuaState, ra: StackIdx) -> bool {
945    if let LuaValue::Int(step) = state.get_at(ra + 2) {
946        let idx = intop_add(
947            match state.get_at(ra) {
948                LuaValue::Int(x) => x,
949                _ => 0,
950            },
951            step,
952        );
953        let limit = match state.get_at(ra + 1) {
954            LuaValue::Int(l) => l,
955            _ => 0,
956        };
957        let cont = if step > 0 { idx <= limit } else { limit <= idx };
958        if cont {
959            state.set_at(ra, LuaValue::Int(idx));
960            state.set_at(ra + 3, LuaValue::Int(idx));
961        }
962        cont
963    } else {
964        let step = match state.get_at(ra + 2) {
965            LuaValue::Float(f) => f,
966            _ => return false,
967        };
968        let idx = match state.get_at(ra) {
969            LuaValue::Float(f) => f,
970            _ => return false,
971        } + step;
972        let limit = match state.get_at(ra + 1) {
973            LuaValue::Float(f) => f,
974            _ => return false,
975        };
976        let cont = if step > 0.0 {
977            idx <= limit
978        } else {
979            limit <= idx
980        };
981        if cont {
982            state.set_at(ra, LuaValue::Float(idx));
983            state.set_at(ra + 3, LuaValue::Float(idx));
984        }
985        cont
986    }
987}
988
989/// Increments the float loop index and returns `true` if the loop continues.
990fn float_for_loop(state: &mut LuaState, ra: StackIdx) -> bool {
991    //    idx  = fltvalue(s2v(ra));
992    let step = match state.get_at(ra + 2) {
993        LuaValue::Float(f) => f,
994        _ => return false,
995    };
996    let limit = match state.get_at(ra + 1) {
997        LuaValue::Float(f) => f,
998        _ => return false,
999    };
1000    let idx = match state.get_at(ra) {
1001        LuaValue::Float(f) => f,
1002        _ => return false,
1003    };
1004    let idx = idx + step;
1005    if if step > 0.0 {
1006        idx <= limit
1007    } else {
1008        limit <= idx
1009    } {
1010        state.set_at(ra, LuaValue::Float(idx));
1011        state.set_at(ra + 3, LuaValue::Float(idx));
1012        true
1013    } else {
1014        false
1015    }
1016}
1017
1018// ─── Table get/set with metamethod chains ────────────────────────────────────
1019
1020/// StkId val, const TValue *slot)`
1021/// Finish a table-get with metamethod lookup.  `slot_was_none = true` means
1022/// `t` is not a table and we should look for `__index` on `t` itself.
1023pub(crate) fn finish_get(
1024    state: &mut LuaState,
1025    t_val: LuaValue,
1026    key: LuaValue,
1027    result_idx: StackIdx,
1028    slot_empty: bool,
1029    t_idx: Option<StackIdx>,
1030    var_hint: Option<(&[u8], &[u8])>,
1031) -> Result<(), LuaError> {
1032    let mut t = t_val;
1033    let mut t_idx = t_idx;
1034    for _loop in 0..MAX_TAG_LOOP {
1035        let tm: LuaValue;
1036        if slot_empty && !matches!(t, LuaValue::Table(_)) {
1037            tm = state.get_tm_by_obj(&t, TagMethod::Index);
1038            if matches!(tm, LuaValue::Nil) {
1039                return Err(match (t_idx, var_hint) {
1040                    (Some(idx), _) => crate::debug::type_error(state, &t, idx, b"index"),
1041                    (None, Some((kind, name))) => {
1042                        crate::debug::type_error_with_hint(state, &t, b"index", kind, name)
1043                    }
1044                    (None, None) => LuaError::type_error(&t, "index"),
1045                });
1046            }
1047        } else {
1048            let mt = state.table_metatable(&t);
1049            tm = state.fast_tm_table(mt.as_ref(), TagMethod::Index);
1050            if matches!(tm, LuaValue::Nil) {
1051                state.set_at(result_idx, LuaValue::Nil);
1052                return Ok(());
1053            }
1054        }
1055        if matches!(tm, LuaValue::Function(_)) {
1056            state.call_tm_res(tm, &t, &key, result_idx)?;
1057            return Ok(());
1058        }
1059        t = tm.clone();
1060        t_idx = None;
1061        if let Some(v) = state.fast_get(&t, &key)? {
1062            state.set_at(result_idx, v);
1063            return Ok(());
1064        }
1065        // else: loop — tail-call luaV_finishget
1066    }
1067    Err(LuaError::runtime(format_args!(
1068        "'__index' chain too long; possible loop"
1069    )))
1070}
1071
1072/// TValue *val, const TValue *slot)`
1073/// Finish a table-set with `__newindex` metamethod lookup.
1074///
1075/// `var_hint` carries a `(kind, name)` pair (e.g. `(b"upvalue", b"a")`) used
1076/// only when `t_idx` is None and the target is non-indexable — typically
1077/// when the LHS is an upvalue (OP_SETTABUP). Pointer-identifying var_info
1078/// won't recover the upvalue's name in that case, so the caller passes it
1079/// in directly.
1080pub(crate) fn finish_set(
1081    state: &mut LuaState,
1082    t_val: LuaValue,
1083    key: LuaValue,
1084    val: LuaValue,
1085    _slot_present: bool,
1086    t_idx: Option<StackIdx>,
1087    var_hint: Option<(&[u8], &[u8])>,
1088) -> Result<(), LuaError> {
1089    let mut t = t_val;
1090    let mut t_idx = t_idx;
1091    for _loop in 0..MAX_TAG_LOOP {
1092        let tm: LuaValue;
1093        if matches!(t, LuaValue::Table(_)) {
1094            let mt = state.table_metatable(&t);
1095            tm = state.fast_tm_table(mt.as_ref(), TagMethod::NewIndex);
1096            if matches!(tm, LuaValue::Nil) {
1097                state.table_raw_set(&t, key, val.clone())?;
1098                state.gc_value_barrier_back(&t, &val);
1099                return Ok(());
1100            }
1101        } else {
1102            tm = state.get_tm_by_obj(&t, TagMethod::NewIndex);
1103            if matches!(tm, LuaValue::Nil) {
1104                return Err(match (t_idx, var_hint) {
1105                    (Some(idx), _) => crate::debug::type_error(state, &t, idx, b"index"),
1106                    (None, Some((kind, name))) => {
1107                        crate::debug::type_error_with_hint(state, &t, b"index", kind, name)
1108                    }
1109                    (None, None) => LuaError::type_error(&t, "index"),
1110                });
1111            }
1112        }
1113        if matches!(tm, LuaValue::Function(_)) {
1114            state.call_tm(tm, &t, &key, &val)?;
1115            return Ok(());
1116        }
1117        t = tm.clone();
1118        t_idx = None;
1119        if state.fast_get(&t, &key)?.is_some() {
1120            state.table_raw_set(&t, key.clone(), val.clone())?;
1121            state.gc_value_barrier_back(&t, &val);
1122            return Ok(());
1123        }
1124    }
1125    Err(LuaError::runtime(format_args!(
1126        "'__newindex' chain too long; possible loop"
1127    )))
1128}
1129
1130// ─── String comparison ───────────────────────────────────────────────────────
1131
1132/// Lexicographic string comparison that handles embedded NULs by segmenting.
1133/// Returns negative / zero / positive like `strcmp`.
1134///
1135/// PORT NOTE: C uses `strcoll` for locale-aware comparison within each NUL-free
1136/// segment.  Rust's standard library has no locale support, so we use
1137/// `slice::cmp` (byte-by-byte lexicographic order, equivalent to `memcmp`).
1138/// This means locale-specific ordering (e.g. accented characters) differs from
1139/// the C reference.  Mark as TODO for a later `libc::strcoll` bridge if needed.
1140fn str_cmp(s1: &[u8], s2: &[u8]) -> std::cmp::Ordering {
1141    // TODO(port): C uses strcoll per-segment; here we use byte-lexicographic
1142    // order.  This affects locale-sensitive string comparisons.
1143    let mut s1 = s1;
1144    let mut s2 = s2;
1145    loop {
1146        // Find the first NUL in each slice to delimit a segment.
1147        let z1 = s1.iter().position(|&b| b == 0).unwrap_or(s1.len());
1148        let z2 = s2.iter().position(|&b| b == 0).unwrap_or(s2.len());
1149        // Compare segment up to first NUL using byte order (not strcoll).
1150        let seg_cmp = s1[..z1].cmp(&s2[..z2]);
1151        if seg_cmp != std::cmp::Ordering::Equal {
1152            return seg_cmp;
1153        }
1154        // Both segments compare equal up to the NUL position.
1155        if z2 == s2.len() {
1156            // s2 is finished
1157            if z1 == s1.len() {
1158                return std::cmp::Ordering::Equal;
1159            }
1160            return std::cmp::Ordering::Greater; // s1 has more
1161        }
1162        if z1 == s1.len() {
1163            return std::cmp::Ordering::Less; // s1 finished, s2 has more
1164        }
1165        // Both have NULs; advance past them.
1166        s1 = &s1[z1 + 1..];
1167        s2 = &s2[z2 + 1..];
1168    }
1169}
1170
1171// ─── Comparison helpers (int vs float mixed comparisons) ────────────────────
1172
1173#[inline]
1174fn lt_int_float(i: i64, f: f64) -> bool {
1175    if int_fits_float(i) {
1176        (i as f64) < f
1177    } else {
1178        match flt_to_integer(f, F2Imod::Ceil) {
1179            Some(fi) => i < fi,
1180            None => f > 0.0, // f is out of integer range; positive means i < f
1181        }
1182    }
1183}
1184
1185#[inline]
1186fn le_int_float(i: i64, f: f64) -> bool {
1187    if int_fits_float(i) {
1188        (i as f64) <= f
1189    } else {
1190        match flt_to_integer(f, F2Imod::Floor) {
1191            Some(fi) => i <= fi,
1192            None => f > 0.0,
1193        }
1194    }
1195}
1196
1197#[inline]
1198fn lt_float_int(f: f64, i: i64) -> bool {
1199    if int_fits_float(i) {
1200        f < (i as f64)
1201    } else {
1202        match flt_to_integer(f, F2Imod::Floor) {
1203            Some(fi) => fi < i,
1204            None => f < 0.0,
1205        }
1206    }
1207}
1208
1209#[inline]
1210fn le_float_int(f: f64, i: i64) -> bool {
1211    if int_fits_float(i) {
1212        f <= (i as f64)
1213    } else {
1214        match flt_to_integer(f, F2Imod::Ceil) {
1215            Some(fi) => fi <= i,
1216            None => f < 0.0,
1217        }
1218    }
1219}
1220
1221#[inline]
1222fn lt_num(l: &LuaValue, r: &LuaValue) -> bool {
1223    debug_assert!(matches!(l, LuaValue::Int(_) | LuaValue::Float(_)));
1224    debug_assert!(matches!(r, LuaValue::Int(_) | LuaValue::Float(_)));
1225    match (l, r) {
1226        (LuaValue::Int(li), LuaValue::Int(ri)) => li < ri,
1227        (LuaValue::Int(li), LuaValue::Float(rf)) => lt_int_float(*li, *rf),
1228        (LuaValue::Float(lf), LuaValue::Float(rf)) => lf < rf,
1229        (LuaValue::Float(lf), LuaValue::Int(ri)) => lt_float_int(*lf, *ri),
1230        _ => false,
1231    }
1232}
1233
1234#[inline]
1235fn le_num(l: &LuaValue, r: &LuaValue) -> bool {
1236    debug_assert!(matches!(l, LuaValue::Int(_) | LuaValue::Float(_)));
1237    debug_assert!(matches!(r, LuaValue::Int(_) | LuaValue::Float(_)));
1238    match (l, r) {
1239        (LuaValue::Int(li), LuaValue::Int(ri)) => li <= ri,
1240        (LuaValue::Int(li), LuaValue::Float(rf)) => le_int_float(*li, *rf),
1241        (LuaValue::Float(lf), LuaValue::Float(rf)) => lf <= rf,
1242        (LuaValue::Float(lf), LuaValue::Int(ri)) => le_float_int(*lf, *ri),
1243        _ => false,
1244    }
1245}
1246
1247/// `l < r` for non-numbers (strings or metamethod).
1248fn less_than_others(state: &mut LuaState, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
1249    debug_assert!(
1250        !(matches!(l, LuaValue::Int(_) | LuaValue::Float(_))
1251            && matches!(r, LuaValue::Int(_) | LuaValue::Float(_)))
1252    );
1253    match (l, r) {
1254        (LuaValue::Str(ts1), LuaValue::Str(ts2)) => {
1255            Ok(str_cmp(ts1.as_bytes(), ts2.as_bytes()) == std::cmp::Ordering::Less)
1256        }
1257        _ => state.call_order_tm(l, r, TagMethod::Lt),
1258    }
1259}
1260
1261pub(crate) fn less_than(
1262    state: &mut LuaState,
1263    l: &LuaValue,
1264    r: &LuaValue,
1265) -> Result<bool, LuaError> {
1266    if matches!(l, LuaValue::Int(_) | LuaValue::Float(_))
1267        && matches!(r, LuaValue::Int(_) | LuaValue::Float(_))
1268    {
1269        Ok(lt_num(l, r))
1270    } else {
1271        less_than_others(state, l, r)
1272    }
1273}
1274
1275fn less_equal_others(state: &mut LuaState, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
1276    match (l, r) {
1277        (LuaValue::Str(ts1), LuaValue::Str(ts2)) => {
1278            Ok(str_cmp(ts1.as_bytes(), ts2.as_bytes()) != std::cmp::Ordering::Greater)
1279        }
1280        _ => state.call_order_tm(l, r, TagMethod::Le),
1281    }
1282}
1283
1284pub(crate) fn less_equal(
1285    state: &mut LuaState,
1286    l: &LuaValue,
1287    r: &LuaValue,
1288) -> Result<bool, LuaError> {
1289    if matches!(l, LuaValue::Int(_) | LuaValue::Float(_))
1290        && matches!(r, LuaValue::Int(_) | LuaValue::Float(_))
1291    {
1292        Ok(le_num(l, r))
1293    } else {
1294        less_equal_others(state, l, r)
1295    }
1296}
1297
1298// ─── Equality ────────────────────────────────────────────────────────────────
1299
1300/// `luaO_rawequalObj`: primitive equality with no metamethod dispatch.
1301///
1302/// Calling `equal_obj` with a `None` state suppresses every metamethod, so the
1303/// result is the raw tag+value/pointer comparison and can never raise. Used by
1304/// the 5.1 same-reference comparison rule to test whether two handler functions
1305/// are the identical reference.
1306pub(crate) fn raw_equal_values(t1: &LuaValue, t2: &LuaValue) -> bool {
1307    matches!(equal_obj(None, t1, t2), Ok(true))
1308}
1309
1310/// Select the `__eq` metamethod for two table/userdata operands.
1311///
1312/// 5.2+ consult the left operand's handler, then the right's (left-then-right).
1313/// 5.1 honours `__eq` only when both operands resolve to the SAME handler
1314/// reference (`get_equalTM`), returning `Nil` (i.e. "not equal") otherwise. The
1315/// caller has already established that the operands are the same kind and not
1316/// the identical object.
1317fn equal_tm(state: &mut LuaState, t1: &LuaValue, t2: &LuaValue) -> LuaValue {
1318    let eq = crate::tagmethods::TagMethod::Eq;
1319    if state.global().lua_version == lua_types::LuaVersion::V51 {
1320        return crate::tagmethods::get_comp_tm_51(state, t1, t2, eq);
1321    }
1322    let tm1 = crate::tagmethods::get_tm_by_obj(state, t1, eq);
1323    if tm1.is_nil() {
1324        crate::tagmethods::get_tm_by_obj(state, t2, eq)
1325    } else {
1326        tm1
1327    }
1328}
1329
1330/// Main equality test.  `raw = true` means no metamethods (L == NULL in C).
1331pub(crate) fn equal_obj(
1332    state: Option<&mut LuaState>,
1333    t1: &LuaValue,
1334    t2: &LuaValue,
1335) -> Result<bool, LuaError> {
1336    // In Rust, same variant = same tag.  If variant differs, check the number
1337    // special case (Int and Float can be equal).
1338    let same_variant = std::mem::discriminant(t1) == std::mem::discriminant(t2);
1339    if !same_variant {
1340        let t1_is_num = matches!(t1, LuaValue::Int(_) | LuaValue::Float(_));
1341        let t2_is_num = matches!(t2, LuaValue::Int(_) | LuaValue::Float(_));
1342        if !(t1_is_num && t2_is_num) {
1343            return Ok(false);
1344        }
1345        // luaV_tointegerns(t1, &i1, F2Ieq) && luaV_tointegerns(t2, &i2, F2Ieq) && i1==i2
1346        let i1 = to_integer_ns(t1, F2Imod::Eq);
1347        let i2 = to_integer_ns(t2, F2Imod::Eq);
1348        return Ok(i1.is_some() && i2.is_some() && i1 == i2);
1349    }
1350
1351    match (t1, t2) {
1352        (LuaValue::Nil, LuaValue::Nil) => Ok(true),
1353        (LuaValue::Bool(b1), LuaValue::Bool(b2)) => Ok(b1 == b2),
1354        (LuaValue::Int(i1), LuaValue::Int(i2)) => Ok(i1 == i2),
1355        (LuaValue::Float(f1), LuaValue::Float(f2)) => Ok(f1 == f2),
1356        (LuaValue::LightUserData(p1), LuaValue::LightUserData(p2)) => Ok(p1 == p2),
1357        (LuaValue::Function(f1), LuaValue::Function(f2)) => {
1358            use lua_types::closure::LuaClosure;
1359            let same = match (f1, f2) {
1360                (LuaClosure::Lua(a), LuaClosure::Lua(b)) => GcRef::ptr_eq(a, b),
1361                (LuaClosure::C(a), LuaClosure::C(b)) => GcRef::ptr_eq(a, b),
1362                (LuaClosure::LightC(a), LuaClosure::LightC(b)) => a == b,
1363                _ => false,
1364            };
1365            Ok(same)
1366        }
1367        (LuaValue::Str(s1), LuaValue::Str(s2)) => {
1368            //    luaS_eqlngstr for long strings (content eq).
1369            // In Rust, LuaString PartialEq handles both.
1370            Ok(s1 == s2)
1371        }
1372        (LuaValue::UserData(u1), LuaValue::UserData(u2)) => {
1373            //    else if (L == NULL) return 0;
1374            //    tm = fasttm(L, uvalue(t1)->metatable, TM_EQ);
1375            if std::ptr::eq(u1.as_ptr(), u2.as_ptr()) {
1376                return Ok(true);
1377            }
1378            let Some(state) = state else {
1379                return Ok(false);
1380            };
1381            let tm = equal_tm(state, t1, t2);
1382            if matches!(tm, LuaValue::Nil) {
1383                return Ok(false);
1384            }
1385            let result = state.call_tm_res_bool(tm, t1, t2)?;
1386            Ok(result)
1387        }
1388        (LuaValue::Table(h1), LuaValue::Table(h2)) => {
1389            if std::ptr::eq(h1.as_ptr(), h2.as_ptr()) {
1390                return Ok(true);
1391            }
1392            let Some(state) = state else {
1393                return Ok(false);
1394            };
1395            let tm = equal_tm(state, t1, t2);
1396            if matches!(tm, LuaValue::Nil) {
1397                return Ok(false);
1398            }
1399            let result = state.call_tm_res_bool(tm, t1, t2)?;
1400            Ok(result)
1401        }
1402        (LuaValue::Thread(a), LuaValue::Thread(b)) => Ok(GcRef::ptr_eq(a, b)),
1403        _ => Ok(std::ptr::eq(t1 as *const _, t2 as *const _)),
1404    }
1405}
1406
1407// ─── Concatenation ───────────────────────────────────────────────────────────
1408
1409/// Copy `n` strings from `top-n .. top-1` into `buff`.
1410fn copy_to_buf(state: &LuaState, top: StackIdx, n: u32, buf: &mut Vec<u8>) {
1411    buf.clear();
1412    let mut remaining = n;
1413    loop {
1414        let idx = top - remaining as i32;
1415        let v = state.get_at(idx);
1416        if let LuaValue::Str(ts) = v {
1417            buf.extend_from_slice(ts.as_bytes());
1418        }
1419        if remaining <= 1 {
1420            break;
1421        }
1422        remaining -= 1;
1423    }
1424}
1425
1426/// Concatenate `total` values on the top of the stack, leaving one result.
1427pub(crate) fn concat(state: &mut LuaState, total: i32) -> Result<(), LuaError> {
1428    if total == 1 {
1429        return Ok(());
1430    }
1431    if total == 2 {
1432        let top = state.top_idx();
1433        let v_tm1 = state.get_at(top - 1);
1434        let v_tm2 = state.get_at(top - 2);
1435        if concat_pair_fast(state, top, v_tm2, v_tm1)? {
1436            return Ok(());
1437        }
1438    }
1439    let mut total = total;
1440    loop {
1441        let top = state.top_idx();
1442        let v_tm1 = state.get_at(top - 1); // top-1
1443        let v_tm2 = state.get_at(top - 2); // top-2
1444
1445        //    luaT_tryconcatTM(L);
1446        let top2_coercible = matches!(v_tm2, LuaValue::Str(_))
1447            || matches!(v_tm2, LuaValue::Int(_) | LuaValue::Float(_));
1448        // tostring converts numbers to strings; we check top-1 too
1449        let top1_stringlike = matches!(v_tm1, LuaValue::Str(_))
1450            || matches!(v_tm1, LuaValue::Int(_) | LuaValue::Float(_));
1451        if !top2_coercible || !top1_stringlike {
1452            state.try_concat_tm(&v_tm1, &v_tm2)?;
1453            // at the bottom of the do-while runs for this branch too.
1454            // The metamethod writes its single result to top-2, leaving
1455            // top-1 stale; popping that stale slot is what makes the next
1456            // iteration see the just-computed result at the new top-1.
1457            total -= 1;
1458            let top = state.top_idx();
1459            state.set_top(top - 1);
1460            if total <= 1 {
1461                break;
1462            }
1463            continue;
1464        }
1465
1466        let is_empty =
1467            |v: &LuaValue| -> bool { matches!(v, LuaValue::Str(s) if s.as_bytes().is_empty()) };
1468
1469        let n: u32;
1470        if is_empty(&v_tm1) {
1471            state.coerce_to_string(top - 2)?;
1472            n = 2;
1473        } else if is_empty(&v_tm2) {
1474            // so top-1 is guaranteed to be a string here. We replicate that
1475            // conversion before the copy so numbers don't leak through.
1476            state.coerce_to_string(top - 1)?;
1477            let v = state.get_at(top - 1);
1478            state.set_at(top - 2, v);
1479            n = 2;
1480        } else {
1481            // Ensure top-1 is a string (coerce if number)
1482            state.coerce_to_string(top - 1)?;
1483            let s1 = match state.get_at(top - 1) {
1484                LuaValue::Str(ts) => ts.as_bytes().len(),
1485                _ => 0,
1486            };
1487            let mut total_len = s1;
1488            let mut count: u32 = 1;
1489            let top = state.top_idx();
1490            loop {
1491                if count as i32 >= total {
1492                    break;
1493                }
1494                let idx = top - (count as i32 + 1);
1495                let v = state.get_at(idx);
1496                if !matches!(v, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_)) {
1497                    break;
1498                }
1499                state.coerce_to_string(idx)?;
1500                let l = match state.get_at(idx) {
1501                    LuaValue::Str(ts) => ts.as_bytes().len(),
1502                    _ => 0,
1503                };
1504                if l >= usize::MAX - total_len {
1505                    // pop strings to avoid wasting stack
1506                    state.set_top(top - total as i32);
1507                    return Err(LuaError::runtime(format_args!("string length overflow")));
1508                }
1509                total_len += l;
1510                count += 1;
1511            }
1512            n = count;
1513
1514            // Build concatenated result
1515            let mut buf: Vec<u8> = Vec::with_capacity(total_len);
1516            let top = state.top_idx();
1517            copy_to_buf(state, top, n, &mut buf);
1518            let ts = state.intern_or_create_str(&buf)?;
1519            state.set_at(top - n as i32, LuaValue::Str(ts));
1520        }
1521        total -= n as i32 - 1;
1522        let top = state.top_idx();
1523        state.set_top(top - ((n - 1) as i32));
1524
1525        if total <= 1 {
1526            break;
1527        }
1528    }
1529    Ok(())
1530}
1531
1532enum ConcatPiece {
1533    Str(GcRef<LuaString>),
1534    Num(Vec<u8>),
1535}
1536
1537impl ConcatPiece {
1538    #[inline]
1539    fn len(&self) -> usize {
1540        match self {
1541            ConcatPiece::Str(s) => s.as_bytes().len(),
1542            ConcatPiece::Num(bytes) => bytes.len(),
1543        }
1544    }
1545
1546    #[inline]
1547    fn append_to(&self, out: &mut Vec<u8>) {
1548        match self {
1549            ConcatPiece::Str(s) => out.extend_from_slice(s.as_bytes()),
1550            ConcatPiece::Num(bytes) => out.extend_from_slice(bytes),
1551        }
1552    }
1553}
1554
1555#[inline]
1556fn concat_piece(v: LuaValue, version: lua_types::LuaVersion) -> Option<ConcatPiece> {
1557    match v {
1558        LuaValue::Str(s) => Some(ConcatPiece::Str(s)),
1559        LuaValue::Int(_) | LuaValue::Float(_) => Some(ConcatPiece::Num(
1560            crate::object::number_to_str_buf(&v, version),
1561        )),
1562        _ => None,
1563    }
1564}
1565
1566#[inline]
1567fn concat_pair_fast(
1568    state: &mut LuaState,
1569    top: StackIdx,
1570    left: LuaValue,
1571    right: LuaValue,
1572) -> Result<bool, LuaError> {
1573    let version = state.global().lua_version;
1574    let Some(left) = concat_piece(left, version) else {
1575        return Ok(false);
1576    };
1577    let Some(right) = concat_piece(right, version) else {
1578        return Ok(false);
1579    };
1580    let total_len = left
1581        .len()
1582        .checked_add(right.len())
1583        .ok_or_else(|| LuaError::runtime(format_args!("string length overflow")))?;
1584    let mut buf = Vec::with_capacity(total_len);
1585    left.append_to(&mut buf);
1586    right.append_to(&mut buf);
1587    let ts = state.intern_or_create_str(&buf)?;
1588    state.set_at(top - 2, LuaValue::Str(ts));
1589    state.set_top(top - 1);
1590    Ok(true)
1591}
1592
1593// ─── Object length ───────────────────────────────────────────────────────────
1594
1595/// Main implementation of the `#` operator.
1596pub(crate) fn obj_len(
1597    state: &mut LuaState,
1598    ra: StackIdx,
1599    rb: LuaValue,
1600    rb_idx: StackIdx,
1601) -> Result<(), LuaError> {
1602    match &rb {
1603        LuaValue::Table(_) => {
1604            //    if (tm) break; else setivalue(s2v(ra), luaH_getn(h));
1605            // Lua 5.1 `#t` never consults a table `__len` metamethod (only
1606            // userdata can intercept `#` there); `__len` on tables was added in
1607            // 5.2. Under V51 we therefore always take the primitive length.
1608            let consult_len_tm = !matches!(state.global().lua_version, lua_types::LuaVersion::V51);
1609            let tm = if consult_len_tm {
1610                let mt = state.table_metatable(&rb);
1611                state.fast_tm_table(mt.as_ref(), TagMethod::Len)
1612            } else {
1613                LuaValue::Nil
1614            };
1615            if matches!(tm, LuaValue::Nil) {
1616                let n = state.table_length(&rb)?;
1617                state.set_at(ra, LuaValue::Int(n as i64));
1618                return Ok(());
1619            }
1620            // Fall through to call metamethod
1621            state.call_tm_res(tm, &rb, &rb, ra)?;
1622        }
1623        LuaValue::Str(ts) => {
1624            //    case LUA_VLNGSTR: setivalue(s2v(ra), tsvalue(rb)->u.lnglen);
1625            // Unified in Rust — just get length
1626            let n = ts.len();
1627            state.set_at(ra, LuaValue::Int(n as i64));
1628        }
1629        other => {
1630            //    if (notm(tm)) luaG_typeerror(L, rb, "get length of");
1631            let tm = state.get_tm_by_obj(other, TagMethod::Len);
1632            if matches!(tm, LuaValue::Nil) {
1633                return Err(crate::debug::type_error(
1634                    state,
1635                    other,
1636                    rb_idx,
1637                    b"get length of",
1638                ));
1639            }
1640            state.call_tm_res(tm, &rb, &rb, ra)?;
1641        }
1642    }
1643    Ok(())
1644}
1645
1646// ─── Integer arithmetic ──────────────────────────────────────────────────────
1647
1648/// Integer floor-division.
1649pub(crate) fn idiv(m: i64, n: i64) -> Result<i64, LuaError> {
1650    if (n as u64).wrapping_add(1) <= 1 {
1651        if n == 0 {
1652            return Err(LuaError::runtime(format_args!("attempt to divide by zero")));
1653        }
1654        return Ok(intop_sub(0, m));
1655    }
1656    let q = m / n;
1657    // Correct toward floor (C division truncates toward zero)
1658    if (m ^ n) < 0 && m % n != 0 {
1659        Ok(q - 1)
1660    } else {
1661        Ok(q)
1662    }
1663}
1664
1665/// Integer modulus (Lua semantics: same sign as divisor).
1666pub(crate) fn imod(m: i64, n: i64) -> Result<i64, LuaError> {
1667    if (n as u64).wrapping_add(1) <= 1 {
1668        if n == 0 {
1669            return Err(LuaError::runtime(format_args!("attempt to perform 'n%0'")));
1670        }
1671        return Ok(0);
1672    }
1673    let r = m % n;
1674    if r != 0 && (r ^ n) < 0 {
1675        Ok(r + n)
1676    } else {
1677        Ok(r)
1678    }
1679}
1680
1681/// Float modulus (Lua semantics).
1682pub(crate) fn fmodf(m: f64, n: f64) -> f64 {
1683    let r = m % n;
1684    let opposite_signs = if r > 0.0 { n < 0.0 } else { r < 0.0 && n > 0.0 };
1685    if opposite_signs {
1686        r + n
1687    } else {
1688        r
1689    }
1690}
1691
1692/// Phase-B helper: map a u8 raw value to a `TagMethod`. Mirrors C's
1693/// `cast(TMS, x)` direct cast; out-of-range returns `TagMethod::Index`.
1694pub(crate) fn tagmethod_from_index(i: usize) -> TagMethod {
1695    use TagMethod::*;
1696    match i {
1697        0 => Index,
1698        1 => NewIndex,
1699        2 => Gc,
1700        3 => Mode,
1701        4 => Len,
1702        5 => Eq,
1703        6 => Add,
1704        7 => Sub,
1705        8 => Mul,
1706        9 => Mod,
1707        10 => Pow,
1708        11 => Div,
1709        12 => Idiv,
1710        13 => Band,
1711        14 => Bor,
1712        15 => Bxor,
1713        16 => Shl,
1714        17 => Shr,
1715        18 => Unm,
1716        19 => Bnot,
1717        20 => Lt,
1718        21 => Le,
1719        22 => Concat,
1720        23 => Call,
1721        24 => Close,
1722        _ => Index,
1723    }
1724}
1725
1726/// Integer floor-mod: Lua's `%` operator on integers. Result has the same sign
1727/// as the divisor. Raises on `n == 0`.
1728pub(crate) fn int_floor_mod(_state: &mut LuaState, a: i64, b: i64) -> Result<i64, LuaError> {
1729    imod(a, b)
1730}
1731
1732/// Integer floor-div: Lua's `//` operator on integers. Truncates toward
1733/// negative infinity. Raises on `n == 0`.
1734pub(crate) fn int_floor_div(_state: &mut LuaState, a: i64, b: i64) -> Result<i64, LuaError> {
1735    idiv(a, b)
1736}
1737
1738/// Float floor-mod: Lua's `%` operator on floats. Result has the same sign as
1739/// the divisor.  NaN / division-by-zero behavior mirrors C `fmod`.
1740pub(crate) fn float_floor_mod(_state: &mut LuaState, a: f64, b: f64) -> Result<f64, LuaError> {
1741    Ok(fmodf(a, b))
1742}
1743
1744/// Left shift; right shift is shift-left by negated count.
1745pub(crate) fn shiftl(x: i64, y: i64) -> i64 {
1746    if y < 0 {
1747        if y <= -(NBITS as i64) {
1748            0
1749        } else {
1750            intop_shr(x, (-y) as u32)
1751        }
1752    } else {
1753        if y >= NBITS as i64 {
1754            0
1755        } else {
1756            intop_shl(x, y as u32)
1757        }
1758    }
1759}
1760
1761// ─── Closure creation ────────────────────────────────────────────────────────
1762
1763/// StkId base, StkId ra)`
1764/// Create a new Lua closure from prototype `p`, initialise its upvalues,
1765/// and push it onto the stack at `ra`.
1766fn push_closure(
1767    state: &mut LuaState,
1768    proto_idx: usize, // index into current closure's proto.p[]
1769    ci: CallInfoIdx,
1770    base: StackIdx,
1771    ra: StackIdx,
1772) -> Result<(), LuaError> {
1773    // TODO(port): pushclosure needs access to the enclosing closure's upvals and
1774    // the child proto from the current frame.  This stub forwards to a LuaState
1775    // method that has the required context.
1776    state.push_closure(proto_idx, ci, base, ra)
1777}
1778
1779// ─── Yield recovery ──────────────────────────────────────────────────────────
1780
1781/// Resume the opcode that was interrupted by a yield.
1782/// Called when a coroutine is resumed after yielding mid-instruction.
1783pub(crate) fn finish_op(state: &mut LuaState) -> Result<(), LuaError> {
1784    //    StkId base = ci->func.p + 1;
1785    //    Instruction inst = *(ci->u.l.savedpc - 1);
1786    //    OpCode op = GET_OPCODE(inst);
1787    let ci = state.current_ci_idx();
1788    let base = state.ci_base(ci);
1789    let inst = state.ci_prev_instruction(ci);
1790    let op = inst.opcode();
1791
1792    match op {
1793        //    setobjs2s(L, base + GETARG_A(*(ci->u.l.savedpc - 2)), --L->top.p);
1794        OpCode::MmBin | OpCode::MmBinI | OpCode::MmBinK => {
1795            let prev_inst = state.ci_prev2_instruction(ci);
1796            let a = prev_inst.arg_a();
1797            state.dec_top();
1798            let top = state.top_idx();
1799            let v = state.get_at(top);
1800            state.set_at(base + a, v);
1801        }
1802        //    setobjs2s(L, base + GETARG_A(inst), --L->top.p);
1803        OpCode::Unm
1804        | OpCode::BNot
1805        | OpCode::Len
1806        | OpCode::GetTabUp
1807        | OpCode::GetTable
1808        | OpCode::GetI
1809        | OpCode::GetField
1810        | OpCode::Self_ => {
1811            let a = inst.arg_a();
1812            state.dec_top();
1813            let top = state.top_idx();
1814            let v = state.get_at(top);
1815            state.set_at(base + a, v);
1816        }
1817        //    case OP_GTI: case OP_GEI: case OP_EQ:
1818        //    int res = !l_isfalse(s2v(L->top.p - 1)); L->top.p--;
1819        //    if (res != GETARG_k(inst)) ci->u.l.savedpc++;
1820        OpCode::Lt
1821        | OpCode::Le
1822        | OpCode::LtI
1823        | OpCode::LeI
1824        | OpCode::GtI
1825        | OpCode::GeI
1826        | OpCode::Eq => {
1827            let top_minus1 = state.top_idx() - 1;
1828            let v = state.get_at(top_minus1);
1829            let mut res = !matches!(v, LuaValue::Nil | LuaValue::Bool(false));
1830            state.dec_top();
1831            // LUA_COMPAT_LT_LE: if this `__le` was derived from a `__lt` that
1832            // yielded (5.1–5.4), the result `b < a` must be negated back to
1833            // `a <= b`. The mark was set in `tagmethods::call_order_tm`.
1834            // C (lvm.c luaV_finishOp): `if (callstatus & CIST_LEQ) { ^= ; res = !res; }`
1835            if (state.get_ci(ci).callstatus & crate::state::CIST_LEQ) != 0 {
1836                state.get_ci_mut(ci).callstatus &= !crate::state::CIST_LEQ;
1837                res = !res;
1838            }
1839            if (res as i32) != inst.arg_k() {
1840                state.ci_skip_next_instruction(ci);
1841            }
1842        }
1843        //    StkId top = L->top.p - 1;
1844        //    int a = GETARG_A(inst);
1845        //    int total = cast_int(top - 1 - (base + a));
1846        //    setobjs2s(L, top - 2, top);  L->top.p = top - 1;
1847        //    luaV_concat(L, total);
1848        OpCode::Concat => {
1849            let top = state.top_idx() - 1; // top when luaT_tryconcatTM was called
1850            let a = inst.arg_a();
1851            let total_concat = (top - 1 - (base + a)) as i32;
1852            let v = state.get_at(top);
1853            state.set_at(top - 2, v);
1854            state.set_top(top - 1);
1855            concat(state, total_concat)?;
1856        }
1857        OpCode::Close => {
1858            state.ci_step_pc_back(ci);
1859        }
1860        //    StkId ra = base + GETARG_A(inst);
1861        //    L->top.p = ra + ci->u2.nres;
1862        //    ci->u.l.savedpc--;
1863        OpCode::Return => {
1864            let a = inst.arg_a();
1865            let ra = base + a;
1866            let nres = state.ci_nres(ci);
1867            state.set_top(ra + nres);
1868            state.ci_step_pc_back(ci);
1869        }
1870        other => {
1871            debug_assert!(
1872                matches!(
1873                    other,
1874                    OpCode::TForCall
1875                        | OpCode::Call
1876                        | OpCode::TailCall
1877                        | OpCode::SetTabUp
1878                        | OpCode::SetTable
1879                        | OpCode::SetI
1880                        | OpCode::SetField
1881                ),
1882                "unexpected opcode in finish_op: {:?}",
1883                other
1884            );
1885        }
1886    }
1887    Ok(())
1888}
1889
1890// ─── Main interpreter loop ───────────────────────────────────────────────────
1891
1892/// Main Lua bytecode interpreter loop.
1893///
1894/// # Control flow modelling
1895/// The C function uses goto labels: `startfunc`, `returning`, `ret`,
1896/// `l_tforcall`, `l_tforloop`.  These are modelled as follows:
1897/// - `'startfunc: loop { ... }` — outer loop; `continue 'startfunc` = goto startfunc
1898/// - `'returning: loop { ... }` — inner loop; `continue 'returning` = goto returning
1899/// - `break 'dispatch` from the inner dispatch loop → runs `ret:` logic
1900/// - `l_tforcall` / `l_tforloop` — inlined at TFORPREP / TFORCALL handlers
1901pub(crate) fn execute(state: &mut LuaState, mut ci: CallInfoIdx) -> Result<(), LuaError> {
1902    let mut trap: bool;
1903    // The numeric-`for` opcodes use legacy (<=5.3) semantics on 5.1/5.2/5.3:
1904    // FORPREP jumps forward to FORLOOP (so iteration 1 enters the body via a
1905    // backward jump, firing one line-hook event per iteration), and FORLOOP is
1906    // compare-based rather than 5.4's precomputed-count form (issue #92). The
1907    // version is fixed for the VM's lifetime, so resolve it once here; the
1908    // 5.4/5.5 path is unchanged and pays nothing.
1909    let legacy_for = matches!(
1910        state.global().lua_version,
1911        lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
1912    );
1913    let tfor_55 = state.global().lua_version == lua_types::LuaVersion::V55;
1914
1915    // PORT NOTE: `startfunc:` is the entry point that (re)sets `trap`.
1916    'startfunc: loop {
1917        trap = state.hook_mask() != 0;
1918
1919        // PORT NOTE: `returning:` is the re-entry after a Lua call returns.
1920        // Re-enters 'returning without resetting trap.
1921        'returning: loop {
1922            let ci_slot = ci.as_usize();
1923            let func_idx = state.call_info[ci_slot].func;
1924            let cl = match state.stack[func_idx.0 as usize].val {
1925                LuaValue::Function(lua_types::closure::LuaClosure::Lua(c)) => c,
1926                _ => {
1927                    return Err(LuaError::runtime(format_args!(
1928                        "internal: execute called on non-Lua frame"
1929                    )));
1930                }
1931            };
1932            let code = &cl.proto.code;
1933            let constants = &cl.proto.k;
1934            // pc is an index into proto.code (u32)
1935            let mut pc: u32 = state.call_info[ci_slot].saved_pc();
1936
1937            if trap {
1938                trap = state.trace_call(ci)?;
1939            }
1940            let mut base: StackIdx = state.call_info[ci.as_usize()].func + 1;
1941
1942            // ── Main dispatch loop ──────────────────────────────────────────
1943            'dispatch: loop {
1944                if trap {
1945                    trap = state.trace_exec(ci, pc)?;
1946                    base = state.ci_base(ci); // updatebase
1947                }
1948                let i: Instruction = code[pc as usize];
1949                pc += 1;
1950                let op = i.opcode();
1951                #[cfg(feature = "opcode-profile")]
1952                crate::opcode_profile::record(op);
1953
1954                debug_assert!(base == state.ci_base(ci));
1955
1956                // In normal C-Lua builds, `lua_assert` compiles away; keep the
1957                // stack-top invalidation only for debug parity so release
1958                // dispatch avoids an opcode-mode lookup and a `top` write.
1959                #[cfg(debug_assertions)]
1960                {
1961                    let op_mode = op_mode_byte(op);
1962                    if (op_mode & (1 << 5)) == 0 || i.arg_b() != 0 {
1963                        state.set_top(base);
1964                    }
1965                }
1966
1967                match op {
1968                    // ── OP_MOVE ──────────────────────────────────────────────
1969                    OpCode::Move => {
1970                        let ra = base + i.arg_a();
1971                        let rb = base + i.arg_b();
1972                        let v = state.stack[rb.0 as usize].val;
1973                        state.stack[ra.0 as usize].val = v;
1974                    }
1975                    // ── OP_LOADI ─────────────────────────────────────────────
1976                    OpCode::LoadI => {
1977                        let ra = base + i.arg_a();
1978                        let b = i.arg_s_bx() as i64;
1979                        state.stack[ra.0 as usize].val = LuaValue::Int(b);
1980                    }
1981                    // ── OP_LOADF ─────────────────────────────────────────────
1982                    OpCode::LoadF => {
1983                        let ra = base + i.arg_a();
1984                        let b = i.arg_s_bx() as f64;
1985                        state.stack[ra.0 as usize].val = LuaValue::Float(b);
1986                    }
1987                    // ── OP_LOADK ─────────────────────────────────────────────
1988                    OpCode::LoadK => {
1989                        let ra = base + i.arg_a();
1990                        let k_idx = i.arg_bx() as usize;
1991                        state.stack[ra.0 as usize].val = constants[k_idx];
1992                    }
1993                    // ── OP_LOADKX ────────────────────────────────────────────
1994                    OpCode::LoadKX => {
1995                        let ra = base + i.arg_a();
1996                        let extra = code[pc as usize];
1997                        pc += 1;
1998                        let k_idx = extra.arg_ax() as usize;
1999                        state.stack[ra.0 as usize].val = constants[k_idx];
2000                    }
2001                    // ── OP_LOADFALSE ─────────────────────────────────────────
2002                    OpCode::LoadFalse => {
2003                        let ra = base + i.arg_a();
2004                        state.stack[ra.0 as usize].val = LuaValue::Bool(false);
2005                    }
2006                    // ── OP_LFALSESKIP ────────────────────────────────────────
2007                    OpCode::LFalseSkip => {
2008                        let ra = base + i.arg_a();
2009                        state.stack[ra.0 as usize].val = LuaValue::Bool(false);
2010                        pc += 1;
2011                    }
2012                    // ── OP_LOADTRUE ──────────────────────────────────────────
2013                    OpCode::LoadTrue => {
2014                        let ra = base + i.arg_a();
2015                        state.stack[ra.0 as usize].val = LuaValue::Bool(true);
2016                    }
2017                    // ── OP_LOADNIL ───────────────────────────────────────────
2018                    OpCode::LoadNil => {
2019                        let ra = base + i.arg_a();
2020                        let b = i.arg_b();
2021                        for k in 0..=b {
2022                            state.stack[(ra + k).0 as usize].val = LuaValue::Nil;
2023                        }
2024                    }
2025                    // ── OP_GETUPVAL ──────────────────────────────────────────
2026                    OpCode::GetUpVal => {
2027                        let ra = base + i.arg_a();
2028                        let b = i.arg_b() as usize;
2029                        let uv = cl.upval(b);
2030                        let v = match uv.try_open_payload() {
2031                            Some((thread_id, idx))
2032                                if thread_id as u64 == state.cached_thread_id =>
2033                            {
2034                                state.stack[idx.0 as usize].val
2035                            }
2036                            Some(_) => state.upvalue_get(&cl, b),
2037                            None => uv.closed_value(),
2038                        };
2039                        state.stack[ra.0 as usize].val = v;
2040                    }
2041                    // ── OP_SETUPVAL ──────────────────────────────────────────
2042                    //    setobj(L, uv->v.p, s2v(ra)); luaC_barrier(L, uv, s2v(ra));
2043                    OpCode::SetUpVal => {
2044                        let ra = base + i.arg_a();
2045                        let b = i.arg_b() as usize;
2046                        let v = state.stack[ra.0 as usize].val;
2047                        let uv = cl.upval(b);
2048                        match uv.try_open_payload() {
2049                            Some((thread_id, idx))
2050                                if thread_id as u64 == state.cached_thread_id =>
2051                            {
2052                                state.stack[idx.0 as usize].val = v;
2053                                if v.is_collectable() {
2054                                    state.gc_barrier_upval(&uv, &v);
2055                                }
2056                            }
2057                            None => {
2058                                uv.set_closed_value(v);
2059                                if v.is_collectable() {
2060                                    state.gc_barrier_upval(&uv, &v);
2061                                }
2062                            }
2063                            _ => {
2064                                state.upvalue_set(&cl, b, v)?;
2065                            }
2066                        }
2067                    }
2068                    // ── OP_GETTABUP ──────────────────────────────────────────
2069                    //    if (luaV_fastget(..., luaH_getshortstr)) setobj2s(L, ra, slot)
2070                    //    else Protect(luaV_finishget(...))
2071                    OpCode::GetTabUp => {
2072                        let ra = base + i.arg_a();
2073                        let b = i.arg_b() as usize;
2074                        let k_idx = i.arg_c() as usize;
2075                        let upval = state.upvalue_get(&cl, b);
2076                        let key = constants[k_idx];
2077                        match state.fast_get_short_str(&upval, &key)? {
2078                            Some(v) => state.set_at(ra, v),
2079                            None => {
2080                                state.set_ci_savedpc(ci, pc);
2081                                state.set_top(state.ci_top(ci));
2082                                let upval_name: Vec<u8> =
2083                                    cl.0.proto
2084                                        .upvalues
2085                                        .get(b)
2086                                        .and_then(|uv| uv.name.as_ref())
2087                                        .map(|s| s.as_bytes().to_vec())
2088                                        .unwrap_or_else(|| b"?".to_vec());
2089                                let hint: Option<(&[u8], &[u8])> = Some((b"upvalue", &upval_name));
2090                                finish_get(state, upval, key, ra, true, None, hint)?;
2091                                trap = state.ci_trap(ci);
2092                            }
2093                        }
2094                    }
2095                    // ── OP_GETTABLE ──────────────────────────────────────────
2096                    //    if (integer key) fastgeti else fastget
2097                    OpCode::GetTable => {
2098                        let ra = base + i.arg_a();
2099                        let rb_idx = base + i.arg_b();
2100                        let rb_v = state.get_at(rb_idx);
2101                        let rc_v = state.get_at(base + i.arg_c());
2102                        let fast_result = if let LuaValue::Int(n) = &rc_v {
2103                            state.fast_get_int(&rb_v, *n)?
2104                        } else {
2105                            state.fast_get(&rb_v, &rc_v)?
2106                        };
2107                        match fast_result {
2108                            Some(v) => state.set_at(ra, v),
2109                            None => {
2110                                state.set_ci_savedpc(ci, pc);
2111                                state.set_top(state.ci_top(ci));
2112                                finish_get(state, rb_v, rc_v, ra, true, Some(rb_idx), None)?;
2113                                trap = state.ci_trap(ci);
2114                            }
2115                        }
2116                    }
2117                    // ── OP_GETI ──────────────────────────────────────────────
2118                    //    if (luaV_fastgeti(L, rb, c, slot)) setobj2s(L, ra, slot)
2119                    //    else { TValue key; setivalue(&key, c); Protect(finishget) }
2120                    OpCode::GetI => {
2121                        let ra = base + i.arg_a();
2122                        let rb_idx = base + i.arg_b();
2123                        let rb_v = state.get_at(rb_idx);
2124                        let c = i.arg_c() as i64;
2125                        match state.fast_get_int(&rb_v, c)? {
2126                            Some(v) => state.set_at(ra, v),
2127                            None => {
2128                                let key = LuaValue::Int(c);
2129                                state.set_ci_savedpc(ci, pc);
2130                                state.set_top(state.ci_top(ci));
2131                                finish_get(state, rb_v, key, ra, true, Some(rb_idx), None)?;
2132                                trap = state.ci_trap(ci);
2133                            }
2134                        }
2135                    }
2136                    // ── OP_GETFIELD ──────────────────────────────────────────
2137                    OpCode::GetField => {
2138                        let ra = base + i.arg_a();
2139                        let rb_idx = base + i.arg_b();
2140                        let rb_v = state.get_at(rb_idx);
2141                        let k_idx = i.arg_c() as usize;
2142                        let key = constants[k_idx];
2143                        match state.fast_get_short_str(&rb_v, &key)? {
2144                            Some(v) => state.set_at(ra, v),
2145                            None => {
2146                                state.set_ci_savedpc(ci, pc);
2147                                state.set_top(state.ci_top(ci));
2148                                finish_get(state, rb_v, key, ra, true, Some(rb_idx), None)?;
2149                                trap = state.ci_trap(ci);
2150                            }
2151                        }
2152                    }
2153                    // ── OP_SETTABUP ──────────────────────────────────────────
2154                    OpCode::SetTabUp => {
2155                        let a = i.arg_a() as usize;
2156                        let b_idx = i.arg_b() as usize; // key is KB(i)
2157                        let rc_v = if i.test_k() {
2158                            constants[i.arg_c() as usize]
2159                        } else {
2160                            state.get_at(base + i.arg_c())
2161                        };
2162                        let upval = state.upvalue_get(&cl, a);
2163                        let key = constants[b_idx];
2164                        if let LuaValue::Table(tbl) = upval {
2165                            if !tbl.has_metatable() {
2166                                if rc_v.is_collectable() {
2167                                    state.gc_table_barrier_back(&tbl, &rc_v);
2168                                }
2169                                if let LuaValue::Str(s) = key {
2170                                    tbl.raw_set_short_str(state, s, rc_v)?;
2171                                } else {
2172                                    tbl.raw_set(state, key, rc_v)?;
2173                                }
2174                                continue 'dispatch;
2175                            }
2176                        }
2177                        match state.fast_get_short_str(&upval, &key)? {
2178                            Some(_slot) => {
2179                                state.gc_value_barrier_back(&upval, &rc_v);
2180                                if let LuaValue::Table(tbl) = upval {
2181                                    if let LuaValue::Str(s) = key {
2182                                        tbl.raw_set_short_str(state, s, rc_v)?;
2183                                    } else {
2184                                        tbl.raw_set(state, key, rc_v)?;
2185                                    }
2186                                } else {
2187                                    state.table_raw_set(&upval, key, rc_v)?;
2188                                }
2189                            }
2190                            None => {
2191                                state.set_ci_savedpc(ci, pc);
2192                                state.set_top(state.ci_top(ci));
2193                                let upval_name: Vec<u8> = cl
2194                                    .proto
2195                                    .upvalues
2196                                    .get(a)
2197                                    .and_then(|uv| uv.name.as_ref())
2198                                    .map(|s| s.as_bytes().to_vec())
2199                                    .unwrap_or_else(|| b"?".to_vec());
2200                                let hint: Option<(&[u8], &[u8])> = Some((b"upvalue", &upval_name));
2201                                finish_set(state, upval, key, rc_v, false, None, hint)?;
2202                                trap = state.ci_trap(ci);
2203                            }
2204                        }
2205                    }
2206                    // ── OP_SETTABLE ───────────────────────────────────────────
2207                    OpCode::SetTable => {
2208                        let ra_idx = base + i.arg_a();
2209                        let ra_v = state.get_at(ra_idx);
2210                        let rb_v = state.get_at(base + i.arg_b());
2211                        let rc_v = if i.test_k() {
2212                            constants[i.arg_c() as usize]
2213                        } else {
2214                            state.get_at(base + i.arg_c())
2215                        };
2216                        if let LuaValue::Table(tbl) = ra_v {
2217                            if !tbl.has_metatable() {
2218                                if rc_v.is_collectable() {
2219                                    state.gc_table_barrier_back(&tbl, &rc_v);
2220                                }
2221                                match rb_v {
2222                                    LuaValue::Int(n) => tbl.raw_set_int(state, n, rc_v)?,
2223                                    LuaValue::Str(s) if s.is_short() => {
2224                                        tbl.raw_set_short_str(state, s, rc_v)?
2225                                    }
2226                                    _ => tbl.raw_set(state, rb_v, rc_v)?,
2227                                }
2228                            } else {
2229                                let fast = if let LuaValue::Int(n) = &rb_v {
2230                                    state.fast_get_int(&ra_v, *n)?
2231                                } else {
2232                                    state.fast_get(&ra_v, &rb_v)?
2233                                };
2234                                if fast.is_some() {
2235                                    state.gc_value_barrier_back(&ra_v, &rc_v);
2236                                    match rb_v {
2237                                        LuaValue::Int(n) => tbl.raw_set_int(state, n, rc_v)?,
2238                                        LuaValue::Str(s) if s.is_short() => {
2239                                            tbl.raw_set_short_str(state, s, rc_v)?
2240                                        }
2241                                        _ => tbl.raw_set(state, rb_v, rc_v)?,
2242                                    }
2243                                } else {
2244                                    state.set_ci_savedpc(ci, pc);
2245                                    state.set_top(state.ci_top(ci));
2246                                    finish_set(state, ra_v, rb_v, rc_v, false, Some(ra_idx), None)?;
2247                                    trap = state.ci_trap(ci);
2248                                }
2249                            }
2250                        } else {
2251                            state.set_ci_savedpc(ci, pc);
2252                            state.set_top(state.ci_top(ci));
2253                            finish_set(state, ra_v, rb_v, rc_v, false, Some(ra_idx), None)?;
2254                            trap = state.ci_trap(ci);
2255                        }
2256                    }
2257                    // ── OP_SETI ───────────────────────────────────────────────
2258                    OpCode::SetI => {
2259                        let ra_idx = base + i.arg_a();
2260                        let ra_v = state.get_at(ra_idx);
2261                        let c = i.arg_b() as i64;
2262                        let rc_v = if i.test_k() {
2263                            constants[i.arg_c() as usize]
2264                        } else {
2265                            state.get_at(base + i.arg_c())
2266                        };
2267                        if let LuaValue::Table(tbl) = ra_v {
2268                            if !tbl.has_metatable() {
2269                                if rc_v.is_collectable() {
2270                                    state.gc_table_barrier_back(&tbl, &rc_v);
2271                                }
2272                                tbl.raw_set_int(state, c, rc_v)?;
2273                            } else {
2274                                let fast = state.fast_get_int(&ra_v, c)?;
2275                                if fast.is_some() {
2276                                    state.gc_value_barrier_back(&ra_v, &rc_v);
2277                                    tbl.raw_set_int(state, c, rc_v)?;
2278                                } else {
2279                                    state.set_ci_savedpc(ci, pc);
2280                                    state.set_top(state.ci_top(ci));
2281                                    finish_set(
2282                                        state,
2283                                        ra_v,
2284                                        LuaValue::Int(c),
2285                                        rc_v,
2286                                        false,
2287                                        Some(ra_idx),
2288                                        None,
2289                                    )?;
2290                                    trap = state.ci_trap(ci);
2291                                }
2292                            }
2293                        } else {
2294                            state.set_ci_savedpc(ci, pc);
2295                            state.set_top(state.ci_top(ci));
2296                            finish_set(
2297                                state,
2298                                ra_v,
2299                                LuaValue::Int(c),
2300                                rc_v,
2301                                false,
2302                                Some(ra_idx),
2303                                None,
2304                            )?;
2305                            trap = state.ci_trap(ci);
2306                        }
2307                    }
2308                    // ── OP_SETFIELD ───────────────────────────────────────────
2309                    OpCode::SetField => {
2310                        let ra_idx = base + i.arg_a();
2311                        let ra_v = state.get_at(ra_idx);
2312                        let b_idx = i.arg_b() as usize;
2313                        let key = constants[b_idx];
2314                        let rc_v = if i.test_k() {
2315                            constants[i.arg_c() as usize]
2316                        } else {
2317                            state.get_at(base + i.arg_c())
2318                        };
2319                        if let LuaValue::Table(tbl) = ra_v {
2320                            if !tbl.has_metatable() {
2321                                if rc_v.is_collectable() {
2322                                    state.gc_table_barrier_back(&tbl, &rc_v);
2323                                }
2324                                if let LuaValue::Str(s) = key {
2325                                    tbl.raw_set_short_str(state, s, rc_v)?;
2326                                } else {
2327                                    tbl.raw_set(state, key, rc_v)?;
2328                                }
2329                            } else {
2330                                match state.fast_get_short_str(&ra_v, &key)? {
2331                                    Some(_) => {
2332                                        state.gc_value_barrier_back(&ra_v, &rc_v);
2333                                        if let LuaValue::Str(s) = key {
2334                                            tbl.raw_set_short_str(state, s, rc_v)?;
2335                                        } else {
2336                                            tbl.raw_set(state, key, rc_v)?;
2337                                        }
2338                                    }
2339                                    None => {
2340                                        state.set_ci_savedpc(ci, pc);
2341                                        state.set_top(state.ci_top(ci));
2342                                        finish_set(
2343                                            state,
2344                                            ra_v,
2345                                            key,
2346                                            rc_v,
2347                                            false,
2348                                            Some(ra_idx),
2349                                            None,
2350                                        )?;
2351                                        trap = state.ci_trap(ci);
2352                                    }
2353                                }
2354                            }
2355                        } else {
2356                            state.set_ci_savedpc(ci, pc);
2357                            state.set_top(state.ci_top(ci));
2358                            finish_set(state, ra_v, key, rc_v, false, Some(ra_idx), None)?;
2359                            trap = state.ci_trap(ci);
2360                        }
2361                    }
2362                    // ── OP_NEWTABLE ───────────────────────────────────────────
2363                    //    if (TESTARG_k(i)) c += GETARG_Ax(*pc) * (MAXARG_C + 1); pc++;
2364                    OpCode::NewTable => {
2365                        let ra = base + i.arg_a();
2366                        let mut b = i.arg_b();
2367                        let mut c = i.arg_c();
2368                        if b > 0 {
2369                            b = 1 << (b - 1);
2370                        }
2371                        if i.test_k() {
2372                            let extra = code[pc as usize];
2373                            pc += 1;
2374                            const MAXARG_C: i32 = (1 << 8) - 1;
2375                            c += extra.arg_ax() * (MAXARG_C + 1);
2376                        } else {
2377                            pc += 1; // skip extra argument even if zero
2378                        }
2379                        state.set_top(ra + 1);
2380                        let t = if b != 0 || c != 0 {
2381                            state.new_table_with_sizes(c as u32, b as u32)?
2382                        } else {
2383                            state.new_table()
2384                        };
2385                        state.set_at(ra, LuaValue::Table(t.clone()));
2386                        state.set_ci_savedpc(ci, pc);
2387                        state.set_top(ra + 1);
2388                        state.gc_cond_step();
2389                        if state.hookmask != 0 {
2390                            trap = state.ci_trap(ci);
2391                        }
2392                    }
2393                    // ── OP_SELF ───────────────────────────────────────────────
2394                    OpCode::Self_ => {
2395                        let ra = base + i.arg_a();
2396                        let rb_idx = base + i.arg_b();
2397                        let rb_v = state.get_at(rb_idx);
2398                        let k_idx = i.arg_c() as usize; // RKC key (always a string)
2399                        let key = if i.test_k() {
2400                            constants[k_idx]
2401                        } else {
2402                            state.get_at(base + i.arg_c())
2403                        };
2404                        state.set_at(ra + 1, rb_v.clone());
2405                        match state.fast_get_short_str(&rb_v, &key)? {
2406                            Some(v) => state.set_at(ra, v),
2407                            None => {
2408                                state.set_ci_savedpc(ci, pc);
2409                                state.set_top(state.ci_top(ci));
2410                                finish_get(state, rb_v, key, ra, true, Some(rb_idx), None)?;
2411                                trap = state.ci_trap(ci);
2412                            }
2413                        }
2414                    }
2415                    // ── Arithmetic immediates ──────────────────────────────────
2416                    OpCode::AddI => {
2417                        let ra = base + i.arg_a();
2418                        let rb = base + i.arg_b();
2419                        let imm = i.arg_s_c() as i64;
2420                        let rb_v = state.stack[rb.0 as usize].val;
2421                        match rb_v {
2422                            LuaValue::Int(iv1) => {
2423                                pc += 1;
2424                                state.stack[ra.0 as usize].val = LuaValue::Int(intop_add(iv1, imm));
2425                            }
2426                            LuaValue::Float(nb) => {
2427                                pc += 1;
2428                                state.stack[ra.0 as usize].val = LuaValue::Float(nb + imm as f64);
2429                            }
2430                            _ => {}
2431                        }
2432                    }
2433                    // ── Arithmetic with K constant operand ─────────────────────
2434                    OpCode::AddK => {
2435                        let ra = base + i.arg_a();
2436                        let rb = base + i.arg_b();
2437                        let kidx = i.arg_c() as usize;
2438                        if let (Some(i1), Some(i2)) =
2439                            (state.get_int_at(rb), state.proto_const_int(&cl, kidx))
2440                        {
2441                            pc += 1;
2442                            state.set_at(ra, LuaValue::Int(intop_add(i1, i2)));
2443                        } else if let (Some(n1), Some(n2)) =
2444                            (state.get_num_at(rb), state.proto_const_num(&cl, kidx))
2445                        {
2446                            pc += 1;
2447                            state.set_at(ra, LuaValue::Float(n1 + n2));
2448                        }
2449                    }
2450                    OpCode::SubK => {
2451                        let ra = base + i.arg_a();
2452                        let rb = base + i.arg_b();
2453                        let kidx = i.arg_c() as usize;
2454                        if let (Some(i1), Some(i2)) =
2455                            (state.get_int_at(rb), state.proto_const_int(&cl, kidx))
2456                        {
2457                            pc += 1;
2458                            state.set_at(ra, LuaValue::Int(intop_sub(i1, i2)));
2459                        } else if let (Some(n1), Some(n2)) =
2460                            (state.get_num_at(rb), state.proto_const_num(&cl, kidx))
2461                        {
2462                            pc += 1;
2463                            state.set_at(ra, LuaValue::Float(n1 - n2));
2464                        }
2465                    }
2466                    OpCode::MulK => {
2467                        let ra = base + i.arg_a();
2468                        let rb = base + i.arg_b();
2469                        let kidx = i.arg_c() as usize;
2470                        if let (Some(i1), Some(i2)) =
2471                            (state.get_int_at(rb), state.proto_const_int(&cl, kidx))
2472                        {
2473                            pc += 1;
2474                            state.set_at(ra, LuaValue::Int(intop_mul(i1, i2)));
2475                        } else if let (Some(n1), Some(n2)) =
2476                            (state.get_num_at(rb), state.proto_const_num(&cl, kidx))
2477                        {
2478                            pc += 1;
2479                            state.set_at(ra, LuaValue::Float(n1 * n2));
2480                        }
2481                    }
2482                    OpCode::ModK => {
2483                        let ra = base + i.arg_a();
2484                        let v1 = state.get_at(base + i.arg_b());
2485                        let v2 = constants[i.arg_c() as usize];
2486                        state.set_ci_savedpc(ci, pc); // savestate for div-by-zero
2487                        state.set_top(state.ci_top(ci));
2488                        arith_op_checked(state, ra, &v1, &v2, &mut pc, |a, b| imod(a, b), fmodf)?;
2489                    }
2490                    OpCode::PowK => {
2491                        let ra = base + i.arg_a();
2492                        let rb = base + i.arg_b();
2493                        let kidx = i.arg_c() as usize;
2494                        if let (Some(n1), Some(n2)) =
2495                            (state.get_num_at(rb), state.proto_const_num(&cl, kidx))
2496                        {
2497                            pc += 1;
2498                            let r = if n2 == 2.0 { n1 * n1 } else { n1.powf(n2) };
2499                            state.set_at(ra, LuaValue::Float(r));
2500                        }
2501                    }
2502                    OpCode::DivK => {
2503                        let ra = base + i.arg_a();
2504                        let rb = base + i.arg_b();
2505                        let kidx = i.arg_c() as usize;
2506                        if let (Some(n1), Some(n2)) =
2507                            (state.get_num_at(rb), state.proto_const_num(&cl, kidx))
2508                        {
2509                            pc += 1;
2510                            state.set_at(ra, LuaValue::Float(n1 / n2));
2511                        }
2512                    }
2513                    OpCode::IDivK => {
2514                        let ra = base + i.arg_a();
2515                        let v1 = state.get_at(base + i.arg_b());
2516                        let v2 = constants[i.arg_c() as usize];
2517                        state.set_ci_savedpc(ci, pc);
2518                        state.set_top(state.ci_top(ci));
2519                        arith_op_checked(
2520                            state,
2521                            ra,
2522                            &v1,
2523                            &v2,
2524                            &mut pc,
2525                            |a, b| idiv(a, b),
2526                            |a, b| (a / b).floor(),
2527                        )?;
2528                    }
2529                    OpCode::BAndK => {
2530                        let ra = base + i.arg_a();
2531                        let v1 = state.get_at(base + i.arg_b());
2532                        let v2 = constants[i.arg_c() as usize];
2533                        bitwise_op_k(state, ra, &v1, &v2, &mut pc, intop_band);
2534                    }
2535                    OpCode::BOrK => {
2536                        let ra = base + i.arg_a();
2537                        let v1 = state.get_at(base + i.arg_b());
2538                        let v2 = constants[i.arg_c() as usize];
2539                        bitwise_op_k(state, ra, &v1, &v2, &mut pc, intop_bor);
2540                    }
2541                    OpCode::BXOrK => {
2542                        let ra = base + i.arg_a();
2543                        let v1 = state.get_at(base + i.arg_b());
2544                        let v2 = constants[i.arg_c() as usize];
2545                        bitwise_op_k(state, ra, &v1, &v2, &mut pc, intop_bxor);
2546                    }
2547                    OpCode::ShrI => {
2548                        let ra = base + i.arg_a();
2549                        let v = state.get_at(base + i.arg_b());
2550                        let ic = i.arg_s_c() as i64;
2551                        if let Some(ib) = to_integer_ns(&v, F2Imod::Eq) {
2552                            pc += 1;
2553                            state.set_at(ra, LuaValue::Int(shiftl(ib, -ic)));
2554                        }
2555                    }
2556                    OpCode::ShlI => {
2557                        let ra = base + i.arg_a();
2558                        let v = state.get_at(base + i.arg_b());
2559                        let ic = i.arg_s_c() as i64;
2560                        if let Some(ib) = to_integer_ns(&v, F2Imod::Eq) {
2561                            pc += 1;
2562                            state.set_at(ra, LuaValue::Int(shiftl(ic, ib)));
2563                        }
2564                    }
2565                    // ── Arithmetic with register operands ──────────────────────
2566                    OpCode::Add => {
2567                        let ra = base + i.arg_a();
2568                        let rb = base + i.arg_b();
2569                        let rc = base + i.arg_c();
2570                        let ra_u = ra.0 as usize;
2571                        let rb_v = state.stack[rb.0 as usize].val;
2572                        let rc_v = state.stack[rc.0 as usize].val;
2573                        if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (rb_v, rc_v) {
2574                            pc += 1;
2575                            state.stack[ra_u].val = LuaValue::Int(intop_add(i1, i2));
2576                        } else if let (Some(n1), Some(n2)) =
2577                            (number_value(rb_v), number_value(rc_v))
2578                        {
2579                            pc += 1;
2580                            state.stack[ra_u].val = LuaValue::Float(n1 + n2);
2581                        }
2582                    }
2583                    OpCode::Sub => {
2584                        let ra = base + i.arg_a();
2585                        let rb = base + i.arg_b();
2586                        let rc = base + i.arg_c();
2587                        let ra_u = ra.0 as usize;
2588                        let rb_v = state.stack[rb.0 as usize].val;
2589                        let rc_v = state.stack[rc.0 as usize].val;
2590                        if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (rb_v, rc_v) {
2591                            pc += 1;
2592                            state.stack[ra_u].val = LuaValue::Int(intop_sub(i1, i2));
2593                        } else if let (Some(n1), Some(n2)) =
2594                            (number_value(rb_v), number_value(rc_v))
2595                        {
2596                            pc += 1;
2597                            state.stack[ra_u].val = LuaValue::Float(n1 - n2);
2598                        }
2599                    }
2600                    OpCode::Mul => {
2601                        let ra = base + i.arg_a();
2602                        let rb = base + i.arg_b();
2603                        let rc = base + i.arg_c();
2604                        if let Some((i1, i2)) = state.get_int_pair_at(rb, rc) {
2605                            pc += 1;
2606                            state.set_at(ra, LuaValue::Int(intop_mul(i1, i2)));
2607                        } else if let Some((n1, n2)) = state.get_num_pair_at(rb, rc) {
2608                            pc += 1;
2609                            state.set_at(ra, LuaValue::Float(n1 * n2));
2610                        }
2611                    }
2612                    OpCode::Mod => {
2613                        let ra = base + i.arg_a();
2614                        let v1 = state.get_at(base + i.arg_b());
2615                        let v2 = state.get_at(base + i.arg_c());
2616                        state.set_ci_savedpc(ci, pc);
2617                        state.set_top(state.ci_top(ci));
2618                        arith_op_checked(state, ra, &v1, &v2, &mut pc, |a, b| imod(a, b), fmodf)?;
2619                    }
2620                    OpCode::Pow => {
2621                        let ra = base + i.arg_a();
2622                        let rb = base + i.arg_b();
2623                        let rc = base + i.arg_c();
2624                        if let Some((n1, n2)) = state.get_num_pair_at(rb, rc) {
2625                            pc += 1;
2626                            let r = if n2 == 2.0 { n1 * n1 } else { n1.powf(n2) };
2627                            state.set_at(ra, LuaValue::Float(r));
2628                        }
2629                    }
2630                    OpCode::Div => {
2631                        let ra = base + i.arg_a();
2632                        let rb = base + i.arg_b();
2633                        let rc = base + i.arg_c();
2634                        if let Some((n1, n2)) = state.get_num_pair_at(rb, rc) {
2635                            pc += 1;
2636                            state.set_at(ra, LuaValue::Float(n1 / n2));
2637                        }
2638                    }
2639                    OpCode::IDiv => {
2640                        let ra = base + i.arg_a();
2641                        let v1 = state.get_at(base + i.arg_b());
2642                        let v2 = state.get_at(base + i.arg_c());
2643                        state.set_ci_savedpc(ci, pc);
2644                        state.set_top(state.ci_top(ci));
2645                        arith_op_checked(
2646                            state,
2647                            ra,
2648                            &v1,
2649                            &v2,
2650                            &mut pc,
2651                            |a, b| idiv(a, b),
2652                            |a, b| (a / b).floor(),
2653                        )?;
2654                    }
2655                    // ── Bitwise with register operands ─────────────────────────
2656                    // if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) { pc++; setivalue... }
2657                    OpCode::BAnd => {
2658                        let ra = base + i.arg_a();
2659                        let v1 = state.get_at(base + i.arg_b());
2660                        let v2 = state.get_at(base + i.arg_c());
2661                        bitwise_op_rr(state, ra, &v1, &v2, &mut pc, intop_band);
2662                    }
2663                    OpCode::BOr => {
2664                        let ra = base + i.arg_a();
2665                        let v1 = state.get_at(base + i.arg_b());
2666                        let v2 = state.get_at(base + i.arg_c());
2667                        bitwise_op_rr(state, ra, &v1, &v2, &mut pc, intop_bor);
2668                    }
2669                    OpCode::BXOr => {
2670                        let ra = base + i.arg_a();
2671                        let v1 = state.get_at(base + i.arg_b());
2672                        let v2 = state.get_at(base + i.arg_c());
2673                        bitwise_op_rr(state, ra, &v1, &v2, &mut pc, intop_bxor);
2674                    }
2675                    OpCode::Shr => {
2676                        let ra = base + i.arg_a();
2677                        let v1 = state.get_at(base + i.arg_b());
2678                        let v2 = state.get_at(base + i.arg_c());
2679                        bitwise_shift_rr(state, ra, &v1, &v2, &mut pc, true);
2680                    }
2681                    OpCode::Shl => {
2682                        let ra = base + i.arg_a();
2683                        let v1 = state.get_at(base + i.arg_b());
2684                        let v2 = state.get_at(base + i.arg_c());
2685                        bitwise_shift_rr(state, ra, &v1, &v2, &mut pc, false);
2686                    }
2687                    // ── OP_MMBIN ─────────────────────────────────────────────
2688                    // Instruction pi = *(pc - 2); TMS tm = (TMS)GETARG_C(i);
2689                    // StkId result = RA(pi);
2690                    // Protect(luaT_trybinTM(L, s2v(ra), rb, result, tm));
2691                    OpCode::MmBin => {
2692                        let ra_idx = base + i.arg_a();
2693                        let rb_idx = base + i.arg_b();
2694                        let ra_v = state.get_at(ra_idx);
2695                        let rb_v = state.get_at(rb_idx);
2696                        let tm = tagmethod_from_index(i.arg_c() as usize);
2697                        let prev_inst = code[(pc - 2) as usize];
2698                        let result_idx = base + prev_inst.arg_a();
2699                        state.set_ci_savedpc(ci, pc);
2700                        state.set_top(state.ci_top(ci));
2701                        state.try_bin_tm(
2702                            &ra_v,
2703                            Some(ra_idx),
2704                            &rb_v,
2705                            Some(rb_idx),
2706                            result_idx,
2707                            tm,
2708                        )?;
2709                        trap = state.ci_trap(ci);
2710                    }
2711                    OpCode::MmBinI => {
2712                        let ra_idx = base + i.arg_a();
2713                        let ra_v = state.get_at(ra_idx);
2714                        let imm = i.arg_s_b() as i64;
2715                        let tm = tagmethod_from_index(i.arg_c() as usize);
2716                        let flip = i.arg_k() != 0;
2717                        let prev_inst = code[(pc - 2) as usize];
2718                        let result_idx = base + prev_inst.arg_a();
2719                        state.set_ci_savedpc(ci, pc);
2720                        state.set_top(state.ci_top(ci));
2721                        state.try_bin_i_tm(&ra_v, Some(ra_idx), imm, flip, result_idx, tm)?;
2722                        trap = state.ci_trap(ci);
2723                    }
2724                    OpCode::MmBinK => {
2725                        let ra_idx = base + i.arg_a();
2726                        let ra_v = state.get_at(ra_idx);
2727                        let imm = constants[i.arg_b() as usize];
2728                        let tm = tagmethod_from_index(i.arg_c() as usize);
2729                        let flip = i.arg_k() != 0;
2730                        let prev_inst = code[(pc - 2) as usize];
2731                        let result_idx = base + prev_inst.arg_a();
2732                        state.set_ci_savedpc(ci, pc);
2733                        state.set_top(state.ci_top(ci));
2734                        state.try_bin_assoc_tm(
2735                            &ra_v,
2736                            Some(ra_idx),
2737                            &imm,
2738                            None,
2739                            flip,
2740                            result_idx,
2741                            tm,
2742                        )?;
2743                        trap = state.ci_trap(ci);
2744                    }
2745                    // ── OP_UNM ───────────────────────────────────────────────
2746                    //    else if (tonumberns(rb, nb)) setfltvalue(s2v(ra), -nb)
2747                    //    else Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM))
2748                    OpCode::Unm => {
2749                        let ra = base + i.arg_a();
2750                        let rb_idx = base + i.arg_b();
2751                        let rb_v = state.get_at(rb_idx);
2752                        match &rb_v {
2753                            LuaValue::Int(ib) => {
2754                                state.set_at(ra, LuaValue::Int(intop_sub(0, *ib)));
2755                            }
2756                            LuaValue::Float(nb) => {
2757                                state.set_at(ra, LuaValue::Float(-nb));
2758                            }
2759                            _ => {
2760                                state.set_ci_savedpc(ci, pc);
2761                                state.set_top(state.ci_top(ci));
2762                                state.try_bin_tm(
2763                                    &rb_v,
2764                                    Some(rb_idx),
2765                                    &rb_v,
2766                                    Some(rb_idx),
2767                                    ra,
2768                                    TagMethod::Unm,
2769                                )?;
2770                                trap = state.ci_trap(ci);
2771                            }
2772                        }
2773                    }
2774                    // ── OP_BNOT ──────────────────────────────────────────────
2775                    OpCode::BNot => {
2776                        let ra = base + i.arg_a();
2777                        let rb_idx = base + i.arg_b();
2778                        let rb_v = state.get_at(rb_idx);
2779                        if let Some(ib) = to_integer_ns(&rb_v, F2Imod::Eq) {
2780                            state.set_at(ra, LuaValue::Int(!ib));
2781                        } else {
2782                            state.set_ci_savedpc(ci, pc);
2783                            state.set_top(state.ci_top(ci));
2784                            state.try_bin_tm(
2785                                &rb_v,
2786                                Some(rb_idx),
2787                                &rb_v,
2788                                Some(rb_idx),
2789                                ra,
2790                                TagMethod::Bnot,
2791                            )?;
2792                            trap = state.ci_trap(ci);
2793                        }
2794                    }
2795                    // ── OP_NOT ───────────────────────────────────────────────
2796                    OpCode::Not => {
2797                        let ra = base + i.arg_a();
2798                        let rb_v = state.get_at(base + i.arg_b());
2799                        let falsy = matches!(rb_v, LuaValue::Nil | LuaValue::Bool(false));
2800                        state.set_at(ra, LuaValue::Bool(falsy));
2801                    }
2802                    // ── OP_LEN ───────────────────────────────────────────────
2803                    OpCode::Len => {
2804                        let ra = base + i.arg_a();
2805                        let rb_idx = base + i.arg_b();
2806                        let rb_v = state.get_at(rb_idx);
2807                        state.set_ci_savedpc(ci, pc);
2808                        state.set_top(state.ci_top(ci));
2809                        obj_len(state, ra, rb_v, rb_idx)?;
2810                        trap = state.ci_trap(ci);
2811                    }
2812                    // ── OP_CONCAT ─────────────────────────────────────────────
2813                    OpCode::Concat => {
2814                        let ra = base + i.arg_a();
2815                        let n = i.arg_b() as i32;
2816                        state.set_top(ra + n as i32);
2817                        state.set_ci_savedpc(ci, pc); // ProtectNT: save pc only
2818                        concat(state, n)?;
2819                        let top = state.top_idx();
2820                        state.set_ci_savedpc(ci, pc);
2821                        state.set_top(top);
2822                        state.gc_cond_step();
2823                        trap = state.ci_trap(ci);
2824                    }
2825                    // ── OP_CLOSE ──────────────────────────────────────────────
2826                    OpCode::Close => {
2827                        let ra = base + i.arg_a();
2828                        state.set_ci_savedpc(ci, pc);
2829                        state.set_top(state.ci_top(ci));
2830                        crate::func::close(
2831                            state,
2832                            ra,
2833                            lua_types::status::LuaStatus::Ok as i32,
2834                            true,
2835                        )?;
2836                        trap = state.ci_trap(ci);
2837                    }
2838                    // ── OP_TBC ────────────────────────────────────────────────
2839                    OpCode::Tbc => {
2840                        let ra = base + i.arg_a();
2841                        state.set_ci_savedpc(ci, pc);
2842                        state.set_top(state.ci_top(ci));
2843                        state.new_tbc_upval(ra)?;
2844                    }
2845                    // ── OP_JMP ────────────────────────────────────────────────
2846                    OpCode::Jmp => {
2847                        pc = (pc as i64 + i.arg_s_j() as i64) as u32;
2848                        trap = state.ci_trap(ci);
2849                    }
2850                    // ── OP_EQ ─────────────────────────────────────────────────
2851                    OpCode::Eq => {
2852                        let ra_v = state.get_at(base + i.arg_a());
2853                        let rb_v = state.get_at(base + i.arg_b());
2854                        state.set_ci_savedpc(ci, pc);
2855                        state.set_top(state.ci_top(ci));
2856                        let cond = equal_obj(Some(state), &ra_v, &rb_v)? as u32;
2857                        trap = state.ci_trap(ci);
2858                        if (cond as i32) != i.arg_k() {
2859                            pc += 1;
2860                        } else {
2861                            let next = code[pc as usize];
2862                            pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2863                            trap = state.ci_trap(ci);
2864                        }
2865                    }
2866                    // ── OP_LT ─────────────────────────────────────────────────
2867                    OpCode::Lt => {
2868                        let ra_v = state.get_at(base + i.arg_a());
2869                        let rb_v = state.get_at(base + i.arg_b());
2870                        let cond = if let (LuaValue::Int(ia), LuaValue::Int(ib)) = (&ra_v, &rb_v) {
2871                            *ia < *ib
2872                        } else if matches!(
2873                            (&ra_v, &rb_v),
2874                            (
2875                                LuaValue::Int(_) | LuaValue::Float(_),
2876                                LuaValue::Int(_) | LuaValue::Float(_)
2877                            )
2878                        ) {
2879                            lt_num(&ra_v, &rb_v)
2880                        } else {
2881                            state.set_ci_savedpc(ci, pc);
2882                            state.set_top(state.ci_top(ci));
2883                            let r = less_than_others(state, &ra_v, &rb_v)?;
2884                            trap = state.ci_trap(ci);
2885                            r
2886                        };
2887                        if (cond as i32) != i.arg_k() {
2888                            pc += 1;
2889                        } else {
2890                            let next = code[pc as usize];
2891                            pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2892                            trap = state.ci_trap(ci);
2893                        }
2894                    }
2895                    // ── OP_LE ─────────────────────────────────────────────────
2896                    OpCode::Le => {
2897                        let ra_v = state.get_at(base + i.arg_a());
2898                        let rb_v = state.get_at(base + i.arg_b());
2899                        let cond = if let (LuaValue::Int(ia), LuaValue::Int(ib)) = (&ra_v, &rb_v) {
2900                            *ia <= *ib
2901                        } else if matches!(
2902                            (&ra_v, &rb_v),
2903                            (
2904                                LuaValue::Int(_) | LuaValue::Float(_),
2905                                LuaValue::Int(_) | LuaValue::Float(_)
2906                            )
2907                        ) {
2908                            le_num(&ra_v, &rb_v)
2909                        } else {
2910                            state.set_ci_savedpc(ci, pc);
2911                            state.set_top(state.ci_top(ci));
2912                            let r = less_equal_others(state, &ra_v, &rb_v)?;
2913                            trap = state.ci_trap(ci);
2914                            r
2915                        };
2916                        if (cond as i32) != i.arg_k() {
2917                            pc += 1;
2918                        } else {
2919                            let next = code[pc as usize];
2920                            pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2921                            trap = state.ci_trap(ci);
2922                        }
2923                    }
2924                    // ── OP_EQK ────────────────────────────────────────────────
2925                    OpCode::EqK => {
2926                        let ra_v = state.get_at(base + i.arg_a());
2927                        let rb_v = constants[i.arg_b() as usize];
2928                        let cond = equal_obj(None, &ra_v, &rb_v)? as u32;
2929                        if (cond as i32) != i.arg_k() {
2930                            pc += 1;
2931                        } else {
2932                            let next = code[pc as usize];
2933                            pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2934                            trap = state.ci_trap(ci);
2935                        }
2936                    }
2937                    // ── OP_EQI ────────────────────────────────────────────────
2938                    //    if (ttisinteger) cond = ivalue == im
2939                    //    elif (ttisfloat) cond = numeq(fltvalue, cast_num(im))
2940                    //    else cond = 0
2941                    OpCode::EqI => {
2942                        let ra_v = state.get_at(base + i.arg_a());
2943                        let im = i.arg_s_b() as i64;
2944                        let cond: bool = match &ra_v {
2945                            LuaValue::Int(iv) => *iv == im,
2946                            LuaValue::Float(fv) => *fv == im as f64,
2947                            _ => false,
2948                        };
2949                        if (cond as i32) != i.arg_k() {
2950                            pc += 1;
2951                        } else {
2952                            let next = code[pc as usize];
2953                            pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2954                            trap = state.ci_trap(ci);
2955                        }
2956                    }
2957                    // ── OP_LTI / OP_LEI / OP_GTI / OP_GEI ───────────────────
2958                    //              inv=0/0/1/1, tm=TM_LT/TM_LE/TM_LT/TM_LE)
2959                    OpCode::LtI => {
2960                        let ra = base + i.arg_a();
2961                        let im = i.arg_s_b() as i64;
2962                        let fast_cond = match &state.stack[ra.0 as usize].val {
2963                            LuaValue::Int(ia) => Some(*ia < im),
2964                            LuaValue::Float(fa) => Some(*fa < im as f64),
2965                            _ => None,
2966                        };
2967                        let cond = match fast_cond {
2968                            Some(cond) => cond,
2969                            None => order_imm_slow(
2970                                state,
2971                                ra,
2972                                pc,
2973                                &mut trap,
2974                                ci,
2975                                i,
2976                                im,
2977                                false,
2978                                TagMethod::Lt,
2979                            )?,
2980                        };
2981                        finish_order_imm_jump(state, code, &mut pc, &mut trap, ci, i, cond);
2982                    }
2983                    OpCode::LeI => {
2984                        let ra = base + i.arg_a();
2985                        let im = i.arg_s_b() as i64;
2986                        let fast_cond = match &state.stack[ra.0 as usize].val {
2987                            LuaValue::Int(ia) => Some(*ia <= im),
2988                            LuaValue::Float(fa) => Some(*fa <= im as f64),
2989                            _ => None,
2990                        };
2991                        let cond = match fast_cond {
2992                            Some(cond) => cond,
2993                            None => order_imm_slow(
2994                                state,
2995                                ra,
2996                                pc,
2997                                &mut trap,
2998                                ci,
2999                                i,
3000                                im,
3001                                false,
3002                                TagMethod::Le,
3003                            )?,
3004                        };
3005                        finish_order_imm_jump(state, code, &mut pc, &mut trap, ci, i, cond);
3006                    }
3007                    OpCode::GtI => {
3008                        let ra = base + i.arg_a();
3009                        let im = i.arg_s_b() as i64;
3010                        let fast_cond = match &state.stack[ra.0 as usize].val {
3011                            LuaValue::Int(ia) => Some(*ia > im),
3012                            LuaValue::Float(fa) => Some(*fa > im as f64),
3013                            _ => None,
3014                        };
3015                        let cond = match fast_cond {
3016                            Some(cond) => cond,
3017                            None => order_imm_slow(
3018                                state,
3019                                ra,
3020                                pc,
3021                                &mut trap,
3022                                ci,
3023                                i,
3024                                im,
3025                                true,
3026                                TagMethod::Lt,
3027                            )?,
3028                        };
3029                        finish_order_imm_jump(state, code, &mut pc, &mut trap, ci, i, cond);
3030                    }
3031                    OpCode::GeI => {
3032                        let ra = base + i.arg_a();
3033                        let im = i.arg_s_b() as i64;
3034                        let fast_cond = match &state.stack[ra.0 as usize].val {
3035                            LuaValue::Int(ia) => Some(*ia >= im),
3036                            LuaValue::Float(fa) => Some(*fa >= im as f64),
3037                            _ => None,
3038                        };
3039                        let cond = match fast_cond {
3040                            Some(cond) => cond,
3041                            None => order_imm_slow(
3042                                state,
3043                                ra,
3044                                pc,
3045                                &mut trap,
3046                                ci,
3047                                i,
3048                                im,
3049                                true,
3050                                TagMethod::Le,
3051                            )?,
3052                        };
3053                        finish_order_imm_jump(state, code, &mut pc, &mut trap, ci, i, cond);
3054                    }
3055                    // ── OP_TEST ────────────────────────────────────────────────
3056                    OpCode::Test => {
3057                        let ra_v = state.get_at(base + i.arg_a());
3058                        let cond = !matches!(ra_v, LuaValue::Nil | LuaValue::Bool(false));
3059                        if (cond as i32) != i.arg_k() {
3060                            pc += 1;
3061                        } else {
3062                            let next = code[pc as usize];
3063                            pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
3064                            trap = state.ci_trap(ci);
3065                        }
3066                    }
3067                    // ── OP_TESTSET ─────────────────────────────────────────────
3068                    //    else { setobj2s(L, ra, rb); donextjump(ci); }
3069                    OpCode::TestSet => {
3070                        let ra = base + i.arg_a();
3071                        let rb_v = state.get_at(base + i.arg_b());
3072                        let falsy = matches!(rb_v, LuaValue::Nil | LuaValue::Bool(false));
3073                        if (falsy as i32) == i.arg_k() {
3074                            pc += 1;
3075                        } else {
3076                            state.set_at(ra, rb_v);
3077                            let next = code[pc as usize];
3078                            pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
3079                            trap = state.ci_trap(ci);
3080                        }
3081                    }
3082                    // ── OP_CALL ────────────────────────────────────────────────
3083                    //      updatetrap(ci);
3084                    //    else { ci = newci; goto startfunc; }
3085                    OpCode::Call => {
3086                        let ra = base + i.arg_a();
3087                        let b = i.arg_b();
3088                        let nresults = i.arg_c() as i32 - 1;
3089                        if b != 0 {
3090                            state.set_top(ra + b);
3091                        }
3092                        state.set_ci_savedpc(ci, pc); // savepc
3093                        let had_hook = state.hookmask != 0;
3094                        match state.precall(ra, nresults)? {
3095                            None => {
3096                                // C functions such as debug.sethook can change
3097                                // hook state during the call, so refresh the VM
3098                                // trap when hooks were or became relevant.
3099                                if had_hook || state.hookmask != 0 {
3100                                    trap = state.ci_trap(ci); // updatetrap
3101                                }
3102                            }
3103                            Some(new_ci) => {
3104                                // Lua function — goto startfunc
3105                                ci = new_ci;
3106                                continue 'startfunc;
3107                            }
3108                        }
3109                    }
3110                    // ── OP_TAILCALL ────────────────────────────────────────────
3111                    //      goto startfunc;
3112                    //    else { ci->func.p -= delta; luaD_poscall(L, ci, n);
3113                    //            updatetrap; goto ret; }
3114                    OpCode::TailCall => {
3115                        let ra = base + i.arg_a();
3116                        let b = i.arg_b();
3117                        let nparams1 = i.arg_c();
3118                        let delta = if nparams1 != 0 {
3119                            state.ci_nextraargs(ci) + nparams1 as i32
3120                        } else {
3121                            0
3122                        };
3123                        let top_b: i32 = if b != 0 {
3124                            state.set_top(ra + b);
3125                            b
3126                        } else {
3127                            state.top_idx() - ra
3128                        };
3129                        state.set_ci_savedpc(ci, pc);
3130                        if i.test_k() {
3131                            state.close_upvals_from_base(ci)?;
3132                        }
3133                        let n = state.pretailcall(ci, ra, top_b, delta)?;
3134                        if n < 0 {
3135                            // Lua function — goto startfunc
3136                            state.note_lua_tailcall(ci);
3137                            continue 'startfunc;
3138                        } else {
3139                            // C function — ci->func.p -= delta; luaD_poscall; goto ret
3140                            state.ci_adjust_func(ci, delta);
3141                            state.poscall(ci, n as u32)?;
3142                            if state.hookmask != 0 {
3143                                trap = state.ci_trap(ci);
3144                            }
3145                            break 'dispatch; // goto ret
3146                        }
3147                    }
3148                    // ── OP_RETURN ──────────────────────────────────────────────
3149                    //    savepc; if TESTARG_k: close upvals;
3150                    //    if nparams1: ci->func -= nextraargs+nparams1;
3151                    //    L->top.p = ra+n; luaD_poscall; goto ret
3152                    OpCode::Return => {
3153                        let ra = base + i.arg_a();
3154                        let n_raw = i.arg_b() as i32 - 1;
3155                        let nparams1 = i.arg_c();
3156                        let n: u32 = if n_raw < 0 {
3157                            (state.top_idx() - ra) as u32
3158                        } else {
3159                            n_raw as u32
3160                        };
3161                        state.set_ci_savedpc(ci, pc);
3162                        if i.test_k() {
3163                            state.ci_nres_set(ci, n as i32);
3164                            let ci_top = state.ci_top(ci);
3165                            if state.top_idx().0 < ci_top.0 {
3166                                state.set_top(ci_top);
3167                            }
3168                            crate::func::close(state, base, crate::func::CLOSE_K_TOP, true)?;
3169                            if state.hookmask != 0 {
3170                                trap = state.ci_trap(ci);
3171                            }
3172                            base = state.ci_base(ci); // updatestack
3173                        }
3174                        if nparams1 != 0 {
3175                            let nextraargs = state.ci_nextraargs(ci) as u32;
3176                            state.ci_adjust_func(ci, nextraargs as i32 + nparams1 as i32);
3177                        }
3178                        state.set_top(ra + n as i32);
3179                        state.poscall(ci, n)?;
3180                        if state.hookmask != 0 {
3181                            trap = state.ci_trap(ci);
3182                        }
3183                        break 'dispatch; // goto ret
3184                    }
3185                    // ── OP_RETURN0 ─────────────────────────────────────────────
3186                    //    else { L->ci = ci->previous; L->top = base-1;
3187                    //           for (nres = ci->nresults; nres > 0; nres--)
3188                    //             setnilvalue(L->top++) }
3189                    //    goto ret;
3190                    OpCode::Return0 => {
3191                        if state.hookmask == 0 {
3192                            let ci_slot = ci.as_usize();
3193                            let nres = state.call_info[ci_slot].nresults as i32;
3194                            state.ci = state.call_info[ci_slot]
3195                                .previous
3196                                .expect("RETURN0: returning frame has no previous CallInfo");
3197                            state.top = base - 1;
3198                            for _ in 0..nres.max(0) {
3199                                state.push(LuaValue::Nil);
3200                            }
3201                        } else {
3202                            return0_hook(state, ci, base, i, pc, &mut trap)?;
3203                        }
3204                        break 'dispatch; // goto ret
3205                    }
3206                    // ── OP_RETURN1 ─────────────────────────────────────────────
3207                    //    else { nres = ci->nresults; ci = ci->previous; ...handle results... }
3208                    //    goto ret;
3209                    OpCode::Return1 => {
3210                        if state.hookmask == 0 {
3211                            let ci_slot = ci.as_usize();
3212                            let nres = state.call_info[ci_slot].nresults as i32;
3213                            state.ci = state.call_info[ci_slot]
3214                                .previous
3215                                .expect("RETURN1: returning frame has no previous CallInfo");
3216                            if nres == 0 {
3217                                state.top = base - 1;
3218                            } else {
3219                                let ra = base + i.arg_a();
3220                                state.stack[(base - 1).0 as usize].val =
3221                                    state.stack[ra.0 as usize].val; // at least this result
3222                                state.top = base;
3223                                for _ in 1..nres.max(0) {
3224                                    state.push(LuaValue::Nil);
3225                                }
3226                            }
3227                        } else {
3228                            return1_hook(state, ci, base, i, pc, &mut trap)?;
3229                        }
3230                        break 'dispatch; // goto ret
3231                    }
3232                    // ── OP_FORLOOP ─────────────────────────────────────────────
3233                    //    else if (floatforloop(ra)) pc -= GETARG_Bx(i)
3234                    //    updatetrap(ci);
3235                    OpCode::ForLoop => {
3236                        let ra = base + i.arg_a();
3237                        if legacy_for {
3238                            if forloop_legacy(state, ra) {
3239                                pc = (pc as i64 - i.arg_bx() as i64) as u32;
3240                            }
3241                            if state.hookmask != 0 {
3242                                trap = state.ci_trap(ci);
3243                            }
3244                        } else {
3245                            let ra_u = ra.0 as usize;
3246                            let window: &mut [crate::state::StackValue; 4] = (&mut state.stack
3247                                [ra_u..ra_u + 4])
3248                                .try_into()
3249                                .expect("FORLOOP register window");
3250                            if let LuaValue::Int(step) = window[2].val {
3251                                let count = match window[1].val {
3252                                    LuaValue::Int(c) => c as u64,
3253                                    _ => 0,
3254                                };
3255                                if count > 0 {
3256                                    let idx = match window[0].val {
3257                                        LuaValue::Int(x) => x,
3258                                        _ => 0,
3259                                    };
3260                                    window[1].val = LuaValue::Int((count - 1) as i64);
3261                                    let new_idx = intop_add(idx, step);
3262                                    window[0].val = LuaValue::Int(new_idx);
3263                                    window[3].val = LuaValue::Int(new_idx);
3264                                    pc = (pc as i64 - i.arg_bx() as i64) as u32;
3265                                }
3266                            } else if float_for_loop(state, ra) {
3267                                pc = (pc as i64 - i.arg_bx() as i64) as u32;
3268                            }
3269                            if state.hookmask != 0 {
3270                                trap = state.ci_trap(ci);
3271                            }
3272                        }
3273                    }
3274                    // ── OP_FORPREP ─────────────────────────────────────────────
3275                    OpCode::ForPrep => {
3276                        let ra = base + i.arg_a();
3277                        state.set_ci_savedpc(ci, pc);
3278                        state.set_top(state.ci_top(ci));
3279                        if legacy_for {
3280                            // 5.3: prep subtracts the step and ALWAYS jumps forward
3281                            // to FORLOOP (which runs the first test).
3282                            forprep_legacy(state, ra)?;
3283                            pc = (pc as i64 + i.arg_bx() as i64) as u32;
3284                        } else if forprep(state, ra)? {
3285                            pc = (pc as i64 + i.arg_bx() as i64 + 1) as u32;
3286                        }
3287                    }
3288                    // ── OP_TFORPREP ────────────────────────────────────────────
3289                    //    pc += GETARG_Bx(i); i = *pc++; assert(OP_TFORCALL && ra==RA(i));
3290                    //    goto l_tforcall;
3291                    OpCode::TForPrep => {
3292                        let ra = base + i.arg_a();
3293                        state.set_ci_savedpc(ci, pc);
3294                        state.set_top(state.ci_top(ci));
3295                        if tfor_55 {
3296                            let closing = state.get_at(ra + 3);
3297                            let control = state.get_at(ra + 2);
3298                            state.set_at(ra + 2, closing);
3299                            state.set_at(ra + 3, control);
3300                            state.new_tbc_upval(ra + 2)?;
3301                        } else {
3302                            state.new_tbc_upval(ra + 3)?;
3303                        }
3304                        pc = (pc as i64 + i.arg_bx() as i64) as u32;
3305                        let tfc_i = code[pc as usize];
3306                        pc += 1;
3307                        debug_assert!(tfc_i.opcode() == OpCode::TForCall);
3308                        // inline l_tforcall:
3309                        let tfc_ra = base + tfc_i.arg_a();
3310                        if tfor_55 {
3311                            let src = tfc_ra.0 as usize;
3312                            let func = state.stack[src].val.clone();
3313                            let state_val = state.stack[src + 1].val.clone();
3314                            let control = state.stack[src + 3].val.clone();
3315                            state.stack[src + 3].val = func;
3316                            state.stack[src + 4].val = state_val;
3317                            state.stack[src + 5].val = control;
3318                            state.set_top(tfc_ra + 6);
3319                            state.set_ci_savedpc(ci, pc);
3320                            if !state.call_known_c_at(tfc_ra + 3, tfc_i.arg_c() as i32)? {
3321                                state.call_at(tfc_ra + 3, tfc_i.arg_c() as i32)?;
3322                            }
3323                        } else {
3324                            let src = tfc_ra.0 as usize;
3325                            let dst = src + 4;
3326                            for k in 0..3usize {
3327                                state.stack[dst + k].val = state.stack[src + k].val.clone();
3328                            }
3329                            state.set_top(tfc_ra + 4 + 3);
3330                            state.set_ci_savedpc(ci, pc);
3331                            if !state.call_known_c_at(tfc_ra + 4, tfc_i.arg_c() as i32)? {
3332                                state.call_at(tfc_ra + 4, tfc_i.arg_c() as i32)?;
3333                            }
3334                        }
3335                        trap = state.ci_trap(ci);
3336                        base = state.ci_base(ci); // updatestack
3337                        let tfl_i = code[pc as usize];
3338                        pc += 1;
3339                        debug_assert!(tfl_i.opcode() == OpCode::TForLoop);
3340                        let tfl_ra = base + tfl_i.arg_a();
3341                        // inline l_tforloop:
3342                        if tfor_55 {
3343                            if !matches!(state.get_at(tfl_ra + 3), LuaValue::Nil) {
3344                                pc = (pc as i64 - tfl_i.arg_bx() as i64) as u32;
3345                            }
3346                        } else if !matches!(state.get_at(tfl_ra + 4), LuaValue::Nil) {
3347                            let v = state.get_at(tfl_ra + 4);
3348                            state.set_at(tfl_ra + 2, v);
3349                            pc = (pc as i64 - tfl_i.arg_bx() as i64) as u32;
3350                        }
3351                    }
3352                    // ── OP_TFORCALL ────────────────────────────────────────────
3353                    OpCode::TForCall => {
3354                        let ra = base + i.arg_a();
3355                        if tfor_55 {
3356                            let src = ra.0 as usize;
3357                            let func = state.stack[src].val.clone();
3358                            let state_val = state.stack[src + 1].val.clone();
3359                            let control = state.stack[src + 3].val.clone();
3360                            state.stack[src + 3].val = func;
3361                            state.stack[src + 4].val = state_val;
3362                            state.stack[src + 5].val = control;
3363                            state.set_top(ra + 6);
3364                            state.set_ci_savedpc(ci, pc);
3365                            if !state.call_known_c_at(ra + 3, i.arg_c() as i32)? {
3366                                state.call_at(ra + 3, i.arg_c() as i32)?;
3367                            }
3368                        } else {
3369                            let src = ra.0 as usize;
3370                            let dst = src + 4;
3371                            for k in 0..3usize {
3372                                state.stack[dst + k].val = state.stack[src + k].val.clone();
3373                            }
3374                            state.set_top(ra + 4 + 3);
3375                            state.set_ci_savedpc(ci, pc);
3376                            if !state.call_known_c_at(ra + 4, i.arg_c() as i32)? {
3377                                state.call_at(ra + 4, i.arg_c() as i32)?;
3378                            }
3379                        }
3380                        trap = state.ci_trap(ci);
3381                        base = state.ci_base(ci); // updatestack
3382                        let tfl_i = code[pc as usize];
3383                        pc += 1;
3384                        debug_assert!(tfl_i.opcode() == OpCode::TForLoop);
3385                        let tfl_ra = base + tfl_i.arg_a();
3386                        if tfor_55 {
3387                            if !matches!(state.get_at(tfl_ra + 3), LuaValue::Nil) {
3388                                pc = (pc as i64 - tfl_i.arg_bx() as i64) as u32;
3389                            }
3390                        } else if !matches!(state.get_at(tfl_ra + 4), LuaValue::Nil) {
3391                            let v = state.get_at(tfl_ra + 4);
3392                            state.set_at(tfl_ra + 2, v);
3393                            pc = (pc as i64 - tfl_i.arg_bx() as i64) as u32;
3394                        }
3395                    }
3396                    // ── OP_TFORLOOP ────────────────────────────────────────────
3397                    OpCode::TForLoop => {
3398                        let ra = base + i.arg_a();
3399                        if tfor_55 {
3400                            if !matches!(state.get_at(ra + 3), LuaValue::Nil) {
3401                                pc = (pc as i64 - i.arg_bx() as i64) as u32;
3402                            }
3403                        } else if !matches!(state.get_at(ra + 4), LuaValue::Nil) {
3404                            let v = state.get_at(ra + 4);
3405                            state.set_at(ra + 2, v);
3406                            pc = (pc as i64 - i.arg_bx() as i64) as u32;
3407                        }
3408                    }
3409                    // ── OP_SETLIST ─────────────────────────────────────────────
3410                    //    if TESTARG_k: last += Ax * (MAXARG_C+1); pc++;
3411                    //    for (; n > 0; n--) h->array[last-1] = val; luaC_barrierback
3412                    OpCode::SetList => {
3413                        let ra = base + i.arg_a();
3414                        let n_raw = i.arg_b();
3415                        let mut last = i.arg_c();
3416                        let t_val = state.get_at(ra);
3417                        let n: i32 = if n_raw == 0 {
3418                            state.top_idx() - ra - 1
3419                        } else {
3420                            state.set_top(state.ci_top(ci));
3421                            n_raw
3422                        };
3423                        last += n;
3424                        if i.test_k() {
3425                            let extra = code[pc as usize];
3426                            pc += 1;
3427                            const MAXARG_C: i32 = (1 << 8) - 1;
3428                            last += extra.arg_ax() * (MAXARG_C + 1);
3429                        }
3430                        state.table_ensure_array(&t_val, last as usize)?;
3431                        for k in (1..=n).rev() {
3432                            let val = state.get_at(ra + k as i32);
3433                            state.table_array_set(&t_val, (last - 1) as usize, val.clone())?;
3434                            last -= 1;
3435                            state.gc_value_barrier_back(&t_val, &val);
3436                        }
3437                    }
3438                    // ── OP_CLOSURE ─────────────────────────────────────────────
3439                    //    halfProtect(pushclosure(L, p, cl->upvals, base, ra));
3440                    //    checkGC(L, ra+1);
3441                    OpCode::Closure => {
3442                        let ra = base + i.arg_a();
3443                        let proto_idx = i.arg_bx() as usize;
3444                        state.set_ci_savedpc(ci, pc);
3445                        state.set_top(state.ci_top(ci));
3446                        push_closure(state, proto_idx, ci, base, ra)?;
3447                        // checkGC
3448                        state.set_ci_savedpc(ci, pc);
3449                        state.set_top(ra + 1);
3450                        state.gc_cond_step();
3451                        trap = state.ci_trap(ci);
3452                    }
3453                    // ── OP_VARARG ──────────────────────────────────────────────
3454                    OpCode::VarArg => {
3455                        let ra = base + i.arg_a();
3456                        let n = i.arg_c() as i32 - 1;
3457                        state.set_ci_savedpc(ci, pc);
3458                        state.set_top(state.ci_top(ci));
3459                        state.get_varargs(ci, ra, n)?;
3460                        trap = state.ci_trap(ci);
3461                    }
3462                    // ── OP_VARARGPREP ──────────────────────────────────────────
3463                    //    if (trap) luaD_hookcall(L, ci); L->oldpc = 1;
3464                    //    updatebase(ci);
3465                    OpCode::VarArgPrep => {
3466                        let nparams = i.arg_a();
3467                        state.set_ci_savedpc(ci, pc);
3468                        state.adjust_varargs(ci, nparams, &cl)?;
3469                        trap = state.ci_trap(ci);
3470                        if trap {
3471                            state.hook_call(ci)?;
3472                            state.set_oldpc(1);
3473                        }
3474                        base = state.ci_base(ci);
3475                    }
3476                    // ── OP_GETVARG (Lua 5.5 virtual named-vararg read) ────────
3477                    OpCode::GetVArg => {
3478                        let ra = base + i.arg_a();
3479                        let vararg_reg = base + i.arg_b();
3480                        let key = state.get_at(base + i.arg_c()).clone();
3481                        let val = if let LuaValue::Table(t) = state.get_at(vararg_reg) {
3482                            t.get(&key)
3483                        } else {
3484                            let nextra = state.ci_nextraargs(ci);
3485                            match key {
3486                                LuaValue::Int(n) if n >= 1 && n <= nextra as i64 => {
3487                                    let ci_func = state.ci_base(ci) - 1;
3488                                    state.get_at(ci_func - nextra + n as i32 - 1)
3489                                }
3490                                LuaValue::Float(f)
3491                                    if f.is_finite()
3492                                        && f.fract() == 0.0
3493                                        && f >= 1.0
3494                                        && f <= nextra as f64 =>
3495                                {
3496                                    let ci_func = state.ci_base(ci) - 1;
3497                                    state.get_at(ci_func - nextra + f as i32 - 1)
3498                                }
3499                                LuaValue::Str(s) if s.as_bytes() == b"n" => {
3500                                    LuaValue::Int(nextra as i64)
3501                                }
3502                                _ => LuaValue::Nil,
3503                            }
3504                        };
3505                        state.set_at(ra, val);
3506                    }
3507                    // ── OP_EXTRAARG ────────────────────────────────────────────
3508                    OpCode::ExtraArg => {
3509                        debug_assert!(false, "OP_EXTRAARG executed directly");
3510                    }
3511                    // ── OP_ERRNNIL (Lua 5.5 global-already-defined guard) ──────
3512                    //    luaG_errnnil: if the global's current value is non-nil,
3513                    //    raise `global '<name>' already defined`. Bx == 0 → "?",
3514                    //    else Bx-1 indexes the constant table for the name.
3515                    OpCode::ErrNNil => {
3516                        let ra = base + i.arg_a();
3517                        if !matches!(state.get_at(ra), LuaValue::Nil) {
3518                            let bx = i.arg_bx();
3519                            let name: Vec<u8> = if bx == 0 {
3520                                b"?".to_vec()
3521                            } else {
3522                                match constants[(bx - 1) as usize] {
3523                                    LuaValue::Str(s) => s.as_bytes().to_vec(),
3524                                    _ => b"?".to_vec(),
3525                                }
3526                            };
3527                            let mut msg = Vec::with_capacity(name.len() + 24);
3528                            msg.extend_from_slice(b"global '");
3529                            msg.extend_from_slice(&name);
3530                            msg.extend_from_slice(b"' already defined");
3531                            state.set_ci_savedpc(ci, pc);
3532                            return Err(crate::debug::prefixed_runtime_pub(state, msg));
3533                        }
3534                    }
3535                    // ── OP_VARARGPACK (Lua 5.5 named varargs) ──────────────────
3536                    //    Pack the current frame's extra varargs into a fresh
3537                    //    table stored in register A. Mirrors `table.pack(...)`:
3538                    //    a 1-based sequence of all extra args plus an integer
3539                    //    `.n` field counting them (nil holes included). The
3540                    //    extra args were moved by VARARGPREP to the slots just
3541                    //    below `ci->func`, i.e. `ci_func - nextra .. ci_func-1`.
3542                    OpCode::VarArgPack => {
3543                        if !cl.proto.vararg_table_needed && !i.test_k() {
3544                            state.set_ci_savedpc(ci, pc);
3545                            continue;
3546                        }
3547                        let ra = base + i.arg_a();
3548                        let nextra = state.ci_nextraargs(ci);
3549                        let ci_func: StackIdx = state.ci_base(ci) - 1;
3550                        let t = if nextra > 0 {
3551                            state.new_table_with_sizes(nextra as u32, 1)?
3552                        } else {
3553                            state.new_table()
3554                        };
3555                        for k in 0..nextra {
3556                            let src: StackIdx = ci_func - nextra as i32 + k as i32;
3557                            let val = state.get_at(src);
3558                            t.raw_set_int(state, (k + 1) as i64, val)?;
3559                        }
3560                        let n_key = state.intern_str(b"n")?;
3561                        t.raw_set(state, LuaValue::Str(n_key), LuaValue::Int(nextra as i64))?;
3562                        state.set_at(ra, LuaValue::Table(t));
3563                        state.set_ci_savedpc(ci, pc);
3564                        state.set_top(ra + 1);
3565                        state.gc_cond_step();
3566                        if state.hookmask != 0 {
3567                            trap = state.ci_trap(ci);
3568                        }
3569                    }
3570                } // end match opcode
3571            } // end 'dispatch loop
3572
3573            // ── ret: label ──────────────────────────────────────────────────
3574            if state.ci_is_fresh(ci) {
3575                return Ok(());
3576            } else {
3577                ci = state
3578                    .ci_previous(ci)
3579                    .expect("ci_previous: not fresh frame must have previous");
3580                continue 'returning;
3581            }
3582        } // end 'returning loop
3583    } // end 'startfunc loop
3584}
3585
3586// ─── Local opcode dispatch helpers ───────────────────────────────────────────
3587
3588#[inline(always)]
3589fn number_value(v: LuaValue) -> Option<f64> {
3590    match v {
3591        LuaValue::Float(f) => Some(f),
3592        LuaValue::Int(i) => Some(i as f64),
3593        _ => None,
3594    }
3595}
3596
3597/// Increments `pc` on success (the `pc++` in the C macros).
3598#[allow(dead_code)]
3599#[inline]
3600fn arith_op_aux_rr(
3601    state: &mut LuaState,
3602    ra: StackIdx,
3603    v1: &LuaValue,
3604    v2: &LuaValue,
3605    pc: &mut u32,
3606    iop: fn(i64, i64) -> i64,
3607    fop: fn(f64, f64) -> f64,
3608) {
3609    if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (v1, v2) {
3610        *pc += 1;
3611        state.set_at(ra, LuaValue::Int(iop(*i1, *i2)));
3612    } else {
3613        arith_float_aux(state, ra, v1, v2, pc, fop);
3614    }
3615}
3616
3617#[allow(dead_code)]
3618#[inline]
3619fn arith_float_aux(
3620    state: &mut LuaState,
3621    ra: StackIdx,
3622    v1: &LuaValue,
3623    v2: &LuaValue,
3624    pc: &mut u32,
3625    fop: fn(f64, f64) -> f64,
3626) {
3627    let n1 = match v1 {
3628        LuaValue::Float(f) => Some(*f),
3629        LuaValue::Int(i) => Some(*i as f64),
3630        _ => None,
3631    };
3632    let n2 = match v2 {
3633        LuaValue::Float(f) => Some(*f),
3634        LuaValue::Int(i) => Some(*i as f64),
3635        _ => None,
3636    };
3637    if let (Some(n1), Some(n2)) = (n1, n2) {
3638        *pc += 1;
3639        state.set_at(ra, LuaValue::Float(fop(n1, n2)));
3640    }
3641}
3642
3643#[allow(dead_code)]
3644#[inline]
3645fn arith_op_checked(
3646    state: &mut LuaState,
3647    ra: StackIdx,
3648    v1: &LuaValue,
3649    v2: &LuaValue,
3650    pc: &mut u32,
3651    iop: fn(i64, i64) -> Result<i64, LuaError>,
3652    fop: fn(f64, f64) -> f64,
3653) -> Result<(), LuaError> {
3654    if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (v1, v2) {
3655        *pc += 1;
3656        let result = iop(*i1, *i2).map_err(|e| match e {
3657            LuaError::Runtime(LuaValue::Str(s)) => {
3658                crate::debug::prefixed_runtime_pub(state, s.as_bytes().to_vec())
3659            }
3660            LuaError::RuntimeMsg(b) => crate::debug::prefixed_runtime_pub(state, b.into_vec()),
3661            other => other,
3662        })?;
3663        state.set_at(ra, LuaValue::Int(result));
3664    } else {
3665        arith_float_aux(state, ra, v1, v2, pc, fop);
3666    }
3667    Ok(())
3668}
3669
3670#[allow(dead_code)]
3671#[inline]
3672fn bitwise_op_k(
3673    state: &mut LuaState,
3674    ra: StackIdx,
3675    v1: &LuaValue,
3676    v2: &LuaValue, // must be integer (K constant)
3677    pc: &mut u32,
3678    op: fn(i64, i64) -> i64,
3679) {
3680    let i2 = match v2 {
3681        LuaValue::Int(i) => *i,
3682        _ => return,
3683    };
3684    if let Some(i1) = to_integer_ns(v1, F2Imod::Eq) {
3685        *pc += 1;
3686        state.set_at(ra, LuaValue::Int(op(i1, i2)));
3687    }
3688}
3689
3690#[allow(dead_code)]
3691#[inline]
3692fn bitwise_op_rr(
3693    state: &mut LuaState,
3694    ra: StackIdx,
3695    v1: &LuaValue,
3696    v2: &LuaValue,
3697    pc: &mut u32,
3698    op: fn(i64, i64) -> i64,
3699) {
3700    if let (Some(i1), Some(i2)) = (to_integer_ns(v1, F2Imod::Eq), to_integer_ns(v2, F2Imod::Eq)) {
3701        *pc += 1;
3702        state.set_at(ra, LuaValue::Int(op(i1, i2)));
3703    }
3704}
3705
3706/// `right = true` negates `y` for right-shift semantics.
3707#[allow(dead_code)]
3708#[inline]
3709fn bitwise_shift_rr(
3710    state: &mut LuaState,
3711    ra: StackIdx,
3712    v1: &LuaValue,
3713    v2: &LuaValue,
3714    pc: &mut u32,
3715    right: bool,
3716) {
3717    if let (Some(i1), Some(i2)) = (to_integer_ns(v1, F2Imod::Eq), to_integer_ns(v2, F2Imod::Eq)) {
3718        let y = if right { intop_sub(0, i2) } else { i2 };
3719        *pc += 1;
3720        state.set_at(ra, LuaValue::Int(shiftl(i1, y)));
3721    }
3722}
3723
3724/// Cold half of C's `op_orderI` macro: only reached when the operand is not a
3725/// plain integer/float and a metamethod lookup may be needed.
3726#[cold]
3727#[inline(never)]
3728#[allow(clippy::too_many_arguments)]
3729fn order_imm_slow(
3730    state: &mut LuaState,
3731    ra: StackIdx,
3732    pc: u32,
3733    trap: &mut bool,
3734    ci: CallInfoIdx,
3735    i: Instruction,
3736    im: i64,
3737    inv: bool,
3738    tm: TagMethod,
3739) -> Result<bool, LuaError> {
3740    let ra_v = state.get_at(ra);
3741    let isf = i.arg_c() != 0;
3742    state.set_ci_savedpc(ci, pc);
3743    state.set_top(state.ci_top(ci));
3744    let r = state.call_order_i_tm(&ra_v, im, inv, isf, tm)?;
3745    *trap = state.ci_trap(ci);
3746    Ok(r)
3747}
3748
3749#[inline(always)]
3750fn finish_order_imm_jump(
3751    state: &mut LuaState,
3752    code: &[Instruction],
3753    pc: &mut u32,
3754    trap: &mut bool,
3755    ci: CallInfoIdx,
3756    i: Instruction,
3757    cond: bool,
3758) {
3759    if (cond as i32) != i.arg_k() {
3760        *pc += 1;
3761    } else {
3762        let next = code[*pc as usize];
3763        *pc = (*pc as i64 + next.arg_s_j() as i64 + 1) as u32;
3764        if state.hookmask != 0 {
3765            *trap = state.ci_trap(ci);
3766        }
3767    }
3768}
3769
3770#[cold]
3771#[inline(never)]
3772fn return0_hook(
3773    state: &mut LuaState,
3774    ci: CallInfoIdx,
3775    base: StackIdx,
3776    i: Instruction,
3777    pc: u32,
3778    trap: &mut bool,
3779) -> Result<(), LuaError> {
3780    let ra = base + i.arg_a();
3781    state.set_top(ra);
3782    state.set_ci_savedpc(ci, pc);
3783    state.poscall(ci, 0)?;
3784    *trap = true;
3785    Ok(())
3786}
3787
3788#[cold]
3789#[inline(never)]
3790fn return1_hook(
3791    state: &mut LuaState,
3792    ci: CallInfoIdx,
3793    base: StackIdx,
3794    i: Instruction,
3795    pc: u32,
3796    trap: &mut bool,
3797) -> Result<(), LuaError> {
3798    let ra = base + i.arg_a();
3799    state.set_top(ra + 1);
3800    state.set_ci_savedpc(ci, pc);
3801    state.poscall(ci, 1)?;
3802    *trap = true;
3803    Ok(())
3804}
3805
3806// ──────────────────────────────────────────────────────────────────────────
3807// PORT STATUS
3808//   source:        src/lvm.c  (1899 lines, 32 functions)
3809//   target_crate:  lua-vm
3810//   confidence:    medium
3811//   todos:         6
3812//   port_notes:    4
3813//   unsafe_blocks: 0   (must be 0 outside explicit unsafe-budget crates)
3814//   notes:         All opcode handlers and helpers translated; LuaState methods
3815//                  referenced (fast_get, precall, poscall, etc.) are stubs that
3816//                  Phase B will land.  The execute() goto flow is modelled with
3817//                  labelled Rust loops ('startfunc/'returning/'dispatch).
3818//                  str_to_number is a stub pending luaO_str2num port (TODO #1).
3819//                  strcoll replaced with byte-lexicographic order (TODO #2).
3820//                  order_imm_op uses LuaValue as a stand-in for GcRef<LuaClosure>
3821//                  (TODO #3).  ClosureRef type alias not yet defined (TODO #4-6).
3822// ──────────────────────────────────────────────────────────────────────────