1use std::cell::RefCell;
22use std::hash::{BuildHasherDefault, Hasher};
23use std::rc::Rc;
24
25use crate::string::StringPool;
26pub use lua_types::error::LuaError;
27pub use lua_types::{CallInfoIdx, StackIdx};
28
29pub struct StackIdxConv(pub StackIdx);
32
33#[inline(always)]
38pub fn stack_idx_to_i32(i: StackIdx) -> i32 {
39 i.0 as i32
40}
41
42impl From<u32> for StackIdxConv {
43 #[inline(always)]
44 fn from(v: u32) -> Self {
45 StackIdxConv(StackIdx(v))
46 }
47}
48impl From<i32> for StackIdxConv {
49 #[inline(always)]
50 fn from(v: i32) -> Self {
51 StackIdxConv(StackIdx(v.max(0) as u32))
52 }
53}
54impl From<usize> for StackIdxConv {
55 #[inline(always)]
56 fn from(v: usize) -> Self {
57 StackIdxConv(StackIdx(v as u32))
58 }
59}
60impl From<StackIdx> for StackIdxConv {
61 #[inline(always)]
62 fn from(v: StackIdx) -> Self {
63 StackIdxConv(v)
64 }
65}
66pub use lua_types::closure::{
67 LuaCClosure as LuaClosureC, LuaCFnPtr, LuaClosure, LuaLClosure as LuaClosureLua,
68};
69pub use lua_types::gc::GcRef;
70pub use lua_types::proto::LuaProto;
71pub use lua_types::string::LuaString;
72pub use lua_types::upval::UpVal;
73pub use lua_types::userdata::LuaUserData;
74pub use lua_types::value::{F2Imod, LuaTable, LuaValue};
75
76pub struct LuaByteHasher {
77 hash: u64,
78}
79
80impl Default for LuaByteHasher {
81 fn default() -> Self {
82 Self {
83 hash: 0xcbf2_9ce4_8422_2325,
84 }
85 }
86}
87
88impl Hasher for LuaByteHasher {
89 #[inline]
90 fn write(&mut self, bytes: &[u8]) {
91 const PRIME: u64 = 0x0000_0100_0000_01b3;
92 for &byte in bytes {
93 self.hash ^= u64::from(byte);
94 self.hash = self.hash.wrapping_mul(PRIME);
95 }
96 }
97
98 #[inline]
99 fn write_u8(&mut self, i: u8) {
100 self.write(&[i]);
101 }
102
103 #[inline]
104 fn write_usize(&mut self, i: usize) {
105 self.write(&i.to_ne_bytes());
106 }
107
108 #[inline]
109 fn finish(&self) -> u64 {
110 self.hash
111 }
112}
113
114pub type LuaByteBuildHasher = BuildHasherDefault<LuaByteHasher>;
115
116pub struct InternedStringMap {
131 buckets: Vec<Vec<GcRef<LuaString>>>,
132 count: usize,
133}
134
135impl Default for InternedStringMap {
136 fn default() -> Self {
137 InternedStringMap {
138 buckets: (0..64).map(|_| Vec::new()).collect(),
139 count: 0,
140 }
141 }
142}
143
144impl InternedStringMap {
145 #[inline]
146 fn mask(&self) -> usize {
147 self.buckets.len() - 1
148 }
149
150 pub fn len(&self) -> usize {
151 self.count
152 }
153
154 pub fn is_empty(&self) -> bool {
155 self.count == 0
156 }
157
158 pub fn bucket_count(&self) -> usize {
163 self.buckets.len()
164 }
165
166 #[inline]
167 pub fn find(&self, bytes: &[u8], hash: u32) -> Option<GcRef<LuaString>> {
168 let bucket = &self.buckets[hash as usize & self.mask()];
169 bucket
170 .iter()
171 .find(|s| s.hash() == hash && s.as_bytes() == bytes)
172 .cloned()
173 }
174
175 pub fn insert(&mut self, s: GcRef<LuaString>) {
177 if self.count >= self.buckets.len() {
178 self.resize(self.buckets.len() * 2);
179 }
180 let m = self.mask();
181 self.buckets[s.hash() as usize & m].push(s);
182 self.count += 1;
183 }
184
185 fn resize(&mut self, new_len: usize) {
186 let old = std::mem::replace(
187 &mut self.buckets,
188 (0..new_len).map(|_| Vec::new()).collect(),
189 );
190 let m = self.mask();
191 for bucket in old {
192 for s in bucket {
193 self.buckets[s.hash() as usize & m].push(s);
194 }
195 }
196 }
197
198 pub fn shrink_if_sparse(&mut self) {
210 if self.count * 4 >= self.buckets.len() {
211 return;
212 }
213 let target = self.count.next_power_of_two().max(64);
214 if target < self.buckets.len() {
215 self.resize(target);
216 }
217 }
218
219 pub fn remove(&mut self, hash: u32, identity: usize) {
221 let m = self.mask();
222 let bucket = &mut self.buckets[hash as usize & m];
223 if let Some(pos) = bucket.iter().position(|s| s.identity() == identity) {
224 bucket.swap_remove(pos);
225 self.count -= 1;
226 }
227 }
228
229 pub fn iter(&self) -> impl Iterator<Item = &GcRef<LuaString>> {
230 self.buckets.iter().flatten()
231 }
232
233 pub fn contains_key(&self, bytes: &[u8]) -> bool {
234 self.find(bytes, LuaString::hash_bytes(bytes, 0)).is_some()
235 }
236}
237
238pub type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
245
246pub type LuaRustFunction = Rc<dyn Fn(&mut LuaState) -> Result<usize, LuaError>>;
247
248#[derive(Clone)]
249pub enum LuaCallable {
250 Bare(LuaCFunction),
251 Rust(LuaRustFunction),
252}
253
254impl std::fmt::Debug for LuaCallable {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 match self {
257 LuaCallable::Bare(_) => f.write_str("LuaCallable::Bare(..)"),
258 LuaCallable::Rust(_) => f.write_str("LuaCallable::Rust(..)"),
259 }
260 }
261}
262
263impl LuaCallable {
264 pub fn bare(f: LuaCFunction) -> Self {
265 LuaCallable::Bare(f)
266 }
267
268 pub fn rust(f: LuaRustFunction) -> Self {
269 LuaCallable::Rust(f)
270 }
271
272 pub fn as_bare(&self) -> Option<LuaCFunction> {
273 match self {
274 LuaCallable::Bare(f) => Some(*f),
275 LuaCallable::Rust(_) => None,
276 }
277 }
278
279 pub fn call(&self, state: &mut LuaState) -> Result<usize, LuaError> {
280 match self {
281 LuaCallable::Bare(f) => f(state),
282 LuaCallable::Rust(f) => f(state),
283 }
284 }
285}
286
287#[derive(Clone, Debug)]
288pub enum FinalizerObject {
289 Table(GcRef<LuaTable>),
290 UserData(GcRef<LuaUserData>),
291}
292
293impl FinalizerObject {
294 pub fn identity(&self) -> usize {
295 match self {
296 FinalizerObject::Table(t) => t.identity(),
297 FinalizerObject::UserData(u) => u.identity(),
298 }
299 }
300
301 pub fn metatable(&self) -> Option<GcRef<LuaTable>> {
302 match self {
303 FinalizerObject::Table(t) => t.metatable(),
304 FinalizerObject::UserData(u) => u.metatable(),
305 }
306 }
307
308 pub fn as_lua_value(&self) -> LuaValue {
309 match self {
310 FinalizerObject::Table(t) => LuaValue::Table(t.clone()),
311 FinalizerObject::UserData(u) => LuaValue::UserData(u.clone()),
312 }
313 }
314
315 pub fn mark(&self, marker: &mut lua_gc::Marker) {
316 match self {
317 FinalizerObject::Table(t) => marker.mark(t.0),
318 FinalizerObject::UserData(u) => marker.mark(u.0),
319 }
320 }
321
322 pub fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
323 Some(match self {
324 FinalizerObject::Table(t) => t.0.as_trace_ptr(),
325 FinalizerObject::UserData(u) => u.0.as_trace_ptr(),
326 })
327 }
328
329 pub fn age(&self) -> lua_gc::GcAge {
330 match self {
331 FinalizerObject::Table(t) => t.0.age(),
332 FinalizerObject::UserData(u) => u.0.age(),
333 }
334 }
335
336 pub fn is_finalized(&self) -> bool {
337 match self {
338 FinalizerObject::Table(t) => t.0.is_finalized(),
339 FinalizerObject::UserData(u) => u.0.is_finalized(),
340 }
341 }
342
343 pub fn set_finalized(&self, finalized: bool) {
344 match self {
345 FinalizerObject::Table(t) => t.0.set_finalized(finalized),
346 FinalizerObject::UserData(u) => u.0.set_finalized(finalized),
347 }
348 }
349}
350
351impl lua_gc::FinalizerEntry for FinalizerObject {
352 fn identity(&self) -> usize {
353 FinalizerObject::identity(self)
354 }
355
356 fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
357 FinalizerObject::heap_ptr(self)
358 }
359
360 fn age(&self) -> lua_gc::GcAge {
361 FinalizerObject::age(self)
362 }
363
364 fn is_finalized(&self) -> bool {
365 FinalizerObject::is_finalized(self)
366 }
367
368 fn set_finalized(&self, finalized: bool) {
369 FinalizerObject::set_finalized(self, finalized);
370 }
371}
372
373#[derive(Clone, Debug)]
374pub struct WeakTableEntry {
375 table: lua_types::gc::GcWeak<LuaTable>,
376 kind: lua_gc::WeakListKind,
377}
378
379impl WeakTableEntry {
380 pub fn new(table: &GcRef<LuaTable>) -> Self {
381 let mode = table.weak_mode();
382 let weak_keys = (mode & (1 << 0)) != 0;
383 let weak_values = (mode & (1 << 1)) != 0;
384 let kind = match (weak_keys, weak_values) {
385 (true, true) => lua_gc::WeakListKind::AllWeak,
386 (true, false) => lua_gc::WeakListKind::Ephemeron,
387 (false, true) => lua_gc::WeakListKind::WeakValues,
388 (false, false) => lua_gc::WeakListKind::WeakValues,
389 };
390 Self {
391 table: table.downgrade(),
392 kind,
393 }
394 }
395}
396
397impl lua_gc::WeakEntry for WeakTableEntry {
398 type Strong = GcRef<LuaTable>;
399
400 fn identity(&self) -> usize {
401 self.table.identity()
402 }
403
404 fn list_kind(&self) -> lua_gc::WeakListKind {
405 self.kind
406 }
407
408 fn upgrade(&self) -> Option<Self::Strong> {
409 self.table.upgrade()
410 }
411}
412
413pub(crate) const EXTRA_STACK: usize = 5;
417
418pub(crate) const LUA_MINSTACK: usize = 20;
420
421pub(crate) const BASIC_STACK_SIZE: usize = 2 * LUA_MINSTACK;
423
424pub(crate) const LUAI_MAXCCALLS: u32 = 200;
444
445pub(crate) const CIST_C: u16 = 1 << 1;
447
448pub(crate) const CIST_OAH: u16 = 1 << 0;
450pub(crate) const CIST_FRESH: u16 = 1 << 2;
451pub(crate) const CIST_HOOKED: u16 = 1 << 3;
452pub(crate) const CIST_YPCALL: u16 = 1 << 4;
453pub(crate) const CIST_TAIL: u16 = 1 << 5;
454pub(crate) const CIST_HOOKYIELD: u16 = 1 << 6;
455pub(crate) const CIST_FIN: u16 = 1 << 7;
456pub(crate) const CIST_TRAN: u16 = 1 << 8;
457pub(crate) const CIST_RECST: u32 = 10;
458pub(crate) const CIST_LEQ: u16 = 1 << 13;
465
466pub(crate) const CIST_TRAP: u16 = 1 << 14;
474
475const LUA_NUMTYPES: usize = 9;
477
478const GCSTPUSR: u8 = 1;
480const GCSTPGC: u8 = 2;
481
482const GCS_PAUSE: u8 = 0;
484
485const LUAI_GCPAUSE: u32 = 200;
486const LUAI_GCMUL: u32 = 100;
487const LUAI_GCSTEPSIZE: u8 = 13;
488const LUAI_GENMAJORMUL: u32 = 100;
489const LUAI_GENMINORMUL: u8 = 20;
490
491const WHITE0BIT: u8 = 0;
492
493const STRCACHE_N: usize = 53;
494const STRCACHE_M: usize = 2;
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
502pub enum GcKind {
503 Incremental = 0,
504 Generational = 1,
505}
506
507#[derive(Debug, Clone, Copy, PartialEq, Eq)]
515pub enum WarnMode {
516 Off,
517 On,
518 Cont,
519}
520
521#[derive(Debug, Clone, Copy, PartialEq, Eq)]
523pub enum TestWarnMode {
524 Normal,
525 Allow,
526 Store,
527}
528
529pub use lua_types::status::LuaStatus;
534
535#[derive(Clone)]
550pub struct StackValue {
551 pub val: LuaValue,
552}
553
554impl Default for StackValue {
555 fn default() -> Self {
556 StackValue {
557 val: LuaValue::Nil,
558 }
559 }
560}
561
562#[derive(Clone)]
571pub struct CallInfo {
572 pub func: StackIdx,
574
575 pub top: StackIdx,
577
578 pub previous: Option<CallInfoIdx>,
580
581 pub next: Option<CallInfoIdx>,
583
584 pub u: CallInfoFrame,
585
586 pub u2: CallInfoExtra,
587
588 pub nresults: i16,
590
591 pub callstatus: u16,
593
594 pub call_metamethods: u8,
598
599 pub tailcalls: u16,
608}
609
610#[cfg(target_pointer_width = "64")]
619const _: () = {
620 assert!(core::mem::size_of::<CallInfoFrame>() == 32);
621 assert!(core::mem::size_of::<CallInfo>() == 72);
622};
623
624#[derive(Clone, Copy)]
641pub struct CallInfoFrame {
642 pub savedpc: u32,
645 pub nextraargs: i32,
648 pub k: Option<LuaKFunction>,
651 pub old_errfunc: isize,
654 pub ctx: isize,
657}
658
659pub type LuaKFunction = fn(&mut LuaState, status: i32, ctx: isize) -> Result<usize, LuaError>;
661
662#[derive(Default, Clone, Copy)]
666pub struct CallInfoExtra {
667 pub value: i32,
668 pub ftransfer: u16,
669 pub ntransfer: u16,
670}
671
672impl CallInfoFrame {
673 pub fn c_default() -> Self {
677 CallInfoFrame {
678 savedpc: 0,
679 nextraargs: 0,
680 k: None,
681 old_errfunc: 0,
682 ctx: 0,
683 }
684 }
685
686 pub fn lua_default() -> Self {
693 CallInfoFrame {
694 savedpc: 0,
695 nextraargs: 0,
696 k: None,
697 old_errfunc: 0,
698 ctx: 0,
699 }
700 }
701}
702
703impl Default for CallInfo {
704 fn default() -> Self {
705 CallInfo {
706 func: StackIdx(0),
707 top: StackIdx(0),
708 previous: None,
709 next: None,
710 u: CallInfoFrame::c_default(),
711 u2: CallInfoExtra::default(),
712 nresults: 0,
713 callstatus: 0,
714 call_metamethods: 0,
715 tailcalls: 0,
716 }
717 }
718}
719
720impl CallInfo {
721 pub fn is_lua(&self) -> bool {
722 (self.callstatus & CIST_C) == 0
723 }
724 pub fn is_lua_code(&self) -> bool {
725 self.is_lua()
726 }
727 pub fn is_vararg_func(&self) -> bool {
734 false
735 }
736 pub fn saved_pc(&self) -> u32 {
741 debug_assert!(self.is_lua(), "saved_pc on a C frame");
742 self.u.savedpc
743 }
744 pub fn set_saved_pc(&mut self, pc: u32) {
746 debug_assert!(self.is_lua(), "set_saved_pc on a C frame");
747 self.u.savedpc = pc;
748 }
749 pub fn nextra_args(&self) -> i32 {
751 debug_assert!(self.is_lua(), "nextra_args on a C frame");
752 self.u.nextraargs
753 }
754 pub fn set_nextra_args(&mut self, n: i32) {
756 debug_assert!(self.is_lua(), "set_nextra_args on a C frame");
757 self.u.nextraargs = n;
758 }
759 pub fn transfer_ftransfer(&self) -> u16 {
760 self.u2.ftransfer
761 }
762 pub fn transfer_ntransfer(&self) -> u16 {
763 self.u2.ntransfer
764 }
765 pub fn set_trap(&mut self, t: bool) {
773 debug_assert!(self.is_lua(), "set_trap on a C frame");
774 if t {
775 self.callstatus |= CIST_TRAP;
776 } else {
777 self.callstatus &= !CIST_TRAP;
778 }
779 }
780 #[inline(always)]
784 pub fn trap(&self) -> bool {
785 (self.callstatus & CIST_TRAP) != 0
786 }
787 pub fn recover_status(&self) -> i32 {
790 ((self.callstatus >> CIST_RECST) & 7) as i32
791 }
792 pub fn set_recover_status<T: Into<i32>>(&mut self, status: T) {
795 let st = (status.into() & 7) as u16;
796 self.callstatus = (self.callstatus & !(7u16 << CIST_RECST)) | (st << CIST_RECST);
797 }
798 pub fn get_oah(&self) -> bool {
799 (self.callstatus & CIST_OAH) != 0
800 }
801 pub fn set_oah(&mut self, allow: bool) {
804 self.callstatus = (self.callstatus & !CIST_OAH) | (if allow { CIST_OAH } else { 0 });
805 }
806 pub fn u_c_old_errfunc(&self) -> isize {
811 debug_assert!(!self.is_lua(), "u_c_old_errfunc on a Lua frame");
812 self.u.old_errfunc
813 }
814 pub fn u_c_ctx(&self) -> isize {
816 debug_assert!(!self.is_lua(), "u_c_ctx on a Lua frame");
817 self.u.ctx
818 }
819 pub fn u_c_k(&self) -> Option<LuaKFunction> {
821 debug_assert!(!self.is_lua(), "u_c_k on a Lua frame");
822 self.u.k
823 }
824 pub fn set_u_c_k(&mut self, k: Option<LuaKFunction>) {
830 debug_assert!(!self.is_lua(), "set_u_c_k on a Lua frame");
831 self.u.k = k;
832 }
833 pub fn set_u_c_ctx(&mut self, ctx: isize) {
835 debug_assert!(!self.is_lua(), "set_u_c_ctx on a Lua frame");
836 self.u.ctx = ctx;
837 }
838 pub fn set_u_c_old_errfunc(&mut self, old_errfunc: isize) {
840 debug_assert!(!self.is_lua(), "set_u_c_old_errfunc on a Lua frame");
841 self.u.old_errfunc = old_errfunc;
842 }
843 pub fn set_u2_funcidx(&mut self, idx: i32) {
846 self.u2.value = idx;
847 }
848}
849
850pub trait LuaValueExt {
856 fn base_type(&self) -> lua_types::LuaType;
857 fn to_number_no_strconv(&self) -> Option<f64>;
858 fn to_number_with_strconv(&self) -> Option<f64>;
859 fn to_integer_no_strconv(&self) -> Option<i64>;
860 fn to_integer_with_strconv(&self) -> Option<i64>;
861 fn full_type_tag(&self) -> u8;
862}
863
864impl LuaValueExt for LuaValue {
865 fn base_type(&self) -> lua_types::LuaType {
866 self.type_tag()
867 }
868 fn to_number_no_strconv(&self) -> Option<f64> {
869 match self {
870 LuaValue::Float(f) => Some(*f),
871 LuaValue::Int(i) => Some(*i as f64),
872 _ => None,
873 }
874 }
875 fn to_number_with_strconv(&self) -> Option<f64> {
876 if let Some(n) = self.to_number_no_strconv() {
877 return Some(n);
878 }
879 if let LuaValue::Str(s) = self {
880 let mut tmp = LuaValue::Nil;
881 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
882 if sz == 0 {
883 return None;
884 }
885 return match tmp {
886 LuaValue::Int(i) => Some(i as f64),
887 LuaValue::Float(f) => Some(f),
888 _ => None,
889 };
890 }
891 None
892 }
893 fn to_integer_no_strconv(&self) -> Option<i64> {
894 match self {
895 LuaValue::Int(i) => Some(*i),
896 LuaValue::Float(f) if f.fract() == 0.0 && f.is_finite() => {
897 let min_f = i64::MIN as f64;
901 let max_plus1_f = -(i64::MIN as f64);
902 if *f >= min_f && *f < max_plus1_f {
903 Some(*f as i64)
904 } else {
905 None
906 }
907 }
908 _ => None,
909 }
910 }
911 fn to_integer_with_strconv(&self) -> Option<i64> {
912 if let Some(i) = self.to_integer_no_strconv() {
913 return Some(i);
914 }
915 if let LuaValue::Str(s) = self {
916 let mut tmp = LuaValue::Nil;
917 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
918 if sz == 0 {
919 return None;
920 }
921 return tmp.to_integer_no_strconv();
922 }
923 None
924 }
925 fn full_type_tag(&self) -> u8 {
926 match self {
927 LuaValue::Nil => 0x00,
928 LuaValue::Bool(false) => 0x01,
929 LuaValue::Bool(true) => 0x11,
930 LuaValue::Int(_) => 0x03,
931 LuaValue::Float(_) => 0x13,
932 LuaValue::Str(s) if s.is_short() => 0x04,
933 LuaValue::Str(_) => 0x14,
934 LuaValue::LightUserData(_) => 0x02,
935 LuaValue::Table(_) => 0x05,
936 LuaValue::Function(LuaClosure::Lua(_)) => 0x06,
937 LuaValue::Function(LuaClosure::LightC(_)) => 0x16,
938 LuaValue::Function(LuaClosure::C(_)) => 0x26,
939 LuaValue::UserData(_) => 0x07,
940 LuaValue::Thread(_) => 0x08,
941 }
942 }
943}
944
945pub trait LuaTypeExt {
947 fn type_name(&self) -> &'static [u8];
948}
949
950impl LuaTypeExt for lua_types::LuaType {
951 fn type_name(&self) -> &'static [u8] {
952 use lua_types::LuaType::*;
953 match self {
954 None => b"no value",
955 Nil => b"nil",
956 Boolean => b"boolean",
957 LightUserData => b"userdata",
958 Number => b"number",
959 String => b"string",
960 Table => b"table",
961 Function => b"function",
962 UserData => b"userdata",
963 Thread => b"thread",
964 }
965 }
966}
967
968pub trait StackIdxExt {
972 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32;
973 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32;
974 fn raw(self) -> u32;
975}
976impl StackIdxExt for StackIdx {
977 #[inline(always)]
978 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32 {
979 self.0.saturating_sub(n.into().0 .0)
980 }
981 #[inline(always)]
982 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32 {
983 self.0.wrapping_sub(n.into().0 .0)
984 }
985 #[inline(always)]
986 fn raw(self) -> u32 {
987 self.0
988 }
989}
990
991pub trait LuaTableRefExt {
1001 fn metatable(&self) -> Option<GcRef<LuaTable>>;
1002 fn has_metatable(&self) -> bool;
1003 fn as_ptr(&self) -> *const ();
1004 fn get(&self, _k: &LuaValue) -> LuaValue;
1005 fn get_int(&self, _k: i64) -> LuaValue;
1006 fn get_short_str(&self, _k: &GcRef<LuaString>) -> LuaValue;
1007 fn raw_set(&self, _state: &mut LuaState, _k: LuaValue, _v: LuaValue) -> Result<(), LuaError>;
1008 fn raw_set_int(&self, _state: &mut LuaState, _k: i64, _v: LuaValue) -> Result<(), LuaError>;
1009 fn raw_set_short_str(
1010 &self,
1011 _state: &mut LuaState,
1012 _k: GcRef<LuaString>,
1013 _v: LuaValue,
1014 ) -> Result<(), LuaError>;
1015 fn invalidate_tm_cache(&self);
1016 fn resize(&self, _state: &mut LuaState, _na: usize, _nh: usize) -> Result<(), LuaError>;
1017 fn next(&self, _k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError>;
1018}
1019impl LuaTableRefExt for GcRef<LuaTable> {
1020 #[inline]
1021 fn metatable(&self) -> Option<GcRef<LuaTable>> {
1022 (**self).metatable()
1023 }
1024 #[inline]
1025 fn has_metatable(&self) -> bool {
1026 (**self).has_metatable()
1027 }
1028 #[inline]
1029 fn as_ptr(&self) -> *const () {
1030 GcRef::identity(self) as *const ()
1031 }
1032 #[inline]
1033 fn get(&self, k: &LuaValue) -> LuaValue {
1034 (**self).get(k)
1035 }
1036 #[inline]
1037 fn get_int(&self, k: i64) -> LuaValue {
1038 (**self).get_int(k)
1039 }
1040 #[inline]
1041 fn get_short_str(&self, k: &GcRef<LuaString>) -> LuaValue {
1042 (**self).get_short_str(k)
1043 }
1044 #[inline(always)]
1053 fn raw_set(&self, state: &mut LuaState, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
1054 match k {
1055 LuaValue::Int(i) => return self.raw_set_int(state, i, v),
1056 LuaValue::Str(s) if s.is_short() => return self.raw_set_short_str(state, s, v),
1057 k => {
1058 if k.is_collectable() {
1059 state.gc_table_barrier_back(self, &k);
1060 }
1061 let before = (**self).buffer_bytes();
1062 let result = (**self).try_raw_set(k, v);
1063 if result.is_ok() {
1064 account_table_buffer_delta(self, before);
1065 }
1066 result
1067 }
1068 }
1069 }
1070 #[inline(always)]
1071 fn raw_set_int(&self, _state: &mut LuaState, k: i64, v: LuaValue) -> Result<(), LuaError> {
1072 match (**self).try_update_int(k, v) {
1073 Ok(()) => Ok(()),
1074 Err(v) => {
1075 let before = (**self).buffer_bytes();
1076 let result = (**self).try_raw_set_int(k, v);
1077 if result.is_ok() {
1078 account_table_buffer_delta(self, before);
1079 }
1080 result
1081 }
1082 }
1083 }
1084 #[inline(always)]
1088 fn raw_set_short_str(
1089 &self,
1090 state: &mut LuaState,
1091 k: GcRef<LuaString>,
1092 v: LuaValue,
1093 ) -> Result<(), LuaError> {
1094 match (**self).try_update_short_str(&k, v) {
1095 Ok(()) => Ok(()),
1096 Err(v) => {
1097 state.gc_table_barrier_back(self, &LuaValue::Str(k));
1098 let before = (**self).buffer_bytes();
1099 let result = (**self).try_raw_set(LuaValue::Str(k), v);
1100 if result.is_ok() {
1101 account_table_buffer_delta(self, before);
1102 }
1103 result
1104 }
1105 }
1106 }
1107 fn invalidate_tm_cache(&self) {}
1108 fn resize(&self, _state: &mut LuaState, na: usize, nh: usize) -> Result<(), LuaError> {
1109 let before = (**self).buffer_bytes();
1110 let na32 = na.min(u32::MAX as usize) as u32;
1111 let nh32 = nh.min(u32::MAX as usize) as u32;
1112 let result = (**self).resize(na32, nh32);
1113 if result.is_ok() {
1114 account_table_buffer_delta(self, before);
1115 }
1116 result
1117 }
1118 fn next(&self, k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
1119 (**self).try_next_pair(&k)
1120 }
1121}
1122
1123#[inline]
1124fn account_table_buffer_delta(t: &GcRef<LuaTable>, before: usize) {
1125 let after = (**t).buffer_bytes();
1126 if after > before {
1127 t.account_buffer((after - before) as isize);
1128 } else if before > after {
1129 t.account_buffer(-((before - after) as isize));
1130 }
1131}
1132
1133pub trait LuaUserDataRefExt {
1134 fn metatable(&self) -> Option<GcRef<LuaTable>>;
1135 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>);
1136 fn as_ptr(&self) -> *const ();
1137 fn len(&self) -> usize;
1138}
1139impl LuaUserDataRefExt for GcRef<LuaUserData> {
1140 fn metatable(&self) -> Option<GcRef<LuaTable>> {
1141 (**self).metatable()
1142 }
1143 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>) {
1144 (**self).set_metatable(mt);
1145 }
1146 fn as_ptr(&self) -> *const () {
1147 GcRef::identity(self) as *const ()
1148 }
1149 fn len(&self) -> usize {
1150 self.0.data.len()
1151 }
1152}
1153
1154pub trait LuaStringRefExt {
1155 fn is_white(&self) -> bool;
1156 fn hash(&self) -> u32;
1157 fn as_gc_ref(&self) -> GcRef<LuaString>;
1158}
1159impl LuaStringRefExt for GcRef<LuaString> {
1160 fn is_white(&self) -> bool {
1161 false
1162 }
1163 fn hash(&self) -> u32 {
1164 self.0.hash()
1165 }
1166 fn as_gc_ref(&self) -> GcRef<LuaString> {
1167 self.clone()
1168 }
1169}
1170
1171pub trait LuaLClosureRefExt {
1172 fn proto(&self) -> &GcRef<LuaProto>;
1173 fn nupvalues(&self) -> usize;
1174}
1175impl LuaLClosureRefExt for GcRef<lua_types::closure::LuaLClosure> {
1176 fn proto(&self) -> &GcRef<LuaProto> {
1177 &self.0.proto
1178 }
1179 fn nupvalues(&self) -> usize {
1180 self.0.upvals.len()
1181 }
1182}
1183
1184pub trait LuaClosureExt {
1186 fn nupvalues(&self) -> usize;
1187}
1188impl LuaClosureExt for LuaClosure {
1189 fn nupvalues(&self) -> usize {
1190 match self {
1191 LuaClosure::Lua(l) => l.0.upvals.len(),
1192 LuaClosure::C(c) => c.0.upvalues.borrow().len(),
1193 LuaClosure::LightC(_) => 0,
1194 }
1195 }
1196}
1197
1198pub trait LuaProtoExt {
1200 fn source_bytes(&self) -> &[u8];
1201 fn source_string(&self) -> Option<&GcRef<LuaString>>;
1202}
1203impl LuaProtoExt for LuaProto {
1204 fn source_bytes(&self) -> &[u8] {
1205 match &self.source {
1206 Some(s) => s.0.as_bytes(),
1207 None => &[],
1208 }
1209 }
1210 fn source_string(&self) -> Option<&GcRef<LuaString>> {
1211 self.source.as_ref()
1212 }
1213}
1214
1215pub trait Collectable: std::fmt::Debug {}
1222
1223impl std::fmt::Debug for LuaState {
1224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1225 write!(f, "LuaState")
1226 }
1227}
1228impl Collectable for LuaState {}
1229
1230pub type ParserHook = fn(
1244 state: &mut LuaState,
1245 z: &mut crate::zio::ZIO,
1246 name: &[u8],
1247 firstchar: i32,
1248) -> Result<GcRef<lua_types::closure::LuaLClosure>, LuaError>;
1249
1250pub type FileLoaderHook = fn(filename: &[u8]) -> Result<Vec<u8>, LuaError>;
1258
1259pub type FileOpenHook =
1270 fn(filename: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1271
1272pub type OutputHook = fn(bytes: &[u8]) -> std::io::Result<()>;
1280
1281pub type InputHook = fn(buf: &mut [u8]) -> std::io::Result<usize>;
1284
1285pub type EnvHook = fn(name: &[u8]) -> Option<Vec<u8>>;
1291
1292pub type UnixTimeHook = fn() -> i64;
1294
1295pub type CpuClockHook = fn() -> f64;
1303
1304pub type LocalOffsetHook = fn(timestamp: i64) -> i64;
1316
1317pub type EntropyHook = fn() -> u64;
1321
1322pub type TempNameHook = fn() -> Result<Vec<u8>, LuaError>;
1327
1328pub type PopenHook =
1339 fn(cmd: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1340
1341pub type FileRemoveHook = fn(filename: &[u8]) -> Result<(), LuaError>;
1347
1348pub type FileRenameHook = fn(from: &[u8], to: &[u8]) -> Result<(), LuaError>;
1354
1355#[derive(Clone, Copy, Debug)]
1361pub enum OsExecuteReason {
1362 Exit,
1364 Signal,
1366}
1367
1368#[derive(Debug)]
1371pub struct OsExecuteResult {
1372 pub success: bool,
1374 pub reason: OsExecuteReason,
1376 pub code: i32,
1378}
1379
1380pub type OsExecuteHook = fn(cmd: &[u8]) -> Result<OsExecuteResult, LuaError>;
1387
1388#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1395pub struct DynLibId(pub u64);
1396
1397pub enum DynamicSymbol {
1405 RustNative(LuaCFunction),
1408 LuaCAbi(*const ()),
1414 Unsupported { reason: Vec<u8> },
1417}
1418
1419pub type DynLibLoadHook =
1430 fn(state: &mut LuaState, path: &[u8], see_global: bool) -> Result<DynLibId, LuaError>;
1431
1432pub type DynLibSymbolHook =
1440 fn(state: &mut LuaState, handle: DynLibId, symbol: &[u8]) -> Result<DynamicSymbol, LuaError>;
1441
1442pub type DynLibUnloadHook = fn(handle: DynLibId);
1450
1451pub struct ThreadRegistryEntry {
1457 pub state: Rc<RefCell<LuaState>>,
1462 pub value: GcRef<lua_types::value::LuaThread>,
1465}
1466
1467#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1472pub struct ExternalRootKey {
1473 index: usize,
1474 generation: u64,
1475}
1476
1477#[derive(Debug)]
1478struct ExternalRootSlot {
1479 value: Option<LuaValue>,
1480 generation: u64,
1481}
1482
1483#[derive(Debug, Default)]
1489pub struct ExternalRootSet {
1490 slots: Vec<ExternalRootSlot>,
1491 free: Vec<usize>,
1492 live: usize,
1493}
1494
1495impl ExternalRootSet {
1496 pub fn insert(&mut self, value: LuaValue) -> ExternalRootKey {
1497 if let Some(index) = self.free.pop() {
1498 let slot = &mut self.slots[index];
1499 debug_assert!(slot.value.is_none(), "free external-root slot is occupied");
1500 slot.generation = slot.generation.wrapping_add(1).max(1);
1501 slot.value = Some(value);
1502 self.live += 1;
1503 ExternalRootKey {
1504 index,
1505 generation: slot.generation,
1506 }
1507 } else {
1508 let index = self.slots.len();
1509 self.slots.push(ExternalRootSlot {
1510 value: Some(value),
1511 generation: 1,
1512 });
1513 self.live += 1;
1514 ExternalRootKey {
1515 index,
1516 generation: 1,
1517 }
1518 }
1519 }
1520
1521 pub fn get(&self, key: ExternalRootKey) -> Option<&LuaValue> {
1522 let slot = self.slots.get(key.index)?;
1523 if slot.generation == key.generation {
1524 slot.value.as_ref()
1525 } else {
1526 None
1527 }
1528 }
1529
1530 pub fn replace(&mut self, key: ExternalRootKey, value: LuaValue) -> Option<LuaValue> {
1531 let slot = self.slots.get_mut(key.index)?;
1532 if slot.generation != key.generation || slot.value.is_none() {
1533 return None;
1534 }
1535 slot.value.replace(value)
1536 }
1537
1538 pub fn remove(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
1539 let slot = self.slots.get_mut(key.index)?;
1540 if slot.generation != key.generation {
1541 return None;
1542 }
1543 let old = slot.value.take()?;
1544 self.free.push(key.index);
1545 self.live -= 1;
1546 Some(old)
1547 }
1548
1549 pub fn iter_values(&self) -> impl Iterator<Item = &LuaValue> {
1550 self.slots.iter().filter_map(|slot| slot.value.as_ref())
1551 }
1552
1553 pub fn len(&self) -> usize {
1554 self.live
1555 }
1556
1557 pub fn is_empty(&self) -> bool {
1558 self.live == 0
1559 }
1560
1561 pub fn vacant_len(&self) -> usize {
1562 self.free.len()
1563 }
1564}
1565
1566pub struct GlobalState {
1572 pub parser_hook: Option<ParserHook>,
1578
1579 pub cli_argv: Option<Vec<Vec<u8>>>,
1586
1587 pub cli_preload: Option<fn(&mut LuaState) -> Result<(), LuaError>>,
1591
1592 pub lua_version: lua_types::LuaVersion,
1599
1600 pub file_loader_hook: Option<FileLoaderHook>,
1605
1606 pub file_open_hook: Option<FileOpenHook>,
1611
1612 pub stdout_hook: Option<OutputHook>,
1616
1617 pub stderr_hook: Option<OutputHook>,
1619
1620 pub stdin_hook: Option<InputHook>,
1623
1624 pub env_hook: Option<EnvHook>,
1626
1627 pub unix_time_hook: Option<UnixTimeHook>,
1630
1631 pub cpu_clock_hook: Option<CpuClockHook>,
1634
1635 pub local_offset_hook: Option<LocalOffsetHook>,
1640
1641 pub entropy_hook: Option<EntropyHook>,
1644
1645 pub temp_name_hook: Option<TempNameHook>,
1647
1648 pub popen_hook: Option<PopenHook>,
1653
1654 pub file_remove_hook: Option<FileRemoveHook>,
1657
1658 pub file_rename_hook: Option<FileRenameHook>,
1661
1662 pub os_execute_hook: Option<OsExecuteHook>,
1666
1667 pub dynlib_load_hook: Option<DynLibLoadHook>,
1672
1673 pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
1677
1678 pub dynlib_unload_hook: Option<DynLibUnloadHook>,
1682
1683 pub sandbox: SandboxLimits,
1686
1687 pub gc_debt: isize,
1689
1690 pub gc_estimate: usize,
1691
1692 pub lastatomic: usize,
1694
1695 pub strt: StringPool,
1697
1698 pub l_registry: LuaValue,
1700
1701 pub external_roots: ExternalRootSet,
1704
1705 pub globals: LuaValue,
1712 pub loaded: LuaValue,
1713
1714 pub nilvalue: LuaValue,
1718
1719 pub seed: u32,
1721
1722 pub currentwhite: u8,
1724
1725 pub gcstate: u8,
1726
1727 pub gckind: u8,
1728
1729 pub gcstopem: bool,
1730
1731 pub genminormul: u8,
1733
1734 pub genmajormul: u8,
1735
1736 pub gcstp: u8,
1737
1738 pub gcemergency: bool,
1739
1740 pub gcpause: u8,
1742
1743 pub gcstepmul: u8,
1745
1746 pub gcstepsize: u8,
1747
1748 pub gc55_params: [i64; 6],
1757
1758 pub sweepgc_cursor: usize,
1763
1764 pub weak_tables_registry: lua_gc::WeakRegistry<WeakTableEntry>,
1773
1774 pub finalizers: lua_gc::FinalizerRegistry<FinalizerObject>,
1777
1778 pub gc_finalizer_error: Option<LuaValue>,
1789
1790 pub twups: Vec<GcRef<LuaState>>,
1800
1801 pub panic: Option<LuaCFunction>,
1803
1804 pub mainthread: Option<GcRef<LuaState>>,
1807
1808 pub threads: std::collections::HashMap<u64, ThreadRegistryEntry>,
1821
1822 pub main_thread_value: GcRef<lua_types::value::LuaThread>,
1827
1828 pub current_thread_id: u64,
1833
1834 pub closing_thread_id: Option<u64>,
1837
1838 pub main_thread_id: u64,
1841
1842 pub next_thread_id: u64,
1845
1846 pub thread_globals: std::collections::HashMap<u64, LuaValue>,
1863
1864 pub closure_envs: std::collections::HashMap<usize, LuaValue>,
1876
1877 pub memerrmsg: GcRef<LuaString>,
1879
1880 pub tmname: Vec<GcRef<LuaString>>,
1883
1884 pub mt: [Option<GcRef<LuaTable>>; LUA_NUMTYPES],
1886
1887 pub strcache: [[GcRef<LuaString>; STRCACHE_M]; STRCACHE_N],
1889
1890 pub interned_lt: InternedStringMap,
1896
1897 pub warnf: Option<Box<dyn FnMut(&[u8], bool)>>,
1899
1900 pub warn_mode: WarnMode,
1905
1906 pub test_warn_enabled: bool,
1911 pub test_warn_on: bool,
1912 pub test_warn_mode: TestWarnMode,
1913 pub test_warn_last_to_cont: bool,
1914 pub test_warn_buffer: Vec<u8>,
1915
1916 pub c_functions: Vec<LuaCallable>,
1921
1922 pub heap: std::rc::Rc<lua_gc::Heap>,
1927
1928 pub cross_thread_upvals: std::collections::HashMap<(u64, StackIdx), LuaValue>,
1942
1943 pub suspended_parent_stacks: Vec<Vec<LuaValue>>,
1957
1958 pub suspended_parent_open_upvals: Vec<Vec<GcRef<UpVal>>>,
1964
1965 pub snapshot_stack_pool: Vec<Vec<LuaValue>>,
1973 pub snapshot_upval_pool: Vec<Vec<GcRef<UpVal>>>,
1974
1975 pub resume_value_pool: Vec<Vec<LuaValue>>,
1983
1984 pub resume_upval_slot_pool: Vec<Vec<(u64, StackIdx)>>,
1990
1991 pub resume_flush_pool: Vec<Vec<(StackIdx, LuaValue)>>,
1995}
1996
1997const SANDBOX_COUNT_MASK: u8 = 1 << 3;
2000
2001pub const SANDBOX_TRIP_NONE: u8 = 0;
2003pub const SANDBOX_TRIP_INSTRUCTIONS: u8 = 1;
2005pub const SANDBOX_TRIP_MEMORY: u8 = 2;
2007
2008#[derive(Default)]
2015pub struct SandboxLimits {
2016 pub interval: std::cell::Cell<i32>,
2018 pub instr_limited: std::cell::Cell<bool>,
2020 pub instr_remaining: std::cell::Cell<u64>,
2022 pub instr_limit: std::cell::Cell<u64>,
2024 pub mem_limit: std::cell::Cell<Option<usize>>,
2026 pub tripped: std::cell::Cell<u8>,
2028 pub aborting: std::cell::Cell<bool>,
2033}
2034
2035impl GlobalState {
2036 pub fn sandbox_active(&self) -> bool {
2038 self.sandbox.interval.get() != 0
2039 }
2040
2041 pub fn total_bytes(&self) -> usize {
2046 self.heap.bytes_used().max(1)
2047 }
2048
2049 pub fn get_thread(&self, id: u64) -> Option<&ThreadRegistryEntry> {
2054 self.threads.get(&id)
2055 }
2056
2057 pub fn thread_value_for(&self, id: u64) -> Option<GcRef<lua_types::value::LuaThread>> {
2061 if id == self.main_thread_id {
2062 Some(self.main_thread_value.clone())
2063 } else {
2064 self.threads.get(&id).map(|e| e.value.clone())
2065 }
2066 }
2067
2068 pub fn is_complete(&self) -> bool {
2075 matches!(self.nilvalue, LuaValue::Nil)
2076 }
2077
2078 pub fn current_white(&self) -> u8 {
2086 self.currentwhite
2087 }
2088
2089 pub fn other_white(&self) -> u8 {
2093 self.currentwhite ^ 0x03
2094 }
2095
2096 pub fn is_gen_mode(&self) -> bool {
2100 self.gckind == GcKind::Generational as u8 || self.lastatomic != 0
2101 }
2102
2103 pub fn gc_running(&self) -> bool {
2107 self.gcstp == 0
2108 }
2109
2110 pub fn keep_invariant(&self) -> bool {
2114 self.heap.gc_state().is_invariant()
2115 }
2116
2117 pub fn is_sweep_phase(&self) -> bool {
2121 self.heap.gc_state().is_sweep()
2122 }
2123
2124 pub fn gc_debt(&self) -> isize {
2126 self.gc_debt
2127 }
2128 pub fn set_gc_debt(&mut self, d: isize) {
2129 self.gc_debt = d;
2130 }
2131 pub fn gc_at_pause(&self) -> bool {
2132 self.heap.gc_state().is_pause()
2133 }
2134 fn get_gc_param(p: u8) -> i32 {
2135 (p as i32) * 4
2136 }
2137 fn set_gc_param_slot(slot: &mut u8, p: i32) {
2138 *slot = (p / 4) as u8;
2139 }
2140 pub fn gc_pause_param(&self) -> i32 {
2141 Self::get_gc_param(self.gcpause)
2142 }
2143 pub fn set_gc_pause_param(&mut self, p: i32) {
2144 Self::set_gc_param_slot(&mut self.gcpause, p);
2145 }
2146 pub fn gc_stepmul_param(&self) -> i32 {
2147 Self::get_gc_param(self.gcstepmul)
2148 }
2149 pub fn set_gc_stepmul_param(&mut self, p: i32) {
2150 Self::set_gc_param_slot(&mut self.gcstepmul, p);
2151 }
2152 pub fn gc_genmajormul_param(&self) -> i32 {
2153 Self::get_gc_param(self.genmajormul)
2154 }
2155 pub fn set_gc_genmajormul(&mut self, p: i32) {
2156 Self::set_gc_param_slot(&mut self.genmajormul, p);
2157 }
2158 pub fn gc55_param(&mut self, idx: usize, value: i64) -> i64 {
2162 let old = self.gc55_params[idx];
2163 if value >= 0 {
2164 self.gc55_params[idx] = value;
2165 }
2166 old
2167 }
2168 pub fn gc_stop_flags(&self) -> u8 {
2169 self.gcstp
2170 }
2171 pub fn set_gc_stop_flags(&mut self, f: u8) {
2172 self.gcstp = f;
2173 }
2174 pub fn stop_gc_internal(&mut self) -> u8 {
2175 let old = self.gcstp;
2176 self.gcstp |= GCSTPGC;
2177 old
2178 }
2179 pub fn set_gc_stop_user(&mut self) {
2180 self.gcstp = GCSTPUSR;
2182 }
2183 pub fn clear_gc_stop(&mut self) {
2184 self.gcstp = 0;
2185 }
2186 pub fn is_gc_running(&self) -> bool {
2187 self.gcstp == 0
2188 }
2189 pub fn is_gc_stopped_internally(&self) -> bool {
2194 (self.gcstp & GCSTPGC) != 0
2195 }
2196
2197 pub fn tm_name<T: TmIndex>(&self, tm: T) -> Option<GcRef<LuaString>> {
2207 self.tmname.get(tm.tm_index()).cloned()
2208 }
2209}
2210
2211pub trait TmIndex: Copy {
2217 fn tm_index(self) -> usize;
2218}
2219impl TmIndex for lua_types::tagmethod::TagMethod {
2220 fn tm_index(self) -> usize {
2221 self as u8 as usize
2222 }
2223}
2224impl TmIndex for crate::tagmethods::TagMethod {
2225 fn tm_index(self) -> usize {
2226 self as u8 as usize
2227 }
2228}
2229impl TmIndex for usize {
2230 fn tm_index(self) -> usize {
2231 self
2232 }
2233}
2234impl TmIndex for u8 {
2235 fn tm_index(self) -> usize {
2236 self as usize
2237 }
2238}
2239
2240use lua_types::tagmethod::TagMethod;
2241
2242pub struct LuaState {
2252 pub status: u8,
2256
2257 pub allowhook: bool,
2259
2260 pub nci: u32,
2262
2263 pub top: StackIdx,
2267
2268 pub stack_last: StackIdx,
2270
2271 pub stack: Vec<StackValue>,
2273
2274 pub ci: CallInfoIdx,
2278
2279 pub call_info: Vec<CallInfo>,
2282
2283 pub openupval: Vec<GcRef<UpVal>>,
2287
2288 pub legacy_open_upval_touched: std::cell::Cell<bool>,
2300
2301 pub tbclist: Vec<StackIdx>,
2303
2304 pub(crate) global: Rc<RefCell<GlobalState>>,
2309
2310 pub hook: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>,
2314
2315 pub hookmask: u8,
2317
2318 pub basehookcount: i32,
2320
2321 pub hookcount: i32,
2323
2324 pub errfunc: isize,
2331
2332 pub n_ccalls: u32,
2336
2337 pub oldpc: u32,
2341
2342 pub marked: u8,
2346
2347 pub cached_thread_id: u64,
2363
2364 pub gc_check_needed: bool,
2369}
2370
2371impl LuaState {
2372 pub fn global(&self) -> std::cell::Ref<'_, GlobalState> {
2380 self.global.borrow()
2381 }
2382
2383 pub fn global_mut(&self) -> std::cell::RefMut<'_, GlobalState> {
2387 self.global.borrow_mut()
2388 }
2389
2390 pub fn global_rc(&self) -> Rc<RefCell<GlobalState>> {
2394 Rc::clone(&self.global)
2395 }
2396
2397 pub fn v51_thread_lgt(&self, id: u64) -> LuaValue {
2406 let g = self.global();
2407 if id == g.main_thread_id {
2408 g.globals.clone()
2409 } else {
2410 g.thread_globals
2411 .get(&id)
2412 .expect("v51 coroutine l_gt must be seeded in new_thread")
2413 .clone()
2414 }
2415 }
2416
2417 pub fn v51_set_thread_lgt(&self, id: u64, t: LuaValue) {
2422 let mut g = self.global_mut();
2423 if id == g.main_thread_id {
2424 g.globals = t;
2425 } else {
2426 g.thread_globals.insert(id, t);
2427 }
2428 }
2429
2430 pub fn enable_test_warning_handler(&mut self) -> Result<(), LuaError> {
2432 {
2433 let mut g = self.global_mut();
2434 g.test_warn_enabled = true;
2435 g.test_warn_on = false;
2436 g.test_warn_mode = TestWarnMode::Normal;
2437 g.test_warn_last_to_cont = false;
2438 g.test_warn_buffer.clear();
2439 }
2440 self.push(LuaValue::Bool(false));
2441 crate::api::set_global(self, b"_WARN")
2442 }
2443
2444 pub fn c_calls(&self) -> u32 {
2448 self.n_ccalls & 0xffff
2449 }
2450
2451 pub fn inc_nny(&mut self) {
2455 self.n_ccalls += 0x10000;
2456 }
2457
2458 pub fn dec_nny(&mut self) {
2462 self.n_ccalls -= 0x10000;
2463 }
2464
2465 pub fn is_yieldable(&self) -> bool {
2469 (self.n_ccalls & 0xffff0000) == 0
2470 }
2471
2472 pub fn reset_hook_count(&mut self) {
2476 self.hookcount = self.basehookcount;
2477 }
2478
2479 pub fn install_sandbox_limits(
2488 &mut self,
2489 interval: i32,
2490 instr_limit: Option<u64>,
2491 mem_limit: Option<usize>,
2492 ) {
2493 let interval = interval.max(1);
2494 {
2495 let g = self.global();
2496 g.sandbox.interval.set(interval);
2497 g.sandbox.instr_limited.set(instr_limit.is_some());
2498 g.sandbox.instr_remaining.set(instr_limit.unwrap_or(0));
2499 g.sandbox.instr_limit.set(instr_limit.unwrap_or(0));
2500 g.sandbox.mem_limit.set(mem_limit);
2501 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2502 }
2503 self.hookmask |= SANDBOX_COUNT_MASK;
2504 self.basehookcount = interval;
2505 self.hookcount = interval;
2506 crate::debug::arm_traps(self);
2507 }
2508
2509 pub fn sandbox_charge_interval(&self) -> Option<LuaError> {
2514 let interval = self.global().sandbox.interval.get();
2515 self.sandbox_charge(interval as u64)
2516 }
2517
2518 pub fn sandbox_charge(&self, amount: u64) -> Option<LuaError> {
2527 let g = self.global();
2528 if g.sandbox.interval.get() == 0 {
2529 return None;
2530 }
2531 if g.sandbox.instr_limited.get() {
2532 let rem = g.sandbox.instr_remaining.get().saturating_sub(amount);
2533 g.sandbox.instr_remaining.set(rem);
2534 if rem == 0 {
2535 g.sandbox.tripped.set(SANDBOX_TRIP_INSTRUCTIONS);
2536 g.sandbox.aborting.set(true);
2537 return Some(LuaError::runtime(format_args!(
2538 "sandbox: instruction budget exhausted"
2539 )));
2540 }
2541 }
2542 if let Some(limit) = g.sandbox.mem_limit.get() {
2543 if g.total_bytes() > limit {
2544 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2545 g.sandbox.aborting.set(true);
2546 return Some(LuaError::runtime(format_args!(
2547 "sandbox: memory limit exceeded"
2548 )));
2549 }
2550 }
2551 None
2552 }
2553
2554 pub fn sandbox_reserve(&self, additional: usize) -> Option<LuaError> {
2562 let g = self.global();
2563 if g.sandbox.interval.get() == 0 {
2564 return None;
2565 }
2566 if let Some(limit) = g.sandbox.mem_limit.get() {
2567 let projected = g.total_bytes().saturating_add(additional);
2568 if projected > limit {
2569 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2570 g.sandbox.aborting.set(true);
2571 return Some(LuaError::runtime(format_args!(
2572 "sandbox: memory limit exceeded"
2573 )));
2574 }
2575 }
2576 None
2577 }
2578
2579 pub fn sandbox_match_step_limit(&self) -> u64 {
2584 let g = self.global();
2585 if g.sandbox.interval.get() != 0 && g.sandbox.instr_limited.get() {
2586 g.sandbox.instr_remaining.get()
2587 } else {
2588 0
2589 }
2590 }
2591
2592 pub fn sandbox_aborting(&self) -> bool {
2596 self.global().sandbox.aborting.get()
2597 }
2598
2599 pub fn sandbox_instr_limited(&self) -> bool {
2601 self.global().sandbox.instr_limited.get()
2602 }
2603
2604 pub fn sandbox_instr_remaining(&self) -> u64 {
2607 self.global().sandbox.instr_remaining.get()
2608 }
2609
2610 pub fn sandbox_instr_limit(&self) -> u64 {
2612 self.global().sandbox.instr_limit.get()
2613 }
2614
2615 pub fn sandbox_tripped_code(&self) -> u8 {
2617 self.global().sandbox.tripped.get()
2618 }
2619
2620 pub fn sandbox_reset(&self) {
2623 let g = self.global();
2624 if g.sandbox.instr_limited.get() {
2625 g.sandbox.instr_remaining.set(g.sandbox.instr_limit.get());
2626 }
2627 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2628 g.sandbox.aborting.set(false);
2629 }
2630
2631 pub fn stack_size(&self) -> usize {
2635 self.stack_last.0 as usize
2636 }
2637
2638 #[inline(always)]
2642 pub fn push(&mut self, val: LuaValue) {
2643 let top = self.top.0 as usize;
2644 if top < self.stack.len() {
2645 self.stack[top] = StackValue { val };
2646 } else {
2647 self.stack.push(StackValue { val });
2648 }
2649 self.top = StackIdx(self.top.0 + 1);
2650 }
2651
2652 #[inline(always)]
2655 pub fn pop(&mut self) -> LuaValue {
2656 if self.top.0 == 0 {
2657 return LuaValue::Nil;
2658 }
2659 self.top = StackIdx(self.top.0 - 1);
2660 self.stack[self.top.0 as usize].val.clone()
2661 }
2662
2663 #[inline(always)]
2667 pub fn stack_val(&self, idx: StackIdx) -> &LuaValue {
2668 &self.stack[idx.0 as usize].val
2669 }
2670
2671 #[inline(always)]
2673 pub fn set_stack_val(&mut self, idx: StackIdx, val: LuaValue) {
2674 self.stack[idx.0 as usize].val = val;
2675 }
2676
2677 pub fn gc(&mut self) -> GcHandle<'_> {
2684 GcHandle { _state: self }
2685 }
2686
2687 pub fn external_root_value(&mut self, value: LuaValue) -> ExternalRootKey {
2689 self.global_mut().external_roots.insert(value)
2690 }
2691
2692 pub fn external_rooted_value(&self, key: ExternalRootKey) -> Option<LuaValue> {
2694 self.global().external_roots.get(key).cloned()
2695 }
2696
2697 pub fn external_replace_root(
2699 &mut self,
2700 key: ExternalRootKey,
2701 value: LuaValue,
2702 ) -> Option<LuaValue> {
2703 self.global_mut().external_roots.replace(key, value)
2704 }
2705
2706 pub fn external_unroot_value(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
2708 self.global_mut().external_roots.remove(key)
2709 }
2710
2711 pub fn try_external_unroot_value(
2714 &mut self,
2715 key: ExternalRootKey,
2716 ) -> std::result::Result<Option<LuaValue>, std::cell::BorrowMutError> {
2717 self.global
2718 .try_borrow_mut()
2719 .map(|mut global| global.external_roots.remove(key))
2720 }
2721
2722 pub fn new_table(&mut self) -> GcRef<LuaTable> {
2726 self.mark_gc_check_needed();
2728 GcRef::new(LuaTable::placeholder())
2729 }
2730
2731 pub fn new_table_with_sizes(
2736 &mut self,
2737 array_size: u32,
2738 hash_size: u32,
2739 ) -> Result<GcRef<LuaTable>, LuaError> {
2740 self.mark_gc_check_needed();
2741 let t = GcRef::new(LuaTable::placeholder());
2742 self.table_resize(&t, array_size as usize, hash_size as usize)?;
2743 Ok(t)
2744 }
2745
2746 pub fn intern_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
2761 if bytes.len() <= crate::string::MAX_SHORT_LEN {
2762 let hash = LuaString::hash_bytes(bytes, 0);
2763 {
2764 let global = self.global();
2765 if let Some(existing) = global.interned_lt.find(bytes, hash) {
2766 return Ok(existing);
2767 }
2768 }
2769 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2770 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2771 self.global_mut().interned_lt.insert(new_ref.clone());
2772 self.mark_gc_check_needed();
2773 Ok(new_ref)
2774 } else {
2775 self.mark_gc_check_needed();
2776 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2777 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2778 Ok(new_ref)
2779 }
2780 }
2781
2782 #[inline(always)]
2784 pub fn top_idx(&self) -> StackIdx {
2785 self.top
2786 }
2787}
2788
2789impl LuaState {
2798 #[inline(always)]
2799 pub fn get_at(&self, idx: impl Into<StackIdxConv>) -> LuaValue {
2800 let i: StackIdx = idx.into().0;
2801 match self.stack.get(i.0 as usize) {
2802 Some(slot) => slot.val.clone(),
2803 None => LuaValue::Nil,
2804 }
2805 }
2806 #[inline(always)]
2807 pub fn set_at(&mut self, idx: impl Into<StackIdxConv>, v: LuaValue) {
2808 let i: StackIdx = idx.into().0;
2809 self.stack[i.0 as usize].val = v;
2810 }
2811
2812 pub fn clear_stack_range(&mut self, start: StackIdx, end: StackIdx) {
2826 if end.0 <= start.0 {
2827 return;
2828 }
2829 let end_u = end.0 as usize;
2830 if end_u > self.stack.len() {
2831 self.stack.resize_with(end_u, StackValue::default);
2832 }
2833 for i in start.0..end.0 {
2834 self.stack[i as usize].val = LuaValue::Nil;
2835 }
2836 }
2837 #[inline(always)]
2845 pub fn get_int_at(&self, idx: impl Into<StackIdxConv>) -> Option<i64> {
2846 let i: StackIdx = idx.into().0;
2847 match self.stack.get(i.0 as usize) {
2848 Some(slot) => match &slot.val {
2849 LuaValue::Int(v) => Some(*v),
2850 _ => None,
2851 },
2852 None => None,
2853 }
2854 }
2855 #[inline(always)]
2862 pub fn get_int_pair_at(
2863 &self,
2864 rb: impl Into<StackIdxConv>,
2865 rc: impl Into<StackIdxConv>,
2866 ) -> Option<(i64, i64)> {
2867 let rb: StackIdx = rb.into().0;
2868 let rc: StackIdx = rc.into().0;
2869 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2870 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib, ic)),
2871 _ => None,
2872 }
2873 }
2874 #[inline(always)]
2879 pub fn get_num_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2880 let i: StackIdx = idx.into().0;
2881 match self.stack.get(i.0 as usize) {
2882 Some(slot) => match &slot.val {
2883 LuaValue::Float(f) => Some(*f),
2884 LuaValue::Int(v) => Some(*v as f64),
2885 _ => None,
2886 },
2887 None => None,
2888 }
2889 }
2890 #[inline(always)]
2895 pub fn get_float_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2896 let i: StackIdx = idx.into().0;
2897 match self.stack.get(i.0 as usize) {
2898 Some(slot) => match &slot.val {
2899 LuaValue::Float(f) => Some(*f),
2900 _ => None,
2901 },
2902 None => None,
2903 }
2904 }
2905 #[inline(always)]
2910 pub fn get_num_pair_at(
2911 &self,
2912 rb: impl Into<StackIdxConv>,
2913 rc: impl Into<StackIdxConv>,
2914 ) -> Option<(f64, f64)> {
2915 let rb: StackIdx = rb.into().0;
2916 let rc: StackIdx = rc.into().0;
2917 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2918 (LuaValue::Float(nb), LuaValue::Float(nc)) => Some((nb, nc)),
2919 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib as f64, ic as f64)),
2920 (LuaValue::Int(ib), LuaValue::Float(nc)) => Some((ib as f64, nc)),
2921 (LuaValue::Float(nb), LuaValue::Int(ic)) => Some((nb, ic as f64)),
2922 _ => None,
2923 }
2924 }
2925 #[inline(always)]
2938 pub fn set_top(&mut self, idx: impl Into<StackIdxConv>) {
2939 let new_top: StackIdx = idx.into().0;
2940 let new_top_u = new_top.0 as usize;
2941 if new_top_u > self.stack.len() {
2942 self.stack.resize_with(new_top_u, StackValue::default);
2943 }
2944 self.top = new_top;
2945 }
2946 #[inline(always)]
2952 pub fn set_top_idx(&mut self, idx: impl Into<StackIdxConv>) {
2953 let new_top: StackIdx = idx.into().0;
2954 self.top = new_top;
2955 }
2956 #[inline(always)]
2959 pub fn dec_top(&mut self) {
2960 if self.top.0 > 0 {
2961 self.top = StackIdx(self.top.0 - 1);
2962 }
2963 }
2964 #[inline(always)]
2965 pub fn pop_n(&mut self, n: usize) {
2966 let cur = self.top.0 as usize;
2967 let new = cur.saturating_sub(n);
2968 self.top = StackIdx(new as u32);
2969 }
2970 #[inline(always)]
2973 pub fn peek_at(&mut self, idx: impl Into<StackIdxConv>) -> LuaValue {
2974 let i: StackIdx = idx.into().0;
2975 match self.stack.get(i.0 as usize) {
2976 Some(slot) => slot.val.clone(),
2977 None => LuaValue::Nil,
2978 }
2979 }
2980 #[inline(always)]
2984 pub fn peek_top(&mut self) -> LuaValue {
2985 if self.top.0 == 0 {
2986 return LuaValue::Nil;
2987 }
2988 self.stack[(self.top.0 - 1) as usize].val.clone()
2989 }
2990 pub fn peek_string_at_top(&mut self) -> GcRef<LuaString> {
2995 match self.peek_top() {
2996 LuaValue::Str(s) => s,
2997 _ => panic!("peek_string_at_top: top of stack is not a string"),
2998 }
2999 }
3000 pub fn stack_at(&mut self, idx: impl Into<StackIdxConv>) -> &mut LuaValue {
3003 let i: StackIdx = idx.into().0;
3004 &mut self.stack[i.0 as usize].val
3005 }
3006 pub fn stack_set_nil(&mut self, idx: impl Into<StackIdxConv>) {
3009 let i: StackIdx = idx.into().0;
3010 let slot = i.0 as usize;
3011 if slot < self.stack.len() {
3012 self.stack[slot].val = LuaValue::Nil;
3013 }
3014 }
3015 pub fn stack_resize(&mut self, size: usize) -> Result<(), LuaError> {
3023 self.stack.resize_with(size, StackValue::default);
3024 Ok(())
3025 }
3026 pub fn stack_available(&mut self) -> usize {
3027 (self.stack_last.0 as usize).saturating_sub(self.top.0 as usize)
3028 }
3029 pub fn check_stack(&mut self, n: i32) -> Result<(), LuaError> {
3030 let free = (self.stack_last.0 as i32) - (self.top.0 as i32);
3031 if free <= n {
3032 self.grow_stack(n, true)?;
3033 }
3034 Ok(())
3035 }
3036 pub fn grow_stack(&mut self, n: i32, raise_error: bool) -> Result<(), LuaError> {
3045 crate::do_::grow_stack(self, n, raise_error).map(|_| ())
3046 }
3047
3048 #[inline(always)]
3049 pub fn get_ci(&self, idx: CallInfoIdx) -> &CallInfo {
3050 &self.call_info[idx.as_usize()]
3051 }
3052 #[inline(always)]
3053 pub fn get_ci_mut(&mut self, idx: CallInfoIdx) -> &mut CallInfo {
3054 &mut self.call_info[idx.as_usize()]
3055 }
3056 #[inline(always)]
3057 pub fn current_call_info(&self) -> &CallInfo {
3058 &self.call_info[self.ci.as_usize()]
3059 }
3060 #[inline(always)]
3061 pub fn current_call_info_mut(&mut self) -> &mut CallInfo {
3062 let i = self.ci.as_usize();
3063 &mut self.call_info[i]
3064 }
3065 #[inline(always)]
3066 pub fn current_ci_idx(&self) -> CallInfoIdx {
3067 self.ci
3068 }
3069 pub fn call_stack_mut(&mut self) -> &mut Vec<CallInfo> {
3070 &mut self.call_info
3071 }
3072 #[inline(always)]
3073 pub fn next_ci(&mut self) -> Result<CallInfoIdx, LuaError> {
3074 let idx = match self.call_info[self.ci.as_usize()].next {
3075 Some(idx) => idx,
3076 None => extend_ci(self),
3077 };
3078 self.call_info[idx.as_usize()].tailcalls = 0;
3079 Ok(idx)
3080 }
3081
3082 #[inline(always)]
3089 pub fn note_lua_tailcall(&mut self, ci: CallInfoIdx) {
3090 if self.global().lua_version == lua_types::LuaVersion::V51 {
3091 self.call_info[ci.as_usize()].tailcalls =
3092 self.call_info[ci.as_usize()].tailcalls.saturating_add(1);
3093 }
3094 }
3095 #[inline(always)]
3096 pub fn prev_ci(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
3097 self.call_info[idx.as_usize()].previous
3098 }
3099 pub fn get_prev_ci(&self, idx: CallInfoIdx) -> Option<&CallInfo> {
3100 self.call_info[idx.as_usize()]
3101 .previous
3102 .map(|p| &self.call_info[p.as_usize()])
3103 }
3104 #[inline(always)]
3105 pub fn is_base_ci(&self, idx: CallInfoIdx) -> bool {
3106 idx.as_usize() == 0
3107 }
3108 #[inline(always)]
3109 pub fn is_current_ci(&self, idx: CallInfoIdx) -> bool {
3110 idx == self.ci
3111 }
3112 pub fn ci_next_func(&self, idx: CallInfoIdx) -> StackIdx {
3113 let next = self.call_info[idx.as_usize()]
3114 .next
3115 .expect("ci_next_func: no next CallInfo");
3116 self.call_info[next.as_usize()].func
3117 }
3118 #[inline(always)]
3119 pub fn ci_top(&self, idx: CallInfoIdx) -> StackIdx {
3120 self.call_info[idx.as_usize()].top
3121 }
3122 #[inline(always)]
3127 pub fn ci_trap(&mut self, idx: CallInfoIdx) -> bool {
3128 self.call_info[idx.as_usize()].trap()
3129 }
3130 #[inline(always)]
3131 pub fn ci_savedpc(&self, idx: CallInfoIdx) -> u32 {
3132 self.call_info[idx.as_usize()].saved_pc()
3133 }
3134 #[inline(always)]
3135 pub fn set_ci_savedpc(&mut self, idx: CallInfoIdx, pc: u32) {
3136 self.call_info[idx.as_usize()].set_saved_pc(pc);
3137 }
3138 #[inline(always)]
3139 pub fn set_ci_previous(&mut self, idx: CallInfoIdx) {
3140 self.ci = self.call_info[idx.as_usize()]
3141 .previous
3142 .expect("set_ci_previous: returning frame has no previous CallInfo");
3143 }
3144 #[inline(always)]
3145 pub fn ci_previous(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
3146 self.call_info[idx.as_usize()].previous
3147 }
3148 #[inline(always)]
3149 pub fn ci_adjust_func(&mut self, idx: CallInfoIdx, delta: i32) {
3150 let ci = &mut self.call_info[idx.as_usize()];
3151 ci.func = StackIdx((ci.func.0 as i32 - delta) as u32);
3152 }
3153 #[inline(always)]
3154 pub fn ci_base(&self, idx: CallInfoIdx) -> StackIdx {
3155 self.call_info[idx.as_usize()].func + 1
3156 }
3157 #[inline(always)]
3158 pub fn ci_is_fresh(&self, idx: CallInfoIdx) -> bool {
3159 (self.call_info[idx.as_usize()].callstatus & CIST_FRESH) != 0
3160 }
3161 #[inline(always)]
3162 pub fn ci_lua_closure(
3163 &self,
3164 idx: CallInfoIdx,
3165 ) -> Option<GcRef<lua_types::closure::LuaLClosure>> {
3166 let func_idx = self.call_info[idx.as_usize()].func;
3167 match self.stack.get(func_idx.0 as usize).map(|slot| slot.val) {
3168 Some(LuaValue::Function(lua_types::closure::LuaClosure::Lua(cl))) => Some(cl),
3169 _ => None,
3170 }
3171 }
3172 #[inline(always)]
3173 pub fn ci_nextraargs(&self, idx: CallInfoIdx) -> i32 {
3174 self.call_info[idx.as_usize()].nextra_args()
3175 }
3176 #[inline(always)]
3177 pub fn ci_nres(&self, idx: CallInfoIdx) -> i32 {
3178 self.call_info[idx.as_usize()].u2.value
3179 }
3180 #[inline(always)]
3181 pub fn ci_nres_set(&mut self, idx: CallInfoIdx, n: i32) {
3182 self.call_info[idx.as_usize()].u2.value = n;
3183 }
3184 #[inline(always)]
3185 pub fn ci_nresults(&self, idx: CallInfoIdx) -> i32 {
3186 self.call_info[idx.as_usize()].nresults as i32
3187 }
3188 pub fn ci_prev_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3189 let pc = self.call_info[idx.as_usize()].saved_pc();
3190 let cl = self
3191 .ci_lua_closure(idx)
3192 .expect("ci_prev_instruction: CallInfo does not hold a Lua closure");
3193 cl.proto.code[(pc - 1) as usize]
3194 }
3195 pub fn ci_prev2_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3196 let pc = self.call_info[idx.as_usize()].saved_pc();
3197 let cl = self
3198 .ci_lua_closure(idx)
3199 .expect("ci_prev2_instruction: CallInfo does not hold a Lua closure");
3200 cl.proto.code[(pc - 2) as usize]
3201 }
3202 pub fn ci_skip_next_instruction(&mut self, idx: CallInfoIdx) {
3203 let pc = self.call_info[idx.as_usize()].saved_pc();
3204 self.call_info[idx.as_usize()].set_saved_pc(pc + 1);
3205 }
3206 pub fn ci_step_pc_back(&mut self, idx: CallInfoIdx) {
3207 let pc = self.call_info[idx.as_usize()].saved_pc();
3208 self.call_info[idx.as_usize()].set_saved_pc(pc - 1);
3209 }
3210 pub fn get_ci_pcrel(&mut self, idx: CallInfoIdx) -> u32 {
3211 self.call_info[idx.as_usize()].saved_pc().saturating_sub(1)
3212 }
3213 pub fn get_ci_u2_funcidx(&mut self, idx: CallInfoIdx) -> i32 {
3214 self.call_info[idx.as_usize()].u2.value
3215 }
3216 pub fn get_ci_u2_nres(&mut self, idx: CallInfoIdx) -> i32 {
3217 self.call_info[idx.as_usize()].u2.value
3218 }
3219 pub fn get_ci_u2_nyield(&mut self, idx: CallInfoIdx) -> i32 {
3220 self.call_info[idx.as_usize()].u2.value
3221 }
3222 pub fn get_ci_vararg_info(&mut self, idx: CallInfoIdx) -> (bool, i32, i32) {
3223 let nextraargs = self.call_info[idx.as_usize()].nextra_args();
3224 match self.ci_lua_closure(idx) {
3225 Some(cl) => (cl.proto.is_vararg, nextraargs, cl.proto.numparams as i32),
3226 None => (false, nextraargs, 0),
3227 }
3228 }
3229 pub fn get_ci_lua_proto_numparams(&mut self, idx: CallInfoIdx) -> u8 {
3230 self.ci_lua_closure(idx)
3231 .map(|cl| cl.proto.numparams)
3232 .unwrap_or(0)
3233 }
3234 pub fn set_ci_u2_nres(&mut self, idx: CallInfoIdx, n: i32) {
3235 self.call_info[idx.as_usize()].u2.value = n;
3236 }
3237 pub fn set_ci_u2_nyield(&mut self, idx: CallInfoIdx, n: i32) {
3238 self.call_info[idx.as_usize()].u2.value = n;
3239 }
3240 pub fn set_ci_transfer_info(&mut self, idx: CallInfoIdx, ftransfer: u16, ntransfer: u16) {
3241 let ci = &mut self.call_info[idx.as_usize()];
3242 ci.u2.ftransfer = ftransfer;
3243 ci.u2.ntransfer = ntransfer;
3244 }
3245 pub fn shrink_ci(&mut self) {
3246 shrink_ci(self)
3247 }
3248 pub fn check_c_stack(&mut self) -> Result<(), LuaError> {
3249 check_c_stack(self)
3250 }
3251
3252 pub fn status(&mut self) -> LuaStatus {
3253 LuaStatus::from_raw(self.status as i32)
3254 }
3255 pub fn errfunc(&mut self) -> isize {
3256 self.errfunc
3257 }
3258 pub fn old_pc(&mut self) -> u32 {
3259 self.oldpc
3260 }
3261 pub fn set_old_pc(&mut self, pc: u32) {
3262 self.oldpc = pc;
3263 }
3264 pub fn set_oldpc(&mut self, pc: u32) {
3265 self.oldpc = pc;
3266 }
3267 pub fn _hook_call_noargs(&mut self) {}
3268 pub fn hook(&self) -> Option<&Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>> {
3269 self.hook.as_ref()
3270 }
3271 pub fn has_hook(&mut self) -> bool {
3272 self.hook.is_some()
3273 }
3274 pub fn hook_count(&mut self) -> i32 {
3275 self.hookcount
3276 }
3277 pub fn set_hook_count(&mut self, n: i32) {
3278 self.hookcount = n;
3279 }
3280 pub fn hook_mask(&self) -> u8 {
3281 self.hookmask
3282 }
3283 pub fn set_hook_mask(&mut self, m: u8) {
3284 self.hookmask = m;
3285 }
3286 pub fn base_hook_count(&self) -> i32 {
3287 self.basehookcount
3288 }
3289 pub fn set_base_hook_count(&mut self, n: i32) {
3290 self.basehookcount = n;
3291 }
3292 pub fn set_hook(&mut self, h: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>) {
3293 self.hook = h;
3294 }
3295 pub fn call_hook_event(&mut self, event: i32, line: i32) -> Result<(), LuaError> {
3314 let saved_ci = self.ci;
3315 let r = crate::do_::hook(self, event, line, 0, 0);
3316 self.ci = saved_ci;
3317 r
3318 }
3319
3320 pub fn registry_value(&self) -> LuaValue {
3321 self.global().l_registry.clone()
3322 }
3323 pub fn registry_get(&self, key: usize) -> LuaValue {
3324 let reg = self.global().l_registry.clone();
3325 match reg {
3326 LuaValue::Table(t) => t.get(&LuaValue::Int(key as i64)),
3327 _ => LuaValue::Nil,
3328 }
3329 }
3330
3331 pub fn new_string(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3332 self.intern_or_create_str(bytes)
3333 }
3334
3335 pub fn new_proto(&mut self) -> GcRef<LuaProto> {
3347 self.mark_gc_check_needed();
3348 GcRef::new(LuaProto::placeholder())
3349 }
3350
3351 pub fn new_lclosure(&mut self, proto: GcRef<LuaProto>, nupvals: usize) -> GcRef<LuaClosureLua> {
3353 self.mark_gc_check_needed();
3354 let mut upvals = Vec::with_capacity(nupvals);
3355 for _ in 0..nupvals {
3356 upvals.push(std::cell::Cell::new(self.new_upval_closed(LuaValue::Nil)));
3357 }
3358 let upvals = upvals.into_boxed_slice();
3359 let closure = GcRef::new(LuaClosureLua { proto, upvals });
3360 closure.account_buffer(closure.buffer_bytes() as isize);
3361 closure
3362 }
3363
3364 pub fn new_upval_closed(&mut self, v: LuaValue) -> GcRef<UpVal> {
3366 self.mark_gc_check_needed();
3367 GcRef::new(UpVal::closed(v))
3368 }
3369
3370 pub fn new_upval_open(&mut self, thread_id: usize, level: StackIdx) -> GcRef<UpVal> {
3372 self.mark_gc_check_needed();
3373 self.legacy_open_upval_touched.set(true);
3374 GcRef::new(UpVal::open(thread_id, level))
3375 }
3376 pub fn intern_or_create_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3383 self.intern_str(bytes)
3384 }
3385 pub fn new_userdata(
3386 &mut self,
3387 _size: usize,
3388 _nuvalue: usize,
3389 ) -> Result<GcRef<LuaUserData>, LuaError> {
3390 Err(LuaError::runtime(format_args!(
3391 "new_userdata not implemented in this Phase-B build; use new_userdata_typed instead"
3392 )))
3393 }
3394 pub fn new_c_closure(&mut self, _f: LuaCFunction, _n: i32) -> Result<LuaClosure, LuaError> {
3395 Err(LuaError::runtime(format_args!("new_c_closure not implemented in this Phase-B build; use push_cclosure in lua_vm::api instead")))
3396 }
3397 pub fn push_closure(
3398 &mut self,
3399 proto_idx: usize,
3400 ci: CallInfoIdx,
3401 base: StackIdx,
3402 ra: StackIdx,
3403 ) -> Result<(), LuaError> {
3404 let parent_cl = self
3405 .ci_lua_closure(ci)
3406 .expect("push_closure: current frame is not a Lua closure");
3407 let child_proto = parent_cl.proto.p[proto_idx].clone();
3408 let nup = child_proto.upvalues.len();
3409 let mut upvals: Vec<std::cell::Cell<GcRef<UpVal>>> = Vec::with_capacity(nup);
3410 for i in 0..nup {
3411 let desc = &child_proto.upvalues[i];
3412 let uv = if desc.instack {
3413 let level = base + desc.idx as i32;
3414 crate::func::find_upval(self, level)
3415 } else {
3416 parent_cl.upval(desc.idx as usize)
3417 };
3418 upvals.push(std::cell::Cell::new(uv));
3419 }
3420 let cache_enabled = matches!(
3424 self.global().lua_version,
3425 lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
3426 );
3427 if cache_enabled {
3428 if let Some(cached) = child_proto.cache.borrow().as_ref() {
3429 if cached.upvals.len() == nup
3430 && (0..nup).all(|i| GcRef::ptr_eq(&cached.upvals[i].get(), &upvals[i].get()))
3431 {
3432 let reused = cached.clone();
3433 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(reused)));
3434 return Ok(());
3435 }
3436 }
3437 }
3438 self.mark_gc_check_needed();
3441 let new_cl = GcRef::new(LuaClosureLua {
3442 proto: child_proto.clone(),
3443 upvals: upvals.into_boxed_slice(),
3444 });
3445 new_cl.account_buffer(new_cl.buffer_bytes() as isize);
3446 if cache_enabled {
3447 *child_proto.cache.borrow_mut() = Some(new_cl.clone());
3448 }
3449 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(new_cl)));
3450 Ok(())
3451 }
3452 pub fn new_tbc_upval(&mut self, idx: StackIdx) -> Result<(), LuaError> {
3453 crate::func::new_tbc_upval(self, idx)
3454 }
3455
3456 #[inline(always)]
3477 pub fn upvalue_get(&self, cl: &GcRef<LuaClosureLua>, n: usize) -> LuaValue {
3478 let uv = cl.upval(n);
3479 let (thread_id, idx) = match uv.try_open_payload() {
3480 Some(p) => p,
3481 None => return uv.closed_value(),
3482 };
3483 let current = self.cached_thread_id;
3484 let tid = thread_id as u64;
3485 if tid == current {
3486 return self.stack[idx.0 as usize].val;
3487 }
3488 self.upvalue_get_cross_thread(tid, idx)
3489 }
3490
3491 #[cold]
3492 #[inline(never)]
3493 fn upvalue_get_cross_thread(&self, tid: u64, idx: StackIdx) -> LuaValue {
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(home_state) = rc.try_borrow() {
3500 return home_state.get_at(idx);
3501 }
3502 }
3503 let g = self.global();
3504 match g.cross_thread_upvals.get(&(tid, idx)) {
3505 Some(v) => *v,
3506 None => LuaValue::Nil,
3507 }
3508 }
3509 #[inline(always)]
3518 pub fn upvalue_set(
3519 &mut self,
3520 cl: &GcRef<LuaClosureLua>,
3521 n: usize,
3522 val: LuaValue,
3523 ) -> Result<(), LuaError> {
3524 let uv = cl.upval(n);
3525 match uv.try_open_payload() {
3526 Some((thread_id, idx)) => {
3527 let tid = thread_id as u64;
3528 let current = self.cached_thread_id;
3529 if tid == current {
3530 self.stack[idx.0 as usize].val = val;
3531 } else {
3532 self.upvalue_set_cross_thread(tid, idx, val)?;
3533 }
3534 }
3535 None => {
3536 uv.set_closed_value(val);
3537 }
3538 }
3539 if val.is_collectable() {
3540 self.gc_barrier_upval(&uv, &val);
3541 }
3542 Ok(())
3543 }
3544
3545 #[cold]
3546 #[inline(never)]
3547 fn upvalue_set_cross_thread(
3548 &mut self,
3549 tid: u64,
3550 idx: StackIdx,
3551 val: LuaValue,
3552 ) -> Result<(), LuaError> {
3553 let entry_rc = {
3554 let g = self.global();
3555 g.threads.get(&tid).map(|e| e.state.clone())
3556 };
3557 if let Some(rc) = entry_rc {
3558 if let Ok(mut home_state) = rc.try_borrow_mut() {
3559 home_state.set_at(idx, val);
3560 return Ok(());
3561 }
3562 }
3563 let mut g = self.global_mut();
3564 g.cross_thread_upvals.insert((tid, idx), val);
3565 Ok(())
3566 }
3567
3568 pub fn protected_call_raw(
3569 &mut self,
3570 func: StackIdx,
3571 nresults: i32,
3572 errfunc: StackIdx,
3573 ) -> Result<(), LuaError> {
3574 let ef = errfunc.0 as isize;
3575 let status = crate::do_::pcall(self, |s| s.call_no_yield(func, nresults), func, ef);
3576 match status {
3577 LuaStatus::Ok => Ok(()),
3578 LuaStatus::ErrSyntax => {
3579 let err_val = self.get_at(func);
3580 self.set_top(func);
3581 Err(LuaError::Syntax(err_val))
3582 }
3583 LuaStatus::Yield => {
3584 self.set_top(func);
3585 Err(LuaError::Yield)
3586 }
3587 _ => {
3588 let err_val = self.get_at(func);
3589 self.set_top(func);
3590 Err(LuaError::Runtime(err_val))
3591 }
3592 }
3593 }
3594 pub fn protected_parser(
3595 &mut self,
3596 z: crate::zio::ZIO,
3597 name: &[u8],
3598 mode: Option<&[u8]>,
3599 ) -> LuaStatus {
3600 crate::do_::protected_parser(self, z, name, mode)
3601 }
3602 pub fn do_call(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3603 crate::do_::call(self, func, nresults)
3604 }
3605 pub fn do_call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3606 crate::do_::callnoyield(self, func, nresults)
3607 }
3608 pub fn call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3609 crate::do_::callnoyield(self, func, nresults)
3610 }
3611 pub fn call_at(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3612 crate::do_::call(self, func, nresults)
3613 }
3614 #[inline(always)]
3615 pub fn call_known_c_at(&mut self, func: StackIdx, nresults: i32) -> Result<bool, LuaError> {
3616 crate::do_::call_known_c(self, func, nresults)
3617 }
3618 #[inline(always)]
3619 pub fn precall(
3620 &mut self,
3621 func: StackIdx,
3622 nresults: i32,
3623 ) -> Result<Option<CallInfoIdx>, LuaError> {
3624 crate::do_::precall(self, func, nresults)
3625 }
3626 #[inline(always)]
3627 pub fn pretailcall(
3628 &mut self,
3629 ci: CallInfoIdx,
3630 func: StackIdx,
3631 narg1: i32,
3632 delta: i32,
3633 ) -> Result<i32, LuaError> {
3634 crate::do_::pretailcall(self, ci, func, narg1, delta)
3635 }
3636 #[inline(always)]
3637 pub fn poscall<N: TryInto<i32>>(&mut self, ci: CallInfoIdx, nres: N) -> Result<(), LuaError>
3638 where
3639 <N as TryInto<i32>>::Error: std::fmt::Debug,
3640 {
3641 let n = nres.try_into().expect("poscall: nres out of i32 range");
3642 crate::do_::poscall(self, ci, n)
3643 }
3644 pub fn adjust_results(&mut self, nresults: i32) {
3645 const LUA_MULTRET: i32 = -1;
3646 if nresults <= LUA_MULTRET {
3647 let ci_idx = self.ci.as_usize();
3648 if self.call_info[ci_idx].top.0 < self.top.0 {
3649 self.call_info[ci_idx].top = self.top;
3650 }
3651 }
3652 }
3653 pub fn adjust_varargs(
3654 &mut self,
3655 ci: CallInfoIdx,
3656 nfixparams: i32,
3657 cl: &GcRef<lua_types::closure::LuaLClosure>,
3658 ) -> Result<(), LuaError> {
3659 crate::tagmethods::adjust_varargs(self, nfixparams, ci, &cl.0.proto)
3660 }
3661 pub fn get_varargs(&mut self, ci: CallInfoIdx, ra: StackIdx, n: i32) -> Result<i32, LuaError> {
3662 crate::tagmethods::get_varargs(self, ci, ra, n)?;
3663 Ok(0)
3664 }
3665
3666 pub fn close_upvals(&mut self, level: StackIdx) -> Result<(), LuaError> {
3667 crate::func::close_upval(self, level);
3668 Ok(())
3669 }
3670 pub fn close_upvals_status(&mut self, level: StackIdx, _status: i32) -> Result<(), LuaError> {
3671 crate::func::close_upval(self, level);
3672 Ok(())
3673 }
3674 pub fn close_upvals_from_base(&mut self, ci: CallInfoIdx) -> Result<(), LuaError> {
3675 let base = self.ci_base(ci);
3676 crate::func::close_upval(self, base);
3677 Ok(())
3678 }
3679
3680 pub fn arith_op(
3681 &mut self,
3682 op: i32,
3683 p1: &LuaValue,
3684 p2: &LuaValue,
3685 ) -> Result<LuaValue, LuaError> {
3686 let arith_op = match op {
3687 0 => lua_types::arith::ArithOp::Add,
3688 1 => lua_types::arith::ArithOp::Sub,
3689 2 => lua_types::arith::ArithOp::Mul,
3690 3 => lua_types::arith::ArithOp::Mod,
3691 4 => lua_types::arith::ArithOp::Pow,
3692 5 => lua_types::arith::ArithOp::Div,
3693 6 => lua_types::arith::ArithOp::Idiv,
3694 7 => lua_types::arith::ArithOp::Band,
3695 8 => lua_types::arith::ArithOp::Bor,
3696 9 => lua_types::arith::ArithOp::Bxor,
3697 10 => lua_types::arith::ArithOp::Shl,
3698 11 => lua_types::arith::ArithOp::Shr,
3699 12 => lua_types::arith::ArithOp::Unm,
3700 13 => lua_types::arith::ArithOp::Bnot,
3701 _ => return Err(LuaError::runtime(format_args!("invalid arith op {}", op))),
3702 };
3703 let mut res = LuaValue::Nil;
3704 if crate::object::raw_arith(self, arith_op, p1, p2, &mut res)? {
3705 Ok(res)
3706 } else {
3707 Err(LuaError::arith_error(p1, p2, "perform arithmetic on"))
3708 }
3709 }
3710 pub fn concat(&mut self, n: i32) -> Result<(), LuaError> {
3711 crate::vm::concat(self, n)
3712 }
3713 pub fn less_than(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3714 crate::vm::less_than(self, l, r)
3715 }
3716 pub fn less_equal(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3717 crate::vm::less_equal(self, l, r)
3718 }
3719 pub fn equal_obj(&self, _ctx: Option<&LuaValue>, l: &LuaValue, r: &LuaValue) -> bool {
3720 crate::vm::equal_obj(None, l, r).unwrap_or(false)
3721 }
3722 pub fn equal_obj_with_tm(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3723 crate::vm::equal_obj(Some(self), l, r)
3724 }
3725 pub fn obj_len(&mut self, v: &LuaValue) -> Result<LuaValue, LuaError> {
3726 match v {
3727 LuaValue::Table(_) => {
3728 let consult_len_tm =
3731 !matches!(self.global().lua_version, lua_types::LuaVersion::V51);
3732 let tm = if consult_len_tm {
3733 let mt = self.table_metatable(v);
3734 self.fast_tm_table(mt.as_ref(), TagMethod::Len)
3735 } else {
3736 LuaValue::Nil
3737 };
3738 if matches!(tm, LuaValue::Nil) {
3739 let n = self.table_length(v)?;
3740 return Ok(LuaValue::Int(n));
3741 }
3742 self.push(LuaValue::Nil);
3743 let slot = StackIdx(self.top.0 - 1);
3744 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3745 Ok(self.pop())
3746 }
3747 LuaValue::Str(s) => Ok(LuaValue::Int(s.len() as i64)),
3748 other => {
3749 let tm = crate::tagmethods::get_tm_by_obj(
3750 self,
3751 other,
3752 crate::tagmethods::TagMethod::Len,
3753 );
3754 if matches!(tm, LuaValue::Nil) {
3755 let mut msg = b"attempt to get length of a ".to_vec();
3756 msg.extend_from_slice(&self.obj_type_name(other));
3757 msg.extend_from_slice(b" value");
3758 return Err(crate::debug::prefixed_runtime_pub(self, msg));
3759 }
3760 self.push(LuaValue::Nil);
3761 let slot = StackIdx(self.top.0 - 1);
3762 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3763 Ok(self.pop())
3764 }
3765 }
3766 }
3767 pub fn obj_to_string(&mut self, idx: i32) -> Result<GcRef<LuaString>, LuaError> {
3768 let slot: StackIdx = if idx > 0 {
3769 let ci_func = self.current_call_info().func;
3770 ci_func + idx
3771 } else {
3772 debug_assert!(idx != 0, "invalid index");
3773 StackIdx((self.top_idx().0 as i32 + idx) as u32)
3774 };
3775 let val = self.get_at(slot);
3776 match val {
3777 LuaValue::Str(s) => Ok(s),
3778 LuaValue::Int(_) | LuaValue::Float(_) => {
3779 let s = crate::object::num_to_string(self, &val)?;
3780 self.set_at(slot, LuaValue::Str(s.clone()));
3781 Ok(s)
3782 }
3783 _ => Err(LuaError::type_error(&val, "convert to string")),
3784 }
3785 }
3786 pub fn coerce_to_string(&mut self, idx: StackIdx) -> Result<GcRef<LuaString>, LuaError> {
3787 let val = self.get_at(idx);
3788 match val {
3789 LuaValue::Str(s) => Ok(s),
3790 LuaValue::Int(_) | LuaValue::Float(_) => {
3791 let s = crate::object::num_to_string(self, &val)?;
3792 self.set_at(idx, LuaValue::Str(s.clone()));
3793 Ok(s)
3794 }
3795 _ => Err(LuaError::type_error(&val, "convert to string")),
3796 }
3797 }
3798 pub fn str_to_num(&mut self, s: &[u8]) -> Option<(LuaValue, usize)> {
3806 let mut out = LuaValue::Nil;
3807 let float_only =
3808 self.global().lua_version.number_model() == lua_types::NumberModel::FloatOnly;
3809 let sz = if float_only {
3810 crate::object::str2num_float_only(s, &mut out)
3811 } else {
3812 crate::object::str2num(s, &mut out)
3813 };
3814 if sz == 0 {
3815 return None;
3816 }
3817 Some((out, sz))
3818 }
3819
3820 #[inline(always)]
3821 pub fn fast_get(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
3822 let LuaValue::Table(tbl) = t else {
3823 return Ok(None);
3824 };
3825 let v = tbl.get(k);
3826 if matches!(v, LuaValue::Nil) {
3827 Ok(None)
3828 } else {
3829 Ok(Some(v))
3830 }
3831 }
3832 #[inline(always)]
3833 pub fn fast_get_int(&mut self, t: &LuaValue, k: i64) -> Result<Option<LuaValue>, LuaError> {
3834 let LuaValue::Table(tbl) = t else {
3835 return Ok(None);
3836 };
3837 let v = tbl.get_int(k);
3838 if matches!(v, LuaValue::Nil) {
3839 Ok(None)
3840 } else {
3841 Ok(Some(v))
3842 }
3843 }
3844 #[inline(always)]
3845 pub fn fast_get_short_str(
3846 &mut self,
3847 t: &LuaValue,
3848 k: &LuaValue,
3849 ) -> Result<Option<LuaValue>, LuaError> {
3850 let LuaValue::Table(tbl) = t else {
3851 return Ok(None);
3852 };
3853 let LuaValue::Str(s) = k else {
3854 return Ok(None);
3855 };
3856 let v = tbl.get_short_str(s);
3857 if matches!(v, LuaValue::Nil) {
3858 Ok(None)
3859 } else {
3860 Ok(Some(v))
3861 }
3862 }
3863 #[inline(always)]
3864 pub fn fast_tm_table(&mut self, t: Option<&GcRef<LuaTable>>, tm: TagMethod) -> LuaValue {
3865 let Some(mt) = t else {
3866 return LuaValue::Nil;
3867 };
3868 debug_assert!((tm as u8) <= TagMethod::Eq as u8);
3869 let ename = self.global().tmname[tm as usize].clone();
3870 mt.get_short_str(&ename)
3871 }
3872 pub fn fast_tm_ud(&mut self, u: &GcRef<LuaUserData>, tm: TagMethod) -> LuaValue {
3873 let mt = u.metatable();
3875 self.fast_tm_table(mt.as_ref(), tm)
3876 }
3877
3878 pub fn table_get_with_tm(&mut self, t: &LuaValue, k: &LuaValue) -> Result<LuaValue, LuaError> {
3879 if let LuaValue::Table(tbl) = t {
3885 if !tbl.has_metatable() {
3886 return Ok(tbl.get(k));
3887 }
3888 }
3889 if let Some(v) = self.fast_get(t, k)? {
3890 return Ok(v);
3891 }
3892 let res = self.top_idx();
3893 self.push(LuaValue::Nil);
3894 crate::vm::finish_get(self, t.clone(), k.clone(), res, true, None, None)?;
3895 let value = self.get_at(res);
3896 self.pop();
3897 Ok(value)
3898 }
3899 #[inline]
3914 pub fn table_set_with_tm(
3915 &mut self,
3916 t: &LuaValue,
3917 k: LuaValue,
3918 v: LuaValue,
3919 ) -> Result<(), LuaError> {
3920 if let LuaValue::Table(tbl) = t {
3921 if !tbl.has_metatable() {
3922 self.gc_table_barrier_back(tbl, &v);
3923 return self.table_raw_set(t, k, v);
3924 }
3925 }
3926 if self.fast_get(t, &k)?.is_some() {
3927 self.gc_value_barrier_back(t, &v);
3928 return self.table_raw_set(t, k, v);
3929 }
3930 crate::vm::finish_set(self, t.clone(), k, v, true, None, None)
3931 }
3932 #[inline]
3933 pub fn table_raw_set(
3934 &mut self,
3935 t: &LuaValue,
3936 k: LuaValue,
3937 v: LuaValue,
3938 ) -> Result<(), LuaError> {
3939 let LuaValue::Table(tbl) = t else {
3940 return Err(LuaError::type_error(t, "index"));
3941 };
3942 let tbl = tbl.clone();
3943 tbl.raw_set(self, k, v)
3944 }
3945 #[inline]
3946 pub fn table_array_set(
3947 &mut self,
3948 t: &LuaValue,
3949 idx: usize,
3950 v: LuaValue,
3951 ) -> Result<(), LuaError> {
3952 let LuaValue::Table(tbl) = t else {
3953 return Err(LuaError::type_error(t, "index"));
3954 };
3955 let tbl = tbl.clone();
3956 tbl.raw_set_int(self, idx as i64 + 1, v)
3957 }
3958 pub fn table_ensure_array(&mut self, t: &LuaValue, n: usize) -> Result<(), LuaError> {
3959 let LuaValue::Table(tbl) = t else {
3960 return Err(LuaError::type_error(t, "index"));
3961 };
3962 if n > tbl.array_len() {
3963 tbl.resize(self, n, 0)?;
3964 }
3965 Ok(())
3966 }
3967 pub fn table_length(&mut self, t: &LuaValue) -> Result<i64, LuaError> {
3968 let LuaValue::Table(tbl) = t else {
3969 return Err(LuaError::type_error(t, "get length of"));
3970 };
3971 Ok(tbl.getn() as i64)
3972 }
3973 pub fn table_metatable(&mut self, v: &LuaValue) -> Option<GcRef<LuaTable>> {
3974 match v {
3975 LuaValue::Table(t) => t.metatable(),
3976 LuaValue::UserData(u) => u.metatable(),
3977 other => {
3978 let idx = other.base_type() as usize;
3979 self.global().mt[idx].clone()
3980 }
3981 }
3982 }
3983 pub fn table_resize(
3984 &mut self,
3985 t: &GcRef<LuaTable>,
3986 na: usize,
3987 nh: usize,
3988 ) -> Result<(), LuaError> {
3989 self.mark_gc_check_needed();
3990 t.resize(self, na, nh)
3991 }
3992 pub fn table_getn(&self, t: &GcRef<LuaTable>) -> i64 {
3993 let mut i: i64 = 1;
4001 loop {
4002 let v = t.get_int(i);
4003 if matches!(v, LuaValue::Nil) {
4004 return i - 1;
4005 }
4006 i += 1;
4007 }
4008 }
4009
4010 pub fn try_bin_tm(
4011 &mut self,
4012 p1: &LuaValue,
4013 p1_idx: Option<StackIdx>,
4014 p2: &LuaValue,
4015 p2_idx: Option<StackIdx>,
4016 res: StackIdx,
4017 tm: lua_types::tagmethod::TagMethod,
4018 ) -> Result<(), LuaError> {
4019 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4020 crate::tagmethods::try_bin_tm(self, p1, p1_idx, p2, p2_idx, res, event)
4021 }
4022 pub fn try_bin_i_tm(
4023 &mut self,
4024 p1: &LuaValue,
4025 p1_idx: Option<StackIdx>,
4026 imm: i64,
4027 flip: bool,
4028 res: StackIdx,
4029 tm: lua_types::tagmethod::TagMethod,
4030 ) -> Result<(), LuaError> {
4031 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4032 crate::tagmethods::try_bini_tm(self, p1, p1_idx, imm, flip, res, event)
4033 }
4034 pub fn try_bin_assoc_tm(
4035 &mut self,
4036 p1: &LuaValue,
4037 p1_idx: Option<StackIdx>,
4038 p2: &LuaValue,
4039 p2_idx: Option<StackIdx>,
4040 flip: bool,
4041 res: StackIdx,
4042 tm: lua_types::tagmethod::TagMethod,
4043 ) -> Result<(), LuaError> {
4044 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4045 crate::tagmethods::try_bin_assoc_tm(self, p1, p1_idx, p2, p2_idx, flip, res, event)
4046 }
4047 pub fn try_concat_tm(&mut self, _p1: &LuaValue, _p2: &LuaValue) -> Result<(), LuaError> {
4048 crate::tagmethods::try_concat_tm(self)
4049 }
4050 pub fn call_tm(
4051 &mut self,
4052 f: LuaValue,
4053 p1: &LuaValue,
4054 p2: &LuaValue,
4055 p3: &LuaValue,
4056 ) -> Result<(), LuaError> {
4057 crate::tagmethods::call_tm(self, f, p1.clone(), p2.clone(), p3.clone())
4058 }
4059 pub fn call_tm_res(
4060 &mut self,
4061 f: LuaValue,
4062 p1: &LuaValue,
4063 p2: &LuaValue,
4064 res: StackIdx,
4065 ) -> Result<(), LuaError> {
4066 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)
4067 }
4068 pub fn call_tm_res_bool(
4069 &mut self,
4070 f: LuaValue,
4071 p1: &LuaValue,
4072 p2: &LuaValue,
4073 ) -> Result<bool, LuaError> {
4074 let res = self.top_idx();
4075 self.push(LuaValue::Nil);
4076 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)?;
4077 let result = self.get_at(res).clone();
4078 self.pop();
4079 Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)))
4080 }
4081 pub fn call_order_tm(
4082 &mut self,
4083 p1: &LuaValue,
4084 p2: &LuaValue,
4085 tm: lua_types::tagmethod::TagMethod,
4086 ) -> Result<bool, LuaError> {
4087 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4088 crate::tagmethods::call_order_tm(self, p1, p2, event)
4089 }
4090 pub fn call_order_i_tm(
4091 &mut self,
4092 p1: &LuaValue,
4093 v2: i64,
4094 flip: bool,
4095 isfloat: bool,
4096 tm: lua_types::tagmethod::TagMethod,
4097 ) -> Result<bool, LuaError> {
4098 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4099 crate::tagmethods::call_orderi_tm(self, p1, v2 as i32, flip, isfloat, event)
4100 }
4101
4102 #[inline(always)]
4103 pub fn proto_code(
4104 &self,
4105 cl: &GcRef<lua_types::closure::LuaLClosure>,
4106 pc: u32,
4107 ) -> lua_types::opcode::Instruction {
4108 cl.proto.code[pc as usize]
4109 }
4110 #[inline(always)]
4111 pub fn proto_const(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> LuaValue {
4112 cl.proto.k[idx].clone()
4113 }
4114 #[inline(always)]
4120 pub fn proto_const_int(
4121 &self,
4122 cl: &GcRef<lua_types::closure::LuaLClosure>,
4123 idx: usize,
4124 ) -> Option<i64> {
4125 match &cl.proto.k[idx] {
4126 LuaValue::Int(v) => Some(*v),
4127 _ => None,
4128 }
4129 }
4130 #[inline(always)]
4134 pub fn proto_const_num(
4135 &self,
4136 cl: &GcRef<lua_types::closure::LuaLClosure>,
4137 idx: usize,
4138 ) -> Option<f64> {
4139 match &cl.proto.k[idx] {
4140 LuaValue::Float(f) => Some(*f),
4141 LuaValue::Int(v) => Some(*v as f64),
4142 _ => None,
4143 }
4144 }
4145 pub fn get_proto_instr(&self, ci: CallInfoIdx, pc: u32) -> lua_types::opcode::Instruction {
4146 let cl = self
4147 .ci_lua_closure(ci)
4148 .expect("get_proto_instr: CallInfo does not hold a Lua closure");
4149 cl.proto.code[pc as usize]
4150 }
4151 pub fn trace_call(&mut self, _idx: CallInfoIdx) -> Result<bool, LuaError> {
4156 Ok(crate::debug::trace_call(self)? != 0)
4157 }
4158 pub fn trace_exec(&mut self, _idx: CallInfoIdx, pc: u32) -> Result<bool, LuaError> {
4161 Ok(crate::debug::trace_exec(self, pc)? != 0)
4162 }
4163 pub fn hook_call(&mut self, idx: CallInfoIdx) -> Result<(), LuaError> {
4174 if self.global().lua_version == lua_types::LuaVersion::V51 {
4175 let had_tail = self.call_info[idx.as_usize()].callstatus & CIST_TAIL;
4176 self.call_info[idx.as_usize()].callstatus &= !CIST_TAIL;
4177 let r = crate::do_::hookcall(self, idx);
4178 self.call_info[idx.as_usize()].callstatus |= had_tail;
4179 return r;
4180 }
4181 crate::do_::hookcall(self, idx)
4182 }
4183 #[inline(always)]
4184 fn gc_step_flags(&self) -> Option<(bool, bool)> {
4185 let g = self.global();
4186 if !g.is_gc_running() {
4187 return None;
4188 }
4189 let should_collect = g.heap.would_collect();
4190 let has_finalizers = g.finalizers.has_to_be_finalized();
4191 if should_collect || has_finalizers {
4192 Some((should_collect, has_finalizers))
4193 } else {
4194 None
4195 }
4196 }
4197
4198 #[inline(always)]
4199 fn should_check_gc(&mut self) -> bool {
4200 if self.gc_check_needed {
4201 return true;
4202 }
4203 if self.global().finalizers.has_to_be_finalized() {
4204 self.gc_check_needed = true;
4205 return true;
4206 }
4207 false
4208 }
4209
4210 #[inline(always)]
4211 pub(crate) fn mark_gc_check_needed(&mut self) {
4212 self.gc_check_needed = true;
4213 }
4214
4215 pub fn gc_trace_bound(&self) -> usize {
4229 (self.top.0 as usize).min(self.stack.len())
4230 }
4231
4232 pub fn clear_dead_stack_tail(&mut self) {
4239 let bound = self.gc_trace_bound();
4240 for slot in &mut self.stack[bound..] {
4241 slot.val = LuaValue::Nil;
4242 }
4243 }
4244
4245 pub fn gc_clear_dead_stack_tails(&mut self) {
4252 self.clear_dead_stack_tail();
4253 let global = self.global.clone();
4254 let g = global.borrow();
4255 for entry in g.threads.values() {
4256 if let Ok(mut t) = entry.state.try_borrow_mut() {
4257 t.clear_dead_stack_tail();
4258 }
4259 }
4260 }
4261
4262 pub fn gc_pre_collect_clear(&mut self) {
4266 if self.global().heap.would_collect() {
4267 self.gc_clear_dead_stack_tails();
4268 }
4269 }
4270
4271 #[inline(always)]
4272 pub fn gc_check_step(&mut self) {
4273 if !self.allowhook {
4274 return;
4275 }
4276 if !self.should_check_gc() {
4277 return;
4278 }
4279 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4280 self.gc_check_needed = false;
4281 return;
4282 };
4283 if should_collect || has_finalizers {
4284 if should_collect {
4285 self.gc_clear_dead_stack_tails();
4286 self.gc().check_step();
4287 }
4288 crate::api::run_pending_finalizers(self);
4289 self.gc_check_needed = true;
4290 }
4291 let should_keep_checking = {
4292 let g = self.global();
4293 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4294 };
4295 self.gc_check_needed = should_keep_checking;
4296 }
4297 #[inline(always)]
4298 pub fn gc_cond_step(&mut self) {
4299 if !self.allowhook {
4300 return;
4301 }
4302 if !self.should_check_gc() {
4303 return;
4304 }
4305 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4306 self.gc_check_needed = false;
4307 return;
4308 };
4309 if should_collect || has_finalizers {
4310 if should_collect {
4311 self.gc_clear_dead_stack_tails();
4312 self.gc().check_step();
4313 }
4314 crate::api::run_pending_finalizers(self);
4315 self.gc_check_needed = true;
4316 }
4317 let should_keep_checking = {
4318 let g = self.global();
4319 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4320 };
4321 self.gc_check_needed = should_keep_checking;
4322 }
4323 pub fn gc_barrier_back(&mut self, t: &dyn std::any::Any, v: &LuaValue) {
4324 self.gc().barrier_back(t, v);
4325 }
4326 #[inline(always)]
4327 pub fn gc_value_barrier_back(&mut self, t: &LuaValue, v: &LuaValue) {
4328 if !v.is_collectable() {
4329 return;
4330 }
4331 if let LuaValue::Table(tbl) = t {
4332 self.gc_table_barrier_back(tbl, v);
4333 } else {
4334 self.gc_barrier_back(t, v);
4335 }
4336 }
4337 #[inline(always)]
4338 pub fn gc_table_barrier_back(&mut self, t: &GcRef<LuaTable>, v: &LuaValue) {
4339 if !v.is_collectable() {
4340 return;
4341 }
4342 self.gc().table_barrier_back(t, v);
4343 }
4344 pub fn gc_barrier_upval(&mut self, uv: &GcRef<UpVal>, v: &LuaValue) {
4345 self.gc().barrier(uv, v);
4346 }
4347 pub fn is_main_thread(&mut self) -> bool {
4353 let g = self.global();
4354 g.current_thread_id == g.main_thread_id
4355 }
4356 pub fn obj_type_name<'v>(&self, v: &'v LuaValue) -> std::borrow::Cow<'static, [u8]> {
4357 let honors_name = self.global().lua_version.honors_name_metafield();
4358 match v {
4359 LuaValue::LightUserData(_) => std::borrow::Cow::Borrowed(b"light userdata"),
4360 LuaValue::Table(t) => {
4361 if honors_name {
4362 if let Some(mt) = t.metatable() {
4363 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4364 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4365 }
4366 }
4367 }
4368 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4369 }
4370 LuaValue::UserData(u) => {
4371 if honors_name {
4372 if let Some(mt) = u.metatable() {
4373 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4374 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4375 }
4376 }
4377 }
4378 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4379 }
4380 _ => std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type())),
4381 }
4382 }
4383
4384 pub fn full_type_name(&mut self, v: &LuaValue) -> Result<Vec<u8>, LuaError> {
4385 crate::tagmethods::obj_type_name(self, v)
4386 }
4387 pub fn emit_warning(&mut self, _msg: &[u8], _to_cont: bool) {
4388 warning(self, _msg, _to_cont)
4389 }
4390}
4391
4392pub struct GcHandle<'a> {
4398 _state: &'a mut LuaState,
4399}
4400
4401struct CollectRoots<'a> {
4408 global: &'a GlobalState,
4409 thread: &'a LuaState,
4410}
4411
4412#[derive(Clone, Copy)]
4413enum HeapCollectMode {
4414 Full,
4415 Step,
4416 Minor,
4417}
4418
4419impl<'a> lua_gc::Trace for CollectRoots<'a> {
4420 fn trace(&self, m: &mut lua_gc::Marker) {
4421 self.global.trace(m);
4422 self.thread.trace(m);
4423 }
4424}
4425
4426#[derive(Clone, Copy)]
4427enum BarrierKind {
4428 Forward,
4429 Backward,
4430}
4431
4432fn barrier_lua_value<P>(
4433 heap: &lua_gc::Heap,
4434 parent: GcRef<P>,
4435 child: &LuaValue,
4436 generational: bool,
4437 kind: BarrierKind,
4438) where
4439 P: lua_gc::Trace + 'static,
4440{
4441 if !child.is_collectable() {
4442 return;
4443 }
4444 if generational && matches!(kind, BarrierKind::Backward) {
4445 heap.generational_backward_barrier(parent.0);
4446 }
4447 match child {
4448 LuaValue::Str(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4449 LuaValue::Table(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4450 LuaValue::Function(LuaClosure::Lua(c)) => {
4451 barrier_gc_child(heap, parent, *c, generational, kind)
4452 }
4453 LuaValue::Function(LuaClosure::C(c)) => {
4454 barrier_gc_child(heap, parent, *c, generational, kind)
4455 }
4456 LuaValue::UserData(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4457 LuaValue::Thread(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4458 LuaValue::Nil
4459 | LuaValue::Bool(_)
4460 | LuaValue::Int(_)
4461 | LuaValue::Float(_)
4462 | LuaValue::LightUserData(_)
4463 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4464 }
4465}
4466
4467fn barrier_gc_child<P, C>(
4468 heap: &lua_gc::Heap,
4469 parent: GcRef<P>,
4470 child: GcRef<C>,
4471 generational: bool,
4472 kind: BarrierKind,
4473) where
4474 P: lua_gc::Trace + 'static,
4475 C: lua_gc::Trace + 'static,
4476{
4477 if generational && matches!(kind, BarrierKind::Forward) {
4478 heap.generational_forward_barrier(parent.0, child.0);
4479 } else if matches!(kind, BarrierKind::Backward) {
4480 heap.barrier_back(parent.0, child.0);
4481 } else {
4482 heap.barrier(parent.0, child.0);
4483 }
4484}
4485
4486fn barrier_child_any<P>(
4487 heap: &lua_gc::Heap,
4488 parent: GcRef<P>,
4489 child: &dyn std::any::Any,
4490 generational: bool,
4491 kind: BarrierKind,
4492) where
4493 P: lua_gc::Trace + 'static,
4494{
4495 if let Some(v) = child.downcast_ref::<LuaValue>() {
4496 barrier_lua_value(heap, parent, v, generational, kind);
4497 } else if let Some(c) = child.downcast_ref::<GcRef<LuaString>>() {
4498 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4499 } else if let Some(c) = child.downcast_ref::<GcRef<LuaTable>>() {
4500 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4501 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureLua>>() {
4502 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4503 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureC>>() {
4504 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4505 } else if let Some(c) = child.downcast_ref::<GcRef<LuaUserData>>() {
4506 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4507 } else if let Some(c) = child.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4508 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4509 } else if let Some(c) = child.downcast_ref::<GcRef<LuaProto>>() {
4510 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4511 } else if let Some(c) = child.downcast_ref::<GcRef<UpVal>>() {
4512 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4513 }
4514}
4515
4516fn barrier_any(
4517 heap: &lua_gc::Heap,
4518 parent: &dyn std::any::Any,
4519 child: &dyn std::any::Any,
4520 generational: bool,
4521 kind: BarrierKind,
4522) {
4523 if let Some(v) = parent.downcast_ref::<LuaValue>() {
4524 match v {
4525 LuaValue::Str(p) => barrier_child_any(heap, *p, child, generational, kind),
4526 LuaValue::Table(p) => barrier_child_any(heap, *p, child, generational, kind),
4527 LuaValue::Function(LuaClosure::Lua(p)) => {
4528 barrier_child_any(heap, *p, child, generational, kind)
4529 }
4530 LuaValue::Function(LuaClosure::C(p)) => {
4531 barrier_child_any(heap, *p, child, generational, kind)
4532 }
4533 LuaValue::UserData(p) => barrier_child_any(heap, *p, child, generational, kind),
4534 LuaValue::Thread(p) => barrier_child_any(heap, *p, child, generational, kind),
4535 LuaValue::Nil
4536 | LuaValue::Bool(_)
4537 | LuaValue::Int(_)
4538 | LuaValue::Float(_)
4539 | LuaValue::LightUserData(_)
4540 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4541 }
4542 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaString>>() {
4543 barrier_child_any(heap, p.clone(), child, generational, kind);
4544 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaTable>>() {
4545 barrier_child_any(heap, p.clone(), child, generational, kind);
4546 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureLua>>() {
4547 barrier_child_any(heap, p.clone(), child, generational, kind);
4548 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureC>>() {
4549 barrier_child_any(heap, p.clone(), child, generational, kind);
4550 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaUserData>>() {
4551 barrier_child_any(heap, p.clone(), child, generational, kind);
4552 } else if let Some(p) = parent.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4553 barrier_child_any(heap, p.clone(), child, generational, kind);
4554 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaProto>>() {
4555 barrier_child_any(heap, p.clone(), child, generational, kind);
4556 } else if let Some(p) = parent.downcast_ref::<GcRef<UpVal>>() {
4557 barrier_child_any(heap, p.clone(), child, generational, kind);
4558 }
4559}
4560
4561fn trace_reachable_threads(
4573 global: &GlobalState,
4574 _current_thread_id: u64,
4575 marker: &mut lua_gc::Marker,
4576) {
4577 use lua_gc::Trace;
4578
4579 #[cfg(debug_assertions)]
4580 let mut uncovered_borrowed: Vec<u64> = Vec::new();
4581
4582 loop {
4583 let visited_before = marker.visited_count();
4584 for (id, entry) in global.threads.iter() {
4585 if thread_entry_marked_alive(marker, *id, entry) {
4586 match entry.state.try_borrow() {
4587 Ok(thread) => thread.trace(marker),
4588 Err(_) => {
4589 #[cfg(debug_assertions)]
4590 if *id != _current_thread_id && !uncovered_borrowed.contains(id) {
4591 uncovered_borrowed.push(*id);
4592 }
4593 }
4594 }
4595 }
4596 }
4597 marker.drain_gray_queue();
4598 if marker.visited_count() == visited_before {
4599 break;
4600 }
4601 }
4602
4603 remark_legacy_open_upvalues(global, marker);
4604
4605 #[cfg(debug_assertions)]
4606 debug_assert!(
4607 uncovered_borrowed.len() <= global.suspended_parent_stacks.len(),
4608 "GC root loss: {} marked-alive coroutine(s) (ids {:?}) were mutably \
4609 borrowed at collect time with only {} parent snapshot(s) covering \
4610 them — their stacks were NOT traced this cycle, so anything only \
4611 reachable from them will be swept (issue #140 bug-A class). A borrow \
4612 of a coroutine's state must not be held across an allocation \
4613 checkpoint without pushing a parent GC snapshot.",
4614 uncovered_borrowed.len(),
4615 uncovered_borrowed,
4616 global.suspended_parent_stacks.len()
4617 );
4618}
4619
4620fn remark_legacy_open_upvalues(global: &GlobalState, marker: &mut lua_gc::Marker) {
4640 use lua_gc::Trace;
4641
4642 let legacy = matches!(
4643 global.lua_version,
4644 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
4645 );
4646 if !legacy {
4647 return;
4648 }
4649
4650 let mut remarked_any = false;
4651 for (id, entry) in global.threads.iter() {
4652 if entry.value.id != *id {
4653 continue;
4654 }
4655 if thread_entry_marked_alive(marker, *id, entry) {
4656 continue;
4657 }
4658 let Ok(thread) = entry.state.try_borrow() else {
4659 continue;
4660 };
4661 if thread.openupval.is_empty() || !thread.legacy_open_upval_touched.get() {
4662 continue;
4663 }
4664 thread.legacy_open_upval_touched.set(false);
4665 for uv in thread.openupval.iter() {
4666 let Some((_tid, idx)) = uv.try_open_payload() else {
4667 continue;
4668 };
4669 let slot = idx.0 as usize;
4670 if slot < thread.stack.len() {
4671 thread.stack[slot].val.trace(marker);
4672 remarked_any = true;
4673 }
4674 }
4675 }
4676
4677 if !remarked_any {
4678 return;
4679 }
4680 marker.drain_gray_queue();
4681
4682 loop {
4683 let visited_before = marker.visited_count();
4684 for (id, entry) in global.threads.iter() {
4685 if thread_entry_marked_alive(marker, *id, entry) {
4686 if let Ok(thread) = entry.state.try_borrow() {
4687 thread.trace(marker);
4688 }
4689 }
4690 }
4691 marker.drain_gray_queue();
4692 if marker.visited_count() == visited_before {
4693 break;
4694 }
4695 }
4696}
4697
4698fn thread_entry_marked_alive(
4699 marker: &lua_gc::Marker,
4700 id: u64,
4701 entry: &ThreadRegistryEntry,
4702) -> bool {
4703 marker.is_marked_or_old(entry.value.0) && entry.value.id == id
4704}
4705
4706fn lua_value_marked_or_old(marker: &lua_gc::Marker, value: &LuaValue) -> bool {
4707 match value {
4708 LuaValue::Str(v) => marker.is_marked_or_old(v.0),
4709 LuaValue::Table(v) => marker.is_marked_or_old(v.0),
4710 LuaValue::Function(LuaClosure::Lua(v)) => marker.is_marked_or_old(v.0),
4711 LuaValue::Function(LuaClosure::C(v)) => marker.is_marked_or_old(v.0),
4712 LuaValue::UserData(v) => marker.is_marked_or_old(v.0),
4713 LuaValue::Thread(v) => marker.is_marked_or_old(v.0),
4714 LuaValue::Nil
4715 | LuaValue::Bool(_)
4716 | LuaValue::Int(_)
4717 | LuaValue::Float(_)
4718 | LuaValue::LightUserData(_)
4719 | LuaValue::Function(LuaClosure::LightC(_)) => true,
4720 }
4721}
4722
4723fn finalizer_marked_or_old(marker: &lua_gc::Marker, object: &FinalizerObject) -> bool {
4724 match object {
4725 FinalizerObject::Table(t) => marker.is_marked_or_old(t.0),
4726 FinalizerObject::UserData(u) => marker.is_marked_or_old(u.0),
4727 }
4728}
4729
4730fn weak_snapshot_tables<'a>(
4731 snapshot: &'a lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>>,
4732) -> impl Iterator<Item = &'a GcRef<LuaTable>> {
4733 snapshot
4734 .weak_values
4735 .iter()
4736 .chain(snapshot.ephemeron.iter())
4737 .chain(snapshot.all_weak.iter())
4738}
4739
4740fn close_open_upvalues_for_unreachable_threads(global: &GlobalState, marker: &mut lua_gc::Marker) {
4741 use lua_gc::Trace;
4742
4743 let mut closed_values = Vec::<LuaValue>::new();
4744 for (id, entry) in global.threads.iter() {
4745 if entry.value.id != *id {
4746 continue;
4747 }
4748 if thread_entry_marked_alive(marker, *id, entry) {
4749 continue;
4750 }
4751 let Ok(thread) = entry.state.try_borrow() else {
4752 continue;
4753 };
4754 for uv in thread.openupval.iter() {
4755 if !marker.is_visited(uv.identity()) {
4756 continue;
4757 }
4758 let Some((thread_id, idx)) = uv.try_open_payload() else {
4759 continue;
4760 };
4761 if thread_id as u64 != *id {
4762 continue;
4763 }
4764 let value = thread.get_at(idx);
4765 uv.close_with(value.clone());
4766 closed_values.push(value);
4767 }
4768 }
4769 for value in closed_values {
4770 value.trace(marker);
4771 }
4772 marker.drain_gray_queue();
4773}
4774
4775fn record_dead_interned_strings(
4782 global: &GlobalState,
4783 marker: &lua_gc::Marker,
4784 dead_pairs: &std::cell::RefCell<Vec<(u32, usize)>>,
4785) {
4786 let mut dead = dead_pairs.borrow_mut();
4787 for s in global.interned_lt.iter() {
4788 let id = s.identity();
4789 if !marker.is_visited(id) {
4790 dead.push((s.hash(), id));
4791 }
4792 }
4793}
4794
4795fn remove_dead_interned_strings(global: &mut GlobalState, dead_pairs: Vec<(u32, usize)>) {
4807 for (hash, id) in dead_pairs {
4808 global.interned_lt.remove(hash, id);
4809 }
4810 global.interned_lt.shrink_if_sparse();
4811}
4812
4813impl<'a> GcHandle<'a> {
4814 pub fn check_step(&self) {
4821 if !self._state.global().is_gc_running() {
4822 return;
4823 }
4824 if self._state.global().is_gen_mode() {
4825 let should_collect = {
4826 let g = self._state.global();
4827 g.heap.would_collect() || g.gc_debt() > 0
4828 };
4829 if should_collect {
4830 self.generational_step();
4831 }
4832 } else {
4833 self.collect_via_heap(false);
4834 }
4835 }
4836
4837 pub fn full_collect(&self) {
4839 if self._state.global().is_gen_mode() {
4840 self.fullgen();
4841 } else {
4842 self.collect_via_heap(true);
4843 }
4844 }
4845
4846 fn negative_debt(bytes: usize) -> isize {
4847 -(bytes.min(isize::MAX as usize) as isize)
4848 }
4849
4850 fn set_minor_debt(&self) {
4851 let mut g = self._state.global_mut();
4852 let total = g.total_bytes();
4853 let growth = (total / 100).saturating_mul(g.genminormul as usize);
4854 g.heap
4855 .set_threshold_bytes(total.saturating_add(growth.max(1)));
4856 set_debt(&mut *g, Self::negative_debt(growth));
4857 }
4858
4859 fn set_pause_debt(&self) {
4860 let mut g = self._state.global_mut();
4861 let total = g.total_bytes();
4862 let pause = g.gc_pause_param().max(0) as usize;
4863 let threshold = g.gc_estimate.max(1).saturating_mul(pause) / 100;
4864 let debt = if threshold > total {
4865 Self::negative_debt(threshold - total)
4866 } else {
4867 0
4868 };
4869 let heap_threshold = if threshold > total {
4870 threshold
4871 } else {
4872 total.saturating_add(1)
4873 };
4874 g.heap.set_threshold_bytes(heap_threshold);
4875 set_debt(&mut *g, debt);
4876 }
4877
4878 fn enter_incremental_mode(&self) {
4879 let mut g = self._state.global_mut();
4880 g.heap.reset_all_ages();
4881 g.finalizers.reset_generation_boundaries();
4882 g.gckind = GcKind::Incremental as u8;
4883 g.lastatomic = 0;
4884 }
4885
4886 fn enter_generational_mode(&self) -> usize {
4887 self.collect_via_heap_mode(HeapCollectMode::Full);
4888 let numobjs = {
4889 let mut g = self._state.global_mut();
4890 g.heap.promote_all_to_old();
4891 g.finalizers.promote_all_pending_to_old();
4892 g.heap.allgc_count()
4893 };
4894 let total = self._state.global().total_bytes();
4895 {
4896 let mut g = self._state.global_mut();
4897 g.gckind = GcKind::Generational as u8;
4898 g.lastatomic = 0;
4899 g.gc_estimate = total;
4900 }
4901 self.set_minor_debt();
4902 numobjs
4903 }
4904
4905 fn fullgen(&self) -> usize {
4906 self.enter_incremental_mode();
4907 self.enter_generational_mode()
4908 }
4909
4910 fn stepgenfull(&self, lastatomic: usize) {
4911 if self._state.global().gckind == GcKind::Generational as u8 {
4912 self.enter_incremental_mode();
4913 }
4914 self.collect_via_heap_mode(HeapCollectMode::Full);
4915 let newatomic = self._state.global().heap.allgc_count().max(1);
4916 if newatomic < lastatomic.saturating_add(lastatomic >> 3) {
4917 {
4918 let mut g = self._state.global_mut();
4919 g.heap.promote_all_to_old();
4920 g.finalizers.promote_all_pending_to_old();
4921 }
4922 let total = self._state.global().total_bytes();
4923 {
4924 let mut g = self._state.global_mut();
4925 g.gckind = GcKind::Generational as u8;
4926 g.lastatomic = 0;
4927 g.gc_estimate = total;
4928 }
4929 self.set_minor_debt();
4930 } else {
4931 {
4932 let mut g = self._state.global_mut();
4933 g.heap.reset_all_ages();
4934 g.finalizers.reset_generation_boundaries();
4935 }
4936 let total = self._state.global().total_bytes();
4937 {
4938 let mut g = self._state.global_mut();
4939 g.gckind = GcKind::Incremental as u8;
4940 g.lastatomic = newatomic;
4941 g.gc_estimate = total;
4942 }
4943 self.set_pause_debt();
4944 }
4945 }
4946
4947 fn collect_via_heap(&self, force: bool) {
4956 self.collect_via_heap_mode(if force {
4957 HeapCollectMode::Full
4958 } else {
4959 HeapCollectMode::Step
4960 });
4961 }
4962
4963 fn collect_via_heap_mode(&self, mode: HeapCollectMode) {
4964 use lua_gc::Trace;
4965 let state_ref: &LuaState = &*self._state;
4966
4967 if matches!(mode, HeapCollectMode::Step) {
4973 let g = state_ref.global.borrow();
4974 if !g.heap.would_collect() {
4975 return;
4976 }
4977 }
4978
4979 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
4983 let mut g = state_ref.global.borrow_mut();
4984 g.weak_tables_registry.live_snapshot_by_kind()
4985 };
4986
4987 let weak_table_capacity = weak_tables_snapshot.len();
4992 let (pending_snapshot, thread_capacity, _interned_capacity): (
4993 Vec<FinalizerObject>,
4994 usize,
4995 usize,
4996 ) = {
4997 let g = state_ref.global.borrow();
4998 let pending = match mode {
4999 HeapCollectMode::Minor => g.finalizers.pending_minor_snapshot(),
5000 HeapCollectMode::Full | HeapCollectMode::Step => g.finalizers.pending_snapshot(),
5001 };
5002 (pending, g.threads.len(), g.interned_lt.len())
5003 };
5004 let finalizer_capacity = pending_snapshot.len();
5005
5006 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5007 std::cell::RefCell::new(std::collections::HashSet::new());
5008 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
5009 std::cell::RefCell::new(Vec::new());
5010 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
5011 std::cell::RefCell::new(std::collections::HashSet::new());
5012 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5013 std::cell::RefCell::new(std::collections::HashSet::new());
5014 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5015 let collect_ran = std::cell::Cell::new(false);
5016
5017 {
5018 let global = state_ref.global.borrow();
5019 global.heap.unpause();
5020 let roots = CollectRoots {
5021 global: &*global,
5022 thread: state_ref,
5023 };
5024 let hook = |marker: &mut lua_gc::Marker| {
5025 collect_ran.set(true);
5026 alive_ids.borrow_mut().reserve(weak_table_capacity);
5027 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
5028 alive_thread_ids.borrow_mut().reserve(thread_capacity);
5029 trace_reachable_threads(&*global, global.current_thread_id, marker);
5030 close_open_upvalues_for_unreachable_threads(&*global, marker);
5031 let legacy_weak_key_values =
5038 matches!(global.lua_version, lua_types::LuaVersion::V51);
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
5085 .weak_values
5086 .iter()
5087 .chain(weak_tables_snapshot.all_weak.iter())
5088 {
5089 let to_mark = t.prune_weak_dead_with_value(
5090 &|_| true,
5091 &|v| lua_value_marked_or_old(marker, v),
5092 );
5093 if marker.is_marked_or_old(t.0) {
5094 for v in &to_mark {
5095 v.trace(marker);
5096 }
5097 }
5098 }
5099 marker.drain_gray_queue();
5100 for pf in &pending_snapshot {
5101 if !finalizer_marked_or_old(marker, pf) {
5102 pf.mark(marker);
5103 newly_unreachable.borrow_mut().push(pf.clone());
5104 }
5105 }
5106 marker.drain_gray_queue();
5107 loop {
5108 let visited_before = marker.visited_count();
5109 for t in &weak_tables_snapshot.ephemeron {
5110 if !marker.is_marked_or_old(t.0) {
5111 continue;
5112 }
5113 if legacy_weak_key_values {
5114 let mut to_mark = Vec::new();
5115 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5116 for v in &to_mark {
5117 v.trace(marker);
5118 }
5119 } else {
5120 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
5121 lua_value_marked_or_old(marker, v)
5122 });
5123 for v in &to_mark {
5124 v.trace(marker);
5125 }
5126 }
5127 }
5128 marker.drain_gray_queue();
5129 if marker.visited_count() == visited_before {
5130 break;
5131 }
5132 }
5133 for t in weak_tables_snapshot
5146 .ephemeron
5147 .iter()
5148 .chain(weak_tables_snapshot.all_weak.iter())
5149 {
5150 if !marker.is_marked_or_old(t.0) {
5151 continue;
5152 }
5153 let to_mark = t.prune_weak_dead_with_value(
5154 &|v| lua_value_marked_or_old(marker, v),
5155 &|_| true,
5156 );
5157 for v in &to_mark {
5158 v.trace(marker);
5159 }
5160 }
5161 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5162 if marker.is_marked_or_old(t.0) {
5163 alive_ids.borrow_mut().insert(t.identity());
5164 }
5165 }
5166 marker.drain_gray_queue();
5167 {
5168 let mut alive = alive_thread_ids.borrow_mut();
5169 for (id, entry) in global.threads.iter() {
5170 if thread_entry_marked_alive(marker, *id, entry) {
5171 alive.insert(*id);
5172 }
5173 }
5174 }
5175 {
5176 let mut alive = alive_closure_env_ids.borrow_mut();
5177 for id in global.closure_envs.keys() {
5178 if marker.is_visited(*id) {
5179 alive.insert(*id);
5180 }
5181 }
5182 }
5183 record_dead_interned_strings(&*global, marker, &dead_interned);
5184 };
5185 match mode {
5186 HeapCollectMode::Full => global.heap.full_collect_with_post_mark(&roots, hook),
5187 HeapCollectMode::Step => global.heap.step_with_post_mark(&roots, hook),
5188 HeapCollectMode::Minor => global.heap.minor_collect_with_post_mark(&roots, hook),
5189 }
5190 }
5191
5192 if !collect_ran.get() {
5193 return;
5194 }
5195
5196 let alive_set = alive_ids.into_inner();
5200 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5201 let alive_thread_ids = alive_thread_ids.into_inner();
5202 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5203 let dead_interned = dead_interned.into_inner();
5204 let mut g = state_ref.global.borrow_mut();
5205 remove_dead_interned_strings(&mut *g, dead_interned);
5206 g.weak_tables_registry.retain_identities(&alive_set);
5207 let main_thread_id = g.main_thread_id;
5208 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5209 g.cross_thread_upvals
5210 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5211 g.thread_globals
5215 .retain(|id, _| alive_thread_ids.contains(id));
5216 g.closure_envs
5217 .retain(|id, _| alive_closure_env_ids.contains(id));
5218 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5222 for object in &promoted {
5223 if let Some(ptr) = object.heap_ptr() {
5224 g.heap.move_finobj_to_tobefnz(ptr);
5225 }
5226 }
5227 if matches!(mode, HeapCollectMode::Minor) {
5228 g.finalizers.finish_minor_collection();
5229 }
5230 }
5231
5232 pub fn generational_step(&self) -> bool {
5234 self.generational_step_with_major(true)
5235 }
5236
5237 pub fn generational_step_minor_only(&self) -> bool {
5243 self.generational_step_with_major(false)
5244 }
5245
5246 fn generational_step_with_major(&self, allow_major: bool) -> bool {
5247 let (lastatomic, majorbase, majorinc, should_major) = {
5248 let g = self._state.global();
5249 let majorbase = if g.gc_estimate == 0 {
5250 g.total_bytes()
5251 } else {
5252 g.gc_estimate
5253 };
5254 let majormul = g.gc_genmajormul_param().max(0) as usize;
5255 let majorinc = (majorbase / 100).saturating_mul(majormul);
5256 let debt_due = g.gc_debt() > 0 || g.heap.would_collect();
5257 let should_major =
5258 allow_major && debt_due && g.total_bytes() > majorbase.saturating_add(majorinc);
5259 (g.lastatomic, majorbase, majorinc, should_major)
5260 };
5261
5262 if lastatomic != 0 {
5263 self.stepgenfull(lastatomic);
5264 debug_assert!(self._state.global().is_gen_mode());
5265 return true;
5266 }
5267
5268 if should_major {
5269 let numobjs = self.fullgen();
5270 let after = self._state.global().total_bytes();
5271 if after < majorbase.saturating_add(majorinc / 2) {
5272 self.set_minor_debt();
5273 } else {
5274 {
5275 let mut g = self._state.global_mut();
5276 g.lastatomic = numobjs.max(1);
5277 }
5278 self.set_pause_debt();
5279 }
5280 } else {
5281 self.collect_via_heap_mode(HeapCollectMode::Minor);
5282 self.set_minor_debt();
5283 self._state.global_mut().gc_estimate = majorbase;
5284 }
5285
5286 debug_assert!(self._state.global().is_gen_mode());
5287 true
5288 }
5289
5290 pub fn step(&self) { }
5293
5294 pub fn incremental_step(&self, work_units: isize) -> bool {
5307 self.incremental_step_to_state(work_units, None)
5308 }
5309
5310 pub fn run_until_gc_state_for_test(&self, target: lua_gc::GcState) -> bool {
5315 self.incremental_step_to_state(isize::MAX / 4, Some(target));
5316 self._state.global().heap.gc_state() == target
5317 }
5318
5319 fn incremental_step_to_state(
5320 &self,
5321 work_units: isize,
5322 target: Option<lua_gc::GcState>,
5323 ) -> bool {
5324 use lua_gc::{StepBudget, StepOutcome, Trace};
5325 let state_ref: &LuaState = &*self._state;
5326
5327 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5328 let mut g = state_ref.global.borrow_mut();
5329 g.weak_tables_registry.live_snapshot_by_kind()
5330 };
5331
5332 let weak_table_capacity = weak_tables_snapshot.len();
5333 let (pending_snapshot, thread_capacity, _interned_capacity): (
5334 Vec<FinalizerObject>,
5335 usize,
5336 usize,
5337 ) = {
5338 let g = state_ref.global.borrow();
5339 (
5340 g.finalizers.pending_snapshot(),
5341 g.threads.len(),
5342 g.interned_lt.len(),
5343 )
5344 };
5345 let finalizer_capacity = pending_snapshot.len();
5346
5347 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5348 std::cell::RefCell::new(std::collections::HashSet::new());
5349 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
5350 std::cell::RefCell::new(Vec::new());
5351 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
5352 std::cell::RefCell::new(std::collections::HashSet::new());
5353 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5354 std::cell::RefCell::new(std::collections::HashSet::new());
5355 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5356 let atomic_ran = std::cell::Cell::new(false);
5357
5358 let stop_target = {
5359 let g = state_ref.global.borrow();
5360 match (target, g.heap.gc_state()) {
5361 (Some(target), _) => Some(target),
5362 (None, lua_gc::GcState::CallFin) => None,
5363 (None, _) => Some(lua_gc::GcState::CallFin),
5364 }
5365 };
5366
5367 let outcome = {
5368 let global = state_ref.global.borrow();
5369 global.heap.unpause();
5370 let roots = CollectRoots {
5371 global: &*global,
5372 thread: state_ref,
5373 };
5374 let hook = |marker: &mut lua_gc::Marker| {
5375 atomic_ran.set(true);
5376 alive_ids.borrow_mut().reserve(weak_table_capacity);
5377 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
5378 alive_thread_ids.borrow_mut().reserve(thread_capacity);
5379 trace_reachable_threads(&*global, global.current_thread_id, marker);
5380 close_open_upvalues_for_unreachable_threads(&*global, marker);
5381 let legacy_weak_key_values =
5385 matches!(global.lua_version, lua_types::LuaVersion::V51);
5386 loop {
5387 let visited_before = marker.visited_count();
5388 for t in &weak_tables_snapshot.ephemeron {
5389 let t_id = t.identity();
5390 if !marker.is_visited(t_id) {
5391 continue;
5392 }
5393 if legacy_weak_key_values {
5394 let mut to_mark = Vec::new();
5395 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5396 for v in &to_mark {
5397 v.trace(marker);
5398 }
5399 } else {
5400 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5401 for v in &to_mark {
5402 v.trace(marker);
5403 }
5404 }
5405 }
5406 marker.drain_gray_queue();
5407 if marker.visited_count() == visited_before {
5408 break;
5409 }
5410 }
5411 for t in weak_tables_snapshot
5424 .weak_values
5425 .iter()
5426 .chain(weak_tables_snapshot.all_weak.iter())
5427 {
5428 let to_mark =
5429 t.prune_weak_dead_with(&|_| true, &|id| marker.is_visited(id));
5430 if marker.is_visited(t.identity()) {
5431 for v in &to_mark {
5432 v.trace(marker);
5433 }
5434 }
5435 }
5436 marker.drain_gray_queue();
5437 for pf in &pending_snapshot {
5438 if !marker.is_visited(pf.identity()) {
5439 pf.mark(marker);
5440 newly_unreachable.borrow_mut().push(pf.clone());
5441 }
5442 }
5443 marker.drain_gray_queue();
5444 loop {
5445 let visited_before = marker.visited_count();
5446 for t in &weak_tables_snapshot.ephemeron {
5447 let t_id = t.identity();
5448 if !marker.is_visited(t_id) {
5449 continue;
5450 }
5451 if legacy_weak_key_values {
5452 let mut to_mark = Vec::new();
5453 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5454 for v in &to_mark {
5455 v.trace(marker);
5456 }
5457 } else {
5458 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5459 for v in &to_mark {
5460 v.trace(marker);
5461 }
5462 }
5463 }
5464 marker.drain_gray_queue();
5465 if marker.visited_count() == visited_before {
5466 break;
5467 }
5468 }
5469 for t in weak_tables_snapshot
5476 .ephemeron
5477 .iter()
5478 .chain(weak_tables_snapshot.all_weak.iter())
5479 {
5480 if !marker.is_visited(t.identity()) {
5481 continue;
5482 }
5483 let to_mark =
5484 t.prune_weak_dead_with(&|id| marker.is_visited(id), &|_| true);
5485 for v in &to_mark {
5486 v.trace(marker);
5487 }
5488 }
5489 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5490 if marker.is_visited(t.identity()) {
5491 alive_ids.borrow_mut().insert(t.identity());
5492 }
5493 }
5494 marker.drain_gray_queue();
5495 {
5496 let mut alive = alive_thread_ids.borrow_mut();
5497 for (id, entry) in global.threads.iter() {
5498 if thread_entry_marked_alive(marker, *id, entry) {
5499 alive.insert(*id);
5500 }
5501 }
5502 }
5503 {
5504 let mut alive = alive_closure_env_ids.borrow_mut();
5505 for id in global.closure_envs.keys() {
5506 if marker.is_visited(*id) {
5507 alive.insert(*id);
5508 }
5509 }
5510 }
5511 record_dead_interned_strings(&*global, marker, &dead_interned);
5512 };
5513 let budget = StepBudget::from_work(work_units);
5514 if let Some(target) = stop_target {
5515 global
5516 .heap
5517 .incremental_run_until_state_with_post_mark(&roots, target, work_units, hook)
5518 } else {
5519 global
5520 .heap
5521 .incremental_step_with_post_mark(&roots, budget, hook)
5522 }
5523 };
5524
5525 if atomic_ran.get() {
5526 let alive_set = alive_ids.into_inner();
5527 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5528 let alive_thread_ids = alive_thread_ids.into_inner();
5529 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5530 let dead_interned = dead_interned.into_inner();
5531 let mut g = state_ref.global.borrow_mut();
5532 remove_dead_interned_strings(&mut *g, dead_interned);
5533 g.weak_tables_registry.retain_identities(&alive_set);
5534 let main_thread_id = g.main_thread_id;
5535 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5536 g.cross_thread_upvals
5537 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5538 g.thread_globals
5542 .retain(|id, _| alive_thread_ids.contains(id));
5543 g.closure_envs
5544 .retain(|id, _| alive_closure_env_ids.contains(id));
5545 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5546 for object in &promoted {
5547 if let Some(ptr) = object.heap_ptr() {
5548 g.heap.move_finobj_to_tobefnz(ptr);
5549 }
5550 }
5551 }
5552
5553 let mut paused = matches!(outcome, StepOutcome::Paused);
5554 if target.is_none()
5555 && self._state.global().heap.gc_state() == lua_gc::GcState::CallFin
5556 && !self._state.global().finalizers.has_to_be_finalized()
5557 {
5558 paused = self._state.global().heap.finish_callfin_phase();
5559 }
5560
5561 paused
5562 }
5563
5564 pub fn prune_weak_tables_mark_only(&self) {
5571 use lua_gc::Trace;
5572 let state_ref: &LuaState = &*self._state;
5573
5574 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5575 let mut g = state_ref.global.borrow_mut();
5576 g.weak_tables_registry.live_snapshot_by_kind()
5577 };
5578 let _interned_capacity = {
5579 let g = state_ref.global.borrow();
5580 g.interned_lt.len()
5581 };
5582
5583 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5584
5585 {
5586 let global = state_ref.global.borrow();
5587 global.heap.unpause();
5588 let roots = CollectRoots {
5589 global: &*global,
5590 thread: state_ref,
5591 };
5592 let hook = |marker: &mut lua_gc::Marker| {
5593 trace_reachable_threads(&*global, global.current_thread_id, marker);
5594 loop {
5595 let visited_before = marker.visited_count();
5596 for t in &weak_tables_snapshot.ephemeron {
5597 let t_id = t.identity();
5598 if !marker.is_visited(t_id) {
5599 continue;
5600 }
5601 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5602 for v in &to_mark {
5603 v.trace(marker);
5604 }
5605 }
5606 marker.drain_gray_queue();
5607 if marker.visited_count() == visited_before {
5608 break;
5609 }
5610 }
5611 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5612 if marker.is_visited(t.identity()) {
5613 let to_mark = t.prune_weak_dead(&|id| marker.is_visited(id));
5614 for v in &to_mark {
5615 v.trace(marker);
5616 }
5617 }
5618 }
5619 marker.drain_gray_queue();
5620 record_dead_interned_strings(&*global, marker, &dead_interned);
5621 };
5622 global.heap.mark_only_with_post_mark(&roots, hook);
5623 }
5624
5625 let dead_interned = dead_interned.into_inner();
5626 let mut g = state_ref.global.borrow_mut();
5627 remove_dead_interned_strings(&mut *g, dead_interned);
5628 }
5629
5630 pub fn change_mode(&self, mode: GcKind) {
5632 let old = self._state.global().gckind;
5633 if old == mode as u8 {
5634 self._state.global_mut().lastatomic = 0;
5635 return;
5636 }
5637 match mode {
5638 GcKind::Generational => {
5639 self.enter_generational_mode();
5640 }
5641 GcKind::Incremental => {
5642 self.enter_incremental_mode();
5643 }
5644 }
5645 }
5646
5647 pub fn fix_object<T: lua_gc::Trace + 'static>(&self, _o: &GcRef<T>) { }
5650
5651 pub fn free_all_objects(&self) {
5655 }
5657
5658 pub fn barrier(&self, p: &dyn std::any::Any, v: &LuaValue) {
5662 let g = self._state.global();
5663 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Forward);
5664 }
5665
5666 pub fn barrier_back(&self, p: &dyn std::any::Any, v: &LuaValue) {
5670 let g = self._state.global();
5671 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Backward);
5672 }
5673
5674 pub fn table_barrier_back(&self, p: &GcRef<LuaTable>, v: &LuaValue) {
5676 let g = self._state.global();
5677 barrier_lua_value(&g.heap, *p, v, g.is_gen_mode(), BarrierKind::Backward);
5678 }
5679
5680 pub fn obj_barrier(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5684 let g = self._state.global();
5685 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Forward);
5686 }
5687
5688 pub fn obj_barrier_back(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5691 let g = self._state.global();
5692 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Backward);
5693 }
5694}
5695
5696fn make_seed() -> u32 {
5705 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5706 {
5707 return crate::string::hash_bytes(b"lua-rs-wasm-seed", 0x9e37_79b9);
5708 }
5709
5710 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
5711 {
5712 use std::time::{SystemTime, UNIX_EPOCH};
5713 let t = SystemTime::now()
5714 .duration_since(UNIX_EPOCH)
5715 .map(|d| d.as_secs() as u32)
5716 .unwrap_or(0);
5717
5718 crate::string::hash_bytes(&t.to_le_bytes(), t)
5726 }
5727}
5728
5729pub(crate) fn set_debt(g: &mut GlobalState, mut debt: isize) {
5743 let tb = g.total_bytes() as isize;
5744 debug_assert!(tb > 0);
5745 if debt < tb.saturating_sub(isize::MAX) {
5747 debt = tb - isize::MAX;
5748 }
5749 g.gc_debt = debt;
5750}
5751
5752pub fn set_c_stack_limit(_state: &mut LuaState, _limit: u32) -> i32 {
5762 let _ = (_state, _limit);
5763 LUAI_MAXCCALLS as i32
5764}
5765
5766pub(crate) fn extend_ci(state: &mut LuaState) -> CallInfoIdx {
5783 debug_assert!(
5784 state.call_info[state.ci.0 as usize].next.is_none(),
5785 "extend_ci: current ci already has a cached next frame"
5786 );
5787
5788 let current_idx = state.ci;
5789 let new_idx = CallInfoIdx(state.call_info.len() as u32);
5791
5792 state.call_info.push(CallInfo {
5793 previous: Some(current_idx),
5794 next: None,
5795 u: CallInfoFrame::lua_default(),
5796 ..CallInfo::default()
5797 });
5798
5799 state.call_info[current_idx.0 as usize].next = Some(new_idx);
5800
5801 state.nci += 1;
5802
5803 new_idx
5804}
5805
5806fn free_ci(state: &mut LuaState) {
5827 let ci_idx = state.ci.0 as usize;
5828
5829 let mut next_opt = state.call_info[ci_idx].next.take();
5830
5831 while let Some(idx) = next_opt {
5832 next_opt = state.call_info[idx.0 as usize].next;
5833 state.nci = state.nci.saturating_sub(1);
5834 }
5835
5836 state.call_info.truncate(ci_idx + 1);
5839}
5840
5841pub(crate) fn shrink_ci(state: &mut LuaState) {
5868 let ci_idx = state.ci.0 as usize;
5869
5870 if state.call_info[ci_idx].next.is_none() {
5871 return;
5872 }
5873
5874 let free_count = state.call_info.len().saturating_sub(ci_idx + 1);
5875 if free_count <= 1 {
5876 return;
5877 }
5878
5879 let keep = free_count / 2;
5883 let removed = free_count - keep;
5884 let new_len = ci_idx + 1 + keep;
5885 state.call_info.truncate(new_len);
5886 state.nci = state.nci.saturating_sub(removed as u32);
5887
5888 if let Some(last) = state.call_info.last_mut() {
5890 last.next = None;
5891 }
5892}
5893
5894pub(crate) fn check_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5906 if state.c_calls() == LUAI_MAXCCALLS {
5909 return Err(LuaError::runtime(format_args!("C stack overflow")));
5910 }
5911 if state.c_calls() >= (LUAI_MAXCCALLS / 10 * 11) {
5913 return Err(LuaError::with_status(LuaStatus::ErrErr));
5914 }
5915 Ok(())
5916}
5917
5918pub fn inc_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5929 state.n_ccalls += 1;
5930 if state.c_calls() >= LUAI_MAXCCALLS {
5932 check_c_stack(state)?;
5933 }
5934 Ok(())
5935}
5936
5937fn stack_init(thread: &mut LuaState) {
5943 let total_slots = BASIC_STACK_SIZE + EXTRA_STACK;
5945 thread.stack = vec![StackValue::default(); total_slots];
5946
5947 thread.tbclist = Vec::new();
5951
5952 thread.top = StackIdx(0);
5957
5958 thread.stack_last = StackIdx(BASIC_STACK_SIZE as u32);
5959
5960 let base_ci = CallInfo {
5961 func: StackIdx(0),
5962 top: StackIdx(1 + LUA_MINSTACK as u32),
5963 previous: None,
5964 next: None,
5965 callstatus: CIST_C,
5966 call_metamethods: 0,
5967 tailcalls: 0,
5968 nresults: 0,
5969 u: CallInfoFrame::c_default(),
5970 u2: CallInfoExtra::default(),
5971 };
5972
5973 if thread.call_info.is_empty() {
5974 thread.call_info.push(base_ci);
5975 } else {
5976 thread.call_info[0] = base_ci;
5977 thread.call_info.truncate(1);
5978 }
5979
5980 thread.stack[0] = StackValue {
5981 val: LuaValue::Nil,
5982 };
5983
5984 thread.top = StackIdx(1);
5985
5986 thread.ci = CallInfoIdx(0);
5987}
5988
5989fn free_stack(state: &mut LuaState) {
5990 if state.stack.is_empty() {
5991 return;
5992 }
5993 state.ci = CallInfoIdx(0);
5994 free_ci(state);
5995 debug_assert_eq!(state.nci, 0, "nci should be 0 after free_ci");
5996 state.stack.clear();
5998 state.stack.shrink_to_fit();
5999}
6000
6001fn init_registry(state: &mut LuaState) -> Result<(), LuaError> {
6002 let registry = state.new_table();
6004
6005 state.global_mut().l_registry = LuaValue::Table(registry.clone());
6007
6008 let globals = state.new_table();
6028 state.global_mut().globals = LuaValue::Table(globals);
6029 let loaded = state.new_table();
6030 state.global_mut().loaded = LuaValue::Table(loaded);
6031
6032 Ok(())
6033}
6034
6035fn lua_open(state: &mut LuaState) -> Result<(), LuaError> {
6036 stack_init(state);
6037 init_registry(state)?;
6038 crate::string::init(state)?;
6039 crate::tagmethods::init(state)?;
6040 state.global_mut().gcstp = 0;
6042 state.global().heap.unpause();
6043 state.global_mut().nilvalue = LuaValue::Nil;
6046 Ok(())
6048}
6049
6050fn preinit_thread(thread: &mut LuaState, global: Rc<RefCell<GlobalState>>) {
6051 thread.global = global;
6052 thread.stack = Vec::new();
6053 thread.call_info = Vec::new();
6054 thread.ci = CallInfoIdx(0);
6057 thread.nci = 0;
6058 thread.n_ccalls = 0;
6062 thread.hook = None;
6063 thread.hookmask = 0;
6064 thread.basehookcount = 0;
6065 thread.allowhook = true;
6066 thread.hookcount = thread.basehookcount;
6068
6069 {
6074 let (active, interval) = {
6075 let g = thread.global.borrow();
6076 (g.sandbox_active(), g.sandbox.interval.get())
6077 };
6078 if active {
6079 thread.hookmask = SANDBOX_COUNT_MASK;
6080 thread.basehookcount = interval;
6081 thread.hookcount = interval;
6082 }
6083 }
6084 thread.openupval = Vec::new();
6085 thread.status = LuaStatus::Ok as u8;
6086 thread.errfunc = 0;
6087 thread.oldpc = 0;
6088 thread.gc_check_needed = true;
6089}
6090
6091fn close_state(state: &mut LuaState) {
6092 let is_complete = state.global().is_complete();
6093
6094 if !is_complete {
6095 state.gc().free_all_objects();
6097 } else {
6098 state.ci = CallInfoIdx(0);
6099 state.gc().free_all_objects();
6102 }
6104
6105 state.global_mut().strt = StringPool::default();
6107
6108 free_stack(state);
6109
6110 }
6115
6116pub fn new_thread(state: &mut LuaState, initial_body: Option<LuaValue>) -> Result<(), LuaError> {
6145 state.gc_pre_collect_clear();
6146 state.gc().check_step();
6147
6148 let global_rc = state.global_rc();
6153 let hookmask = state.hookmask;
6154 let basehookcount = state.basehookcount;
6155
6156 let reserved_id = {
6157 let mut g = state.global_mut();
6158 let id = g.next_thread_id;
6159 g.next_thread_id += 1;
6160 id
6161 };
6162
6163 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
6169 let creator_id = state.global().current_thread_id;
6170 let creator_lgt = state.v51_thread_lgt(creator_id);
6171 state
6172 .global_mut()
6173 .thread_globals
6174 .insert(reserved_id, creator_lgt);
6175 }
6176
6177 let mut new_thread = LuaState {
6178 status: LuaStatus::Ok as u8,
6179 allowhook: true,
6180 nci: 0,
6181 top: StackIdx(0),
6182 stack_last: StackIdx(0),
6183 stack: Vec::new(),
6184 ci: CallInfoIdx(0),
6185 call_info: Vec::new(),
6186 openupval: Vec::new(),
6187 legacy_open_upval_touched: std::cell::Cell::new(false),
6188 tbclist: Vec::new(),
6189 global: global_rc.clone(),
6190 hook: None,
6191 hookmask: 0,
6192 basehookcount: 0,
6193 hookcount: 0,
6194 errfunc: 0,
6195 n_ccalls: 0,
6196 oldpc: 0,
6197 marked: 0,
6198 cached_thread_id: reserved_id,
6199 gc_check_needed: false,
6200 };
6201
6202 preinit_thread(&mut new_thread, global_rc);
6203
6204 new_thread.hookmask = hookmask;
6205 new_thread.basehookcount = basehookcount;
6206 new_thread.reset_hook_count();
6209
6210 stack_init(&mut new_thread);
6216
6217 if let Some(body) = initial_body {
6218 new_thread.push(body);
6219 }
6220
6221 let thread_ref: Rc<RefCell<LuaState>> = Rc::new(RefCell::new(new_thread));
6222
6223 let value = {
6224 let mut g = state.global_mut();
6225 let id = reserved_id;
6226 let value = GcRef::new(lua_types::value::LuaThread::new(id));
6227 g.threads.insert(
6228 id,
6229 ThreadRegistryEntry {
6230 state: thread_ref,
6231 value: value.clone(),
6232 },
6233 );
6234 value
6235 };
6236
6237 state.push(LuaValue::Thread(value));
6238
6239 Ok(())
6240}
6241
6242pub fn reset_thread(state: &mut LuaState, status: i32) -> i32 {
6264 let _heap_guard = {
6268 let g = state.global.borrow();
6269 lua_gc::HeapGuard::push(&g.heap)
6270 };
6271 state.ci = CallInfoIdx(0);
6272 let ci_idx = 0usize;
6273
6274 if !state.stack.is_empty() {
6276 state.stack[0].val = LuaValue::Nil;
6277 }
6278
6279 state.call_info[ci_idx].func = StackIdx(0);
6280 state.call_info[ci_idx].call_metamethods = 0;
6281 state.call_info[ci_idx].callstatus = CIST_C;
6282
6283 let mut status = if status == LuaStatus::Yield as i32 {
6284 LuaStatus::Ok as i32
6285 } else {
6286 status
6287 };
6288
6289 state.status = LuaStatus::Ok as u8;
6290
6291 let close_status = crate::do_::close_protected(state, StackIdx(1), LuaStatus::from_raw(status));
6292 status = close_status as i32;
6293
6294 if status != LuaStatus::Ok as i32 {
6295 crate::do_::set_error_obj(state, LuaStatus::from_raw(status), StackIdx(1));
6296 } else {
6297 state.top = StackIdx(1);
6298 }
6299
6300 let new_ci_top = StackIdx(state.top.0 + LUA_MINSTACK as u32);
6301 state.call_info[ci_idx].top = new_ci_top;
6302
6303 let needed = new_ci_top.0 as usize;
6306 if state.stack.len() < needed {
6307 state.stack.resize(needed, StackValue::default());
6308 }
6309
6310 status
6311}
6312
6313pub fn close_thread(state: &mut LuaState, from: Option<&LuaState>) -> i32 {
6327 state.n_ccalls = match from {
6329 Some(f) => f.c_calls(),
6330 None => 0,
6331 };
6332 let current_status = state.status as i32;
6333 let result = reset_thread(state, current_status);
6334 result
6335}
6336
6337pub fn reset_thread_api(state: &mut LuaState) -> i32 {
6346 close_thread(state, None)
6347}
6348
6349pub fn new_state() -> Option<LuaState> {
6385 let heap = lua_gc::Heap::new();
6399 let placeholder_str = GcRef(heap.allocate_uncollected(LuaString::placeholder()));
6400 let main_thread_value = GcRef(heap.allocate_uncollected(lua_types::value::LuaThread::new(0)));
6401
6402 let initial_white = 1u8 << WHITE0BIT;
6404
6405 let global = GlobalState {
6409 parser_hook: None,
6410 cli_argv: None,
6411 cli_preload: None,
6412 lua_version: lua_types::LuaVersion::default(),
6413 file_loader_hook: None,
6414 file_open_hook: None,
6415 stdout_hook: None,
6416 stderr_hook: None,
6417 stdin_hook: None,
6418 env_hook: None,
6419 unix_time_hook: None,
6420 cpu_clock_hook: None,
6421 local_offset_hook: None,
6422 entropy_hook: None,
6423 temp_name_hook: None,
6424 popen_hook: None,
6425 file_remove_hook: None,
6426 file_rename_hook: None,
6427 os_execute_hook: None,
6428 dynlib_load_hook: None,
6429 dynlib_symbol_hook: None,
6430 dynlib_unload_hook: None,
6431 sandbox: SandboxLimits::default(),
6432 gc_debt: 0,
6433 gc_estimate: 0,
6434 lastatomic: 0,
6435 strt: StringPool::default(),
6436 l_registry: LuaValue::Nil,
6437 external_roots: ExternalRootSet::default(),
6438 globals: LuaValue::Nil,
6439 loaded: LuaValue::Nil,
6440 nilvalue: LuaValue::Int(0),
6441 seed: make_seed(),
6442 currentwhite: initial_white,
6443 gcstate: GCS_PAUSE,
6444 gckind: GcKind::Incremental as u8,
6446 gcstopem: false,
6447 genminormul: LUAI_GENMINORMUL,
6448 genmajormul: (LUAI_GENMAJORMUL / 4) as u8,
6450 gcstp: GCSTPGC,
6451 gcemergency: false,
6452 gcpause: (LUAI_GCPAUSE / 4) as u8,
6453 gcstepmul: (LUAI_GCMUL / 4) as u8,
6454 gcstepsize: LUAI_GCSTEPSIZE,
6455 gc55_params: [20, 50, 68, 250, 200, 9600],
6458 sweepgc_cursor: 0,
6459 weak_tables_registry: lua_gc::WeakRegistry::default(),
6460 finalizers: lua_gc::FinalizerRegistry::default(),
6461 gc_finalizer_error: None,
6462 twups: Vec::new(),
6463 panic: None,
6464 mainthread: None,
6465 threads: std::collections::HashMap::new(),
6466 main_thread_value,
6467 current_thread_id: 0,
6468 closing_thread_id: None,
6469 main_thread_id: 0,
6470 next_thread_id: 1,
6471 thread_globals: std::collections::HashMap::new(),
6472 closure_envs: std::collections::HashMap::new(),
6473 memerrmsg: placeholder_str.clone(),
6474 tmname: Vec::new(),
6475 mt: std::array::from_fn(|_| None),
6476 strcache: std::array::from_fn(|_| std::array::from_fn(|_| placeholder_str.clone())),
6477 interned_lt: InternedStringMap::default(),
6478 warnf: None,
6479 warn_mode: WarnMode::Off,
6480 test_warn_enabled: false,
6481 test_warn_on: false,
6482 test_warn_mode: TestWarnMode::Normal,
6483 test_warn_last_to_cont: false,
6484 test_warn_buffer: Vec::new(),
6485 c_functions: Vec::new(),
6486 heap,
6487 cross_thread_upvals: std::collections::HashMap::new(),
6488 suspended_parent_stacks: Vec::new(),
6489 suspended_parent_open_upvals: Vec::new(),
6490 snapshot_stack_pool: Vec::new(),
6491 snapshot_upval_pool: Vec::new(),
6492 resume_value_pool: Vec::new(),
6493 resume_upval_slot_pool: Vec::new(),
6494 resume_flush_pool: Vec::new(),
6495 };
6496
6497 let global_rc = Rc::new(RefCell::new(global));
6498
6499 let _bootstrap_scope = global_rc.borrow().heap.bootstrap_scope();
6513 let _bootstrap_guard = lua_gc::HeapGuard::push(&global_rc.borrow().heap);
6514
6515 let initial_marked = initial_white;
6517
6518 let mut main_thread = LuaState {
6519 status: LuaStatus::Ok as u8,
6520 allowhook: true,
6521 nci: 0,
6522 top: StackIdx(0),
6523 stack_last: StackIdx(0),
6524 stack: Vec::new(),
6525 ci: CallInfoIdx(0),
6526 call_info: Vec::new(),
6527 openupval: Vec::new(),
6528 legacy_open_upval_touched: std::cell::Cell::new(false),
6529 tbclist: Vec::new(),
6530 global: global_rc.clone(),
6531 hook: None,
6532 hookmask: 0,
6533 basehookcount: 0,
6534 hookcount: 0,
6535 errfunc: 0,
6536 n_ccalls: 0,
6537 oldpc: 0,
6538 marked: initial_marked,
6539 cached_thread_id: 0,
6540 gc_check_needed: false,
6541 };
6542
6543 preinit_thread(&mut main_thread, global_rc.clone());
6544
6545 main_thread.inc_nny();
6547
6548 match lua_open(&mut main_thread) {
6558 Ok(()) => {}
6559 Err(_) => {
6560 close_state(&mut main_thread);
6561 return None;
6562 }
6563 }
6564
6565 Some(main_thread)
6566}
6567
6568pub fn close(mut state: LuaState) {
6584 close_state(&mut state);
6589}
6590
6591pub(crate) fn warning(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6601 let test_warn_enabled = state.global().test_warn_enabled;
6602 if test_warn_enabled {
6603 test_warn(state, msg, to_cont);
6604 return;
6605 }
6606
6607 let has_warnf = state.global().warnf.is_some();
6616 if has_warnf {
6617 let mut warnf = state.global_mut().warnf.take();
6619 if let Some(ref mut f) = warnf {
6620 f(msg, to_cont);
6621 }
6622 state.global_mut().warnf = warnf;
6624 return;
6625 }
6626 default_warn(state, msg, to_cont);
6627}
6628
6629fn test_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6630 let is_control = {
6631 let g = state.global();
6632 !g.test_warn_last_to_cont && !to_cont && msg.first() == Some(&b'@')
6633 };
6634 if is_control {
6635 let mut g = state.global_mut();
6636 match &msg[1..] {
6637 b"off" => g.test_warn_on = false,
6638 b"on" => g.test_warn_on = true,
6639 b"normal" => g.test_warn_mode = TestWarnMode::Normal,
6640 b"allow" => g.test_warn_mode = TestWarnMode::Allow,
6641 b"store" => g.test_warn_mode = TestWarnMode::Store,
6642 _ => {}
6643 }
6644 return;
6645 }
6646
6647 let finished = {
6648 let mut g = state.global_mut();
6649 g.test_warn_last_to_cont = to_cont;
6650 g.test_warn_buffer.extend_from_slice(msg);
6651 if to_cont {
6652 None
6653 } else {
6654 Some((
6655 std::mem::take(&mut g.test_warn_buffer),
6656 g.test_warn_mode,
6657 g.test_warn_on,
6658 ))
6659 }
6660 };
6661
6662 let Some((message, mode, warn_on)) = finished else {
6663 return;
6664 };
6665 match mode {
6666 TestWarnMode::Normal => {
6667 if warn_on && message.first() == Some(&b'#') {
6668 write_warning_message(&message);
6669 }
6670 }
6671 TestWarnMode::Allow => {
6672 if warn_on {
6673 write_warning_message(&message);
6674 }
6675 }
6676 TestWarnMode::Store => {
6677 if let Ok(s) = state.intern_str(&message) {
6678 state.push(LuaValue::Str(s));
6679 let _ = crate::api::set_global(state, b"_WARN");
6680 }
6681 }
6682 }
6683}
6684
6685fn write_warning_message(message: &[u8]) {
6686 use std::io::Write;
6687 let stderr = std::io::stderr();
6688 let mut h = stderr.lock();
6689 let _ = h.write_all(b"Lua warning: ");
6690 let _ = h.write_all(message);
6691 let _ = h.write_all(b"\n");
6692}
6693
6694fn default_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6699 use std::io::Write;
6700 if !to_cont && msg.first() == Some(&b'@') {
6702 match &msg[1..] {
6703 b"off" => state.global_mut().warn_mode = WarnMode::Off,
6704 b"on" => state.global_mut().warn_mode = WarnMode::On,
6705 _ => {}
6706 }
6707 return;
6708 }
6709 let mode = state.global().warn_mode;
6710 match mode {
6711 WarnMode::Off => {}
6712 WarnMode::On | WarnMode::Cont => {
6713 let stderr = std::io::stderr();
6714 let mut h = stderr.lock();
6715 if mode == WarnMode::On {
6716 let _ = h.write_all(b"Lua warning: ");
6717 }
6718 let _ = h.write_all(msg);
6719 if to_cont {
6720 state.global_mut().warn_mode = WarnMode::Cont;
6721 } else {
6722 let _ = h.write_all(b"\n");
6723 state.global_mut().warn_mode = WarnMode::On;
6724 }
6725 }
6726 }
6727}
6728
6729#[cfg(test)]
6730mod tests {
6731 use super::*;
6732
6733 fn test_noop_cclosure(_: &mut LuaState) -> Result<usize, LuaError> {
6734 Ok(0)
6735 }
6736
6737 #[test]
6738 fn external_root_keys_reject_stale_slot_after_reuse() {
6739 let mut roots = ExternalRootSet::default();
6740
6741 let first = roots.insert(LuaValue::Int(1));
6742 assert_eq!(roots.len(), 1);
6743 assert_eq!(roots.get(first), Some(&LuaValue::Int(1)));
6744
6745 assert_eq!(roots.remove(first), Some(LuaValue::Int(1)));
6746 assert!(roots.get(first).is_none());
6747 assert!(roots.remove(first).is_none());
6748 assert_eq!(roots.len(), 0);
6749 assert_eq!(roots.vacant_len(), 1);
6750 assert!(roots.replace(first, LuaValue::Int(9)).is_none());
6751 assert!(roots.is_empty());
6752
6753 let second = roots.insert(LuaValue::Int(2));
6754 assert_eq!(first.index, second.index);
6755 assert_ne!(first, second);
6756 assert!(roots.get(first).is_none());
6757 assert_eq!(roots.get(second), Some(&LuaValue::Int(2)));
6758 assert!(roots.replace(first, LuaValue::Int(3)).is_none());
6759 }
6760
6761 #[test]
6762 fn external_roots_keep_heap_value_alive_until_unrooted() {
6763 let mut state = new_state().expect("state should initialize");
6764 let _heap_guard = {
6765 let g = state.global();
6766 lua_gc::HeapGuard::push(&g.heap)
6767 };
6768
6769 let table = state.new_table();
6770 assert_eq!(state.global().heap.allgc_count(), 1);
6771
6772 let key = state.external_root_value(LuaValue::Table(table));
6773 state.gc().full_collect();
6774 assert_eq!(state.global().heap.allgc_count(), 1);
6775 assert_eq!(state.global().external_roots.len(), 1);
6776
6777 assert!(state.external_unroot_value(key).is_some());
6778 state.gc().full_collect();
6779 assert_eq!(state.global().heap.allgc_count(), 0);
6780 assert!(state.global().external_roots.is_empty());
6781 }
6782
6783 #[test]
6784 fn interned_string_table_grows_then_shrinks_on_collection() {
6785 let mut state = new_state().expect("state should initialize");
6786 let _heap_guard = {
6787 let g = state.global();
6788 lua_gc::HeapGuard::push(&g.heap)
6789 };
6790
6791 let initial_buckets = state.global().interned_lt.bucket_count();
6792 assert_eq!(
6793 initial_buckets, 64,
6794 "the intern table starts at C's MINSTRTABSIZE-equivalent of 64"
6795 );
6796 let baseline_live = state.global().interned_lt.len();
6797
6798 let mut anchors: Vec<GcRef<LuaString>> = Vec::with_capacity(2000);
6799 for i in 0..2000usize {
6800 let key = format!("intern-shrink-probe-{i:08}");
6801 let s = state
6802 .intern_str(key.as_bytes())
6803 .expect("short string interns");
6804 anchors.push(s);
6805 }
6806
6807 let grown_buckets = state.global().interned_lt.bucket_count();
6808 assert_eq!(
6809 state.global().interned_lt.len(),
6810 baseline_live + 2000,
6811 "all 2000 distinct short strings are interned and live alongside \
6812 the runtime's own rooted strings"
6813 );
6814 assert!(
6815 grown_buckets >= 2048,
6816 "interning 2000 strings must force several bucket doublings past \
6817 the initial 64 (saw {grown_buckets})"
6818 );
6819
6820 drop(anchors);
6821 state.gc().full_collect();
6822
6823 let shrunk_buckets = state.global().interned_lt.bucket_count();
6824 let surviving_live = state.global().interned_lt.len();
6825 assert!(
6826 surviving_live <= baseline_live,
6827 "every probe string is unreachable and must be swept out of the \
6828 intern table, leaving at most the runtime's own strings (baseline \
6829 {baseline_live}, surviving {surviving_live})"
6830 );
6831 assert!(
6832 surviving_live < 2000,
6833 "the 2000 probe strings must not survive collection (surviving \
6834 {surviving_live})"
6835 );
6836 assert_eq!(
6837 shrunk_buckets, 64,
6838 "the batch-end shrink check must reclaim the stale buckets back to \
6839 the 64-bucket floor (saw {shrunk_buckets}, grew to {grown_buckets})"
6840 );
6841 assert!(
6842 shrunk_buckets < grown_buckets,
6843 "shrink must strictly reduce the bucket array"
6844 );
6845 }
6846
6847 #[test]
6848 fn table_buffer_accounting_refunds_on_sweep() {
6849 let mut state = new_state().expect("state should initialize");
6850 let _heap_guard = {
6851 let g = state.global();
6852 lua_gc::HeapGuard::push(&g.heap)
6853 };
6854
6855 let table = state.new_table();
6856 let key = state.external_root_value(LuaValue::Table(table));
6857 let header_bytes = state.global().heap.bytes_used();
6858 assert!(header_bytes > 0);
6859
6860 for i in 1..=128 {
6861 table
6862 .raw_set_int(&mut state, i, LuaValue::Int(i))
6863 .expect("integer table insert should succeed");
6864 }
6865 let grown_bytes = state.global().heap.bytes_used();
6866 assert!(
6867 grown_bytes > header_bytes,
6868 "table array/hash buffer growth must be charged to the GC heap"
6869 );
6870
6871 state.gc().full_collect();
6872 assert_eq!(
6873 state.global().heap.bytes_used(),
6874 grown_bytes,
6875 "rooted table buffer bytes should remain charged after collection"
6876 );
6877
6878 assert!(state.external_unroot_value(key).is_some());
6879 state.gc().full_collect();
6880 assert_eq!(state.global().heap.bytes_used(), 0);
6881 assert_eq!(state.global().heap.allgc_count(), 0);
6882 }
6883
6884 #[test]
6885 fn userdata_buffer_accounting_refunds_on_sweep() {
6886 let mut state = new_state().expect("state should initialize");
6887 let _heap_guard = {
6888 let g = state.global();
6889 lua_gc::HeapGuard::push(&g.heap)
6890 };
6891
6892 let payload_len = 4096;
6893 let userdata = state
6894 .new_userdata_typed(b"accounting", payload_len, 3)
6895 .expect("userdata allocation should succeed");
6896 state.pop_n(1);
6897 let key = state.external_root_value(LuaValue::UserData(userdata));
6898 let allocated_bytes = state.global().heap.bytes_used();
6899 assert!(
6900 allocated_bytes > payload_len,
6901 "userdata payload bytes must be charged to the GC heap"
6902 );
6903
6904 state.gc().full_collect();
6905 assert_eq!(
6906 state.global().heap.bytes_used(),
6907 allocated_bytes,
6908 "rooted userdata payload bytes should remain charged after collection"
6909 );
6910
6911 assert!(state.external_unroot_value(key).is_some());
6912 state.gc().full_collect();
6913 assert_eq!(state.global().heap.bytes_used(), 0);
6914 assert_eq!(state.global().heap.allgc_count(), 0);
6915 }
6916
6917 #[test]
6918 fn cclosure_upvalue_accounting_refunds_on_sweep() {
6919 let mut state = new_state().expect("state should initialize");
6920 let _heap_guard = {
6921 let g = state.global();
6922 lua_gc::HeapGuard::push(&g.heap)
6923 };
6924
6925 let nupvalues = 64;
6926 for i in 0..nupvalues {
6927 state.push(LuaValue::Int(i as i64));
6928 }
6929 crate::api::push_cclosure(&mut state, test_noop_cclosure, nupvalues as i32)
6930 .expect("C closure creation should succeed");
6931 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6932 panic!("expected heavy C closure");
6933 };
6934 let expected_payload = ccl.buffer_bytes();
6935 let key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
6936 state.pop_n(1);
6937 let allocated_bytes = state.global().heap.bytes_used();
6938 assert!(
6939 allocated_bytes >= expected_payload,
6940 "C closure upvalue vector bytes must be charged to the GC heap"
6941 );
6942
6943 state.gc().full_collect();
6944 assert_eq!(
6945 state.global().heap.bytes_used(),
6946 allocated_bytes,
6947 "rooted C closure payload bytes should remain charged after collection"
6948 );
6949
6950 assert!(state.external_unroot_value(key).is_some());
6951 state.gc().full_collect();
6952 assert_eq!(state.global().heap.bytes_used(), 0);
6953 assert_eq!(state.global().heap.allgc_count(), 0);
6954 }
6955
6956 #[test]
6957 fn proto_and_lclosure_accounting_refunds_on_sweep() {
6958 let mut state = new_state().expect("state should initialize");
6959 let _heap_guard = {
6960 let g = state.global();
6961 lua_gc::HeapGuard::push(&g.heap)
6962 };
6963
6964 let mut proto = LuaProto::placeholder();
6965 proto.code = vec![lua_types::opcode::Instruction(0); 2048];
6966 proto.lineinfo = vec![0; 2048];
6967 proto.k = vec![LuaValue::Int(1); 512];
6968 let expected_proto_payload = proto.buffer_bytes();
6969 let proto = GcRef::new(proto);
6970 proto.account_buffer(expected_proto_payload as isize);
6971
6972 let closure = state.new_lclosure(proto, 16);
6973 let expected_closure_payload = closure.buffer_bytes();
6974 let key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6975 let allocated_bytes = state.global().heap.bytes_used();
6976 assert!(
6977 allocated_bytes >= expected_proto_payload + expected_closure_payload,
6978 "proto and Lua closure vector bytes must be charged to the GC heap"
6979 );
6980
6981 state.gc().full_collect();
6982 assert_eq!(
6983 state.global().heap.bytes_used(),
6984 allocated_bytes,
6985 "rooted proto and Lua closure payload bytes should remain charged after collection"
6986 );
6987
6988 assert!(state.external_unroot_value(key).is_some());
6989 state.gc().full_collect();
6990 assert_eq!(state.global().heap.bytes_used(), 0);
6991 assert_eq!(state.global().heap.allgc_count(), 0);
6992 }
6993
6994 #[test]
6995 fn string_buffer_accounting_refunds_on_sweep() {
6996 let mut state = new_state().expect("state should initialize");
6997 let _heap_guard = {
6998 let g = state.global();
6999 lua_gc::HeapGuard::push(&g.heap)
7000 };
7001
7002 let payload = vec![b'x'; crate::string::MAX_SHORT_LEN + 4096];
7003 let string = state
7004 .intern_str(&payload)
7005 .expect("long string should allocate");
7006 let key = state.external_root_value(LuaValue::Str(string));
7007 let allocated_bytes = state.global().heap.bytes_used();
7008 assert!(
7009 allocated_bytes > payload.len(),
7010 "long string backing bytes must be charged to the GC heap"
7011 );
7012
7013 state.gc().full_collect();
7014 assert_eq!(
7015 state.global().heap.bytes_used(),
7016 allocated_bytes,
7017 "rooted string buffer bytes should remain charged after collection"
7018 );
7019
7020 assert!(state.external_unroot_value(key).is_some());
7021 state.gc().full_collect();
7022 assert_eq!(state.global().heap.bytes_used(), 0);
7023 assert_eq!(state.global().heap.allgc_count(), 0);
7024 }
7025
7026 #[test]
7027 fn interned_short_string_cache_does_not_root_unreferenced_string() {
7028 let mut state = new_state().expect("state should initialize");
7029 let _heap_guard = {
7030 let g = state.global();
7031 lua_gc::HeapGuard::push(&g.heap)
7032 };
7033
7034 let payload = b"weak-cache-probe-a";
7035 let string = state
7036 .intern_str(payload)
7037 .expect("short string should intern");
7038 let id = string.identity();
7039 assert!(state.global().interned_lt.contains_key(&payload[..]));
7040 assert_eq!(
7041 state.global().heap.register_allocation_token(id),
7042 state.global().heap.register_allocation_token(id),
7043 "token registration is get-or-insert while the string is provably live"
7044 );
7045 assert!(state.global().heap.allocation_token(id).is_some());
7046
7047 state.gc().full_collect();
7048 assert!(!state.global().interned_lt.contains_key(&payload[..]));
7049 assert_eq!(state.global().heap.allocation_token(id), None);
7050 }
7051
7052 #[test]
7053 fn interned_short_string_cache_keeps_reachable_string_until_unrooted() {
7054 let mut state = new_state().expect("state should initialize");
7055 let _heap_guard = {
7056 let g = state.global();
7057 lua_gc::HeapGuard::push(&g.heap)
7058 };
7059
7060 let payload = b"weak-cache-probe-b";
7061 let string = state
7062 .intern_str(payload)
7063 .expect("short string should intern");
7064 let id = string.identity();
7065 state.global().heap.register_allocation_token(id);
7066 let key = state.external_root_value(LuaValue::Str(string));
7067
7068 state.gc().full_collect();
7069 assert!(state.global().interned_lt.contains_key(&payload[..]));
7070 assert!(state.global().heap.allocation_token(id).is_some());
7071
7072 assert!(state.external_unroot_value(key).is_some());
7073 state.gc().full_collect();
7074 assert!(!state.global().interned_lt.contains_key(&payload[..]));
7075 assert_eq!(state.global().heap.allocation_token(id), None);
7076 }
7077
7078 #[test]
7079 fn gc_phase_predicates_follow_heap_state() {
7080 let mut state = new_state().expect("state should initialize");
7081 let _heap_guard = {
7082 let g = state.global();
7083 lua_gc::HeapGuard::push(&g.heap)
7084 };
7085
7086 {
7087 let mut g = state.global_mut();
7088 g.gckind = GcKind::Incremental as u8;
7089 g.lastatomic = 0;
7090 assert!(!g.is_gen_mode());
7091 g.lastatomic = 1;
7092 assert!(g.is_gen_mode());
7093 g.lastatomic = 0;
7094 }
7095
7096 let mut roots = Vec::new();
7097 for _ in 0..16 {
7098 let table = state.new_table();
7099 roots.push(state.external_root_value(LuaValue::Table(table)));
7100 }
7101
7102 let mut saw_keep = false;
7103 let mut saw_sweep = false;
7104 for _ in 0..128 {
7105 state.gc().incremental_step(1);
7106 let g = state.global();
7107 let heap_state = g.heap.gc_state();
7108 assert_eq!(g.keep_invariant(), heap_state.is_invariant());
7109 assert_eq!(g.is_sweep_phase(), heap_state.is_sweep());
7110 saw_keep |= g.keep_invariant();
7111 saw_sweep |= g.is_sweep_phase();
7112 if heap_state.is_pause() && saw_keep && saw_sweep {
7113 break;
7114 }
7115 }
7116
7117 assert!(
7118 saw_keep,
7119 "incremental cycle should expose an invariant phase"
7120 );
7121 assert!(saw_sweep, "incremental cycle should expose a sweep phase");
7122
7123 for key in roots {
7124 assert!(state.external_unroot_value(key).is_some());
7125 }
7126 state.gc().full_collect();
7127 }
7128
7129 #[test]
7130 fn gc_barrier_keeps_new_child_stored_in_black_parent() {
7131 let mut state = new_state().expect("state should initialize");
7132 let _heap_guard = {
7133 let g = state.global();
7134 lua_gc::HeapGuard::push(&g.heap)
7135 };
7136
7137 let parent = state.new_table();
7138 let parent_key = state.external_root_value(LuaValue::Table(parent));
7139 state.gc().incremental_step(1);
7140 assert!(
7141 state.global().keep_invariant(),
7142 "test setup should leave the parent marked during an active cycle"
7143 );
7144
7145 let child = state.new_table();
7146 let parent_value = LuaValue::Table(parent);
7147 let child_value = LuaValue::Table(child);
7148 parent
7149 .raw_set_int(&mut state, 1, child_value)
7150 .expect("table store should succeed");
7151 state.gc_barrier_back(&parent_value, &child_value);
7152
7153 for _ in 0..128 {
7154 if state.gc().incremental_step(1) {
7155 break;
7156 }
7157 }
7158
7159 assert_eq!(state.global().heap.allgc_count(), 2);
7160 assert_eq!(
7161 parent.get_int(1).as_table().map(|t| t.identity()),
7162 Some(child.identity())
7163 );
7164
7165 assert!(state.external_unroot_value(parent_key).is_some());
7166 state.gc().full_collect();
7167 assert_eq!(state.global().heap.allgc_count(), 0);
7168 }
7169
7170 #[test]
7171 fn generational_mode_promotes_and_barriers_age_objects() {
7172 let mut state = new_state().expect("state should initialize");
7173 let _heap_guard = {
7174 let g = state.global();
7175 lua_gc::HeapGuard::push(&g.heap)
7176 };
7177
7178 let parent = state.new_table();
7179 let parent_key = state.external_root_value(LuaValue::Table(parent));
7180
7181 state.gc().change_mode(GcKind::Generational);
7182 assert_eq!(parent.0.age(), lua_gc::GcAge::Old);
7183 assert_eq!(parent.0.color(), lua_gc::Color::Black);
7184 let majorbase = state.global().gc_estimate;
7185 assert!(majorbase > 0);
7186 assert!(state.global().gc_debt() <= 0);
7187
7188 let child = state.new_table();
7189 let parent_value = LuaValue::Table(parent);
7190 let child_value = LuaValue::Table(child);
7191 parent
7192 .raw_set_int(&mut state, 1, child_value.clone())
7193 .expect("table store should succeed");
7194 state.gc_barrier_back(&parent_value, &child_value);
7195 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched1);
7196 assert_eq!(parent.0.color(), lua_gc::Color::Gray);
7197 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7198
7199 let metatable = state.new_table();
7200 parent.set_metatable(Some(metatable));
7201 state.gc().obj_barrier(&parent, &metatable);
7202 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old0);
7203
7204 assert!(state.gc().generational_step_minor_only());
7205 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched2);
7206 assert_eq!(child.0.age(), lua_gc::GcAge::Survival);
7207 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old1);
7208 assert_eq!(state.global().gc_estimate, majorbase);
7209 assert!(state.global().gc_debt() <= 0);
7210
7211 state.gc().change_mode(GcKind::Incremental);
7212 assert_eq!(parent.0.age(), lua_gc::GcAge::New);
7213 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7214 assert_eq!(metatable.0.age(), lua_gc::GcAge::New);
7215
7216 assert!(state.external_unroot_value(parent_key).is_some());
7217 state.gc().full_collect();
7218 }
7219
7220 #[test]
7221 fn generational_upvalue_write_barrier_marks_young_child_old0() {
7222 let mut state = new_state().expect("state should initialize");
7223 let _heap_guard = {
7224 let g = state.global();
7225 lua_gc::HeapGuard::push(&g.heap)
7226 };
7227
7228 let proto = state.new_proto();
7229 let closure = state.new_lclosure(proto, 1);
7230 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7231 state.gc().change_mode(GcKind::Generational);
7232 let uv = closure.upval(0);
7233 assert_eq!(uv.0.age(), lua_gc::GcAge::Old);
7234
7235 let child = state.new_table();
7236 state
7237 .upvalue_set(&closure, 0, LuaValue::Table(child))
7238 .expect("closed upvalue write should succeed");
7239 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7240
7241 assert!(state.external_unroot_value(closure_key).is_some());
7242 state.gc().full_collect();
7243 }
7244
7245 #[test]
7246 fn cclosure_setupvalue_replaces_upvalue() {
7247 let mut state = new_state().expect("state should initialize");
7248 let _heap_guard = {
7249 let g = state.global();
7250 lua_gc::HeapGuard::push(&g.heap)
7251 };
7252
7253 let first = state.new_table();
7254 state.push(LuaValue::Table(first));
7255 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7256 .expect("C closure creation should succeed");
7257 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7258 panic!("expected heavy C closure");
7259 };
7260
7261 let second = state.new_table();
7262 state.push(LuaValue::Table(second));
7263 let name =
7264 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7265
7266 assert!(name.is_empty());
7267 let upvalues = ccl.upvalues.borrow();
7268 let LuaValue::Table(actual) = upvalues[0].clone() else {
7269 panic!("expected table upvalue");
7270 };
7271 assert_eq!(actual.identity(), second.identity());
7272 }
7273
7274 #[test]
7275 fn generational_cclosure_setupvalue_barrier_marks_young_child_old0() {
7276 let mut state = new_state().expect("state should initialize");
7277 let _heap_guard = {
7278 let g = state.global();
7279 lua_gc::HeapGuard::push(&g.heap)
7280 };
7281
7282 state.push(LuaValue::Nil);
7283 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7284 .expect("C closure creation should succeed");
7285 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7286 panic!("expected heavy C closure");
7287 };
7288 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
7289
7290 state.gc().change_mode(GcKind::Generational);
7291 assert_eq!(ccl.0.age(), lua_gc::GcAge::Old);
7292
7293 let child = state.new_table();
7294 state.push(LuaValue::Table(child));
7295 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7296
7297 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7298
7299 assert!(state.external_unroot_value(closure_key).is_some());
7300 state.gc().full_collect();
7301 }
7302
7303 #[test]
7304 fn generational_closure_upvalue_slot_barrier_marks_new_upval_old0() {
7305 let mut state = new_state().expect("state should initialize");
7306 let _heap_guard = {
7307 let g = state.global();
7308 lua_gc::HeapGuard::push(&g.heap)
7309 };
7310
7311 let proto = state.new_proto();
7312 let closure = state.new_lclosure(proto, 1);
7313 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7314 state.gc().change_mode(GcKind::Generational);
7315 assert_eq!(closure.0.age(), lua_gc::GcAge::Old);
7316
7317 let replacement = state.new_upval_closed(LuaValue::Nil);
7318 closure.set_upval(0, replacement);
7319 state.gc().obj_barrier(&closure, &replacement);
7320 assert_eq!(replacement.0.age(), lua_gc::GcAge::Old0);
7321
7322 assert!(state.external_unroot_value(closure_key).is_some());
7323 state.gc().full_collect();
7324 }
7325
7326 #[test]
7327 fn cross_thread_upvalue_mirror_traces_values_as_roots() {
7328 let mut state = new_state().expect("state should initialize");
7329 let _heap_guard = {
7330 let g = state.global();
7331 lua_gc::HeapGuard::push(&g.heap)
7332 };
7333
7334 let mirrored = state.new_table();
7335 state
7336 .global_mut()
7337 .cross_thread_upvals
7338 .insert((999, StackIdx(0)), LuaValue::Table(mirrored));
7339
7340 state.gc().full_collect();
7341 assert_eq!(state.global().heap.allgc_count(), 1);
7342
7343 state.global_mut().cross_thread_upvals.clear();
7344 state.gc().full_collect();
7345 assert_eq!(state.global().heap.allgc_count(), 0);
7346 }
7347
7348 #[test]
7349 fn generational_full_collect_promotes_new_survivors_to_old() {
7350 let mut state = new_state().expect("state should initialize");
7351 let _heap_guard = {
7352 let g = state.global();
7353 lua_gc::HeapGuard::push(&g.heap)
7354 };
7355
7356 state.gc().change_mode(GcKind::Generational);
7357 let table = state.new_table();
7358 let table_key = state.external_root_value(LuaValue::Table(table));
7359 assert_eq!(table.0.age(), lua_gc::GcAge::New);
7360
7361 state.gc().full_collect();
7362 assert_eq!(table.0.age(), lua_gc::GcAge::Old);
7363 assert_eq!(table.0.color(), lua_gc::Color::Black);
7364
7365 assert!(state.external_unroot_value(table_key).is_some());
7366 state.gc().full_collect();
7367 }
7368
7369 #[test]
7370 fn gc_packed_params_return_user_visible_values() {
7371 let mut state = new_state().expect("state should initialize");
7372 assert_eq!(
7373 crate::api::gc(&mut state, crate::api::GcArgs::SetPause { value: 200 }),
7374 200
7375 );
7376 assert_eq!(state.global().gc_pause_param(), 200);
7377 assert_eq!(
7378 crate::api::gc(&mut state, crate::api::GcArgs::SetStepMul { value: 200 }),
7379 100
7380 );
7381 assert_eq!(state.global().gc_stepmul_param(), 200);
7382
7383 crate::api::gc(
7384 &mut state,
7385 crate::api::GcArgs::Gen {
7386 minormul: 0,
7387 majormul: 200,
7388 },
7389 );
7390 assert_eq!(state.global().gc_genmajormul_param(), 200);
7391 }
7392
7393 #[test]
7394 fn generational_step_runs_bad_major_when_growth_exceeds_genmajormul() {
7395 let mut state = new_state().expect("state should initialize");
7396 let _heap_guard = {
7397 let g = state.global();
7398 lua_gc::HeapGuard::push(&g.heap)
7399 };
7400
7401 let root = state.new_table();
7402 let root_key = state.external_root_value(LuaValue::Table(root));
7403 state.gc().change_mode(GcKind::Generational);
7404
7405 let root_value = LuaValue::Table(root);
7406 for i in 1..=64 {
7407 let child = state.new_table();
7408 let child_value = LuaValue::Table(child);
7409 root.raw_set_int(&mut state, i, child_value.clone())
7410 .expect("table store should succeed");
7411 state.gc_barrier_back(&root_value, &child_value);
7412 }
7413
7414 {
7415 let mut g = state.global_mut();
7416 g.gc_estimate = 1;
7417 set_debt(&mut *g, 1);
7418 }
7419
7420 assert!(state.gc().generational_step());
7421 let g = state.global();
7422 assert!(g.is_gen_mode());
7423 assert!(
7424 g.lastatomic > 0,
7425 "bad major collection should arm stepgenfull"
7426 );
7427 assert!(g.gc_estimate > 1);
7428 assert!(g.gc_debt() <= 0);
7429 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
7430 drop(g);
7431
7432 assert!(state.external_unroot_value(root_key).is_some());
7433 state.gc().full_collect();
7434 }
7435
7436 #[test]
7437 fn generational_implicit_step_runs_major_when_heap_threshold_exceeded() {
7438 let mut state = new_state().expect("state should initialize");
7439 let _heap_guard = {
7440 let g = state.global();
7441 lua_gc::HeapGuard::push(&g.heap)
7442 };
7443
7444 let root = state.new_table();
7445 let root_key = state.external_root_value(LuaValue::Table(root));
7446 state.gc().change_mode(GcKind::Generational);
7447
7448 let root_value = LuaValue::Table(root);
7449 for i in 1..=64 {
7450 let child = state.new_table();
7451 let child_value = LuaValue::Table(child);
7452 root.raw_set_int(&mut state, i, child_value.clone())
7453 .expect("table store should succeed");
7454 state.gc_barrier_back(&root_value, &child_value);
7455 }
7456
7457 {
7458 let mut g = state.global_mut();
7459 g.gc_estimate = 1;
7460 set_debt(&mut *g, -1);
7461 g.heap.set_threshold_bytes(1);
7462 }
7463
7464 assert!(state.gc().generational_step());
7465 let g = state.global();
7466 assert!(g.is_gen_mode());
7467 assert!(
7468 g.lastatomic > 0,
7469 "implicit threshold-triggered growth should arm a bad major"
7470 );
7471 assert!(g.gc_debt() <= 0);
7472 drop(g);
7473
7474 assert!(state.external_unroot_value(root_key).is_some());
7475 state.gc().full_collect();
7476 }
7477
7478 #[test]
7479 fn generational_stepgenfull_returns_to_gen_after_good_collection() {
7480 let mut state = new_state().expect("state should initialize");
7481 let _heap_guard = {
7482 let g = state.global();
7483 lua_gc::HeapGuard::push(&g.heap)
7484 };
7485
7486 let root = state.new_table();
7487 let root_key = state.external_root_value(LuaValue::Table(root));
7488 state.gc().change_mode(GcKind::Generational);
7489 {
7490 let mut g = state.global_mut();
7491 g.lastatomic = 1024;
7492 }
7493
7494 assert!(state.gc().generational_step());
7495 let g = state.global();
7496 assert_eq!(g.gckind, GcKind::Generational as u8);
7497 assert_eq!(g.lastatomic, 0);
7498 assert!(g.gc_debt() <= 0);
7499 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
7500 assert_eq!(root.0.color(), lua_gc::Color::Black);
7501 drop(g);
7502
7503 assert!(state.external_unroot_value(root_key).is_some());
7504 state.gc().full_collect();
7505 }
7506
7507 #[test]
7508 fn generational_step_zero_reports_false_without_positive_debt() {
7509 let mut state = new_state().expect("state should initialize");
7510 let _heap_guard = {
7511 let g = state.global();
7512 lua_gc::HeapGuard::push(&g.heap)
7513 };
7514
7515 state.gc().change_mode(GcKind::Generational);
7516 assert_eq!(
7517 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 0 }),
7518 0
7519 );
7520 assert_eq!(
7521 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 1 }),
7522 1
7523 );
7524 }
7525}
7526
7527