Skip to main content

luau_vm/vm/
mod.rs

1mod entry;
2mod execute;
3
4pub use entry::{PreCallResult, VmCallFrame};
5pub use execute::VmExecution;
6
7use core::cmp::Ordering;
8use core::ptr;
9
10use luau_common::{ByteSlice, flags};
11
12use crate::Table;
13use crate::call::{CallRuntime, ThreadStack};
14use crate::debug::DebugRuntime;
15use crate::gc::GcBarrier;
16use crate::gc::GcObject;
17use crate::handle::RawHandle;
18use crate::handle::sealed::Sealed;
19use crate::metamethod::{MetamethodRuntime, TmEvent};
20use crate::native::NativeCallContext;
21use crate::number::str_to_num;
22use crate::state::ThreadState;
23use crate::string::{MAX_STRING_SIZE, StringRuntime, TString};
24use crate::table::TableRuntime;
25use crate::thread::Thread;
26use crate::types::{
27    LUA_TBOOLEAN, LUA_TCLASS, LUA_TINTEGER, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNUMBER, LUA_TOBJECT,
28    LUA_TSTRING, LUA_TTABLE, LUA_TUSERDATA, LUA_TVECTOR, LUA_VECTOR_SIZE,
29};
30use crate::value::{RAW_TVALUE_NIL, TValue, TValueCursor, nil_object};
31use crate::{VmErrorResult, VmResult};
32
33const MAX_TAG_LOOP: i32 = 100;
34
35/// Unstable raw-value conversion capability.
36///
37/// # Safety
38///
39/// Values and result slots must be live, correctly tagged records belonging
40/// to this thread's VM. Conversion can allocate, mutate stack values, raise an
41/// error, and collect unrooted objects.
42#[allow(
43    clippy::missing_safety_doc,
44    reason = "all methods share the capability-level safety contract"
45)]
46pub trait VmConversions: Sealed {
47    /// `luaV_tonumber`
48    unsafe fn to_number_internal(&self, value: TValue, result: TValue) -> Option<TValue>;
49
50    /// `luaV_tovector`
51    unsafe fn to_vector_internal(
52        &self,
53        value: TValue,
54    ) -> Option<[f32; crate::types::LUA_VECTOR_SIZE]>;
55
56    /// `luaV_tostring`
57    unsafe fn to_string_internal(&self, value: TValue) -> VmErrorResult<i32>;
58}
59
60/// Unstable semantic VM operation capability.
61///
62/// # Safety
63///
64/// All values, tables, constants, and cursors must be live and belong to this
65/// thread's VM. Stack shapes and indices must match each operation; operations
66/// may invoke metamethods, relocate the stack, allocate, collect, or unwind.
67#[allow(
68    clippy::missing_safety_doc,
69    reason = "all methods share the capability-level safety contract"
70)]
71pub trait VmOperations: Sealed {
72    /// `luaV_prepareFORN`
73    unsafe fn prepare_forn(
74        &self,
75        limit: TValueCursor,
76        step: TValueCursor,
77        index: TValueCursor,
78    ) -> VmResult;
79
80    /// `luaV_callTM`
81    unsafe fn call_tm(&self, n_params: i32, result: i32) -> VmResult;
82
83    /// `luaV_lessthan`
84    unsafe fn less_than_internal(&self, left: TValue, right: TValue) -> VmResult<i32>;
85
86    /// `luaV_lessequal`
87    unsafe fn less_equal(&self, left: TValue, right: TValue) -> VmResult<i32>;
88
89    /// `luaV_equalval`
90    unsafe fn equal_value(&self, left: TValue, right: TValue) -> VmResult<i32>;
91
92    /// `luaV_doarithimpl`
93    unsafe fn do_arith_impl(
94        &self,
95        result: TValueCursor,
96        left: TValue,
97        right: TValue,
98        op: TmEvent,
99    ) -> VmResult;
100
101    /// `luaV_dolen`
102    unsafe fn do_len(&self, result: TValueCursor, value: TValue) -> VmResult;
103
104    /// `luaV_gettable`
105    unsafe fn get_table_internal(
106        &self,
107        table: TValue,
108        key: TValue,
109        value: TValueCursor,
110    ) -> VmResult;
111
112    /// `luaV_settable`
113    unsafe fn set_table_internal(&self, table: TValue, key: TValue, value: TValue) -> VmResult;
114
115    /// `luaV_concat`
116    unsafe fn concat_internal(&self, total: i32, last: i32) -> VmResult;
117
118    /// `luaV_getimport`
119    unsafe fn get_import(
120        &self,
121        env: Table,
122        constants: TValueCursor,
123        target: TValueCursor,
124        id: u32,
125        propagate_nil: bool,
126    ) -> VmResult;
127}
128
129/// `luaV_strcmp`
130pub fn string_compare(left: TString, right: TString) -> i32 {
131    if left == right {
132        return 0;
133    }
134
135    let left_bytes = unsafe { left.as_bytes() };
136    let right_bytes = unsafe { right.as_bytes() };
137
138    let left_first = left_bytes.first().copied().unwrap_or(0);
139    let right_first = right_bytes.first().copied().unwrap_or(0);
140    if left_first != right_first {
141        return i32::from(left_first) - i32::from(right_first);
142    }
143
144    match left_bytes.cmp(right_bytes) {
145        Ordering::Less => -1,
146        Ordering::Equal => 0,
147        Ordering::Greater => 1,
148    }
149}
150
151/// `get_compTM`
152/// Finds a shared comparison metamethod for two metatables.
153///
154/// # Safety
155///
156/// `thread` must be live, both metatables must be live tables in that thread's
157/// VM, and their metamethod caches must be coherent with table mutation.
158pub unsafe fn get_comp_tm(
159    thread: &Thread,
160    left: Option<Table>,
161    right: Option<Table>,
162    event: TmEvent,
163) -> Option<TValue> {
164    let left = left?;
165    let tm_left = unsafe { thread.fast_tm(Some(left), event) }?;
166
167    if right == Some(left) {
168        return Some(tm_left);
169    }
170
171    let tm_right = unsafe { thread.fast_tm(right, event) }?;
172    tm_left.raw_equal(tm_right).then_some(tm_left)
173}
174
175impl Thread {
176    /// `callTMres`
177    unsafe fn call_tm_result(
178        &self,
179        result: TValueCursor,
180        function: TValue,
181        p1: TValue,
182        p2: TValue,
183    ) -> VmResult<TValueCursor> {
184        unsafe {
185            let result_offset = self.save_stack(result);
186            let top = self.stack_top();
187            let top_offset = self.save_stack(top);
188
189            top.value_unchecked().set_obj(function);
190            top.add(1).value_unchecked().set_obj(p1);
191            top.add(2).value_unchecked().set_obj(p2);
192            self.check_stack_internal(3)?;
193
194            let top = self.restore_stack(top_offset);
195            self.set_stack_top(top.add(3));
196            self.call_internal(top, 1)?;
197
198            let result = self.restore_stack(result_offset);
199            let top = self.stack_top().sub(1);
200            self.set_stack_top(top);
201            result.value_unchecked().set_obj(top.value_unchecked());
202            Ok(result)
203        }
204    }
205
206    /// `callTM`
207    unsafe fn call_tm3(&self, function: TValue, p1: TValue, p2: TValue, p3: TValue) -> VmResult {
208        unsafe {
209            let top = self.stack_top();
210            let top_offset = self.save_stack(top);
211
212            top.value_unchecked().set_obj(function);
213            top.add(1).value_unchecked().set_obj(p1);
214            top.add(2).value_unchecked().set_obj(p2);
215            top.add(3).value_unchecked().set_obj(p3);
216            self.check_stack_internal(4)?;
217
218            let top = self.restore_stack(top_offset);
219            self.set_stack_top(top.add(4));
220            self.call_internal(top, 0)
221        }
222    }
223
224    /// `callorderTM`
225    unsafe fn call_order_tm(
226        &self,
227        left: TValue,
228        right: TValue,
229        event: TmEvent,
230        error: bool,
231    ) -> VmResult<i32> {
232        unsafe {
233            let Some(tm_left) = self.get_tm_by_obj(left, event) else {
234                if error {
235                    return self.order_error(left, right, event).map_err(Into::into);
236                }
237                return Ok(-1);
238            };
239
240            let Some(tm_right) = self.get_tm_by_obj(right, event) else {
241                if error {
242                    return self.order_error(left, right, event).map_err(Into::into);
243                }
244                return Ok(-1);
245            };
246
247            if !tm_left.raw_equal(tm_right) {
248                if error {
249                    return self.order_error(left, right, event).map_err(Into::into);
250                }
251                return Ok(-1);
252            }
253
254            let result = self.call_tm_result(self.stack_top(), tm_left, left, right)?;
255            Ok(i32::from(!result.value_unchecked().is_false()))
256        }
257    }
258
259    /// `callbinTM`
260    unsafe fn call_bin_tm(
261        &self,
262        left: TValue,
263        right: TValue,
264        result: TValueCursor,
265        event: TmEvent,
266    ) -> VmResult<bool> {
267        unsafe {
268            let tm = self
269                .get_tm_by_obj(left, event)
270                .or_else(|| self.get_tm_by_obj(right, event));
271
272            let Some(tm) = tm else {
273                return Ok(false);
274            };
275
276            self.call_tm_result(result, tm, left, right)?;
277            Ok(true)
278        }
279    }
280
281    /// `tostringslot`
282    unsafe fn coerce_string_slot(&self, object: TValue) -> VmResult<bool> {
283        Ok(object.is_string() || unsafe { self.to_string_internal(object)? != 0 })
284    }
285}
286
287unsafe fn set_vector_result(result: TValueCursor, value: [f32; LUA_VECTOR_SIZE]) {
288    #[cfg(not(feature = "vector4"))]
289    unsafe {
290        result
291            .value_unchecked()
292            .set_vector([value[0], value[1], value[2]]);
293    }
294
295    #[cfg(feature = "vector4")]
296    unsafe {
297        result
298            .value_unchecked()
299            .set_vector([value[0], value[1], value[2], value[3]]);
300    }
301}
302
303impl Thread {
304    /// `luaV_callTM`
305    unsafe fn call_tm_operation(&self, n_params: i32, result: i32) -> VmResult {
306        unsafe {
307            self.increment_native_call_depth();
308            if self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
309                >= crate::thread::LUAI_MAX_NATIVE_CALLS
310            {
311                self.check_c_stack()?;
312            }
313
314            self.check_stack_internal(crate::thread::LUA_MIN_STACK as i32)?;
315
316            let top = self.stack_top();
317            let function = top.sub(n_params as usize + 1);
318            let closure = function.value_unchecked().closure_value();
319            let call_info = self.incr_ci()?.call_info_unchecked();
320
321            call_info.init_call(
322                function,
323                top.add(crate::thread::LUA_MIN_STACK),
324                i32::from(result >= 0),
325                closure.proto(),
326            );
327
328            debug_assert!(call_info.top() <= self.stack_last());
329
330            debug_assert!(function.value_unchecked().is_function());
331            debug_assert!(closure.is_native());
332
333            self.set_stack_base(function.add(1));
334            debug_assert!(self.stack_top() == self.stack_base().add(n_params as usize));
335
336            let function = closure.native_data().function.unwrap_unchecked();
337            let produced = function(NativeCallContext::new(self))?;
338
339            let parent_cursor = self.current_call_info_cursor().sub(1);
340            let parent = parent_cursor.call_info_unchecked();
341
342            if result >= 0 {
343                let destination = parent.base().add(result as usize);
344                if produced > 0 {
345                    let produced_value = self.stack_top().sub(produced);
346                    destination
347                        .value_unchecked()
348                        .set_obj(produced_value.value_unchecked());
349                } else {
350                    destination.value_unchecked().set_nil();
351                }
352            }
353
354            self.decrement_native_call_depth();
355            self.restore_call_frame(parent_cursor, parent.top());
356        }
357        Ok(())
358    }
359
360    /// `luaV_prepareFORN`
361    unsafe fn prepare_forn_operation(
362        &self,
363        limit: TValueCursor,
364        step: TValueCursor,
365        index: TValueCursor,
366    ) -> VmResult {
367        unsafe {
368            if !index.value_unchecked().is_number()
369                && self
370                    .to_number_internal(index.value_unchecked(), index.value_unchecked())
371                    .is_none()
372            {
373                return self
374                    .for_error(index.value_unchecked(), "initial value")
375                    .map_err(Into::into);
376            }
377
378            if !limit.value_unchecked().is_number()
379                && self
380                    .to_number_internal(limit.value_unchecked(), limit.value_unchecked())
381                    .is_none()
382            {
383                return self
384                    .for_error(limit.value_unchecked(), "limit")
385                    .map_err(Into::into);
386            }
387
388            if !step.value_unchecked().is_number()
389                && self
390                    .to_number_internal(step.value_unchecked(), step.value_unchecked())
391                    .is_none()
392            {
393                return self
394                    .for_error(step.value_unchecked(), "step")
395                    .map_err(Into::into);
396            }
397        }
398        Ok(())
399    }
400}
401impl VmConversions for Thread {
402    /// `luaV_tonumber`
403    unsafe fn to_number_internal(&self, value: TValue, result: TValue) -> Option<TValue> {
404        if value.is_number() {
405            return Some(value);
406        }
407
408        if value.is_string() {
409            let string = value.string_value();
410            if let Some(number) = str_to_num(unsafe { string.as_bytes() }) {
411                result.set_number(number);
412                return Some(result);
413            }
414        }
415
416        None
417    }
418
419    /// `luaV_tovector`
420    unsafe fn to_vector_internal(&self, value: TValue) -> Option<[f32; LUA_VECTOR_SIZE]> {
421        if value.is_vector() {
422            Some(value.vector_value())
423        } else {
424            None
425        }
426    }
427
428    /// `luaV_tostring`
429    unsafe fn to_string_internal(&self, value: TValue) -> VmErrorResult<i32> {
430        if !value.is_number() {
431            return Ok(0);
432        }
433
434        let mut digits = [0u8; crate::number::LUAI_MAXNUM2STR];
435        let len = crate::number::num_to_str(&mut digits, value.number_value());
436        let string = unsafe { self.intern_string(digits[..len].as_bstr())? };
437        value.set_string_value(string);
438        Ok(1)
439    }
440}
441
442impl VmOperations for Thread {
443    unsafe fn prepare_forn(
444        &self,
445        limit: TValueCursor,
446        step: TValueCursor,
447        index: TValueCursor,
448    ) -> VmResult {
449        unsafe { self.prepare_forn_operation(limit, step, index) }
450    }
451
452    unsafe fn call_tm(&self, n_params: i32, result: i32) -> VmResult {
453        unsafe { self.call_tm_operation(n_params, result) }
454    }
455
456    /// `luaV_lessthan`
457    unsafe fn less_than_internal(&self, left: TValue, right: TValue) -> VmResult<i32> {
458        if left.tt() != right.tt() {
459            unsafe {
460                self.order_error(left, right, TmEvent::Lt)
461                    .map_err(Into::into)
462            }
463        } else if left.is_number() {
464            Ok(i32::from(crate::number::num_lt(
465                left.number_value(),
466                right.number_value(),
467            )))
468        } else if left.is_string() {
469            Ok(i32::from(
470                string_compare(left.string_value(), right.string_value()) < 0,
471            ))
472        } else {
473            unsafe { self.call_order_tm(left, right, TmEvent::Lt, true) }
474        }
475    }
476
477    /// `luaV_lessequal`
478    unsafe fn less_equal(&self, left: TValue, right: TValue) -> VmResult<i32> {
479        unsafe {
480            if left.tt() != right.tt() {
481                self.order_error(left, right, TmEvent::Le)
482                    .map_err(Into::into)
483            } else if left.is_number() {
484                Ok(i32::from(crate::number::num_le(
485                    left.number_value(),
486                    right.number_value(),
487                )))
488            } else if left.is_string() {
489                Ok(i32::from(
490                    string_compare(left.string_value(), right.string_value()) <= 0,
491                ))
492            } else {
493                let result = self.call_order_tm(left, right, TmEvent::Le, false)?;
494                if result != -1 {
495                    Ok(result)
496                } else {
497                    let fallback = self.call_order_tm(right, left, TmEvent::Lt, false)?;
498                    if fallback == -1 {
499                        return self
500                            .order_error(left, right, TmEvent::Le)
501                            .map_err(Into::into);
502                    }
503
504                    Ok(i32::from(fallback == 0))
505                }
506            }
507        }
508    }
509
510    /// `luaV_equalval`
511    unsafe fn equal_value(&self, left: TValue, right: TValue) -> VmResult<i32> {
512        debug_assert_eq!(left.tt(), right.tt());
513
514        unsafe {
515            let tm = match left.tt() {
516                x if x == LUA_TNIL => return Ok(1),
517                x if x == LUA_TNUMBER => {
518                    return Ok(i32::from(crate::number::num_eq(
519                        left.number_value(),
520                        right.number_value(),
521                    )));
522                }
523                x if x == LUA_TINTEGER => {
524                    return Ok(i32::from(crate::number::int_eq(
525                        left.integer_value(),
526                        right.integer_value(),
527                    )));
528                }
529                x if x == LUA_TVECTOR => {
530                    return Ok(i32::from(crate::number::vec_eq(
531                        &left.vector_value(),
532                        &right.vector_value(),
533                    )));
534                }
535                x if x == LUA_TBOOLEAN => {
536                    return Ok(i32::from(left.boolean_value() == right.boolean_value()));
537                }
538                x if x == LUA_TLIGHTUSERDATA => {
539                    return Ok(i32::from(
540                        left.pointer_value() == right.pointer_value()
541                            && left.light_userdata_tag() == right.light_userdata_tag(),
542                    ));
543                }
544                x if x == LUA_TUSERDATA => {
545                    let left_userdata = left.userdata_value();
546                    let right_userdata = right.userdata_value();
547                    let tm = get_comp_tm(
548                        self,
549                        left_userdata.metatable(),
550                        right_userdata.metatable(),
551                        TmEvent::Eq,
552                    );
553
554                    if tm.is_none() {
555                        return Ok(i32::from(left_userdata == right_userdata));
556                    }
557
558                    tm.unwrap_unchecked()
559                }
560                x if x == LUA_TCLASS => {
561                    return Ok(i32::from(left.class_value() == right.class_value()));
562                }
563                x if x == LUA_TOBJECT => {
564                    let left_object = left.object_value();
565                    let right_object = right.object_value();
566
567                    if left_object.class() != right_object.class() {
568                        return Ok(0);
569                    }
570
571                    let Some(tm) = self.get_tm_by_obj(left, TmEvent::Eq) else {
572                        return Ok(i32::from(left_object == right_object));
573                    };
574
575                    tm
576                }
577                x if x == LUA_TTABLE => {
578                    let left_table = left.table_value();
579                    let right_table = right.table_value();
580                    let tm = get_comp_tm(
581                        self,
582                        left_table.metatable(),
583                        right_table.metatable(),
584                        TmEvent::Eq,
585                    );
586
587                    if tm.is_none() {
588                        return Ok(i32::from(left_table == right_table));
589                    }
590
591                    tm.unwrap_unchecked()
592                }
593                _ => return Ok(i32::from(left.gc_value() == right.gc_value())),
594            };
595
596            let result = self.call_tm_result(self.stack_top(), tm, left, right)?;
597            Ok(i32::from(!result.value_unchecked().is_false()))
598        }
599    }
600
601    /// `luaV_doarithimpl`
602    unsafe fn do_arith_impl(
603        &self,
604        result: TValueCursor,
605        left: TValue,
606        right: TValue,
607        op: TmEvent,
608    ) -> VmResult {
609        let mut left_number_storage = RAW_TVALUE_NIL;
610        let mut right_number_storage = RAW_TVALUE_NIL;
611        unsafe {
612            let (left_number, right_number) = (
613                TValue::from_mut(&mut left_number_storage),
614                TValue::from_mut(&mut right_number_storage),
615            );
616
617            let left_vector = left.is_vector().then(|| left.vector_value());
618            let right_vector = right.is_vector().then(|| right.vector_value());
619
620            if let (Some(left_vector), Some(right_vector)) = (left_vector, right_vector) {
621                match op {
622                    TmEvent::Add | TmEvent::Sub | TmEvent::Mul | TmEvent::Div | TmEvent::IDiv => {
623                        let mut value = [0.0; LUA_VECTOR_SIZE];
624                        for index in 0..LUA_VECTOR_SIZE {
625                            value[index] = match op {
626                                TmEvent::Add => left_vector[index] + right_vector[index],
627                                TmEvent::Sub => left_vector[index] - right_vector[index],
628                                TmEvent::Mul => left_vector[index] * right_vector[index],
629                                TmEvent::Div => left_vector[index] / right_vector[index],
630                                TmEvent::IDiv => crate::number::num_idiv(
631                                    left_vector[index] as f64,
632                                    right_vector[index] as f64,
633                                ) as f32,
634                                _ => unreachable!(),
635                            };
636                        }
637                        set_vector_result(result, value);
638                        return Ok(());
639                    }
640                    TmEvent::Unm => {
641                        let mut value = [0.0; LUA_VECTOR_SIZE];
642                        for index in 0..LUA_VECTOR_SIZE {
643                            value[index] = -left_vector[index];
644                        }
645                        set_vector_result(result, value);
646                        return Ok(());
647                    }
648                    _ => {}
649                }
650            } else if let Some(left_vector) = left_vector {
651                if let Some(right_number) = self
652                    .to_number_internal(right, right_number)
653                    .map(|n| n.number_value() as f32)
654                {
655                    match op {
656                        TmEvent::Mul | TmEvent::Div | TmEvent::IDiv => {
657                            let mut value = [0.0; LUA_VECTOR_SIZE];
658                            for index in 0..LUA_VECTOR_SIZE {
659                                value[index] = match op {
660                                    TmEvent::Mul => left_vector[index] * right_number,
661                                    TmEvent::Div => left_vector[index] / right_number,
662                                    TmEvent::IDiv => crate::number::num_idiv(
663                                        left_vector[index] as f64,
664                                        right_number as f64,
665                                    ) as f32,
666                                    _ => unreachable!(),
667                                };
668                            }
669                            set_vector_result(result, value);
670                            return Ok(());
671                        }
672                        _ => {}
673                    }
674                }
675            } else if let Some(right_vector) = right_vector
676                && let Some(left_number) = self
677                    .to_number_internal(left, left_number)
678                    .map(|n| n.number_value() as f32)
679            {
680                match op {
681                    TmEvent::Mul | TmEvent::Div | TmEvent::IDiv => {
682                        let mut value = [0.0; LUA_VECTOR_SIZE];
683                        for index in 0..LUA_VECTOR_SIZE {
684                            value[index] = match op {
685                                TmEvent::Mul => left_number * right_vector[index],
686                                TmEvent::Div => left_number / right_vector[index],
687                                TmEvent::IDiv => crate::number::num_idiv(
688                                    left_number as f64,
689                                    right_vector[index] as f64,
690                                ) as f32,
691                                _ => unreachable!(),
692                            };
693                        }
694                        set_vector_result(result, value);
695                        return Ok(());
696                    }
697                    _ => {}
698                }
699            }
700
701            if let (Some(left_number), Some(right_number)) = (
702                self.to_number_internal(left, left_number)
703                    .map(|n| n.number_value()),
704                self.to_number_internal(right, right_number)
705                    .map(|n| n.number_value()),
706            ) {
707                result.value_unchecked().set_number(match op {
708                    TmEvent::Add => crate::number::num_add(left_number, right_number),
709                    TmEvent::Sub => crate::number::num_sub(left_number, right_number),
710                    TmEvent::Mul => crate::number::num_mul(left_number, right_number),
711                    TmEvent::Div => crate::number::num_div(left_number, right_number),
712                    TmEvent::IDiv => crate::number::num_idiv(left_number, right_number),
713                    TmEvent::Mod => crate::number::num_mod(left_number, right_number),
714                    TmEvent::Pow => crate::number::num_pow(left_number, right_number),
715                    TmEvent::Unm => crate::number::num_unm(left_number),
716                    _ => unreachable!("invalid arithmetic metamethod {:?}", op),
717                });
718                return Ok(());
719            }
720
721            if !self.call_bin_tm(left, right, result, op)? {
722                return self.arith_error(left, right, op).map_err(Into::into);
723            }
724        }
725        Ok(())
726    }
727
728    /// `luaV_dolen`
729    unsafe fn do_len(&self, result: TValueCursor, value: TValue) -> VmResult {
730        unsafe {
731            let tm = match value.tt() {
732                x if x == LUA_TTABLE => {
733                    let table = value.table_value();
734                    match self.fast_tm(table.metatable(), TmEvent::Len) {
735                        Some(tm) => tm,
736                        None => {
737                            result.value_unchecked().set_number(table.getn() as f64);
738                            return Ok(());
739                        }
740                    }
741                }
742                x if x == LUA_TSTRING => {
743                    result.value_unchecked().set_number(
744                        value
745                            .string_value()
746                            .as_ptr()
747                            .as_ref()
748                            .unwrap_unchecked()
749                            .len as f64,
750                    );
751                    return Ok(());
752                }
753                _ => match self.get_tm_by_obj(value, TmEvent::Len) {
754                    Some(tm) => tm,
755                    None => return self.type_error(value, "get length of").map_err(Into::into),
756                },
757            };
758
759            let result = self.call_tm_result(result, tm, value, nil_object())?;
760            if !result.value_unchecked().is_number() {
761                return crate::run_error!(self, "'__len' must return a number").map_err(Into::into);
762            }
763        }
764        Ok(())
765    }
766
767    /// `luaV_gettable`
768    unsafe fn get_table_internal(
769        &self,
770        table: TValue,
771        key: TValue,
772        value: TValueCursor,
773    ) -> VmResult {
774        let mut table = table;
775
776        unsafe {
777            for _ in 0..MAX_TAG_LOOP {
778                let tm;
779                if table.is_table() {
780                    let hash = table.table_value();
781                    let result = hash.get(key);
782
783                    if result != nil_object() {
784                        self.set_cached_slot(hash.value_slot_unchecked(result));
785                    }
786
787                    if !result.is_nil() || {
788                        tm = self.fast_tm(hash.metatable(), TmEvent::Index);
789                        tm.is_none()
790                    } {
791                        value.value_unchecked().set_obj(result);
792                        return Ok(());
793                    }
794                } else if flags::DebugLuauUserDefinedClassesRuntime.get() && table.is_object() {
795                    let instance = table.object_value();
796                    let Some(member) = instance.lookup_member(key) else {
797                        return self.missing_member_error(table, key).map_err(Into::into);
798                    };
799                    value.value_unchecked().set_obj(member);
800                    return Ok(());
801                } else if flags::DebugLuauUserDefinedClassesRuntime.get() && table.is_class() {
802                    let class = table.class_value();
803                    let Some(static_member) = class.lookup_static_member(key) else {
804                        return self.missing_member_error(table, key).map_err(Into::into);
805                    };
806                    value.value_unchecked().set_obj(static_member);
807                    return Ok(());
808                } else {
809                    let Some(candidate) = self.get_tm_by_obj(table, TmEvent::Index) else {
810                        return self.index_error(table, key).map_err(Into::into);
811                    };
812                    tm = Some(candidate);
813                }
814
815                let tm = tm.unwrap_unchecked();
816                if tm.is_function() {
817                    self.call_tm_result(value, tm, table, key)?;
818                    return Ok(());
819                }
820
821                table = tm;
822            }
823
824            crate::run_error!(self, "'__index' chain too long; possible loop").map_err(Into::into)
825        }
826    }
827
828    /// `luaV_settable`
829    unsafe fn set_table_internal(&self, table: TValue, key: TValue, value: TValue) -> VmResult {
830        let mut temp_storage = RAW_TVALUE_NIL;
831        let temp = unsafe { TValue::from_mut(&mut temp_storage) };
832        let mut table = table;
833
834        unsafe {
835            for _ in 0..MAX_TAG_LOOP {
836                let tm;
837                if table.is_table() {
838                    let hash = table.table_value();
839                    let old_value = hash.get(key);
840
841                    if !old_value.is_nil() || {
842                        tm = self.fast_tm(hash.metatable(), TmEvent::NewIndex);
843                        tm.is_none()
844                    } {
845                        if hash.as_ptr().as_ref().unwrap_unchecked().readonly != 0 {
846                            return self.readonly_error().map_err(Into::into);
847                        }
848
849                        let new_value = self.set_slot(hash, old_value, key)?;
850                        self.set_cached_slot(hash.value_slot_unchecked(new_value));
851                        new_value.set_obj(value);
852
853                        if value.is_collectable() {
854                            let table_object: GcObject = hash.into();
855                            let child = value.gc_value();
856                            if table_object.is_black() && child.is_white() {
857                                self.barrier_table(hash, child);
858                            }
859                        }
860                        return Ok(());
861                    }
862                } else if flags::DebugLuauUserDefinedClassesRuntime.get() && table.is_object() {
863                    let instance = table.object_value();
864                    let class = instance.class();
865                    let Some(offset) = class.member_offset(key) else {
866                        return self.missing_member_error(table, key).map_err(Into::into);
867                    };
868                    debug_assert!(instance.offset_in_bounds(offset));
869                    if offset
870                        >= class
871                            .as_ptr()
872                            .as_ref()
873                            .unwrap_unchecked()
874                            .number_of_instance_members
875                    {
876                        return self.index_error(table, key).map_err(Into::into);
877                    }
878
879                    let member = instance.lookup_member_at_offset(offset);
880                    member.set_obj(value);
881
882                    if value.is_collectable() {
883                        let object: GcObject = instance.into();
884                        let child = value.gc_value();
885                        if object.is_black() && child.is_white() {
886                            self.barrier_forward(object, child);
887                        }
888                    }
889                    return Ok(());
890                } else {
891                    let Some(candidate) = self.get_tm_by_obj(table, TmEvent::NewIndex) else {
892                        return self.index_error(table, key).map_err(Into::into);
893                    };
894                    tm = Some(candidate);
895                }
896
897                let tm = tm.unwrap_unchecked();
898                if tm.is_function() {
899                    self.call_tm3(tm, table, key, value)?;
900                    return Ok(());
901                }
902
903                temp.set_obj(tm);
904                table = temp;
905            }
906
907            crate::run_error!(self, "'__newindex' chain too long; possible loop")
908                .map_err(Into::into)
909        }
910    }
911
912    /// `luaV_concat`
913    unsafe fn concat_internal(&self, mut total: i32, mut last: i32) -> VmResult {
914        unsafe {
915            while total > 1 {
916                let top = self.stack_base().add(last as usize + 1);
917                let mut count = 2;
918
919                if !(top.sub(2).value_unchecked().is_string()
920                    || top.sub(2).value_unchecked().is_number())
921                    || !self.coerce_string_slot(top.sub(1).value_unchecked())?
922                {
923                    if !self.call_bin_tm(
924                        top.sub(2).value_unchecked(),
925                        top.sub(1).value_unchecked(),
926                        top.sub(2),
927                        TmEvent::Concat,
928                    )? {
929                        return self
930                            .concat_error(
931                                top.sub(2).value_unchecked(),
932                                top.sub(1).value_unchecked(),
933                            )
934                            .map_err(Into::into);
935                    }
936                } else if top
937                    .sub(1)
938                    .value_unchecked()
939                    .string_value()
940                    .as_ptr()
941                    .as_ref()
942                    .unwrap_unchecked()
943                    .len
944                    == 0
945                {
946                    let _ = self.coerce_string_slot(top.sub(2).value_unchecked())?;
947                } else {
948                    let mut total_len = top
949                        .sub(1)
950                        .value_unchecked()
951                        .string_value()
952                        .as_ptr()
953                        .as_ref()
954                        .unwrap_unchecked()
955                        .len as usize;
956
957                    count = 1;
958                    while count < total {
959                        let slot = top.sub(count as usize + 1);
960                        if !self.coerce_string_slot(slot.value_unchecked())? {
961                            break;
962                        }
963
964                        let len = slot
965                            .value_unchecked()
966                            .string_value()
967                            .as_ptr()
968                            .as_ref()
969                            .unwrap_unchecked()
970                            .len as usize;
971                        if len > MAX_STRING_SIZE - total_len {
972                            return crate::run_error!(self, "string length overflow")
973                                .map_err(Into::into);
974                        }
975                        total_len += len;
976                        count += 1;
977                    }
978
979                    let mut local_buf = [0u8; crate::thread::LUA_BUFFER_SIZE];
980                    let (buffer, heap_string) = if total_len < crate::thread::LUA_BUFFER_SIZE {
981                        (local_buf.as_mut_ptr(), None)
982                    } else {
983                        let string = self.buffer_start(total_len)?;
984                        (string.data_mut_ptr(), Some(string))
985                    };
986
987                    let mut write = 0usize;
988                    for index in (1..=count).rev() {
989                        let string = top.sub(index as usize).value_unchecked().string_value();
990                        let bytes = string.as_bytes();
991                        ptr::copy_nonoverlapping(bytes.as_ptr(), buffer.add(write), bytes.len());
992                        write += bytes.len();
993                    }
994
995                    let dest = top.sub(count as usize);
996                    let result = if let Some(string) = heap_string {
997                        self.buffer_finish(string)?
998                    } else {
999                        self.intern_string(
1000                            core::slice::from_raw_parts(buffer, total_len).as_bstr(),
1001                        )?
1002                    };
1003
1004                    dest.value_unchecked().set_string_value(result);
1005                }
1006
1007                total -= count - 1;
1008                last -= count - 1;
1009            }
1010        }
1011        Ok(())
1012    }
1013
1014    /// `luaV_getimport`
1015    unsafe fn get_import(
1016        &self,
1017        env: Table,
1018        constants: TValueCursor,
1019        target: TValueCursor,
1020        id: u32,
1021        propagate_nil: bool,
1022    ) -> VmResult {
1023        let count = (id >> 30) as i32;
1024        debug_assert!(count > 0);
1025
1026        let id0 = ((id >> 20) & 1023) as usize;
1027        let id1 = ((id >> 10) & 1023) as usize;
1028        let id2 = (id & 1023) as usize;
1029
1030        let mut global_storage = RAW_TVALUE_NIL;
1031        unsafe {
1032            let result_offset = self.save_stack(target);
1033            let global = TValue::from_mut(&mut global_storage);
1034            global.set_table_value(env);
1035            self.get_table_internal(global, constants.add(id0).value_unchecked(), target)?;
1036
1037            if count < 2 {
1038                return Ok(());
1039            }
1040
1041            let target = self.restore_stack(result_offset);
1042            if !propagate_nil || !target.value_unchecked().is_nil() {
1043                self.get_table_internal(
1044                    target.value_unchecked(),
1045                    constants.add(id1).value_unchecked(),
1046                    target,
1047                )?;
1048            }
1049
1050            if count < 3 {
1051                return Ok(());
1052            }
1053
1054            let target = self.restore_stack(result_offset);
1055            if !propagate_nil || !target.value_unchecked().is_nil() {
1056                self.get_table_internal(
1057                    target.value_unchecked(),
1058                    constants.add(id2).value_unchecked(),
1059                    target,
1060                )?;
1061            }
1062        }
1063        Ok(())
1064    }
1065}