1use std::cell::RefCell;
13use std::hash::{BuildHasherDefault, Hasher};
14use std::rc::Rc;
15
16pub use lua_types::error::LuaError;
17pub use lua_types::{CallInfoIdx, StackIdx};
18
19pub struct StackIdxConv(pub StackIdx);
22
23#[inline(always)]
26pub fn stack_idx_to_i32(i: StackIdx) -> i32 {
27 i.0 as i32
28}
29
30impl From<u32> for StackIdxConv {
31 #[inline(always)]
32 fn from(v: u32) -> Self {
33 StackIdxConv(StackIdx(v))
34 }
35}
36impl From<i32> for StackIdxConv {
37 #[inline(always)]
38 fn from(v: i32) -> Self {
39 StackIdxConv(StackIdx(v.max(0) as u32))
40 }
41}
42impl From<usize> for StackIdxConv {
43 #[inline(always)]
44 fn from(v: usize) -> Self {
45 StackIdxConv(StackIdx(v as u32))
46 }
47}
48impl From<StackIdx> for StackIdxConv {
49 #[inline(always)]
50 fn from(v: StackIdx) -> Self {
51 StackIdxConv(v)
52 }
53}
54pub use lua_types::closure::{
55 LuaCClosure as LuaClosureC, LuaCFnPtr, LuaClosure, LuaLClosure as LuaClosureLua,
56};
57pub use lua_types::gc::GcRef;
58pub use lua_types::proto::LuaProto;
59pub use lua_types::string::LuaString;
60pub use lua_types::upval::UpVal;
61pub use lua_types::userdata::LuaUserData;
62pub use lua_types::value::{F2Imod, LuaTable, LuaValue};
63
64pub struct LuaByteHasher {
65 hash: u64,
66}
67
68impl Default for LuaByteHasher {
69 fn default() -> Self {
70 Self {
71 hash: 0xcbf2_9ce4_8422_2325,
72 }
73 }
74}
75
76impl Hasher for LuaByteHasher {
77 #[inline]
78 fn write(&mut self, bytes: &[u8]) {
79 const PRIME: u64 = 0x0000_0100_0000_01b3;
80 for &byte in bytes {
81 self.hash ^= u64::from(byte);
82 self.hash = self.hash.wrapping_mul(PRIME);
83 }
84 }
85
86 #[inline]
87 fn write_u8(&mut self, i: u8) {
88 self.write(&[i]);
89 }
90
91 #[inline]
92 fn write_usize(&mut self, i: usize) {
93 self.write(&i.to_ne_bytes());
94 }
95
96 #[inline]
97 fn finish(&self) -> u64 {
98 self.hash
99 }
100}
101
102pub type LuaByteBuildHasher = BuildHasherDefault<LuaByteHasher>;
103
104pub struct InternedStringMap {
119 buckets: Vec<Vec<GcRef<LuaString>>>,
120 count: usize,
121}
122
123impl Default for InternedStringMap {
124 fn default() -> Self {
125 InternedStringMap {
126 buckets: (0..64).map(|_| Vec::new()).collect(),
127 count: 0,
128 }
129 }
130}
131
132impl InternedStringMap {
133 #[inline]
134 fn mask(&self) -> usize {
135 self.buckets.len() - 1
136 }
137
138 pub fn len(&self) -> usize {
139 self.count
140 }
141
142 pub fn is_empty(&self) -> bool {
143 self.count == 0
144 }
145
146 pub fn bucket_count(&self) -> usize {
151 self.buckets.len()
152 }
153
154 #[inline]
155 pub fn find(&self, bytes: &[u8], hash: u32) -> Option<GcRef<LuaString>> {
156 let bucket = &self.buckets[hash as usize & self.mask()];
157 bucket
158 .iter()
159 .find(|s| s.hash() == hash && s.as_bytes() == bytes)
160 .cloned()
161 }
162
163 pub fn insert(&mut self, s: GcRef<LuaString>) {
165 if self.count >= self.buckets.len() {
166 self.resize(self.buckets.len() * 2);
167 }
168 let m = self.mask();
169 self.buckets[s.hash() as usize & m].push(s);
170 self.count += 1;
171 }
172
173 fn resize(&mut self, new_len: usize) {
174 let old = std::mem::replace(
175 &mut self.buckets,
176 (0..new_len).map(|_| Vec::new()).collect(),
177 );
178 let m = self.mask();
179 for bucket in old {
180 for s in bucket {
181 self.buckets[s.hash() as usize & m].push(s);
182 }
183 }
184 }
185
186 pub fn shrink_if_sparse(&mut self) {
198 if self.count * 4 >= self.buckets.len() {
199 return;
200 }
201 let target = self.count.next_power_of_two().max(64);
202 if target < self.buckets.len() {
203 self.resize(target);
204 }
205 }
206
207 pub fn remove(&mut self, hash: u32, identity: usize) {
209 let m = self.mask();
210 let bucket = &mut self.buckets[hash as usize & m];
211 if let Some(pos) = bucket.iter().position(|s| s.identity() == identity) {
212 bucket.swap_remove(pos);
213 self.count -= 1;
214 }
215 }
216
217 pub fn iter(&self) -> impl Iterator<Item = &GcRef<LuaString>> {
218 self.buckets.iter().flatten()
219 }
220
221 pub fn contains_key(&self, bytes: &[u8]) -> bool {
222 self.find(bytes, LuaString::hash_bytes(bytes, 0)).is_some()
223 }
224}
225
226pub type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
232
233pub type LuaRustFunction = Rc<dyn Fn(&mut LuaState) -> Result<usize, LuaError>>;
234
235#[derive(Clone)]
236pub enum LuaCallable {
237 Bare(LuaCFunction),
238 Rust(LuaRustFunction),
239}
240
241impl std::fmt::Debug for LuaCallable {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 match self {
244 LuaCallable::Bare(_) => f.write_str("LuaCallable::Bare(..)"),
245 LuaCallable::Rust(_) => f.write_str("LuaCallable::Rust(..)"),
246 }
247 }
248}
249
250impl LuaCallable {
251 pub fn bare(f: LuaCFunction) -> Self {
252 LuaCallable::Bare(f)
253 }
254
255 pub fn rust(f: LuaRustFunction) -> Self {
256 LuaCallable::Rust(f)
257 }
258
259 pub fn as_bare(&self) -> Option<LuaCFunction> {
260 match self {
261 LuaCallable::Bare(f) => Some(*f),
262 LuaCallable::Rust(_) => None,
263 }
264 }
265
266 pub fn call(&self, state: &mut LuaState) -> Result<usize, LuaError> {
267 match self {
268 LuaCallable::Bare(f) => f(state),
269 LuaCallable::Rust(f) => f(state),
270 }
271 }
272}
273
274#[derive(Clone, Debug)]
275pub enum FinalizerObject {
276 Table(GcRef<LuaTable>),
277 UserData(GcRef<LuaUserData>),
278}
279
280impl FinalizerObject {
281 pub fn identity(&self) -> usize {
282 match self {
283 FinalizerObject::Table(t) => t.identity(),
284 FinalizerObject::UserData(u) => u.identity(),
285 }
286 }
287
288 pub fn metatable(&self) -> Option<GcRef<LuaTable>> {
289 match self {
290 FinalizerObject::Table(t) => t.metatable(),
291 FinalizerObject::UserData(u) => u.metatable(),
292 }
293 }
294
295 pub fn as_lua_value(&self) -> LuaValue {
296 match self {
297 FinalizerObject::Table(t) => LuaValue::Table(t.clone()),
298 FinalizerObject::UserData(u) => LuaValue::UserData(u.clone()),
299 }
300 }
301
302 pub fn mark(&self, marker: &mut lua_gc::Marker) {
303 match self {
304 FinalizerObject::Table(t) => marker.mark(t.0),
305 FinalizerObject::UserData(u) => marker.mark(u.0),
306 }
307 }
308
309 pub fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
310 Some(match self {
311 FinalizerObject::Table(t) => t.0.as_trace_ptr(),
312 FinalizerObject::UserData(u) => u.0.as_trace_ptr(),
313 })
314 }
315
316 pub fn age(&self) -> lua_gc::GcAge {
317 match self {
318 FinalizerObject::Table(t) => t.0.age(),
319 FinalizerObject::UserData(u) => u.0.age(),
320 }
321 }
322
323 pub fn is_finalized(&self) -> bool {
324 match self {
325 FinalizerObject::Table(t) => t.0.is_finalized(),
326 FinalizerObject::UserData(u) => u.0.is_finalized(),
327 }
328 }
329
330 pub fn set_finalized(&self, finalized: bool) {
331 match self {
332 FinalizerObject::Table(t) => t.0.set_finalized(finalized),
333 FinalizerObject::UserData(u) => u.0.set_finalized(finalized),
334 }
335 }
336}
337
338impl lua_gc::FinalizerEntry for FinalizerObject {
339 fn identity(&self) -> usize {
340 FinalizerObject::identity(self)
341 }
342
343 fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
344 FinalizerObject::heap_ptr(self)
345 }
346
347 fn age(&self) -> lua_gc::GcAge {
348 FinalizerObject::age(self)
349 }
350
351 fn is_finalized(&self) -> bool {
352 FinalizerObject::is_finalized(self)
353 }
354
355 fn set_finalized(&self, finalized: bool) {
356 FinalizerObject::set_finalized(self, finalized);
357 }
358}
359
360#[derive(Clone, Debug)]
361pub struct WeakTableEntry {
362 table: lua_types::gc::GcWeak<LuaTable>,
363 kind: lua_gc::WeakListKind,
364}
365
366impl WeakTableEntry {
367 pub fn new(table: &GcRef<LuaTable>) -> Self {
368 let mode = table.weak_mode();
369 let weak_keys = (mode & (1 << 0)) != 0;
370 let weak_values = (mode & (1 << 1)) != 0;
371 let kind = match (weak_keys, weak_values) {
372 (true, true) => lua_gc::WeakListKind::AllWeak,
373 (true, false) => lua_gc::WeakListKind::Ephemeron,
374 (false, true) => lua_gc::WeakListKind::WeakValues,
375 (false, false) => lua_gc::WeakListKind::WeakValues,
376 };
377 Self {
378 table: table.downgrade(),
379 kind,
380 }
381 }
382}
383
384impl lua_gc::WeakEntry for WeakTableEntry {
385 type Strong = GcRef<LuaTable>;
386
387 fn identity(&self) -> usize {
388 self.table.identity()
389 }
390
391 fn list_kind(&self) -> lua_gc::WeakListKind {
392 self.kind
393 }
394
395 fn upgrade(&self) -> Option<Self::Strong> {
396 self.table.upgrade()
397 }
398}
399
400pub(crate) const EXTRA_STACK: usize = 5;
403
404pub(crate) const LUA_MINSTACK: usize = 20;
405
406pub(crate) const BASIC_STACK_SIZE: usize = 2 * LUA_MINSTACK;
407
408pub(crate) const LUAI_MAXCCALLS: u32 = 200;
428
429pub(crate) const CIST_C: u16 = 1 << 1;
430
431pub(crate) const CIST_OAH: u16 = 1 << 0;
432pub(crate) const CIST_FRESH: u16 = 1 << 2;
433pub(crate) const CIST_HOOKED: u16 = 1 << 3;
434pub(crate) const CIST_YPCALL: u16 = 1 << 4;
435pub(crate) const CIST_TAIL: u16 = 1 << 5;
436pub(crate) const CIST_HOOKYIELD: u16 = 1 << 6;
437pub(crate) const CIST_FIN: u16 = 1 << 7;
438pub(crate) const CIST_TRAN: u16 = 1 << 8;
439pub(crate) const CIST_RECST: u32 = 10;
440pub(crate) const CIST_LEQ: u16 = 1 << 13;
446
447pub(crate) const CIST_TRAP: u16 = 1 << 14;
455
456const LUA_NUMTYPES: usize = 9;
457
458const GCSTPUSR: u8 = 1;
459const GCSTPGC: u8 = 2;
460
461const GCS_PAUSE: u8 = 0;
462
463const LUAI_GCPAUSE: u32 = 200;
464const LUAI_GCMUL: u32 = 100;
465const LUAI_GCSTEPSIZE: u8 = 13;
466const LUAI_GENMAJORMUL: u32 = 100;
467const LUAI_GENMINORMUL: u8 = 20;
468
469const WHITE0BIT: u8 = 0;
470
471const STRCACHE_N: usize = 53;
472const STRCACHE_M: usize = 2;
473
474const LUA_RIDX_MAINTHREAD: i64 = 1;
478
479const LUA_RIDX_GLOBALS: i64 = 2;
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
487pub enum GcKind {
488 Incremental = 0,
489 Generational = 1,
490}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub enum WarnMode {
501 Off,
502 On,
503 Cont,
504}
505
506#[derive(Debug, Clone, Copy, PartialEq, Eq)]
508pub enum TestWarnMode {
509 Normal,
510 Allow,
511 Store,
512}
513
514pub use lua_types::status::LuaStatus;
519
520#[derive(Clone)]
532pub struct StackValue {
533 pub val: LuaValue,
534}
535
536impl Default for StackValue {
537 fn default() -> Self {
538 StackValue {
539 val: LuaValue::Nil,
540 }
541 }
542}
543
544#[derive(Clone)]
551pub struct CallInfo {
552 pub func: StackIdx,
553
554 pub top: StackIdx,
555
556 pub previous: Option<CallInfoIdx>,
557
558 pub next: Option<CallInfoIdx>,
559
560 pub u: CallInfoFrame,
561
562 pub u2: CallInfoExtra,
563
564 pub nresults: i16,
565
566 pub callstatus: u16,
568
569 pub call_metamethods: u8,
573
574 pub tailcalls: u16,
583}
584
585#[cfg(target_pointer_width = "64")]
594const _: () = {
595 assert!(core::mem::size_of::<CallInfoFrame>() == 32);
596 assert!(core::mem::size_of::<CallInfo>() == 72);
597};
598
599#[derive(Clone, Copy)]
616pub struct CallInfoFrame {
617 pub savedpc: u32,
619 pub nextraargs: i32,
621 pub k: Option<LuaKFunction>,
623 pub old_errfunc: isize,
625 pub ctx: isize,
627}
628
629pub type LuaKFunction = fn(&mut LuaState, status: i32, ctx: isize) -> Result<usize, LuaError>;
631
632#[derive(Default, Clone, Copy)]
634pub struct CallInfoExtra {
635 pub value: i32,
636 pub ftransfer: u16,
637 pub ntransfer: u16,
638}
639
640impl CallInfoFrame {
641 pub fn c_default() -> Self {
645 CallInfoFrame {
646 savedpc: 0,
647 nextraargs: 0,
648 k: None,
649 old_errfunc: 0,
650 ctx: 0,
651 }
652 }
653
654 pub fn lua_default() -> Self {
661 CallInfoFrame {
662 savedpc: 0,
663 nextraargs: 0,
664 k: None,
665 old_errfunc: 0,
666 ctx: 0,
667 }
668 }
669}
670
671impl Default for CallInfo {
672 fn default() -> Self {
673 CallInfo {
674 func: StackIdx(0),
675 top: StackIdx(0),
676 previous: None,
677 next: None,
678 u: CallInfoFrame::c_default(),
679 u2: CallInfoExtra::default(),
680 nresults: 0,
681 callstatus: 0,
682 call_metamethods: 0,
683 tailcalls: 0,
684 }
685 }
686}
687
688impl CallInfo {
689 pub fn is_lua(&self) -> bool {
690 (self.callstatus & CIST_C) == 0
691 }
692 pub fn is_lua_code(&self) -> bool {
693 self.is_lua()
694 }
695 pub fn is_vararg_func(&self) -> bool {
700 false
701 }
702 pub fn saved_pc(&self) -> u32 {
707 debug_assert!(self.is_lua(), "saved_pc on a C frame");
708 self.u.savedpc
709 }
710 pub fn set_saved_pc(&mut self, pc: u32) {
712 debug_assert!(self.is_lua(), "set_saved_pc on a C frame");
713 self.u.savedpc = pc;
714 }
715 pub fn nextra_args(&self) -> i32 {
717 debug_assert!(self.is_lua(), "nextra_args on a C frame");
718 self.u.nextraargs
719 }
720 pub fn set_nextra_args(&mut self, n: i32) {
722 debug_assert!(self.is_lua(), "set_nextra_args on a C frame");
723 self.u.nextraargs = n;
724 }
725 pub fn transfer_ftransfer(&self) -> u16 {
726 self.u2.ftransfer
727 }
728 pub fn transfer_ntransfer(&self) -> u16 {
729 self.u2.ntransfer
730 }
731 pub fn set_trap(&mut self, t: bool) {
739 debug_assert!(self.is_lua(), "set_trap on a C frame");
740 if t {
741 self.callstatus |= CIST_TRAP;
742 } else {
743 self.callstatus &= !CIST_TRAP;
744 }
745 }
746 #[inline(always)]
750 pub fn trap(&self) -> bool {
751 (self.callstatus & CIST_TRAP) != 0
752 }
753 pub fn recover_status(&self) -> i32 {
756 ((self.callstatus >> CIST_RECST) & 7) as i32
757 }
758 pub fn set_recover_status<T: Into<i32>>(&mut self, status: T) {
761 let st = (status.into() & 7) as u16;
762 self.callstatus = (self.callstatus & !(7u16 << CIST_RECST)) | (st << CIST_RECST);
763 }
764 pub fn get_oah(&self) -> bool {
765 (self.callstatus & CIST_OAH) != 0
766 }
767 pub fn set_oah(&mut self, allow: bool) {
770 self.callstatus = (self.callstatus & !CIST_OAH) | (if allow { CIST_OAH } else { 0 });
771 }
772 pub fn u_c_old_errfunc(&self) -> isize {
777 debug_assert!(!self.is_lua(), "u_c_old_errfunc on a Lua frame");
778 self.u.old_errfunc
779 }
780 pub fn u_c_ctx(&self) -> isize {
782 debug_assert!(!self.is_lua(), "u_c_ctx on a Lua frame");
783 self.u.ctx
784 }
785 pub fn u_c_k(&self) -> Option<LuaKFunction> {
787 debug_assert!(!self.is_lua(), "u_c_k on a Lua frame");
788 self.u.k
789 }
790 pub fn set_u_c_k(&mut self, k: Option<LuaKFunction>) {
796 debug_assert!(!self.is_lua(), "set_u_c_k on a Lua frame");
797 self.u.k = k;
798 }
799 pub fn set_u_c_ctx(&mut self, ctx: isize) {
801 debug_assert!(!self.is_lua(), "set_u_c_ctx on a Lua frame");
802 self.u.ctx = ctx;
803 }
804 pub fn set_u_c_old_errfunc(&mut self, old_errfunc: isize) {
806 debug_assert!(!self.is_lua(), "set_u_c_old_errfunc on a Lua frame");
807 self.u.old_errfunc = old_errfunc;
808 }
809 pub fn set_u2_funcidx(&mut self, idx: i32) {
812 self.u2.value = idx;
813 }
814}
815
816pub trait LuaValueExt {
821 fn base_type(&self) -> lua_types::LuaType;
822 fn to_number_no_strconv(&self) -> Option<f64>;
823 fn to_number_with_strconv(&self) -> Option<f64>;
824 fn to_integer_no_strconv(&self) -> Option<i64>;
825 fn to_integer_with_strconv(&self) -> Option<i64>;
826 fn full_type_tag(&self) -> u8;
827}
828
829impl LuaValueExt for LuaValue {
830 fn base_type(&self) -> lua_types::LuaType {
831 self.type_tag()
832 }
833 fn to_number_no_strconv(&self) -> Option<f64> {
834 match self {
835 LuaValue::Float(f) => Some(*f),
836 LuaValue::Int(i) => Some(*i as f64),
837 _ => None,
838 }
839 }
840 fn to_number_with_strconv(&self) -> Option<f64> {
841 if let Some(n) = self.to_number_no_strconv() {
842 return Some(n);
843 }
844 if let LuaValue::Str(s) = self {
845 let mut tmp = LuaValue::Nil;
846 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
847 if sz == 0 {
848 return None;
849 }
850 return match tmp {
851 LuaValue::Int(i) => Some(i as f64),
852 LuaValue::Float(f) => Some(f),
853 _ => None,
854 };
855 }
856 None
857 }
858 fn to_integer_no_strconv(&self) -> Option<i64> {
859 match self {
860 LuaValue::Int(i) => Some(*i),
861 LuaValue::Float(f) if f.fract() == 0.0 && f.is_finite() => {
862 let min_f = i64::MIN as f64;
866 let max_plus1_f = -(i64::MIN as f64);
867 if *f >= min_f && *f < max_plus1_f {
868 Some(*f as i64)
869 } else {
870 None
871 }
872 }
873 _ => None,
874 }
875 }
876 fn to_integer_with_strconv(&self) -> Option<i64> {
877 if let Some(i) = self.to_integer_no_strconv() {
878 return Some(i);
879 }
880 if let LuaValue::Str(s) = self {
881 let mut tmp = LuaValue::Nil;
882 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
883 if sz == 0 {
884 return None;
885 }
886 return tmp.to_integer_no_strconv();
887 }
888 None
889 }
890 fn full_type_tag(&self) -> u8 {
891 match self {
892 LuaValue::Nil => 0x00,
893 LuaValue::Bool(false) => 0x01,
894 LuaValue::Bool(true) => 0x11,
895 LuaValue::Int(_) => 0x03,
896 LuaValue::Float(_) => 0x13,
897 LuaValue::Str(s) if s.is_short() => 0x04,
898 LuaValue::Str(_) => 0x14,
899 LuaValue::LightUserData(_) => 0x02,
900 LuaValue::Table(_) => 0x05,
901 LuaValue::Function(LuaClosure::Lua(_)) => 0x06,
902 LuaValue::Function(LuaClosure::LightC(_)) => 0x16,
903 LuaValue::Function(LuaClosure::C(_)) => 0x26,
904 LuaValue::UserData(_) => 0x07,
905 LuaValue::Thread(_) => 0x08,
906 }
907 }
908}
909
910pub trait LuaTypeExt {
912 fn type_name(&self) -> &'static [u8];
913}
914
915impl LuaTypeExt for lua_types::LuaType {
916 fn type_name(&self) -> &'static [u8] {
917 use lua_types::LuaType::*;
918 match self {
919 None => b"no value",
920 Nil => b"nil",
921 Boolean => b"boolean",
922 LightUserData => b"userdata",
923 Number => b"number",
924 String => b"string",
925 Table => b"table",
926 Function => b"function",
927 UserData => b"userdata",
928 Thread => b"thread",
929 }
930 }
931}
932
933pub trait StackIdxExt {
937 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32;
938 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32;
939 fn raw(self) -> u32;
940}
941impl StackIdxExt for StackIdx {
942 #[inline(always)]
943 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32 {
944 self.0.saturating_sub(n.into().0 .0)
945 }
946 #[inline(always)]
947 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32 {
948 self.0.wrapping_sub(n.into().0 .0)
949 }
950 #[inline(always)]
951 fn raw(self) -> u32 {
952 self.0
953 }
954}
955
956pub trait LuaTableRefExt {
963 fn metatable(&self) -> Option<GcRef<LuaTable>>;
964 fn has_metatable(&self) -> bool;
965 fn as_ptr(&self) -> *const ();
966 fn get(&self, _k: &LuaValue) -> LuaValue;
967 fn get_int(&self, _k: i64) -> LuaValue;
968 fn get_short_str(&self, _k: &GcRef<LuaString>) -> LuaValue;
969 fn raw_set(&self, _state: &mut LuaState, _k: LuaValue, _v: LuaValue) -> Result<(), LuaError>;
970 fn raw_set_int(&self, _state: &mut LuaState, _k: i64, _v: LuaValue) -> Result<(), LuaError>;
971 fn raw_set_short_str(
972 &self,
973 _state: &mut LuaState,
974 _k: GcRef<LuaString>,
975 _v: LuaValue,
976 ) -> Result<(), LuaError>;
977 fn invalidate_tm_cache(&self);
978 fn resize(&self, _state: &mut LuaState, _na: usize, _nh: usize) -> Result<(), LuaError>;
979 fn next(&self, _k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError>;
980}
981impl LuaTableRefExt for GcRef<LuaTable> {
982 #[inline]
983 fn metatable(&self) -> Option<GcRef<LuaTable>> {
984 (**self).metatable()
985 }
986 #[inline]
987 fn has_metatable(&self) -> bool {
988 (**self).has_metatable()
989 }
990 #[inline]
991 fn as_ptr(&self) -> *const () {
992 GcRef::identity(self) as *const ()
993 }
994 #[inline]
995 fn get(&self, k: &LuaValue) -> LuaValue {
996 (**self).get(k)
997 }
998 #[inline]
999 fn get_int(&self, k: i64) -> LuaValue {
1000 (**self).get_int(k)
1001 }
1002 #[inline]
1003 fn get_short_str(&self, k: &GcRef<LuaString>) -> LuaValue {
1004 (**self).get_short_str(k)
1005 }
1006 #[inline(always)]
1015 fn raw_set(&self, state: &mut LuaState, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
1016 match k {
1017 LuaValue::Int(i) => return self.raw_set_int(state, i, v),
1018 LuaValue::Str(s) if s.is_short() => return self.raw_set_short_str(state, s, v),
1019 k => {
1020 if k.is_collectable() {
1021 state.gc_table_barrier_back(self, &k);
1022 }
1023 let before = (**self).buffer_bytes();
1024 let result = (**self).try_raw_set(k, v);
1025 if result.is_ok() {
1026 account_table_buffer_delta(self, before);
1027 }
1028 result
1029 }
1030 }
1031 }
1032 #[inline(always)]
1033 fn raw_set_int(&self, _state: &mut LuaState, k: i64, v: LuaValue) -> Result<(), LuaError> {
1034 match (**self).try_update_int(k, v) {
1035 Ok(()) => Ok(()),
1036 Err(v) => {
1037 let before = (**self).buffer_bytes();
1038 let result = (**self).try_raw_set_int(k, v);
1039 if result.is_ok() {
1040 account_table_buffer_delta(self, before);
1041 }
1042 result
1043 }
1044 }
1045 }
1046 #[inline(always)]
1050 fn raw_set_short_str(
1051 &self,
1052 state: &mut LuaState,
1053 k: GcRef<LuaString>,
1054 v: LuaValue,
1055 ) -> Result<(), LuaError> {
1056 match (**self).try_update_short_str(&k, v) {
1057 Ok(()) => Ok(()),
1058 Err(v) => {
1059 state.gc_table_barrier_back(self, &LuaValue::Str(k));
1060 let before = (**self).buffer_bytes();
1061 let result = (**self).try_raw_set(LuaValue::Str(k), v);
1062 if result.is_ok() {
1063 account_table_buffer_delta(self, before);
1064 }
1065 result
1066 }
1067 }
1068 }
1069 fn invalidate_tm_cache(&self) {}
1070 fn resize(&self, _state: &mut LuaState, na: usize, nh: usize) -> Result<(), LuaError> {
1071 let before = (**self).buffer_bytes();
1072 let na32 = na.min(u32::MAX as usize) as u32;
1073 let nh32 = nh.min(u32::MAX as usize) as u32;
1074 let result = (**self).resize(na32, nh32);
1075 if result.is_ok() {
1076 account_table_buffer_delta(self, before);
1077 }
1078 result
1079 }
1080 fn next(&self, k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
1081 (**self).try_next_pair(&k)
1082 }
1083}
1084
1085#[inline]
1086fn account_table_buffer_delta(t: &GcRef<LuaTable>, before: usize) {
1087 let after = (**t).buffer_bytes();
1088 if after > before {
1089 t.account_buffer((after - before) as isize);
1090 } else if before > after {
1091 t.account_buffer(-((before - after) as isize));
1092 }
1093}
1094
1095pub trait LuaUserDataRefExt {
1096 fn metatable(&self) -> Option<GcRef<LuaTable>>;
1097 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>);
1098 fn as_ptr(&self) -> *const ();
1099 fn len(&self) -> usize;
1100}
1101impl LuaUserDataRefExt for GcRef<LuaUserData> {
1102 fn metatable(&self) -> Option<GcRef<LuaTable>> {
1103 (**self).metatable()
1104 }
1105 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>) {
1106 (**self).set_metatable(mt);
1107 }
1108 fn as_ptr(&self) -> *const () {
1109 GcRef::identity(self) as *const ()
1110 }
1111 fn len(&self) -> usize {
1112 self.0.data.len()
1113 }
1114}
1115
1116pub trait LuaStringRefExt {
1117 fn is_white(&self) -> bool;
1118 fn hash(&self) -> u32;
1119 fn as_gc_ref(&self) -> GcRef<LuaString>;
1120}
1121impl LuaStringRefExt for GcRef<LuaString> {
1122 fn is_white(&self) -> bool {
1123 false
1124 }
1125 fn hash(&self) -> u32 {
1126 self.0.hash()
1127 }
1128 fn as_gc_ref(&self) -> GcRef<LuaString> {
1129 self.clone()
1130 }
1131}
1132
1133pub trait LuaLClosureRefExt {
1134 fn proto(&self) -> &GcRef<LuaProto>;
1135 fn nupvalues(&self) -> usize;
1136}
1137impl LuaLClosureRefExt for GcRef<lua_types::closure::LuaLClosure> {
1138 fn proto(&self) -> &GcRef<LuaProto> {
1139 &self.0.proto
1140 }
1141 fn nupvalues(&self) -> usize {
1142 self.0.upvals.len()
1143 }
1144}
1145
1146pub trait LuaClosureExt {
1148 fn nupvalues(&self) -> usize;
1149}
1150impl LuaClosureExt for LuaClosure {
1151 fn nupvalues(&self) -> usize {
1152 match self {
1153 LuaClosure::Lua(l) => l.0.upvals.len(),
1154 LuaClosure::C(c) => c.0.upvalues.borrow().len(),
1155 LuaClosure::LightC(_) => 0,
1156 }
1157 }
1158}
1159
1160pub trait LuaProtoExt {
1162 fn source_bytes(&self) -> &[u8];
1163 fn source_string(&self) -> Option<&GcRef<LuaString>>;
1164}
1165impl LuaProtoExt for LuaProto {
1166 fn source_bytes(&self) -> &[u8] {
1167 match &self.source {
1168 Some(s) => s.0.as_bytes(),
1169 None => &[],
1170 }
1171 }
1172 fn source_string(&self) -> Option<&GcRef<LuaString>> {
1173 self.source.as_ref()
1174 }
1175}
1176
1177pub trait Collectable: std::fmt::Debug {}
1181
1182impl std::fmt::Debug for LuaState {
1183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1184 write!(f, "LuaState")
1185 }
1186}
1187impl Collectable for LuaState {}
1188
1189pub type ParserHook = fn(
1203 state: &mut LuaState,
1204 z: &mut crate::zio::ZIO,
1205 name: &[u8],
1206 firstchar: i32,
1207) -> Result<GcRef<lua_types::closure::LuaLClosure>, LuaError>;
1208
1209pub type FileLoaderHook = fn(filename: &[u8]) -> Result<Vec<u8>, LuaError>;
1217
1218pub type FileOpenHook =
1237 fn(filename: &[u8], mode: &[u8]) -> std::io::Result<Box<dyn lua_types::LuaFileHandle>>;
1238
1239pub type OutputHook = fn(bytes: &[u8]) -> std::io::Result<()>;
1247
1248pub type InputHook = fn(buf: &mut [u8]) -> std::io::Result<usize>;
1251
1252pub type EnvHook = fn(name: &[u8]) -> Option<Vec<u8>>;
1258
1259pub type UnixTimeHook = fn() -> i64;
1261
1262pub type CpuClockHook = fn() -> f64;
1270
1271pub type LocalOffsetHook = fn(timestamp: i64) -> i64;
1283
1284pub type EntropyHook = fn() -> u64;
1288
1289pub type TempNameHook = fn() -> Result<Vec<u8>, LuaError>;
1294
1295pub type PopenHook =
1306 fn(cmd: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1307
1308pub type FileRemoveHook = fn(filename: &[u8]) -> std::io::Result<()>;
1318
1319pub type FileRenameHook = fn(from: &[u8], to: &[u8]) -> std::io::Result<()>;
1328
1329#[derive(Clone, Copy, Debug)]
1335pub enum OsExecuteReason {
1336 Exit,
1338 Signal,
1340}
1341
1342#[derive(Debug)]
1345pub struct OsExecuteResult {
1346 pub success: bool,
1348 pub reason: OsExecuteReason,
1350 pub code: i32,
1352}
1353
1354pub type OsExecuteHook = fn(cmd: &[u8]) -> Result<OsExecuteResult, LuaError>;
1361
1362#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1369pub struct DynLibId(pub u64);
1370
1371pub enum DynamicSymbol {
1379 RustNative(LuaCFunction),
1382 LuaCAbi(*const ()),
1388 Unsupported { reason: Vec<u8> },
1391}
1392
1393pub type DynLibLoadHook =
1404 fn(state: &mut LuaState, path: &[u8], see_global: bool) -> Result<DynLibId, LuaError>;
1405
1406pub type DynLibSymbolHook =
1414 fn(state: &mut LuaState, handle: DynLibId, symbol: &[u8]) -> Result<DynamicSymbol, LuaError>;
1415
1416pub type DynLibUnloadHook = fn(handle: DynLibId);
1424
1425pub struct ThreadRegistryEntry {
1431 pub state: Rc<RefCell<LuaState>>,
1436 pub value: GcRef<lua_types::value::LuaThread>,
1439}
1440
1441#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1446pub struct ExternalRootKey {
1447 index: usize,
1448 generation: u64,
1449}
1450
1451#[derive(Debug)]
1452struct ExternalRootSlot {
1453 value: Option<LuaValue>,
1454 generation: u64,
1455}
1456
1457#[derive(Debug, Default)]
1463pub struct ExternalRootSet {
1464 slots: Vec<ExternalRootSlot>,
1465 free: Vec<usize>,
1466 live: usize,
1467}
1468
1469impl ExternalRootSet {
1470 pub fn insert(&mut self, value: LuaValue) -> ExternalRootKey {
1471 if let Some(index) = self.free.pop() {
1472 let slot = &mut self.slots[index];
1473 debug_assert!(slot.value.is_none(), "free external-root slot is occupied");
1474 slot.generation = slot.generation.wrapping_add(1).max(1);
1475 slot.value = Some(value);
1476 self.live += 1;
1477 ExternalRootKey {
1478 index,
1479 generation: slot.generation,
1480 }
1481 } else {
1482 let index = self.slots.len();
1483 self.slots.push(ExternalRootSlot {
1484 value: Some(value),
1485 generation: 1,
1486 });
1487 self.live += 1;
1488 ExternalRootKey {
1489 index,
1490 generation: 1,
1491 }
1492 }
1493 }
1494
1495 pub fn get(&self, key: ExternalRootKey) -> Option<&LuaValue> {
1496 let slot = self.slots.get(key.index)?;
1497 if slot.generation == key.generation {
1498 slot.value.as_ref()
1499 } else {
1500 None
1501 }
1502 }
1503
1504 pub fn replace(&mut self, key: ExternalRootKey, value: LuaValue) -> Option<LuaValue> {
1505 let slot = self.slots.get_mut(key.index)?;
1506 if slot.generation != key.generation || slot.value.is_none() {
1507 return None;
1508 }
1509 slot.value.replace(value)
1510 }
1511
1512 pub fn remove(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
1513 let slot = self.slots.get_mut(key.index)?;
1514 if slot.generation != key.generation {
1515 return None;
1516 }
1517 let old = slot.value.take()?;
1518 self.free.push(key.index);
1519 self.live -= 1;
1520 Some(old)
1521 }
1522
1523 pub fn iter_values(&self) -> impl Iterator<Item = &LuaValue> {
1524 self.slots.iter().filter_map(|slot| slot.value.as_ref())
1525 }
1526
1527 pub fn len(&self) -> usize {
1528 self.live
1529 }
1530
1531 pub fn is_empty(&self) -> bool {
1532 self.live == 0
1533 }
1534
1535 pub fn vacant_len(&self) -> usize {
1536 self.free.len()
1537 }
1538}
1539
1540pub struct GlobalState {
1544 pub parser_hook: Option<ParserHook>,
1550
1551 pub cli_argv: Option<Vec<Vec<u8>>>,
1558
1559 pub cli_preload: Option<fn(&mut LuaState) -> Result<(), LuaError>>,
1563
1564 pub lua_version: lua_types::LuaVersion,
1571
1572 pub file_loader_hook: Option<FileLoaderHook>,
1577
1578 pub file_open_hook: Option<FileOpenHook>,
1583
1584 pub stdout_hook: Option<OutputHook>,
1588
1589 pub stderr_hook: Option<OutputHook>,
1591
1592 pub stdin_hook: Option<InputHook>,
1595
1596 pub env_hook: Option<EnvHook>,
1598
1599 pub unix_time_hook: Option<UnixTimeHook>,
1602
1603 pub cpu_clock_hook: Option<CpuClockHook>,
1606
1607 pub local_offset_hook: Option<LocalOffsetHook>,
1612
1613 pub entropy_hook: Option<EntropyHook>,
1616
1617 pub temp_name_hook: Option<TempNameHook>,
1619
1620 pub popen_hook: Option<PopenHook>,
1625
1626 pub file_remove_hook: Option<FileRemoveHook>,
1629
1630 pub file_rename_hook: Option<FileRenameHook>,
1633
1634 pub os_execute_hook: Option<OsExecuteHook>,
1638
1639 pub dynlib_load_hook: Option<DynLibLoadHook>,
1644
1645 pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
1649
1650 pub dynlib_unload_hook: Option<DynLibUnloadHook>,
1654
1655 pub sandbox: SandboxLimits,
1658
1659 pub gc_debt: isize,
1660
1661 pub gc_estimate: usize,
1662
1663 pub lastatomic: usize,
1664
1665 pub l_registry: LuaValue,
1666
1667 pub external_roots: ExternalRootSet,
1670
1671 pub globals: LuaValue,
1677 pub loaded: LuaValue,
1678
1679 pub nilvalue: LuaValue,
1682
1683 pub seed: u32,
1684
1685 pub currentwhite: u8,
1686
1687 pub gcstate: u8,
1688
1689 pub gckind: u8,
1690
1691 pub gcstopem: bool,
1692
1693 pub genminormul: u8,
1694
1695 pub genmajormul: u8,
1696
1697 pub gcstp: u8,
1698
1699 pub gcemergency: bool,
1700
1701 pub gcpause: u8,
1702
1703 pub gcstepmul: u8,
1704
1705 pub gcstepsize: u8,
1706
1707 pub gc55_params: [i64; 6],
1716
1717 pub sweepgc_cursor: usize,
1720
1721 pub weak_tables_registry: lua_gc::WeakRegistry<WeakTableEntry>,
1730
1731 pub finalizers: lua_gc::FinalizerRegistry<FinalizerObject>,
1734
1735 pub gc_finalizer_error: Option<LuaValue>,
1746
1747 pub twups: Vec<GcRef<LuaState>>,
1748
1749 pub panic: Option<LuaCFunction>,
1750
1751 pub mainthread: Option<GcRef<LuaState>>,
1755
1756 pub threads: std::collections::HashMap<u64, ThreadRegistryEntry>,
1768
1769 pub main_thread_value: GcRef<lua_types::value::LuaThread>,
1774
1775 pub current_thread_id: u64,
1779
1780 pub closing_thread_id: Option<u64>,
1783
1784 pub main_thread_id: u64,
1787
1788 pub next_thread_id: u64,
1791
1792 pub thread_globals: std::collections::HashMap<u64, LuaValue>,
1809
1810 pub closure_envs: std::collections::HashMap<usize, LuaValue>,
1822
1823 pub memerrmsg: GcRef<LuaString>,
1824
1825 pub tmname: Vec<GcRef<LuaString>>,
1826
1827 pub mt: [Option<GcRef<LuaTable>>; LUA_NUMTYPES],
1828
1829 pub strcache: [[GcRef<LuaString>; STRCACHE_M]; STRCACHE_N],
1830
1831 pub interned_lt: InternedStringMap,
1836
1837 pub warnf: Option<Box<dyn FnMut(&[u8], bool)>>,
1838
1839 pub warn_mode: WarnMode,
1844
1845 pub test_warn_enabled: bool,
1850 pub test_warn_on: bool,
1851 pub test_warn_mode: TestWarnMode,
1852 pub test_warn_last_to_cont: bool,
1853 pub test_warn_buffer: Vec<u8>,
1854
1855 pub c_functions: Vec<LuaCallable>,
1860
1861 pub heap: std::rc::Rc<lua_gc::Heap>,
1865
1866 pub cross_thread_upvals: std::collections::HashMap<(u64, StackIdx), LuaValue>,
1880
1881 pub suspended_parent_stacks: Vec<Vec<LuaValue>>,
1895
1896 pub suspended_parent_open_upvals: Vec<Vec<GcRef<UpVal>>>,
1902
1903 pub snapshot_stack_pool: Vec<Vec<LuaValue>>,
1911 pub snapshot_upval_pool: Vec<Vec<GcRef<UpVal>>>,
1912
1913 pub resume_value_pool: Vec<Vec<LuaValue>>,
1921
1922 pub resume_upval_slot_pool: Vec<Vec<(u64, StackIdx)>>,
1928
1929 pub resume_flush_pool: Vec<Vec<(StackIdx, LuaValue)>>,
1933}
1934
1935const SANDBOX_COUNT_MASK: u8 = 1 << 3;
1938
1939pub const SANDBOX_TRIP_NONE: u8 = 0;
1941pub const SANDBOX_TRIP_INSTRUCTIONS: u8 = 1;
1943pub const SANDBOX_TRIP_MEMORY: u8 = 2;
1945
1946#[derive(Default)]
1953pub struct SandboxLimits {
1954 pub interval: std::cell::Cell<i32>,
1956 pub instr_limited: std::cell::Cell<bool>,
1958 pub instr_remaining: std::cell::Cell<u64>,
1960 pub instr_limit: std::cell::Cell<u64>,
1962 pub mem_limit: std::cell::Cell<Option<usize>>,
1964 pub tripped: std::cell::Cell<u8>,
1966 pub aborting: std::cell::Cell<bool>,
1971}
1972
1973impl GlobalState {
1974 pub fn sandbox_active(&self) -> bool {
1976 self.sandbox.interval.get() != 0
1977 }
1978
1979 pub fn total_bytes(&self) -> usize {
1982 self.heap.bytes_used().max(1)
1983 }
1984
1985 pub fn get_thread(&self, id: u64) -> Option<&ThreadRegistryEntry> {
1990 self.threads.get(&id)
1991 }
1992
1993 pub fn thread_value_for(&self, id: u64) -> Option<GcRef<lua_types::value::LuaThread>> {
1997 if id == self.main_thread_id {
1998 Some(self.main_thread_value.clone())
1999 } else {
2000 self.threads.get(&id).map(|e| e.value.clone())
2001 }
2002 }
2003
2004 pub fn is_complete(&self) -> bool {
2007 matches!(self.nilvalue, LuaValue::Nil)
2008 }
2009
2010 pub fn current_white(&self) -> u8 {
2016 self.currentwhite
2017 }
2018
2019 pub fn other_white(&self) -> u8 {
2021 self.currentwhite ^ 0x03
2022 }
2023
2024 pub fn is_gen_mode(&self) -> bool {
2026 self.gckind == GcKind::Generational as u8 || self.lastatomic != 0
2027 }
2028
2029 pub fn gc_running(&self) -> bool {
2031 self.gcstp == 0
2032 }
2033
2034 pub fn keep_invariant(&self) -> bool {
2036 self.heap.gc_state().is_invariant()
2037 }
2038
2039 pub fn is_sweep_phase(&self) -> bool {
2041 self.heap.gc_state().is_sweep()
2042 }
2043
2044 pub fn gc_debt(&self) -> isize {
2046 self.gc_debt
2047 }
2048 pub fn set_gc_debt(&mut self, d: isize) {
2049 self.gc_debt = d;
2050 }
2051 pub fn gc_at_pause(&self) -> bool {
2052 self.heap.gc_state().is_pause()
2053 }
2054 fn get_gc_param(p: u8) -> i32 {
2055 (p as i32) * 4
2056 }
2057 fn set_gc_param_slot(slot: &mut u8, p: i32) {
2058 *slot = (p / 4) as u8;
2059 }
2060 pub fn gc_pause_param(&self) -> i32 {
2061 Self::get_gc_param(self.gcpause)
2062 }
2063 pub fn set_gc_pause_param(&mut self, p: i32) {
2064 Self::set_gc_param_slot(&mut self.gcpause, p);
2065 }
2066 pub fn gc_stepmul_param(&self) -> i32 {
2067 Self::get_gc_param(self.gcstepmul)
2068 }
2069 pub fn set_gc_stepmul_param(&mut self, p: i32) {
2070 Self::set_gc_param_slot(&mut self.gcstepmul, p);
2071 }
2072 pub fn gc_genmajormul_param(&self) -> i32 {
2073 Self::get_gc_param(self.genmajormul)
2074 }
2075 pub fn set_gc_genmajormul(&mut self, p: i32) {
2076 Self::set_gc_param_slot(&mut self.genmajormul, p);
2077 }
2078 pub fn gc55_param(&mut self, idx: usize, value: i64) -> i64 {
2082 let old = self.gc55_params[idx];
2083 if value >= 0 {
2084 self.gc55_params[idx] = value;
2085 }
2086 old
2087 }
2088 pub fn gc_stop_flags(&self) -> u8 {
2089 self.gcstp
2090 }
2091 pub fn set_gc_stop_flags(&mut self, f: u8) {
2092 self.gcstp = f;
2093 }
2094 pub fn stop_gc_internal(&mut self) -> u8 {
2095 let old = self.gcstp;
2096 self.gcstp |= GCSTPGC;
2097 old
2098 }
2099 pub fn set_gc_stop_user(&mut self) {
2100 self.gcstp = GCSTPUSR;
2102 }
2103 pub fn clear_gc_stop(&mut self) {
2104 self.gcstp = 0;
2105 }
2106 pub fn is_gc_running(&self) -> bool {
2107 self.gcstp == 0
2108 }
2109 pub fn is_gc_stopped_internally(&self) -> bool {
2114 (self.gcstp & GCSTPGC) != 0
2115 }
2116
2117 pub fn tm_name<T: TmIndex>(&self, tm: T) -> Option<GcRef<LuaString>> {
2125 self.tmname.get(tm.tm_index()).cloned()
2126 }
2127}
2128
2129pub trait TmIndex: Copy {
2135 fn tm_index(self) -> usize;
2136}
2137impl TmIndex for lua_types::tagmethod::TagMethod {
2138 fn tm_index(self) -> usize {
2139 self as u8 as usize
2140 }
2141}
2142impl TmIndex for crate::tagmethods::TagMethod {
2143 fn tm_index(self) -> usize {
2144 self as u8 as usize
2145 }
2146}
2147impl TmIndex for usize {
2148 fn tm_index(self) -> usize {
2149 self
2150 }
2151}
2152impl TmIndex for u8 {
2153 fn tm_index(self) -> usize {
2154 self as usize
2155 }
2156}
2157
2158use lua_types::tagmethod::TagMethod;
2159
2160pub struct LuaState {
2178 pub status: u8,
2181
2182 pub allowhook: bool,
2183
2184 pub nci: u32,
2185
2186 pub top: StackIdx,
2189
2190 pub stack_last: StackIdx,
2193
2194 pub stack: Vec<StackValue>,
2195
2196 pub ci: CallInfoIdx,
2199
2200 pub call_info: Vec<CallInfo>,
2202
2203 pub openupval: Vec<GcRef<UpVal>>,
2206
2207 pub legacy_open_upval_touched: std::cell::Cell<bool>,
2219
2220 pub tbclist: Vec<StackIdx>,
2221
2222 pub(crate) global: Rc<RefCell<GlobalState>>,
2228
2229 pub hook: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>,
2232
2233 pub hookmask: u8,
2234
2235 pub basehookcount: i32,
2236
2237 pub hookcount: i32,
2238
2239 pub errfunc: isize,
2245
2246 pub n_ccalls: u32,
2249
2250 pub oldpc: u32,
2253
2254 pub marked: u8,
2257
2258 pub cached_thread_id: u64,
2274
2275 pub gc_check_needed: bool,
2280}
2281
2282impl LuaState {
2283 pub fn global(&self) -> std::cell::Ref<'_, GlobalState> {
2289 self.global.borrow()
2290 }
2291
2292 pub fn global_mut(&self) -> std::cell::RefMut<'_, GlobalState> {
2294 self.global.borrow_mut()
2295 }
2296
2297 pub fn global_rc(&self) -> Rc<RefCell<GlobalState>> {
2301 Rc::clone(&self.global)
2302 }
2303
2304 pub fn v51_thread_lgt(&self, id: u64) -> LuaValue {
2313 let g = self.global();
2314 if id == g.main_thread_id {
2315 g.globals.clone()
2316 } else {
2317 g.thread_globals
2318 .get(&id)
2319 .expect("v51 coroutine l_gt must be seeded in new_thread")
2320 .clone()
2321 }
2322 }
2323
2324 pub fn v51_set_thread_lgt(&self, id: u64, t: LuaValue) {
2329 let mut g = self.global_mut();
2330 if id == g.main_thread_id {
2331 g.globals = t;
2332 } else {
2333 g.thread_globals.insert(id, t);
2334 }
2335 }
2336
2337 pub fn enable_test_warning_handler(&mut self) -> Result<(), LuaError> {
2339 {
2340 let mut g = self.global_mut();
2341 g.test_warn_enabled = true;
2342 g.test_warn_on = false;
2343 g.test_warn_mode = TestWarnMode::Normal;
2344 g.test_warn_last_to_cont = false;
2345 g.test_warn_buffer.clear();
2346 }
2347 self.push(LuaValue::Bool(false));
2348 crate::api::set_global(self, b"_WARN")
2349 }
2350
2351 pub fn c_calls(&self) -> u32 {
2353 self.n_ccalls & 0xffff
2354 }
2355
2356 pub fn inc_nny(&mut self) {
2358 self.n_ccalls += 0x10000;
2359 }
2360
2361 pub fn dec_nny(&mut self) {
2363 self.n_ccalls -= 0x10000;
2364 }
2365
2366 pub fn is_yieldable(&self) -> bool {
2368 (self.n_ccalls & 0xffff0000) == 0
2369 }
2370
2371 pub fn reset_hook_count(&mut self) {
2373 self.hookcount = self.basehookcount;
2374 }
2375
2376 pub fn install_sandbox_limits(
2385 &mut self,
2386 interval: i32,
2387 instr_limit: Option<u64>,
2388 mem_limit: Option<usize>,
2389 ) {
2390 let interval = interval.max(1);
2391 {
2392 let g = self.global();
2393 g.sandbox.interval.set(interval);
2394 g.sandbox.instr_limited.set(instr_limit.is_some());
2395 g.sandbox.instr_remaining.set(instr_limit.unwrap_or(0));
2396 g.sandbox.instr_limit.set(instr_limit.unwrap_or(0));
2397 g.sandbox.mem_limit.set(mem_limit);
2398 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2399 }
2400 self.hookmask |= SANDBOX_COUNT_MASK;
2401 self.basehookcount = interval;
2402 self.hookcount = interval;
2403 crate::debug::arm_traps(self);
2404 }
2405
2406 pub fn sandbox_charge_interval(&self) -> Option<LuaError> {
2411 let interval = self.global().sandbox.interval.get();
2412 self.sandbox_charge(interval as u64)
2413 }
2414
2415 pub fn sandbox_charge(&self, amount: u64) -> Option<LuaError> {
2424 let g = self.global();
2425 if g.sandbox.interval.get() == 0 {
2426 return None;
2427 }
2428 if g.sandbox.instr_limited.get() {
2429 let rem = g.sandbox.instr_remaining.get().saturating_sub(amount);
2430 g.sandbox.instr_remaining.set(rem);
2431 if rem == 0 {
2432 g.sandbox.tripped.set(SANDBOX_TRIP_INSTRUCTIONS);
2433 g.sandbox.aborting.set(true);
2434 return Some(LuaError::runtime(format_args!(
2435 "sandbox: instruction budget exhausted"
2436 )));
2437 }
2438 }
2439 if let Some(limit) = g.sandbox.mem_limit.get() {
2440 if g.total_bytes() > limit {
2441 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2442 g.sandbox.aborting.set(true);
2443 return Some(LuaError::runtime(format_args!(
2444 "sandbox: memory limit exceeded"
2445 )));
2446 }
2447 }
2448 None
2449 }
2450
2451 pub fn sandbox_reserve(&self, additional: usize) -> Option<LuaError> {
2459 let g = self.global();
2460 if g.sandbox.interval.get() == 0 {
2461 return None;
2462 }
2463 if let Some(limit) = g.sandbox.mem_limit.get() {
2464 let projected = g.total_bytes().saturating_add(additional);
2465 if projected > limit {
2466 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2467 g.sandbox.aborting.set(true);
2468 return Some(LuaError::runtime(format_args!(
2469 "sandbox: memory limit exceeded"
2470 )));
2471 }
2472 }
2473 None
2474 }
2475
2476 pub fn sandbox_match_step_limit(&self) -> u64 {
2481 let g = self.global();
2482 if g.sandbox.interval.get() != 0 && g.sandbox.instr_limited.get() {
2483 g.sandbox.instr_remaining.get()
2484 } else {
2485 0
2486 }
2487 }
2488
2489 pub fn sandbox_aborting(&self) -> bool {
2493 self.global().sandbox.aborting.get()
2494 }
2495
2496 pub fn sandbox_instr_limited(&self) -> bool {
2498 self.global().sandbox.instr_limited.get()
2499 }
2500
2501 pub fn sandbox_instr_remaining(&self) -> u64 {
2504 self.global().sandbox.instr_remaining.get()
2505 }
2506
2507 pub fn sandbox_instr_limit(&self) -> u64 {
2509 self.global().sandbox.instr_limit.get()
2510 }
2511
2512 pub fn sandbox_tripped_code(&self) -> u8 {
2514 self.global().sandbox.tripped.get()
2515 }
2516
2517 pub fn sandbox_reset(&self) {
2520 let g = self.global();
2521 if g.sandbox.instr_limited.get() {
2522 g.sandbox.instr_remaining.set(g.sandbox.instr_limit.get());
2523 }
2524 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2525 g.sandbox.aborting.set(false);
2526 }
2527
2528 pub fn stack_size(&self) -> usize {
2530 self.stack_last.0 as usize
2531 }
2532
2533 #[inline(always)]
2535 pub fn push(&mut self, val: LuaValue) {
2536 let top = self.top.0 as usize;
2537 if top < self.stack.len() {
2538 self.stack[top] = StackValue { val };
2539 } else {
2540 self.stack.push(StackValue { val });
2541 }
2542 self.top = StackIdx(self.top.0 + 1);
2543 }
2544
2545 #[inline(always)]
2548 pub fn pop(&mut self) -> LuaValue {
2549 if self.top.0 == 0 {
2550 return LuaValue::Nil;
2551 }
2552 self.top = StackIdx(self.top.0 - 1);
2553 self.stack[self.top.0 as usize].val.clone()
2554 }
2555
2556 #[inline(always)]
2558 pub fn stack_val(&self, idx: StackIdx) -> &LuaValue {
2559 &self.stack[idx.0 as usize].val
2560 }
2561
2562 #[inline(always)]
2564 pub fn set_stack_val(&mut self, idx: StackIdx, val: LuaValue) {
2565 self.stack[idx.0 as usize].val = val;
2566 }
2567
2568 pub fn gc(&mut self) -> GcHandle<'_> {
2571 GcHandle { _state: self }
2572 }
2573
2574 pub fn external_root_value(&mut self, value: LuaValue) -> ExternalRootKey {
2576 self.global_mut().external_roots.insert(value)
2577 }
2578
2579 pub fn external_rooted_value(&self, key: ExternalRootKey) -> Option<LuaValue> {
2581 self.global().external_roots.get(key).cloned()
2582 }
2583
2584 pub fn external_replace_root(
2586 &mut self,
2587 key: ExternalRootKey,
2588 value: LuaValue,
2589 ) -> Option<LuaValue> {
2590 self.global_mut().external_roots.replace(key, value)
2591 }
2592
2593 pub fn external_unroot_value(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
2595 self.global_mut().external_roots.remove(key)
2596 }
2597
2598 pub fn try_external_unroot_value(
2601 &mut self,
2602 key: ExternalRootKey,
2603 ) -> std::result::Result<Option<LuaValue>, std::cell::BorrowMutError> {
2604 self.global
2605 .try_borrow_mut()
2606 .map(|mut global| global.external_roots.remove(key))
2607 }
2608
2609 pub fn new_table(&mut self) -> GcRef<LuaTable> {
2611 self.mark_gc_check_needed();
2612 GcRef::new(LuaTable::placeholder())
2613 }
2614
2615 pub fn new_table_with_sizes(
2620 &mut self,
2621 array_size: u32,
2622 hash_size: u32,
2623 ) -> Result<GcRef<LuaTable>, LuaError> {
2624 self.mark_gc_check_needed();
2625 let t = GcRef::new(LuaTable::placeholder());
2626 self.table_resize(&t, array_size as usize, hash_size as usize)?;
2627 Ok(t)
2628 }
2629
2630 pub fn intern_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
2644 if bytes.len() <= crate::string::MAX_SHORT_LEN {
2645 let hash = LuaString::hash_bytes(bytes, 0);
2646 {
2647 let global = self.global();
2648 if let Some(existing) = global.interned_lt.find(bytes, hash) {
2649 return Ok(existing);
2650 }
2651 }
2652 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2653 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2654 self.global_mut().interned_lt.insert(new_ref.clone());
2655 self.mark_gc_check_needed();
2656 Ok(new_ref)
2657 } else {
2658 self.mark_gc_check_needed();
2659 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2660 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2661 Ok(new_ref)
2662 }
2663 }
2664
2665 #[inline(always)]
2667 pub fn top_idx(&self) -> StackIdx {
2668 self.top
2669 }
2670}
2671
2672impl LuaState {
2677 #[inline(always)]
2678 pub fn get_at(&self, idx: impl Into<StackIdxConv>) -> LuaValue {
2679 let i: StackIdx = idx.into().0;
2680 match self.stack.get(i.0 as usize) {
2681 Some(slot) => slot.val.clone(),
2682 None => LuaValue::Nil,
2683 }
2684 }
2685 #[inline(always)]
2686 pub fn set_at(&mut self, idx: impl Into<StackIdxConv>, v: LuaValue) {
2687 let i: StackIdx = idx.into().0;
2688 self.stack[i.0 as usize].val = v;
2689 }
2690
2691 pub fn clear_stack_range(&mut self, start: StackIdx, end: StackIdx) {
2705 if end.0 <= start.0 {
2706 return;
2707 }
2708 let end_u = end.0 as usize;
2709 if end_u > self.stack.len() {
2710 self.stack.resize_with(end_u, StackValue::default);
2711 }
2712 for i in start.0..end.0 {
2713 self.stack[i as usize].val = LuaValue::Nil;
2714 }
2715 }
2716 #[inline(always)]
2724 pub fn get_int_at(&self, idx: impl Into<StackIdxConv>) -> Option<i64> {
2725 let i: StackIdx = idx.into().0;
2726 match self.stack.get(i.0 as usize) {
2727 Some(slot) => match &slot.val {
2728 LuaValue::Int(v) => Some(*v),
2729 _ => None,
2730 },
2731 None => None,
2732 }
2733 }
2734 #[inline(always)]
2741 pub fn get_int_pair_at(
2742 &self,
2743 rb: impl Into<StackIdxConv>,
2744 rc: impl Into<StackIdxConv>,
2745 ) -> Option<(i64, i64)> {
2746 let rb: StackIdx = rb.into().0;
2747 let rc: StackIdx = rc.into().0;
2748 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2749 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib, ic)),
2750 _ => None,
2751 }
2752 }
2753 #[inline(always)]
2758 pub fn get_num_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2759 let i: StackIdx = idx.into().0;
2760 match self.stack.get(i.0 as usize) {
2761 Some(slot) => match &slot.val {
2762 LuaValue::Float(f) => Some(*f),
2763 LuaValue::Int(v) => Some(*v as f64),
2764 _ => None,
2765 },
2766 None => None,
2767 }
2768 }
2769 #[inline(always)]
2774 pub fn get_float_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2775 let i: StackIdx = idx.into().0;
2776 match self.stack.get(i.0 as usize) {
2777 Some(slot) => match &slot.val {
2778 LuaValue::Float(f) => Some(*f),
2779 _ => None,
2780 },
2781 None => None,
2782 }
2783 }
2784 #[inline(always)]
2789 pub fn get_num_pair_at(
2790 &self,
2791 rb: impl Into<StackIdxConv>,
2792 rc: impl Into<StackIdxConv>,
2793 ) -> Option<(f64, f64)> {
2794 let rb: StackIdx = rb.into().0;
2795 let rc: StackIdx = rc.into().0;
2796 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2797 (LuaValue::Float(nb), LuaValue::Float(nc)) => Some((nb, nc)),
2798 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib as f64, ic as f64)),
2799 (LuaValue::Int(ib), LuaValue::Float(nc)) => Some((ib as f64, nc)),
2800 (LuaValue::Float(nb), LuaValue::Int(ic)) => Some((nb, ic as f64)),
2801 _ => None,
2802 }
2803 }
2804 #[inline(always)]
2817 pub fn set_top(&mut self, idx: impl Into<StackIdxConv>) {
2818 let new_top: StackIdx = idx.into().0;
2819 let new_top_u = new_top.0 as usize;
2820 if new_top_u > self.stack.len() {
2821 self.stack.resize_with(new_top_u, StackValue::default);
2822 }
2823 self.top = new_top;
2824 }
2825 #[inline(always)]
2831 pub fn set_top_idx(&mut self, idx: impl Into<StackIdxConv>) {
2832 let new_top: StackIdx = idx.into().0;
2833 self.top = new_top;
2834 }
2835 #[inline(always)]
2838 pub fn dec_top(&mut self) {
2839 if self.top.0 > 0 {
2840 self.top = StackIdx(self.top.0 - 1);
2841 }
2842 }
2843 #[inline(always)]
2844 pub fn pop_n(&mut self, n: usize) {
2845 let cur = self.top.0 as usize;
2846 let new = cur.saturating_sub(n);
2847 self.top = StackIdx(new as u32);
2848 }
2849 #[inline(always)]
2852 pub fn peek_at(&mut self, idx: impl Into<StackIdxConv>) -> LuaValue {
2853 let i: StackIdx = idx.into().0;
2854 match self.stack.get(i.0 as usize) {
2855 Some(slot) => slot.val.clone(),
2856 None => LuaValue::Nil,
2857 }
2858 }
2859 #[inline(always)]
2863 pub fn peek_top(&mut self) -> LuaValue {
2864 if self.top.0 == 0 {
2865 return LuaValue::Nil;
2866 }
2867 self.stack[(self.top.0 - 1) as usize].val.clone()
2868 }
2869 pub fn peek_string_at_top(&mut self) -> GcRef<LuaString> {
2874 match self.peek_top() {
2875 LuaValue::Str(s) => s,
2876 _ => panic!("peek_string_at_top: top of stack is not a string"),
2877 }
2878 }
2879 pub fn stack_at(&mut self, idx: impl Into<StackIdxConv>) -> &mut LuaValue {
2882 let i: StackIdx = idx.into().0;
2883 &mut self.stack[i.0 as usize].val
2884 }
2885 pub fn stack_set_nil(&mut self, idx: impl Into<StackIdxConv>) {
2888 let i: StackIdx = idx.into().0;
2889 let slot = i.0 as usize;
2890 if slot < self.stack.len() {
2891 self.stack[slot].val = LuaValue::Nil;
2892 }
2893 }
2894 pub fn stack_resize(&mut self, size: usize) -> Result<(), LuaError> {
2902 self.stack.resize_with(size, StackValue::default);
2903 Ok(())
2904 }
2905 pub fn stack_available(&mut self) -> usize {
2906 (self.stack_last.0 as usize).saturating_sub(self.top.0 as usize)
2907 }
2908 pub fn check_stack(&mut self, n: i32) -> Result<(), LuaError> {
2909 let free = (self.stack_last.0 as i32) - (self.top.0 as i32);
2910 if free <= n {
2911 self.grow_stack(n, true)?;
2912 }
2913 Ok(())
2914 }
2915 pub fn grow_stack(&mut self, n: i32, raise_error: bool) -> Result<(), LuaError> {
2924 crate::do_::grow_stack(self, n, raise_error).map(|_| ())
2925 }
2926
2927 #[inline(always)]
2928 pub fn get_ci(&self, idx: CallInfoIdx) -> &CallInfo {
2929 &self.call_info[idx.as_usize()]
2930 }
2931 #[inline(always)]
2932 pub fn get_ci_mut(&mut self, idx: CallInfoIdx) -> &mut CallInfo {
2933 &mut self.call_info[idx.as_usize()]
2934 }
2935 #[inline(always)]
2936 pub fn current_call_info(&self) -> &CallInfo {
2937 &self.call_info[self.ci.as_usize()]
2938 }
2939 #[inline(always)]
2940 pub fn current_call_info_mut(&mut self) -> &mut CallInfo {
2941 let i = self.ci.as_usize();
2942 &mut self.call_info[i]
2943 }
2944 #[inline(always)]
2945 pub fn current_ci_idx(&self) -> CallInfoIdx {
2946 self.ci
2947 }
2948 pub fn call_stack_mut(&mut self) -> &mut Vec<CallInfo> {
2949 &mut self.call_info
2950 }
2951 #[inline(always)]
2952 pub fn next_ci(&mut self) -> Result<CallInfoIdx, LuaError> {
2953 let idx = match self.call_info[self.ci.as_usize()].next {
2954 Some(idx) => idx,
2955 None => extend_ci(self),
2956 };
2957 self.call_info[idx.as_usize()].tailcalls = 0;
2958 Ok(idx)
2959 }
2960
2961 #[inline(always)]
2968 pub fn note_lua_tailcall(&mut self, ci: CallInfoIdx) {
2969 if self.global().lua_version == lua_types::LuaVersion::V51 {
2970 self.call_info[ci.as_usize()].tailcalls =
2971 self.call_info[ci.as_usize()].tailcalls.saturating_add(1);
2972 }
2973 }
2974 #[inline(always)]
2975 pub fn prev_ci(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
2976 self.call_info[idx.as_usize()].previous
2977 }
2978 pub fn get_prev_ci(&self, idx: CallInfoIdx) -> Option<&CallInfo> {
2979 self.call_info[idx.as_usize()]
2980 .previous
2981 .map(|p| &self.call_info[p.as_usize()])
2982 }
2983 #[inline(always)]
2984 pub fn is_base_ci(&self, idx: CallInfoIdx) -> bool {
2985 idx.as_usize() == 0
2986 }
2987 #[inline(always)]
2988 pub fn is_current_ci(&self, idx: CallInfoIdx) -> bool {
2989 idx == self.ci
2990 }
2991 pub fn ci_next_func(&self, idx: CallInfoIdx) -> StackIdx {
2992 let next = self.call_info[idx.as_usize()]
2993 .next
2994 .expect("ci_next_func: no next CallInfo");
2995 self.call_info[next.as_usize()].func
2996 }
2997 #[inline(always)]
2998 pub fn ci_top(&self, idx: CallInfoIdx) -> StackIdx {
2999 self.call_info[idx.as_usize()].top
3000 }
3001 #[inline(always)]
3006 pub fn ci_trap(&mut self, idx: CallInfoIdx) -> bool {
3007 self.call_info[idx.as_usize()].trap()
3008 }
3009 #[inline(always)]
3010 pub fn ci_savedpc(&self, idx: CallInfoIdx) -> u32 {
3011 self.call_info[idx.as_usize()].saved_pc()
3012 }
3013 #[inline(always)]
3014 pub fn set_ci_savedpc(&mut self, idx: CallInfoIdx, pc: u32) {
3015 self.call_info[idx.as_usize()].set_saved_pc(pc);
3016 }
3017 #[inline(always)]
3018 pub fn set_ci_previous(&mut self, idx: CallInfoIdx) {
3019 self.ci = self.call_info[idx.as_usize()]
3020 .previous
3021 .expect("set_ci_previous: returning frame has no previous CallInfo");
3022 }
3023 #[inline(always)]
3024 pub fn ci_previous(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
3025 self.call_info[idx.as_usize()].previous
3026 }
3027 #[inline(always)]
3028 pub fn ci_adjust_func(&mut self, idx: CallInfoIdx, delta: i32) {
3029 let ci = &mut self.call_info[idx.as_usize()];
3030 ci.func = StackIdx((ci.func.0 as i32 - delta) as u32);
3031 }
3032 #[inline(always)]
3033 pub fn ci_base(&self, idx: CallInfoIdx) -> StackIdx {
3034 self.call_info[idx.as_usize()].func + 1
3035 }
3036 #[inline(always)]
3037 pub fn ci_is_fresh(&self, idx: CallInfoIdx) -> bool {
3038 (self.call_info[idx.as_usize()].callstatus & CIST_FRESH) != 0
3039 }
3040 #[inline(always)]
3041 pub fn ci_lua_closure(
3042 &self,
3043 idx: CallInfoIdx,
3044 ) -> Option<GcRef<lua_types::closure::LuaLClosure>> {
3045 let func_idx = self.call_info[idx.as_usize()].func;
3046 match self.stack.get(func_idx.0 as usize).map(|slot| slot.val) {
3047 Some(LuaValue::Function(lua_types::closure::LuaClosure::Lua(cl))) => Some(cl),
3048 _ => None,
3049 }
3050 }
3051 #[inline(always)]
3052 pub fn ci_nextraargs(&self, idx: CallInfoIdx) -> i32 {
3053 self.call_info[idx.as_usize()].nextra_args()
3054 }
3055 #[inline(always)]
3056 pub fn ci_nres(&self, idx: CallInfoIdx) -> i32 {
3057 self.call_info[idx.as_usize()].u2.value
3058 }
3059 #[inline(always)]
3060 pub fn ci_nres_set(&mut self, idx: CallInfoIdx, n: i32) {
3061 self.call_info[idx.as_usize()].u2.value = n;
3062 }
3063 #[inline(always)]
3064 pub fn ci_nresults(&self, idx: CallInfoIdx) -> i32 {
3065 self.call_info[idx.as_usize()].nresults as i32
3066 }
3067 pub fn ci_prev_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3068 let pc = self.call_info[idx.as_usize()].saved_pc();
3069 let cl = self
3070 .ci_lua_closure(idx)
3071 .expect("ci_prev_instruction: CallInfo does not hold a Lua closure");
3072 cl.proto.code[(pc - 1) as usize]
3073 }
3074 pub fn ci_prev2_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3075 let pc = self.call_info[idx.as_usize()].saved_pc();
3076 let cl = self
3077 .ci_lua_closure(idx)
3078 .expect("ci_prev2_instruction: CallInfo does not hold a Lua closure");
3079 cl.proto.code[(pc - 2) as usize]
3080 }
3081 pub fn ci_skip_next_instruction(&mut self, idx: CallInfoIdx) {
3082 let pc = self.call_info[idx.as_usize()].saved_pc();
3083 self.call_info[idx.as_usize()].set_saved_pc(pc + 1);
3084 }
3085 pub fn ci_step_pc_back(&mut self, idx: CallInfoIdx) {
3086 let pc = self.call_info[idx.as_usize()].saved_pc();
3087 self.call_info[idx.as_usize()].set_saved_pc(pc - 1);
3088 }
3089 pub fn get_ci_pcrel(&mut self, idx: CallInfoIdx) -> u32 {
3090 self.call_info[idx.as_usize()].saved_pc().saturating_sub(1)
3091 }
3092 pub fn get_ci_u2_funcidx(&mut self, idx: CallInfoIdx) -> i32 {
3093 self.call_info[idx.as_usize()].u2.value
3094 }
3095 pub fn get_ci_u2_nres(&mut self, idx: CallInfoIdx) -> i32 {
3096 self.call_info[idx.as_usize()].u2.value
3097 }
3098 pub fn get_ci_u2_nyield(&mut self, idx: CallInfoIdx) -> i32 {
3099 self.call_info[idx.as_usize()].u2.value
3100 }
3101 pub fn get_ci_vararg_info(&mut self, idx: CallInfoIdx) -> (bool, i32, i32) {
3102 let nextraargs = self.call_info[idx.as_usize()].nextra_args();
3103 match self.ci_lua_closure(idx) {
3104 Some(cl) => (cl.proto.is_vararg, nextraargs, cl.proto.numparams as i32),
3105 None => (false, nextraargs, 0),
3106 }
3107 }
3108 pub fn get_ci_lua_proto_numparams(&mut self, idx: CallInfoIdx) -> u8 {
3109 self.ci_lua_closure(idx)
3110 .map(|cl| cl.proto.numparams)
3111 .unwrap_or(0)
3112 }
3113 pub fn set_ci_u2_nres(&mut self, idx: CallInfoIdx, n: i32) {
3114 self.call_info[idx.as_usize()].u2.value = n;
3115 }
3116 pub fn set_ci_u2_nyield(&mut self, idx: CallInfoIdx, n: i32) {
3117 self.call_info[idx.as_usize()].u2.value = n;
3118 }
3119 pub fn set_ci_transfer_info(&mut self, idx: CallInfoIdx, ftransfer: u16, ntransfer: u16) {
3120 let ci = &mut self.call_info[idx.as_usize()];
3121 ci.u2.ftransfer = ftransfer;
3122 ci.u2.ntransfer = ntransfer;
3123 }
3124 pub fn shrink_ci(&mut self) {
3125 shrink_ci(self)
3126 }
3127 pub fn check_c_stack(&mut self) -> Result<(), LuaError> {
3128 check_c_stack(self)
3129 }
3130
3131 pub fn status(&mut self) -> LuaStatus {
3132 LuaStatus::from_raw(self.status as i32)
3133 }
3134 pub fn errfunc(&mut self) -> isize {
3135 self.errfunc
3136 }
3137 pub fn old_pc(&mut self) -> u32 {
3138 self.oldpc
3139 }
3140 pub fn set_old_pc(&mut self, pc: u32) {
3141 self.oldpc = pc;
3142 }
3143 pub fn set_oldpc(&mut self, pc: u32) {
3144 self.oldpc = pc;
3145 }
3146 pub fn _hook_call_noargs(&mut self) {}
3147 pub fn hook(&self) -> Option<&Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>> {
3148 self.hook.as_ref()
3149 }
3150 pub fn has_hook(&mut self) -> bool {
3151 self.hook.is_some()
3152 }
3153 pub fn hook_count(&mut self) -> i32 {
3154 self.hookcount
3155 }
3156 pub fn set_hook_count(&mut self, n: i32) {
3157 self.hookcount = n;
3158 }
3159 pub fn hook_mask(&self) -> u8 {
3160 self.hookmask
3161 }
3162 pub fn set_hook_mask(&mut self, m: u8) {
3163 self.hookmask = m;
3164 }
3165 pub fn base_hook_count(&self) -> i32 {
3166 self.basehookcount
3167 }
3168 pub fn set_base_hook_count(&mut self, n: i32) {
3169 self.basehookcount = n;
3170 }
3171 pub fn set_hook(&mut self, h: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>) {
3172 self.hook = h;
3173 }
3174 pub fn call_hook_event(&mut self, event: i32, line: i32) -> Result<(), LuaError> {
3193 let saved_ci = self.ci;
3194 let r = crate::do_::hook(self, event, line, 0, 0);
3195 self.ci = saved_ci;
3196 r
3197 }
3198
3199 pub fn registry_value(&self) -> LuaValue {
3200 self.global().l_registry.clone()
3201 }
3202 pub fn registry_get(&self, key: usize) -> LuaValue {
3203 let reg = self.global().l_registry.clone();
3204 match reg {
3205 LuaValue::Table(t) => t.get(&LuaValue::Int(key as i64)),
3206 _ => LuaValue::Nil,
3207 }
3208 }
3209
3210 pub fn new_string(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3211 self.intern_or_create_str(bytes)
3212 }
3213
3214 pub fn new_proto(&mut self) -> GcRef<LuaProto> {
3225 self.mark_gc_check_needed();
3226 GcRef::new(LuaProto::placeholder())
3227 }
3228
3229 pub fn new_lclosure(&mut self, proto: GcRef<LuaProto>, nupvals: usize) -> GcRef<LuaClosureLua> {
3231 self.mark_gc_check_needed();
3232 let mut upvals = Vec::with_capacity(nupvals);
3233 for _ in 0..nupvals {
3234 upvals.push(std::cell::Cell::new(self.new_upval_closed(LuaValue::Nil)));
3235 }
3236 let upvals = upvals.into_boxed_slice();
3237 let closure = GcRef::new(LuaClosureLua { proto, upvals });
3238 closure.account_buffer(closure.buffer_bytes() as isize);
3239 closure
3240 }
3241
3242 pub fn new_upval_closed(&mut self, v: LuaValue) -> GcRef<UpVal> {
3244 self.mark_gc_check_needed();
3245 GcRef::new(UpVal::closed(v))
3246 }
3247
3248 pub fn new_upval_open(&mut self, thread_id: u64, level: StackIdx) -> GcRef<UpVal> {
3250 self.mark_gc_check_needed();
3251 self.legacy_open_upval_touched.set(true);
3252 GcRef::new(UpVal::open(thread_id, level))
3253 }
3254 pub fn intern_or_create_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3261 self.intern_str(bytes)
3262 }
3263 pub fn new_userdata(
3270 &mut self,
3271 size: usize,
3272 nuvalue: usize,
3273 ) -> Result<GcRef<LuaUserData>, LuaError> {
3274 debug_assert!(nuvalue < u16::MAX as usize, "invalid value");
3275 self.mark_gc_check_needed();
3276 let u = GcRef::new(LuaUserData {
3277 data: vec![0u8; size].into_boxed_slice(),
3278 uv: std::cell::RefCell::new(vec![LuaValue::Nil; nuvalue]),
3279 metatable: std::cell::RefCell::new(None),
3280 host_value: std::cell::RefCell::new(None),
3281 });
3282 u.account_buffer(u.buffer_bytes() as isize);
3283 Ok(u)
3284 }
3285
3286 pub fn register_c_function(&mut self, f: LuaCFunction, dedup: bool) -> LuaCFnPtr {
3294 let mut g = self.global_mut();
3295 if dedup {
3296 if let Some(i) = g.c_functions.iter().position(|existing| {
3297 existing
3298 .as_bare()
3299 .is_some_and(|existing| std::ptr::fn_addr_eq(existing, f))
3300 }) {
3301 return i;
3302 }
3303 }
3304 let i = g.c_functions.len();
3305 g.c_functions.push(LuaCallable::bare(f));
3306 i
3307 }
3308
3309 pub fn new_c_closure(&mut self, f: LuaCFunction, n: i32) -> Result<LuaClosure, LuaError> {
3319 if n < 0 || n as i64 > crate::api::MAX_UPVAL as i64 {
3320 return Err(LuaError::runtime(format_args!(
3321 "C closure upvalue count {n} out of range (0..={})",
3322 crate::api::MAX_UPVAL
3323 )));
3324 }
3325 let idx = self.register_c_function(f, n == 0);
3326 if n == 0 {
3327 return Ok(LuaClosure::LightC(idx));
3328 }
3329 self.mark_gc_check_needed();
3330 let cl = LuaClosure::C(GcRef::new(lua_types::closure::LuaCClosure {
3331 func: idx,
3332 upvalues: std::cell::RefCell::new(vec![LuaValue::Nil; n as usize]),
3333 }));
3334 if let LuaClosure::C(ccl) = &cl {
3335 ccl.account_buffer(ccl.buffer_bytes() as isize);
3336 }
3337 Ok(cl)
3338 }
3339 pub fn push_closure(
3340 &mut self,
3341 proto_idx: usize,
3342 ci: CallInfoIdx,
3343 base: StackIdx,
3344 ra: StackIdx,
3345 ) -> Result<(), LuaError> {
3346 let parent_cl = self
3347 .ci_lua_closure(ci)
3348 .expect("push_closure: current frame is not a Lua closure");
3349 let child_proto = parent_cl.proto.p[proto_idx].clone();
3350 let nup = child_proto.upvalues.len();
3351 let mut upvals: Vec<std::cell::Cell<GcRef<UpVal>>> = Vec::with_capacity(nup);
3352 for i in 0..nup {
3353 let desc = &child_proto.upvalues[i];
3354 let uv = if desc.instack {
3355 let level = base + desc.idx as i32;
3356 crate::func::find_upval(self, level)
3357 } else {
3358 parent_cl.upval(desc.idx as usize)
3359 };
3360 upvals.push(std::cell::Cell::new(uv));
3361 }
3362 let cache_enabled = matches!(
3366 self.global().lua_version,
3367 lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
3368 );
3369 if cache_enabled {
3370 if let Some(cached) = child_proto.cache.borrow().as_ref() {
3371 if cached.upvals.len() == nup
3372 && (0..nup).all(|i| GcRef::ptr_eq(&cached.upvals[i].get(), &upvals[i].get()))
3373 {
3374 let reused = cached.clone();
3375 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(reused)));
3376 return Ok(());
3377 }
3378 }
3379 }
3380 self.mark_gc_check_needed();
3384 let new_cl = GcRef::new(LuaClosureLua {
3385 proto: child_proto.clone(),
3386 upvals: upvals.into_boxed_slice(),
3387 });
3388 new_cl.account_buffer(new_cl.buffer_bytes() as isize);
3389 if cache_enabled {
3390 *child_proto.cache.borrow_mut() = Some(new_cl.clone());
3391 }
3392 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(new_cl)));
3393 Ok(())
3394 }
3395 pub fn new_tbc_upval(&mut self, idx: StackIdx) -> Result<(), LuaError> {
3396 crate::func::new_tbc_upval(self, idx)
3397 }
3398
3399 #[inline(always)]
3420 pub fn upvalue_get(&self, cl: &GcRef<LuaClosureLua>, n: usize) -> LuaValue {
3421 let uv = cl.upval(n);
3422 let (thread_id, idx) = match uv.try_open_payload() {
3423 Some(p) => p,
3424 None => return uv.closed_value(),
3425 };
3426 let current = self.cached_thread_id;
3427 if thread_id == current {
3428 return self.stack[idx.0 as usize].val;
3429 }
3430 self.upvalue_get_cross_thread(thread_id, idx)
3431 }
3432
3433 #[cold]
3434 #[inline(never)]
3435 fn upvalue_get_cross_thread(&self, tid: u64, idx: StackIdx) -> LuaValue {
3436 let entry_rc = {
3437 let g = self.global();
3438 g.threads.get(&tid).map(|e| e.state.clone())
3439 };
3440 if let Some(rc) = entry_rc {
3441 if let Ok(home_state) = rc.try_borrow() {
3442 return home_state.get_at(idx);
3443 }
3444 }
3445 let g = self.global();
3446 match g.cross_thread_upvals.get(&(tid, idx)) {
3447 Some(v) => *v,
3448 None => LuaValue::Nil,
3449 }
3450 }
3451 #[inline(always)]
3460 pub fn upvalue_set(
3461 &mut self,
3462 cl: &GcRef<LuaClosureLua>,
3463 n: usize,
3464 val: LuaValue,
3465 ) -> Result<(), LuaError> {
3466 let uv = cl.upval(n);
3467 match uv.try_open_payload() {
3468 Some((thread_id, idx)) => {
3469 let current = self.cached_thread_id;
3470 if thread_id == current {
3471 self.stack[idx.0 as usize].val = val;
3472 } else {
3473 self.upvalue_set_cross_thread(thread_id, idx, val)?;
3474 }
3475 }
3476 None => {
3477 uv.set_closed_value(val);
3478 }
3479 }
3480 if val.is_collectable() {
3481 self.gc_barrier_upval(&uv, &val);
3482 }
3483 Ok(())
3484 }
3485
3486 #[cold]
3487 #[inline(never)]
3488 fn upvalue_set_cross_thread(
3489 &mut self,
3490 tid: u64,
3491 idx: StackIdx,
3492 val: LuaValue,
3493 ) -> Result<(), LuaError> {
3494 let entry_rc = {
3495 let g = self.global();
3496 g.threads.get(&tid).map(|e| e.state.clone())
3497 };
3498 if let Some(rc) = entry_rc {
3499 if let Ok(mut home_state) = rc.try_borrow_mut() {
3500 home_state.set_at(idx, val);
3501 return Ok(());
3502 }
3503 }
3504 let mut g = self.global_mut();
3505 g.cross_thread_upvals.insert((tid, idx), val);
3506 Ok(())
3507 }
3508
3509 pub fn protected_call_raw(
3510 &mut self,
3511 func: StackIdx,
3512 nresults: i32,
3513 errfunc: StackIdx,
3514 ) -> Result<(), LuaError> {
3515 let ef = errfunc.0 as isize;
3516 let status = crate::do_::pcall(self, |s| s.call_no_yield(func, nresults), func, ef);
3517 match status {
3518 LuaStatus::Ok => Ok(()),
3519 LuaStatus::ErrSyntax => {
3520 let err_val = self.get_at(func);
3521 self.set_top(func);
3522 Err(LuaError::Syntax(err_val))
3523 }
3524 LuaStatus::Yield => {
3525 self.set_top(func);
3526 Err(LuaError::Yield)
3527 }
3528 _ => {
3529 let err_val = self.get_at(func);
3530 self.set_top(func);
3531 Err(LuaError::Runtime(err_val))
3532 }
3533 }
3534 }
3535 pub fn protected_parser(
3536 &mut self,
3537 z: crate::zio::ZIO,
3538 name: &[u8],
3539 mode: Option<&[u8]>,
3540 ) -> LuaStatus {
3541 crate::do_::protected_parser(self, z, name, mode)
3542 }
3543 pub fn do_call(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3544 crate::do_::call(self, func, nresults)
3545 }
3546 pub fn do_call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3547 crate::do_::callnoyield(self, func, nresults)
3548 }
3549 pub fn call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3550 crate::do_::callnoyield(self, func, nresults)
3551 }
3552 pub fn call_at(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3553 crate::do_::call(self, func, nresults)
3554 }
3555 #[inline(always)]
3556 pub fn call_known_c_at(&mut self, func: StackIdx, nresults: i32) -> Result<bool, LuaError> {
3557 crate::do_::call_known_c(self, func, nresults)
3558 }
3559 #[inline(always)]
3560 pub fn precall(
3561 &mut self,
3562 func: StackIdx,
3563 nresults: i32,
3564 ) -> Result<Option<CallInfoIdx>, LuaError> {
3565 crate::do_::precall(self, func, nresults)
3566 }
3567 #[inline(always)]
3568 pub fn pretailcall(
3569 &mut self,
3570 ci: CallInfoIdx,
3571 func: StackIdx,
3572 narg1: i32,
3573 delta: i32,
3574 ) -> Result<i32, LuaError> {
3575 crate::do_::pretailcall(self, ci, func, narg1, delta)
3576 }
3577 #[inline(always)]
3578 pub fn poscall<N: TryInto<i32>>(&mut self, ci: CallInfoIdx, nres: N) -> Result<(), LuaError>
3579 where
3580 <N as TryInto<i32>>::Error: std::fmt::Debug,
3581 {
3582 let n = nres.try_into().expect("poscall: nres out of i32 range");
3583 crate::do_::poscall(self, ci, n)
3584 }
3585 pub fn adjust_results(&mut self, nresults: i32) {
3586 const LUA_MULTRET: i32 = -1;
3587 if nresults <= LUA_MULTRET {
3588 let ci_idx = self.ci.as_usize();
3589 if self.call_info[ci_idx].top.0 < self.top.0 {
3590 self.call_info[ci_idx].top = self.top;
3591 }
3592 }
3593 }
3594 pub fn adjust_varargs(
3595 &mut self,
3596 ci: CallInfoIdx,
3597 nfixparams: i32,
3598 cl: &GcRef<lua_types::closure::LuaLClosure>,
3599 ) -> Result<(), LuaError> {
3600 crate::tagmethods::adjust_varargs(self, nfixparams, ci, &cl.0.proto)
3601 }
3602 pub fn get_varargs(&mut self, ci: CallInfoIdx, ra: StackIdx, n: i32) -> Result<i32, LuaError> {
3603 crate::tagmethods::get_varargs(self, ci, ra, n)?;
3604 Ok(0)
3605 }
3606
3607 pub fn close_upvals(&mut self, level: StackIdx) -> Result<(), LuaError> {
3608 crate::func::close_upval(self, level);
3609 Ok(())
3610 }
3611 pub fn close_upvals_status(&mut self, level: StackIdx, _status: i32) -> Result<(), LuaError> {
3612 crate::func::close_upval(self, level);
3613 Ok(())
3614 }
3615 pub fn close_upvals_from_base(&mut self, ci: CallInfoIdx) -> Result<(), LuaError> {
3616 let base = self.ci_base(ci);
3617 crate::func::close_upval(self, base);
3618 Ok(())
3619 }
3620
3621 pub fn arith_op(
3622 &mut self,
3623 op: i32,
3624 p1: &LuaValue,
3625 p2: &LuaValue,
3626 ) -> Result<LuaValue, LuaError> {
3627 let arith_op = match op {
3628 0 => lua_types::arith::ArithOp::Add,
3629 1 => lua_types::arith::ArithOp::Sub,
3630 2 => lua_types::arith::ArithOp::Mul,
3631 3 => lua_types::arith::ArithOp::Mod,
3632 4 => lua_types::arith::ArithOp::Pow,
3633 5 => lua_types::arith::ArithOp::Div,
3634 6 => lua_types::arith::ArithOp::Idiv,
3635 7 => lua_types::arith::ArithOp::Band,
3636 8 => lua_types::arith::ArithOp::Bor,
3637 9 => lua_types::arith::ArithOp::Bxor,
3638 10 => lua_types::arith::ArithOp::Shl,
3639 11 => lua_types::arith::ArithOp::Shr,
3640 12 => lua_types::arith::ArithOp::Unm,
3641 13 => lua_types::arith::ArithOp::Bnot,
3642 _ => return Err(LuaError::runtime(format_args!("invalid arith op {}", op))),
3643 };
3644 let mut res = LuaValue::Nil;
3645 if crate::object::raw_arith(self, arith_op, p1, p2, &mut res)? {
3646 Ok(res)
3647 } else {
3648 Err(LuaError::arith_error(p1, p2, "perform arithmetic on"))
3649 }
3650 }
3651 pub fn concat(&mut self, n: i32) -> Result<(), LuaError> {
3652 crate::vm::concat(self, n)
3653 }
3654 pub fn less_than(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3655 crate::vm::less_than(self, l, r)
3656 }
3657 pub fn less_equal(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3658 crate::vm::less_equal(self, l, r)
3659 }
3660 pub fn equal_obj(&self, _ctx: Option<&LuaValue>, l: &LuaValue, r: &LuaValue) -> bool {
3661 crate::vm::equal_obj(None, l, r).unwrap_or(false)
3662 }
3663 pub fn equal_obj_with_tm(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3664 crate::vm::equal_obj(Some(self), l, r)
3665 }
3666 pub fn obj_len(&mut self, v: &LuaValue) -> Result<LuaValue, LuaError> {
3667 match v {
3668 LuaValue::Table(_) => {
3669 let consult_len_tm =
3672 !matches!(self.global().lua_version, lua_types::LuaVersion::V51);
3673 let tm = if consult_len_tm {
3674 let mt = self.table_metatable(v);
3675 self.fast_tm_table(mt.as_ref(), TagMethod::Len)
3676 } else {
3677 LuaValue::Nil
3678 };
3679 if matches!(tm, LuaValue::Nil) {
3680 let n = self.table_length(v)?;
3681 return Ok(LuaValue::Int(n));
3682 }
3683 self.push(LuaValue::Nil);
3684 let slot = StackIdx(self.top.0 - 1);
3685 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3686 Ok(self.pop())
3687 }
3688 LuaValue::Str(s) => Ok(LuaValue::Int(s.len() as i64)),
3689 other => {
3690 let tm = crate::tagmethods::get_tm_by_obj(
3691 self,
3692 other,
3693 crate::tagmethods::TagMethod::Len,
3694 );
3695 if matches!(tm, LuaValue::Nil) {
3696 let mut msg = b"attempt to get length of a ".to_vec();
3697 msg.extend_from_slice(&self.obj_type_name(other));
3698 msg.extend_from_slice(b" value");
3699 return Err(crate::debug::prefixed_runtime_pub(self, msg));
3700 }
3701 self.push(LuaValue::Nil);
3702 let slot = StackIdx(self.top.0 - 1);
3703 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3704 Ok(self.pop())
3705 }
3706 }
3707 }
3708 pub fn obj_to_string(&mut self, idx: i32) -> Result<GcRef<LuaString>, LuaError> {
3709 let slot: StackIdx = if idx > 0 {
3710 let ci_func = self.current_call_info().func;
3711 ci_func + idx
3712 } else {
3713 debug_assert!(idx != 0, "invalid index");
3714 StackIdx((self.top_idx().0 as i32 + idx) as u32)
3715 };
3716 let val = self.get_at(slot);
3717 match val {
3718 LuaValue::Str(s) => Ok(s),
3719 LuaValue::Int(_) | LuaValue::Float(_) => {
3720 let s = crate::object::num_to_string(self, &val)?;
3721 self.set_at(slot, LuaValue::Str(s.clone()));
3722 Ok(s)
3723 }
3724 _ => Err(LuaError::type_error(&val, "convert to string")),
3725 }
3726 }
3727 pub fn coerce_to_string(&mut self, idx: StackIdx) -> Result<GcRef<LuaString>, LuaError> {
3728 let val = self.get_at(idx);
3729 match val {
3730 LuaValue::Str(s) => Ok(s),
3731 LuaValue::Int(_) | LuaValue::Float(_) => {
3732 let s = crate::object::num_to_string(self, &val)?;
3733 self.set_at(idx, LuaValue::Str(s.clone()));
3734 Ok(s)
3735 }
3736 _ => Err(LuaError::type_error(&val, "convert to string")),
3737 }
3738 }
3739 pub fn str_to_num(&mut self, s: &[u8]) -> Option<(LuaValue, usize)> {
3747 let mut out = LuaValue::Nil;
3748 let float_only =
3749 self.global().lua_version.number_model() == lua_types::NumberModel::FloatOnly;
3750 let sz = if float_only {
3751 crate::object::str2num_float_only(s, &mut out)
3752 } else {
3753 crate::object::str2num(s, &mut out)
3754 };
3755 if sz == 0 {
3756 return None;
3757 }
3758 Some((out, sz))
3759 }
3760
3761 #[inline(always)]
3762 pub fn fast_get(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
3763 let LuaValue::Table(tbl) = t else {
3764 return Ok(None);
3765 };
3766 let v = tbl.get(k);
3767 if matches!(v, LuaValue::Nil) {
3768 Ok(None)
3769 } else {
3770 Ok(Some(v))
3771 }
3772 }
3773 #[inline(always)]
3774 pub fn fast_get_int(&mut self, t: &LuaValue, k: i64) -> Result<Option<LuaValue>, LuaError> {
3775 let LuaValue::Table(tbl) = t else {
3776 return Ok(None);
3777 };
3778 let v = tbl.get_int(k);
3779 if matches!(v, LuaValue::Nil) {
3780 Ok(None)
3781 } else {
3782 Ok(Some(v))
3783 }
3784 }
3785 #[inline(always)]
3786 pub fn fast_get_short_str(
3787 &mut self,
3788 t: &LuaValue,
3789 k: &LuaValue,
3790 ) -> Result<Option<LuaValue>, LuaError> {
3791 let LuaValue::Table(tbl) = t else {
3792 return Ok(None);
3793 };
3794 let LuaValue::Str(s) = k else {
3795 return Ok(None);
3796 };
3797 let v = tbl.get_short_str(s);
3798 if matches!(v, LuaValue::Nil) {
3799 Ok(None)
3800 } else {
3801 Ok(Some(v))
3802 }
3803 }
3804 #[inline(always)]
3805 pub fn fast_tm_table(&mut self, t: Option<&GcRef<LuaTable>>, tm: TagMethod) -> LuaValue {
3806 let Some(mt) = t else {
3807 return LuaValue::Nil;
3808 };
3809 debug_assert!((tm as u8) <= TagMethod::Eq as u8);
3810 let ename = self.global().tmname[tm as usize].clone();
3811 mt.get_short_str(&ename)
3812 }
3813 pub fn fast_tm_ud(&mut self, u: &GcRef<LuaUserData>, tm: TagMethod) -> LuaValue {
3814 let mt = u.metatable();
3816 self.fast_tm_table(mt.as_ref(), tm)
3817 }
3818
3819 pub fn table_get_with_tm(&mut self, t: &LuaValue, k: &LuaValue) -> Result<LuaValue, LuaError> {
3820 if let LuaValue::Table(tbl) = t {
3826 if !tbl.has_metatable() {
3827 return Ok(tbl.get(k));
3828 }
3829 }
3830 if let Some(v) = self.fast_get(t, k)? {
3831 return Ok(v);
3832 }
3833 let res = self.top_idx();
3834 self.push(LuaValue::Nil);
3835 crate::vm::finish_get(self, t.clone(), k.clone(), res, true, None, None)?;
3836 let value = self.get_at(res);
3837 self.pop();
3838 Ok(value)
3839 }
3840 #[inline]
3855 pub fn table_set_with_tm(
3856 &mut self,
3857 t: &LuaValue,
3858 k: LuaValue,
3859 v: LuaValue,
3860 ) -> Result<(), LuaError> {
3861 if let LuaValue::Table(tbl) = t {
3862 if !tbl.has_metatable() {
3863 self.gc_table_barrier_back(tbl, &v);
3864 return self.table_raw_set(t, k, v);
3865 }
3866 }
3867 if self.fast_get(t, &k)?.is_some() {
3868 self.gc_value_barrier_back(t, &v);
3869 return self.table_raw_set(t, k, v);
3870 }
3871 crate::vm::finish_set(self, t.clone(), k, v, true, None, None)
3872 }
3873 #[inline]
3874 pub fn table_raw_set(
3875 &mut self,
3876 t: &LuaValue,
3877 k: LuaValue,
3878 v: LuaValue,
3879 ) -> Result<(), LuaError> {
3880 let LuaValue::Table(tbl) = t else {
3881 return Err(LuaError::type_error(t, "index"));
3882 };
3883 let tbl = tbl.clone();
3884 tbl.raw_set(self, k, v)
3885 }
3886 #[inline]
3887 pub fn table_array_set(
3888 &mut self,
3889 t: &LuaValue,
3890 idx: usize,
3891 v: LuaValue,
3892 ) -> Result<(), LuaError> {
3893 let LuaValue::Table(tbl) = t else {
3894 return Err(LuaError::type_error(t, "index"));
3895 };
3896 let tbl = tbl.clone();
3897 tbl.raw_set_int(self, idx as i64 + 1, v)
3898 }
3899 pub fn table_ensure_array(&mut self, t: &LuaValue, n: usize) -> Result<(), LuaError> {
3900 let LuaValue::Table(tbl) = t else {
3901 return Err(LuaError::type_error(t, "index"));
3902 };
3903 if n > tbl.array_len() {
3904 tbl.resize(self, n, 0)?;
3905 }
3906 Ok(())
3907 }
3908 pub fn table_length(&mut self, t: &LuaValue) -> Result<i64, LuaError> {
3909 let LuaValue::Table(tbl) = t else {
3910 return Err(LuaError::type_error(t, "get length of"));
3911 };
3912 Ok(tbl.getn() as i64)
3913 }
3914 pub fn table_metatable(&mut self, v: &LuaValue) -> Option<GcRef<LuaTable>> {
3915 match v {
3916 LuaValue::Table(t) => t.metatable(),
3917 LuaValue::UserData(u) => u.metatable(),
3918 other => {
3919 let idx = other.base_type() as usize;
3920 self.global().mt[idx].clone()
3921 }
3922 }
3923 }
3924 pub fn table_resize(
3925 &mut self,
3926 t: &GcRef<LuaTable>,
3927 na: usize,
3928 nh: usize,
3929 ) -> Result<(), LuaError> {
3930 self.mark_gc_check_needed();
3931 t.resize(self, na, nh)
3932 }
3933 pub fn table_getn(&self, t: &GcRef<LuaTable>) -> i64 {
3934 let mut i: i64 = 1;
3940 loop {
3941 let v = t.get_int(i);
3942 if matches!(v, LuaValue::Nil) {
3943 return i - 1;
3944 }
3945 i += 1;
3946 }
3947 }
3948
3949 pub fn try_bin_tm(
3950 &mut self,
3951 p1: &LuaValue,
3952 p1_idx: Option<StackIdx>,
3953 p2: &LuaValue,
3954 p2_idx: Option<StackIdx>,
3955 res: StackIdx,
3956 tm: lua_types::tagmethod::TagMethod,
3957 ) -> Result<(), LuaError> {
3958 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3959 crate::tagmethods::try_bin_tm(self, p1, p1_idx, p2, p2_idx, res, event)
3960 }
3961 pub fn try_bin_i_tm(
3962 &mut self,
3963 p1: &LuaValue,
3964 p1_idx: Option<StackIdx>,
3965 imm: i64,
3966 flip: bool,
3967 res: StackIdx,
3968 tm: lua_types::tagmethod::TagMethod,
3969 ) -> Result<(), LuaError> {
3970 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3971 crate::tagmethods::try_bini_tm(self, p1, p1_idx, imm, flip, res, event)
3972 }
3973 pub fn try_bin_assoc_tm(
3974 &mut self,
3975 p1: &LuaValue,
3976 p1_idx: Option<StackIdx>,
3977 p2: &LuaValue,
3978 p2_idx: Option<StackIdx>,
3979 flip: bool,
3980 res: StackIdx,
3981 tm: lua_types::tagmethod::TagMethod,
3982 ) -> Result<(), LuaError> {
3983 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3984 crate::tagmethods::try_bin_assoc_tm(self, p1, p1_idx, p2, p2_idx, flip, res, event)
3985 }
3986 pub fn try_concat_tm(&mut self, _p1: &LuaValue, _p2: &LuaValue) -> Result<(), LuaError> {
3987 crate::tagmethods::try_concat_tm(self)
3988 }
3989 pub fn call_tm(
3990 &mut self,
3991 f: LuaValue,
3992 p1: &LuaValue,
3993 p2: &LuaValue,
3994 p3: &LuaValue,
3995 ) -> Result<(), LuaError> {
3996 crate::tagmethods::call_tm(self, f, p1.clone(), p2.clone(), p3.clone())
3997 }
3998 pub fn call_tm_res(
3999 &mut self,
4000 f: LuaValue,
4001 p1: &LuaValue,
4002 p2: &LuaValue,
4003 res: StackIdx,
4004 ) -> Result<(), LuaError> {
4005 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)
4006 }
4007 pub fn call_tm_res_bool(
4008 &mut self,
4009 f: LuaValue,
4010 p1: &LuaValue,
4011 p2: &LuaValue,
4012 ) -> Result<bool, LuaError> {
4013 let res = self.top_idx();
4014 self.push(LuaValue::Nil);
4015 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)?;
4016 let result = self.get_at(res).clone();
4017 self.pop();
4018 Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)))
4019 }
4020 pub fn call_order_tm(
4021 &mut self,
4022 p1: &LuaValue,
4023 p2: &LuaValue,
4024 tm: lua_types::tagmethod::TagMethod,
4025 ) -> Result<bool, LuaError> {
4026 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4027 crate::tagmethods::call_order_tm(self, p1, p2, event)
4028 }
4029 pub fn call_order_i_tm(
4030 &mut self,
4031 p1: &LuaValue,
4032 v2: i64,
4033 flip: bool,
4034 isfloat: bool,
4035 tm: lua_types::tagmethod::TagMethod,
4036 ) -> Result<bool, LuaError> {
4037 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4038 crate::tagmethods::call_orderi_tm(self, p1, v2 as i32, flip, isfloat, event)
4039 }
4040
4041 #[inline(always)]
4042 pub fn proto_code(
4043 &self,
4044 cl: &GcRef<lua_types::closure::LuaLClosure>,
4045 pc: u32,
4046 ) -> lua_types::opcode::Instruction {
4047 cl.proto.code[pc as usize]
4048 }
4049 #[inline(always)]
4050 pub fn proto_const(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> LuaValue {
4051 cl.proto.k[idx].clone()
4052 }
4053 #[inline(always)]
4059 pub fn proto_const_int(
4060 &self,
4061 cl: &GcRef<lua_types::closure::LuaLClosure>,
4062 idx: usize,
4063 ) -> Option<i64> {
4064 match &cl.proto.k[idx] {
4065 LuaValue::Int(v) => Some(*v),
4066 _ => None,
4067 }
4068 }
4069 #[inline(always)]
4073 pub fn proto_const_num(
4074 &self,
4075 cl: &GcRef<lua_types::closure::LuaLClosure>,
4076 idx: usize,
4077 ) -> Option<f64> {
4078 match &cl.proto.k[idx] {
4079 LuaValue::Float(f) => Some(*f),
4080 LuaValue::Int(v) => Some(*v as f64),
4081 _ => None,
4082 }
4083 }
4084 pub fn get_proto_instr(&self, ci: CallInfoIdx, pc: u32) -> lua_types::opcode::Instruction {
4085 let cl = self
4086 .ci_lua_closure(ci)
4087 .expect("get_proto_instr: CallInfo does not hold a Lua closure");
4088 cl.proto.code[pc as usize]
4089 }
4090 pub fn trace_call(&mut self, _idx: CallInfoIdx) -> Result<bool, LuaError> {
4095 Ok(crate::debug::trace_call(self)? != 0)
4096 }
4097 pub fn trace_exec(&mut self, _idx: CallInfoIdx, pc: u32) -> Result<bool, LuaError> {
4100 Ok(crate::debug::trace_exec(self, pc)? != 0)
4101 }
4102 pub fn hook_call(&mut self, idx: CallInfoIdx) -> Result<(), LuaError> {
4113 if self.global().lua_version == lua_types::LuaVersion::V51 {
4114 let had_tail = self.call_info[idx.as_usize()].callstatus & CIST_TAIL;
4115 self.call_info[idx.as_usize()].callstatus &= !CIST_TAIL;
4116 let r = crate::do_::hookcall(self, idx);
4117 self.call_info[idx.as_usize()].callstatus |= had_tail;
4118 return r;
4119 }
4120 crate::do_::hookcall(self, idx)
4121 }
4122 #[inline(always)]
4123 fn gc_step_flags(&self) -> Option<(bool, bool)> {
4124 let g = self.global();
4125 if !g.is_gc_running() {
4126 return None;
4127 }
4128 let should_collect = g.heap.would_collect();
4129 let has_finalizers = g.finalizers.has_to_be_finalized();
4130 if should_collect || has_finalizers {
4131 Some((should_collect, has_finalizers))
4132 } else {
4133 None
4134 }
4135 }
4136
4137 #[inline(always)]
4138 fn should_check_gc(&mut self) -> bool {
4139 if self.gc_check_needed {
4140 return true;
4141 }
4142 if self.global().finalizers.has_to_be_finalized() {
4143 self.gc_check_needed = true;
4144 return true;
4145 }
4146 false
4147 }
4148
4149 #[inline(always)]
4150 pub(crate) fn mark_gc_check_needed(&mut self) {
4151 self.gc_check_needed = true;
4152 }
4153
4154 pub fn gc_trace_bound(&self) -> usize {
4168 (self.top.0 as usize).min(self.stack.len())
4169 }
4170
4171 pub fn clear_dead_stack_tail(&mut self) {
4178 let bound = self.gc_trace_bound();
4179 for slot in &mut self.stack[bound..] {
4180 slot.val = LuaValue::Nil;
4181 }
4182 }
4183
4184 pub fn gc_clear_dead_stack_tails(&mut self) {
4191 self.clear_dead_stack_tail();
4192 let global = self.global.clone();
4193 let g = global.borrow();
4194 for entry in g.threads.values() {
4195 if let Ok(mut t) = entry.state.try_borrow_mut() {
4196 t.clear_dead_stack_tail();
4197 }
4198 }
4199 }
4200
4201 pub fn gc_pre_collect_clear(&mut self) {
4205 if self.global().heap.would_collect() {
4206 self.gc_clear_dead_stack_tails();
4207 }
4208 }
4209
4210 #[inline(always)]
4211 pub fn gc_check_step(&mut self) {
4212 if !self.allowhook {
4213 return;
4214 }
4215 if !self.should_check_gc() {
4216 return;
4217 }
4218 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4219 self.gc_check_needed = false;
4220 return;
4221 };
4222 if should_collect || has_finalizers {
4223 if should_collect {
4224 self.gc_clear_dead_stack_tails();
4225 self.gc().check_step();
4226 }
4227 crate::api::run_pending_finalizers(self);
4228 self.gc_check_needed = true;
4229 }
4230 let should_keep_checking = {
4231 let g = self.global();
4232 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4233 };
4234 self.gc_check_needed = should_keep_checking;
4235 }
4236 #[inline(always)]
4237 pub fn gc_cond_step(&mut self) {
4238 if !self.allowhook {
4239 return;
4240 }
4241 if !self.should_check_gc() {
4242 return;
4243 }
4244 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4245 self.gc_check_needed = false;
4246 return;
4247 };
4248 if should_collect || has_finalizers {
4249 if should_collect {
4250 self.gc_clear_dead_stack_tails();
4251 self.gc().check_step();
4252 }
4253 crate::api::run_pending_finalizers(self);
4254 self.gc_check_needed = true;
4255 }
4256 let should_keep_checking = {
4257 let g = self.global();
4258 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4259 };
4260 self.gc_check_needed = should_keep_checking;
4261 }
4262 pub fn gc_barrier_back(&mut self, t: &dyn std::any::Any, v: &LuaValue) {
4263 self.gc().barrier_back(t, v);
4264 }
4265 #[inline(always)]
4266 pub fn gc_value_barrier_back(&mut self, t: &LuaValue, v: &LuaValue) {
4267 if !v.is_collectable() {
4268 return;
4269 }
4270 if let LuaValue::Table(tbl) = t {
4271 self.gc_table_barrier_back(tbl, v);
4272 } else {
4273 self.gc_barrier_back(t, v);
4274 }
4275 }
4276 #[inline(always)]
4277 pub fn gc_table_barrier_back(&mut self, t: &GcRef<LuaTable>, v: &LuaValue) {
4278 if !v.is_collectable() {
4279 return;
4280 }
4281 self.gc().table_barrier_back(t, v);
4282 }
4283 pub fn gc_barrier_upval(&mut self, uv: &GcRef<UpVal>, v: &LuaValue) {
4284 self.gc().barrier(uv, v);
4285 }
4286 pub fn is_main_thread(&mut self) -> bool {
4290 let g = self.global();
4291 g.current_thread_id == g.main_thread_id
4292 }
4293 pub fn obj_type_name<'v>(&self, v: &'v LuaValue) -> std::borrow::Cow<'static, [u8]> {
4294 let honors_name = self.global().lua_version.honors_name_metafield();
4295 match v {
4296 LuaValue::LightUserData(_) => std::borrow::Cow::Borrowed(b"light userdata"),
4297 LuaValue::Table(t) => {
4298 if honors_name {
4299 if let Some(mt) = t.metatable() {
4300 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4301 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4302 }
4303 }
4304 }
4305 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4306 }
4307 LuaValue::UserData(u) => {
4308 if honors_name {
4309 if let Some(mt) = u.metatable() {
4310 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4311 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4312 }
4313 }
4314 }
4315 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4316 }
4317 _ => std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type())),
4318 }
4319 }
4320
4321 pub fn full_type_name(&mut self, v: &LuaValue) -> Result<Vec<u8>, LuaError> {
4322 crate::tagmethods::obj_type_name(self, v)
4323 }
4324 pub fn emit_warning(&mut self, _msg: &[u8], _to_cont: bool) {
4325 warning(self, _msg, _to_cont)
4326 }
4327}
4328
4329pub struct GcHandle<'a> {
4333 _state: &'a mut LuaState,
4334}
4335
4336struct CollectRoots<'a> {
4343 global: &'a GlobalState,
4344 thread: &'a LuaState,
4345}
4346
4347#[derive(Clone, Copy)]
4348enum HeapCollectMode {
4349 Full,
4350 Step,
4351 Minor,
4352}
4353
4354impl<'a> lua_gc::Trace for CollectRoots<'a> {
4355 fn trace(&self, m: &mut lua_gc::Marker) {
4356 self.global.trace(m);
4357 self.thread.trace(m);
4358 }
4359}
4360
4361#[derive(Clone, Copy)]
4362enum BarrierKind {
4363 Forward,
4364 Backward,
4365}
4366
4367fn barrier_lua_value<P>(
4368 heap: &lua_gc::Heap,
4369 parent: GcRef<P>,
4370 child: &LuaValue,
4371 generational: bool,
4372 kind: BarrierKind,
4373) where
4374 P: lua_gc::Trace + 'static,
4375{
4376 if !child.is_collectable() {
4377 return;
4378 }
4379 if generational && matches!(kind, BarrierKind::Backward) {
4380 heap.generational_backward_barrier(parent.0);
4381 }
4382 match child {
4383 LuaValue::Str(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4384 LuaValue::Table(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4385 LuaValue::Function(LuaClosure::Lua(c)) => {
4386 barrier_gc_child(heap, parent, *c, generational, kind)
4387 }
4388 LuaValue::Function(LuaClosure::C(c)) => {
4389 barrier_gc_child(heap, parent, *c, generational, kind)
4390 }
4391 LuaValue::UserData(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4392 LuaValue::Thread(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4393 LuaValue::Nil
4394 | LuaValue::Bool(_)
4395 | LuaValue::Int(_)
4396 | LuaValue::Float(_)
4397 | LuaValue::LightUserData(_)
4398 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4399 }
4400}
4401
4402fn barrier_gc_child<P, C>(
4403 heap: &lua_gc::Heap,
4404 parent: GcRef<P>,
4405 child: GcRef<C>,
4406 generational: bool,
4407 kind: BarrierKind,
4408) where
4409 P: lua_gc::Trace + 'static,
4410 C: lua_gc::Trace + 'static,
4411{
4412 if generational && matches!(kind, BarrierKind::Forward) {
4413 heap.generational_forward_barrier(parent.0, child.0);
4414 } else if matches!(kind, BarrierKind::Backward) {
4415 heap.barrier_back(parent.0, child.0);
4416 } else {
4417 heap.barrier(parent.0, child.0);
4418 }
4419}
4420
4421fn barrier_child_any<P>(
4422 heap: &lua_gc::Heap,
4423 parent: GcRef<P>,
4424 child: &dyn std::any::Any,
4425 generational: bool,
4426 kind: BarrierKind,
4427) where
4428 P: lua_gc::Trace + 'static,
4429{
4430 if let Some(v) = child.downcast_ref::<LuaValue>() {
4431 barrier_lua_value(heap, parent, v, generational, kind);
4432 } else if let Some(c) = child.downcast_ref::<GcRef<LuaString>>() {
4433 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4434 } else if let Some(c) = child.downcast_ref::<GcRef<LuaTable>>() {
4435 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4436 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureLua>>() {
4437 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4438 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureC>>() {
4439 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4440 } else if let Some(c) = child.downcast_ref::<GcRef<LuaUserData>>() {
4441 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4442 } else if let Some(c) = child.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4443 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4444 } else if let Some(c) = child.downcast_ref::<GcRef<LuaProto>>() {
4445 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4446 } else if let Some(c) = child.downcast_ref::<GcRef<UpVal>>() {
4447 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4448 }
4449}
4450
4451fn barrier_any(
4452 heap: &lua_gc::Heap,
4453 parent: &dyn std::any::Any,
4454 child: &dyn std::any::Any,
4455 generational: bool,
4456 kind: BarrierKind,
4457) {
4458 if let Some(v) = parent.downcast_ref::<LuaValue>() {
4459 match v {
4460 LuaValue::Str(p) => barrier_child_any(heap, *p, child, generational, kind),
4461 LuaValue::Table(p) => barrier_child_any(heap, *p, child, generational, kind),
4462 LuaValue::Function(LuaClosure::Lua(p)) => {
4463 barrier_child_any(heap, *p, child, generational, kind)
4464 }
4465 LuaValue::Function(LuaClosure::C(p)) => {
4466 barrier_child_any(heap, *p, child, generational, kind)
4467 }
4468 LuaValue::UserData(p) => barrier_child_any(heap, *p, child, generational, kind),
4469 LuaValue::Thread(p) => barrier_child_any(heap, *p, child, generational, kind),
4470 LuaValue::Nil
4471 | LuaValue::Bool(_)
4472 | LuaValue::Int(_)
4473 | LuaValue::Float(_)
4474 | LuaValue::LightUserData(_)
4475 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4476 }
4477 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaString>>() {
4478 barrier_child_any(heap, p.clone(), child, generational, kind);
4479 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaTable>>() {
4480 barrier_child_any(heap, p.clone(), child, generational, kind);
4481 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureLua>>() {
4482 barrier_child_any(heap, p.clone(), child, generational, kind);
4483 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureC>>() {
4484 barrier_child_any(heap, p.clone(), child, generational, kind);
4485 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaUserData>>() {
4486 barrier_child_any(heap, p.clone(), child, generational, kind);
4487 } else if let Some(p) = parent.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4488 barrier_child_any(heap, p.clone(), child, generational, kind);
4489 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaProto>>() {
4490 barrier_child_any(heap, p.clone(), child, generational, kind);
4491 } else if let Some(p) = parent.downcast_ref::<GcRef<UpVal>>() {
4492 barrier_child_any(heap, p.clone(), child, generational, kind);
4493 }
4494}
4495
4496fn trace_reachable_threads(
4508 global: &GlobalState,
4509 _current_thread_id: u64,
4510 marker: &mut lua_gc::Marker,
4511) {
4512 use lua_gc::Trace;
4513
4514 #[cfg(debug_assertions)]
4515 let mut uncovered_borrowed: Vec<u64> = Vec::new();
4516
4517 loop {
4518 let visited_before = marker.visited_count();
4519 for (id, entry) in global.threads.iter() {
4520 if thread_entry_marked_alive(marker, *id, entry) {
4521 match entry.state.try_borrow() {
4522 Ok(thread) => thread.trace(marker),
4523 Err(_) => {
4524 #[cfg(debug_assertions)]
4525 if *id != _current_thread_id && !uncovered_borrowed.contains(id) {
4526 uncovered_borrowed.push(*id);
4527 }
4528 }
4529 }
4530 }
4531 }
4532 marker.drain_gray_queue();
4533 if marker.visited_count() == visited_before {
4534 break;
4535 }
4536 }
4537
4538 remark_legacy_open_upvalues(global, marker);
4539
4540 #[cfg(debug_assertions)]
4541 debug_assert!(
4542 uncovered_borrowed.len() <= global.suspended_parent_stacks.len(),
4543 "GC root loss: {} marked-alive coroutine(s) (ids {:?}) were mutably \
4544 borrowed at collect time with only {} parent snapshot(s) covering \
4545 them — their stacks were NOT traced this cycle, so anything only \
4546 reachable from them will be swept (issue #140 bug-A class). A borrow \
4547 of a coroutine's state must not be held across an allocation \
4548 checkpoint without pushing a parent GC snapshot.",
4549 uncovered_borrowed.len(),
4550 uncovered_borrowed,
4551 global.suspended_parent_stacks.len()
4552 );
4553}
4554
4555fn remark_legacy_open_upvalues(global: &GlobalState, marker: &mut lua_gc::Marker) {
4575 use lua_gc::Trace;
4576
4577 let legacy = matches!(
4578 global.lua_version,
4579 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
4580 );
4581 if !legacy {
4582 return;
4583 }
4584
4585 let mut remarked_any = false;
4586 for (id, entry) in global.threads.iter() {
4587 if entry.value.id != *id {
4588 continue;
4589 }
4590 if thread_entry_marked_alive(marker, *id, entry) {
4591 continue;
4592 }
4593 let Ok(thread) = entry.state.try_borrow() else {
4594 continue;
4595 };
4596 if thread.openupval.is_empty() || !thread.legacy_open_upval_touched.get() {
4597 continue;
4598 }
4599 thread.legacy_open_upval_touched.set(false);
4600 for uv in thread.openupval.iter() {
4601 let Some((_tid, idx)) = uv.try_open_payload() else {
4602 continue;
4603 };
4604 let slot = idx.0 as usize;
4605 if slot < thread.stack.len() {
4606 thread.stack[slot].val.trace(marker);
4607 remarked_any = true;
4608 }
4609 }
4610 }
4611
4612 if !remarked_any {
4613 return;
4614 }
4615 marker.drain_gray_queue();
4616
4617 loop {
4618 let visited_before = marker.visited_count();
4619 for (id, entry) in global.threads.iter() {
4620 if thread_entry_marked_alive(marker, *id, entry) {
4621 if let Ok(thread) = entry.state.try_borrow() {
4622 thread.trace(marker);
4623 }
4624 }
4625 }
4626 marker.drain_gray_queue();
4627 if marker.visited_count() == visited_before {
4628 break;
4629 }
4630 }
4631}
4632
4633fn thread_entry_marked_alive(
4634 marker: &lua_gc::Marker,
4635 id: u64,
4636 entry: &ThreadRegistryEntry,
4637) -> bool {
4638 marker.is_marked_or_old(entry.value.0) && entry.value.id == id
4639}
4640
4641fn lua_value_marked_or_old(marker: &lua_gc::Marker, value: &LuaValue) -> bool {
4642 match value {
4643 LuaValue::Str(v) => marker.is_marked_or_old(v.0),
4644 LuaValue::Table(v) => marker.is_marked_or_old(v.0),
4645 LuaValue::Function(LuaClosure::Lua(v)) => marker.is_marked_or_old(v.0),
4646 LuaValue::Function(LuaClosure::C(v)) => marker.is_marked_or_old(v.0),
4647 LuaValue::UserData(v) => marker.is_marked_or_old(v.0),
4648 LuaValue::Thread(v) => marker.is_marked_or_old(v.0),
4649 LuaValue::Nil
4650 | LuaValue::Bool(_)
4651 | LuaValue::Int(_)
4652 | LuaValue::Float(_)
4653 | LuaValue::LightUserData(_)
4654 | LuaValue::Function(LuaClosure::LightC(_)) => true,
4655 }
4656}
4657
4658fn finalizer_marked_or_old(marker: &lua_gc::Marker, object: &FinalizerObject) -> bool {
4659 match object {
4660 FinalizerObject::Table(t) => marker.is_marked_or_old(t.0),
4661 FinalizerObject::UserData(u) => marker.is_marked_or_old(u.0),
4662 }
4663}
4664
4665fn weak_snapshot_tables<'a>(
4666 snapshot: &'a lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>>,
4667) -> impl Iterator<Item = &'a GcRef<LuaTable>> {
4668 snapshot
4669 .weak_values
4670 .iter()
4671 .chain(snapshot.ephemeron.iter())
4672 .chain(snapshot.all_weak.iter())
4673}
4674
4675fn close_open_upvalues_for_unreachable_threads(global: &GlobalState, marker: &mut lua_gc::Marker) {
4676 use lua_gc::Trace;
4677
4678 let mut closed_values = Vec::<LuaValue>::new();
4679 for (id, entry) in global.threads.iter() {
4680 if entry.value.id != *id {
4681 continue;
4682 }
4683 if thread_entry_marked_alive(marker, *id, entry) {
4684 continue;
4685 }
4686 let Ok(thread) = entry.state.try_borrow() else {
4687 continue;
4688 };
4689 for uv in thread.openupval.iter() {
4690 if !marker.is_visited(uv.identity()) {
4691 continue;
4692 }
4693 let Some((thread_id, idx)) = uv.try_open_payload() else {
4694 continue;
4695 };
4696 if thread_id != *id {
4697 continue;
4698 }
4699 let value = thread.get_at(idx);
4700 uv.close_with(value.clone());
4701 closed_values.push(value);
4702 }
4703 }
4704 for value in closed_values {
4705 value.trace(marker);
4706 }
4707 marker.drain_gray_queue();
4708}
4709
4710fn record_dead_interned_strings(
4717 global: &GlobalState,
4718 marker: &lua_gc::Marker,
4719 dead_pairs: &std::cell::RefCell<Vec<(u32, usize)>>,
4720) {
4721 let mut dead = dead_pairs.borrow_mut();
4722 for s in global.interned_lt.iter() {
4723 let id = s.identity();
4724 if !marker.is_visited(id) {
4725 dead.push((s.hash(), id));
4726 }
4727 }
4728}
4729
4730fn remove_dead_interned_strings(global: &mut GlobalState, dead_pairs: Vec<(u32, usize)>) {
4742 for (hash, id) in dead_pairs {
4743 global.interned_lt.remove(hash, id);
4744 }
4745 global.interned_lt.shrink_if_sparse();
4746}
4747
4748impl<'a> GcHandle<'a> {
4749 pub fn check_step(&self) {
4754 if !self._state.global().is_gc_running() {
4755 return;
4756 }
4757 if self._state.global().is_gen_mode() {
4758 let should_collect = {
4759 let g = self._state.global();
4760 g.heap.would_collect() || g.gc_debt() > 0
4761 };
4762 if should_collect {
4763 self.generational_step();
4764 }
4765 } else {
4766 self.collect_via_heap(false);
4767 }
4768 }
4769
4770 pub fn full_collect(&self) {
4771 if self._state.global().is_gen_mode() {
4772 self.fullgen();
4773 } else {
4774 self.collect_via_heap(true);
4775 }
4776 }
4777
4778 fn negative_debt(bytes: usize) -> isize {
4779 -(bytes.min(isize::MAX as usize) as isize)
4780 }
4781
4782 fn set_minor_debt(&self) {
4783 let mut g = self._state.global_mut();
4784 let total = g.total_bytes();
4785 let growth = (total / 100).saturating_mul(g.genminormul as usize);
4786 g.heap
4787 .set_threshold_bytes(total.saturating_add(growth.max(1)));
4788 set_debt(&mut *g, Self::negative_debt(growth));
4789 }
4790
4791 fn set_pause_debt(&self) {
4792 let mut g = self._state.global_mut();
4793 let total = g.total_bytes();
4794 let pause = g.gc_pause_param().max(0) as usize;
4795 let threshold = g.gc_estimate.max(1).saturating_mul(pause) / 100;
4796 let debt = if threshold > total {
4797 Self::negative_debt(threshold - total)
4798 } else {
4799 0
4800 };
4801 let heap_threshold = if threshold > total {
4802 threshold
4803 } else {
4804 total.saturating_add(1)
4805 };
4806 g.heap.set_threshold_bytes(heap_threshold);
4807 set_debt(&mut *g, debt);
4808 }
4809
4810 fn enter_incremental_mode(&self) {
4811 let mut g = self._state.global_mut();
4812 g.heap.reset_all_ages();
4813 g.finalizers.reset_generation_boundaries();
4814 g.gckind = GcKind::Incremental as u8;
4815 g.lastatomic = 0;
4816 }
4817
4818 fn enter_generational_mode(&self) -> usize {
4819 self.collect_via_heap_mode(HeapCollectMode::Full);
4820 let numobjs = {
4821 let mut g = self._state.global_mut();
4822 g.heap.promote_all_to_old();
4823 g.finalizers.promote_all_pending_to_old();
4824 g.heap.allgc_count()
4825 };
4826 let total = self._state.global().total_bytes();
4827 {
4828 let mut g = self._state.global_mut();
4829 g.gckind = GcKind::Generational as u8;
4830 g.lastatomic = 0;
4831 g.gc_estimate = total;
4832 }
4833 self.set_minor_debt();
4834 numobjs
4835 }
4836
4837 fn fullgen(&self) -> usize {
4838 self.enter_incremental_mode();
4839 self.enter_generational_mode()
4840 }
4841
4842 fn stepgenfull(&self, lastatomic: usize) {
4843 if self._state.global().gckind == GcKind::Generational as u8 {
4844 self.enter_incremental_mode();
4845 }
4846 self.collect_via_heap_mode(HeapCollectMode::Full);
4847 let newatomic = self._state.global().heap.allgc_count().max(1);
4848 if newatomic < lastatomic.saturating_add(lastatomic >> 3) {
4849 {
4850 let mut g = self._state.global_mut();
4851 g.heap.promote_all_to_old();
4852 g.finalizers.promote_all_pending_to_old();
4853 }
4854 let total = self._state.global().total_bytes();
4855 {
4856 let mut g = self._state.global_mut();
4857 g.gckind = GcKind::Generational as u8;
4858 g.lastatomic = 0;
4859 g.gc_estimate = total;
4860 }
4861 self.set_minor_debt();
4862 } else {
4863 {
4864 let mut g = self._state.global_mut();
4865 g.heap.reset_all_ages();
4866 g.finalizers.reset_generation_boundaries();
4867 }
4868 let total = self._state.global().total_bytes();
4869 {
4870 let mut g = self._state.global_mut();
4871 g.gckind = GcKind::Incremental as u8;
4872 g.lastatomic = newatomic;
4873 g.gc_estimate = total;
4874 }
4875 self.set_pause_debt();
4876 }
4877 }
4878
4879 fn collect_via_heap(&self, force: bool) {
4888 self.collect_via_heap_mode(if force {
4889 HeapCollectMode::Full
4890 } else {
4891 HeapCollectMode::Step
4892 });
4893 }
4894
4895 fn collect_via_heap_mode(&self, mode: HeapCollectMode) {
4896 use lua_gc::Trace;
4897 let state_ref: &LuaState = &*self._state;
4898
4899 if matches!(mode, HeapCollectMode::Step) {
4905 let g = state_ref.global.borrow();
4906 if !g.heap.would_collect() {
4907 return;
4908 }
4909 }
4910
4911 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
4915 let mut g = state_ref.global.borrow_mut();
4916 g.weak_tables_registry.live_snapshot_by_kind()
4917 };
4918
4919 let weak_table_capacity = weak_tables_snapshot.len();
4924 let (pending_snapshot, thread_capacity, _interned_capacity): (
4925 Vec<FinalizerObject>,
4926 usize,
4927 usize,
4928 ) = {
4929 let g = state_ref.global.borrow();
4930 let pending = match mode {
4931 HeapCollectMode::Minor => g.finalizers.pending_minor_snapshot(),
4932 HeapCollectMode::Full | HeapCollectMode::Step => g.finalizers.pending_snapshot(),
4933 };
4934 (pending, g.threads.len(), g.interned_lt.len())
4935 };
4936 let finalizer_capacity = pending_snapshot.len();
4937
4938 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4939 std::cell::RefCell::new(std::collections::HashSet::new());
4940 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
4941 std::cell::RefCell::new(Vec::new());
4942 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
4943 std::cell::RefCell::new(std::collections::HashSet::new());
4944 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4945 std::cell::RefCell::new(std::collections::HashSet::new());
4946 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
4947 let collect_ran = std::cell::Cell::new(false);
4948
4949 {
4950 let global = state_ref.global.borrow();
4951 global.heap.unpause();
4952 let roots = CollectRoots {
4953 global: &*global,
4954 thread: state_ref,
4955 };
4956 let hook = |marker: &mut lua_gc::Marker| {
4957 collect_ran.set(true);
4958 alive_ids.borrow_mut().reserve(weak_table_capacity);
4959 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
4960 alive_thread_ids.borrow_mut().reserve(thread_capacity);
4961 trace_reachable_threads(&*global, global.current_thread_id, marker);
4962 close_open_upvalues_for_unreachable_threads(&*global, marker);
4963 let legacy_weak_key_values =
4970 matches!(global.lua_version, lua_types::LuaVersion::V51);
4971 loop {
4972 let visited_before = marker.visited_count();
4973 for t in &weak_tables_snapshot.ephemeron {
4974 if !marker.is_marked_or_old(t.0) {
4975 continue;
4976 }
4977 if legacy_weak_key_values {
4978 let mut to_mark = Vec::new();
4979 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
4980 for v in &to_mark {
4981 v.trace(marker);
4982 }
4983 } else {
4984 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
4985 lua_value_marked_or_old(marker, v)
4986 });
4987 for v in &to_mark {
4988 v.trace(marker);
4989 }
4990 }
4991 }
4992 marker.drain_gray_queue();
4993 if marker.visited_count() == visited_before {
4994 break;
4995 }
4996 }
4997 for t in weak_tables_snapshot
5017 .weak_values
5018 .iter()
5019 .chain(weak_tables_snapshot.all_weak.iter())
5020 {
5021 let to_mark = t.prune_weak_dead_with_value(
5022 &|_| true,
5023 &|v| lua_value_marked_or_old(marker, v),
5024 );
5025 if marker.is_marked_or_old(t.0) {
5026 for v in &to_mark {
5027 v.trace(marker);
5028 }
5029 }
5030 }
5031 marker.drain_gray_queue();
5032 for pf in &pending_snapshot {
5033 if !finalizer_marked_or_old(marker, pf) {
5034 pf.mark(marker);
5035 newly_unreachable.borrow_mut().push(pf.clone());
5036 }
5037 }
5038 marker.drain_gray_queue();
5039 loop {
5040 let visited_before = marker.visited_count();
5041 for t in &weak_tables_snapshot.ephemeron {
5042 if !marker.is_marked_or_old(t.0) {
5043 continue;
5044 }
5045 if legacy_weak_key_values {
5046 let mut to_mark = Vec::new();
5047 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5048 for v in &to_mark {
5049 v.trace(marker);
5050 }
5051 } else {
5052 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
5053 lua_value_marked_or_old(marker, v)
5054 });
5055 for v in &to_mark {
5056 v.trace(marker);
5057 }
5058 }
5059 }
5060 marker.drain_gray_queue();
5061 if marker.visited_count() == visited_before {
5062 break;
5063 }
5064 }
5065 for t in weak_tables_snapshot
5078 .ephemeron
5079 .iter()
5080 .chain(weak_tables_snapshot.all_weak.iter())
5081 {
5082 if !marker.is_marked_or_old(t.0) {
5083 continue;
5084 }
5085 let to_mark = t.prune_weak_dead_with_value(
5086 &|v| lua_value_marked_or_old(marker, v),
5087 &|_| true,
5088 );
5089 for v in &to_mark {
5090 v.trace(marker);
5091 }
5092 }
5093 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5094 if marker.is_marked_or_old(t.0) {
5095 alive_ids.borrow_mut().insert(t.identity());
5096 }
5097 }
5098 marker.drain_gray_queue();
5099 {
5100 let mut alive = alive_thread_ids.borrow_mut();
5101 for (id, entry) in global.threads.iter() {
5102 if thread_entry_marked_alive(marker, *id, entry) {
5103 alive.insert(*id);
5104 }
5105 }
5106 }
5107 {
5108 let mut alive = alive_closure_env_ids.borrow_mut();
5109 for id in global.closure_envs.keys() {
5110 if marker.is_visited(*id) {
5111 alive.insert(*id);
5112 }
5113 }
5114 }
5115 record_dead_interned_strings(&*global, marker, &dead_interned);
5116 };
5117 match mode {
5118 HeapCollectMode::Full => global.heap.full_collect_with_post_mark(&roots, hook),
5119 HeapCollectMode::Step => global.heap.step_with_post_mark(&roots, hook),
5120 HeapCollectMode::Minor => global.heap.minor_collect_with_post_mark(&roots, hook),
5121 }
5122 }
5123
5124 if !collect_ran.get() {
5125 return;
5126 }
5127
5128 let alive_set = alive_ids.into_inner();
5132 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5133 let alive_thread_ids = alive_thread_ids.into_inner();
5134 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5135 let dead_interned = dead_interned.into_inner();
5136 let mut g = state_ref.global.borrow_mut();
5137 remove_dead_interned_strings(&mut *g, dead_interned);
5138 g.weak_tables_registry.retain_identities(&alive_set);
5139 let main_thread_id = g.main_thread_id;
5140 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5141 g.cross_thread_upvals
5142 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5143 g.thread_globals
5147 .retain(|id, _| alive_thread_ids.contains(id));
5148 g.closure_envs
5149 .retain(|id, _| alive_closure_env_ids.contains(id));
5150 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5154 for object in &promoted {
5155 if let Some(ptr) = object.heap_ptr() {
5156 g.heap.move_finobj_to_tobefnz(ptr);
5157 }
5158 }
5159 if matches!(mode, HeapCollectMode::Minor) {
5160 g.finalizers.finish_minor_collection();
5161 }
5162 }
5163
5164 pub fn generational_step(&self) -> bool {
5166 self.generational_step_with_major(true)
5167 }
5168
5169 pub fn generational_step_minor_only(&self) -> bool {
5175 self.generational_step_with_major(false)
5176 }
5177
5178 fn generational_step_with_major(&self, allow_major: bool) -> bool {
5179 let (lastatomic, majorbase, majorinc, should_major) = {
5180 let g = self._state.global();
5181 let majorbase = if g.gc_estimate == 0 {
5182 g.total_bytes()
5183 } else {
5184 g.gc_estimate
5185 };
5186 let majormul = g.gc_genmajormul_param().max(0) as usize;
5187 let majorinc = (majorbase / 100).saturating_mul(majormul);
5188 let debt_due = g.gc_debt() > 0 || g.heap.would_collect();
5189 let should_major =
5190 allow_major && debt_due && g.total_bytes() > majorbase.saturating_add(majorinc);
5191 (g.lastatomic, majorbase, majorinc, should_major)
5192 };
5193
5194 if lastatomic != 0 {
5195 self.stepgenfull(lastatomic);
5196 debug_assert!(self._state.global().is_gen_mode());
5197 return true;
5198 }
5199
5200 if should_major {
5201 let numobjs = self.fullgen();
5202 let after = self._state.global().total_bytes();
5203 if after < majorbase.saturating_add(majorinc / 2) {
5204 self.set_minor_debt();
5205 } else {
5206 {
5207 let mut g = self._state.global_mut();
5208 g.lastatomic = numobjs.max(1);
5209 }
5210 self.set_pause_debt();
5211 }
5212 } else {
5213 self.collect_via_heap_mode(HeapCollectMode::Minor);
5214 self.set_minor_debt();
5215 self._state.global_mut().gc_estimate = majorbase;
5216 }
5217
5218 debug_assert!(self._state.global().is_gen_mode());
5219 true
5220 }
5221
5222 pub fn step(&self) { }
5226
5227 pub fn incremental_step(&self, work_units: isize) -> bool {
5240 self.incremental_step_to_state(work_units, None)
5241 }
5242
5243 pub fn run_until_gc_state_for_test(&self, target: lua_gc::GcState) -> bool {
5248 self.incremental_step_to_state(isize::MAX / 4, Some(target));
5249 self._state.global().heap.gc_state() == target
5250 }
5251
5252 fn incremental_step_to_state(
5253 &self,
5254 work_units: isize,
5255 target: Option<lua_gc::GcState>,
5256 ) -> bool {
5257 use lua_gc::{StepBudget, StepOutcome, Trace};
5258 let state_ref: &LuaState = &*self._state;
5259
5260 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5261 let mut g = state_ref.global.borrow_mut();
5262 g.weak_tables_registry.live_snapshot_by_kind()
5263 };
5264
5265 let weak_table_capacity = weak_tables_snapshot.len();
5266 let (pending_snapshot, thread_capacity, _interned_capacity): (
5267 Vec<FinalizerObject>,
5268 usize,
5269 usize,
5270 ) = {
5271 let g = state_ref.global.borrow();
5272 (
5273 g.finalizers.pending_snapshot(),
5274 g.threads.len(),
5275 g.interned_lt.len(),
5276 )
5277 };
5278 let finalizer_capacity = pending_snapshot.len();
5279
5280 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5281 std::cell::RefCell::new(std::collections::HashSet::new());
5282 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
5283 std::cell::RefCell::new(Vec::new());
5284 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
5285 std::cell::RefCell::new(std::collections::HashSet::new());
5286 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5287 std::cell::RefCell::new(std::collections::HashSet::new());
5288 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5289 let atomic_ran = std::cell::Cell::new(false);
5290
5291 let stop_target = {
5292 let g = state_ref.global.borrow();
5293 match (target, g.heap.gc_state()) {
5294 (Some(target), _) => Some(target),
5295 (None, lua_gc::GcState::CallFin) => None,
5296 (None, _) => Some(lua_gc::GcState::CallFin),
5297 }
5298 };
5299
5300 let outcome = {
5301 let global = state_ref.global.borrow();
5302 global.heap.unpause();
5303 let roots = CollectRoots {
5304 global: &*global,
5305 thread: state_ref,
5306 };
5307 let hook = |marker: &mut lua_gc::Marker| {
5308 atomic_ran.set(true);
5309 alive_ids.borrow_mut().reserve(weak_table_capacity);
5310 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
5311 alive_thread_ids.borrow_mut().reserve(thread_capacity);
5312 trace_reachable_threads(&*global, global.current_thread_id, marker);
5313 close_open_upvalues_for_unreachable_threads(&*global, marker);
5314 let legacy_weak_key_values =
5318 matches!(global.lua_version, lua_types::LuaVersion::V51);
5319 loop {
5320 let visited_before = marker.visited_count();
5321 for t in &weak_tables_snapshot.ephemeron {
5322 let t_id = t.identity();
5323 if !marker.is_visited(t_id) {
5324 continue;
5325 }
5326 if legacy_weak_key_values {
5327 let mut to_mark = Vec::new();
5328 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5329 for v in &to_mark {
5330 v.trace(marker);
5331 }
5332 } else {
5333 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5334 for v in &to_mark {
5335 v.trace(marker);
5336 }
5337 }
5338 }
5339 marker.drain_gray_queue();
5340 if marker.visited_count() == visited_before {
5341 break;
5342 }
5343 }
5344 for t in weak_tables_snapshot
5357 .weak_values
5358 .iter()
5359 .chain(weak_tables_snapshot.all_weak.iter())
5360 {
5361 let to_mark =
5362 t.prune_weak_dead_with(&|_| true, &|id| marker.is_visited(id));
5363 if marker.is_visited(t.identity()) {
5364 for v in &to_mark {
5365 v.trace(marker);
5366 }
5367 }
5368 }
5369 marker.drain_gray_queue();
5370 for pf in &pending_snapshot {
5371 if !marker.is_visited(pf.identity()) {
5372 pf.mark(marker);
5373 newly_unreachable.borrow_mut().push(pf.clone());
5374 }
5375 }
5376 marker.drain_gray_queue();
5377 loop {
5378 let visited_before = marker.visited_count();
5379 for t in &weak_tables_snapshot.ephemeron {
5380 let t_id = t.identity();
5381 if !marker.is_visited(t_id) {
5382 continue;
5383 }
5384 if legacy_weak_key_values {
5385 let mut to_mark = Vec::new();
5386 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5387 for v in &to_mark {
5388 v.trace(marker);
5389 }
5390 } else {
5391 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5392 for v in &to_mark {
5393 v.trace(marker);
5394 }
5395 }
5396 }
5397 marker.drain_gray_queue();
5398 if marker.visited_count() == visited_before {
5399 break;
5400 }
5401 }
5402 for t in weak_tables_snapshot
5409 .ephemeron
5410 .iter()
5411 .chain(weak_tables_snapshot.all_weak.iter())
5412 {
5413 if !marker.is_visited(t.identity()) {
5414 continue;
5415 }
5416 let to_mark =
5417 t.prune_weak_dead_with(&|id| marker.is_visited(id), &|_| true);
5418 for v in &to_mark {
5419 v.trace(marker);
5420 }
5421 }
5422 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5423 if marker.is_visited(t.identity()) {
5424 alive_ids.borrow_mut().insert(t.identity());
5425 }
5426 }
5427 marker.drain_gray_queue();
5428 {
5429 let mut alive = alive_thread_ids.borrow_mut();
5430 for (id, entry) in global.threads.iter() {
5431 if thread_entry_marked_alive(marker, *id, entry) {
5432 alive.insert(*id);
5433 }
5434 }
5435 }
5436 {
5437 let mut alive = alive_closure_env_ids.borrow_mut();
5438 for id in global.closure_envs.keys() {
5439 if marker.is_visited(*id) {
5440 alive.insert(*id);
5441 }
5442 }
5443 }
5444 record_dead_interned_strings(&*global, marker, &dead_interned);
5445 };
5446 let budget = StepBudget::from_work(work_units);
5447 if let Some(target) = stop_target {
5448 global
5449 .heap
5450 .incremental_run_until_state_with_post_mark(&roots, target, work_units, hook)
5451 } else {
5452 global
5453 .heap
5454 .incremental_step_with_post_mark(&roots, budget, hook)
5455 }
5456 };
5457
5458 if atomic_ran.get() {
5459 let alive_set = alive_ids.into_inner();
5460 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5461 let alive_thread_ids = alive_thread_ids.into_inner();
5462 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5463 let dead_interned = dead_interned.into_inner();
5464 let mut g = state_ref.global.borrow_mut();
5465 remove_dead_interned_strings(&mut *g, dead_interned);
5466 g.weak_tables_registry.retain_identities(&alive_set);
5467 let main_thread_id = g.main_thread_id;
5468 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5469 g.cross_thread_upvals
5470 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5471 g.thread_globals
5475 .retain(|id, _| alive_thread_ids.contains(id));
5476 g.closure_envs
5477 .retain(|id, _| alive_closure_env_ids.contains(id));
5478 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5479 for object in &promoted {
5480 if let Some(ptr) = object.heap_ptr() {
5481 g.heap.move_finobj_to_tobefnz(ptr);
5482 }
5483 }
5484 }
5485
5486 let mut paused = matches!(outcome, StepOutcome::Paused);
5487 if target.is_none()
5488 && self._state.global().heap.gc_state() == lua_gc::GcState::CallFin
5489 && !self._state.global().finalizers.has_to_be_finalized()
5490 {
5491 paused = self._state.global().heap.finish_callfin_phase();
5492 }
5493
5494 paused
5495 }
5496
5497 pub fn prune_weak_tables_mark_only(&self) {
5504 use lua_gc::Trace;
5505 let state_ref: &LuaState = &*self._state;
5506
5507 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5508 let mut g = state_ref.global.borrow_mut();
5509 g.weak_tables_registry.live_snapshot_by_kind()
5510 };
5511 let _interned_capacity = {
5512 let g = state_ref.global.borrow();
5513 g.interned_lt.len()
5514 };
5515
5516 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5517
5518 {
5519 let global = state_ref.global.borrow();
5520 global.heap.unpause();
5521 let roots = CollectRoots {
5522 global: &*global,
5523 thread: state_ref,
5524 };
5525 let hook = |marker: &mut lua_gc::Marker| {
5526 trace_reachable_threads(&*global, global.current_thread_id, marker);
5527 loop {
5528 let visited_before = marker.visited_count();
5529 for t in &weak_tables_snapshot.ephemeron {
5530 let t_id = t.identity();
5531 if !marker.is_visited(t_id) {
5532 continue;
5533 }
5534 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5535 for v in &to_mark {
5536 v.trace(marker);
5537 }
5538 }
5539 marker.drain_gray_queue();
5540 if marker.visited_count() == visited_before {
5541 break;
5542 }
5543 }
5544 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5545 if marker.is_visited(t.identity()) {
5546 let to_mark = t.prune_weak_dead(&|id| marker.is_visited(id));
5547 for v in &to_mark {
5548 v.trace(marker);
5549 }
5550 }
5551 }
5552 marker.drain_gray_queue();
5553 record_dead_interned_strings(&*global, marker, &dead_interned);
5554 };
5555 global.heap.mark_only_with_post_mark(&roots, hook);
5556 }
5557
5558 let dead_interned = dead_interned.into_inner();
5559 let mut g = state_ref.global.borrow_mut();
5560 remove_dead_interned_strings(&mut *g, dead_interned);
5561 }
5562
5563 pub fn change_mode(&self, mode: GcKind) {
5565 let old = self._state.global().gckind;
5566 if old == mode as u8 {
5567 self._state.global_mut().lastatomic = 0;
5568 return;
5569 }
5570 match mode {
5571 GcKind::Generational => {
5572 self.enter_generational_mode();
5573 }
5574 GcKind::Incremental => {
5575 self.enter_incremental_mode();
5576 }
5577 }
5578 }
5579
5580 pub fn fix_object<T: lua_gc::Trace + 'static>(&self, _o: &GcRef<T>) { }
5586
5587 pub fn free_all_objects(&self) {
5623 let heap = self._state.global().heap.clone();
5624 {
5625 let _own_heap = lua_gc::HeapGuard::push(&heap);
5626 heap.drop_all();
5627 }
5628 let mut g = self._state.global_mut();
5629 g.weak_tables_registry = lua_gc::WeakRegistry::default();
5630 g.finalizers = lua_gc::FinalizerRegistry::default();
5631 g.external_roots = ExternalRootSet::default();
5632 g.threads.clear();
5633 g.thread_globals.clear();
5634 g.cross_thread_upvals.clear();
5635 g.suspended_parent_stacks.clear();
5636 g.suspended_parent_open_upvals.clear();
5637 g.twups.clear();
5638 }
5639
5640 pub fn barrier(&self, p: &dyn std::any::Any, v: &LuaValue) {
5642 let g = self._state.global();
5643 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Forward);
5644 }
5645
5646 pub fn barrier_back(&self, p: &dyn std::any::Any, v: &LuaValue) {
5648 let g = self._state.global();
5649 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Backward);
5650 }
5651
5652 pub fn table_barrier_back(&self, p: &GcRef<LuaTable>, v: &LuaValue) {
5654 let g = self._state.global();
5655 barrier_lua_value(&g.heap, *p, v, g.is_gen_mode(), BarrierKind::Backward);
5656 }
5657
5658 pub fn obj_barrier(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5660 let g = self._state.global();
5661 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Forward);
5662 }
5663
5664 pub fn obj_barrier_back(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5667 let g = self._state.global();
5668 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Backward);
5669 }
5670}
5671
5672fn make_seed() -> u32 {
5682 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5683 {
5684 return crate::string::hash_bytes(b"lua-rs-wasm-seed", 0x9e37_79b9);
5685 }
5686
5687 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
5688 {
5689 use std::time::{SystemTime, UNIX_EPOCH};
5690 let t = SystemTime::now()
5691 .duration_since(UNIX_EPOCH)
5692 .map(|d| d.as_secs() as u32)
5693 .unwrap_or(0);
5694
5695 crate::string::hash_bytes(&t.to_le_bytes(), t)
5696 }
5697}
5698
5699pub(crate) fn set_debt(g: &mut GlobalState, mut debt: isize) {
5702 let tb = g.total_bytes() as isize;
5703 debug_assert!(tb > 0);
5704 if debt < tb.saturating_sub(isize::MAX) {
5706 debt = tb - isize::MAX;
5707 }
5708 g.gc_debt = debt;
5709}
5710
5711pub fn set_c_stack_limit(_state: &mut LuaState, _limit: u32) -> i32 {
5713 let _ = (_state, _limit);
5714 LUAI_MAXCCALLS as i32
5715}
5716
5717pub(crate) fn extend_ci(state: &mut LuaState) -> CallInfoIdx {
5719 debug_assert!(
5720 state.call_info[state.ci.0 as usize].next.is_none(),
5721 "extend_ci: current ci already has a cached next frame"
5722 );
5723
5724 let current_idx = state.ci;
5725 let new_idx = CallInfoIdx(state.call_info.len() as u32);
5727
5728 state.call_info.push(CallInfo {
5729 previous: Some(current_idx),
5730 next: None,
5731 u: CallInfoFrame::lua_default(),
5732 ..CallInfo::default()
5733 });
5734
5735 state.call_info[current_idx.0 as usize].next = Some(new_idx);
5736
5737 state.nci += 1;
5738
5739 new_idx
5740}
5741
5742fn free_ci(state: &mut LuaState) {
5750 let ci_idx = state.ci.0 as usize;
5751
5752 let mut next_opt = state.call_info[ci_idx].next.take();
5753
5754 while let Some(idx) = next_opt {
5755 next_opt = state.call_info[idx.0 as usize].next;
5756 state.nci = state.nci.saturating_sub(1);
5757 }
5758
5759 state.call_info.truncate(ci_idx + 1);
5762}
5763
5764pub(crate) fn shrink_ci(state: &mut LuaState) {
5773 let ci_idx = state.ci.0 as usize;
5774
5775 if state.call_info[ci_idx].next.is_none() {
5776 return;
5777 }
5778
5779 let free_count = state.call_info.len().saturating_sub(ci_idx + 1);
5780 if free_count <= 1 {
5781 return;
5782 }
5783
5784 let keep = free_count / 2;
5786 let removed = free_count - keep;
5787 let new_len = ci_idx + 1 + keep;
5788 state.call_info.truncate(new_len);
5789 state.nci = state.nci.saturating_sub(removed as u32);
5790
5791 if let Some(last) = state.call_info.last_mut() {
5793 last.next = None;
5794 }
5795}
5796
5797pub(crate) fn check_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5799 if state.c_calls() == LUAI_MAXCCALLS {
5801 return Err(LuaError::runtime(format_args!("C stack overflow")));
5802 }
5803 if state.c_calls() >= (LUAI_MAXCCALLS / 10 * 11) {
5805 return Err(LuaError::with_status(LuaStatus::ErrErr));
5806 }
5807 Ok(())
5808}
5809
5810pub fn inc_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5812 state.n_ccalls += 1;
5813 if state.c_calls() >= LUAI_MAXCCALLS {
5814 check_c_stack(state)?;
5815 }
5816 Ok(())
5817}
5818
5819fn stack_init(thread: &mut LuaState) {
5824 let total_slots = BASIC_STACK_SIZE + EXTRA_STACK;
5825 thread.stack = vec![StackValue::default(); total_slots];
5826
5827 thread.tbclist = Vec::new();
5830
5831 thread.top = StackIdx(0);
5835
5836 thread.stack_last = StackIdx(BASIC_STACK_SIZE as u32);
5837
5838 let base_ci = CallInfo {
5839 func: StackIdx(0),
5840 top: StackIdx(1 + LUA_MINSTACK as u32),
5841 previous: None,
5842 next: None,
5843 callstatus: CIST_C,
5844 call_metamethods: 0,
5845 tailcalls: 0,
5846 nresults: 0,
5847 u: CallInfoFrame::c_default(),
5848 u2: CallInfoExtra::default(),
5849 };
5850
5851 if thread.call_info.is_empty() {
5852 thread.call_info.push(base_ci);
5853 } else {
5854 thread.call_info[0] = base_ci;
5855 thread.call_info.truncate(1);
5856 }
5857
5858 thread.stack[0] = StackValue {
5859 val: LuaValue::Nil,
5860 };
5861
5862 thread.top = StackIdx(1);
5863
5864 thread.ci = CallInfoIdx(0);
5865}
5866
5867fn free_stack(state: &mut LuaState) {
5868 if state.stack.is_empty() {
5869 return;
5870 }
5871 state.ci = CallInfoIdx(0);
5872 free_ci(state);
5873 debug_assert_eq!(state.nci, 0, "nci should be 0 after free_ci");
5874 state.stack.clear();
5875 state.stack.shrink_to_fit();
5876}
5877
5878fn init_registry(state: &mut LuaState) -> Result<(), LuaError> {
5904 let registry = state.new_table();
5905 state.global_mut().l_registry = LuaValue::Table(registry);
5906
5907 let globals = state.new_table();
5908 state.global_mut().globals = LuaValue::Table(globals);
5909 let loaded = state.new_table();
5910 state.global_mut().loaded = LuaValue::Table(loaded);
5911
5912 set_versioned_registry_slots(state)?;
5913
5914 Ok(())
5915}
5916
5917pub fn set_versioned_registry_slots(state: &mut LuaState) -> Result<(), LuaError> {
5950 let registry = match state.global().l_registry.clone() {
5951 LuaValue::Table(t) => t,
5952 _ => return Ok(()),
5953 };
5954 let main_thread = LuaValue::Thread(state.global().main_thread_value.clone());
5955 let globals = state.global().globals.clone();
5956 let version = state.global().lua_version;
5957
5958 for idx in [1i64, LUA_RIDX_GLOBALS, 3] {
5959 let cur = registry.get_int(idx);
5960 if cur == main_thread || cur == globals {
5961 registry.raw_set_int(state, idx, LuaValue::Nil)?;
5962 }
5963 }
5964
5965 match version {
5966 lua_types::LuaVersion::V51 => {}
5967 lua_types::LuaVersion::V55 => {
5968 registry.raw_set_int(state, 1, LuaValue::Bool(false))?;
5969 registry.raw_set_int(state, LUA_RIDX_GLOBALS, globals)?;
5970 registry.raw_set_int(state, 3, main_thread)?;
5971 }
5972 _ => {
5973 registry.raw_set_int(state, LUA_RIDX_MAINTHREAD, main_thread)?;
5974 registry.raw_set_int(state, LUA_RIDX_GLOBALS, globals)?;
5975 }
5976 }
5977 Ok(())
5978}
5979
5980const MEMERR_MSG: &[u8] = b"not enough memory";
5983
5984fn init_memerrmsg(state: &mut LuaState) -> Result<(), LuaError> {
6000 let memerrmsg = state.intern_str(MEMERR_MSG)?;
6001 state.global_mut().memerrmsg = memerrmsg.clone();
6002
6003 for i in 0..STRCACHE_N {
6004 for j in 0..STRCACHE_M {
6005 state.global_mut().strcache[i][j] = memerrmsg.clone();
6006 }
6007 }
6008
6009 Ok(())
6010}
6011
6012fn lua_open(state: &mut LuaState) -> Result<(), LuaError> {
6020 stack_init(state);
6021 init_registry(state)?;
6022 init_memerrmsg(state)?;
6023 crate::tagmethods::init(state)?;
6024 state.global_mut().gcstp = 0;
6025 state.global().heap.unpause();
6026 state.global_mut().nilvalue = LuaValue::Nil;
6028 Ok(())
6029}
6030
6031fn preinit_thread(thread: &mut LuaState, global: Rc<RefCell<GlobalState>>) {
6032 thread.global = global;
6033 thread.stack = Vec::new();
6034 thread.call_info = Vec::new();
6035 thread.ci = CallInfoIdx(0);
6038 thread.nci = 0;
6039 thread.n_ccalls = 0;
6043 thread.hook = None;
6044 thread.hookmask = 0;
6045 thread.basehookcount = 0;
6046 thread.allowhook = true;
6047 thread.hookcount = thread.basehookcount;
6048
6049 {
6054 let (active, interval) = {
6055 let g = thread.global.borrow();
6056 (g.sandbox_active(), g.sandbox.interval.get())
6057 };
6058 if active {
6059 thread.hookmask = SANDBOX_COUNT_MASK;
6060 thread.basehookcount = interval;
6061 thread.hookcount = interval;
6062 }
6063 }
6064 thread.openupval = Vec::new();
6065 thread.status = LuaStatus::Ok as u8;
6066 thread.errfunc = 0;
6067 thread.oldpc = 0;
6068 thread.gc_check_needed = true;
6069}
6070
6071fn close_state(state: &mut LuaState) {
6083 let is_complete = state.global().is_complete();
6084
6085 if !is_complete {
6086 state.gc().free_all_objects();
6087 } else {
6088 state.ci = CallInfoIdx(0);
6089 crate::api::run_close_finalizers(state);
6090 state.gc().free_all_objects();
6091 }
6092
6093 free_stack(state);
6094
6095 }
6098
6099pub fn new_thread(state: &mut LuaState, initial_body: Option<LuaValue>) -> Result<(), LuaError> {
6108 state.gc_pre_collect_clear();
6109 state.gc().check_step();
6110
6111 let global_rc = state.global_rc();
6118 let hookmask = state.hookmask;
6119 let basehookcount = state.basehookcount;
6120
6121 let reserved_id = {
6122 let mut g = state.global_mut();
6123 let id = g.next_thread_id;
6124 g.next_thread_id += 1;
6125 id
6126 };
6127
6128 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
6134 let creator_id = state.global().current_thread_id;
6135 let creator_lgt = state.v51_thread_lgt(creator_id);
6136 state
6137 .global_mut()
6138 .thread_globals
6139 .insert(reserved_id, creator_lgt);
6140 }
6141
6142 let mut new_thread = LuaState {
6143 status: LuaStatus::Ok as u8,
6144 allowhook: true,
6145 nci: 0,
6146 top: StackIdx(0),
6147 stack_last: StackIdx(0),
6148 stack: Vec::new(),
6149 ci: CallInfoIdx(0),
6150 call_info: Vec::new(),
6151 openupval: Vec::new(),
6152 legacy_open_upval_touched: std::cell::Cell::new(false),
6153 tbclist: Vec::new(),
6154 global: global_rc.clone(),
6155 hook: None,
6156 hookmask: 0,
6157 basehookcount: 0,
6158 hookcount: 0,
6159 errfunc: 0,
6160 n_ccalls: 0,
6161 oldpc: 0,
6162 marked: 0,
6163 cached_thread_id: reserved_id,
6164 gc_check_needed: false,
6165 };
6166
6167 preinit_thread(&mut new_thread, global_rc);
6168
6169 new_thread.hookmask = hookmask;
6170 new_thread.basehookcount = basehookcount;
6171 new_thread.reset_hook_count();
6175
6176 stack_init(&mut new_thread);
6177
6178 if let Some(body) = initial_body {
6179 new_thread.push(body);
6180 }
6181
6182 let thread_ref: Rc<RefCell<LuaState>> = Rc::new(RefCell::new(new_thread));
6183
6184 let value = {
6185 let mut g = state.global_mut();
6186 let id = reserved_id;
6187 let value = GcRef::new(lua_types::value::LuaThread::new(id));
6188 g.threads.insert(
6189 id,
6190 ThreadRegistryEntry {
6191 state: thread_ref,
6192 value: value.clone(),
6193 },
6194 );
6195 value
6196 };
6197
6198 state.push(LuaValue::Thread(value));
6199
6200 Ok(())
6201}
6202
6203pub fn reset_thread(state: &mut LuaState, status: i32) -> i32 {
6224 let _heap_guard = {
6228 let g = state.global.borrow();
6229 lua_gc::HeapGuard::push(&g.heap)
6230 };
6231 state.ci = CallInfoIdx(0);
6232 let ci_idx = 0usize;
6233
6234 if !state.stack.is_empty() {
6235 state.stack[0].val = LuaValue::Nil;
6236 }
6237
6238 state.call_info[ci_idx].func = StackIdx(0);
6239 state.call_info[ci_idx].call_metamethods = 0;
6240 state.call_info[ci_idx].callstatus = CIST_C;
6241
6242 let mut status = if status == LuaStatus::Yield as i32 {
6243 LuaStatus::Ok as i32
6244 } else {
6245 status
6246 };
6247
6248 state.status = LuaStatus::Ok as u8;
6249
6250 let close_status = crate::do_::close_protected(state, StackIdx(1), LuaStatus::from_raw(status));
6251 status = close_status as i32;
6252
6253 if status != LuaStatus::Ok as i32 {
6254 crate::do_::set_error_obj(state, LuaStatus::from_raw(status), StackIdx(1));
6255 } else {
6256 state.top = StackIdx(1);
6257 }
6258
6259 let new_ci_top = StackIdx(state.top.0 + LUA_MINSTACK as u32);
6260 state.call_info[ci_idx].top = new_ci_top;
6261
6262 let _ = crate::do_::realloc_stack(state, new_ci_top.0 as usize, false);
6263 state.stack.shrink_to_fit();
6264
6265 status
6266}
6267
6268pub fn close_thread(state: &mut LuaState, from: Option<&LuaState>) -> i32 {
6270 state.n_ccalls = match from {
6271 Some(f) => f.c_calls(),
6272 None => 0,
6273 };
6274 let current_status = state.status as i32;
6275 let result = reset_thread(state, current_status);
6276 result
6277}
6278
6279pub fn reset_thread_api(state: &mut LuaState) -> i32 {
6281 close_thread(state, None)
6282}
6283
6284pub fn new_state() -> Option<LuaState> {
6290 let heap = lua_gc::Heap::new();
6304 let placeholder_str = GcRef(heap.allocate_uncollected(LuaString::placeholder()));
6305 let main_thread_value = GcRef(heap.allocate_uncollected(lua_types::value::LuaThread::new(0)));
6306
6307 let initial_white = 1u8 << WHITE0BIT;
6308
6309 let global = GlobalState {
6312 parser_hook: None,
6313 cli_argv: None,
6314 cli_preload: None,
6315 lua_version: lua_types::LuaVersion::default(),
6316 file_loader_hook: None,
6317 file_open_hook: None,
6318 stdout_hook: None,
6319 stderr_hook: None,
6320 stdin_hook: None,
6321 env_hook: None,
6322 unix_time_hook: None,
6323 cpu_clock_hook: None,
6324 local_offset_hook: None,
6325 entropy_hook: None,
6326 temp_name_hook: None,
6327 popen_hook: None,
6328 file_remove_hook: None,
6329 file_rename_hook: None,
6330 os_execute_hook: None,
6331 dynlib_load_hook: None,
6332 dynlib_symbol_hook: None,
6333 dynlib_unload_hook: None,
6334 sandbox: SandboxLimits::default(),
6335 gc_debt: 0,
6336 gc_estimate: 0,
6337 lastatomic: 0,
6338 l_registry: LuaValue::Nil,
6339 external_roots: ExternalRootSet::default(),
6340 globals: LuaValue::Nil,
6341 loaded: LuaValue::Nil,
6342 nilvalue: LuaValue::Int(0),
6343 seed: make_seed(),
6344 currentwhite: initial_white,
6345 gcstate: GCS_PAUSE,
6346 gckind: GcKind::Incremental as u8,
6347 gcstopem: false,
6348 genminormul: LUAI_GENMINORMUL,
6349 genmajormul: (LUAI_GENMAJORMUL / 4) as u8,
6350 gcstp: GCSTPGC,
6351 gcemergency: false,
6352 gcpause: (LUAI_GCPAUSE / 4) as u8,
6353 gcstepmul: (LUAI_GCMUL / 4) as u8,
6354 gcstepsize: LUAI_GCSTEPSIZE,
6355 gc55_params: [20, 50, 68, 250, 200, 9600],
6358 sweepgc_cursor: 0,
6359 weak_tables_registry: lua_gc::WeakRegistry::default(),
6360 finalizers: lua_gc::FinalizerRegistry::default(),
6361 gc_finalizer_error: None,
6362 twups: Vec::new(),
6363 panic: None,
6364 mainthread: None,
6365 threads: std::collections::HashMap::new(),
6366 main_thread_value,
6367 current_thread_id: 0,
6368 closing_thread_id: None,
6369 main_thread_id: 0,
6370 next_thread_id: 1,
6371 thread_globals: std::collections::HashMap::new(),
6372 closure_envs: std::collections::HashMap::new(),
6373 memerrmsg: placeholder_str.clone(),
6374 tmname: Vec::new(),
6375 mt: std::array::from_fn(|_| None),
6376 strcache: std::array::from_fn(|_| std::array::from_fn(|_| placeholder_str.clone())),
6377 interned_lt: InternedStringMap::default(),
6378 warnf: None,
6379 warn_mode: WarnMode::Off,
6380 test_warn_enabled: false,
6381 test_warn_on: false,
6382 test_warn_mode: TestWarnMode::Normal,
6383 test_warn_last_to_cont: false,
6384 test_warn_buffer: Vec::new(),
6385 c_functions: Vec::new(),
6386 heap,
6387 cross_thread_upvals: std::collections::HashMap::new(),
6388 suspended_parent_stacks: Vec::new(),
6389 suspended_parent_open_upvals: Vec::new(),
6390 snapshot_stack_pool: Vec::new(),
6391 snapshot_upval_pool: Vec::new(),
6392 resume_value_pool: Vec::new(),
6393 resume_upval_slot_pool: Vec::new(),
6394 resume_flush_pool: Vec::new(),
6395 };
6396
6397 let global_rc = Rc::new(RefCell::new(global));
6398
6399 let _bootstrap_scope = global_rc.borrow().heap.bootstrap_scope();
6413 let _bootstrap_guard = lua_gc::HeapGuard::push(&global_rc.borrow().heap);
6414
6415 let initial_marked = initial_white;
6416
6417 let mut main_thread = LuaState {
6418 status: LuaStatus::Ok as u8,
6419 allowhook: true,
6420 nci: 0,
6421 top: StackIdx(0),
6422 stack_last: StackIdx(0),
6423 stack: Vec::new(),
6424 ci: CallInfoIdx(0),
6425 call_info: Vec::new(),
6426 openupval: Vec::new(),
6427 legacy_open_upval_touched: std::cell::Cell::new(false),
6428 tbclist: Vec::new(),
6429 global: global_rc.clone(),
6430 hook: None,
6431 hookmask: 0,
6432 basehookcount: 0,
6433 hookcount: 0,
6434 errfunc: 0,
6435 n_ccalls: 0,
6436 oldpc: 0,
6437 marked: initial_marked,
6438 cached_thread_id: 0,
6439 gc_check_needed: false,
6440 };
6441
6442 preinit_thread(&mut main_thread, global_rc.clone());
6443
6444 main_thread.inc_nny();
6445
6446 match lua_open(&mut main_thread) {
6453 Ok(()) => {}
6454 Err(_) => {
6455 close_state(&mut main_thread);
6456 return None;
6457 }
6458 }
6459
6460 Some(main_thread)
6461}
6462
6463pub fn close(mut state: LuaState) {
6471 close_state(&mut state);
6472}
6473
6474pub(crate) fn warning(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6476 let test_warn_enabled = state.global().test_warn_enabled;
6477 if test_warn_enabled {
6478 test_warn(state, msg, to_cont);
6479 return;
6480 }
6481
6482 let has_warnf = state.global().warnf.is_some();
6486 if has_warnf {
6487 let mut warnf = state.global_mut().warnf.take();
6489 if let Some(ref mut f) = warnf {
6490 f(msg, to_cont);
6491 }
6492 state.global_mut().warnf = warnf;
6494 return;
6495 }
6496 default_warn(state, msg, to_cont);
6497}
6498
6499fn test_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6500 let is_control = {
6501 let g = state.global();
6502 !g.test_warn_last_to_cont && !to_cont && msg.first() == Some(&b'@')
6503 };
6504 if is_control {
6505 let mut g = state.global_mut();
6506 match &msg[1..] {
6507 b"off" => g.test_warn_on = false,
6508 b"on" => g.test_warn_on = true,
6509 b"normal" => g.test_warn_mode = TestWarnMode::Normal,
6510 b"allow" => g.test_warn_mode = TestWarnMode::Allow,
6511 b"store" => g.test_warn_mode = TestWarnMode::Store,
6512 _ => {}
6513 }
6514 return;
6515 }
6516
6517 let finished = {
6518 let mut g = state.global_mut();
6519 g.test_warn_last_to_cont = to_cont;
6520 g.test_warn_buffer.extend_from_slice(msg);
6521 if to_cont {
6522 None
6523 } else {
6524 Some((
6525 std::mem::take(&mut g.test_warn_buffer),
6526 g.test_warn_mode,
6527 g.test_warn_on,
6528 ))
6529 }
6530 };
6531
6532 let Some((message, mode, warn_on)) = finished else {
6533 return;
6534 };
6535 match mode {
6536 TestWarnMode::Normal => {
6537 if warn_on && message.first() == Some(&b'#') {
6538 write_warning_message(&message);
6539 }
6540 }
6541 TestWarnMode::Allow => {
6542 if warn_on {
6543 write_warning_message(&message);
6544 }
6545 }
6546 TestWarnMode::Store => {
6547 if let Ok(s) = state.intern_str(&message) {
6548 state.push(LuaValue::Str(s));
6549 let _ = crate::api::set_global(state, b"_WARN");
6550 }
6551 }
6552 }
6553}
6554
6555fn write_warning_message(message: &[u8]) {
6556 use std::io::Write;
6557 let stderr = std::io::stderr();
6558 let mut h = stderr.lock();
6559 let _ = h.write_all(b"Lua warning: ");
6560 let _ = h.write_all(message);
6561 let _ = h.write_all(b"\n");
6562}
6563
6564fn default_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6569 use std::io::Write;
6570 if !to_cont && msg.first() == Some(&b'@') {
6572 match &msg[1..] {
6573 b"off" => state.global_mut().warn_mode = WarnMode::Off,
6574 b"on" => state.global_mut().warn_mode = WarnMode::On,
6575 _ => {}
6576 }
6577 return;
6578 }
6579 let mode = state.global().warn_mode;
6580 match mode {
6581 WarnMode::Off => {}
6582 WarnMode::On | WarnMode::Cont => {
6583 let stderr = std::io::stderr();
6584 let mut h = stderr.lock();
6585 if mode == WarnMode::On {
6586 let _ = h.write_all(b"Lua warning: ");
6587 }
6588 let _ = h.write_all(msg);
6589 if to_cont {
6590 state.global_mut().warn_mode = WarnMode::Cont;
6591 } else {
6592 let _ = h.write_all(b"\n");
6593 state.global_mut().warn_mode = WarnMode::On;
6594 }
6595 }
6596 }
6597}
6598
6599#[cfg(test)]
6600mod tests {
6601 use super::*;
6602
6603 fn test_noop_cclosure(_: &mut LuaState) -> Result<usize, LuaError> {
6604 Ok(0)
6605 }
6606
6607 #[test]
6611 fn new_userdata_builds_untyped_full_userdata() {
6612 let mut state = new_state().expect("state should initialize");
6613 let _heap_guard = {
6614 let g = state.global();
6615 lua_gc::HeapGuard::push(&g.heap)
6616 };
6617 let ud = state
6618 .new_userdata(16, 2)
6619 .expect("new_userdata should build, not error");
6620 assert_eq!(ud.data.len(), 16);
6621 assert_eq!(ud.uv.borrow().len(), 2);
6622 assert!(ud.uv.borrow().iter().all(|v| matches!(v, LuaValue::Nil)));
6623 assert!(ud.metatable.borrow().is_none());
6624 }
6625
6626 #[test]
6630 fn new_c_closure_builds_light_and_full_closures() {
6631 let mut state = new_state().expect("state should initialize");
6632 let _heap_guard = {
6633 let g = state.global();
6634 lua_gc::HeapGuard::push(&g.heap)
6635 };
6636 let light = state
6637 .new_c_closure(test_noop_cclosure, 0)
6638 .expect("light closure should build");
6639 assert!(
6640 matches!(light, LuaClosure::LightC(_)),
6641 "n==0 must yield a light C function"
6642 );
6643
6644 let full = state
6645 .new_c_closure(test_noop_cclosure, 3)
6646 .expect("full closure should build");
6647 match full {
6648 LuaClosure::C(ccl) => {
6649 let ups = ccl.upvalues.borrow();
6650 assert_eq!(ups.len(), 3);
6651 assert!(ups.iter().all(|v| matches!(v, LuaValue::Nil)));
6652 }
6653 _ => panic!("n>0 must yield a full C closure"),
6654 }
6655 }
6656
6657 #[test]
6661 fn register_c_function_dedups_only_when_requested() {
6662 let mut state = new_state().expect("state should initialize");
6663 let _heap_guard = {
6664 let g = state.global();
6665 lua_gc::HeapGuard::push(&g.heap)
6666 };
6667 let a = state.register_c_function(test_noop_cclosure, true);
6668 let b = state.register_c_function(test_noop_cclosure, true);
6669 assert_eq!(a, b, "dedup=true should reuse the identical fn's slot");
6670 let c = state.register_c_function(test_noop_cclosure, false);
6671 assert_ne!(a, c, "dedup=false must always allocate a fresh slot");
6672 }
6673
6674 #[test]
6680 fn to_display_string_renders_distinct_real_table_addresses() {
6681 let mut state = new_state().expect("state should initialize");
6682 let _heap_guard = {
6683 let g = state.global();
6684 lua_gc::HeapGuard::push(&g.heap)
6685 };
6686
6687 let t1 = state.new_table();
6688 state.push(LuaValue::Table(t1));
6689 let s1 = state
6690 .to_display_string(-1)
6691 .expect("to_display_string on table 1");
6692
6693 let t2 = state.new_table();
6694 state.push(LuaValue::Table(t2));
6695 let s2 = state
6696 .to_display_string(-1)
6697 .expect("to_display_string on table 2");
6698
6699 assert!(
6700 s1.starts_with(b"table: 0x"),
6701 "expected a real address, got {:?}",
6702 String::from_utf8_lossy(&s1)
6703 );
6704 assert!(
6705 !s1.windows(3).any(|w| w == b"0x?"),
6706 "the fixed '0x?' placeholder must be gone, got {:?}",
6707 String::from_utf8_lossy(&s1)
6708 );
6709 assert_ne!(s1, s2, "two distinct tables must render distinct addresses");
6710 }
6711
6712 #[test]
6717 fn raw_constructors_flag_gc_check_needed() {
6718 let mut state = new_state().expect("state should initialize");
6719 let _heap_guard = {
6720 let g = state.global();
6721 lua_gc::HeapGuard::push(&g.heap)
6722 };
6723
6724 state.gc_check_needed = false;
6725 let _ = state.new_userdata(32, 1).expect("new_userdata");
6726 assert!(state.gc_check_needed, "new_userdata must flag a GC check");
6727
6728 state.gc_check_needed = false;
6729 let _ = state
6730 .new_c_closure(test_noop_cclosure, 2)
6731 .expect("new_c_closure n>0");
6732 assert!(
6733 state.gc_check_needed,
6734 "new_c_closure(n>0) must flag a GC check"
6735 );
6736 }
6737
6738 #[test]
6742 fn to_display_string_light_userdata_uses_plain_userdata_name() {
6743 let mut state = new_state().expect("state should initialize");
6744 let _heap_guard = {
6745 let g = state.global();
6746 lua_gc::HeapGuard::push(&g.heap)
6747 };
6748 let ptr = 0x1234usize as *mut core::ffi::c_void;
6749 state.push(LuaValue::LightUserData(ptr));
6750 let s = state
6751 .to_display_string(-1)
6752 .expect("display light userdata");
6753 assert!(
6754 s.starts_with(b"userdata: 0x"),
6755 "expected 'userdata: 0x...', got {:?}",
6756 String::from_utf8_lossy(&s)
6757 );
6758 assert!(
6759 !s.starts_with(b"light userdata"),
6760 "must not use the error-message name 'light userdata'"
6761 );
6762 }
6763
6764 #[test]
6767 fn to_pointer_lightc_is_real_address_not_index() {
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 let cl = state
6774 .new_c_closure(test_noop_cclosure, 0)
6775 .expect("light C function");
6776 let idx = match cl {
6777 LuaClosure::LightC(i) => i,
6778 _ => panic!("n==0 must yield LightC"),
6779 };
6780 state.push(LuaValue::Function(cl));
6781 let ptr = crate::api::to_pointer(&state, -1).expect("LightC must have a pointer");
6782 assert!(
6783 ptr > u16::MAX as usize,
6784 "expected a real address, got {ptr:#x} (registry index was {idx})"
6785 );
6786 assert_eq!(
6787 ptr, test_noop_cclosure as usize,
6788 "must resolve to the registered fn's real address"
6789 );
6790 }
6791
6792 #[test]
6795 fn to_cfunction_resolves_registered_fn() {
6796 let mut state = new_state().expect("state should initialize");
6797 let _heap_guard = {
6798 let g = state.global();
6799 lua_gc::HeapGuard::push(&g.heap)
6800 };
6801 let cl = state
6802 .new_c_closure(test_noop_cclosure, 0)
6803 .expect("light C function");
6804 state.push(LuaValue::Function(cl));
6805 let f = crate::api::to_cfunction(&state, -1).expect("to_cfunction must resolve");
6806 assert_eq!(f as usize, test_noop_cclosure as usize);
6807
6808 state.push(LuaValue::Int(7));
6809 assert!(
6810 crate::api::to_cfunction(&state, -1).is_none(),
6811 "non-function must yield None"
6812 );
6813 }
6814
6815 #[test]
6819 fn new_c_closure_rejects_out_of_range_upvalue_count() {
6820 let mut state = new_state().expect("state should initialize");
6821 let _heap_guard = {
6822 let g = state.global();
6823 lua_gc::HeapGuard::push(&g.heap)
6824 };
6825 let before = state.global().c_functions.len();
6826 assert!(
6827 state.new_c_closure(test_noop_cclosure, 256).is_err(),
6828 "n>255 must error"
6829 );
6830 assert!(
6831 state.new_c_closure(test_noop_cclosure, -1).is_err(),
6832 "negative n must error"
6833 );
6834 assert_eq!(
6835 before,
6836 state.global().c_functions.len(),
6837 "an invalid upvalue count must not pollute c_functions"
6838 );
6839 assert!(
6840 state.new_c_closure(test_noop_cclosure, 255).is_ok(),
6841 "n==255 (MAX_UPVAL) must succeed"
6842 );
6843 }
6844
6845 #[test]
6855 fn registry_versioned_slots_match_version() {
6856 let mut state = new_state().expect("state should initialize");
6857 let _heap_guard = {
6858 let g = state.global();
6859 lua_gc::HeapGuard::push(&g.heap)
6860 };
6861
6862 let registry = match state.global().l_registry.clone() {
6863 LuaValue::Table(t) => t,
6864 other => panic!("l_registry should be a table, got {other:?}"),
6865 };
6866 let main_thread = LuaValue::Thread(state.global().main_thread_value.clone());
6867 let globals = state.global().globals.clone();
6868 assert!(matches!(globals, LuaValue::Table(_)), "globals should be a table");
6869
6870 assert_eq!(registry.get_int(1), main_thread);
6871 assert_eq!(registry.get_int(LUA_RIDX_GLOBALS), globals);
6872 assert_eq!(registry.get_int(3), LuaValue::Nil);
6873
6874 state.global_mut().lua_version = lua_types::LuaVersion::V51;
6875 set_versioned_registry_slots(&mut state).expect("V51 slot update should succeed");
6876 assert_eq!(registry.get_int(1), LuaValue::Nil);
6877 assert_eq!(registry.get_int(LUA_RIDX_GLOBALS), LuaValue::Nil);
6878 assert_eq!(registry.get_int(3), LuaValue::Nil);
6879
6880 state.global_mut().lua_version = lua_types::LuaVersion::V55;
6881 set_versioned_registry_slots(&mut state).expect("V55 slot update should succeed");
6882 assert_eq!(registry.get_int(1), LuaValue::Bool(false));
6883 assert_eq!(registry.get_int(LUA_RIDX_GLOBALS), globals);
6884 assert_eq!(registry.get_int(3), main_thread);
6885
6886 state.global_mut().lua_version = lua_types::LuaVersion::V52;
6887 set_versioned_registry_slots(&mut state).expect("V52 slot update should succeed");
6888 assert_eq!(registry.get_int(1), main_thread);
6889 assert_eq!(registry.get_int(LUA_RIDX_GLOBALS), globals);
6890 assert_eq!(registry.get_int(3), LuaValue::Nil);
6891 }
6892
6893 #[test]
6901 fn versioned_registry_slots_preserve_foreign_entries_on_rerun() {
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 registry = match state.global().l_registry.clone() {
6909 LuaValue::Table(t) => t,
6910 other => panic!("l_registry should be a table, got {other:?}"),
6911 };
6912 let main_thread = LuaValue::Thread(state.global().main_thread_value.clone());
6913 let globals = state.global().globals.clone();
6914
6915 registry
6916 .raw_set_int(&mut state, 3, LuaValue::Int(99))
6917 .expect("seeding a foreign registry entry should succeed");
6918
6919 set_versioned_registry_slots(&mut state).expect("V54 re-run should succeed");
6920
6921 assert_eq!(registry.get_int(3), LuaValue::Int(99));
6922 assert_eq!(registry.get_int(1), main_thread);
6923 assert_eq!(registry.get_int(LUA_RIDX_GLOBALS), globals);
6924 }
6925
6926 #[test]
6939 fn reset_thread_shrinks_stack_and_updates_stack_last() {
6940 let mut state = new_state().expect("state should initialize");
6941 let _heap_guard = {
6942 let g = state.global();
6943 lua_gc::HeapGuard::push(&g.heap)
6944 };
6945
6946 let grown_size = state.stack_size() + 500;
6947 crate::do_::realloc_stack(&mut state, grown_size, false)
6948 .expect("growing the stack should succeed");
6949 assert_eq!(state.stack_size(), grown_size);
6950 assert_eq!(state.stack.len(), grown_size + EXTRA_STACK);
6951 let grown_capacity = state.stack.capacity();
6952
6953 let status = reset_thread(&mut state, LuaStatus::Ok as i32);
6954 assert_eq!(status, LuaStatus::Ok as i32);
6955
6956 let expected_size = (state.top.0 + LUA_MINSTACK as u32) as usize;
6957 assert_eq!(
6958 state.stack_size(),
6959 expected_size,
6960 "stack_last should track reset_thread's realloc instead of the stale pre-reset size"
6961 );
6962 assert_eq!(
6963 state.stack.len(),
6964 expected_size + EXTRA_STACK,
6965 "reset_thread should shrink the stack length back down, matching C's luaE_resetthread"
6966 );
6967 assert!(
6968 state.stack.capacity() < grown_capacity,
6969 "reset_thread should reclaim the backing capacity (was {}, now {})",
6970 grown_capacity,
6971 state.stack.capacity()
6972 );
6973 }
6974
6975 #[test]
6976 fn memerrmsg_is_the_same_object_a_matching_intern_str_call_returns() {
6977 let mut state = new_state().expect("state should initialize");
6978 let _heap_guard = {
6979 let g = state.global();
6980 lua_gc::HeapGuard::push(&g.heap)
6981 };
6982
6983 let memerrmsg = state.global().memerrmsg.clone();
6984 let interned = state
6985 .intern_str(crate::string::MAX_SHORT_LEN.to_string().as_bytes())
6986 .expect("interning a short string should not fail");
6987 assert!(!GcRef::ptr_eq(&memerrmsg, &interned));
6988
6989 let same_bytes = state
6990 .intern_str(memerrmsg.as_bytes())
6991 .expect("interning the memerrmsg bytes should not fail");
6992 assert!(
6993 GcRef::ptr_eq(&memerrmsg, &same_bytes),
6994 "memerrmsg must be a member of the same intern table as every other \
6995 short string, matching upstream Lua's `eqshrstr` identity semantics \
6996 (a script string spelling the OOM message aliases memerrmsg)"
6997 );
6998 }
6999
7000 #[test]
7001 fn external_root_keys_reject_stale_slot_after_reuse() {
7002 let mut roots = ExternalRootSet::default();
7003
7004 let first = roots.insert(LuaValue::Int(1));
7005 assert_eq!(roots.len(), 1);
7006 assert_eq!(roots.get(first), Some(&LuaValue::Int(1)));
7007
7008 assert_eq!(roots.remove(first), Some(LuaValue::Int(1)));
7009 assert!(roots.get(first).is_none());
7010 assert!(roots.remove(first).is_none());
7011 assert_eq!(roots.len(), 0);
7012 assert_eq!(roots.vacant_len(), 1);
7013 assert!(roots.replace(first, LuaValue::Int(9)).is_none());
7014 assert!(roots.is_empty());
7015
7016 let second = roots.insert(LuaValue::Int(2));
7017 assert_eq!(first.index, second.index);
7018 assert_ne!(first, second);
7019 assert!(roots.get(first).is_none());
7020 assert_eq!(roots.get(second), Some(&LuaValue::Int(2)));
7021 assert!(roots.replace(first, LuaValue::Int(3)).is_none());
7022 }
7023
7024 #[test]
7025 fn external_roots_keep_heap_value_alive_until_unrooted() {
7026 let mut state = new_state().expect("state should initialize");
7027 let _heap_guard = {
7028 let g = state.global();
7029 lua_gc::HeapGuard::push(&g.heap)
7030 };
7031
7032 let table = state.new_table();
7033 assert_eq!(state.global().heap.allgc_count(), 1);
7034
7035 let key = state.external_root_value(LuaValue::Table(table));
7036 state.gc().full_collect();
7037 assert_eq!(state.global().heap.allgc_count(), 1);
7038 assert_eq!(state.global().external_roots.len(), 1);
7039
7040 assert!(state.external_unroot_value(key).is_some());
7041 state.gc().full_collect();
7042 assert_eq!(state.global().heap.allgc_count(), 0);
7043 assert!(state.global().external_roots.is_empty());
7044 }
7045
7046 #[test]
7052 fn free_all_objects_kills_weak_handle_while_guard_and_heap_alive() {
7053 let mut state = new_state().expect("state should initialize");
7054 let heap_rc = state.global().heap.clone();
7055 let guard = lua_gc::HeapGuard::push(&heap_rc);
7056
7057 let table = state.new_table();
7058 let weak = table.downgrade();
7059 assert!(
7060 weak.upgrade().is_some(),
7061 "weak handle upgrades while the table is live"
7062 );
7063
7064 state.gc().free_all_objects();
7065
7066 assert!(
7067 weak.upgrade().is_none(),
7068 "free_all_objects must free the table and drop its weak token during \
7069 the close call, with the outer HeapGuard and a strong heap Rc still alive"
7070 );
7071 assert!(state.global().heap.is_closed());
7072 assert!(std::rc::Rc::strong_count(&heap_rc) >= 1);
7073
7074 drop(guard);
7075 drop(state);
7076 }
7077
7078 thread_local! {
7079 static CLOSE_GC_RUNS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
7080 }
7081
7082 fn close_gc_probe(_state: &mut LuaState) -> Result<usize, LuaError> {
7083 CLOSE_GC_RUNS.with(|c| c.set(c.get() + 1));
7084 Ok(0)
7085 }
7086
7087 fn install_finalizable_table_with(state: &mut LuaState, gc_fn: LuaCFunction) {
7092 let heap = state.global().heap.clone();
7093 let _guard = lua_gc::HeapGuard::push(&heap);
7094 crate::api::create_table(state, 0, 0).expect("object table");
7095 crate::api::create_table(state, 0, 1).expect("metatable");
7096 crate::api::push_cclosure(state, gc_fn, 0).expect("push __gc");
7097 crate::api::set_field(state, -2, b"__gc").expect("mt.__gc");
7098 crate::api::set_metatable(state, -2).expect("setmetatable");
7099 }
7100
7101 fn install_finalizable_table(state: &mut LuaState) {
7102 install_finalizable_table_with(state, close_gc_probe);
7103 }
7104
7105 #[test]
7110 fn state_close_runs_gc_finalizer_exactly_once() {
7111 CLOSE_GC_RUNS.with(|c| c.set(0));
7112 let mut state = new_state().expect("state should initialize");
7113 install_finalizable_table(&mut state);
7114 assert_eq!(
7115 CLOSE_GC_RUNS.with(|c| c.get()),
7116 0,
7117 "finalizer must not fire while the object is live"
7118 );
7119
7120 close(state);
7121 assert_eq!(
7122 CLOSE_GC_RUNS.with(|c| c.get()),
7123 1,
7124 "close must run the pending __gc exactly once before freeing"
7125 );
7126 }
7127
7128 #[test]
7133 fn manual_close_finalizer_pass_then_close_runs_gc_exactly_once() {
7134 CLOSE_GC_RUNS.with(|c| c.set(0));
7135 let mut state = new_state().expect("state should initialize");
7136 install_finalizable_table(&mut state);
7137
7138 crate::api::run_close_finalizers(&mut state);
7139 assert_eq!(CLOSE_GC_RUNS.with(|c| c.get()), 1);
7140
7141 close(state);
7142 assert_eq!(
7143 CLOSE_GC_RUNS.with(|c| c.get()),
7144 1,
7145 "close after a manual run_close_finalizers pass must not double-run __gc"
7146 );
7147 }
7148
7149 #[test]
7154 fn close_runs_queue_only_finalizer_leftovers() {
7155 CLOSE_GC_RUNS.with(|c| c.set(0));
7156 let mut state = new_state().expect("state should initialize");
7157 install_finalizable_table(&mut state);
7158
7159 {
7160 let mut g = state.global_mut();
7161 let pending: Vec<FinalizerObject> = g.finalizers.take_pending();
7162 assert!(!pending.is_empty());
7163 for object in pending.into_iter().rev() {
7164 let heap_ptr = object.heap_ptr();
7165 g.finalizers.push_to_be_finalized(object);
7166 if let Some(ptr) = heap_ptr {
7167 g.heap.move_finobj_to_tobefnz(ptr);
7168 }
7169 }
7170 assert_eq!(g.finalizers.pending_len(), 0);
7171 assert!(g.finalizers.has_to_be_finalized());
7172 }
7173
7174 close(state);
7175 assert_eq!(
7176 CLOSE_GC_RUNS.with(|c| c.get()),
7177 1,
7178 "a finalizer parked in to_be_finalized with pending empty must \
7179 still run at close"
7180 );
7181 }
7182
7183 fn self_reregister_gc_probe(state: &mut LuaState) -> Result<usize, LuaError> {
7188 CLOSE_GC_RUNS.with(|c| c.set(c.get() + 1));
7189 crate::api::push_value(state, 1);
7190 crate::api::create_table(state, 0, 1)?;
7191 crate::api::push_cclosure(state, self_reregister_gc_probe, 0)?;
7192 crate::api::set_field(state, -2, b"__gc")?;
7193 crate::api::set_metatable(state, -2)?;
7194 state.pop();
7195 Ok(0)
7196 }
7197
7198 #[test]
7204 fn queue_only_self_reregistering_finalizer_runs_exactly_once() {
7205 CLOSE_GC_RUNS.with(|c| c.set(0));
7206 let mut state = new_state().expect("state should initialize");
7207 install_finalizable_table_with(&mut state, self_reregister_gc_probe);
7208
7209 {
7210 let mut g = state.global_mut();
7211 let pending: Vec<FinalizerObject> = g.finalizers.take_pending();
7212 assert!(!pending.is_empty());
7213 for object in pending.into_iter().rev() {
7214 let heap_ptr = object.heap_ptr();
7215 g.finalizers.push_to_be_finalized(object);
7216 if let Some(ptr) = heap_ptr {
7217 g.heap.move_finobj_to_tobefnz(ptr);
7218 }
7219 }
7220 assert_eq!(g.finalizers.pending_len(), 0);
7221 assert!(g.finalizers.has_to_be_finalized());
7222 }
7223
7224 close(state);
7225 assert_eq!(
7226 CLOSE_GC_RUNS.with(|c| c.get()),
7227 1,
7228 "a self-re-registering queue-only finalizer must run exactly \
7229 once at close — seen must be seeded from to_be_finalized"
7230 );
7231 }
7232
7233 fn regen_gc_probe(state: &mut LuaState) -> Result<usize, LuaError> {
7234 CLOSE_GC_RUNS.with(|c| c.set(c.get() + 1));
7235 crate::api::create_table(state, 0, 0)?;
7236 crate::api::create_table(state, 0, 1)?;
7237 crate::api::push_cclosure(state, close_gc_probe, 0)?;
7238 crate::api::set_field(state, -2, b"__gc")?;
7239 crate::api::set_metatable(state, -2)?;
7240 state.pop();
7241 Ok(0)
7242 }
7243
7244 #[test]
7249 fn finalizer_registering_new_finalizable_during_close_runs_it() {
7250 CLOSE_GC_RUNS.with(|c| c.set(0));
7251 let mut state = new_state().expect("state should initialize");
7252 install_finalizable_table_with(&mut state, regen_gc_probe);
7253
7254 close(state);
7255 assert_eq!(
7256 CLOSE_GC_RUNS.with(|c| c.get()),
7257 2,
7258 "regen_gc_probe runs once, and the fresh finalizable it registered \
7259 during the drain runs exactly once on a later pass"
7260 );
7261 }
7262
7263 #[test]
7269 fn close_with_live_coroutine_lets_heap_drop() {
7270 let mut state = new_state().expect("state should initialize");
7271 let heap_weak = std::rc::Rc::downgrade(&state.global().heap);
7272 {
7273 let heap = state.global().heap.clone();
7274 let _guard = lua_gc::HeapGuard::push(&heap);
7275 new_thread(&mut state, None).expect("coroutine creation");
7276 }
7277 assert_eq!(state.global().threads.len(), 1);
7278
7279 close(state);
7280 assert!(
7281 heap_weak.upgrade().is_none(),
7282 "the heap must actually drop once the outer state drops after \
7283 close — a lingering threads-map cycle would keep it alive"
7284 );
7285 }
7286
7287 struct VmDropFlag(std::rc::Rc<std::cell::Cell<bool>>);
7290 impl lua_gc::Trace for VmDropFlag {
7291 fn trace(&self, _m: &mut lua_gc::Marker) {}
7292 }
7293 impl Drop for VmDropFlag {
7294 fn drop(&mut self) {
7295 self.0.set(true);
7296 }
7297 }
7298
7299 struct VmAllocOnDrop {
7305 flag: std::rc::Rc<std::cell::Cell<bool>>,
7306 inner: std::rc::Rc<std::cell::Cell<bool>>,
7307 }
7308 impl lua_gc::Trace for VmAllocOnDrop {
7309 fn trace(&self, _m: &mut lua_gc::Marker) {}
7310 }
7311 impl Drop for VmAllocOnDrop {
7312 fn drop(&mut self) {
7313 self.flag.set(true);
7314 let _ = GcRef::new(VmDropFlag(self.inner.clone()));
7315 }
7316 }
7317
7318 #[test]
7319 fn free_all_objects_provides_own_heap_guard_with_no_ambient_guard() {
7320 let mut state = new_state().expect("state should initialize");
7321 let heap = state.global().heap.clone();
7322 let outer = std::rc::Rc::new(std::cell::Cell::new(false));
7323 let inner = std::rc::Rc::new(std::cell::Cell::new(false));
7324 let _gc = heap.allocate(VmAllocOnDrop {
7325 flag: outer.clone(),
7326 inner: inner.clone(),
7327 });
7328
7329 state.gc().free_all_objects();
7330
7331 assert!(outer.get(), "the destructor itself must have run");
7332 assert!(
7333 inner.get(),
7334 "GcRef::new inside a teardown destructor must land in the closing \
7335 heap (via free_all_objects's own guard) and be drained, even with \
7336 no ambient HeapGuard"
7337 );
7338 assert!(heap.is_closed());
7339 }
7340
7341 #[test]
7342 fn free_all_objects_targets_own_heap_under_foreign_guard() {
7343 let mut state1 = new_state().expect("state 1 should initialize");
7344 let state2 = new_state().expect("state 2 should initialize");
7345 let heap1 = state1.global().heap.clone();
7346 let heap2 = state2.global().heap.clone();
7347 let _foreign = lua_gc::HeapGuard::push(&heap2);
7348
7349 let outer = std::rc::Rc::new(std::cell::Cell::new(false));
7350 let inner = std::rc::Rc::new(std::cell::Cell::new(false));
7351 let _gc = heap1.allocate(VmAllocOnDrop {
7352 flag: outer.clone(),
7353 inner: inner.clone(),
7354 });
7355 let heap2_baseline = heap2.allgc_count();
7356
7357 state1.gc().free_all_objects();
7358
7359 assert!(outer.get());
7360 assert!(
7361 inner.get(),
7362 "the teardown allocation must land in (and be drained from) the \
7363 heap being closed, not the foreign heap on the TLS guard stack"
7364 );
7365 assert_eq!(
7366 heap2.allgc_count(),
7367 heap2_baseline,
7368 "the foreign heap must be untouched by another state's close"
7369 );
7370 assert!(heap1.is_closed());
7371 assert!(!heap2.is_closed());
7372 drop(state2);
7373 }
7374
7375 #[test]
7376 fn interned_string_table_grows_then_shrinks_on_collection() {
7377 let mut state = new_state().expect("state should initialize");
7378 let _heap_guard = {
7379 let g = state.global();
7380 lua_gc::HeapGuard::push(&g.heap)
7381 };
7382
7383 let initial_buckets = state.global().interned_lt.bucket_count();
7384 assert_eq!(
7385 initial_buckets, 64,
7386 "the intern table starts at C's MINSTRTABSIZE-equivalent of 64"
7387 );
7388 let baseline_live = state.global().interned_lt.len();
7389
7390 let mut anchors: Vec<GcRef<LuaString>> = Vec::with_capacity(2000);
7391 for i in 0..2000usize {
7392 let key = format!("intern-shrink-probe-{i:08}");
7393 let s = state
7394 .intern_str(key.as_bytes())
7395 .expect("short string interns");
7396 anchors.push(s);
7397 }
7398
7399 let grown_buckets = state.global().interned_lt.bucket_count();
7400 assert_eq!(
7401 state.global().interned_lt.len(),
7402 baseline_live + 2000,
7403 "all 2000 distinct short strings are interned and live alongside \
7404 the runtime's own rooted strings"
7405 );
7406 assert!(
7407 grown_buckets >= 2048,
7408 "interning 2000 strings must force several bucket doublings past \
7409 the initial 64 (saw {grown_buckets})"
7410 );
7411
7412 drop(anchors);
7413 state.gc().full_collect();
7414
7415 let shrunk_buckets = state.global().interned_lt.bucket_count();
7416 let surviving_live = state.global().interned_lt.len();
7417 assert!(
7418 surviving_live <= baseline_live,
7419 "every probe string is unreachable and must be swept out of the \
7420 intern table, leaving at most the runtime's own strings (baseline \
7421 {baseline_live}, surviving {surviving_live})"
7422 );
7423 assert!(
7424 surviving_live < 2000,
7425 "the 2000 probe strings must not survive collection (surviving \
7426 {surviving_live})"
7427 );
7428 assert_eq!(
7429 shrunk_buckets, 64,
7430 "the batch-end shrink check must reclaim the stale buckets back to \
7431 the 64-bucket floor (saw {shrunk_buckets}, grew to {grown_buckets})"
7432 );
7433 assert!(
7434 shrunk_buckets < grown_buckets,
7435 "shrink must strictly reduce the bucket array"
7436 );
7437 }
7438
7439 #[test]
7440 fn table_buffer_accounting_refunds_on_sweep() {
7441 let mut state = new_state().expect("state should initialize");
7442 let _heap_guard = {
7443 let g = state.global();
7444 lua_gc::HeapGuard::push(&g.heap)
7445 };
7446
7447 let table = state.new_table();
7448 let key = state.external_root_value(LuaValue::Table(table));
7449 let header_bytes = state.global().heap.bytes_used();
7450 assert!(header_bytes > 0);
7451
7452 for i in 1..=128 {
7453 table
7454 .raw_set_int(&mut state, i, LuaValue::Int(i))
7455 .expect("integer table insert should succeed");
7456 }
7457 let grown_bytes = state.global().heap.bytes_used();
7458 assert!(
7459 grown_bytes > header_bytes,
7460 "table array/hash buffer growth must be charged to the GC heap"
7461 );
7462
7463 state.gc().full_collect();
7464 assert_eq!(
7465 state.global().heap.bytes_used(),
7466 grown_bytes,
7467 "rooted table buffer bytes should remain charged after collection"
7468 );
7469
7470 assert!(state.external_unroot_value(key).is_some());
7471 state.gc().full_collect();
7472 assert_eq!(state.global().heap.bytes_used(), 0);
7473 assert_eq!(state.global().heap.allgc_count(), 0);
7474 }
7475
7476 #[test]
7477 fn userdata_buffer_accounting_refunds_on_sweep() {
7478 let mut state = new_state().expect("state should initialize");
7479 let _heap_guard = {
7480 let g = state.global();
7481 lua_gc::HeapGuard::push(&g.heap)
7482 };
7483
7484 let payload_len = 4096;
7485 let userdata = state
7486 .new_userdata_typed(b"accounting", payload_len, 3)
7487 .expect("userdata allocation should succeed");
7488 state.pop_n(1);
7489 let key = state.external_root_value(LuaValue::UserData(userdata));
7490 let allocated_bytes = state.global().heap.bytes_used();
7491 assert!(
7492 allocated_bytes > payload_len,
7493 "userdata payload bytes must be charged to the GC heap"
7494 );
7495
7496 state.gc().full_collect();
7497 assert_eq!(
7498 state.global().heap.bytes_used(),
7499 allocated_bytes,
7500 "rooted userdata payload bytes should remain charged after collection"
7501 );
7502
7503 assert!(state.external_unroot_value(key).is_some());
7504 state.gc().full_collect();
7505 assert_eq!(state.global().heap.bytes_used(), 0);
7506 assert_eq!(state.global().heap.allgc_count(), 0);
7507 }
7508
7509 #[test]
7510 fn cclosure_upvalue_accounting_refunds_on_sweep() {
7511 let mut state = new_state().expect("state should initialize");
7512 let _heap_guard = {
7513 let g = state.global();
7514 lua_gc::HeapGuard::push(&g.heap)
7515 };
7516
7517 let nupvalues = 64;
7518 for i in 0..nupvalues {
7519 state.push(LuaValue::Int(i as i64));
7520 }
7521 crate::api::push_cclosure(&mut state, test_noop_cclosure, nupvalues as i32)
7522 .expect("C closure creation should succeed");
7523 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7524 panic!("expected heavy C closure");
7525 };
7526 let expected_payload = ccl.buffer_bytes();
7527 let key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
7528 state.pop_n(1);
7529 let allocated_bytes = state.global().heap.bytes_used();
7530 assert!(
7531 allocated_bytes >= expected_payload,
7532 "C closure upvalue vector bytes must be charged to the GC heap"
7533 );
7534
7535 state.gc().full_collect();
7536 assert_eq!(
7537 state.global().heap.bytes_used(),
7538 allocated_bytes,
7539 "rooted C closure payload bytes should remain charged after collection"
7540 );
7541
7542 assert!(state.external_unroot_value(key).is_some());
7543 state.gc().full_collect();
7544 assert_eq!(state.global().heap.bytes_used(), 0);
7545 assert_eq!(state.global().heap.allgc_count(), 0);
7546 }
7547
7548 #[test]
7549 fn proto_and_lclosure_accounting_refunds_on_sweep() {
7550 let mut state = new_state().expect("state should initialize");
7551 let _heap_guard = {
7552 let g = state.global();
7553 lua_gc::HeapGuard::push(&g.heap)
7554 };
7555
7556 let mut proto = LuaProto::placeholder();
7557 proto.code = vec![lua_types::opcode::Instruction(0); 2048];
7558 proto.lineinfo = vec![0; 2048];
7559 proto.k = vec![LuaValue::Int(1); 512];
7560 let expected_proto_payload = proto.buffer_bytes();
7561 let proto = GcRef::new(proto);
7562 proto.account_buffer(expected_proto_payload as isize);
7563
7564 let closure = state.new_lclosure(proto, 16);
7565 let expected_closure_payload = closure.buffer_bytes();
7566 let key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7567 let allocated_bytes = state.global().heap.bytes_used();
7568 assert!(
7569 allocated_bytes >= expected_proto_payload + expected_closure_payload,
7570 "proto and Lua closure vector bytes must be charged to the GC heap"
7571 );
7572
7573 state.gc().full_collect();
7574 assert_eq!(
7575 state.global().heap.bytes_used(),
7576 allocated_bytes,
7577 "rooted proto and Lua closure payload bytes should remain charged after collection"
7578 );
7579
7580 assert!(state.external_unroot_value(key).is_some());
7581 state.gc().full_collect();
7582 assert_eq!(state.global().heap.bytes_used(), 0);
7583 assert_eq!(state.global().heap.allgc_count(), 0);
7584 }
7585
7586 #[test]
7587 fn string_buffer_accounting_refunds_on_sweep() {
7588 let mut state = new_state().expect("state should initialize");
7589 let _heap_guard = {
7590 let g = state.global();
7591 lua_gc::HeapGuard::push(&g.heap)
7592 };
7593
7594 let payload = vec![b'x'; crate::string::MAX_SHORT_LEN + 4096];
7595 let string = state
7596 .intern_str(&payload)
7597 .expect("long string should allocate");
7598 let key = state.external_root_value(LuaValue::Str(string));
7599 let allocated_bytes = state.global().heap.bytes_used();
7600 assert!(
7601 allocated_bytes > payload.len(),
7602 "long string backing bytes must be charged to the GC heap"
7603 );
7604
7605 state.gc().full_collect();
7606 assert_eq!(
7607 state.global().heap.bytes_used(),
7608 allocated_bytes,
7609 "rooted string buffer bytes should remain charged after collection"
7610 );
7611
7612 assert!(state.external_unroot_value(key).is_some());
7613 state.gc().full_collect();
7614 assert_eq!(state.global().heap.bytes_used(), 0);
7615 assert_eq!(state.global().heap.allgc_count(), 0);
7616 }
7617
7618 #[test]
7619 fn interned_short_string_cache_does_not_root_unreferenced_string() {
7620 let mut state = new_state().expect("state should initialize");
7621 let _heap_guard = {
7622 let g = state.global();
7623 lua_gc::HeapGuard::push(&g.heap)
7624 };
7625
7626 let payload = b"weak-cache-probe-a";
7627 let string = state
7628 .intern_str(payload)
7629 .expect("short string should intern");
7630 let id = string.identity();
7631 assert!(state.global().interned_lt.contains_key(&payload[..]));
7632 assert_eq!(
7633 state.global().heap.register_allocation_token(id),
7634 state.global().heap.register_allocation_token(id),
7635 "token registration is get-or-insert while the string is provably live"
7636 );
7637 assert!(state.global().heap.allocation_token(id).is_some());
7638
7639 state.gc().full_collect();
7640 assert!(!state.global().interned_lt.contains_key(&payload[..]));
7641 assert_eq!(state.global().heap.allocation_token(id), None);
7642 }
7643
7644 #[test]
7645 fn interned_short_string_cache_keeps_reachable_string_until_unrooted() {
7646 let mut state = new_state().expect("state should initialize");
7647 let _heap_guard = {
7648 let g = state.global();
7649 lua_gc::HeapGuard::push(&g.heap)
7650 };
7651
7652 let payload = b"weak-cache-probe-b";
7653 let string = state
7654 .intern_str(payload)
7655 .expect("short string should intern");
7656 let id = string.identity();
7657 state.global().heap.register_allocation_token(id);
7658 let key = state.external_root_value(LuaValue::Str(string));
7659
7660 state.gc().full_collect();
7661 assert!(state.global().interned_lt.contains_key(&payload[..]));
7662 assert!(state.global().heap.allocation_token(id).is_some());
7663
7664 assert!(state.external_unroot_value(key).is_some());
7665 state.gc().full_collect();
7666 assert!(!state.global().interned_lt.contains_key(&payload[..]));
7667 assert_eq!(state.global().heap.allocation_token(id), None);
7668 }
7669
7670 #[test]
7671 fn gc_phase_predicates_follow_heap_state() {
7672 let mut state = new_state().expect("state should initialize");
7673 let _heap_guard = {
7674 let g = state.global();
7675 lua_gc::HeapGuard::push(&g.heap)
7676 };
7677
7678 {
7679 let mut g = state.global_mut();
7680 g.gckind = GcKind::Incremental as u8;
7681 g.lastatomic = 0;
7682 assert!(!g.is_gen_mode());
7683 g.lastatomic = 1;
7684 assert!(g.is_gen_mode());
7685 g.lastatomic = 0;
7686 }
7687
7688 let mut roots = Vec::new();
7689 for _ in 0..16 {
7690 let table = state.new_table();
7691 roots.push(state.external_root_value(LuaValue::Table(table)));
7692 }
7693
7694 let mut saw_keep = false;
7695 let mut saw_sweep = false;
7696 for _ in 0..128 {
7697 state.gc().incremental_step(1);
7698 let g = state.global();
7699 let heap_state = g.heap.gc_state();
7700 assert_eq!(g.keep_invariant(), heap_state.is_invariant());
7701 assert_eq!(g.is_sweep_phase(), heap_state.is_sweep());
7702 saw_keep |= g.keep_invariant();
7703 saw_sweep |= g.is_sweep_phase();
7704 if heap_state.is_pause() && saw_keep && saw_sweep {
7705 break;
7706 }
7707 }
7708
7709 assert!(
7710 saw_keep,
7711 "incremental cycle should expose an invariant phase"
7712 );
7713 assert!(saw_sweep, "incremental cycle should expose a sweep phase");
7714
7715 for key in roots {
7716 assert!(state.external_unroot_value(key).is_some());
7717 }
7718 state.gc().full_collect();
7719 }
7720
7721 #[test]
7722 fn gc_barrier_keeps_new_child_stored_in_black_parent() {
7723 let mut state = new_state().expect("state should initialize");
7724 let _heap_guard = {
7725 let g = state.global();
7726 lua_gc::HeapGuard::push(&g.heap)
7727 };
7728
7729 let parent = state.new_table();
7730 let parent_key = state.external_root_value(LuaValue::Table(parent));
7731 state.gc().incremental_step(1);
7732 assert!(
7733 state.global().keep_invariant(),
7734 "test setup should leave the parent marked during an active cycle"
7735 );
7736
7737 let child = state.new_table();
7738 let parent_value = LuaValue::Table(parent);
7739 let child_value = LuaValue::Table(child);
7740 parent
7741 .raw_set_int(&mut state, 1, child_value)
7742 .expect("table store should succeed");
7743 state.gc_barrier_back(&parent_value, &child_value);
7744
7745 for _ in 0..128 {
7746 if state.gc().incremental_step(1) {
7747 break;
7748 }
7749 }
7750
7751 assert_eq!(state.global().heap.allgc_count(), 2);
7752 assert_eq!(
7753 parent.get_int(1).as_table().map(|t| t.identity()),
7754 Some(child.identity())
7755 );
7756
7757 assert!(state.external_unroot_value(parent_key).is_some());
7758 state.gc().full_collect();
7759 assert_eq!(state.global().heap.allgc_count(), 0);
7760 }
7761
7762 #[test]
7763 fn generational_mode_promotes_and_barriers_age_objects() {
7764 let mut state = new_state().expect("state should initialize");
7765 let _heap_guard = {
7766 let g = state.global();
7767 lua_gc::HeapGuard::push(&g.heap)
7768 };
7769
7770 let parent = state.new_table();
7771 let parent_key = state.external_root_value(LuaValue::Table(parent));
7772
7773 state.gc().change_mode(GcKind::Generational);
7774 assert_eq!(parent.0.age(), lua_gc::GcAge::Old);
7775 assert_eq!(parent.0.color(), lua_gc::Color::Black);
7776 let majorbase = state.global().gc_estimate;
7777 assert!(majorbase > 0);
7778 assert!(state.global().gc_debt() <= 0);
7779
7780 let child = state.new_table();
7781 let parent_value = LuaValue::Table(parent);
7782 let child_value = LuaValue::Table(child);
7783 parent
7784 .raw_set_int(&mut state, 1, child_value.clone())
7785 .expect("table store should succeed");
7786 state.gc_barrier_back(&parent_value, &child_value);
7787 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched1);
7788 assert_eq!(parent.0.color(), lua_gc::Color::Gray);
7789 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7790
7791 let metatable = state.new_table();
7792 parent.set_metatable(Some(metatable));
7793 state.gc().obj_barrier(&parent, &metatable);
7794 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old0);
7795
7796 assert!(state.gc().generational_step_minor_only());
7797 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched2);
7798 assert_eq!(child.0.age(), lua_gc::GcAge::Survival);
7799 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old1);
7800 assert_eq!(state.global().gc_estimate, majorbase);
7801 assert!(state.global().gc_debt() <= 0);
7802
7803 state.gc().change_mode(GcKind::Incremental);
7804 assert_eq!(parent.0.age(), lua_gc::GcAge::New);
7805 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7806 assert_eq!(metatable.0.age(), lua_gc::GcAge::New);
7807
7808 assert!(state.external_unroot_value(parent_key).is_some());
7809 state.gc().full_collect();
7810 }
7811
7812 #[test]
7813 fn generational_upvalue_write_barrier_marks_young_child_old0() {
7814 let mut state = new_state().expect("state should initialize");
7815 let _heap_guard = {
7816 let g = state.global();
7817 lua_gc::HeapGuard::push(&g.heap)
7818 };
7819
7820 let proto = state.new_proto();
7821 let closure = state.new_lclosure(proto, 1);
7822 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7823 state.gc().change_mode(GcKind::Generational);
7824 let uv = closure.upval(0);
7825 assert_eq!(uv.0.age(), lua_gc::GcAge::Old);
7826
7827 let child = state.new_table();
7828 state
7829 .upvalue_set(&closure, 0, LuaValue::Table(child))
7830 .expect("closed upvalue write should succeed");
7831 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7832
7833 assert!(state.external_unroot_value(closure_key).is_some());
7834 state.gc().full_collect();
7835 }
7836
7837 #[test]
7838 fn cclosure_setupvalue_replaces_upvalue() {
7839 let mut state = new_state().expect("state should initialize");
7840 let _heap_guard = {
7841 let g = state.global();
7842 lua_gc::HeapGuard::push(&g.heap)
7843 };
7844
7845 let first = state.new_table();
7846 state.push(LuaValue::Table(first));
7847 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7848 .expect("C closure creation should succeed");
7849 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7850 panic!("expected heavy C closure");
7851 };
7852
7853 let second = state.new_table();
7854 state.push(LuaValue::Table(second));
7855 let name =
7856 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7857
7858 assert!(name.is_empty());
7859 let upvalues = ccl.upvalues.borrow();
7860 let LuaValue::Table(actual) = upvalues[0].clone() else {
7861 panic!("expected table upvalue");
7862 };
7863 assert_eq!(actual.identity(), second.identity());
7864 }
7865
7866 #[test]
7867 fn generational_cclosure_setupvalue_barrier_marks_young_child_old0() {
7868 let mut state = new_state().expect("state should initialize");
7869 let _heap_guard = {
7870 let g = state.global();
7871 lua_gc::HeapGuard::push(&g.heap)
7872 };
7873
7874 state.push(LuaValue::Nil);
7875 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7876 .expect("C closure creation should succeed");
7877 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7878 panic!("expected heavy C closure");
7879 };
7880 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
7881
7882 state.gc().change_mode(GcKind::Generational);
7883 assert_eq!(ccl.0.age(), lua_gc::GcAge::Old);
7884
7885 let child = state.new_table();
7886 state.push(LuaValue::Table(child));
7887 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7888
7889 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7890
7891 assert!(state.external_unroot_value(closure_key).is_some());
7892 state.gc().full_collect();
7893 }
7894
7895 #[test]
7896 fn generational_closure_upvalue_slot_barrier_marks_new_upval_old0() {
7897 let mut state = new_state().expect("state should initialize");
7898 let _heap_guard = {
7899 let g = state.global();
7900 lua_gc::HeapGuard::push(&g.heap)
7901 };
7902
7903 let proto = state.new_proto();
7904 let closure = state.new_lclosure(proto, 1);
7905 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7906 state.gc().change_mode(GcKind::Generational);
7907 assert_eq!(closure.0.age(), lua_gc::GcAge::Old);
7908
7909 let replacement = state.new_upval_closed(LuaValue::Nil);
7910 closure.set_upval(0, replacement);
7911 state.gc().obj_barrier(&closure, &replacement);
7912 assert_eq!(replacement.0.age(), lua_gc::GcAge::Old0);
7913
7914 assert!(state.external_unroot_value(closure_key).is_some());
7915 state.gc().full_collect();
7916 }
7917
7918 #[test]
7919 fn cross_thread_upvalue_mirror_traces_values_as_roots() {
7920 let mut state = new_state().expect("state should initialize");
7921 let _heap_guard = {
7922 let g = state.global();
7923 lua_gc::HeapGuard::push(&g.heap)
7924 };
7925
7926 let mirrored = state.new_table();
7927 state
7928 .global_mut()
7929 .cross_thread_upvals
7930 .insert((999, StackIdx(0)), LuaValue::Table(mirrored));
7931
7932 state.gc().full_collect();
7933 assert_eq!(state.global().heap.allgc_count(), 1);
7934
7935 state.global_mut().cross_thread_upvals.clear();
7936 state.gc().full_collect();
7937 assert_eq!(state.global().heap.allgc_count(), 0);
7938 }
7939
7940 #[test]
7941 fn generational_full_collect_promotes_new_survivors_to_old() {
7942 let mut state = new_state().expect("state should initialize");
7943 let _heap_guard = {
7944 let g = state.global();
7945 lua_gc::HeapGuard::push(&g.heap)
7946 };
7947
7948 state.gc().change_mode(GcKind::Generational);
7949 let table = state.new_table();
7950 let table_key = state.external_root_value(LuaValue::Table(table));
7951 assert_eq!(table.0.age(), lua_gc::GcAge::New);
7952
7953 state.gc().full_collect();
7954 assert_eq!(table.0.age(), lua_gc::GcAge::Old);
7955 assert_eq!(table.0.color(), lua_gc::Color::Black);
7956
7957 assert!(state.external_unroot_value(table_key).is_some());
7958 state.gc().full_collect();
7959 }
7960
7961 #[test]
7962 fn gc_packed_params_return_user_visible_values() {
7963 let mut state = new_state().expect("state should initialize");
7964 assert_eq!(
7965 crate::api::gc(&mut state, crate::api::GcArgs::SetPause { value: 200 }),
7966 200
7967 );
7968 assert_eq!(state.global().gc_pause_param(), 200);
7969 assert_eq!(
7970 crate::api::gc(&mut state, crate::api::GcArgs::SetStepMul { value: 200 }),
7971 100
7972 );
7973 assert_eq!(state.global().gc_stepmul_param(), 200);
7974
7975 crate::api::gc(
7976 &mut state,
7977 crate::api::GcArgs::Gen {
7978 minormul: 0,
7979 majormul: 200,
7980 },
7981 );
7982 assert_eq!(state.global().gc_genmajormul_param(), 200);
7983 }
7984
7985 #[test]
7986 fn generational_step_runs_bad_major_when_growth_exceeds_genmajormul() {
7987 let mut state = new_state().expect("state should initialize");
7988 let _heap_guard = {
7989 let g = state.global();
7990 lua_gc::HeapGuard::push(&g.heap)
7991 };
7992
7993 let root = state.new_table();
7994 let root_key = state.external_root_value(LuaValue::Table(root));
7995 state.gc().change_mode(GcKind::Generational);
7996
7997 let root_value = LuaValue::Table(root);
7998 for i in 1..=64 {
7999 let child = state.new_table();
8000 let child_value = LuaValue::Table(child);
8001 root.raw_set_int(&mut state, i, child_value.clone())
8002 .expect("table store should succeed");
8003 state.gc_barrier_back(&root_value, &child_value);
8004 }
8005
8006 {
8007 let mut g = state.global_mut();
8008 g.gc_estimate = 1;
8009 set_debt(&mut *g, 1);
8010 }
8011
8012 assert!(state.gc().generational_step());
8013 let g = state.global();
8014 assert!(g.is_gen_mode());
8015 assert!(
8016 g.lastatomic > 0,
8017 "bad major collection should arm stepgenfull"
8018 );
8019 assert!(g.gc_estimate > 1);
8020 assert!(g.gc_debt() <= 0);
8021 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
8022 drop(g);
8023
8024 assert!(state.external_unroot_value(root_key).is_some());
8025 state.gc().full_collect();
8026 }
8027
8028 #[test]
8029 fn generational_implicit_step_runs_major_when_heap_threshold_exceeded() {
8030 let mut state = new_state().expect("state should initialize");
8031 let _heap_guard = {
8032 let g = state.global();
8033 lua_gc::HeapGuard::push(&g.heap)
8034 };
8035
8036 let root = state.new_table();
8037 let root_key = state.external_root_value(LuaValue::Table(root));
8038 state.gc().change_mode(GcKind::Generational);
8039
8040 let root_value = LuaValue::Table(root);
8041 for i in 1..=64 {
8042 let child = state.new_table();
8043 let child_value = LuaValue::Table(child);
8044 root.raw_set_int(&mut state, i, child_value.clone())
8045 .expect("table store should succeed");
8046 state.gc_barrier_back(&root_value, &child_value);
8047 }
8048
8049 {
8050 let mut g = state.global_mut();
8051 g.gc_estimate = 1;
8052 set_debt(&mut *g, -1);
8053 g.heap.set_threshold_bytes(1);
8054 }
8055
8056 assert!(state.gc().generational_step());
8057 let g = state.global();
8058 assert!(g.is_gen_mode());
8059 assert!(
8060 g.lastatomic > 0,
8061 "implicit threshold-triggered growth should arm a bad major"
8062 );
8063 assert!(g.gc_debt() <= 0);
8064 drop(g);
8065
8066 assert!(state.external_unroot_value(root_key).is_some());
8067 state.gc().full_collect();
8068 }
8069
8070 #[test]
8071 fn generational_stepgenfull_returns_to_gen_after_good_collection() {
8072 let mut state = new_state().expect("state should initialize");
8073 let _heap_guard = {
8074 let g = state.global();
8075 lua_gc::HeapGuard::push(&g.heap)
8076 };
8077
8078 let root = state.new_table();
8079 let root_key = state.external_root_value(LuaValue::Table(root));
8080 state.gc().change_mode(GcKind::Generational);
8081 {
8082 let mut g = state.global_mut();
8083 g.lastatomic = 1024;
8084 }
8085
8086 assert!(state.gc().generational_step());
8087 let g = state.global();
8088 assert_eq!(g.gckind, GcKind::Generational as u8);
8089 assert_eq!(g.lastatomic, 0);
8090 assert!(g.gc_debt() <= 0);
8091 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
8092 assert_eq!(root.0.color(), lua_gc::Color::Black);
8093 drop(g);
8094
8095 assert!(state.external_unroot_value(root_key).is_some());
8096 state.gc().full_collect();
8097 }
8098
8099 #[test]
8100 fn generational_step_zero_reports_false_without_positive_debt() {
8101 let mut state = new_state().expect("state should initialize");
8102 let _heap_guard = {
8103 let g = state.global();
8104 lua_gc::HeapGuard::push(&g.heap)
8105 };
8106
8107 state.gc().change_mode(GcKind::Generational);
8108 assert_eq!(
8109 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 0 }),
8110 0
8111 );
8112 assert_eq!(
8113 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 1 }),
8114 1
8115 );
8116 }
8117}