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(
1239 state: &mut LuaState,
1240 source: &[u8],
1241 name: &[u8],
1242 firstchar: i32,
1243) -> Result<GcRef<lua_types::closure::LuaLClosure>, LuaError>;
1244
1245pub type FileLoaderHook = fn(filename: &[u8]) -> Result<Vec<u8>, LuaError>;
1253
1254pub type FileOpenHook =
1265 fn(filename: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1266
1267pub type OutputHook = fn(bytes: &[u8]) -> std::io::Result<()>;
1275
1276pub type InputHook = fn(buf: &mut [u8]) -> std::io::Result<usize>;
1279
1280pub type EnvHook = fn(name: &[u8]) -> Option<Vec<u8>>;
1286
1287pub type UnixTimeHook = fn() -> i64;
1289
1290pub type CpuClockHook = fn() -> f64;
1298
1299pub type LocalOffsetHook = fn(timestamp: i64) -> i64;
1311
1312pub type EntropyHook = fn() -> u64;
1316
1317pub type TempNameHook = fn() -> Result<Vec<u8>, LuaError>;
1322
1323pub type PopenHook =
1334 fn(cmd: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1335
1336pub type FileRemoveHook = fn(filename: &[u8]) -> Result<(), LuaError>;
1342
1343pub type FileRenameHook = fn(from: &[u8], to: &[u8]) -> Result<(), LuaError>;
1349
1350#[derive(Clone, Copy, Debug)]
1356pub enum OsExecuteReason {
1357 Exit,
1359 Signal,
1361}
1362
1363#[derive(Debug)]
1366pub struct OsExecuteResult {
1367 pub success: bool,
1369 pub reason: OsExecuteReason,
1371 pub code: i32,
1373}
1374
1375pub type OsExecuteHook = fn(cmd: &[u8]) -> Result<OsExecuteResult, LuaError>;
1382
1383#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1390pub struct DynLibId(pub u64);
1391
1392pub enum DynamicSymbol {
1400 RustNative(LuaCFunction),
1403 LuaCAbi(*const ()),
1409 Unsupported { reason: Vec<u8> },
1412}
1413
1414pub type DynLibLoadHook =
1425 fn(state: &mut LuaState, path: &[u8], see_global: bool) -> Result<DynLibId, LuaError>;
1426
1427pub type DynLibSymbolHook =
1435 fn(state: &mut LuaState, handle: DynLibId, symbol: &[u8]) -> Result<DynamicSymbol, LuaError>;
1436
1437pub type DynLibUnloadHook = fn(handle: DynLibId);
1445
1446pub struct ThreadRegistryEntry {
1452 pub state: Rc<RefCell<LuaState>>,
1457 pub value: GcRef<lua_types::value::LuaThread>,
1460}
1461
1462#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1467pub struct ExternalRootKey {
1468 index: usize,
1469 generation: u64,
1470}
1471
1472#[derive(Debug)]
1473struct ExternalRootSlot {
1474 value: Option<LuaValue>,
1475 generation: u64,
1476}
1477
1478#[derive(Debug, Default)]
1484pub struct ExternalRootSet {
1485 slots: Vec<ExternalRootSlot>,
1486 free: Vec<usize>,
1487 live: usize,
1488}
1489
1490impl ExternalRootSet {
1491 pub fn insert(&mut self, value: LuaValue) -> ExternalRootKey {
1492 if let Some(index) = self.free.pop() {
1493 let slot = &mut self.slots[index];
1494 debug_assert!(slot.value.is_none(), "free external-root slot is occupied");
1495 slot.generation = slot.generation.wrapping_add(1).max(1);
1496 slot.value = Some(value);
1497 self.live += 1;
1498 ExternalRootKey {
1499 index,
1500 generation: slot.generation,
1501 }
1502 } else {
1503 let index = self.slots.len();
1504 self.slots.push(ExternalRootSlot {
1505 value: Some(value),
1506 generation: 1,
1507 });
1508 self.live += 1;
1509 ExternalRootKey {
1510 index,
1511 generation: 1,
1512 }
1513 }
1514 }
1515
1516 pub fn get(&self, key: ExternalRootKey) -> Option<&LuaValue> {
1517 let slot = self.slots.get(key.index)?;
1518 if slot.generation == key.generation {
1519 slot.value.as_ref()
1520 } else {
1521 None
1522 }
1523 }
1524
1525 pub fn replace(&mut self, key: ExternalRootKey, value: LuaValue) -> Option<LuaValue> {
1526 let slot = self.slots.get_mut(key.index)?;
1527 if slot.generation != key.generation || slot.value.is_none() {
1528 return None;
1529 }
1530 slot.value.replace(value)
1531 }
1532
1533 pub fn remove(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
1534 let slot = self.slots.get_mut(key.index)?;
1535 if slot.generation != key.generation {
1536 return None;
1537 }
1538 let old = slot.value.take()?;
1539 self.free.push(key.index);
1540 self.live -= 1;
1541 Some(old)
1542 }
1543
1544 pub fn iter_values(&self) -> impl Iterator<Item = &LuaValue> {
1545 self.slots.iter().filter_map(|slot| slot.value.as_ref())
1546 }
1547
1548 pub fn len(&self) -> usize {
1549 self.live
1550 }
1551
1552 pub fn is_empty(&self) -> bool {
1553 self.live == 0
1554 }
1555
1556 pub fn vacant_len(&self) -> usize {
1557 self.free.len()
1558 }
1559}
1560
1561pub struct GlobalState {
1567 pub parser_hook: Option<ParserHook>,
1573
1574 pub cli_argv: Option<Vec<Vec<u8>>>,
1581
1582 pub cli_preload: Option<fn(&mut LuaState) -> Result<(), LuaError>>,
1586
1587 pub lua_version: lua_types::LuaVersion,
1594
1595 pub file_loader_hook: Option<FileLoaderHook>,
1600
1601 pub file_open_hook: Option<FileOpenHook>,
1606
1607 pub stdout_hook: Option<OutputHook>,
1611
1612 pub stderr_hook: Option<OutputHook>,
1614
1615 pub stdin_hook: Option<InputHook>,
1618
1619 pub env_hook: Option<EnvHook>,
1621
1622 pub unix_time_hook: Option<UnixTimeHook>,
1625
1626 pub cpu_clock_hook: Option<CpuClockHook>,
1629
1630 pub local_offset_hook: Option<LocalOffsetHook>,
1635
1636 pub entropy_hook: Option<EntropyHook>,
1639
1640 pub temp_name_hook: Option<TempNameHook>,
1642
1643 pub popen_hook: Option<PopenHook>,
1648
1649 pub file_remove_hook: Option<FileRemoveHook>,
1652
1653 pub file_rename_hook: Option<FileRenameHook>,
1656
1657 pub os_execute_hook: Option<OsExecuteHook>,
1661
1662 pub dynlib_load_hook: Option<DynLibLoadHook>,
1667
1668 pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
1672
1673 pub dynlib_unload_hook: Option<DynLibUnloadHook>,
1677
1678 pub sandbox: SandboxLimits,
1681
1682 pub gc_debt: isize,
1684
1685 pub gc_estimate: usize,
1686
1687 pub lastatomic: usize,
1689
1690 pub strt: StringPool,
1692
1693 pub l_registry: LuaValue,
1695
1696 pub external_roots: ExternalRootSet,
1699
1700 pub globals: LuaValue,
1707 pub loaded: LuaValue,
1708
1709 pub nilvalue: LuaValue,
1713
1714 pub seed: u32,
1716
1717 pub currentwhite: u8,
1719
1720 pub gcstate: u8,
1721
1722 pub gckind: u8,
1723
1724 pub gcstopem: bool,
1725
1726 pub genminormul: u8,
1728
1729 pub genmajormul: u8,
1730
1731 pub gcstp: u8,
1732
1733 pub gcemergency: bool,
1734
1735 pub gcpause: u8,
1737
1738 pub gcstepmul: u8,
1740
1741 pub gcstepsize: u8,
1742
1743 pub gc55_params: [i64; 6],
1752
1753 pub sweepgc_cursor: usize,
1758
1759 pub weak_tables_registry: lua_gc::WeakRegistry<WeakTableEntry>,
1768
1769 pub finalizers: lua_gc::FinalizerRegistry<FinalizerObject>,
1772
1773 pub gc_finalizer_error: Option<LuaValue>,
1784
1785 pub twups: Vec<GcRef<LuaState>>,
1795
1796 pub panic: Option<LuaCFunction>,
1798
1799 pub mainthread: Option<GcRef<LuaState>>,
1802
1803 pub threads: std::collections::HashMap<u64, ThreadRegistryEntry>,
1816
1817 pub main_thread_value: GcRef<lua_types::value::LuaThread>,
1822
1823 pub current_thread_id: u64,
1828
1829 pub closing_thread_id: Option<u64>,
1832
1833 pub main_thread_id: u64,
1836
1837 pub next_thread_id: u64,
1840
1841 pub thread_globals: std::collections::HashMap<u64, LuaValue>,
1858
1859 pub closure_envs: std::collections::HashMap<usize, LuaValue>,
1871
1872 pub memerrmsg: GcRef<LuaString>,
1874
1875 pub tmname: Vec<GcRef<LuaString>>,
1878
1879 pub mt: [Option<GcRef<LuaTable>>; LUA_NUMTYPES],
1881
1882 pub strcache: [[GcRef<LuaString>; STRCACHE_M]; STRCACHE_N],
1884
1885 pub interned_lt: InternedStringMap,
1891
1892 pub warnf: Option<Box<dyn FnMut(&[u8], bool)>>,
1894
1895 pub warn_mode: WarnMode,
1900
1901 pub test_warn_enabled: bool,
1906 pub test_warn_on: bool,
1907 pub test_warn_mode: TestWarnMode,
1908 pub test_warn_last_to_cont: bool,
1909 pub test_warn_buffer: Vec<u8>,
1910
1911 pub c_functions: Vec<LuaCallable>,
1916
1917 pub heap: lua_gc::Heap,
1922
1923 pub cross_thread_upvals: std::collections::HashMap<(u64, StackIdx), LuaValue>,
1937
1938 pub suspended_parent_stacks: Vec<Vec<LuaValue>>,
1952
1953 pub suspended_parent_open_upvals: Vec<Vec<GcRef<UpVal>>>,
1959
1960 pub snapshot_stack_pool: Vec<Vec<LuaValue>>,
1968 pub snapshot_upval_pool: Vec<Vec<GcRef<UpVal>>>,
1969
1970 pub resume_value_pool: Vec<Vec<LuaValue>>,
1978
1979 pub resume_upval_slot_pool: Vec<Vec<(u64, StackIdx)>>,
1985
1986 pub resume_flush_pool: Vec<Vec<(StackIdx, LuaValue)>>,
1990}
1991
1992const SANDBOX_COUNT_MASK: u8 = 1 << 3;
1995
1996pub const SANDBOX_TRIP_NONE: u8 = 0;
1998pub const SANDBOX_TRIP_INSTRUCTIONS: u8 = 1;
2000pub const SANDBOX_TRIP_MEMORY: u8 = 2;
2002
2003#[derive(Default)]
2010pub struct SandboxLimits {
2011 pub interval: std::cell::Cell<i32>,
2013 pub instr_limited: std::cell::Cell<bool>,
2015 pub instr_remaining: std::cell::Cell<u64>,
2017 pub instr_limit: std::cell::Cell<u64>,
2019 pub mem_limit: std::cell::Cell<Option<usize>>,
2021 pub tripped: std::cell::Cell<u8>,
2023 pub aborting: std::cell::Cell<bool>,
2028}
2029
2030impl GlobalState {
2031 pub fn sandbox_active(&self) -> bool {
2033 self.sandbox.interval.get() != 0
2034 }
2035
2036 pub fn total_bytes(&self) -> usize {
2041 self.heap.bytes_used().max(1)
2042 }
2043
2044 pub fn get_thread(&self, id: u64) -> Option<&ThreadRegistryEntry> {
2049 self.threads.get(&id)
2050 }
2051
2052 pub fn thread_value_for(&self, id: u64) -> Option<GcRef<lua_types::value::LuaThread>> {
2056 if id == self.main_thread_id {
2057 Some(self.main_thread_value.clone())
2058 } else {
2059 self.threads.get(&id).map(|e| e.value.clone())
2060 }
2061 }
2062
2063 pub fn is_complete(&self) -> bool {
2070 matches!(self.nilvalue, LuaValue::Nil)
2071 }
2072
2073 pub fn current_white(&self) -> u8 {
2081 self.currentwhite
2082 }
2083
2084 pub fn other_white(&self) -> u8 {
2088 self.currentwhite ^ 0x03
2089 }
2090
2091 pub fn is_gen_mode(&self) -> bool {
2095 self.gckind == GcKind::Generational as u8 || self.lastatomic != 0
2096 }
2097
2098 pub fn gc_running(&self) -> bool {
2102 self.gcstp == 0
2103 }
2104
2105 pub fn keep_invariant(&self) -> bool {
2109 self.heap.gc_state().is_invariant()
2110 }
2111
2112 pub fn is_sweep_phase(&self) -> bool {
2116 self.heap.gc_state().is_sweep()
2117 }
2118
2119 pub fn gc_debt(&self) -> isize {
2121 self.gc_debt
2122 }
2123 pub fn set_gc_debt(&mut self, d: isize) {
2124 self.gc_debt = d;
2125 }
2126 pub fn gc_at_pause(&self) -> bool {
2127 self.heap.gc_state().is_pause()
2128 }
2129 fn get_gc_param(p: u8) -> i32 {
2130 (p as i32) * 4
2131 }
2132 fn set_gc_param_slot(slot: &mut u8, p: i32) {
2133 *slot = (p / 4) as u8;
2134 }
2135 pub fn gc_pause_param(&self) -> i32 {
2136 Self::get_gc_param(self.gcpause)
2137 }
2138 pub fn set_gc_pause_param(&mut self, p: i32) {
2139 Self::set_gc_param_slot(&mut self.gcpause, p);
2140 }
2141 pub fn gc_stepmul_param(&self) -> i32 {
2142 Self::get_gc_param(self.gcstepmul)
2143 }
2144 pub fn set_gc_stepmul_param(&mut self, p: i32) {
2145 Self::set_gc_param_slot(&mut self.gcstepmul, p);
2146 }
2147 pub fn gc_genmajormul_param(&self) -> i32 {
2148 Self::get_gc_param(self.genmajormul)
2149 }
2150 pub fn set_gc_genmajormul(&mut self, p: i32) {
2151 Self::set_gc_param_slot(&mut self.genmajormul, p);
2152 }
2153 pub fn gc55_param(&mut self, idx: usize, value: i64) -> i64 {
2157 let old = self.gc55_params[idx];
2158 if value >= 0 {
2159 self.gc55_params[idx] = value;
2160 }
2161 old
2162 }
2163 pub fn gc_stop_flags(&self) -> u8 {
2164 self.gcstp
2165 }
2166 pub fn set_gc_stop_flags(&mut self, f: u8) {
2167 self.gcstp = f;
2168 }
2169 pub fn stop_gc_internal(&mut self) -> u8 {
2170 let old = self.gcstp;
2171 self.gcstp |= GCSTPGC;
2172 old
2173 }
2174 pub fn set_gc_stop_user(&mut self) {
2175 self.gcstp = GCSTPUSR;
2177 }
2178 pub fn clear_gc_stop(&mut self) {
2179 self.gcstp = 0;
2180 }
2181 pub fn is_gc_running(&self) -> bool {
2182 self.gcstp == 0
2183 }
2184 pub fn is_gc_stopped_internally(&self) -> bool {
2189 (self.gcstp & GCSTPGC) != 0
2190 }
2191
2192 pub fn tm_name<T: TmIndex>(&self, tm: T) -> Option<GcRef<LuaString>> {
2202 self.tmname.get(tm.tm_index()).cloned()
2203 }
2204}
2205
2206pub trait TmIndex: Copy {
2212 fn tm_index(self) -> usize;
2213}
2214impl TmIndex for lua_types::tagmethod::TagMethod {
2215 fn tm_index(self) -> usize {
2216 self as u8 as usize
2217 }
2218}
2219impl TmIndex for crate::tagmethods::TagMethod {
2220 fn tm_index(self) -> usize {
2221 self as u8 as usize
2222 }
2223}
2224impl TmIndex for usize {
2225 fn tm_index(self) -> usize {
2226 self
2227 }
2228}
2229impl TmIndex for u8 {
2230 fn tm_index(self) -> usize {
2231 self as usize
2232 }
2233}
2234
2235use lua_types::tagmethod::TagMethod;
2236
2237pub struct LuaState {
2247 pub status: u8,
2251
2252 pub allowhook: bool,
2254
2255 pub nci: u32,
2257
2258 pub top: StackIdx,
2262
2263 pub stack_last: StackIdx,
2265
2266 pub stack: Vec<StackValue>,
2268
2269 pub ci: CallInfoIdx,
2273
2274 pub call_info: Vec<CallInfo>,
2277
2278 pub openupval: Vec<GcRef<UpVal>>,
2282
2283 pub legacy_open_upval_touched: std::cell::Cell<bool>,
2295
2296 pub tbclist: Vec<StackIdx>,
2298
2299 pub(crate) global: Rc<RefCell<GlobalState>>,
2304
2305 pub hook: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>,
2309
2310 pub hookmask: u8,
2312
2313 pub basehookcount: i32,
2315
2316 pub hookcount: i32,
2318
2319 pub errfunc: isize,
2326
2327 pub n_ccalls: u32,
2331
2332 pub oldpc: u32,
2336
2337 pub marked: u8,
2341
2342 pub cached_thread_id: u64,
2358
2359 pub gc_check_needed: bool,
2364}
2365
2366impl LuaState {
2367 pub fn global(&self) -> std::cell::Ref<'_, GlobalState> {
2375 self.global.borrow()
2376 }
2377
2378 pub fn global_mut(&self) -> std::cell::RefMut<'_, GlobalState> {
2382 self.global.borrow_mut()
2383 }
2384
2385 pub fn global_rc(&self) -> Rc<RefCell<GlobalState>> {
2389 Rc::clone(&self.global)
2390 }
2391
2392 pub fn v51_thread_lgt(&self, id: u64) -> LuaValue {
2401 let g = self.global();
2402 if id == g.main_thread_id {
2403 g.globals.clone()
2404 } else {
2405 g.thread_globals
2406 .get(&id)
2407 .expect("v51 coroutine l_gt must be seeded in new_thread")
2408 .clone()
2409 }
2410 }
2411
2412 pub fn v51_set_thread_lgt(&self, id: u64, t: LuaValue) {
2417 let mut g = self.global_mut();
2418 if id == g.main_thread_id {
2419 g.globals = t;
2420 } else {
2421 g.thread_globals.insert(id, t);
2422 }
2423 }
2424
2425 pub fn enable_test_warning_handler(&mut self) -> Result<(), LuaError> {
2427 {
2428 let mut g = self.global_mut();
2429 g.test_warn_enabled = true;
2430 g.test_warn_on = false;
2431 g.test_warn_mode = TestWarnMode::Normal;
2432 g.test_warn_last_to_cont = false;
2433 g.test_warn_buffer.clear();
2434 }
2435 self.push(LuaValue::Bool(false));
2436 crate::api::set_global(self, b"_WARN")
2437 }
2438
2439 pub fn c_calls(&self) -> u32 {
2443 self.n_ccalls & 0xffff
2444 }
2445
2446 pub fn inc_nny(&mut self) {
2450 self.n_ccalls += 0x10000;
2451 }
2452
2453 pub fn dec_nny(&mut self) {
2457 self.n_ccalls -= 0x10000;
2458 }
2459
2460 pub fn is_yieldable(&self) -> bool {
2464 (self.n_ccalls & 0xffff0000) == 0
2465 }
2466
2467 pub fn reset_hook_count(&mut self) {
2471 self.hookcount = self.basehookcount;
2472 }
2473
2474 pub fn install_sandbox_limits(
2483 &mut self,
2484 interval: i32,
2485 instr_limit: Option<u64>,
2486 mem_limit: Option<usize>,
2487 ) {
2488 let interval = interval.max(1);
2489 {
2490 let g = self.global();
2491 g.sandbox.interval.set(interval);
2492 g.sandbox.instr_limited.set(instr_limit.is_some());
2493 g.sandbox.instr_remaining.set(instr_limit.unwrap_or(0));
2494 g.sandbox.instr_limit.set(instr_limit.unwrap_or(0));
2495 g.sandbox.mem_limit.set(mem_limit);
2496 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2497 }
2498 self.hookmask |= SANDBOX_COUNT_MASK;
2499 self.basehookcount = interval;
2500 self.hookcount = interval;
2501 crate::debug::arm_traps(self);
2502 }
2503
2504 pub fn sandbox_charge_interval(&self) -> Option<LuaError> {
2509 let interval = self.global().sandbox.interval.get();
2510 self.sandbox_charge(interval as u64)
2511 }
2512
2513 pub fn sandbox_charge(&self, amount: u64) -> Option<LuaError> {
2522 let g = self.global();
2523 if g.sandbox.interval.get() == 0 {
2524 return None;
2525 }
2526 if g.sandbox.instr_limited.get() {
2527 let rem = g.sandbox.instr_remaining.get().saturating_sub(amount);
2528 g.sandbox.instr_remaining.set(rem);
2529 if rem == 0 {
2530 g.sandbox.tripped.set(SANDBOX_TRIP_INSTRUCTIONS);
2531 g.sandbox.aborting.set(true);
2532 return Some(LuaError::runtime(format_args!(
2533 "sandbox: instruction budget exhausted"
2534 )));
2535 }
2536 }
2537 if let Some(limit) = g.sandbox.mem_limit.get() {
2538 if g.total_bytes() > limit {
2539 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2540 g.sandbox.aborting.set(true);
2541 return Some(LuaError::runtime(format_args!(
2542 "sandbox: memory limit exceeded"
2543 )));
2544 }
2545 }
2546 None
2547 }
2548
2549 pub fn sandbox_reserve(&self, additional: usize) -> Option<LuaError> {
2557 let g = self.global();
2558 if g.sandbox.interval.get() == 0 {
2559 return None;
2560 }
2561 if let Some(limit) = g.sandbox.mem_limit.get() {
2562 let projected = g.total_bytes().saturating_add(additional);
2563 if projected > limit {
2564 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2565 g.sandbox.aborting.set(true);
2566 return Some(LuaError::runtime(format_args!(
2567 "sandbox: memory limit exceeded"
2568 )));
2569 }
2570 }
2571 None
2572 }
2573
2574 pub fn sandbox_match_step_limit(&self) -> u64 {
2579 let g = self.global();
2580 if g.sandbox.interval.get() != 0 && g.sandbox.instr_limited.get() {
2581 g.sandbox.instr_remaining.get()
2582 } else {
2583 0
2584 }
2585 }
2586
2587 pub fn sandbox_aborting(&self) -> bool {
2591 self.global().sandbox.aborting.get()
2592 }
2593
2594 pub fn sandbox_instr_limited(&self) -> bool {
2596 self.global().sandbox.instr_limited.get()
2597 }
2598
2599 pub fn sandbox_instr_remaining(&self) -> u64 {
2602 self.global().sandbox.instr_remaining.get()
2603 }
2604
2605 pub fn sandbox_instr_limit(&self) -> u64 {
2607 self.global().sandbox.instr_limit.get()
2608 }
2609
2610 pub fn sandbox_tripped_code(&self) -> u8 {
2612 self.global().sandbox.tripped.get()
2613 }
2614
2615 pub fn sandbox_reset(&self) {
2618 let g = self.global();
2619 if g.sandbox.instr_limited.get() {
2620 g.sandbox.instr_remaining.set(g.sandbox.instr_limit.get());
2621 }
2622 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2623 g.sandbox.aborting.set(false);
2624 }
2625
2626 pub fn stack_size(&self) -> usize {
2630 self.stack_last.0 as usize
2631 }
2632
2633 #[inline(always)]
2637 pub fn push(&mut self, val: LuaValue) {
2638 let top = self.top.0 as usize;
2639 if top < self.stack.len() {
2640 self.stack[top] = StackValue { val };
2641 } else {
2642 self.stack.push(StackValue { val });
2643 }
2644 self.top = StackIdx(self.top.0 + 1);
2645 }
2646
2647 #[inline(always)]
2650 pub fn pop(&mut self) -> LuaValue {
2651 if self.top.0 == 0 {
2652 return LuaValue::Nil;
2653 }
2654 self.top = StackIdx(self.top.0 - 1);
2655 self.stack[self.top.0 as usize].val.clone()
2656 }
2657
2658 #[inline(always)]
2662 pub fn stack_val(&self, idx: StackIdx) -> &LuaValue {
2663 &self.stack[idx.0 as usize].val
2664 }
2665
2666 #[inline(always)]
2668 pub fn set_stack_val(&mut self, idx: StackIdx, val: LuaValue) {
2669 self.stack[idx.0 as usize].val = val;
2670 }
2671
2672 pub fn gc(&mut self) -> GcHandle<'_> {
2679 GcHandle { _state: self }
2680 }
2681
2682 pub fn external_root_value(&mut self, value: LuaValue) -> ExternalRootKey {
2684 self.global_mut().external_roots.insert(value)
2685 }
2686
2687 pub fn external_rooted_value(&self, key: ExternalRootKey) -> Option<LuaValue> {
2689 self.global().external_roots.get(key).cloned()
2690 }
2691
2692 pub fn external_replace_root(
2694 &mut self,
2695 key: ExternalRootKey,
2696 value: LuaValue,
2697 ) -> Option<LuaValue> {
2698 self.global_mut().external_roots.replace(key, value)
2699 }
2700
2701 pub fn external_unroot_value(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
2703 self.global_mut().external_roots.remove(key)
2704 }
2705
2706 pub fn try_external_unroot_value(
2709 &mut self,
2710 key: ExternalRootKey,
2711 ) -> std::result::Result<Option<LuaValue>, std::cell::BorrowMutError> {
2712 self.global
2713 .try_borrow_mut()
2714 .map(|mut global| global.external_roots.remove(key))
2715 }
2716
2717 pub fn new_table(&mut self) -> GcRef<LuaTable> {
2721 self.mark_gc_check_needed();
2723 GcRef::new(LuaTable::placeholder())
2724 }
2725
2726 pub fn new_table_with_sizes(
2731 &mut self,
2732 array_size: u32,
2733 hash_size: u32,
2734 ) -> Result<GcRef<LuaTable>, LuaError> {
2735 self.mark_gc_check_needed();
2736 let t = GcRef::new(LuaTable::placeholder());
2737 self.table_resize(&t, array_size as usize, hash_size as usize)?;
2738 Ok(t)
2739 }
2740
2741 pub fn intern_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
2756 if bytes.len() <= crate::string::MAX_SHORT_LEN {
2757 let hash = LuaString::hash_bytes(bytes, 0);
2758 {
2759 let global = self.global();
2760 if let Some(existing) = global.interned_lt.find(bytes, hash) {
2761 return Ok(existing);
2762 }
2763 }
2764 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2765 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2766 self.global_mut().interned_lt.insert(new_ref.clone());
2767 self.mark_gc_check_needed();
2768 Ok(new_ref)
2769 } else {
2770 self.mark_gc_check_needed();
2771 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2772 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2773 Ok(new_ref)
2774 }
2775 }
2776
2777 #[inline(always)]
2779 pub fn top_idx(&self) -> StackIdx {
2780 self.top
2781 }
2782}
2783
2784impl LuaState {
2793 #[inline(always)]
2794 pub fn get_at(&self, idx: impl Into<StackIdxConv>) -> LuaValue {
2795 let i: StackIdx = idx.into().0;
2796 match self.stack.get(i.0 as usize) {
2797 Some(slot) => slot.val.clone(),
2798 None => LuaValue::Nil,
2799 }
2800 }
2801 #[inline(always)]
2802 pub fn set_at(&mut self, idx: impl Into<StackIdxConv>, v: LuaValue) {
2803 let i: StackIdx = idx.into().0;
2804 self.stack[i.0 as usize].val = v;
2805 }
2806
2807 pub fn clear_stack_range(&mut self, start: StackIdx, end: StackIdx) {
2821 if end.0 <= start.0 {
2822 return;
2823 }
2824 let end_u = end.0 as usize;
2825 if end_u > self.stack.len() {
2826 self.stack.resize_with(end_u, StackValue::default);
2827 }
2828 for i in start.0..end.0 {
2829 self.stack[i as usize].val = LuaValue::Nil;
2830 }
2831 }
2832 #[inline(always)]
2840 pub fn get_int_at(&self, idx: impl Into<StackIdxConv>) -> Option<i64> {
2841 let i: StackIdx = idx.into().0;
2842 match self.stack.get(i.0 as usize) {
2843 Some(slot) => match &slot.val {
2844 LuaValue::Int(v) => Some(*v),
2845 _ => None,
2846 },
2847 None => None,
2848 }
2849 }
2850 #[inline(always)]
2857 pub fn get_int_pair_at(
2858 &self,
2859 rb: impl Into<StackIdxConv>,
2860 rc: impl Into<StackIdxConv>,
2861 ) -> Option<(i64, i64)> {
2862 let rb: StackIdx = rb.into().0;
2863 let rc: StackIdx = rc.into().0;
2864 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2865 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib, ic)),
2866 _ => None,
2867 }
2868 }
2869 #[inline(always)]
2874 pub fn get_num_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2875 let i: StackIdx = idx.into().0;
2876 match self.stack.get(i.0 as usize) {
2877 Some(slot) => match &slot.val {
2878 LuaValue::Float(f) => Some(*f),
2879 LuaValue::Int(v) => Some(*v as f64),
2880 _ => None,
2881 },
2882 None => None,
2883 }
2884 }
2885 #[inline(always)]
2890 pub fn get_float_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2891 let i: StackIdx = idx.into().0;
2892 match self.stack.get(i.0 as usize) {
2893 Some(slot) => match &slot.val {
2894 LuaValue::Float(f) => Some(*f),
2895 _ => None,
2896 },
2897 None => None,
2898 }
2899 }
2900 #[inline(always)]
2905 pub fn get_num_pair_at(
2906 &self,
2907 rb: impl Into<StackIdxConv>,
2908 rc: impl Into<StackIdxConv>,
2909 ) -> Option<(f64, f64)> {
2910 let rb: StackIdx = rb.into().0;
2911 let rc: StackIdx = rc.into().0;
2912 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2913 (LuaValue::Float(nb), LuaValue::Float(nc)) => Some((nb, nc)),
2914 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib as f64, ic as f64)),
2915 (LuaValue::Int(ib), LuaValue::Float(nc)) => Some((ib as f64, nc)),
2916 (LuaValue::Float(nb), LuaValue::Int(ic)) => Some((nb, ic as f64)),
2917 _ => None,
2918 }
2919 }
2920 #[inline(always)]
2933 pub fn set_top(&mut self, idx: impl Into<StackIdxConv>) {
2934 let new_top: StackIdx = idx.into().0;
2935 let new_top_u = new_top.0 as usize;
2936 if new_top_u > self.stack.len() {
2937 self.stack.resize_with(new_top_u, StackValue::default);
2938 }
2939 self.top = new_top;
2940 }
2941 #[inline(always)]
2947 pub fn set_top_idx(&mut self, idx: impl Into<StackIdxConv>) {
2948 let new_top: StackIdx = idx.into().0;
2949 self.top = new_top;
2950 }
2951 #[inline(always)]
2954 pub fn dec_top(&mut self) {
2955 if self.top.0 > 0 {
2956 self.top = StackIdx(self.top.0 - 1);
2957 }
2958 }
2959 #[inline(always)]
2960 pub fn pop_n(&mut self, n: usize) {
2961 let cur = self.top.0 as usize;
2962 let new = cur.saturating_sub(n);
2963 self.top = StackIdx(new as u32);
2964 }
2965 #[inline(always)]
2968 pub fn peek_at(&mut self, idx: impl Into<StackIdxConv>) -> LuaValue {
2969 let i: StackIdx = idx.into().0;
2970 match self.stack.get(i.0 as usize) {
2971 Some(slot) => slot.val.clone(),
2972 None => LuaValue::Nil,
2973 }
2974 }
2975 #[inline(always)]
2979 pub fn peek_top(&mut self) -> LuaValue {
2980 if self.top.0 == 0 {
2981 return LuaValue::Nil;
2982 }
2983 self.stack[(self.top.0 - 1) as usize].val.clone()
2984 }
2985 pub fn peek_string_at_top(&mut self) -> GcRef<LuaString> {
2990 match self.peek_top() {
2991 LuaValue::Str(s) => s,
2992 _ => panic!("peek_string_at_top: top of stack is not a string"),
2993 }
2994 }
2995 pub fn stack_at(&mut self, idx: impl Into<StackIdxConv>) -> &mut LuaValue {
2998 let i: StackIdx = idx.into().0;
2999 &mut self.stack[i.0 as usize].val
3000 }
3001 pub fn stack_set_nil(&mut self, idx: impl Into<StackIdxConv>) {
3004 let i: StackIdx = idx.into().0;
3005 let slot = i.0 as usize;
3006 if slot < self.stack.len() {
3007 self.stack[slot].val = LuaValue::Nil;
3008 }
3009 }
3010 pub fn stack_resize(&mut self, size: usize) -> Result<(), LuaError> {
3018 self.stack.resize_with(size, StackValue::default);
3019 Ok(())
3020 }
3021 pub fn stack_available(&mut self) -> usize {
3022 (self.stack_last.0 as usize).saturating_sub(self.top.0 as usize)
3023 }
3024 pub fn check_stack(&mut self, n: i32) -> Result<(), LuaError> {
3025 let free = (self.stack_last.0 as i32) - (self.top.0 as i32);
3026 if free <= n {
3027 self.grow_stack(n, true)?;
3028 }
3029 Ok(())
3030 }
3031 pub fn grow_stack(&mut self, n: i32, raise_error: bool) -> Result<(), LuaError> {
3040 crate::do_::grow_stack(self, n, raise_error).map(|_| ())
3041 }
3042
3043 #[inline(always)]
3044 pub fn get_ci(&self, idx: CallInfoIdx) -> &CallInfo {
3045 &self.call_info[idx.as_usize()]
3046 }
3047 #[inline(always)]
3048 pub fn get_ci_mut(&mut self, idx: CallInfoIdx) -> &mut CallInfo {
3049 &mut self.call_info[idx.as_usize()]
3050 }
3051 #[inline(always)]
3052 pub fn current_call_info(&self) -> &CallInfo {
3053 &self.call_info[self.ci.as_usize()]
3054 }
3055 #[inline(always)]
3056 pub fn current_call_info_mut(&mut self) -> &mut CallInfo {
3057 let i = self.ci.as_usize();
3058 &mut self.call_info[i]
3059 }
3060 #[inline(always)]
3061 pub fn current_ci_idx(&self) -> CallInfoIdx {
3062 self.ci
3063 }
3064 pub fn call_stack_mut(&mut self) -> &mut Vec<CallInfo> {
3065 &mut self.call_info
3066 }
3067 #[inline(always)]
3068 pub fn next_ci(&mut self) -> Result<CallInfoIdx, LuaError> {
3069 let idx = match self.call_info[self.ci.as_usize()].next {
3070 Some(idx) => idx,
3071 None => extend_ci(self),
3072 };
3073 self.call_info[idx.as_usize()].tailcalls = 0;
3074 Ok(idx)
3075 }
3076
3077 #[inline(always)]
3084 pub fn note_lua_tailcall(&mut self, ci: CallInfoIdx) {
3085 if self.global().lua_version == lua_types::LuaVersion::V51 {
3086 self.call_info[ci.as_usize()].tailcalls =
3087 self.call_info[ci.as_usize()].tailcalls.saturating_add(1);
3088 }
3089 }
3090 #[inline(always)]
3091 pub fn prev_ci(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
3092 self.call_info[idx.as_usize()].previous
3093 }
3094 pub fn get_prev_ci(&self, idx: CallInfoIdx) -> Option<&CallInfo> {
3095 self.call_info[idx.as_usize()]
3096 .previous
3097 .map(|p| &self.call_info[p.as_usize()])
3098 }
3099 #[inline(always)]
3100 pub fn is_base_ci(&self, idx: CallInfoIdx) -> bool {
3101 idx.as_usize() == 0
3102 }
3103 #[inline(always)]
3104 pub fn is_current_ci(&self, idx: CallInfoIdx) -> bool {
3105 idx == self.ci
3106 }
3107 pub fn ci_next_func(&self, idx: CallInfoIdx) -> StackIdx {
3108 let next = self.call_info[idx.as_usize()]
3109 .next
3110 .expect("ci_next_func: no next CallInfo");
3111 self.call_info[next.as_usize()].func
3112 }
3113 #[inline(always)]
3114 pub fn ci_top(&self, idx: CallInfoIdx) -> StackIdx {
3115 self.call_info[idx.as_usize()].top
3116 }
3117 #[inline(always)]
3122 pub fn ci_trap(&mut self, idx: CallInfoIdx) -> bool {
3123 self.call_info[idx.as_usize()].trap()
3124 }
3125 #[inline(always)]
3126 pub fn ci_savedpc(&self, idx: CallInfoIdx) -> u32 {
3127 self.call_info[idx.as_usize()].saved_pc()
3128 }
3129 #[inline(always)]
3130 pub fn set_ci_savedpc(&mut self, idx: CallInfoIdx, pc: u32) {
3131 self.call_info[idx.as_usize()].set_saved_pc(pc);
3132 }
3133 #[inline(always)]
3134 pub fn set_ci_previous(&mut self, idx: CallInfoIdx) {
3135 self.ci = self.call_info[idx.as_usize()]
3136 .previous
3137 .expect("set_ci_previous: returning frame has no previous CallInfo");
3138 }
3139 #[inline(always)]
3140 pub fn ci_previous(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
3141 self.call_info[idx.as_usize()].previous
3142 }
3143 #[inline(always)]
3144 pub fn ci_adjust_func(&mut self, idx: CallInfoIdx, delta: i32) {
3145 let ci = &mut self.call_info[idx.as_usize()];
3146 ci.func = StackIdx((ci.func.0 as i32 - delta) as u32);
3147 }
3148 #[inline(always)]
3149 pub fn ci_base(&self, idx: CallInfoIdx) -> StackIdx {
3150 self.call_info[idx.as_usize()].func + 1
3151 }
3152 #[inline(always)]
3153 pub fn ci_is_fresh(&self, idx: CallInfoIdx) -> bool {
3154 (self.call_info[idx.as_usize()].callstatus & CIST_FRESH) != 0
3155 }
3156 #[inline(always)]
3157 pub fn ci_lua_closure(
3158 &self,
3159 idx: CallInfoIdx,
3160 ) -> Option<GcRef<lua_types::closure::LuaLClosure>> {
3161 let func_idx = self.call_info[idx.as_usize()].func;
3162 match self.stack.get(func_idx.0 as usize).map(|slot| slot.val) {
3163 Some(LuaValue::Function(lua_types::closure::LuaClosure::Lua(cl))) => Some(cl),
3164 _ => None,
3165 }
3166 }
3167 #[inline(always)]
3168 pub fn ci_nextraargs(&self, idx: CallInfoIdx) -> i32 {
3169 self.call_info[idx.as_usize()].nextra_args()
3170 }
3171 #[inline(always)]
3172 pub fn ci_nres(&self, idx: CallInfoIdx) -> i32 {
3173 self.call_info[idx.as_usize()].u2.value
3174 }
3175 #[inline(always)]
3176 pub fn ci_nres_set(&mut self, idx: CallInfoIdx, n: i32) {
3177 self.call_info[idx.as_usize()].u2.value = n;
3178 }
3179 #[inline(always)]
3180 pub fn ci_nresults(&self, idx: CallInfoIdx) -> i32 {
3181 self.call_info[idx.as_usize()].nresults as i32
3182 }
3183 pub fn ci_prev_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3184 let pc = self.call_info[idx.as_usize()].saved_pc();
3185 let cl = self
3186 .ci_lua_closure(idx)
3187 .expect("ci_prev_instruction: CallInfo does not hold a Lua closure");
3188 cl.proto.code[(pc - 1) as usize]
3189 }
3190 pub fn ci_prev2_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3191 let pc = self.call_info[idx.as_usize()].saved_pc();
3192 let cl = self
3193 .ci_lua_closure(idx)
3194 .expect("ci_prev2_instruction: CallInfo does not hold a Lua closure");
3195 cl.proto.code[(pc - 2) as usize]
3196 }
3197 pub fn ci_skip_next_instruction(&mut self, idx: CallInfoIdx) {
3198 let pc = self.call_info[idx.as_usize()].saved_pc();
3199 self.call_info[idx.as_usize()].set_saved_pc(pc + 1);
3200 }
3201 pub fn ci_step_pc_back(&mut self, idx: CallInfoIdx) {
3202 let pc = self.call_info[idx.as_usize()].saved_pc();
3203 self.call_info[idx.as_usize()].set_saved_pc(pc - 1);
3204 }
3205 pub fn get_ci_pcrel(&mut self, idx: CallInfoIdx) -> u32 {
3206 self.call_info[idx.as_usize()].saved_pc().saturating_sub(1)
3207 }
3208 pub fn get_ci_u2_funcidx(&mut self, idx: CallInfoIdx) -> i32 {
3209 self.call_info[idx.as_usize()].u2.value
3210 }
3211 pub fn get_ci_u2_nres(&mut self, idx: CallInfoIdx) -> i32 {
3212 self.call_info[idx.as_usize()].u2.value
3213 }
3214 pub fn get_ci_u2_nyield(&mut self, idx: CallInfoIdx) -> i32 {
3215 self.call_info[idx.as_usize()].u2.value
3216 }
3217 pub fn get_ci_vararg_info(&mut self, idx: CallInfoIdx) -> (bool, i32, i32) {
3218 let nextraargs = self.call_info[idx.as_usize()].nextra_args();
3219 match self.ci_lua_closure(idx) {
3220 Some(cl) => (cl.proto.is_vararg, nextraargs, cl.proto.numparams as i32),
3221 None => (false, nextraargs, 0),
3222 }
3223 }
3224 pub fn get_ci_lua_proto_numparams(&mut self, idx: CallInfoIdx) -> u8 {
3225 self.ci_lua_closure(idx)
3226 .map(|cl| cl.proto.numparams)
3227 .unwrap_or(0)
3228 }
3229 pub fn set_ci_u2_nres(&mut self, idx: CallInfoIdx, n: i32) {
3230 self.call_info[idx.as_usize()].u2.value = n;
3231 }
3232 pub fn set_ci_u2_nyield(&mut self, idx: CallInfoIdx, n: i32) {
3233 self.call_info[idx.as_usize()].u2.value = n;
3234 }
3235 pub fn set_ci_transfer_info(&mut self, idx: CallInfoIdx, ftransfer: u16, ntransfer: u16) {
3236 let ci = &mut self.call_info[idx.as_usize()];
3237 ci.u2.ftransfer = ftransfer;
3238 ci.u2.ntransfer = ntransfer;
3239 }
3240 pub fn shrink_ci(&mut self) {
3241 shrink_ci(self)
3242 }
3243 pub fn check_c_stack(&mut self) -> Result<(), LuaError> {
3244 check_c_stack(self)
3245 }
3246
3247 pub fn status(&mut self) -> LuaStatus {
3248 LuaStatus::from_raw(self.status as i32)
3249 }
3250 pub fn errfunc(&mut self) -> isize {
3251 self.errfunc
3252 }
3253 pub fn old_pc(&mut self) -> u32 {
3254 self.oldpc
3255 }
3256 pub fn set_old_pc(&mut self, pc: u32) {
3257 self.oldpc = pc;
3258 }
3259 pub fn set_oldpc(&mut self, pc: u32) {
3260 self.oldpc = pc;
3261 }
3262 pub fn _hook_call_noargs(&mut self) {}
3263 pub fn hook(&self) -> Option<&Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>> {
3264 self.hook.as_ref()
3265 }
3266 pub fn has_hook(&mut self) -> bool {
3267 self.hook.is_some()
3268 }
3269 pub fn hook_count(&mut self) -> i32 {
3270 self.hookcount
3271 }
3272 pub fn set_hook_count(&mut self, n: i32) {
3273 self.hookcount = n;
3274 }
3275 pub fn hook_mask(&self) -> u8 {
3276 self.hookmask
3277 }
3278 pub fn set_hook_mask(&mut self, m: u8) {
3279 self.hookmask = m;
3280 }
3281 pub fn base_hook_count(&self) -> i32 {
3282 self.basehookcount
3283 }
3284 pub fn set_base_hook_count(&mut self, n: i32) {
3285 self.basehookcount = n;
3286 }
3287 pub fn set_hook(&mut self, h: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>) {
3288 self.hook = h;
3289 }
3290 pub fn call_hook_event(&mut self, event: i32, line: i32) -> Result<(), LuaError> {
3309 let saved_ci = self.ci;
3310 let r = crate::do_::hook(self, event, line, 0, 0);
3311 self.ci = saved_ci;
3312 r
3313 }
3314
3315 pub fn registry_value(&self) -> LuaValue {
3316 self.global().l_registry.clone()
3317 }
3318 pub fn registry_get(&self, key: usize) -> LuaValue {
3319 let reg = self.global().l_registry.clone();
3320 match reg {
3321 LuaValue::Table(t) => t.get(&LuaValue::Int(key as i64)),
3322 _ => LuaValue::Nil,
3323 }
3324 }
3325
3326 pub fn new_string(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3327 self.intern_or_create_str(bytes)
3328 }
3329
3330 pub fn new_proto(&mut self) -> GcRef<LuaProto> {
3342 self.mark_gc_check_needed();
3343 GcRef::new(LuaProto::placeholder())
3344 }
3345
3346 pub fn new_lclosure(&mut self, proto: GcRef<LuaProto>, nupvals: usize) -> GcRef<LuaClosureLua> {
3348 self.mark_gc_check_needed();
3349 let mut upvals = Vec::with_capacity(nupvals);
3350 for _ in 0..nupvals {
3351 upvals.push(std::cell::Cell::new(self.new_upval_closed(LuaValue::Nil)));
3352 }
3353 let upvals = upvals.into_boxed_slice();
3354 let closure = GcRef::new(LuaClosureLua { proto, upvals });
3355 closure.account_buffer(closure.buffer_bytes() as isize);
3356 closure
3357 }
3358
3359 pub fn new_upval_closed(&mut self, v: LuaValue) -> GcRef<UpVal> {
3361 self.mark_gc_check_needed();
3362 GcRef::new(UpVal::closed(v))
3363 }
3364
3365 pub fn new_upval_open(&mut self, thread_id: usize, level: StackIdx) -> GcRef<UpVal> {
3367 self.mark_gc_check_needed();
3368 self.legacy_open_upval_touched.set(true);
3369 GcRef::new(UpVal::open(thread_id, level))
3370 }
3371 pub fn intern_or_create_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3378 self.intern_str(bytes)
3379 }
3380 pub fn new_userdata(
3381 &mut self,
3382 _size: usize,
3383 _nuvalue: usize,
3384 ) -> Result<GcRef<LuaUserData>, LuaError> {
3385 Err(LuaError::runtime(format_args!(
3386 "new_userdata not implemented in this Phase-B build; use new_userdata_typed instead"
3387 )))
3388 }
3389 pub fn new_c_closure(&mut self, _f: LuaCFunction, _n: i32) -> Result<LuaClosure, LuaError> {
3390 Err(LuaError::runtime(format_args!("new_c_closure not implemented in this Phase-B build; use push_cclosure in lua_vm::api instead")))
3391 }
3392 pub fn push_closure(
3393 &mut self,
3394 proto_idx: usize,
3395 ci: CallInfoIdx,
3396 base: StackIdx,
3397 ra: StackIdx,
3398 ) -> Result<(), LuaError> {
3399 let parent_cl = self
3400 .ci_lua_closure(ci)
3401 .expect("push_closure: current frame is not a Lua closure");
3402 let child_proto = parent_cl.proto.p[proto_idx].clone();
3403 let nup = child_proto.upvalues.len();
3404 let mut upvals: Vec<std::cell::Cell<GcRef<UpVal>>> = Vec::with_capacity(nup);
3405 for i in 0..nup {
3406 let desc = &child_proto.upvalues[i];
3407 let uv = if desc.instack {
3408 let level = base + desc.idx as i32;
3409 crate::func::find_upval(self, level)
3410 } else {
3411 parent_cl.upval(desc.idx as usize)
3412 };
3413 upvals.push(std::cell::Cell::new(uv));
3414 }
3415 let cache_enabled = matches!(
3419 self.global().lua_version,
3420 lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
3421 );
3422 if cache_enabled {
3423 if let Some(cached) = child_proto.cache.borrow().as_ref() {
3424 if cached.upvals.len() == nup
3425 && (0..nup).all(|i| GcRef::ptr_eq(&cached.upvals[i].get(), &upvals[i].get()))
3426 {
3427 let reused = cached.clone();
3428 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(reused)));
3429 return Ok(());
3430 }
3431 }
3432 }
3433 self.mark_gc_check_needed();
3436 let new_cl = GcRef::new(LuaClosureLua {
3437 proto: child_proto.clone(),
3438 upvals: upvals.into_boxed_slice(),
3439 });
3440 new_cl.account_buffer(new_cl.buffer_bytes() as isize);
3441 if cache_enabled {
3442 *child_proto.cache.borrow_mut() = Some(new_cl.clone());
3443 }
3444 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(new_cl)));
3445 Ok(())
3446 }
3447 pub fn new_tbc_upval(&mut self, idx: StackIdx) -> Result<(), LuaError> {
3448 crate::func::new_tbc_upval(self, idx)
3449 }
3450
3451 #[inline(always)]
3472 pub fn upvalue_get(&self, cl: &GcRef<LuaClosureLua>, n: usize) -> LuaValue {
3473 let uv = cl.upval(n);
3474 let (thread_id, idx) = match uv.try_open_payload() {
3475 Some(p) => p,
3476 None => return uv.closed_value(),
3477 };
3478 let current = self.cached_thread_id;
3479 let tid = thread_id as u64;
3480 if tid == current {
3481 return self.stack[idx.0 as usize].val;
3482 }
3483 self.upvalue_get_cross_thread(tid, idx)
3484 }
3485
3486 #[cold]
3487 #[inline(never)]
3488 fn upvalue_get_cross_thread(&self, tid: u64, idx: StackIdx) -> LuaValue {
3489 let entry_rc = {
3490 let g = self.global();
3491 g.threads.get(&tid).map(|e| e.state.clone())
3492 };
3493 if let Some(rc) = entry_rc {
3494 if let Ok(home_state) = rc.try_borrow() {
3495 return home_state.get_at(idx);
3496 }
3497 }
3498 let g = self.global();
3499 match g.cross_thread_upvals.get(&(tid, idx)) {
3500 Some(v) => *v,
3501 None => LuaValue::Nil,
3502 }
3503 }
3504 #[inline(always)]
3513 pub fn upvalue_set(
3514 &mut self,
3515 cl: &GcRef<LuaClosureLua>,
3516 n: usize,
3517 val: LuaValue,
3518 ) -> Result<(), LuaError> {
3519 let uv = cl.upval(n);
3520 match uv.try_open_payload() {
3521 Some((thread_id, idx)) => {
3522 let tid = thread_id as u64;
3523 let current = self.cached_thread_id;
3524 if tid == current {
3525 self.stack[idx.0 as usize].val = val;
3526 } else {
3527 self.upvalue_set_cross_thread(tid, idx, val)?;
3528 }
3529 }
3530 None => {
3531 uv.set_closed_value(val);
3532 }
3533 }
3534 if val.is_collectable() {
3535 self.gc_barrier_upval(&uv, &val);
3536 }
3537 Ok(())
3538 }
3539
3540 #[cold]
3541 #[inline(never)]
3542 fn upvalue_set_cross_thread(
3543 &mut self,
3544 tid: u64,
3545 idx: StackIdx,
3546 val: LuaValue,
3547 ) -> Result<(), LuaError> {
3548 let entry_rc = {
3549 let g = self.global();
3550 g.threads.get(&tid).map(|e| e.state.clone())
3551 };
3552 if let Some(rc) = entry_rc {
3553 if let Ok(mut home_state) = rc.try_borrow_mut() {
3554 home_state.set_at(idx, val);
3555 return Ok(());
3556 }
3557 }
3558 let mut g = self.global_mut();
3559 g.cross_thread_upvals.insert((tid, idx), val);
3560 Ok(())
3561 }
3562
3563 pub fn protected_call_raw(
3564 &mut self,
3565 func: StackIdx,
3566 nresults: i32,
3567 errfunc: StackIdx,
3568 ) -> Result<(), LuaError> {
3569 let ef = errfunc.0 as isize;
3570 let status = crate::do_::pcall(self, |s| s.call_no_yield(func, nresults), func, ef);
3571 match status {
3572 LuaStatus::Ok => Ok(()),
3573 LuaStatus::ErrSyntax => {
3574 let err_val = self.get_at(func);
3575 self.set_top(func);
3576 Err(LuaError::Syntax(err_val))
3577 }
3578 LuaStatus::Yield => {
3579 self.set_top(func);
3580 Err(LuaError::Yield)
3581 }
3582 _ => {
3583 let err_val = self.get_at(func);
3584 self.set_top(func);
3585 Err(LuaError::Runtime(err_val))
3586 }
3587 }
3588 }
3589 pub fn protected_parser(
3590 &mut self,
3591 z: crate::zio::ZIO,
3592 name: &[u8],
3593 mode: Option<&[u8]>,
3594 ) -> LuaStatus {
3595 crate::do_::protected_parser(self, z, name, mode)
3596 }
3597 pub fn do_call(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3598 crate::do_::call(self, func, nresults)
3599 }
3600 pub fn do_call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3601 crate::do_::callnoyield(self, func, nresults)
3602 }
3603 pub fn call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3604 crate::do_::callnoyield(self, func, nresults)
3605 }
3606 pub fn call_at(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3607 crate::do_::call(self, func, nresults)
3608 }
3609 #[inline(always)]
3610 pub fn call_known_c_at(&mut self, func: StackIdx, nresults: i32) -> Result<bool, LuaError> {
3611 crate::do_::call_known_c(self, func, nresults)
3612 }
3613 #[inline(always)]
3614 pub fn precall(
3615 &mut self,
3616 func: StackIdx,
3617 nresults: i32,
3618 ) -> Result<Option<CallInfoIdx>, LuaError> {
3619 crate::do_::precall(self, func, nresults)
3620 }
3621 #[inline(always)]
3622 pub fn pretailcall(
3623 &mut self,
3624 ci: CallInfoIdx,
3625 func: StackIdx,
3626 narg1: i32,
3627 delta: i32,
3628 ) -> Result<i32, LuaError> {
3629 crate::do_::pretailcall(self, ci, func, narg1, delta)
3630 }
3631 #[inline(always)]
3632 pub fn poscall<N: TryInto<i32>>(&mut self, ci: CallInfoIdx, nres: N) -> Result<(), LuaError>
3633 where
3634 <N as TryInto<i32>>::Error: std::fmt::Debug,
3635 {
3636 let n = nres.try_into().expect("poscall: nres out of i32 range");
3637 crate::do_::poscall(self, ci, n)
3638 }
3639 pub fn adjust_results(&mut self, nresults: i32) {
3640 const LUA_MULTRET: i32 = -1;
3641 if nresults <= LUA_MULTRET {
3642 let ci_idx = self.ci.as_usize();
3643 if self.call_info[ci_idx].top.0 < self.top.0 {
3644 self.call_info[ci_idx].top = self.top;
3645 }
3646 }
3647 }
3648 pub fn adjust_varargs(
3649 &mut self,
3650 ci: CallInfoIdx,
3651 nfixparams: i32,
3652 cl: &GcRef<lua_types::closure::LuaLClosure>,
3653 ) -> Result<(), LuaError> {
3654 crate::tagmethods::adjust_varargs(self, nfixparams, ci, &cl.0.proto)
3655 }
3656 pub fn get_varargs(&mut self, ci: CallInfoIdx, ra: StackIdx, n: i32) -> Result<i32, LuaError> {
3657 crate::tagmethods::get_varargs(self, ci, ra, n)?;
3658 Ok(0)
3659 }
3660
3661 pub fn close_upvals(&mut self, level: StackIdx) -> Result<(), LuaError> {
3662 crate::func::close_upval(self, level);
3663 Ok(())
3664 }
3665 pub fn close_upvals_status(&mut self, level: StackIdx, _status: i32) -> Result<(), LuaError> {
3666 crate::func::close_upval(self, level);
3667 Ok(())
3668 }
3669 pub fn close_upvals_from_base(&mut self, ci: CallInfoIdx) -> Result<(), LuaError> {
3670 let base = self.ci_base(ci);
3671 crate::func::close_upval(self, base);
3672 Ok(())
3673 }
3674
3675 pub fn arith_op(
3676 &mut self,
3677 op: i32,
3678 p1: &LuaValue,
3679 p2: &LuaValue,
3680 ) -> Result<LuaValue, LuaError> {
3681 let arith_op = match op {
3682 0 => lua_types::arith::ArithOp::Add,
3683 1 => lua_types::arith::ArithOp::Sub,
3684 2 => lua_types::arith::ArithOp::Mul,
3685 3 => lua_types::arith::ArithOp::Mod,
3686 4 => lua_types::arith::ArithOp::Pow,
3687 5 => lua_types::arith::ArithOp::Div,
3688 6 => lua_types::arith::ArithOp::Idiv,
3689 7 => lua_types::arith::ArithOp::Band,
3690 8 => lua_types::arith::ArithOp::Bor,
3691 9 => lua_types::arith::ArithOp::Bxor,
3692 10 => lua_types::arith::ArithOp::Shl,
3693 11 => lua_types::arith::ArithOp::Shr,
3694 12 => lua_types::arith::ArithOp::Unm,
3695 13 => lua_types::arith::ArithOp::Bnot,
3696 _ => return Err(LuaError::runtime(format_args!("invalid arith op {}", op))),
3697 };
3698 let mut res = LuaValue::Nil;
3699 if crate::object::raw_arith(self, arith_op, p1, p2, &mut res)? {
3700 Ok(res)
3701 } else {
3702 Err(LuaError::arith_error(p1, p2, "perform arithmetic on"))
3703 }
3704 }
3705 pub fn concat(&mut self, n: i32) -> Result<(), LuaError> {
3706 crate::vm::concat(self, n)
3707 }
3708 pub fn less_than(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3709 crate::vm::less_than(self, l, r)
3710 }
3711 pub fn less_equal(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3712 crate::vm::less_equal(self, l, r)
3713 }
3714 pub fn equal_obj(&self, _ctx: Option<&LuaValue>, l: &LuaValue, r: &LuaValue) -> bool {
3715 crate::vm::equal_obj(None, l, r).unwrap_or(false)
3716 }
3717 pub fn equal_obj_with_tm(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3718 crate::vm::equal_obj(Some(self), l, r)
3719 }
3720 pub fn obj_len(&mut self, v: &LuaValue) -> Result<LuaValue, LuaError> {
3721 match v {
3722 LuaValue::Table(_) => {
3723 let consult_len_tm =
3726 !matches!(self.global().lua_version, lua_types::LuaVersion::V51);
3727 let tm = if consult_len_tm {
3728 let mt = self.table_metatable(v);
3729 self.fast_tm_table(mt.as_ref(), TagMethod::Len)
3730 } else {
3731 LuaValue::Nil
3732 };
3733 if matches!(tm, LuaValue::Nil) {
3734 let n = self.table_length(v)?;
3735 return Ok(LuaValue::Int(n));
3736 }
3737 self.push(LuaValue::Nil);
3738 let slot = StackIdx(self.top.0 - 1);
3739 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3740 Ok(self.pop())
3741 }
3742 LuaValue::Str(s) => Ok(LuaValue::Int(s.len() as i64)),
3743 other => {
3744 let tm = crate::tagmethods::get_tm_by_obj(
3745 self,
3746 other,
3747 crate::tagmethods::TagMethod::Len,
3748 );
3749 if matches!(tm, LuaValue::Nil) {
3750 let mut msg = b"attempt to get length of a ".to_vec();
3751 msg.extend_from_slice(&self.obj_type_name(other));
3752 msg.extend_from_slice(b" value");
3753 return Err(crate::debug::prefixed_runtime_pub(self, msg));
3754 }
3755 self.push(LuaValue::Nil);
3756 let slot = StackIdx(self.top.0 - 1);
3757 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3758 Ok(self.pop())
3759 }
3760 }
3761 }
3762 pub fn obj_to_string(&mut self, idx: i32) -> Result<GcRef<LuaString>, LuaError> {
3763 let slot: StackIdx = if idx > 0 {
3764 let ci_func = self.current_call_info().func;
3765 ci_func + idx
3766 } else {
3767 debug_assert!(idx != 0, "invalid index");
3768 StackIdx((self.top_idx().0 as i32 + idx) as u32)
3769 };
3770 let val = self.get_at(slot);
3771 match val {
3772 LuaValue::Str(s) => Ok(s),
3773 LuaValue::Int(_) | LuaValue::Float(_) => {
3774 let s = crate::object::num_to_string(self, &val)?;
3775 self.set_at(slot, LuaValue::Str(s.clone()));
3776 Ok(s)
3777 }
3778 _ => Err(LuaError::type_error(&val, "convert to string")),
3779 }
3780 }
3781 pub fn coerce_to_string(&mut self, idx: StackIdx) -> Result<GcRef<LuaString>, LuaError> {
3782 let val = self.get_at(idx);
3783 match val {
3784 LuaValue::Str(s) => Ok(s),
3785 LuaValue::Int(_) | LuaValue::Float(_) => {
3786 let s = crate::object::num_to_string(self, &val)?;
3787 self.set_at(idx, LuaValue::Str(s.clone()));
3788 Ok(s)
3789 }
3790 _ => Err(LuaError::type_error(&val, "convert to string")),
3791 }
3792 }
3793 pub fn str_to_num(&mut self, s: &[u8]) -> Option<(LuaValue, usize)> {
3801 let mut out = LuaValue::Nil;
3802 let float_only =
3803 self.global().lua_version.number_model() == lua_types::NumberModel::FloatOnly;
3804 let sz = if float_only {
3805 crate::object::str2num_float_only(s, &mut out)
3806 } else {
3807 crate::object::str2num(s, &mut out)
3808 };
3809 if sz == 0 {
3810 return None;
3811 }
3812 Some((out, sz))
3813 }
3814
3815 #[inline(always)]
3816 pub fn fast_get(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
3817 let LuaValue::Table(tbl) = t else {
3818 return Ok(None);
3819 };
3820 let v = tbl.get(k);
3821 if matches!(v, LuaValue::Nil) {
3822 Ok(None)
3823 } else {
3824 Ok(Some(v))
3825 }
3826 }
3827 #[inline(always)]
3828 pub fn fast_get_int(&mut self, t: &LuaValue, k: i64) -> Result<Option<LuaValue>, LuaError> {
3829 let LuaValue::Table(tbl) = t else {
3830 return Ok(None);
3831 };
3832 let v = tbl.get_int(k);
3833 if matches!(v, LuaValue::Nil) {
3834 Ok(None)
3835 } else {
3836 Ok(Some(v))
3837 }
3838 }
3839 #[inline(always)]
3840 pub fn fast_get_short_str(
3841 &mut self,
3842 t: &LuaValue,
3843 k: &LuaValue,
3844 ) -> Result<Option<LuaValue>, LuaError> {
3845 let LuaValue::Table(tbl) = t else {
3846 return Ok(None);
3847 };
3848 let LuaValue::Str(s) = k else {
3849 return Ok(None);
3850 };
3851 let v = tbl.get_short_str(s);
3852 if matches!(v, LuaValue::Nil) {
3853 Ok(None)
3854 } else {
3855 Ok(Some(v))
3856 }
3857 }
3858 #[inline(always)]
3859 pub fn fast_tm_table(&mut self, t: Option<&GcRef<LuaTable>>, tm: TagMethod) -> LuaValue {
3860 let Some(mt) = t else {
3861 return LuaValue::Nil;
3862 };
3863 debug_assert!((tm as u8) <= TagMethod::Eq as u8);
3864 let ename = self.global().tmname[tm as usize].clone();
3865 mt.get_short_str(&ename)
3866 }
3867 pub fn fast_tm_ud(&mut self, u: &GcRef<LuaUserData>, tm: TagMethod) -> LuaValue {
3868 let mt = u.metatable();
3870 self.fast_tm_table(mt.as_ref(), tm)
3871 }
3872
3873 pub fn table_get_with_tm(&mut self, t: &LuaValue, k: &LuaValue) -> Result<LuaValue, LuaError> {
3874 if let LuaValue::Table(tbl) = t {
3880 if !tbl.has_metatable() {
3881 return Ok(tbl.get(k));
3882 }
3883 }
3884 if let Some(v) = self.fast_get(t, k)? {
3885 return Ok(v);
3886 }
3887 let res = self.top_idx();
3888 self.push(LuaValue::Nil);
3889 crate::vm::finish_get(self, t.clone(), k.clone(), res, true, None, None)?;
3890 let value = self.get_at(res);
3891 self.pop();
3892 Ok(value)
3893 }
3894 #[inline]
3909 pub fn table_set_with_tm(
3910 &mut self,
3911 t: &LuaValue,
3912 k: LuaValue,
3913 v: LuaValue,
3914 ) -> Result<(), LuaError> {
3915 if let LuaValue::Table(tbl) = t {
3916 if !tbl.has_metatable() {
3917 self.gc_table_barrier_back(tbl, &v);
3918 return self.table_raw_set(t, k, v);
3919 }
3920 }
3921 if self.fast_get(t, &k)?.is_some() {
3922 self.gc_value_barrier_back(t, &v);
3923 return self.table_raw_set(t, k, v);
3924 }
3925 crate::vm::finish_set(self, t.clone(), k, v, true, None, None)
3926 }
3927 #[inline]
3928 pub fn table_raw_set(
3929 &mut self,
3930 t: &LuaValue,
3931 k: LuaValue,
3932 v: LuaValue,
3933 ) -> Result<(), LuaError> {
3934 let LuaValue::Table(tbl) = t else {
3935 return Err(LuaError::type_error(t, "index"));
3936 };
3937 let tbl = tbl.clone();
3938 tbl.raw_set(self, k, v)
3939 }
3940 #[inline]
3941 pub fn table_array_set(
3942 &mut self,
3943 t: &LuaValue,
3944 idx: usize,
3945 v: LuaValue,
3946 ) -> Result<(), LuaError> {
3947 let LuaValue::Table(tbl) = t else {
3948 return Err(LuaError::type_error(t, "index"));
3949 };
3950 let tbl = tbl.clone();
3951 tbl.raw_set_int(self, idx as i64 + 1, v)
3952 }
3953 pub fn table_ensure_array(&mut self, t: &LuaValue, n: usize) -> Result<(), LuaError> {
3954 let LuaValue::Table(tbl) = t else {
3955 return Err(LuaError::type_error(t, "index"));
3956 };
3957 if n > tbl.array_len() {
3958 tbl.resize(self, n, 0)?;
3959 }
3960 Ok(())
3961 }
3962 pub fn table_length(&mut self, t: &LuaValue) -> Result<i64, LuaError> {
3963 let LuaValue::Table(tbl) = t else {
3964 return Err(LuaError::type_error(t, "get length of"));
3965 };
3966 Ok(tbl.getn() as i64)
3967 }
3968 pub fn table_metatable(&mut self, v: &LuaValue) -> Option<GcRef<LuaTable>> {
3969 match v {
3970 LuaValue::Table(t) => t.metatable(),
3971 LuaValue::UserData(u) => u.metatable(),
3972 other => {
3973 let idx = other.base_type() as usize;
3974 self.global().mt[idx].clone()
3975 }
3976 }
3977 }
3978 pub fn table_resize(
3979 &mut self,
3980 t: &GcRef<LuaTable>,
3981 na: usize,
3982 nh: usize,
3983 ) -> Result<(), LuaError> {
3984 self.mark_gc_check_needed();
3985 t.resize(self, na, nh)
3986 }
3987 pub fn table_getn(&self, t: &GcRef<LuaTable>) -> i64 {
3988 let mut i: i64 = 1;
3996 loop {
3997 let v = t.get_int(i);
3998 if matches!(v, LuaValue::Nil) {
3999 return i - 1;
4000 }
4001 i += 1;
4002 }
4003 }
4004
4005 pub fn try_bin_tm(
4006 &mut self,
4007 p1: &LuaValue,
4008 p1_idx: Option<StackIdx>,
4009 p2: &LuaValue,
4010 p2_idx: Option<StackIdx>,
4011 res: StackIdx,
4012 tm: lua_types::tagmethod::TagMethod,
4013 ) -> Result<(), LuaError> {
4014 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4015 crate::tagmethods::try_bin_tm(self, p1, p1_idx, p2, p2_idx, res, event)
4016 }
4017 pub fn try_bin_i_tm(
4018 &mut self,
4019 p1: &LuaValue,
4020 p1_idx: Option<StackIdx>,
4021 imm: i64,
4022 flip: bool,
4023 res: StackIdx,
4024 tm: lua_types::tagmethod::TagMethod,
4025 ) -> Result<(), LuaError> {
4026 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4027 crate::tagmethods::try_bini_tm(self, p1, p1_idx, imm, flip, res, event)
4028 }
4029 pub fn try_bin_assoc_tm(
4030 &mut self,
4031 p1: &LuaValue,
4032 p1_idx: Option<StackIdx>,
4033 p2: &LuaValue,
4034 p2_idx: Option<StackIdx>,
4035 flip: bool,
4036 res: StackIdx,
4037 tm: lua_types::tagmethod::TagMethod,
4038 ) -> Result<(), LuaError> {
4039 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4040 crate::tagmethods::try_bin_assoc_tm(self, p1, p1_idx, p2, p2_idx, flip, res, event)
4041 }
4042 pub fn try_concat_tm(&mut self, _p1: &LuaValue, _p2: &LuaValue) -> Result<(), LuaError> {
4043 crate::tagmethods::try_concat_tm(self)
4044 }
4045 pub fn call_tm(
4046 &mut self,
4047 f: LuaValue,
4048 p1: &LuaValue,
4049 p2: &LuaValue,
4050 p3: &LuaValue,
4051 ) -> Result<(), LuaError> {
4052 crate::tagmethods::call_tm(self, f, p1.clone(), p2.clone(), p3.clone())
4053 }
4054 pub fn call_tm_res(
4055 &mut self,
4056 f: LuaValue,
4057 p1: &LuaValue,
4058 p2: &LuaValue,
4059 res: StackIdx,
4060 ) -> Result<(), LuaError> {
4061 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)
4062 }
4063 pub fn call_tm_res_bool(
4064 &mut self,
4065 f: LuaValue,
4066 p1: &LuaValue,
4067 p2: &LuaValue,
4068 ) -> Result<bool, LuaError> {
4069 let res = self.top_idx();
4070 self.push(LuaValue::Nil);
4071 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)?;
4072 let result = self.get_at(res).clone();
4073 self.pop();
4074 Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)))
4075 }
4076 pub fn call_order_tm(
4077 &mut self,
4078 p1: &LuaValue,
4079 p2: &LuaValue,
4080 tm: lua_types::tagmethod::TagMethod,
4081 ) -> Result<bool, LuaError> {
4082 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4083 crate::tagmethods::call_order_tm(self, p1, p2, event)
4084 }
4085 pub fn call_order_i_tm(
4086 &mut self,
4087 p1: &LuaValue,
4088 v2: i64,
4089 flip: bool,
4090 isfloat: bool,
4091 tm: lua_types::tagmethod::TagMethod,
4092 ) -> Result<bool, LuaError> {
4093 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
4094 crate::tagmethods::call_orderi_tm(self, p1, v2 as i32, flip, isfloat, event)
4095 }
4096
4097 #[inline(always)]
4098 pub fn proto_code(
4099 &self,
4100 cl: &GcRef<lua_types::closure::LuaLClosure>,
4101 pc: u32,
4102 ) -> lua_types::opcode::Instruction {
4103 cl.proto.code[pc as usize]
4104 }
4105 #[inline(always)]
4106 pub fn proto_const(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> LuaValue {
4107 cl.proto.k[idx].clone()
4108 }
4109 #[inline(always)]
4115 pub fn proto_const_int(
4116 &self,
4117 cl: &GcRef<lua_types::closure::LuaLClosure>,
4118 idx: usize,
4119 ) -> Option<i64> {
4120 match &cl.proto.k[idx] {
4121 LuaValue::Int(v) => Some(*v),
4122 _ => None,
4123 }
4124 }
4125 #[inline(always)]
4129 pub fn proto_const_num(
4130 &self,
4131 cl: &GcRef<lua_types::closure::LuaLClosure>,
4132 idx: usize,
4133 ) -> Option<f64> {
4134 match &cl.proto.k[idx] {
4135 LuaValue::Float(f) => Some(*f),
4136 LuaValue::Int(v) => Some(*v as f64),
4137 _ => None,
4138 }
4139 }
4140 pub fn get_proto_instr(&self, ci: CallInfoIdx, pc: u32) -> lua_types::opcode::Instruction {
4141 let cl = self
4142 .ci_lua_closure(ci)
4143 .expect("get_proto_instr: CallInfo does not hold a Lua closure");
4144 cl.proto.code[pc as usize]
4145 }
4146 pub fn trace_call(&mut self, _idx: CallInfoIdx) -> Result<bool, LuaError> {
4151 Ok(crate::debug::trace_call(self)? != 0)
4152 }
4153 pub fn trace_exec(&mut self, _idx: CallInfoIdx, pc: u32) -> Result<bool, LuaError> {
4156 Ok(crate::debug::trace_exec(self, pc)? != 0)
4157 }
4158 pub fn hook_call(&mut self, idx: CallInfoIdx) -> Result<(), LuaError> {
4159 crate::do_::hookcall(self, idx)
4160 }
4161 #[inline(always)]
4162 fn gc_step_flags(&self) -> Option<(bool, bool)> {
4163 let g = self.global();
4164 if !g.is_gc_running() {
4165 return None;
4166 }
4167 let should_collect = g.heap.would_collect();
4168 let has_finalizers = g.finalizers.has_to_be_finalized();
4169 if should_collect || has_finalizers {
4170 Some((should_collect, has_finalizers))
4171 } else {
4172 None
4173 }
4174 }
4175
4176 #[inline(always)]
4177 fn should_check_gc(&mut self) -> bool {
4178 if self.gc_check_needed {
4179 return true;
4180 }
4181 if self.global().finalizers.has_to_be_finalized() {
4182 self.gc_check_needed = true;
4183 return true;
4184 }
4185 false
4186 }
4187
4188 #[inline(always)]
4189 pub(crate) fn mark_gc_check_needed(&mut self) {
4190 self.gc_check_needed = true;
4191 }
4192
4193 pub fn gc_trace_bound(&self) -> usize {
4207 (self.top.0 as usize).min(self.stack.len())
4208 }
4209
4210 pub fn clear_dead_stack_tail(&mut self) {
4217 let bound = self.gc_trace_bound();
4218 for slot in &mut self.stack[bound..] {
4219 slot.val = LuaValue::Nil;
4220 }
4221 }
4222
4223 pub fn gc_clear_dead_stack_tails(&mut self) {
4230 self.clear_dead_stack_tail();
4231 let global = self.global.clone();
4232 let g = global.borrow();
4233 for entry in g.threads.values() {
4234 if let Ok(mut t) = entry.state.try_borrow_mut() {
4235 t.clear_dead_stack_tail();
4236 }
4237 }
4238 }
4239
4240 pub fn gc_pre_collect_clear(&mut self) {
4244 if self.global().heap.would_collect() {
4245 self.gc_clear_dead_stack_tails();
4246 }
4247 }
4248
4249 #[inline(always)]
4250 pub fn gc_check_step(&mut self) {
4251 if !self.allowhook {
4252 return;
4253 }
4254 if !self.should_check_gc() {
4255 return;
4256 }
4257 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4258 self.gc_check_needed = false;
4259 return;
4260 };
4261 if should_collect || has_finalizers {
4262 if should_collect {
4263 self.gc_clear_dead_stack_tails();
4264 self.gc().check_step();
4265 }
4266 crate::api::run_pending_finalizers(self);
4267 self.gc_check_needed = true;
4268 }
4269 let should_keep_checking = {
4270 let g = self.global();
4271 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4272 };
4273 self.gc_check_needed = should_keep_checking;
4274 }
4275 #[inline(always)]
4276 pub fn gc_cond_step(&mut self) {
4277 if !self.allowhook {
4278 return;
4279 }
4280 if !self.should_check_gc() {
4281 return;
4282 }
4283 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4284 self.gc_check_needed = false;
4285 return;
4286 };
4287 if should_collect || has_finalizers {
4288 if should_collect {
4289 self.gc_clear_dead_stack_tails();
4290 self.gc().check_step();
4291 }
4292 crate::api::run_pending_finalizers(self);
4293 self.gc_check_needed = true;
4294 }
4295 let should_keep_checking = {
4296 let g = self.global();
4297 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4298 };
4299 self.gc_check_needed = should_keep_checking;
4300 }
4301 pub fn gc_barrier_back(&mut self, t: &dyn std::any::Any, v: &LuaValue) {
4302 self.gc().barrier_back(t, v);
4303 }
4304 #[inline(always)]
4305 pub fn gc_value_barrier_back(&mut self, t: &LuaValue, v: &LuaValue) {
4306 if !v.is_collectable() {
4307 return;
4308 }
4309 if let LuaValue::Table(tbl) = t {
4310 self.gc_table_barrier_back(tbl, v);
4311 } else {
4312 self.gc_barrier_back(t, v);
4313 }
4314 }
4315 #[inline(always)]
4316 pub fn gc_table_barrier_back(&mut self, t: &GcRef<LuaTable>, v: &LuaValue) {
4317 if !v.is_collectable() {
4318 return;
4319 }
4320 self.gc().table_barrier_back(t, v);
4321 }
4322 pub fn gc_barrier_upval(&mut self, uv: &GcRef<UpVal>, v: &LuaValue) {
4323 self.gc().barrier(uv, v);
4324 }
4325 pub fn is_main_thread(&mut self) -> bool {
4331 let g = self.global();
4332 g.current_thread_id == g.main_thread_id
4333 }
4334 pub fn obj_type_name<'v>(&self, v: &'v LuaValue) -> std::borrow::Cow<'static, [u8]> {
4335 let honors_name = self.global().lua_version.honors_name_metafield();
4336 match v {
4337 LuaValue::LightUserData(_) => std::borrow::Cow::Borrowed(b"light userdata"),
4338 LuaValue::Table(t) => {
4339 if honors_name {
4340 if let Some(mt) = t.metatable() {
4341 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4342 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4343 }
4344 }
4345 }
4346 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4347 }
4348 LuaValue::UserData(u) => {
4349 if honors_name {
4350 if let Some(mt) = u.metatable() {
4351 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4352 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4353 }
4354 }
4355 }
4356 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4357 }
4358 _ => std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type())),
4359 }
4360 }
4361
4362 pub fn full_type_name(&mut self, v: &LuaValue) -> Result<Vec<u8>, LuaError> {
4363 crate::tagmethods::obj_type_name(self, v)
4364 }
4365 pub fn emit_warning(&mut self, _msg: &[u8], _to_cont: bool) {
4366 warning(self, _msg, _to_cont)
4367 }
4368}
4369
4370pub struct GcHandle<'a> {
4376 _state: &'a mut LuaState,
4377}
4378
4379struct CollectRoots<'a> {
4386 global: &'a GlobalState,
4387 thread: &'a LuaState,
4388}
4389
4390#[derive(Clone, Copy)]
4391enum HeapCollectMode {
4392 Full,
4393 Step,
4394 Minor,
4395}
4396
4397impl<'a> lua_gc::Trace for CollectRoots<'a> {
4398 fn trace(&self, m: &mut lua_gc::Marker) {
4399 self.global.trace(m);
4400 self.thread.trace(m);
4401 }
4402}
4403
4404#[derive(Clone, Copy)]
4405enum BarrierKind {
4406 Forward,
4407 Backward,
4408}
4409
4410fn barrier_lua_value<P>(
4411 heap: &lua_gc::Heap,
4412 parent: GcRef<P>,
4413 child: &LuaValue,
4414 generational: bool,
4415 kind: BarrierKind,
4416) where
4417 P: lua_gc::Trace + 'static,
4418{
4419 if !child.is_collectable() {
4420 return;
4421 }
4422 if generational && matches!(kind, BarrierKind::Backward) {
4423 heap.generational_backward_barrier(parent.0);
4424 }
4425 match child {
4426 LuaValue::Str(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4427 LuaValue::Table(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4428 LuaValue::Function(LuaClosure::Lua(c)) => {
4429 barrier_gc_child(heap, parent, *c, generational, kind)
4430 }
4431 LuaValue::Function(LuaClosure::C(c)) => {
4432 barrier_gc_child(heap, parent, *c, generational, kind)
4433 }
4434 LuaValue::UserData(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4435 LuaValue::Thread(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4436 LuaValue::Nil
4437 | LuaValue::Bool(_)
4438 | LuaValue::Int(_)
4439 | LuaValue::Float(_)
4440 | LuaValue::LightUserData(_)
4441 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4442 }
4443}
4444
4445fn barrier_gc_child<P, C>(
4446 heap: &lua_gc::Heap,
4447 parent: GcRef<P>,
4448 child: GcRef<C>,
4449 generational: bool,
4450 kind: BarrierKind,
4451) where
4452 P: lua_gc::Trace + 'static,
4453 C: lua_gc::Trace + 'static,
4454{
4455 if generational && matches!(kind, BarrierKind::Forward) {
4456 heap.generational_forward_barrier(parent.0, child.0);
4457 } else if matches!(kind, BarrierKind::Backward) {
4458 heap.barrier_back(parent.0, child.0);
4459 } else {
4460 heap.barrier(parent.0, child.0);
4461 }
4462}
4463
4464fn barrier_child_any<P>(
4465 heap: &lua_gc::Heap,
4466 parent: GcRef<P>,
4467 child: &dyn std::any::Any,
4468 generational: bool,
4469 kind: BarrierKind,
4470) where
4471 P: lua_gc::Trace + 'static,
4472{
4473 if let Some(v) = child.downcast_ref::<LuaValue>() {
4474 barrier_lua_value(heap, parent, v, generational, kind);
4475 } else if let Some(c) = child.downcast_ref::<GcRef<LuaString>>() {
4476 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4477 } else if let Some(c) = child.downcast_ref::<GcRef<LuaTable>>() {
4478 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4479 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureLua>>() {
4480 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4481 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureC>>() {
4482 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4483 } else if let Some(c) = child.downcast_ref::<GcRef<LuaUserData>>() {
4484 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4485 } else if let Some(c) = child.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4486 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4487 } else if let Some(c) = child.downcast_ref::<GcRef<LuaProto>>() {
4488 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4489 } else if let Some(c) = child.downcast_ref::<GcRef<UpVal>>() {
4490 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4491 }
4492}
4493
4494fn barrier_any(
4495 heap: &lua_gc::Heap,
4496 parent: &dyn std::any::Any,
4497 child: &dyn std::any::Any,
4498 generational: bool,
4499 kind: BarrierKind,
4500) {
4501 if let Some(v) = parent.downcast_ref::<LuaValue>() {
4502 match v {
4503 LuaValue::Str(p) => barrier_child_any(heap, *p, child, generational, kind),
4504 LuaValue::Table(p) => barrier_child_any(heap, *p, child, generational, kind),
4505 LuaValue::Function(LuaClosure::Lua(p)) => {
4506 barrier_child_any(heap, *p, child, generational, kind)
4507 }
4508 LuaValue::Function(LuaClosure::C(p)) => {
4509 barrier_child_any(heap, *p, child, generational, kind)
4510 }
4511 LuaValue::UserData(p) => barrier_child_any(heap, *p, child, generational, kind),
4512 LuaValue::Thread(p) => barrier_child_any(heap, *p, child, generational, kind),
4513 LuaValue::Nil
4514 | LuaValue::Bool(_)
4515 | LuaValue::Int(_)
4516 | LuaValue::Float(_)
4517 | LuaValue::LightUserData(_)
4518 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4519 }
4520 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaString>>() {
4521 barrier_child_any(heap, p.clone(), child, generational, kind);
4522 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaTable>>() {
4523 barrier_child_any(heap, p.clone(), child, generational, kind);
4524 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureLua>>() {
4525 barrier_child_any(heap, p.clone(), child, generational, kind);
4526 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureC>>() {
4527 barrier_child_any(heap, p.clone(), child, generational, kind);
4528 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaUserData>>() {
4529 barrier_child_any(heap, p.clone(), child, generational, kind);
4530 } else if let Some(p) = parent.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4531 barrier_child_any(heap, p.clone(), child, generational, kind);
4532 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaProto>>() {
4533 barrier_child_any(heap, p.clone(), child, generational, kind);
4534 } else if let Some(p) = parent.downcast_ref::<GcRef<UpVal>>() {
4535 barrier_child_any(heap, p.clone(), child, generational, kind);
4536 }
4537}
4538
4539fn trace_reachable_threads(
4551 global: &GlobalState,
4552 _current_thread_id: u64,
4553 marker: &mut lua_gc::Marker,
4554) {
4555 use lua_gc::Trace;
4556
4557 #[cfg(debug_assertions)]
4558 let mut uncovered_borrowed: Vec<u64> = Vec::new();
4559
4560 loop {
4561 let visited_before = marker.visited_count();
4562 for (id, entry) in global.threads.iter() {
4563 if thread_entry_marked_alive(marker, *id, entry) {
4564 match entry.state.try_borrow() {
4565 Ok(thread) => thread.trace(marker),
4566 Err(_) => {
4567 #[cfg(debug_assertions)]
4568 if *id != _current_thread_id && !uncovered_borrowed.contains(id) {
4569 uncovered_borrowed.push(*id);
4570 }
4571 }
4572 }
4573 }
4574 }
4575 marker.drain_gray_queue();
4576 if marker.visited_count() == visited_before {
4577 break;
4578 }
4579 }
4580
4581 remark_legacy_open_upvalues(global, marker);
4582
4583 #[cfg(debug_assertions)]
4584 debug_assert!(
4585 uncovered_borrowed.len() <= global.suspended_parent_stacks.len(),
4586 "GC root loss: {} marked-alive coroutine(s) (ids {:?}) were mutably \
4587 borrowed at collect time with only {} parent snapshot(s) covering \
4588 them — their stacks were NOT traced this cycle, so anything only \
4589 reachable from them will be swept (issue #140 bug-A class). A borrow \
4590 of a coroutine's state must not be held across an allocation \
4591 checkpoint without pushing a parent GC snapshot.",
4592 uncovered_borrowed.len(),
4593 uncovered_borrowed,
4594 global.suspended_parent_stacks.len()
4595 );
4596}
4597
4598fn remark_legacy_open_upvalues(global: &GlobalState, marker: &mut lua_gc::Marker) {
4618 use lua_gc::Trace;
4619
4620 let legacy = matches!(
4621 global.lua_version,
4622 lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
4623 );
4624 if !legacy {
4625 return;
4626 }
4627
4628 let mut remarked_any = false;
4629 for (id, entry) in global.threads.iter() {
4630 if entry.value.id != *id {
4631 continue;
4632 }
4633 if thread_entry_marked_alive(marker, *id, entry) {
4634 continue;
4635 }
4636 let Ok(thread) = entry.state.try_borrow() else {
4637 continue;
4638 };
4639 if thread.openupval.is_empty() || !thread.legacy_open_upval_touched.get() {
4640 continue;
4641 }
4642 thread.legacy_open_upval_touched.set(false);
4643 for uv in thread.openupval.iter() {
4644 let Some((_tid, idx)) = uv.try_open_payload() else {
4645 continue;
4646 };
4647 let slot = idx.0 as usize;
4648 if slot < thread.stack.len() {
4649 thread.stack[slot].val.trace(marker);
4650 remarked_any = true;
4651 }
4652 }
4653 }
4654
4655 if !remarked_any {
4656 return;
4657 }
4658 marker.drain_gray_queue();
4659
4660 loop {
4661 let visited_before = marker.visited_count();
4662 for (id, entry) in global.threads.iter() {
4663 if thread_entry_marked_alive(marker, *id, entry) {
4664 if let Ok(thread) = entry.state.try_borrow() {
4665 thread.trace(marker);
4666 }
4667 }
4668 }
4669 marker.drain_gray_queue();
4670 if marker.visited_count() == visited_before {
4671 break;
4672 }
4673 }
4674}
4675
4676fn thread_entry_marked_alive(
4677 marker: &lua_gc::Marker,
4678 id: u64,
4679 entry: &ThreadRegistryEntry,
4680) -> bool {
4681 marker.is_marked_or_old(entry.value.0) && entry.value.id == id
4682}
4683
4684fn lua_value_marked_or_old(marker: &lua_gc::Marker, value: &LuaValue) -> bool {
4685 match value {
4686 LuaValue::Str(v) => marker.is_marked_or_old(v.0),
4687 LuaValue::Table(v) => marker.is_marked_or_old(v.0),
4688 LuaValue::Function(LuaClosure::Lua(v)) => marker.is_marked_or_old(v.0),
4689 LuaValue::Function(LuaClosure::C(v)) => marker.is_marked_or_old(v.0),
4690 LuaValue::UserData(v) => marker.is_marked_or_old(v.0),
4691 LuaValue::Thread(v) => marker.is_marked_or_old(v.0),
4692 LuaValue::Nil
4693 | LuaValue::Bool(_)
4694 | LuaValue::Int(_)
4695 | LuaValue::Float(_)
4696 | LuaValue::LightUserData(_)
4697 | LuaValue::Function(LuaClosure::LightC(_)) => true,
4698 }
4699}
4700
4701fn finalizer_marked_or_old(marker: &lua_gc::Marker, object: &FinalizerObject) -> bool {
4702 match object {
4703 FinalizerObject::Table(t) => marker.is_marked_or_old(t.0),
4704 FinalizerObject::UserData(u) => marker.is_marked_or_old(u.0),
4705 }
4706}
4707
4708fn weak_snapshot_tables<'a>(
4709 snapshot: &'a lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>>,
4710) -> impl Iterator<Item = &'a GcRef<LuaTable>> {
4711 snapshot
4712 .weak_values
4713 .iter()
4714 .chain(snapshot.ephemeron.iter())
4715 .chain(snapshot.all_weak.iter())
4716}
4717
4718fn close_open_upvalues_for_unreachable_threads(global: &GlobalState, marker: &mut lua_gc::Marker) {
4719 use lua_gc::Trace;
4720
4721 let mut closed_values = Vec::<LuaValue>::new();
4722 for (id, entry) in global.threads.iter() {
4723 if entry.value.id != *id {
4724 continue;
4725 }
4726 if thread_entry_marked_alive(marker, *id, entry) {
4727 continue;
4728 }
4729 let Ok(thread) = entry.state.try_borrow() else {
4730 continue;
4731 };
4732 for uv in thread.openupval.iter() {
4733 if !marker.is_visited(uv.identity()) {
4734 continue;
4735 }
4736 let Some((thread_id, idx)) = uv.try_open_payload() else {
4737 continue;
4738 };
4739 if thread_id as u64 != *id {
4740 continue;
4741 }
4742 let value = thread.get_at(idx);
4743 uv.close_with(value.clone());
4744 closed_values.push(value);
4745 }
4746 }
4747 for value in closed_values {
4748 value.trace(marker);
4749 }
4750 marker.drain_gray_queue();
4751}
4752
4753fn record_dead_interned_strings(
4760 global: &GlobalState,
4761 marker: &lua_gc::Marker,
4762 dead_pairs: &std::cell::RefCell<Vec<(u32, usize)>>,
4763) {
4764 let mut dead = dead_pairs.borrow_mut();
4765 for s in global.interned_lt.iter() {
4766 let id = s.identity();
4767 if !marker.is_visited(id) {
4768 dead.push((s.hash(), id));
4769 }
4770 }
4771}
4772
4773fn remove_dead_interned_strings(global: &mut GlobalState, dead_pairs: Vec<(u32, usize)>) {
4785 for (hash, id) in dead_pairs {
4786 global.interned_lt.remove(hash, id);
4787 }
4788 global.interned_lt.shrink_if_sparse();
4789}
4790
4791impl<'a> GcHandle<'a> {
4792 pub fn check_step(&self) {
4799 if !self._state.global().is_gc_running() {
4800 return;
4801 }
4802 if self._state.global().is_gen_mode() {
4803 let should_collect = {
4804 let g = self._state.global();
4805 g.heap.would_collect() || g.gc_debt() > 0
4806 };
4807 if should_collect {
4808 self.generational_step();
4809 }
4810 } else {
4811 self.collect_via_heap(false);
4812 }
4813 }
4814
4815 pub fn full_collect(&self) {
4817 if self._state.global().is_gen_mode() {
4818 self.fullgen();
4819 } else {
4820 self.collect_via_heap(true);
4821 }
4822 }
4823
4824 fn negative_debt(bytes: usize) -> isize {
4825 -(bytes.min(isize::MAX as usize) as isize)
4826 }
4827
4828 fn set_minor_debt(&self) {
4829 let mut g = self._state.global_mut();
4830 let total = g.total_bytes();
4831 let growth = (total / 100).saturating_mul(g.genminormul as usize);
4832 g.heap
4833 .set_threshold_bytes(total.saturating_add(growth.max(1)));
4834 set_debt(&mut *g, Self::negative_debt(growth));
4835 }
4836
4837 fn set_pause_debt(&self) {
4838 let mut g = self._state.global_mut();
4839 let total = g.total_bytes();
4840 let pause = g.gc_pause_param().max(0) as usize;
4841 let threshold = g.gc_estimate.max(1).saturating_mul(pause) / 100;
4842 let debt = if threshold > total {
4843 Self::negative_debt(threshold - total)
4844 } else {
4845 0
4846 };
4847 let heap_threshold = if threshold > total {
4848 threshold
4849 } else {
4850 total.saturating_add(1)
4851 };
4852 g.heap.set_threshold_bytes(heap_threshold);
4853 set_debt(&mut *g, debt);
4854 }
4855
4856 fn enter_incremental_mode(&self) {
4857 let mut g = self._state.global_mut();
4858 g.heap.reset_all_ages();
4859 g.finalizers.reset_generation_boundaries();
4860 g.gckind = GcKind::Incremental as u8;
4861 g.lastatomic = 0;
4862 }
4863
4864 fn enter_generational_mode(&self) -> usize {
4865 self.collect_via_heap_mode(HeapCollectMode::Full);
4866 let numobjs = {
4867 let mut g = self._state.global_mut();
4868 g.heap.promote_all_to_old();
4869 g.finalizers.promote_all_pending_to_old();
4870 g.heap.allgc_count()
4871 };
4872 let total = self._state.global().total_bytes();
4873 {
4874 let mut g = self._state.global_mut();
4875 g.gckind = GcKind::Generational as u8;
4876 g.lastatomic = 0;
4877 g.gc_estimate = total;
4878 }
4879 self.set_minor_debt();
4880 numobjs
4881 }
4882
4883 fn fullgen(&self) -> usize {
4884 self.enter_incremental_mode();
4885 self.enter_generational_mode()
4886 }
4887
4888 fn stepgenfull(&self, lastatomic: usize) {
4889 if self._state.global().gckind == GcKind::Generational as u8 {
4890 self.enter_incremental_mode();
4891 }
4892 self.collect_via_heap_mode(HeapCollectMode::Full);
4893 let newatomic = self._state.global().heap.allgc_count().max(1);
4894 if newatomic < lastatomic.saturating_add(lastatomic >> 3) {
4895 {
4896 let mut g = self._state.global_mut();
4897 g.heap.promote_all_to_old();
4898 g.finalizers.promote_all_pending_to_old();
4899 }
4900 let total = self._state.global().total_bytes();
4901 {
4902 let mut g = self._state.global_mut();
4903 g.gckind = GcKind::Generational as u8;
4904 g.lastatomic = 0;
4905 g.gc_estimate = total;
4906 }
4907 self.set_minor_debt();
4908 } else {
4909 {
4910 let mut g = self._state.global_mut();
4911 g.heap.reset_all_ages();
4912 g.finalizers.reset_generation_boundaries();
4913 }
4914 let total = self._state.global().total_bytes();
4915 {
4916 let mut g = self._state.global_mut();
4917 g.gckind = GcKind::Incremental as u8;
4918 g.lastatomic = newatomic;
4919 g.gc_estimate = total;
4920 }
4921 self.set_pause_debt();
4922 }
4923 }
4924
4925 fn collect_via_heap(&self, force: bool) {
4934 self.collect_via_heap_mode(if force {
4935 HeapCollectMode::Full
4936 } else {
4937 HeapCollectMode::Step
4938 });
4939 }
4940
4941 fn collect_via_heap_mode(&self, mode: HeapCollectMode) {
4942 use lua_gc::Trace;
4943 let state_ref: &LuaState = &*self._state;
4944
4945 if matches!(mode, HeapCollectMode::Step) {
4951 let g = state_ref.global.borrow();
4952 if !g.heap.would_collect() {
4953 return;
4954 }
4955 }
4956
4957 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
4961 let mut g = state_ref.global.borrow_mut();
4962 g.weak_tables_registry.live_snapshot_by_kind()
4963 };
4964
4965 let weak_table_capacity = weak_tables_snapshot.len();
4970 let (pending_snapshot, thread_capacity, _interned_capacity): (
4971 Vec<FinalizerObject>,
4972 usize,
4973 usize,
4974 ) = {
4975 let g = state_ref.global.borrow();
4976 let pending = match mode {
4977 HeapCollectMode::Minor => g.finalizers.pending_minor_snapshot(),
4978 HeapCollectMode::Full | HeapCollectMode::Step => g.finalizers.pending_snapshot(),
4979 };
4980 (pending, g.threads.len(), g.interned_lt.len())
4981 };
4982 let finalizer_capacity = pending_snapshot.len();
4983
4984 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4985 std::cell::RefCell::new(std::collections::HashSet::new());
4986 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
4987 std::cell::RefCell::new(Vec::new());
4988 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
4989 std::cell::RefCell::new(std::collections::HashSet::new());
4990 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4991 std::cell::RefCell::new(std::collections::HashSet::new());
4992 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
4993 let collect_ran = std::cell::Cell::new(false);
4994
4995 {
4996 let global = state_ref.global.borrow();
4997 global.heap.unpause();
4998 let roots = CollectRoots {
4999 global: &*global,
5000 thread: state_ref,
5001 };
5002 let hook = |marker: &mut lua_gc::Marker| {
5003 collect_ran.set(true);
5004 alive_ids.borrow_mut().reserve(weak_table_capacity);
5005 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
5006 alive_thread_ids.borrow_mut().reserve(thread_capacity);
5007 trace_reachable_threads(&*global, global.current_thread_id, marker);
5008 close_open_upvalues_for_unreachable_threads(&*global, marker);
5009 let legacy_weak_key_values =
5016 matches!(global.lua_version, lua_types::LuaVersion::V51);
5017 loop {
5018 let visited_before = marker.visited_count();
5019 for t in &weak_tables_snapshot.ephemeron {
5020 if !marker.is_marked_or_old(t.0) {
5021 continue;
5022 }
5023 if legacy_weak_key_values {
5024 let mut to_mark = Vec::new();
5025 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5026 for v in &to_mark {
5027 v.trace(marker);
5028 }
5029 } else {
5030 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
5031 lua_value_marked_or_old(marker, v)
5032 });
5033 for v in &to_mark {
5034 v.trace(marker);
5035 }
5036 }
5037 }
5038 marker.drain_gray_queue();
5039 if marker.visited_count() == visited_before {
5040 break;
5041 }
5042 }
5043 for t in weak_tables_snapshot
5063 .weak_values
5064 .iter()
5065 .chain(weak_tables_snapshot.all_weak.iter())
5066 {
5067 let to_mark = t.prune_weak_dead_with_value(
5068 &|_| true,
5069 &|v| lua_value_marked_or_old(marker, v),
5070 );
5071 if marker.is_marked_or_old(t.0) {
5072 for v in &to_mark {
5073 v.trace(marker);
5074 }
5075 }
5076 }
5077 marker.drain_gray_queue();
5078 for pf in &pending_snapshot {
5079 if !finalizer_marked_or_old(marker, pf) {
5080 pf.mark(marker);
5081 newly_unreachable.borrow_mut().push(pf.clone());
5082 }
5083 }
5084 marker.drain_gray_queue();
5085 loop {
5086 let visited_before = marker.visited_count();
5087 for t in &weak_tables_snapshot.ephemeron {
5088 if !marker.is_marked_or_old(t.0) {
5089 continue;
5090 }
5091 if legacy_weak_key_values {
5092 let mut to_mark = Vec::new();
5093 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5094 for v in &to_mark {
5095 v.trace(marker);
5096 }
5097 } else {
5098 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
5099 lua_value_marked_or_old(marker, v)
5100 });
5101 for v in &to_mark {
5102 v.trace(marker);
5103 }
5104 }
5105 }
5106 marker.drain_gray_queue();
5107 if marker.visited_count() == visited_before {
5108 break;
5109 }
5110 }
5111 for t in weak_tables_snapshot
5124 .ephemeron
5125 .iter()
5126 .chain(weak_tables_snapshot.all_weak.iter())
5127 {
5128 if !marker.is_marked_or_old(t.0) {
5129 continue;
5130 }
5131 let to_mark = t.prune_weak_dead_with_value(
5132 &|v| lua_value_marked_or_old(marker, v),
5133 &|_| true,
5134 );
5135 for v in &to_mark {
5136 v.trace(marker);
5137 }
5138 }
5139 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5140 if marker.is_marked_or_old(t.0) {
5141 alive_ids.borrow_mut().insert(t.identity());
5142 }
5143 }
5144 marker.drain_gray_queue();
5145 {
5146 let mut alive = alive_thread_ids.borrow_mut();
5147 for (id, entry) in global.threads.iter() {
5148 if thread_entry_marked_alive(marker, *id, entry) {
5149 alive.insert(*id);
5150 }
5151 }
5152 }
5153 {
5154 let mut alive = alive_closure_env_ids.borrow_mut();
5155 for id in global.closure_envs.keys() {
5156 if marker.is_visited(*id) {
5157 alive.insert(*id);
5158 }
5159 }
5160 }
5161 record_dead_interned_strings(&*global, marker, &dead_interned);
5162 };
5163 match mode {
5164 HeapCollectMode::Full => global.heap.full_collect_with_post_mark(&roots, hook),
5165 HeapCollectMode::Step => global.heap.step_with_post_mark(&roots, hook),
5166 HeapCollectMode::Minor => global.heap.minor_collect_with_post_mark(&roots, hook),
5167 }
5168 }
5169
5170 if !collect_ran.get() {
5171 return;
5172 }
5173
5174 let alive_set = alive_ids.into_inner();
5178 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5179 let alive_thread_ids = alive_thread_ids.into_inner();
5180 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5181 let dead_interned = dead_interned.into_inner();
5182 let mut g = state_ref.global.borrow_mut();
5183 remove_dead_interned_strings(&mut *g, dead_interned);
5184 g.weak_tables_registry.retain_identities(&alive_set);
5185 let main_thread_id = g.main_thread_id;
5186 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5187 g.cross_thread_upvals
5188 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5189 g.thread_globals
5193 .retain(|id, _| alive_thread_ids.contains(id));
5194 g.closure_envs
5195 .retain(|id, _| alive_closure_env_ids.contains(id));
5196 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5200 for object in &promoted {
5201 if let Some(ptr) = object.heap_ptr() {
5202 g.heap.move_finobj_to_tobefnz(ptr);
5203 }
5204 }
5205 if matches!(mode, HeapCollectMode::Minor) {
5206 g.finalizers.finish_minor_collection();
5207 }
5208 }
5209
5210 pub fn generational_step(&self) -> bool {
5212 self.generational_step_with_major(true)
5213 }
5214
5215 pub fn generational_step_minor_only(&self) -> bool {
5221 self.generational_step_with_major(false)
5222 }
5223
5224 fn generational_step_with_major(&self, allow_major: bool) -> bool {
5225 let (lastatomic, majorbase, majorinc, should_major) = {
5226 let g = self._state.global();
5227 let majorbase = if g.gc_estimate == 0 {
5228 g.total_bytes()
5229 } else {
5230 g.gc_estimate
5231 };
5232 let majormul = g.gc_genmajormul_param().max(0) as usize;
5233 let majorinc = (majorbase / 100).saturating_mul(majormul);
5234 let debt_due = g.gc_debt() > 0 || g.heap.would_collect();
5235 let should_major =
5236 allow_major && debt_due && g.total_bytes() > majorbase.saturating_add(majorinc);
5237 (g.lastatomic, majorbase, majorinc, should_major)
5238 };
5239
5240 if lastatomic != 0 {
5241 self.stepgenfull(lastatomic);
5242 debug_assert!(self._state.global().is_gen_mode());
5243 return true;
5244 }
5245
5246 if should_major {
5247 let numobjs = self.fullgen();
5248 let after = self._state.global().total_bytes();
5249 if after < majorbase.saturating_add(majorinc / 2) {
5250 self.set_minor_debt();
5251 } else {
5252 {
5253 let mut g = self._state.global_mut();
5254 g.lastatomic = numobjs.max(1);
5255 }
5256 self.set_pause_debt();
5257 }
5258 } else {
5259 self.collect_via_heap_mode(HeapCollectMode::Minor);
5260 self.set_minor_debt();
5261 self._state.global_mut().gc_estimate = majorbase;
5262 }
5263
5264 debug_assert!(self._state.global().is_gen_mode());
5265 true
5266 }
5267
5268 pub fn step(&self) { }
5271
5272 pub fn incremental_step(&self, work_units: isize) -> bool {
5285 self.incremental_step_to_state(work_units, None)
5286 }
5287
5288 pub fn run_until_gc_state_for_test(&self, target: lua_gc::GcState) -> bool {
5293 self.incremental_step_to_state(isize::MAX / 4, Some(target));
5294 self._state.global().heap.gc_state() == target
5295 }
5296
5297 fn incremental_step_to_state(
5298 &self,
5299 work_units: isize,
5300 target: Option<lua_gc::GcState>,
5301 ) -> bool {
5302 use lua_gc::{StepBudget, StepOutcome, Trace};
5303 let state_ref: &LuaState = &*self._state;
5304
5305 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5306 let mut g = state_ref.global.borrow_mut();
5307 g.weak_tables_registry.live_snapshot_by_kind()
5308 };
5309
5310 let weak_table_capacity = weak_tables_snapshot.len();
5311 let (pending_snapshot, thread_capacity, _interned_capacity): (
5312 Vec<FinalizerObject>,
5313 usize,
5314 usize,
5315 ) = {
5316 let g = state_ref.global.borrow();
5317 (
5318 g.finalizers.pending_snapshot(),
5319 g.threads.len(),
5320 g.interned_lt.len(),
5321 )
5322 };
5323 let finalizer_capacity = pending_snapshot.len();
5324
5325 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5326 std::cell::RefCell::new(std::collections::HashSet::new());
5327 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
5328 std::cell::RefCell::new(Vec::new());
5329 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
5330 std::cell::RefCell::new(std::collections::HashSet::new());
5331 let alive_closure_env_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5332 std::cell::RefCell::new(std::collections::HashSet::new());
5333 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5334 let atomic_ran = std::cell::Cell::new(false);
5335
5336 let stop_target = {
5337 let g = state_ref.global.borrow();
5338 match (target, g.heap.gc_state()) {
5339 (Some(target), _) => Some(target),
5340 (None, lua_gc::GcState::CallFin) => None,
5341 (None, _) => Some(lua_gc::GcState::CallFin),
5342 }
5343 };
5344
5345 let outcome = {
5346 let global = state_ref.global.borrow();
5347 global.heap.unpause();
5348 let roots = CollectRoots {
5349 global: &*global,
5350 thread: state_ref,
5351 };
5352 let hook = |marker: &mut lua_gc::Marker| {
5353 atomic_ran.set(true);
5354 alive_ids.borrow_mut().reserve(weak_table_capacity);
5355 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
5356 alive_thread_ids.borrow_mut().reserve(thread_capacity);
5357 trace_reachable_threads(&*global, global.current_thread_id, marker);
5358 close_open_upvalues_for_unreachable_threads(&*global, marker);
5359 let legacy_weak_key_values =
5363 matches!(global.lua_version, lua_types::LuaVersion::V51);
5364 loop {
5365 let visited_before = marker.visited_count();
5366 for t in &weak_tables_snapshot.ephemeron {
5367 let t_id = t.identity();
5368 if !marker.is_visited(t_id) {
5369 continue;
5370 }
5371 if legacy_weak_key_values {
5372 let mut to_mark = Vec::new();
5373 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5374 for v in &to_mark {
5375 v.trace(marker);
5376 }
5377 } else {
5378 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5379 for v in &to_mark {
5380 v.trace(marker);
5381 }
5382 }
5383 }
5384 marker.drain_gray_queue();
5385 if marker.visited_count() == visited_before {
5386 break;
5387 }
5388 }
5389 for t in weak_tables_snapshot
5402 .weak_values
5403 .iter()
5404 .chain(weak_tables_snapshot.all_weak.iter())
5405 {
5406 let to_mark =
5407 t.prune_weak_dead_with(&|_| true, &|id| marker.is_visited(id));
5408 if marker.is_visited(t.identity()) {
5409 for v in &to_mark {
5410 v.trace(marker);
5411 }
5412 }
5413 }
5414 marker.drain_gray_queue();
5415 for pf in &pending_snapshot {
5416 if !marker.is_visited(pf.identity()) {
5417 pf.mark(marker);
5418 newly_unreachable.borrow_mut().push(pf.clone());
5419 }
5420 }
5421 marker.drain_gray_queue();
5422 loop {
5423 let visited_before = marker.visited_count();
5424 for t in &weak_tables_snapshot.ephemeron {
5425 let t_id = t.identity();
5426 if !marker.is_visited(t_id) {
5427 continue;
5428 }
5429 if legacy_weak_key_values {
5430 let mut to_mark = Vec::new();
5431 t.for_each_entry(|_k, v| to_mark.push(v.clone()));
5432 for v in &to_mark {
5433 v.trace(marker);
5434 }
5435 } else {
5436 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5437 for v in &to_mark {
5438 v.trace(marker);
5439 }
5440 }
5441 }
5442 marker.drain_gray_queue();
5443 if marker.visited_count() == visited_before {
5444 break;
5445 }
5446 }
5447 for t in weak_tables_snapshot
5454 .ephemeron
5455 .iter()
5456 .chain(weak_tables_snapshot.all_weak.iter())
5457 {
5458 if !marker.is_visited(t.identity()) {
5459 continue;
5460 }
5461 let to_mark =
5462 t.prune_weak_dead_with(&|id| marker.is_visited(id), &|_| true);
5463 for v in &to_mark {
5464 v.trace(marker);
5465 }
5466 }
5467 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5468 if marker.is_visited(t.identity()) {
5469 alive_ids.borrow_mut().insert(t.identity());
5470 }
5471 }
5472 marker.drain_gray_queue();
5473 {
5474 let mut alive = alive_thread_ids.borrow_mut();
5475 for (id, entry) in global.threads.iter() {
5476 if thread_entry_marked_alive(marker, *id, entry) {
5477 alive.insert(*id);
5478 }
5479 }
5480 }
5481 {
5482 let mut alive = alive_closure_env_ids.borrow_mut();
5483 for id in global.closure_envs.keys() {
5484 if marker.is_visited(*id) {
5485 alive.insert(*id);
5486 }
5487 }
5488 }
5489 record_dead_interned_strings(&*global, marker, &dead_interned);
5490 };
5491 let budget = StepBudget::from_work(work_units);
5492 if let Some(target) = stop_target {
5493 global
5494 .heap
5495 .incremental_run_until_state_with_post_mark(&roots, target, work_units, hook)
5496 } else {
5497 global
5498 .heap
5499 .incremental_step_with_post_mark(&roots, budget, hook)
5500 }
5501 };
5502
5503 if atomic_ran.get() {
5504 let alive_set = alive_ids.into_inner();
5505 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5506 let alive_thread_ids = alive_thread_ids.into_inner();
5507 let alive_closure_env_ids = alive_closure_env_ids.into_inner();
5508 let dead_interned = dead_interned.into_inner();
5509 let mut g = state_ref.global.borrow_mut();
5510 remove_dead_interned_strings(&mut *g, dead_interned);
5511 g.weak_tables_registry.retain_identities(&alive_set);
5512 let main_thread_id = g.main_thread_id;
5513 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5514 g.cross_thread_upvals
5515 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5516 g.thread_globals
5520 .retain(|id, _| alive_thread_ids.contains(id));
5521 g.closure_envs
5522 .retain(|id, _| alive_closure_env_ids.contains(id));
5523 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5524 for object in &promoted {
5525 if let Some(ptr) = object.heap_ptr() {
5526 g.heap.move_finobj_to_tobefnz(ptr);
5527 }
5528 }
5529 }
5530
5531 let mut paused = matches!(outcome, StepOutcome::Paused);
5532 if target.is_none()
5533 && self._state.global().heap.gc_state() == lua_gc::GcState::CallFin
5534 && !self._state.global().finalizers.has_to_be_finalized()
5535 {
5536 paused = self._state.global().heap.finish_callfin_phase();
5537 }
5538
5539 paused
5540 }
5541
5542 pub fn prune_weak_tables_mark_only(&self) {
5549 use lua_gc::Trace;
5550 let state_ref: &LuaState = &*self._state;
5551
5552 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5553 let mut g = state_ref.global.borrow_mut();
5554 g.weak_tables_registry.live_snapshot_by_kind()
5555 };
5556 let _interned_capacity = {
5557 let g = state_ref.global.borrow();
5558 g.interned_lt.len()
5559 };
5560
5561 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5562
5563 {
5564 let global = state_ref.global.borrow();
5565 global.heap.unpause();
5566 let roots = CollectRoots {
5567 global: &*global,
5568 thread: state_ref,
5569 };
5570 let hook = |marker: &mut lua_gc::Marker| {
5571 trace_reachable_threads(&*global, global.current_thread_id, marker);
5572 loop {
5573 let visited_before = marker.visited_count();
5574 for t in &weak_tables_snapshot.ephemeron {
5575 let t_id = t.identity();
5576 if !marker.is_visited(t_id) {
5577 continue;
5578 }
5579 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5580 for v in &to_mark {
5581 v.trace(marker);
5582 }
5583 }
5584 marker.drain_gray_queue();
5585 if marker.visited_count() == visited_before {
5586 break;
5587 }
5588 }
5589 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5590 if marker.is_visited(t.identity()) {
5591 let to_mark = t.prune_weak_dead(&|id| marker.is_visited(id));
5592 for v in &to_mark {
5593 v.trace(marker);
5594 }
5595 }
5596 }
5597 marker.drain_gray_queue();
5598 record_dead_interned_strings(&*global, marker, &dead_interned);
5599 };
5600 global.heap.mark_only_with_post_mark(&roots, hook);
5601 }
5602
5603 let dead_interned = dead_interned.into_inner();
5604 let mut g = state_ref.global.borrow_mut();
5605 remove_dead_interned_strings(&mut *g, dead_interned);
5606 }
5607
5608 pub fn change_mode(&self, mode: GcKind) {
5610 let old = self._state.global().gckind;
5611 if old == mode as u8 {
5612 self._state.global_mut().lastatomic = 0;
5613 return;
5614 }
5615 match mode {
5616 GcKind::Generational => {
5617 self.enter_generational_mode();
5618 }
5619 GcKind::Incremental => {
5620 self.enter_incremental_mode();
5621 }
5622 }
5623 }
5624
5625 pub fn fix_object<T: lua_gc::Trace + 'static>(&self, _o: &GcRef<T>) { }
5628
5629 pub fn free_all_objects(&self) {
5633 }
5635
5636 pub fn barrier(&self, p: &dyn std::any::Any, v: &LuaValue) {
5640 let g = self._state.global();
5641 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Forward);
5642 }
5643
5644 pub fn barrier_back(&self, p: &dyn std::any::Any, v: &LuaValue) {
5648 let g = self._state.global();
5649 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Backward);
5650 }
5651
5652 pub fn table_barrier_back(&self, p: &GcRef<LuaTable>, v: &LuaValue) {
5654 let g = self._state.global();
5655 barrier_lua_value(&g.heap, *p, v, g.is_gen_mode(), BarrierKind::Backward);
5656 }
5657
5658 pub fn obj_barrier(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5662 let g = self._state.global();
5663 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Forward);
5664 }
5665
5666 pub fn obj_barrier_back(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5669 let g = self._state.global();
5670 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Backward);
5671 }
5672}
5673
5674fn make_seed() -> u32 {
5683 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5684 {
5685 return crate::string::hash_bytes(b"lua-rs-wasm-seed", 0x9e37_79b9);
5686 }
5687
5688 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
5689 {
5690 use std::time::{SystemTime, UNIX_EPOCH};
5691 let t = SystemTime::now()
5692 .duration_since(UNIX_EPOCH)
5693 .map(|d| d.as_secs() as u32)
5694 .unwrap_or(0);
5695
5696 crate::string::hash_bytes(&t.to_le_bytes(), t)
5704 }
5705}
5706
5707pub(crate) fn set_debt(g: &mut GlobalState, mut debt: isize) {
5721 let tb = g.total_bytes() as isize;
5722 debug_assert!(tb > 0);
5723 if debt < tb.saturating_sub(isize::MAX) {
5725 debt = tb - isize::MAX;
5726 }
5727 g.gc_debt = debt;
5728}
5729
5730pub fn set_c_stack_limit(_state: &mut LuaState, _limit: u32) -> i32 {
5740 let _ = (_state, _limit);
5741 LUAI_MAXCCALLS as i32
5742}
5743
5744pub(crate) fn extend_ci(state: &mut LuaState) -> CallInfoIdx {
5761 debug_assert!(
5762 state.call_info[state.ci.0 as usize].next.is_none(),
5763 "extend_ci: current ci already has a cached next frame"
5764 );
5765
5766 let current_idx = state.ci;
5767 let new_idx = CallInfoIdx(state.call_info.len() as u32);
5769
5770 state.call_info.push(CallInfo {
5771 previous: Some(current_idx),
5772 next: None,
5773 u: CallInfoFrame::lua_default(),
5774 ..CallInfo::default()
5775 });
5776
5777 state.call_info[current_idx.0 as usize].next = Some(new_idx);
5778
5779 state.nci += 1;
5780
5781 new_idx
5782}
5783
5784fn free_ci(state: &mut LuaState) {
5805 let ci_idx = state.ci.0 as usize;
5806
5807 let mut next_opt = state.call_info[ci_idx].next.take();
5808
5809 while let Some(idx) = next_opt {
5810 next_opt = state.call_info[idx.0 as usize].next;
5811 state.nci = state.nci.saturating_sub(1);
5812 }
5813
5814 state.call_info.truncate(ci_idx + 1);
5817}
5818
5819pub(crate) fn shrink_ci(state: &mut LuaState) {
5846 let ci_idx = state.ci.0 as usize;
5847
5848 if state.call_info[ci_idx].next.is_none() {
5849 return;
5850 }
5851
5852 let free_count = state.call_info.len().saturating_sub(ci_idx + 1);
5853 if free_count <= 1 {
5854 return;
5855 }
5856
5857 let keep = free_count / 2;
5861 let removed = free_count - keep;
5862 let new_len = ci_idx + 1 + keep;
5863 state.call_info.truncate(new_len);
5864 state.nci = state.nci.saturating_sub(removed as u32);
5865
5866 if let Some(last) = state.call_info.last_mut() {
5868 last.next = None;
5869 }
5870}
5871
5872pub(crate) fn check_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5884 if state.c_calls() == LUAI_MAXCCALLS {
5887 return Err(LuaError::runtime(format_args!("C stack overflow")));
5888 }
5889 if state.c_calls() >= (LUAI_MAXCCALLS / 10 * 11) {
5891 return Err(LuaError::with_status(LuaStatus::ErrErr));
5892 }
5893 Ok(())
5894}
5895
5896pub fn inc_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5907 state.n_ccalls += 1;
5908 if state.c_calls() >= LUAI_MAXCCALLS {
5910 check_c_stack(state)?;
5911 }
5912 Ok(())
5913}
5914
5915fn stack_init(thread: &mut LuaState) {
5921 let total_slots = BASIC_STACK_SIZE + EXTRA_STACK;
5923 thread.stack = vec![StackValue::default(); total_slots];
5924
5925 thread.tbclist = Vec::new();
5929
5930 thread.top = StackIdx(0);
5935
5936 thread.stack_last = StackIdx(BASIC_STACK_SIZE as u32);
5937
5938 let base_ci = CallInfo {
5939 func: StackIdx(0),
5940 top: StackIdx(1 + LUA_MINSTACK as u32),
5941 previous: None,
5942 next: None,
5943 callstatus: CIST_C,
5944 call_metamethods: 0,
5945 tailcalls: 0,
5946 nresults: 0,
5947 u: CallInfoFrame::c_default(),
5948 u2: CallInfoExtra::default(),
5949 };
5950
5951 if thread.call_info.is_empty() {
5952 thread.call_info.push(base_ci);
5953 } else {
5954 thread.call_info[0] = base_ci;
5955 thread.call_info.truncate(1);
5956 }
5957
5958 thread.stack[0] = StackValue {
5959 val: LuaValue::Nil,
5960 };
5961
5962 thread.top = StackIdx(1);
5963
5964 thread.ci = CallInfoIdx(0);
5965}
5966
5967fn free_stack(state: &mut LuaState) {
5968 if state.stack.is_empty() {
5969 return;
5970 }
5971 state.ci = CallInfoIdx(0);
5972 free_ci(state);
5973 debug_assert_eq!(state.nci, 0, "nci should be 0 after free_ci");
5974 state.stack.clear();
5976 state.stack.shrink_to_fit();
5977}
5978
5979fn init_registry(state: &mut LuaState) -> Result<(), LuaError> {
5980 let registry = state.new_table();
5982
5983 state.global_mut().l_registry = LuaValue::Table(registry.clone());
5985
5986 let globals = state.new_table();
6006 state.global_mut().globals = LuaValue::Table(globals);
6007 let loaded = state.new_table();
6008 state.global_mut().loaded = LuaValue::Table(loaded);
6009
6010 Ok(())
6011}
6012
6013fn lua_open(state: &mut LuaState) -> Result<(), LuaError> {
6014 stack_init(state);
6015 init_registry(state)?;
6016 crate::string::init(state)?;
6017 crate::tagmethods::init(state)?;
6018 state.global_mut().gcstp = 0;
6020 state.global().heap.unpause();
6021 state.global_mut().nilvalue = LuaValue::Nil;
6024 Ok(())
6026}
6027
6028fn preinit_thread(thread: &mut LuaState, global: Rc<RefCell<GlobalState>>) {
6029 thread.global = global;
6030 thread.stack = Vec::new();
6031 thread.call_info = Vec::new();
6032 thread.ci = CallInfoIdx(0);
6035 thread.nci = 0;
6036 thread.n_ccalls = 0;
6040 thread.hook = None;
6041 thread.hookmask = 0;
6042 thread.basehookcount = 0;
6043 thread.allowhook = true;
6044 thread.hookcount = thread.basehookcount;
6046
6047 {
6052 let (active, interval) = {
6053 let g = thread.global.borrow();
6054 (g.sandbox_active(), g.sandbox.interval.get())
6055 };
6056 if active {
6057 thread.hookmask = SANDBOX_COUNT_MASK;
6058 thread.basehookcount = interval;
6059 thread.hookcount = interval;
6060 }
6061 }
6062 thread.openupval = Vec::new();
6063 thread.status = LuaStatus::Ok as u8;
6064 thread.errfunc = 0;
6065 thread.oldpc = 0;
6066 thread.gc_check_needed = true;
6067}
6068
6069fn close_state(state: &mut LuaState) {
6070 let is_complete = state.global().is_complete();
6071
6072 if !is_complete {
6073 state.gc().free_all_objects();
6075 } else {
6076 state.ci = CallInfoIdx(0);
6077 state.gc().free_all_objects();
6080 }
6082
6083 state.global_mut().strt = StringPool::default();
6085
6086 free_stack(state);
6087
6088 }
6093
6094pub fn new_thread(state: &mut LuaState, initial_body: Option<LuaValue>) -> Result<(), LuaError> {
6123 state.gc_pre_collect_clear();
6124 state.gc().check_step();
6125
6126 let global_rc = state.global_rc();
6131 let hookmask = state.hookmask;
6132 let basehookcount = state.basehookcount;
6133
6134 let reserved_id = {
6135 let mut g = state.global_mut();
6136 let id = g.next_thread_id;
6137 g.next_thread_id += 1;
6138 id
6139 };
6140
6141 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
6147 let creator_id = state.global().current_thread_id;
6148 let creator_lgt = state.v51_thread_lgt(creator_id);
6149 state
6150 .global_mut()
6151 .thread_globals
6152 .insert(reserved_id, creator_lgt);
6153 }
6154
6155 let mut new_thread = LuaState {
6156 status: LuaStatus::Ok as u8,
6157 allowhook: true,
6158 nci: 0,
6159 top: StackIdx(0),
6160 stack_last: StackIdx(0),
6161 stack: Vec::new(),
6162 ci: CallInfoIdx(0),
6163 call_info: Vec::new(),
6164 openupval: Vec::new(),
6165 legacy_open_upval_touched: std::cell::Cell::new(false),
6166 tbclist: Vec::new(),
6167 global: global_rc.clone(),
6168 hook: None,
6169 hookmask: 0,
6170 basehookcount: 0,
6171 hookcount: 0,
6172 errfunc: 0,
6173 n_ccalls: 0,
6174 oldpc: 0,
6175 marked: 0,
6176 cached_thread_id: reserved_id,
6177 gc_check_needed: false,
6178 };
6179
6180 preinit_thread(&mut new_thread, global_rc);
6181
6182 new_thread.hookmask = hookmask;
6183 new_thread.basehookcount = basehookcount;
6184 new_thread.reset_hook_count();
6187
6188 stack_init(&mut new_thread);
6194
6195 if let Some(body) = initial_body {
6196 new_thread.push(body);
6197 }
6198
6199 let thread_ref: Rc<RefCell<LuaState>> = Rc::new(RefCell::new(new_thread));
6200
6201 let value = {
6202 let mut g = state.global_mut();
6203 let id = reserved_id;
6204 let value = GcRef::new(lua_types::value::LuaThread::new(id));
6205 g.threads.insert(
6206 id,
6207 ThreadRegistryEntry {
6208 state: thread_ref,
6209 value: value.clone(),
6210 },
6211 );
6212 value
6213 };
6214
6215 state.push(LuaValue::Thread(value));
6216
6217 Ok(())
6218}
6219
6220pub fn reset_thread(state: &mut LuaState, status: i32) -> i32 {
6242 state.ci = CallInfoIdx(0);
6243 let ci_idx = 0usize;
6244
6245 if !state.stack.is_empty() {
6247 state.stack[0].val = LuaValue::Nil;
6248 }
6249
6250 state.call_info[ci_idx].func = StackIdx(0);
6251 state.call_info[ci_idx].call_metamethods = 0;
6252 state.call_info[ci_idx].callstatus = CIST_C;
6253
6254 let mut status = if status == LuaStatus::Yield as i32 {
6255 LuaStatus::Ok as i32
6256 } else {
6257 status
6258 };
6259
6260 state.status = LuaStatus::Ok as u8;
6261
6262 let close_status = crate::do_::close_protected(state, StackIdx(1), LuaStatus::from_raw(status));
6263 status = close_status as i32;
6264
6265 if status != LuaStatus::Ok as i32 {
6266 crate::do_::set_error_obj(state, LuaStatus::from_raw(status), StackIdx(1));
6267 } else {
6268 state.top = StackIdx(1);
6269 }
6270
6271 let new_ci_top = StackIdx(state.top.0 + LUA_MINSTACK as u32);
6272 state.call_info[ci_idx].top = new_ci_top;
6273
6274 let needed = new_ci_top.0 as usize;
6277 if state.stack.len() < needed {
6278 state.stack.resize(needed, StackValue::default());
6279 }
6280
6281 status
6282}
6283
6284pub fn close_thread(state: &mut LuaState, from: Option<&LuaState>) -> i32 {
6298 state.n_ccalls = match from {
6300 Some(f) => f.c_calls(),
6301 None => 0,
6302 };
6303 let current_status = state.status as i32;
6304 let result = reset_thread(state, current_status);
6305 result
6306}
6307
6308pub fn reset_thread_api(state: &mut LuaState) -> i32 {
6317 close_thread(state, None)
6318}
6319
6320pub fn new_state() -> Option<LuaState> {
6356 let placeholder_str = GcRef::new(LuaString::placeholder());
6365
6366 let initial_white = 1u8 << WHITE0BIT;
6368
6369 let global = GlobalState {
6373 parser_hook: None,
6374 cli_argv: None,
6375 cli_preload: None,
6376 lua_version: lua_types::LuaVersion::default(),
6377 file_loader_hook: None,
6378 file_open_hook: None,
6379 stdout_hook: None,
6380 stderr_hook: None,
6381 stdin_hook: None,
6382 env_hook: None,
6383 unix_time_hook: None,
6384 cpu_clock_hook: None,
6385 local_offset_hook: None,
6386 entropy_hook: None,
6387 temp_name_hook: None,
6388 popen_hook: None,
6389 file_remove_hook: None,
6390 file_rename_hook: None,
6391 os_execute_hook: None,
6392 dynlib_load_hook: None,
6393 dynlib_symbol_hook: None,
6394 dynlib_unload_hook: None,
6395 sandbox: SandboxLimits::default(),
6396 gc_debt: 0,
6397 gc_estimate: 0,
6398 lastatomic: 0,
6399 strt: StringPool::default(),
6400 l_registry: LuaValue::Nil,
6401 external_roots: ExternalRootSet::default(),
6402 globals: LuaValue::Nil,
6403 loaded: LuaValue::Nil,
6404 nilvalue: LuaValue::Int(0),
6405 seed: make_seed(),
6406 currentwhite: initial_white,
6407 gcstate: GCS_PAUSE,
6408 gckind: GcKind::Incremental as u8,
6410 gcstopem: false,
6411 genminormul: LUAI_GENMINORMUL,
6412 genmajormul: (LUAI_GENMAJORMUL / 4) as u8,
6414 gcstp: GCSTPGC,
6415 gcemergency: false,
6416 gcpause: (LUAI_GCPAUSE / 4) as u8,
6417 gcstepmul: (LUAI_GCMUL / 4) as u8,
6418 gcstepsize: LUAI_GCSTEPSIZE,
6419 gc55_params: [20, 50, 68, 250, 200, 9600],
6422 sweepgc_cursor: 0,
6423 weak_tables_registry: lua_gc::WeakRegistry::default(),
6424 finalizers: lua_gc::FinalizerRegistry::default(),
6425 gc_finalizer_error: None,
6426 twups: Vec::new(),
6427 panic: None,
6428 mainthread: None,
6429 threads: std::collections::HashMap::new(),
6430 main_thread_value: GcRef::new(lua_types::value::LuaThread::new(0)),
6431 current_thread_id: 0,
6432 closing_thread_id: None,
6433 main_thread_id: 0,
6434 next_thread_id: 1,
6435 thread_globals: std::collections::HashMap::new(),
6436 closure_envs: std::collections::HashMap::new(),
6437 memerrmsg: placeholder_str.clone(),
6438 tmname: Vec::new(),
6439 mt: std::array::from_fn(|_| None),
6440 strcache: std::array::from_fn(|_| std::array::from_fn(|_| placeholder_str.clone())),
6441 interned_lt: InternedStringMap::default(),
6442 warnf: None,
6443 warn_mode: WarnMode::Off,
6444 test_warn_enabled: false,
6445 test_warn_on: false,
6446 test_warn_mode: TestWarnMode::Normal,
6447 test_warn_last_to_cont: false,
6448 test_warn_buffer: Vec::new(),
6449 c_functions: Vec::new(),
6450 heap: lua_gc::Heap::new(),
6451 cross_thread_upvals: std::collections::HashMap::new(),
6452 suspended_parent_stacks: Vec::new(),
6453 suspended_parent_open_upvals: Vec::new(),
6454 snapshot_stack_pool: Vec::new(),
6455 snapshot_upval_pool: Vec::new(),
6456 resume_value_pool: Vec::new(),
6457 resume_upval_slot_pool: Vec::new(),
6458 resume_flush_pool: Vec::new(),
6459 };
6460
6461 let global_rc = Rc::new(RefCell::new(global));
6462
6463 let initial_marked = initial_white;
6465
6466 let mut main_thread = LuaState {
6467 status: LuaStatus::Ok as u8,
6468 allowhook: true,
6469 nci: 0,
6470 top: StackIdx(0),
6471 stack_last: StackIdx(0),
6472 stack: Vec::new(),
6473 ci: CallInfoIdx(0),
6474 call_info: Vec::new(),
6475 openupval: Vec::new(),
6476 legacy_open_upval_touched: std::cell::Cell::new(false),
6477 tbclist: Vec::new(),
6478 global: global_rc.clone(),
6479 hook: None,
6480 hookmask: 0,
6481 basehookcount: 0,
6482 hookcount: 0,
6483 errfunc: 0,
6484 n_ccalls: 0,
6485 oldpc: 0,
6486 marked: initial_marked,
6487 cached_thread_id: 0,
6488 gc_check_needed: false,
6489 };
6490
6491 preinit_thread(&mut main_thread, global_rc.clone());
6492
6493 main_thread.inc_nny();
6495
6496 match lua_open(&mut main_thread) {
6506 Ok(()) => {}
6507 Err(_) => {
6508 close_state(&mut main_thread);
6509 return None;
6510 }
6511 }
6512
6513 Some(main_thread)
6514}
6515
6516pub fn close(mut state: LuaState) {
6532 close_state(&mut state);
6537}
6538
6539pub(crate) fn warning(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6549 let test_warn_enabled = state.global().test_warn_enabled;
6550 if test_warn_enabled {
6551 test_warn(state, msg, to_cont);
6552 return;
6553 }
6554
6555 let has_warnf = state.global().warnf.is_some();
6564 if has_warnf {
6565 let mut warnf = state.global_mut().warnf.take();
6567 if let Some(ref mut f) = warnf {
6568 f(msg, to_cont);
6569 }
6570 state.global_mut().warnf = warnf;
6572 return;
6573 }
6574 default_warn(state, msg, to_cont);
6575}
6576
6577fn test_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6578 let is_control = {
6579 let g = state.global();
6580 !g.test_warn_last_to_cont && !to_cont && msg.first() == Some(&b'@')
6581 };
6582 if is_control {
6583 let mut g = state.global_mut();
6584 match &msg[1..] {
6585 b"off" => g.test_warn_on = false,
6586 b"on" => g.test_warn_on = true,
6587 b"normal" => g.test_warn_mode = TestWarnMode::Normal,
6588 b"allow" => g.test_warn_mode = TestWarnMode::Allow,
6589 b"store" => g.test_warn_mode = TestWarnMode::Store,
6590 _ => {}
6591 }
6592 return;
6593 }
6594
6595 let finished = {
6596 let mut g = state.global_mut();
6597 g.test_warn_last_to_cont = to_cont;
6598 g.test_warn_buffer.extend_from_slice(msg);
6599 if to_cont {
6600 None
6601 } else {
6602 Some((
6603 std::mem::take(&mut g.test_warn_buffer),
6604 g.test_warn_mode,
6605 g.test_warn_on,
6606 ))
6607 }
6608 };
6609
6610 let Some((message, mode, warn_on)) = finished else {
6611 return;
6612 };
6613 match mode {
6614 TestWarnMode::Normal => {
6615 if warn_on && message.first() == Some(&b'#') {
6616 write_warning_message(&message);
6617 }
6618 }
6619 TestWarnMode::Allow => {
6620 if warn_on {
6621 write_warning_message(&message);
6622 }
6623 }
6624 TestWarnMode::Store => {
6625 if let Ok(s) = state.intern_str(&message) {
6626 state.push(LuaValue::Str(s));
6627 let _ = crate::api::set_global(state, b"_WARN");
6628 }
6629 }
6630 }
6631}
6632
6633fn write_warning_message(message: &[u8]) {
6634 use std::io::Write;
6635 let stderr = std::io::stderr();
6636 let mut h = stderr.lock();
6637 let _ = h.write_all(b"Lua warning: ");
6638 let _ = h.write_all(message);
6639 let _ = h.write_all(b"\n");
6640}
6641
6642fn default_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6647 use std::io::Write;
6648 if !to_cont && msg.first() == Some(&b'@') {
6650 match &msg[1..] {
6651 b"off" => state.global_mut().warn_mode = WarnMode::Off,
6652 b"on" => state.global_mut().warn_mode = WarnMode::On,
6653 _ => {}
6654 }
6655 return;
6656 }
6657 let mode = state.global().warn_mode;
6658 match mode {
6659 WarnMode::Off => {}
6660 WarnMode::On | WarnMode::Cont => {
6661 let stderr = std::io::stderr();
6662 let mut h = stderr.lock();
6663 if mode == WarnMode::On {
6664 let _ = h.write_all(b"Lua warning: ");
6665 }
6666 let _ = h.write_all(msg);
6667 if to_cont {
6668 state.global_mut().warn_mode = WarnMode::Cont;
6669 } else {
6670 let _ = h.write_all(b"\n");
6671 state.global_mut().warn_mode = WarnMode::On;
6672 }
6673 }
6674 }
6675}
6676
6677#[cfg(test)]
6678mod tests {
6679 use super::*;
6680
6681 fn test_noop_cclosure(_: &mut LuaState) -> Result<usize, LuaError> {
6682 Ok(0)
6683 }
6684
6685 #[test]
6686 fn external_root_keys_reject_stale_slot_after_reuse() {
6687 let mut roots = ExternalRootSet::default();
6688
6689 let first = roots.insert(LuaValue::Int(1));
6690 assert_eq!(roots.len(), 1);
6691 assert_eq!(roots.get(first), Some(&LuaValue::Int(1)));
6692
6693 assert_eq!(roots.remove(first), Some(LuaValue::Int(1)));
6694 assert!(roots.get(first).is_none());
6695 assert!(roots.remove(first).is_none());
6696 assert_eq!(roots.len(), 0);
6697 assert_eq!(roots.vacant_len(), 1);
6698 assert!(roots.replace(first, LuaValue::Int(9)).is_none());
6699 assert!(roots.is_empty());
6700
6701 let second = roots.insert(LuaValue::Int(2));
6702 assert_eq!(first.index, second.index);
6703 assert_ne!(first, second);
6704 assert!(roots.get(first).is_none());
6705 assert_eq!(roots.get(second), Some(&LuaValue::Int(2)));
6706 assert!(roots.replace(first, LuaValue::Int(3)).is_none());
6707 }
6708
6709 #[test]
6710 fn external_roots_keep_heap_value_alive_until_unrooted() {
6711 let mut state = new_state().expect("state should initialize");
6712 let _heap_guard = {
6713 let g = state.global();
6714 lua_gc::HeapGuard::push(&g.heap)
6715 };
6716
6717 let table = state.new_table();
6718 assert_eq!(state.global().heap.allgc_count(), 1);
6719
6720 let key = state.external_root_value(LuaValue::Table(table));
6721 state.gc().full_collect();
6722 assert_eq!(state.global().heap.allgc_count(), 1);
6723 assert_eq!(state.global().external_roots.len(), 1);
6724
6725 assert!(state.external_unroot_value(key).is_some());
6726 state.gc().full_collect();
6727 assert_eq!(state.global().heap.allgc_count(), 0);
6728 assert!(state.global().external_roots.is_empty());
6729 }
6730
6731 #[test]
6732 fn interned_string_table_grows_then_shrinks_on_collection() {
6733 let mut state = new_state().expect("state should initialize");
6734 let _heap_guard = {
6735 let g = state.global();
6736 lua_gc::HeapGuard::push(&g.heap)
6737 };
6738
6739 let initial_buckets = state.global().interned_lt.bucket_count();
6740 assert_eq!(
6741 initial_buckets, 64,
6742 "the intern table starts at C's MINSTRTABSIZE-equivalent of 64"
6743 );
6744 let baseline_live = state.global().interned_lt.len();
6745
6746 let mut anchors: Vec<GcRef<LuaString>> = Vec::with_capacity(2000);
6747 for i in 0..2000usize {
6748 let key = format!("intern-shrink-probe-{i:08}");
6749 let s = state
6750 .intern_str(key.as_bytes())
6751 .expect("short string interns");
6752 anchors.push(s);
6753 }
6754
6755 let grown_buckets = state.global().interned_lt.bucket_count();
6756 assert_eq!(
6757 state.global().interned_lt.len(),
6758 baseline_live + 2000,
6759 "all 2000 distinct short strings are interned and live alongside \
6760 the runtime's own rooted strings"
6761 );
6762 assert!(
6763 grown_buckets >= 2048,
6764 "interning 2000 strings must force several bucket doublings past \
6765 the initial 64 (saw {grown_buckets})"
6766 );
6767
6768 drop(anchors);
6769 state.gc().full_collect();
6770
6771 let shrunk_buckets = state.global().interned_lt.bucket_count();
6772 let surviving_live = state.global().interned_lt.len();
6773 assert!(
6774 surviving_live <= baseline_live,
6775 "every probe string is unreachable and must be swept out of the \
6776 intern table, leaving at most the runtime's own strings (baseline \
6777 {baseline_live}, surviving {surviving_live})"
6778 );
6779 assert!(
6780 surviving_live < 2000,
6781 "the 2000 probe strings must not survive collection (surviving \
6782 {surviving_live})"
6783 );
6784 assert_eq!(
6785 shrunk_buckets, 64,
6786 "the batch-end shrink check must reclaim the stale buckets back to \
6787 the 64-bucket floor (saw {shrunk_buckets}, grew to {grown_buckets})"
6788 );
6789 assert!(
6790 shrunk_buckets < grown_buckets,
6791 "shrink must strictly reduce the bucket array"
6792 );
6793 }
6794
6795 #[test]
6796 fn table_buffer_accounting_refunds_on_sweep() {
6797 let mut state = new_state().expect("state should initialize");
6798 let _heap_guard = {
6799 let g = state.global();
6800 lua_gc::HeapGuard::push(&g.heap)
6801 };
6802
6803 let table = state.new_table();
6804 let key = state.external_root_value(LuaValue::Table(table));
6805 let header_bytes = state.global().heap.bytes_used();
6806 assert!(header_bytes > 0);
6807
6808 for i in 1..=128 {
6809 table
6810 .raw_set_int(&mut state, i, LuaValue::Int(i))
6811 .expect("integer table insert should succeed");
6812 }
6813 let grown_bytes = state.global().heap.bytes_used();
6814 assert!(
6815 grown_bytes > header_bytes,
6816 "table array/hash buffer growth must be charged to the GC heap"
6817 );
6818
6819 state.gc().full_collect();
6820 assert_eq!(
6821 state.global().heap.bytes_used(),
6822 grown_bytes,
6823 "rooted table buffer bytes should remain charged after collection"
6824 );
6825
6826 assert!(state.external_unroot_value(key).is_some());
6827 state.gc().full_collect();
6828 assert_eq!(state.global().heap.bytes_used(), 0);
6829 assert_eq!(state.global().heap.allgc_count(), 0);
6830 }
6831
6832 #[test]
6833 fn userdata_buffer_accounting_refunds_on_sweep() {
6834 let mut state = new_state().expect("state should initialize");
6835 let _heap_guard = {
6836 let g = state.global();
6837 lua_gc::HeapGuard::push(&g.heap)
6838 };
6839
6840 let payload_len = 4096;
6841 let userdata = state
6842 .new_userdata_typed(b"accounting", payload_len, 3)
6843 .expect("userdata allocation should succeed");
6844 state.pop_n(1);
6845 let key = state.external_root_value(LuaValue::UserData(userdata));
6846 let allocated_bytes = state.global().heap.bytes_used();
6847 assert!(
6848 allocated_bytes > payload_len,
6849 "userdata payload bytes must be charged to the GC heap"
6850 );
6851
6852 state.gc().full_collect();
6853 assert_eq!(
6854 state.global().heap.bytes_used(),
6855 allocated_bytes,
6856 "rooted userdata payload bytes should remain charged after collection"
6857 );
6858
6859 assert!(state.external_unroot_value(key).is_some());
6860 state.gc().full_collect();
6861 assert_eq!(state.global().heap.bytes_used(), 0);
6862 assert_eq!(state.global().heap.allgc_count(), 0);
6863 }
6864
6865 #[test]
6866 fn cclosure_upvalue_accounting_refunds_on_sweep() {
6867 let mut state = new_state().expect("state should initialize");
6868 let _heap_guard = {
6869 let g = state.global();
6870 lua_gc::HeapGuard::push(&g.heap)
6871 };
6872
6873 let nupvalues = 64;
6874 for i in 0..nupvalues {
6875 state.push(LuaValue::Int(i as i64));
6876 }
6877 crate::api::push_cclosure(&mut state, test_noop_cclosure, nupvalues as i32)
6878 .expect("C closure creation should succeed");
6879 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6880 panic!("expected heavy C closure");
6881 };
6882 let expected_payload = ccl.buffer_bytes();
6883 let key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
6884 state.pop_n(1);
6885 let allocated_bytes = state.global().heap.bytes_used();
6886 assert!(
6887 allocated_bytes >= expected_payload,
6888 "C closure upvalue vector bytes must be charged to the GC heap"
6889 );
6890
6891 state.gc().full_collect();
6892 assert_eq!(
6893 state.global().heap.bytes_used(),
6894 allocated_bytes,
6895 "rooted C closure payload bytes should remain charged after collection"
6896 );
6897
6898 assert!(state.external_unroot_value(key).is_some());
6899 state.gc().full_collect();
6900 assert_eq!(state.global().heap.bytes_used(), 0);
6901 assert_eq!(state.global().heap.allgc_count(), 0);
6902 }
6903
6904 #[test]
6905 fn proto_and_lclosure_accounting_refunds_on_sweep() {
6906 let mut state = new_state().expect("state should initialize");
6907 let _heap_guard = {
6908 let g = state.global();
6909 lua_gc::HeapGuard::push(&g.heap)
6910 };
6911
6912 let mut proto = LuaProto::placeholder();
6913 proto.code = vec![lua_types::opcode::Instruction(0); 2048];
6914 proto.lineinfo = vec![0; 2048];
6915 proto.k = vec![LuaValue::Int(1); 512];
6916 let expected_proto_payload = proto.buffer_bytes();
6917 let proto = GcRef::new(proto);
6918 proto.account_buffer(expected_proto_payload as isize);
6919
6920 let closure = state.new_lclosure(proto, 16);
6921 let expected_closure_payload = closure.buffer_bytes();
6922 let key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6923 let allocated_bytes = state.global().heap.bytes_used();
6924 assert!(
6925 allocated_bytes >= expected_proto_payload + expected_closure_payload,
6926 "proto and Lua closure vector bytes must be charged to the GC heap"
6927 );
6928
6929 state.gc().full_collect();
6930 assert_eq!(
6931 state.global().heap.bytes_used(),
6932 allocated_bytes,
6933 "rooted proto and Lua closure payload bytes should remain charged after collection"
6934 );
6935
6936 assert!(state.external_unroot_value(key).is_some());
6937 state.gc().full_collect();
6938 assert_eq!(state.global().heap.bytes_used(), 0);
6939 assert_eq!(state.global().heap.allgc_count(), 0);
6940 }
6941
6942 #[test]
6943 fn string_buffer_accounting_refunds_on_sweep() {
6944 let mut state = new_state().expect("state should initialize");
6945 let _heap_guard = {
6946 let g = state.global();
6947 lua_gc::HeapGuard::push(&g.heap)
6948 };
6949
6950 let payload = vec![b'x'; crate::string::MAX_SHORT_LEN + 4096];
6951 let string = state
6952 .intern_str(&payload)
6953 .expect("long string should allocate");
6954 let key = state.external_root_value(LuaValue::Str(string));
6955 let allocated_bytes = state.global().heap.bytes_used();
6956 assert!(
6957 allocated_bytes > payload.len(),
6958 "long string backing bytes must be charged to the GC heap"
6959 );
6960
6961 state.gc().full_collect();
6962 assert_eq!(
6963 state.global().heap.bytes_used(),
6964 allocated_bytes,
6965 "rooted string buffer bytes should remain charged after collection"
6966 );
6967
6968 assert!(state.external_unroot_value(key).is_some());
6969 state.gc().full_collect();
6970 assert_eq!(state.global().heap.bytes_used(), 0);
6971 assert_eq!(state.global().heap.allgc_count(), 0);
6972 }
6973
6974 #[test]
6975 fn interned_short_string_cache_does_not_root_unreferenced_string() {
6976 let mut state = new_state().expect("state should initialize");
6977 let _heap_guard = {
6978 let g = state.global();
6979 lua_gc::HeapGuard::push(&g.heap)
6980 };
6981
6982 let payload = b"weak-cache-probe-a";
6983 let string = state
6984 .intern_str(payload)
6985 .expect("short string should intern");
6986 let id = string.identity();
6987 assert!(state.global().interned_lt.contains_key(&payload[..]));
6988 assert_eq!(
6989 state.global().heap.register_allocation_token(id),
6990 state.global().heap.register_allocation_token(id),
6991 "token registration is get-or-insert while the string is provably live"
6992 );
6993 assert!(state.global().heap.allocation_token(id).is_some());
6994
6995 state.gc().full_collect();
6996 assert!(!state.global().interned_lt.contains_key(&payload[..]));
6997 assert_eq!(state.global().heap.allocation_token(id), None);
6998 }
6999
7000 #[test]
7001 fn interned_short_string_cache_keeps_reachable_string_until_unrooted() {
7002 let mut state = new_state().expect("state should initialize");
7003 let _heap_guard = {
7004 let g = state.global();
7005 lua_gc::HeapGuard::push(&g.heap)
7006 };
7007
7008 let payload = b"weak-cache-probe-b";
7009 let string = state
7010 .intern_str(payload)
7011 .expect("short string should intern");
7012 let id = string.identity();
7013 state.global().heap.register_allocation_token(id);
7014 let key = state.external_root_value(LuaValue::Str(string));
7015
7016 state.gc().full_collect();
7017 assert!(state.global().interned_lt.contains_key(&payload[..]));
7018 assert!(state.global().heap.allocation_token(id).is_some());
7019
7020 assert!(state.external_unroot_value(key).is_some());
7021 state.gc().full_collect();
7022 assert!(!state.global().interned_lt.contains_key(&payload[..]));
7023 assert_eq!(state.global().heap.allocation_token(id), None);
7024 }
7025
7026 #[test]
7027 fn gc_phase_predicates_follow_heap_state() {
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 {
7035 let mut g = state.global_mut();
7036 g.gckind = GcKind::Incremental as u8;
7037 g.lastatomic = 0;
7038 assert!(!g.is_gen_mode());
7039 g.lastatomic = 1;
7040 assert!(g.is_gen_mode());
7041 g.lastatomic = 0;
7042 }
7043
7044 let mut roots = Vec::new();
7045 for _ in 0..16 {
7046 let table = state.new_table();
7047 roots.push(state.external_root_value(LuaValue::Table(table)));
7048 }
7049
7050 let mut saw_keep = false;
7051 let mut saw_sweep = false;
7052 for _ in 0..128 {
7053 state.gc().incremental_step(1);
7054 let g = state.global();
7055 let heap_state = g.heap.gc_state();
7056 assert_eq!(g.keep_invariant(), heap_state.is_invariant());
7057 assert_eq!(g.is_sweep_phase(), heap_state.is_sweep());
7058 saw_keep |= g.keep_invariant();
7059 saw_sweep |= g.is_sweep_phase();
7060 if heap_state.is_pause() && saw_keep && saw_sweep {
7061 break;
7062 }
7063 }
7064
7065 assert!(
7066 saw_keep,
7067 "incremental cycle should expose an invariant phase"
7068 );
7069 assert!(saw_sweep, "incremental cycle should expose a sweep phase");
7070
7071 for key in roots {
7072 assert!(state.external_unroot_value(key).is_some());
7073 }
7074 state.gc().full_collect();
7075 }
7076
7077 #[test]
7078 fn gc_barrier_keeps_new_child_stored_in_black_parent() {
7079 let mut state = new_state().expect("state should initialize");
7080 let _heap_guard = {
7081 let g = state.global();
7082 lua_gc::HeapGuard::push(&g.heap)
7083 };
7084
7085 let parent = state.new_table();
7086 let parent_key = state.external_root_value(LuaValue::Table(parent));
7087 state.gc().incremental_step(1);
7088 assert!(
7089 state.global().keep_invariant(),
7090 "test setup should leave the parent marked during an active cycle"
7091 );
7092
7093 let child = state.new_table();
7094 let parent_value = LuaValue::Table(parent);
7095 let child_value = LuaValue::Table(child);
7096 parent
7097 .raw_set_int(&mut state, 1, child_value)
7098 .expect("table store should succeed");
7099 state.gc_barrier_back(&parent_value, &child_value);
7100
7101 for _ in 0..128 {
7102 if state.gc().incremental_step(1) {
7103 break;
7104 }
7105 }
7106
7107 assert_eq!(state.global().heap.allgc_count(), 2);
7108 assert_eq!(
7109 parent.get_int(1).as_table().map(|t| t.identity()),
7110 Some(child.identity())
7111 );
7112
7113 assert!(state.external_unroot_value(parent_key).is_some());
7114 state.gc().full_collect();
7115 assert_eq!(state.global().heap.allgc_count(), 0);
7116 }
7117
7118 #[test]
7119 fn generational_mode_promotes_and_barriers_age_objects() {
7120 let mut state = new_state().expect("state should initialize");
7121 let _heap_guard = {
7122 let g = state.global();
7123 lua_gc::HeapGuard::push(&g.heap)
7124 };
7125
7126 let parent = state.new_table();
7127 let parent_key = state.external_root_value(LuaValue::Table(parent));
7128
7129 state.gc().change_mode(GcKind::Generational);
7130 assert_eq!(parent.0.age(), lua_gc::GcAge::Old);
7131 assert_eq!(parent.0.color(), lua_gc::Color::Black);
7132 let majorbase = state.global().gc_estimate;
7133 assert!(majorbase > 0);
7134 assert!(state.global().gc_debt() <= 0);
7135
7136 let child = state.new_table();
7137 let parent_value = LuaValue::Table(parent);
7138 let child_value = LuaValue::Table(child);
7139 parent
7140 .raw_set_int(&mut state, 1, child_value.clone())
7141 .expect("table store should succeed");
7142 state.gc_barrier_back(&parent_value, &child_value);
7143 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched1);
7144 assert_eq!(parent.0.color(), lua_gc::Color::Gray);
7145 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7146
7147 let metatable = state.new_table();
7148 parent.set_metatable(Some(metatable));
7149 state.gc().obj_barrier(&parent, &metatable);
7150 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old0);
7151
7152 assert!(state.gc().generational_step_minor_only());
7153 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched2);
7154 assert_eq!(child.0.age(), lua_gc::GcAge::Survival);
7155 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old1);
7156 assert_eq!(state.global().gc_estimate, majorbase);
7157 assert!(state.global().gc_debt() <= 0);
7158
7159 state.gc().change_mode(GcKind::Incremental);
7160 assert_eq!(parent.0.age(), lua_gc::GcAge::New);
7161 assert_eq!(child.0.age(), lua_gc::GcAge::New);
7162 assert_eq!(metatable.0.age(), lua_gc::GcAge::New);
7163
7164 assert!(state.external_unroot_value(parent_key).is_some());
7165 state.gc().full_collect();
7166 }
7167
7168 #[test]
7169 fn generational_upvalue_write_barrier_marks_young_child_old0() {
7170 let mut state = new_state().expect("state should initialize");
7171 let _heap_guard = {
7172 let g = state.global();
7173 lua_gc::HeapGuard::push(&g.heap)
7174 };
7175
7176 let proto = state.new_proto();
7177 let closure = state.new_lclosure(proto, 1);
7178 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7179 state.gc().change_mode(GcKind::Generational);
7180 let uv = closure.upval(0);
7181 assert_eq!(uv.0.age(), lua_gc::GcAge::Old);
7182
7183 let child = state.new_table();
7184 state
7185 .upvalue_set(&closure, 0, LuaValue::Table(child))
7186 .expect("closed upvalue write should succeed");
7187 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7188
7189 assert!(state.external_unroot_value(closure_key).is_some());
7190 state.gc().full_collect();
7191 }
7192
7193 #[test]
7194 fn cclosure_setupvalue_replaces_upvalue() {
7195 let mut state = new_state().expect("state should initialize");
7196 let _heap_guard = {
7197 let g = state.global();
7198 lua_gc::HeapGuard::push(&g.heap)
7199 };
7200
7201 let first = state.new_table();
7202 state.push(LuaValue::Table(first));
7203 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7204 .expect("C closure creation should succeed");
7205 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7206 panic!("expected heavy C closure");
7207 };
7208
7209 let second = state.new_table();
7210 state.push(LuaValue::Table(second));
7211 let name =
7212 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7213
7214 assert!(name.is_empty());
7215 let upvalues = ccl.upvalues.borrow();
7216 let LuaValue::Table(actual) = upvalues[0].clone() else {
7217 panic!("expected table upvalue");
7218 };
7219 assert_eq!(actual.identity(), second.identity());
7220 }
7221
7222 #[test]
7223 fn generational_cclosure_setupvalue_barrier_marks_young_child_old0() {
7224 let mut state = new_state().expect("state should initialize");
7225 let _heap_guard = {
7226 let g = state.global();
7227 lua_gc::HeapGuard::push(&g.heap)
7228 };
7229
7230 state.push(LuaValue::Nil);
7231 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
7232 .expect("C closure creation should succeed");
7233 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
7234 panic!("expected heavy C closure");
7235 };
7236 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
7237
7238 state.gc().change_mode(GcKind::Generational);
7239 assert_eq!(ccl.0.age(), lua_gc::GcAge::Old);
7240
7241 let child = state.new_table();
7242 state.push(LuaValue::Table(child));
7243 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
7244
7245 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
7246
7247 assert!(state.external_unroot_value(closure_key).is_some());
7248 state.gc().full_collect();
7249 }
7250
7251 #[test]
7252 fn generational_closure_upvalue_slot_barrier_marks_new_upval_old0() {
7253 let mut state = new_state().expect("state should initialize");
7254 let _heap_guard = {
7255 let g = state.global();
7256 lua_gc::HeapGuard::push(&g.heap)
7257 };
7258
7259 let proto = state.new_proto();
7260 let closure = state.new_lclosure(proto, 1);
7261 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
7262 state.gc().change_mode(GcKind::Generational);
7263 assert_eq!(closure.0.age(), lua_gc::GcAge::Old);
7264
7265 let replacement = state.new_upval_closed(LuaValue::Nil);
7266 closure.set_upval(0, replacement);
7267 state.gc().obj_barrier(&closure, &replacement);
7268 assert_eq!(replacement.0.age(), lua_gc::GcAge::Old0);
7269
7270 assert!(state.external_unroot_value(closure_key).is_some());
7271 state.gc().full_collect();
7272 }
7273
7274 #[test]
7275 fn cross_thread_upvalue_mirror_traces_values_as_roots() {
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 let mirrored = state.new_table();
7283 state
7284 .global_mut()
7285 .cross_thread_upvals
7286 .insert((999, StackIdx(0)), LuaValue::Table(mirrored));
7287
7288 state.gc().full_collect();
7289 assert_eq!(state.global().heap.allgc_count(), 1);
7290
7291 state.global_mut().cross_thread_upvals.clear();
7292 state.gc().full_collect();
7293 assert_eq!(state.global().heap.allgc_count(), 0);
7294 }
7295
7296 #[test]
7297 fn generational_full_collect_promotes_new_survivors_to_old() {
7298 let mut state = new_state().expect("state should initialize");
7299 let _heap_guard = {
7300 let g = state.global();
7301 lua_gc::HeapGuard::push(&g.heap)
7302 };
7303
7304 state.gc().change_mode(GcKind::Generational);
7305 let table = state.new_table();
7306 let table_key = state.external_root_value(LuaValue::Table(table));
7307 assert_eq!(table.0.age(), lua_gc::GcAge::New);
7308
7309 state.gc().full_collect();
7310 assert_eq!(table.0.age(), lua_gc::GcAge::Old);
7311 assert_eq!(table.0.color(), lua_gc::Color::Black);
7312
7313 assert!(state.external_unroot_value(table_key).is_some());
7314 state.gc().full_collect();
7315 }
7316
7317 #[test]
7318 fn gc_packed_params_return_user_visible_values() {
7319 let mut state = new_state().expect("state should initialize");
7320 assert_eq!(
7321 crate::api::gc(&mut state, crate::api::GcArgs::SetPause { value: 200 }),
7322 200
7323 );
7324 assert_eq!(state.global().gc_pause_param(), 200);
7325 assert_eq!(
7326 crate::api::gc(&mut state, crate::api::GcArgs::SetStepMul { value: 200 }),
7327 100
7328 );
7329 assert_eq!(state.global().gc_stepmul_param(), 200);
7330
7331 crate::api::gc(
7332 &mut state,
7333 crate::api::GcArgs::Gen {
7334 minormul: 0,
7335 majormul: 200,
7336 },
7337 );
7338 assert_eq!(state.global().gc_genmajormul_param(), 200);
7339 }
7340
7341 #[test]
7342 fn generational_step_runs_bad_major_when_growth_exceeds_genmajormul() {
7343 let mut state = new_state().expect("state should initialize");
7344 let _heap_guard = {
7345 let g = state.global();
7346 lua_gc::HeapGuard::push(&g.heap)
7347 };
7348
7349 let root = state.new_table();
7350 let root_key = state.external_root_value(LuaValue::Table(root));
7351 state.gc().change_mode(GcKind::Generational);
7352
7353 let root_value = LuaValue::Table(root);
7354 for i in 1..=64 {
7355 let child = state.new_table();
7356 let child_value = LuaValue::Table(child);
7357 root.raw_set_int(&mut state, i, child_value.clone())
7358 .expect("table store should succeed");
7359 state.gc_barrier_back(&root_value, &child_value);
7360 }
7361
7362 {
7363 let mut g = state.global_mut();
7364 g.gc_estimate = 1;
7365 set_debt(&mut *g, 1);
7366 }
7367
7368 assert!(state.gc().generational_step());
7369 let g = state.global();
7370 assert!(g.is_gen_mode());
7371 assert!(
7372 g.lastatomic > 0,
7373 "bad major collection should arm stepgenfull"
7374 );
7375 assert!(g.gc_estimate > 1);
7376 assert!(g.gc_debt() <= 0);
7377 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
7378 drop(g);
7379
7380 assert!(state.external_unroot_value(root_key).is_some());
7381 state.gc().full_collect();
7382 }
7383
7384 #[test]
7385 fn generational_implicit_step_runs_major_when_heap_threshold_exceeded() {
7386 let mut state = new_state().expect("state should initialize");
7387 let _heap_guard = {
7388 let g = state.global();
7389 lua_gc::HeapGuard::push(&g.heap)
7390 };
7391
7392 let root = state.new_table();
7393 let root_key = state.external_root_value(LuaValue::Table(root));
7394 state.gc().change_mode(GcKind::Generational);
7395
7396 let root_value = LuaValue::Table(root);
7397 for i in 1..=64 {
7398 let child = state.new_table();
7399 let child_value = LuaValue::Table(child);
7400 root.raw_set_int(&mut state, i, child_value.clone())
7401 .expect("table store should succeed");
7402 state.gc_barrier_back(&root_value, &child_value);
7403 }
7404
7405 {
7406 let mut g = state.global_mut();
7407 g.gc_estimate = 1;
7408 set_debt(&mut *g, -1);
7409 g.heap.set_threshold_bytes(1);
7410 }
7411
7412 assert!(state.gc().generational_step());
7413 let g = state.global();
7414 assert!(g.is_gen_mode());
7415 assert!(
7416 g.lastatomic > 0,
7417 "implicit threshold-triggered growth should arm a bad major"
7418 );
7419 assert!(g.gc_debt() <= 0);
7420 drop(g);
7421
7422 assert!(state.external_unroot_value(root_key).is_some());
7423 state.gc().full_collect();
7424 }
7425
7426 #[test]
7427 fn generational_stepgenfull_returns_to_gen_after_good_collection() {
7428 let mut state = new_state().expect("state should initialize");
7429 let _heap_guard = {
7430 let g = state.global();
7431 lua_gc::HeapGuard::push(&g.heap)
7432 };
7433
7434 let root = state.new_table();
7435 let root_key = state.external_root_value(LuaValue::Table(root));
7436 state.gc().change_mode(GcKind::Generational);
7437 {
7438 let mut g = state.global_mut();
7439 g.lastatomic = 1024;
7440 }
7441
7442 assert!(state.gc().generational_step());
7443 let g = state.global();
7444 assert_eq!(g.gckind, GcKind::Generational as u8);
7445 assert_eq!(g.lastatomic, 0);
7446 assert!(g.gc_debt() <= 0);
7447 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
7448 assert_eq!(root.0.color(), lua_gc::Color::Black);
7449 drop(g);
7450
7451 assert!(state.external_unroot_value(root_key).is_some());
7452 state.gc().full_collect();
7453 }
7454
7455 #[test]
7456 fn generational_step_zero_reports_false_without_positive_debt() {
7457 let mut state = new_state().expect("state should initialize");
7458 let _heap_guard = {
7459 let g = state.global();
7460 lua_gc::HeapGuard::push(&g.heap)
7461 };
7462
7463 state.gc().change_mode(GcKind::Generational);
7464 assert_eq!(
7465 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 0 }),
7466 0
7467 );
7468 assert_eq!(
7469 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 1 }),
7470 1
7471 );
7472 }
7473}
7474
7475