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
600#[cfg(target_pointer_width = "64")]
609const _: () = {
610 assert!(core::mem::size_of::<CallInfoFrame>() == 32);
611 assert!(core::mem::size_of::<CallInfo>() == 72);
612};
613
614#[derive(Clone, Copy)]
631pub struct CallInfoFrame {
632 pub savedpc: u32,
635 pub nextraargs: i32,
638 pub k: Option<LuaKFunction>,
641 pub old_errfunc: isize,
644 pub ctx: isize,
647}
648
649pub type LuaKFunction = fn(&mut LuaState, status: i32, ctx: isize) -> Result<usize, LuaError>;
651
652#[derive(Default, Clone, Copy)]
656pub struct CallInfoExtra {
657 pub value: i32,
658 pub ftransfer: u16,
659 pub ntransfer: u16,
660}
661
662impl CallInfoFrame {
663 pub fn c_default() -> Self {
667 CallInfoFrame {
668 savedpc: 0,
669 nextraargs: 0,
670 k: None,
671 old_errfunc: 0,
672 ctx: 0,
673 }
674 }
675
676 pub fn lua_default() -> Self {
683 CallInfoFrame {
684 savedpc: 0,
685 nextraargs: 0,
686 k: None,
687 old_errfunc: 0,
688 ctx: 0,
689 }
690 }
691}
692
693impl Default for CallInfo {
694 fn default() -> Self {
695 CallInfo {
696 func: StackIdx(0),
697 top: StackIdx(0),
698 previous: None,
699 next: None,
700 u: CallInfoFrame::c_default(),
701 u2: CallInfoExtra::default(),
702 nresults: 0,
703 callstatus: 0,
704 call_metamethods: 0,
705 }
706 }
707}
708
709impl CallInfo {
710 pub fn is_lua(&self) -> bool {
711 (self.callstatus & CIST_C) == 0
712 }
713 pub fn is_lua_code(&self) -> bool {
714 self.is_lua()
715 }
716 pub fn is_vararg_func(&self) -> bool {
723 false
724 }
725 pub fn saved_pc(&self) -> u32 {
730 debug_assert!(self.is_lua(), "saved_pc on a C frame");
731 self.u.savedpc
732 }
733 pub fn set_saved_pc(&mut self, pc: u32) {
735 debug_assert!(self.is_lua(), "set_saved_pc on a C frame");
736 self.u.savedpc = pc;
737 }
738 pub fn nextra_args(&self) -> i32 {
740 debug_assert!(self.is_lua(), "nextra_args on a C frame");
741 self.u.nextraargs
742 }
743 pub fn set_nextra_args(&mut self, n: i32) {
745 debug_assert!(self.is_lua(), "set_nextra_args on a C frame");
746 self.u.nextraargs = n;
747 }
748 pub fn transfer_ftransfer(&self) -> u16 {
749 self.u2.ftransfer
750 }
751 pub fn transfer_ntransfer(&self) -> u16 {
752 self.u2.ntransfer
753 }
754 pub fn set_trap(&mut self, t: bool) {
762 debug_assert!(self.is_lua(), "set_trap on a C frame");
763 if t {
764 self.callstatus |= CIST_TRAP;
765 } else {
766 self.callstatus &= !CIST_TRAP;
767 }
768 }
769 #[inline(always)]
773 pub fn trap(&self) -> bool {
774 (self.callstatus & CIST_TRAP) != 0
775 }
776 pub fn recover_status(&self) -> i32 {
779 ((self.callstatus >> CIST_RECST) & 7) as i32
780 }
781 pub fn set_recover_status<T: Into<i32>>(&mut self, status: T) {
784 let st = (status.into() & 7) as u16;
785 self.callstatus = (self.callstatus & !(7u16 << CIST_RECST)) | (st << CIST_RECST);
786 }
787 pub fn get_oah(&self) -> bool {
788 (self.callstatus & CIST_OAH) != 0
789 }
790 pub fn set_oah(&mut self, allow: bool) {
793 self.callstatus = (self.callstatus & !CIST_OAH) | (if allow { CIST_OAH } else { 0 });
794 }
795 pub fn u_c_old_errfunc(&self) -> isize {
800 debug_assert!(!self.is_lua(), "u_c_old_errfunc on a Lua frame");
801 self.u.old_errfunc
802 }
803 pub fn u_c_ctx(&self) -> isize {
805 debug_assert!(!self.is_lua(), "u_c_ctx on a Lua frame");
806 self.u.ctx
807 }
808 pub fn u_c_k(&self) -> Option<LuaKFunction> {
810 debug_assert!(!self.is_lua(), "u_c_k on a Lua frame");
811 self.u.k
812 }
813 pub fn set_u_c_k(&mut self, k: Option<LuaKFunction>) {
819 debug_assert!(!self.is_lua(), "set_u_c_k on a Lua frame");
820 self.u.k = k;
821 }
822 pub fn set_u_c_ctx(&mut self, ctx: isize) {
824 debug_assert!(!self.is_lua(), "set_u_c_ctx on a Lua frame");
825 self.u.ctx = ctx;
826 }
827 pub fn set_u_c_old_errfunc(&mut self, old_errfunc: isize) {
829 debug_assert!(!self.is_lua(), "set_u_c_old_errfunc on a Lua frame");
830 self.u.old_errfunc = old_errfunc;
831 }
832 pub fn set_u2_funcidx(&mut self, idx: i32) {
835 self.u2.value = idx;
836 }
837}
838
839pub trait LuaValueExt {
845 fn base_type(&self) -> lua_types::LuaType;
846 fn to_number_no_strconv(&self) -> Option<f64>;
847 fn to_number_with_strconv(&self) -> Option<f64>;
848 fn to_integer_no_strconv(&self) -> Option<i64>;
849 fn to_integer_with_strconv(&self) -> Option<i64>;
850 fn full_type_tag(&self) -> u8;
851}
852
853impl LuaValueExt for LuaValue {
854 fn base_type(&self) -> lua_types::LuaType {
855 self.type_tag()
856 }
857 fn to_number_no_strconv(&self) -> Option<f64> {
858 match self {
859 LuaValue::Float(f) => Some(*f),
860 LuaValue::Int(i) => Some(*i as f64),
861 _ => None,
862 }
863 }
864 fn to_number_with_strconv(&self) -> Option<f64> {
865 if let Some(n) = self.to_number_no_strconv() {
866 return Some(n);
867 }
868 if let LuaValue::Str(s) = self {
869 let mut tmp = LuaValue::Nil;
870 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
871 if sz == 0 {
872 return None;
873 }
874 return match tmp {
875 LuaValue::Int(i) => Some(i as f64),
876 LuaValue::Float(f) => Some(f),
877 _ => None,
878 };
879 }
880 None
881 }
882 fn to_integer_no_strconv(&self) -> Option<i64> {
883 match self {
884 LuaValue::Int(i) => Some(*i),
885 LuaValue::Float(f) if f.fract() == 0.0 && f.is_finite() => {
886 let min_f = i64::MIN as f64;
890 let max_plus1_f = -(i64::MIN as f64);
891 if *f >= min_f && *f < max_plus1_f {
892 Some(*f as i64)
893 } else {
894 None
895 }
896 }
897 _ => None,
898 }
899 }
900 fn to_integer_with_strconv(&self) -> Option<i64> {
901 if let Some(i) = self.to_integer_no_strconv() {
902 return Some(i);
903 }
904 if let LuaValue::Str(s) = self {
905 let mut tmp = LuaValue::Nil;
906 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
907 if sz == 0 {
908 return None;
909 }
910 return tmp.to_integer_no_strconv();
911 }
912 None
913 }
914 fn full_type_tag(&self) -> u8 {
915 match self {
916 LuaValue::Nil => 0x00,
917 LuaValue::Bool(false) => 0x01,
918 LuaValue::Bool(true) => 0x11,
919 LuaValue::Int(_) => 0x03,
920 LuaValue::Float(_) => 0x13,
921 LuaValue::Str(s) if s.is_short() => 0x04,
922 LuaValue::Str(_) => 0x14,
923 LuaValue::LightUserData(_) => 0x02,
924 LuaValue::Table(_) => 0x05,
925 LuaValue::Function(LuaClosure::Lua(_)) => 0x06,
926 LuaValue::Function(LuaClosure::LightC(_)) => 0x16,
927 LuaValue::Function(LuaClosure::C(_)) => 0x26,
928 LuaValue::UserData(_) => 0x07,
929 LuaValue::Thread(_) => 0x08,
930 }
931 }
932}
933
934pub trait LuaTypeExt {
936 fn type_name(&self) -> &'static [u8];
937}
938
939impl LuaTypeExt for lua_types::LuaType {
940 fn type_name(&self) -> &'static [u8] {
941 use lua_types::LuaType::*;
942 match self {
943 None => b"no value",
944 Nil => b"nil",
945 Boolean => b"boolean",
946 LightUserData => b"userdata",
947 Number => b"number",
948 String => b"string",
949 Table => b"table",
950 Function => b"function",
951 UserData => b"userdata",
952 Thread => b"thread",
953 }
954 }
955}
956
957pub trait StackIdxExt {
961 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32;
962 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32;
963 fn raw(self) -> u32;
964}
965impl StackIdxExt for StackIdx {
966 #[inline(always)]
967 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32 {
968 self.0.saturating_sub(n.into().0 .0)
969 }
970 #[inline(always)]
971 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32 {
972 self.0.wrapping_sub(n.into().0 .0)
973 }
974 #[inline(always)]
975 fn raw(self) -> u32 {
976 self.0
977 }
978}
979
980pub trait LuaTableRefExt {
990 fn metatable(&self) -> Option<GcRef<LuaTable>>;
991 fn has_metatable(&self) -> bool;
992 fn as_ptr(&self) -> *const ();
993 fn get(&self, _k: &LuaValue) -> LuaValue;
994 fn get_int(&self, _k: i64) -> LuaValue;
995 fn get_short_str(&self, _k: &GcRef<LuaString>) -> LuaValue;
996 fn raw_set(&self, _state: &mut LuaState, _k: LuaValue, _v: LuaValue) -> Result<(), LuaError>;
997 fn raw_set_int(&self, _state: &mut LuaState, _k: i64, _v: LuaValue) -> Result<(), LuaError>;
998 fn raw_set_short_str(
999 &self,
1000 _state: &mut LuaState,
1001 _k: GcRef<LuaString>,
1002 _v: LuaValue,
1003 ) -> Result<(), LuaError>;
1004 fn invalidate_tm_cache(&self);
1005 fn resize(&self, _state: &mut LuaState, _na: usize, _nh: usize) -> Result<(), LuaError>;
1006 fn next(&self, _k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError>;
1007}
1008impl LuaTableRefExt for GcRef<LuaTable> {
1009 #[inline]
1010 fn metatable(&self) -> Option<GcRef<LuaTable>> {
1011 (**self).metatable()
1012 }
1013 #[inline]
1014 fn has_metatable(&self) -> bool {
1015 (**self).has_metatable()
1016 }
1017 #[inline]
1018 fn as_ptr(&self) -> *const () {
1019 GcRef::identity(self) as *const ()
1020 }
1021 #[inline]
1022 fn get(&self, k: &LuaValue) -> LuaValue {
1023 (**self).get(k)
1024 }
1025 #[inline]
1026 fn get_int(&self, k: i64) -> LuaValue {
1027 (**self).get_int(k)
1028 }
1029 #[inline]
1030 fn get_short_str(&self, k: &GcRef<LuaString>) -> LuaValue {
1031 (**self).get_short_str(k)
1032 }
1033 #[inline(always)]
1042 fn raw_set(&self, state: &mut LuaState, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
1043 match k {
1044 LuaValue::Int(i) => return self.raw_set_int(state, i, v),
1045 LuaValue::Str(s) if s.is_short() => return self.raw_set_short_str(state, s, v),
1046 k => {
1047 if k.is_collectable() {
1048 state.gc_table_barrier_back(self, &k);
1049 }
1050 let before = (**self).buffer_bytes();
1051 let result = (**self).try_raw_set(k, v);
1052 if result.is_ok() {
1053 account_table_buffer_delta(self, before);
1054 }
1055 result
1056 }
1057 }
1058 }
1059 #[inline(always)]
1060 fn raw_set_int(&self, _state: &mut LuaState, k: i64, v: LuaValue) -> Result<(), LuaError> {
1061 match (**self).try_update_int(k, v) {
1062 Ok(()) => Ok(()),
1063 Err(v) => {
1064 let before = (**self).buffer_bytes();
1065 let result = (**self).try_raw_set_int(k, v);
1066 if result.is_ok() {
1067 account_table_buffer_delta(self, before);
1068 }
1069 result
1070 }
1071 }
1072 }
1073 #[inline(always)]
1077 fn raw_set_short_str(
1078 &self,
1079 state: &mut LuaState,
1080 k: GcRef<LuaString>,
1081 v: LuaValue,
1082 ) -> Result<(), LuaError> {
1083 match (**self).try_update_short_str(&k, v) {
1084 Ok(()) => Ok(()),
1085 Err(v) => {
1086 state.gc_table_barrier_back(self, &LuaValue::Str(k));
1087 let before = (**self).buffer_bytes();
1088 let result = (**self).try_raw_set(LuaValue::Str(k), v);
1089 if result.is_ok() {
1090 account_table_buffer_delta(self, before);
1091 }
1092 result
1093 }
1094 }
1095 }
1096 fn invalidate_tm_cache(&self) {}
1097 fn resize(&self, _state: &mut LuaState, na: usize, nh: usize) -> Result<(), LuaError> {
1098 let before = (**self).buffer_bytes();
1099 let na32 = na.min(u32::MAX as usize) as u32;
1100 let nh32 = nh.min(u32::MAX as usize) as u32;
1101 let result = (**self).resize(na32, nh32);
1102 if result.is_ok() {
1103 account_table_buffer_delta(self, before);
1104 }
1105 result
1106 }
1107 fn next(&self, k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
1108 (**self).try_next_pair(&k)
1109 }
1110}
1111
1112#[inline]
1113fn account_table_buffer_delta(t: &GcRef<LuaTable>, before: usize) {
1114 let after = (**t).buffer_bytes();
1115 if after > before {
1116 t.account_buffer((after - before) as isize);
1117 } else if before > after {
1118 t.account_buffer(-((before - after) as isize));
1119 }
1120}
1121
1122pub trait LuaUserDataRefExt {
1123 fn metatable(&self) -> Option<GcRef<LuaTable>>;
1124 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>);
1125 fn as_ptr(&self) -> *const ();
1126 fn len(&self) -> usize;
1127}
1128impl LuaUserDataRefExt for GcRef<LuaUserData> {
1129 fn metatable(&self) -> Option<GcRef<LuaTable>> {
1130 (**self).metatable()
1131 }
1132 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>) {
1133 (**self).set_metatable(mt);
1134 }
1135 fn as_ptr(&self) -> *const () {
1136 GcRef::identity(self) as *const ()
1137 }
1138 fn len(&self) -> usize {
1139 self.0.data.len()
1140 }
1141}
1142
1143pub trait LuaStringRefExt {
1144 fn is_white(&self) -> bool;
1145 fn hash(&self) -> u32;
1146 fn as_gc_ref(&self) -> GcRef<LuaString>;
1147}
1148impl LuaStringRefExt for GcRef<LuaString> {
1149 fn is_white(&self) -> bool {
1150 false
1151 }
1152 fn hash(&self) -> u32 {
1153 self.0.hash()
1154 }
1155 fn as_gc_ref(&self) -> GcRef<LuaString> {
1156 self.clone()
1157 }
1158}
1159
1160pub trait LuaLClosureRefExt {
1161 fn proto(&self) -> &GcRef<LuaProto>;
1162 fn nupvalues(&self) -> usize;
1163}
1164impl LuaLClosureRefExt for GcRef<lua_types::closure::LuaLClosure> {
1165 fn proto(&self) -> &GcRef<LuaProto> {
1166 &self.0.proto
1167 }
1168 fn nupvalues(&self) -> usize {
1169 self.0.upvals.len()
1170 }
1171}
1172
1173pub trait LuaClosureExt {
1175 fn nupvalues(&self) -> usize;
1176}
1177impl LuaClosureExt for LuaClosure {
1178 fn nupvalues(&self) -> usize {
1179 match self {
1180 LuaClosure::Lua(l) => l.0.upvals.len(),
1181 LuaClosure::C(c) => c.0.upvalues.borrow().len(),
1182 LuaClosure::LightC(_) => 0,
1183 }
1184 }
1185}
1186
1187pub trait LuaProtoExt {
1189 fn source_bytes(&self) -> &[u8];
1190 fn source_string(&self) -> Option<&GcRef<LuaString>>;
1191}
1192impl LuaProtoExt for LuaProto {
1193 fn source_bytes(&self) -> &[u8] {
1194 match &self.source {
1195 Some(s) => s.0.as_bytes(),
1196 None => &[],
1197 }
1198 }
1199 fn source_string(&self) -> Option<&GcRef<LuaString>> {
1200 self.source.as_ref()
1201 }
1202}
1203
1204pub trait Collectable: std::fmt::Debug {}
1211
1212impl std::fmt::Debug for LuaState {
1213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1214 write!(f, "LuaState")
1215 }
1216}
1217impl Collectable for LuaState {}
1218
1219pub type ParserHook = fn(
1228 state: &mut LuaState,
1229 source: &[u8],
1230 name: &[u8],
1231 firstchar: i32,
1232) -> Result<GcRef<lua_types::closure::LuaLClosure>, LuaError>;
1233
1234pub type FileLoaderHook = fn(filename: &[u8]) -> Result<Vec<u8>, LuaError>;
1242
1243pub type FileOpenHook =
1254 fn(filename: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1255
1256pub type OutputHook = fn(bytes: &[u8]) -> std::io::Result<()>;
1264
1265pub type InputHook = fn(buf: &mut [u8]) -> std::io::Result<usize>;
1268
1269pub type EnvHook = fn(name: &[u8]) -> Option<Vec<u8>>;
1275
1276pub type UnixTimeHook = fn() -> i64;
1278
1279pub type CpuClockHook = fn() -> f64;
1287
1288pub type LocalOffsetHook = fn(timestamp: i64) -> i64;
1300
1301pub type EntropyHook = fn() -> u64;
1305
1306pub type TempNameHook = fn() -> Result<Vec<u8>, LuaError>;
1311
1312pub type PopenHook =
1323 fn(cmd: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1324
1325pub type FileRemoveHook = fn(filename: &[u8]) -> Result<(), LuaError>;
1331
1332pub type FileRenameHook = fn(from: &[u8], to: &[u8]) -> Result<(), LuaError>;
1338
1339#[derive(Clone, Copy, Debug)]
1345pub enum OsExecuteReason {
1346 Exit,
1348 Signal,
1350}
1351
1352#[derive(Debug)]
1355pub struct OsExecuteResult {
1356 pub success: bool,
1358 pub reason: OsExecuteReason,
1360 pub code: i32,
1362}
1363
1364pub type OsExecuteHook = fn(cmd: &[u8]) -> Result<OsExecuteResult, LuaError>;
1371
1372#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1379pub struct DynLibId(pub u64);
1380
1381pub enum DynamicSymbol {
1389 RustNative(LuaCFunction),
1392 LuaCAbi(*const ()),
1398 Unsupported { reason: Vec<u8> },
1401}
1402
1403pub type DynLibLoadHook =
1414 fn(state: &mut LuaState, path: &[u8], see_global: bool) -> Result<DynLibId, LuaError>;
1415
1416pub type DynLibSymbolHook =
1424 fn(state: &mut LuaState, handle: DynLibId, symbol: &[u8]) -> Result<DynamicSymbol, LuaError>;
1425
1426pub type DynLibUnloadHook = fn(handle: DynLibId);
1434
1435pub struct ThreadRegistryEntry {
1441 pub state: Rc<RefCell<LuaState>>,
1446 pub value: GcRef<lua_types::value::LuaThread>,
1449}
1450
1451#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1456pub struct ExternalRootKey {
1457 index: usize,
1458 generation: u64,
1459}
1460
1461#[derive(Debug)]
1462struct ExternalRootSlot {
1463 value: Option<LuaValue>,
1464 generation: u64,
1465}
1466
1467#[derive(Debug, Default)]
1473pub struct ExternalRootSet {
1474 slots: Vec<ExternalRootSlot>,
1475 free: Vec<usize>,
1476 live: usize,
1477}
1478
1479impl ExternalRootSet {
1480 pub fn insert(&mut self, value: LuaValue) -> ExternalRootKey {
1481 if let Some(index) = self.free.pop() {
1482 let slot = &mut self.slots[index];
1483 debug_assert!(slot.value.is_none(), "free external-root slot is occupied");
1484 slot.generation = slot.generation.wrapping_add(1).max(1);
1485 slot.value = Some(value);
1486 self.live += 1;
1487 ExternalRootKey {
1488 index,
1489 generation: slot.generation,
1490 }
1491 } else {
1492 let index = self.slots.len();
1493 self.slots.push(ExternalRootSlot {
1494 value: Some(value),
1495 generation: 1,
1496 });
1497 self.live += 1;
1498 ExternalRootKey {
1499 index,
1500 generation: 1,
1501 }
1502 }
1503 }
1504
1505 pub fn get(&self, key: ExternalRootKey) -> Option<&LuaValue> {
1506 let slot = self.slots.get(key.index)?;
1507 if slot.generation == key.generation {
1508 slot.value.as_ref()
1509 } else {
1510 None
1511 }
1512 }
1513
1514 pub fn replace(&mut self, key: ExternalRootKey, value: LuaValue) -> Option<LuaValue> {
1515 let slot = self.slots.get_mut(key.index)?;
1516 if slot.generation != key.generation || slot.value.is_none() {
1517 return None;
1518 }
1519 slot.value.replace(value)
1520 }
1521
1522 pub fn remove(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
1523 let slot = self.slots.get_mut(key.index)?;
1524 if slot.generation != key.generation {
1525 return None;
1526 }
1527 let old = slot.value.take()?;
1528 self.free.push(key.index);
1529 self.live -= 1;
1530 Some(old)
1531 }
1532
1533 pub fn iter_values(&self) -> impl Iterator<Item = &LuaValue> {
1534 self.slots.iter().filter_map(|slot| slot.value.as_ref())
1535 }
1536
1537 pub fn len(&self) -> usize {
1538 self.live
1539 }
1540
1541 pub fn is_empty(&self) -> bool {
1542 self.live == 0
1543 }
1544
1545 pub fn vacant_len(&self) -> usize {
1546 self.free.len()
1547 }
1548}
1549
1550pub struct GlobalState {
1556 pub parser_hook: Option<ParserHook>,
1562
1563 pub cli_argv: Option<Vec<Vec<u8>>>,
1570
1571 pub cli_preload: Option<fn(&mut LuaState) -> Result<(), LuaError>>,
1575
1576 pub lua_version: lua_types::LuaVersion,
1583
1584 pub file_loader_hook: Option<FileLoaderHook>,
1589
1590 pub file_open_hook: Option<FileOpenHook>,
1595
1596 pub stdout_hook: Option<OutputHook>,
1600
1601 pub stderr_hook: Option<OutputHook>,
1603
1604 pub stdin_hook: Option<InputHook>,
1607
1608 pub env_hook: Option<EnvHook>,
1610
1611 pub unix_time_hook: Option<UnixTimeHook>,
1614
1615 pub cpu_clock_hook: Option<CpuClockHook>,
1618
1619 pub local_offset_hook: Option<LocalOffsetHook>,
1624
1625 pub entropy_hook: Option<EntropyHook>,
1628
1629 pub temp_name_hook: Option<TempNameHook>,
1631
1632 pub popen_hook: Option<PopenHook>,
1637
1638 pub file_remove_hook: Option<FileRemoveHook>,
1641
1642 pub file_rename_hook: Option<FileRenameHook>,
1645
1646 pub os_execute_hook: Option<OsExecuteHook>,
1650
1651 pub dynlib_load_hook: Option<DynLibLoadHook>,
1656
1657 pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
1661
1662 pub dynlib_unload_hook: Option<DynLibUnloadHook>,
1666
1667 pub sandbox: SandboxLimits,
1670
1671 pub gc_debt: isize,
1673
1674 pub gc_estimate: usize,
1675
1676 pub lastatomic: usize,
1678
1679 pub strt: StringPool,
1681
1682 pub l_registry: LuaValue,
1684
1685 pub external_roots: ExternalRootSet,
1688
1689 pub globals: LuaValue,
1696 pub loaded: LuaValue,
1697
1698 pub nilvalue: LuaValue,
1702
1703 pub seed: u32,
1705
1706 pub currentwhite: u8,
1708
1709 pub gcstate: u8,
1710
1711 pub gckind: u8,
1712
1713 pub gcstopem: bool,
1714
1715 pub genminormul: u8,
1717
1718 pub genmajormul: u8,
1719
1720 pub gcstp: u8,
1721
1722 pub gcemergency: bool,
1723
1724 pub gcpause: u8,
1726
1727 pub gcstepmul: u8,
1729
1730 pub gcstepsize: u8,
1731
1732 pub gc55_params: [i64; 6],
1741
1742 pub sweepgc_cursor: usize,
1747
1748 pub weak_tables_registry: lua_gc::WeakRegistry<WeakTableEntry>,
1757
1758 pub finalizers: lua_gc::FinalizerRegistry<FinalizerObject>,
1761
1762 pub gc_finalizer_error: Option<LuaValue>,
1773
1774 pub twups: Vec<GcRef<LuaState>>,
1784
1785 pub panic: Option<LuaCFunction>,
1787
1788 pub mainthread: Option<GcRef<LuaState>>,
1791
1792 pub threads: std::collections::HashMap<u64, ThreadRegistryEntry>,
1805
1806 pub main_thread_value: GcRef<lua_types::value::LuaThread>,
1811
1812 pub current_thread_id: u64,
1817
1818 pub closing_thread_id: Option<u64>,
1821
1822 pub main_thread_id: u64,
1825
1826 pub next_thread_id: u64,
1829
1830 pub memerrmsg: GcRef<LuaString>,
1832
1833 pub tmname: Vec<GcRef<LuaString>>,
1836
1837 pub mt: [Option<GcRef<LuaTable>>; LUA_NUMTYPES],
1839
1840 pub strcache: [[GcRef<LuaString>; STRCACHE_M]; STRCACHE_N],
1842
1843 pub interned_lt: InternedStringMap,
1849
1850 pub warnf: Option<Box<dyn FnMut(&[u8], bool)>>,
1852
1853 pub warn_mode: WarnMode,
1858
1859 pub test_warn_enabled: bool,
1864 pub test_warn_on: bool,
1865 pub test_warn_mode: TestWarnMode,
1866 pub test_warn_last_to_cont: bool,
1867 pub test_warn_buffer: Vec<u8>,
1868
1869 pub c_functions: Vec<LuaCallable>,
1874
1875 pub heap: lua_gc::Heap,
1880
1881 pub cross_thread_upvals: std::collections::HashMap<(u64, StackIdx), LuaValue>,
1895
1896 pub suspended_parent_stacks: Vec<Vec<LuaValue>>,
1910
1911 pub suspended_parent_open_upvals: Vec<Vec<GcRef<UpVal>>>,
1917
1918 pub snapshot_stack_pool: Vec<Vec<LuaValue>>,
1926 pub snapshot_upval_pool: Vec<Vec<GcRef<UpVal>>>,
1927
1928 pub resume_value_pool: Vec<Vec<LuaValue>>,
1936
1937 pub resume_upval_slot_pool: Vec<Vec<(u64, StackIdx)>>,
1943
1944 pub resume_flush_pool: Vec<Vec<(StackIdx, LuaValue)>>,
1948}
1949
1950const SANDBOX_COUNT_MASK: u8 = 1 << 3;
1953
1954pub const SANDBOX_TRIP_NONE: u8 = 0;
1956pub const SANDBOX_TRIP_INSTRUCTIONS: u8 = 1;
1958pub const SANDBOX_TRIP_MEMORY: u8 = 2;
1960
1961#[derive(Default)]
1968pub struct SandboxLimits {
1969 pub interval: std::cell::Cell<i32>,
1971 pub instr_limited: std::cell::Cell<bool>,
1973 pub instr_remaining: std::cell::Cell<u64>,
1975 pub instr_limit: std::cell::Cell<u64>,
1977 pub mem_limit: std::cell::Cell<Option<usize>>,
1979 pub tripped: std::cell::Cell<u8>,
1981 pub aborting: std::cell::Cell<bool>,
1986}
1987
1988impl GlobalState {
1989 pub fn sandbox_active(&self) -> bool {
1991 self.sandbox.interval.get() != 0
1992 }
1993
1994 pub fn total_bytes(&self) -> usize {
1999 self.heap.bytes_used().max(1)
2000 }
2001
2002 pub fn get_thread(&self, id: u64) -> Option<&ThreadRegistryEntry> {
2007 self.threads.get(&id)
2008 }
2009
2010 pub fn thread_value_for(&self, id: u64) -> Option<GcRef<lua_types::value::LuaThread>> {
2014 if id == self.main_thread_id {
2015 Some(self.main_thread_value.clone())
2016 } else {
2017 self.threads.get(&id).map(|e| e.value.clone())
2018 }
2019 }
2020
2021 pub fn is_complete(&self) -> bool {
2028 matches!(self.nilvalue, LuaValue::Nil)
2029 }
2030
2031 pub fn current_white(&self) -> u8 {
2039 self.currentwhite
2040 }
2041
2042 pub fn other_white(&self) -> u8 {
2046 self.currentwhite ^ 0x03
2047 }
2048
2049 pub fn is_gen_mode(&self) -> bool {
2053 self.gckind == GcKind::Generational as u8 || self.lastatomic != 0
2054 }
2055
2056 pub fn gc_running(&self) -> bool {
2060 self.gcstp == 0
2061 }
2062
2063 pub fn keep_invariant(&self) -> bool {
2067 self.heap.gc_state().is_invariant()
2068 }
2069
2070 pub fn is_sweep_phase(&self) -> bool {
2074 self.heap.gc_state().is_sweep()
2075 }
2076
2077 pub fn gc_debt(&self) -> isize {
2079 self.gc_debt
2080 }
2081 pub fn set_gc_debt(&mut self, d: isize) {
2082 self.gc_debt = d;
2083 }
2084 pub fn gc_at_pause(&self) -> bool {
2085 self.heap.gc_state().is_pause()
2086 }
2087 fn get_gc_param(p: u8) -> i32 {
2088 (p as i32) * 4
2089 }
2090 fn set_gc_param_slot(slot: &mut u8, p: i32) {
2091 *slot = (p / 4) as u8;
2092 }
2093 pub fn gc_pause_param(&self) -> i32 {
2094 Self::get_gc_param(self.gcpause)
2095 }
2096 pub fn set_gc_pause_param(&mut self, p: i32) {
2097 Self::set_gc_param_slot(&mut self.gcpause, p);
2098 }
2099 pub fn gc_stepmul_param(&self) -> i32 {
2100 Self::get_gc_param(self.gcstepmul)
2101 }
2102 pub fn set_gc_stepmul_param(&mut self, p: i32) {
2103 Self::set_gc_param_slot(&mut self.gcstepmul, p);
2104 }
2105 pub fn gc_genmajormul_param(&self) -> i32 {
2106 Self::get_gc_param(self.genmajormul)
2107 }
2108 pub fn set_gc_genmajormul(&mut self, p: i32) {
2109 Self::set_gc_param_slot(&mut self.genmajormul, p);
2110 }
2111 pub fn gc55_param(&mut self, idx: usize, value: i64) -> i64 {
2115 let old = self.gc55_params[idx];
2116 if value >= 0 {
2117 self.gc55_params[idx] = value;
2118 }
2119 old
2120 }
2121 pub fn gc_stop_flags(&self) -> u8 {
2122 self.gcstp
2123 }
2124 pub fn set_gc_stop_flags(&mut self, f: u8) {
2125 self.gcstp = f;
2126 }
2127 pub fn stop_gc_internal(&mut self) -> u8 {
2128 let old = self.gcstp;
2129 self.gcstp |= GCSTPGC;
2130 old
2131 }
2132 pub fn set_gc_stop_user(&mut self) {
2133 self.gcstp = GCSTPUSR;
2135 }
2136 pub fn clear_gc_stop(&mut self) {
2137 self.gcstp = 0;
2138 }
2139 pub fn is_gc_running(&self) -> bool {
2140 self.gcstp == 0
2141 }
2142 pub fn is_gc_stopped_internally(&self) -> bool {
2147 (self.gcstp & GCSTPGC) != 0
2148 }
2149
2150 pub fn tm_name<T: TmIndex>(&self, tm: T) -> Option<GcRef<LuaString>> {
2160 self.tmname.get(tm.tm_index()).cloned()
2161 }
2162}
2163
2164pub trait TmIndex: Copy {
2170 fn tm_index(self) -> usize;
2171}
2172impl TmIndex for lua_types::tagmethod::TagMethod {
2173 fn tm_index(self) -> usize {
2174 self as u8 as usize
2175 }
2176}
2177impl TmIndex for crate::tagmethods::TagMethod {
2178 fn tm_index(self) -> usize {
2179 self as u8 as usize
2180 }
2181}
2182impl TmIndex for usize {
2183 fn tm_index(self) -> usize {
2184 self
2185 }
2186}
2187impl TmIndex for u8 {
2188 fn tm_index(self) -> usize {
2189 self as usize
2190 }
2191}
2192
2193use lua_types::tagmethod::TagMethod;
2194
2195pub struct LuaState {
2205 pub status: u8,
2209
2210 pub allowhook: bool,
2212
2213 pub nci: u32,
2215
2216 pub top: StackIdx,
2220
2221 pub stack_last: StackIdx,
2223
2224 pub stack: Vec<StackValue>,
2226
2227 pub ci: CallInfoIdx,
2231
2232 pub call_info: Vec<CallInfo>,
2235
2236 pub openupval: Vec<GcRef<UpVal>>,
2240
2241 pub tbclist: Vec<StackIdx>,
2243
2244 pub(crate) global: Rc<RefCell<GlobalState>>,
2249
2250 pub hook: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>,
2254
2255 pub hookmask: u8,
2257
2258 pub basehookcount: i32,
2260
2261 pub hookcount: i32,
2263
2264 pub errfunc: isize,
2271
2272 pub n_ccalls: u32,
2276
2277 pub oldpc: u32,
2281
2282 pub marked: u8,
2286
2287 pub cached_thread_id: u64,
2303
2304 pub gc_check_needed: bool,
2309}
2310
2311impl LuaState {
2312 pub fn global(&self) -> std::cell::Ref<'_, GlobalState> {
2320 self.global.borrow()
2321 }
2322
2323 pub fn global_mut(&self) -> std::cell::RefMut<'_, GlobalState> {
2327 self.global.borrow_mut()
2328 }
2329
2330 pub fn global_rc(&self) -> Rc<RefCell<GlobalState>> {
2334 Rc::clone(&self.global)
2335 }
2336
2337 pub fn enable_test_warning_handler(&mut self) -> Result<(), LuaError> {
2339 {
2340 let mut g = self.global_mut();
2341 g.test_warn_enabled = true;
2342 g.test_warn_on = false;
2343 g.test_warn_mode = TestWarnMode::Normal;
2344 g.test_warn_last_to_cont = false;
2345 g.test_warn_buffer.clear();
2346 }
2347 self.push(LuaValue::Bool(false));
2348 crate::api::set_global(self, b"_WARN")
2349 }
2350
2351 pub fn c_calls(&self) -> u32 {
2355 self.n_ccalls & 0xffff
2356 }
2357
2358 pub fn inc_nny(&mut self) {
2362 self.n_ccalls += 0x10000;
2363 }
2364
2365 pub fn dec_nny(&mut self) {
2369 self.n_ccalls -= 0x10000;
2370 }
2371
2372 pub fn is_yieldable(&self) -> bool {
2376 (self.n_ccalls & 0xffff0000) == 0
2377 }
2378
2379 pub fn reset_hook_count(&mut self) {
2383 self.hookcount = self.basehookcount;
2384 }
2385
2386 pub fn install_sandbox_limits(
2395 &mut self,
2396 interval: i32,
2397 instr_limit: Option<u64>,
2398 mem_limit: Option<usize>,
2399 ) {
2400 let interval = interval.max(1);
2401 {
2402 let g = self.global();
2403 g.sandbox.interval.set(interval);
2404 g.sandbox.instr_limited.set(instr_limit.is_some());
2405 g.sandbox.instr_remaining.set(instr_limit.unwrap_or(0));
2406 g.sandbox.instr_limit.set(instr_limit.unwrap_or(0));
2407 g.sandbox.mem_limit.set(mem_limit);
2408 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2409 }
2410 self.hookmask |= SANDBOX_COUNT_MASK;
2411 self.basehookcount = interval;
2412 self.hookcount = interval;
2413 crate::debug::arm_traps(self);
2414 }
2415
2416 pub fn sandbox_charge_interval(&self) -> Option<LuaError> {
2421 let interval = self.global().sandbox.interval.get();
2422 self.sandbox_charge(interval as u64)
2423 }
2424
2425 pub fn sandbox_charge(&self, amount: u64) -> Option<LuaError> {
2434 let g = self.global();
2435 if g.sandbox.interval.get() == 0 {
2436 return None;
2437 }
2438 if g.sandbox.instr_limited.get() {
2439 let rem = g.sandbox.instr_remaining.get().saturating_sub(amount);
2440 g.sandbox.instr_remaining.set(rem);
2441 if rem == 0 {
2442 g.sandbox.tripped.set(SANDBOX_TRIP_INSTRUCTIONS);
2443 g.sandbox.aborting.set(true);
2444 return Some(LuaError::runtime(format_args!(
2445 "sandbox: instruction budget exhausted"
2446 )));
2447 }
2448 }
2449 if let Some(limit) = g.sandbox.mem_limit.get() {
2450 if g.total_bytes() > limit {
2451 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2452 g.sandbox.aborting.set(true);
2453 return Some(LuaError::runtime(format_args!(
2454 "sandbox: memory limit exceeded"
2455 )));
2456 }
2457 }
2458 None
2459 }
2460
2461 pub fn sandbox_reserve(&self, additional: usize) -> Option<LuaError> {
2469 let g = self.global();
2470 if g.sandbox.interval.get() == 0 {
2471 return None;
2472 }
2473 if let Some(limit) = g.sandbox.mem_limit.get() {
2474 let projected = g.total_bytes().saturating_add(additional);
2475 if projected > limit {
2476 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2477 g.sandbox.aborting.set(true);
2478 return Some(LuaError::runtime(format_args!(
2479 "sandbox: memory limit exceeded"
2480 )));
2481 }
2482 }
2483 None
2484 }
2485
2486 pub fn sandbox_match_step_limit(&self) -> u64 {
2491 let g = self.global();
2492 if g.sandbox.interval.get() != 0 && g.sandbox.instr_limited.get() {
2493 g.sandbox.instr_remaining.get()
2494 } else {
2495 0
2496 }
2497 }
2498
2499 pub fn sandbox_aborting(&self) -> bool {
2503 self.global().sandbox.aborting.get()
2504 }
2505
2506 pub fn sandbox_instr_limited(&self) -> bool {
2508 self.global().sandbox.instr_limited.get()
2509 }
2510
2511 pub fn sandbox_instr_remaining(&self) -> u64 {
2514 self.global().sandbox.instr_remaining.get()
2515 }
2516
2517 pub fn sandbox_instr_limit(&self) -> u64 {
2519 self.global().sandbox.instr_limit.get()
2520 }
2521
2522 pub fn sandbox_tripped_code(&self) -> u8 {
2524 self.global().sandbox.tripped.get()
2525 }
2526
2527 pub fn sandbox_reset(&self) {
2530 let g = self.global();
2531 if g.sandbox.instr_limited.get() {
2532 g.sandbox.instr_remaining.set(g.sandbox.instr_limit.get());
2533 }
2534 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2535 g.sandbox.aborting.set(false);
2536 }
2537
2538 pub fn stack_size(&self) -> usize {
2542 self.stack_last.0 as usize
2543 }
2544
2545 #[inline(always)]
2549 pub fn push(&mut self, val: LuaValue) {
2550 let top = self.top.0 as usize;
2551 if top < self.stack.len() {
2552 self.stack[top] = StackValue { val };
2553 } else {
2554 self.stack.push(StackValue { val });
2555 }
2556 self.top = StackIdx(self.top.0 + 1);
2557 }
2558
2559 #[inline(always)]
2562 pub fn pop(&mut self) -> LuaValue {
2563 if self.top.0 == 0 {
2564 return LuaValue::Nil;
2565 }
2566 self.top = StackIdx(self.top.0 - 1);
2567 self.stack[self.top.0 as usize].val.clone()
2568 }
2569
2570 #[inline(always)]
2574 pub fn stack_val(&self, idx: StackIdx) -> &LuaValue {
2575 &self.stack[idx.0 as usize].val
2576 }
2577
2578 #[inline(always)]
2580 pub fn set_stack_val(&mut self, idx: StackIdx, val: LuaValue) {
2581 self.stack[idx.0 as usize].val = val;
2582 }
2583
2584 pub fn gc(&mut self) -> GcHandle<'_> {
2591 GcHandle { _state: self }
2592 }
2593
2594 pub fn external_root_value(&mut self, value: LuaValue) -> ExternalRootKey {
2596 self.global_mut().external_roots.insert(value)
2597 }
2598
2599 pub fn external_rooted_value(&self, key: ExternalRootKey) -> Option<LuaValue> {
2601 self.global().external_roots.get(key).cloned()
2602 }
2603
2604 pub fn external_replace_root(
2606 &mut self,
2607 key: ExternalRootKey,
2608 value: LuaValue,
2609 ) -> Option<LuaValue> {
2610 self.global_mut().external_roots.replace(key, value)
2611 }
2612
2613 pub fn external_unroot_value(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
2615 self.global_mut().external_roots.remove(key)
2616 }
2617
2618 pub fn try_external_unroot_value(
2621 &mut self,
2622 key: ExternalRootKey,
2623 ) -> std::result::Result<Option<LuaValue>, std::cell::BorrowMutError> {
2624 self.global
2625 .try_borrow_mut()
2626 .map(|mut global| global.external_roots.remove(key))
2627 }
2628
2629 pub fn new_table(&mut self) -> GcRef<LuaTable> {
2633 self.mark_gc_check_needed();
2635 GcRef::new(LuaTable::placeholder())
2636 }
2637
2638 pub fn new_table_with_sizes(
2643 &mut self,
2644 array_size: u32,
2645 hash_size: u32,
2646 ) -> Result<GcRef<LuaTable>, LuaError> {
2647 self.mark_gc_check_needed();
2648 let t = GcRef::new(LuaTable::placeholder());
2649 self.table_resize(&t, array_size as usize, hash_size as usize)?;
2650 Ok(t)
2651 }
2652
2653 pub fn intern_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
2668 if bytes.len() <= crate::string::MAX_SHORT_LEN {
2669 let hash = LuaString::hash_bytes(bytes, 0);
2670 {
2671 let global = self.global();
2672 if let Some(existing) = global.interned_lt.find(bytes, hash) {
2673 return Ok(existing);
2674 }
2675 }
2676 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2677 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2678 self.global_mut().interned_lt.insert(new_ref.clone());
2679 self.mark_gc_check_needed();
2680 Ok(new_ref)
2681 } else {
2682 self.mark_gc_check_needed();
2683 let new_ref = GcRef::new(LuaString::from_slice(bytes));
2684 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2685 Ok(new_ref)
2686 }
2687 }
2688
2689 #[inline(always)]
2691 pub fn top_idx(&self) -> StackIdx {
2692 self.top
2693 }
2694}
2695
2696impl LuaState {
2705 #[inline(always)]
2706 pub fn get_at(&self, idx: impl Into<StackIdxConv>) -> LuaValue {
2707 let i: StackIdx = idx.into().0;
2708 match self.stack.get(i.0 as usize) {
2709 Some(slot) => slot.val.clone(),
2710 None => LuaValue::Nil,
2711 }
2712 }
2713 #[inline(always)]
2714 pub fn set_at(&mut self, idx: impl Into<StackIdxConv>, v: LuaValue) {
2715 let i: StackIdx = idx.into().0;
2716 self.stack[i.0 as usize].val = v;
2717 }
2718
2719 pub fn clear_stack_range(&mut self, start: StackIdx, end: StackIdx) {
2733 if end.0 <= start.0 {
2734 return;
2735 }
2736 let end_u = end.0 as usize;
2737 if end_u > self.stack.len() {
2738 self.stack.resize_with(end_u, StackValue::default);
2739 }
2740 for i in start.0..end.0 {
2741 self.stack[i as usize].val = LuaValue::Nil;
2742 }
2743 }
2744 #[inline(always)]
2752 pub fn get_int_at(&self, idx: impl Into<StackIdxConv>) -> Option<i64> {
2753 let i: StackIdx = idx.into().0;
2754 match self.stack.get(i.0 as usize) {
2755 Some(slot) => match &slot.val {
2756 LuaValue::Int(v) => Some(*v),
2757 _ => None,
2758 },
2759 None => None,
2760 }
2761 }
2762 #[inline(always)]
2769 pub fn get_int_pair_at(
2770 &self,
2771 rb: impl Into<StackIdxConv>,
2772 rc: impl Into<StackIdxConv>,
2773 ) -> Option<(i64, i64)> {
2774 let rb: StackIdx = rb.into().0;
2775 let rc: StackIdx = rc.into().0;
2776 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2777 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib, ic)),
2778 _ => None,
2779 }
2780 }
2781 #[inline(always)]
2786 pub fn get_num_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2787 let i: StackIdx = idx.into().0;
2788 match self.stack.get(i.0 as usize) {
2789 Some(slot) => match &slot.val {
2790 LuaValue::Float(f) => Some(*f),
2791 LuaValue::Int(v) => Some(*v as f64),
2792 _ => None,
2793 },
2794 None => None,
2795 }
2796 }
2797 #[inline(always)]
2802 pub fn get_float_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2803 let i: StackIdx = idx.into().0;
2804 match self.stack.get(i.0 as usize) {
2805 Some(slot) => match &slot.val {
2806 LuaValue::Float(f) => Some(*f),
2807 _ => None,
2808 },
2809 None => None,
2810 }
2811 }
2812 #[inline(always)]
2817 pub fn get_num_pair_at(
2818 &self,
2819 rb: impl Into<StackIdxConv>,
2820 rc: impl Into<StackIdxConv>,
2821 ) -> Option<(f64, f64)> {
2822 let rb: StackIdx = rb.into().0;
2823 let rc: StackIdx = rc.into().0;
2824 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2825 (LuaValue::Float(nb), LuaValue::Float(nc)) => Some((nb, nc)),
2826 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib as f64, ic as f64)),
2827 (LuaValue::Int(ib), LuaValue::Float(nc)) => Some((ib as f64, nc)),
2828 (LuaValue::Float(nb), LuaValue::Int(ic)) => Some((nb, ic as f64)),
2829 _ => None,
2830 }
2831 }
2832 #[inline(always)]
2845 pub fn set_top(&mut self, idx: impl Into<StackIdxConv>) {
2846 let new_top: StackIdx = idx.into().0;
2847 let new_top_u = new_top.0 as usize;
2848 if new_top_u > self.stack.len() {
2849 self.stack.resize_with(new_top_u, StackValue::default);
2850 }
2851 self.top = new_top;
2852 }
2853 #[inline(always)]
2859 pub fn set_top_idx(&mut self, idx: impl Into<StackIdxConv>) {
2860 let new_top: StackIdx = idx.into().0;
2861 self.top = new_top;
2862 }
2863 #[inline(always)]
2866 pub fn dec_top(&mut self) {
2867 if self.top.0 > 0 {
2868 self.top = StackIdx(self.top.0 - 1);
2869 }
2870 }
2871 #[inline(always)]
2872 pub fn pop_n(&mut self, n: usize) {
2873 let cur = self.top.0 as usize;
2874 let new = cur.saturating_sub(n);
2875 self.top = StackIdx(new as u32);
2876 }
2877 #[inline(always)]
2880 pub fn peek_at(&mut self, idx: impl Into<StackIdxConv>) -> LuaValue {
2881 let i: StackIdx = idx.into().0;
2882 match self.stack.get(i.0 as usize) {
2883 Some(slot) => slot.val.clone(),
2884 None => LuaValue::Nil,
2885 }
2886 }
2887 #[inline(always)]
2891 pub fn peek_top(&mut self) -> LuaValue {
2892 if self.top.0 == 0 {
2893 return LuaValue::Nil;
2894 }
2895 self.stack[(self.top.0 - 1) as usize].val.clone()
2896 }
2897 pub fn peek_string_at_top(&mut self) -> GcRef<LuaString> {
2902 match self.peek_top() {
2903 LuaValue::Str(s) => s,
2904 _ => panic!("peek_string_at_top: top of stack is not a string"),
2905 }
2906 }
2907 pub fn stack_at(&mut self, idx: impl Into<StackIdxConv>) -> &mut LuaValue {
2910 let i: StackIdx = idx.into().0;
2911 &mut self.stack[i.0 as usize].val
2912 }
2913 pub fn stack_set_nil(&mut self, idx: impl Into<StackIdxConv>) {
2916 let i: StackIdx = idx.into().0;
2917 let slot = i.0 as usize;
2918 if slot < self.stack.len() {
2919 self.stack[slot].val = LuaValue::Nil;
2920 }
2921 }
2922 pub fn stack_resize(&mut self, size: usize) -> Result<(), LuaError> {
2930 self.stack.resize_with(size, StackValue::default);
2931 Ok(())
2932 }
2933 pub fn stack_available(&mut self) -> usize {
2934 (self.stack_last.0 as usize).saturating_sub(self.top.0 as usize)
2935 }
2936 pub fn check_stack(&mut self, n: i32) -> Result<(), LuaError> {
2937 let free = (self.stack_last.0 as i32) - (self.top.0 as i32);
2938 if free <= n {
2939 self.grow_stack(n, true)?;
2940 }
2941 Ok(())
2942 }
2943 pub fn grow_stack(&mut self, n: i32, raise_error: bool) -> Result<(), LuaError> {
2952 crate::do_::grow_stack(self, n, raise_error).map(|_| ())
2953 }
2954
2955 #[inline(always)]
2956 pub fn get_ci(&self, idx: CallInfoIdx) -> &CallInfo {
2957 &self.call_info[idx.as_usize()]
2958 }
2959 #[inline(always)]
2960 pub fn get_ci_mut(&mut self, idx: CallInfoIdx) -> &mut CallInfo {
2961 &mut self.call_info[idx.as_usize()]
2962 }
2963 #[inline(always)]
2964 pub fn current_call_info(&self) -> &CallInfo {
2965 &self.call_info[self.ci.as_usize()]
2966 }
2967 #[inline(always)]
2968 pub fn current_call_info_mut(&mut self) -> &mut CallInfo {
2969 let i = self.ci.as_usize();
2970 &mut self.call_info[i]
2971 }
2972 #[inline(always)]
2973 pub fn current_ci_idx(&self) -> CallInfoIdx {
2974 self.ci
2975 }
2976 pub fn call_stack_mut(&mut self) -> &mut Vec<CallInfo> {
2977 &mut self.call_info
2978 }
2979 #[inline(always)]
2980 pub fn next_ci(&mut self) -> Result<CallInfoIdx, LuaError> {
2981 match self.call_info[self.ci.as_usize()].next {
2982 Some(idx) => Ok(idx),
2983 None => Ok(extend_ci(self)),
2984 }
2985 }
2986 #[inline(always)]
2987 pub fn prev_ci(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
2988 self.call_info[idx.as_usize()].previous
2989 }
2990 pub fn get_prev_ci(&self, idx: CallInfoIdx) -> Option<&CallInfo> {
2991 self.call_info[idx.as_usize()]
2992 .previous
2993 .map(|p| &self.call_info[p.as_usize()])
2994 }
2995 #[inline(always)]
2996 pub fn is_base_ci(&self, idx: CallInfoIdx) -> bool {
2997 idx.as_usize() == 0
2998 }
2999 #[inline(always)]
3000 pub fn is_current_ci(&self, idx: CallInfoIdx) -> bool {
3001 idx == self.ci
3002 }
3003 pub fn ci_next_func(&self, idx: CallInfoIdx) -> StackIdx {
3004 let next = self.call_info[idx.as_usize()]
3005 .next
3006 .expect("ci_next_func: no next CallInfo");
3007 self.call_info[next.as_usize()].func
3008 }
3009 #[inline(always)]
3010 pub fn ci_top(&self, idx: CallInfoIdx) -> StackIdx {
3011 self.call_info[idx.as_usize()].top
3012 }
3013 #[inline(always)]
3018 pub fn ci_trap(&mut self, idx: CallInfoIdx) -> bool {
3019 self.call_info[idx.as_usize()].trap()
3020 }
3021 #[inline(always)]
3022 pub fn ci_savedpc(&self, idx: CallInfoIdx) -> u32 {
3023 self.call_info[idx.as_usize()].saved_pc()
3024 }
3025 #[inline(always)]
3026 pub fn set_ci_savedpc(&mut self, idx: CallInfoIdx, pc: u32) {
3027 self.call_info[idx.as_usize()].set_saved_pc(pc);
3028 }
3029 #[inline(always)]
3030 pub fn set_ci_previous(&mut self, idx: CallInfoIdx) {
3031 self.ci = self.call_info[idx.as_usize()]
3032 .previous
3033 .expect("set_ci_previous: returning frame has no previous CallInfo");
3034 }
3035 #[inline(always)]
3036 pub fn ci_previous(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
3037 self.call_info[idx.as_usize()].previous
3038 }
3039 #[inline(always)]
3040 pub fn ci_adjust_func(&mut self, idx: CallInfoIdx, delta: i32) {
3041 let ci = &mut self.call_info[idx.as_usize()];
3042 ci.func = StackIdx((ci.func.0 as i32 - delta) as u32);
3043 }
3044 #[inline(always)]
3045 pub fn ci_base(&self, idx: CallInfoIdx) -> StackIdx {
3046 self.call_info[idx.as_usize()].func + 1
3047 }
3048 #[inline(always)]
3049 pub fn ci_is_fresh(&self, idx: CallInfoIdx) -> bool {
3050 (self.call_info[idx.as_usize()].callstatus & CIST_FRESH) != 0
3051 }
3052 #[inline(always)]
3053 pub fn ci_lua_closure(
3054 &self,
3055 idx: CallInfoIdx,
3056 ) -> Option<GcRef<lua_types::closure::LuaLClosure>> {
3057 let func_idx = self.call_info[idx.as_usize()].func;
3058 match self.stack.get(func_idx.0 as usize).map(|slot| slot.val) {
3059 Some(LuaValue::Function(lua_types::closure::LuaClosure::Lua(cl))) => Some(cl),
3060 _ => None,
3061 }
3062 }
3063 #[inline(always)]
3064 pub fn ci_nextraargs(&self, idx: CallInfoIdx) -> i32 {
3065 self.call_info[idx.as_usize()].nextra_args()
3066 }
3067 #[inline(always)]
3068 pub fn ci_nres(&self, idx: CallInfoIdx) -> i32 {
3069 self.call_info[idx.as_usize()].u2.value
3070 }
3071 #[inline(always)]
3072 pub fn ci_nres_set(&mut self, idx: CallInfoIdx, n: i32) {
3073 self.call_info[idx.as_usize()].u2.value = n;
3074 }
3075 #[inline(always)]
3076 pub fn ci_nresults(&self, idx: CallInfoIdx) -> i32 {
3077 self.call_info[idx.as_usize()].nresults as i32
3078 }
3079 pub fn ci_prev_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3080 let pc = self.call_info[idx.as_usize()].saved_pc();
3081 let cl = self
3082 .ci_lua_closure(idx)
3083 .expect("ci_prev_instruction: CallInfo does not hold a Lua closure");
3084 cl.proto.code[(pc - 1) as usize]
3085 }
3086 pub fn ci_prev2_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
3087 let pc = self.call_info[idx.as_usize()].saved_pc();
3088 let cl = self
3089 .ci_lua_closure(idx)
3090 .expect("ci_prev2_instruction: CallInfo does not hold a Lua closure");
3091 cl.proto.code[(pc - 2) as usize]
3092 }
3093 pub fn ci_skip_next_instruction(&mut self, idx: CallInfoIdx) {
3094 let pc = self.call_info[idx.as_usize()].saved_pc();
3095 self.call_info[idx.as_usize()].set_saved_pc(pc + 1);
3096 }
3097 pub fn ci_step_pc_back(&mut self, idx: CallInfoIdx) {
3098 let pc = self.call_info[idx.as_usize()].saved_pc();
3099 self.call_info[idx.as_usize()].set_saved_pc(pc - 1);
3100 }
3101 pub fn get_ci_pcrel(&mut self, idx: CallInfoIdx) -> u32 {
3102 self.call_info[idx.as_usize()].saved_pc().saturating_sub(1)
3103 }
3104 pub fn get_ci_u2_funcidx(&mut self, idx: CallInfoIdx) -> i32 {
3105 self.call_info[idx.as_usize()].u2.value
3106 }
3107 pub fn get_ci_u2_nres(&mut self, idx: CallInfoIdx) -> i32 {
3108 self.call_info[idx.as_usize()].u2.value
3109 }
3110 pub fn get_ci_u2_nyield(&mut self, idx: CallInfoIdx) -> i32 {
3111 self.call_info[idx.as_usize()].u2.value
3112 }
3113 pub fn get_ci_vararg_info(&mut self, idx: CallInfoIdx) -> (bool, i32, i32) {
3114 let nextraargs = self.call_info[idx.as_usize()].nextra_args();
3115 match self.ci_lua_closure(idx) {
3116 Some(cl) => (cl.proto.is_vararg, nextraargs, cl.proto.numparams as i32),
3117 None => (false, nextraargs, 0),
3118 }
3119 }
3120 pub fn get_ci_lua_proto_numparams(&mut self, idx: CallInfoIdx) -> u8 {
3121 self.ci_lua_closure(idx)
3122 .map(|cl| cl.proto.numparams)
3123 .unwrap_or(0)
3124 }
3125 pub fn set_ci_u2_nres(&mut self, idx: CallInfoIdx, n: i32) {
3126 self.call_info[idx.as_usize()].u2.value = n;
3127 }
3128 pub fn set_ci_u2_nyield(&mut self, idx: CallInfoIdx, n: i32) {
3129 self.call_info[idx.as_usize()].u2.value = n;
3130 }
3131 pub fn set_ci_transfer_info(&mut self, idx: CallInfoIdx, ftransfer: u16, ntransfer: u16) {
3132 let ci = &mut self.call_info[idx.as_usize()];
3133 ci.u2.ftransfer = ftransfer;
3134 ci.u2.ntransfer = ntransfer;
3135 }
3136 pub fn shrink_ci(&mut self) {
3137 shrink_ci(self)
3138 }
3139 pub fn check_c_stack(&mut self) -> Result<(), LuaError> {
3140 check_c_stack(self)
3141 }
3142
3143 pub fn status(&mut self) -> LuaStatus {
3144 LuaStatus::from_raw(self.status as i32)
3145 }
3146 pub fn errfunc(&mut self) -> isize {
3147 self.errfunc
3148 }
3149 pub fn old_pc(&mut self) -> u32 {
3150 self.oldpc
3151 }
3152 pub fn set_old_pc(&mut self, pc: u32) {
3153 self.oldpc = pc;
3154 }
3155 pub fn set_oldpc(&mut self, pc: u32) {
3156 self.oldpc = pc;
3157 }
3158 pub fn _hook_call_noargs(&mut self) {}
3159 pub fn hook(&self) -> Option<&Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>> {
3160 self.hook.as_ref()
3161 }
3162 pub fn has_hook(&mut self) -> bool {
3163 self.hook.is_some()
3164 }
3165 pub fn hook_count(&mut self) -> i32 {
3166 self.hookcount
3167 }
3168 pub fn set_hook_count(&mut self, n: i32) {
3169 self.hookcount = n;
3170 }
3171 pub fn hook_mask(&self) -> u8 {
3172 self.hookmask
3173 }
3174 pub fn set_hook_mask(&mut self, m: u8) {
3175 self.hookmask = m;
3176 }
3177 pub fn base_hook_count(&self) -> i32 {
3178 self.basehookcount
3179 }
3180 pub fn set_base_hook_count(&mut self, n: i32) {
3181 self.basehookcount = n;
3182 }
3183 pub fn set_hook(&mut self, h: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>) {
3184 self.hook = h;
3185 }
3186 pub fn call_hook_event(&mut self, event: i32, line: i32) -> Result<(), LuaError> {
3187 crate::do_::hook(self, event, line, 0, 0)
3188 }
3189
3190 pub fn registry_value(&self) -> LuaValue {
3191 self.global().l_registry.clone()
3192 }
3193 pub fn registry_get(&self, key: usize) -> LuaValue {
3194 let reg = self.global().l_registry.clone();
3195 match reg {
3196 LuaValue::Table(t) => t.get(&LuaValue::Int(key as i64)),
3197 _ => LuaValue::Nil,
3198 }
3199 }
3200
3201 pub fn new_string(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3202 self.intern_or_create_str(bytes)
3203 }
3204
3205 pub fn new_proto(&mut self) -> GcRef<LuaProto> {
3217 self.mark_gc_check_needed();
3218 GcRef::new(LuaProto::placeholder())
3219 }
3220
3221 pub fn new_lclosure(&mut self, proto: GcRef<LuaProto>, nupvals: usize) -> GcRef<LuaClosureLua> {
3223 self.mark_gc_check_needed();
3224 let mut upvals = Vec::with_capacity(nupvals);
3225 for _ in 0..nupvals {
3226 upvals.push(std::cell::Cell::new(self.new_upval_closed(LuaValue::Nil)));
3227 }
3228 let upvals = upvals.into_boxed_slice();
3229 let closure = GcRef::new(LuaClosureLua { proto, upvals });
3230 closure.account_buffer(closure.buffer_bytes() as isize);
3231 closure
3232 }
3233
3234 pub fn new_upval_closed(&mut self, v: LuaValue) -> GcRef<UpVal> {
3236 self.mark_gc_check_needed();
3237 GcRef::new(UpVal::closed(v))
3238 }
3239
3240 pub fn new_upval_open(&mut self, thread_id: usize, level: StackIdx) -> GcRef<UpVal> {
3242 self.mark_gc_check_needed();
3243 GcRef::new(UpVal::open(thread_id, level))
3244 }
3245 pub fn intern_or_create_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3252 self.intern_str(bytes)
3253 }
3254 pub fn new_userdata(
3255 &mut self,
3256 _size: usize,
3257 _nuvalue: usize,
3258 ) -> Result<GcRef<LuaUserData>, LuaError> {
3259 Err(LuaError::runtime(format_args!(
3260 "new_userdata not implemented in this Phase-B build; use new_userdata_typed instead"
3261 )))
3262 }
3263 pub fn new_c_closure(&mut self, _f: LuaCFunction, _n: i32) -> Result<LuaClosure, LuaError> {
3264 Err(LuaError::runtime(format_args!("new_c_closure not implemented in this Phase-B build; use push_cclosure in lua_vm::api instead")))
3265 }
3266 pub fn push_closure(
3267 &mut self,
3268 proto_idx: usize,
3269 ci: CallInfoIdx,
3270 base: StackIdx,
3271 ra: StackIdx,
3272 ) -> Result<(), LuaError> {
3273 let parent_cl = self
3274 .ci_lua_closure(ci)
3275 .expect("push_closure: current frame is not a Lua closure");
3276 let child_proto = parent_cl.proto.p[proto_idx].clone();
3277 let nup = child_proto.upvalues.len();
3278 let mut upvals: Vec<std::cell::Cell<GcRef<UpVal>>> = Vec::with_capacity(nup);
3279 for i in 0..nup {
3280 let desc = &child_proto.upvalues[i];
3281 let uv = if desc.instack {
3282 let level = base + desc.idx as i32;
3283 crate::func::find_upval(self, level)
3284 } else {
3285 parent_cl.upval(desc.idx as usize)
3286 };
3287 upvals.push(std::cell::Cell::new(uv));
3288 }
3289 let cache_enabled = matches!(
3293 self.global().lua_version,
3294 lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
3295 );
3296 if cache_enabled {
3297 if let Some(cached) = child_proto.cache.borrow().as_ref() {
3298 if cached.upvals.len() == nup
3299 && (0..nup).all(|i| GcRef::ptr_eq(&cached.upvals[i].get(), &upvals[i].get()))
3300 {
3301 let reused = cached.clone();
3302 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(reused)));
3303 return Ok(());
3304 }
3305 }
3306 }
3307 self.mark_gc_check_needed();
3310 let new_cl = GcRef::new(LuaClosureLua {
3311 proto: child_proto.clone(),
3312 upvals: upvals.into_boxed_slice(),
3313 });
3314 new_cl.account_buffer(new_cl.buffer_bytes() as isize);
3315 if cache_enabled {
3316 *child_proto.cache.borrow_mut() = Some(new_cl.clone());
3317 }
3318 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(new_cl)));
3319 Ok(())
3320 }
3321 pub fn new_tbc_upval(&mut self, idx: StackIdx) -> Result<(), LuaError> {
3322 crate::func::new_tbc_upval(self, idx)
3323 }
3324
3325 #[inline(always)]
3346 pub fn upvalue_get(&self, cl: &GcRef<LuaClosureLua>, n: usize) -> LuaValue {
3347 let uv = cl.upval(n);
3348 let (thread_id, idx) = match uv.try_open_payload() {
3349 Some(p) => p,
3350 None => return uv.closed_value(),
3351 };
3352 let current = self.cached_thread_id;
3353 let tid = thread_id as u64;
3354 if tid == current {
3355 return self.stack[idx.0 as usize].val;
3356 }
3357 self.upvalue_get_cross_thread(tid, idx)
3358 }
3359
3360 #[cold]
3361 #[inline(never)]
3362 fn upvalue_get_cross_thread(&self, tid: u64, idx: StackIdx) -> LuaValue {
3363 let entry_rc = {
3364 let g = self.global();
3365 g.threads.get(&tid).map(|e| e.state.clone())
3366 };
3367 if let Some(rc) = entry_rc {
3368 if let Ok(home_state) = rc.try_borrow() {
3369 return home_state.get_at(idx);
3370 }
3371 }
3372 let g = self.global();
3373 match g.cross_thread_upvals.get(&(tid, idx)) {
3374 Some(v) => *v,
3375 None => LuaValue::Nil,
3376 }
3377 }
3378 #[inline(always)]
3387 pub fn upvalue_set(
3388 &mut self,
3389 cl: &GcRef<LuaClosureLua>,
3390 n: usize,
3391 val: LuaValue,
3392 ) -> Result<(), LuaError> {
3393 let uv = cl.upval(n);
3394 match uv.try_open_payload() {
3395 Some((thread_id, idx)) => {
3396 let tid = thread_id as u64;
3397 let current = self.cached_thread_id;
3398 if tid == current {
3399 self.stack[idx.0 as usize].val = val;
3400 } else {
3401 self.upvalue_set_cross_thread(tid, idx, val)?;
3402 }
3403 }
3404 None => {
3405 uv.set_closed_value(val);
3406 }
3407 }
3408 if val.is_collectable() {
3409 self.gc_barrier_upval(&uv, &val);
3410 }
3411 Ok(())
3412 }
3413
3414 #[cold]
3415 #[inline(never)]
3416 fn upvalue_set_cross_thread(
3417 &mut self,
3418 tid: u64,
3419 idx: StackIdx,
3420 val: LuaValue,
3421 ) -> Result<(), LuaError> {
3422 let entry_rc = {
3423 let g = self.global();
3424 g.threads.get(&tid).map(|e| e.state.clone())
3425 };
3426 if let Some(rc) = entry_rc {
3427 if let Ok(mut home_state) = rc.try_borrow_mut() {
3428 home_state.set_at(idx, val);
3429 return Ok(());
3430 }
3431 }
3432 let mut g = self.global_mut();
3433 g.cross_thread_upvals.insert((tid, idx), val);
3434 Ok(())
3435 }
3436
3437 pub fn protected_call_raw(
3438 &mut self,
3439 func: StackIdx,
3440 nresults: i32,
3441 errfunc: StackIdx,
3442 ) -> Result<(), LuaError> {
3443 let ef = errfunc.0 as isize;
3444 let status = crate::do_::pcall(self, |s| s.call_no_yield(func, nresults), func, ef);
3445 match status {
3446 LuaStatus::Ok => Ok(()),
3447 LuaStatus::ErrSyntax => {
3448 let err_val = self.get_at(func);
3449 self.set_top(func);
3450 Err(LuaError::Syntax(err_val))
3451 }
3452 LuaStatus::Yield => {
3453 self.set_top(func);
3454 Err(LuaError::Yield)
3455 }
3456 _ => {
3457 let err_val = self.get_at(func);
3458 self.set_top(func);
3459 Err(LuaError::Runtime(err_val))
3460 }
3461 }
3462 }
3463 pub fn protected_parser(
3464 &mut self,
3465 z: crate::zio::ZIO,
3466 name: &[u8],
3467 mode: Option<&[u8]>,
3468 ) -> LuaStatus {
3469 crate::do_::protected_parser(self, z, name, mode)
3470 }
3471 pub fn do_call(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3472 crate::do_::call(self, func, nresults)
3473 }
3474 pub fn do_call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3475 crate::do_::callnoyield(self, func, nresults)
3476 }
3477 pub fn call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3478 crate::do_::callnoyield(self, func, nresults)
3479 }
3480 pub fn call_at(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3481 crate::do_::call(self, func, nresults)
3482 }
3483 #[inline(always)]
3484 pub fn call_known_c_at(&mut self, func: StackIdx, nresults: i32) -> Result<bool, LuaError> {
3485 crate::do_::call_known_c(self, func, nresults)
3486 }
3487 #[inline(always)]
3488 pub fn precall(
3489 &mut self,
3490 func: StackIdx,
3491 nresults: i32,
3492 ) -> Result<Option<CallInfoIdx>, LuaError> {
3493 crate::do_::precall(self, func, nresults)
3494 }
3495 #[inline(always)]
3496 pub fn pretailcall(
3497 &mut self,
3498 ci: CallInfoIdx,
3499 func: StackIdx,
3500 narg1: i32,
3501 delta: i32,
3502 ) -> Result<i32, LuaError> {
3503 crate::do_::pretailcall(self, ci, func, narg1, delta)
3504 }
3505 #[inline(always)]
3506 pub fn poscall<N: TryInto<i32>>(&mut self, ci: CallInfoIdx, nres: N) -> Result<(), LuaError>
3507 where
3508 <N as TryInto<i32>>::Error: std::fmt::Debug,
3509 {
3510 let n = nres.try_into().expect("poscall: nres out of i32 range");
3511 crate::do_::poscall(self, ci, n)
3512 }
3513 pub fn adjust_results(&mut self, nresults: i32) {
3514 const LUA_MULTRET: i32 = -1;
3515 if nresults <= LUA_MULTRET {
3516 let ci_idx = self.ci.as_usize();
3517 if self.call_info[ci_idx].top.0 < self.top.0 {
3518 self.call_info[ci_idx].top = self.top;
3519 }
3520 }
3521 }
3522 pub fn adjust_varargs(
3523 &mut self,
3524 ci: CallInfoIdx,
3525 nfixparams: i32,
3526 cl: &GcRef<lua_types::closure::LuaLClosure>,
3527 ) -> Result<(), LuaError> {
3528 crate::tagmethods::adjust_varargs(self, nfixparams, ci, &cl.0.proto)
3529 }
3530 pub fn get_varargs(&mut self, ci: CallInfoIdx, ra: StackIdx, n: i32) -> Result<i32, LuaError> {
3531 crate::tagmethods::get_varargs(self, ci, ra, n)?;
3532 Ok(0)
3533 }
3534
3535 pub fn close_upvals(&mut self, level: StackIdx) -> Result<(), LuaError> {
3536 crate::func::close_upval(self, level);
3537 Ok(())
3538 }
3539 pub fn close_upvals_status(&mut self, level: StackIdx, _status: i32) -> Result<(), LuaError> {
3540 crate::func::close_upval(self, level);
3541 Ok(())
3542 }
3543 pub fn close_upvals_from_base(&mut self, ci: CallInfoIdx) -> Result<(), LuaError> {
3544 let base = self.ci_base(ci);
3545 crate::func::close_upval(self, base);
3546 Ok(())
3547 }
3548
3549 pub fn arith_op(
3550 &mut self,
3551 op: i32,
3552 p1: &LuaValue,
3553 p2: &LuaValue,
3554 ) -> Result<LuaValue, LuaError> {
3555 let arith_op = match op {
3556 0 => lua_types::arith::ArithOp::Add,
3557 1 => lua_types::arith::ArithOp::Sub,
3558 2 => lua_types::arith::ArithOp::Mul,
3559 3 => lua_types::arith::ArithOp::Mod,
3560 4 => lua_types::arith::ArithOp::Pow,
3561 5 => lua_types::arith::ArithOp::Div,
3562 6 => lua_types::arith::ArithOp::Idiv,
3563 7 => lua_types::arith::ArithOp::Band,
3564 8 => lua_types::arith::ArithOp::Bor,
3565 9 => lua_types::arith::ArithOp::Bxor,
3566 10 => lua_types::arith::ArithOp::Shl,
3567 11 => lua_types::arith::ArithOp::Shr,
3568 12 => lua_types::arith::ArithOp::Unm,
3569 13 => lua_types::arith::ArithOp::Bnot,
3570 _ => return Err(LuaError::runtime(format_args!("invalid arith op {}", op))),
3571 };
3572 let mut res = LuaValue::Nil;
3573 if crate::object::raw_arith(self, arith_op, p1, p2, &mut res)? {
3574 Ok(res)
3575 } else {
3576 Err(LuaError::arith_error(p1, p2, "perform arithmetic on"))
3577 }
3578 }
3579 pub fn concat(&mut self, n: i32) -> Result<(), LuaError> {
3580 crate::vm::concat(self, n)
3581 }
3582 pub fn less_than(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3583 crate::vm::less_than(self, l, r)
3584 }
3585 pub fn less_equal(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3586 crate::vm::less_equal(self, l, r)
3587 }
3588 pub fn equal_obj(&self, _ctx: Option<&LuaValue>, l: &LuaValue, r: &LuaValue) -> bool {
3589 crate::vm::equal_obj(None, l, r).unwrap_or(false)
3590 }
3591 pub fn equal_obj_with_tm(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3592 crate::vm::equal_obj(Some(self), l, r)
3593 }
3594 pub fn obj_len(&mut self, v: &LuaValue) -> Result<LuaValue, LuaError> {
3595 match v {
3596 LuaValue::Table(_) => {
3597 let consult_len_tm =
3600 !matches!(self.global().lua_version, lua_types::LuaVersion::V51);
3601 let tm = if consult_len_tm {
3602 let mt = self.table_metatable(v);
3603 self.fast_tm_table(mt.as_ref(), TagMethod::Len)
3604 } else {
3605 LuaValue::Nil
3606 };
3607 if matches!(tm, LuaValue::Nil) {
3608 let n = self.table_length(v)?;
3609 return Ok(LuaValue::Int(n));
3610 }
3611 self.push(LuaValue::Nil);
3612 let slot = StackIdx(self.top.0 - 1);
3613 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3614 Ok(self.pop())
3615 }
3616 LuaValue::Str(s) => Ok(LuaValue::Int(s.len() as i64)),
3617 other => {
3618 let tm = crate::tagmethods::get_tm_by_obj(
3619 self,
3620 other,
3621 crate::tagmethods::TagMethod::Len,
3622 );
3623 if matches!(tm, LuaValue::Nil) {
3624 let mut msg = b"attempt to get length of a ".to_vec();
3625 msg.extend_from_slice(&self.obj_type_name(other));
3626 msg.extend_from_slice(b" value");
3627 return Err(crate::debug::prefixed_runtime_pub(self, msg));
3628 }
3629 self.push(LuaValue::Nil);
3630 let slot = StackIdx(self.top.0 - 1);
3631 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3632 Ok(self.pop())
3633 }
3634 }
3635 }
3636 pub fn obj_to_string(&mut self, idx: i32) -> Result<GcRef<LuaString>, LuaError> {
3637 let slot: StackIdx = if idx > 0 {
3638 let ci_func = self.current_call_info().func;
3639 ci_func + idx
3640 } else {
3641 debug_assert!(idx != 0, "invalid index");
3642 StackIdx((self.top_idx().0 as i32 + idx) as u32)
3643 };
3644 let val = self.get_at(slot);
3645 match val {
3646 LuaValue::Str(s) => Ok(s),
3647 LuaValue::Int(_) | LuaValue::Float(_) => {
3648 let s = crate::object::num_to_string(self, &val)?;
3649 self.set_at(slot, LuaValue::Str(s.clone()));
3650 Ok(s)
3651 }
3652 _ => Err(LuaError::type_error(&val, "convert to string")),
3653 }
3654 }
3655 pub fn coerce_to_string(&mut self, idx: StackIdx) -> Result<GcRef<LuaString>, LuaError> {
3656 let val = self.get_at(idx);
3657 match val {
3658 LuaValue::Str(s) => Ok(s),
3659 LuaValue::Int(_) | LuaValue::Float(_) => {
3660 let s = crate::object::num_to_string(self, &val)?;
3661 self.set_at(idx, LuaValue::Str(s.clone()));
3662 Ok(s)
3663 }
3664 _ => Err(LuaError::type_error(&val, "convert to string")),
3665 }
3666 }
3667 pub fn str_to_num(&mut self, s: &[u8]) -> Option<(LuaValue, usize)> {
3668 let mut out = LuaValue::Nil;
3669 let sz = crate::object::str2num(s, &mut out);
3670 if sz == 0 {
3671 None
3672 } else {
3673 Some((out, sz))
3674 }
3675 }
3676
3677 #[inline(always)]
3678 pub fn fast_get(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
3679 let LuaValue::Table(tbl) = t else {
3680 return Ok(None);
3681 };
3682 let v = tbl.get(k);
3683 if matches!(v, LuaValue::Nil) {
3684 Ok(None)
3685 } else {
3686 Ok(Some(v))
3687 }
3688 }
3689 #[inline(always)]
3690 pub fn fast_get_int(&mut self, t: &LuaValue, k: i64) -> Result<Option<LuaValue>, LuaError> {
3691 let LuaValue::Table(tbl) = t else {
3692 return Ok(None);
3693 };
3694 let v = tbl.get_int(k);
3695 if matches!(v, LuaValue::Nil) {
3696 Ok(None)
3697 } else {
3698 Ok(Some(v))
3699 }
3700 }
3701 #[inline(always)]
3702 pub fn fast_get_short_str(
3703 &mut self,
3704 t: &LuaValue,
3705 k: &LuaValue,
3706 ) -> Result<Option<LuaValue>, LuaError> {
3707 let LuaValue::Table(tbl) = t else {
3708 return Ok(None);
3709 };
3710 let LuaValue::Str(s) = k else {
3711 return Ok(None);
3712 };
3713 let v = tbl.get_short_str(s);
3714 if matches!(v, LuaValue::Nil) {
3715 Ok(None)
3716 } else {
3717 Ok(Some(v))
3718 }
3719 }
3720 #[inline(always)]
3721 pub fn fast_tm_table(&mut self, t: Option<&GcRef<LuaTable>>, tm: TagMethod) -> LuaValue {
3722 let Some(mt) = t else {
3723 return LuaValue::Nil;
3724 };
3725 debug_assert!((tm as u8) <= TagMethod::Eq as u8);
3726 let ename = self.global().tmname[tm as usize].clone();
3727 mt.get_short_str(&ename)
3728 }
3729 pub fn fast_tm_ud(&mut self, u: &GcRef<LuaUserData>, tm: TagMethod) -> LuaValue {
3730 let mt = u.metatable();
3732 self.fast_tm_table(mt.as_ref(), tm)
3733 }
3734
3735 pub fn table_get_with_tm(&mut self, t: &LuaValue, k: &LuaValue) -> Result<LuaValue, LuaError> {
3736 if let LuaValue::Table(tbl) = t {
3742 if !tbl.has_metatable() {
3743 return Ok(tbl.get(k));
3744 }
3745 }
3746 if let Some(v) = self.fast_get(t, k)? {
3747 return Ok(v);
3748 }
3749 let res = self.top_idx();
3750 self.push(LuaValue::Nil);
3751 crate::vm::finish_get(self, t.clone(), k.clone(), res, true, None, None)?;
3752 let value = self.get_at(res);
3753 self.pop();
3754 Ok(value)
3755 }
3756 #[inline]
3771 pub fn table_set_with_tm(
3772 &mut self,
3773 t: &LuaValue,
3774 k: LuaValue,
3775 v: LuaValue,
3776 ) -> Result<(), LuaError> {
3777 if let LuaValue::Table(tbl) = t {
3778 if !tbl.has_metatable() {
3779 self.gc_table_barrier_back(tbl, &v);
3780 return self.table_raw_set(t, k, v);
3781 }
3782 }
3783 if self.fast_get(t, &k)?.is_some() {
3784 self.gc_value_barrier_back(t, &v);
3785 return self.table_raw_set(t, k, v);
3786 }
3787 crate::vm::finish_set(self, t.clone(), k, v, true, None, None)
3788 }
3789 #[inline]
3790 pub fn table_raw_set(
3791 &mut self,
3792 t: &LuaValue,
3793 k: LuaValue,
3794 v: LuaValue,
3795 ) -> Result<(), LuaError> {
3796 let LuaValue::Table(tbl) = t else {
3797 return Err(LuaError::type_error(t, "index"));
3798 };
3799 let tbl = tbl.clone();
3800 tbl.raw_set(self, k, v)
3801 }
3802 #[inline]
3803 pub fn table_array_set(
3804 &mut self,
3805 t: &LuaValue,
3806 idx: usize,
3807 v: LuaValue,
3808 ) -> Result<(), LuaError> {
3809 let LuaValue::Table(tbl) = t else {
3810 return Err(LuaError::type_error(t, "index"));
3811 };
3812 let tbl = tbl.clone();
3813 tbl.raw_set_int(self, idx as i64 + 1, v)
3814 }
3815 pub fn table_ensure_array(&mut self, t: &LuaValue, n: usize) -> Result<(), LuaError> {
3816 let LuaValue::Table(tbl) = t else {
3817 return Err(LuaError::type_error(t, "index"));
3818 };
3819 if n > tbl.array_len() {
3820 tbl.resize(self, n, 0)?;
3821 }
3822 Ok(())
3823 }
3824 pub fn table_length(&mut self, t: &LuaValue) -> Result<i64, LuaError> {
3825 let LuaValue::Table(tbl) = t else {
3826 return Err(LuaError::type_error(t, "get length of"));
3827 };
3828 Ok(tbl.getn() as i64)
3829 }
3830 pub fn table_metatable(&mut self, v: &LuaValue) -> Option<GcRef<LuaTable>> {
3831 match v {
3832 LuaValue::Table(t) => t.metatable(),
3833 LuaValue::UserData(u) => u.metatable(),
3834 other => {
3835 let idx = other.base_type() as usize;
3836 self.global().mt[idx].clone()
3837 }
3838 }
3839 }
3840 pub fn table_resize(
3841 &mut self,
3842 t: &GcRef<LuaTable>,
3843 na: usize,
3844 nh: usize,
3845 ) -> Result<(), LuaError> {
3846 self.mark_gc_check_needed();
3847 t.resize(self, na, nh)
3848 }
3849 pub fn table_getn(&self, t: &GcRef<LuaTable>) -> i64 {
3850 let mut i: i64 = 1;
3858 loop {
3859 let v = t.get_int(i);
3860 if matches!(v, LuaValue::Nil) {
3861 return i - 1;
3862 }
3863 i += 1;
3864 }
3865 }
3866
3867 pub fn try_bin_tm(
3868 &mut self,
3869 p1: &LuaValue,
3870 p1_idx: Option<StackIdx>,
3871 p2: &LuaValue,
3872 p2_idx: Option<StackIdx>,
3873 res: StackIdx,
3874 tm: lua_types::tagmethod::TagMethod,
3875 ) -> Result<(), LuaError> {
3876 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3877 crate::tagmethods::try_bin_tm(self, p1, p1_idx, p2, p2_idx, res, event)
3878 }
3879 pub fn try_bin_i_tm(
3880 &mut self,
3881 p1: &LuaValue,
3882 p1_idx: Option<StackIdx>,
3883 imm: i64,
3884 flip: bool,
3885 res: StackIdx,
3886 tm: lua_types::tagmethod::TagMethod,
3887 ) -> Result<(), LuaError> {
3888 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3889 crate::tagmethods::try_bini_tm(self, p1, p1_idx, imm, flip, res, event)
3890 }
3891 pub fn try_bin_assoc_tm(
3892 &mut self,
3893 p1: &LuaValue,
3894 p1_idx: Option<StackIdx>,
3895 p2: &LuaValue,
3896 p2_idx: Option<StackIdx>,
3897 flip: bool,
3898 res: StackIdx,
3899 tm: lua_types::tagmethod::TagMethod,
3900 ) -> Result<(), LuaError> {
3901 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3902 crate::tagmethods::try_bin_assoc_tm(self, p1, p1_idx, p2, p2_idx, flip, res, event)
3903 }
3904 pub fn try_concat_tm(&mut self, _p1: &LuaValue, _p2: &LuaValue) -> Result<(), LuaError> {
3905 crate::tagmethods::try_concat_tm(self)
3906 }
3907 pub fn call_tm(
3908 &mut self,
3909 f: LuaValue,
3910 p1: &LuaValue,
3911 p2: &LuaValue,
3912 p3: &LuaValue,
3913 ) -> Result<(), LuaError> {
3914 crate::tagmethods::call_tm(self, f, p1.clone(), p2.clone(), p3.clone())
3915 }
3916 pub fn call_tm_res(
3917 &mut self,
3918 f: LuaValue,
3919 p1: &LuaValue,
3920 p2: &LuaValue,
3921 res: StackIdx,
3922 ) -> Result<(), LuaError> {
3923 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)
3924 }
3925 pub fn call_tm_res_bool(
3926 &mut self,
3927 f: LuaValue,
3928 p1: &LuaValue,
3929 p2: &LuaValue,
3930 ) -> Result<bool, LuaError> {
3931 let res = self.top_idx();
3932 self.push(LuaValue::Nil);
3933 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)?;
3934 let result = self.get_at(res).clone();
3935 self.pop();
3936 Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)))
3937 }
3938 pub fn call_order_tm(
3939 &mut self,
3940 p1: &LuaValue,
3941 p2: &LuaValue,
3942 tm: lua_types::tagmethod::TagMethod,
3943 ) -> Result<bool, LuaError> {
3944 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3945 crate::tagmethods::call_order_tm(self, p1, p2, event)
3946 }
3947 pub fn call_order_i_tm(
3948 &mut self,
3949 p1: &LuaValue,
3950 v2: i64,
3951 flip: bool,
3952 isfloat: bool,
3953 tm: lua_types::tagmethod::TagMethod,
3954 ) -> Result<bool, LuaError> {
3955 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3956 crate::tagmethods::call_orderi_tm(self, p1, v2 as i32, flip, isfloat, event)
3957 }
3958
3959 #[inline(always)]
3960 pub fn proto_code(
3961 &self,
3962 cl: &GcRef<lua_types::closure::LuaLClosure>,
3963 pc: u32,
3964 ) -> lua_types::opcode::Instruction {
3965 cl.proto.code[pc as usize]
3966 }
3967 #[inline(always)]
3968 pub fn proto_const(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> LuaValue {
3969 cl.proto.k[idx].clone()
3970 }
3971 #[inline(always)]
3977 pub fn proto_const_int(
3978 &self,
3979 cl: &GcRef<lua_types::closure::LuaLClosure>,
3980 idx: usize,
3981 ) -> Option<i64> {
3982 match &cl.proto.k[idx] {
3983 LuaValue::Int(v) => Some(*v),
3984 _ => None,
3985 }
3986 }
3987 #[inline(always)]
3991 pub fn proto_const_num(
3992 &self,
3993 cl: &GcRef<lua_types::closure::LuaLClosure>,
3994 idx: usize,
3995 ) -> Option<f64> {
3996 match &cl.proto.k[idx] {
3997 LuaValue::Float(f) => Some(*f),
3998 LuaValue::Int(v) => Some(*v as f64),
3999 _ => None,
4000 }
4001 }
4002 pub fn get_proto_instr(&self, ci: CallInfoIdx, pc: u32) -> lua_types::opcode::Instruction {
4003 let cl = self
4004 .ci_lua_closure(ci)
4005 .expect("get_proto_instr: CallInfo does not hold a Lua closure");
4006 cl.proto.code[pc as usize]
4007 }
4008 pub fn trace_call(&mut self, _idx: CallInfoIdx) -> Result<bool, LuaError> {
4013 Ok(crate::debug::trace_call(self)? != 0)
4014 }
4015 pub fn trace_exec(&mut self, _idx: CallInfoIdx, pc: u32) -> Result<bool, LuaError> {
4018 Ok(crate::debug::trace_exec(self, pc)? != 0)
4019 }
4020 pub fn hook_call(&mut self, idx: CallInfoIdx) -> Result<(), LuaError> {
4021 crate::do_::hookcall(self, idx)
4022 }
4023 #[inline(always)]
4024 fn gc_step_flags(&self) -> Option<(bool, bool)> {
4025 let g = self.global();
4026 if !g.is_gc_running() {
4027 return None;
4028 }
4029 let should_collect = g.heap.would_collect();
4030 let has_finalizers = g.finalizers.has_to_be_finalized();
4031 if should_collect || has_finalizers {
4032 Some((should_collect, has_finalizers))
4033 } else {
4034 None
4035 }
4036 }
4037
4038 #[inline(always)]
4039 fn should_check_gc(&mut self) -> bool {
4040 if self.gc_check_needed {
4041 return true;
4042 }
4043 if self.global().finalizers.has_to_be_finalized() {
4044 self.gc_check_needed = true;
4045 return true;
4046 }
4047 false
4048 }
4049
4050 #[inline(always)]
4051 pub(crate) fn mark_gc_check_needed(&mut self) {
4052 self.gc_check_needed = true;
4053 }
4054
4055 pub fn gc_trace_bound(&self) -> usize {
4069 (self.top.0 as usize).min(self.stack.len())
4070 }
4071
4072 pub fn clear_dead_stack_tail(&mut self) {
4079 let bound = self.gc_trace_bound();
4080 for slot in &mut self.stack[bound..] {
4081 slot.val = LuaValue::Nil;
4082 }
4083 }
4084
4085 pub fn gc_clear_dead_stack_tails(&mut self) {
4092 self.clear_dead_stack_tail();
4093 let global = self.global.clone();
4094 let g = global.borrow();
4095 for entry in g.threads.values() {
4096 if let Ok(mut t) = entry.state.try_borrow_mut() {
4097 t.clear_dead_stack_tail();
4098 }
4099 }
4100 }
4101
4102 pub fn gc_pre_collect_clear(&mut self) {
4106 if self.global().heap.would_collect() {
4107 self.gc_clear_dead_stack_tails();
4108 }
4109 }
4110
4111 #[inline(always)]
4112 pub fn gc_check_step(&mut self) {
4113 if !self.allowhook {
4114 return;
4115 }
4116 if !self.should_check_gc() {
4117 return;
4118 }
4119 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4120 self.gc_check_needed = false;
4121 return;
4122 };
4123 if should_collect || has_finalizers {
4124 if should_collect {
4125 self.gc_clear_dead_stack_tails();
4126 self.gc().check_step();
4127 }
4128 crate::api::run_pending_finalizers(self);
4129 self.gc_check_needed = true;
4130 }
4131 let should_keep_checking = {
4132 let g = self.global();
4133 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4134 };
4135 self.gc_check_needed = should_keep_checking;
4136 }
4137 #[inline(always)]
4138 pub fn gc_cond_step(&mut self) {
4139 if !self.allowhook {
4140 return;
4141 }
4142 if !self.should_check_gc() {
4143 return;
4144 }
4145 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4146 self.gc_check_needed = false;
4147 return;
4148 };
4149 if should_collect || has_finalizers {
4150 if should_collect {
4151 self.gc_clear_dead_stack_tails();
4152 self.gc().check_step();
4153 }
4154 crate::api::run_pending_finalizers(self);
4155 self.gc_check_needed = true;
4156 }
4157 let should_keep_checking = {
4158 let g = self.global();
4159 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4160 };
4161 self.gc_check_needed = should_keep_checking;
4162 }
4163 pub fn gc_barrier_back(&mut self, t: &dyn std::any::Any, v: &LuaValue) {
4164 self.gc().barrier_back(t, v);
4165 }
4166 #[inline(always)]
4167 pub fn gc_value_barrier_back(&mut self, t: &LuaValue, v: &LuaValue) {
4168 if !v.is_collectable() {
4169 return;
4170 }
4171 if let LuaValue::Table(tbl) = t {
4172 self.gc_table_barrier_back(tbl, v);
4173 } else {
4174 self.gc_barrier_back(t, v);
4175 }
4176 }
4177 #[inline(always)]
4178 pub fn gc_table_barrier_back(&mut self, t: &GcRef<LuaTable>, v: &LuaValue) {
4179 if !v.is_collectable() {
4180 return;
4181 }
4182 self.gc().table_barrier_back(t, v);
4183 }
4184 pub fn gc_barrier_upval(&mut self, uv: &GcRef<UpVal>, v: &LuaValue) {
4185 self.gc().barrier(uv, v);
4186 }
4187 pub fn is_main_thread(&mut self) -> bool {
4193 let g = self.global();
4194 g.current_thread_id == g.main_thread_id
4195 }
4196 pub fn obj_type_name<'v>(&self, v: &'v LuaValue) -> std::borrow::Cow<'static, [u8]> {
4197 match v {
4198 LuaValue::LightUserData(_) => std::borrow::Cow::Borrowed(b"light userdata"),
4199 LuaValue::Table(t) => {
4200 if let Some(mt) = t.metatable() {
4201 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4202 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4203 }
4204 }
4205 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4206 }
4207 LuaValue::UserData(u) => {
4208 if let Some(mt) = u.metatable() {
4209 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4210 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4211 }
4212 }
4213 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4214 }
4215 _ => std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type())),
4216 }
4217 }
4218
4219 pub fn full_type_name(&mut self, v: &LuaValue) -> Result<Vec<u8>, LuaError> {
4220 crate::tagmethods::obj_type_name(self, v)
4221 }
4222 pub fn emit_warning(&mut self, _msg: &[u8], _to_cont: bool) {
4223 warning(self, _msg, _to_cont)
4224 }
4225}
4226
4227pub struct GcHandle<'a> {
4233 _state: &'a mut LuaState,
4234}
4235
4236struct CollectRoots<'a> {
4243 global: &'a GlobalState,
4244 thread: &'a LuaState,
4245}
4246
4247#[derive(Clone, Copy)]
4248enum HeapCollectMode {
4249 Full,
4250 Step,
4251 Minor,
4252}
4253
4254impl<'a> lua_gc::Trace for CollectRoots<'a> {
4255 fn trace(&self, m: &mut lua_gc::Marker) {
4256 self.global.trace(m);
4257 self.thread.trace(m);
4258 }
4259}
4260
4261#[derive(Clone, Copy)]
4262enum BarrierKind {
4263 Forward,
4264 Backward,
4265}
4266
4267fn barrier_lua_value<P>(
4268 heap: &lua_gc::Heap,
4269 parent: GcRef<P>,
4270 child: &LuaValue,
4271 generational: bool,
4272 kind: BarrierKind,
4273) where
4274 P: lua_gc::Trace + 'static,
4275{
4276 if !child.is_collectable() {
4277 return;
4278 }
4279 if generational && matches!(kind, BarrierKind::Backward) {
4280 heap.generational_backward_barrier(parent.0);
4281 }
4282 match child {
4283 LuaValue::Str(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4284 LuaValue::Table(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4285 LuaValue::Function(LuaClosure::Lua(c)) => {
4286 barrier_gc_child(heap, parent, *c, generational, kind)
4287 }
4288 LuaValue::Function(LuaClosure::C(c)) => {
4289 barrier_gc_child(heap, parent, *c, generational, kind)
4290 }
4291 LuaValue::UserData(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4292 LuaValue::Thread(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4293 LuaValue::Nil
4294 | LuaValue::Bool(_)
4295 | LuaValue::Int(_)
4296 | LuaValue::Float(_)
4297 | LuaValue::LightUserData(_)
4298 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4299 }
4300}
4301
4302fn barrier_gc_child<P, C>(
4303 heap: &lua_gc::Heap,
4304 parent: GcRef<P>,
4305 child: GcRef<C>,
4306 generational: bool,
4307 kind: BarrierKind,
4308) where
4309 P: lua_gc::Trace + 'static,
4310 C: lua_gc::Trace + 'static,
4311{
4312 if generational && matches!(kind, BarrierKind::Forward) {
4313 heap.generational_forward_barrier(parent.0, child.0);
4314 } else if matches!(kind, BarrierKind::Backward) {
4315 heap.barrier_back(parent.0, child.0);
4316 } else {
4317 heap.barrier(parent.0, child.0);
4318 }
4319}
4320
4321fn barrier_child_any<P>(
4322 heap: &lua_gc::Heap,
4323 parent: GcRef<P>,
4324 child: &dyn std::any::Any,
4325 generational: bool,
4326 kind: BarrierKind,
4327) where
4328 P: lua_gc::Trace + 'static,
4329{
4330 if let Some(v) = child.downcast_ref::<LuaValue>() {
4331 barrier_lua_value(heap, parent, v, generational, kind);
4332 } else if let Some(c) = child.downcast_ref::<GcRef<LuaString>>() {
4333 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4334 } else if let Some(c) = child.downcast_ref::<GcRef<LuaTable>>() {
4335 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4336 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureLua>>() {
4337 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4338 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureC>>() {
4339 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4340 } else if let Some(c) = child.downcast_ref::<GcRef<LuaUserData>>() {
4341 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4342 } else if let Some(c) = child.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4343 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4344 } else if let Some(c) = child.downcast_ref::<GcRef<LuaProto>>() {
4345 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4346 } else if let Some(c) = child.downcast_ref::<GcRef<UpVal>>() {
4347 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4348 }
4349}
4350
4351fn barrier_any(
4352 heap: &lua_gc::Heap,
4353 parent: &dyn std::any::Any,
4354 child: &dyn std::any::Any,
4355 generational: bool,
4356 kind: BarrierKind,
4357) {
4358 if let Some(v) = parent.downcast_ref::<LuaValue>() {
4359 match v {
4360 LuaValue::Str(p) => barrier_child_any(heap, *p, child, generational, kind),
4361 LuaValue::Table(p) => barrier_child_any(heap, *p, child, generational, kind),
4362 LuaValue::Function(LuaClosure::Lua(p)) => {
4363 barrier_child_any(heap, *p, child, generational, kind)
4364 }
4365 LuaValue::Function(LuaClosure::C(p)) => {
4366 barrier_child_any(heap, *p, child, generational, kind)
4367 }
4368 LuaValue::UserData(p) => barrier_child_any(heap, *p, child, generational, kind),
4369 LuaValue::Thread(p) => barrier_child_any(heap, *p, child, generational, kind),
4370 LuaValue::Nil
4371 | LuaValue::Bool(_)
4372 | LuaValue::Int(_)
4373 | LuaValue::Float(_)
4374 | LuaValue::LightUserData(_)
4375 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4376 }
4377 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaString>>() {
4378 barrier_child_any(heap, p.clone(), child, generational, kind);
4379 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaTable>>() {
4380 barrier_child_any(heap, p.clone(), child, generational, kind);
4381 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureLua>>() {
4382 barrier_child_any(heap, p.clone(), child, generational, kind);
4383 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureC>>() {
4384 barrier_child_any(heap, p.clone(), child, generational, kind);
4385 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaUserData>>() {
4386 barrier_child_any(heap, p.clone(), child, generational, kind);
4387 } else if let Some(p) = parent.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4388 barrier_child_any(heap, p.clone(), child, generational, kind);
4389 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaProto>>() {
4390 barrier_child_any(heap, p.clone(), child, generational, kind);
4391 } else if let Some(p) = parent.downcast_ref::<GcRef<UpVal>>() {
4392 barrier_child_any(heap, p.clone(), child, generational, kind);
4393 }
4394}
4395
4396fn trace_reachable_threads(
4408 global: &GlobalState,
4409 _current_thread_id: u64,
4410 marker: &mut lua_gc::Marker,
4411) {
4412 use lua_gc::Trace;
4413
4414 #[cfg(debug_assertions)]
4415 let mut uncovered_borrowed: Vec<u64> = Vec::new();
4416
4417 loop {
4418 let visited_before = marker.visited_count();
4419 for (id, entry) in global.threads.iter() {
4420 if thread_entry_marked_alive(marker, *id, entry) {
4421 match entry.state.try_borrow() {
4422 Ok(thread) => thread.trace(marker),
4423 Err(_) => {
4424 #[cfg(debug_assertions)]
4425 if *id != _current_thread_id && !uncovered_borrowed.contains(id) {
4426 uncovered_borrowed.push(*id);
4427 }
4428 }
4429 }
4430 }
4431 }
4432 marker.drain_gray_queue();
4433 if marker.visited_count() == visited_before {
4434 break;
4435 }
4436 }
4437
4438 #[cfg(debug_assertions)]
4439 debug_assert!(
4440 uncovered_borrowed.len() <= global.suspended_parent_stacks.len(),
4441 "GC root loss: {} marked-alive coroutine(s) (ids {:?}) were mutably \
4442 borrowed at collect time with only {} parent snapshot(s) covering \
4443 them — their stacks were NOT traced this cycle, so anything only \
4444 reachable from them will be swept (issue #140 bug-A class). A borrow \
4445 of a coroutine's state must not be held across an allocation \
4446 checkpoint without pushing a parent GC snapshot.",
4447 uncovered_borrowed.len(),
4448 uncovered_borrowed,
4449 global.suspended_parent_stacks.len()
4450 );
4451}
4452
4453fn thread_entry_marked_alive(
4454 marker: &lua_gc::Marker,
4455 id: u64,
4456 entry: &ThreadRegistryEntry,
4457) -> bool {
4458 marker.is_marked_or_old(entry.value.0) && entry.value.id == id
4459}
4460
4461fn lua_value_marked_or_old(marker: &lua_gc::Marker, value: &LuaValue) -> bool {
4462 match value {
4463 LuaValue::Str(v) => marker.is_marked_or_old(v.0),
4464 LuaValue::Table(v) => marker.is_marked_or_old(v.0),
4465 LuaValue::Function(LuaClosure::Lua(v)) => marker.is_marked_or_old(v.0),
4466 LuaValue::Function(LuaClosure::C(v)) => marker.is_marked_or_old(v.0),
4467 LuaValue::UserData(v) => marker.is_marked_or_old(v.0),
4468 LuaValue::Thread(v) => marker.is_marked_or_old(v.0),
4469 LuaValue::Nil
4470 | LuaValue::Bool(_)
4471 | LuaValue::Int(_)
4472 | LuaValue::Float(_)
4473 | LuaValue::LightUserData(_)
4474 | LuaValue::Function(LuaClosure::LightC(_)) => true,
4475 }
4476}
4477
4478fn lua_value_identity(value: &LuaValue) -> Option<usize> {
4479 match value {
4480 LuaValue::Str(v) => Some(v.identity()),
4481 LuaValue::Table(v) => Some(v.identity()),
4482 LuaValue::Function(LuaClosure::Lua(v)) => Some(v.identity()),
4483 LuaValue::Function(LuaClosure::C(v)) => Some(v.identity()),
4484 LuaValue::UserData(v) => Some(v.identity()),
4485 LuaValue::Thread(v) => Some(v.identity()),
4486 LuaValue::Nil
4487 | LuaValue::Bool(_)
4488 | LuaValue::Int(_)
4489 | LuaValue::Float(_)
4490 | LuaValue::LightUserData(_)
4491 | LuaValue::Function(LuaClosure::LightC(_)) => None,
4492 }
4493}
4494
4495fn finalizer_marked_or_old(marker: &lua_gc::Marker, object: &FinalizerObject) -> bool {
4496 match object {
4497 FinalizerObject::Table(t) => marker.is_marked_or_old(t.0),
4498 FinalizerObject::UserData(u) => marker.is_marked_or_old(u.0),
4499 }
4500}
4501
4502fn weak_snapshot_tables<'a>(
4503 snapshot: &'a lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>>,
4504) -> impl Iterator<Item = &'a GcRef<LuaTable>> {
4505 snapshot
4506 .weak_values
4507 .iter()
4508 .chain(snapshot.ephemeron.iter())
4509 .chain(snapshot.all_weak.iter())
4510}
4511
4512fn close_open_upvalues_for_unreachable_threads(global: &GlobalState, marker: &mut lua_gc::Marker) {
4513 use lua_gc::Trace;
4514
4515 let mut closed_values = Vec::<LuaValue>::new();
4516 for (id, entry) in global.threads.iter() {
4517 if entry.value.id != *id {
4518 continue;
4519 }
4520 if thread_entry_marked_alive(marker, *id, entry) {
4521 continue;
4522 }
4523 let Ok(thread) = entry.state.try_borrow() else {
4524 continue;
4525 };
4526 for uv in thread.openupval.iter() {
4527 if !marker.is_visited(uv.identity()) {
4528 continue;
4529 }
4530 let Some((thread_id, idx)) = uv.try_open_payload() else {
4531 continue;
4532 };
4533 if thread_id as u64 != *id {
4534 continue;
4535 }
4536 let value = thread.get_at(idx);
4537 uv.close_with(value.clone());
4538 closed_values.push(value);
4539 }
4540 }
4541 for value in closed_values {
4542 value.trace(marker);
4543 }
4544 marker.drain_gray_queue();
4545}
4546
4547fn record_dead_interned_strings(
4554 global: &GlobalState,
4555 marker: &lua_gc::Marker,
4556 dead_pairs: &std::cell::RefCell<Vec<(u32, usize)>>,
4557) {
4558 let mut dead = dead_pairs.borrow_mut();
4559 for s in global.interned_lt.iter() {
4560 let id = s.identity();
4561 if !marker.is_visited(id) {
4562 dead.push((s.hash(), id));
4563 }
4564 }
4565}
4566
4567fn remove_dead_interned_strings(global: &mut GlobalState, dead_pairs: Vec<(u32, usize)>) {
4579 for (hash, id) in dead_pairs {
4580 global.interned_lt.remove(hash, id);
4581 }
4582 global.interned_lt.shrink_if_sparse();
4583}
4584
4585impl<'a> GcHandle<'a> {
4586 pub fn check_step(&self) {
4593 if !self._state.global().is_gc_running() {
4594 return;
4595 }
4596 if self._state.global().is_gen_mode() {
4597 let should_collect = {
4598 let g = self._state.global();
4599 g.heap.would_collect() || g.gc_debt() > 0
4600 };
4601 if should_collect {
4602 self.generational_step();
4603 }
4604 } else {
4605 self.collect_via_heap(false);
4606 }
4607 }
4608
4609 pub fn full_collect(&self) {
4611 if self._state.global().is_gen_mode() {
4612 self.fullgen();
4613 } else {
4614 self.collect_via_heap(true);
4615 }
4616 }
4617
4618 fn negative_debt(bytes: usize) -> isize {
4619 -(bytes.min(isize::MAX as usize) as isize)
4620 }
4621
4622 fn set_minor_debt(&self) {
4623 let mut g = self._state.global_mut();
4624 let total = g.total_bytes();
4625 let growth = (total / 100).saturating_mul(g.genminormul as usize);
4626 g.heap
4627 .set_threshold_bytes(total.saturating_add(growth.max(1)));
4628 set_debt(&mut *g, Self::negative_debt(growth));
4629 }
4630
4631 fn set_pause_debt(&self) {
4632 let mut g = self._state.global_mut();
4633 let total = g.total_bytes();
4634 let pause = g.gc_pause_param().max(0) as usize;
4635 let threshold = g.gc_estimate.max(1).saturating_mul(pause) / 100;
4636 let debt = if threshold > total {
4637 Self::negative_debt(threshold - total)
4638 } else {
4639 0
4640 };
4641 let heap_threshold = if threshold > total {
4642 threshold
4643 } else {
4644 total.saturating_add(1)
4645 };
4646 g.heap.set_threshold_bytes(heap_threshold);
4647 set_debt(&mut *g, debt);
4648 }
4649
4650 fn enter_incremental_mode(&self) {
4651 let mut g = self._state.global_mut();
4652 g.heap.reset_all_ages();
4653 g.finalizers.reset_generation_boundaries();
4654 g.gckind = GcKind::Incremental as u8;
4655 g.lastatomic = 0;
4656 }
4657
4658 fn enter_generational_mode(&self) -> usize {
4659 self.collect_via_heap_mode(HeapCollectMode::Full);
4660 let numobjs = {
4661 let mut g = self._state.global_mut();
4662 g.heap.promote_all_to_old();
4663 g.finalizers.promote_all_pending_to_old();
4664 g.heap.allgc_count()
4665 };
4666 let total = self._state.global().total_bytes();
4667 {
4668 let mut g = self._state.global_mut();
4669 g.gckind = GcKind::Generational as u8;
4670 g.lastatomic = 0;
4671 g.gc_estimate = total;
4672 }
4673 self.set_minor_debt();
4674 numobjs
4675 }
4676
4677 fn fullgen(&self) -> usize {
4678 self.enter_incremental_mode();
4679 self.enter_generational_mode()
4680 }
4681
4682 fn stepgenfull(&self, lastatomic: usize) {
4683 if self._state.global().gckind == GcKind::Generational as u8 {
4684 self.enter_incremental_mode();
4685 }
4686 self.collect_via_heap_mode(HeapCollectMode::Full);
4687 let newatomic = self._state.global().heap.allgc_count().max(1);
4688 if newatomic < lastatomic.saturating_add(lastatomic >> 3) {
4689 {
4690 let mut g = self._state.global_mut();
4691 g.heap.promote_all_to_old();
4692 g.finalizers.promote_all_pending_to_old();
4693 }
4694 let total = self._state.global().total_bytes();
4695 {
4696 let mut g = self._state.global_mut();
4697 g.gckind = GcKind::Generational as u8;
4698 g.lastatomic = 0;
4699 g.gc_estimate = total;
4700 }
4701 self.set_minor_debt();
4702 } else {
4703 {
4704 let mut g = self._state.global_mut();
4705 g.heap.reset_all_ages();
4706 g.finalizers.reset_generation_boundaries();
4707 }
4708 let total = self._state.global().total_bytes();
4709 {
4710 let mut g = self._state.global_mut();
4711 g.gckind = GcKind::Incremental as u8;
4712 g.lastatomic = newatomic;
4713 g.gc_estimate = total;
4714 }
4715 self.set_pause_debt();
4716 }
4717 }
4718
4719 fn collect_via_heap(&self, force: bool) {
4728 self.collect_via_heap_mode(if force {
4729 HeapCollectMode::Full
4730 } else {
4731 HeapCollectMode::Step
4732 });
4733 }
4734
4735 fn collect_via_heap_mode(&self, mode: HeapCollectMode) {
4736 use lua_gc::Trace;
4737 let state_ref: &LuaState = &*self._state;
4738
4739 if matches!(mode, HeapCollectMode::Step) {
4745 let g = state_ref.global.borrow();
4746 if !g.heap.would_collect() {
4747 return;
4748 }
4749 }
4750
4751 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
4755 let mut g = state_ref.global.borrow_mut();
4756 g.weak_tables_registry.live_snapshot_by_kind()
4757 };
4758
4759 let weak_table_capacity = weak_tables_snapshot.len();
4764 let (pending_snapshot, thread_capacity, _interned_capacity): (
4765 Vec<FinalizerObject>,
4766 usize,
4767 usize,
4768 ) = {
4769 let g = state_ref.global.borrow();
4770 let pending = match mode {
4771 HeapCollectMode::Minor => g.finalizers.pending_minor_snapshot(),
4772 HeapCollectMode::Full | HeapCollectMode::Step => g.finalizers.pending_snapshot(),
4773 };
4774 (pending, g.threads.len(), g.interned_lt.len())
4775 };
4776 let finalizer_capacity = pending_snapshot.len();
4777
4778 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4779 std::cell::RefCell::new(std::collections::HashSet::new());
4780 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
4781 std::cell::RefCell::new(Vec::new());
4782 let finalizing_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4783 std::cell::RefCell::new(std::collections::HashSet::new());
4784 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
4785 std::cell::RefCell::new(std::collections::HashSet::new());
4786 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
4787 let collect_ran = std::cell::Cell::new(false);
4788
4789 {
4790 let global = state_ref.global.borrow();
4791 global.heap.unpause();
4792 let roots = CollectRoots {
4793 global: &*global,
4794 thread: state_ref,
4795 };
4796 let hook = |marker: &mut lua_gc::Marker| {
4797 collect_ran.set(true);
4798 alive_ids.borrow_mut().reserve(weak_table_capacity);
4799 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
4800 finalizing_ids.borrow_mut().reserve(finalizer_capacity);
4801 alive_thread_ids.borrow_mut().reserve(thread_capacity);
4802 trace_reachable_threads(&*global, global.current_thread_id, marker);
4803 close_open_upvalues_for_unreachable_threads(&*global, marker);
4804 loop {
4805 let visited_before = marker.visited_count();
4806 for t in &weak_tables_snapshot.ephemeron {
4807 if !marker.is_marked_or_old(t.0) {
4808 continue;
4809 }
4810 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
4811 lua_value_marked_or_old(marker, v)
4812 });
4813 for v in &to_mark {
4814 v.trace(marker);
4815 }
4816 }
4817 marker.drain_gray_queue();
4818 if marker.visited_count() == visited_before {
4819 break;
4820 }
4821 }
4822 for pf in &pending_snapshot {
4823 if !finalizer_marked_or_old(marker, pf) {
4824 pf.mark(marker);
4825 finalizing_ids.borrow_mut().insert(pf.identity());
4826 newly_unreachable.borrow_mut().push(pf.clone());
4827 }
4828 }
4829 marker.drain_gray_queue();
4830 loop {
4831 let visited_before = marker.visited_count();
4832 for t in &weak_tables_snapshot.ephemeron {
4833 if !marker.is_marked_or_old(t.0) {
4834 continue;
4835 }
4836 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
4837 lua_value_marked_or_old(marker, v)
4838 });
4839 for v in &to_mark {
4840 v.trace(marker);
4841 }
4842 }
4843 marker.drain_gray_queue();
4844 if marker.visited_count() == visited_before {
4845 break;
4846 }
4847 }
4848 for t in weak_snapshot_tables(&weak_tables_snapshot) {
4849 let id = t.identity();
4850 if marker.is_marked_or_old(t.0) {
4851 let to_mark = {
4852 let finalizing = finalizing_ids.borrow();
4853 t.prune_weak_dead_with_value(
4854 &|v| lua_value_marked_or_old(marker, v),
4855 &|v| {
4856 lua_value_marked_or_old(marker, v)
4857 && lua_value_identity(v)
4858 .map_or(true, |id| !finalizing.contains(&id))
4859 },
4860 )
4861 };
4862 for v in &to_mark {
4863 v.trace(marker);
4864 }
4865 alive_ids.borrow_mut().insert(id);
4866 }
4867 }
4868 marker.drain_gray_queue();
4869 {
4870 let mut alive = alive_thread_ids.borrow_mut();
4871 for (id, entry) in global.threads.iter() {
4872 if thread_entry_marked_alive(marker, *id, entry) {
4873 alive.insert(*id);
4874 }
4875 }
4876 }
4877 record_dead_interned_strings(&*global, marker, &dead_interned);
4878 };
4879 match mode {
4880 HeapCollectMode::Full => global.heap.full_collect_with_post_mark(&roots, hook),
4881 HeapCollectMode::Step => global.heap.step_with_post_mark(&roots, hook),
4882 HeapCollectMode::Minor => global.heap.minor_collect_with_post_mark(&roots, hook),
4883 }
4884 }
4885
4886 if !collect_ran.get() {
4887 return;
4888 }
4889
4890 let alive_set = alive_ids.into_inner();
4894 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
4895 let alive_thread_ids = alive_thread_ids.into_inner();
4896 let dead_interned = dead_interned.into_inner();
4897 let mut g = state_ref.global.borrow_mut();
4898 remove_dead_interned_strings(&mut *g, dead_interned);
4899 g.weak_tables_registry.retain_identities(&alive_set);
4900 let main_thread_id = g.main_thread_id;
4901 g.threads.retain(|id, _| alive_thread_ids.contains(id));
4902 g.cross_thread_upvals
4903 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
4904 let promoted = g.finalizers.promote_pending_to_finalized(promote);
4908 for object in &promoted {
4909 if let Some(ptr) = object.heap_ptr() {
4910 g.heap.move_finobj_to_tobefnz(ptr);
4911 }
4912 }
4913 if matches!(mode, HeapCollectMode::Minor) {
4914 g.finalizers.finish_minor_collection();
4915 }
4916 }
4917
4918 pub fn generational_step(&self) -> bool {
4920 self.generational_step_with_major(true)
4921 }
4922
4923 pub fn generational_step_minor_only(&self) -> bool {
4929 self.generational_step_with_major(false)
4930 }
4931
4932 fn generational_step_with_major(&self, allow_major: bool) -> bool {
4933 let (lastatomic, majorbase, majorinc, should_major) = {
4934 let g = self._state.global();
4935 let majorbase = if g.gc_estimate == 0 {
4936 g.total_bytes()
4937 } else {
4938 g.gc_estimate
4939 };
4940 let majormul = g.gc_genmajormul_param().max(0) as usize;
4941 let majorinc = (majorbase / 100).saturating_mul(majormul);
4942 let debt_due = g.gc_debt() > 0 || g.heap.would_collect();
4943 let should_major =
4944 allow_major && debt_due && g.total_bytes() > majorbase.saturating_add(majorinc);
4945 (g.lastatomic, majorbase, majorinc, should_major)
4946 };
4947
4948 if lastatomic != 0 {
4949 self.stepgenfull(lastatomic);
4950 debug_assert!(self._state.global().is_gen_mode());
4951 return true;
4952 }
4953
4954 if should_major {
4955 let numobjs = self.fullgen();
4956 let after = self._state.global().total_bytes();
4957 if after < majorbase.saturating_add(majorinc / 2) {
4958 self.set_minor_debt();
4959 } else {
4960 {
4961 let mut g = self._state.global_mut();
4962 g.lastatomic = numobjs.max(1);
4963 }
4964 self.set_pause_debt();
4965 }
4966 } else {
4967 self.collect_via_heap_mode(HeapCollectMode::Minor);
4968 self.set_minor_debt();
4969 self._state.global_mut().gc_estimate = majorbase;
4970 }
4971
4972 debug_assert!(self._state.global().is_gen_mode());
4973 true
4974 }
4975
4976 pub fn step(&self) { }
4979
4980 pub fn incremental_step(&self, work_units: isize) -> bool {
4993 self.incremental_step_to_state(work_units, None)
4994 }
4995
4996 pub fn run_until_gc_state_for_test(&self, target: lua_gc::GcState) -> bool {
5001 self.incremental_step_to_state(isize::MAX / 4, Some(target));
5002 self._state.global().heap.gc_state() == target
5003 }
5004
5005 fn incremental_step_to_state(
5006 &self,
5007 work_units: isize,
5008 target: Option<lua_gc::GcState>,
5009 ) -> bool {
5010 use lua_gc::{StepBudget, StepOutcome, Trace};
5011 let state_ref: &LuaState = &*self._state;
5012
5013 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5014 let mut g = state_ref.global.borrow_mut();
5015 g.weak_tables_registry.live_snapshot_by_kind()
5016 };
5017
5018 let weak_table_capacity = weak_tables_snapshot.len();
5019 let (pending_snapshot, thread_capacity, _interned_capacity): (
5020 Vec<FinalizerObject>,
5021 usize,
5022 usize,
5023 ) = {
5024 let g = state_ref.global.borrow();
5025 (
5026 g.finalizers.pending_snapshot(),
5027 g.threads.len(),
5028 g.interned_lt.len(),
5029 )
5030 };
5031 let finalizer_capacity = pending_snapshot.len();
5032
5033 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5034 std::cell::RefCell::new(std::collections::HashSet::new());
5035 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
5036 std::cell::RefCell::new(Vec::new());
5037 let finalizing_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
5038 std::cell::RefCell::new(std::collections::HashSet::new());
5039 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
5040 std::cell::RefCell::new(std::collections::HashSet::new());
5041 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5042 let atomic_ran = std::cell::Cell::new(false);
5043
5044 let stop_target = {
5045 let g = state_ref.global.borrow();
5046 match (target, g.heap.gc_state()) {
5047 (Some(target), _) => Some(target),
5048 (None, lua_gc::GcState::CallFin) => None,
5049 (None, _) => Some(lua_gc::GcState::CallFin),
5050 }
5051 };
5052
5053 let outcome = {
5054 let global = state_ref.global.borrow();
5055 global.heap.unpause();
5056 let roots = CollectRoots {
5057 global: &*global,
5058 thread: state_ref,
5059 };
5060 let hook = |marker: &mut lua_gc::Marker| {
5061 atomic_ran.set(true);
5062 alive_ids.borrow_mut().reserve(weak_table_capacity);
5063 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
5064 finalizing_ids.borrow_mut().reserve(finalizer_capacity);
5065 alive_thread_ids.borrow_mut().reserve(thread_capacity);
5066 trace_reachable_threads(&*global, global.current_thread_id, marker);
5067 close_open_upvalues_for_unreachable_threads(&*global, marker);
5068 loop {
5069 let visited_before = marker.visited_count();
5070 for t in &weak_tables_snapshot.ephemeron {
5071 let t_id = t.identity();
5072 if !marker.is_visited(t_id) {
5073 continue;
5074 }
5075 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5076 for v in &to_mark {
5077 v.trace(marker);
5078 }
5079 }
5080 marker.drain_gray_queue();
5081 if marker.visited_count() == visited_before {
5082 break;
5083 }
5084 }
5085 for pf in &pending_snapshot {
5086 if !marker.is_visited(pf.identity()) {
5087 pf.mark(marker);
5088 finalizing_ids.borrow_mut().insert(pf.identity());
5089 newly_unreachable.borrow_mut().push(pf.clone());
5090 }
5091 }
5092 marker.drain_gray_queue();
5093 loop {
5094 let visited_before = marker.visited_count();
5095 for t in &weak_tables_snapshot.ephemeron {
5096 let t_id = t.identity();
5097 if !marker.is_visited(t_id) {
5098 continue;
5099 }
5100 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5101 for v in &to_mark {
5102 v.trace(marker);
5103 }
5104 }
5105 marker.drain_gray_queue();
5106 if marker.visited_count() == visited_before {
5107 break;
5108 }
5109 }
5110 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5111 let id = t.identity();
5112 if marker.is_visited(id) {
5113 let to_mark = {
5114 let finalizing = finalizing_ids.borrow();
5115 t.prune_weak_dead_with(&|id| marker.is_visited(id), &|id| {
5116 marker.is_visited(id) && !finalizing.contains(&id)
5117 })
5118 };
5119 for v in &to_mark {
5120 v.trace(marker);
5121 }
5122 alive_ids.borrow_mut().insert(id);
5123 }
5124 }
5125 marker.drain_gray_queue();
5126 {
5127 let mut alive = alive_thread_ids.borrow_mut();
5128 for (id, entry) in global.threads.iter() {
5129 if thread_entry_marked_alive(marker, *id, entry) {
5130 alive.insert(*id);
5131 }
5132 }
5133 }
5134 record_dead_interned_strings(&*global, marker, &dead_interned);
5135 };
5136 let budget = StepBudget::from_work(work_units);
5137 if let Some(target) = stop_target {
5138 global
5139 .heap
5140 .incremental_run_until_state_with_post_mark(&roots, target, work_units, hook)
5141 } else {
5142 global
5143 .heap
5144 .incremental_step_with_post_mark(&roots, budget, hook)
5145 }
5146 };
5147
5148 if atomic_ran.get() {
5149 let alive_set = alive_ids.into_inner();
5150 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5151 let alive_thread_ids = alive_thread_ids.into_inner();
5152 let dead_interned = dead_interned.into_inner();
5153 let mut g = state_ref.global.borrow_mut();
5154 remove_dead_interned_strings(&mut *g, dead_interned);
5155 g.weak_tables_registry.retain_identities(&alive_set);
5156 let main_thread_id = g.main_thread_id;
5157 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5158 g.cross_thread_upvals
5159 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5160 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5161 for object in &promoted {
5162 if let Some(ptr) = object.heap_ptr() {
5163 g.heap.move_finobj_to_tobefnz(ptr);
5164 }
5165 }
5166 }
5167
5168 let mut paused = matches!(outcome, StepOutcome::Paused);
5169 if target.is_none()
5170 && self._state.global().heap.gc_state() == lua_gc::GcState::CallFin
5171 && !self._state.global().finalizers.has_to_be_finalized()
5172 {
5173 paused = self._state.global().heap.finish_callfin_phase();
5174 }
5175
5176 paused
5177 }
5178
5179 pub fn prune_weak_tables_mark_only(&self) {
5186 use lua_gc::Trace;
5187 let state_ref: &LuaState = &*self._state;
5188
5189 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5190 let mut g = state_ref.global.borrow_mut();
5191 g.weak_tables_registry.live_snapshot_by_kind()
5192 };
5193 let _interned_capacity = {
5194 let g = state_ref.global.borrow();
5195 g.interned_lt.len()
5196 };
5197
5198 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5199
5200 {
5201 let global = state_ref.global.borrow();
5202 global.heap.unpause();
5203 let roots = CollectRoots {
5204 global: &*global,
5205 thread: state_ref,
5206 };
5207 let hook = |marker: &mut lua_gc::Marker| {
5208 trace_reachable_threads(&*global, global.current_thread_id, marker);
5209 loop {
5210 let visited_before = marker.visited_count();
5211 for t in &weak_tables_snapshot.ephemeron {
5212 let t_id = t.identity();
5213 if !marker.is_visited(t_id) {
5214 continue;
5215 }
5216 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5217 for v in &to_mark {
5218 v.trace(marker);
5219 }
5220 }
5221 marker.drain_gray_queue();
5222 if marker.visited_count() == visited_before {
5223 break;
5224 }
5225 }
5226 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5227 if marker.is_visited(t.identity()) {
5228 let to_mark = t.prune_weak_dead(&|id| marker.is_visited(id));
5229 for v in &to_mark {
5230 v.trace(marker);
5231 }
5232 }
5233 }
5234 marker.drain_gray_queue();
5235 record_dead_interned_strings(&*global, marker, &dead_interned);
5236 };
5237 global.heap.mark_only_with_post_mark(&roots, hook);
5238 }
5239
5240 let dead_interned = dead_interned.into_inner();
5241 let mut g = state_ref.global.borrow_mut();
5242 remove_dead_interned_strings(&mut *g, dead_interned);
5243 }
5244
5245 pub fn change_mode(&self, mode: GcKind) {
5247 let old = self._state.global().gckind;
5248 if old == mode as u8 {
5249 self._state.global_mut().lastatomic = 0;
5250 return;
5251 }
5252 match mode {
5253 GcKind::Generational => {
5254 self.enter_generational_mode();
5255 }
5256 GcKind::Incremental => {
5257 self.enter_incremental_mode();
5258 }
5259 }
5260 }
5261
5262 pub fn fix_object<T: lua_gc::Trace + 'static>(&self, _o: &GcRef<T>) { }
5265
5266 pub fn free_all_objects(&self) {
5270 }
5272
5273 pub fn barrier(&self, p: &dyn std::any::Any, v: &LuaValue) {
5277 let g = self._state.global();
5278 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Forward);
5279 }
5280
5281 pub fn barrier_back(&self, p: &dyn std::any::Any, v: &LuaValue) {
5285 let g = self._state.global();
5286 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Backward);
5287 }
5288
5289 pub fn table_barrier_back(&self, p: &GcRef<LuaTable>, v: &LuaValue) {
5291 let g = self._state.global();
5292 barrier_lua_value(&g.heap, *p, v, g.is_gen_mode(), BarrierKind::Backward);
5293 }
5294
5295 pub fn obj_barrier(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5299 let g = self._state.global();
5300 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Forward);
5301 }
5302
5303 pub fn obj_barrier_back(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5306 let g = self._state.global();
5307 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Backward);
5308 }
5309}
5310
5311fn make_seed() -> u32 {
5320 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5321 {
5322 return crate::string::hash_bytes(b"lua-rs-wasm-seed", 0x9e37_79b9);
5323 }
5324
5325 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
5326 {
5327 use std::time::{SystemTime, UNIX_EPOCH};
5328 let t = SystemTime::now()
5329 .duration_since(UNIX_EPOCH)
5330 .map(|d| d.as_secs() as u32)
5331 .unwrap_or(0);
5332
5333 crate::string::hash_bytes(&t.to_le_bytes(), t)
5341 }
5342}
5343
5344pub(crate) fn set_debt(g: &mut GlobalState, mut debt: isize) {
5358 let tb = g.total_bytes() as isize;
5359 debug_assert!(tb > 0);
5360 if debt < tb.saturating_sub(isize::MAX) {
5362 debt = tb - isize::MAX;
5363 }
5364 g.gc_debt = debt;
5365}
5366
5367pub fn set_c_stack_limit(_state: &mut LuaState, _limit: u32) -> i32 {
5377 let _ = (_state, _limit);
5378 LUAI_MAXCCALLS as i32
5379}
5380
5381pub(crate) fn extend_ci(state: &mut LuaState) -> CallInfoIdx {
5398 debug_assert!(
5399 state.call_info[state.ci.0 as usize].next.is_none(),
5400 "extend_ci: current ci already has a cached next frame"
5401 );
5402
5403 let current_idx = state.ci;
5404 let new_idx = CallInfoIdx(state.call_info.len() as u32);
5406
5407 state.call_info.push(CallInfo {
5408 previous: Some(current_idx),
5409 next: None,
5410 u: CallInfoFrame::lua_default(),
5411 ..CallInfo::default()
5412 });
5413
5414 state.call_info[current_idx.0 as usize].next = Some(new_idx);
5415
5416 state.nci += 1;
5417
5418 new_idx
5419}
5420
5421fn free_ci(state: &mut LuaState) {
5442 let ci_idx = state.ci.0 as usize;
5443
5444 let mut next_opt = state.call_info[ci_idx].next.take();
5445
5446 while let Some(idx) = next_opt {
5447 next_opt = state.call_info[idx.0 as usize].next;
5448 state.nci = state.nci.saturating_sub(1);
5449 }
5450
5451 state.call_info.truncate(ci_idx + 1);
5454}
5455
5456pub(crate) fn shrink_ci(state: &mut LuaState) {
5483 let ci_idx = state.ci.0 as usize;
5484
5485 if state.call_info[ci_idx].next.is_none() {
5486 return;
5487 }
5488
5489 let free_count = state.call_info.len().saturating_sub(ci_idx + 1);
5490 if free_count <= 1 {
5491 return;
5492 }
5493
5494 let keep = free_count / 2;
5498 let removed = free_count - keep;
5499 let new_len = ci_idx + 1 + keep;
5500 state.call_info.truncate(new_len);
5501 state.nci = state.nci.saturating_sub(removed as u32);
5502
5503 if let Some(last) = state.call_info.last_mut() {
5505 last.next = None;
5506 }
5507}
5508
5509pub(crate) fn check_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5521 if state.c_calls() == LUAI_MAXCCALLS {
5524 return Err(LuaError::runtime(format_args!("C stack overflow")));
5525 }
5526 if state.c_calls() >= (LUAI_MAXCCALLS / 10 * 11) {
5528 return Err(LuaError::with_status(LuaStatus::ErrErr));
5529 }
5530 Ok(())
5531}
5532
5533pub fn inc_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5544 state.n_ccalls += 1;
5545 if state.c_calls() >= LUAI_MAXCCALLS {
5547 check_c_stack(state)?;
5548 }
5549 Ok(())
5550}
5551
5552fn stack_init(thread: &mut LuaState) {
5558 let total_slots = BASIC_STACK_SIZE + EXTRA_STACK;
5560 thread.stack = vec![StackValue::default(); total_slots];
5561
5562 thread.tbclist = Vec::new();
5566
5567 thread.top = StackIdx(0);
5572
5573 thread.stack_last = StackIdx(BASIC_STACK_SIZE as u32);
5574
5575 let base_ci = CallInfo {
5576 func: StackIdx(0),
5577 top: StackIdx(1 + LUA_MINSTACK as u32),
5578 previous: None,
5579 next: None,
5580 callstatus: CIST_C,
5581 call_metamethods: 0,
5582 nresults: 0,
5583 u: CallInfoFrame::c_default(),
5584 u2: CallInfoExtra::default(),
5585 };
5586
5587 if thread.call_info.is_empty() {
5588 thread.call_info.push(base_ci);
5589 } else {
5590 thread.call_info[0] = base_ci;
5591 thread.call_info.truncate(1);
5592 }
5593
5594 thread.stack[0] = StackValue {
5595 val: LuaValue::Nil,
5596 };
5597
5598 thread.top = StackIdx(1);
5599
5600 thread.ci = CallInfoIdx(0);
5601}
5602
5603fn free_stack(state: &mut LuaState) {
5604 if state.stack.is_empty() {
5605 return;
5606 }
5607 state.ci = CallInfoIdx(0);
5608 free_ci(state);
5609 debug_assert_eq!(state.nci, 0, "nci should be 0 after free_ci");
5610 state.stack.clear();
5612 state.stack.shrink_to_fit();
5613}
5614
5615fn init_registry(state: &mut LuaState) -> Result<(), LuaError> {
5616 let registry = state.new_table();
5618
5619 state.global_mut().l_registry = LuaValue::Table(registry.clone());
5621
5622 let globals = state.new_table();
5642 state.global_mut().globals = LuaValue::Table(globals);
5643 let loaded = state.new_table();
5644 state.global_mut().loaded = LuaValue::Table(loaded);
5645
5646 Ok(())
5647}
5648
5649fn lua_open(state: &mut LuaState) -> Result<(), LuaError> {
5650 stack_init(state);
5651 init_registry(state)?;
5652 crate::string::init(state)?;
5653 crate::tagmethods::init(state)?;
5654 state.global_mut().gcstp = 0;
5656 state.global().heap.unpause();
5657 state.global_mut().nilvalue = LuaValue::Nil;
5660 Ok(())
5662}
5663
5664fn preinit_thread(thread: &mut LuaState, global: Rc<RefCell<GlobalState>>) {
5665 thread.global = global;
5666 thread.stack = Vec::new();
5667 thread.call_info = Vec::new();
5668 thread.ci = CallInfoIdx(0);
5671 thread.nci = 0;
5672 thread.n_ccalls = 0;
5676 thread.hook = None;
5677 thread.hookmask = 0;
5678 thread.basehookcount = 0;
5679 thread.allowhook = true;
5680 thread.hookcount = thread.basehookcount;
5682
5683 {
5688 let (active, interval) = {
5689 let g = thread.global.borrow();
5690 (g.sandbox_active(), g.sandbox.interval.get())
5691 };
5692 if active {
5693 thread.hookmask = SANDBOX_COUNT_MASK;
5694 thread.basehookcount = interval;
5695 thread.hookcount = interval;
5696 }
5697 }
5698 thread.openupval = Vec::new();
5699 thread.status = LuaStatus::Ok as u8;
5700 thread.errfunc = 0;
5701 thread.oldpc = 0;
5702 thread.gc_check_needed = true;
5703}
5704
5705fn close_state(state: &mut LuaState) {
5706 let is_complete = state.global().is_complete();
5707
5708 if !is_complete {
5709 state.gc().free_all_objects();
5711 } else {
5712 state.ci = CallInfoIdx(0);
5713 state.gc().free_all_objects();
5716 }
5718
5719 state.global_mut().strt = StringPool::default();
5721
5722 free_stack(state);
5723
5724 }
5729
5730pub fn new_thread(state: &mut LuaState, initial_body: Option<LuaValue>) -> Result<(), LuaError> {
5759 state.gc_pre_collect_clear();
5760 state.gc().check_step();
5761
5762 let global_rc = state.global_rc();
5767 let hookmask = state.hookmask;
5768 let basehookcount = state.basehookcount;
5769
5770 let reserved_id = {
5771 let mut g = state.global_mut();
5772 let id = g.next_thread_id;
5773 g.next_thread_id += 1;
5774 id
5775 };
5776
5777 let mut new_thread = LuaState {
5778 status: LuaStatus::Ok as u8,
5779 allowhook: true,
5780 nci: 0,
5781 top: StackIdx(0),
5782 stack_last: StackIdx(0),
5783 stack: Vec::new(),
5784 ci: CallInfoIdx(0),
5785 call_info: Vec::new(),
5786 openupval: Vec::new(),
5787 tbclist: Vec::new(),
5788 global: global_rc.clone(),
5789 hook: None,
5790 hookmask: 0,
5791 basehookcount: 0,
5792 hookcount: 0,
5793 errfunc: 0,
5794 n_ccalls: 0,
5795 oldpc: 0,
5796 marked: 0,
5797 cached_thread_id: reserved_id,
5798 gc_check_needed: false,
5799 };
5800
5801 preinit_thread(&mut new_thread, global_rc);
5802
5803 new_thread.hookmask = hookmask;
5804 new_thread.basehookcount = basehookcount;
5805 new_thread.reset_hook_count();
5808
5809 stack_init(&mut new_thread);
5815
5816 if let Some(body) = initial_body {
5817 new_thread.push(body);
5818 }
5819
5820 let thread_ref: Rc<RefCell<LuaState>> = Rc::new(RefCell::new(new_thread));
5821
5822 let value = {
5823 let mut g = state.global_mut();
5824 let id = reserved_id;
5825 let value = GcRef::new(lua_types::value::LuaThread::new(id));
5826 g.threads.insert(
5827 id,
5828 ThreadRegistryEntry {
5829 state: thread_ref,
5830 value: value.clone(),
5831 },
5832 );
5833 value
5834 };
5835
5836 state.push(LuaValue::Thread(value));
5837
5838 Ok(())
5839}
5840
5841pub fn reset_thread(state: &mut LuaState, status: i32) -> i32 {
5863 state.ci = CallInfoIdx(0);
5864 let ci_idx = 0usize;
5865
5866 if !state.stack.is_empty() {
5868 state.stack[0].val = LuaValue::Nil;
5869 }
5870
5871 state.call_info[ci_idx].func = StackIdx(0);
5872 state.call_info[ci_idx].call_metamethods = 0;
5873 state.call_info[ci_idx].callstatus = CIST_C;
5874
5875 let mut status = if status == LuaStatus::Yield as i32 {
5876 LuaStatus::Ok as i32
5877 } else {
5878 status
5879 };
5880
5881 state.status = LuaStatus::Ok as u8;
5882
5883 let close_status = crate::do_::close_protected(state, StackIdx(1), LuaStatus::from_raw(status));
5884 status = close_status as i32;
5885
5886 if status != LuaStatus::Ok as i32 {
5887 crate::do_::set_error_obj(state, LuaStatus::from_raw(status), StackIdx(1));
5888 } else {
5889 state.top = StackIdx(1);
5890 }
5891
5892 let new_ci_top = StackIdx(state.top.0 + LUA_MINSTACK as u32);
5893 state.call_info[ci_idx].top = new_ci_top;
5894
5895 let needed = new_ci_top.0 as usize;
5898 if state.stack.len() < needed {
5899 state.stack.resize(needed, StackValue::default());
5900 }
5901
5902 status
5903}
5904
5905pub fn close_thread(state: &mut LuaState, from: Option<&LuaState>) -> i32 {
5919 state.n_ccalls = match from {
5921 Some(f) => f.c_calls(),
5922 None => 0,
5923 };
5924 let current_status = state.status as i32;
5925 let result = reset_thread(state, current_status);
5926 result
5927}
5928
5929pub fn reset_thread_api(state: &mut LuaState) -> i32 {
5938 close_thread(state, None)
5939}
5940
5941pub fn new_state() -> Option<LuaState> {
5977 let placeholder_str = GcRef::new(LuaString::placeholder());
5986
5987 let initial_white = 1u8 << WHITE0BIT;
5989
5990 let global = GlobalState {
5994 parser_hook: None,
5995 cli_argv: None,
5996 cli_preload: None,
5997 lua_version: lua_types::LuaVersion::default(),
5998 file_loader_hook: None,
5999 file_open_hook: None,
6000 stdout_hook: None,
6001 stderr_hook: None,
6002 stdin_hook: None,
6003 env_hook: None,
6004 unix_time_hook: None,
6005 cpu_clock_hook: None,
6006 local_offset_hook: None,
6007 entropy_hook: None,
6008 temp_name_hook: None,
6009 popen_hook: None,
6010 file_remove_hook: None,
6011 file_rename_hook: None,
6012 os_execute_hook: None,
6013 dynlib_load_hook: None,
6014 dynlib_symbol_hook: None,
6015 dynlib_unload_hook: None,
6016 sandbox: SandboxLimits::default(),
6017 gc_debt: 0,
6018 gc_estimate: 0,
6019 lastatomic: 0,
6020 strt: StringPool::default(),
6021 l_registry: LuaValue::Nil,
6022 external_roots: ExternalRootSet::default(),
6023 globals: LuaValue::Nil,
6024 loaded: LuaValue::Nil,
6025 nilvalue: LuaValue::Int(0),
6026 seed: make_seed(),
6027 currentwhite: initial_white,
6028 gcstate: GCS_PAUSE,
6029 gckind: GcKind::Incremental as u8,
6031 gcstopem: false,
6032 genminormul: LUAI_GENMINORMUL,
6033 genmajormul: (LUAI_GENMAJORMUL / 4) as u8,
6035 gcstp: GCSTPGC,
6036 gcemergency: false,
6037 gcpause: (LUAI_GCPAUSE / 4) as u8,
6038 gcstepmul: (LUAI_GCMUL / 4) as u8,
6039 gcstepsize: LUAI_GCSTEPSIZE,
6040 gc55_params: [20, 50, 68, 250, 200, 9600],
6043 sweepgc_cursor: 0,
6044 weak_tables_registry: lua_gc::WeakRegistry::default(),
6045 finalizers: lua_gc::FinalizerRegistry::default(),
6046 gc_finalizer_error: None,
6047 twups: Vec::new(),
6048 panic: None,
6049 mainthread: None,
6050 threads: std::collections::HashMap::new(),
6051 main_thread_value: GcRef::new(lua_types::value::LuaThread::new(0)),
6052 current_thread_id: 0,
6053 closing_thread_id: None,
6054 main_thread_id: 0,
6055 next_thread_id: 1,
6056 memerrmsg: placeholder_str.clone(),
6057 tmname: Vec::new(),
6058 mt: std::array::from_fn(|_| None),
6059 strcache: std::array::from_fn(|_| std::array::from_fn(|_| placeholder_str.clone())),
6060 interned_lt: InternedStringMap::default(),
6061 warnf: None,
6062 warn_mode: WarnMode::Off,
6063 test_warn_enabled: false,
6064 test_warn_on: false,
6065 test_warn_mode: TestWarnMode::Normal,
6066 test_warn_last_to_cont: false,
6067 test_warn_buffer: Vec::new(),
6068 c_functions: Vec::new(),
6069 heap: lua_gc::Heap::new(),
6070 cross_thread_upvals: std::collections::HashMap::new(),
6071 suspended_parent_stacks: Vec::new(),
6072 suspended_parent_open_upvals: Vec::new(),
6073 snapshot_stack_pool: Vec::new(),
6074 snapshot_upval_pool: Vec::new(),
6075 resume_value_pool: Vec::new(),
6076 resume_upval_slot_pool: Vec::new(),
6077 resume_flush_pool: Vec::new(),
6078 };
6079
6080 let global_rc = Rc::new(RefCell::new(global));
6081
6082 let initial_marked = initial_white;
6084
6085 let mut main_thread = LuaState {
6086 status: LuaStatus::Ok as u8,
6087 allowhook: true,
6088 nci: 0,
6089 top: StackIdx(0),
6090 stack_last: StackIdx(0),
6091 stack: Vec::new(),
6092 ci: CallInfoIdx(0),
6093 call_info: Vec::new(),
6094 openupval: Vec::new(),
6095 tbclist: Vec::new(),
6096 global: global_rc.clone(),
6097 hook: None,
6098 hookmask: 0,
6099 basehookcount: 0,
6100 hookcount: 0,
6101 errfunc: 0,
6102 n_ccalls: 0,
6103 oldpc: 0,
6104 marked: initial_marked,
6105 cached_thread_id: 0,
6106 gc_check_needed: false,
6107 };
6108
6109 preinit_thread(&mut main_thread, global_rc.clone());
6110
6111 main_thread.inc_nny();
6113
6114 match lua_open(&mut main_thread) {
6124 Ok(()) => {}
6125 Err(_) => {
6126 close_state(&mut main_thread);
6127 return None;
6128 }
6129 }
6130
6131 Some(main_thread)
6132}
6133
6134pub fn close(mut state: LuaState) {
6150 close_state(&mut state);
6155}
6156
6157pub(crate) fn warning(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6167 let test_warn_enabled = state.global().test_warn_enabled;
6168 if test_warn_enabled {
6169 test_warn(state, msg, to_cont);
6170 return;
6171 }
6172
6173 let has_warnf = state.global().warnf.is_some();
6182 if has_warnf {
6183 let mut warnf = state.global_mut().warnf.take();
6185 if let Some(ref mut f) = warnf {
6186 f(msg, to_cont);
6187 }
6188 state.global_mut().warnf = warnf;
6190 return;
6191 }
6192 default_warn(state, msg, to_cont);
6193}
6194
6195fn test_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6196 let is_control = {
6197 let g = state.global();
6198 !g.test_warn_last_to_cont && !to_cont && msg.first() == Some(&b'@')
6199 };
6200 if is_control {
6201 let mut g = state.global_mut();
6202 match &msg[1..] {
6203 b"off" => g.test_warn_on = false,
6204 b"on" => g.test_warn_on = true,
6205 b"normal" => g.test_warn_mode = TestWarnMode::Normal,
6206 b"allow" => g.test_warn_mode = TestWarnMode::Allow,
6207 b"store" => g.test_warn_mode = TestWarnMode::Store,
6208 _ => {}
6209 }
6210 return;
6211 }
6212
6213 let finished = {
6214 let mut g = state.global_mut();
6215 g.test_warn_last_to_cont = to_cont;
6216 g.test_warn_buffer.extend_from_slice(msg);
6217 if to_cont {
6218 None
6219 } else {
6220 Some((
6221 std::mem::take(&mut g.test_warn_buffer),
6222 g.test_warn_mode,
6223 g.test_warn_on,
6224 ))
6225 }
6226 };
6227
6228 let Some((message, mode, warn_on)) = finished else {
6229 return;
6230 };
6231 match mode {
6232 TestWarnMode::Normal => {
6233 if warn_on && message.first() == Some(&b'#') {
6234 write_warning_message(&message);
6235 }
6236 }
6237 TestWarnMode::Allow => {
6238 if warn_on {
6239 write_warning_message(&message);
6240 }
6241 }
6242 TestWarnMode::Store => {
6243 if let Ok(s) = state.intern_str(&message) {
6244 state.push(LuaValue::Str(s));
6245 let _ = crate::api::set_global(state, b"_WARN");
6246 }
6247 }
6248 }
6249}
6250
6251fn write_warning_message(message: &[u8]) {
6252 use std::io::Write;
6253 let stderr = std::io::stderr();
6254 let mut h = stderr.lock();
6255 let _ = h.write_all(b"Lua warning: ");
6256 let _ = h.write_all(message);
6257 let _ = h.write_all(b"\n");
6258}
6259
6260fn default_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6265 use std::io::Write;
6266 if !to_cont && msg.first() == Some(&b'@') {
6268 match &msg[1..] {
6269 b"off" => state.global_mut().warn_mode = WarnMode::Off,
6270 b"on" => state.global_mut().warn_mode = WarnMode::On,
6271 _ => {}
6272 }
6273 return;
6274 }
6275 let mode = state.global().warn_mode;
6276 match mode {
6277 WarnMode::Off => {}
6278 WarnMode::On | WarnMode::Cont => {
6279 let stderr = std::io::stderr();
6280 let mut h = stderr.lock();
6281 if mode == WarnMode::On {
6282 let _ = h.write_all(b"Lua warning: ");
6283 }
6284 let _ = h.write_all(msg);
6285 if to_cont {
6286 state.global_mut().warn_mode = WarnMode::Cont;
6287 } else {
6288 let _ = h.write_all(b"\n");
6289 state.global_mut().warn_mode = WarnMode::On;
6290 }
6291 }
6292 }
6293}
6294
6295#[cfg(test)]
6296mod tests {
6297 use super::*;
6298
6299 fn test_noop_cclosure(_: &mut LuaState) -> Result<usize, LuaError> {
6300 Ok(0)
6301 }
6302
6303 #[test]
6304 fn external_root_keys_reject_stale_slot_after_reuse() {
6305 let mut roots = ExternalRootSet::default();
6306
6307 let first = roots.insert(LuaValue::Int(1));
6308 assert_eq!(roots.len(), 1);
6309 assert_eq!(roots.get(first), Some(&LuaValue::Int(1)));
6310
6311 assert_eq!(roots.remove(first), Some(LuaValue::Int(1)));
6312 assert!(roots.get(first).is_none());
6313 assert!(roots.remove(first).is_none());
6314 assert_eq!(roots.len(), 0);
6315 assert_eq!(roots.vacant_len(), 1);
6316 assert!(roots.replace(first, LuaValue::Int(9)).is_none());
6317 assert!(roots.is_empty());
6318
6319 let second = roots.insert(LuaValue::Int(2));
6320 assert_eq!(first.index, second.index);
6321 assert_ne!(first, second);
6322 assert!(roots.get(first).is_none());
6323 assert_eq!(roots.get(second), Some(&LuaValue::Int(2)));
6324 assert!(roots.replace(first, LuaValue::Int(3)).is_none());
6325 }
6326
6327 #[test]
6328 fn external_roots_keep_heap_value_alive_until_unrooted() {
6329 let mut state = new_state().expect("state should initialize");
6330 let _heap_guard = {
6331 let g = state.global();
6332 lua_gc::HeapGuard::push(&g.heap)
6333 };
6334
6335 let table = state.new_table();
6336 assert_eq!(state.global().heap.allgc_count(), 1);
6337
6338 let key = state.external_root_value(LuaValue::Table(table));
6339 state.gc().full_collect();
6340 assert_eq!(state.global().heap.allgc_count(), 1);
6341 assert_eq!(state.global().external_roots.len(), 1);
6342
6343 assert!(state.external_unroot_value(key).is_some());
6344 state.gc().full_collect();
6345 assert_eq!(state.global().heap.allgc_count(), 0);
6346 assert!(state.global().external_roots.is_empty());
6347 }
6348
6349 #[test]
6350 fn interned_string_table_grows_then_shrinks_on_collection() {
6351 let mut state = new_state().expect("state should initialize");
6352 let _heap_guard = {
6353 let g = state.global();
6354 lua_gc::HeapGuard::push(&g.heap)
6355 };
6356
6357 let initial_buckets = state.global().interned_lt.bucket_count();
6358 assert_eq!(
6359 initial_buckets, 64,
6360 "the intern table starts at C's MINSTRTABSIZE-equivalent of 64"
6361 );
6362 let baseline_live = state.global().interned_lt.len();
6363
6364 let mut anchors: Vec<GcRef<LuaString>> = Vec::with_capacity(2000);
6365 for i in 0..2000usize {
6366 let key = format!("intern-shrink-probe-{i:08}");
6367 let s = state
6368 .intern_str(key.as_bytes())
6369 .expect("short string interns");
6370 anchors.push(s);
6371 }
6372
6373 let grown_buckets = state.global().interned_lt.bucket_count();
6374 assert_eq!(
6375 state.global().interned_lt.len(),
6376 baseline_live + 2000,
6377 "all 2000 distinct short strings are interned and live alongside \
6378 the runtime's own rooted strings"
6379 );
6380 assert!(
6381 grown_buckets >= 2048,
6382 "interning 2000 strings must force several bucket doublings past \
6383 the initial 64 (saw {grown_buckets})"
6384 );
6385
6386 drop(anchors);
6387 state.gc().full_collect();
6388
6389 let shrunk_buckets = state.global().interned_lt.bucket_count();
6390 let surviving_live = state.global().interned_lt.len();
6391 assert!(
6392 surviving_live <= baseline_live,
6393 "every probe string is unreachable and must be swept out of the \
6394 intern table, leaving at most the runtime's own strings (baseline \
6395 {baseline_live}, surviving {surviving_live})"
6396 );
6397 assert!(
6398 surviving_live < 2000,
6399 "the 2000 probe strings must not survive collection (surviving \
6400 {surviving_live})"
6401 );
6402 assert_eq!(
6403 shrunk_buckets, 64,
6404 "the batch-end shrink check must reclaim the stale buckets back to \
6405 the 64-bucket floor (saw {shrunk_buckets}, grew to {grown_buckets})"
6406 );
6407 assert!(
6408 shrunk_buckets < grown_buckets,
6409 "shrink must strictly reduce the bucket array"
6410 );
6411 }
6412
6413 #[test]
6414 fn table_buffer_accounting_refunds_on_sweep() {
6415 let mut state = new_state().expect("state should initialize");
6416 let _heap_guard = {
6417 let g = state.global();
6418 lua_gc::HeapGuard::push(&g.heap)
6419 };
6420
6421 let table = state.new_table();
6422 let key = state.external_root_value(LuaValue::Table(table));
6423 let header_bytes = state.global().heap.bytes_used();
6424 assert!(header_bytes > 0);
6425
6426 for i in 1..=128 {
6427 table
6428 .raw_set_int(&mut state, i, LuaValue::Int(i))
6429 .expect("integer table insert should succeed");
6430 }
6431 let grown_bytes = state.global().heap.bytes_used();
6432 assert!(
6433 grown_bytes > header_bytes,
6434 "table array/hash buffer growth must be charged to the GC heap"
6435 );
6436
6437 state.gc().full_collect();
6438 assert_eq!(
6439 state.global().heap.bytes_used(),
6440 grown_bytes,
6441 "rooted table buffer bytes should remain charged after collection"
6442 );
6443
6444 assert!(state.external_unroot_value(key).is_some());
6445 state.gc().full_collect();
6446 assert_eq!(state.global().heap.bytes_used(), 0);
6447 assert_eq!(state.global().heap.allgc_count(), 0);
6448 }
6449
6450 #[test]
6451 fn userdata_buffer_accounting_refunds_on_sweep() {
6452 let mut state = new_state().expect("state should initialize");
6453 let _heap_guard = {
6454 let g = state.global();
6455 lua_gc::HeapGuard::push(&g.heap)
6456 };
6457
6458 let payload_len = 4096;
6459 let userdata = state
6460 .new_userdata_typed(b"accounting", payload_len, 3)
6461 .expect("userdata allocation should succeed");
6462 state.pop_n(1);
6463 let key = state.external_root_value(LuaValue::UserData(userdata));
6464 let allocated_bytes = state.global().heap.bytes_used();
6465 assert!(
6466 allocated_bytes > payload_len,
6467 "userdata payload bytes must be charged to the GC heap"
6468 );
6469
6470 state.gc().full_collect();
6471 assert_eq!(
6472 state.global().heap.bytes_used(),
6473 allocated_bytes,
6474 "rooted userdata payload bytes should remain charged after collection"
6475 );
6476
6477 assert!(state.external_unroot_value(key).is_some());
6478 state.gc().full_collect();
6479 assert_eq!(state.global().heap.bytes_used(), 0);
6480 assert_eq!(state.global().heap.allgc_count(), 0);
6481 }
6482
6483 #[test]
6484 fn cclosure_upvalue_accounting_refunds_on_sweep() {
6485 let mut state = new_state().expect("state should initialize");
6486 let _heap_guard = {
6487 let g = state.global();
6488 lua_gc::HeapGuard::push(&g.heap)
6489 };
6490
6491 let nupvalues = 64;
6492 for i in 0..nupvalues {
6493 state.push(LuaValue::Int(i as i64));
6494 }
6495 crate::api::push_cclosure(&mut state, test_noop_cclosure, nupvalues as i32)
6496 .expect("C closure creation should succeed");
6497 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6498 panic!("expected heavy C closure");
6499 };
6500 let expected_payload = ccl.buffer_bytes();
6501 let key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
6502 state.pop_n(1);
6503 let allocated_bytes = state.global().heap.bytes_used();
6504 assert!(
6505 allocated_bytes >= expected_payload,
6506 "C closure upvalue vector bytes must be charged to the GC heap"
6507 );
6508
6509 state.gc().full_collect();
6510 assert_eq!(
6511 state.global().heap.bytes_used(),
6512 allocated_bytes,
6513 "rooted C closure payload bytes should remain charged after collection"
6514 );
6515
6516 assert!(state.external_unroot_value(key).is_some());
6517 state.gc().full_collect();
6518 assert_eq!(state.global().heap.bytes_used(), 0);
6519 assert_eq!(state.global().heap.allgc_count(), 0);
6520 }
6521
6522 #[test]
6523 fn proto_and_lclosure_accounting_refunds_on_sweep() {
6524 let mut state = new_state().expect("state should initialize");
6525 let _heap_guard = {
6526 let g = state.global();
6527 lua_gc::HeapGuard::push(&g.heap)
6528 };
6529
6530 let mut proto = LuaProto::placeholder();
6531 proto.code = vec![lua_types::opcode::Instruction(0); 2048];
6532 proto.lineinfo = vec![0; 2048];
6533 proto.k = vec![LuaValue::Int(1); 512];
6534 let expected_proto_payload = proto.buffer_bytes();
6535 let proto = GcRef::new(proto);
6536 proto.account_buffer(expected_proto_payload as isize);
6537
6538 let closure = state.new_lclosure(proto, 16);
6539 let expected_closure_payload = closure.buffer_bytes();
6540 let key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6541 let allocated_bytes = state.global().heap.bytes_used();
6542 assert!(
6543 allocated_bytes >= expected_proto_payload + expected_closure_payload,
6544 "proto and Lua closure vector bytes must be charged to the GC heap"
6545 );
6546
6547 state.gc().full_collect();
6548 assert_eq!(
6549 state.global().heap.bytes_used(),
6550 allocated_bytes,
6551 "rooted proto and Lua closure payload bytes should remain charged after collection"
6552 );
6553
6554 assert!(state.external_unroot_value(key).is_some());
6555 state.gc().full_collect();
6556 assert_eq!(state.global().heap.bytes_used(), 0);
6557 assert_eq!(state.global().heap.allgc_count(), 0);
6558 }
6559
6560 #[test]
6561 fn string_buffer_accounting_refunds_on_sweep() {
6562 let mut state = new_state().expect("state should initialize");
6563 let _heap_guard = {
6564 let g = state.global();
6565 lua_gc::HeapGuard::push(&g.heap)
6566 };
6567
6568 let payload = vec![b'x'; crate::string::MAX_SHORT_LEN + 4096];
6569 let string = state
6570 .intern_str(&payload)
6571 .expect("long string should allocate");
6572 let key = state.external_root_value(LuaValue::Str(string));
6573 let allocated_bytes = state.global().heap.bytes_used();
6574 assert!(
6575 allocated_bytes > payload.len(),
6576 "long string backing bytes must be charged to the GC heap"
6577 );
6578
6579 state.gc().full_collect();
6580 assert_eq!(
6581 state.global().heap.bytes_used(),
6582 allocated_bytes,
6583 "rooted string buffer bytes should remain charged after collection"
6584 );
6585
6586 assert!(state.external_unroot_value(key).is_some());
6587 state.gc().full_collect();
6588 assert_eq!(state.global().heap.bytes_used(), 0);
6589 assert_eq!(state.global().heap.allgc_count(), 0);
6590 }
6591
6592 #[test]
6593 fn interned_short_string_cache_does_not_root_unreferenced_string() {
6594 let mut state = new_state().expect("state should initialize");
6595 let _heap_guard = {
6596 let g = state.global();
6597 lua_gc::HeapGuard::push(&g.heap)
6598 };
6599
6600 let payload = b"weak-cache-probe-a";
6601 let string = state
6602 .intern_str(payload)
6603 .expect("short string should intern");
6604 let id = string.identity();
6605 assert!(state.global().interned_lt.contains_key(&payload[..]));
6606 assert_eq!(
6607 state.global().heap.register_allocation_token(id),
6608 state.global().heap.register_allocation_token(id),
6609 "token registration is get-or-insert while the string is provably live"
6610 );
6611 assert!(state.global().heap.allocation_token(id).is_some());
6612
6613 state.gc().full_collect();
6614 assert!(!state.global().interned_lt.contains_key(&payload[..]));
6615 assert_eq!(state.global().heap.allocation_token(id), None);
6616 }
6617
6618 #[test]
6619 fn interned_short_string_cache_keeps_reachable_string_until_unrooted() {
6620 let mut state = new_state().expect("state should initialize");
6621 let _heap_guard = {
6622 let g = state.global();
6623 lua_gc::HeapGuard::push(&g.heap)
6624 };
6625
6626 let payload = b"weak-cache-probe-b";
6627 let string = state
6628 .intern_str(payload)
6629 .expect("short string should intern");
6630 let id = string.identity();
6631 state.global().heap.register_allocation_token(id);
6632 let key = state.external_root_value(LuaValue::Str(string));
6633
6634 state.gc().full_collect();
6635 assert!(state.global().interned_lt.contains_key(&payload[..]));
6636 assert!(state.global().heap.allocation_token(id).is_some());
6637
6638 assert!(state.external_unroot_value(key).is_some());
6639 state.gc().full_collect();
6640 assert!(!state.global().interned_lt.contains_key(&payload[..]));
6641 assert_eq!(state.global().heap.allocation_token(id), None);
6642 }
6643
6644 #[test]
6645 fn gc_phase_predicates_follow_heap_state() {
6646 let mut state = new_state().expect("state should initialize");
6647 let _heap_guard = {
6648 let g = state.global();
6649 lua_gc::HeapGuard::push(&g.heap)
6650 };
6651
6652 {
6653 let mut g = state.global_mut();
6654 g.gckind = GcKind::Incremental as u8;
6655 g.lastatomic = 0;
6656 assert!(!g.is_gen_mode());
6657 g.lastatomic = 1;
6658 assert!(g.is_gen_mode());
6659 g.lastatomic = 0;
6660 }
6661
6662 let mut roots = Vec::new();
6663 for _ in 0..16 {
6664 let table = state.new_table();
6665 roots.push(state.external_root_value(LuaValue::Table(table)));
6666 }
6667
6668 let mut saw_keep = false;
6669 let mut saw_sweep = false;
6670 for _ in 0..128 {
6671 state.gc().incremental_step(1);
6672 let g = state.global();
6673 let heap_state = g.heap.gc_state();
6674 assert_eq!(g.keep_invariant(), heap_state.is_invariant());
6675 assert_eq!(g.is_sweep_phase(), heap_state.is_sweep());
6676 saw_keep |= g.keep_invariant();
6677 saw_sweep |= g.is_sweep_phase();
6678 if heap_state.is_pause() && saw_keep && saw_sweep {
6679 break;
6680 }
6681 }
6682
6683 assert!(
6684 saw_keep,
6685 "incremental cycle should expose an invariant phase"
6686 );
6687 assert!(saw_sweep, "incremental cycle should expose a sweep phase");
6688
6689 for key in roots {
6690 assert!(state.external_unroot_value(key).is_some());
6691 }
6692 state.gc().full_collect();
6693 }
6694
6695 #[test]
6696 fn gc_barrier_keeps_new_child_stored_in_black_parent() {
6697 let mut state = new_state().expect("state should initialize");
6698 let _heap_guard = {
6699 let g = state.global();
6700 lua_gc::HeapGuard::push(&g.heap)
6701 };
6702
6703 let parent = state.new_table();
6704 let parent_key = state.external_root_value(LuaValue::Table(parent));
6705 state.gc().incremental_step(1);
6706 assert!(
6707 state.global().keep_invariant(),
6708 "test setup should leave the parent marked during an active cycle"
6709 );
6710
6711 let child = state.new_table();
6712 let parent_value = LuaValue::Table(parent);
6713 let child_value = LuaValue::Table(child);
6714 parent
6715 .raw_set_int(&mut state, 1, child_value)
6716 .expect("table store should succeed");
6717 state.gc_barrier_back(&parent_value, &child_value);
6718
6719 for _ in 0..128 {
6720 if state.gc().incremental_step(1) {
6721 break;
6722 }
6723 }
6724
6725 assert_eq!(state.global().heap.allgc_count(), 2);
6726 assert_eq!(
6727 parent.get_int(1).as_table().map(|t| t.identity()),
6728 Some(child.identity())
6729 );
6730
6731 assert!(state.external_unroot_value(parent_key).is_some());
6732 state.gc().full_collect();
6733 assert_eq!(state.global().heap.allgc_count(), 0);
6734 }
6735
6736 #[test]
6737 fn generational_mode_promotes_and_barriers_age_objects() {
6738 let mut state = new_state().expect("state should initialize");
6739 let _heap_guard = {
6740 let g = state.global();
6741 lua_gc::HeapGuard::push(&g.heap)
6742 };
6743
6744 let parent = state.new_table();
6745 let parent_key = state.external_root_value(LuaValue::Table(parent));
6746
6747 state.gc().change_mode(GcKind::Generational);
6748 assert_eq!(parent.0.age(), lua_gc::GcAge::Old);
6749 assert_eq!(parent.0.color(), lua_gc::Color::Black);
6750 let majorbase = state.global().gc_estimate;
6751 assert!(majorbase > 0);
6752 assert!(state.global().gc_debt() <= 0);
6753
6754 let child = state.new_table();
6755 let parent_value = LuaValue::Table(parent);
6756 let child_value = LuaValue::Table(child);
6757 parent
6758 .raw_set_int(&mut state, 1, child_value.clone())
6759 .expect("table store should succeed");
6760 state.gc_barrier_back(&parent_value, &child_value);
6761 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched1);
6762 assert_eq!(parent.0.color(), lua_gc::Color::Gray);
6763 assert_eq!(child.0.age(), lua_gc::GcAge::New);
6764
6765 let metatable = state.new_table();
6766 parent.set_metatable(Some(metatable));
6767 state.gc().obj_barrier(&parent, &metatable);
6768 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old0);
6769
6770 assert!(state.gc().generational_step_minor_only());
6771 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched2);
6772 assert_eq!(child.0.age(), lua_gc::GcAge::Survival);
6773 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old1);
6774 assert_eq!(state.global().gc_estimate, majorbase);
6775 assert!(state.global().gc_debt() <= 0);
6776
6777 state.gc().change_mode(GcKind::Incremental);
6778 assert_eq!(parent.0.age(), lua_gc::GcAge::New);
6779 assert_eq!(child.0.age(), lua_gc::GcAge::New);
6780 assert_eq!(metatable.0.age(), lua_gc::GcAge::New);
6781
6782 assert!(state.external_unroot_value(parent_key).is_some());
6783 state.gc().full_collect();
6784 }
6785
6786 #[test]
6787 fn generational_upvalue_write_barrier_marks_young_child_old0() {
6788 let mut state = new_state().expect("state should initialize");
6789 let _heap_guard = {
6790 let g = state.global();
6791 lua_gc::HeapGuard::push(&g.heap)
6792 };
6793
6794 let proto = state.new_proto();
6795 let closure = state.new_lclosure(proto, 1);
6796 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6797 state.gc().change_mode(GcKind::Generational);
6798 let uv = closure.upval(0);
6799 assert_eq!(uv.0.age(), lua_gc::GcAge::Old);
6800
6801 let child = state.new_table();
6802 state
6803 .upvalue_set(&closure, 0, LuaValue::Table(child))
6804 .expect("closed upvalue write should succeed");
6805 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
6806
6807 assert!(state.external_unroot_value(closure_key).is_some());
6808 state.gc().full_collect();
6809 }
6810
6811 #[test]
6812 fn cclosure_setupvalue_replaces_upvalue() {
6813 let mut state = new_state().expect("state should initialize");
6814 let _heap_guard = {
6815 let g = state.global();
6816 lua_gc::HeapGuard::push(&g.heap)
6817 };
6818
6819 let first = state.new_table();
6820 state.push(LuaValue::Table(first));
6821 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
6822 .expect("C closure creation should succeed");
6823 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6824 panic!("expected heavy C closure");
6825 };
6826
6827 let second = state.new_table();
6828 state.push(LuaValue::Table(second));
6829 let name =
6830 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
6831
6832 assert!(name.is_empty());
6833 let upvalues = ccl.upvalues.borrow();
6834 let LuaValue::Table(actual) = upvalues[0].clone() else {
6835 panic!("expected table upvalue");
6836 };
6837 assert_eq!(actual.identity(), second.identity());
6838 }
6839
6840 #[test]
6841 fn generational_cclosure_setupvalue_barrier_marks_young_child_old0() {
6842 let mut state = new_state().expect("state should initialize");
6843 let _heap_guard = {
6844 let g = state.global();
6845 lua_gc::HeapGuard::push(&g.heap)
6846 };
6847
6848 state.push(LuaValue::Nil);
6849 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
6850 .expect("C closure creation should succeed");
6851 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6852 panic!("expected heavy C closure");
6853 };
6854 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
6855
6856 state.gc().change_mode(GcKind::Generational);
6857 assert_eq!(ccl.0.age(), lua_gc::GcAge::Old);
6858
6859 let child = state.new_table();
6860 state.push(LuaValue::Table(child));
6861 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
6862
6863 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
6864
6865 assert!(state.external_unroot_value(closure_key).is_some());
6866 state.gc().full_collect();
6867 }
6868
6869 #[test]
6870 fn generational_closure_upvalue_slot_barrier_marks_new_upval_old0() {
6871 let mut state = new_state().expect("state should initialize");
6872 let _heap_guard = {
6873 let g = state.global();
6874 lua_gc::HeapGuard::push(&g.heap)
6875 };
6876
6877 let proto = state.new_proto();
6878 let closure = state.new_lclosure(proto, 1);
6879 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6880 state.gc().change_mode(GcKind::Generational);
6881 assert_eq!(closure.0.age(), lua_gc::GcAge::Old);
6882
6883 let replacement = state.new_upval_closed(LuaValue::Nil);
6884 closure.set_upval(0, replacement);
6885 state.gc().obj_barrier(&closure, &replacement);
6886 assert_eq!(replacement.0.age(), lua_gc::GcAge::Old0);
6887
6888 assert!(state.external_unroot_value(closure_key).is_some());
6889 state.gc().full_collect();
6890 }
6891
6892 #[test]
6893 fn cross_thread_upvalue_mirror_traces_values_as_roots() {
6894 let mut state = new_state().expect("state should initialize");
6895 let _heap_guard = {
6896 let g = state.global();
6897 lua_gc::HeapGuard::push(&g.heap)
6898 };
6899
6900 let mirrored = state.new_table();
6901 state
6902 .global_mut()
6903 .cross_thread_upvals
6904 .insert((999, StackIdx(0)), LuaValue::Table(mirrored));
6905
6906 state.gc().full_collect();
6907 assert_eq!(state.global().heap.allgc_count(), 1);
6908
6909 state.global_mut().cross_thread_upvals.clear();
6910 state.gc().full_collect();
6911 assert_eq!(state.global().heap.allgc_count(), 0);
6912 }
6913
6914 #[test]
6915 fn generational_full_collect_promotes_new_survivors_to_old() {
6916 let mut state = new_state().expect("state should initialize");
6917 let _heap_guard = {
6918 let g = state.global();
6919 lua_gc::HeapGuard::push(&g.heap)
6920 };
6921
6922 state.gc().change_mode(GcKind::Generational);
6923 let table = state.new_table();
6924 let table_key = state.external_root_value(LuaValue::Table(table));
6925 assert_eq!(table.0.age(), lua_gc::GcAge::New);
6926
6927 state.gc().full_collect();
6928 assert_eq!(table.0.age(), lua_gc::GcAge::Old);
6929 assert_eq!(table.0.color(), lua_gc::Color::Black);
6930
6931 assert!(state.external_unroot_value(table_key).is_some());
6932 state.gc().full_collect();
6933 }
6934
6935 #[test]
6936 fn gc_packed_params_return_user_visible_values() {
6937 let mut state = new_state().expect("state should initialize");
6938 assert_eq!(
6939 crate::api::gc(&mut state, crate::api::GcArgs::SetPause { value: 200 }),
6940 200
6941 );
6942 assert_eq!(state.global().gc_pause_param(), 200);
6943 assert_eq!(
6944 crate::api::gc(&mut state, crate::api::GcArgs::SetStepMul { value: 200 }),
6945 100
6946 );
6947 assert_eq!(state.global().gc_stepmul_param(), 200);
6948
6949 crate::api::gc(
6950 &mut state,
6951 crate::api::GcArgs::Gen {
6952 minormul: 0,
6953 majormul: 200,
6954 },
6955 );
6956 assert_eq!(state.global().gc_genmajormul_param(), 200);
6957 }
6958
6959 #[test]
6960 fn generational_step_runs_bad_major_when_growth_exceeds_genmajormul() {
6961 let mut state = new_state().expect("state should initialize");
6962 let _heap_guard = {
6963 let g = state.global();
6964 lua_gc::HeapGuard::push(&g.heap)
6965 };
6966
6967 let root = state.new_table();
6968 let root_key = state.external_root_value(LuaValue::Table(root));
6969 state.gc().change_mode(GcKind::Generational);
6970
6971 let root_value = LuaValue::Table(root);
6972 for i in 1..=64 {
6973 let child = state.new_table();
6974 let child_value = LuaValue::Table(child);
6975 root.raw_set_int(&mut state, i, child_value.clone())
6976 .expect("table store should succeed");
6977 state.gc_barrier_back(&root_value, &child_value);
6978 }
6979
6980 {
6981 let mut g = state.global_mut();
6982 g.gc_estimate = 1;
6983 set_debt(&mut *g, 1);
6984 }
6985
6986 assert!(state.gc().generational_step());
6987 let g = state.global();
6988 assert!(g.is_gen_mode());
6989 assert!(
6990 g.lastatomic > 0,
6991 "bad major collection should arm stepgenfull"
6992 );
6993 assert!(g.gc_estimate > 1);
6994 assert!(g.gc_debt() <= 0);
6995 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
6996 drop(g);
6997
6998 assert!(state.external_unroot_value(root_key).is_some());
6999 state.gc().full_collect();
7000 }
7001
7002 #[test]
7003 fn generational_implicit_step_runs_major_when_heap_threshold_exceeded() {
7004 let mut state = new_state().expect("state should initialize");
7005 let _heap_guard = {
7006 let g = state.global();
7007 lua_gc::HeapGuard::push(&g.heap)
7008 };
7009
7010 let root = state.new_table();
7011 let root_key = state.external_root_value(LuaValue::Table(root));
7012 state.gc().change_mode(GcKind::Generational);
7013
7014 let root_value = LuaValue::Table(root);
7015 for i in 1..=64 {
7016 let child = state.new_table();
7017 let child_value = LuaValue::Table(child);
7018 root.raw_set_int(&mut state, i, child_value.clone())
7019 .expect("table store should succeed");
7020 state.gc_barrier_back(&root_value, &child_value);
7021 }
7022
7023 {
7024 let mut g = state.global_mut();
7025 g.gc_estimate = 1;
7026 set_debt(&mut *g, -1);
7027 g.heap.set_threshold_bytes(1);
7028 }
7029
7030 assert!(state.gc().generational_step());
7031 let g = state.global();
7032 assert!(g.is_gen_mode());
7033 assert!(
7034 g.lastatomic > 0,
7035 "implicit threshold-triggered growth should arm a bad major"
7036 );
7037 assert!(g.gc_debt() <= 0);
7038 drop(g);
7039
7040 assert!(state.external_unroot_value(root_key).is_some());
7041 state.gc().full_collect();
7042 }
7043
7044 #[test]
7045 fn generational_stepgenfull_returns_to_gen_after_good_collection() {
7046 let mut state = new_state().expect("state should initialize");
7047 let _heap_guard = {
7048 let g = state.global();
7049 lua_gc::HeapGuard::push(&g.heap)
7050 };
7051
7052 let root = state.new_table();
7053 let root_key = state.external_root_value(LuaValue::Table(root));
7054 state.gc().change_mode(GcKind::Generational);
7055 {
7056 let mut g = state.global_mut();
7057 g.lastatomic = 1024;
7058 }
7059
7060 assert!(state.gc().generational_step());
7061 let g = state.global();
7062 assert_eq!(g.gckind, GcKind::Generational as u8);
7063 assert_eq!(g.lastatomic, 0);
7064 assert!(g.gc_debt() <= 0);
7065 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
7066 assert_eq!(root.0.color(), lua_gc::Color::Black);
7067 drop(g);
7068
7069 assert!(state.external_unroot_value(root_key).is_some());
7070 state.gc().full_collect();
7071 }
7072
7073 #[test]
7074 fn generational_step_zero_reports_false_without_positive_debt() {
7075 let mut state = new_state().expect("state should initialize");
7076 let _heap_guard = {
7077 let g = state.global();
7078 lua_gc::HeapGuard::push(&g.heap)
7079 };
7080
7081 state.gc().change_mode(GcKind::Generational);
7082 assert_eq!(
7083 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 0 }),
7084 0
7085 );
7086 assert_eq!(
7087 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 1 }),
7088 1
7089 );
7090 }
7091}
7092
7093