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// C: #include "lprefix.h"
23// C: #include "lua.h"
24// C: #include "ldebug.h" "ldo.h" "lfunc.h" "lgc.h" "lobject.h"
25// C: #include "lopcodes.h" "lstate.h" "lstring.h" "ltable.h" "ltm.h" "lvm.h"
26
27#[allow(unused_imports)] use crate::prelude::*;
28use lua_types::{
29 CallInfoIdx, GcRef, LuaError, LuaValue, StackIdx,
30};
31use lua_types::tagmethod::TagMethod;
32use lua_types::opcode::Instruction;
33use crate::state::LuaState;
34
35/// TODO(phase-b): lua-types does not yet expose `OpCode`. Stubbed locally with
36/// all 5.4 opcodes so call sites in vm.rs/debug.rs resolve; the real numeric
37/// values and per-opcode mode flags live in `lua-types/src/opcode.rs` once
38/// translated.
39///
40/// `#[repr(u8)]` with explicit discriminants matching C-Lua's `lopcodes.h`
41/// numbering (0=OP_MOVE, 1=OP_LOADI, ..., 82=OP_EXTRAARG). The ordered, dense
42/// 0..=82 layout lets LLVM compile `opcode()` to a bounds-checked cast on the
43/// low 7 bits of the instruction word and fuse it with the dispatch `match`
44/// downstream. Discriminant order intentionally matches the integer keys in
45/// `InstructionExt::opcode`, not the prior compile-order grouping.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[allow(non_camel_case_types)]
48#[repr(u8)]
49pub enum OpCode {
50 Move = 0,
51 LoadI = 1,
52 LoadF = 2,
53 LoadK = 3,
54 LoadKX = 4,
55 LoadFalse = 5,
56 LFalseSkip = 6,
57 LoadTrue = 7,
58 LoadNil = 8,
59 GetUpVal = 9,
60 SetUpVal = 10,
61 GetTabUp = 11,
62 GetTable = 12,
63 GetI = 13,
64 GetField = 14,
65 SetTabUp = 15,
66 SetTable = 16,
67 SetI = 17,
68 SetField = 18,
69 NewTable = 19,
70 Self_ = 20,
71 AddI = 21,
72 AddK = 22,
73 SubK = 23,
74 MulK = 24,
75 ModK = 25,
76 PowK = 26,
77 DivK = 27,
78 IDivK = 28,
79 BAndK = 29,
80 BOrK = 30,
81 BXOrK = 31,
82 ShrI = 32,
83 ShlI = 33,
84 Add = 34,
85 Sub = 35,
86 Mul = 36,
87 Mod = 37,
88 Pow = 38,
89 Div = 39,
90 IDiv = 40,
91 BAnd = 41,
92 BOr = 42,
93 BXOr = 43,
94 Shl = 44,
95 Shr = 45,
96 MmBin = 46,
97 MmBinI = 47,
98 MmBinK = 48,
99 Unm = 49,
100 BNot = 50,
101 Not = 51,
102 Len = 52,
103 Concat = 53,
104 Close = 54,
105 Tbc = 55,
106 Jmp = 56,
107 Eq = 57,
108 Lt = 58,
109 Le = 59,
110 EqK = 60,
111 EqI = 61,
112 LtI = 62,
113 LeI = 63,
114 GtI = 64,
115 GeI = 65,
116 Test = 66,
117 TestSet = 67,
118 Call = 68,
119 TailCall = 69,
120 Return = 70,
121 Return0 = 71,
122 Return1 = 72,
123 ForLoop = 73,
124 ForPrep = 74,
125 TForPrep = 75,
126 TForCall = 76,
127 TForLoop = 77,
128 SetList = 78,
129 Closure = 79,
130 VarArg = 80,
131 VarArgPrep = 81,
132 ExtraArg = 82,
133}
134
135/// Number of distinct opcodes (matches C-Lua's `NUM_OPCODES`). Held for
136/// downstream debug/dump callers that count opcodes by name; the dispatch
137/// hot path in `InstructionExt::opcode` does its own per-arm match.
138#[allow(dead_code)]
139const NUM_OPCODES: u8 = 83;
140
141impl OpCode {
142 /// Legacy alias retained because the prior duplicate enum variant
143 /// `LoadKx` (case-typo of `LoadKX`) is still referenced from
144 /// `crates/lua-vm/src/debug.rs`. Both names denote the same C
145 /// `OP_LOADKX` opcode. Kept as an associated `const` so existing call
146 /// sites compile unchanged while the enum remains a clean 0..=82 dense
147 /// discriminant set required by `#[repr(u8)]`.
148 #[allow(non_upper_case_globals)]
149 pub const LoadKx: OpCode = OpCode::LoadKX;
150
151 /// Legacy alias for `GetUpVal` retained for the same reason as `LoadKx`.
152 #[allow(non_upper_case_globals)]
153 pub const GetUpval: OpCode = OpCode::GetUpVal;
154}
155
156/// TODO(phase-b): Instruction accessor extension trait. The real per-mode
157/// decode helpers live in `lua-types::opcode` once translated. Stubbed locally
158/// so call sites resolve; bodies are inferred from `lopcodes.h` macro shapes.
159pub trait InstructionExt {
160 fn opcode(&self) -> OpCode;
161 fn arg_a(&self) -> i32;
162 fn arg_b(&self) -> i32;
163 fn arg_c(&self) -> i32;
164 fn arg_k(&self) -> i32;
165 fn arg_ax(&self) -> i32;
166 fn arg_bx(&self) -> i32;
167 fn arg_s_b(&self) -> i32;
168 fn arg_s_c(&self) -> i32;
169 fn arg_s_j(&self) -> i32;
170 fn arg_s_bx(&self) -> i32;
171 fn test_k(&self) -> bool;
172 fn test_a_mode(&self) -> bool;
173 fn is_mm_mode(&self) -> bool;
174 fn is_vararg_prep(&self) -> bool;
175 fn is_in_top(&self) -> bool;
176}
177
178impl InstructionExt for Instruction {
179 /// C: `GET_OPCODE(i) = (OpCode)((i) >> POS_OP & MASK1(SIZE_OP,0))`.
180 ///
181 /// The 83-arm match looks expensive, but because `OpCode` is
182 /// `#[repr(u8)]` with explicit discriminants 0..=82 matching each match
183 /// arm's integer key exactly, LLVM compiles this to a single bounds
184 /// check + identity cast — no jump table, no memory indirection. The
185 /// previous array-lookup form forced an extra `OPCODE_TABLE` byte load
186 /// per dispatch tick that LLVM could not see through.
187 #[inline(always)]
188 fn opcode(&self) -> OpCode {
189 match (self.raw() & 0x7F) as u8 {
190 0 => OpCode::Move,
191 1 => OpCode::LoadI,
192 2 => OpCode::LoadF,
193 3 => OpCode::LoadK,
194 4 => OpCode::LoadKX,
195 5 => OpCode::LoadFalse,
196 6 => OpCode::LFalseSkip,
197 7 => OpCode::LoadTrue,
198 8 => OpCode::LoadNil,
199 9 => OpCode::GetUpVal,
200 10 => OpCode::SetUpVal,
201 11 => OpCode::GetTabUp,
202 12 => OpCode::GetTable,
203 13 => OpCode::GetI,
204 14 => OpCode::GetField,
205 15 => OpCode::SetTabUp,
206 16 => OpCode::SetTable,
207 17 => OpCode::SetI,
208 18 => OpCode::SetField,
209 19 => OpCode::NewTable,
210 20 => OpCode::Self_,
211 21 => OpCode::AddI,
212 22 => OpCode::AddK,
213 23 => OpCode::SubK,
214 24 => OpCode::MulK,
215 25 => OpCode::ModK,
216 26 => OpCode::PowK,
217 27 => OpCode::DivK,
218 28 => OpCode::IDivK,
219 29 => OpCode::BAndK,
220 30 => OpCode::BOrK,
221 31 => OpCode::BXOrK,
222 32 => OpCode::ShrI,
223 33 => OpCode::ShlI,
224 34 => OpCode::Add,
225 35 => OpCode::Sub,
226 36 => OpCode::Mul,
227 37 => OpCode::Mod,
228 38 => OpCode::Pow,
229 39 => OpCode::Div,
230 40 => OpCode::IDiv,
231 41 => OpCode::BAnd,
232 42 => OpCode::BOr,
233 43 => OpCode::BXOr,
234 44 => OpCode::Shl,
235 45 => OpCode::Shr,
236 46 => OpCode::MmBin,
237 47 => OpCode::MmBinI,
238 48 => OpCode::MmBinK,
239 49 => OpCode::Unm,
240 50 => OpCode::BNot,
241 51 => OpCode::Not,
242 52 => OpCode::Len,
243 53 => OpCode::Concat,
244 54 => OpCode::Close,
245 55 => OpCode::Tbc,
246 56 => OpCode::Jmp,
247 57 => OpCode::Eq,
248 58 => OpCode::Lt,
249 59 => OpCode::Le,
250 60 => OpCode::EqK,
251 61 => OpCode::EqI,
252 62 => OpCode::LtI,
253 63 => OpCode::LeI,
254 64 => OpCode::GtI,
255 65 => OpCode::GeI,
256 66 => OpCode::Test,
257 67 => OpCode::TestSet,
258 68 => OpCode::Call,
259 69 => OpCode::TailCall,
260 70 => OpCode::Return,
261 71 => OpCode::Return0,
262 72 => OpCode::Return1,
263 73 => OpCode::ForLoop,
264 74 => OpCode::ForPrep,
265 75 => OpCode::TForPrep,
266 76 => OpCode::TForCall,
267 77 => OpCode::TForLoop,
268 78 => OpCode::SetList,
269 79 => OpCode::Closure,
270 80 => OpCode::VarArg,
271 81 => OpCode::VarArgPrep,
272 82 => OpCode::ExtraArg,
273 _ => OpCode::ExtraArg,
274 }
275 }
276 #[inline] fn arg_a(&self) -> i32 { ((self.raw() >> 7) & 0xFF) as i32 }
277 #[inline] fn arg_b(&self) -> i32 { ((self.raw() >> 16) & 0xFF) as i32 }
278 #[inline] fn arg_c(&self) -> i32 { ((self.raw() >> 24) & 0xFF) as i32 }
279 #[inline] fn arg_k(&self) -> i32 { ((self.raw() >> 15) & 0x1) as i32 }
280 #[inline] fn arg_ax(&self) -> i32 { (self.raw() >> 7) as i32 }
281 #[inline] fn arg_bx(&self) -> i32 { (self.raw() >> 15) as i32 }
282 #[inline] fn arg_s_b(&self) -> i32 { self.arg_b() - 0x7F }
283 #[inline] fn arg_s_c(&self) -> i32 { self.arg_c() - 0x7F }
284 #[inline] fn arg_s_j(&self) -> i32 { self.arg_ax() - 0xFFFFFF }
285 #[inline] fn arg_s_bx(&self) -> i32 { self.arg_bx() - 0xFFFF }
286 #[inline] fn test_k(&self) -> bool { (self.raw() & (1 << 15)) != 0 }
287 #[inline]
288 fn test_a_mode(&self) -> bool {
289 (op_mode_byte(self.opcode()) & (1 << 3)) != 0
290 }
291 #[inline]
292 fn is_mm_mode(&self) -> bool {
293 (op_mode_byte(self.opcode()) & (1 << 7)) != 0
294 }
295 #[inline]
296 fn is_vararg_prep(&self) -> bool {
297 matches!(self.opcode(), OpCode::VarArgPrep)
298 }
299 #[inline]
300 fn is_in_top(&self) -> bool {
301 (op_mode_byte(self.opcode()) & (1 << 5)) != 0 && self.arg_b() == 0
302 }
303}
304
305/// C: `luaP_opmodes[op]` — bit-packed opcode property byte.
306///
307/// Layout (from lopcodes.h `opmode` macro):
308/// bit 7: MM (metamethod call)
309/// bit 6: OT (instruction sets `L->top` for next when C == 0)
310/// bit 5: IT (instruction reads `L->top` from prev when B == 0)
311/// bit 4: T (test; next instruction must be a jump)
312/// bit 3: A (instruction writes register A)
313/// bits 0-2: op format mode (iABC, iABx, iAsBx, iAx, isJ)
314///
315/// PORT NOTE: lua-types does not yet expose the canonical `OP_MODES` table; this
316/// is a local stand-in keyed off the vm.rs `OpCode` stub so the four mode
317/// predicates above can answer correctly until the real table lands.
318const OP_MODE_BYTES: [u8; NUM_OPCODES as usize] = [
319 0x08, // Move
320 0x0a, // LoadI
321 0x0a, // LoadF
322 0x09, // LoadK
323 0x09, // LoadKX
324 0x08, // LoadFalse
325 0x08, // LFalseSkip
326 0x08, // LoadTrue
327 0x08, // LoadNil
328 0x08, // GetUpVal
329 0x00, // SetUpVal
330 0x08, // GetTabUp
331 0x08, // GetTable
332 0x08, // GetI
333 0x08, // GetField
334 0x00, // SetTabUp
335 0x00, // SetTable
336 0x00, // SetI
337 0x00, // SetField
338 0x08, // NewTable
339 0x08, // Self_
340 0x08, // AddI
341 0x08, // AddK
342 0x08, // SubK
343 0x08, // MulK
344 0x08, // ModK
345 0x08, // PowK
346 0x08, // DivK
347 0x08, // IDivK
348 0x08, // BAndK
349 0x08, // BOrK
350 0x08, // BXOrK
351 0x08, // ShrI
352 0x08, // ShlI
353 0x08, // Add
354 0x08, // Sub
355 0x08, // Mul
356 0x08, // Mod
357 0x08, // Pow
358 0x08, // Div
359 0x08, // IDiv
360 0x08, // BAnd
361 0x08, // BOr
362 0x08, // BXOr
363 0x08, // Shl
364 0x08, // Shr
365 0x80, // MmBin
366 0x80, // MmBinI
367 0x80, // MmBinK
368 0x08, // Unm
369 0x08, // BNot
370 0x08, // Not
371 0x08, // Len
372 0x08, // Concat
373 0x00, // Close
374 0x00, // Tbc
375 0x04, // Jmp
376 0x10, // Eq
377 0x10, // Lt
378 0x10, // Le
379 0x10, // EqK
380 0x10, // EqI
381 0x10, // LtI
382 0x10, // LeI
383 0x10, // GtI
384 0x10, // GeI
385 0x10, // Test
386 0x18, // TestSet
387 0x68, // Call
388 0x68, // TailCall
389 0x20, // Return
390 0x00, // Return0
391 0x00, // Return1
392 0x09, // ForLoop
393 0x09, // ForPrep
394 0x01, // TForPrep
395 0x00, // TForCall
396 0x09, // TForLoop
397 0x20, // SetList
398 0x09, // Closure
399 0x48, // VarArg
400 0x28, // VarArgPrep
401 0x03, // ExtraArg
402];
403
404#[inline(always)]
405fn op_mode_byte(op: OpCode) -> u8 {
406 OP_MODE_BYTES[op as usize]
407}
408
409// ─── Constants ───────────────────────────────────────────────────────────────
410
411/// C: #define MAXTAGLOOP 2000
412/// Limit for tag-method chains to avoid infinite loops.
413const MAX_TAG_LOOP: i32 = 2000;
414
415/// C: NBITS — number of bits in lua_Integer (i64).
416const NBITS: u32 = 64;
417
418// ─── F2Imod — float-to-integer rounding mode ────────────────────────────────
419
420/// C: `typedef enum { F2Ieq, F2Ifloor, F2Iceil } F2Imod;`
421/// Rounding mode for float→integer coercions.
422#[derive(Debug, Clone, Copy, PartialEq, Eq)]
423pub(crate) enum F2Imod {
424 /// Accept only exact integral values (no rounding).
425 Eq,
426 /// Round toward negative infinity.
427 Floor,
428 /// Round toward positive infinity.
429 Ceil,
430}
431
432// ─── Integer-overflow-safe helpers ──────────────────────────────────────────
433
434/// C: `intop(+, a, b)` — wrapping add on i64 operands.
435#[inline]
436fn intop_add(a: i64, b: i64) -> i64 {
437 (a as u64).wrapping_add(b as u64) as i64
438}
439
440/// C: `intop(-, a, b)` — wrapping sub.
441#[inline]
442fn intop_sub(a: i64, b: i64) -> i64 {
443 (a as u64).wrapping_sub(b as u64) as i64
444}
445
446/// C: `intop(*, a, b)` — wrapping mul.
447#[inline]
448fn intop_mul(a: i64, b: i64) -> i64 {
449 (a as u64).wrapping_mul(b as u64) as i64
450}
451
452/// C: `intop(>>, x, -y)` or `intop(<<, x, y)` — wrapping shift.
453/// Shifts via unsigned intermediate to get logical (not arithmetic) semantics.
454#[inline]
455fn intop_shr(x: i64, n: u32) -> i64 {
456 // PERF(port): logical right shift via unsigned; matches C unsigned semantics
457 (x as u64 >> n) as i64
458}
459
460#[inline]
461fn intop_shl(x: i64, n: u32) -> i64 {
462 (x as u64).wrapping_shl(n) as i64
463}
464
465/// C: `intop(&, a, b)`, `intop(|, a, b)`, `intop(^, a, b)`
466#[inline]
467fn intop_band(a: i64, b: i64) -> i64 { ((a as u64) & (b as u64)) as i64 }
468#[inline]
469fn intop_bor(a: i64, b: i64) -> i64 { ((a as u64) | (b as u64)) as i64 }
470#[inline]
471fn intop_bxor(a: i64, b: i64) -> i64 { ((a as u64) ^ (b as u64)) as i64 }
472
473// ─── l_intfitsf ─────────────────────────────────────────────────────────────
474
475/// C: `l_intfitsf(i)` — does integer `i` fit exactly in an f64 mantissa?
476/// f64 has 53 bits of mantissa (including implicit leading 1).
477/// All i64 values with |i| <= 2^53 are exactly representable.
478#[inline]
479fn int_fits_float(i: i64) -> bool {
480 // C: MAXINTFITSF = 1u64 << NBM (NBM = f64::MANTISSA_DIGITS = 53)
481 const MAXINTFITSF: u64 = 1u64 << f64::MANTISSA_DIGITS;
482 // C: (MAXINTFITSF + l_castS2U(i)) <= (2 * MAXINTFITSF)
483 (MAXINTFITSF.wrapping_add(i as u64)) <= 2 * MAXINTFITSF
484}
485
486// ─── Private helper: string-to-number coercion ──────────────────────────────
487
488/// C: `static int l_strton(const TValue *obj, TValue *result)`
489/// Attempt to convert a string value to a number in-place.
490/// Returns `Some(LuaValue)` with the numeric result, or `None` if the
491/// value is not a string or cannot be parsed as a numeral.
492fn str_to_number(obj: &LuaValue) -> Option<LuaValue> {
493 // C: if (!cvt2num(obj)) return 0;
494 // cvt2num(o) = matches!(o, LuaValue::Str(_))
495 let s = match obj {
496 LuaValue::Str(ts) => ts.as_bytes().to_vec(),
497 _ => return None,
498 };
499 // C: return (luaO_str2num(getstr(st), result) == tsslen(st) + 1)
500 // Trim whitespace as Lua allows spaces around numerals in coercions.
501 let trimmed = trim_whitespace(&s);
502 if trimmed.is_empty() {
503 return None;
504 }
505 let mut result = LuaValue::Nil;
506 if crate::object::str2num(trimmed, &mut result) != 0 {
507 return Some(result);
508 }
509 None
510}
511
512fn trim_whitespace(s: &[u8]) -> &[u8] {
513 let start = s.iter().position(|&b| !b.is_ascii_whitespace()).unwrap_or(s.len());
514 let end = s.iter().rposition(|&b| !b.is_ascii_whitespace()).map(|i| i + 1).unwrap_or(0);
515 if start <= end { &s[start..end] } else { &s[0..0] }
516}
517
518// ─── Number coercion (public API matching lvm.h exports) ────────────────────
519
520/// C: `int luaV_tonumber_(const TValue *obj, lua_Number *n)`
521/// Convert `obj` to f64, with string coercion. Returns `Some(f64)` on
522/// success. The fast path (already float) is handled by the caller's
523/// `tonumber` macro (inlined at call sites).
524pub(crate) fn tonumber_(obj: &LuaValue) -> Option<f64> {
525 // C: if (ttisinteger(obj)) { *n = cast_num(ivalue(obj)); return 1; }
526 if let LuaValue::Int(i) = obj {
527 return Some(*i as f64);
528 }
529 // C: else if (l_strton(obj, &v)) { *n = nvalue(&v); return 1; }
530 if let Some(v) = str_to_number(obj) {
531 return match v {
532 LuaValue::Float(f) => Some(f),
533 LuaValue::Int(i) => Some(i as f64),
534 _ => None,
535 };
536 }
537 None
538}
539
540/// C: `#define tonumber(o,n) (ttisfloat(o) ? (*(n) = fltvalue(o), 1) : luaV_tonumber_(o,n))`
541/// Full numeric coercion including the float fast-path that `tonumber_` omits.
542fn tonumber(obj: &LuaValue) -> Option<f64> {
543 if let LuaValue::Float(f) = obj {
544 return Some(*f);
545 }
546 tonumber_(obj)
547}
548
549/// C: `int luaV_flttointeger(lua_Number n, lua_Integer *p, F2Imod mode)`
550/// Convert float `n` to an integer according to `mode`.
551/// Returns `Some(i64)` on success.
552pub(crate) fn flt_to_integer(n: f64, mode: F2Imod) -> Option<i64> {
553 // C: lua_Number f = l_floor(n);
554 let f = n.floor();
555 // C: if (n != f) { if (mode == F2Ieq) return 0; else if (mode == F2Iceil) f += 1; }
556 if n != f {
557 match mode {
558 F2Imod::Eq => return None,
559 F2Imod::Ceil => {
560 // f = floor(n) + 1 = ceil(n) since n is not integral
561 let f = f + 1.0;
562 // C: return lua_numbertointeger(f, p)
563 // lua_numbertointeger checks i64::MIN <= f <= i64::MAX
564 if f >= i64::MIN as f64 && f < (i64::MAX as f64 + 1.0) {
565 return Some(f as i64);
566 }
567 return None;
568 }
569 F2Imod::Floor => { /* f is already floor(n) */ }
570 }
571 }
572 // C: return lua_numbertointeger(f, p)
573 if f >= i64::MIN as f64 && f < (i64::MAX as f64 + 1.0) {
574 Some(f as i64)
575 } else {
576 None
577 }
578}
579
580/// C: `int luaV_tointegerns(const TValue *obj, lua_Integer *p, F2Imod mode)`
581/// Convert a value to integer without string coercion.
582pub(crate) fn to_integer_ns(obj: &LuaValue, mode: F2Imod) -> Option<i64> {
583 // C: if (ttisfloat(obj)) return luaV_flttointeger(fltvalue(obj), p, mode);
584 if let LuaValue::Float(f) = obj {
585 return flt_to_integer(*f, mode);
586 }
587 // C: else if (ttisinteger(obj)) { *p = ivalue(obj); return 1; }
588 if let LuaValue::Int(i) = obj {
589 return Some(*i);
590 }
591 None
592}
593
594/// C: `int luaV_tointeger(const TValue *obj, lua_Integer *p, F2Imod mode)`
595/// Convert a value to integer, with string coercion.
596pub(crate) fn to_integer(obj: &LuaValue, mode: F2Imod) -> Option<i64> {
597 // C: TValue v; if (l_strton(obj, &v)) obj = &v;
598 let coerced;
599 let obj = if let Some(v) = str_to_number(obj) {
600 coerced = v;
601 &coerced
602 } else {
603 obj
604 };
605 to_integer_ns(obj, mode)
606}
607
608// ─── for-loop helpers ────────────────────────────────────────────────────────
609
610/// C: `static int forlimit(lua_State *L, lua_Integer init, const TValue *lim,
611/// lua_Integer *p, lua_Integer step)`
612/// Compute the integer loop limit. Returns `Ok(true)` to skip the loop,
613/// `Ok(false)` with `*p` set to the limit, or `Err` if the limit is not a
614/// number at all.
615fn forlimit(
616 state: &mut LuaState,
617 init: i64,
618 lim: &LuaValue,
619 step: i64,
620) -> Result<(bool, i64), LuaError> {
621 let round = if step < 0 { F2Imod::Ceil } else { F2Imod::Floor };
622 if let Some(p) = to_integer(lim, round) {
623 let skip = if step > 0 { init > p } else { init < p };
624 return Ok((skip, p));
625 }
626 let flim = match tonumber(lim) {
627 Some(f) => f,
628 None => return Err(crate::debug::for_error(state, lim, b"limit")),
629 };
630 // C: float out of integer bounds — clip to LUA_MAXINTEGER or MININTEGER
631 if 0.0_f64 < flim {
632 // positive → too large
633 if step < 0 {
634 // C: if (step < 0) return 1; /* initial value must be less than it */
635 return Ok((true, 0));
636 }
637 Ok((false, i64::MAX))
638 } else {
639 // negative → less than min integer
640 if step > 0 {
641 // C: if (step > 0) return 1;
642 return Ok((true, 0));
643 }
644 Ok((false, i64::MIN))
645 }
646}
647
648/// C: `static int forprep(lua_State *L, StkId ra)`
649/// Prepare a numeric `for` loop (OP_FORPREP).
650/// Stack layout at `ra`:
651/// ra+0: init, ra+1: limit, ra+2: step, ra+3: control variable (written here)
652/// Returns `Ok(true)` to skip the loop body entirely.
653pub(crate) fn forprep(state: &mut LuaState, ra: StackIdx) -> Result<bool, LuaError> {
654 // C: TValue *pinit = s2v(ra); *plimit = s2v(ra+1); *pstep = s2v(ra+2);
655 let pinit = state.get_at(ra);
656 let plimit = state.get_at(ra + 1);
657 let pstep = state.get_at(ra + 2);
658
659 if let (LuaValue::Int(init), LuaValue::Int(step)) = (&pinit, &pstep) {
660 // C: integer loop
661 let init = *init;
662 let step = *step;
663 if step == 0 {
664 return Err(LuaError::runtime(format_args!("'for' step is zero")));
665 }
666 // C: setivalue(s2v(ra+3), init)
667 state.set_at(ra + 3, LuaValue::Int(init));
668
669 let (skip, limit) = forlimit(state, init, &plimit, step)?;
670 if skip {
671 return Ok(true);
672 }
673 // C: compute loop counter (iteration count) from limit, init, step
674 let count: u64 = if step > 0 {
675 // C: count = l_castS2U(limit) - l_castS2U(init);
676 let c = (limit as u64).wrapping_sub(init as u64);
677 // C: if (step != 1) count /= l_castS2U(step);
678 if step != 1 { c / (step as u64) } else { c }
679 } else {
680 // C: count = l_castS2U(init) - l_castS2U(limit);
681 let c = (init as u64).wrapping_sub(limit as u64);
682 // C: count /= l_castS2U(-(step + 1)) + 1u
683 c / (((-(step + 1)) as u64).wrapping_add(1))
684 };
685 // C: setivalue(plimit, l_castU2S(count)) — store counter in limit slot
686 state.set_at(ra + 1, LuaValue::Int(count as i64));
687 Ok(false)
688 } else {
689 // C: float loop — coerce all three values to floats
690 let limit_f = match tonumber(&plimit) {
691 Some(f) => f,
692 None => return Err(crate::debug::for_error(state, &plimit, b"limit")),
693 };
694 let step_f = match tonumber(&pstep) {
695 Some(f) => f,
696 None => return Err(crate::debug::for_error(state, &pstep, b"step")),
697 };
698 let init_f = match tonumber(&pinit) {
699 Some(f) => f,
700 None => return Err(crate::debug::for_error(state, &pinit, b"initial value")),
701 };
702 if step_f == 0.0 {
703 return Err(LuaError::runtime(format_args!("'for' step is zero")));
704 }
705 // C: if (step>0 ? limit<init : init<limit) return 1
706 let skip = if step_f > 0.0 { limit_f < init_f } else { init_f < limit_f };
707 if skip {
708 return Ok(true);
709 }
710 // C: setfltvalue(plimit, limit); setfltvalue(pstep, step);
711 // setfltvalue(s2v(ra), init); setfltvalue(s2v(ra+3), init);
712 state.set_at(ra + 1, LuaValue::Float(limit_f));
713 state.set_at(ra + 2, LuaValue::Float(step_f));
714 state.set_at(ra, LuaValue::Float(init_f));
715 state.set_at(ra + 3, LuaValue::Float(init_f));
716 Ok(false)
717 }
718}
719
720/// C: `static int floatforloop(StkId ra)` — float for-loop step.
721/// Increments the float loop index and returns `true` if the loop continues.
722fn float_for_loop(state: &mut LuaState, ra: StackIdx) -> bool {
723 // C: step = fltvalue(s2v(ra+2)); limit = fltvalue(s2v(ra+1));
724 // idx = fltvalue(s2v(ra));
725 let step = match state.get_at(ra + 2) {
726 LuaValue::Float(f) => f,
727 _ => return false,
728 };
729 let limit = match state.get_at(ra + 1) {
730 LuaValue::Float(f) => f,
731 _ => return false,
732 };
733 let idx = match state.get_at(ra) {
734 LuaValue::Float(f) => f,
735 _ => return false,
736 };
737 // C: idx = luai_numadd(L, idx, step);
738 let idx = idx + step;
739 // C: if (step>0 ? idx<=limit : limit<=idx)
740 if if step > 0.0 { idx <= limit } else { limit <= idx } {
741 // C: chgfltvalue(s2v(ra), idx); setfltvalue(s2v(ra+3), idx);
742 state.set_at(ra, LuaValue::Float(idx));
743 state.set_at(ra + 3, LuaValue::Float(idx));
744 true
745 } else {
746 false
747 }
748}
749
750// ─── Table get/set with metamethod chains ────────────────────────────────────
751
752/// C: `void luaV_finishget(lua_State *L, const TValue *t, TValue *key,
753/// StkId val, const TValue *slot)`
754/// Finish a table-get with metamethod lookup. `slot_was_none = true` means
755/// `t` is not a table and we should look for `__index` on `t` itself.
756pub(crate) fn finish_get(
757 state: &mut LuaState,
758 t_val: LuaValue,
759 key: LuaValue,
760 result_idx: StackIdx,
761 slot_empty: bool,
762 t_idx: Option<StackIdx>,
763) -> Result<(), LuaError> {
764 // C: for (loop = 0; loop < MAXTAGLOOP; loop++)
765 let mut t = t_val;
766 let mut t_idx = t_idx;
767 for _loop in 0..MAX_TAG_LOOP {
768 let tm: LuaValue;
769 if slot_empty && !matches!(t, LuaValue::Table(_)) {
770 // C: if (slot == NULL) { tm = luaT_gettmbyobj(L, t, TM_INDEX); }
771 // C: if (l_unlikely(notm(tm))) luaG_typeerror(L, t, "index");
772 tm = state.get_tm_by_obj(&t, TagMethod::Index);
773 if matches!(tm, LuaValue::Nil) {
774 return Err(match t_idx {
775 Some(idx) => crate::debug::type_error(state, &t, idx, b"index"),
776 None => LuaError::type_error(&t, "index"),
777 });
778 }
779 } else {
780 // C: t is a table; tm = fasttm(L, hvalue(t)->metatable, TM_INDEX)
781 // C: if (tm == NULL) { setnilvalue(s2v(val)); return; }
782 let mt = state.table_metatable(&t);
783 tm = state.fast_tm_table(mt.as_ref(), TagMethod::Index);
784 if matches!(tm, LuaValue::Nil) {
785 state.set_at(result_idx, LuaValue::Nil);
786 return Ok(());
787 }
788 }
789 // C: if (ttisfunction(tm)) { luaT_callTMres(...); return; }
790 if matches!(tm, LuaValue::Function(_)) {
791 state.call_tm_res(tm, &t, &key, result_idx)?;
792 return Ok(());
793 }
794 // C: t = tm; try t[key] again
795 t = tm.clone();
796 t_idx = None;
797 // C: if (luaV_fastget(L, t, key, slot, luaH_get))
798 if let Some(v) = state.fast_get(&t, &key)? {
799 state.set_at(result_idx, v);
800 return Ok(());
801 }
802 // else: loop — tail-call luaV_finishget
803 }
804 Err(LuaError::runtime(format_args!("'__index' chain too long; possible loop")))
805}
806
807/// C: `void luaV_finishset(lua_State *L, const TValue *t, TValue *key,
808/// TValue *val, const TValue *slot)`
809/// Finish a table-set with `__newindex` metamethod lookup.
810///
811/// `var_hint` carries a `(kind, name)` pair (e.g. `(b"upvalue", b"a")`) used
812/// only when `t_idx` is None and the target is non-indexable — typically
813/// when the LHS is an upvalue (OP_SETTABUP). Pointer-identifying var_info
814/// won't recover the upvalue's name in that case, so the caller passes it
815/// in directly.
816pub(crate) fn finish_set(
817 state: &mut LuaState,
818 t_val: LuaValue,
819 key: LuaValue,
820 val: LuaValue,
821 _slot_present: bool,
822 t_idx: Option<StackIdx>,
823 var_hint: Option<(&[u8], &[u8])>,
824) -> Result<(), LuaError> {
825 let mut t = t_val;
826 let mut t_idx = t_idx;
827 for _loop in 0..MAX_TAG_LOOP {
828 let tm: LuaValue;
829 if matches!(t, LuaValue::Table(_)) {
830 // C: tm = fasttm(L, h->metatable, TM_NEWINDEX)
831 let mt = state.table_metatable(&t);
832 tm = state.fast_tm_table(mt.as_ref(), TagMethod::NewIndex);
833 if matches!(tm, LuaValue::Nil) {
834 // C: luaH_finishset(L, h, key, slot, val); invalidate; barrier; return;
835 state.table_raw_set(&t, key, val.clone())?;
836 state.gc_barrier_back(&t, &val);
837 return Ok(());
838 }
839 } else {
840 // C: tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)
841 // C: if (notm(tm)) luaG_typeerror(L, t, "index");
842 tm = state.get_tm_by_obj(&t, TagMethod::NewIndex);
843 if matches!(tm, LuaValue::Nil) {
844 return Err(match (t_idx, var_hint) {
845 (Some(idx), _) => crate::debug::type_error(state, &t, idx, b"index"),
846 (None, Some((kind, name))) => {
847 crate::debug::type_error_with_hint(state, &t, b"index", kind, name)
848 }
849 (None, None) => LuaError::type_error(&t, "index"),
850 });
851 }
852 }
853 // C: if (ttisfunction(tm)) { luaT_callTM(L, tm, t, key, val); return; }
854 if matches!(tm, LuaValue::Function(_)) {
855 state.call_tm(tm, &t, &key, &val)?;
856 return Ok(());
857 }
858 // C: t = tm; luaV_fastget again
859 t = tm.clone();
860 t_idx = None;
861 if state.fast_get(&t, &key)?.is_some() {
862 // C: luaV_finishfastset(L, t, slot, val)
863 state.table_raw_set(&t, key.clone(), val.clone())?;
864 state.gc_barrier_back(&t, &val);
865 return Ok(());
866 }
867 }
868 Err(LuaError::runtime(format_args!("'__newindex' chain too long; possible loop")))
869}
870
871// ─── String comparison ───────────────────────────────────────────────────────
872
873/// C: `static int l_strcmp(const TString *ts1, const TString *ts2)`
874/// Lexicographic string comparison that handles embedded NULs by segmenting.
875/// Returns negative / zero / positive like `strcmp`.
876///
877/// PORT NOTE: C uses `strcoll` for locale-aware comparison within each NUL-free
878/// segment. Rust's standard library has no locale support, so we use
879/// `slice::cmp` (byte-by-byte lexicographic order, equivalent to `memcmp`).
880/// This means locale-specific ordering (e.g. accented characters) differs from
881/// the C reference. Mark as TODO for a later `libc::strcoll` bridge if needed.
882fn str_cmp(s1: &[u8], s2: &[u8]) -> std::cmp::Ordering {
883 // TODO(port): C uses strcoll per-segment; here we use byte-lexicographic
884 // order. This affects locale-sensitive string comparisons.
885 let mut s1 = s1;
886 let mut s2 = s2;
887 loop {
888 // Find the first NUL in each slice to delimit a segment.
889 let z1 = s1.iter().position(|&b| b == 0).unwrap_or(s1.len());
890 let z2 = s2.iter().position(|&b| b == 0).unwrap_or(s2.len());
891 // Compare segment up to first NUL using byte order (not strcoll).
892 let seg_cmp = s1[..z1].cmp(&s2[..z2]);
893 if seg_cmp != std::cmp::Ordering::Equal {
894 return seg_cmp;
895 }
896 // Both segments compare equal up to the NUL position.
897 if z2 == s2.len() {
898 // s2 is finished
899 if z1 == s1.len() {
900 return std::cmp::Ordering::Equal;
901 }
902 return std::cmp::Ordering::Greater; // s1 has more
903 }
904 if z1 == s1.len() {
905 return std::cmp::Ordering::Less; // s1 finished, s2 has more
906 }
907 // Both have NULs; advance past them.
908 s1 = &s1[z1 + 1..];
909 s2 = &s2[z2 + 1..];
910 }
911}
912
913// ─── Comparison helpers (int vs float mixed comparisons) ────────────────────
914
915/// C: `l_sinline int LTintfloat(lua_Integer i, lua_Number f)` — `i < f`
916#[inline]
917fn lt_int_float(i: i64, f: f64) -> bool {
918 // C: if (l_intfitsf(i)) return luai_numlt(cast_num(i), f);
919 if int_fits_float(i) {
920 (i as f64) < f
921 } else {
922 // C: i < f <=> i < ceil(f)
923 match flt_to_integer(f, F2Imod::Ceil) {
924 Some(fi) => i < fi,
925 None => f > 0.0, // f is out of integer range; positive means i < f
926 }
927 }
928}
929
930/// C: `l_sinline int LEintfloat(lua_Integer i, lua_Number f)` — `i <= f`
931#[inline]
932fn le_int_float(i: i64, f: f64) -> bool {
933 if int_fits_float(i) {
934 (i as f64) <= f
935 } else {
936 // C: i <= f <=> i <= floor(f)
937 match flt_to_integer(f, F2Imod::Floor) {
938 Some(fi) => i <= fi,
939 None => f > 0.0,
940 }
941 }
942}
943
944/// C: `l_sinline int LTfloatint(lua_Number f, lua_Integer i)` — `f < i`
945#[inline]
946fn lt_float_int(f: f64, i: i64) -> bool {
947 if int_fits_float(i) {
948 f < (i as f64)
949 } else {
950 // C: f < i <=> floor(f) < i
951 match flt_to_integer(f, F2Imod::Floor) {
952 Some(fi) => fi < i,
953 None => f < 0.0,
954 }
955 }
956}
957
958/// C: `l_sinline int LEfloatint(lua_Number f, lua_Integer i)` — `f <= i`
959#[inline]
960fn le_float_int(f: f64, i: i64) -> bool {
961 if int_fits_float(i) {
962 f <= (i as f64)
963 } else {
964 // C: f <= i <=> ceil(f) <= i
965 match flt_to_integer(f, F2Imod::Ceil) {
966 Some(fi) => fi <= i,
967 None => f < 0.0,
968 }
969 }
970}
971
972/// C: `l_sinline int LTnum(const TValue *l, const TValue *r)` — `l < r` for numbers.
973#[inline]
974fn lt_num(l: &LuaValue, r: &LuaValue) -> bool {
975 debug_assert!(matches!(l, LuaValue::Int(_) | LuaValue::Float(_)));
976 debug_assert!(matches!(r, LuaValue::Int(_) | LuaValue::Float(_)));
977 match (l, r) {
978 (LuaValue::Int(li), LuaValue::Int(ri)) => li < ri,
979 (LuaValue::Int(li), LuaValue::Float(rf)) => lt_int_float(*li, *rf),
980 (LuaValue::Float(lf), LuaValue::Float(rf)) => lf < rf,
981 (LuaValue::Float(lf), LuaValue::Int(ri)) => lt_float_int(*lf, *ri),
982 _ => false,
983 }
984}
985
986/// C: `l_sinline int LEnum(const TValue *l, const TValue *r)` — `l <= r` for numbers.
987#[inline]
988fn le_num(l: &LuaValue, r: &LuaValue) -> bool {
989 debug_assert!(matches!(l, LuaValue::Int(_) | LuaValue::Float(_)));
990 debug_assert!(matches!(r, LuaValue::Int(_) | LuaValue::Float(_)));
991 match (l, r) {
992 (LuaValue::Int(li), LuaValue::Int(ri)) => li <= ri,
993 (LuaValue::Int(li), LuaValue::Float(rf)) => le_int_float(*li, *rf),
994 (LuaValue::Float(lf), LuaValue::Float(rf)) => lf <= rf,
995 (LuaValue::Float(lf), LuaValue::Int(ri)) => le_float_int(*lf, *ri),
996 _ => false,
997 }
998}
999
1000/// C: `static int lessthanothers(lua_State *L, const TValue *l, const TValue *r)`
1001/// `l < r` for non-numbers (strings or metamethod).
1002fn less_than_others(state: &mut LuaState, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
1003 debug_assert!(!(matches!(l, LuaValue::Int(_) | LuaValue::Float(_))
1004 && matches!(r, LuaValue::Int(_) | LuaValue::Float(_))));
1005 match (l, r) {
1006 (LuaValue::Str(ts1), LuaValue::Str(ts2)) => {
1007 Ok(str_cmp(ts1.as_bytes(), ts2.as_bytes()) == std::cmp::Ordering::Less)
1008 }
1009 _ => state.call_order_tm(l, r, TagMethod::Lt),
1010 }
1011}
1012
1013/// C: `int luaV_lessthan(lua_State *L, const TValue *l, const TValue *r)`
1014pub(crate) fn less_than(state: &mut LuaState, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
1015 if matches!(l, LuaValue::Int(_) | LuaValue::Float(_))
1016 && matches!(r, LuaValue::Int(_) | LuaValue::Float(_))
1017 {
1018 Ok(lt_num(l, r))
1019 } else {
1020 less_than_others(state, l, r)
1021 }
1022}
1023
1024/// C: `static int lessequalothers(lua_State *L, const TValue *l, const TValue *r)`
1025fn less_equal_others(state: &mut LuaState, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
1026 match (l, r) {
1027 (LuaValue::Str(ts1), LuaValue::Str(ts2)) => {
1028 Ok(str_cmp(ts1.as_bytes(), ts2.as_bytes()) != std::cmp::Ordering::Greater)
1029 }
1030 _ => state.call_order_tm(l, r, TagMethod::Le),
1031 }
1032}
1033
1034/// C: `int luaV_lessequal(lua_State *L, const TValue *l, const TValue *r)`
1035pub(crate) fn less_equal(state: &mut LuaState, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
1036 if matches!(l, LuaValue::Int(_) | LuaValue::Float(_))
1037 && matches!(r, LuaValue::Int(_) | LuaValue::Float(_))
1038 {
1039 Ok(le_num(l, r))
1040 } else {
1041 less_equal_others(state, l, r)
1042 }
1043}
1044
1045// ─── Equality ────────────────────────────────────────────────────────────────
1046
1047/// C: `int luaV_equalobj(lua_State *L, const TValue *t1, const TValue *t2)`
1048/// Main equality test. `raw = true` means no metamethods (L == NULL in C).
1049pub(crate) fn equal_obj(
1050 state: Option<&mut LuaState>,
1051 t1: &LuaValue,
1052 t2: &LuaValue,
1053) -> Result<bool, LuaError> {
1054 // C: if (ttypetag(t1) != ttypetag(t2)) — different full type tags?
1055 // In Rust, same variant = same tag. If variant differs, check the number
1056 // special case (Int and Float can be equal).
1057 let same_variant = std::mem::discriminant(t1) == std::mem::discriminant(t2);
1058 if !same_variant {
1059 // C: if (ttype(t1) != ttype(t2) || ttype(t1) != LUA_TNUMBER) return 0;
1060 let t1_is_num = matches!(t1, LuaValue::Int(_) | LuaValue::Float(_));
1061 let t2_is_num = matches!(t2, LuaValue::Int(_) | LuaValue::Float(_));
1062 if !(t1_is_num && t2_is_num) {
1063 return Ok(false);
1064 }
1065 // C: two numbers with different variants — compare via integer conversion
1066 // luaV_tointegerns(t1, &i1, F2Ieq) && luaV_tointegerns(t2, &i2, F2Ieq) && i1==i2
1067 let i1 = to_integer_ns(t1, F2Imod::Eq);
1068 let i2 = to_integer_ns(t2, F2Imod::Eq);
1069 return Ok(i1.is_some() && i2.is_some() && i1 == i2);
1070 }
1071
1072 // C: same variant — switch on type tag
1073 match (t1, t2) {
1074 (LuaValue::Nil, LuaValue::Nil) => Ok(true),
1075 (LuaValue::Bool(b1), LuaValue::Bool(b2)) => Ok(b1 == b2),
1076 (LuaValue::Int(i1), LuaValue::Int(i2)) => Ok(i1 == i2),
1077 (LuaValue::Float(f1), LuaValue::Float(f2)) => Ok(f1 == f2),
1078 (LuaValue::LightUserData(p1), LuaValue::LightUserData(p2)) => Ok(p1 == p2),
1079 (LuaValue::Function(f1), LuaValue::Function(f2)) => {
1080 use lua_types::closure::LuaClosure;
1081 let same = match (f1, f2) {
1082 (LuaClosure::Lua(a), LuaClosure::Lua(b)) => GcRef::ptr_eq(a, b),
1083 (LuaClosure::C(a), LuaClosure::C(b)) => GcRef::ptr_eq(a, b),
1084 (LuaClosure::LightC(a), LuaClosure::LightC(b)) => a == b,
1085 _ => false,
1086 };
1087 Ok(same)
1088 }
1089 (LuaValue::Str(s1), LuaValue::Str(s2)) => {
1090 // C: eqshrstr for short strings (pointer eq after interning),
1091 // luaS_eqlngstr for long strings (content eq).
1092 // In Rust, LuaString PartialEq handles both.
1093 Ok(s1 == s2)
1094 }
1095 (LuaValue::UserData(u1), LuaValue::UserData(u2)) => {
1096 // C: if (uvalue(t1) == uvalue(t2)) return 1;
1097 // else if (L == NULL) return 0;
1098 // tm = fasttm(L, uvalue(t1)->metatable, TM_EQ);
1099 if std::ptr::eq(u1.as_ptr(), u2.as_ptr()) {
1100 return Ok(true);
1101 }
1102 let Some(state) = state else { return Ok(false); };
1103 let tm1 = state.fast_tm_ud(u1, TagMethod::Eq);
1104 let tm = if matches!(tm1, LuaValue::Nil) {
1105 state.fast_tm_ud(u2, TagMethod::Eq)
1106 } else {
1107 tm1
1108 };
1109 if matches!(tm, LuaValue::Nil) {
1110 return Ok(false);
1111 }
1112 // C: luaT_callTMres(L, tm, t1, t2, L->top.p); return !l_isfalse(s2v(L->top.p));
1113 let result = state.call_tm_res_bool(tm, t1, t2)?;
1114 Ok(result)
1115 }
1116 (LuaValue::Table(h1), LuaValue::Table(h2)) => {
1117 // C: if (hvalue(t1) == hvalue(t2)) return 1;
1118 if std::ptr::eq(h1.as_ptr(), h2.as_ptr()) {
1119 return Ok(true);
1120 }
1121 let Some(state) = state else { return Ok(false); };
1122 // C: tm = fasttm(L, hvalue(t1)->metatable, TM_EQ);
1123 // if (tm == NULL) tm = fasttm(L, hvalue(t2)->metatable, TM_EQ);
1124 let mt1 = h1.metatable();
1125 let mt2 = h2.metatable();
1126 let tm1 = state.fast_tm_table(mt1.as_ref(), TagMethod::Eq);
1127 let tm = if matches!(tm1, LuaValue::Nil) {
1128 state.fast_tm_table(mt2.as_ref(), TagMethod::Eq)
1129 } else {
1130 tm1
1131 };
1132 if matches!(tm, LuaValue::Nil) {
1133 return Ok(false);
1134 }
1135 let result = state.call_tm_res_bool(tm, t1, t2)?;
1136 Ok(result)
1137 }
1138 (LuaValue::Thread(a), LuaValue::Thread(b)) => Ok(GcRef::ptr_eq(a, b)),
1139 // C: default: return gcvalue(t1) == gcvalue(t2)
1140 _ => Ok(std::ptr::eq(t1 as *const _, t2 as *const _)),
1141 }
1142}
1143
1144// ─── Concatenation ───────────────────────────────────────────────────────────
1145
1146/// C: `static void copy2buff(StkId top, int n, char *buff)`
1147/// Copy `n` strings from `top-n .. top-1` into `buff`.
1148fn copy_to_buf(state: &LuaState, top: StackIdx, n: u32, buf: &mut Vec<u8>) {
1149 buf.clear();
1150 // C: do { TString *st = tsvalue(s2v(top - n)); ... } while (--n > 0)
1151 let mut remaining = n;
1152 loop {
1153 let idx = top - remaining as i32;
1154 let v = state.get_at(idx);
1155 if let LuaValue::Str(ts) = v {
1156 buf.extend_from_slice(ts.as_bytes());
1157 }
1158 if remaining <= 1 {
1159 break;
1160 }
1161 remaining -= 1;
1162 }
1163}
1164
1165/// C: `void luaV_concat(lua_State *L, int total)`
1166/// Concatenate `total` values on the top of the stack, leaving one result.
1167pub(crate) fn concat(state: &mut LuaState, total: i32) -> Result<(), LuaError> {
1168 if total == 1 {
1169 return Ok(()); // C: "all values already concatenated"
1170 }
1171 let mut total = total;
1172 // C: do { ... } while (total > 1)
1173 loop {
1174 let top = state.top_idx();
1175 let v_tm1 = state.get_at(top - 1); // top-1
1176 let v_tm2 = state.get_at(top - 2); // top-2
1177
1178 // C: if (!(ttisstring(s2v(top-2)) || cvt2str(s2v(top-2))) || !tostring(L, s2v(top-1)))
1179 // luaT_tryconcatTM(L);
1180 let top2_coercible = matches!(v_tm2, LuaValue::Str(_))
1181 || matches!(v_tm2, LuaValue::Int(_) | LuaValue::Float(_));
1182 // tostring converts numbers to strings; we check top-1 too
1183 let top1_stringlike = matches!(v_tm1, LuaValue::Str(_))
1184 || matches!(v_tm1, LuaValue::Int(_) | LuaValue::Float(_));
1185 if !top2_coercible || !top1_stringlike {
1186 state.try_concat_tm(&v_tm1, &v_tm2)?;
1187 // C: n stays at 2; the shared `total -= n-1; L->top.p -= n-1`
1188 // at the bottom of the do-while runs for this branch too.
1189 // The metamethod writes its single result to top-2, leaving
1190 // top-1 stale; popping that stale slot is what makes the next
1191 // iteration see the just-computed result at the new top-1.
1192 total -= 1;
1193 let top = state.top_idx();
1194 state.set_top(top - 1);
1195 if total <= 1 {
1196 break;
1197 }
1198 continue;
1199 }
1200
1201 // C: isemptystr — short string with shrlen == 0
1202 let is_empty = |v: &LuaValue| -> bool {
1203 matches!(v, LuaValue::Str(s) if s.as_bytes().is_empty())
1204 };
1205
1206 let n: u32;
1207 if is_empty(&v_tm1) {
1208 // C: result is top-2 (tostring it if needed); consumed 2 inputs → 1 result
1209 state.coerce_to_string(top - 2)?;
1210 n = 2;
1211 } else if is_empty(&v_tm2) {
1212 // C: tostring(L, s2v(top-1)) ran as part of the entry condition,
1213 // so top-1 is guaranteed to be a string here. We replicate that
1214 // conversion before the copy so numbers don't leak through.
1215 state.coerce_to_string(top - 1)?;
1216 let v = state.get_at(top - 1);
1217 state.set_at(top - 2, v);
1218 n = 2;
1219 } else {
1220 // C: collect as many consecutive string/number values as possible
1221 // Ensure top-1 is a string (coerce if number)
1222 state.coerce_to_string(top - 1)?;
1223 let s1 = match state.get_at(top - 1) {
1224 LuaValue::Str(ts) => ts.as_bytes().len(),
1225 _ => 0,
1226 };
1227 let mut total_len = s1;
1228 let mut count: u32 = 1;
1229 // C: for (n = 1; n < total && tostring(L, s2v(top - n - 1)); n++)
1230 let top = state.top_idx();
1231 loop {
1232 if count as i32 >= total {
1233 break;
1234 }
1235 let idx = top - (count as i32 + 1);
1236 let v = state.get_at(idx);
1237 if !matches!(v, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_)) {
1238 break;
1239 }
1240 state.coerce_to_string(idx)?;
1241 let l = match state.get_at(idx) {
1242 LuaValue::Str(ts) => ts.as_bytes().len(),
1243 _ => 0,
1244 };
1245 // C: if (l >= MAX_SIZE - sizeof(TString) - tl) luaG_runerror
1246 if l >= usize::MAX - total_len {
1247 // pop strings to avoid wasting stack
1248 state.set_top(top - total as i32);
1249 return Err(LuaError::runtime(format_args!("string length overflow")));
1250 }
1251 total_len += l;
1252 count += 1;
1253 }
1254 n = count;
1255
1256 // Build concatenated result
1257 // C: if (tl <= LUAI_MAXSHORTLEN) short string; else luaS_createlngstrobj
1258 let mut buf: Vec<u8> = Vec::with_capacity(total_len);
1259 let top = state.top_idx();
1260 copy_to_buf(state, top, n, &mut buf);
1261 let ts = state.intern_or_create_str(&buf)?;
1262 state.set_at(top - n as i32, LuaValue::Str(ts));
1263 }
1264 // C: total -= n - 1; L->top.p -= n - 1;
1265 total -= n as i32 - 1;
1266 let top = state.top_idx();
1267 state.set_top(top - ((n - 1) as i32));
1268
1269 if total <= 1 {
1270 break;
1271 }
1272 }
1273 Ok(())
1274}
1275
1276// ─── Object length ───────────────────────────────────────────────────────────
1277
1278/// C: `void luaV_objlen(lua_State *L, StkId ra, const TValue *rb)`
1279/// Main implementation of the `#` operator.
1280pub(crate) fn obj_len(state: &mut LuaState, ra: StackIdx, rb: LuaValue) -> Result<(), LuaError> {
1281 // C: switch (ttypetag(rb))
1282 match &rb {
1283 LuaValue::Table(_) => {
1284 // C: tm = fasttm(L, h->metatable, TM_LEN)
1285 // if (tm) break; else setivalue(s2v(ra), luaH_getn(h));
1286 let mt = state.table_metatable(&rb);
1287 let tm = state.fast_tm_table(mt.as_ref(), TagMethod::Len);
1288 if matches!(tm, LuaValue::Nil) {
1289 let n = state.table_length(&rb)?;
1290 state.set_at(ra, LuaValue::Int(n as i64));
1291 return Ok(());
1292 }
1293 // Fall through to call metamethod
1294 state.call_tm_res(tm, &rb, &rb, ra)?;
1295 }
1296 LuaValue::Str(ts) => {
1297 // C: case LUA_VSHRSTR: setivalue(s2v(ra), tsvalue(rb)->shrlen);
1298 // case LUA_VLNGSTR: setivalue(s2v(ra), tsvalue(rb)->u.lnglen);
1299 // Unified in Rust — just get length
1300 let n = ts.len();
1301 state.set_at(ra, LuaValue::Int(n as i64));
1302 }
1303 other => {
1304 // C: default: tm = luaT_gettmbyobj(L, rb, TM_LEN)
1305 // if (notm(tm)) luaG_typeerror(L, rb, "get length of");
1306 let tm = state.get_tm_by_obj(other, TagMethod::Len);
1307 if matches!(tm, LuaValue::Nil) {
1308 return Err(LuaError::type_error(other, "get length of"));
1309 }
1310 state.call_tm_res(tm, &rb, &rb, ra)?;
1311 }
1312 }
1313 Ok(())
1314}
1315
1316// ─── Integer arithmetic ──────────────────────────────────────────────────────
1317
1318/// C: `lua_Integer luaV_idiv(lua_State *L, lua_Integer m, lua_Integer n)`
1319/// Integer floor-division.
1320pub(crate) fn idiv(m: i64, n: i64) -> Result<i64, LuaError> {
1321 // C: if (l_unlikely(l_castS2U(n) + 1u <= 1u)) — handles n==0 and n==-1
1322 if (n as u64).wrapping_add(1) <= 1 {
1323 if n == 0 {
1324 return Err(LuaError::runtime(format_args!("attempt to divide by zero")));
1325 }
1326 // C: n == -1; avoid overflow with 0x80000...// -1 → intop(-, 0, m)
1327 return Ok(intop_sub(0, m));
1328 }
1329 // C: q = m / n; if ((m ^ n) < 0 && m % n != 0) q -= 1;
1330 let q = m / n;
1331 // Correct toward floor (C division truncates toward zero)
1332 if (m ^ n) < 0 && m % n != 0 {
1333 Ok(q - 1)
1334 } else {
1335 Ok(q)
1336 }
1337}
1338
1339/// C: `lua_Integer luaV_mod(lua_State *L, lua_Integer m, lua_Integer n)`
1340/// Integer modulus (Lua semantics: same sign as divisor).
1341pub(crate) fn imod(m: i64, n: i64) -> Result<i64, LuaError> {
1342 if (n as u64).wrapping_add(1) <= 1 {
1343 if n == 0 {
1344 return Err(LuaError::runtime(format_args!("attempt to perform 'n%0'")));
1345 }
1346 // C: m % -1 == 0; avoid overflow
1347 return Ok(0);
1348 }
1349 let r = m % n;
1350 // C: if (r != 0 && (r ^ n) < 0) r += n
1351 if r != 0 && (r ^ n) < 0 {
1352 Ok(r + n)
1353 } else {
1354 Ok(r)
1355 }
1356}
1357
1358/// C: `lua_Number luaV_modf(lua_State *L, lua_Number m, lua_Number n)`
1359/// Float modulus (Lua semantics).
1360pub(crate) fn fmodf(m: f64, n: f64) -> f64 {
1361 let r = m % n;
1362 let opposite_signs = if r > 0.0 { n < 0.0 } else { r < 0.0 && n > 0.0 };
1363 if opposite_signs {
1364 r + n
1365 } else {
1366 r
1367 }
1368}
1369
1370/// Phase-B helper: map a u8 raw value to a `TagMethod`. Mirrors C's
1371/// `cast(TMS, x)` direct cast; out-of-range returns `TagMethod::Index`.
1372pub(crate) fn tagmethod_from_index(i: usize) -> TagMethod {
1373 use TagMethod::*;
1374 match i {
1375 0 => Index, 1 => NewIndex, 2 => Gc, 3 => Mode, 4 => Len, 5 => Eq,
1376 6 => Add, 7 => Sub, 8 => Mul, 9 => Mod, 10 => Pow, 11 => Div,
1377 12 => Idiv, 13 => Band, 14 => Bor, 15 => Bxor, 16 => Shl, 17 => Shr,
1378 18 => Unm, 19 => Bnot, 20 => Lt, 21 => Le, 22 => Concat, 23 => Call,
1379 24 => Close,
1380 _ => Index,
1381 }
1382}
1383
1384/// C: `lua_Integer luaV_mod(lua_State *L, lua_Integer m, lua_Integer n)`
1385/// Integer floor-mod: Lua's `%` operator on integers. Result has the same sign
1386/// as the divisor. Raises on `n == 0`.
1387pub(crate) fn int_floor_mod(_state: &mut LuaState, a: i64, b: i64) -> Result<i64, LuaError> {
1388 imod(a, b)
1389}
1390
1391/// C: `lua_Integer luaV_idiv(lua_State *L, lua_Integer m, lua_Integer n)`
1392/// Integer floor-div: Lua's `//` operator on integers. Truncates toward
1393/// negative infinity. Raises on `n == 0`.
1394pub(crate) fn int_floor_div(_state: &mut LuaState, a: i64, b: i64) -> Result<i64, LuaError> {
1395 idiv(a, b)
1396}
1397
1398/// C: `lua_Number luaV_modf(lua_State *L, lua_Number m, lua_Number n)`
1399/// Float floor-mod: Lua's `%` operator on floats. Result has the same sign as
1400/// the divisor. NaN / division-by-zero behavior mirrors C `fmod`.
1401pub(crate) fn float_floor_mod(_state: &mut LuaState, a: f64, b: f64) -> Result<f64, LuaError> {
1402 Ok(fmodf(a, b))
1403}
1404
1405/// C: `lua_Integer luaV_shiftl(lua_Integer x, lua_Integer y)`
1406/// Left shift; right shift is shift-left by negated count.
1407pub(crate) fn shiftl(x: i64, y: i64) -> i64 {
1408 if y < 0 {
1409 // C: shift right (luaV_shiftr via intop negation)
1410 if y <= -(NBITS as i64) {
1411 0
1412 } else {
1413 intop_shr(x, (-y) as u32)
1414 }
1415 } else {
1416 // C: shift left
1417 if y >= NBITS as i64 {
1418 0
1419 } else {
1420 intop_shl(x, y as u32)
1421 }
1422 }
1423}
1424
1425// ─── Closure creation ────────────────────────────────────────────────────────
1426
1427/// C: `static void pushclosure(lua_State *L, Proto *p, UpVal **encup,
1428/// StkId base, StkId ra)`
1429/// Create a new Lua closure from prototype `p`, initialise its upvalues,
1430/// and push it onto the stack at `ra`.
1431fn push_closure(
1432 state: &mut LuaState,
1433 proto_idx: usize, // index into current closure's proto.p[]
1434 ci: CallInfoIdx,
1435 base: StackIdx,
1436 ra: StackIdx,
1437) -> Result<(), LuaError> {
1438 // C: int nup = p->sizeupvalues; Upvaldesc *uv = p->upvalues;
1439 // C: LClosure *ncl = luaF_newLclosure(L, nup); ncl->p = p;
1440 // C: setclLvalue2s(L, ra, ncl);
1441 // C: for (i = 0; i < nup; i++) { ... }
1442 // TODO(port): pushclosure needs access to the enclosing closure's upvals and
1443 // the child proto from the current frame. This stub forwards to a LuaState
1444 // method that has the required context.
1445 state.push_closure(proto_idx, ci, base, ra)
1446}
1447
1448// ─── Yield recovery ──────────────────────────────────────────────────────────
1449
1450/// C: `void luaV_finishOp(lua_State *L)`
1451/// Resume the opcode that was interrupted by a yield.
1452/// Called when a coroutine is resumed after yielding mid-instruction.
1453pub(crate) fn finish_op(state: &mut LuaState) -> Result<(), LuaError> {
1454 // C: CallInfo *ci = L->ci;
1455 // StkId base = ci->func.p + 1;
1456 // Instruction inst = *(ci->u.l.savedpc - 1);
1457 // OpCode op = GET_OPCODE(inst);
1458 let ci = state.current_ci_idx();
1459 let base = state.ci_base(ci);
1460 let inst = state.ci_prev_instruction(ci);
1461 let op = inst.opcode();
1462
1463 match op {
1464 // C: case OP_MMBIN: case OP_MMBINI: case OP_MMBINK:
1465 // setobjs2s(L, base + GETARG_A(*(ci->u.l.savedpc - 2)), --L->top.p);
1466 OpCode::MmBin | OpCode::MmBinI | OpCode::MmBinK => {
1467 let prev_inst = state.ci_prev2_instruction(ci);
1468 let a = prev_inst.arg_a();
1469 state.dec_top();
1470 let top = state.top_idx();
1471 let v = state.get_at(top);
1472 state.set_at(base + a, v);
1473 }
1474 // C: case OP_UNM: ... case OP_SELF:
1475 // setobjs2s(L, base + GETARG_A(inst), --L->top.p);
1476 OpCode::Unm | OpCode::BNot | OpCode::Len
1477 | OpCode::GetTabUp | OpCode::GetTable | OpCode::GetI
1478 | OpCode::GetField | OpCode::Self_ => {
1479 let a = inst.arg_a();
1480 state.dec_top();
1481 let top = state.top_idx();
1482 let v = state.get_at(top);
1483 state.set_at(base + a, v);
1484 }
1485 // C: case OP_LT: case OP_LE: case OP_LTI: case OP_LEI:
1486 // case OP_GTI: case OP_GEI: case OP_EQ:
1487 // int res = !l_isfalse(s2v(L->top.p - 1)); L->top.p--;
1488 // if (res != GETARG_k(inst)) ci->u.l.savedpc++;
1489 OpCode::Lt | OpCode::Le | OpCode::LtI | OpCode::LeI
1490 | OpCode::GtI | OpCode::GeI | OpCode::Eq => {
1491 let top_minus1 = state.top_idx() - 1;
1492 let v = state.get_at(top_minus1);
1493 let res = !matches!(v, LuaValue::Nil | LuaValue::Bool(false));
1494 state.dec_top();
1495 // C: if (res != GETARG_k(inst)) ci->u.l.savedpc++;
1496 if (res as i32) != inst.arg_k() {
1497 state.ci_skip_next_instruction(ci);
1498 }
1499 // Note: CIST_LEQ compatibility not supported (LUA_COMPAT_LT_LE dropped)
1500 }
1501 // C: case OP_CONCAT:
1502 // StkId top = L->top.p - 1;
1503 // int a = GETARG_A(inst);
1504 // int total = cast_int(top - 1 - (base + a));
1505 // setobjs2s(L, top - 2, top); L->top.p = top - 1;
1506 // luaV_concat(L, total);
1507 OpCode::Concat => {
1508 let top = state.top_idx() - 1; // top when luaT_tryconcatTM was called
1509 let a = inst.arg_a();
1510 let total_concat = (top - 1 - (base + a)) as i32;
1511 // C: setobjs2s(L, top - 2, top) — put TM result in proper position
1512 let v = state.get_at(top);
1513 state.set_at(top - 2, v);
1514 // C: L->top.p = top - 1
1515 state.set_top(top - 1);
1516 concat(state, total_concat)?;
1517 }
1518 // C: case OP_CLOSE: ci->u.l.savedpc--; (repeat to close other vars)
1519 OpCode::Close => {
1520 state.ci_step_pc_back(ci);
1521 }
1522 // C: case OP_RETURN:
1523 // StkId ra = base + GETARG_A(inst);
1524 // L->top.p = ra + ci->u2.nres;
1525 // ci->u.l.savedpc--;
1526 OpCode::Return => {
1527 let a = inst.arg_a();
1528 let ra = base + a;
1529 let nres = state.ci_nres(ci);
1530 state.set_top(ra + nres);
1531 state.ci_step_pc_back(ci);
1532 }
1533 other => {
1534 // C: only those other opcodes can yield
1535 debug_assert!(
1536 matches!(
1537 other,
1538 OpCode::TForCall | OpCode::Call | OpCode::TailCall
1539 | OpCode::SetTabUp | OpCode::SetTable | OpCode::SetI | OpCode::SetField
1540 ),
1541 "unexpected opcode in finish_op: {:?}",
1542 other
1543 );
1544 }
1545 }
1546 Ok(())
1547}
1548
1549// ─── Main interpreter loop ───────────────────────────────────────────────────
1550
1551/// C: `void luaV_execute(lua_State *L, CallInfo *ci)`
1552/// Main Lua bytecode interpreter loop.
1553///
1554/// # Control flow modelling
1555/// The C function uses goto labels: `startfunc`, `returning`, `ret`,
1556/// `l_tforcall`, `l_tforloop`. These are modelled as follows:
1557/// - `'startfunc: loop { ... }` — outer loop; `continue 'startfunc` = goto startfunc
1558/// - `'returning: loop { ... }` — inner loop; `continue 'returning` = goto returning
1559/// - `break 'dispatch` from the inner dispatch loop → runs `ret:` logic
1560/// - `l_tforcall` / `l_tforloop` — inlined at TFORPREP / TFORCALL handlers
1561pub(crate) fn execute(state: &mut LuaState, mut ci: CallInfoIdx) -> Result<(), LuaError> {
1562 let mut trap: bool;
1563
1564 // PORT NOTE: `startfunc:` is the entry point that (re)sets `trap`.
1565 'startfunc: loop {
1566 // C: startfunc: trap = L->hookmask;
1567 trap = state.hook_mask() != 0;
1568
1569 // PORT NOTE: `returning:` is the re-entry after a Lua call returns.
1570 // Re-enters 'returning without resetting trap.
1571 'returning: loop {
1572 // C: cl = ci_func(ci); k = cl->p->k; pc = ci->u.l.savedpc;
1573 let cl = match state.ci_lua_closure(ci) {
1574 Some(c) => c,
1575 None => {
1576 return Err(LuaError::runtime(format_args!(
1577 "internal: execute called on non-Lua frame"
1578 )));
1579 }
1580 };
1581 // pc is an index into proto.code (u32)
1582 let mut pc: u32 = state.ci_savedpc(ci);
1583
1584 // C: if (l_unlikely(trap)) trap = luaG_tracecall(L);
1585 if trap {
1586 trap = state.trace_call(ci)?;
1587 }
1588 // C: base = ci->func.p + 1;
1589 let mut base: StackIdx = state.ci_base(ci);
1590
1591 // ── Main dispatch loop ──────────────────────────────────────────
1592 // C: for (;;) { Instruction i; vmfetch(); vmdispatch(GET_OPCODE(i)) {...} }
1593 'dispatch: loop {
1594 // C: vmfetch() — handle hooks, then fetch+advance pc
1595 if trap {
1596 // C: trap = luaG_traceexec(L, pc); updatebase(ci);
1597 trap = state.trace_exec(ci, pc)?;
1598 base = state.ci_base(ci); // updatebase
1599 }
1600 let i: Instruction = state.proto_code(&cl, pc);
1601 pc += 1;
1602 let op = i.opcode();
1603
1604 debug_assert!(base == state.ci_base(ci));
1605
1606 // C: `lua_assert(isIT(i) || (cast_void(L->top.p = base), 1));`
1607 // In normal C-Lua builds, `lua_assert` compiles away; keep the
1608 // stack-top invalidation only for debug parity so release
1609 // dispatch avoids an opcode-mode lookup and a `top` write.
1610 #[cfg(debug_assertions)]
1611 {
1612 let op_mode = op_mode_byte(op);
1613 if (op_mode & (1 << 5)) == 0 || i.arg_b() != 0 {
1614 state.set_top(base);
1615 }
1616 }
1617
1618 // C: vmdispatch(GET_OPCODE(i))
1619 match op {
1620 // ── OP_MOVE ──────────────────────────────────────────────
1621 // C: StkId ra = RA(i); setobjs2s(L, ra, RB(i));
1622 OpCode::Move => {
1623 let ra = base + i.arg_a();
1624 let rb = base + i.arg_b();
1625 let v = state.get_at(rb);
1626 state.set_at(ra, v);
1627 }
1628 // ── OP_LOADI ─────────────────────────────────────────────
1629 // C: lua_Integer b = GETARG_sBx(i); setivalue(s2v(ra), b);
1630 OpCode::LoadI => {
1631 let ra = base + i.arg_a();
1632 let b = i.arg_s_bx() as i64;
1633 state.set_at(ra, LuaValue::Int(b));
1634 }
1635 // ── OP_LOADF ─────────────────────────────────────────────
1636 // C: int b = GETARG_sBx(i); setfltvalue(s2v(ra), cast_num(b));
1637 OpCode::LoadF => {
1638 let ra = base + i.arg_a();
1639 let b = i.arg_s_bx() as f64;
1640 state.set_at(ra, LuaValue::Float(b));
1641 }
1642 // ── OP_LOADK ─────────────────────────────────────────────
1643 // C: TValue *rb = k + GETARG_Bx(i); setobj2s(L, ra, rb);
1644 OpCode::LoadK => {
1645 let ra = base + i.arg_a();
1646 let k_idx = i.arg_bx() as usize;
1647 let v = state.proto_const(&cl, k_idx).clone();
1648 state.set_at(ra, v);
1649 }
1650 // ── OP_LOADKX ────────────────────────────────────────────
1651 // C: rb = k + GETARG_Ax(*pc); pc++;
1652 OpCode::LoadKX => {
1653 let ra = base + i.arg_a();
1654 let extra = state.proto_code(&cl, pc);
1655 pc += 1;
1656 let k_idx = extra.arg_ax() as usize;
1657 let v = state.proto_const(&cl, k_idx).clone();
1658 state.set_at(ra, v);
1659 }
1660 // ── OP_LOADFALSE ─────────────────────────────────────────
1661 OpCode::LoadFalse => {
1662 let ra = base + i.arg_a();
1663 state.set_at(ra, LuaValue::Bool(false));
1664 }
1665 // ── OP_LFALSESKIP ────────────────────────────────────────
1666 // C: setbfvalue(s2v(ra)); pc++; (skip next instruction)
1667 OpCode::LFalseSkip => {
1668 let ra = base + i.arg_a();
1669 state.set_at(ra, LuaValue::Bool(false));
1670 pc += 1;
1671 }
1672 // ── OP_LOADTRUE ──────────────────────────────────────────
1673 OpCode::LoadTrue => {
1674 let ra = base + i.arg_a();
1675 state.set_at(ra, LuaValue::Bool(true));
1676 }
1677 // ── OP_LOADNIL ───────────────────────────────────────────
1678 // C: int b = GETARG_B(i); do { setnilvalue(s2v(ra++)); } while (b--);
1679 OpCode::LoadNil => {
1680 let ra = base + i.arg_a();
1681 let b = i.arg_b();
1682 for k in 0..=b {
1683 state.set_at(ra + k, LuaValue::Nil);
1684 }
1685 }
1686 // ── OP_GETUPVAL ──────────────────────────────────────────
1687 // C: setobj2s(L, ra, cl->upvals[b]->v.p);
1688 OpCode::GetUpVal => {
1689 let ra = base + i.arg_a();
1690 let b = i.arg_b() as usize;
1691 let v = state.upvalue_get(&cl, b);
1692 state.set_at(ra, v);
1693 }
1694 // ── OP_SETUPVAL ──────────────────────────────────────────
1695 // C: UpVal *uv = cl->upvals[GETARG_B(i)];
1696 // setobj(L, uv->v.p, s2v(ra)); luaC_barrier(L, uv, s2v(ra));
1697 OpCode::SetUpVal => {
1698 let ra = base + i.arg_a();
1699 let b = i.arg_b() as usize;
1700 let v = state.stack[ra.0 as usize].val;
1701 let uv = cl.upval(b);
1702 match uv.try_open_payload() {
1703 Some((thread_id, idx)) if thread_id as u64 == state.cached_thread_id => {
1704 state.stack[idx.0 as usize].val = v;
1705 }
1706 _ => {
1707 state.upvalue_set(&cl, b, v)?;
1708 }
1709 }
1710 }
1711 // ── OP_GETTABUP ──────────────────────────────────────────
1712 // C: upval = cl->upvals[B]->v.p; rc = KC(i) (short string key)
1713 // if (luaV_fastget(..., luaH_getshortstr)) setobj2s(L, ra, slot)
1714 // else Protect(luaV_finishget(...))
1715 OpCode::GetTabUp => {
1716 let ra = base + i.arg_a();
1717 let b = i.arg_b() as usize;
1718 let k_idx = i.arg_c() as usize;
1719 let upval = state.upvalue_get(&cl, b);
1720 let key = state.proto_const(&cl, k_idx).clone();
1721 match state.fast_get_short_str(&upval, &key)? {
1722 Some(v) => state.set_at(ra, v),
1723 None => {
1724 // C: Protect(luaV_finishget(...))
1725 state.set_ci_savedpc(ci, pc);
1726 state.set_top(state.ci_top(ci));
1727 finish_get(state, upval, key, ra, true, None)?;
1728 trap = state.ci_trap(ci);
1729 }
1730 }
1731 }
1732 // ── OP_GETTABLE ──────────────────────────────────────────
1733 // C: rb = vRB(i); rc = vRC(i);
1734 // if (integer key) fastgeti else fastget
1735 OpCode::GetTable => {
1736 let ra = base + i.arg_a();
1737 let rb_idx = base + i.arg_b();
1738 let rb_v = state.get_at(rb_idx);
1739 let rc_v = state.get_at(base + i.arg_c());
1740 let fast_result = if let LuaValue::Int(n) = &rc_v {
1741 state.fast_get_int(&rb_v, *n)?
1742 } else {
1743 state.fast_get(&rb_v, &rc_v)?
1744 };
1745 match fast_result {
1746 Some(v) => state.set_at(ra, v),
1747 None => {
1748 state.set_ci_savedpc(ci, pc);
1749 state.set_top(state.ci_top(ci));
1750 finish_get(state, rb_v, rc_v, ra, true, Some(rb_idx))?;
1751 trap = state.ci_trap(ci);
1752 }
1753 }
1754 }
1755 // ── OP_GETI ──────────────────────────────────────────────
1756 // C: rb = vRB(i); c = GETARG_C(i);
1757 // if (luaV_fastgeti(L, rb, c, slot)) setobj2s(L, ra, slot)
1758 // else { TValue key; setivalue(&key, c); Protect(finishget) }
1759 OpCode::GetI => {
1760 let ra = base + i.arg_a();
1761 let rb_idx = base + i.arg_b();
1762 let rb_v = state.get_at(rb_idx);
1763 let c = i.arg_c() as i64;
1764 match state.fast_get_int(&rb_v, c)? {
1765 Some(v) => state.set_at(ra, v),
1766 None => {
1767 let key = LuaValue::Int(c);
1768 state.set_ci_savedpc(ci, pc);
1769 state.set_top(state.ci_top(ci));
1770 finish_get(state, rb_v, key, ra, true, Some(rb_idx))?;
1771 trap = state.ci_trap(ci);
1772 }
1773 }
1774 }
1775 // ── OP_GETFIELD ──────────────────────────────────────────
1776 // C: rb = vRB(i); rc = KC(i) (short string key)
1777 OpCode::GetField => {
1778 let ra = base + i.arg_a();
1779 let rb_idx = base + i.arg_b();
1780 let rb_v = state.get_at(rb_idx);
1781 let k_idx = i.arg_c() as usize;
1782 let key = state.proto_const(&cl, k_idx).clone();
1783 match state.fast_get_short_str(&rb_v, &key)? {
1784 Some(v) => state.set_at(ra, v),
1785 None => {
1786 state.set_ci_savedpc(ci, pc);
1787 state.set_top(state.ci_top(ci));
1788 finish_get(state, rb_v, key, ra, true, Some(rb_idx))?;
1789 trap = state.ci_trap(ci);
1790 }
1791 }
1792 }
1793 // ── OP_SETTABUP ──────────────────────────────────────────
1794 // C: upval = cl->upvals[A]->v.p; rb = KB(i) key; rc = RKC(i) val
1795 OpCode::SetTabUp => {
1796 let a = i.arg_a() as usize;
1797 let b_idx = i.arg_b() as usize; // key is KB(i)
1798 let rc_v = if i.test_k() {
1799 state.proto_const(&cl, i.arg_c() as usize).clone()
1800 } else {
1801 state.get_at(base + i.arg_c())
1802 };
1803 let upval = state.upvalue_get(&cl, a);
1804 let key = state.proto_const(&cl, b_idx).clone();
1805 match state.fast_get_short_str(&upval, &key)? {
1806 Some(_slot) => {
1807 // C: luaV_finishfastset(L, upval, slot, rc)
1808 state.table_raw_set(&upval, key, rc_v.clone())?;
1809 state.gc_barrier_back(&upval, &rc_v);
1810 }
1811 None => {
1812 state.set_ci_savedpc(ci, pc);
1813 state.set_top(state.ci_top(ci));
1814 let upval_name: Vec<u8> = cl
1815 .proto
1816 .upvalues
1817 .get(a)
1818 .and_then(|uv| uv.name.as_ref())
1819 .map(|s| s.as_bytes().to_vec())
1820 .unwrap_or_else(|| b"?".to_vec());
1821 let hint: Option<(&[u8], &[u8])> =
1822 Some((b"upvalue", &upval_name));
1823 finish_set(state, upval, key, rc_v, false, None, hint)?;
1824 trap = state.ci_trap(ci);
1825 }
1826 }
1827 }
1828 // ── OP_SETTABLE ───────────────────────────────────────────
1829 // C: ra = RA(i) (table); rb = vRB(i) key; rc = RKC(i) val
1830 OpCode::SetTable => {
1831 let ra_idx = base + i.arg_a();
1832 let ra_v = state.get_at(ra_idx);
1833 let rb_v = state.get_at(base + i.arg_b());
1834 let rc_v = if i.test_k() {
1835 state.proto_const(&cl, i.arg_c() as usize).clone()
1836 } else {
1837 state.get_at(base + i.arg_c())
1838 };
1839 let fast = if let LuaValue::Int(n) = &rb_v {
1840 state.fast_get_int(&ra_v, *n)?
1841 } else {
1842 state.fast_get(&ra_v, &rb_v)?
1843 };
1844 if fast.is_some() {
1845 state.table_raw_set(&ra_v, rb_v, rc_v.clone())?;
1846 state.gc_barrier_back(&ra_v, &rc_v);
1847 } else {
1848 state.set_ci_savedpc(ci, pc);
1849 state.set_top(state.ci_top(ci));
1850 finish_set(state, ra_v, rb_v, rc_v, false, Some(ra_idx), None)?;
1851 trap = state.ci_trap(ci);
1852 }
1853 }
1854 // ── OP_SETI ───────────────────────────────────────────────
1855 // C: ra = RA(i) (table); c = GETARG_B(i) (int key); rc = RKC(i)
1856 OpCode::SetI => {
1857 let ra_idx = base + i.arg_a();
1858 let ra_v = state.get_at(ra_idx);
1859 let c = i.arg_b() as i64;
1860 let rc_v = if i.test_k() {
1861 state.proto_const(&cl, i.arg_c() as usize).clone()
1862 } else {
1863 state.get_at(base + i.arg_c())
1864 };
1865 let fast = state.fast_get_int(&ra_v, c)?;
1866 if fast.is_some() {
1867 state.table_raw_set(&ra_v, LuaValue::Int(c), rc_v.clone())?;
1868 state.gc_barrier_back(&ra_v, &rc_v);
1869 } else {
1870 state.set_ci_savedpc(ci, pc);
1871 state.set_top(state.ci_top(ci));
1872 finish_set(state, ra_v, LuaValue::Int(c), rc_v, false, Some(ra_idx), None)?;
1873 trap = state.ci_trap(ci);
1874 }
1875 }
1876 // ── OP_SETFIELD ───────────────────────────────────────────
1877 // C: ra = RA(i) table; rb = KB(i) key; rc = RKC(i) val
1878 OpCode::SetField => {
1879 let ra_idx = base + i.arg_a();
1880 let ra_v = state.get_at(ra_idx);
1881 let b_idx = i.arg_b() as usize;
1882 let key = state.proto_const(&cl, b_idx).clone();
1883 let rc_v = if i.test_k() {
1884 state.proto_const(&cl, i.arg_c() as usize).clone()
1885 } else {
1886 state.get_at(base + i.arg_c())
1887 };
1888 match state.fast_get_short_str(&ra_v, &key)? {
1889 Some(_) => {
1890 state.table_raw_set(&ra_v, key, rc_v.clone())?;
1891 state.gc_barrier_back(&ra_v, &rc_v);
1892 }
1893 None => {
1894 state.set_ci_savedpc(ci, pc);
1895 state.set_top(state.ci_top(ci));
1896 finish_set(state, ra_v, key, rc_v, false, Some(ra_idx), None)?;
1897 trap = state.ci_trap(ci);
1898 }
1899 }
1900 }
1901 // ── OP_NEWTABLE ───────────────────────────────────────────
1902 // C: b = log2(hash size)+1; c = array size
1903 // if (TESTARG_k(i)) c += GETARG_Ax(*pc) * (MAXARG_C + 1); pc++;
1904 OpCode::NewTable => {
1905 let ra = base + i.arg_a();
1906 let mut b = i.arg_b();
1907 let mut c = i.arg_c();
1908 if b > 0 {
1909 b = 1 << (b - 1); // C: b = 1 << (b - 1)
1910 }
1911 if i.test_k() {
1912 let extra = state.proto_code(&cl, pc);
1913 pc += 1;
1914 // C: c += GETARG_Ax(*pc) * (MAXARG_C + 1)
1915 const MAXARG_C: i32 = (1 << 8) - 1;
1916 c += extra.arg_ax() * (MAXARG_C + 1);
1917 } else {
1918 pc += 1; // skip extra argument even if zero
1919 }
1920 // C: L->top.p = ra + 1; (for emergency GC)
1921 state.set_top(ra + 1);
1922 let t = state.new_table();
1923 state.set_at(ra, LuaValue::Table(t.clone()));
1924 if b != 0 || c != 0 {
1925 state.table_resize(&t, c as usize, b as usize)?;
1926 }
1927 // C: checkGC(L, ra + 1)
1928 state.set_ci_savedpc(ci, pc);
1929 state.set_top(ra + 1);
1930 state.gc_cond_step();
1931 trap = state.ci_trap(ci);
1932 }
1933 // ── OP_SELF ───────────────────────────────────────────────
1934 // C: ra+1 = rb; if fastget(rb, key) ra=slot else finishget
1935 OpCode::Self_ => {
1936 let ra = base + i.arg_a();
1937 let rb_idx = base + i.arg_b();
1938 let rb_v = state.get_at(rb_idx);
1939 let k_idx = i.arg_c() as usize; // RKC key (always a string)
1940 let key = if i.test_k() {
1941 state.proto_const(&cl, k_idx).clone()
1942 } else {
1943 state.get_at(base + i.arg_c())
1944 };
1945 // C: setobj2s(L, ra+1, rb)
1946 state.set_at(ra + 1, rb_v.clone());
1947 match state.fast_get_short_str(&rb_v, &key)? {
1948 Some(v) => state.set_at(ra, v),
1949 None => {
1950 state.set_ci_savedpc(ci, pc);
1951 state.set_top(state.ci_top(ci));
1952 finish_get(state, rb_v, key, ra, true, Some(rb_idx))?;
1953 trap = state.ci_trap(ci);
1954 }
1955 }
1956 }
1957 // ── Arithmetic immediates ──────────────────────────────────
1958 // C: op_arithI(L, l_addi, luai_numadd)
1959 OpCode::AddI => {
1960 let ra = base + i.arg_a();
1961 let rb = base + i.arg_b();
1962 let imm = i.arg_s_c() as i64;
1963 if let Some(iv1) = state.get_int_at(rb) {
1964 pc += 1;
1965 state.set_at(ra, LuaValue::Int(intop_add(iv1, imm)));
1966 } else if let Some(nb) = state.get_float_at(rb) {
1967 pc += 1;
1968 state.set_at(ra, LuaValue::Float(nb + imm as f64));
1969 }
1970 }
1971 // ── Arithmetic with K constant operand ─────────────────────
1972 // C: op_arithK(L, l_addi, luai_numadd)
1973 OpCode::AddK => {
1974 let ra = base + i.arg_a();
1975 let rb = base + i.arg_b();
1976 let kidx = i.arg_c() as usize;
1977 if let (Some(i1), Some(i2)) = (state.get_int_at(rb), state.proto_const_int(&cl, kidx)) {
1978 pc += 1;
1979 state.set_at(ra, LuaValue::Int(intop_add(i1, i2)));
1980 } else if let (Some(n1), Some(n2)) = (state.get_num_at(rb), state.proto_const_num(&cl, kidx)) {
1981 pc += 1;
1982 state.set_at(ra, LuaValue::Float(n1 + n2));
1983 }
1984 }
1985 OpCode::SubK => {
1986 let ra = base + i.arg_a();
1987 let rb = base + i.arg_b();
1988 let kidx = i.arg_c() as usize;
1989 if let (Some(i1), Some(i2)) = (state.get_int_at(rb), state.proto_const_int(&cl, kidx)) {
1990 pc += 1;
1991 state.set_at(ra, LuaValue::Int(intop_sub(i1, i2)));
1992 } else if let (Some(n1), Some(n2)) = (state.get_num_at(rb), state.proto_const_num(&cl, kidx)) {
1993 pc += 1;
1994 state.set_at(ra, LuaValue::Float(n1 - n2));
1995 }
1996 }
1997 OpCode::MulK => {
1998 let ra = base + i.arg_a();
1999 let rb = base + i.arg_b();
2000 let kidx = i.arg_c() as usize;
2001 if let (Some(i1), Some(i2)) = (state.get_int_at(rb), state.proto_const_int(&cl, kidx)) {
2002 pc += 1;
2003 state.set_at(ra, LuaValue::Int(intop_mul(i1, i2)));
2004 } else if let (Some(n1), Some(n2)) = (state.get_num_at(rb), state.proto_const_num(&cl, kidx)) {
2005 pc += 1;
2006 state.set_at(ra, LuaValue::Float(n1 * n2));
2007 }
2008 }
2009 // C: op_arithK(L, luaV_mod, luaV_modf) — division by zero possible
2010 OpCode::ModK => {
2011 let ra = base + i.arg_a();
2012 let v1 = state.get_at(base + i.arg_b());
2013 let v2 = state.proto_const(&cl, i.arg_c() as usize).clone();
2014 state.set_ci_savedpc(ci, pc); // savestate for div-by-zero
2015 state.set_top(state.ci_top(ci));
2016 arith_op_checked(state, ra, &v1, &v2, &mut pc,
2017 |a, b| imod(a, b), fmodf)?;
2018 }
2019 // C: op_arithfK(L, luai_numpow) — float only
2020 OpCode::PowK => {
2021 let ra = base + i.arg_a();
2022 let rb = base + i.arg_b();
2023 let kidx = i.arg_c() as usize;
2024 if let (Some(n1), Some(n2)) = (state.get_num_at(rb), state.proto_const_num(&cl, kidx)) {
2025 pc += 1;
2026 let r = if n2 == 2.0 { n1 * n1 } else { n1.powf(n2) };
2027 state.set_at(ra, LuaValue::Float(r));
2028 }
2029 }
2030 // C: op_arithfK(L, luai_numdiv) — float division
2031 OpCode::DivK => {
2032 let ra = base + i.arg_a();
2033 let rb = base + i.arg_b();
2034 let kidx = i.arg_c() as usize;
2035 if let (Some(n1), Some(n2)) = (state.get_num_at(rb), state.proto_const_num(&cl, kidx)) {
2036 pc += 1;
2037 state.set_at(ra, LuaValue::Float(n1 / n2));
2038 }
2039 }
2040 // C: op_arithK(L, luaV_idiv, luai_numidiv)
2041 OpCode::IDivK => {
2042 let ra = base + i.arg_a();
2043 let v1 = state.get_at(base + i.arg_b());
2044 let v2 = state.proto_const(&cl, i.arg_c() as usize).clone();
2045 state.set_ci_savedpc(ci, pc);
2046 state.set_top(state.ci_top(ci));
2047 arith_op_checked(state, ra, &v1, &v2, &mut pc,
2048 |a, b| idiv(a, b), |a, b| (a / b).floor())?;
2049 }
2050 // C: op_bitwiseK(L, l_band/l_bor/l_bxor)
2051 OpCode::BAndK => {
2052 let ra = base + i.arg_a();
2053 let v1 = state.get_at(base + i.arg_b());
2054 let v2 = state.proto_const(&cl, i.arg_c() as usize).clone();
2055 bitwise_op_k(state, ra, &v1, &v2, &mut pc, intop_band);
2056 }
2057 OpCode::BOrK => {
2058 let ra = base + i.arg_a();
2059 let v1 = state.get_at(base + i.arg_b());
2060 let v2 = state.proto_const(&cl, i.arg_c() as usize).clone();
2061 bitwise_op_k(state, ra, &v1, &v2, &mut pc, intop_bor);
2062 }
2063 OpCode::BXOrK => {
2064 let ra = base + i.arg_a();
2065 let v1 = state.get_at(base + i.arg_b());
2066 let v2 = state.proto_const(&cl, i.arg_c() as usize).clone();
2067 bitwise_op_k(state, ra, &v1, &v2, &mut pc, intop_bxor);
2068 }
2069 // C: OP_SHRI — rb >> sC (shift right by immediate)
2070 // C: if (tointegerns(rb, &ib)) { pc++; setivalue(s2v(ra), luaV_shiftl(ib, -ic)); }
2071 OpCode::ShrI => {
2072 let ra = base + i.arg_a();
2073 let v = state.get_at(base + i.arg_b());
2074 let ic = i.arg_s_c() as i64;
2075 if let Some(ib) = to_integer_ns(&v, F2Imod::Eq) {
2076 pc += 1;
2077 state.set_at(ra, LuaValue::Int(shiftl(ib, -ic)));
2078 }
2079 }
2080 // C: OP_SHLI — sC << rb
2081 // C: if (tointegerns(rb, &ib)) { pc++; setivalue(s2v(ra), luaV_shiftl(ic, ib)); }
2082 OpCode::ShlI => {
2083 let ra = base + i.arg_a();
2084 let v = state.get_at(base + i.arg_b());
2085 let ic = i.arg_s_c() as i64;
2086 if let Some(ib) = to_integer_ns(&v, F2Imod::Eq) {
2087 pc += 1;
2088 state.set_at(ra, LuaValue::Int(shiftl(ic, ib)));
2089 }
2090 }
2091 // ── Arithmetic with register operands ──────────────────────
2092 OpCode::Add => {
2093 let ra = base + i.arg_a();
2094 let rb = base + i.arg_b();
2095 let rc = base + i.arg_c();
2096 let ra_u = ra.0 as usize;
2097 let rb_v = state.stack[rb.0 as usize].val;
2098 let rc_v = state.stack[rc.0 as usize].val;
2099 if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (rb_v, rc_v) {
2100 pc += 1;
2101 state.stack[ra_u].val = LuaValue::Int(intop_add(i1, i2));
2102 } else if let (Some(n1), Some(n2)) = (number_value(rb_v), number_value(rc_v)) {
2103 pc += 1;
2104 state.stack[ra_u].val = LuaValue::Float(n1 + n2);
2105 }
2106 }
2107 OpCode::Sub => {
2108 let ra = base + i.arg_a();
2109 let rb = base + i.arg_b();
2110 let rc = base + i.arg_c();
2111 let ra_u = ra.0 as usize;
2112 let rb_v = state.stack[rb.0 as usize].val;
2113 let rc_v = state.stack[rc.0 as usize].val;
2114 if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (rb_v, rc_v) {
2115 pc += 1;
2116 state.stack[ra_u].val = LuaValue::Int(intop_sub(i1, i2));
2117 } else if let (Some(n1), Some(n2)) = (number_value(rb_v), number_value(rc_v)) {
2118 pc += 1;
2119 state.stack[ra_u].val = LuaValue::Float(n1 - n2);
2120 }
2121 }
2122 OpCode::Mul => {
2123 let ra = base + i.arg_a();
2124 let rb = base + i.arg_b();
2125 let rc = base + i.arg_c();
2126 if let Some((i1, i2)) = state.get_int_pair_at(rb, rc) {
2127 pc += 1;
2128 state.set_at(ra, LuaValue::Int(intop_mul(i1, i2)));
2129 } else if let Some((n1, n2)) = state.get_num_pair_at(rb, rc) {
2130 pc += 1;
2131 state.set_at(ra, LuaValue::Float(n1 * n2));
2132 }
2133 }
2134 OpCode::Mod => {
2135 let ra = base + i.arg_a();
2136 let v1 = state.get_at(base + i.arg_b());
2137 let v2 = state.get_at(base + i.arg_c());
2138 state.set_ci_savedpc(ci, pc);
2139 state.set_top(state.ci_top(ci));
2140 arith_op_checked(state, ra, &v1, &v2, &mut pc,
2141 |a, b| imod(a, b), fmodf)?;
2142 }
2143 OpCode::Pow => {
2144 let ra = base + i.arg_a();
2145 let rb = base + i.arg_b();
2146 let rc = base + i.arg_c();
2147 if let Some((n1, n2)) = state.get_num_pair_at(rb, rc) {
2148 pc += 1;
2149 let r = if n2 == 2.0 { n1 * n1 } else { n1.powf(n2) };
2150 state.set_at(ra, LuaValue::Float(r));
2151 }
2152 }
2153 OpCode::Div => {
2154 let ra = base + i.arg_a();
2155 let rb = base + i.arg_b();
2156 let rc = base + i.arg_c();
2157 if let Some((n1, n2)) = state.get_num_pair_at(rb, rc) {
2158 pc += 1;
2159 state.set_at(ra, LuaValue::Float(n1 / n2));
2160 }
2161 }
2162 OpCode::IDiv => {
2163 let ra = base + i.arg_a();
2164 let v1 = state.get_at(base + i.arg_b());
2165 let v2 = state.get_at(base + i.arg_c());
2166 state.set_ci_savedpc(ci, pc);
2167 state.set_top(state.ci_top(ci));
2168 arith_op_checked(state, ra, &v1, &v2, &mut pc,
2169 |a, b| idiv(a, b), |a, b| (a / b).floor())?;
2170 }
2171 // ── Bitwise with register operands ─────────────────────────
2172 // C: op_bitwise(L, l_band/l_bor/l_bxor)
2173 // if (tointegerns(v1, &i1) && tointegerns(v2, &i2)) { pc++; setivalue... }
2174 OpCode::BAnd => {
2175 let ra = base + i.arg_a();
2176 let v1 = state.get_at(base + i.arg_b());
2177 let v2 = state.get_at(base + i.arg_c());
2178 bitwise_op_rr(state, ra, &v1, &v2, &mut pc, intop_band);
2179 }
2180 OpCode::BOr => {
2181 let ra = base + i.arg_a();
2182 let v1 = state.get_at(base + i.arg_b());
2183 let v2 = state.get_at(base + i.arg_c());
2184 bitwise_op_rr(state, ra, &v1, &v2, &mut pc, intop_bor);
2185 }
2186 OpCode::BXOr => {
2187 let ra = base + i.arg_a();
2188 let v1 = state.get_at(base + i.arg_b());
2189 let v2 = state.get_at(base + i.arg_c());
2190 bitwise_op_rr(state, ra, &v1, &v2, &mut pc, intop_bxor);
2191 }
2192 // C: op_bitwise(L, luaV_shiftr) — shift right via shiftl(-y)
2193 OpCode::Shr => {
2194 let ra = base + i.arg_a();
2195 let v1 = state.get_at(base + i.arg_b());
2196 let v2 = state.get_at(base + i.arg_c());
2197 bitwise_shift_rr(state, ra, &v1, &v2, &mut pc, true);
2198 }
2199 // C: op_bitwise(L, luaV_shiftl)
2200 OpCode::Shl => {
2201 let ra = base + i.arg_a();
2202 let v1 = state.get_at(base + i.arg_b());
2203 let v2 = state.get_at(base + i.arg_c());
2204 bitwise_shift_rr(state, ra, &v1, &v2, &mut pc, false);
2205 }
2206 // ── OP_MMBIN ─────────────────────────────────────────────
2207 // C: fallback metamethod for binary arith ops
2208 // Instruction pi = *(pc - 2); TMS tm = (TMS)GETARG_C(i);
2209 // StkId result = RA(pi);
2210 // Protect(luaT_trybinTM(L, s2v(ra), rb, result, tm));
2211 OpCode::MmBin => {
2212 let ra_idx = base + i.arg_a();
2213 let rb_idx = base + i.arg_b();
2214 let ra_v = state.get_at(ra_idx);
2215 let rb_v = state.get_at(rb_idx);
2216 let tm = tagmethod_from_index(i.arg_c() as usize);
2217 let prev_inst = state.proto_code(&cl, pc - 2);
2218 let result_idx = base + prev_inst.arg_a();
2219 state.set_ci_savedpc(ci, pc);
2220 state.set_top(state.ci_top(ci));
2221 state.try_bin_tm(&ra_v, Some(ra_idx), &rb_v, Some(rb_idx), result_idx, tm)?;
2222 trap = state.ci_trap(ci);
2223 }
2224 // C: OP_MMBINI — metamethod for arith-with-immediate
2225 OpCode::MmBinI => {
2226 let ra_idx = base + i.arg_a();
2227 let ra_v = state.get_at(ra_idx);
2228 let imm = i.arg_s_b() as i64;
2229 let tm = tagmethod_from_index(i.arg_c() as usize);
2230 let flip = i.arg_k() != 0;
2231 let prev_inst = state.proto_code(&cl, pc - 2);
2232 let result_idx = base + prev_inst.arg_a();
2233 state.set_ci_savedpc(ci, pc);
2234 state.set_top(state.ci_top(ci));
2235 state.try_bin_i_tm(&ra_v, Some(ra_idx), imm, flip, result_idx, tm)?;
2236 trap = state.ci_trap(ci);
2237 }
2238 // C: OP_MMBINK — metamethod for arith-with-K
2239 OpCode::MmBinK => {
2240 let ra_idx = base + i.arg_a();
2241 let ra_v = state.get_at(ra_idx);
2242 let imm = state.proto_const(&cl, i.arg_b() as usize).clone();
2243 let tm = tagmethod_from_index(i.arg_c() as usize);
2244 let flip = i.arg_k() != 0;
2245 let prev_inst = state.proto_code(&cl, pc - 2);
2246 let result_idx = base + prev_inst.arg_a();
2247 state.set_ci_savedpc(ci, pc);
2248 state.set_top(state.ci_top(ci));
2249 state.try_bin_assoc_tm(&ra_v, Some(ra_idx), &imm, None, flip, result_idx, tm)?;
2250 trap = state.ci_trap(ci);
2251 }
2252 // ── OP_UNM ───────────────────────────────────────────────
2253 // C: if (ttisinteger(rb)) setivalue(s2v(ra), intop(-,0,ib))
2254 // else if (tonumberns(rb, nb)) setfltvalue(s2v(ra), -nb)
2255 // else Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM))
2256 OpCode::Unm => {
2257 let ra = base + i.arg_a();
2258 let rb_idx = base + i.arg_b();
2259 let rb_v = state.get_at(rb_idx);
2260 match &rb_v {
2261 LuaValue::Int(ib) => {
2262 state.set_at(ra, LuaValue::Int(intop_sub(0, *ib)));
2263 }
2264 LuaValue::Float(nb) => {
2265 state.set_at(ra, LuaValue::Float(-nb));
2266 }
2267 _ => {
2268 state.set_ci_savedpc(ci, pc);
2269 state.set_top(state.ci_top(ci));
2270 state.try_bin_tm(&rb_v, Some(rb_idx), &rb_v, Some(rb_idx), ra, TagMethod::Unm)?;
2271 trap = state.ci_trap(ci);
2272 }
2273 }
2274 }
2275 // ── OP_BNOT ──────────────────────────────────────────────
2276 // C: if (tointegerns(rb, &ib)) setivalue(s2v(ra), intop(^, ~0u64, ib))
2277 OpCode::BNot => {
2278 let ra = base + i.arg_a();
2279 let rb_idx = base + i.arg_b();
2280 let rb_v = state.get_at(rb_idx);
2281 if let Some(ib) = to_integer_ns(&rb_v, F2Imod::Eq) {
2282 // C: intop(^, ~l_castS2U(0), ib) == bitwise NOT of ib
2283 state.set_at(ra, LuaValue::Int(!ib));
2284 } else {
2285 state.set_ci_savedpc(ci, pc);
2286 state.set_top(state.ci_top(ci));
2287 state.try_bin_tm(&rb_v, Some(rb_idx), &rb_v, Some(rb_idx), ra, TagMethod::Bnot)?;
2288 trap = state.ci_trap(ci);
2289 }
2290 }
2291 // ── OP_NOT ───────────────────────────────────────────────
2292 // C: if (l_isfalse(rb)) setbtvalue else setbfvalue
2293 OpCode::Not => {
2294 let ra = base + i.arg_a();
2295 let rb_v = state.get_at(base + i.arg_b());
2296 let falsy = matches!(rb_v, LuaValue::Nil | LuaValue::Bool(false));
2297 state.set_at(ra, LuaValue::Bool(falsy));
2298 }
2299 // ── OP_LEN ───────────────────────────────────────────────
2300 // C: Protect(luaV_objlen(L, ra, vRB(i)));
2301 OpCode::Len => {
2302 let ra = base + i.arg_a();
2303 let rb_v = state.get_at(base + i.arg_b());
2304 state.set_ci_savedpc(ci, pc);
2305 state.set_top(state.ci_top(ci));
2306 obj_len(state, ra, rb_v)?;
2307 trap = state.ci_trap(ci);
2308 }
2309 // ── OP_CONCAT ─────────────────────────────────────────────
2310 // C: n = GETARG_B(i); L->top.p = ra+n; ProtectNT(luaV_concat(L,n));
2311 OpCode::Concat => {
2312 let ra = base + i.arg_a();
2313 let n = i.arg_b() as i32;
2314 state.set_top(ra + n as i32);
2315 state.set_ci_savedpc(ci, pc); // ProtectNT: save pc only
2316 concat(state, n)?;
2317 trap = state.ci_trap(ci);
2318 // C: checkGC
2319 let top = state.top_idx();
2320 state.set_ci_savedpc(ci, pc);
2321 state.set_top(top);
2322 state.gc_cond_step();
2323 trap = state.ci_trap(ci);
2324 }
2325 // ── OP_CLOSE ──────────────────────────────────────────────
2326 // C: Protect(luaF_close(L, ra, LUA_OK, 1));
2327 OpCode::Close => {
2328 let ra = base + i.arg_a();
2329 state.set_ci_savedpc(ci, pc);
2330 state.set_top(state.ci_top(ci));
2331 crate::func::close(state, ra, lua_types::status::LuaStatus::Ok as i32, true)?;
2332 trap = state.ci_trap(ci);
2333 }
2334 // ── OP_TBC ────────────────────────────────────────────────
2335 // C: halfProtect(luaF_newtbcupval(L, ra));
2336 OpCode::Tbc => {
2337 let ra = base + i.arg_a();
2338 state.set_ci_savedpc(ci, pc);
2339 state.set_top(state.ci_top(ci));
2340 state.new_tbc_upval(ra)?;
2341 }
2342 // ── OP_JMP ────────────────────────────────────────────────
2343 // C: dojump(ci, i, 0) → pc += GETARG_sJ(i) + 0; updatetrap(ci);
2344 OpCode::Jmp => {
2345 pc = (pc as i64 + i.arg_s_j() as i64) as u32;
2346 trap = state.ci_trap(ci);
2347 }
2348 // ── OP_EQ ─────────────────────────────────────────────────
2349 // C: Protect(cond = luaV_equalobj(L, s2v(ra), rb)); docondjump()
2350 OpCode::Eq => {
2351 let ra_v = state.get_at(base + i.arg_a());
2352 let rb_v = state.get_at(base + i.arg_b());
2353 state.set_ci_savedpc(ci, pc);
2354 state.set_top(state.ci_top(ci));
2355 let cond = equal_obj(Some(state), &ra_v, &rb_v)? as u32;
2356 trap = state.ci_trap(ci);
2357 // C: docondjump() — if cond != GETARG_k(i): pc++; else: dojump
2358 if (cond as i32) != i.arg_k() {
2359 pc += 1;
2360 } else {
2361 let next = state.proto_code(&cl, pc);
2362 pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2363 trap = state.ci_trap(ci);
2364 }
2365 }
2366 // ── OP_LT ─────────────────────────────────────────────────
2367 // C: op_order(L, l_lti, LTnum, lessthanothers)
2368 OpCode::Lt => {
2369 let ra_v = state.get_at(base + i.arg_a());
2370 let rb_v = state.get_at(base + i.arg_b());
2371 let cond = if let (LuaValue::Int(ia), LuaValue::Int(ib)) = (&ra_v, &rb_v) {
2372 *ia < *ib
2373 } else if matches!((&ra_v, &rb_v),
2374 (LuaValue::Int(_) | LuaValue::Float(_),
2375 LuaValue::Int(_) | LuaValue::Float(_))) {
2376 lt_num(&ra_v, &rb_v)
2377 } else {
2378 state.set_ci_savedpc(ci, pc);
2379 state.set_top(state.ci_top(ci));
2380 let r = less_than_others(state, &ra_v, &rb_v)?;
2381 trap = state.ci_trap(ci);
2382 r
2383 };
2384 if (cond as i32) != i.arg_k() {
2385 pc += 1;
2386 } else {
2387 let next = state.proto_code(&cl, pc);
2388 pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2389 trap = state.ci_trap(ci);
2390 }
2391 }
2392 // ── OP_LE ─────────────────────────────────────────────────
2393 // C: op_order(L, l_lei, LEnum, lessequalothers)
2394 OpCode::Le => {
2395 let ra_v = state.get_at(base + i.arg_a());
2396 let rb_v = state.get_at(base + i.arg_b());
2397 let cond = if let (LuaValue::Int(ia), LuaValue::Int(ib)) = (&ra_v, &rb_v) {
2398 *ia <= *ib
2399 } else if matches!((&ra_v, &rb_v),
2400 (LuaValue::Int(_) | LuaValue::Float(_),
2401 LuaValue::Int(_) | LuaValue::Float(_))) {
2402 le_num(&ra_v, &rb_v)
2403 } else {
2404 state.set_ci_savedpc(ci, pc);
2405 state.set_top(state.ci_top(ci));
2406 let r = less_equal_others(state, &ra_v, &rb_v)?;
2407 trap = state.ci_trap(ci);
2408 r
2409 };
2410 if (cond as i32) != i.arg_k() {
2411 pc += 1;
2412 } else {
2413 let next = state.proto_code(&cl, pc);
2414 pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2415 trap = state.ci_trap(ci);
2416 }
2417 }
2418 // ── OP_EQK ────────────────────────────────────────────────
2419 // C: int cond = luaV_rawequalobj(s2v(ra), rb); docondjump()
2420 OpCode::EqK => {
2421 let ra_v = state.get_at(base + i.arg_a());
2422 let rb_v = state.proto_const(&cl, i.arg_b() as usize).clone();
2423 let cond = equal_obj(None, &ra_v, &rb_v)? as u32;
2424 if (cond as i32) != i.arg_k() {
2425 pc += 1;
2426 } else {
2427 let next = state.proto_code(&cl, pc);
2428 pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2429 trap = state.ci_trap(ci);
2430 }
2431 }
2432 // ── OP_EQI ────────────────────────────────────────────────
2433 // C: int im = GETARG_sB(i)
2434 // if (ttisinteger) cond = ivalue == im
2435 // elif (ttisfloat) cond = numeq(fltvalue, cast_num(im))
2436 // else cond = 0
2437 OpCode::EqI => {
2438 let ra_v = state.get_at(base + i.arg_a());
2439 let im = i.arg_s_b() as i64;
2440 let cond: bool = match &ra_v {
2441 LuaValue::Int(iv) => *iv == im,
2442 LuaValue::Float(fv) => *fv == im as f64,
2443 _ => false,
2444 };
2445 if (cond as i32) != i.arg_k() {
2446 pc += 1;
2447 } else {
2448 let next = state.proto_code(&cl, pc);
2449 pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2450 trap = state.ci_trap(ci);
2451 }
2452 }
2453 // ── OP_LTI / OP_LEI / OP_GTI / OP_GEI ───────────────────
2454 // C: op_orderI(L, l_lti/l_lei/l_gti/l_gei, luai_numlt/le/gt/ge,
2455 // inv=0/0/1/1, tm=TM_LT/TM_LE/TM_LT/TM_LE)
2456 OpCode::LtI => {
2457 let ra = base + i.arg_a();
2458 let im = i.arg_s_b() as i64;
2459 let fast_cond = match &state.stack[ra.0 as usize].val {
2460 LuaValue::Int(ia) => Some(*ia < im),
2461 LuaValue::Float(fa) => Some(*fa < im as f64),
2462 _ => None,
2463 };
2464 let cond = match fast_cond {
2465 Some(cond) => cond,
2466 None => order_imm_slow(state, ra, pc, &mut trap, ci, i, im, false, TagMethod::Lt)?,
2467 };
2468 finish_order_imm_jump(state, &cl, &mut pc, &mut trap, ci, i, cond);
2469 }
2470 OpCode::LeI => {
2471 let ra = base + i.arg_a();
2472 let im = i.arg_s_b() as i64;
2473 let fast_cond = match &state.stack[ra.0 as usize].val {
2474 LuaValue::Int(ia) => Some(*ia <= im),
2475 LuaValue::Float(fa) => Some(*fa <= im as f64),
2476 _ => None,
2477 };
2478 let cond = match fast_cond {
2479 Some(cond) => cond,
2480 None => order_imm_slow(state, ra, pc, &mut trap, ci, i, im, false, TagMethod::Le)?,
2481 };
2482 finish_order_imm_jump(state, &cl, &mut pc, &mut trap, ci, i, cond);
2483 }
2484 OpCode::GtI => {
2485 let ra = base + i.arg_a();
2486 let im = i.arg_s_b() as i64;
2487 let fast_cond = match &state.stack[ra.0 as usize].val {
2488 LuaValue::Int(ia) => Some(*ia > im),
2489 LuaValue::Float(fa) => Some(*fa > im as f64),
2490 _ => None,
2491 };
2492 let cond = match fast_cond {
2493 Some(cond) => cond,
2494 None => order_imm_slow(state, ra, pc, &mut trap, ci, i, im, true, TagMethod::Lt)?,
2495 };
2496 finish_order_imm_jump(state, &cl, &mut pc, &mut trap, ci, i, cond);
2497 }
2498 OpCode::GeI => {
2499 let ra = base + i.arg_a();
2500 let im = i.arg_s_b() as i64;
2501 let fast_cond = match &state.stack[ra.0 as usize].val {
2502 LuaValue::Int(ia) => Some(*ia >= im),
2503 LuaValue::Float(fa) => Some(*fa >= im as f64),
2504 _ => None,
2505 };
2506 let cond = match fast_cond {
2507 Some(cond) => cond,
2508 None => order_imm_slow(state, ra, pc, &mut trap, ci, i, im, true, TagMethod::Le)?,
2509 };
2510 finish_order_imm_jump(state, &cl, &mut pc, &mut trap, ci, i, cond);
2511 }
2512 // ── OP_TEST ────────────────────────────────────────────────
2513 // C: int cond = !l_isfalse(s2v(ra)); docondjump()
2514 OpCode::Test => {
2515 let ra_v = state.get_at(base + i.arg_a());
2516 let cond = !matches!(ra_v, LuaValue::Nil | LuaValue::Bool(false));
2517 if (cond as i32) != i.arg_k() {
2518 pc += 1;
2519 } else {
2520 let next = state.proto_code(&cl, pc);
2521 pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2522 trap = state.ci_trap(ci);
2523 }
2524 }
2525 // ── OP_TESTSET ─────────────────────────────────────────────
2526 // C: if (l_isfalse(rb) == GETARG_k(i)) pc++;
2527 // else { setobj2s(L, ra, rb); donextjump(ci); }
2528 OpCode::TestSet => {
2529 let ra = base + i.arg_a();
2530 let rb_v = state.get_at(base + i.arg_b());
2531 let falsy = matches!(rb_v, LuaValue::Nil | LuaValue::Bool(false));
2532 if (falsy as i32) == i.arg_k() {
2533 pc += 1;
2534 } else {
2535 state.set_at(ra, rb_v);
2536 let next = state.proto_code(&cl, pc);
2537 pc = (pc as i64 + next.arg_s_j() as i64 + 1) as u32;
2538 trap = state.ci_trap(ci);
2539 }
2540 }
2541 // ── OP_CALL ────────────────────────────────────────────────
2542 // C: if ((newci = luaD_precall(L, ra, nresults)) == NULL)
2543 // updatetrap(ci);
2544 // else { ci = newci; goto startfunc; }
2545 OpCode::Call => {
2546 let ra = base + i.arg_a();
2547 let b = i.arg_b();
2548 let nresults = i.arg_c() as i32 - 1;
2549 if b != 0 {
2550 state.set_top(ra + b);
2551 }
2552 state.set_ci_savedpc(ci, pc); // savepc
2553 match state.precall(ra, nresults)? {
2554 None => {
2555 // C function — nothing else to do
2556 trap = state.ci_trap(ci); // updatetrap
2557 }
2558 Some(new_ci) => {
2559 // Lua function — goto startfunc
2560 ci = new_ci;
2561 continue 'startfunc;
2562 }
2563 }
2564 }
2565 // ── OP_TAILCALL ────────────────────────────────────────────
2566 // C: if ((n = luaD_pretailcall(L, ci, ra, b, delta)) < 0)
2567 // goto startfunc;
2568 // else { ci->func.p -= delta; luaD_poscall(L, ci, n);
2569 // updatetrap; goto ret; }
2570 OpCode::TailCall => {
2571 let ra = base + i.arg_a();
2572 let b = i.arg_b();
2573 let nparams1 = i.arg_c();
2574 let delta = if nparams1 != 0 {
2575 state.ci_nextraargs(ci) + nparams1 as i32
2576 } else {
2577 0
2578 };
2579 let top_b: i32 = if b != 0 {
2580 state.set_top(ra + b);
2581 b
2582 } else {
2583 state.top_idx() - ra
2584 };
2585 state.set_ci_savedpc(ci, pc);
2586 if i.test_k() {
2587 // C: luaF_closeupval(L, base); assert(L->tbclist.p < base);
2588 state.close_upvals_from_base(ci)?;
2589 }
2590 let n = state.pretailcall(ci, ra, top_b, delta)?;
2591 if n < 0 {
2592 // Lua function — goto startfunc
2593 continue 'startfunc;
2594 } else {
2595 // C function — ci->func.p -= delta; luaD_poscall; goto ret
2596 state.ci_adjust_func(ci, delta);
2597 state.poscall(ci, n as u32)?;
2598 trap = state.ci_trap(ci);
2599 break 'dispatch; // goto ret
2600 }
2601 }
2602 // ── OP_RETURN ──────────────────────────────────────────────
2603 // C: n = GETARG_B(i)-1; if (n<0) n = cast_int(L->top.p - ra);
2604 // savepc; if TESTARG_k: close upvals;
2605 // if nparams1: ci->func -= nextraargs+nparams1;
2606 // L->top.p = ra+n; luaD_poscall; goto ret
2607 OpCode::Return => {
2608 let ra = base + i.arg_a();
2609 let n_raw = i.arg_b() as i32 - 1;
2610 let nparams1 = i.arg_c();
2611 let n: u32 = if n_raw < 0 {
2612 (state.top_idx() - ra) as u32
2613 } else {
2614 n_raw as u32
2615 };
2616 state.set_ci_savedpc(ci, pc);
2617 if i.test_k() {
2618 state.ci_nres_set(ci, n as i32);
2619 let ci_top = state.ci_top(ci);
2620 if state.top_idx().0 < ci_top.0 {
2621 state.set_top(ci_top);
2622 }
2623 crate::func::close(state, base, crate::func::CLOSE_K_TOP, true)?;
2624 trap = state.ci_trap(ci);
2625 base = state.ci_base(ci); // updatestack
2626 }
2627 if nparams1 != 0 {
2628 let nextraargs = state.ci_nextraargs(ci) as u32;
2629 state.ci_adjust_func(ci, (nextraargs as i32 + nparams1 as i32));
2630 }
2631 state.set_top(ra + n as i32);
2632 state.poscall(ci, n)?;
2633 trap = state.ci_trap(ci);
2634 break 'dispatch; // goto ret
2635 }
2636 // ── OP_RETURN0 ─────────────────────────────────────────────
2637 // C: if (L->hookmask) { ra = RA; L->top = ra; savepc; poscall(0); trap=1; }
2638 // else { L->ci = ci->previous; L->top = base-1;
2639 // for (nres = ci->nresults; nres > 0; nres--)
2640 // setnilvalue(L->top++) }
2641 // goto ret;
2642 OpCode::Return0 => {
2643 if state.hookmask == 0 {
2644 let ci_slot = ci.as_usize();
2645 let nres = state.call_info[ci_slot].nresults as i32;
2646 state.ci = state.call_info[ci_slot]
2647 .previous
2648 .expect("RETURN0: returning frame has no previous CallInfo");
2649 state.top = base - 1;
2650 for _ in 0..nres.max(0) {
2651 state.push(LuaValue::Nil);
2652 }
2653 } else {
2654 return0_hook(state, ci, base, i, pc, &mut trap)?;
2655 }
2656 break 'dispatch; // goto ret
2657 }
2658 // ── OP_RETURN1 ─────────────────────────────────────────────
2659 // C: if (L->hookmask) { L->top = ra+1; savepc; poscall(1); trap=1; }
2660 // else { nres = ci->nresults; ci = ci->previous; ...handle results... }
2661 // goto ret;
2662 OpCode::Return1 => {
2663 if state.hookmask == 0 {
2664 let ci_slot = ci.as_usize();
2665 let nres = state.call_info[ci_slot].nresults as i32;
2666 state.ci = state.call_info[ci_slot]
2667 .previous
2668 .expect("RETURN1: returning frame has no previous CallInfo");
2669 if nres == 0 {
2670 state.top = base - 1;
2671 } else {
2672 let ra = base + i.arg_a();
2673 state.stack[(base - 1).0 as usize].val =
2674 state.stack[ra.0 as usize].val; // at least this result
2675 state.top = base;
2676 for _ in 1..nres.max(0) {
2677 state.push(LuaValue::Nil);
2678 }
2679 }
2680 } else {
2681 return1_hook(state, ci, base, i, pc, &mut trap)?;
2682 }
2683 break 'dispatch; // goto ret
2684 }
2685 // ── OP_FORLOOP ─────────────────────────────────────────────
2686 // C: if (ttisinteger(s2v(ra+2))) { integer loop }
2687 // else if (floatforloop(ra)) pc -= GETARG_Bx(i)
2688 // updatetrap(ci);
2689 OpCode::ForLoop => {
2690 let ra = base + i.arg_a();
2691 let ra_u = ra.0 as usize;
2692 if let LuaValue::Int(step) = state.stack[ra_u + 2].val {
2693 // C: count = l_castS2U(ivalue(s2v(ra+1)));
2694 let count = match state.stack[ra_u + 1].val {
2695 LuaValue::Int(c) => c as u64,
2696 _ => 0,
2697 };
2698 if count > 0 {
2699 let idx = match state.stack[ra_u].val {
2700 LuaValue::Int(x) => x,
2701 _ => 0,
2702 };
2703 // C: chgivalue(s2v(ra+1), count-1)
2704 state.stack[ra_u + 1].val = LuaValue::Int((count - 1) as i64);
2705 // C: idx = intop(+, idx, step)
2706 let new_idx = intop_add(idx, step);
2707 state.stack[ra_u].val = LuaValue::Int(new_idx);
2708 state.stack[ra_u + 3].val = LuaValue::Int(new_idx);
2709 // C: pc -= GETARG_Bx(i)
2710 pc = (pc as i64 - i.arg_bx() as i64) as u32;
2711 }
2712 } else if float_for_loop(state, ra) {
2713 pc = (pc as i64 - i.arg_bx() as i64) as u32;
2714 }
2715 trap = state.ci_trap(ci);
2716 }
2717 // ── OP_FORPREP ─────────────────────────────────────────────
2718 // C: savestate; if (forprep(L, ra)) pc += Bx + 1; (skip loop)
2719 OpCode::ForPrep => {
2720 let ra = base + i.arg_a();
2721 state.set_ci_savedpc(ci, pc);
2722 state.set_top(state.ci_top(ci));
2723 if forprep(state, ra)? {
2724 pc = (pc as i64 + i.arg_bx() as i64 + 1) as u32;
2725 }
2726 }
2727 // ── OP_TFORPREP ────────────────────────────────────────────
2728 // C: halfProtect(luaF_newtbcupval(L, ra+3));
2729 // pc += GETARG_Bx(i); i = *pc++; assert(OP_TFORCALL && ra==RA(i));
2730 // goto l_tforcall;
2731 OpCode::TForPrep => {
2732 let ra = base + i.arg_a();
2733 state.set_ci_savedpc(ci, pc);
2734 state.set_top(state.ci_top(ci));
2735 state.new_tbc_upval(ra + 3)?;
2736 // C: pc += GETARG_Bx(i); advance to TFORCALL
2737 pc = (pc as i64 + i.arg_bx() as i64) as u32;
2738 // C: i = *pc++; goto l_tforcall
2739 let tfc_i = state.proto_code(&cl, pc);
2740 pc += 1;
2741 debug_assert!(tfc_i.opcode() == OpCode::TForCall);
2742 // inline l_tforcall:
2743 let tfc_ra = base + tfc_i.arg_a();
2744 // C: memcpy(ra+4, ra, 3*sizeof(*ra)) — copy func, state, ctrl
2745 for k in 0..3u32 {
2746 let v = state.get_at(tfc_ra + k as i32);
2747 state.set_at(tfc_ra + 4 + k as i32, v);
2748 }
2749 state.set_top(tfc_ra + 4 + 3);
2750 // C: ProtectNT(luaD_call(L, ra+4, GETARG_C(i)));
2751 state.set_ci_savedpc(ci, pc);
2752 state.call_at(tfc_ra + 4, tfc_i.arg_c() as i32)?;
2753 trap = state.ci_trap(ci);
2754 base = state.ci_base(ci); // updatestack
2755 // C: i = *pc++; goto l_tforloop
2756 let tfl_i = state.proto_code(&cl, pc);
2757 pc += 1;
2758 debug_assert!(tfl_i.opcode() == OpCode::TForLoop);
2759 let tfl_ra = base + tfl_i.arg_a();
2760 // inline l_tforloop:
2761 if !matches!(state.get_at(tfl_ra + 4), LuaValue::Nil) {
2762 // C: setobjs2s(L, ra+2, ra+4)
2763 let v = state.get_at(tfl_ra + 4);
2764 state.set_at(tfl_ra + 2, v);
2765 // C: pc -= GETARG_Bx(i)
2766 pc = (pc as i64 - tfl_i.arg_bx() as i64) as u32;
2767 }
2768 }
2769 // ── OP_TFORCALL ────────────────────────────────────────────
2770 // C: l_tforcall: { push func/state/ctrl; call; goto l_tforloop }
2771 OpCode::TForCall => {
2772 let ra = base + i.arg_a();
2773 for k in 0..3u32 {
2774 let v = state.get_at(ra + k as i32);
2775 state.set_at(ra + 4 + k as i32, v);
2776 }
2777 state.set_top(ra + 4 + 3);
2778 state.set_ci_savedpc(ci, pc);
2779 state.call_at(ra + 4, i.arg_c() as i32)?;
2780 trap = state.ci_trap(ci);
2781 base = state.ci_base(ci); // updatestack
2782 // C: i = *pc++; goto l_tforloop
2783 let tfl_i = state.proto_code(&cl, pc);
2784 pc += 1;
2785 debug_assert!(tfl_i.opcode() == OpCode::TForLoop);
2786 let tfl_ra = base + tfl_i.arg_a();
2787 if !matches!(state.get_at(tfl_ra + 4), LuaValue::Nil) {
2788 let v = state.get_at(tfl_ra + 4);
2789 state.set_at(tfl_ra + 2, v);
2790 pc = (pc as i64 - tfl_i.arg_bx() as i64) as u32;
2791 }
2792 }
2793 // ── OP_TFORLOOP ────────────────────────────────────────────
2794 // C: l_tforloop: if (!ttisnil(s2v(ra+4))) { save ctrl; jump back }
2795 OpCode::TForLoop => {
2796 let ra = base + i.arg_a();
2797 if !matches!(state.get_at(ra + 4), LuaValue::Nil) {
2798 let v = state.get_at(ra + 4);
2799 state.set_at(ra + 2, v);
2800 pc = (pc as i64 - i.arg_bx() as i64) as u32;
2801 }
2802 }
2803 // ── OP_SETLIST ─────────────────────────────────────────────
2804 // C: n = GETARG_B; if n==0: n = top - ra - 1; last = C;
2805 // if TESTARG_k: last += Ax * (MAXARG_C+1); pc++;
2806 // for (; n > 0; n--) h->array[last-1] = val; luaC_barrierback
2807 OpCode::SetList => {
2808 let ra = base + i.arg_a();
2809 let n_raw = i.arg_b();
2810 let mut last = i.arg_c();
2811 let t_val = state.get_at(ra);
2812 let n: i32 = if n_raw == 0 {
2813 state.top_idx() - ra - 1
2814 } else {
2815 // C: L->top.p = ci->top.p — correct top
2816 state.set_top(state.ci_top(ci));
2817 n_raw
2818 };
2819 last += n;
2820 if i.test_k() {
2821 let extra = state.proto_code(&cl, pc);
2822 pc += 1;
2823 const MAXARG_C: i32 = (1 << 8) - 1;
2824 last += extra.arg_ax() * (MAXARG_C + 1);
2825 }
2826 // C: if (last > luaH_realasize(h)) luaH_resizearray(L, h, last)
2827 state.table_ensure_array(&t_val, last as usize)?;
2828 // C: for (; n > 0; n--) { val = s2v(ra + n as i32); h->array[last-1] = *val; last--; }
2829 for k in (1..=n).rev() {
2830 let val = state.get_at(ra + k as i32);
2831 state.table_array_set(&t_val, (last - 1) as usize, val.clone())?;
2832 last -= 1;
2833 state.gc_barrier_back(&t_val, &val);
2834 }
2835 }
2836 // ── OP_CLOSURE ─────────────────────────────────────────────
2837 // C: Proto *p = cl->p->p[GETARG_Bx(i)];
2838 // halfProtect(pushclosure(L, p, cl->upvals, base, ra));
2839 // checkGC(L, ra+1);
2840 OpCode::Closure => {
2841 let ra = base + i.arg_a();
2842 let proto_idx = i.arg_bx() as usize;
2843 state.set_ci_savedpc(ci, pc);
2844 state.set_top(state.ci_top(ci));
2845 push_closure(state, proto_idx, ci, base, ra)?;
2846 // checkGC
2847 state.set_ci_savedpc(ci, pc);
2848 state.set_top(ra + 1);
2849 state.gc_cond_step();
2850 trap = state.ci_trap(ci);
2851 }
2852 // ── OP_VARARG ──────────────────────────────────────────────
2853 // C: n = GETARG_C(i)-1; Protect(luaT_getvarargs(L, ci, ra, n));
2854 OpCode::VarArg => {
2855 let ra = base + i.arg_a();
2856 let n = i.arg_c() as i32 - 1;
2857 state.set_ci_savedpc(ci, pc);
2858 state.set_top(state.ci_top(ci));
2859 state.get_varargs(ci, ra, n)?;
2860 trap = state.ci_trap(ci);
2861 }
2862 // ── OP_VARARGPREP ──────────────────────────────────────────
2863 // C: ProtectNT(luaT_adjustvarargs(L, GETARG_A(i), ci, cl->p));
2864 // if (trap) luaD_hookcall(L, ci); L->oldpc = 1;
2865 // updatebase(ci);
2866 OpCode::VarArgPrep => {
2867 let nparams = i.arg_a();
2868 state.set_ci_savedpc(ci, pc);
2869 state.adjust_varargs(ci, nparams, &cl)?;
2870 trap = state.ci_trap(ci);
2871 if trap {
2872 state.hook_call(ci)?;
2873 state.set_oldpc(1);
2874 }
2875 base = state.ci_base(ci);
2876 }
2877 // ── OP_EXTRAARG ────────────────────────────────────────────
2878 // C: lua_assert(0) — should never be executed directly
2879 OpCode::ExtraArg => {
2880 debug_assert!(false, "OP_EXTRAARG executed directly");
2881 }
2882 // Unknown opcode
2883 #[allow(unreachable_patterns)]
2884 _ => {
2885 // TODO(port): unrecognised opcode {:?} — add to match
2886 todo!("unrecognised opcode");
2887 }
2888 } // end match opcode
2889 } // end 'dispatch loop
2890
2891 // ── ret: label ──────────────────────────────────────────────────
2892 // C: ret: if (ci->callstatus & CIST_FRESH) return; else { ci = ci->previous; goto returning; }
2893 if state.ci_is_fresh(ci) {
2894 return Ok(()); // C: return; (end this fresh frame)
2895 } else {
2896 ci = state.ci_previous(ci).expect("ci_previous: not fresh frame must have previous");
2897 // C: goto returning — re-enter 'returning without resetting trap
2898 continue 'returning;
2899 }
2900 } // end 'returning loop
2901 } // end 'startfunc loop
2902}
2903
2904// ─── Local opcode dispatch helpers ───────────────────────────────────────────
2905
2906#[inline(always)]
2907fn number_value(v: LuaValue) -> Option<f64> {
2908 match v {
2909 LuaValue::Float(f) => Some(f),
2910 LuaValue::Int(i) => Some(i as f64),
2911 _ => None,
2912 }
2913}
2914
2915/// C: `op_arith_aux` — try both-int fast path then float fallback.
2916/// Increments `pc` on success (the `pc++` in the C macros).
2917#[allow(dead_code)]
2918#[inline]
2919fn arith_op_aux_rr(
2920 state: &mut LuaState,
2921 ra: StackIdx,
2922 v1: &LuaValue,
2923 v2: &LuaValue,
2924 pc: &mut u32,
2925 iop: fn(i64, i64) -> i64,
2926 fop: fn(f64, f64) -> f64,
2927) {
2928 if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (v1, v2) {
2929 *pc += 1;
2930 state.set_at(ra, LuaValue::Int(iop(*i1, *i2)));
2931 } else {
2932 arith_float_aux(state, ra, v1, v2, pc, fop);
2933 }
2934}
2935
2936/// C: `op_arithf_aux` — float-only arithmetic (no integer path).
2937#[allow(dead_code)]
2938#[inline]
2939fn arith_float_aux(
2940 state: &mut LuaState,
2941 ra: StackIdx,
2942 v1: &LuaValue,
2943 v2: &LuaValue,
2944 pc: &mut u32,
2945 fop: fn(f64, f64) -> f64,
2946) {
2947 // C: tonumberns(v1, n1) && tonumberns(v2, n2)
2948 let n1 = match v1 {
2949 LuaValue::Float(f) => Some(*f),
2950 LuaValue::Int(i) => Some(*i as f64),
2951 _ => None,
2952 };
2953 let n2 = match v2 {
2954 LuaValue::Float(f) => Some(*f),
2955 LuaValue::Int(i) => Some(*i as f64),
2956 _ => None,
2957 };
2958 if let (Some(n1), Some(n2)) = (n1, n2) {
2959 *pc += 1;
2960 state.set_at(ra, LuaValue::Float(fop(n1, n2)));
2961 }
2962}
2963
2964/// C: `op_arith_aux` with fallible integer op (mod / idiv).
2965#[allow(dead_code)]
2966#[inline]
2967fn arith_op_checked(
2968 state: &mut LuaState,
2969 ra: StackIdx,
2970 v1: &LuaValue,
2971 v2: &LuaValue,
2972 pc: &mut u32,
2973 iop: fn(i64, i64) -> Result<i64, LuaError>,
2974 fop: fn(f64, f64) -> f64,
2975) -> Result<(), LuaError> {
2976 if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (v1, v2) {
2977 *pc += 1;
2978 let result = iop(*i1, *i2).map_err(|e| match e {
2979 LuaError::Runtime(LuaValue::Str(s)) => {
2980 crate::debug::prefixed_runtime_pub(state, s.as_bytes().to_vec())
2981 }
2982 other => other,
2983 })?;
2984 state.set_at(ra, LuaValue::Int(result));
2985 } else {
2986 arith_float_aux(state, ra, v1, v2, pc, fop);
2987 }
2988 Ok(())
2989}
2990
2991/// C: `op_bitwiseK` — bitwise op with one integer constant operand.
2992#[allow(dead_code)]
2993#[inline]
2994fn bitwise_op_k(
2995 state: &mut LuaState,
2996 ra: StackIdx,
2997 v1: &LuaValue,
2998 v2: &LuaValue, // must be integer (K constant)
2999 pc: &mut u32,
3000 op: fn(i64, i64) -> i64,
3001) {
3002 let i2 = match v2 {
3003 LuaValue::Int(i) => *i,
3004 _ => return,
3005 };
3006 if let Some(i1) = to_integer_ns(v1, F2Imod::Eq) {
3007 *pc += 1;
3008 state.set_at(ra, LuaValue::Int(op(i1, i2)));
3009 }
3010}
3011
3012/// C: `op_bitwise` — bitwise op with two register operands.
3013#[allow(dead_code)]
3014#[inline]
3015fn bitwise_op_rr(
3016 state: &mut LuaState,
3017 ra: StackIdx,
3018 v1: &LuaValue,
3019 v2: &LuaValue,
3020 pc: &mut u32,
3021 op: fn(i64, i64) -> i64,
3022) {
3023 if let (Some(i1), Some(i2)) = (
3024 to_integer_ns(v1, F2Imod::Eq),
3025 to_integer_ns(v2, F2Imod::Eq),
3026 ) {
3027 *pc += 1;
3028 state.set_at(ra, LuaValue::Int(op(i1, i2)));
3029 }
3030}
3031
3032/// C: `op_bitwise(L, luaV_shiftl)` / `op_bitwise(L, luaV_shiftr)`.
3033/// `right = true` negates `y` for right-shift semantics.
3034#[allow(dead_code)]
3035#[inline]
3036fn bitwise_shift_rr(
3037 state: &mut LuaState,
3038 ra: StackIdx,
3039 v1: &LuaValue,
3040 v2: &LuaValue,
3041 pc: &mut u32,
3042 right: bool,
3043) {
3044 if let (Some(i1), Some(i2)) = (
3045 to_integer_ns(v1, F2Imod::Eq),
3046 to_integer_ns(v2, F2Imod::Eq),
3047 ) {
3048 let y = if right { intop_sub(0, i2) } else { i2 };
3049 *pc += 1;
3050 state.set_at(ra, LuaValue::Int(shiftl(i1, y)));
3051 }
3052}
3053
3054/// Cold half of C's `op_orderI` macro: only reached when the operand is not a
3055/// plain integer/float and a metamethod lookup may be needed.
3056#[cold]
3057#[inline(never)]
3058#[allow(clippy::too_many_arguments)]
3059fn order_imm_slow(
3060 state: &mut LuaState,
3061 ra: StackIdx,
3062 pc: u32,
3063 trap: &mut bool,
3064 ci: CallInfoIdx,
3065 i: Instruction,
3066 im: i64,
3067 inv: bool,
3068 tm: TagMethod,
3069) -> Result<bool, LuaError> {
3070 let ra_v = state.get_at(ra);
3071 let isf = i.arg_c() != 0;
3072 state.set_ci_savedpc(ci, pc);
3073 state.set_top(state.ci_top(ci));
3074 let r = state.call_order_i_tm(&ra_v, im, inv, isf, tm)?;
3075 *trap = state.ci_trap(ci);
3076 Ok(r)
3077}
3078
3079#[inline(always)]
3080fn finish_order_imm_jump(
3081 state: &mut LuaState,
3082 cl: &lua_types::GcRef<lua_types::LuaLClosure>,
3083 pc: &mut u32,
3084 trap: &mut bool,
3085 ci: CallInfoIdx,
3086 i: Instruction,
3087 cond: bool,
3088) {
3089 // C: docondjump()
3090 if (cond as i32) != i.arg_k() {
3091 *pc += 1;
3092 } else {
3093 let next = state.proto_code(&cl, *pc);
3094 *pc = (*pc as i64 + next.arg_s_j() as i64 + 1) as u32;
3095 *trap = state.ci_trap(ci);
3096 }
3097}
3098
3099#[cold]
3100#[inline(never)]
3101fn return0_hook(
3102 state: &mut LuaState,
3103 ci: CallInfoIdx,
3104 base: StackIdx,
3105 i: Instruction,
3106 pc: u32,
3107 trap: &mut bool,
3108) -> Result<(), LuaError> {
3109 let ra = base + i.arg_a();
3110 state.set_top(ra);
3111 state.set_ci_savedpc(ci, pc);
3112 state.poscall(ci, 0)?;
3113 *trap = true;
3114 Ok(())
3115}
3116
3117#[cold]
3118#[inline(never)]
3119fn return1_hook(
3120 state: &mut LuaState,
3121 ci: CallInfoIdx,
3122 base: StackIdx,
3123 i: Instruction,
3124 pc: u32,
3125 trap: &mut bool,
3126) -> Result<(), LuaError> {
3127 let ra = base + i.arg_a();
3128 state.set_top(ra + 1);
3129 state.set_ci_savedpc(ci, pc);
3130 state.poscall(ci, 1)?;
3131 *trap = true;
3132 Ok(())
3133}
3134
3135// ──────────────────────────────────────────────────────────────────────────
3136// PORT STATUS
3137// source: src/lvm.c (1899 lines, 32 functions)
3138// target_crate: lua-vm
3139// confidence: medium
3140// todos: 6
3141// port_notes: 4
3142// unsafe_blocks: 0 (must be 0 outside explicit unsafe-budget crates)
3143// notes: All opcode handlers and helpers translated; LuaState methods
3144// referenced (fast_get, precall, poscall, etc.) are stubs that
3145// Phase B will land. The execute() goto flow is modelled with
3146// labelled Rust loops ('startfunc/'returning/'dispatch).
3147// str_to_number is a stub pending luaO_str2num port (TODO #1).
3148// strcoll replaced with byte-lexicographic order (TODO #2).
3149// order_imm_op uses LuaValue as a stand-in for GcRef<LuaClosure>
3150// (TODO #3). ClosureRef type alias not yet defined (TODO #4-6).
3151// ──────────────────────────────────────────────────────────────────────────