1use std::cell::RefCell;
13use std::hash::{BuildHasherDefault, Hasher};
14use std::rc::Rc;
15
16use crate::string::StringPool;
17pub use lua_types::error::LuaError;
18pub use lua_types::{CallInfoIdx, StackIdx};
19
20pub struct StackIdxConv(pub StackIdx);
23
24#[inline(always)]
27pub fn stack_idx_to_i32(i: StackIdx) -> i32 {
28 i.0 as i32
29}
30
31impl From<u32> for StackIdxConv {
32 #[inline(always)]
33 fn from(v: u32) -> Self {
34 StackIdxConv(StackIdx(v))
35 }
36}
37impl From<i32> for StackIdxConv {
38 #[inline(always)]
39 fn from(v: i32) -> Self {
40 StackIdxConv(StackIdx(v.max(0) as u32))
41 }
42}
43impl From<usize> for StackIdxConv {
44 #[inline(always)]
45 fn from(v: usize) -> Self {
46 StackIdxConv(StackIdx(v as u32))
47 }
48}
49impl From<StackIdx> for StackIdxConv {
50 #[inline(always)]
51 fn from(v: StackIdx) -> Self {
52 StackIdxConv(v)
53 }
54}
55pub use lua_types::closure::{
56 LuaCClosure as LuaClosureC, LuaCFnPtr, LuaClosure, LuaLClosure as LuaClosureLua,
57};
58pub use lua_types::gc::GcRef;
59pub use lua_types::proto::LuaProto;
60pub use lua_types::string::LuaString;
61pub use lua_types::upval::UpVal;
62pub use lua_types::userdata::LuaUserData;
63pub use lua_types::value::{F2Imod, LuaTable, LuaValue};
64
65pub struct LuaByteHasher {
66 hash: u64,
67}
68
69impl Default for LuaByteHasher {
70 fn default() -> Self {
71 Self {
72 hash: 0xcbf2_9ce4_8422_2325,
73 }
74 }
75}
76
77impl Hasher for LuaByteHasher {
78 #[inline]
79 fn write(&mut self, bytes: &[u8]) {
80 const PRIME: u64 = 0x0000_0100_0000_01b3;
81 for &byte in bytes {
82 self.hash ^= u64::from(byte);
83 self.hash = self.hash.wrapping_mul(PRIME);
84 }
85 }
86
87 #[inline]
88 fn write_u8(&mut self, i: u8) {
89 self.write(&[i]);
90 }
91
92 #[inline]
93 fn write_usize(&mut self, i: usize) {
94 self.write(&i.to_ne_bytes());
95 }
96
97 #[inline]
98 fn finish(&self) -> u64 {
99 self.hash
100 }
101}
102
103pub type LuaByteBuildHasher = BuildHasherDefault<LuaByteHasher>;
104
105pub struct InternedStringMap {
120 buckets: Vec<Vec<GcRef<LuaString>>>,
121 count: usize,
122}
123
124impl Default for InternedStringMap {
125 fn default() -> Self {
126 InternedStringMap {
127 buckets: (0..64).map(|_| Vec::new()).collect(),
128 count: 0,
129 }
130 }
131}
132
133impl InternedStringMap {
134 #[inline]
135 fn mask(&self) -> usize {
136 self.buckets.len() - 1
137 }
138
139 pub fn len(&self) -> usize {
140 self.count
141 }
142
143 pub fn is_empty(&self) -> bool {
144 self.count == 0
145 }
146
147 pub fn bucket_count(&self) -> usize {
152 self.buckets.len()
153 }
154
155 #[inline]
156 pub fn find(&self, bytes: &[u8], hash: u32) -> Option<GcRef<LuaString>> {
157 let bucket = &self.buckets[hash as usize & self.mask()];
158 bucket
159 .iter()
160 .find(|s| s.hash() == hash && s.as_bytes() == bytes)
161 .cloned()
162 }
163
164 pub fn insert(&mut self, s: GcRef<LuaString>) {
166 if self.count >= self.buckets.len() {
167 self.resize(self.buckets.len() * 2);
168 }
169 let m = self.mask();
170 self.buckets[s.hash() as usize & m].push(s);
171 self.count += 1;
172 }
173
174 fn resize(&mut self, new_len: usize) {
175 let old = std::mem::replace(
176 &mut self.buckets,
177 (0..new_len).map(|_| Vec::new()).collect(),
178 );
179 let m = self.mask();
180 for bucket in old {
181 for s in bucket {
182 self.buckets[s.hash() as usize & m].push(s);
183 }
184 }
185 }
186
187 pub fn shrink_if_sparse(&mut self) {
199 if self.count * 4 >= self.buckets.len() {
200 return;
201 }
202 let target = self.count.next_power_of_two().max(64);
203 if target < self.buckets.len() {
204 self.resize(target);
205 }
206 }
207
208 pub fn remove(&mut self, hash: u32, identity: usize) {
210 let m = self.mask();
211 let bucket = &mut self.buckets[hash as usize & m];
212 if let Some(pos) = bucket.iter().position(|s| s.identity() == identity) {
213 bucket.swap_remove(pos);
214 self.count -= 1;
215 }
216 }
217
218 pub fn iter(&self) -> impl Iterator<Item = &GcRef<LuaString>> {
219 self.buckets.iter().flatten()
220 }
221
222 pub fn contains_key(&self, bytes: &[u8]) -> bool {
223 self.find(bytes, LuaString::hash_bytes(bytes, 0)).is_some()
224 }
225}
226
227pub type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
233
234pub type LuaRustFunction = Rc<dyn Fn(&mut LuaState) -> Result<usize, LuaError>>;
235
236#[derive(Clone)]
237pub enum LuaCallable {
238 Bare(LuaCFunction),
239 Rust(LuaRustFunction),
240}
241
242impl std::fmt::Debug for LuaCallable {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 match self {
245 LuaCallable::Bare(_) => f.write_str("LuaCallable::Bare(..)"),
246 LuaCallable::Rust(_) => f.write_str("LuaCallable::Rust(..)"),
247 }
248 }
249}
250
251impl LuaCallable {
252 pub fn bare(f: LuaCFunction) -> Self {
253 LuaCallable::Bare(f)
254 }
255
256 pub fn rust(f: LuaRustFunction) -> Self {
257 LuaCallable::Rust(f)
258 }
259
260 pub fn as_bare(&self) -> Option<LuaCFunction> {
261 match self {
262 LuaCallable::Bare(f) => Some(*f),
263 LuaCallable::Rust(_) => None,
264 }
265 }
266
267 pub fn call(&self, state: &mut LuaState) -> Result<usize, LuaError> {
268 match self {
269 LuaCallable::Bare(f) => f(state),
270 LuaCallable::Rust(f) => f(state),
271 }
272 }
273}
274
275#[derive(Clone, Debug)]
276pub enum FinalizerObject {
277 Table(GcRef<LuaTable>),
278 UserData(GcRef<LuaUserData>),
279}
280
281impl FinalizerObject {
282 pub fn identity(&self) -> usize {
283 match self {
284 FinalizerObject::Table(t) => t.identity(),
285 FinalizerObject::UserData(u) => u.identity(),
286 }
287 }
288
289 pub fn metatable(&self) -> Option<GcRef<LuaTable>> {
290 match self {
291 FinalizerObject::Table(t) => t.metatable(),
292 FinalizerObject::UserData(u) => u.metatable(),
293 }
294 }
295
296 pub fn as_lua_value(&self) -> LuaValue {
297 match self {
298 FinalizerObject::Table(t) => LuaValue::Table(t.clone()),
299 FinalizerObject::UserData(u) => LuaValue::UserData(u.clone()),
300 }
301 }
302
303 pub fn mark(&self, marker: &mut lua_gc::Marker) {
304 match self {
305 FinalizerObject::Table(t) => marker.mark(t.0),
306 FinalizerObject::UserData(u) => marker.mark(u.0),
307 }
308 }
309
310 pub fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
311 Some(match self {
312 FinalizerObject::Table(t) => t.0.as_trace_ptr(),
313 FinalizerObject::UserData(u) => u.0.as_trace_ptr(),
314 })
315 }
316
317 pub fn age(&self) -> lua_gc::GcAge {
318 match self {
319 FinalizerObject::Table(t) => t.0.age(),
320 FinalizerObject::UserData(u) => u.0.age(),
321 }
322 }
323
324 pub fn is_finalized(&self) -> bool {
325 match self {
326 FinalizerObject::Table(t) => t.0.is_finalized(),
327 FinalizerObject::UserData(u) => u.0.is_finalized(),
328 }
329 }
330
331 pub fn set_finalized(&self, finalized: bool) {
332 match self {
333 FinalizerObject::Table(t) => t.0.set_finalized(finalized),
334 FinalizerObject::UserData(u) => u.0.set_finalized(finalized),
335 }
336 }
337}
338
339impl lua_gc::FinalizerEntry for FinalizerObject {
340 fn identity(&self) -> usize {
341 FinalizerObject::identity(self)
342 }
343
344 fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
345 FinalizerObject::heap_ptr(self)
346 }
347
348 fn age(&self) -> lua_gc::GcAge {
349 FinalizerObject::age(self)
350 }
351
352 fn is_finalized(&self) -> bool {
353 FinalizerObject::is_finalized(self)
354 }
355
356 fn set_finalized(&self, finalized: bool) {
357 FinalizerObject::set_finalized(self, finalized);
358 }
359}
360
361#[derive(Clone, Debug)]
362pub struct WeakTableEntry {
363 table: lua_types::gc::GcWeak<LuaTable>,
364 kind: lua_gc::WeakListKind,
365}
366
367impl WeakTableEntry {
368 pub fn new(table: &GcRef<LuaTable>) -> Self {
369 let mode = table.weak_mode();
370 let weak_keys = (mode & (1 << 0)) != 0;
371 let weak_values = (mode & (1 << 1)) != 0;
372 let kind = match (weak_keys, weak_values) {
373 (true, true) => lua_gc::WeakListKind::AllWeak,
374 (true, false) => lua_gc::WeakListKind::Ephemeron,
375 (false, true) => lua_gc::WeakListKind::WeakValues,
376 (false, false) => lua_gc::WeakListKind::WeakValues,
377 };
378 Self {
379 table: table.downgrade(),
380 kind,
381 }
382 }
383}
384
385impl lua_gc::WeakEntry for WeakTableEntry {
386 type Strong = GcRef<LuaTable>;
387
388 fn identity(&self) -> usize {
389 self.table.identity()
390 }
391
392 fn list_kind(&self) -> lua_gc::WeakListKind {
393 self.kind
394 }
395
396 fn upgrade(&self) -> Option<Self::Strong> {
397 self.table.upgrade()
398 }
399}
400
401pub(crate) const EXTRA_STACK: usize = 5;
404
405pub(crate) const LUA_MINSTACK: usize = 20;
406
407pub(crate) const BASIC_STACK_SIZE: usize = 2 * LUA_MINSTACK;
408
409pub(crate) const LUAI_MAXCCALLS: u32 = 200;
429
430pub(crate) const CIST_C: u16 = 1 << 1;
431
432pub(crate) const CIST_OAH: u16 = 1 << 0;
433pub(crate) const CIST_FRESH: u16 = 1 << 2;
434pub(crate) const CIST_HOOKED: u16 = 1 << 3;
435pub(crate) const CIST_YPCALL: u16 = 1 << 4;
436pub(crate) const CIST_TAIL: u16 = 1 << 5;
437pub(crate) const CIST_HOOKYIELD: u16 = 1 << 6;
438pub(crate) const CIST_FIN: u16 = 1 << 7;
439pub(crate) const CIST_TRAN: u16 = 1 << 8;
440pub(crate) const CIST_RECST: u32 = 10;
441pub(crate) const CIST_LEQ: u16 = 1 << 13;
447
448pub(crate) const CIST_TRAP: u16 = 1 << 14;
456
457const LUA_NUMTYPES: usize = 9;
458
459const GCSTPUSR: u8 = 1;
460const GCSTPGC: u8 = 2;
461
462const GCS_PAUSE: u8 = 0;
463
464const LUAI_GCPAUSE: u32 = 200;
465const LUAI_GCMUL: u32 = 100;
466const LUAI_GCSTEPSIZE: u8 = 13;
467const LUAI_GENMAJORMUL: u32 = 100;
468const LUAI_GENMINORMUL: u8 = 20;
469
470const WHITE0BIT: u8 = 0;
471
472const STRCACHE_N: usize = 53;
473const STRCACHE_M: usize = 2;
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
479pub enum GcKind {
480 Incremental = 0,
481 Generational = 1,
482}
483
484#[derive(Debug, Clone, Copy, PartialEq, Eq)]
492pub enum WarnMode {
493 Off,
494 On,
495 Cont,
496}
497
498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub enum TestWarnMode {
501 Normal,
502 Allow,
503 Store,
504}
505
506pub use lua_types::status::LuaStatus;
511
512#[derive(Clone)]
524pub struct StackValue {
525 pub val: LuaValue,
526}
527
528impl Default for StackValue {
529 fn default() -> Self {
530 StackValue {
531 val: LuaValue::Nil,
532 }
533 }
534}
535
536#[derive(Clone)]
543pub struct CallInfo {
544 pub func: StackIdx,
545
546 pub top: StackIdx,
547
548 pub previous: Option<CallInfoIdx>,
549
550 pub next: Option<CallInfoIdx>,
551
552 pub u: CallInfoFrame,
553
554 pub u2: CallInfoExtra,
555
556 pub nresults: i16,
557
558 pub callstatus: u16,
560
561 pub call_metamethods: u8,
565
566 pub tailcalls: u16,
575}
576
577#[cfg(target_pointer_width = "64")]
586const _: () = {
587 assert!(core::mem::size_of::<CallInfoFrame>() == 32);
588 assert!(core::mem::size_of::<CallInfo>() == 72);
589};
590
591#[derive(Clone, Copy)]
608pub struct CallInfoFrame {
609 pub savedpc: u32,
611 pub nextraargs: i32,
613 pub k: Option<LuaKFunction>,
615 pub old_errfunc: isize,
617 pub ctx: isize,
619}
620
621pub type LuaKFunction = fn(&mut LuaState, status: i32, ctx: isize) -> Result<usize, LuaError>;
623
624#[derive(Default, Clone, Copy)]
626pub struct CallInfoExtra {
627 pub value: i32,
628 pub ftransfer: u16,
629 pub ntransfer: u16,
630}
631
632impl CallInfoFrame {
633 pub fn c_default() -> Self {
637 CallInfoFrame {
638 savedpc: 0,
639 nextraargs: 0,
640 k: None,
641 old_errfunc: 0,
642 ctx: 0,
643 }
644 }
645
646 pub fn lua_default() -> Self {
653 CallInfoFrame {
654 savedpc: 0,
655 nextraargs: 0,
656 k: None,
657 old_errfunc: 0,
658 ctx: 0,
659 }
660 }
661}
662
663impl Default for CallInfo {
664 fn default() -> Self {
665 CallInfo {
666 func: StackIdx(0),
667 top: StackIdx(0),
668 previous: None,
669 next: None,
670 u: CallInfoFrame::c_default(),
671 u2: CallInfoExtra::default(),
672 nresults: 0,
673 callstatus: 0,
674 call_metamethods: 0,
675 tailcalls: 0,
676 }
677 }
678}
679
680impl CallInfo {
681 pub fn is_lua(&self) -> bool {
682 (self.callstatus & CIST_C) == 0
683 }
684 pub fn is_lua_code(&self) -> bool {
685 self.is_lua()
686 }
687 pub fn is_vararg_func(&self) -> bool {
692 false
693 }
694 pub fn saved_pc(&self) -> u32 {
699 debug_assert!(self.is_lua(), "saved_pc on a C frame");
700 self.u.savedpc
701 }
702 pub fn set_saved_pc(&mut self, pc: u32) {
704 debug_assert!(self.is_lua(), "set_saved_pc on a C frame");
705 self.u.savedpc = pc;
706 }
707 pub fn nextra_args(&self) -> i32 {
709 debug_assert!(self.is_lua(), "nextra_args on a C frame");
710 self.u.nextraargs
711 }
712 pub fn set_nextra_args(&mut self, n: i32) {
714 debug_assert!(self.is_lua(), "set_nextra_args on a C frame");
715 self.u.nextraargs = n;
716 }
717 pub fn transfer_ftransfer(&self) -> u16 {
718 self.u2.ftransfer
719 }
720 pub fn transfer_ntransfer(&self) -> u16 {
721 self.u2.ntransfer
722 }
723 pub fn set_trap(&mut self, t: bool) {
731 debug_assert!(self.is_lua(), "set_trap on a C frame");
732 if t {
733 self.callstatus |= CIST_TRAP;
734 } else {
735 self.callstatus &= !CIST_TRAP;
736 }
737 }
738 #[inline(always)]
742 pub fn trap(&self) -> bool {
743 (self.callstatus & CIST_TRAP) != 0
744 }
745 pub fn recover_status(&self) -> i32 {
748 ((self.callstatus >> CIST_RECST) & 7) as i32
749 }
750 pub fn set_recover_status<T: Into<i32>>(&mut self, status: T) {
753 let st = (status.into() & 7) as u16;
754 self.callstatus = (self.callstatus & !(7u16 << CIST_RECST)) | (st << CIST_RECST);
755 }
756 pub fn get_oah(&self) -> bool {
757 (self.callstatus & CIST_OAH) != 0
758 }
759 pub fn set_oah(&mut self, allow: bool) {
762 self.callstatus = (self.callstatus & !CIST_OAH) | (if allow { CIST_OAH } else { 0 });
763 }
764 pub fn u_c_old_errfunc(&self) -> isize {
769 debug_assert!(!self.is_lua(), "u_c_old_errfunc on a Lua frame");
770 self.u.old_errfunc
771 }
772 pub fn u_c_ctx(&self) -> isize {
774 debug_assert!(!self.is_lua(), "u_c_ctx on a Lua frame");
775 self.u.ctx
776 }
777 pub fn u_c_k(&self) -> Option<LuaKFunction> {
779 debug_assert!(!self.is_lua(), "u_c_k on a Lua frame");
780 self.u.k
781 }
782 pub fn set_u_c_k(&mut self, k: Option<LuaKFunction>) {
788 debug_assert!(!self.is_lua(), "set_u_c_k on a Lua frame");
789 self.u.k = k;
790 }
791 pub fn set_u_c_ctx(&mut self, ctx: isize) {
793 debug_assert!(!self.is_lua(), "set_u_c_ctx on a Lua frame");
794 self.u.ctx = ctx;
795 }
796 pub fn set_u_c_old_errfunc(&mut self, old_errfunc: isize) {
798 debug_assert!(!self.is_lua(), "set_u_c_old_errfunc on a Lua frame");
799 self.u.old_errfunc = old_errfunc;
800 }
801 pub fn set_u2_funcidx(&mut self, idx: i32) {
804 self.u2.value = idx;
805 }
806}
807
808pub trait LuaValueExt {
813 fn base_type(&self) -> lua_types::LuaType;
814 fn to_number_no_strconv(&self) -> Option<f64>;
815 fn to_number_with_strconv(&self) -> Option<f64>;
816 fn to_integer_no_strconv(&self) -> Option<i64>;
817 fn to_integer_with_strconv(&self) -> Option<i64>;
818 fn full_type_tag(&self) -> u8;
819}
820
821impl LuaValueExt for LuaValue {
822 fn base_type(&self) -> lua_types::LuaType {
823 self.type_tag()
824 }
825 fn to_number_no_strconv(&self) -> Option<f64> {
826 match self {
827 LuaValue::Float(f) => Some(*f),
828 LuaValue::Int(i) => Some(*i as f64),
829 _ => None,
830 }
831 }
832 fn to_number_with_strconv(&self) -> Option<f64> {
833 if let Some(n) = self.to_number_no_strconv() {
834 return Some(n);
835 }
836 if let LuaValue::Str(s) = self {
837 let mut tmp = LuaValue::Nil;
838 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
839 if sz == 0 {
840 return None;
841 }
842 return match tmp {
843 LuaValue::Int(i) => Some(i as f64),
844 LuaValue::Float(f) => Some(f),
845 _ => None,
846 };
847 }
848 None
849 }
850 fn to_integer_no_strconv(&self) -> Option<i64> {
851 match self {
852 LuaValue::Int(i) => Some(*i),
853 LuaValue::Float(f) if f.fract() == 0.0 && f.is_finite() => {
854 let min_f = i64::MIN as f64;
858 let max_plus1_f = -(i64::MIN as f64);
859 if *f >= min_f && *f < max_plus1_f {
860 Some(*f as i64)
861 } else {
862 None
863 }
864 }
865 _ => None,
866 }
867 }
868 fn to_integer_with_strconv(&self) -> Option<i64> {
869 if let Some(i) = self.to_integer_no_strconv() {
870 return Some(i);
871 }
872 if let LuaValue::Str(s) = self {
873 let mut tmp = LuaValue::Nil;
874 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
875 if sz == 0 {
876 return None;
877 }
878 return tmp.to_integer_no_strconv();
879 }
880 None
881 }
882 fn full_type_tag(&self) -> u8 {
883 match self {
884 LuaValue::Nil => 0x00,
885 LuaValue::Bool(false) => 0x01,
886 LuaValue::Bool(true) => 0x11,
887 LuaValue::Int(_) => 0x03,
888 LuaValue::Float(_) => 0x13,
889 LuaValue::Str(s) if s.is_short() => 0x04,
890 LuaValue::Str(_) => 0x14,
891 LuaValue::LightUserData(_) => 0x02,
892 LuaValue::Table(_) => 0x05,
893 LuaValue::Function(LuaClosure::Lua(_)) => 0x06,
894 LuaValue::Function(LuaClosure::LightC(_)) => 0x16,
895 LuaValue::Function(LuaClosure::C(_)) => 0x26,
896 LuaValue::UserData(_) => 0x07,
897 LuaValue::Thread(_) => 0x08,
898 }
899 }
900}
901
902pub trait LuaTypeExt {
904 fn type_name(&self) -> &'static [u8];
905}
906
907impl LuaTypeExt for lua_types::LuaType {
908 fn type_name(&self) -> &'static [u8] {
909 use lua_types::LuaType::*;
910 match self {
911 None => b"no value",
912 Nil => b"nil",
913 Boolean => b"boolean",
914 LightUserData => b"userdata",
915 Number => b"number",
916 String => b"string",
917 Table => b"table",
918 Function => b"function",
919 UserData => b"userdata",
920 Thread => b"thread",
921 }
922 }
923}
924
925pub trait StackIdxExt {
929 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32;
930 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32;
931 fn raw(self) -> u32;
932}
933impl StackIdxExt for StackIdx {
934 #[inline(always)]
935 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32 {
936 self.0.saturating_sub(n.into().0 .0)
937 }
938 #[inline(always)]
939 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32 {
940 self.0.wrapping_sub(n.into().0 .0)
941 }
942 #[inline(always)]
943 fn raw(self) -> u32 {
944 self.0
945 }
946}
947
948pub trait LuaTableRefExt {
955 fn metatable(&self) -> Option<GcRef<LuaTable>>;
956 fn has_metatable(&self) -> bool;
957 fn as_ptr(&self) -> *const ();
958 fn get(&self, _k: &LuaValue) -> LuaValue;
959 fn get_int(&self, _k: i64) -> LuaValue;
960 fn get_short_str(&self, _k: &GcRef<LuaString>) -> LuaValue;
961 fn raw_set(&self, _state: &mut LuaState, _k: LuaValue, _v: LuaValue) -> Result<(), LuaError>;
962 fn raw_set_int(&self, _state: &mut LuaState, _k: i64, _v: LuaValue) -> Result<(), LuaError>;
963 fn raw_set_short_str(
964 &self,
965 _state: &mut LuaState,
966 _k: GcRef<LuaString>,
967 _v: LuaValue,
968 ) -> Result<(), LuaError>;
969 fn invalidate_tm_cache(&self);
970 fn resize(&self, _state: &mut LuaState, _na: usize, _nh: usize) -> Result<(), LuaError>;
971 fn next(&self, _k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError>;
972}
973impl LuaTableRefExt for GcRef<LuaTable> {
974 #[inline]
975 fn metatable(&self) -> Option<GcRef<LuaTable>> {
976 (**self).metatable()
977 }
978 #[inline]
979 fn has_metatable(&self) -> bool {
980 (**self).has_metatable()
981 }
982 #[inline]
983 fn as_ptr(&self) -> *const () {
984 GcRef::identity(self) as *const ()
985 }
986 #[inline]
987 fn get(&self, k: &LuaValue) -> LuaValue {
988 (**self).get(k)
989 }
990 #[inline]
991 fn get_int(&self, k: i64) -> LuaValue {
992 (**self).get_int(k)
993 }
994 #[inline]
995 fn get_short_str(&self, k: &GcRef<LuaString>) -> LuaValue {
996 (**self).get_short_str(k)
997 }
998 #[inline(always)]
1007 fn raw_set(&self, state: &mut LuaState, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
1008 match k {
1009 LuaValue::Int(i) => return self.raw_set_int(state, i, v),
1010 LuaValue::Str(s) if s.is_short() => return self.raw_set_short_str(state, s, v),
1011 k => {
1012 if k.is_collectable() {
1013 state.gc_table_barrier_back(self, &k);
1014 }
1015 let before = (**self).buffer_bytes();
1016 let result = (**self).try_raw_set(k, v);
1017 if result.is_ok() {
1018 account_table_buffer_delta(self, before);
1019 }
1020 result
1021 }
1022 }
1023 }
1024 #[inline(always)]
1025 fn raw_set_int(&self, _state: &mut LuaState, k: i64, v: LuaValue) -> Result<(), LuaError> {
1026 match (**self).try_update_int(k, v) {
1027 Ok(()) => Ok(()),
1028 Err(v) => {
1029 let before = (**self).buffer_bytes();
1030 let result = (**self).try_raw_set_int(k, v);
1031 if result.is_ok() {
1032 account_table_buffer_delta(self, before);
1033 }
1034 result
1035 }
1036 }
1037 }
1038 #[inline(always)]
1042 fn raw_set_short_str(
1043 &self,
1044 state: &mut LuaState,
1045 k: GcRef<LuaString>,
1046 v: LuaValue,
1047 ) -> Result<(), LuaError> {
1048 match (**self).try_update_short_str(&k, v) {
1049 Ok(()) => Ok(()),
1050 Err(v) => {
1051 state.gc_table_barrier_back(self, &LuaValue::Str(k));
1052 let before = (**self).buffer_bytes();
1053 let result = (**self).try_raw_set(LuaValue::Str(k), v);
1054 if result.is_ok() {
1055 account_table_buffer_delta(self, before);
1056 }
1057 result
1058 }
1059 }
1060 }
1061 fn invalidate_tm_cache(&self) {}
1062 fn resize(&self, _state: &mut LuaState, na: usize, nh: usize) -> Result<(), LuaError> {
1063 let before = (**self).buffer_bytes();
1064 let na32 = na.min(u32::MAX as usize) as u32;
1065 let nh32 = nh.min(u32::MAX as usize) as u32;
1066 let result = (**self).resize(na32, nh32);
1067 if result.is_ok() {
1068 account_table_buffer_delta(self, before);
1069 }
1070 result
1071 }
1072 fn next(&self, k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
1073 (**self).try_next_pair(&k)
1074 }
1075}
1076
1077#[inline]
1078fn account_table_buffer_delta(t: &GcRef<LuaTable>, before: usize) {
1079 let after = (**t).buffer_bytes();
1080 if after > before {
1081 t.account_buffer((after - before) as isize);
1082 } else if before > after {
1083 t.account_buffer(-((before - after) as isize));
1084 }
1085}
1086
1087pub trait LuaUserDataRefExt {
1088 fn metatable(&self) -> Option<GcRef<LuaTable>>;
1089 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>);
1090 fn as_ptr(&self) -> *const ();
1091 fn len(&self) -> usize;
1092}
1093impl LuaUserDataRefExt for GcRef<LuaUserData> {
1094 fn metatable(&self) -> Option<GcRef<LuaTable>> {
1095 (**self).metatable()
1096 }
1097 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>) {
1098 (**self).set_metatable(mt);
1099 }
1100 fn as_ptr(&self) -> *const () {
1101 GcRef::identity(self) as *const ()
1102 }
1103 fn len(&self) -> usize {
1104 self.0.data.len()
1105 }
1106}
1107
1108pub trait LuaStringRefExt {
1109 fn is_white(&self) -> bool;
1110 fn hash(&self) -> u32;
1111 fn as_gc_ref(&self) -> GcRef<LuaString>;
1112}
1113impl LuaStringRefExt for GcRef<LuaString> {
1114 fn is_white(&self) -> bool {
1115 false
1116 }
1117 fn hash(&self) -> u32 {
1118 self.0.hash()
1119 }
1120 fn as_gc_ref(&self) -> GcRef<LuaString> {
1121 self.clone()
1122 }
1123}
1124
1125pub trait LuaLClosureRefExt {
1126 fn proto(&self) -> &GcRef<LuaProto>;
1127 fn nupvalues(&self) -> usize;
1128}
1129impl LuaLClosureRefExt for GcRef<lua_types::closure::LuaLClosure> {
1130 fn proto(&self) -> &GcRef<LuaProto> {
1131 &self.0.proto
1132 }
1133 fn nupvalues(&self) -> usize {
1134 self.0.upvals.len()
1135 }
1136}
1137
1138pub trait LuaClosureExt {
1140 fn nupvalues(&self) -> usize;
1141}
1142impl LuaClosureExt for LuaClosure {
1143 fn nupvalues(&self) -> usize {
1144 match self {
1145 LuaClosure::Lua(l) => l.0.upvals.len(),
1146 LuaClosure::C(c) => c.0.upvalues.borrow().len(),
1147 LuaClosure::LightC(_) => 0,
1148 }
1149 }
1150}
1151
1152pub trait LuaProtoExt {
1154 fn source_bytes(&self) -> &[u8];
1155 fn source_string(&self) -> Option<&GcRef<LuaString>>;
1156}
1157impl LuaProtoExt for LuaProto {
1158 fn source_bytes(&self) -> &[u8] {
1159 match &self.source {
1160 Some(s) => s.0.as_bytes(),
1161 None => &[],
1162 }
1163 }
1164 fn source_string(&self) -> Option<&GcRef<LuaString>> {
1165 self.source.as_ref()
1166 }
1167}
1168
1169pub trait Collectable: std::fmt::Debug {}
1173
1174impl std::fmt::Debug for LuaState {
1175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1176 write!(f, "LuaState")
1177 }
1178}
1179impl Collectable for LuaState {}
1180
1181pub type ParserHook = fn(
1195 state: &mut LuaState,
1196 z: &mut crate::zio::ZIO,
1197 name: &[u8],
1198 firstchar: i32,
1199) -> Result<GcRef<lua_types::closure::LuaLClosure>, LuaError>;
1200
1201pub type FileLoaderHook = fn(filename: &[u8]) -> Result<Vec<u8>, LuaError>;
1209
1210pub type FileOpenHook =
1221 fn(filename: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1222
1223pub type OutputHook = fn(bytes: &[u8]) -> std::io::Result<()>;
1231
1232pub type InputHook = fn(buf: &mut [u8]) -> std::io::Result<usize>;
1235
1236pub type EnvHook = fn(name: &[u8]) -> Option<Vec<u8>>;
1242
1243pub type UnixTimeHook = fn() -> i64;
1245
1246pub type CpuClockHook = fn() -> f64;
1254
1255pub type LocalOffsetHook = fn(timestamp: i64) -> i64;
1267
1268pub type EntropyHook = fn() -> u64;
1272
1273pub type TempNameHook = fn() -> Result<Vec<u8>, LuaError>;
1278
1279pub type PopenHook =
1290 fn(cmd: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1291
1292pub type FileRemoveHook = fn(filename: &[u8]) -> Result<(), LuaError>;
1298
1299pub type FileRenameHook = fn(from: &[u8], to: &[u8]) -> Result<(), LuaError>;
1305
1306#[derive(Clone, Copy, Debug)]
1312pub enum OsExecuteReason {
1313 Exit,
1315 Signal,
1317}
1318
1319#[derive(Debug)]
1322pub struct OsExecuteResult {
1323 pub success: bool,
1325 pub reason: OsExecuteReason,
1327 pub code: i32,
1329}
1330
1331pub type OsExecuteHook = fn(cmd: &[u8]) -> Result<OsExecuteResult, LuaError>;
1338
1339#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1346pub struct DynLibId(pub u64);
1347
1348pub enum DynamicSymbol {
1356 RustNative(LuaCFunction),
1359 LuaCAbi(*const ()),
1365 Unsupported { reason: Vec<u8> },
1368}
1369
1370pub type DynLibLoadHook =
1381 fn(state: &mut LuaState, path: &[u8], see_global: bool) -> Result<DynLibId, LuaError>;
1382
1383pub type DynLibSymbolHook =
1391 fn(state: &mut LuaState, handle: DynLibId, symbol: &[u8]) -> Result<DynamicSymbol, LuaError>;
1392
1393pub type DynLibUnloadHook = fn(handle: DynLibId);
1401
1402pub struct ThreadRegistryEntry {
1408 pub state: Rc<RefCell<LuaState>>,
1413 pub value: GcRef<lua_types::value::LuaThread>,
1416}
1417
1418#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1423pub struct ExternalRootKey {
1424 index: usize,
1425 generation: u64,
1426}
1427
1428#[derive(Debug)]
1429struct ExternalRootSlot {
1430 value: Option<LuaValue>,
1431 generation: u64,
1432}
1433
1434#[derive(Debug, Default)]
1440pub struct ExternalRootSet {
1441 slots: Vec<ExternalRootSlot>,
1442 free: Vec<usize>,
1443 live: usize,
1444}
1445
1446impl ExternalRootSet {
1447 pub fn insert(&mut self, value: LuaValue) -> ExternalRootKey {
1448 if let Some(index) = self.free.pop() {
1449 let slot = &mut self.slots[index];
1450 debug_assert!(slot.value.is_none(), "free external-root slot is occupied");
1451 slot.generation = slot.generation.wrapping_add(1).max(1);
1452 slot.value = Some(value);
1453 self.live += 1;
1454 ExternalRootKey {
1455 index,
1456 generation: slot.generation,
1457 }
1458 } else {
1459 let index = self.slots.len();
1460 self.slots.push(ExternalRootSlot {
1461 value: Some(value),
1462 generation: 1,
1463 });
1464 self.live += 1;
1465 ExternalRootKey {
1466 index,
1467 generation: 1,
1468 }
1469 }
1470 }
1471
1472 pub fn get(&self, key: ExternalRootKey) -> Option<&LuaValue> {
1473 let slot = self.slots.get(key.index)?;
1474 if slot.generation == key.generation {
1475 slot.value.as_ref()
1476 } else {
1477 None
1478 }
1479 }
1480
1481 pub fn replace(&mut self, key: ExternalRootKey, value: LuaValue) -> Option<LuaValue> {
1482 let slot = self.slots.get_mut(key.index)?;
1483 if slot.generation != key.generation || slot.value.is_none() {
1484 return None;
1485 }
1486 slot.value.replace(value)
1487 }
1488
1489 pub fn remove(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
1490 let slot = self.slots.get_mut(key.index)?;
1491 if slot.generation != key.generation {
1492 return None;
1493 }
1494 let old = slot.value.take()?;
1495 self.free.push(key.index);
1496 self.live -= 1;
1497 Some(old)
1498 }
1499
1500 pub fn iter_values(&self) -> impl Iterator<Item = &LuaValue> {
1501 self.slots.iter().filter_map(|slot| slot.value.as_ref())
1502 }
1503
1504 pub fn len(&self) -> usize {
1505 self.live
1506 }
1507
1508 pub fn is_empty(&self) -> bool {
1509 self.live == 0
1510 }
1511
1512 pub fn vacant_len(&self) -> usize {
1513 self.free.len()
1514 }
1515}
1516
1517pub struct GlobalState {
1521 pub parser_hook: Option<ParserHook>,
1527
1528 pub cli_argv: Option<Vec<Vec<u8>>>,
1535
1536 pub cli_preload: Option<fn(&mut LuaState) -> Result<(), LuaError>>,
1540
1541 pub lua_version: lua_types::LuaVersion,
1548
1549 pub file_loader_hook: Option<FileLoaderHook>,
1554
1555 pub file_open_hook: Option<FileOpenHook>,
1560
1561 pub stdout_hook: Option<OutputHook>,
1565
1566 pub stderr_hook: Option<OutputHook>,
1568
1569 pub stdin_hook: Option<InputHook>,
1572
1573 pub env_hook: Option<EnvHook>,
1575
1576 pub unix_time_hook: Option<UnixTimeHook>,
1579
1580 pub cpu_clock_hook: Option<CpuClockHook>,
1583
1584 pub local_offset_hook: Option<LocalOffsetHook>,
1589
1590 pub entropy_hook: Option<EntropyHook>,
1593
1594 pub temp_name_hook: Option<TempNameHook>,
1596
1597 pub popen_hook: Option<PopenHook>,
1602
1603 pub file_remove_hook: Option<FileRemoveHook>,
1606
1607 pub file_rename_hook: Option<FileRenameHook>,
1610
1611 pub os_execute_hook: Option<OsExecuteHook>,
1615
1616 pub dynlib_load_hook: Option<DynLibLoadHook>,
1621
1622 pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
1626
1627 pub dynlib_unload_hook: Option<DynLibUnloadHook>,
1631
1632 pub sandbox: SandboxLimits,
1635
1636 pub gc_debt: isize,
1637
1638 pub gc_estimate: usize,
1639
1640 pub lastatomic: usize,
1641
1642 pub strt: StringPool,
1643
1644 pub l_registry: LuaValue,
1645
1646 pub external_roots: ExternalRootSet,
1649
1650 pub globals: LuaValue,
1656 pub loaded: LuaValue,
1657
1658 pub nilvalue: LuaValue,
1661
1662 pub seed: u32,
1663
1664 pub currentwhite: u8,
1665
1666 pub gcstate: u8,
1667
1668 pub gckind: u8,
1669
1670 pub gcstopem: bool,
1671
1672 pub genminormul: u8,
1673
1674 pub genmajormul: u8,
1675
1676 pub gcstp: u8,
1677
1678 pub gcemergency: bool,
1679
1680 pub gcpause: u8,
1681
1682 pub gcstepmul: u8,
1683
1684 pub gcstepsize: u8,
1685
1686 pub gc55_params: [i64; 6],
1695
1696 pub sweepgc_cursor: usize,
1699
1700 pub weak_tables_registry: lua_gc::WeakRegistry<WeakTableEntry>,
1709
1710 pub finalizers: lua_gc::FinalizerRegistry<FinalizerObject>,
1713
1714 pub gc_finalizer_error: Option<LuaValue>,
1725
1726 pub twups: Vec<GcRef<LuaState>>,
1727
1728 pub panic: Option<LuaCFunction>,
1729
1730 pub mainthread: Option<GcRef<LuaState>>,
1734
1735 pub threads: std::collections::HashMap<u64, ThreadRegistryEntry>,
1747
1748 pub main_thread_value: GcRef<lua_types::value::LuaThread>,
1753
1754 pub current_thread_id: u64,
1758
1759 pub closing_thread_id: Option<u64>,
1762
1763 pub main_thread_id: u64,
1766
1767 pub next_thread_id: u64,
1770
1771 pub thread_globals: std::collections::HashMap<u64, LuaValue>,
1788
1789 pub closure_envs: std::collections::HashMap<usize, LuaValue>,
1801
1802 pub memerrmsg: GcRef<LuaString>,
1803
1804 pub tmname: Vec<GcRef<LuaString>>,
1805
1806 pub mt: [Option<GcRef<LuaTable>>; LUA_NUMTYPES],
1807
1808 pub strcache: [[GcRef<LuaString>; STRCACHE_M]; STRCACHE_N],
1809
1810 pub interned_lt: InternedStringMap,
1816
1817 pub warnf: Option<Box<dyn FnMut(&[u8], bool)>>,
1818
1819 pub warn_mode: WarnMode,
1824
1825 pub test_warn_enabled: bool,
1830 pub test_warn_on: bool,
1831 pub test_warn_mode: TestWarnMode,
1832 pub test_warn_last_to_cont: bool,
1833 pub test_warn_buffer: Vec<u8>,
1834
1835 pub c_functions: Vec<LuaCallable>,
1840
1841 pub heap: std::rc::Rc<lua_gc::Heap>,
1845
1846 pub cross_thread_upvals: std::collections::HashMap<(u64, StackIdx), LuaValue>,
1860
1861 pub suspended_parent_stacks: Vec<Vec<LuaValue>>,
1875
1876 pub suspended_parent_open_upvals: Vec<Vec<GcRef<UpVal>>>,
1882
1883 pub snapshot_stack_pool: Vec<Vec<LuaValue>>,
1891 pub snapshot_upval_pool: Vec<Vec<GcRef<UpVal>>>,
1892
1893 pub resume_value_pool: Vec<Vec<LuaValue>>,
1901
1902 pub resume_upval_slot_pool: Vec<Vec<(u64, StackIdx)>>,
1908
1909 pub resume_flush_pool: Vec<Vec<(StackIdx, LuaValue)>>,
1913}
1914
1915const SANDBOX_COUNT_MASK: u8 = 1 << 3;
1918
1919pub const SANDBOX_TRIP_NONE: u8 = 0;
1921pub const SANDBOX_TRIP_INSTRUCTIONS: u8 = 1;
1923pub const SANDBOX_TRIP_MEMORY: u8 = 2;
1925
1926#[derive(Default)]
1933pub struct SandboxLimits {
1934 pub interval: std::cell::Cell<i32>,
1936 pub instr_limited: std::cell::Cell<bool>,
1938 pub instr_remaining: std::cell::Cell<u64>,
1940 pub instr_limit: std::cell::Cell<u64>,
1942 pub mem_limit: std::cell::Cell<Option<usize>>,
1944 pub tripped: std::cell::Cell<u8>,
1946 pub aborting: std::cell::Cell<bool>,
1951}
1952
1953impl GlobalState {
1954 pub fn sandbox_active(&self) -> bool {
1956 self.sandbox.interval.get() != 0
1957 }
1958
1959 pub fn total_bytes(&self) -> usize {
1962 self.heap.bytes_used().max(1)
1963 }
1964
1965 pub fn get_thread(&self, id: u64) -> Option<&ThreadRegistryEntry> {
1970 self.threads.get(&id)
1971 }
1972
1973 pub fn thread_value_for(&self, id: u64) -> Option<GcRef<lua_types::value::LuaThread>> {
1977 if id == self.main_thread_id {
1978 Some(self.main_thread_value.clone())
1979 } else {
1980 self.threads.get(&id).map(|e| e.value.clone())
1981 }
1982 }
1983
1984 pub fn is_complete(&self) -> bool {
1987 matches!(self.nilvalue, LuaValue::Nil)
1988 }
1989
1990 pub fn current_white(&self) -> u8 {
1996 self.currentwhite
1997 }
1998
1999 pub fn other_white(&self) -> u8 {
2001 self.currentwhite ^ 0x03
2002 }
2003
2004 pub fn is_gen_mode(&self) -> bool {
2006 self.gckind == GcKind::Generational as u8 || self.lastatomic != 0
2007 }
2008
2009 pub fn gc_running(&self) -> bool {
2011 self.gcstp == 0
2012 }
2013
2014 pub fn keep_invariant(&self) -> bool {
2016 self.heap.gc_state().is_invariant()
2017 }
2018
2019 pub fn is_sweep_phase(&self) -> bool {
2021 self.heap.gc_state().is_sweep()
2022 }
2023
2024 pub fn gc_debt(&self) -> isize {
2026 self.gc_debt
2027 }
2028 pub fn set_gc_debt(&mut self, d: isize) {
2029 self.gc_debt = d;
2030 }
2031 pub fn gc_at_pause(&self) -> bool {
2032 self.heap.gc_state().is_pause()
2033 }
2034 fn get_gc_param(p: u8) -> i32 {
2035 (p as i32) * 4
2036 }
2037 fn set_gc_param_slot(slot: &mut u8, p: i32) {
2038 *slot = (p / 4) as u8;
2039 }
2040 pub fn gc_pause_param(&self) -> i32 {
2041 Self::get_gc_param(self.gcpause)
2042 }
2043 pub fn set_gc_pause_param(&mut self, p: i32) {
2044 Self::set_gc_param_slot(&mut self.gcpause, p);
2045 }
2046 pub fn gc_stepmul_param(&self) -> i32 {
2047 Self::get_gc_param(self.gcstepmul)
2048 }
2049 pub fn set_gc_stepmul_param(&mut self, p: i32) {
2050 Self::set_gc_param_slot(&mut self.gcstepmul, p);
2051 }
2052 pub fn gc_genmajormul_param(&self) -> i32 {
2053 Self::get_gc_param(self.genmajormul)
2054 }
2055 pub fn set_gc_genmajormul(&mut self, p: i32) {
2056 Self::set_gc_param_slot(&mut self.genmajormul, p);
2057 }
2058 pub fn gc55_param(&mut self, idx: usize, value: i64) -> i64 {
2062 let old = self.gc55_params[idx];
2063 if value >= 0 {
2064 self.gc55_params[idx] = value;
2065 }
2066 old
2067 }
2068 pub fn gc_stop_flags(&self) -> u8 {
2069 self.gcstp
2070 }
2071 pub fn set_gc_stop_flags(&mut self, f: u8) {
2072 self.gcstp = f;
2073 }
2074 pub fn stop_gc_internal(&mut self) -> u8 {
2075 let old = self.gcstp;
2076 self.gcstp |= GCSTPGC;
2077 old
2078 }
2079 pub fn set_gc_stop_user(&mut self) {
2080 self.gcstp = GCSTPUSR;
2082 }
2083 pub fn clear_gc_stop(&mut self) {
2084 self.gcstp = 0;
2085 }
2086 pub fn is_gc_running(&self) -> bool {
2087 self.gcstp == 0
2088 }
2089 pub fn is_gc_stopped_internally(&self) -> bool {
2094 (self.gcstp & GCSTPGC) != 0
2095 }
2096
2097 pub fn tm_name<T: TmIndex>(&self, tm: T) -> Option<GcRef<LuaString>> {
2105 self.tmname.get(tm.tm_index()).cloned()
2106 }
2107}
2108
2109pub trait TmIndex: Copy {
2115 fn tm_index(self) -> usize;
2116}
2117impl TmIndex for lua_types::tagmethod::TagMethod {
2118 fn tm_index(self) -> usize {
2119 self as u8 as usize
2120 }
2121}
2122impl TmIndex for crate::tagmethods::TagMethod {
2123 fn tm_index(self) -> usize {
2124 self as u8 as usize
2125 }
2126}
2127impl TmIndex for usize {
2128 fn tm_index(self) -> usize {
2129 self
2130 }
2131}
2132impl TmIndex for u8 {
2133 fn tm_index(self) -> usize {
2134 self as usize
2135 }
2136}
2137
2138use lua_types::tagmethod::TagMethod;
2139
2140pub struct LuaState {
2148 pub status: u8,
2151
2152 pub allowhook: bool,
2153
2154 pub nci: u32,
2155
2156 pub top: StackIdx,
2159
2160 pub stack_last: StackIdx,
2163
2164 pub stack: Vec<StackValue>,
2165
2166 pub ci: CallInfoIdx,
2169
2170 pub call_info: Vec<CallInfo>,
2172
2173 pub openupval: Vec<GcRef<UpVal>>,
2176
2177 pub legacy_open_upval_touched: std::cell::Cell<bool>,
2189
2190 pub tbclist: Vec<StackIdx>,
2191
2192 pub(crate) global: Rc<RefCell<GlobalState>>,
2198
2199 pub hook: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>,
2202
2203 pub hookmask: u8,
2204
2205 pub basehookcount: i32,
2206
2207 pub hookcount: i32,
2208
2209 pub errfunc: isize,
2215
2216 pub n_ccalls: u32,
2219
2220 pub oldpc: u32,
2223
2224 pub marked: u8,
2227
2228 pub cached_thread_id: u64,
2244
2245 pub gc_check_needed: bool,
2250}
2251
2252impl LuaState {
2253 pub fn global(&self) -> std::cell::Ref<'_, GlobalState> {
2259 self.global.borrow()
2260 }
2261
2262 pub fn global_mut(&self) -> std::cell::RefMut<'_, GlobalState> {
2264 self.global.borrow_mut()
2265 }
2266
2267 pub fn global_rc(&self) -> Rc<RefCell<GlobalState>> {
2271 Rc::clone(&self.global)
2272 }
2273
2274 pub fn v51_thread_lgt(&self, id: u64) -> LuaValue {
2283 let g = self.global();
2284 if id == g.main_thread_id {
2285 g.globals.clone()
2286 } else {
2287 g.thread_globals
2288 .get(&id)
2289 .expect("v51 coroutine l_gt must be seeded in new_thread")
2290 .clone()
2291 }
2292 }
2293
2294 pub fn v51_set_thread_lgt(&self, id: u64, t: LuaValue) {
2299 let mut g = self.global_mut();
2300 if id == g.main_thread_id {
2301 g.globals = t;
2302 } else {
2303 g.thread_globals.insert(id, t);
2304 }
2305 }
2306
2307 pub fn enable_test_warning_handler(&mut self) -> Result<(), LuaError> {
2309 {
2310 let mut g = self.global_mut();
2311 g.test_warn_enabled = true;
2312 g.test_warn_on = false;
2313 g.test_warn_mode = TestWarnMode::Normal;
2314 g.test_warn_last_to_cont = false;
2315 g.test_warn_buffer.clear();
2316 }
2317 self.push(LuaValue::Bool(false));
2318 crate::api::set_global(self, b"_WARN")
2319 }
2320
2321 pub fn c_calls(&self) -> u32 {
2323 self.n_ccalls & 0xffff
2324 }
2325
2326 pub fn inc_nny(&mut self) {
2328 self.n_ccalls += 0x10000;
2329 }
2330
2331 pub fn dec_nny(&mut self) {
2333 self.n_ccalls -= 0x10000;
2334 }
2335
2336 pub fn is_yieldable(&self) -> bool {
2338 (self.n_ccalls & 0xffff0000) == 0
2339 }
2340
2341 pub fn reset_hook_count(&mut self) {
2343 self.hookcount = self.basehookcount;
2344 }
2345
2346 pub fn install_sandbox_limits(
2355 &mut self,
2356 interval: i32,
2357 instr_limit: Option<u64>,
2358 mem_limit: Option<usize>,
2359 ) {
2360 let interval = interval.max(1);
2361 {
2362 let g = self.global();
2363 g.sandbox.interval.set(interval);
2364 g.sandbox.instr_limited.set(instr_limit.is_some());
2365 g.sandbox.instr_remaining.set(instr_limit.unwrap_or(0));
2366 g.sandbox.instr_limit.set(instr_limit.unwrap_or(0));
2367 g.sandbox.mem_limit.set(mem_limit);
2368 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2369 }
2370 self.hookmask |= SANDBOX_COUNT_MASK;
2371 self.basehookcount = interval;
2372 self.hookcount = interval;
2373 crate::debug::arm_traps(self);
2374 }
2375
2376 pub fn sandbox_charge_interval(&self) -> Option<LuaError> {
2381 let interval = self.global().sandbox.interval.get();
2382 self.sandbox_charge(interval as u64)
2383 }
2384
2385 pub fn sandbox_charge(&self, amount: u64) -> Option<LuaError> {
2394 let g = self.global();
2395 if g.sandbox.interval.get() == 0 {
2396 return None;
2397 }
2398 if g.sandbox.instr_limited.get() {
2399 let rem = g.sandbox.instr_remaining.get().saturating_sub(amount);
2400 g.sandbox.instr_remaining.set(rem);
2401 if rem == 0 {
2402 g.sandbox.tripped.set(SANDBOX_TRIP_INSTRUCTIONS);
2403 g.sandbox.aborting.set(true);
2404 return Some(LuaError::runtime(format_args!(
2405 "sandbox: instruction budget exhausted"
2406 )));
2407 }
2408 }
2409 if let Some(limit) = g.sandbox.mem_limit.get() {
2410 if g.total_bytes() > limit {
2411 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2412 g.sandbox.aborting.set(true);
2413 return Some(LuaError::runtime(format_args!(
2414 "sandbox: memory limit exceeded"
2415 )));
2416 }
2417 }
2418 None
2419 }
2420
2421 pub fn sandbox_reserve(&self, additional: usize) -> Option<LuaError> {
2429 let g = self.global();
2430 if g.sandbox.interval.get() == 0 {
2431 return None;
2432 }
2433 if let Some(limit) = g.sandbox.mem_limit.get() {
2434 let projected = g.total_bytes().saturating_add(additional);
2435 if projected > limit {
2436 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2437 g.sandbox.aborting.set(true);
2438 return Some(LuaError::runtime(format_args!(
2439 "sandbox: memory limit exceeded"
2440 )));
2441 }
2442 }
2443 None
2444 }
2445
2446 pub fn sandbox_match_step_limit(&self) -> u64 {
2451 let g = self.global();
2452 if g.sandbox.interval.get() != 0 && g.sandbox.instr_limited.get() {
2453 g.sandbox.instr_remaining.get()
2454 } else {
2455 0
2456 }
2457 }
2458
2459 pub fn sandbox_aborting(&self) -> bool {
2463 self.global().sandbox.aborting.get()
2464 }
2465
2466 pub fn sandbox_instr_limited(&self) -> bool {
2468 self.global().sandbox.instr_limited.get()
2469 }
2470
2471 pub fn sandbox_instr_remaining(&self) -> u64 {
2474 self.global().sandbox.instr_remaining.get()
2475 }
2476
2477 pub fn sandbox_instr_limit(&self) -> u64 {
2479 self.global().sandbox.instr_limit.get()
2480 }
2481
2482 pub fn sandbox_tripped_code(&self) -> u8 {
2484 self.global().sandbox.tripped.get()
2485 }
2486
2487 pub fn sandbox_reset(&self) {
2490 let g = self.global();
2491 if g.sandbox.instr_limited.get() {
2492 g.sandbox.instr_remaining.set(g.sandbox.instr_limit.get());
2493 }
2494 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2495 g.sandbox.aborting.set(false);
2496 }
2497
2498 pub fn stack_size(&self) -> usize {
2500 self.stack_last.0 as usize
2501 }
2502
2503 #[inline(always)]
2505 pub fn push(&mut self, val: LuaValue) {
2506 let top = self.top.0 as usize;
2507 if top < self.stack.len() {
2508 self.stack[top] = StackValue { val };
2509 } else {
2510 self.stack.push(StackValue { val });
2511 }
2512 self.top = StackIdx(self.top.0 + 1);
2513 }
2514
2515 #[inline(always)]
2518 pub fn pop(&mut self) -> LuaValue {
2519 if self.top.0 == 0 {
2520 return LuaValue::Nil;
2521 }
2522 self.top = StackIdx(self.top.0 - 1);
2523 self.stack[self.top.0 as usize].val.clone()
2524 }
2525
2526 #[inline(always)]
2528 pub fn stack_val(&self, idx: StackIdx) -> &LuaValue {
2529 &self.stack[idx.0 as usize].val
2530 }
2531
2532 #[inline(always)]
2534 pub fn set_stack_val(&mut self, idx: StackIdx, val: LuaValue) {
2535 self.stack[idx.0 as usize].val = val;
2536 }
2537
2538 pub fn gc(&mut self) -> GcHandle<'_> {
2541 GcHandle { _state: self }
2542 }
2543
2544 pub fn external_root_value(&mut self, value: LuaValue) -> ExternalRootKey {
2546 self.global_mut().external_roots.insert(value)
2547 }
2548
2549 pub fn external_rooted_value(&self, key: ExternalRootKey) -> Option<LuaValue> {
2551 self.global().external_roots.get(key).cloned()
2552 }
2553
2554 pub fn external_replace_root(
2556 &mut self,
2557 key: ExternalRootKey,
2558 value: LuaValue,
2559 ) -> Option<LuaValue> {
2560 self.global_mut().external_roots.replace(key, value)
2561 }
2562
2563 pub fn external_unroot_value(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
2565 self.global_mut().external_roots.remove(key)
2566 }
2567
2568 pub fn try_external_unroot_value(
2571 &mut self,
2572 key: ExternalRootKey,
2573 ) -> std::result::Result<Option<LuaValue>, std::cell::BorrowMutError> {
2574 self.global
2575 .try_borrow_mut()
2576 .map(|mut global| global.external_roots.remove(key))
2577 }
2578
2579 pub fn new_table(&mut self) -> GcRef<LuaTable> {
2581 self.mark_gc_check_needed();
2582 GcRef::new(LuaTable::placeholder())
2583 }
2584
2585 pub fn new_table_with_sizes(
2590 &mut self,
2591 array_size: u32,
2592 hash_size: u32,
2593 ) -> Result<GcRef<LuaTable>, LuaError> {
2594 self.mark_gc_check_needed();
2595 let t = GcRef::new(LuaTable::placeholder());
2596 self.table_resize(&t, array_size as usize, hash_size as usize)?;
2597 Ok(t)
2598 }
2599
2600 pub fn intern_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
2614 if bytes.len() <= crate::string::MAX_SHORT_LEN {
2615 let hash = LuaString::hash_bytes(bytes, 0);
2616 {
2617 let global = self.global();
2618 if let Some(existing) = global.interned_lt.find(bytes, hash) {
2619 return Ok(existing);
2620 }
2621 }
2622 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2623 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2624 self.global_mut().interned_lt.insert(new_ref.clone());
2625 self.mark_gc_check_needed();
2626 Ok(new_ref)
2627 } else {
2628 self.mark_gc_check_needed();
2629 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2630 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2631 Ok(new_ref)
2632 }
2633 }
2634
2635 #[inline(always)]
2637 pub fn top_idx(&self) -> StackIdx {
2638 self.top
2639 }
2640}
2641
2642impl LuaState {
2647 #[inline(always)]
2648 pub fn get_at(&self, idx: impl Into<StackIdxConv>) -> LuaValue {
2649 let i: StackIdx = idx.into().0;
2650 match self.stack.get(i.0 as usize) {
2651 Some(slot) => slot.val.clone(),
2652 None => LuaValue::Nil,
2653 }
2654 }
2655 #[inline(always)]
2656 pub fn set_at(&mut self, idx: impl Into<StackIdxConv>, v: LuaValue) {
2657 let i: StackIdx = idx.into().0;
2658 self.stack[i.0 as usize].val = v;
2659 }
2660
2661 pub fn clear_stack_range(&mut self, start: StackIdx, end: StackIdx) {
2675 if end.0 <= start.0 {
2676 return;
2677 }
2678 let end_u = end.0 as usize;
2679 if end_u > self.stack.len() {
2680 self.stack.resize_with(end_u, StackValue::default);
2681 }
2682 for i in start.0..end.0 {
2683 self.stack[i as usize].val = LuaValue::Nil;
2684 }
2685 }
2686 #[inline(always)]
2694 pub fn get_int_at(&self, idx: impl Into<StackIdxConv>) -> Option<i64> {
2695 let i: StackIdx = idx.into().0;
2696 match self.stack.get(i.0 as usize) {
2697 Some(slot) => match &slot.val {
2698 LuaValue::Int(v) => Some(*v),
2699 _ => None,
2700 },
2701 None => None,
2702 }
2703 }
2704 #[inline(always)]
2711 pub fn get_int_pair_at(
2712 &self,
2713 rb: impl Into<StackIdxConv>,
2714 rc: impl Into<StackIdxConv>,
2715 ) -> Option<(i64, i64)> {
2716 let rb: StackIdx = rb.into().0;
2717 let rc: StackIdx = rc.into().0;
2718 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2719 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib, ic)),
2720 _ => None,
2721 }
2722 }
2723 #[inline(always)]
2728 pub fn get_num_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2729 let i: StackIdx = idx.into().0;
2730 match self.stack.get(i.0 as usize) {
2731 Some(slot) => match &slot.val {
2732 LuaValue::Float(f) => Some(*f),
2733 LuaValue::Int(v) => Some(*v as f64),
2734 _ => None,
2735 },
2736 None => None,
2737 }
2738 }
2739 #[inline(always)]
2744 pub fn get_float_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2745 let i: StackIdx = idx.into().0;
2746 match self.stack.get(i.0 as usize) {
2747 Some(slot) => match &slot.val {
2748 LuaValue::Float(f) => Some(*f),
2749 _ => None,
2750 },
2751 None => None,
2752 }
2753 }
2754 #[inline(always)]
2759 pub fn get_num_pair_at(
2760 &self,
2761 rb: impl Into<StackIdxConv>,
2762 rc: impl Into<StackIdxConv>,
2763 ) -> Option<(f64, f64)> {
2764 let rb: StackIdx = rb.into().0;
2765 let rc: StackIdx = rc.into().0;
2766 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2767 (LuaValue::Float(nb), LuaValue::Float(nc)) => Some((nb, nc)),
2768 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib as f64, ic as f64)),
2769 (LuaValue::Int(ib), LuaValue::Float(nc)) => Some((ib as f64, nc)),
2770 (LuaValue::Float(nb), LuaValue::Int(ic)) => Some((nb, ic as f64)),
2771 _ => None,
2772 }
2773 }
2774 #[inline(always)]
2787 pub fn set_top(&mut self, idx: impl Into<StackIdxConv>) {
2788 let new_top: StackIdx = idx.into().0;
2789 let new_top_u = new_top.0 as usize;
2790 if new_top_u > self.stack.len() {
2791 self.stack.resize_with(new_top_u, StackValue::default);
2792 }
2793 self.top = new_top;
2794 }
2795 #[inline(always)]
2801 pub fn set_top_idx(&mut self, idx: impl Into<StackIdxConv>) {
2802 let new_top: StackIdx = idx.into().0;
2803 self.top = new_top;
2804 }
2805 #[inline(always)]
2808 pub fn dec_top(&mut self) {
2809 if self.top.0 > 0 {
2810 self.top = StackIdx(self.top.0 - 1);
2811 }
2812 }
2813 #[inline(always)]
2814 pub fn pop_n(&mut self, n: usize) {
2815 let cur = self.top.0 as usize;
2816 let new = cur.saturating_sub(n);
2817 self.top = StackIdx(new as u32);
2818 }
2819 #[inline(always)]
2822 pub fn peek_at(&mut self, idx: impl Into<StackIdxConv>) -> LuaValue {
2823 let i: StackIdx = idx.into().0;
2824 match self.stack.get(i.0 as usize) {
2825 Some(slot) => slot.val.clone(),
2826 None => LuaValue::Nil,
2827 }
2828 }
2829 #[inline(always)]
2833 pub fn peek_top(&mut self) -> LuaValue {
2834 if self.top.0 == 0 {
2835 return LuaValue::Nil;
2836 }
2837 self.stack[(self.top.0 - 1) as usize].val.clone()
2838 }
2839 pub fn peek_string_at_top(&mut self) -> GcRef<LuaString> {
2844 match self.peek_top() {
2845 LuaValue::Str(s) => s,
2846 _ => panic!("peek_string_at_top: top of stack is not a string"),
2847 }
2848 }
2849 pub fn stack_at(&mut self, idx: impl Into<StackIdxConv>) -> &mut LuaValue {
2852 let i: StackIdx = idx.into().0;
2853 &mut self.stack[i.0 as usize].val
2854 }
2855 pub fn stack_set_nil(&mut self, idx: impl Into<StackIdxConv>) {
2858 let i: StackIdx = idx.into().0;
2859 let slot = i.0 as usize;
2860 if slot < self.stack.len() {
2861 self.stack[slot].val = LuaValue::Nil;
2862 }
2863 }
2864 pub fn stack_resize(&mut self, size: usize) -> Result<(), LuaError> {
2872 self.stack.resize_with(size, StackValue::default);
2873 Ok(())
2874 }
2875 pub fn stack_available(&mut self) -> usize {
2876 (self.stack_last.0 as usize).saturating_sub(self.top.0 as usize)
2877 }
2878 pub fn check_stack(&mut self, n: i32) -> Result<(), LuaError> {
2879 let free = (self.stack_last.0 as i32) - (self.top.0 as i32);
2880 if free <= n {
2881 self.grow_stack(n, true)?;
2882 }
2883 Ok(())
2884 }
2885 pub fn grow_stack(&mut self, n: i32, raise_error: bool) -> Result<(), LuaError> {
2894 crate::do_::grow_stack(self, n, raise_error).map(|_| ())
2895 }
2896
2897 #[inline(always)]
2898 pub fn get_ci(&self, idx: CallInfoIdx) -> &CallInfo {
2899 &self.call_info[idx.as_usize()]
2900 }
2901 #[inline(always)]
2902 pub fn get_ci_mut(&mut self, idx: CallInfoIdx) -> &mut CallInfo {
2903 &mut self.call_info[idx.as_usize()]
2904 }
2905 #[inline(always)]
2906 pub fn current_call_info(&self) -> &CallInfo {
2907 &self.call_info[self.ci.as_usize()]
2908 }
2909 #[inline(always)]
2910 pub fn current_call_info_mut(&mut self) -> &mut CallInfo {
2911 let i = self.ci.as_usize();
2912 &mut self.call_info[i]
2913 }
2914 #[inline(always)]
2915 pub fn current_ci_idx(&self) -> CallInfoIdx {
2916 self.ci
2917 }
2918 pub fn call_stack_mut(&mut self) -> &mut Vec<CallInfo> {
2919 &mut self.call_info
2920 }
2921 #[inline(always)]
2922 pub fn next_ci(&mut self) -> Result<CallInfoIdx, LuaError> {
2923 let idx = match self.call_info[self.ci.as_usize()].next {
2924 Some(idx) => idx,
2925 None => extend_ci(self),
2926 };
2927 self.call_info[idx.as_usize()].tailcalls = 0;
2928 Ok(idx)
2929 }
2930
2931 #[inline(always)]
2938 pub fn note_lua_tailcall(&mut self, ci: CallInfoIdx) {
2939 if self.global().lua_version == lua_types::LuaVersion::V51 {
2940 self.call_info[ci.as_usize()].tailcalls =
2941 self.call_info[ci.as_usize()].tailcalls.saturating_add(1);
2942 }
2943 }
2944 #[inline(always)]
2945 pub fn prev_ci(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
2946 self.call_info[idx.as_usize()].previous
2947 }
2948 pub fn get_prev_ci(&self, idx: CallInfoIdx) -> Option<&CallInfo> {
2949 self.call_info[idx.as_usize()]
2950 .previous
2951 .map(|p| &self.call_info[p.as_usize()])
2952 }
2953 #[inline(always)]
2954 pub fn is_base_ci(&self, idx: CallInfoIdx) -> bool {
2955 idx.as_usize() == 0
2956 }
2957 #[inline(always)]
2958 pub fn is_current_ci(&self, idx: CallInfoIdx) -> bool {
2959 idx == self.ci
2960 }
2961 pub fn ci_next_func(&self, idx: CallInfoIdx) -> StackIdx {
2962 let next = self.call_info[idx.as_usize()]
2963 .next
2964 .expect("ci_next_func: no next CallInfo");
2965 self.call_info[next.as_usize()].func
2966 }
2967 #[inline(always)]
2968 pub fn ci_top(&self, idx: CallInfoIdx) -> StackIdx {
2969 self.call_info[idx.as_usize()].top
2970 }
2971 #[inline(always)]
2976 pub fn ci_trap(&mut self, idx: CallInfoIdx) -> bool {
2977 self.call_info[idx.as_usize()].trap()
2978 }
2979 #[inline(always)]
2980 pub fn ci_savedpc(&self, idx: CallInfoIdx) -> u32 {
2981 self.call_info[idx.as_usize()].saved_pc()
2982 }
2983 #[inline(always)]
2984 pub fn set_ci_savedpc(&mut self, idx: CallInfoIdx, pc: u32) {
2985 self.call_info[idx.as_usize()].set_saved_pc(pc);
2986 }
2987 #[inline(always)]
2988 pub fn set_ci_previous(&mut self, idx: CallInfoIdx) {
2989 self.ci = self.call_info[idx.as_usize()]
2990 .previous
2991 .expect("set_ci_previous: returning frame has no previous CallInfo");
2992 }
2993 #[inline(always)]
2994 pub fn ci_previous(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
2995 self.call_info[idx.as_usize()].previous
2996 }
2997 #[inline(always)]
2998 pub fn ci_adjust_func(&mut self, idx: CallInfoIdx, delta: i32) {
2999 let ci = &mut self.call_info[idx.as_usize()];
3000 ci.func = StackIdx((ci.func.0 as i32 - delta) as u32);
3001 }
3002 #[inline(always)]
3003 pub fn ci_base(&self, idx: CallInfoIdx) -> StackIdx {
3004 self.call_info[idx.as_usize()].func + 1
3005 }
3006 #[inline(always)]
3007 pub fn ci_is_fresh(&self, idx: CallInfoIdx) -> bool {
3008 (self.call_info[idx.as_usize()].callstatus & CIST_FRESH) != 0
3009 }
3010 #[inline(always)]
3011 pub fn ci_lua_closure(
3012 &self,
3013 idx: CallInfoIdx,
3014 ) -> Option<GcRef<lua_types::closure::LuaLClosure>> {
3015 let func_idx = self.call_info[idx.as_usize()].func;
3016 match self.stack.get(func_idx.0 as usize).map(|slot| slot.val) {
3017 Some(LuaValue::Function(lua_types::closure::LuaClosure::Lua(cl))) => Some(cl),
3018 _ => None,
3019 }
3020 }
3021 #[inline(always)]
3022 pub fn ci_nextraargs(&self, idx: CallInfoIdx) -> i32 {
3023 self.call_info[idx.as_usize()].nextra_args()
3024 }
3025 #[inline(always)]
3026 pub fn ci_nres(&self, idx: CallInfoIdx) -> i32 {
3027 self.call_info[idx.as_usize()].u2.value
3028 }
3029 #[inline(always)]
3030 pub fn ci_nres_set(&mut self, idx: CallInfoIdx, n: i32) {
3031 self.call_info[idx.as_usize()].u2.value = n;
3032 }
3033 #[inline(always)]
3034 pub fn ci_nresults(&self, idx: CallInfoIdx) -> i32 {
3035 self.call_info[idx.as_usize()].nresults as i32
3036 }
3037 pub fn ci_prev_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3038 let pc = self.call_info[idx.as_usize()].saved_pc();
3039 let cl = self
3040 .ci_lua_closure(idx)
3041 .expect("ci_prev_instruction: CallInfo does not hold a Lua closure");
3042 cl.proto.code[(pc - 1) as usize]
3043 }
3044 pub fn ci_prev2_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3045 let pc = self.call_info[idx.as_usize()].saved_pc();
3046 let cl = self
3047 .ci_lua_closure(idx)
3048 .expect("ci_prev2_instruction: CallInfo does not hold a Lua closure");
3049 cl.proto.code[(pc - 2) as usize]
3050 }
3051 pub fn ci_skip_next_instruction(&mut self, idx: CallInfoIdx) {
3052 let pc = self.call_info[idx.as_usize()].saved_pc();
3053 self.call_info[idx.as_usize()].set_saved_pc(pc + 1);
3054 }
3055 pub fn ci_step_pc_back(&mut self, idx: CallInfoIdx) {
3056 let pc = self.call_info[idx.as_usize()].saved_pc();
3057 self.call_info[idx.as_usize()].set_saved_pc(pc - 1);
3058 }
3059 pub fn get_ci_pcrel(&mut self, idx: CallInfoIdx) -> u32 {
3060 self.call_info[idx.as_usize()].saved_pc().saturating_sub(1)
3061 }
3062 pub fn get_ci_u2_funcidx(&mut self, idx: CallInfoIdx) -> i32 {
3063 self.call_info[idx.as_usize()].u2.value
3064 }
3065 pub fn get_ci_u2_nres(&mut self, idx: CallInfoIdx) -> i32 {
3066 self.call_info[idx.as_usize()].u2.value
3067 }
3068 pub fn get_ci_u2_nyield(&mut self, idx: CallInfoIdx) -> i32 {
3069 self.call_info[idx.as_usize()].u2.value
3070 }
3071 pub fn get_ci_vararg_info(&mut self, idx: CallInfoIdx) -> (bool, i32, i32) {
3072 let nextraargs = self.call_info[idx.as_usize()].nextra_args();
3073 match self.ci_lua_closure(idx) {
3074 Some(cl) => (cl.proto.is_vararg, nextraargs, cl.proto.numparams as i32),
3075 None => (false, nextraargs, 0),
3076 }
3077 }
3078 pub fn get_ci_lua_proto_numparams(&mut self, idx: CallInfoIdx) -> u8 {
3079 self.ci_lua_closure(idx)
3080 .map(|cl| cl.proto.numparams)
3081 .unwrap_or(0)
3082 }
3083 pub fn set_ci_u2_nres(&mut self, idx: CallInfoIdx, n: i32) {
3084 self.call_info[idx.as_usize()].u2.value = n;
3085 }
3086 pub fn set_ci_u2_nyield(&mut self, idx: CallInfoIdx, n: i32) {
3087 self.call_info[idx.as_usize()].u2.value = n;
3088 }
3089 pub fn set_ci_transfer_info(&mut self, idx: CallInfoIdx, ftransfer: u16, ntransfer: u16) {
3090 let ci = &mut self.call_info[idx.as_usize()];
3091 ci.u2.ftransfer = ftransfer;
3092 ci.u2.ntransfer = ntransfer;
3093 }
3094 pub fn shrink_ci(&mut self) {
3095 shrink_ci(self)
3096 }
3097 pub fn check_c_stack(&mut self) -> Result<(), LuaError> {
3098 check_c_stack(self)
3099 }
3100
3101 pub fn status(&mut self) -> LuaStatus {
3102 LuaStatus::from_raw(self.status as i32)
3103 }
3104 pub fn errfunc(&mut self) -> isize {
3105 self.errfunc
3106 }
3107 pub fn old_pc(&mut self) -> u32 {
3108 self.oldpc
3109 }
3110 pub fn set_old_pc(&mut self, pc: u32) {
3111 self.oldpc = pc;
3112 }
3113 pub fn set_oldpc(&mut self, pc: u32) {
3114 self.oldpc = pc;
3115 }
3116 pub fn _hook_call_noargs(&mut self) {}
3117 pub fn hook(&self) -> Option<&Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>> {
3118 self.hook.as_ref()
3119 }
3120 pub fn has_hook(&mut self) -> bool {
3121 self.hook.is_some()
3122 }
3123 pub fn hook_count(&mut self) -> i32 {
3124 self.hookcount
3125 }
3126 pub fn set_hook_count(&mut self, n: i32) {
3127 self.hookcount = n;
3128 }
3129 pub fn hook_mask(&self) -> u8 {
3130 self.hookmask
3131 }
3132 pub fn set_hook_mask(&mut self, m: u8) {
3133 self.hookmask = m;
3134 }
3135 pub fn base_hook_count(&self) -> i32 {
3136 self.basehookcount
3137 }
3138 pub fn set_base_hook_count(&mut self, n: i32) {
3139 self.basehookcount = n;
3140 }
3141 pub fn set_hook(&mut self, h: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>) {
3142 self.hook = h;
3143 }
3144 pub fn call_hook_event(&mut self, event: i32, line: i32) -> Result<(), LuaError> {
3163 let saved_ci = self.ci;
3164 let r = crate::do_::hook(self, event, line, 0, 0);
3165 self.ci = saved_ci;
3166 r
3167 }
3168
3169 pub fn registry_value(&self) -> LuaValue {
3170 self.global().l_registry.clone()
3171 }
3172 pub fn registry_get(&self, key: usize) -> LuaValue {
3173 let reg = self.global().l_registry.clone();
3174 match reg {
3175 LuaValue::Table(t) => t.get(&LuaValue::Int(key as i64)),
3176 _ => LuaValue::Nil,
3177 }
3178 }
3179
3180 pub fn new_string(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3181 self.intern_or_create_str(bytes)
3182 }
3183
3184 pub fn new_proto(&mut self) -> GcRef<LuaProto> {
3195 self.mark_gc_check_needed();
3196 GcRef::new(LuaProto::placeholder())
3197 }
3198
3199 pub fn new_lclosure(&mut self, proto: GcRef<LuaProto>, nupvals: usize) -> GcRef<LuaClosureLua> {
3201 self.mark_gc_check_needed();
3202 let mut upvals = Vec::with_capacity(nupvals);
3203 for _ in 0..nupvals {
3204 upvals.push(std::cell::Cell::new(self.new_upval_closed(LuaValue::Nil)));
3205 }
3206 let upvals = upvals.into_boxed_slice();
3207 let closure = GcRef::new(LuaClosureLua { proto, upvals });
3208 closure.account_buffer(closure.buffer_bytes() as isize);
3209 closure
3210 }
3211
3212 pub fn new_upval_closed(&mut self, v: LuaValue) -> GcRef<UpVal> {
3214 self.mark_gc_check_needed();
3215 GcRef::new(UpVal::closed(v))
3216 }
3217
3218 pub fn new_upval_open(&mut self, thread_id: usize, level: StackIdx) -> GcRef<UpVal> {
3220 self.mark_gc_check_needed();
3221 self.legacy_open_upval_touched.set(true);
3222 GcRef::new(UpVal::open(thread_id, level))
3223 }
3224 pub fn intern_or_create_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3231 self.intern_str(bytes)
3232 }
3233 pub fn new_userdata(
3234 &mut self,
3235 _size: usize,
3236 _nuvalue: usize,
3237 ) -> Result<GcRef<LuaUserData>, LuaError> {
3238 Err(LuaError::runtime(format_args!(
3239 "new_userdata not implemented in this Phase-B build; use new_userdata_typed instead"
3240 )))
3241 }
3242 pub fn new_c_closure(&mut self, _f: LuaCFunction, _n: i32) -> Result<LuaClosure, LuaError> {
3243 Err(LuaError::runtime(format_args!("new_c_closure not implemented in this Phase-B build; use push_cclosure in lua_vm::api instead")))
3244 }
3245 pub fn push_closure(
3246 &mut self,
3247 proto_idx: usize,
3248 ci: CallInfoIdx,
3249 base: StackIdx,
3250 ra: StackIdx,
3251 ) -> Result<(), LuaError> {
3252 let parent_cl = self
3253 .ci_lua_closure(ci)
3254 .expect("push_closure: current frame is not a Lua closure");
3255 let child_proto = parent_cl.proto.p[proto_idx].clone();
3256 let nup = child_proto.upvalues.len();
3257 let mut upvals: Vec<std::cell::Cell<GcRef<UpVal>>> = Vec::with_capacity(nup);
3258 for i in 0..nup {
3259 let desc = &child_proto.upvalues[i];
3260 let uv = if desc.instack {
3261 let level = base + desc.idx as i32;
3262 crate::func::find_upval(self, level)
3263 } else {
3264 parent_cl.upval(desc.idx as usize)
3265 };
3266 upvals.push(std::cell::Cell::new(uv));
3267 }
3268 let cache_enabled = matches!(
3272 self.global().lua_version,
3273 lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
3274 );
3275 if cache_enabled {
3276 if let Some(cached) = child_proto.cache.borrow().as_ref() {
3277 if cached.upvals.len() == nup
3278 && (0..nup).all(|i| GcRef::ptr_eq(&cached.upvals[i].get(), &upvals[i].get()))
3279 {
3280 let reused = cached.clone();
3281 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(reused)));
3282 return Ok(());
3283 }
3284 }
3285 }
3286 self.mark_gc_check_needed();
3290 let new_cl = GcRef::new(LuaClosureLua {
3291 proto: child_proto.clone(),
3292 upvals: upvals.into_boxed_slice(),
3293 });
3294 new_cl.account_buffer(new_cl.buffer_bytes() as isize);
3295 if cache_enabled {
3296 *child_proto.cache.borrow_mut() = Some(new_cl.clone());
3297 }
3298 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(new_cl)));
3299 Ok(())
3300 }
3301 pub fn new_tbc_upval(&mut self, idx: StackIdx) -> Result<(), LuaError> {
3302 crate::func::new_tbc_upval(self, idx)
3303 }
3304
3305 #[inline(always)]
3326 pub fn upvalue_get(&self, cl: &GcRef<LuaClosureLua>, n: usize) -> LuaValue {
3327 let uv = cl.upval(n);
3328 let (thread_id, idx) = match uv.try_open_payload() {
3329 Some(p) => p,
3330 None => return uv.closed_value(),
3331 };
3332 let current = self.cached_thread_id;
3333 let tid = thread_id as u64;
3334 if tid == current {
3335 return self.stack[idx.0 as usize].val;
3336 }
3337 self.upvalue_get_cross_thread(tid, idx)
3338 }
3339
3340 #[cold]
3341 #[inline(never)]
3342 fn upvalue_get_cross_thread(&self, tid: u64, idx: StackIdx) -> LuaValue {
3343 let entry_rc = {
3344 let g = self.global();
3345 g.threads.get(&tid).map(|e| e.state.clone())
3346 };
3347 if let Some(rc) = entry_rc {
3348 if let Ok(home_state) = rc.try_borrow() {
3349 return home_state.get_at(idx);
3350 }
3351 }
3352 let g = self.global();
3353 match g.cross_thread_upvals.get(&(tid, idx)) {
3354 Some(v) => *v,
3355 None => LuaValue::Nil,
3356 }
3357 }
3358 #[inline(always)]
3367 pub fn upvalue_set(
3368 &mut self,
3369 cl: &GcRef<LuaClosureLua>,
3370 n: usize,
3371 val: LuaValue,
3372 ) -> Result<(), LuaError> {
3373 let uv = cl.upval(n);
3374 match uv.try_open_payload() {
3375 Some((thread_id, idx)) => {
3376 let tid = thread_id as u64;
3377 let current = self.cached_thread_id;
3378 if tid == current {
3379 self.stack[idx.0 as usize].val = val;
3380 } else {
3381 self.upvalue_set_cross_thread(tid, idx, val)?;
3382 }
3383 }
3384 None => {
3385 uv.set_closed_value(val);
3386 }
3387 }
3388 if val.is_collectable() {
3389 self.gc_barrier_upval(&uv, &val);
3390 }
3391 Ok(())
3392 }
3393
3394 #[cold]
3395 #[inline(never)]
3396 fn upvalue_set_cross_thread(
3397 &mut self,
3398 tid: u64,
3399 idx: StackIdx,
3400 val: LuaValue,
3401 ) -> Result<(), LuaError> {
3402 let entry_rc = {
3403 let g = self.global();
3404 g.threads.get(&tid).map(|e| e.state.clone())
3405 };
3406 if let Some(rc) = entry_rc {
3407 if let Ok(mut home_state) = rc.try_borrow_mut() {
3408 home_state.set_at(idx, val);
3409 return Ok(());
3410 }
3411 }
3412 let mut g = self.global_mut();
3413 g.cross_thread_upvals.insert((tid, idx), val);
3414 Ok(())
3415 }
3416
3417 pub fn protected_call_raw(
3418 &mut self,
3419 func: StackIdx,
3420 nresults: i32,
3421 errfunc: StackIdx,
3422 ) -> Result<(), LuaError> {
3423 let ef = errfunc.0 as isize;
3424 let status = crate::do_::pcall(self, |s| s.call_no_yield(func, nresults), func, ef);
3425 match status {
3426 LuaStatus::Ok => Ok(()),
3427 LuaStatus::ErrSyntax => {
3428 let err_val = self.get_at(func);
3429 self.set_top(func);
3430 Err(LuaError::Syntax(err_val))
3431 }
3432 LuaStatus::Yield => {
3433 self.set_top(func);
3434 Err(LuaError::Yield)
3435 }
3436 _ => {
3437 let err_val = self.get_at(func);
3438 self.set_top(func);
3439 Err(LuaError::Runtime(err_val))
3440 }
3441 }
3442 }
3443 pub fn protected_parser(
3444 &mut self,
3445 z: crate::zio::ZIO,
3446 name: &[u8],
3447 mode: Option<&[u8]>,
3448 ) -> LuaStatus {
3449 crate::do_::protected_parser(self, z, name, mode)
3450 }
3451 pub fn do_call(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3452 crate::do_::call(self, func, nresults)
3453 }
3454 pub fn do_call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3455 crate::do_::callnoyield(self, func, nresults)
3456 }
3457 pub fn call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3458 crate::do_::callnoyield(self, func, nresults)
3459 }
3460 pub fn call_at(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3461 crate::do_::call(self, func, nresults)
3462 }
3463 #[inline(always)]
3464 pub fn call_known_c_at(&mut self, func: StackIdx, nresults: i32) -> Result<bool, LuaError> {
3465 crate::do_::call_known_c(self, func, nresults)
3466 }
3467 #[inline(always)]
3468 pub fn precall(
3469 &mut self,
3470 func: StackIdx,
3471 nresults: i32,
3472 ) -> Result<Option<CallInfoIdx>, LuaError> {
3473 crate::do_::precall(self, func, nresults)
3474 }
3475 #[inline(always)]
3476 pub fn pretailcall(
3477 &mut self,
3478 ci: CallInfoIdx,
3479 func: StackIdx,
3480 narg1: i32,
3481 delta: i32,
3482 ) -> Result<i32, LuaError> {
3483 crate::do_::pretailcall(self, ci, func, narg1, delta)
3484 }
3485 #[inline(always)]
3486 pub fn poscall<N: TryInto<i32>>(&mut self, ci: CallInfoIdx, nres: N) -> Result<(), LuaError>
3487 where
3488 <N as TryInto<i32>>::Error: std::fmt::Debug,
3489 {
3490 let n = nres.try_into().expect("poscall: nres out of i32 range");
3491 crate::do_::poscall(self, ci, n)
3492 }
3493 pub fn adjust_results(&mut self, nresults: i32) {
3494 const LUA_MULTRET: i32 = -1;
3495 if nresults <= LUA_MULTRET {
3496 let ci_idx = self.ci.as_usize();
3497 if self.call_info[ci_idx].top.0 < self.top.0 {
3498 self.call_info[ci_idx].top = self.top;
3499 }
3500 }
3501 }
3502 pub fn adjust_varargs(
3503 &mut self,
3504 ci: CallInfoIdx,
3505 nfixparams: i32,
3506 cl: &GcRef<lua_types::closure::LuaLClosure>,
3507 ) -> Result<(), LuaError> {
3508 crate::tagmethods::adjust_varargs(self, nfixparams, ci, &cl.0.proto)
3509 }
3510 pub fn get_varargs(&mut self, ci: CallInfoIdx, ra: StackIdx, n: i32) -> Result<i32, LuaError> {
3511 crate::tagmethods::get_varargs(self, ci, ra, n)?;
3512 Ok(0)
3513 }
3514
3515 pub fn close_upvals(&mut self, level: StackIdx) -> Result<(), LuaError> {
3516 crate::func::close_upval(self, level);
3517 Ok(())
3518 }
3519 pub fn close_upvals_status(&mut self, level: StackIdx, _status: i32) -> Result<(), LuaError> {
3520 crate::func::close_upval(self, level);
3521 Ok(())
3522 }
3523 pub fn close_upvals_from_base(&mut self, ci: CallInfoIdx) -> Result<(), LuaError> {
3524 let base = self.ci_base(ci);
3525 crate::func::close_upval(self, base);
3526 Ok(())
3527 }
3528
3529 pub fn arith_op(
3530 &mut self,
3531 op: i32,
3532 p1: &LuaValue,
3533 p2: &LuaValue,
3534 ) -> Result<LuaValue, LuaError> {
3535 let arith_op = match op {
3536 0 => lua_types::arith::ArithOp::Add,
3537 1 => lua_types::arith::ArithOp::Sub,
3538 2 => lua_types::arith::ArithOp::Mul,
3539 3 => lua_types::arith::ArithOp::Mod,
3540 4 => lua_types::arith::ArithOp::Pow,
3541 5 => lua_types::arith::ArithOp::Div,
3542 6 => lua_types::arith::ArithOp::Idiv,
3543 7 => lua_types::arith::ArithOp::Band,
3544 8 => lua_types::arith::ArithOp::Bor,
3545 9 => lua_types::arith::ArithOp::Bxor,
3546 10 => lua_types::arith::ArithOp::Shl,
3547 11 => lua_types::arith::ArithOp::Shr,
3548 12 => lua_types::arith::ArithOp::Unm,
3549 13 => lua_types::arith::ArithOp::Bnot,
3550 _ => return Err(LuaError::runtime(format_args!("invalid arith op {}", op))),
3551 };
3552 let mut res = LuaValue::Nil;
3553 if crate::object::raw_arith(self, arith_op, p1, p2, &mut res)? {
3554 Ok(res)
3555 } else {
3556 Err(LuaError::arith_error(p1, p2, "perform arithmetic on"))
3557 }
3558 }
3559 pub fn concat(&mut self, n: i32) -> Result<(), LuaError> {
3560 crate::vm::concat(self, n)
3561 }
3562 pub fn less_than(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3563 crate::vm::less_than(self, l, r)
3564 }
3565 pub fn less_equal(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3566 crate::vm::less_equal(self, l, r)
3567 }
3568 pub fn equal_obj(&self, _ctx: Option<&LuaValue>, l: &LuaValue, r: &LuaValue) -> bool {
3569 crate::vm::equal_obj(None, l, r).unwrap_or(false)
3570 }
3571 pub fn equal_obj_with_tm(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3572 crate::vm::equal_obj(Some(self), l, r)
3573 }
3574 pub fn obj_len(&mut self, v: &LuaValue) -> Result<LuaValue, LuaError> {
3575 match v {
3576 LuaValue::Table(_) => {
3577 let consult_len_tm =
3580 !matches!(self.global().lua_version, lua_types::LuaVersion::V51);
3581 let tm = if consult_len_tm {
3582 let mt = self.table_metatable(v);
3583 self.fast_tm_table(mt.as_ref(), TagMethod::Len)
3584 } else {
3585 LuaValue::Nil
3586 };
3587 if matches!(tm, LuaValue::Nil) {
3588 let n = self.table_length(v)?;
3589 return Ok(LuaValue::Int(n));
3590 }
3591 self.push(LuaValue::Nil);
3592 let slot = StackIdx(self.top.0 - 1);
3593 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3594 Ok(self.pop())
3595 }
3596 LuaValue::Str(s) => Ok(LuaValue::Int(s.len() as i64)),
3597 other => {
3598 let tm = crate::tagmethods::get_tm_by_obj(
3599 self,
3600 other,
3601 crate::tagmethods::TagMethod::Len,
3602 );
3603 if matches!(tm, LuaValue::Nil) {
3604 let mut msg = b"attempt to get length of a ".to_vec();
3605 msg.extend_from_slice(&self.obj_type_name(other));
3606 msg.extend_from_slice(b" value");
3607 return Err(crate::debug::prefixed_runtime_pub(self, msg));
3608 }
3609 self.push(LuaValue::Nil);
3610 let slot = StackIdx(self.top.0 - 1);
3611 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3612 Ok(self.pop())
3613 }
3614 }
3615 }
3616 pub fn obj_to_string(&mut self, idx: i32) -> Result<GcRef<LuaString>, LuaError> {
3617 let slot: StackIdx = if idx > 0 {
3618 let ci_func = self.current_call_info().func;
3619 ci_func + idx
3620 } else {
3621 debug_assert!(idx != 0, "invalid index");
3622 StackIdx((self.top_idx().0 as i32 + idx) as u32)
3623 };
3624 let val = self.get_at(slot);
3625 match val {
3626 LuaValue::Str(s) => Ok(s),
3627 LuaValue::Int(_) | LuaValue::Float(_) => {
3628 let s = crate::object::num_to_string(self, &val)?;
3629 self.set_at(slot, LuaValue::Str(s.clone()));
3630 Ok(s)
3631 }
3632 _ => Err(LuaError::type_error(&val, "convert to string")),
3633 }
3634 }
3635 pub fn coerce_to_string(&mut self, idx: StackIdx) -> Result<GcRef<LuaString>, LuaError> {
3636 let val = self.get_at(idx);
3637 match val {
3638 LuaValue::Str(s) => Ok(s),
3639 LuaValue::Int(_) | LuaValue::Float(_) => {
3640 let s = crate::object::num_to_string(self, &val)?;
3641 self.set_at(idx, LuaValue::Str(s.clone()));
3642 Ok(s)
3643 }
3644 _ => Err(LuaError::type_error(&val, "convert to string")),
3645 }
3646 }
3647 pub fn str_to_num(&mut self, s: &[u8]) -> Option<(LuaValue, usize)> {
3655 let mut out = LuaValue::Nil;
3656 let float_only =
3657 self.global().lua_version.number_model() == lua_types::NumberModel::FloatOnly;
3658 let sz = if float_only {
3659 crate::object::str2num_float_only(s, &mut out)
3660 } else {
3661 crate::object::str2num(s, &mut out)
3662 };
3663 if sz == 0 {
3664 return None;
3665 }
3666 Some((out, sz))
3667 }
3668
3669 #[inline(always)]
3670 pub fn fast_get(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
3671 let LuaValue::Table(tbl) = t else {
3672 return Ok(None);
3673 };
3674 let v = tbl.get(k);
3675 if matches!(v, LuaValue::Nil) {
3676 Ok(None)
3677 } else {
3678 Ok(Some(v))
3679 }
3680 }
3681 #[inline(always)]
3682 pub fn fast_get_int(&mut self, t: &LuaValue, k: i64) -> Result<Option<LuaValue>, LuaError> {
3683 let LuaValue::Table(tbl) = t else {
3684 return Ok(None);
3685 };
3686 let v = tbl.get_int(k);
3687 if matches!(v, LuaValue::Nil) {
3688 Ok(None)
3689 } else {
3690 Ok(Some(v))
3691 }
3692 }
3693 #[inline(always)]
3694 pub fn fast_get_short_str(
3695 &mut self,
3696 t: &LuaValue,
3697 k: &LuaValue,
3698 ) -> Result<Option<LuaValue>, LuaError> {
3699 let LuaValue::Table(tbl) = t else {
3700 return Ok(None);
3701 };
3702 let LuaValue::Str(s) = k else {
3703 return Ok(None);
3704 };
3705 let v = tbl.get_short_str(s);
3706 if matches!(v, LuaValue::Nil) {
3707 Ok(None)
3708 } else {
3709 Ok(Some(v))
3710 }
3711 }
3712 #[inline(always)]
3713 pub fn fast_tm_table(&mut self, t: Option<&GcRef<LuaTable>>, tm: TagMethod) -> LuaValue {
3714 let Some(mt) = t else {
3715 return LuaValue::Nil;
3716 };
3717 debug_assert!((tm as u8) <= TagMethod::Eq as u8);
3718 let ename = self.global().tmname[tm as usize].clone();
3719 mt.get_short_str(&ename)
3720 }
3721 pub fn fast_tm_ud(&mut self, u: &GcRef<LuaUserData>, tm: TagMethod) -> LuaValue {
3722 let mt = u.metatable();
3724 self.fast_tm_table(mt.as_ref(), tm)
3725 }
3726
3727 pub fn table_get_with_tm(&mut self, t: &LuaValue, k: &LuaValue) -> Result<LuaValue, LuaError> {
3728 if let LuaValue::Table(tbl) = t {
3734 if !tbl.has_metatable() {
3735 return Ok(tbl.get(k));
3736 }
3737 }
3738 if let Some(v) = self.fast_get(t, k)? {
3739 return Ok(v);
3740 }
3741 let res = self.top_idx();
3742 self.push(LuaValue::Nil);
3743 crate::vm::finish_get(self, t.clone(), k.clone(), res, true, None, None)?;
3744 let value = self.get_at(res);
3745 self.pop();
3746 Ok(value)
3747 }
3748 #[inline]
3763 pub fn table_set_with_tm(
3764 &mut self,
3765 t: &LuaValue,
3766 k: LuaValue,
3767 v: LuaValue,
3768 ) -> Result<(), LuaError> {
3769 if let LuaValue::Table(tbl) = t {
3770 if !tbl.has_metatable() {
3771 self.gc_table_barrier_back(tbl, &v);
3772 return self.table_raw_set(t, k, v);
3773 }
3774 }
3775 if self.fast_get(t, &k)?.is_some() {
3776 self.gc_value_barrier_back(t, &v);
3777 return self.table_raw_set(t, k, v);
3778 }
3779 crate::vm::finish_set(self, t.clone(), k, v, true, None, None)
3780 }
3781 #[inline]
3782 pub fn table_raw_set(
3783 &mut self,
3784 t: &LuaValue,
3785 k: LuaValue,
3786 v: LuaValue,
3787 ) -> Result<(), LuaError> {
3788 let LuaValue::Table(tbl) = t else {
3789 return Err(LuaError::type_error(t, "index"));
3790 };
3791 let tbl = tbl.clone();
3792 tbl.raw_set(self, k, v)
3793 }
3794 #[inline]
3795 pub fn table_array_set(
3796 &mut self,
3797 t: &LuaValue,
3798 idx: usize,
3799 v: LuaValue,
3800 ) -> Result<(), LuaError> {
3801 let LuaValue::Table(tbl) = t else {
3802 return Err(LuaError::type_error(t, "index"));
3803 };
3804 let tbl = tbl.clone();
3805 tbl.raw_set_int(self, idx as i64 + 1, v)
3806 }
3807 pub fn table_ensure_array(&mut self, t: &LuaValue, n: usize) -> Result<(), LuaError> {
3808 let LuaValue::Table(tbl) = t else {
3809 return Err(LuaError::type_error(t, "index"));
3810 };
3811 if n > tbl.array_len() {
3812 tbl.resize(self, n, 0)?;
3813 }
3814 Ok(())
3815 }
3816 pub fn table_length(&mut self, t: &LuaValue) -> Result<i64, LuaError> {
3817 let LuaValue::Table(tbl) = t else {
3818 return Err(LuaError::type_error(t, "get length of"));
3819 };
3820 Ok(tbl.getn() as i64)
3821 }
3822 pub fn table_metatable(&mut self, v: &LuaValue) -> Option<GcRef<LuaTable>> {
3823 match v {
3824 LuaValue::Table(t) => t.metatable(),
3825 LuaValue::UserData(u) => u.metatable(),
3826 other => {
3827 let idx = other.base_type() as usize;
3828 self.global().mt[idx].clone()
3829 }
3830 }
3831 }
3832 pub fn table_resize(
3833 &mut self,
3834 t: &GcRef<LuaTable>,
3835 na: usize,
3836 nh: usize,
3837 ) -> Result<(), LuaError> {
3838 self.mark_gc_check_needed();
3839 t.resize(self, na, nh)
3840 }
3841 pub fn table_getn(&self, t: &GcRef<LuaTable>) -> i64 {
3842 let mut i: i64 = 1;
3848 loop {
3849 let v = t.get_int(i);
3850 if matches!(v, LuaValue::Nil) {
3851 return i - 1;
3852 }
3853 i += 1;
3854 }
3855 }
3856
3857 pub fn try_bin_tm(
3858 &mut self,
3859 p1: &LuaValue,
3860 p1_idx: Option<StackIdx>,
3861 p2: &LuaValue,
3862 p2_idx: Option<StackIdx>,
3863 res: StackIdx,
3864 tm: lua_types::tagmethod::TagMethod,
3865 ) -> Result<(), LuaError> {
3866 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3867 crate::tagmethods::try_bin_tm(self, p1, p1_idx, p2, p2_idx, res, event)
3868 }
3869 pub fn try_bin_i_tm(
3870 &mut self,
3871 p1: &LuaValue,
3872 p1_idx: Option<StackIdx>,
3873 imm: i64,
3874 flip: bool,
3875 res: StackIdx,
3876 tm: lua_types::tagmethod::TagMethod,
3877 ) -> Result<(), LuaError> {
3878 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3879 crate::tagmethods::try_bini_tm(self, p1, p1_idx, imm, flip, res, event)
3880 }
3881 pub fn try_bin_assoc_tm(
3882 &mut self,
3883 p1: &LuaValue,
3884 p1_idx: Option<StackIdx>,
3885 p2: &LuaValue,
3886 p2_idx: Option<StackIdx>,
3887 flip: bool,
3888 res: StackIdx,
3889 tm: lua_types::tagmethod::TagMethod,
3890 ) -> Result<(), LuaError> {
3891 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3892 crate::tagmethods::try_bin_assoc_tm(self, p1, p1_idx, p2, p2_idx, flip, res, event)
3893 }
3894 pub fn try_concat_tm(&mut self, _p1: &LuaValue, _p2: &LuaValue) -> Result<(), LuaError> {
3895 crate::tagmethods::try_concat_tm(self)
3896 }
3897 pub fn call_tm(
3898 &mut self,
3899 f: LuaValue,
3900 p1: &LuaValue,
3901 p2: &LuaValue,
3902 p3: &LuaValue,
3903 ) -> Result<(), LuaError> {
3904 crate::tagmethods::call_tm(self, f, p1.clone(), p2.clone(), p3.clone())
3905 }
3906 pub fn call_tm_res(
3907 &mut self,
3908 f: LuaValue,
3909 p1: &LuaValue,
3910 p2: &LuaValue,
3911 res: StackIdx,
3912 ) -> Result<(), LuaError> {
3913 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)
3914 }
3915 pub fn call_tm_res_bool(
3916 &mut self,
3917 f: LuaValue,
3918 p1: &LuaValue,
3919 p2: &LuaValue,
3920 ) -> Result<bool, LuaError> {
3921 let res = self.top_idx();
3922 self.push(LuaValue::Nil);
3923 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)?;
3924 let result = self.get_at(res).clone();
3925 self.pop();
3926 Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)))
3927 }
3928 pub fn call_order_tm(
3929 &mut self,
3930 p1: &LuaValue,
3931 p2: &LuaValue,
3932 tm: lua_types::tagmethod::TagMethod,
3933 ) -> Result<bool, LuaError> {
3934 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3935 crate::tagmethods::call_order_tm(self, p1, p2, event)
3936 }
3937 pub fn call_order_i_tm(
3938 &mut self,
3939 p1: &LuaValue,
3940 v2: i64,
3941 flip: bool,
3942 isfloat: bool,
3943 tm: lua_types::tagmethod::TagMethod,
3944 ) -> Result<bool, LuaError> {
3945 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3946 crate::tagmethods::call_orderi_tm(self, p1, v2 as i32, flip, isfloat, event)
3947 }
3948
3949 #[inline(always)]
3950 pub fn proto_code(
3951 &self,
3952 cl: &GcRef<lua_types::closure::LuaLClosure>,
3953 pc: u32,
3954 ) -> lua_types::opcode::Instruction {
3955 cl.proto.code[pc as usize]
3956 }
3957 #[inline(always)]
3958 pub fn proto_const(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> LuaValue {
3959 cl.proto.k[idx].clone()
3960 }
3961 #[inline(always)]
3967 pub fn proto_const_int(
3968 &self,
3969 cl: &GcRef<lua_types::closure::LuaLClosure>,
3970 idx: usize,
3971 ) -> Option<i64> {
3972 match &cl.proto.k[idx] {
3973 LuaValue::Int(v) => Some(*v),
3974 _ => None,
3975 }
3976 }
3977 #[inline(always)]
3981 pub fn proto_const_num(
3982 &self,
3983 cl: &GcRef<lua_types::closure::LuaLClosure>,
3984 idx: usize,
3985 ) -> Option<f64> {
3986 match &cl.proto.k[idx] {
3987 LuaValue::Float(f) => Some(*f),
3988 LuaValue::Int(v) => Some(*v as f64),
3989 _ => None,
3990 }
3991 }
3992 pub fn get_proto_instr(&self, ci: CallInfoIdx, pc: u32) -> lua_types::opcode::Instruction {
3993 let cl = self
3994 .ci_lua_closure(ci)
3995 .expect("get_proto_instr: CallInfo does not hold a Lua closure");
3996 cl.proto.code[pc as usize]
3997 }
3998 pub fn trace_call(&mut self, _idx: CallInfoIdx) -> Result<bool, LuaError> {
4003 Ok(crate::debug::trace_call(self)? != 0)
4004 }
4005 pub fn trace_exec(&mut self, _idx: CallInfoIdx, pc: u32) -> Result<bool, LuaError> {
4008 Ok(crate::debug::trace_exec(self, pc)? != 0)
4009 }
4010 pub fn hook_call(&mut self, idx: CallInfoIdx) -> Result<(), LuaError> {
4021 if self.global().lua_version == lua_types::LuaVersion::V51 {
4022 let had_tail = self.call_info[idx.as_usize()].callstatus & CIST_TAIL;
4023 self.call_info[idx.as_usize()].callstatus &= !CIST_TAIL;
4024 let r = crate::do_::hookcall(self, idx);
4025 self.call_info[idx.as_usize()].callstatus |= had_tail;
4026 return r;
4027 }
4028 crate::do_::hookcall(self, idx)
4029 }
4030 #[inline(always)]
4031 fn gc_step_flags(&self) -> Option<(bool, bool)> {
4032 let g = self.global();
4033 if !g.is_gc_running() {
4034 return None;
4035 }
4036 let should_collect = g.heap.would_collect();
4037 let has_finalizers = g.finalizers.has_to_be_finalized();
4038 if should_collect || has_finalizers {
4039 Some((should_collect, has_finalizers))
4040 } else {
4041 None
4042 }
4043 }
4044
4045 #[inline(always)]
4046 fn should_check_gc(&mut self) -> bool {
4047 if self.gc_check_needed {
4048 return true;
4049 }
4050 if self.global().finalizers.has_to_be_finalized() {
4051 self.gc_check_needed = true;
4052 return true;
4053 }
4054 false
4055 }
4056
4057 #[inline(always)]
4058 pub(crate) fn mark_gc_check_needed(&mut self) {
4059 self.gc_check_needed = true;
4060 }
4061
4062 pub fn gc_trace_bound(&self) -> usize {
4076 (self.top.0 as usize).min(self.stack.len())
4077 }
4078
4079 pub fn clear_dead_stack_tail(&mut self) {
4086 let bound = self.gc_trace_bound();
4087 for slot in &mut self.stack[bound..] {
4088 slot.val = LuaValue::Nil;
4089 }
4090 }
4091
4092 pub fn gc_clear_dead_stack_tails(&mut self) {
4099 self.clear_dead_stack_tail();
4100 let global = self.global.clone();
4101 let g = global.borrow();
4102 for entry in g.threads.values() {
4103 if let Ok(mut t) = entry.state.try_borrow_mut() {
4104 t.clear_dead_stack_tail();
4105 }
4106 }
4107 }
4108
4109 pub fn gc_pre_collect_clear(&mut self) {
4113 if self.global().heap.would_collect() {
4114 self.gc_clear_dead_stack_tails();
4115 }
4116 }
4117
4118 #[inline(always)]
4119 pub fn gc_check_step(&mut self) {
4120 if !self.allowhook {
4121 return;
4122 }
4123 if !self.should_check_gc() {
4124 return;
4125 }
4126 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4127 self.gc_check_needed = false;
4128 return;
4129 };
4130 if should_collect || has_finalizers {
4131 if should_collect {
4132 self.gc_clear_dead_stack_tails();
4133 self.gc().check_step();
4134 }
4135 crate::api::run_pending_finalizers(self);
4136 self.gc_check_needed = true;
4137 }
4138 let should_keep_checking = {
4139 let g = self.global();
4140 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4141 };
4142 self.gc_check_needed = should_keep_checking;
4143 }
4144 #[inline(always)]
4145 pub fn gc_cond_step(&mut self) {
4146 if !self.allowhook {
4147 return;
4148 }
4149 if !self.should_check_gc() {
4150 return;
4151 }
4152 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4153 self.gc_check_needed = false;
4154 return;
4155 };
4156 if should_collect || has_finalizers {
4157 if should_collect {
4158 self.gc_clear_dead_stack_tails();
4159 self.gc().check_step();
4160 }
4161 crate::api::run_pending_finalizers(self);
4162 self.gc_check_needed = true;
4163 }
4164 let should_keep_checking = {
4165 let g = self.global();
4166 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4167 };
4168 self.gc_check_needed = should_keep_checking;
4169 }
4170 pub fn gc_barrier_back(&mut self, t: &dyn std::any::Any, v: &LuaValue) {
4171 self.gc().barrier_back(t, v);
4172 }
4173 #[inline(always)]
4174 pub fn gc_value_barrier_back(&mut self, t: &LuaValue, v: &LuaValue) {
4175 if !v.is_collectable() {
4176 return;
4177 }
4178 if let LuaValue::Table(tbl) = t {
4179 self.gc_table_barrier_back(tbl, v);
4180 } else {
4181 self.gc_barrier_back(t, v);
4182 }
4183 }
4184 #[inline(always)]
4185 pub fn gc_table_barrier_back(&mut self, t: &GcRef<LuaTable>, v: &LuaValue) {
4186 if !v.is_collectable() {
4187 return;
4188 }
4189 self.gc().table_barrier_back(t, v);
4190 }
4191 pub fn gc_barrier_upval(&mut self, uv: &GcRef<UpVal>, v: &LuaValue) {
4192 self.gc().barrier(uv, v);
4193 }
4194 pub fn is_main_thread(&mut self) -> bool {
4198 let g = self.global();
4199 g.current_thread_id == g.main_thread_id
4200 }
4201 pub fn obj_type_name<'v>(&self, v: &'v LuaValue) -> std::borrow::Cow<'static, [u8]> {
4202 let honors_name = self.global().lua_version.honors_name_metafield();
4203 match v {
4204 LuaValue::LightUserData(_) => std::borrow::Cow::Borrowed(b"light userdata"),
4205 LuaValue::Table(t) => {
4206 if honors_name {
4207 if let Some(mt) = t.metatable() {
4208 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4209 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4210 }
4211 }
4212 }
4213 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4214 }
4215 LuaValue::UserData(u) => {
4216 if honors_name {
4217 if let Some(mt) = u.metatable() {
4218 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4219 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4220 }
4221 }
4222 }
4223 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4224 }
4225 _ => std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type())),
4226 }
4227 }
4228
4229 pub fn full_type_name(&mut self, v: &LuaValue) -> Result<Vec<u8>, LuaError> {
4230 crate::tagmethods::obj_type_name(self, v)
4231 }
4232 pub fn emit_warning(&mut self, _msg: &[u8], _to_cont: bool) {
4233 warning(self, _msg, _to_cont)
4234 }
4235}
4236
4237pub struct GcHandle<'a> {
4241 _state: &'a mut LuaState,
4242}
4243
4244struct CollectRoots<'a> {
4251 global: &'a GlobalState,
4252 thread: &'a LuaState,
4253}
4254
4255#[derive(Clone, Copy)]
4256enum HeapCollectMode {
4257 Full,
4258 Step,
4259 Minor,
4260}
4261
4262impl<'a> lua_gc::Trace for CollectRoots<'a> {
4263 fn trace(&self, m: &mut lua_gc::Marker) {
4264 self.global.trace(m);
4265 self.thread.trace(m);
4266 }
4267}
4268
4269#[derive(Clone, Copy)]
4270enum BarrierKind {
4271 Forward,
4272 Backward,
4273}
4274
4275fn barrier_lua_value<P>(
4276 heap: &lua_gc::Heap,
4277 parent: GcRef<P>,
4278 child: &LuaValue,
4279 generational: bool,
4280 kind: BarrierKind,
4281) where
4282 P: lua_gc::Trace + 'static,
4283{
4284 if !child.is_collectable() {
4285 return;
4286 }
4287 if generational && matches!(kind, BarrierKind::Backward) {
4288 heap.generational_backward_barrier(parent.0);
4289 }
4290 match child {
4291 LuaValue::Str(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4292 LuaValue::Table(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4293 LuaValue::Function(LuaClosure::Lua(c)) => {
4294 barrier_gc_child(heap, parent, *c, generational, kind)
4295 }
4296 LuaValue::Function(LuaClosure::C(c)) => {
4297 barrier_gc_child(heap, parent, *c, generational, kind)
4298 }
4299 LuaValue::UserData(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4300 LuaValue::Thread(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4301 LuaValue::Nil
4302 | LuaValue::Bool(_)
4303 | LuaValue::Int(_)
4304 | LuaValue::Float(_)
4305 | LuaValue::LightUserData(_)
4306 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4307 }
4308}
4309
4310fn barrier_gc_child<P, C>(
4311 heap: &lua_gc::Heap,
4312 parent: GcRef<P>,
4313 child: GcRef<C>,
4314 generational: bool,
4315 kind: BarrierKind,
4316) where
4317 P: lua_gc::Trace + 'static,
4318 C: lua_gc::Trace + 'static,
4319{
4320 if generational && matches!(kind, BarrierKind::Forward) {
4321 heap.generational_forward_barrier(parent.0, child.0);
4322 } else if matches!(kind, BarrierKind::Backward) {
4323 heap.barrier_back(parent.0, child.0);
4324 } else {
4325 heap.barrier(parent.0, child.0);
4326 }
4327}
4328
4329fn barrier_child_any<P>(
4330 heap: &lua_gc::Heap,
4331 parent: GcRef<P>,
4332 child: &dyn std::any::Any,
4333 generational: bool,
4334 kind: BarrierKind,
4335) where
4336 P: lua_gc::Trace + 'static,
4337{
4338 if let Some(v) = child.downcast_ref::<LuaValue>() {
4339 barrier_lua_value(heap, parent, v, generational, kind);
4340 } else if let Some(c) = child.downcast_ref::<GcRef<LuaString>>() {
4341 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4342 } else if let Some(c) = child.downcast_ref::<GcRef<LuaTable>>() {
4343 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4344 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureLua>>() {
4345 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4346 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureC>>() {
4347 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4348 } else if let Some(c) = child.downcast_ref::<GcRef<LuaUserData>>() {
4349 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4350 } else if let Some(c) = child.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4351 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4352 } else if let Some(c) = child.downcast_ref::<GcRef<LuaProto>>() {
4353 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4354 } else if let Some(c) = child.downcast_ref::<GcRef<UpVal>>() {
4355 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4356 }
4357}
4358
4359fn barrier_any(
4360 heap: &lua_gc::Heap,
4361 parent: &dyn std::any::Any,
4362 child: &dyn std::any::Any,
4363 generational: bool,
4364 kind: BarrierKind,
4365) {
4366 if let Some(v) = parent.downcast_ref::<LuaValue>() {
4367 match v {
4368 LuaValue::Str(p) => barrier_child_any(heap, *p, child, generational, kind),
4369 LuaValue::Table(p) => barrier_child_any(heap, *p, child, generational, kind),
4370 LuaValue::Function(LuaClosure::Lua(p)) => {
4371 barrier_child_any(heap, *p, child, generational, kind)
4372 }
4373 LuaValue::Function(LuaClosure::C(p)) => {
4374 barrier_child_any(heap, *p, child, generational, kind)
4375 }
4376 LuaValue::UserData(p) => barrier_child_any(heap, *p, child, generational, kind),
4377 LuaValue::Thread(p) => barrier_child_any(heap, *p, child, generational, kind),
4378 LuaValue::Nil
4379 | LuaValue::Bool(_)
4380 | LuaValue::Int(_)
4381 | LuaValue::Float(_)
4382 | LuaValue::LightUserData(_)
4383 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4384 }
4385 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaString>>() {
4386 barrier_child_any(heap, p.clone(), child, generational, kind);
4387 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaTable>>() {
4388 barrier_child_any(heap, p.clone(), child, generational, kind);
4389 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureLua>>() {
4390 barrier_child_any(heap, p.clone(), child, generational, kind);
4391 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureC>>() {
4392 barrier_child_any(heap, p.clone(), child, generational, kind);
4393 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaUserData>>() {
4394 barrier_child_any(heap, p.clone(), child, generational, kind);
4395 } else if let Some(p) = parent.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4396 barrier_child_any(heap, p.clone(), child, generational, kind);
4397 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaProto>>() {
4398 barrier_child_any(heap, p.clone(), child, generational, kind);
4399 } else if let Some(p) = parent.downcast_ref::<GcRef<UpVal>>() {
4400 barrier_child_any(heap, p.clone(), child, generational, kind);
4401 }
4402}
4403
4404fn trace_reachable_threads(
4416 global: &GlobalState,
4417 _current_thread_id: u64,
4418 marker: &mut lua_gc::Marker,
4419) {
4420 use lua_gc::Trace;
4421
4422 #[cfg(debug_assertions)]
4423 let mut uncovered_borrowed: Vec<u64> = Vec::new();
4424
4425 loop {
4426 let visited_before = marker.visited_count();
4427 for (id, entry) in global.threads.iter() {
4428 if thread_entry_marked_alive(marker, *id, entry) {
4429 match entry.state.try_borrow() {
4430 Ok(thread) => thread.trace(marker),
4431 Err(_) => {
4432 #[cfg(debug_assertions)]
4433 if *id != _current_thread_id && !uncovered_borrowed.contains(id) {
4434 uncovered_borrowed.push(*id);
4435 }
4436 }
4437 }
4438 }
4439 }
4440 marker.drain_gray_queue();
4441 if marker.visited_count() == visited_before {
4442 break;
4443 }
4444 }
4445
4446 remark_legacy_open_upvalues(global, marker);
4447
4448 #[cfg(debug_assertions)]
4449 debug_assert!(
4450 uncovered_borrowed.len() <= global.suspended_parent_stacks.len(),
4451 "GC root loss: {} marked-alive coroutine(s) (ids {:?}) were mutably \
4452 borrowed at collect time with only {} parent snapshot(s) covering \
4453 them — their stacks were NOT traced this cycle, so anything only \
4454 reachable from them will be swept (issue #140 bug-A class). A borrow \
4455 of a coroutine's state must not be held across an allocation \
4456 checkpoint without pushing a parent GC snapshot.",
4457 uncovered_borrowed.len(),
4458 uncovered_borrowed,
4459 global.suspended_parent_stacks.len()
4460 );
4461}
4462
4463fn remark_legacy_open_upvalues(global: &GlobalState, marker: &mut lua_gc::Marker) {
4483 use lua_gc::Trace;
4484
4485 let legacy = matches!(
4486 global.lua_version,
4487 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
4488 );
4489 if !legacy {
4490 return;
4491 }
4492
4493 let mut remarked_any = false;
4494 for (id, entry) in global.threads.iter() {
4495 if entry.value.id != *id {
4496 continue;
4497 }
4498 if thread_entry_marked_alive(marker, *id, entry) {
4499 continue;
4500 }
4501 let Ok(thread) = entry.state.try_borrow() else {
4502 continue;
4503 };
4504 if thread.openupval.is_empty() || !thread.legacy_open_upval_touched.get() {
4505 continue;
4506 }
4507 thread.legacy_open_upval_touched.set(false);
4508 for uv in thread.openupval.iter() {
4509 let Some((_tid, idx)) = uv.try_open_payload() else {
4510 continue;
4511 };
4512 let slot = idx.0 as usize;
4513 if slot < thread.stack.len() {
4514 thread.stack[slot].val.trace(marker);
4515 remarked_any = true;
4516 }
4517 }
4518 }
4519
4520 if !remarked_any {
4521 return;
4522 }
4523 marker.drain_gray_queue();
4524
4525 loop {
4526 let visited_before = marker.visited_count();
4527 for (id, entry) in global.threads.iter() {
4528 if thread_entry_marked_alive(marker, *id, entry) {
4529 if let Ok(thread) = entry.state.try_borrow() {
4530 thread.trace(marker);
4531 }
4532 }
4533 }
4534 marker.drain_gray_queue();
4535 if marker.visited_count() == visited_before {
4536 break;
4537 }
4538 }
4539}
4540
4541fn thread_entry_marked_alive(
4542 marker: &lua_gc::Marker,
4543 id: u64,
4544 entry: &ThreadRegistryEntry,
4545) -> bool {
4546 marker.is_marked_or_old(entry.value.0) && entry.value.id == id
4547}
4548
4549fn lua_value_marked_or_old(marker: &lua_gc::Marker, value: &LuaValue) -> bool {
4550 match value {
4551 LuaValue::Str(v) => marker.is_marked_or_old(v.0),
4552 LuaValue::Table(v) => marker.is_marked_or_old(v.0),
4553 LuaValue::Function(LuaClosure::Lua(v)) => marker.is_marked_or_old(v.0),
4554 LuaValue::Function(LuaClosure::C(v)) => marker.is_marked_or_old(v.0),
4555 LuaValue::UserData(v) => marker.is_marked_or_old(v.0),
4556 LuaValue::Thread(v) => marker.is_marked_or_old(v.0),
4557 LuaValue::Nil
4558 | LuaValue::Bool(_)
4559 | LuaValue::Int(_)
4560 | LuaValue::Float(_)
4561 | LuaValue::LightUserData(_)
4562 | LuaValue::Function(LuaClosure::LightC(_)) => true,
4563 }
4564}
4565
4566fn finalizer_marked_or_old(marker: &lua_gc::Marker, object: &FinalizerObject) -> bool {
4567 match object {
4568 FinalizerObject::Table(t) => marker.is_marked_or_old(t.0),
4569 FinalizerObject::UserData(u) => marker.is_marked_or_old(u.0),
4570 }
4571}
4572
4573fn weak_snapshot_tables<'a>(
4574 snapshot: &'a lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>>,
4575) -> impl Iterator<Item = &'a GcRef<LuaTable>> {
4576 snapshot
4577 .weak_values
4578 .iter()
4579 .chain(snapshot.ephemeron.iter())
4580 .chain(snapshot.all_weak.iter())
4581}
4582
4583fn close_open_upvalues_for_unreachable_threads(global: &GlobalState, marker: &mut lua_gc::Marker) {
4584 use lua_gc::Trace;
4585
4586 let mut closed_values = Vec::<LuaValue>::new();
4587 for (id, entry) in global.threads.iter() {
4588 if entry.value.id != *id {
4589 continue;
4590 }
4591 if thread_entry_marked_alive(marker, *id, entry) {
4592 continue;
4593 }
4594 let Ok(thread) = entry.state.try_borrow() else {
4595 continue;
4596 };
4597 for uv in thread.openupval.iter() {
4598 if !marker.is_visited(uv.identity()) {
4599 continue;
4600 }
4601 let Some((thread_id, idx)) = uv.try_open_payload() else {
4602 continue;
4603 };
4604 if thread_id as u64 != *id {
4605 continue;
4606 }
4607 let value = thread.get_at(idx);
4608 uv.close_with(value.clone());
4609 closed_values.push(value);
4610 }
4611 }
4612 for value in closed_values {
4613 value.trace(marker);
4614 }
4615 marker.drain_gray_queue();
4616}
4617
4618fn record_dead_interned_strings(
4625 global: &GlobalState,
4626 marker: &lua_gc::Marker,
4627 dead_pairs: &std::cell::RefCell<Vec<(u32, usize)>>,
4628) {
4629 let mut dead = dead_pairs.borrow_mut();
4630 for s in global.interned_lt.iter() {
4631 let id = s.identity();
4632 if !marker.is_visited(id) {
4633 dead.push((s.hash(), id));
4634 }
4635 }
4636}
4637
4638fn remove_dead_interned_strings(global: &mut GlobalState, dead_pairs: Vec<(u32, usize)>) {
4650 for (hash, id) in dead_pairs {
4651 global.interned_lt.remove(hash, id);
4652 }
4653 global.interned_lt.shrink_if_sparse();
4654}
4655
4656impl<'a> GcHandle<'a> {
4657 pub fn check_step(&self) {
4662 if !self._state.global().is_gc_running() {
4663 return;
4664 }
4665 if self._state.global().is_gen_mode() {
4666 let should_collect = {
4667 let g = self._state.global();
4668 g.heap.would_collect() || g.gc_debt() > 0
4669 };
4670 if should_collect {
4671 self.generational_step();
4672 }
4673 } else {
4674 self.collect_via_heap(false);
4675 }
4676 }
4677
4678 pub fn full_collect(&self) {
4679 if self._state.global().is_gen_mode() {
4680 self.fullgen();
4681 } else {
4682 self.collect_via_heap(true);
4683 }
4684 }
4685
4686 fn negative_debt(bytes: usize) -> isize {
4687 -(bytes.min(isize::MAX as usize) as isize)
4688 }
4689
4690 fn set_minor_debt(&self) {
4691 let mut g = self._state.global_mut();
4692 let total = g.total_bytes();
4693 let growth = (total / 100).saturating_mul(g.genminormul as usize);
4694 g.heap
4695 .set_threshold_bytes(total.saturating_add(growth.max(1)));
4696 set_debt(&mut *g, Self::negative_debt(growth));
4697 }
4698
4699 fn set_pause_debt(&self) {
4700 let mut g = self._state.global_mut();
4701 let total = g.total_bytes();
4702 let pause = g.gc_pause_param().max(0) as usize;
4703 let threshold = g.gc_estimate.max(1).saturating_mul(pause) / 100;
4704 let debt = if threshold > total {
4705 Self::negative_debt(threshold - total)
4706 } else {
4707 0
4708 };
4709 let heap_threshold = if threshold > total {
4710 threshold
4711 } else {
4712 total.saturating_add(1)
4713 };
4714 g.heap.set_threshold_bytes(heap_threshold);
4715 set_debt(&mut *g, debt);
4716 }
4717
4718 fn enter_incremental_mode(&self) {
4719 let mut g = self._state.global_mut();
4720 g.heap.reset_all_ages();
4721 g.finalizers.reset_generation_boundaries();
4722 g.gckind = GcKind::Incremental as u8;
4723 g.lastatomic = 0;
4724 }
4725
4726 fn enter_generational_mode(&self) -> usize {
4727 self.collect_via_heap_mode(HeapCollectMode::Full);
4728 let numobjs = {
4729 let mut g = self._state.global_mut();
4730 g.heap.promote_all_to_old();
4731 g.finalizers.promote_all_pending_to_old();
4732 g.heap.allgc_count()
4733 };
4734 let total = self._state.global().total_bytes();
4735 {
4736 let mut g = self._state.global_mut();
4737 g.gckind = GcKind::Generational as u8;
4738 g.lastatomic = 0;
4739 g.gc_estimate = total;
4740 }
4741 self.set_minor_debt();
4742 numobjs
4743 }
4744
4745 fn fullgen(&self) -> usize {
4746 self.enter_incremental_mode();
4747 self.enter_generational_mode()
4748 }
4749
4750 fn stepgenfull(&self, lastatomic: usize) {
4751 if self._state.global().gckind == GcKind::Generational as u8 {
4752 self.enter_incremental_mode();
4753 }
4754 self.collect_via_heap_mode(HeapCollectMode::Full);
4755 let newatomic = self._state.global().heap.allgc_count().max(1);
4756 if newatomic < lastatomic.saturating_add(lastatomic >> 3) {
4757 {
4758 let mut g = self._state.global_mut();
4759 g.heap.promote_all_to_old();
4760 g.finalizers.promote_all_pending_to_old();
4761 }
4762 let total = self._state.global().total_bytes();
4763 {
4764 let mut g = self._state.global_mut();
4765 g.gckind = GcKind::Generational as u8;
4766 g.lastatomic = 0;
4767 g.gc_estimate = total;
4768 }
4769 self.set_minor_debt();
4770 } else {
4771 {
4772 let mut g = self._state.global_mut();
4773 g.heap.reset_all_ages();
4774 g.finalizers.reset_generation_boundaries();
4775 }
4776 let total = self._state.global().total_bytes();
4777 {
4778 let mut g = self._state.global_mut();
4779 g.gckind = GcKind::Incremental as u8;
4780 g.lastatomic = newatomic;
4781 g.gc_estimate = total;
4782 }
4783 self.set_pause_debt();
4784 }
4785 }
4786
4787 fn collect_via_heap(&self, force: bool) {
4796 self.collect_via_heap_mode(if force {
4797 HeapCollectMode::Full
4798 } else {
4799 HeapCollectMode::Step
4800 });
4801 }
4802
4803 fn collect_via_heap_mode(&self, mode: HeapCollectMode) {
4804 use lua_gc::Trace;
4805 let state_ref: &LuaState = &*self._state;
4806
4807 if matches!(mode, HeapCollectMode::Step) {
4813 let g = state_ref.global.borrow();
4814 if !g.heap.would_collect() {
4815 return;
4816 }
4817 }
4818
4819 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
4823 let mut g = state_ref.global.borrow_mut();
4824 g.weak_tables_registry.live_snapshot_by_kind()
4825 };
4826
4827 let weak_table_capacity = weak_tables_snapshot.len();
4832 let (pending_snapshot, thread_capacity, _interned_capacity): (
4833 Vec<FinalizerObject>,
4834 usize,
4835 usize,
4836 ) = {
4837 let g = state_ref.global.borrow();
4838 let pending = match mode {
4839 HeapCollectMode::Minor => g.finalizers.pending_minor_snapshot(),
4840 HeapCollectMode::Full | HeapCollectMode::Step => g.finalizers.pending_snapshot(),
4841 };
4842 (pending, g.threads.len(), g.interned_lt.len())
4843 };
4844 let finalizer_capacity = pending_snapshot.len();
4845
4846 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4847 std::cell::RefCell::new(std::collections::HashSet::new());
4848 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
4849 std::cell::RefCell::new(Vec::new());
4850 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
4851 std::cell::RefCell::new(std::collections::HashSet::new());
4852 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4853 std::cell::RefCell::new(std::collections::HashSet::new());
4854 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
4855 let collect_ran = std::cell::Cell::new(false);
4856
4857 {
4858 let global = state_ref.global.borrow();
4859 global.heap.unpause();
4860 let roots = CollectRoots {
4861 global: &*global,
4862 thread: state_ref,
4863 };
4864 let hook = |marker: &mut lua_gc::Marker| {
4865 collect_ran.set(true);
4866 alive_ids.borrow_mut().reserve(weak_table_capacity);
4867 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
4868 alive_thread_ids.borrow_mut().reserve(thread_capacity);
4869 trace_reachable_threads(&*global, global.current_thread_id, marker);
4870 close_open_upvalues_for_unreachable_threads(&*global, marker);
4871 let legacy_weak_key_values =
4878 matches!(global.lua_version, lua_types::LuaVersion::V51);
4879 loop {
4880 let visited_before = marker.visited_count();
4881 for t in &weak_tables_snapshot.ephemeron {
4882 if !marker.is_marked_or_old(t.0) {
4883 continue;
4884 }
4885 if legacy_weak_key_values {
4886 let mut to_mark = Vec::new();
4887 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
4888 for v in &to_mark {
4889 v.trace(marker);
4890 }
4891 } else {
4892 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
4893 lua_value_marked_or_old(marker, v)
4894 });
4895 for v in &to_mark {
4896 v.trace(marker);
4897 }
4898 }
4899 }
4900 marker.drain_gray_queue();
4901 if marker.visited_count() == visited_before {
4902 break;
4903 }
4904 }
4905 for t in weak_tables_snapshot
4925 .weak_values
4926 .iter()
4927 .chain(weak_tables_snapshot.all_weak.iter())
4928 {
4929 let to_mark = t.prune_weak_dead_with_value(
4930 &|_| true,
4931 &|v| lua_value_marked_or_old(marker, v),
4932 );
4933 if marker.is_marked_or_old(t.0) {
4934 for v in &to_mark {
4935 v.trace(marker);
4936 }
4937 }
4938 }
4939 marker.drain_gray_queue();
4940 for pf in &pending_snapshot {
4941 if !finalizer_marked_or_old(marker, pf) {
4942 pf.mark(marker);
4943 newly_unreachable.borrow_mut().push(pf.clone());
4944 }
4945 }
4946 marker.drain_gray_queue();
4947 loop {
4948 let visited_before = marker.visited_count();
4949 for t in &weak_tables_snapshot.ephemeron {
4950 if !marker.is_marked_or_old(t.0) {
4951 continue;
4952 }
4953 if legacy_weak_key_values {
4954 let mut to_mark = Vec::new();
4955 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
4956 for v in &to_mark {
4957 v.trace(marker);
4958 }
4959 } else {
4960 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
4961 lua_value_marked_or_old(marker, v)
4962 });
4963 for v in &to_mark {
4964 v.trace(marker);
4965 }
4966 }
4967 }
4968 marker.drain_gray_queue();
4969 if marker.visited_count() == visited_before {
4970 break;
4971 }
4972 }
4973 for t in weak_tables_snapshot
4986 .ephemeron
4987 .iter()
4988 .chain(weak_tables_snapshot.all_weak.iter())
4989 {
4990 if !marker.is_marked_or_old(t.0) {
4991 continue;
4992 }
4993 let to_mark = t.prune_weak_dead_with_value(
4994 &|v| lua_value_marked_or_old(marker, v),
4995 &|_| true,
4996 );
4997 for v in &to_mark {
4998 v.trace(marker);
4999 }
5000 }
5001 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5002 if marker.is_marked_or_old(t.0) {
5003 alive_ids.borrow_mut().insert(t.identity());
5004 }
5005 }
5006 marker.drain_gray_queue();
5007 {
5008 let mut alive = alive_thread_ids.borrow_mut();
5009 for (id, entry) in global.threads.iter() {
5010 if thread_entry_marked_alive(marker, *id, entry) {
5011 alive.insert(*id);
5012 }
5013 }
5014 }
5015 {
5016 let mut alive = alive_closure_env_ids.borrow_mut();
5017 for id in global.closure_envs.keys() {
5018 if marker.is_visited(*id) {
5019 alive.insert(*id);
5020 }
5021 }
5022 }
5023 record_dead_interned_strings(&*global, marker, &dead_interned);
5024 };
5025 match mode {
5026 HeapCollectMode::Full => global.heap.full_collect_with_post_mark(&roots, hook),
5027 HeapCollectMode::Step => global.heap.step_with_post_mark(&roots, hook),
5028 HeapCollectMode::Minor => global.heap.minor_collect_with_post_mark(&roots, hook),
5029 }
5030 }
5031
5032 if !collect_ran.get() {
5033 return;
5034 }
5035
5036 let alive_set = alive_ids.into_inner();
5040 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5041 let alive_thread_ids = alive_thread_ids.into_inner();
5042 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5043 let dead_interned = dead_interned.into_inner();
5044 let mut g = state_ref.global.borrow_mut();
5045 remove_dead_interned_strings(&mut *g, dead_interned);
5046 g.weak_tables_registry.retain_identities(&alive_set);
5047 let main_thread_id = g.main_thread_id;
5048 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5049 g.cross_thread_upvals
5050 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5051 g.thread_globals
5055 .retain(|id, _| alive_thread_ids.contains(id));
5056 g.closure_envs
5057 .retain(|id, _| alive_closure_env_ids.contains(id));
5058 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5062 for object in &promoted {
5063 if let Some(ptr) = object.heap_ptr() {
5064 g.heap.move_finobj_to_tobefnz(ptr);
5065 }
5066 }
5067 if matches!(mode, HeapCollectMode::Minor) {
5068 g.finalizers.finish_minor_collection();
5069 }
5070 }
5071
5072 pub fn generational_step(&self) -> bool {
5074 self.generational_step_with_major(true)
5075 }
5076
5077 pub fn generational_step_minor_only(&self) -> bool {
5083 self.generational_step_with_major(false)
5084 }
5085
5086 fn generational_step_with_major(&self, allow_major: bool) -> bool {
5087 let (lastatomic, majorbase, majorinc, should_major) = {
5088 let g = self._state.global();
5089 let majorbase = if g.gc_estimate == 0 {
5090 g.total_bytes()
5091 } else {
5092 g.gc_estimate
5093 };
5094 let majormul = g.gc_genmajormul_param().max(0) as usize;
5095 let majorinc = (majorbase / 100).saturating_mul(majormul);
5096 let debt_due = g.gc_debt() > 0 || g.heap.would_collect();
5097 let should_major =
5098 allow_major && debt_due && g.total_bytes() > majorbase.saturating_add(majorinc);
5099 (g.lastatomic, majorbase, majorinc, should_major)
5100 };
5101
5102 if lastatomic != 0 {
5103 self.stepgenfull(lastatomic);
5104 debug_assert!(self._state.global().is_gen_mode());
5105 return true;
5106 }
5107
5108 if should_major {
5109 let numobjs = self.fullgen();
5110 let after = self._state.global().total_bytes();
5111 if after < majorbase.saturating_add(majorinc / 2) {
5112 self.set_minor_debt();
5113 } else {
5114 {
5115 let mut g = self._state.global_mut();
5116 g.lastatomic = numobjs.max(1);
5117 }
5118 self.set_pause_debt();
5119 }
5120 } else {
5121 self.collect_via_heap_mode(HeapCollectMode::Minor);
5122 self.set_minor_debt();
5123 self._state.global_mut().gc_estimate = majorbase;
5124 }
5125
5126 debug_assert!(self._state.global().is_gen_mode());
5127 true
5128 }
5129
5130 pub fn step(&self) { }
5134
5135 pub fn incremental_step(&self, work_units: isize) -> bool {
5148 self.incremental_step_to_state(work_units, None)
5149 }
5150
5151 pub fn run_until_gc_state_for_test(&self, target: lua_gc::GcState) -> bool {
5156 self.incremental_step_to_state(isize::MAX / 4, Some(target));
5157 self._state.global().heap.gc_state() == target
5158 }
5159
5160 fn incremental_step_to_state(
5161 &self,
5162 work_units: isize,
5163 target: Option<lua_gc::GcState>,
5164 ) -> bool {
5165 use lua_gc::{StepBudget, StepOutcome, Trace};
5166 let state_ref: &LuaState = &*self._state;
5167
5168 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5169 let mut g = state_ref.global.borrow_mut();
5170 g.weak_tables_registry.live_snapshot_by_kind()
5171 };
5172
5173 let weak_table_capacity = weak_tables_snapshot.len();
5174 let (pending_snapshot, thread_capacity, _interned_capacity): (
5175 Vec<FinalizerObject>,
5176 usize,
5177 usize,
5178 ) = {
5179 let g = state_ref.global.borrow();
5180 (
5181 g.finalizers.pending_snapshot(),
5182 g.threads.len(),
5183 g.interned_lt.len(),
5184 )
5185 };
5186 let finalizer_capacity = pending_snapshot.len();
5187
5188 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5189 std::cell::RefCell::new(std::collections::HashSet::new());
5190 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
5191 std::cell::RefCell::new(Vec::new());
5192 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
5193 std::cell::RefCell::new(std::collections::HashSet::new());
5194 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5195 std::cell::RefCell::new(std::collections::HashSet::new());
5196 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5197 let atomic_ran = std::cell::Cell::new(false);
5198
5199 let stop_target = {
5200 let g = state_ref.global.borrow();
5201 match (target, g.heap.gc_state()) {
5202 (Some(target), _) => Some(target),
5203 (None, lua_gc::GcState::CallFin) => None,
5204 (None, _) => Some(lua_gc::GcState::CallFin),
5205 }
5206 };
5207
5208 let outcome = {
5209 let global = state_ref.global.borrow();
5210 global.heap.unpause();
5211 let roots = CollectRoots {
5212 global: &*global,
5213 thread: state_ref,
5214 };
5215 let hook = |marker: &mut lua_gc::Marker| {
5216 atomic_ran.set(true);
5217 alive_ids.borrow_mut().reserve(weak_table_capacity);
5218 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
5219 alive_thread_ids.borrow_mut().reserve(thread_capacity);
5220 trace_reachable_threads(&*global, global.current_thread_id, marker);
5221 close_open_upvalues_for_unreachable_threads(&*global, marker);
5222 let legacy_weak_key_values =
5226 matches!(global.lua_version, lua_types::LuaVersion::V51);
5227 loop {
5228 let visited_before = marker.visited_count();
5229 for t in &weak_tables_snapshot.ephemeron {
5230 let t_id = t.identity();
5231 if !marker.is_visited(t_id) {
5232 continue;
5233 }
5234 if legacy_weak_key_values {
5235 let mut to_mark = Vec::new();
5236 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5237 for v in &to_mark {
5238 v.trace(marker);
5239 }
5240 } else {
5241 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5242 for v in &to_mark {
5243 v.trace(marker);
5244 }
5245 }
5246 }
5247 marker.drain_gray_queue();
5248 if marker.visited_count() == visited_before {
5249 break;
5250 }
5251 }
5252 for t in weak_tables_snapshot
5265 .weak_values
5266 .iter()
5267 .chain(weak_tables_snapshot.all_weak.iter())
5268 {
5269 let to_mark =
5270 t.prune_weak_dead_with(&|_| true, &|id| marker.is_visited(id));
5271 if marker.is_visited(t.identity()) {
5272 for v in &to_mark {
5273 v.trace(marker);
5274 }
5275 }
5276 }
5277 marker.drain_gray_queue();
5278 for pf in &pending_snapshot {
5279 if !marker.is_visited(pf.identity()) {
5280 pf.mark(marker);
5281 newly_unreachable.borrow_mut().push(pf.clone());
5282 }
5283 }
5284 marker.drain_gray_queue();
5285 loop {
5286 let visited_before = marker.visited_count();
5287 for t in &weak_tables_snapshot.ephemeron {
5288 let t_id = t.identity();
5289 if !marker.is_visited(t_id) {
5290 continue;
5291 }
5292 if legacy_weak_key_values {
5293 let mut to_mark = Vec::new();
5294 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5295 for v in &to_mark {
5296 v.trace(marker);
5297 }
5298 } else {
5299 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5300 for v in &to_mark {
5301 v.trace(marker);
5302 }
5303 }
5304 }
5305 marker.drain_gray_queue();
5306 if marker.visited_count() == visited_before {
5307 break;
5308 }
5309 }
5310 for t in weak_tables_snapshot
5317 .ephemeron
5318 .iter()
5319 .chain(weak_tables_snapshot.all_weak.iter())
5320 {
5321 if !marker.is_visited(t.identity()) {
5322 continue;
5323 }
5324 let to_mark =
5325 t.prune_weak_dead_with(&|id| marker.is_visited(id), &|_| true);
5326 for v in &to_mark {
5327 v.trace(marker);
5328 }
5329 }
5330 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5331 if marker.is_visited(t.identity()) {
5332 alive_ids.borrow_mut().insert(t.identity());
5333 }
5334 }
5335 marker.drain_gray_queue();
5336 {
5337 let mut alive = alive_thread_ids.borrow_mut();
5338 for (id, entry) in global.threads.iter() {
5339 if thread_entry_marked_alive(marker, *id, entry) {
5340 alive.insert(*id);
5341 }
5342 }
5343 }
5344 {
5345 let mut alive = alive_closure_env_ids.borrow_mut();
5346 for id in global.closure_envs.keys() {
5347 if marker.is_visited(*id) {
5348 alive.insert(*id);
5349 }
5350 }
5351 }
5352 record_dead_interned_strings(&*global, marker, &dead_interned);
5353 };
5354 let budget = StepBudget::from_work(work_units);
5355 if let Some(target) = stop_target {
5356 global
5357 .heap
5358 .incremental_run_until_state_with_post_mark(&roots, target, work_units, hook)
5359 } else {
5360 global
5361 .heap
5362 .incremental_step_with_post_mark(&roots, budget, hook)
5363 }
5364 };
5365
5366 if atomic_ran.get() {
5367 let alive_set = alive_ids.into_inner();
5368 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5369 let alive_thread_ids = alive_thread_ids.into_inner();
5370 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5371 let dead_interned = dead_interned.into_inner();
5372 let mut g = state_ref.global.borrow_mut();
5373 remove_dead_interned_strings(&mut *g, dead_interned);
5374 g.weak_tables_registry.retain_identities(&alive_set);
5375 let main_thread_id = g.main_thread_id;
5376 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5377 g.cross_thread_upvals
5378 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5379 g.thread_globals
5383 .retain(|id, _| alive_thread_ids.contains(id));
5384 g.closure_envs
5385 .retain(|id, _| alive_closure_env_ids.contains(id));
5386 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5387 for object in &promoted {
5388 if let Some(ptr) = object.heap_ptr() {
5389 g.heap.move_finobj_to_tobefnz(ptr);
5390 }
5391 }
5392 }
5393
5394 let mut paused = matches!(outcome, StepOutcome::Paused);
5395 if target.is_none()
5396 && self._state.global().heap.gc_state() == lua_gc::GcState::CallFin
5397 && !self._state.global().finalizers.has_to_be_finalized()
5398 {
5399 paused = self._state.global().heap.finish_callfin_phase();
5400 }
5401
5402 paused
5403 }
5404
5405 pub fn prune_weak_tables_mark_only(&self) {
5412 use lua_gc::Trace;
5413 let state_ref: &LuaState = &*self._state;
5414
5415 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5416 let mut g = state_ref.global.borrow_mut();
5417 g.weak_tables_registry.live_snapshot_by_kind()
5418 };
5419 let _interned_capacity = {
5420 let g = state_ref.global.borrow();
5421 g.interned_lt.len()
5422 };
5423
5424 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5425
5426 {
5427 let global = state_ref.global.borrow();
5428 global.heap.unpause();
5429 let roots = CollectRoots {
5430 global: &*global,
5431 thread: state_ref,
5432 };
5433 let hook = |marker: &mut lua_gc::Marker| {
5434 trace_reachable_threads(&*global, global.current_thread_id, marker);
5435 loop {
5436 let visited_before = marker.visited_count();
5437 for t in &weak_tables_snapshot.ephemeron {
5438 let t_id = t.identity();
5439 if !marker.is_visited(t_id) {
5440 continue;
5441 }
5442 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5443 for v in &to_mark {
5444 v.trace(marker);
5445 }
5446 }
5447 marker.drain_gray_queue();
5448 if marker.visited_count() == visited_before {
5449 break;
5450 }
5451 }
5452 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5453 if marker.is_visited(t.identity()) {
5454 let to_mark = t.prune_weak_dead(&|id| marker.is_visited(id));
5455 for v in &to_mark {
5456 v.trace(marker);
5457 }
5458 }
5459 }
5460 marker.drain_gray_queue();
5461 record_dead_interned_strings(&*global, marker, &dead_interned);
5462 };
5463 global.heap.mark_only_with_post_mark(&roots, hook);
5464 }
5465
5466 let dead_interned = dead_interned.into_inner();
5467 let mut g = state_ref.global.borrow_mut();
5468 remove_dead_interned_strings(&mut *g, dead_interned);
5469 }
5470
5471 pub fn change_mode(&self, mode: GcKind) {
5473 let old = self._state.global().gckind;
5474 if old == mode as u8 {
5475 self._state.global_mut().lastatomic = 0;
5476 return;
5477 }
5478 match mode {
5479 GcKind::Generational => {
5480 self.enter_generational_mode();
5481 }
5482 GcKind::Incremental => {
5483 self.enter_incremental_mode();
5484 }
5485 }
5486 }
5487
5488 pub fn fix_object<T: lua_gc::Trace + 'static>(&self, _o: &GcRef<T>) { }
5494
5495 pub fn free_all_objects(&self) {
5531 let heap = self._state.global().heap.clone();
5532 {
5533 let _own_heap = lua_gc::HeapGuard::push(&heap);
5534 heap.drop_all();
5535 }
5536 let mut g = self._state.global_mut();
5537 g.weak_tables_registry = lua_gc::WeakRegistry::default();
5538 g.finalizers = lua_gc::FinalizerRegistry::default();
5539 g.external_roots = ExternalRootSet::default();
5540 g.threads.clear();
5541 g.thread_globals.clear();
5542 g.cross_thread_upvals.clear();
5543 g.suspended_parent_stacks.clear();
5544 g.suspended_parent_open_upvals.clear();
5545 g.twups.clear();
5546 }
5547
5548 pub fn barrier(&self, p: &dyn std::any::Any, v: &LuaValue) {
5550 let g = self._state.global();
5551 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Forward);
5552 }
5553
5554 pub fn barrier_back(&self, p: &dyn std::any::Any, v: &LuaValue) {
5556 let g = self._state.global();
5557 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Backward);
5558 }
5559
5560 pub fn table_barrier_back(&self, p: &GcRef<LuaTable>, v: &LuaValue) {
5562 let g = self._state.global();
5563 barrier_lua_value(&g.heap, *p, v, g.is_gen_mode(), BarrierKind::Backward);
5564 }
5565
5566 pub fn obj_barrier(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5568 let g = self._state.global();
5569 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Forward);
5570 }
5571
5572 pub fn obj_barrier_back(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5575 let g = self._state.global();
5576 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Backward);
5577 }
5578}
5579
5580fn make_seed() -> u32 {
5590 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5591 {
5592 return crate::string::hash_bytes(b"lua-rs-wasm-seed", 0x9e37_79b9);
5593 }
5594
5595 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
5596 {
5597 use std::time::{SystemTime, UNIX_EPOCH};
5598 let t = SystemTime::now()
5599 .duration_since(UNIX_EPOCH)
5600 .map(|d| d.as_secs() as u32)
5601 .unwrap_or(0);
5602
5603 crate::string::hash_bytes(&t.to_le_bytes(), t)
5604 }
5605}
5606
5607pub(crate) fn set_debt(g: &mut GlobalState, mut debt: isize) {
5610 let tb = g.total_bytes() as isize;
5611 debug_assert!(tb > 0);
5612 if debt < tb.saturating_sub(isize::MAX) {
5614 debt = tb - isize::MAX;
5615 }
5616 g.gc_debt = debt;
5617}
5618
5619pub fn set_c_stack_limit(_state: &mut LuaState, _limit: u32) -> i32 {
5621 let _ = (_state, _limit);
5622 LUAI_MAXCCALLS as i32
5623}
5624
5625pub(crate) fn extend_ci(state: &mut LuaState) -> CallInfoIdx {
5627 debug_assert!(
5628 state.call_info[state.ci.0 as usize].next.is_none(),
5629 "extend_ci: current ci already has a cached next frame"
5630 );
5631
5632 let current_idx = state.ci;
5633 let new_idx = CallInfoIdx(state.call_info.len() as u32);
5635
5636 state.call_info.push(CallInfo {
5637 previous: Some(current_idx),
5638 next: None,
5639 u: CallInfoFrame::lua_default(),
5640 ..CallInfo::default()
5641 });
5642
5643 state.call_info[current_idx.0 as usize].next = Some(new_idx);
5644
5645 state.nci += 1;
5646
5647 new_idx
5648}
5649
5650fn free_ci(state: &mut LuaState) {
5658 let ci_idx = state.ci.0 as usize;
5659
5660 let mut next_opt = state.call_info[ci_idx].next.take();
5661
5662 while let Some(idx) = next_opt {
5663 next_opt = state.call_info[idx.0 as usize].next;
5664 state.nci = state.nci.saturating_sub(1);
5665 }
5666
5667 state.call_info.truncate(ci_idx + 1);
5670}
5671
5672pub(crate) fn shrink_ci(state: &mut LuaState) {
5681 let ci_idx = state.ci.0 as usize;
5682
5683 if state.call_info[ci_idx].next.is_none() {
5684 return;
5685 }
5686
5687 let free_count = state.call_info.len().saturating_sub(ci_idx + 1);
5688 if free_count <= 1 {
5689 return;
5690 }
5691
5692 let keep = free_count / 2;
5694 let removed = free_count - keep;
5695 let new_len = ci_idx + 1 + keep;
5696 state.call_info.truncate(new_len);
5697 state.nci = state.nci.saturating_sub(removed as u32);
5698
5699 if let Some(last) = state.call_info.last_mut() {
5701 last.next = None;
5702 }
5703}
5704
5705pub(crate) fn check_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5707 if state.c_calls() == LUAI_MAXCCALLS {
5709 return Err(LuaError::runtime(format_args!("C stack overflow")));
5710 }
5711 if state.c_calls() >= (LUAI_MAXCCALLS / 10 * 11) {
5713 return Err(LuaError::with_status(LuaStatus::ErrErr));
5714 }
5715 Ok(())
5716}
5717
5718pub fn inc_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5720 state.n_ccalls += 1;
5721 if state.c_calls() >= LUAI_MAXCCALLS {
5722 check_c_stack(state)?;
5723 }
5724 Ok(())
5725}
5726
5727fn stack_init(thread: &mut LuaState) {
5732 let total_slots = BASIC_STACK_SIZE + EXTRA_STACK;
5733 thread.stack = vec![StackValue::default(); total_slots];
5734
5735 thread.tbclist = Vec::new();
5738
5739 thread.top = StackIdx(0);
5743
5744 thread.stack_last = StackIdx(BASIC_STACK_SIZE as u32);
5745
5746 let base_ci = CallInfo {
5747 func: StackIdx(0),
5748 top: StackIdx(1 + LUA_MINSTACK as u32),
5749 previous: None,
5750 next: None,
5751 callstatus: CIST_C,
5752 call_metamethods: 0,
5753 tailcalls: 0,
5754 nresults: 0,
5755 u: CallInfoFrame::c_default(),
5756 u2: CallInfoExtra::default(),
5757 };
5758
5759 if thread.call_info.is_empty() {
5760 thread.call_info.push(base_ci);
5761 } else {
5762 thread.call_info[0] = base_ci;
5763 thread.call_info.truncate(1);
5764 }
5765
5766 thread.stack[0] = StackValue {
5767 val: LuaValue::Nil,
5768 };
5769
5770 thread.top = StackIdx(1);
5771
5772 thread.ci = CallInfoIdx(0);
5773}
5774
5775fn free_stack(state: &mut LuaState) {
5776 if state.stack.is_empty() {
5777 return;
5778 }
5779 state.ci = CallInfoIdx(0);
5780 free_ci(state);
5781 debug_assert_eq!(state.nci, 0, "nci should be 0 after free_ci");
5782 state.stack.clear();
5783 state.stack.shrink_to_fit();
5784}
5785
5786fn init_registry(state: &mut LuaState) -> Result<(), LuaError> {
5787 let registry = state.new_table();
5788
5789 state.global_mut().l_registry = LuaValue::Table(registry.clone());
5790
5791 let globals = state.new_table();
5798 state.global_mut().globals = LuaValue::Table(globals);
5799 let loaded = state.new_table();
5800 state.global_mut().loaded = LuaValue::Table(loaded);
5801
5802 Ok(())
5803}
5804
5805fn lua_open(state: &mut LuaState) -> Result<(), LuaError> {
5806 stack_init(state);
5807 init_registry(state)?;
5808 crate::string::init(state)?;
5809 crate::tagmethods::init(state)?;
5810 state.global_mut().gcstp = 0;
5814 state.global().heap.unpause();
5815 state.global_mut().nilvalue = LuaValue::Nil;
5817 Ok(())
5818}
5819
5820fn preinit_thread(thread: &mut LuaState, global: Rc<RefCell<GlobalState>>) {
5821 thread.global = global;
5822 thread.stack = Vec::new();
5823 thread.call_info = Vec::new();
5824 thread.ci = CallInfoIdx(0);
5827 thread.nci = 0;
5828 thread.n_ccalls = 0;
5832 thread.hook = None;
5833 thread.hookmask = 0;
5834 thread.basehookcount = 0;
5835 thread.allowhook = true;
5836 thread.hookcount = thread.basehookcount;
5837
5838 {
5843 let (active, interval) = {
5844 let g = thread.global.borrow();
5845 (g.sandbox_active(), g.sandbox.interval.get())
5846 };
5847 if active {
5848 thread.hookmask = SANDBOX_COUNT_MASK;
5849 thread.basehookcount = interval;
5850 thread.hookcount = interval;
5851 }
5852 }
5853 thread.openupval = Vec::new();
5854 thread.status = LuaStatus::Ok as u8;
5855 thread.errfunc = 0;
5856 thread.oldpc = 0;
5857 thread.gc_check_needed = true;
5858}
5859
5860fn close_state(state: &mut LuaState) {
5872 let is_complete = state.global().is_complete();
5873
5874 if !is_complete {
5875 state.gc().free_all_objects();
5876 } else {
5877 state.ci = CallInfoIdx(0);
5878 crate::api::run_close_finalizers(state);
5879 state.gc().free_all_objects();
5880 }
5881
5882 state.global_mut().strt = StringPool::default();
5883
5884 free_stack(state);
5885
5886 }
5889
5890pub fn new_thread(state: &mut LuaState, initial_body: Option<LuaValue>) -> Result<(), LuaError> {
5899 state.gc_pre_collect_clear();
5900 state.gc().check_step();
5901
5902 let global_rc = state.global_rc();
5909 let hookmask = state.hookmask;
5910 let basehookcount = state.basehookcount;
5911
5912 let reserved_id = {
5913 let mut g = state.global_mut();
5914 let id = g.next_thread_id;
5915 g.next_thread_id += 1;
5916 id
5917 };
5918
5919 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
5925 let creator_id = state.global().current_thread_id;
5926 let creator_lgt = state.v51_thread_lgt(creator_id);
5927 state
5928 .global_mut()
5929 .thread_globals
5930 .insert(reserved_id, creator_lgt);
5931 }
5932
5933 let mut new_thread = LuaState {
5934 status: LuaStatus::Ok as u8,
5935 allowhook: true,
5936 nci: 0,
5937 top: StackIdx(0),
5938 stack_last: StackIdx(0),
5939 stack: Vec::new(),
5940 ci: CallInfoIdx(0),
5941 call_info: Vec::new(),
5942 openupval: Vec::new(),
5943 legacy_open_upval_touched: std::cell::Cell::new(false),
5944 tbclist: Vec::new(),
5945 global: global_rc.clone(),
5946 hook: None,
5947 hookmask: 0,
5948 basehookcount: 0,
5949 hookcount: 0,
5950 errfunc: 0,
5951 n_ccalls: 0,
5952 oldpc: 0,
5953 marked: 0,
5954 cached_thread_id: reserved_id,
5955 gc_check_needed: false,
5956 };
5957
5958 preinit_thread(&mut new_thread, global_rc);
5959
5960 new_thread.hookmask = hookmask;
5961 new_thread.basehookcount = basehookcount;
5962 new_thread.reset_hook_count();
5966
5967 stack_init(&mut new_thread);
5972
5973 if let Some(body) = initial_body {
5974 new_thread.push(body);
5975 }
5976
5977 let thread_ref: Rc<RefCell<LuaState>> = Rc::new(RefCell::new(new_thread));
5978
5979 let value = {
5980 let mut g = state.global_mut();
5981 let id = reserved_id;
5982 let value = GcRef::new(lua_types::value::LuaThread::new(id));
5983 g.threads.insert(
5984 id,
5985 ThreadRegistryEntry {
5986 state: thread_ref,
5987 value: value.clone(),
5988 },
5989 );
5990 value
5991 };
5992
5993 state.push(LuaValue::Thread(value));
5994
5995 Ok(())
5996}
5997
5998pub fn reset_thread(state: &mut LuaState, status: i32) -> i32 {
6002 let _heap_guard = {
6006 let g = state.global.borrow();
6007 lua_gc::HeapGuard::push(&g.heap)
6008 };
6009 state.ci = CallInfoIdx(0);
6010 let ci_idx = 0usize;
6011
6012 if !state.stack.is_empty() {
6013 state.stack[0].val = LuaValue::Nil;
6014 }
6015
6016 state.call_info[ci_idx].func = StackIdx(0);
6017 state.call_info[ci_idx].call_metamethods = 0;
6018 state.call_info[ci_idx].callstatus = CIST_C;
6019
6020 let mut status = if status == LuaStatus::Yield as i32 {
6021 LuaStatus::Ok as i32
6022 } else {
6023 status
6024 };
6025
6026 state.status = LuaStatus::Ok as u8;
6027
6028 let close_status = crate::do_::close_protected(state, StackIdx(1), LuaStatus::from_raw(status));
6029 status = close_status as i32;
6030
6031 if status != LuaStatus::Ok as i32 {
6032 crate::do_::set_error_obj(state, LuaStatus::from_raw(status), StackIdx(1));
6033 } else {
6034 state.top = StackIdx(1);
6035 }
6036
6037 let new_ci_top = StackIdx(state.top.0 + LUA_MINSTACK as u32);
6038 state.call_info[ci_idx].top = new_ci_top;
6039
6040 let needed = new_ci_top.0 as usize;
6044 if state.stack.len() < needed {
6045 state.stack.resize(needed, StackValue::default());
6046 }
6047
6048 status
6049}
6050
6051pub fn close_thread(state: &mut LuaState, from: Option<&LuaState>) -> i32 {
6053 state.n_ccalls = match from {
6054 Some(f) => f.c_calls(),
6055 None => 0,
6056 };
6057 let current_status = state.status as i32;
6058 let result = reset_thread(state, current_status);
6059 result
6060}
6061
6062pub fn reset_thread_api(state: &mut LuaState) -> i32 {
6064 close_thread(state, None)
6065}
6066
6067pub fn new_state() -> Option<LuaState> {
6073 let heap = lua_gc::Heap::new();
6087 let placeholder_str = GcRef(heap.allocate_uncollected(LuaString::placeholder()));
6088 let main_thread_value = GcRef(heap.allocate_uncollected(lua_types::value::LuaThread::new(0)));
6089
6090 let initial_white = 1u8 << WHITE0BIT;
6091
6092 let global = GlobalState {
6095 parser_hook: None,
6096 cli_argv: None,
6097 cli_preload: None,
6098 lua_version: lua_types::LuaVersion::default(),
6099 file_loader_hook: None,
6100 file_open_hook: None,
6101 stdout_hook: None,
6102 stderr_hook: None,
6103 stdin_hook: None,
6104 env_hook: None,
6105 unix_time_hook: None,
6106 cpu_clock_hook: None,
6107 local_offset_hook: None,
6108 entropy_hook: None,
6109 temp_name_hook: None,
6110 popen_hook: None,
6111 file_remove_hook: None,
6112 file_rename_hook: None,
6113 os_execute_hook: None,
6114 dynlib_load_hook: None,
6115 dynlib_symbol_hook: None,
6116 dynlib_unload_hook: None,
6117 sandbox: SandboxLimits::default(),
6118 gc_debt: 0,
6119 gc_estimate: 0,
6120 lastatomic: 0,
6121 strt: StringPool::default(),
6122 l_registry: LuaValue::Nil,
6123 external_roots: ExternalRootSet::default(),
6124 globals: LuaValue::Nil,
6125 loaded: LuaValue::Nil,
6126 nilvalue: LuaValue::Int(0),
6127 seed: make_seed(),
6128 currentwhite: initial_white,
6129 gcstate: GCS_PAUSE,
6130 gckind: GcKind::Incremental as u8,
6131 gcstopem: false,
6132 genminormul: LUAI_GENMINORMUL,
6133 genmajormul: (LUAI_GENMAJORMUL / 4) as u8,
6134 gcstp: GCSTPGC,
6135 gcemergency: false,
6136 gcpause: (LUAI_GCPAUSE / 4) as u8,
6137 gcstepmul: (LUAI_GCMUL / 4) as u8,
6138 gcstepsize: LUAI_GCSTEPSIZE,
6139 gc55_params: [20, 50, 68, 250, 200, 9600],
6142 sweepgc_cursor: 0,
6143 weak_tables_registry: lua_gc::WeakRegistry::default(),
6144 finalizers: lua_gc::FinalizerRegistry::default(),
6145 gc_finalizer_error: None,
6146 twups: Vec::new(),
6147 panic: None,
6148 mainthread: None,
6149 threads: std::collections::HashMap::new(),
6150 main_thread_value,
6151 current_thread_id: 0,
6152 closing_thread_id: None,
6153 main_thread_id: 0,
6154 next_thread_id: 1,
6155 thread_globals: std::collections::HashMap::new(),
6156 closure_envs: std::collections::HashMap::new(),
6157 memerrmsg: placeholder_str.clone(),
6158 tmname: Vec::new(),
6159 mt: std::array::from_fn(|_| None),
6160 strcache: std::array::from_fn(|_| std::array::from_fn(|_| placeholder_str.clone())),
6161 interned_lt: InternedStringMap::default(),
6162 warnf: None,
6163 warn_mode: WarnMode::Off,
6164 test_warn_enabled: false,
6165 test_warn_on: false,
6166 test_warn_mode: TestWarnMode::Normal,
6167 test_warn_last_to_cont: false,
6168 test_warn_buffer: Vec::new(),
6169 c_functions: Vec::new(),
6170 heap,
6171 cross_thread_upvals: std::collections::HashMap::new(),
6172 suspended_parent_stacks: Vec::new(),
6173 suspended_parent_open_upvals: Vec::new(),
6174 snapshot_stack_pool: Vec::new(),
6175 snapshot_upval_pool: Vec::new(),
6176 resume_value_pool: Vec::new(),
6177 resume_upval_slot_pool: Vec::new(),
6178 resume_flush_pool: Vec::new(),
6179 };
6180
6181 let global_rc = Rc::new(RefCell::new(global));
6182
6183 let _bootstrap_scope = global_rc.borrow().heap.bootstrap_scope();
6197 let _bootstrap_guard = lua_gc::HeapGuard::push(&global_rc.borrow().heap);
6198
6199 let initial_marked = initial_white;
6200
6201 let mut main_thread = LuaState {
6202 status: LuaStatus::Ok as u8,
6203 allowhook: true,
6204 nci: 0,
6205 top: StackIdx(0),
6206 stack_last: StackIdx(0),
6207 stack: Vec::new(),
6208 ci: CallInfoIdx(0),
6209 call_info: Vec::new(),
6210 openupval: Vec::new(),
6211 legacy_open_upval_touched: std::cell::Cell::new(false),
6212 tbclist: Vec::new(),
6213 global: global_rc.clone(),
6214 hook: None,
6215 hookmask: 0,
6216 basehookcount: 0,
6217 hookcount: 0,
6218 errfunc: 0,
6219 n_ccalls: 0,
6220 oldpc: 0,
6221 marked: initial_marked,
6222 cached_thread_id: 0,
6223 gc_check_needed: false,
6224 };
6225
6226 preinit_thread(&mut main_thread, global_rc.clone());
6227
6228 main_thread.inc_nny();
6229
6230 match lua_open(&mut main_thread) {
6237 Ok(()) => {}
6238 Err(_) => {
6239 close_state(&mut main_thread);
6240 return None;
6241 }
6242 }
6243
6244 Some(main_thread)
6245}
6246
6247pub fn close(mut state: LuaState) {
6255 close_state(&mut state);
6256}
6257
6258pub(crate) fn warning(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6260 let test_warn_enabled = state.global().test_warn_enabled;
6261 if test_warn_enabled {
6262 test_warn(state, msg, to_cont);
6263 return;
6264 }
6265
6266 let has_warnf = state.global().warnf.is_some();
6270 if has_warnf {
6271 let mut warnf = state.global_mut().warnf.take();
6273 if let Some(ref mut f) = warnf {
6274 f(msg, to_cont);
6275 }
6276 state.global_mut().warnf = warnf;
6278 return;
6279 }
6280 default_warn(state, msg, to_cont);
6281}
6282
6283fn test_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6284 let is_control = {
6285 let g = state.global();
6286 !g.test_warn_last_to_cont && !to_cont && msg.first() == Some(&b'@')
6287 };
6288 if is_control {
6289 let mut g = state.global_mut();
6290 match &msg[1..] {
6291 b"off" => g.test_warn_on = false,
6292 b"on" => g.test_warn_on = true,
6293 b"normal" => g.test_warn_mode = TestWarnMode::Normal,
6294 b"allow" => g.test_warn_mode = TestWarnMode::Allow,
6295 b"store" => g.test_warn_mode = TestWarnMode::Store,
6296 _ => {}
6297 }
6298 return;
6299 }
6300
6301 let finished = {
6302 let mut g = state.global_mut();
6303 g.test_warn_last_to_cont = to_cont;
6304 g.test_warn_buffer.extend_from_slice(msg);
6305 if to_cont {
6306 None
6307 } else {
6308 Some((
6309 std::mem::take(&mut g.test_warn_buffer),
6310 g.test_warn_mode,
6311 g.test_warn_on,
6312 ))
6313 }
6314 };
6315
6316 let Some((message, mode, warn_on)) = finished else {
6317 return;
6318 };
6319 match mode {
6320 TestWarnMode::Normal => {
6321 if warn_on && message.first() == Some(&b'#') {
6322 write_warning_message(&message);
6323 }
6324 }
6325 TestWarnMode::Allow => {
6326 if warn_on {
6327 write_warning_message(&message);
6328 }
6329 }
6330 TestWarnMode::Store => {
6331 if let Ok(s) = state.intern_str(&message) {
6332 state.push(LuaValue::Str(s));
6333 let _ = crate::api::set_global(state, b"_WARN");
6334 }
6335 }
6336 }
6337}
6338
6339fn write_warning_message(message: &[u8]) {
6340 use std::io::Write;
6341 let stderr = std::io::stderr();
6342 let mut h = stderr.lock();
6343 let _ = h.write_all(b"Lua warning: ");
6344 let _ = h.write_all(message);
6345 let _ = h.write_all(b"\n");
6346}
6347
6348fn default_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6353 use std::io::Write;
6354 if !to_cont && msg.first() == Some(&b'@') {
6356 match &msg[1..] {
6357 b"off" => state.global_mut().warn_mode = WarnMode::Off,
6358 b"on" => state.global_mut().warn_mode = WarnMode::On,
6359 _ => {}
6360 }
6361 return;
6362 }
6363 let mode = state.global().warn_mode;
6364 match mode {
6365 WarnMode::Off => {}
6366 WarnMode::On | WarnMode::Cont => {
6367 let stderr = std::io::stderr();
6368 let mut h = stderr.lock();
6369 if mode == WarnMode::On {
6370 let _ = h.write_all(b"Lua warning: ");
6371 }
6372 let _ = h.write_all(msg);
6373 if to_cont {
6374 state.global_mut().warn_mode = WarnMode::Cont;
6375 } else {
6376 let _ = h.write_all(b"\n");
6377 state.global_mut().warn_mode = WarnMode::On;
6378 }
6379 }
6380 }
6381}
6382
6383#[cfg(test)]
6384mod tests {
6385 use super::*;
6386
6387 fn test_noop_cclosure(_: &mut LuaState) -> Result<usize, LuaError> {
6388 Ok(0)
6389 }
6390
6391 #[test]
6392 fn external_root_keys_reject_stale_slot_after_reuse() {
6393 let mut roots = ExternalRootSet::default();
6394
6395 let first = roots.insert(LuaValue::Int(1));
6396 assert_eq!(roots.len(), 1);
6397 assert_eq!(roots.get(first), Some(&LuaValue::Int(1)));
6398
6399 assert_eq!(roots.remove(first), Some(LuaValue::Int(1)));
6400 assert!(roots.get(first).is_none());
6401 assert!(roots.remove(first).is_none());
6402 assert_eq!(roots.len(), 0);
6403 assert_eq!(roots.vacant_len(), 1);
6404 assert!(roots.replace(first, LuaValue::Int(9)).is_none());
6405 assert!(roots.is_empty());
6406
6407 let second = roots.insert(LuaValue::Int(2));
6408 assert_eq!(first.index, second.index);
6409 assert_ne!(first, second);
6410 assert!(roots.get(first).is_none());
6411 assert_eq!(roots.get(second), Some(&LuaValue::Int(2)));
6412 assert!(roots.replace(first, LuaValue::Int(3)).is_none());
6413 }
6414
6415 #[test]
6416 fn external_roots_keep_heap_value_alive_until_unrooted() {
6417 let mut state = new_state().expect("state should initialize");
6418 let _heap_guard = {
6419 let g = state.global();
6420 lua_gc::HeapGuard::push(&g.heap)
6421 };
6422
6423 let table = state.new_table();
6424 assert_eq!(state.global().heap.allgc_count(), 1);
6425
6426 let key = state.external_root_value(LuaValue::Table(table));
6427 state.gc().full_collect();
6428 assert_eq!(state.global().heap.allgc_count(), 1);
6429 assert_eq!(state.global().external_roots.len(), 1);
6430
6431 assert!(state.external_unroot_value(key).is_some());
6432 state.gc().full_collect();
6433 assert_eq!(state.global().heap.allgc_count(), 0);
6434 assert!(state.global().external_roots.is_empty());
6435 }
6436
6437 #[test]
6443 fn free_all_objects_kills_weak_handle_while_guard_and_heap_alive() {
6444 let mut state = new_state().expect("state should initialize");
6445 let heap_rc = state.global().heap.clone();
6446 let guard = lua_gc::HeapGuard::push(&heap_rc);
6447
6448 let table = state.new_table();
6449 let weak = table.downgrade();
6450 assert!(
6451 weak.upgrade().is_some(),
6452 "weak handle upgrades while the table is live"
6453 );
6454
6455 state.gc().free_all_objects();
6456
6457 assert!(
6458 weak.upgrade().is_none(),
6459 "free_all_objects must free the table and drop its weak token during \
6460 the close call, with the outer HeapGuard and a strong heap Rc still alive"
6461 );
6462 assert!(state.global().heap.is_closed());
6463 assert!(std::rc::Rc::strong_count(&heap_rc) >= 1);
6464
6465 drop(guard);
6466 drop(state);
6467 }
6468
6469 thread_local! {
6470 static CLOSE_GC_RUNS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
6471 }
6472
6473 fn close_gc_probe(_state: &mut LuaState) -> Result<usize, LuaError> {
6474 CLOSE_GC_RUNS.with(|c| c.set(c.get() + 1));
6475 Ok(0)
6476 }
6477
6478 fn install_finalizable_table_with(state: &mut LuaState, gc_fn: LuaCFunction) {
6483 let heap = state.global().heap.clone();
6484 let _guard = lua_gc::HeapGuard::push(&heap);
6485 crate::api::create_table(state, 0, 0).expect("object table");
6486 crate::api::create_table(state, 0, 1).expect("metatable");
6487 crate::api::push_cclosure(state, gc_fn, 0).expect("push __gc");
6488 crate::api::set_field(state, -2, b"__gc").expect("mt.__gc");
6489 crate::api::set_metatable(state, -2).expect("setmetatable");
6490 }
6491
6492 fn install_finalizable_table(state: &mut LuaState) {
6493 install_finalizable_table_with(state, close_gc_probe);
6494 }
6495
6496 #[test]
6501 fn state_close_runs_gc_finalizer_exactly_once() {
6502 CLOSE_GC_RUNS.with(|c| c.set(0));
6503 let mut state = new_state().expect("state should initialize");
6504 install_finalizable_table(&mut state);
6505 assert_eq!(
6506 CLOSE_GC_RUNS.with(|c| c.get()),
6507 0,
6508 "finalizer must not fire while the object is live"
6509 );
6510
6511 close(state);
6512 assert_eq!(
6513 CLOSE_GC_RUNS.with(|c| c.get()),
6514 1,
6515 "close must run the pending __gc exactly once before freeing"
6516 );
6517 }
6518
6519 #[test]
6524 fn manual_close_finalizer_pass_then_close_runs_gc_exactly_once() {
6525 CLOSE_GC_RUNS.with(|c| c.set(0));
6526 let mut state = new_state().expect("state should initialize");
6527 install_finalizable_table(&mut state);
6528
6529 crate::api::run_close_finalizers(&mut state);
6530 assert_eq!(CLOSE_GC_RUNS.with(|c| c.get()), 1);
6531
6532 close(state);
6533 assert_eq!(
6534 CLOSE_GC_RUNS.with(|c| c.get()),
6535 1,
6536 "close after a manual run_close_finalizers pass must not double-run __gc"
6537 );
6538 }
6539
6540 #[test]
6545 fn close_runs_queue_only_finalizer_leftovers() {
6546 CLOSE_GC_RUNS.with(|c| c.set(0));
6547 let mut state = new_state().expect("state should initialize");
6548 install_finalizable_table(&mut state);
6549
6550 {
6551 let mut g = state.global_mut();
6552 let pending: Vec<FinalizerObject> = g.finalizers.take_pending();
6553 assert!(!pending.is_empty());
6554 for object in pending.into_iter().rev() {
6555 let heap_ptr = object.heap_ptr();
6556 g.finalizers.push_to_be_finalized(object);
6557 if let Some(ptr) = heap_ptr {
6558 g.heap.move_finobj_to_tobefnz(ptr);
6559 }
6560 }
6561 assert_eq!(g.finalizers.pending_len(), 0);
6562 assert!(g.finalizers.has_to_be_finalized());
6563 }
6564
6565 close(state);
6566 assert_eq!(
6567 CLOSE_GC_RUNS.with(|c| c.get()),
6568 1,
6569 "a finalizer parked in to_be_finalized with pending empty must \
6570 still run at close"
6571 );
6572 }
6573
6574 fn self_reregister_gc_probe(state: &mut LuaState) -> Result<usize, LuaError> {
6579 CLOSE_GC_RUNS.with(|c| c.set(c.get() + 1));
6580 crate::api::push_value(state, 1);
6581 crate::api::create_table(state, 0, 1)?;
6582 crate::api::push_cclosure(state, self_reregister_gc_probe, 0)?;
6583 crate::api::set_field(state, -2, b"__gc")?;
6584 crate::api::set_metatable(state, -2)?;
6585 state.pop();
6586 Ok(0)
6587 }
6588
6589 #[test]
6595 fn queue_only_self_reregistering_finalizer_runs_exactly_once() {
6596 CLOSE_GC_RUNS.with(|c| c.set(0));
6597 let mut state = new_state().expect("state should initialize");
6598 install_finalizable_table_with(&mut state, self_reregister_gc_probe);
6599
6600 {
6601 let mut g = state.global_mut();
6602 let pending: Vec<FinalizerObject> = g.finalizers.take_pending();
6603 assert!(!pending.is_empty());
6604 for object in pending.into_iter().rev() {
6605 let heap_ptr = object.heap_ptr();
6606 g.finalizers.push_to_be_finalized(object);
6607 if let Some(ptr) = heap_ptr {
6608 g.heap.move_finobj_to_tobefnz(ptr);
6609 }
6610 }
6611 assert_eq!(g.finalizers.pending_len(), 0);
6612 assert!(g.finalizers.has_to_be_finalized());
6613 }
6614
6615 close(state);
6616 assert_eq!(
6617 CLOSE_GC_RUNS.with(|c| c.get()),
6618 1,
6619 "a self-re-registering queue-only finalizer must run exactly \
6620 once at close — seen must be seeded from to_be_finalized"
6621 );
6622 }
6623
6624 fn regen_gc_probe(state: &mut LuaState) -> Result<usize, LuaError> {
6625 CLOSE_GC_RUNS.with(|c| c.set(c.get() + 1));
6626 crate::api::create_table(state, 0, 0)?;
6627 crate::api::create_table(state, 0, 1)?;
6628 crate::api::push_cclosure(state, close_gc_probe, 0)?;
6629 crate::api::set_field(state, -2, b"__gc")?;
6630 crate::api::set_metatable(state, -2)?;
6631 state.pop();
6632 Ok(0)
6633 }
6634
6635 #[test]
6640 fn finalizer_registering_new_finalizable_during_close_runs_it() {
6641 CLOSE_GC_RUNS.with(|c| c.set(0));
6642 let mut state = new_state().expect("state should initialize");
6643 install_finalizable_table_with(&mut state, regen_gc_probe);
6644
6645 close(state);
6646 assert_eq!(
6647 CLOSE_GC_RUNS.with(|c| c.get()),
6648 2,
6649 "regen_gc_probe runs once, and the fresh finalizable it registered \
6650 during the drain runs exactly once on a later pass"
6651 );
6652 }
6653
6654 #[test]
6660 fn close_with_live_coroutine_lets_heap_drop() {
6661 let mut state = new_state().expect("state should initialize");
6662 let heap_weak = std::rc::Rc::downgrade(&state.global().heap);
6663 {
6664 let heap = state.global().heap.clone();
6665 let _guard = lua_gc::HeapGuard::push(&heap);
6666 new_thread(&mut state, None).expect("coroutine creation");
6667 }
6668 assert_eq!(state.global().threads.len(), 1);
6669
6670 close(state);
6671 assert!(
6672 heap_weak.upgrade().is_none(),
6673 "the heap must actually drop once the outer state drops after \
6674 close — a lingering threads-map cycle would keep it alive"
6675 );
6676 }
6677
6678 struct VmDropFlag(std::rc::Rc<std::cell::Cell<bool>>);
6681 impl lua_gc::Trace for VmDropFlag {
6682 fn trace(&self, _m: &mut lua_gc::Marker) {}
6683 }
6684 impl Drop for VmDropFlag {
6685 fn drop(&mut self) {
6686 self.0.set(true);
6687 }
6688 }
6689
6690 struct VmAllocOnDrop {
6696 flag: std::rc::Rc<std::cell::Cell<bool>>,
6697 inner: std::rc::Rc<std::cell::Cell<bool>>,
6698 }
6699 impl lua_gc::Trace for VmAllocOnDrop {
6700 fn trace(&self, _m: &mut lua_gc::Marker) {}
6701 }
6702 impl Drop for VmAllocOnDrop {
6703 fn drop(&mut self) {
6704 self.flag.set(true);
6705 let _ = GcRef::new(VmDropFlag(self.inner.clone()));
6706 }
6707 }
6708
6709 #[test]
6710 fn free_all_objects_provides_own_heap_guard_with_no_ambient_guard() {
6711 let mut state = new_state().expect("state should initialize");
6712 let heap = state.global().heap.clone();
6713 let outer = std::rc::Rc::new(std::cell::Cell::new(false));
6714 let inner = std::rc::Rc::new(std::cell::Cell::new(false));
6715 let _gc = heap.allocate(VmAllocOnDrop {
6716 flag: outer.clone(),
6717 inner: inner.clone(),
6718 });
6719
6720 state.gc().free_all_objects();
6721
6722 assert!(outer.get(), "the destructor itself must have run");
6723 assert!(
6724 inner.get(),
6725 "GcRef::new inside a teardown destructor must land in the closing \
6726 heap (via free_all_objects's own guard) and be drained, even with \
6727 no ambient HeapGuard"
6728 );
6729 assert!(heap.is_closed());
6730 }
6731
6732 #[test]
6733 fn free_all_objects_targets_own_heap_under_foreign_guard() {
6734 let mut state1 = new_state().expect("state 1 should initialize");
6735 let state2 = new_state().expect("state 2 should initialize");
6736 let heap1 = state1.global().heap.clone();
6737 let heap2 = state2.global().heap.clone();
6738 let _foreign = lua_gc::HeapGuard::push(&heap2);
6739
6740 let outer = std::rc::Rc::new(std::cell::Cell::new(false));
6741 let inner = std::rc::Rc::new(std::cell::Cell::new(false));
6742 let _gc = heap1.allocate(VmAllocOnDrop {
6743 flag: outer.clone(),
6744 inner: inner.clone(),
6745 });
6746 let heap2_baseline = heap2.allgc_count();
6747
6748 state1.gc().free_all_objects();
6749
6750 assert!(outer.get());
6751 assert!(
6752 inner.get(),
6753 "the teardown allocation must land in (and be drained from) the \
6754 heap being closed, not the foreign heap on the TLS guard stack"
6755 );
6756 assert_eq!(
6757 heap2.allgc_count(),
6758 heap2_baseline,
6759 "the foreign heap must be untouched by another state's close"
6760 );
6761 assert!(heap1.is_closed());
6762 assert!(!heap2.is_closed());
6763 drop(state2);
6764 }
6765
6766 #[test]
6767 fn interned_string_table_grows_then_shrinks_on_collection() {
6768 let mut state = new_state().expect("state should initialize");
6769 let _heap_guard = {
6770 let g = state.global();
6771 lua_gc::HeapGuard::push(&g.heap)
6772 };
6773
6774 let initial_buckets = state.global().interned_lt.bucket_count();
6775 assert_eq!(
6776 initial_buckets, 64,
6777 "the intern table starts at C's MINSTRTABSIZE-equivalent of 64"
6778 );
6779 let baseline_live = state.global().interned_lt.len();
6780
6781 let mut anchors: Vec<GcRef<LuaString>> = Vec::with_capacity(2000);
6782 for i in 0..2000usize {
6783 let key = format!("intern-shrink-probe-{i:08}");
6784 let s = state
6785 .intern_str(key.as_bytes())
6786 .expect("short string interns");
6787 anchors.push(s);
6788 }
6789
6790 let grown_buckets = state.global().interned_lt.bucket_count();
6791 assert_eq!(
6792 state.global().interned_lt.len(),
6793 baseline_live + 2000,
6794 "all 2000 distinct short strings are interned and live alongside \
6795 the runtime's own rooted strings"
6796 );
6797 assert!(
6798 grown_buckets >= 2048,
6799 "interning 2000 strings must force several bucket doublings past \
6800 the initial 64 (saw {grown_buckets})"
6801 );
6802
6803 drop(anchors);
6804 state.gc().full_collect();
6805
6806 let shrunk_buckets = state.global().interned_lt.bucket_count();
6807 let surviving_live = state.global().interned_lt.len();
6808 assert!(
6809 surviving_live <= baseline_live,
6810 "every probe string is unreachable and must be swept out of the \
6811 intern table, leaving at most the runtime's own strings (baseline \
6812 {baseline_live}, surviving {surviving_live})"
6813 );
6814 assert!(
6815 surviving_live < 2000,
6816 "the 2000 probe strings must not survive collection (surviving \
6817 {surviving_live})"
6818 );
6819 assert_eq!(
6820 shrunk_buckets, 64,
6821 "the batch-end shrink check must reclaim the stale buckets back to \
6822 the 64-bucket floor (saw {shrunk_buckets}, grew to {grown_buckets})"
6823 );
6824 assert!(
6825 shrunk_buckets < grown_buckets,
6826 "shrink must strictly reduce the bucket array"
6827 );
6828 }
6829
6830 #[test]
6831 fn table_buffer_accounting_refunds_on_sweep() {
6832 let mut state = new_state().expect("state should initialize");
6833 let _heap_guard = {
6834 let g = state.global();
6835 lua_gc::HeapGuard::push(&g.heap)
6836 };
6837
6838 let table = state.new_table();
6839 let key = state.external_root_value(LuaValue::Table(table));
6840 let header_bytes = state.global().heap.bytes_used();
6841 assert!(header_bytes > 0);
6842
6843 for i in 1..=128 {
6844 table
6845 .raw_set_int(&mut state, i, LuaValue::Int(i))
6846 .expect("integer table insert should succeed");
6847 }
6848 let grown_bytes = state.global().heap.bytes_used();
6849 assert!(
6850 grown_bytes > header_bytes,
6851 "table array/hash buffer growth must be charged to the GC heap"
6852 );
6853
6854 state.gc().full_collect();
6855 assert_eq!(
6856 state.global().heap.bytes_used(),
6857 grown_bytes,
6858 "rooted table buffer bytes should remain charged after collection"
6859 );
6860
6861 assert!(state.external_unroot_value(key).is_some());
6862 state.gc().full_collect();
6863 assert_eq!(state.global().heap.bytes_used(), 0);
6864 assert_eq!(state.global().heap.allgc_count(), 0);
6865 }
6866
6867 #[test]
6868 fn userdata_buffer_accounting_refunds_on_sweep() {
6869 let mut state = new_state().expect("state should initialize");
6870 let _heap_guard = {
6871 let g = state.global();
6872 lua_gc::HeapGuard::push(&g.heap)
6873 };
6874
6875 let payload_len = 4096;
6876 let userdata = state
6877 .new_userdata_typed(b"accounting", payload_len, 3)
6878 .expect("userdata allocation should succeed");
6879 state.pop_n(1);
6880 let key = state.external_root_value(LuaValue::UserData(userdata));
6881 let allocated_bytes = state.global().heap.bytes_used();
6882 assert!(
6883 allocated_bytes > payload_len,
6884 "userdata payload bytes must be charged to the GC heap"
6885 );
6886
6887 state.gc().full_collect();
6888 assert_eq!(
6889 state.global().heap.bytes_used(),
6890 allocated_bytes,
6891 "rooted userdata payload bytes should remain charged after collection"
6892 );
6893
6894 assert!(state.external_unroot_value(key).is_some());
6895 state.gc().full_collect();
6896 assert_eq!(state.global().heap.bytes_used(), 0);
6897 assert_eq!(state.global().heap.allgc_count(), 0);
6898 }
6899
6900 #[test]
6901 fn cclosure_upvalue_accounting_refunds_on_sweep() {
6902 let mut state = new_state().expect("state should initialize");
6903 let _heap_guard = {
6904 let g = state.global();
6905 lua_gc::HeapGuard::push(&g.heap)
6906 };
6907
6908 let nupvalues = 64;
6909 for i in 0..nupvalues {
6910 state.push(LuaValue::Int(i as i64));
6911 }
6912 crate::api::push_cclosure(&mut state, test_noop_cclosure, nupvalues as i32)
6913 .expect("C closure creation should succeed");
6914 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6915 panic!("expected heavy C closure");
6916 };
6917 let expected_payload = ccl.buffer_bytes();
6918 let key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
6919 state.pop_n(1);
6920 let allocated_bytes = state.global().heap.bytes_used();
6921 assert!(
6922 allocated_bytes >= expected_payload,
6923 "C closure upvalue vector bytes must be charged to the GC heap"
6924 );
6925
6926 state.gc().full_collect();
6927 assert_eq!(
6928 state.global().heap.bytes_used(),
6929 allocated_bytes,
6930 "rooted C closure payload bytes should remain charged after collection"
6931 );
6932
6933 assert!(state.external_unroot_value(key).is_some());
6934 state.gc().full_collect();
6935 assert_eq!(state.global().heap.bytes_used(), 0);
6936 assert_eq!(state.global().heap.allgc_count(), 0);
6937 }
6938
6939 #[test]
6940 fn proto_and_lclosure_accounting_refunds_on_sweep() {
6941 let mut state = new_state().expect("state should initialize");
6942 let _heap_guard = {
6943 let g = state.global();
6944 lua_gc::HeapGuard::push(&g.heap)
6945 };
6946
6947 let mut proto = LuaProto::placeholder();
6948 proto.code = vec![lua_types::opcode::Instruction(0); 2048];
6949 proto.lineinfo = vec![0; 2048];
6950 proto.k = vec![LuaValue::Int(1); 512];
6951 let expected_proto_payload = proto.buffer_bytes();
6952 let proto = GcRef::new(proto);
6953 proto.account_buffer(expected_proto_payload as isize);
6954
6955 let closure = state.new_lclosure(proto, 16);
6956 let expected_closure_payload = closure.buffer_bytes();
6957 let key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6958 let allocated_bytes = state.global().heap.bytes_used();
6959 assert!(
6960 allocated_bytes >= expected_proto_payload + expected_closure_payload,
6961 "proto and Lua closure vector bytes must be charged to the GC heap"
6962 );
6963
6964 state.gc().full_collect();
6965 assert_eq!(
6966 state.global().heap.bytes_used(),
6967 allocated_bytes,
6968 "rooted proto and Lua closure payload bytes should remain charged after collection"
6969 );
6970
6971 assert!(state.external_unroot_value(key).is_some());
6972 state.gc().full_collect();
6973 assert_eq!(state.global().heap.bytes_used(), 0);
6974 assert_eq!(state.global().heap.allgc_count(), 0);
6975 }
6976
6977 #[test]
6978 fn string_buffer_accounting_refunds_on_sweep() {
6979 let mut state = new_state().expect("state should initialize");
6980 let _heap_guard = {
6981 let g = state.global();
6982 lua_gc::HeapGuard::push(&g.heap)
6983 };
6984
6985 let payload = vec![b'x'; crate::string::MAX_SHORT_LEN + 4096];
6986 let string = state
6987 .intern_str(&payload)
6988 .expect("long string should allocate");
6989 let key = state.external_root_value(LuaValue::Str(string));
6990 let allocated_bytes = state.global().heap.bytes_used();
6991 assert!(
6992 allocated_bytes > payload.len(),
6993 "long string backing bytes must be charged to the GC heap"
6994 );
6995
6996 state.gc().full_collect();
6997 assert_eq!(
6998 state.global().heap.bytes_used(),
6999 allocated_bytes,
7000 "rooted string buffer bytes should remain charged after collection"
7001 );
7002
7003 assert!(state.external_unroot_value(key).is_some());
7004 state.gc().full_collect();
7005 assert_eq!(state.global().heap.bytes_used(), 0);
7006 assert_eq!(state.global().heap.allgc_count(), 0);
7007 }
7008
7009 #[test]
7010 fn interned_short_string_cache_does_not_root_unreferenced_string() {
7011 let mut state = new_state().expect("state should initialize");
7012 let _heap_guard = {
7013 let g = state.global();
7014 lua_gc::HeapGuard::push(&g.heap)
7015 };
7016
7017 let payload = b"weak-cache-probe-a";
7018 let string = state
7019 .intern_str(payload)
7020 .expect("short string should intern");
7021 let id = string.identity();
7022 assert!(state.global().interned_lt.contains_key(&payload[..]));
7023 assert_eq!(
7024 state.global().heap.register_allocation_token(id),
7025 state.global().heap.register_allocation_token(id),
7026 "token registration is get-or-insert while the string is provably live"
7027 );
7028 assert!(state.global().heap.allocation_token(id).is_some());
7029
7030 state.gc().full_collect();
7031 assert!(!state.global().interned_lt.contains_key(&payload[..]));
7032 assert_eq!(state.global().heap.allocation_token(id), None);
7033 }
7034
7035 #[test]
7036 fn interned_short_string_cache_keeps_reachable_string_until_unrooted() {
7037 let mut state = new_state().expect("state should initialize");
7038 let _heap_guard = {
7039 let g = state.global();
7040 lua_gc::HeapGuard::push(&g.heap)
7041 };
7042
7043 let payload = b"weak-cache-probe-b";
7044 let string = state
7045 .intern_str(payload)
7046 .expect("short string should intern");
7047 let id = string.identity();
7048 state.global().heap.register_allocation_token(id);
7049 let key = state.external_root_value(LuaValue::Str(string));
7050
7051 state.gc().full_collect();
7052 assert!(state.global().interned_lt.contains_key(&payload[..]));
7053 assert!(state.global().heap.allocation_token(id).is_some());
7054
7055 assert!(state.external_unroot_value(key).is_some());
7056 state.gc().full_collect();
7057 assert!(!state.global().interned_lt.contains_key(&payload[..]));
7058 assert_eq!(state.global().heap.allocation_token(id), None);
7059 }
7060
7061 #[test]
7062 fn gc_phase_predicates_follow_heap_state() {
7063 let mut state = new_state().expect("state should initialize");
7064 let _heap_guard = {
7065 let g = state.global();
7066 lua_gc::HeapGuard::push(&g.heap)
7067 };
7068
7069 {
7070 let mut g = state.global_mut();
7071 g.gckind = GcKind::Incremental as u8;
7072 g.lastatomic = 0;
7073 assert!(!g.is_gen_mode());
7074 g.lastatomic = 1;
7075 assert!(g.is_gen_mode());
7076 g.lastatomic = 0;
7077 }
7078
7079 let mut roots = Vec::new();
7080 for _ in 0..16 {
7081 let table = state.new_table();
7082 roots.push(state.external_root_value(LuaValue::Table(table)));
7083 }
7084
7085 let mut saw_keep = false;
7086 let mut saw_sweep = false;
7087 for _ in 0..128 {
7088 state.gc().incremental_step(1);
7089 let g = state.global();
7090 let heap_state = g.heap.gc_state();
7091 assert_eq!(g.keep_invariant(), heap_state.is_invariant());
7092 assert_eq!(g.is_sweep_phase(), heap_state.is_sweep());
7093 saw_keep |= g.keep_invariant();
7094 saw_sweep |= g.is_sweep_phase();
7095 if heap_state.is_pause() && saw_keep && saw_sweep {
7096 break;
7097 }
7098 }
7099
7100 assert!(
7101 saw_keep,
7102 "incremental cycle should expose an invariant phase"
7103 );
7104 assert!(saw_sweep, "incremental cycle should expose a sweep phase");
7105
7106 for key in roots {
7107 assert!(state.external_unroot_value(key).is_some());
7108 }
7109 state.gc().full_collect();
7110 }
7111
7112 #[test]
7113 fn gc_barrier_keeps_new_child_stored_in_black_parent() {
7114 let mut state = new_state().expect("state should initialize");
7115 let _heap_guard = {
7116 let g = state.global();
7117 lua_gc::HeapGuard::push(&g.heap)
7118 };
7119
7120 let parent = state.new_table();
7121 let parent_key = state.external_root_value(LuaValue::Table(parent));
7122 state.gc().incremental_step(1);
7123 assert!(
7124 state.global().keep_invariant(),
7125 "test setup should leave the parent marked during an active cycle"
7126 );
7127
7128 let child = state.new_table();
7129 let parent_value = LuaValue::Table(parent);
7130 let child_value = LuaValue::Table(child);
7131 parent
7132 .raw_set_int(&mut state, 1, child_value)
7133 .expect("table store should succeed");
7134 state.gc_barrier_back(&parent_value, &child_value);
7135
7136 for _ in 0..128 {
7137 if state.gc().incremental_step(1) {
7138 break;
7139 }
7140 }
7141
7142 assert_eq!(state.global().heap.allgc_count(), 2);
7143 assert_eq!(
7144 parent.get_int(1).as_table().map(|t| t.identity()),
7145 Some(child.identity())
7146 );
7147
7148 assert!(state.external_unroot_value(parent_key).is_some());
7149 state.gc().full_collect();
7150 assert_eq!(state.global().heap.allgc_count(), 0);
7151 }
7152
7153 #[test]
7154 fn generational_mode_promotes_and_barriers_age_objects() {
7155 let mut state = new_state().expect("state should initialize");
7156 let _heap_guard = {
7157 let g = state.global();
7158 lua_gc::HeapGuard::push(&g.heap)
7159 };
7160
7161 let parent = state.new_table();
7162 let parent_key = state.external_root_value(LuaValue::Table(parent));
7163
7164 state.gc().change_mode(GcKind::Generational);
7165 assert_eq!(parent.0.age(), lua_gc::GcAge::Old);
7166 assert_eq!(parent.0.color(), lua_gc::Color::Black);
7167 let majorbase = state.global().gc_estimate;
7168 assert!(majorbase > 0);
7169 assert!(state.global().gc_debt() <= 0);
7170
7171 let child = state.new_table();
7172 let parent_value = LuaValue::Table(parent);
7173 let child_value = LuaValue::Table(child);
7174 parent
7175 .raw_set_int(&mut state, 1, child_value.clone())
7176 .expect("table store should succeed");
7177 state.gc_barrier_back(&parent_value, &child_value);
7178 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched1);
7179 assert_eq!(parent.0.color(), lua_gc::Color::Gray);
7180 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7181
7182 let metatable = state.new_table();
7183 parent.set_metatable(Some(metatable));
7184 state.gc().obj_barrier(&parent, &metatable);
7185 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old0);
7186
7187 assert!(state.gc().generational_step_minor_only());
7188 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched2);
7189 assert_eq!(child.0.age(), lua_gc::GcAge::Survival);
7190 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old1);
7191 assert_eq!(state.global().gc_estimate, majorbase);
7192 assert!(state.global().gc_debt() <= 0);
7193
7194 state.gc().change_mode(GcKind::Incremental);
7195 assert_eq!(parent.0.age(), lua_gc::GcAge::New);
7196 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7197 assert_eq!(metatable.0.age(), lua_gc::GcAge::New);
7198
7199 assert!(state.external_unroot_value(parent_key).is_some());
7200 state.gc().full_collect();
7201 }
7202
7203 #[test]
7204 fn generational_upvalue_write_barrier_marks_young_child_old0() {
7205 let mut state = new_state().expect("state should initialize");
7206 let _heap_guard = {
7207 let g = state.global();
7208 lua_gc::HeapGuard::push(&g.heap)
7209 };
7210
7211 let proto = state.new_proto();
7212 let closure = state.new_lclosure(proto, 1);
7213 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7214 state.gc().change_mode(GcKind::Generational);
7215 let uv = closure.upval(0);
7216 assert_eq!(uv.0.age(), lua_gc::GcAge::Old);
7217
7218 let child = state.new_table();
7219 state
7220 .upvalue_set(&closure, 0, LuaValue::Table(child))
7221 .expect("closed upvalue write should succeed");
7222 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7223
7224 assert!(state.external_unroot_value(closure_key).is_some());
7225 state.gc().full_collect();
7226 }
7227
7228 #[test]
7229 fn cclosure_setupvalue_replaces_upvalue() {
7230 let mut state = new_state().expect("state should initialize");
7231 let _heap_guard = {
7232 let g = state.global();
7233 lua_gc::HeapGuard::push(&g.heap)
7234 };
7235
7236 let first = state.new_table();
7237 state.push(LuaValue::Table(first));
7238 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7239 .expect("C closure creation should succeed");
7240 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7241 panic!("expected heavy C closure");
7242 };
7243
7244 let second = state.new_table();
7245 state.push(LuaValue::Table(second));
7246 let name =
7247 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7248
7249 assert!(name.is_empty());
7250 let upvalues = ccl.upvalues.borrow();
7251 let LuaValue::Table(actual) = upvalues[0].clone() else {
7252 panic!("expected table upvalue");
7253 };
7254 assert_eq!(actual.identity(), second.identity());
7255 }
7256
7257 #[test]
7258 fn generational_cclosure_setupvalue_barrier_marks_young_child_old0() {
7259 let mut state = new_state().expect("state should initialize");
7260 let _heap_guard = {
7261 let g = state.global();
7262 lua_gc::HeapGuard::push(&g.heap)
7263 };
7264
7265 state.push(LuaValue::Nil);
7266 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7267 .expect("C closure creation should succeed");
7268 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7269 panic!("expected heavy C closure");
7270 };
7271 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
7272
7273 state.gc().change_mode(GcKind::Generational);
7274 assert_eq!(ccl.0.age(), lua_gc::GcAge::Old);
7275
7276 let child = state.new_table();
7277 state.push(LuaValue::Table(child));
7278 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7279
7280 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7281
7282 assert!(state.external_unroot_value(closure_key).is_some());
7283 state.gc().full_collect();
7284 }
7285
7286 #[test]
7287 fn generational_closure_upvalue_slot_barrier_marks_new_upval_old0() {
7288 let mut state = new_state().expect("state should initialize");
7289 let _heap_guard = {
7290 let g = state.global();
7291 lua_gc::HeapGuard::push(&g.heap)
7292 };
7293
7294 let proto = state.new_proto();
7295 let closure = state.new_lclosure(proto, 1);
7296 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7297 state.gc().change_mode(GcKind::Generational);
7298 assert_eq!(closure.0.age(), lua_gc::GcAge::Old);
7299
7300 let replacement = state.new_upval_closed(LuaValue::Nil);
7301 closure.set_upval(0, replacement);
7302 state.gc().obj_barrier(&closure, &replacement);
7303 assert_eq!(replacement.0.age(), lua_gc::GcAge::Old0);
7304
7305 assert!(state.external_unroot_value(closure_key).is_some());
7306 state.gc().full_collect();
7307 }
7308
7309 #[test]
7310 fn cross_thread_upvalue_mirror_traces_values_as_roots() {
7311 let mut state = new_state().expect("state should initialize");
7312 let _heap_guard = {
7313 let g = state.global();
7314 lua_gc::HeapGuard::push(&g.heap)
7315 };
7316
7317 let mirrored = state.new_table();
7318 state
7319 .global_mut()
7320 .cross_thread_upvals
7321 .insert((999, StackIdx(0)), LuaValue::Table(mirrored));
7322
7323 state.gc().full_collect();
7324 assert_eq!(state.global().heap.allgc_count(), 1);
7325
7326 state.global_mut().cross_thread_upvals.clear();
7327 state.gc().full_collect();
7328 assert_eq!(state.global().heap.allgc_count(), 0);
7329 }
7330
7331 #[test]
7332 fn generational_full_collect_promotes_new_survivors_to_old() {
7333 let mut state = new_state().expect("state should initialize");
7334 let _heap_guard = {
7335 let g = state.global();
7336 lua_gc::HeapGuard::push(&g.heap)
7337 };
7338
7339 state.gc().change_mode(GcKind::Generational);
7340 let table = state.new_table();
7341 let table_key = state.external_root_value(LuaValue::Table(table));
7342 assert_eq!(table.0.age(), lua_gc::GcAge::New);
7343
7344 state.gc().full_collect();
7345 assert_eq!(table.0.age(), lua_gc::GcAge::Old);
7346 assert_eq!(table.0.color(), lua_gc::Color::Black);
7347
7348 assert!(state.external_unroot_value(table_key).is_some());
7349 state.gc().full_collect();
7350 }
7351
7352 #[test]
7353 fn gc_packed_params_return_user_visible_values() {
7354 let mut state = new_state().expect("state should initialize");
7355 assert_eq!(
7356 crate::api::gc(&mut state, crate::api::GcArgs::SetPause { value: 200 }),
7357 200
7358 );
7359 assert_eq!(state.global().gc_pause_param(), 200);
7360 assert_eq!(
7361 crate::api::gc(&mut state, crate::api::GcArgs::SetStepMul { value: 200 }),
7362 100
7363 );
7364 assert_eq!(state.global().gc_stepmul_param(), 200);
7365
7366 crate::api::gc(
7367 &mut state,
7368 crate::api::GcArgs::Gen {
7369 minormul: 0,
7370 majormul: 200,
7371 },
7372 );
7373 assert_eq!(state.global().gc_genmajormul_param(), 200);
7374 }
7375
7376 #[test]
7377 fn generational_step_runs_bad_major_when_growth_exceeds_genmajormul() {
7378 let mut state = new_state().expect("state should initialize");
7379 let _heap_guard = {
7380 let g = state.global();
7381 lua_gc::HeapGuard::push(&g.heap)
7382 };
7383
7384 let root = state.new_table();
7385 let root_key = state.external_root_value(LuaValue::Table(root));
7386 state.gc().change_mode(GcKind::Generational);
7387
7388 let root_value = LuaValue::Table(root);
7389 for i in 1..=64 {
7390 let child = state.new_table();
7391 let child_value = LuaValue::Table(child);
7392 root.raw_set_int(&mut state, i, child_value.clone())
7393 .expect("table store should succeed");
7394 state.gc_barrier_back(&root_value, &child_value);
7395 }
7396
7397 {
7398 let mut g = state.global_mut();
7399 g.gc_estimate = 1;
7400 set_debt(&mut *g, 1);
7401 }
7402
7403 assert!(state.gc().generational_step());
7404 let g = state.global();
7405 assert!(g.is_gen_mode());
7406 assert!(
7407 g.lastatomic > 0,
7408 "bad major collection should arm stepgenfull"
7409 );
7410 assert!(g.gc_estimate > 1);
7411 assert!(g.gc_debt() <= 0);
7412 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
7413 drop(g);
7414
7415 assert!(state.external_unroot_value(root_key).is_some());
7416 state.gc().full_collect();
7417 }
7418
7419 #[test]
7420 fn generational_implicit_step_runs_major_when_heap_threshold_exceeded() {
7421 let mut state = new_state().expect("state should initialize");
7422 let _heap_guard = {
7423 let g = state.global();
7424 lua_gc::HeapGuard::push(&g.heap)
7425 };
7426
7427 let root = state.new_table();
7428 let root_key = state.external_root_value(LuaValue::Table(root));
7429 state.gc().change_mode(GcKind::Generational);
7430
7431 let root_value = LuaValue::Table(root);
7432 for i in 1..=64 {
7433 let child = state.new_table();
7434 let child_value = LuaValue::Table(child);
7435 root.raw_set_int(&mut state, i, child_value.clone())
7436 .expect("table store should succeed");
7437 state.gc_barrier_back(&root_value, &child_value);
7438 }
7439
7440 {
7441 let mut g = state.global_mut();
7442 g.gc_estimate = 1;
7443 set_debt(&mut *g, -1);
7444 g.heap.set_threshold_bytes(1);
7445 }
7446
7447 assert!(state.gc().generational_step());
7448 let g = state.global();
7449 assert!(g.is_gen_mode());
7450 assert!(
7451 g.lastatomic > 0,
7452 "implicit threshold-triggered growth should arm a bad major"
7453 );
7454 assert!(g.gc_debt() <= 0);
7455 drop(g);
7456
7457 assert!(state.external_unroot_value(root_key).is_some());
7458 state.gc().full_collect();
7459 }
7460
7461 #[test]
7462 fn generational_stepgenfull_returns_to_gen_after_good_collection() {
7463 let mut state = new_state().expect("state should initialize");
7464 let _heap_guard = {
7465 let g = state.global();
7466 lua_gc::HeapGuard::push(&g.heap)
7467 };
7468
7469 let root = state.new_table();
7470 let root_key = state.external_root_value(LuaValue::Table(root));
7471 state.gc().change_mode(GcKind::Generational);
7472 {
7473 let mut g = state.global_mut();
7474 g.lastatomic = 1024;
7475 }
7476
7477 assert!(state.gc().generational_step());
7478 let g = state.global();
7479 assert_eq!(g.gckind, GcKind::Generational as u8);
7480 assert_eq!(g.lastatomic, 0);
7481 assert!(g.gc_debt() <= 0);
7482 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
7483 assert_eq!(root.0.color(), lua_gc::Color::Black);
7484 drop(g);
7485
7486 assert!(state.external_unroot_value(root_key).is_some());
7487 state.gc().full_collect();
7488 }
7489
7490 #[test]
7491 fn generational_step_zero_reports_false_without_positive_debt() {
7492 let mut state = new_state().expect("state should initialize");
7493 let _heap_guard = {
7494 let g = state.global();
7495 lua_gc::HeapGuard::push(&g.heap)
7496 };
7497
7498 state.gc().change_mode(GcKind::Generational);
7499 assert_eq!(
7500 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 0 }),
7501 0
7502 );
7503 assert_eq!(
7504 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 1 }),
7505 1
7506 );
7507 }
7508}