Skip to main content

luau_vm/
class.rs

1use core::mem::size_of;
2use core::ptr::{self, NonNull};
3
4use luau_common::{ByteSlice, flags};
5
6use crate::call::ThreadStack;
7use crate::debug::DebugRuntime;
8use crate::function::{FunctionRuntime, RawNativeClosure};
9use crate::gc::GcBarrier;
10use crate::gc::{GcObject, RawGcObject};
11use crate::handle::RawHandle;
12use crate::handle::sealed::Sealed;
13use crate::memory::{LuaPage, MemoryRuntime};
14use crate::metamethod::{TM_N, TmEvent};
15use crate::native::{NativeCallContext, NativeCallResult};
16use crate::state::ThreadState;
17use crate::string::{RawTString, StringRuntime, TString};
18use crate::table::{RawLuaTable, TableRuntime};
19use crate::thread::Thread;
20use crate::types::{LUA_TCLASS, LUA_TOBJECT};
21use crate::value::{RAW_TVALUE_NIL, RawTValue, TValue};
22use crate::vm::VmOperations;
23use crate::{Table, VmErrorResult};
24
25#[repr(C)]
26pub struct RawLuauClass {
27    pub tt: u8,
28    pub marked: u8,
29    pub memcat: u8,
30    pub gc_list: *mut RawGcObject,
31    pub name: *mut RawTString,
32    pub static_members: *mut RawTValue,
33    pub members_to_offset: *mut RawLuaTable,
34    pub offset_to_member: *mut *mut RawTString,
35    pub metatable: *mut RawLuaTable,
36    pub instance_metatable: *mut RawLuaTable,
37    pub number_of_instance_members: u32,
38    pub number_of_all_members: u32,
39}
40
41#[repr(C)]
42pub struct RawLuauObject {
43    pub tt: u8,
44    pub marked: u8,
45    pub memcat: u8,
46    pub gc_list: *mut RawGcObject,
47    pub class: *mut RawLuauClass,
48    pub number_of_members: u32,
49    pub members: *mut RawTValue,
50}
51
52#[derive(Clone, Copy, PartialEq, Eq)]
53#[repr(transparent)]
54/// Non-owning identity of a VM class record.
55///
56/// # Safety model for unsafe methods
57///
58/// The class and every referenced handle must be live and belong to the same
59/// VM. Member indices and offsets must be in bounds. Mutation must preserve
60/// class layout, metatable cache, and GC barrier invariants.
61pub struct Class {
62    pub(crate) raw: NonNull<RawLuauClass>,
63}
64
65/// Unstable class/object allocation capability.
66///
67/// # Safety
68///
69/// Handles and member arrays must be live, correctly tagged records from the
70/// same VM. Counts, offsets, allocation sizes, and deallocation pages must
71/// match the corresponding class or object layout, with GC barriers preserved.
72#[allow(
73    clippy::missing_safety_doc,
74    reason = "all methods share the capability-level safety contract"
75)]
76pub trait ClassRuntime: Sealed {
77    /// `luaR_newblankclass`
78    unsafe fn new_blank_class(&self, name: TString) -> VmErrorResult<Class>;
79
80    /// `luaR_addclassmetatable`
81    unsafe fn add_class_metatable(&self, class: Class) -> VmErrorResult;
82
83    /// `luaR_newclass`
84    unsafe fn new_class(
85        &self,
86        name: TString,
87        members_to_offset: Table,
88        offset_to_member: *mut TString,
89        number_of_instance_members: u32,
90        number_of_static_members: u32,
91    ) -> VmErrorResult<Class>;
92
93    /// `luaR_inheritclass`
94    unsafe fn inherit_class(&self, child: Class, parent: Class) -> VmErrorResult<Class>;
95
96    /// `luaR_freeclass`
97    unsafe fn free_class(&self, class: Class, page: LuaPage);
98
99    /// `luaR_freeobject`
100    unsafe fn free_object(&self, object: Object, page: LuaPage);
101
102    /// `luaR_createobject`
103    unsafe fn create_object(&self) -> NativeCallResult;
104}
105
106#[allow(
107    clippy::missing_safety_doc,
108    reason = "Class's shared raw-handle contract is documented on Class"
109)]
110impl Class {
111    pub const unsafe fn from_raw(raw: NonNull<RawLuauClass>) -> Self {
112        Self { raw }
113    }
114
115    pub unsafe fn name(&self) -> TString {
116        unsafe {
117            TString::from_raw(NonNull::new_unchecked(
118                self.as_ptr().as_ref().unwrap_unchecked().name,
119            ))
120        }
121    }
122
123    pub unsafe fn members_to_offset(&self) -> Table {
124        unsafe {
125            Table::from_raw(NonNull::new_unchecked(
126                self.as_ptr().as_ref().unwrap_unchecked().members_to_offset,
127            ))
128        }
129    }
130
131    pub unsafe fn offset_to_member(&self, index: usize) -> TString {
132        unsafe {
133            TString::from_raw(NonNull::new_unchecked(
134                *self
135                    .as_ptr()
136                    .as_ref()
137                    .unwrap_unchecked()
138                    .offset_to_member
139                    .add(index),
140            ))
141        }
142    }
143
144    pub unsafe fn static_member(&self, index: usize) -> TValue {
145        unsafe {
146            TValue::from_raw(NonNull::new_unchecked(
147                self.as_ptr()
148                    .as_ref()
149                    .unwrap_unchecked()
150                    .static_members
151                    .add(index),
152            ))
153        }
154    }
155
156    pub fn static_member_count(&self) -> usize {
157        let class = unsafe { self.as_ptr().as_ref().unwrap_unchecked() };
158        (class.number_of_all_members - class.number_of_instance_members) as usize
159    }
160
161    pub fn member_count(&self) -> usize {
162        unsafe {
163            self.as_ptr()
164                .as_ref()
165                .unwrap_unchecked()
166                .number_of_all_members as usize
167        }
168    }
169
170    pub unsafe fn allocation_size(&self) -> usize {
171        size_of::<RawLuauClass>()
172            + self.static_member_count() * size_of::<RawTValue>()
173            + self.member_count() * size_of::<*mut RawTString>()
174    }
175
176    pub unsafe fn gc_list(&self) -> Option<GcObject> {
177        unsafe {
178            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gc_list)
179                .map(|raw| GcObject::from_raw(raw))
180        }
181    }
182
183    pub unsafe fn set_gc_list(&self, gc_list: Option<GcObject>) {
184        unsafe {
185            self.as_ptr().as_mut().unwrap_unchecked().gc_list =
186                gc_list.map_or(ptr::null_mut(), |object| object.as_ptr());
187        }
188    }
189
190    pub unsafe fn metatable(&self) -> Option<Table> {
191        unsafe {
192            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().metatable)
193                .map(|raw| Table::from_raw(raw))
194        }
195    }
196
197    pub unsafe fn set_metatable(&self, metatable: Option<Table>) {
198        unsafe {
199            self.as_ptr().as_mut().unwrap_unchecked().metatable =
200                metatable.map_or(core::ptr::null_mut(), |table| table.as_ptr());
201        }
202    }
203
204    pub unsafe fn instance_metatable(&self) -> Option<Table> {
205        unsafe {
206            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().instance_metatable)
207                .map(|raw| Table::from_raw(raw))
208        }
209    }
210
211    pub unsafe fn set_instance_metatable(&self, metatable: Option<Table>) {
212        unsafe {
213            self.as_ptr().as_mut().unwrap_unchecked().instance_metatable =
214                metatable.map_or(core::ptr::null_mut(), |table| table.as_ptr());
215        }
216    }
217
218    pub unsafe fn member_offset(&self, key: TValue) -> Option<u32> {
219        let offset = unsafe { self.members_to_offset().get(key) };
220        if offset.is_nil() {
221            None
222        } else {
223            debug_assert!(offset.is_number());
224            Some(offset.number_value() as u32)
225        }
226    }
227
228    pub unsafe fn member_offset_str(&self, key: TString) -> Option<u32> {
229        let offset = unsafe { self.members_to_offset().get_str(key) };
230        if offset.is_nil() {
231            None
232        } else {
233            debug_assert!(offset.is_number());
234            Some(offset.number_value() as u32)
235        }
236    }
237
238    pub unsafe fn lookup_static_member(&self, key: TValue) -> Option<TValue> {
239        unsafe {
240            let offset = self.member_offset(key)?;
241            if offset
242                < self
243                    .as_ptr()
244                    .as_ref()
245                    .unwrap_unchecked()
246                    .number_of_instance_members
247            {
248                None
249            } else {
250                Some(
251                    self.static_member(
252                        (offset
253                            - self
254                                .as_ptr()
255                                .as_ref()
256                                .unwrap_unchecked()
257                                .number_of_instance_members) as usize,
258                    ),
259                )
260            }
261        }
262    }
263
264    /// `luaR_addclassmember`
265    pub unsafe fn add_class_member(
266        &self,
267        thread: &Thread,
268        name: TString,
269        method: TValue,
270    ) -> VmErrorResult {
271        unsafe {
272            let Some(offset) = self.member_offset_str(name) else {
273                return Ok(());
274            };
275            debug_assert!(
276                offset
277                    >= self
278                        .as_ptr()
279                        .as_ref()
280                        .unwrap_unchecked()
281                        .number_of_instance_members
282                    && offset
283                        < self
284                            .as_ptr()
285                            .as_ref()
286                            .unwrap_unchecked()
287                            .number_of_all_members
288            );
289            debug_assert!(method.is_function());
290
291            let static_member = self.static_member(
292                (offset
293                    - self
294                        .as_ptr()
295                        .as_ref()
296                        .unwrap_unchecked()
297                        .number_of_instance_members) as usize,
298            );
299            static_member.set_obj(method);
300
301            let class_object: GcObject = (*self).into();
302            if method.is_collectable() && class_object.is_black() {
303                let child = method.gc_value();
304                if child.is_white() {
305                    thread.barrier_forward(class_object, child);
306                }
307            }
308
309            let global = thread.global();
310            let mut is_metamethod = name == thread.intern_string(b"__tostring".as_bstr())?;
311            for index in 0..TM_N {
312                if is_metamethod {
313                    break;
314                }
315
316                is_metamethod = global.tm_name(index) == name;
317            }
318
319            if is_metamethod {
320                let instance_metatable = match self.instance_metatable() {
321                    Some(table) => table,
322                    None => {
323                        let table = thread.new_table_internal(0, 1)?;
324                        self.set_instance_metatable(Some(table));
325
326                        let table_object: GcObject = table.into();
327                        if class_object.is_black() && table_object.is_white() {
328                            thread.barrier_forward(class_object, table_object);
329                        }
330
331                        table
332                    }
333                };
334
335                let node_cursor = thread.set_str(instance_metatable, name)?;
336                node_cursor
337                    .node_unchecked()
338                    .value_unchecked()
339                    .set_obj(method);
340
341                let table_object: GcObject = instance_metatable.into();
342                if method.is_collectable() && table_object.is_black() {
343                    let child = method.gc_value();
344                    if child.is_white() {
345                        thread.barrier_forward(table_object, child);
346                    }
347                }
348            }
349        }
350        Ok(())
351    }
352
353    /// `luaR_registerstaticmember`
354    unsafe fn register_static_member(
355        &self,
356        thread: &Thread,
357        name: TString,
358        value: TValue,
359        offset: u32,
360        static_offset: u32,
361    ) -> VmErrorResult {
362        unsafe {
363            let static_member = self.static_member(static_offset as usize);
364            static_member.set_obj(value);
365
366            let class_object: GcObject = (*self).into();
367            if value.is_collectable() && class_object.is_black() {
368                let child = value.gc_value();
369                if child.is_white() {
370                    thread.barrier_forward(class_object, child);
371                }
372            }
373
374            *self
375                .as_ptr()
376                .as_ref()
377                .unwrap_unchecked()
378                .offset_to_member
379                .add(offset as usize) = name.as_ptr();
380
381            let members_to_offset = self.members_to_offset();
382            let node = thread.set_str(members_to_offset, name)?;
383            node.node_unchecked()
384                .value_unchecked()
385                .set_number(offset as f64);
386        }
387
388        Ok(())
389    }
390}
391
392impl crate::handle::sealed::Sealed for Class {}
393
394impl RawHandle for Class {
395    type Raw = RawLuauClass;
396
397    fn as_ptr(&self) -> *mut Self::Raw {
398        self.raw.as_ptr()
399    }
400}
401
402impl AsRef<Class> for Class {
403    fn as_ref(&self) -> &Class {
404        self
405    }
406}
407
408#[derive(Clone, Copy, PartialEq, Eq)]
409#[repr(transparent)]
410/// Non-owning identity of a VM object-instance record.
411///
412/// # Safety model for unsafe methods
413///
414/// The object and its class must be live records in the same VM. Member
415/// indices and cached offsets must be valid for that class, and writes must
416/// preserve the collector's barrier and list invariants.
417pub struct Object {
418    pub(crate) raw: NonNull<RawLuauObject>,
419}
420
421#[derive(Clone, Copy)]
422pub enum ObjectMemberLookup {
423    Cached(TValue),
424    Resolved { offset: u32, member: TValue },
425    Missing,
426}
427
428#[allow(
429    clippy::missing_safety_doc,
430    reason = "Object's shared raw-handle contract is documented on Object"
431)]
432impl Object {
433    pub const unsafe fn from_raw(raw: NonNull<RawLuauObject>) -> Self {
434        Self { raw }
435    }
436
437    pub unsafe fn class(&self) -> Class {
438        unsafe {
439            Class::from_raw(NonNull::new_unchecked(
440                self.as_ptr().as_ref().unwrap_unchecked().class,
441            ))
442        }
443    }
444
445    pub unsafe fn member(&self, index: usize) -> TValue {
446        unsafe {
447            TValue::from_raw(NonNull::new_unchecked(
448                self.as_ptr().as_ref().unwrap_unchecked().members.add(index),
449            ))
450        }
451    }
452
453    pub unsafe fn gc_list(&self) -> Option<GcObject> {
454        unsafe {
455            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gc_list)
456                .map(|raw| GcObject::from_raw(raw))
457        }
458    }
459
460    pub unsafe fn set_gc_list(&self, gc_list: Option<GcObject>) {
461        unsafe {
462            self.as_ptr().as_mut().unwrap_unchecked().gc_list =
463                gc_list.map_or(ptr::null_mut(), |object| object.as_ptr());
464        }
465    }
466
467    pub unsafe fn gc_list_slot(&self) -> *mut *mut RawGcObject {
468        unsafe { &mut self.as_ptr().as_mut().unwrap_unchecked().gc_list }
469    }
470
471    pub unsafe fn allocation_size(&self) -> usize {
472        unsafe {
473            size_of::<RawLuauObject>()
474                + self.as_ptr().as_ref().unwrap_unchecked().number_of_members as usize
475                    * size_of::<RawTValue>()
476        }
477    }
478
479    /// `luaR_checkoffsetinbounds`
480    pub fn offset_in_bounds(&self, offset: u32) -> bool {
481        let class = unsafe { self.class() };
482        offset
483            < unsafe {
484                class
485                    .as_ptr()
486                    .as_ref()
487                    .unwrap_unchecked()
488                    .number_of_all_members
489            }
490    }
491
492    /// `luaR_lookupmemberatoffset`
493    pub unsafe fn lookup_member_at_offset(&self, offset: u32) -> TValue {
494        debug_assert!(self.offset_in_bounds(offset));
495
496        unsafe {
497            let class = self.class();
498
499            if offset
500                < class
501                    .as_ptr()
502                    .as_ref()
503                    .unwrap_unchecked()
504                    .number_of_instance_members
505            {
506                self.member(offset as usize)
507            } else {
508                class.static_member(
509                    (offset
510                        - class
511                            .as_ptr()
512                            .as_ref()
513                            .unwrap_unchecked()
514                            .number_of_instance_members) as usize,
515                )
516            }
517        }
518    }
519
520    pub unsafe fn lookup_member(&self, key: TValue) -> Option<TValue> {
521        unsafe {
522            self.class()
523                .member_offset(key)
524                .map(|offset| self.lookup_member_at_offset(offset))
525        }
526    }
527
528    pub unsafe fn lookup_member_cached(&self, key: TString, slot: u8) -> ObjectMemberLookup {
529        unsafe {
530            let class = self.class();
531            let slot_offset = u32::from(slot);
532            if slot_offset
533                < class
534                    .as_ptr()
535                    .as_ref()
536                    .unwrap_unchecked()
537                    .number_of_all_members
538                && class.offset_to_member(slot as usize) == key
539            {
540                return ObjectMemberLookup::Cached(self.lookup_member_at_offset(slot_offset));
541            }
542
543            match class.member_offset_str(key) {
544                Some(offset) => ObjectMemberLookup::Resolved {
545                    offset,
546                    member: self.lookup_member_at_offset(offset),
547                },
548                None => ObjectMemberLookup::Missing,
549            }
550        }
551    }
552}
553
554impl crate::handle::sealed::Sealed for Object {}
555
556impl RawHandle for Object {
557    type Raw = RawLuauObject;
558
559    fn as_ptr(&self) -> *mut Self::Raw {
560        self.raw.as_ptr()
561    }
562}
563
564impl AsRef<Object> for Object {
565    fn as_ref(&self) -> &Object {
566        self
567    }
568}
569
570fn create_object_callback(ctx: NativeCallContext) -> NativeCallResult {
571    let thread = ctx.raw_thread();
572    unsafe { thread.create_object() }
573}
574
575impl ClassRuntime for Thread {
576    /// `luaR_newblankclass`
577    unsafe fn new_blank_class(&self, name: TString) -> VmErrorResult<Class> {
578        unsafe {
579            let class = self.new_gco::<Class>(
580                core::mem::size_of::<RawLuauClass>(),
581                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
582            )?;
583            GcObject::from(class).init_header(self, LUA_TCLASS as u8);
584
585            let class_ref = class.as_ptr().as_mut().unwrap_unchecked();
586            class_ref.gc_list = ptr::null_mut();
587            class_ref.name = name.as_ptr();
588            class_ref.static_members = ptr::null_mut();
589            class_ref.members_to_offset = ptr::null_mut();
590            class_ref.offset_to_member = ptr::null_mut();
591            class_ref.metatable = ptr::null_mut();
592            class_ref.instance_metatable = ptr::null_mut();
593            class_ref.number_of_instance_members = 0;
594            class_ref.number_of_all_members = 0;
595
596            Ok(class)
597        }
598    }
599
600    /// `luaR_addclassmetatable`
601    unsafe fn add_class_metatable(&self, class: Class) -> VmErrorResult {
602        unsafe {
603            let metatable = self.new_table_internal(0, 1)?;
604            let global_env = self.globals();
605            let constructor = self.new_native_closure(0, Some(global_env))?;
606            let debug_name = if flags::LuauManagedDebugNames.get() {
607                self.intern_string(b"luaR_createobject".as_bstr())?.as_ptr()
608            } else {
609                ptr::null_mut()
610            };
611            constructor.set_native_data(RawNativeClosure {
612                function: Some(create_object_callback),
613                continuation: None,
614                debug_name_deprecated: (!flags::LuauManagedDebugNames.get())
615                    .then_some("luaR_createobject"),
616                debug_name,
617            });
618
619            let call_name = self.global().tm_name(TmEvent::Call as usize);
620            let node_cursor = self.set_str(metatable, call_name)?;
621            node_cursor
622                .node_unchecked()
623                .value_unchecked()
624                .set_closure_value(constructor);
625            metatable.as_ptr().as_mut().unwrap_unchecked().readonly = 1;
626            class.set_metatable(Some(metatable));
627
628            Ok(())
629        }
630    }
631
632    /// `luaR_newclass`
633    unsafe fn new_class(
634        &self,
635        name: TString,
636        members_to_offset: Table,
637        offset_to_member: *mut TString,
638        number_of_instance_members: u32,
639        number_of_static_members: u32,
640    ) -> VmErrorResult<Class> {
641        unsafe {
642            debug_assert_eq!(
643                self.global()
644                    .as_ptr()
645                    .as_ref()
646                    .unwrap_unchecked()
647                    .gc_threshold,
648                usize::MAX
649            );
650
651            let class = self.new_blank_class(name)?;
652
653            let static_members = self.new_array::<RawTValue>(
654                number_of_static_members as usize,
655                class.as_ptr().as_ref().unwrap_unchecked().memcat,
656            )?;
657
658            let class_ref = class.as_ptr().as_mut().unwrap_unchecked();
659            class_ref.static_members = static_members;
660            class_ref.members_to_offset = members_to_offset.as_ptr();
661            class_ref.offset_to_member = offset_to_member.cast();
662            class_ref.number_of_instance_members = number_of_instance_members;
663            class_ref.number_of_all_members = number_of_instance_members + number_of_static_members;
664
665            for index in 0..number_of_static_members as usize {
666                class.static_member(index).set_nil();
667            }
668
669            self.add_class_metatable(class)?;
670
671            Ok(class)
672        }
673    }
674
675    /// `luaR_inheritclass`
676    unsafe fn inherit_class(&self, child: Class, parent: Class) -> VmErrorResult<Class> {
677        unsafe {
678            let child_instance_members = child
679                .as_ptr()
680                .as_ref()
681                .unwrap_unchecked()
682                .number_of_instance_members;
683            let child_members = child
684                .as_ptr()
685                .as_ref()
686                .unwrap_unchecked()
687                .number_of_all_members;
688            let parent_instance_members = parent
689                .as_ptr()
690                .as_ref()
691                .unwrap_unchecked()
692                .number_of_instance_members;
693            let parent_members = parent
694                .as_ptr()
695                .as_ref()
696                .unwrap_unchecked()
697                .number_of_all_members;
698
699            for index in 0..parent_instance_members {
700                let name = parent.offset_to_member(index as usize);
701                if child.member_offset_str(name).is_some() {
702                    let parent_name = parent.name();
703                    let child_name = child.name();
704                    return crate::run_error!(
705                        self,
706                        "Cannot override instance member '%s' of parent class '%s' in child class '%s'",
707                        name.as_bytes(),
708                        parent_name.as_bytes(),
709                        child_name.as_bytes(),
710                    );
711                }
712            }
713
714            let class = self.new_blank_class(child.name())?;
715
716            let mut inherited_static_members = 0u32;
717            for index in parent_instance_members..parent_members {
718                let name = parent.offset_to_member(index as usize);
719                if child.member_offset_str(name).is_none() {
720                    inherited_static_members += 1;
721                }
722            }
723
724            let member_count = child_members + parent_instance_members + inherited_static_members;
725            let memcat = class.as_ptr().as_ref().unwrap_unchecked().memcat;
726            let offset_to_member =
727                self.new_array::<*mut RawTString>(member_count as usize, memcat)?;
728            let members_to_offset = self.new_table_internal(0, member_count as i32)?;
729
730            {
731                let class_ref = class.as_ptr().as_mut().unwrap_unchecked();
732                class_ref.offset_to_member = offset_to_member;
733                class_ref.members_to_offset = members_to_offset.as_ptr();
734                class_ref.number_of_all_members = member_count;
735            }
736
737            let mut offset = 0u32;
738            for index in 0..parent_instance_members {
739                let name = parent.offset_to_member(index as usize);
740                *offset_to_member.add(offset as usize) = name.as_ptr();
741                let value = self.set_str(members_to_offset, name)?;
742                value
743                    .node_unchecked()
744                    .value_unchecked()
745                    .set_number(offset as f64);
746                offset += 1;
747            }
748
749            for index in 0..child_instance_members {
750                let name = child.offset_to_member(index as usize);
751                *offset_to_member.add(offset as usize) = name.as_ptr();
752                let value = self.set_str(members_to_offset, name)?;
753                value
754                    .node_unchecked()
755                    .value_unchecked()
756                    .set_number(offset as f64);
757                offset += 1;
758            }
759
760            let static_members =
761                self.new_array::<RawTValue>((member_count - offset) as usize, memcat)?;
762            {
763                let class_ref = class.as_ptr().as_mut().unwrap_unchecked();
764                class_ref.static_members = static_members;
765                class_ref.number_of_instance_members = offset;
766            }
767
768            let mut static_offset = 0u32;
769            for index in parent_instance_members..parent_members {
770                let name = parent.offset_to_member(index as usize);
771                if child.member_offset_str(name).is_none() {
772                    let value = parent.static_member((index - parent_instance_members) as usize);
773                    class.register_static_member(self, name, value, offset, static_offset)?;
774                    offset += 1;
775                    static_offset += 1;
776                }
777            }
778
779            for index in child_instance_members..child_members {
780                let name = child.offset_to_member(index as usize);
781                let value = child.static_member((index - child_instance_members) as usize);
782                class.register_static_member(self, name, value, offset, static_offset)?;
783                offset += 1;
784                static_offset += 1;
785            }
786
787            debug_assert_eq!(offset, member_count);
788
789            self.add_class_metatable(class)?;
790
791            if let Some(instance_metatable) = parent.instance_metatable() {
792                let cloned = self.clone_table_internal(instance_metatable)?;
793                class.set_instance_metatable(Some(cloned));
794            }
795
796            Ok(class)
797        }
798    }
799    /// `luaR_freeclass`
800    unsafe fn free_class(&self, class: Class, page: LuaPage) {
801        unsafe {
802            let class_ref = class.as_ptr().as_ref().unwrap_unchecked();
803            let memcat = class_ref.memcat;
804            let static_members = class_ref.static_members;
805            let static_member_count =
806                (class_ref.number_of_all_members - class_ref.number_of_instance_members) as usize;
807            let offset_to_member = class_ref.offset_to_member;
808            let total_members = class_ref.number_of_all_members as usize;
809            if !static_members.is_null() {
810                self.free_array(static_members, static_member_count, memcat);
811            }
812            if !offset_to_member.is_null() {
813                self.free_array(offset_to_member, total_members, memcat);
814            }
815            self.free_gco(
816                class.into(),
817                core::mem::size_of::<RawLuauClass>(),
818                memcat,
819                page,
820            );
821        }
822    }
823
824    /// `luaR_freeobject`
825    unsafe fn free_object(&self, object: Object, page: LuaPage) {
826        unsafe {
827            let object_ref = object.as_ptr().as_ref().unwrap_unchecked();
828            let members = object_ref.members;
829            let number_of_members = object_ref.number_of_members as usize;
830            let memcat = object_ref.memcat;
831            self.free_array(members, number_of_members, memcat);
832            self.free_gco(
833                object.into(),
834                core::mem::size_of::<RawLuauObject>(),
835                memcat,
836                page,
837            );
838        }
839    }
840    /// `luaR_createobject`
841    unsafe fn create_object(&self) -> NativeCallResult {
842        unsafe {
843            self.check_type(1, LUA_TCLASS)?;
844
845            let class = self.stack_base().value_unchecked().class_value();
846            let num_args = self.get_top();
847            let active_memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
848
849            let object =
850                self.new_gco::<Object>(core::mem::size_of::<RawLuauObject>(), active_memcat)?;
851            let object_gco: GcObject = object.into();
852            object_gco.init_header(self, LUA_TOBJECT as u8);
853
854            let member_count = class
855                .as_ptr()
856                .as_ref()
857                .unwrap_unchecked()
858                .number_of_instance_members as usize;
859            let members = self.new_array::<RawTValue>(member_count, active_memcat)?;
860
861            let object_ref = object.as_ptr().as_mut().unwrap_unchecked();
862            object_ref.gc_list = core::ptr::null_mut();
863            object_ref.class = class.as_ptr();
864            object_ref.number_of_members = class
865                .as_ptr()
866                .as_ref()
867                .unwrap_unchecked()
868                .number_of_instance_members;
869            object_ref.members = members;
870
871            for index in 0..member_count {
872                object.member(index).set_nil();
873            }
874
875            let object_slot = self.stack_top();
876            object_slot.value_unchecked().set_object_value(object);
877            self.set_stack_top(object_slot.add(1));
878
879            let value_slot = self.stack_top();
880            value_slot.value_unchecked().set_nil();
881            self.set_stack_top(value_slot.add(1));
882            let value_slot_offset = self.save_stack(value_slot);
883
884            match num_args {
885                1 => {}
886                2 => {
887                    for index in 0..member_count {
888                        let mut key_storage = RAW_TVALUE_NIL;
889                        let key = TValue::from_mut(&mut key_storage);
890                        key.set_string_value(class.offset_to_member(index));
891                        let indexed_value = self.stack_base().add(1).value_unchecked();
892                        let value_slot = self.restore_stack(value_slot_offset);
893
894                        self.get_table_internal(indexed_value, key, value_slot)?;
895                        let value_slot = self.restore_stack(value_slot_offset);
896                        object.member(index).set_obj(value_slot.value_unchecked());
897                    }
898                }
899                _ => {
900                    let class_name = class.name();
901                    return crate::error!(
902                        self,
903                        "wrong number of arguments for constructing a '%s'",
904                        class_name.as_bytes()
905                    )
906                    .map_err(Into::into);
907                }
908            }
909
910            self.set_stack_top(self.restore_stack(value_slot_offset));
911
912            if object_gco.is_black() {
913                self.barrier_back(object_gco, object.gc_list_slot());
914            }
915
916            Ok(1)
917        }
918    }
919}