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, UpValState};
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 #[inline]
159 pub fn find(&self, bytes: &[u8], hash: u32) -> Option<GcRef<LuaString>> {
160 let bucket = &self.buckets[hash as usize & self.mask()];
161 bucket
162 .iter()
163 .find(|s| s.hash() == hash && s.as_bytes() == bytes)
164 .cloned()
165 }
166
167 pub fn insert(&mut self, s: GcRef<LuaString>) {
169 if self.count >= self.buckets.len() {
170 self.resize(self.buckets.len() * 2);
171 }
172 let m = self.mask();
173 self.buckets[s.hash() as usize & m].push(s);
174 self.count += 1;
175 }
176
177 fn resize(&mut self, new_len: usize) {
178 let old = std::mem::replace(
179 &mut self.buckets,
180 (0..new_len).map(|_| Vec::new()).collect(),
181 );
182 let m = self.mask();
183 for bucket in old {
184 for s in bucket {
185 self.buckets[s.hash() as usize & m].push(s);
186 }
187 }
188 }
189
190 pub fn remove(&mut self, hash: u32, identity: usize) {
192 let m = self.mask();
193 let bucket = &mut self.buckets[hash as usize & m];
194 if let Some(pos) = bucket.iter().position(|s| s.identity() == identity) {
195 bucket.swap_remove(pos);
196 self.count -= 1;
197 }
198 }
199
200 pub fn iter(&self) -> impl Iterator<Item = &GcRef<LuaString>> {
201 self.buckets.iter().flatten()
202 }
203
204 pub fn contains_key(&self, bytes: &[u8]) -> bool {
205 self.find(bytes, LuaString::hash_bytes(bytes, 0)).is_some()
206 }
207}
208
209pub type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
216
217pub type LuaRustFunction = Rc<dyn Fn(&mut LuaState) -> Result<usize, LuaError>>;
218
219#[derive(Clone)]
220pub enum LuaCallable {
221 Bare(LuaCFunction),
222 Rust(LuaRustFunction),
223}
224
225impl std::fmt::Debug for LuaCallable {
226 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227 match self {
228 LuaCallable::Bare(_) => f.write_str("LuaCallable::Bare(..)"),
229 LuaCallable::Rust(_) => f.write_str("LuaCallable::Rust(..)"),
230 }
231 }
232}
233
234impl LuaCallable {
235 pub fn bare(f: LuaCFunction) -> Self {
236 LuaCallable::Bare(f)
237 }
238
239 pub fn rust(f: LuaRustFunction) -> Self {
240 LuaCallable::Rust(f)
241 }
242
243 pub fn as_bare(&self) -> Option<LuaCFunction> {
244 match self {
245 LuaCallable::Bare(f) => Some(*f),
246 LuaCallable::Rust(_) => None,
247 }
248 }
249
250 pub fn call(&self, state: &mut LuaState) -> Result<usize, LuaError> {
251 match self {
252 LuaCallable::Bare(f) => f(state),
253 LuaCallable::Rust(f) => f(state),
254 }
255 }
256}
257
258#[derive(Clone, Debug)]
259pub enum FinalizerObject {
260 Table(GcRef<LuaTable>),
261 UserData(GcRef<LuaUserData>),
262}
263
264impl FinalizerObject {
265 pub fn identity(&self) -> usize {
266 match self {
267 FinalizerObject::Table(t) => t.identity(),
268 FinalizerObject::UserData(u) => u.identity(),
269 }
270 }
271
272 pub fn metatable(&self) -> Option<GcRef<LuaTable>> {
273 match self {
274 FinalizerObject::Table(t) => t.metatable(),
275 FinalizerObject::UserData(u) => u.metatable(),
276 }
277 }
278
279 pub fn as_lua_value(&self) -> LuaValue {
280 match self {
281 FinalizerObject::Table(t) => LuaValue::Table(t.clone()),
282 FinalizerObject::UserData(u) => LuaValue::UserData(u.clone()),
283 }
284 }
285
286 pub fn mark(&self, marker: &mut lua_gc::Marker) {
287 match self {
288 FinalizerObject::Table(t) => marker.mark(t.0),
289 FinalizerObject::UserData(u) => marker.mark(u.0),
290 }
291 }
292
293 pub fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
294 Some(match self {
295 FinalizerObject::Table(t) => t.0.as_trace_ptr(),
296 FinalizerObject::UserData(u) => u.0.as_trace_ptr(),
297 })
298 }
299
300 pub fn age(&self) -> lua_gc::GcAge {
301 match self {
302 FinalizerObject::Table(t) => t.0.age(),
303 FinalizerObject::UserData(u) => u.0.age(),
304 }
305 }
306
307 pub fn is_finalized(&self) -> bool {
308 match self {
309 FinalizerObject::Table(t) => t.0.is_finalized(),
310 FinalizerObject::UserData(u) => u.0.is_finalized(),
311 }
312 }
313
314 pub fn set_finalized(&self, finalized: bool) {
315 match self {
316 FinalizerObject::Table(t) => t.0.set_finalized(finalized),
317 FinalizerObject::UserData(u) => u.0.set_finalized(finalized),
318 }
319 }
320}
321
322impl lua_gc::FinalizerEntry for FinalizerObject {
323 fn identity(&self) -> usize {
324 FinalizerObject::identity(self)
325 }
326
327 fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
328 FinalizerObject::heap_ptr(self)
329 }
330
331 fn age(&self) -> lua_gc::GcAge {
332 FinalizerObject::age(self)
333 }
334
335 fn is_finalized(&self) -> bool {
336 FinalizerObject::is_finalized(self)
337 }
338
339 fn set_finalized(&self, finalized: bool) {
340 FinalizerObject::set_finalized(self, finalized);
341 }
342}
343
344#[derive(Clone, Debug)]
345pub struct WeakTableEntry {
346 table: lua_types::gc::GcWeak<LuaTable>,
347 kind: lua_gc::WeakListKind,
348}
349
350impl WeakTableEntry {
351 pub fn new(table: &GcRef<LuaTable>) -> Self {
352 let mode = table.weak_mode();
353 let weak_keys = (mode & (1 << 0)) != 0;
354 let weak_values = (mode & (1 << 1)) != 0;
355 let kind = match (weak_keys, weak_values) {
356 (true, true) => lua_gc::WeakListKind::AllWeak,
357 (true, false) => lua_gc::WeakListKind::Ephemeron,
358 (false, true) => lua_gc::WeakListKind::WeakValues,
359 (false, false) => lua_gc::WeakListKind::WeakValues,
360 };
361 Self {
362 table: table.downgrade(),
363 kind,
364 }
365 }
366}
367
368impl lua_gc::WeakEntry for WeakTableEntry {
369 type Strong = GcRef<LuaTable>;
370
371 fn identity(&self) -> usize {
372 self.table.identity()
373 }
374
375 fn list_kind(&self) -> lua_gc::WeakListKind {
376 self.kind
377 }
378
379 fn upgrade(&self) -> Option<Self::Strong> {
380 self.table.upgrade()
381 }
382}
383
384pub(crate) const EXTRA_STACK: usize = 5;
388
389pub(crate) const LUA_MINSTACK: usize = 20;
391
392pub(crate) const BASIC_STACK_SIZE: usize = 2 * LUA_MINSTACK;
394
395pub(crate) const LUAI_MAXCCALLS: u32 = 200;
415
416pub(crate) const CIST_C: u16 = 1 << 1;
418
419pub(crate) const CIST_OAH: u16 = 1 << 0;
421pub(crate) const CIST_FRESH: u16 = 1 << 2;
422pub(crate) const CIST_HOOKED: u16 = 1 << 3;
423pub(crate) const CIST_YPCALL: u16 = 1 << 4;
424pub(crate) const CIST_TAIL: u16 = 1 << 5;
425pub(crate) const CIST_HOOKYIELD: u16 = 1 << 6;
426pub(crate) const CIST_FIN: u16 = 1 << 7;
427pub(crate) const CIST_TRAN: u16 = 1 << 8;
428pub(crate) const CIST_RECST: u32 = 10;
429pub(crate) const CIST_LEQ: u16 = 1 << 13;
436
437const LUA_NUMTYPES: usize = 9;
439
440const GCSTPUSR: u8 = 1;
442const GCSTPGC: u8 = 2;
443
444const GCS_PAUSE: u8 = 0;
446
447const LUAI_GCPAUSE: u32 = 200;
448const LUAI_GCMUL: u32 = 100;
449const LUAI_GCSTEPSIZE: u8 = 13;
450const LUAI_GENMAJORMUL: u32 = 100;
451const LUAI_GENMINORMUL: u8 = 20;
452
453const WHITE0BIT: u8 = 0;
454
455const STRCACHE_N: usize = 53;
456const STRCACHE_M: usize = 2;
457
458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464pub enum GcKind {
465 Incremental = 0,
466 Generational = 1,
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq)]
477pub enum WarnMode {
478 Off,
479 On,
480 Cont,
481}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
485pub enum TestWarnMode {
486 Normal,
487 Allow,
488 Store,
489}
490
491pub use lua_types::status::LuaStatus;
496
497#[derive(Clone)]
512pub struct StackValue {
513 pub val: LuaValue,
514}
515
516impl Default for StackValue {
517 fn default() -> Self {
518 StackValue {
519 val: LuaValue::Nil,
520 }
521 }
522}
523
524#[derive(Clone)]
533pub struct CallInfo {
534 pub func: StackIdx,
536
537 pub top: StackIdx,
539
540 pub previous: Option<CallInfoIdx>,
542
543 pub next: Option<CallInfoIdx>,
545
546 pub u: CallInfoFrame,
547
548 pub u2: CallInfoExtra,
549
550 pub nresults: i16,
552
553 pub callstatus: u16,
555
556 pub call_metamethods: u8,
560}
561
562#[derive(Clone, Copy)]
565pub enum CallInfoFrame {
566 Lua {
567 savedpc: u32,
569 trap: bool,
571 nextraargs: i32,
573 },
574 C {
575 k: Option<LuaKFunction>,
577 old_errfunc: isize,
579 ctx: isize,
581 },
582}
583
584pub type LuaKFunction = fn(&mut LuaState, status: i32, ctx: isize) -> Result<usize, LuaError>;
586
587#[derive(Default, Clone, Copy)]
591pub struct CallInfoExtra {
592 pub value: i32,
593 pub ftransfer: u16,
594 pub ntransfer: u16,
595}
596
597impl CallInfoFrame {
598 pub fn c_default() -> Self {
600 CallInfoFrame::C {
601 k: None,
602 old_errfunc: 0,
603 ctx: 0,
604 }
605 }
606
607 pub fn lua_default() -> Self {
609 CallInfoFrame::Lua {
610 savedpc: 0,
611 trap: false,
612 nextraargs: 0,
613 }
614 }
615}
616
617impl Default for CallInfo {
618 fn default() -> Self {
619 CallInfo {
620 func: StackIdx(0),
621 top: StackIdx(0),
622 previous: None,
623 next: None,
624 u: CallInfoFrame::c_default(),
625 u2: CallInfoExtra::default(),
626 nresults: 0,
627 callstatus: 0,
628 call_metamethods: 0,
629 }
630 }
631}
632
633impl CallInfo {
634 pub fn is_lua(&self) -> bool {
635 (self.callstatus & CIST_C) == 0
636 }
637 pub fn is_lua_code(&self) -> bool {
638 self.is_lua()
639 }
640 pub fn is_vararg_func(&self) -> bool {
647 false
648 }
649 pub fn saved_pc(&self) -> u32 {
650 if let CallInfoFrame::Lua { savedpc, .. } = self.u {
651 savedpc
652 } else {
653 0
654 }
655 }
656 pub fn set_saved_pc(&mut self, pc: u32) {
657 if let CallInfoFrame::Lua {
658 ref mut savedpc, ..
659 } = self.u
660 {
661 *savedpc = pc;
662 }
663 }
664 pub fn nextra_args(&self) -> i32 {
665 if let CallInfoFrame::Lua { nextraargs, .. } = self.u {
666 nextraargs
667 } else {
668 0
669 }
670 }
671 pub fn transfer_ftransfer(&self) -> u16 {
672 self.u2.ftransfer
673 }
674 pub fn transfer_ntransfer(&self) -> u16 {
675 self.u2.ntransfer
676 }
677 pub fn set_trap(&mut self, t: bool) {
678 if let CallInfoFrame::Lua { ref mut trap, .. } = self.u {
679 *trap = t;
680 }
681 }
682 pub fn recover_status(&self) -> i32 {
685 ((self.callstatus >> CIST_RECST) & 7) as i32
686 }
687 pub fn set_recover_status<T: Into<i32>>(&mut self, status: T) {
690 let st = (status.into() & 7) as u16;
691 self.callstatus = (self.callstatus & !(7u16 << CIST_RECST)) | (st << CIST_RECST);
692 }
693 pub fn get_oah(&self) -> bool {
694 (self.callstatus & CIST_OAH) != 0
695 }
696 pub fn set_oah(&mut self, allow: bool) {
699 self.callstatus = (self.callstatus & !CIST_OAH) | (if allow { CIST_OAH } else { 0 });
700 }
701 pub fn u_c_old_errfunc(&self) -> isize {
702 if let CallInfoFrame::C { old_errfunc, .. } = self.u {
703 old_errfunc
704 } else {
705 0
706 }
707 }
708 pub fn u_c_ctx(&self) -> isize {
709 if let CallInfoFrame::C { ctx, .. } = self.u {
710 ctx
711 } else {
712 0
713 }
714 }
715 pub fn u_c_k(&self) -> Option<LuaKFunction> {
716 if let CallInfoFrame::C { k, .. } = self.u {
717 k
718 } else {
719 None
720 }
721 }
722 pub fn set_u_c_k(&mut self, k: Option<LuaKFunction>) {
726 if let CallInfoFrame::C {
727 k: ref mut slot, ..
728 } = self.u
729 {
730 *slot = k;
731 }
732 }
733 pub fn set_u_c_ctx(&mut self, ctx: isize) {
735 if let CallInfoFrame::C {
736 ctx: ref mut slot, ..
737 } = self.u
738 {
739 *slot = ctx;
740 }
741 }
742 pub fn set_u_c_old_errfunc(&mut self, old_errfunc: isize) {
744 if let CallInfoFrame::C {
745 old_errfunc: ref mut slot,
746 ..
747 } = self.u
748 {
749 *slot = old_errfunc;
750 }
751 }
752 pub fn set_u2_funcidx(&mut self, idx: i32) {
755 self.u2.value = idx;
756 }
757}
758
759pub trait LuaValueExt {
765 fn base_type(&self) -> lua_types::LuaType;
766 fn to_number_no_strconv(&self) -> Option<f64>;
767 fn to_number_with_strconv(&self) -> Option<f64>;
768 fn to_integer_no_strconv(&self) -> Option<i64>;
769 fn to_integer_with_strconv(&self) -> Option<i64>;
770 fn full_type_tag(&self) -> u8;
771}
772
773impl LuaValueExt for LuaValue {
774 fn base_type(&self) -> lua_types::LuaType {
775 self.type_tag()
776 }
777 fn to_number_no_strconv(&self) -> Option<f64> {
778 match self {
779 LuaValue::Float(f) => Some(*f),
780 LuaValue::Int(i) => Some(*i as f64),
781 _ => None,
782 }
783 }
784 fn to_number_with_strconv(&self) -> Option<f64> {
785 if let Some(n) = self.to_number_no_strconv() {
786 return Some(n);
787 }
788 if let LuaValue::Str(s) = self {
789 let mut tmp = LuaValue::Nil;
790 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
791 if sz == 0 {
792 return None;
793 }
794 return match tmp {
795 LuaValue::Int(i) => Some(i as f64),
796 LuaValue::Float(f) => Some(f),
797 _ => None,
798 };
799 }
800 None
801 }
802 fn to_integer_no_strconv(&self) -> Option<i64> {
803 match self {
804 LuaValue::Int(i) => Some(*i),
805 LuaValue::Float(f) if f.fract() == 0.0 && f.is_finite() => {
806 let min_f = i64::MIN as f64;
810 let max_plus1_f = -(i64::MIN as f64);
811 if *f >= min_f && *f < max_plus1_f {
812 Some(*f as i64)
813 } else {
814 None
815 }
816 }
817 _ => None,
818 }
819 }
820 fn to_integer_with_strconv(&self) -> Option<i64> {
821 if let Some(i) = self.to_integer_no_strconv() {
822 return Some(i);
823 }
824 if let LuaValue::Str(s) = self {
825 let mut tmp = LuaValue::Nil;
826 let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
827 if sz == 0 {
828 return None;
829 }
830 return tmp.to_integer_no_strconv();
831 }
832 None
833 }
834 fn full_type_tag(&self) -> u8 {
835 match self {
836 LuaValue::Nil => 0x00,
837 LuaValue::Bool(false) => 0x01,
838 LuaValue::Bool(true) => 0x11,
839 LuaValue::Int(_) => 0x03,
840 LuaValue::Float(_) => 0x13,
841 LuaValue::Str(s) if s.is_short() => 0x04,
842 LuaValue::Str(_) => 0x14,
843 LuaValue::LightUserData(_) => 0x02,
844 LuaValue::Table(_) => 0x05,
845 LuaValue::Function(LuaClosure::Lua(_)) => 0x06,
846 LuaValue::Function(LuaClosure::LightC(_)) => 0x16,
847 LuaValue::Function(LuaClosure::C(_)) => 0x26,
848 LuaValue::UserData(_) => 0x07,
849 LuaValue::Thread(_) => 0x08,
850 }
851 }
852}
853
854pub trait LuaTypeExt {
856 fn type_name(&self) -> &'static [u8];
857}
858
859impl LuaTypeExt for lua_types::LuaType {
860 fn type_name(&self) -> &'static [u8] {
861 use lua_types::LuaType::*;
862 match self {
863 None => b"no value",
864 Nil => b"nil",
865 Boolean => b"boolean",
866 LightUserData => b"userdata",
867 Number => b"number",
868 String => b"string",
869 Table => b"table",
870 Function => b"function",
871 UserData => b"userdata",
872 Thread => b"thread",
873 }
874 }
875}
876
877pub trait StackIdxExt {
881 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32;
882 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32;
883 fn raw(self) -> u32;
884}
885impl StackIdxExt for StackIdx {
886 #[inline(always)]
887 fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32 {
888 self.0.saturating_sub(n.into().0 .0)
889 }
890 #[inline(always)]
891 fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32 {
892 self.0.wrapping_sub(n.into().0 .0)
893 }
894 #[inline(always)]
895 fn raw(self) -> u32 {
896 self.0
897 }
898}
899
900pub trait LuaTableRefExt {
910 fn metatable(&self) -> Option<GcRef<LuaTable>>;
911 fn has_metatable(&self) -> bool;
912 fn as_ptr(&self) -> *const ();
913 fn get(&self, _k: &LuaValue) -> LuaValue;
914 fn get_int(&self, _k: i64) -> LuaValue;
915 fn get_short_str(&self, _k: &GcRef<LuaString>) -> LuaValue;
916 fn raw_set(&self, _state: &mut LuaState, _k: LuaValue, _v: LuaValue) -> Result<(), LuaError>;
917 fn raw_set_int(&self, _state: &mut LuaState, _k: i64, _v: LuaValue) -> Result<(), LuaError>;
918 fn raw_set_short_str(
919 &self,
920 _state: &mut LuaState,
921 _k: GcRef<LuaString>,
922 _v: LuaValue,
923 ) -> Result<(), LuaError>;
924 fn invalidate_tm_cache(&self);
925 fn resize(&self, _state: &mut LuaState, _na: usize, _nh: usize) -> Result<(), LuaError>;
926 fn next(&self, _k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError>;
927}
928impl LuaTableRefExt for GcRef<LuaTable> {
929 #[inline]
930 fn metatable(&self) -> Option<GcRef<LuaTable>> {
931 (**self).metatable()
932 }
933 #[inline]
934 fn has_metatable(&self) -> bool {
935 (**self).has_metatable()
936 }
937 #[inline]
938 fn as_ptr(&self) -> *const () {
939 GcRef::identity(self) as *const ()
940 }
941 #[inline]
942 fn get(&self, k: &LuaValue) -> LuaValue {
943 (**self).get(k)
944 }
945 #[inline]
946 fn get_int(&self, k: i64) -> LuaValue {
947 (**self).get_int(k)
948 }
949 #[inline]
950 fn get_short_str(&self, k: &GcRef<LuaString>) -> LuaValue {
951 (**self).get_short_str(k)
952 }
953 #[inline(always)]
962 fn raw_set(&self, state: &mut LuaState, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
963 match k {
964 LuaValue::Int(i) => return self.raw_set_int(state, i, v),
965 LuaValue::Str(s) if s.is_short() => return self.raw_set_short_str(state, s, v),
966 k => {
967 if k.is_collectable() {
968 state.gc_table_barrier_back(self, &k);
969 }
970 let before = (**self).buffer_bytes();
971 let result = (**self).try_raw_set(k, v);
972 if result.is_ok() {
973 account_table_buffer_delta(self, before);
974 }
975 result
976 }
977 }
978 }
979 #[inline(always)]
980 fn raw_set_int(&self, _state: &mut LuaState, k: i64, v: LuaValue) -> Result<(), LuaError> {
981 match (**self).try_update_int(k, v) {
982 Ok(()) => Ok(()),
983 Err(v) => {
984 let before = (**self).buffer_bytes();
985 let result = (**self).try_raw_set_int(k, v);
986 if result.is_ok() {
987 account_table_buffer_delta(self, before);
988 }
989 result
990 }
991 }
992 }
993 #[inline(always)]
997 fn raw_set_short_str(
998 &self,
999 state: &mut LuaState,
1000 k: GcRef<LuaString>,
1001 v: LuaValue,
1002 ) -> Result<(), LuaError> {
1003 match (**self).try_update_short_str(&k, v) {
1004 Ok(()) => Ok(()),
1005 Err(v) => {
1006 state.gc_table_barrier_back(self, &LuaValue::Str(k));
1007 let before = (**self).buffer_bytes();
1008 let result = (**self).try_raw_set(LuaValue::Str(k), v);
1009 if result.is_ok() {
1010 account_table_buffer_delta(self, before);
1011 }
1012 result
1013 }
1014 }
1015 }
1016 fn invalidate_tm_cache(&self) {}
1017 fn resize(&self, _state: &mut LuaState, na: usize, nh: usize) -> Result<(), LuaError> {
1018 let before = (**self).buffer_bytes();
1019 let na32 = na.min(u32::MAX as usize) as u32;
1020 let nh32 = nh.min(u32::MAX as usize) as u32;
1021 let result = (**self).resize(na32, nh32);
1022 if result.is_ok() {
1023 account_table_buffer_delta(self, before);
1024 }
1025 result
1026 }
1027 fn next(&self, k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
1028 (**self).try_next_pair(&k)
1029 }
1030}
1031
1032#[inline]
1033fn account_table_buffer_delta(t: &GcRef<LuaTable>, before: usize) {
1034 let after = (**t).buffer_bytes();
1035 if after > before {
1036 t.account_buffer((after - before) as isize);
1037 } else if before > after {
1038 t.account_buffer(-((before - after) as isize));
1039 }
1040}
1041
1042pub trait LuaUserDataRefExt {
1043 fn metatable(&self) -> Option<GcRef<LuaTable>>;
1044 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>);
1045 fn as_ptr(&self) -> *const ();
1046 fn len(&self) -> usize;
1047}
1048impl LuaUserDataRefExt for GcRef<LuaUserData> {
1049 fn metatable(&self) -> Option<GcRef<LuaTable>> {
1050 (**self).metatable()
1051 }
1052 fn set_metatable(&self, mt: Option<GcRef<LuaTable>>) {
1053 (**self).set_metatable(mt);
1054 }
1055 fn as_ptr(&self) -> *const () {
1056 GcRef::identity(self) as *const ()
1057 }
1058 fn len(&self) -> usize {
1059 self.0.data.len()
1060 }
1061}
1062
1063pub trait LuaStringRefExt {
1064 fn is_white(&self) -> bool;
1065 fn hash(&self) -> u32;
1066 fn as_gc_ref(&self) -> GcRef<LuaString>;
1067}
1068impl LuaStringRefExt for GcRef<LuaString> {
1069 fn is_white(&self) -> bool {
1070 false
1071 }
1072 fn hash(&self) -> u32 {
1073 self.0.hash()
1074 }
1075 fn as_gc_ref(&self) -> GcRef<LuaString> {
1076 self.clone()
1077 }
1078}
1079
1080pub trait LuaLClosureRefExt {
1081 fn proto(&self) -> &GcRef<LuaProto>;
1082 fn nupvalues(&self) -> usize;
1083}
1084impl LuaLClosureRefExt for GcRef<lua_types::closure::LuaLClosure> {
1085 fn proto(&self) -> &GcRef<LuaProto> {
1086 &self.0.proto
1087 }
1088 fn nupvalues(&self) -> usize {
1089 self.0.upvals.len()
1090 }
1091}
1092
1093pub trait LuaClosureExt {
1095 fn nupvalues(&self) -> usize;
1096}
1097impl LuaClosureExt for LuaClosure {
1098 fn nupvalues(&self) -> usize {
1099 match self {
1100 LuaClosure::Lua(l) => l.0.upvals.len(),
1101 LuaClosure::C(c) => c.0.upvalues.borrow().len(),
1102 LuaClosure::LightC(_) => 0,
1103 }
1104 }
1105}
1106
1107pub trait LuaProtoExt {
1109 fn source_bytes(&self) -> &[u8];
1110 fn source_string(&self) -> Option<&GcRef<LuaString>>;
1111}
1112impl LuaProtoExt for LuaProto {
1113 fn source_bytes(&self) -> &[u8] {
1114 match &self.source {
1115 Some(s) => s.0.as_bytes(),
1116 None => &[],
1117 }
1118 }
1119 fn source_string(&self) -> Option<&GcRef<LuaString>> {
1120 self.source.as_ref()
1121 }
1122}
1123
1124pub trait Collectable: std::fmt::Debug {}
1131
1132impl std::fmt::Debug for LuaState {
1133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1134 write!(f, "LuaState")
1135 }
1136}
1137impl Collectable for LuaState {}
1138
1139pub type ParserHook = fn(
1148 state: &mut LuaState,
1149 source: &[u8],
1150 name: &[u8],
1151 firstchar: i32,
1152) -> Result<GcRef<lua_types::closure::LuaLClosure>, LuaError>;
1153
1154pub type FileLoaderHook = fn(filename: &[u8]) -> Result<Vec<u8>, LuaError>;
1162
1163pub type FileOpenHook =
1174 fn(filename: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1175
1176pub type OutputHook = fn(bytes: &[u8]) -> std::io::Result<()>;
1184
1185pub type InputHook = fn(buf: &mut [u8]) -> std::io::Result<usize>;
1188
1189pub type EnvHook = fn(name: &[u8]) -> Option<Vec<u8>>;
1195
1196pub type UnixTimeHook = fn() -> i64;
1198
1199pub type CpuClockHook = fn() -> f64;
1207
1208pub type LocalOffsetHook = fn(timestamp: i64) -> i64;
1220
1221pub type EntropyHook = fn() -> u64;
1225
1226pub type TempNameHook = fn() -> Result<Vec<u8>, LuaError>;
1231
1232pub type PopenHook =
1243 fn(cmd: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
1244
1245pub type FileRemoveHook = fn(filename: &[u8]) -> Result<(), LuaError>;
1251
1252pub type FileRenameHook = fn(from: &[u8], to: &[u8]) -> Result<(), LuaError>;
1258
1259#[derive(Clone, Copy, Debug)]
1265pub enum OsExecuteReason {
1266 Exit,
1268 Signal,
1270}
1271
1272#[derive(Debug)]
1275pub struct OsExecuteResult {
1276 pub success: bool,
1278 pub reason: OsExecuteReason,
1280 pub code: i32,
1282}
1283
1284pub type OsExecuteHook = fn(cmd: &[u8]) -> Result<OsExecuteResult, LuaError>;
1291
1292#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1299pub struct DynLibId(pub u64);
1300
1301pub enum DynamicSymbol {
1309 RustNative(LuaCFunction),
1312 LuaCAbi(*const ()),
1318 Unsupported { reason: Vec<u8> },
1321}
1322
1323pub type DynLibLoadHook =
1334 fn(state: &mut LuaState, path: &[u8], see_global: bool) -> Result<DynLibId, LuaError>;
1335
1336pub type DynLibSymbolHook =
1344 fn(state: &mut LuaState, handle: DynLibId, symbol: &[u8]) -> Result<DynamicSymbol, LuaError>;
1345
1346pub type DynLibUnloadHook = fn(handle: DynLibId);
1354
1355pub struct ThreadRegistryEntry {
1361 pub state: Rc<RefCell<LuaState>>,
1366 pub value: GcRef<lua_types::value::LuaThread>,
1369}
1370
1371#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1376pub struct ExternalRootKey {
1377 index: usize,
1378 generation: u64,
1379}
1380
1381#[derive(Debug)]
1382struct ExternalRootSlot {
1383 value: Option<LuaValue>,
1384 generation: u64,
1385}
1386
1387#[derive(Debug, Default)]
1393pub struct ExternalRootSet {
1394 slots: Vec<ExternalRootSlot>,
1395 free: Vec<usize>,
1396 live: usize,
1397}
1398
1399impl ExternalRootSet {
1400 pub fn insert(&mut self, value: LuaValue) -> ExternalRootKey {
1401 if let Some(index) = self.free.pop() {
1402 let slot = &mut self.slots[index];
1403 debug_assert!(slot.value.is_none(), "free external-root slot is occupied");
1404 slot.generation = slot.generation.wrapping_add(1).max(1);
1405 slot.value = Some(value);
1406 self.live += 1;
1407 ExternalRootKey {
1408 index,
1409 generation: slot.generation,
1410 }
1411 } else {
1412 let index = self.slots.len();
1413 self.slots.push(ExternalRootSlot {
1414 value: Some(value),
1415 generation: 1,
1416 });
1417 self.live += 1;
1418 ExternalRootKey {
1419 index,
1420 generation: 1,
1421 }
1422 }
1423 }
1424
1425 pub fn get(&self, key: ExternalRootKey) -> Option<&LuaValue> {
1426 let slot = self.slots.get(key.index)?;
1427 if slot.generation == key.generation {
1428 slot.value.as_ref()
1429 } else {
1430 None
1431 }
1432 }
1433
1434 pub fn replace(&mut self, key: ExternalRootKey, value: LuaValue) -> Option<LuaValue> {
1435 let slot = self.slots.get_mut(key.index)?;
1436 if slot.generation != key.generation || slot.value.is_none() {
1437 return None;
1438 }
1439 slot.value.replace(value)
1440 }
1441
1442 pub fn remove(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
1443 let slot = self.slots.get_mut(key.index)?;
1444 if slot.generation != key.generation {
1445 return None;
1446 }
1447 let old = slot.value.take()?;
1448 self.free.push(key.index);
1449 self.live -= 1;
1450 Some(old)
1451 }
1452
1453 pub fn iter_values(&self) -> impl Iterator<Item = &LuaValue> {
1454 self.slots.iter().filter_map(|slot| slot.value.as_ref())
1455 }
1456
1457 pub fn len(&self) -> usize {
1458 self.live
1459 }
1460
1461 pub fn is_empty(&self) -> bool {
1462 self.live == 0
1463 }
1464
1465 pub fn vacant_len(&self) -> usize {
1466 self.free.len()
1467 }
1468}
1469
1470pub struct GlobalState {
1476 pub parser_hook: Option<ParserHook>,
1482
1483 pub cli_argv: Option<Vec<Vec<u8>>>,
1490
1491 pub cli_preload: Option<fn(&mut LuaState) -> Result<(), LuaError>>,
1495
1496 pub lua_version: lua_types::LuaVersion,
1503
1504 pub file_loader_hook: Option<FileLoaderHook>,
1509
1510 pub file_open_hook: Option<FileOpenHook>,
1515
1516 pub stdout_hook: Option<OutputHook>,
1520
1521 pub stderr_hook: Option<OutputHook>,
1523
1524 pub stdin_hook: Option<InputHook>,
1527
1528 pub env_hook: Option<EnvHook>,
1530
1531 pub unix_time_hook: Option<UnixTimeHook>,
1534
1535 pub cpu_clock_hook: Option<CpuClockHook>,
1538
1539 pub local_offset_hook: Option<LocalOffsetHook>,
1544
1545 pub entropy_hook: Option<EntropyHook>,
1548
1549 pub temp_name_hook: Option<TempNameHook>,
1551
1552 pub popen_hook: Option<PopenHook>,
1557
1558 pub file_remove_hook: Option<FileRemoveHook>,
1561
1562 pub file_rename_hook: Option<FileRenameHook>,
1565
1566 pub os_execute_hook: Option<OsExecuteHook>,
1570
1571 pub dynlib_load_hook: Option<DynLibLoadHook>,
1576
1577 pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
1581
1582 pub dynlib_unload_hook: Option<DynLibUnloadHook>,
1586
1587 pub sandbox: SandboxLimits,
1590
1591 pub gc_debt: isize,
1593
1594 pub gc_estimate: usize,
1595
1596 pub lastatomic: usize,
1598
1599 pub strt: StringPool,
1601
1602 pub l_registry: LuaValue,
1604
1605 pub external_roots: ExternalRootSet,
1608
1609 pub globals: LuaValue,
1616 pub loaded: LuaValue,
1617
1618 pub nilvalue: LuaValue,
1622
1623 pub seed: u32,
1625
1626 pub currentwhite: u8,
1628
1629 pub gcstate: u8,
1630
1631 pub gckind: u8,
1632
1633 pub gcstopem: bool,
1634
1635 pub genminormul: u8,
1637
1638 pub genmajormul: u8,
1639
1640 pub gcstp: u8,
1641
1642 pub gcemergency: bool,
1643
1644 pub gcpause: u8,
1646
1647 pub gcstepmul: u8,
1649
1650 pub gcstepsize: u8,
1651
1652 pub gc55_params: [i64; 6],
1661
1662 pub sweepgc_cursor: usize,
1667
1668 pub weak_tables_registry: lua_gc::WeakRegistry<WeakTableEntry>,
1677
1678 pub finalizers: lua_gc::FinalizerRegistry<FinalizerObject>,
1681
1682 pub gc_finalizer_error: Option<LuaValue>,
1693
1694 pub twups: Vec<GcRef<LuaState>>,
1704
1705 pub panic: Option<LuaCFunction>,
1707
1708 pub mainthread: Option<GcRef<LuaState>>,
1711
1712 pub threads: std::collections::HashMap<u64, ThreadRegistryEntry>,
1725
1726 pub main_thread_value: GcRef<lua_types::value::LuaThread>,
1731
1732 pub current_thread_id: u64,
1737
1738 pub closing_thread_id: Option<u64>,
1741
1742 pub main_thread_id: u64,
1745
1746 pub next_thread_id: u64,
1749
1750 pub memerrmsg: GcRef<LuaString>,
1752
1753 pub tmname: Vec<GcRef<LuaString>>,
1756
1757 pub mt: [Option<GcRef<LuaTable>>; LUA_NUMTYPES],
1759
1760 pub strcache: [[GcRef<LuaString>; STRCACHE_M]; STRCACHE_N],
1762
1763 pub interned_lt: InternedStringMap,
1769
1770 pub warnf: Option<Box<dyn FnMut(&[u8], bool)>>,
1772
1773 pub warn_mode: WarnMode,
1778
1779 pub test_warn_enabled: bool,
1784 pub test_warn_on: bool,
1785 pub test_warn_mode: TestWarnMode,
1786 pub test_warn_last_to_cont: bool,
1787 pub test_warn_buffer: Vec<u8>,
1788
1789 pub c_functions: Vec<LuaCallable>,
1794
1795 pub heap: lua_gc::Heap,
1800
1801 pub cross_thread_upvals: std::collections::HashMap<(u64, StackIdx), LuaValue>,
1815
1816 pub suspended_parent_stacks: Vec<Vec<LuaValue>>,
1830
1831 pub suspended_parent_open_upvals: Vec<Vec<GcRef<UpVal>>>,
1837
1838 pub snapshot_stack_pool: Vec<Vec<LuaValue>>,
1846 pub snapshot_upval_pool: Vec<Vec<GcRef<UpVal>>>,
1847}
1848
1849const SANDBOX_COUNT_MASK: u8 = 1 << 3;
1852
1853pub const SANDBOX_TRIP_NONE: u8 = 0;
1855pub const SANDBOX_TRIP_INSTRUCTIONS: u8 = 1;
1857pub const SANDBOX_TRIP_MEMORY: u8 = 2;
1859
1860#[derive(Default)]
1867pub struct SandboxLimits {
1868 pub interval: std::cell::Cell<i32>,
1870 pub instr_limited: std::cell::Cell<bool>,
1872 pub instr_remaining: std::cell::Cell<u64>,
1874 pub instr_limit: std::cell::Cell<u64>,
1876 pub mem_limit: std::cell::Cell<Option<usize>>,
1878 pub tripped: std::cell::Cell<u8>,
1880 pub aborting: std::cell::Cell<bool>,
1885}
1886
1887impl GlobalState {
1888 pub fn sandbox_active(&self) -> bool {
1890 self.sandbox.interval.get() != 0
1891 }
1892
1893 pub fn total_bytes(&self) -> usize {
1898 self.heap.bytes_used().max(1)
1899 }
1900
1901 pub fn get_thread(&self, id: u64) -> Option<&ThreadRegistryEntry> {
1906 self.threads.get(&id)
1907 }
1908
1909 pub fn thread_value_for(&self, id: u64) -> Option<GcRef<lua_types::value::LuaThread>> {
1913 if id == self.main_thread_id {
1914 Some(self.main_thread_value.clone())
1915 } else {
1916 self.threads.get(&id).map(|e| e.value.clone())
1917 }
1918 }
1919
1920 pub fn is_complete(&self) -> bool {
1927 matches!(self.nilvalue, LuaValue::Nil)
1928 }
1929
1930 pub fn current_white(&self) -> u8 {
1938 self.currentwhite
1939 }
1940
1941 pub fn other_white(&self) -> u8 {
1945 self.currentwhite ^ 0x03
1946 }
1947
1948 pub fn is_gen_mode(&self) -> bool {
1952 self.gckind == GcKind::Generational as u8 || self.lastatomic != 0
1953 }
1954
1955 pub fn gc_running(&self) -> bool {
1959 self.gcstp == 0
1960 }
1961
1962 pub fn keep_invariant(&self) -> bool {
1966 self.heap.gc_state().is_invariant()
1967 }
1968
1969 pub fn is_sweep_phase(&self) -> bool {
1973 self.heap.gc_state().is_sweep()
1974 }
1975
1976 pub fn gc_debt(&self) -> isize {
1978 self.gc_debt
1979 }
1980 pub fn set_gc_debt(&mut self, d: isize) {
1981 self.gc_debt = d;
1982 }
1983 pub fn gc_at_pause(&self) -> bool {
1984 self.heap.gc_state().is_pause()
1985 }
1986 fn get_gc_param(p: u8) -> i32 {
1987 (p as i32) * 4
1988 }
1989 fn set_gc_param_slot(slot: &mut u8, p: i32) {
1990 *slot = (p / 4) as u8;
1991 }
1992 pub fn gc_pause_param(&self) -> i32 {
1993 Self::get_gc_param(self.gcpause)
1994 }
1995 pub fn set_gc_pause_param(&mut self, p: i32) {
1996 Self::set_gc_param_slot(&mut self.gcpause, p);
1997 }
1998 pub fn gc_stepmul_param(&self) -> i32 {
1999 Self::get_gc_param(self.gcstepmul)
2000 }
2001 pub fn set_gc_stepmul_param(&mut self, p: i32) {
2002 Self::set_gc_param_slot(&mut self.gcstepmul, p);
2003 }
2004 pub fn gc_genmajormul_param(&self) -> i32 {
2005 Self::get_gc_param(self.genmajormul)
2006 }
2007 pub fn set_gc_genmajormul(&mut self, p: i32) {
2008 Self::set_gc_param_slot(&mut self.genmajormul, p);
2009 }
2010 pub fn gc55_param(&mut self, idx: usize, value: i64) -> i64 {
2014 let old = self.gc55_params[idx];
2015 if value >= 0 {
2016 self.gc55_params[idx] = value;
2017 }
2018 old
2019 }
2020 pub fn gc_stop_flags(&self) -> u8 {
2021 self.gcstp
2022 }
2023 pub fn set_gc_stop_flags(&mut self, f: u8) {
2024 self.gcstp = f;
2025 }
2026 pub fn stop_gc_internal(&mut self) -> u8 {
2027 let old = self.gcstp;
2028 self.gcstp |= GCSTPGC;
2029 old
2030 }
2031 pub fn set_gc_stop_user(&mut self) {
2032 self.gcstp = GCSTPUSR;
2034 }
2035 pub fn clear_gc_stop(&mut self) {
2036 self.gcstp = 0;
2037 }
2038 pub fn is_gc_running(&self) -> bool {
2039 self.gcstp == 0
2040 }
2041 pub fn is_gc_stopped_internally(&self) -> bool {
2046 (self.gcstp & GCSTPGC) != 0
2047 }
2048
2049 pub fn tm_name<T: TmIndex>(&self, tm: T) -> Option<GcRef<LuaString>> {
2059 self.tmname.get(tm.tm_index()).cloned()
2060 }
2061}
2062
2063pub trait TmIndex: Copy {
2069 fn tm_index(self) -> usize;
2070}
2071impl TmIndex for lua_types::tagmethod::TagMethod {
2072 fn tm_index(self) -> usize {
2073 self as u8 as usize
2074 }
2075}
2076impl TmIndex for crate::tagmethods::TagMethod {
2077 fn tm_index(self) -> usize {
2078 self as u8 as usize
2079 }
2080}
2081impl TmIndex for usize {
2082 fn tm_index(self) -> usize {
2083 self
2084 }
2085}
2086impl TmIndex for u8 {
2087 fn tm_index(self) -> usize {
2088 self as usize
2089 }
2090}
2091
2092use lua_types::tagmethod::TagMethod;
2093
2094pub struct LuaState {
2104 pub status: u8,
2108
2109 pub allowhook: bool,
2111
2112 pub nci: u32,
2114
2115 pub top: StackIdx,
2119
2120 pub stack_last: StackIdx,
2122
2123 pub stack: Vec<StackValue>,
2125
2126 pub ci: CallInfoIdx,
2130
2131 pub call_info: Vec<CallInfo>,
2134
2135 pub openupval: Vec<GcRef<UpVal>>,
2139
2140 pub tbclist: Vec<StackIdx>,
2142
2143 pub(crate) global: Rc<RefCell<GlobalState>>,
2148
2149 pub hook: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>,
2153
2154 pub hookmask: u8,
2156
2157 pub basehookcount: i32,
2159
2160 pub hookcount: i32,
2162
2163 pub errfunc: isize,
2170
2171 pub n_ccalls: u32,
2175
2176 pub oldpc: u32,
2180
2181 pub marked: u8,
2185
2186 pub cached_thread_id: u64,
2202
2203 pub gc_check_needed: bool,
2208}
2209
2210impl LuaState {
2211 pub fn global(&self) -> std::cell::Ref<'_, GlobalState> {
2219 self.global.borrow()
2220 }
2221
2222 pub fn global_mut(&self) -> std::cell::RefMut<'_, GlobalState> {
2226 self.global.borrow_mut()
2227 }
2228
2229 pub fn global_rc(&self) -> Rc<RefCell<GlobalState>> {
2233 Rc::clone(&self.global)
2234 }
2235
2236 pub fn enable_test_warning_handler(&mut self) -> Result<(), LuaError> {
2238 {
2239 let mut g = self.global_mut();
2240 g.test_warn_enabled = true;
2241 g.test_warn_on = false;
2242 g.test_warn_mode = TestWarnMode::Normal;
2243 g.test_warn_last_to_cont = false;
2244 g.test_warn_buffer.clear();
2245 }
2246 self.push(LuaValue::Bool(false));
2247 crate::api::set_global(self, b"_WARN")
2248 }
2249
2250 pub fn c_calls(&self) -> u32 {
2254 self.n_ccalls & 0xffff
2255 }
2256
2257 pub fn inc_nny(&mut self) {
2261 self.n_ccalls += 0x10000;
2262 }
2263
2264 pub fn dec_nny(&mut self) {
2268 self.n_ccalls -= 0x10000;
2269 }
2270
2271 pub fn is_yieldable(&self) -> bool {
2275 (self.n_ccalls & 0xffff0000) == 0
2276 }
2277
2278 pub fn reset_hook_count(&mut self) {
2282 self.hookcount = self.basehookcount;
2283 }
2284
2285 pub fn install_sandbox_limits(
2294 &mut self,
2295 interval: i32,
2296 instr_limit: Option<u64>,
2297 mem_limit: Option<usize>,
2298 ) {
2299 let interval = interval.max(1);
2300 {
2301 let g = self.global();
2302 g.sandbox.interval.set(interval);
2303 g.sandbox.instr_limited.set(instr_limit.is_some());
2304 g.sandbox.instr_remaining.set(instr_limit.unwrap_or(0));
2305 g.sandbox.instr_limit.set(instr_limit.unwrap_or(0));
2306 g.sandbox.mem_limit.set(mem_limit);
2307 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2308 }
2309 self.hookmask |= SANDBOX_COUNT_MASK;
2310 self.basehookcount = interval;
2311 self.hookcount = interval;
2312 crate::debug::arm_traps(self);
2313 }
2314
2315 pub fn sandbox_charge_interval(&self) -> Option<LuaError> {
2320 let interval = self.global().sandbox.interval.get();
2321 self.sandbox_charge(interval as u64)
2322 }
2323
2324 pub fn sandbox_charge(&self, amount: u64) -> Option<LuaError> {
2333 let g = self.global();
2334 if g.sandbox.interval.get() == 0 {
2335 return None;
2336 }
2337 if g.sandbox.instr_limited.get() {
2338 let rem = g.sandbox.instr_remaining.get().saturating_sub(amount);
2339 g.sandbox.instr_remaining.set(rem);
2340 if rem == 0 {
2341 g.sandbox.tripped.set(SANDBOX_TRIP_INSTRUCTIONS);
2342 g.sandbox.aborting.set(true);
2343 return Some(LuaError::runtime(format_args!(
2344 "sandbox: instruction budget exhausted"
2345 )));
2346 }
2347 }
2348 if let Some(limit) = g.sandbox.mem_limit.get() {
2349 if g.total_bytes() > limit {
2350 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2351 g.sandbox.aborting.set(true);
2352 return Some(LuaError::runtime(format_args!(
2353 "sandbox: memory limit exceeded"
2354 )));
2355 }
2356 }
2357 None
2358 }
2359
2360 pub fn sandbox_reserve(&self, additional: usize) -> Option<LuaError> {
2368 let g = self.global();
2369 if g.sandbox.interval.get() == 0 {
2370 return None;
2371 }
2372 if let Some(limit) = g.sandbox.mem_limit.get() {
2373 let projected = g.total_bytes().saturating_add(additional);
2374 if projected > limit {
2375 g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
2376 g.sandbox.aborting.set(true);
2377 return Some(LuaError::runtime(format_args!(
2378 "sandbox: memory limit exceeded"
2379 )));
2380 }
2381 }
2382 None
2383 }
2384
2385 pub fn sandbox_match_step_limit(&self) -> u64 {
2390 let g = self.global();
2391 if g.sandbox.interval.get() != 0 && g.sandbox.instr_limited.get() {
2392 g.sandbox.instr_remaining.get()
2393 } else {
2394 0
2395 }
2396 }
2397
2398 pub fn sandbox_aborting(&self) -> bool {
2402 self.global().sandbox.aborting.get()
2403 }
2404
2405 pub fn sandbox_instr_limited(&self) -> bool {
2407 self.global().sandbox.instr_limited.get()
2408 }
2409
2410 pub fn sandbox_instr_remaining(&self) -> u64 {
2413 self.global().sandbox.instr_remaining.get()
2414 }
2415
2416 pub fn sandbox_instr_limit(&self) -> u64 {
2418 self.global().sandbox.instr_limit.get()
2419 }
2420
2421 pub fn sandbox_tripped_code(&self) -> u8 {
2423 self.global().sandbox.tripped.get()
2424 }
2425
2426 pub fn sandbox_reset(&self) {
2429 let g = self.global();
2430 if g.sandbox.instr_limited.get() {
2431 g.sandbox.instr_remaining.set(g.sandbox.instr_limit.get());
2432 }
2433 g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
2434 g.sandbox.aborting.set(false);
2435 }
2436
2437 pub fn stack_size(&self) -> usize {
2441 self.stack_last.0 as usize
2442 }
2443
2444 #[inline(always)]
2448 pub fn push(&mut self, val: LuaValue) {
2449 let top = self.top.0 as usize;
2450 if top < self.stack.len() {
2451 self.stack[top] = StackValue { val };
2452 } else {
2453 self.stack.push(StackValue { val });
2454 }
2455 self.top = StackIdx(self.top.0 + 1);
2456 }
2457
2458 #[inline(always)]
2461 pub fn pop(&mut self) -> LuaValue {
2462 if self.top.0 == 0 {
2463 return LuaValue::Nil;
2464 }
2465 self.top = StackIdx(self.top.0 - 1);
2466 self.stack[self.top.0 as usize].val.clone()
2467 }
2468
2469 #[inline(always)]
2473 pub fn stack_val(&self, idx: StackIdx) -> &LuaValue {
2474 &self.stack[idx.0 as usize].val
2475 }
2476
2477 #[inline(always)]
2479 pub fn set_stack_val(&mut self, idx: StackIdx, val: LuaValue) {
2480 self.stack[idx.0 as usize].val = val;
2481 }
2482
2483 pub fn gc(&mut self) -> GcHandle<'_> {
2490 GcHandle { _state: self }
2491 }
2492
2493 pub fn external_root_value(&mut self, value: LuaValue) -> ExternalRootKey {
2495 self.global_mut().external_roots.insert(value)
2496 }
2497
2498 pub fn external_rooted_value(&self, key: ExternalRootKey) -> Option<LuaValue> {
2500 self.global().external_roots.get(key).cloned()
2501 }
2502
2503 pub fn external_replace_root(
2505 &mut self,
2506 key: ExternalRootKey,
2507 value: LuaValue,
2508 ) -> Option<LuaValue> {
2509 self.global_mut().external_roots.replace(key, value)
2510 }
2511
2512 pub fn external_unroot_value(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
2514 self.global_mut().external_roots.remove(key)
2515 }
2516
2517 pub fn try_external_unroot_value(
2520 &mut self,
2521 key: ExternalRootKey,
2522 ) -> std::result::Result<Option<LuaValue>, std::cell::BorrowMutError> {
2523 self.global
2524 .try_borrow_mut()
2525 .map(|mut global| global.external_roots.remove(key))
2526 }
2527
2528 pub fn new_table(&mut self) -> GcRef<LuaTable> {
2532 self.mark_gc_check_needed();
2534 GcRef::new(LuaTable::placeholder())
2535 }
2536
2537 pub fn new_table_with_sizes(
2542 &mut self,
2543 array_size: u32,
2544 hash_size: u32,
2545 ) -> Result<GcRef<LuaTable>, LuaError> {
2546 self.mark_gc_check_needed();
2547 let t = GcRef::new(LuaTable::placeholder());
2548 self.table_resize(&t, array_size as usize, hash_size as usize)?;
2549 Ok(t)
2550 }
2551
2552 pub fn intern_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
2567 if bytes.len() <= crate::string::MAX_SHORT_LEN {
2568 let hash = LuaString::hash_bytes(bytes, 0);
2569 {
2570 let global = self.global();
2571 if let Some(existing) = global.interned_lt.find(bytes, hash) {
2572 return Ok(existing);
2573 }
2574 }
2575 let new_ref = GcRef::new(LuaString::from_bytes(bytes.to_vec()));
2576 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2577 self.global_mut().interned_lt.insert(new_ref.clone());
2578 self.mark_gc_check_needed();
2579 Ok(new_ref)
2580 } else {
2581 self.mark_gc_check_needed();
2582 let new_ref = GcRef::new(LuaString::from_bytes(bytes.to_vec()));
2583 new_ref.account_buffer(new_ref.buffer_bytes() as isize);
2584 Ok(new_ref)
2585 }
2586 }
2587
2588 #[inline(always)]
2590 pub fn top_idx(&self) -> StackIdx {
2591 self.top
2592 }
2593}
2594
2595impl LuaState {
2604 #[inline(always)]
2605 pub fn get_at(&self, idx: impl Into<StackIdxConv>) -> LuaValue {
2606 let i: StackIdx = idx.into().0;
2607 match self.stack.get(i.0 as usize) {
2608 Some(slot) => slot.val.clone(),
2609 None => LuaValue::Nil,
2610 }
2611 }
2612 #[inline(always)]
2613 pub fn set_at(&mut self, idx: impl Into<StackIdxConv>, v: LuaValue) {
2614 let i: StackIdx = idx.into().0;
2615 self.stack[i.0 as usize].val = v;
2616 }
2617
2618 pub fn clear_stack_range(&mut self, start: StackIdx, end: StackIdx) {
2624 if end.0 <= start.0 {
2625 return;
2626 }
2627 let end_u = end.0 as usize;
2628 if end_u > self.stack.len() {
2629 self.stack.resize_with(end_u, StackValue::default);
2630 }
2631 for i in start.0..end.0 {
2632 self.stack[i as usize].val = LuaValue::Nil;
2633 }
2634 }
2635 #[inline(always)]
2643 pub fn get_int_at(&self, idx: impl Into<StackIdxConv>) -> Option<i64> {
2644 let i: StackIdx = idx.into().0;
2645 match self.stack.get(i.0 as usize) {
2646 Some(slot) => match &slot.val {
2647 LuaValue::Int(v) => Some(*v),
2648 _ => None,
2649 },
2650 None => None,
2651 }
2652 }
2653 #[inline(always)]
2660 pub fn get_int_pair_at(
2661 &self,
2662 rb: impl Into<StackIdxConv>,
2663 rc: impl Into<StackIdxConv>,
2664 ) -> Option<(i64, i64)> {
2665 let rb: StackIdx = rb.into().0;
2666 let rc: StackIdx = rc.into().0;
2667 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2668 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib, ic)),
2669 _ => None,
2670 }
2671 }
2672 #[inline(always)]
2677 pub fn get_num_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2678 let i: StackIdx = idx.into().0;
2679 match self.stack.get(i.0 as usize) {
2680 Some(slot) => match &slot.val {
2681 LuaValue::Float(f) => Some(*f),
2682 LuaValue::Int(v) => Some(*v as f64),
2683 _ => None,
2684 },
2685 None => None,
2686 }
2687 }
2688 #[inline(always)]
2693 pub fn get_float_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
2694 let i: StackIdx = idx.into().0;
2695 match self.stack.get(i.0 as usize) {
2696 Some(slot) => match &slot.val {
2697 LuaValue::Float(f) => Some(*f),
2698 _ => None,
2699 },
2700 None => None,
2701 }
2702 }
2703 #[inline(always)]
2708 pub fn get_num_pair_at(
2709 &self,
2710 rb: impl Into<StackIdxConv>,
2711 rc: impl Into<StackIdxConv>,
2712 ) -> Option<(f64, f64)> {
2713 let rb: StackIdx = rb.into().0;
2714 let rc: StackIdx = rc.into().0;
2715 match (self.stack[rb.0 as usize].val, self.stack[rc.0 as usize].val) {
2716 (LuaValue::Float(nb), LuaValue::Float(nc)) => Some((nb, nc)),
2717 (LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib as f64, ic as f64)),
2718 (LuaValue::Int(ib), LuaValue::Float(nc)) => Some((ib as f64, nc)),
2719 (LuaValue::Float(nb), LuaValue::Int(ic)) => Some((nb, ic as f64)),
2720 _ => None,
2721 }
2722 }
2723 #[inline(always)]
2736 pub fn set_top(&mut self, idx: impl Into<StackIdxConv>) {
2737 let new_top: StackIdx = idx.into().0;
2738 let new_top_u = new_top.0 as usize;
2739 if new_top_u > self.stack.len() {
2740 self.stack.resize_with(new_top_u, StackValue::default);
2741 }
2742 self.top = new_top;
2743 }
2744 #[inline(always)]
2750 pub fn set_top_idx(&mut self, idx: impl Into<StackIdxConv>) {
2751 let new_top: StackIdx = idx.into().0;
2752 self.top = new_top;
2753 }
2754 #[inline(always)]
2757 pub fn dec_top(&mut self) {
2758 if self.top.0 > 0 {
2759 self.top = StackIdx(self.top.0 - 1);
2760 }
2761 }
2762 #[inline(always)]
2763 pub fn pop_n(&mut self, n: usize) {
2764 let cur = self.top.0 as usize;
2765 let new = cur.saturating_sub(n);
2766 self.top = StackIdx(new as u32);
2767 }
2768 #[inline(always)]
2771 pub fn peek_at(&mut self, idx: impl Into<StackIdxConv>) -> LuaValue {
2772 let i: StackIdx = idx.into().0;
2773 match self.stack.get(i.0 as usize) {
2774 Some(slot) => slot.val.clone(),
2775 None => LuaValue::Nil,
2776 }
2777 }
2778 #[inline(always)]
2782 pub fn peek_top(&mut self) -> LuaValue {
2783 if self.top.0 == 0 {
2784 return LuaValue::Nil;
2785 }
2786 self.stack[(self.top.0 - 1) as usize].val.clone()
2787 }
2788 pub fn peek_string_at_top(&mut self) -> GcRef<LuaString> {
2793 match self.peek_top() {
2794 LuaValue::Str(s) => s,
2795 _ => panic!("peek_string_at_top: top of stack is not a string"),
2796 }
2797 }
2798 pub fn stack_at(&mut self, idx: impl Into<StackIdxConv>) -> &mut LuaValue {
2801 let i: StackIdx = idx.into().0;
2802 &mut self.stack[i.0 as usize].val
2803 }
2804 pub fn stack_set_nil(&mut self, idx: impl Into<StackIdxConv>) {
2807 let i: StackIdx = idx.into().0;
2808 let slot = i.0 as usize;
2809 if slot < self.stack.len() {
2810 self.stack[slot].val = LuaValue::Nil;
2811 }
2812 }
2813 pub fn stack_resize(&mut self, size: usize) -> Result<(), LuaError> {
2821 self.stack.resize_with(size, StackValue::default);
2822 Ok(())
2823 }
2824 pub fn stack_available(&mut self) -> usize {
2825 (self.stack_last.0 as usize).saturating_sub(self.top.0 as usize)
2826 }
2827 pub fn check_stack(&mut self, n: i32) -> Result<(), LuaError> {
2828 let free = (self.stack_last.0 as i32) - (self.top.0 as i32);
2829 if free <= n {
2830 self.grow_stack(n, true)?;
2831 }
2832 Ok(())
2833 }
2834 pub fn grow_stack(&mut self, n: i32, raise_error: bool) -> Result<(), LuaError> {
2843 crate::do_::grow_stack(self, n, raise_error).map(|_| ())
2844 }
2845
2846 #[inline(always)]
2847 pub fn get_ci(&self, idx: CallInfoIdx) -> &CallInfo {
2848 &self.call_info[idx.as_usize()]
2849 }
2850 #[inline(always)]
2851 pub fn get_ci_mut(&mut self, idx: CallInfoIdx) -> &mut CallInfo {
2852 &mut self.call_info[idx.as_usize()]
2853 }
2854 #[inline(always)]
2855 pub fn current_call_info(&self) -> &CallInfo {
2856 &self.call_info[self.ci.as_usize()]
2857 }
2858 #[inline(always)]
2859 pub fn current_call_info_mut(&mut self) -> &mut CallInfo {
2860 let i = self.ci.as_usize();
2861 &mut self.call_info[i]
2862 }
2863 #[inline(always)]
2864 pub fn current_ci_idx(&self) -> CallInfoIdx {
2865 self.ci
2866 }
2867 pub fn call_stack_mut(&mut self) -> &mut Vec<CallInfo> {
2868 &mut self.call_info
2869 }
2870 #[inline(always)]
2871 pub fn next_ci(&mut self) -> Result<CallInfoIdx, LuaError> {
2872 match self.call_info[self.ci.as_usize()].next {
2873 Some(idx) => Ok(idx),
2874 None => Ok(extend_ci(self)),
2875 }
2876 }
2877 #[inline(always)]
2878 pub fn prev_ci(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
2879 self.call_info[idx.as_usize()].previous
2880 }
2881 pub fn get_prev_ci(&self, idx: CallInfoIdx) -> Option<&CallInfo> {
2882 self.call_info[idx.as_usize()]
2883 .previous
2884 .map(|p| &self.call_info[p.as_usize()])
2885 }
2886 #[inline(always)]
2887 pub fn is_base_ci(&self, idx: CallInfoIdx) -> bool {
2888 idx.as_usize() == 0
2889 }
2890 #[inline(always)]
2891 pub fn is_current_ci(&self, idx: CallInfoIdx) -> bool {
2892 idx == self.ci
2893 }
2894 pub fn ci_next_func(&self, idx: CallInfoIdx) -> StackIdx {
2895 let next = self.call_info[idx.as_usize()]
2896 .next
2897 .expect("ci_next_func: no next CallInfo");
2898 self.call_info[next.as_usize()].func
2899 }
2900 #[inline(always)]
2901 pub fn ci_top(&self, idx: CallInfoIdx) -> StackIdx {
2902 self.call_info[idx.as_usize()].top
2903 }
2904 #[inline(always)]
2905 pub fn ci_trap(&mut self, idx: CallInfoIdx) -> bool {
2906 if let CallInfoFrame::Lua { trap, .. } = self.call_info[idx.as_usize()].u {
2907 trap
2908 } else {
2909 false
2910 }
2911 }
2912 #[inline(always)]
2913 pub fn ci_savedpc(&self, idx: CallInfoIdx) -> u32 {
2914 self.call_info[idx.as_usize()].saved_pc()
2915 }
2916 #[inline(always)]
2917 pub fn set_ci_savedpc(&mut self, idx: CallInfoIdx, pc: u32) {
2918 self.call_info[idx.as_usize()].set_saved_pc(pc);
2919 }
2920 #[inline(always)]
2921 pub fn set_ci_previous(&mut self, idx: CallInfoIdx) {
2922 self.ci = self.call_info[idx.as_usize()]
2923 .previous
2924 .expect("set_ci_previous: returning frame has no previous CallInfo");
2925 }
2926 #[inline(always)]
2927 pub fn ci_previous(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> {
2928 self.call_info[idx.as_usize()].previous
2929 }
2930 #[inline(always)]
2931 pub fn ci_adjust_func(&mut self, idx: CallInfoIdx, delta: i32) {
2932 let ci = &mut self.call_info[idx.as_usize()];
2933 ci.func = StackIdx((ci.func.0 as i32 - delta) as u32);
2934 }
2935 #[inline(always)]
2936 pub fn ci_base(&self, idx: CallInfoIdx) -> StackIdx {
2937 self.call_info[idx.as_usize()].func + 1
2938 }
2939 #[inline(always)]
2940 pub fn ci_is_fresh(&self, idx: CallInfoIdx) -> bool {
2941 (self.call_info[idx.as_usize()].callstatus & CIST_FRESH) != 0
2942 }
2943 #[inline(always)]
2944 pub fn ci_lua_closure(
2945 &self,
2946 idx: CallInfoIdx,
2947 ) -> Option<GcRef<lua_types::closure::LuaLClosure>> {
2948 let func_idx = self.call_info[idx.as_usize()].func;
2949 match self.stack.get(func_idx.0 as usize).map(|slot| slot.val) {
2950 Some(LuaValue::Function(lua_types::closure::LuaClosure::Lua(cl))) => Some(cl),
2951 _ => None,
2952 }
2953 }
2954 #[inline(always)]
2955 pub fn ci_nextraargs(&self, idx: CallInfoIdx) -> i32 {
2956 self.call_info[idx.as_usize()].nextra_args()
2957 }
2958 #[inline(always)]
2959 pub fn ci_nres(&self, idx: CallInfoIdx) -> i32 {
2960 self.call_info[idx.as_usize()].u2.value
2961 }
2962 #[inline(always)]
2963 pub fn ci_nres_set(&mut self, idx: CallInfoIdx, n: i32) {
2964 self.call_info[idx.as_usize()].u2.value = n;
2965 }
2966 #[inline(always)]
2967 pub fn ci_nresults(&self, idx: CallInfoIdx) -> i32 {
2968 self.call_info[idx.as_usize()].nresults as i32
2969 }
2970 pub fn ci_prev_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
2971 let pc = self.call_info[idx.as_usize()].saved_pc();
2972 let cl = self
2973 .ci_lua_closure(idx)
2974 .expect("ci_prev_instruction: CallInfo does not hold a Lua closure");
2975 cl.proto.code[(pc - 1) as usize]
2976 }
2977 pub fn ci_prev2_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
2978 let pc = self.call_info[idx.as_usize()].saved_pc();
2979 let cl = self
2980 .ci_lua_closure(idx)
2981 .expect("ci_prev2_instruction: CallInfo does not hold a Lua closure");
2982 cl.proto.code[(pc - 2) as usize]
2983 }
2984 pub fn ci_skip_next_instruction(&mut self, idx: CallInfoIdx) {
2985 let pc = self.call_info[idx.as_usize()].saved_pc();
2986 self.call_info[idx.as_usize()].set_saved_pc(pc + 1);
2987 }
2988 pub fn ci_step_pc_back(&mut self, idx: CallInfoIdx) {
2989 let pc = self.call_info[idx.as_usize()].saved_pc();
2990 self.call_info[idx.as_usize()].set_saved_pc(pc - 1);
2991 }
2992 pub fn get_ci_pcrel(&mut self, idx: CallInfoIdx) -> u32 {
2993 self.call_info[idx.as_usize()].saved_pc().saturating_sub(1)
2994 }
2995 pub fn get_ci_u2_funcidx(&mut self, idx: CallInfoIdx) -> i32 {
2996 self.call_info[idx.as_usize()].u2.value
2997 }
2998 pub fn get_ci_u2_nres(&mut self, idx: CallInfoIdx) -> i32 {
2999 self.call_info[idx.as_usize()].u2.value
3000 }
3001 pub fn get_ci_u2_nyield(&mut self, idx: CallInfoIdx) -> i32 {
3002 self.call_info[idx.as_usize()].u2.value
3003 }
3004 pub fn get_ci_vararg_info(&mut self, idx: CallInfoIdx) -> (bool, i32, i32) {
3005 let nextraargs = self.call_info[idx.as_usize()].nextra_args();
3006 match self.ci_lua_closure(idx) {
3007 Some(cl) => (cl.proto.is_vararg, nextraargs, cl.proto.numparams as i32),
3008 None => (false, nextraargs, 0),
3009 }
3010 }
3011 pub fn get_ci_lua_proto_numparams(&mut self, idx: CallInfoIdx) -> u8 {
3012 self.ci_lua_closure(idx)
3013 .map(|cl| cl.proto.numparams)
3014 .unwrap_or(0)
3015 }
3016 pub fn set_ci_u2_nres(&mut self, idx: CallInfoIdx, n: i32) {
3017 self.call_info[idx.as_usize()].u2.value = n;
3018 }
3019 pub fn set_ci_u2_nyield(&mut self, idx: CallInfoIdx, n: i32) {
3020 self.call_info[idx.as_usize()].u2.value = n;
3021 }
3022 pub fn set_ci_transfer_info(&mut self, idx: CallInfoIdx, ftransfer: u16, ntransfer: u16) {
3023 let ci = &mut self.call_info[idx.as_usize()];
3024 ci.u2.ftransfer = ftransfer;
3025 ci.u2.ntransfer = ntransfer;
3026 }
3027 pub fn shrink_ci(&mut self) {
3028 shrink_ci(self)
3029 }
3030 pub fn check_c_stack(&mut self) -> Result<(), LuaError> {
3031 check_c_stack(self)
3032 }
3033
3034 pub fn status(&mut self) -> LuaStatus {
3035 LuaStatus::from_raw(self.status as i32)
3036 }
3037 pub fn errfunc(&mut self) -> isize {
3038 self.errfunc
3039 }
3040 pub fn old_pc(&mut self) -> u32 {
3041 self.oldpc
3042 }
3043 pub fn set_old_pc(&mut self, pc: u32) {
3044 self.oldpc = pc;
3045 }
3046 pub fn set_oldpc(&mut self, pc: u32) {
3047 self.oldpc = pc;
3048 }
3049 pub fn _hook_call_noargs(&mut self) {}
3050 pub fn hook(&self) -> Option<&Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>> {
3051 self.hook.as_ref()
3052 }
3053 pub fn has_hook(&mut self) -> bool {
3054 self.hook.is_some()
3055 }
3056 pub fn hook_count(&mut self) -> i32 {
3057 self.hookcount
3058 }
3059 pub fn set_hook_count(&mut self, n: i32) {
3060 self.hookcount = n;
3061 }
3062 pub fn hook_mask(&self) -> u8 {
3063 self.hookmask
3064 }
3065 pub fn set_hook_mask(&mut self, m: u8) {
3066 self.hookmask = m;
3067 }
3068 pub fn base_hook_count(&self) -> i32 {
3069 self.basehookcount
3070 }
3071 pub fn set_base_hook_count(&mut self, n: i32) {
3072 self.basehookcount = n;
3073 }
3074 pub fn set_hook(&mut self, h: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>) {
3075 self.hook = h;
3076 }
3077 pub fn call_hook_event(&mut self, event: i32, line: i32) -> Result<(), LuaError> {
3078 crate::do_::hook(self, event, line, 0, 0)
3079 }
3080
3081 pub fn registry_value(&self) -> LuaValue {
3082 self.global().l_registry.clone()
3083 }
3084 pub fn registry_get(&self, key: usize) -> LuaValue {
3085 let reg = self.global().l_registry.clone();
3086 match reg {
3087 LuaValue::Table(t) => t.get(&LuaValue::Int(key as i64)),
3088 _ => LuaValue::Nil,
3089 }
3090 }
3091
3092 pub fn new_string(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3093 self.intern_or_create_str(bytes)
3094 }
3095
3096 pub fn new_proto(&mut self) -> GcRef<LuaProto> {
3108 self.mark_gc_check_needed();
3109 GcRef::new(LuaProto::placeholder())
3110 }
3111
3112 pub fn new_lclosure(&mut self, proto: GcRef<LuaProto>, nupvals: usize) -> GcRef<LuaClosureLua> {
3114 self.mark_gc_check_needed();
3115 let mut upvals = Vec::with_capacity(nupvals);
3116 for _ in 0..nupvals {
3117 upvals.push(std::cell::Cell::new(self.new_upval_closed(LuaValue::Nil)));
3118 }
3119 let closure = GcRef::new(LuaClosureLua { proto, upvals });
3120 closure.account_buffer(closure.buffer_bytes() as isize);
3121 closure
3122 }
3123
3124 pub fn new_upval_closed(&mut self, v: LuaValue) -> GcRef<UpVal> {
3126 self.mark_gc_check_needed();
3127 GcRef::new(UpVal::closed(v))
3128 }
3129
3130 pub fn new_upval_open(&mut self, thread_id: usize, level: StackIdx) -> GcRef<UpVal> {
3132 self.mark_gc_check_needed();
3133 GcRef::new(UpVal::open(thread_id, level))
3134 }
3135 pub fn intern_or_create_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
3142 self.intern_str(bytes)
3143 }
3144 pub fn new_userdata(
3145 &mut self,
3146 _size: usize,
3147 _nuvalue: usize,
3148 ) -> Result<GcRef<LuaUserData>, LuaError> {
3149 Err(LuaError::runtime(format_args!(
3150 "new_userdata not implemented in this Phase-B build; use new_userdata_typed instead"
3151 )))
3152 }
3153 pub fn new_c_closure(&mut self, _f: LuaCFunction, _n: i32) -> Result<LuaClosure, LuaError> {
3154 Err(LuaError::runtime(format_args!("new_c_closure not implemented in this Phase-B build; use push_cclosure in lua_vm::api instead")))
3155 }
3156 pub fn push_closure(
3157 &mut self,
3158 proto_idx: usize,
3159 ci: CallInfoIdx,
3160 base: StackIdx,
3161 ra: StackIdx,
3162 ) -> Result<(), LuaError> {
3163 let parent_cl = self
3164 .ci_lua_closure(ci)
3165 .expect("push_closure: current frame is not a Lua closure");
3166 let child_proto = parent_cl.proto.p[proto_idx].clone();
3167 let nup = child_proto.upvalues.len();
3168 let mut upvals: Vec<std::cell::Cell<GcRef<UpVal>>> = Vec::with_capacity(nup);
3169 for i in 0..nup {
3170 let desc = &child_proto.upvalues[i];
3171 let uv = if desc.instack {
3172 let level = base + desc.idx as i32;
3173 crate::func::find_upval(self, level)
3174 } else {
3175 parent_cl.upval(desc.idx as usize)
3176 };
3177 upvals.push(std::cell::Cell::new(uv));
3178 }
3179 let cache_enabled = matches!(
3183 self.global().lua_version,
3184 lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
3185 );
3186 if cache_enabled {
3187 if let Some(cached) = child_proto.cache.borrow().as_ref() {
3188 if cached.upvals.len() == nup
3189 && (0..nup).all(|i| GcRef::ptr_eq(&cached.upvals[i].get(), &upvals[i].get()))
3190 {
3191 let reused = cached.clone();
3192 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(reused)));
3193 return Ok(());
3194 }
3195 }
3196 }
3197 self.mark_gc_check_needed();
3200 let new_cl = GcRef::new(LuaClosureLua {
3201 proto: child_proto.clone(),
3202 upvals,
3203 });
3204 new_cl.account_buffer(new_cl.buffer_bytes() as isize);
3205 if cache_enabled {
3206 *child_proto.cache.borrow_mut() = Some(new_cl.clone());
3207 }
3208 self.set_at(ra, LuaValue::Function(LuaClosure::Lua(new_cl)));
3209 Ok(())
3210 }
3211 pub fn new_tbc_upval(&mut self, idx: StackIdx) -> Result<(), LuaError> {
3212 crate::func::new_tbc_upval(self, idx)
3213 }
3214
3215 #[inline(always)]
3236 pub fn upvalue_get(&self, cl: &GcRef<LuaClosureLua>, n: usize) -> LuaValue {
3237 let uv = cl.upval(n);
3238 let (thread_id, idx) = match uv.try_open_payload() {
3239 Some(p) => p,
3240 None => return uv.closed_value(),
3241 };
3242 let current = self.cached_thread_id;
3243 let tid = thread_id as u64;
3244 if tid == current {
3245 return self.stack[idx.0 as usize].val;
3246 }
3247 self.upvalue_get_cross_thread(tid, idx)
3248 }
3249
3250 #[cold]
3251 #[inline(never)]
3252 fn upvalue_get_cross_thread(&self, tid: u64, idx: StackIdx) -> LuaValue {
3253 let entry_rc = {
3254 let g = self.global();
3255 g.threads.get(&tid).map(|e| e.state.clone())
3256 };
3257 if let Some(rc) = entry_rc {
3258 if let Ok(home_state) = rc.try_borrow() {
3259 return home_state.get_at(idx);
3260 }
3261 }
3262 let g = self.global();
3263 match g.cross_thread_upvals.get(&(tid, idx)) {
3264 Some(v) => *v,
3265 None => LuaValue::Nil,
3266 }
3267 }
3268 #[inline(always)]
3277 pub fn upvalue_set(
3278 &mut self,
3279 cl: &GcRef<LuaClosureLua>,
3280 n: usize,
3281 val: LuaValue,
3282 ) -> Result<(), LuaError> {
3283 let uv = cl.upval(n);
3284 match uv.try_open_payload() {
3285 Some((thread_id, idx)) => {
3286 let tid = thread_id as u64;
3287 let current = self.cached_thread_id;
3288 if tid == current {
3289 self.stack[idx.0 as usize].val = val;
3290 } else {
3291 self.upvalue_set_cross_thread(tid, idx, val)?;
3292 }
3293 }
3294 None => {
3295 uv.set_closed_value(val);
3296 }
3297 }
3298 if val.is_collectable() {
3299 self.gc_barrier_upval(&uv, &val);
3300 }
3301 Ok(())
3302 }
3303
3304 #[cold]
3305 #[inline(never)]
3306 fn upvalue_set_cross_thread(
3307 &mut self,
3308 tid: u64,
3309 idx: StackIdx,
3310 val: LuaValue,
3311 ) -> Result<(), LuaError> {
3312 let entry_rc = {
3313 let g = self.global();
3314 g.threads.get(&tid).map(|e| e.state.clone())
3315 };
3316 if let Some(rc) = entry_rc {
3317 if let Ok(mut home_state) = rc.try_borrow_mut() {
3318 home_state.set_at(idx, val);
3319 return Ok(());
3320 }
3321 }
3322 let mut g = self.global_mut();
3323 g.cross_thread_upvals.insert((tid, idx), val);
3324 Ok(())
3325 }
3326
3327 pub fn protected_call_raw(
3328 &mut self,
3329 func: StackIdx,
3330 nresults: i32,
3331 errfunc: StackIdx,
3332 ) -> Result<(), LuaError> {
3333 let ef = errfunc.0 as isize;
3334 let status = crate::do_::pcall(self, |s| s.call_no_yield(func, nresults), func, ef);
3335 match status {
3336 LuaStatus::Ok => Ok(()),
3337 LuaStatus::ErrSyntax => {
3338 let err_val = self.get_at(func);
3339 self.set_top(func);
3340 Err(LuaError::Syntax(err_val))
3341 }
3342 LuaStatus::Yield => {
3343 self.set_top(func);
3344 Err(LuaError::Yield)
3345 }
3346 _ => {
3347 let err_val = self.get_at(func);
3348 self.set_top(func);
3349 Err(LuaError::Runtime(err_val))
3350 }
3351 }
3352 }
3353 pub fn protected_parser(
3354 &mut self,
3355 z: crate::zio::ZIO,
3356 name: &[u8],
3357 mode: Option<&[u8]>,
3358 ) -> LuaStatus {
3359 crate::do_::protected_parser(self, z, name, mode)
3360 }
3361 pub fn do_call(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3362 crate::do_::call(self, func, nresults)
3363 }
3364 pub fn do_call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3365 crate::do_::callnoyield(self, func, nresults)
3366 }
3367 pub fn call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3368 crate::do_::callnoyield(self, func, nresults)
3369 }
3370 pub fn call_at(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
3371 crate::do_::call(self, func, nresults)
3372 }
3373 #[inline(always)]
3374 pub fn call_known_c_at(&mut self, func: StackIdx, nresults: i32) -> Result<bool, LuaError> {
3375 crate::do_::call_known_c(self, func, nresults)
3376 }
3377 #[inline(always)]
3378 pub fn precall(
3379 &mut self,
3380 func: StackIdx,
3381 nresults: i32,
3382 ) -> Result<Option<CallInfoIdx>, LuaError> {
3383 crate::do_::precall(self, func, nresults)
3384 }
3385 #[inline(always)]
3386 pub fn pretailcall(
3387 &mut self,
3388 ci: CallInfoIdx,
3389 func: StackIdx,
3390 narg1: i32,
3391 delta: i32,
3392 ) -> Result<i32, LuaError> {
3393 crate::do_::pretailcall(self, ci, func, narg1, delta)
3394 }
3395 #[inline(always)]
3396 pub fn poscall<N: TryInto<i32>>(&mut self, ci: CallInfoIdx, nres: N) -> Result<(), LuaError>
3397 where
3398 <N as TryInto<i32>>::Error: std::fmt::Debug,
3399 {
3400 let n = nres.try_into().expect("poscall: nres out of i32 range");
3401 crate::do_::poscall(self, ci, n)
3402 }
3403 pub fn adjust_results(&mut self, nresults: i32) {
3404 const LUA_MULTRET: i32 = -1;
3405 if nresults <= LUA_MULTRET {
3406 let ci_idx = self.ci.as_usize();
3407 if self.call_info[ci_idx].top.0 < self.top.0 {
3408 self.call_info[ci_idx].top = self.top;
3409 }
3410 }
3411 }
3412 pub fn adjust_varargs(
3413 &mut self,
3414 ci: CallInfoIdx,
3415 nfixparams: i32,
3416 cl: &GcRef<lua_types::closure::LuaLClosure>,
3417 ) -> Result<(), LuaError> {
3418 crate::tagmethods::adjust_varargs(self, nfixparams, ci, &cl.0.proto)
3419 }
3420 pub fn get_varargs(&mut self, ci: CallInfoIdx, ra: StackIdx, n: i32) -> Result<i32, LuaError> {
3421 crate::tagmethods::get_varargs(self, ci, ra, n)?;
3422 Ok(0)
3423 }
3424
3425 pub fn close_upvals(&mut self, level: StackIdx) -> Result<(), LuaError> {
3426 crate::func::close_upval(self, level);
3427 Ok(())
3428 }
3429 pub fn close_upvals_status(&mut self, level: StackIdx, _status: i32) -> Result<(), LuaError> {
3430 crate::func::close_upval(self, level);
3431 Ok(())
3432 }
3433 pub fn close_upvals_from_base(&mut self, ci: CallInfoIdx) -> Result<(), LuaError> {
3434 let base = self.ci_base(ci);
3435 crate::func::close_upval(self, base);
3436 Ok(())
3437 }
3438
3439 pub fn arith_op(
3440 &mut self,
3441 op: i32,
3442 p1: &LuaValue,
3443 p2: &LuaValue,
3444 ) -> Result<LuaValue, LuaError> {
3445 let arith_op = match op {
3446 0 => lua_types::arith::ArithOp::Add,
3447 1 => lua_types::arith::ArithOp::Sub,
3448 2 => lua_types::arith::ArithOp::Mul,
3449 3 => lua_types::arith::ArithOp::Mod,
3450 4 => lua_types::arith::ArithOp::Pow,
3451 5 => lua_types::arith::ArithOp::Div,
3452 6 => lua_types::arith::ArithOp::Idiv,
3453 7 => lua_types::arith::ArithOp::Band,
3454 8 => lua_types::arith::ArithOp::Bor,
3455 9 => lua_types::arith::ArithOp::Bxor,
3456 10 => lua_types::arith::ArithOp::Shl,
3457 11 => lua_types::arith::ArithOp::Shr,
3458 12 => lua_types::arith::ArithOp::Unm,
3459 13 => lua_types::arith::ArithOp::Bnot,
3460 _ => return Err(LuaError::runtime(format_args!("invalid arith op {}", op))),
3461 };
3462 let mut res = LuaValue::Nil;
3463 if crate::object::raw_arith(self, arith_op, p1, p2, &mut res)? {
3464 Ok(res)
3465 } else {
3466 Err(LuaError::arith_error(p1, p2, "perform arithmetic on"))
3467 }
3468 }
3469 pub fn concat(&mut self, n: i32) -> Result<(), LuaError> {
3470 crate::vm::concat(self, n)
3471 }
3472 pub fn less_than(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3473 crate::vm::less_than(self, l, r)
3474 }
3475 pub fn less_equal(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3476 crate::vm::less_equal(self, l, r)
3477 }
3478 pub fn equal_obj(&self, _ctx: Option<&LuaValue>, l: &LuaValue, r: &LuaValue) -> bool {
3479 crate::vm::equal_obj(None, l, r).unwrap_or(false)
3480 }
3481 pub fn equal_obj_with_tm(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
3482 crate::vm::equal_obj(Some(self), l, r)
3483 }
3484 pub fn obj_len(&mut self, v: &LuaValue) -> Result<LuaValue, LuaError> {
3485 match v {
3486 LuaValue::Table(_) => {
3487 let consult_len_tm =
3490 !matches!(self.global().lua_version, lua_types::LuaVersion::V51);
3491 let tm = if consult_len_tm {
3492 let mt = self.table_metatable(v);
3493 self.fast_tm_table(mt.as_ref(), TagMethod::Len)
3494 } else {
3495 LuaValue::Nil
3496 };
3497 if matches!(tm, LuaValue::Nil) {
3498 let n = self.table_length(v)?;
3499 return Ok(LuaValue::Int(n));
3500 }
3501 self.push(LuaValue::Nil);
3502 let slot = StackIdx(self.top.0 - 1);
3503 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3504 Ok(self.pop())
3505 }
3506 LuaValue::Str(s) => Ok(LuaValue::Int(s.len() as i64)),
3507 other => {
3508 let tm = crate::tagmethods::get_tm_by_obj(
3509 self,
3510 other,
3511 crate::tagmethods::TagMethod::Len,
3512 );
3513 if matches!(tm, LuaValue::Nil) {
3514 let mut msg = b"attempt to get length of a ".to_vec();
3515 msg.extend_from_slice(&self.obj_type_name(other));
3516 msg.extend_from_slice(b" value");
3517 return Err(crate::debug::prefixed_runtime_pub(self, msg));
3518 }
3519 self.push(LuaValue::Nil);
3520 let slot = StackIdx(self.top.0 - 1);
3521 crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
3522 Ok(self.pop())
3523 }
3524 }
3525 }
3526 pub fn obj_to_string(&mut self, idx: i32) -> Result<GcRef<LuaString>, LuaError> {
3527 let slot: StackIdx = if idx > 0 {
3528 let ci_func = self.current_call_info().func;
3529 ci_func + idx
3530 } else {
3531 debug_assert!(idx != 0, "invalid index");
3532 StackIdx((self.top_idx().0 as i32 + idx) as u32)
3533 };
3534 let val = self.get_at(slot);
3535 match val {
3536 LuaValue::Str(s) => Ok(s),
3537 LuaValue::Int(_) | LuaValue::Float(_) => {
3538 let s = crate::object::num_to_string(self, &val)?;
3539 self.set_at(slot, LuaValue::Str(s.clone()));
3540 Ok(s)
3541 }
3542 _ => Err(LuaError::type_error(&val, "convert to string")),
3543 }
3544 }
3545 pub fn coerce_to_string(&mut self, idx: StackIdx) -> Result<GcRef<LuaString>, LuaError> {
3546 let val = self.get_at(idx);
3547 match val {
3548 LuaValue::Str(s) => Ok(s),
3549 LuaValue::Int(_) | LuaValue::Float(_) => {
3550 let s = crate::object::num_to_string(self, &val)?;
3551 self.set_at(idx, LuaValue::Str(s.clone()));
3552 Ok(s)
3553 }
3554 _ => Err(LuaError::type_error(&val, "convert to string")),
3555 }
3556 }
3557 pub fn str_to_num(&mut self, s: &[u8]) -> Option<(LuaValue, usize)> {
3558 let mut out = LuaValue::Nil;
3559 let sz = crate::object::str2num(s, &mut out);
3560 if sz == 0 {
3561 None
3562 } else {
3563 Some((out, sz))
3564 }
3565 }
3566
3567 #[inline(always)]
3568 pub fn fast_get(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
3569 let LuaValue::Table(tbl) = t else {
3570 return Ok(None);
3571 };
3572 let v = tbl.get(k);
3573 if matches!(v, LuaValue::Nil) {
3574 Ok(None)
3575 } else {
3576 Ok(Some(v))
3577 }
3578 }
3579 #[inline(always)]
3580 pub fn fast_get_int(&mut self, t: &LuaValue, k: i64) -> Result<Option<LuaValue>, LuaError> {
3581 let LuaValue::Table(tbl) = t else {
3582 return Ok(None);
3583 };
3584 let v = tbl.get_int(k);
3585 if matches!(v, LuaValue::Nil) {
3586 Ok(None)
3587 } else {
3588 Ok(Some(v))
3589 }
3590 }
3591 #[inline(always)]
3592 pub fn fast_get_short_str(
3593 &mut self,
3594 t: &LuaValue,
3595 k: &LuaValue,
3596 ) -> Result<Option<LuaValue>, LuaError> {
3597 let LuaValue::Table(tbl) = t else {
3598 return Ok(None);
3599 };
3600 let LuaValue::Str(s) = k else {
3601 return Ok(None);
3602 };
3603 let v = tbl.get_short_str(s);
3604 if matches!(v, LuaValue::Nil) {
3605 Ok(None)
3606 } else {
3607 Ok(Some(v))
3608 }
3609 }
3610 #[inline(always)]
3611 pub fn fast_tm_table(&mut self, t: Option<&GcRef<LuaTable>>, tm: TagMethod) -> LuaValue {
3612 let Some(mt) = t else {
3613 return LuaValue::Nil;
3614 };
3615 debug_assert!((tm as u8) <= TagMethod::Eq as u8);
3616 let ename = self.global().tmname[tm as usize].clone();
3617 mt.get_short_str(&ename)
3618 }
3619 pub fn fast_tm_ud(&mut self, u: &GcRef<LuaUserData>, tm: TagMethod) -> LuaValue {
3620 let mt = u.metatable();
3622 self.fast_tm_table(mt.as_ref(), tm)
3623 }
3624
3625 pub fn table_get_with_tm(&mut self, t: &LuaValue, k: &LuaValue) -> Result<LuaValue, LuaError> {
3626 if let LuaValue::Table(tbl) = t {
3632 if !tbl.has_metatable() {
3633 return Ok(tbl.get(k));
3634 }
3635 }
3636 if let Some(v) = self.fast_get(t, k)? {
3637 return Ok(v);
3638 }
3639 let res = self.top_idx();
3640 self.push(LuaValue::Nil);
3641 crate::vm::finish_get(self, t.clone(), k.clone(), res, true, None, None)?;
3642 let value = self.get_at(res);
3643 self.pop();
3644 Ok(value)
3645 }
3646 #[inline]
3661 pub fn table_set_with_tm(
3662 &mut self,
3663 t: &LuaValue,
3664 k: LuaValue,
3665 v: LuaValue,
3666 ) -> Result<(), LuaError> {
3667 if let LuaValue::Table(tbl) = t {
3668 if !tbl.has_metatable() {
3669 self.gc_table_barrier_back(tbl, &v);
3670 return self.table_raw_set(t, k, v);
3671 }
3672 }
3673 if self.fast_get(t, &k)?.is_some() {
3674 self.gc_value_barrier_back(t, &v);
3675 return self.table_raw_set(t, k, v);
3676 }
3677 crate::vm::finish_set(self, t.clone(), k, v, true, None, None)
3678 }
3679 #[inline]
3680 pub fn table_raw_set(
3681 &mut self,
3682 t: &LuaValue,
3683 k: LuaValue,
3684 v: LuaValue,
3685 ) -> Result<(), LuaError> {
3686 let LuaValue::Table(tbl) = t else {
3687 return Err(LuaError::type_error(t, "index"));
3688 };
3689 let tbl = tbl.clone();
3690 tbl.raw_set(self, k, v)
3691 }
3692 #[inline]
3693 pub fn table_array_set(
3694 &mut self,
3695 t: &LuaValue,
3696 idx: usize,
3697 v: LuaValue,
3698 ) -> Result<(), LuaError> {
3699 let LuaValue::Table(tbl) = t else {
3700 return Err(LuaError::type_error(t, "index"));
3701 };
3702 let tbl = tbl.clone();
3703 tbl.raw_set_int(self, idx as i64 + 1, v)
3704 }
3705 pub fn table_ensure_array(&mut self, t: &LuaValue, n: usize) -> Result<(), LuaError> {
3706 let LuaValue::Table(tbl) = t else {
3707 return Err(LuaError::type_error(t, "index"));
3708 };
3709 if n > tbl.array_len() {
3710 tbl.resize(self, n, 0)?;
3711 }
3712 Ok(())
3713 }
3714 pub fn table_length(&mut self, t: &LuaValue) -> Result<i64, LuaError> {
3715 let LuaValue::Table(tbl) = t else {
3716 return Err(LuaError::type_error(t, "get length of"));
3717 };
3718 Ok(tbl.getn() as i64)
3719 }
3720 pub fn table_metatable(&mut self, v: &LuaValue) -> Option<GcRef<LuaTable>> {
3721 match v {
3722 LuaValue::Table(t) => t.metatable(),
3723 LuaValue::UserData(u) => u.metatable(),
3724 other => {
3725 let idx = other.base_type() as usize;
3726 self.global().mt[idx].clone()
3727 }
3728 }
3729 }
3730 pub fn table_resize(
3731 &mut self,
3732 t: &GcRef<LuaTable>,
3733 na: usize,
3734 nh: usize,
3735 ) -> Result<(), LuaError> {
3736 self.mark_gc_check_needed();
3737 t.resize(self, na, nh)
3738 }
3739 pub fn table_getn(&self, t: &GcRef<LuaTable>) -> i64 {
3740 let mut i: i64 = 1;
3748 loop {
3749 let v = t.get_int(i);
3750 if matches!(v, LuaValue::Nil) {
3751 return i - 1;
3752 }
3753 i += 1;
3754 }
3755 }
3756
3757 pub fn try_bin_tm(
3758 &mut self,
3759 p1: &LuaValue,
3760 p1_idx: Option<StackIdx>,
3761 p2: &LuaValue,
3762 p2_idx: Option<StackIdx>,
3763 res: StackIdx,
3764 tm: lua_types::tagmethod::TagMethod,
3765 ) -> Result<(), LuaError> {
3766 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3767 crate::tagmethods::try_bin_tm(self, p1, p1_idx, p2, p2_idx, res, event)
3768 }
3769 pub fn try_bin_i_tm(
3770 &mut self,
3771 p1: &LuaValue,
3772 p1_idx: Option<StackIdx>,
3773 imm: i64,
3774 flip: bool,
3775 res: StackIdx,
3776 tm: lua_types::tagmethod::TagMethod,
3777 ) -> Result<(), LuaError> {
3778 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3779 crate::tagmethods::try_bini_tm(self, p1, p1_idx, imm, flip, res, event)
3780 }
3781 pub fn try_bin_assoc_tm(
3782 &mut self,
3783 p1: &LuaValue,
3784 p1_idx: Option<StackIdx>,
3785 p2: &LuaValue,
3786 p2_idx: Option<StackIdx>,
3787 flip: bool,
3788 res: StackIdx,
3789 tm: lua_types::tagmethod::TagMethod,
3790 ) -> Result<(), LuaError> {
3791 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3792 crate::tagmethods::try_bin_assoc_tm(self, p1, p1_idx, p2, p2_idx, flip, res, event)
3793 }
3794 pub fn try_concat_tm(&mut self, _p1: &LuaValue, _p2: &LuaValue) -> Result<(), LuaError> {
3795 crate::tagmethods::try_concat_tm(self)
3796 }
3797 pub fn call_tm(
3798 &mut self,
3799 f: LuaValue,
3800 p1: &LuaValue,
3801 p2: &LuaValue,
3802 p3: &LuaValue,
3803 ) -> Result<(), LuaError> {
3804 crate::tagmethods::call_tm(self, f, p1.clone(), p2.clone(), p3.clone())
3805 }
3806 pub fn call_tm_res(
3807 &mut self,
3808 f: LuaValue,
3809 p1: &LuaValue,
3810 p2: &LuaValue,
3811 res: StackIdx,
3812 ) -> Result<(), LuaError> {
3813 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)
3814 }
3815 pub fn call_tm_res_bool(
3816 &mut self,
3817 f: LuaValue,
3818 p1: &LuaValue,
3819 p2: &LuaValue,
3820 ) -> Result<bool, LuaError> {
3821 let res = self.top_idx();
3822 self.push(LuaValue::Nil);
3823 crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)?;
3824 let result = self.get_at(res).clone();
3825 self.pop();
3826 Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)))
3827 }
3828 pub fn call_order_tm(
3829 &mut self,
3830 p1: &LuaValue,
3831 p2: &LuaValue,
3832 tm: lua_types::tagmethod::TagMethod,
3833 ) -> Result<bool, LuaError> {
3834 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3835 crate::tagmethods::call_order_tm(self, p1, p2, event)
3836 }
3837 pub fn call_order_i_tm(
3838 &mut self,
3839 p1: &LuaValue,
3840 v2: i64,
3841 flip: bool,
3842 isfloat: bool,
3843 tm: lua_types::tagmethod::TagMethod,
3844 ) -> Result<bool, LuaError> {
3845 let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
3846 crate::tagmethods::call_orderi_tm(self, p1, v2 as i32, flip, isfloat, event)
3847 }
3848
3849 #[inline(always)]
3850 pub fn proto_code(
3851 &self,
3852 cl: &GcRef<lua_types::closure::LuaLClosure>,
3853 pc: u32,
3854 ) -> lua_types::opcode::Instruction {
3855 cl.proto.code[pc as usize]
3856 }
3857 #[inline(always)]
3858 pub fn proto_const(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> LuaValue {
3859 cl.proto.k[idx].clone()
3860 }
3861 #[inline(always)]
3867 pub fn proto_const_int(
3868 &self,
3869 cl: &GcRef<lua_types::closure::LuaLClosure>,
3870 idx: usize,
3871 ) -> Option<i64> {
3872 match &cl.proto.k[idx] {
3873 LuaValue::Int(v) => Some(*v),
3874 _ => None,
3875 }
3876 }
3877 #[inline(always)]
3881 pub fn proto_const_num(
3882 &self,
3883 cl: &GcRef<lua_types::closure::LuaLClosure>,
3884 idx: usize,
3885 ) -> Option<f64> {
3886 match &cl.proto.k[idx] {
3887 LuaValue::Float(f) => Some(*f),
3888 LuaValue::Int(v) => Some(*v as f64),
3889 _ => None,
3890 }
3891 }
3892 pub fn get_proto_instr(&self, ci: CallInfoIdx, pc: u32) -> lua_types::opcode::Instruction {
3893 let cl = self
3894 .ci_lua_closure(ci)
3895 .expect("get_proto_instr: CallInfo does not hold a Lua closure");
3896 cl.proto.code[pc as usize]
3897 }
3898 pub fn trace_call(&mut self, _idx: CallInfoIdx) -> Result<bool, LuaError> {
3903 Ok(crate::debug::trace_call(self)? != 0)
3904 }
3905 pub fn trace_exec(&mut self, _idx: CallInfoIdx, pc: u32) -> Result<bool, LuaError> {
3908 Ok(crate::debug::trace_exec(self, pc)? != 0)
3909 }
3910 pub fn hook_call(&mut self, idx: CallInfoIdx) -> Result<(), LuaError> {
3911 crate::do_::hookcall(self, idx)
3912 }
3913 #[inline(always)]
3914 fn gc_step_flags(&self) -> Option<(bool, bool)> {
3915 let g = self.global();
3916 if !g.is_gc_running() {
3917 return None;
3918 }
3919 let should_collect = g.heap.would_collect();
3920 let has_finalizers = g.finalizers.has_to_be_finalized();
3921 if should_collect || has_finalizers {
3922 Some((should_collect, has_finalizers))
3923 } else {
3924 None
3925 }
3926 }
3927
3928 #[inline(always)]
3929 fn should_check_gc(&mut self) -> bool {
3930 if self.gc_check_needed {
3931 return true;
3932 }
3933 if self.global().finalizers.has_to_be_finalized() {
3934 self.gc_check_needed = true;
3935 return true;
3936 }
3937 false
3938 }
3939
3940 #[inline(always)]
3941 pub(crate) fn mark_gc_check_needed(&mut self) {
3942 self.gc_check_needed = true;
3943 }
3944
3945 pub fn gc_trace_bound(&self) -> usize {
3959 (self.top.0 as usize).min(self.stack.len())
3960 }
3961
3962 pub fn clear_dead_stack_tail(&mut self) {
3969 let bound = self.gc_trace_bound();
3970 for slot in &mut self.stack[bound..] {
3971 slot.val = LuaValue::Nil;
3972 }
3973 }
3974
3975 pub fn gc_clear_dead_stack_tails(&mut self) {
3982 self.clear_dead_stack_tail();
3983 let global = self.global.clone();
3984 let g = global.borrow();
3985 for entry in g.threads.values() {
3986 if let Ok(mut t) = entry.state.try_borrow_mut() {
3987 t.clear_dead_stack_tail();
3988 }
3989 }
3990 }
3991
3992 pub fn gc_pre_collect_clear(&mut self) {
3996 if self.global().heap.would_collect() {
3997 self.gc_clear_dead_stack_tails();
3998 }
3999 }
4000
4001 #[inline(always)]
4002 pub fn gc_check_step(&mut self) {
4003 if !self.allowhook {
4004 return;
4005 }
4006 if !self.should_check_gc() {
4007 return;
4008 }
4009 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4010 self.gc_check_needed = false;
4011 return;
4012 };
4013 if should_collect || has_finalizers {
4014 if should_collect {
4015 self.gc_clear_dead_stack_tails();
4016 self.gc().check_step();
4017 }
4018 crate::api::run_pending_finalizers(self);
4019 self.gc_check_needed = true;
4020 }
4021 let should_keep_checking = {
4022 let g = self.global();
4023 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4024 };
4025 self.gc_check_needed = should_keep_checking;
4026 }
4027 #[inline(always)]
4028 pub fn gc_cond_step(&mut self) {
4029 if !self.allowhook {
4030 return;
4031 }
4032 if !self.should_check_gc() {
4033 return;
4034 }
4035 let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
4036 self.gc_check_needed = false;
4037 return;
4038 };
4039 if should_collect || has_finalizers {
4040 if should_collect {
4041 self.gc_clear_dead_stack_tails();
4042 self.gc().check_step();
4043 }
4044 crate::api::run_pending_finalizers(self);
4045 self.gc_check_needed = true;
4046 }
4047 let should_keep_checking = {
4048 let g = self.global();
4049 g.heap.would_collect() || g.finalizers.has_to_be_finalized()
4050 };
4051 self.gc_check_needed = should_keep_checking;
4052 }
4053 pub fn gc_barrier_back(&mut self, t: &dyn std::any::Any, v: &LuaValue) {
4054 self.gc().barrier_back(t, v);
4055 }
4056 #[inline(always)]
4057 pub fn gc_value_barrier_back(&mut self, t: &LuaValue, v: &LuaValue) {
4058 if !v.is_collectable() {
4059 return;
4060 }
4061 if let LuaValue::Table(tbl) = t {
4062 self.gc_table_barrier_back(tbl, v);
4063 } else {
4064 self.gc_barrier_back(t, v);
4065 }
4066 }
4067 #[inline(always)]
4068 pub fn gc_table_barrier_back(&mut self, t: &GcRef<LuaTable>, v: &LuaValue) {
4069 if !v.is_collectable() {
4070 return;
4071 }
4072 self.gc().table_barrier_back(t, v);
4073 }
4074 pub fn gc_barrier_upval(&mut self, uv: &GcRef<UpVal>, v: &LuaValue) {
4075 self.gc().barrier(uv, v);
4076 }
4077 pub fn is_main_thread(&mut self) -> bool {
4083 let g = self.global();
4084 g.current_thread_id == g.main_thread_id
4085 }
4086 pub fn obj_type_name<'v>(&self, v: &'v LuaValue) -> std::borrow::Cow<'static, [u8]> {
4087 match v {
4088 LuaValue::LightUserData(_) => std::borrow::Cow::Borrowed(b"light userdata"),
4089 LuaValue::Table(t) => {
4090 if let Some(mt) = t.metatable() {
4091 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4092 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4093 }
4094 }
4095 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4096 }
4097 LuaValue::UserData(u) => {
4098 if let Some(mt) = u.metatable() {
4099 if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
4100 return std::borrow::Cow::Owned(s.as_bytes().to_vec());
4101 }
4102 }
4103 std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
4104 }
4105 _ => std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type())),
4106 }
4107 }
4108
4109 pub fn full_type_name(&mut self, v: &LuaValue) -> Result<Vec<u8>, LuaError> {
4110 crate::tagmethods::obj_type_name(self, v)
4111 }
4112 pub fn emit_warning(&mut self, _msg: &[u8], _to_cont: bool) {
4113 warning(self, _msg, _to_cont)
4114 }
4115}
4116
4117pub struct GcHandle<'a> {
4123 _state: &'a mut LuaState,
4124}
4125
4126struct CollectRoots<'a> {
4133 global: &'a GlobalState,
4134 thread: &'a LuaState,
4135}
4136
4137#[derive(Clone, Copy)]
4138enum HeapCollectMode {
4139 Full,
4140 Step,
4141 Minor,
4142}
4143
4144impl<'a> lua_gc::Trace for CollectRoots<'a> {
4145 fn trace(&self, m: &mut lua_gc::Marker) {
4146 self.global.trace(m);
4147 self.thread.trace(m);
4148 }
4149}
4150
4151#[derive(Clone, Copy)]
4152enum BarrierKind {
4153 Forward,
4154 Backward,
4155}
4156
4157fn barrier_lua_value<P>(
4158 heap: &lua_gc::Heap,
4159 parent: GcRef<P>,
4160 child: &LuaValue,
4161 generational: bool,
4162 kind: BarrierKind,
4163) where
4164 P: lua_gc::Trace + 'static,
4165{
4166 if !child.is_collectable() {
4167 return;
4168 }
4169 if generational && matches!(kind, BarrierKind::Backward) {
4170 heap.generational_backward_barrier(parent.0);
4171 }
4172 match child {
4173 LuaValue::Str(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4174 LuaValue::Table(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4175 LuaValue::Function(LuaClosure::Lua(c)) => {
4176 barrier_gc_child(heap, parent, *c, generational, kind)
4177 }
4178 LuaValue::Function(LuaClosure::C(c)) => {
4179 barrier_gc_child(heap, parent, *c, generational, kind)
4180 }
4181 LuaValue::UserData(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4182 LuaValue::Thread(c) => barrier_gc_child(heap, parent, *c, generational, kind),
4183 LuaValue::Nil
4184 | LuaValue::Bool(_)
4185 | LuaValue::Int(_)
4186 | LuaValue::Float(_)
4187 | LuaValue::LightUserData(_)
4188 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4189 }
4190}
4191
4192fn barrier_gc_child<P, C>(
4193 heap: &lua_gc::Heap,
4194 parent: GcRef<P>,
4195 child: GcRef<C>,
4196 generational: bool,
4197 kind: BarrierKind,
4198) where
4199 P: lua_gc::Trace + 'static,
4200 C: lua_gc::Trace + 'static,
4201{
4202 if generational && matches!(kind, BarrierKind::Forward) {
4203 heap.generational_forward_barrier(parent.0, child.0);
4204 } else if matches!(kind, BarrierKind::Backward) {
4205 heap.barrier_back(parent.0, child.0);
4206 } else {
4207 heap.barrier(parent.0, child.0);
4208 }
4209}
4210
4211fn barrier_child_any<P>(
4212 heap: &lua_gc::Heap,
4213 parent: GcRef<P>,
4214 child: &dyn std::any::Any,
4215 generational: bool,
4216 kind: BarrierKind,
4217) where
4218 P: lua_gc::Trace + 'static,
4219{
4220 if let Some(v) = child.downcast_ref::<LuaValue>() {
4221 barrier_lua_value(heap, parent, v, generational, kind);
4222 } else if let Some(c) = child.downcast_ref::<GcRef<LuaString>>() {
4223 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4224 } else if let Some(c) = child.downcast_ref::<GcRef<LuaTable>>() {
4225 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4226 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureLua>>() {
4227 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4228 } else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureC>>() {
4229 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4230 } else if let Some(c) = child.downcast_ref::<GcRef<LuaUserData>>() {
4231 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4232 } else if let Some(c) = child.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4233 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4234 } else if let Some(c) = child.downcast_ref::<GcRef<LuaProto>>() {
4235 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4236 } else if let Some(c) = child.downcast_ref::<GcRef<UpVal>>() {
4237 barrier_gc_child(heap, parent, c.clone(), generational, kind);
4238 }
4239}
4240
4241fn barrier_any(
4242 heap: &lua_gc::Heap,
4243 parent: &dyn std::any::Any,
4244 child: &dyn std::any::Any,
4245 generational: bool,
4246 kind: BarrierKind,
4247) {
4248 if let Some(v) = parent.downcast_ref::<LuaValue>() {
4249 match v {
4250 LuaValue::Str(p) => barrier_child_any(heap, *p, child, generational, kind),
4251 LuaValue::Table(p) => barrier_child_any(heap, *p, child, generational, kind),
4252 LuaValue::Function(LuaClosure::Lua(p)) => {
4253 barrier_child_any(heap, *p, child, generational, kind)
4254 }
4255 LuaValue::Function(LuaClosure::C(p)) => {
4256 barrier_child_any(heap, *p, child, generational, kind)
4257 }
4258 LuaValue::UserData(p) => barrier_child_any(heap, *p, child, generational, kind),
4259 LuaValue::Thread(p) => barrier_child_any(heap, *p, child, generational, kind),
4260 LuaValue::Nil
4261 | LuaValue::Bool(_)
4262 | LuaValue::Int(_)
4263 | LuaValue::Float(_)
4264 | LuaValue::LightUserData(_)
4265 | LuaValue::Function(LuaClosure::LightC(_)) => {}
4266 }
4267 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaString>>() {
4268 barrier_child_any(heap, p.clone(), child, generational, kind);
4269 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaTable>>() {
4270 barrier_child_any(heap, p.clone(), child, generational, kind);
4271 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureLua>>() {
4272 barrier_child_any(heap, p.clone(), child, generational, kind);
4273 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureC>>() {
4274 barrier_child_any(heap, p.clone(), child, generational, kind);
4275 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaUserData>>() {
4276 barrier_child_any(heap, p.clone(), child, generational, kind);
4277 } else if let Some(p) = parent.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
4278 barrier_child_any(heap, p.clone(), child, generational, kind);
4279 } else if let Some(p) = parent.downcast_ref::<GcRef<LuaProto>>() {
4280 barrier_child_any(heap, p.clone(), child, generational, kind);
4281 } else if let Some(p) = parent.downcast_ref::<GcRef<UpVal>>() {
4282 barrier_child_any(heap, p.clone(), child, generational, kind);
4283 }
4284}
4285
4286fn trace_reachable_threads(
4298 global: &GlobalState,
4299 _current_thread_id: u64,
4300 marker: &mut lua_gc::Marker,
4301) {
4302 use lua_gc::Trace;
4303
4304 #[cfg(debug_assertions)]
4305 let mut uncovered_borrowed: Vec<u64> = Vec::new();
4306
4307 loop {
4308 let visited_before = marker.visited_count();
4309 for (id, entry) in global.threads.iter() {
4310 if thread_entry_marked_alive(marker, *id, entry) {
4311 match entry.state.try_borrow() {
4312 Ok(thread) => thread.trace(marker),
4313 Err(_) => {
4314 #[cfg(debug_assertions)]
4315 if *id != _current_thread_id && !uncovered_borrowed.contains(id) {
4316 uncovered_borrowed.push(*id);
4317 }
4318 }
4319 }
4320 }
4321 }
4322 marker.drain_gray_queue();
4323 if marker.visited_count() == visited_before {
4324 break;
4325 }
4326 }
4327
4328 #[cfg(debug_assertions)]
4329 debug_assert!(
4330 uncovered_borrowed.len() <= global.suspended_parent_stacks.len(),
4331 "GC root loss: {} marked-alive coroutine(s) (ids {:?}) were mutably \
4332 borrowed at collect time with only {} parent snapshot(s) covering \
4333 them — their stacks were NOT traced this cycle, so anything only \
4334 reachable from them will be swept (issue #140 bug-A class). A borrow \
4335 of a coroutine's state must not be held across an allocation \
4336 checkpoint without pushing a parent GC snapshot.",
4337 uncovered_borrowed.len(),
4338 uncovered_borrowed,
4339 global.suspended_parent_stacks.len()
4340 );
4341}
4342
4343fn thread_entry_marked_alive(
4344 marker: &lua_gc::Marker,
4345 id: u64,
4346 entry: &ThreadRegistryEntry,
4347) -> bool {
4348 marker.is_marked_or_old(entry.value.0) && entry.value.id == id
4349}
4350
4351fn lua_value_marked_or_old(marker: &lua_gc::Marker, value: &LuaValue) -> bool {
4352 match value {
4353 LuaValue::Str(v) => marker.is_marked_or_old(v.0),
4354 LuaValue::Table(v) => marker.is_marked_or_old(v.0),
4355 LuaValue::Function(LuaClosure::Lua(v)) => marker.is_marked_or_old(v.0),
4356 LuaValue::Function(LuaClosure::C(v)) => marker.is_marked_or_old(v.0),
4357 LuaValue::UserData(v) => marker.is_marked_or_old(v.0),
4358 LuaValue::Thread(v) => marker.is_marked_or_old(v.0),
4359 LuaValue::Nil
4360 | LuaValue::Bool(_)
4361 | LuaValue::Int(_)
4362 | LuaValue::Float(_)
4363 | LuaValue::LightUserData(_)
4364 | LuaValue::Function(LuaClosure::LightC(_)) => true,
4365 }
4366}
4367
4368fn lua_value_identity(value: &LuaValue) -> Option<usize> {
4369 match value {
4370 LuaValue::Str(v) => Some(v.identity()),
4371 LuaValue::Table(v) => Some(v.identity()),
4372 LuaValue::Function(LuaClosure::Lua(v)) => Some(v.identity()),
4373 LuaValue::Function(LuaClosure::C(v)) => Some(v.identity()),
4374 LuaValue::UserData(v) => Some(v.identity()),
4375 LuaValue::Thread(v) => Some(v.identity()),
4376 LuaValue::Nil
4377 | LuaValue::Bool(_)
4378 | LuaValue::Int(_)
4379 | LuaValue::Float(_)
4380 | LuaValue::LightUserData(_)
4381 | LuaValue::Function(LuaClosure::LightC(_)) => None,
4382 }
4383}
4384
4385fn finalizer_marked_or_old(marker: &lua_gc::Marker, object: &FinalizerObject) -> bool {
4386 match object {
4387 FinalizerObject::Table(t) => marker.is_marked_or_old(t.0),
4388 FinalizerObject::UserData(u) => marker.is_marked_or_old(u.0),
4389 }
4390}
4391
4392fn weak_snapshot_tables<'a>(
4393 snapshot: &'a lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>>,
4394) -> impl Iterator<Item = &'a GcRef<LuaTable>> {
4395 snapshot
4396 .weak_values
4397 .iter()
4398 .chain(snapshot.ephemeron.iter())
4399 .chain(snapshot.all_weak.iter())
4400}
4401
4402fn close_open_upvalues_for_unreachable_threads(global: &GlobalState, marker: &mut lua_gc::Marker) {
4403 use lua_gc::Trace;
4404
4405 let mut closed_values = Vec::<LuaValue>::new();
4406 for (id, entry) in global.threads.iter() {
4407 if entry.value.id != *id {
4408 continue;
4409 }
4410 if thread_entry_marked_alive(marker, *id, entry) {
4411 continue;
4412 }
4413 let Ok(thread) = entry.state.try_borrow() else {
4414 continue;
4415 };
4416 for uv in thread.openupval.iter() {
4417 if !marker.is_visited(uv.identity()) {
4418 continue;
4419 }
4420 let Some((thread_id, idx)) = uv.try_open_payload() else {
4421 continue;
4422 };
4423 if thread_id as u64 != *id {
4424 continue;
4425 }
4426 let value = thread.get_at(idx);
4427 uv.close_with(value.clone());
4428 closed_values.push(value);
4429 }
4430 }
4431 for value in closed_values {
4432 value.trace(marker);
4433 }
4434 marker.drain_gray_queue();
4435}
4436
4437fn record_dead_interned_strings(
4444 global: &GlobalState,
4445 marker: &lua_gc::Marker,
4446 dead_pairs: &std::cell::RefCell<Vec<(u32, usize)>>,
4447) {
4448 let mut dead = dead_pairs.borrow_mut();
4449 for s in global.interned_lt.iter() {
4450 let id = s.identity();
4451 if !marker.is_visited(id) {
4452 dead.push((s.hash(), id));
4453 }
4454 }
4455}
4456
4457fn remove_dead_interned_strings(global: &mut GlobalState, dead_pairs: Vec<(u32, usize)>) {
4459 for (hash, id) in dead_pairs {
4460 global.interned_lt.remove(hash, id);
4461 }
4462}
4463
4464impl<'a> GcHandle<'a> {
4465 pub fn check_step(&self) {
4472 if !self._state.global().is_gc_running() {
4473 return;
4474 }
4475 if self._state.global().is_gen_mode() {
4476 let should_collect = {
4477 let g = self._state.global();
4478 g.heap.would_collect() || g.gc_debt() > 0
4479 };
4480 if should_collect {
4481 self.generational_step();
4482 }
4483 } else {
4484 self.collect_via_heap(false);
4485 }
4486 }
4487
4488 pub fn full_collect(&self) {
4490 if self._state.global().is_gen_mode() {
4491 self.fullgen();
4492 } else {
4493 self.collect_via_heap(true);
4494 }
4495 }
4496
4497 fn negative_debt(bytes: usize) -> isize {
4498 -(bytes.min(isize::MAX as usize) as isize)
4499 }
4500
4501 fn set_minor_debt(&self) {
4502 let mut g = self._state.global_mut();
4503 let total = g.total_bytes();
4504 let growth = (total / 100).saturating_mul(g.genminormul as usize);
4505 g.heap
4506 .set_threshold_bytes(total.saturating_add(growth.max(1)));
4507 set_debt(&mut *g, Self::negative_debt(growth));
4508 }
4509
4510 fn set_pause_debt(&self) {
4511 let mut g = self._state.global_mut();
4512 let total = g.total_bytes();
4513 let pause = g.gc_pause_param().max(0) as usize;
4514 let threshold = g.gc_estimate.max(1).saturating_mul(pause) / 100;
4515 let debt = if threshold > total {
4516 Self::negative_debt(threshold - total)
4517 } else {
4518 0
4519 };
4520 let heap_threshold = if threshold > total {
4521 threshold
4522 } else {
4523 total.saturating_add(1)
4524 };
4525 g.heap.set_threshold_bytes(heap_threshold);
4526 set_debt(&mut *g, debt);
4527 }
4528
4529 fn enter_incremental_mode(&self) {
4530 let mut g = self._state.global_mut();
4531 g.heap.reset_all_ages();
4532 g.finalizers.reset_generation_boundaries();
4533 g.gckind = GcKind::Incremental as u8;
4534 g.lastatomic = 0;
4535 }
4536
4537 fn enter_generational_mode(&self) -> usize {
4538 self.collect_via_heap_mode(HeapCollectMode::Full);
4539 let numobjs = {
4540 let mut g = self._state.global_mut();
4541 g.heap.promote_all_to_old();
4542 g.finalizers.promote_all_pending_to_old();
4543 g.heap.allgc_count()
4544 };
4545 let total = self._state.global().total_bytes();
4546 {
4547 let mut g = self._state.global_mut();
4548 g.gckind = GcKind::Generational as u8;
4549 g.lastatomic = 0;
4550 g.gc_estimate = total;
4551 }
4552 self.set_minor_debt();
4553 numobjs
4554 }
4555
4556 fn fullgen(&self) -> usize {
4557 self.enter_incremental_mode();
4558 self.enter_generational_mode()
4559 }
4560
4561 fn stepgenfull(&self, lastatomic: usize) {
4562 if self._state.global().gckind == GcKind::Generational as u8 {
4563 self.enter_incremental_mode();
4564 }
4565 self.collect_via_heap_mode(HeapCollectMode::Full);
4566 let newatomic = self._state.global().heap.allgc_count().max(1);
4567 if newatomic < lastatomic.saturating_add(lastatomic >> 3) {
4568 {
4569 let mut g = self._state.global_mut();
4570 g.heap.promote_all_to_old();
4571 g.finalizers.promote_all_pending_to_old();
4572 }
4573 let total = self._state.global().total_bytes();
4574 {
4575 let mut g = self._state.global_mut();
4576 g.gckind = GcKind::Generational as u8;
4577 g.lastatomic = 0;
4578 g.gc_estimate = total;
4579 }
4580 self.set_minor_debt();
4581 } else {
4582 {
4583 let mut g = self._state.global_mut();
4584 g.heap.reset_all_ages();
4585 g.finalizers.reset_generation_boundaries();
4586 }
4587 let total = self._state.global().total_bytes();
4588 {
4589 let mut g = self._state.global_mut();
4590 g.gckind = GcKind::Incremental as u8;
4591 g.lastatomic = newatomic;
4592 g.gc_estimate = total;
4593 }
4594 self.set_pause_debt();
4595 }
4596 }
4597
4598 fn collect_via_heap(&self, force: bool) {
4607 self.collect_via_heap_mode(if force {
4608 HeapCollectMode::Full
4609 } else {
4610 HeapCollectMode::Step
4611 });
4612 }
4613
4614 fn collect_via_heap_mode(&self, mode: HeapCollectMode) {
4615 use lua_gc::Trace;
4616 let state_ref: &LuaState = &*self._state;
4617
4618 if matches!(mode, HeapCollectMode::Step) {
4624 let g = state_ref.global.borrow();
4625 if !g.heap.would_collect() {
4626 return;
4627 }
4628 }
4629
4630 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
4634 let mut g = state_ref.global.borrow_mut();
4635 g.weak_tables_registry.live_snapshot_by_kind()
4636 };
4637
4638 let weak_table_capacity = weak_tables_snapshot.len();
4643 let (pending_snapshot, thread_capacity, _interned_capacity): (
4644 Vec<FinalizerObject>,
4645 usize,
4646 usize,
4647 ) = {
4648 let g = state_ref.global.borrow();
4649 let pending = match mode {
4650 HeapCollectMode::Minor => g.finalizers.pending_minor_snapshot(),
4651 HeapCollectMode::Full | HeapCollectMode::Step => g.finalizers.pending_snapshot(),
4652 };
4653 (pending, g.threads.len(), g.interned_lt.len())
4654 };
4655 let finalizer_capacity = pending_snapshot.len();
4656
4657 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4658 std::cell::RefCell::new(std::collections::HashSet::new());
4659 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
4660 std::cell::RefCell::new(Vec::new());
4661 let finalizing_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4662 std::cell::RefCell::new(std::collections::HashSet::new());
4663 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
4664 std::cell::RefCell::new(std::collections::HashSet::new());
4665 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
4666 let collect_ran = std::cell::Cell::new(false);
4667
4668 {
4669 let global = state_ref.global.borrow();
4670 global.heap.unpause();
4671 let roots = CollectRoots {
4672 global: &*global,
4673 thread: state_ref,
4674 };
4675 let hook = |marker: &mut lua_gc::Marker| {
4676 collect_ran.set(true);
4677 alive_ids.borrow_mut().reserve(weak_table_capacity);
4678 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
4679 finalizing_ids.borrow_mut().reserve(finalizer_capacity);
4680 alive_thread_ids.borrow_mut().reserve(thread_capacity);
4681 trace_reachable_threads(&*global, global.current_thread_id, marker);
4682 close_open_upvalues_for_unreachable_threads(&*global, marker);
4683 loop {
4684 let visited_before = marker.visited_count();
4685 for t in &weak_tables_snapshot.ephemeron {
4686 if !marker.is_marked_or_old(t.0) {
4687 continue;
4688 }
4689 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
4690 lua_value_marked_or_old(marker, v)
4691 });
4692 for v in &to_mark {
4693 v.trace(marker);
4694 }
4695 }
4696 marker.drain_gray_queue();
4697 if marker.visited_count() == visited_before {
4698 break;
4699 }
4700 }
4701 for pf in &pending_snapshot {
4702 if !finalizer_marked_or_old(marker, pf) {
4703 pf.mark(marker);
4704 finalizing_ids.borrow_mut().insert(pf.identity());
4705 newly_unreachable.borrow_mut().push(pf.clone());
4706 }
4707 }
4708 marker.drain_gray_queue();
4709 loop {
4710 let visited_before = marker.visited_count();
4711 for t in &weak_tables_snapshot.ephemeron {
4712 if !marker.is_marked_or_old(t.0) {
4713 continue;
4714 }
4715 let to_mark = t.ephemeron_values_to_mark_with_value(&|v| {
4716 lua_value_marked_or_old(marker, v)
4717 });
4718 for v in &to_mark {
4719 v.trace(marker);
4720 }
4721 }
4722 marker.drain_gray_queue();
4723 if marker.visited_count() == visited_before {
4724 break;
4725 }
4726 }
4727 for t in weak_snapshot_tables(&weak_tables_snapshot) {
4728 let id = t.identity();
4729 if marker.is_marked_or_old(t.0) {
4730 let to_mark = {
4731 let finalizing = finalizing_ids.borrow();
4732 t.prune_weak_dead_with_value(
4733 &|v| lua_value_marked_or_old(marker, v),
4734 &|v| {
4735 lua_value_marked_or_old(marker, v)
4736 && lua_value_identity(v)
4737 .map_or(true, |id| !finalizing.contains(&id))
4738 },
4739 )
4740 };
4741 for v in &to_mark {
4742 v.trace(marker);
4743 }
4744 alive_ids.borrow_mut().insert(id);
4745 }
4746 }
4747 marker.drain_gray_queue();
4748 {
4749 let mut alive = alive_thread_ids.borrow_mut();
4750 for (id, entry) in global.threads.iter() {
4751 if thread_entry_marked_alive(marker, *id, entry) {
4752 alive.insert(*id);
4753 }
4754 }
4755 }
4756 record_dead_interned_strings(&*global, marker, &dead_interned);
4757 };
4758 match mode {
4759 HeapCollectMode::Full => global.heap.full_collect_with_post_mark(&roots, hook),
4760 HeapCollectMode::Step => global.heap.step_with_post_mark(&roots, hook),
4761 HeapCollectMode::Minor => global.heap.minor_collect_with_post_mark(&roots, hook),
4762 }
4763 }
4764
4765 if !collect_ran.get() {
4766 return;
4767 }
4768
4769 let alive_set = alive_ids.into_inner();
4773 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
4774 let alive_thread_ids = alive_thread_ids.into_inner();
4775 let dead_interned = dead_interned.into_inner();
4776 let mut g = state_ref.global.borrow_mut();
4777 remove_dead_interned_strings(&mut *g, dead_interned);
4778 g.weak_tables_registry.retain_identities(&alive_set);
4779 let main_thread_id = g.main_thread_id;
4780 g.threads.retain(|id, _| alive_thread_ids.contains(id));
4781 g.cross_thread_upvals
4782 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
4783 let promoted = g.finalizers.promote_pending_to_finalized(promote);
4787 for object in &promoted {
4788 if let Some(ptr) = object.heap_ptr() {
4789 g.heap.move_finobj_to_tobefnz(ptr);
4790 }
4791 }
4792 if matches!(mode, HeapCollectMode::Minor) {
4793 g.finalizers.finish_minor_collection();
4794 }
4795 }
4796
4797 pub fn generational_step(&self) -> bool {
4799 self.generational_step_with_major(true)
4800 }
4801
4802 pub fn generational_step_minor_only(&self) -> bool {
4808 self.generational_step_with_major(false)
4809 }
4810
4811 fn generational_step_with_major(&self, allow_major: bool) -> bool {
4812 let (lastatomic, majorbase, majorinc, should_major) = {
4813 let g = self._state.global();
4814 let majorbase = if g.gc_estimate == 0 {
4815 g.total_bytes()
4816 } else {
4817 g.gc_estimate
4818 };
4819 let majormul = g.gc_genmajormul_param().max(0) as usize;
4820 let majorinc = (majorbase / 100).saturating_mul(majormul);
4821 let debt_due = g.gc_debt() > 0 || g.heap.would_collect();
4822 let should_major =
4823 allow_major && debt_due && g.total_bytes() > majorbase.saturating_add(majorinc);
4824 (g.lastatomic, majorbase, majorinc, should_major)
4825 };
4826
4827 if lastatomic != 0 {
4828 self.stepgenfull(lastatomic);
4829 debug_assert!(self._state.global().is_gen_mode());
4830 return true;
4831 }
4832
4833 if should_major {
4834 let numobjs = self.fullgen();
4835 let after = self._state.global().total_bytes();
4836 if after < majorbase.saturating_add(majorinc / 2) {
4837 self.set_minor_debt();
4838 } else {
4839 {
4840 let mut g = self._state.global_mut();
4841 g.lastatomic = numobjs.max(1);
4842 }
4843 self.set_pause_debt();
4844 }
4845 } else {
4846 self.collect_via_heap_mode(HeapCollectMode::Minor);
4847 self.set_minor_debt();
4848 self._state.global_mut().gc_estimate = majorbase;
4849 }
4850
4851 debug_assert!(self._state.global().is_gen_mode());
4852 true
4853 }
4854
4855 pub fn step(&self) { }
4858
4859 pub fn incremental_step(&self, work_units: isize) -> bool {
4872 self.incremental_step_to_state(work_units, None)
4873 }
4874
4875 pub fn run_until_gc_state_for_test(&self, target: lua_gc::GcState) -> bool {
4880 self.incremental_step_to_state(isize::MAX / 4, Some(target));
4881 self._state.global().heap.gc_state() == target
4882 }
4883
4884 fn incremental_step_to_state(
4885 &self,
4886 work_units: isize,
4887 target: Option<lua_gc::GcState>,
4888 ) -> bool {
4889 use lua_gc::{StepBudget, StepOutcome, Trace};
4890 let state_ref: &LuaState = &*self._state;
4891
4892 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
4893 let mut g = state_ref.global.borrow_mut();
4894 g.weak_tables_registry.live_snapshot_by_kind()
4895 };
4896
4897 let weak_table_capacity = weak_tables_snapshot.len();
4898 let (pending_snapshot, thread_capacity, _interned_capacity): (
4899 Vec<FinalizerObject>,
4900 usize,
4901 usize,
4902 ) = {
4903 let g = state_ref.global.borrow();
4904 (
4905 g.finalizers.pending_snapshot(),
4906 g.threads.len(),
4907 g.interned_lt.len(),
4908 )
4909 };
4910 let finalizer_capacity = pending_snapshot.len();
4911
4912 let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4913 std::cell::RefCell::new(std::collections::HashSet::new());
4914 let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
4915 std::cell::RefCell::new(Vec::new());
4916 let finalizing_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
4917 std::cell::RefCell::new(std::collections::HashSet::new());
4918 let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
4919 std::cell::RefCell::new(std::collections::HashSet::new());
4920 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
4921 let atomic_ran = std::cell::Cell::new(false);
4922
4923 let stop_target = {
4924 let g = state_ref.global.borrow();
4925 match (target, g.heap.gc_state()) {
4926 (Some(target), _) => Some(target),
4927 (None, lua_gc::GcState::CallFin) => None,
4928 (None, _) => Some(lua_gc::GcState::CallFin),
4929 }
4930 };
4931
4932 let outcome = {
4933 let global = state_ref.global.borrow();
4934 global.heap.unpause();
4935 let roots = CollectRoots {
4936 global: &*global,
4937 thread: state_ref,
4938 };
4939 let hook = |marker: &mut lua_gc::Marker| {
4940 atomic_ran.set(true);
4941 alive_ids.borrow_mut().reserve(weak_table_capacity);
4942 newly_unreachable.borrow_mut().reserve(finalizer_capacity);
4943 finalizing_ids.borrow_mut().reserve(finalizer_capacity);
4944 alive_thread_ids.borrow_mut().reserve(thread_capacity);
4945 trace_reachable_threads(&*global, global.current_thread_id, marker);
4946 close_open_upvalues_for_unreachable_threads(&*global, marker);
4947 loop {
4948 let visited_before = marker.visited_count();
4949 for t in &weak_tables_snapshot.ephemeron {
4950 let t_id = t.identity();
4951 if !marker.is_visited(t_id) {
4952 continue;
4953 }
4954 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
4955 for v in &to_mark {
4956 v.trace(marker);
4957 }
4958 }
4959 marker.drain_gray_queue();
4960 if marker.visited_count() == visited_before {
4961 break;
4962 }
4963 }
4964 for pf in &pending_snapshot {
4965 if !marker.is_visited(pf.identity()) {
4966 pf.mark(marker);
4967 finalizing_ids.borrow_mut().insert(pf.identity());
4968 newly_unreachable.borrow_mut().push(pf.clone());
4969 }
4970 }
4971 marker.drain_gray_queue();
4972 loop {
4973 let visited_before = marker.visited_count();
4974 for t in &weak_tables_snapshot.ephemeron {
4975 let t_id = t.identity();
4976 if !marker.is_visited(t_id) {
4977 continue;
4978 }
4979 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
4980 for v in &to_mark {
4981 v.trace(marker);
4982 }
4983 }
4984 marker.drain_gray_queue();
4985 if marker.visited_count() == visited_before {
4986 break;
4987 }
4988 }
4989 for t in weak_snapshot_tables(&weak_tables_snapshot) {
4990 let id = t.identity();
4991 if marker.is_visited(id) {
4992 let to_mark = {
4993 let finalizing = finalizing_ids.borrow();
4994 t.prune_weak_dead_with(&|id| marker.is_visited(id), &|id| {
4995 marker.is_visited(id) && !finalizing.contains(&id)
4996 })
4997 };
4998 for v in &to_mark {
4999 v.trace(marker);
5000 }
5001 alive_ids.borrow_mut().insert(id);
5002 }
5003 }
5004 marker.drain_gray_queue();
5005 {
5006 let mut alive = alive_thread_ids.borrow_mut();
5007 for (id, entry) in global.threads.iter() {
5008 if thread_entry_marked_alive(marker, *id, entry) {
5009 alive.insert(*id);
5010 }
5011 }
5012 }
5013 record_dead_interned_strings(&*global, marker, &dead_interned);
5014 };
5015 let budget = StepBudget::from_work(work_units);
5016 if let Some(target) = stop_target {
5017 global
5018 .heap
5019 .incremental_run_until_state_with_post_mark(&roots, target, work_units, hook)
5020 } else {
5021 global
5022 .heap
5023 .incremental_step_with_post_mark(&roots, budget, hook)
5024 }
5025 };
5026
5027 if atomic_ran.get() {
5028 let alive_set = alive_ids.into_inner();
5029 let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
5030 let alive_thread_ids = alive_thread_ids.into_inner();
5031 let dead_interned = dead_interned.into_inner();
5032 let mut g = state_ref.global.borrow_mut();
5033 remove_dead_interned_strings(&mut *g, dead_interned);
5034 g.weak_tables_registry.retain_identities(&alive_set);
5035 let main_thread_id = g.main_thread_id;
5036 g.threads.retain(|id, _| alive_thread_ids.contains(id));
5037 g.cross_thread_upvals
5038 .retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
5039 let promoted = g.finalizers.promote_pending_to_finalized(promote);
5040 for object in &promoted {
5041 if let Some(ptr) = object.heap_ptr() {
5042 g.heap.move_finobj_to_tobefnz(ptr);
5043 }
5044 }
5045 }
5046
5047 let mut paused = matches!(outcome, StepOutcome::Paused);
5048 if target.is_none()
5049 && self._state.global().heap.gc_state() == lua_gc::GcState::CallFin
5050 && !self._state.global().finalizers.has_to_be_finalized()
5051 {
5052 paused = self._state.global().heap.finish_callfin_phase();
5053 }
5054
5055 paused
5056 }
5057
5058 pub fn prune_weak_tables_mark_only(&self) {
5065 use lua_gc::Trace;
5066 let state_ref: &LuaState = &*self._state;
5067
5068 let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
5069 let mut g = state_ref.global.borrow_mut();
5070 g.weak_tables_registry.live_snapshot_by_kind()
5071 };
5072 let _interned_capacity = {
5073 let g = state_ref.global.borrow();
5074 g.interned_lt.len()
5075 };
5076
5077 let dead_interned: std::cell::RefCell<Vec<(u32, usize)>> = std::cell::RefCell::new(Vec::new());
5078
5079 {
5080 let global = state_ref.global.borrow();
5081 global.heap.unpause();
5082 let roots = CollectRoots {
5083 global: &*global,
5084 thread: state_ref,
5085 };
5086 let hook = |marker: &mut lua_gc::Marker| {
5087 trace_reachable_threads(&*global, global.current_thread_id, marker);
5088 loop {
5089 let visited_before = marker.visited_count();
5090 for t in &weak_tables_snapshot.ephemeron {
5091 let t_id = t.identity();
5092 if !marker.is_visited(t_id) {
5093 continue;
5094 }
5095 let to_mark = t.ephemeron_values_to_mark(&|id| marker.is_visited(id));
5096 for v in &to_mark {
5097 v.trace(marker);
5098 }
5099 }
5100 marker.drain_gray_queue();
5101 if marker.visited_count() == visited_before {
5102 break;
5103 }
5104 }
5105 for t in weak_snapshot_tables(&weak_tables_snapshot) {
5106 if marker.is_visited(t.identity()) {
5107 let to_mark = t.prune_weak_dead(&|id| marker.is_visited(id));
5108 for v in &to_mark {
5109 v.trace(marker);
5110 }
5111 }
5112 }
5113 marker.drain_gray_queue();
5114 record_dead_interned_strings(&*global, marker, &dead_interned);
5115 };
5116 global.heap.mark_only_with_post_mark(&roots, hook);
5117 }
5118
5119 let dead_interned = dead_interned.into_inner();
5120 let mut g = state_ref.global.borrow_mut();
5121 remove_dead_interned_strings(&mut *g, dead_interned);
5122 }
5123
5124 pub fn change_mode(&self, mode: GcKind) {
5126 let old = self._state.global().gckind;
5127 if old == mode as u8 {
5128 self._state.global_mut().lastatomic = 0;
5129 return;
5130 }
5131 match mode {
5132 GcKind::Generational => {
5133 self.enter_generational_mode();
5134 }
5135 GcKind::Incremental => {
5136 self.enter_incremental_mode();
5137 }
5138 }
5139 }
5140
5141 pub fn fix_object<T: lua_gc::Trace + 'static>(&self, _o: &GcRef<T>) { }
5144
5145 pub fn free_all_objects(&self) {
5149 }
5151
5152 pub fn barrier(&self, p: &dyn std::any::Any, v: &LuaValue) {
5156 let g = self._state.global();
5157 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Forward);
5158 }
5159
5160 pub fn barrier_back(&self, p: &dyn std::any::Any, v: &LuaValue) {
5164 let g = self._state.global();
5165 barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Backward);
5166 }
5167
5168 pub fn table_barrier_back(&self, p: &GcRef<LuaTable>, v: &LuaValue) {
5170 let g = self._state.global();
5171 barrier_lua_value(&g.heap, *p, v, g.is_gen_mode(), BarrierKind::Backward);
5172 }
5173
5174 pub fn obj_barrier(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5178 let g = self._state.global();
5179 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Forward);
5180 }
5181
5182 pub fn obj_barrier_back(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
5185 let g = self._state.global();
5186 barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Backward);
5187 }
5188}
5189
5190fn make_seed() -> u32 {
5199 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5200 {
5201 return crate::string::hash_bytes(b"lua-rs-wasm-seed", 0x9e37_79b9);
5202 }
5203
5204 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
5205 {
5206 use std::time::{SystemTime, UNIX_EPOCH};
5207 let t = SystemTime::now()
5208 .duration_since(UNIX_EPOCH)
5209 .map(|d| d.as_secs() as u32)
5210 .unwrap_or(0);
5211
5212 crate::string::hash_bytes(&t.to_le_bytes(), t)
5220 }
5221}
5222
5223pub(crate) fn set_debt(g: &mut GlobalState, mut debt: isize) {
5237 let tb = g.total_bytes() as isize;
5238 debug_assert!(tb > 0);
5239 if debt < tb.saturating_sub(isize::MAX) {
5241 debt = tb - isize::MAX;
5242 }
5243 g.gc_debt = debt;
5244}
5245
5246pub fn set_c_stack_limit(_state: &mut LuaState, _limit: u32) -> i32 {
5256 let _ = (_state, _limit);
5257 LUAI_MAXCCALLS as i32
5258}
5259
5260pub(crate) fn extend_ci(state: &mut LuaState) -> CallInfoIdx {
5277 debug_assert!(
5278 state.call_info[state.ci.0 as usize].next.is_none(),
5279 "extend_ci: current ci already has a cached next frame"
5280 );
5281
5282 let current_idx = state.ci;
5283 let new_idx = CallInfoIdx(state.call_info.len() as u32);
5285
5286 state.call_info.push(CallInfo {
5287 previous: Some(current_idx),
5288 next: None,
5289 u: CallInfoFrame::lua_default(),
5290 ..CallInfo::default()
5291 });
5292
5293 state.call_info[current_idx.0 as usize].next = Some(new_idx);
5294
5295 state.nci += 1;
5296
5297 new_idx
5298}
5299
5300fn free_ci(state: &mut LuaState) {
5321 let ci_idx = state.ci.0 as usize;
5322
5323 let mut next_opt = state.call_info[ci_idx].next.take();
5324
5325 while let Some(idx) = next_opt {
5326 next_opt = state.call_info[idx.0 as usize].next;
5327 state.nci = state.nci.saturating_sub(1);
5328 }
5329
5330 state.call_info.truncate(ci_idx + 1);
5333}
5334
5335pub(crate) fn shrink_ci(state: &mut LuaState) {
5362 let ci_idx = state.ci.0 as usize;
5363
5364 if state.call_info[ci_idx].next.is_none() {
5365 return;
5366 }
5367
5368 let free_count = state.call_info.len().saturating_sub(ci_idx + 1);
5369 if free_count <= 1 {
5370 return;
5371 }
5372
5373 let keep = free_count / 2;
5377 let removed = free_count - keep;
5378 let new_len = ci_idx + 1 + keep;
5379 state.call_info.truncate(new_len);
5380 state.nci = state.nci.saturating_sub(removed as u32);
5381
5382 if let Some(last) = state.call_info.last_mut() {
5384 last.next = None;
5385 }
5386}
5387
5388pub(crate) fn check_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5400 if state.c_calls() == LUAI_MAXCCALLS {
5403 return Err(LuaError::runtime(format_args!("C stack overflow")));
5404 }
5405 if state.c_calls() >= (LUAI_MAXCCALLS / 10 * 11) {
5407 return Err(LuaError::with_status(LuaStatus::ErrErr));
5408 }
5409 Ok(())
5410}
5411
5412pub fn inc_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
5423 state.n_ccalls += 1;
5424 if state.c_calls() >= LUAI_MAXCCALLS {
5426 check_c_stack(state)?;
5427 }
5428 Ok(())
5429}
5430
5431fn stack_init(thread: &mut LuaState) {
5437 let total_slots = BASIC_STACK_SIZE + EXTRA_STACK;
5439 thread.stack = vec![StackValue::default(); total_slots];
5440
5441 thread.tbclist = Vec::new();
5445
5446 thread.top = StackIdx(0);
5451
5452 thread.stack_last = StackIdx(BASIC_STACK_SIZE as u32);
5453
5454 let base_ci = CallInfo {
5455 func: StackIdx(0),
5456 top: StackIdx(1 + LUA_MINSTACK as u32),
5457 previous: None,
5458 next: None,
5459 callstatus: CIST_C,
5460 call_metamethods: 0,
5461 nresults: 0,
5462 u: CallInfoFrame::c_default(),
5463 u2: CallInfoExtra::default(),
5464 };
5465
5466 if thread.call_info.is_empty() {
5467 thread.call_info.push(base_ci);
5468 } else {
5469 thread.call_info[0] = base_ci;
5470 thread.call_info.truncate(1);
5471 }
5472
5473 thread.stack[0] = StackValue {
5474 val: LuaValue::Nil,
5475 };
5476
5477 thread.top = StackIdx(1);
5478
5479 thread.ci = CallInfoIdx(0);
5480}
5481
5482fn free_stack(state: &mut LuaState) {
5483 if state.stack.is_empty() {
5484 return;
5485 }
5486 state.ci = CallInfoIdx(0);
5487 free_ci(state);
5488 debug_assert_eq!(state.nci, 0, "nci should be 0 after free_ci");
5489 state.stack.clear();
5491 state.stack.shrink_to_fit();
5492}
5493
5494fn init_registry(state: &mut LuaState) -> Result<(), LuaError> {
5495 let registry = state.new_table();
5497
5498 state.global_mut().l_registry = LuaValue::Table(registry.clone());
5500
5501 let globals = state.new_table();
5521 state.global_mut().globals = LuaValue::Table(globals);
5522 let loaded = state.new_table();
5523 state.global_mut().loaded = LuaValue::Table(loaded);
5524
5525 Ok(())
5526}
5527
5528fn lua_open(state: &mut LuaState) -> Result<(), LuaError> {
5529 stack_init(state);
5530 init_registry(state)?;
5531 crate::string::init(state)?;
5532 crate::tagmethods::init(state)?;
5533 state.global_mut().gcstp = 0;
5535 state.global().heap.unpause();
5536 state.global_mut().nilvalue = LuaValue::Nil;
5539 Ok(())
5541}
5542
5543fn preinit_thread(thread: &mut LuaState, global: Rc<RefCell<GlobalState>>) {
5544 thread.global = global;
5545 thread.stack = Vec::new();
5546 thread.call_info = Vec::new();
5547 thread.ci = CallInfoIdx(0);
5550 thread.nci = 0;
5551 thread.n_ccalls = 0;
5555 thread.hook = None;
5556 thread.hookmask = 0;
5557 thread.basehookcount = 0;
5558 thread.allowhook = true;
5559 thread.hookcount = thread.basehookcount;
5561
5562 {
5567 let (active, interval) = {
5568 let g = thread.global.borrow();
5569 (g.sandbox_active(), g.sandbox.interval.get())
5570 };
5571 if active {
5572 thread.hookmask = SANDBOX_COUNT_MASK;
5573 thread.basehookcount = interval;
5574 thread.hookcount = interval;
5575 }
5576 }
5577 thread.openupval = Vec::new();
5578 thread.status = LuaStatus::Ok as u8;
5579 thread.errfunc = 0;
5580 thread.oldpc = 0;
5581 thread.gc_check_needed = true;
5582}
5583
5584fn close_state(state: &mut LuaState) {
5585 let is_complete = state.global().is_complete();
5586
5587 if !is_complete {
5588 state.gc().free_all_objects();
5590 } else {
5591 state.ci = CallInfoIdx(0);
5592 state.gc().free_all_objects();
5595 }
5597
5598 state.global_mut().strt = StringPool::default();
5600
5601 free_stack(state);
5602
5603 }
5608
5609pub fn new_thread(state: &mut LuaState, initial_body: Option<LuaValue>) -> Result<(), LuaError> {
5638 state.gc_pre_collect_clear();
5639 state.gc().check_step();
5640
5641 let global_rc = state.global_rc();
5646 let hookmask = state.hookmask;
5647 let basehookcount = state.basehookcount;
5648
5649 let reserved_id = {
5650 let mut g = state.global_mut();
5651 let id = g.next_thread_id;
5652 g.next_thread_id += 1;
5653 id
5654 };
5655
5656 let mut new_thread = LuaState {
5657 status: LuaStatus::Ok as u8,
5658 allowhook: true,
5659 nci: 0,
5660 top: StackIdx(0),
5661 stack_last: StackIdx(0),
5662 stack: Vec::new(),
5663 ci: CallInfoIdx(0),
5664 call_info: Vec::new(),
5665 openupval: Vec::new(),
5666 tbclist: Vec::new(),
5667 global: global_rc.clone(),
5668 hook: None,
5669 hookmask: 0,
5670 basehookcount: 0,
5671 hookcount: 0,
5672 errfunc: 0,
5673 n_ccalls: 0,
5674 oldpc: 0,
5675 marked: 0,
5676 cached_thread_id: reserved_id,
5677 gc_check_needed: false,
5678 };
5679
5680 preinit_thread(&mut new_thread, global_rc);
5681
5682 new_thread.hookmask = hookmask;
5683 new_thread.basehookcount = basehookcount;
5684 new_thread.reset_hook_count();
5687
5688 stack_init(&mut new_thread);
5694
5695 if let Some(body) = initial_body {
5696 new_thread.push(body);
5697 }
5698
5699 let thread_ref: Rc<RefCell<LuaState>> = Rc::new(RefCell::new(new_thread));
5700
5701 let value = {
5702 let mut g = state.global_mut();
5703 let id = reserved_id;
5704 let value = GcRef::new(lua_types::value::LuaThread::new(id));
5705 g.threads.insert(
5706 id,
5707 ThreadRegistryEntry {
5708 state: thread_ref,
5709 value: value.clone(),
5710 },
5711 );
5712 value
5713 };
5714
5715 state.push(LuaValue::Thread(value));
5716
5717 Ok(())
5718}
5719
5720pub fn reset_thread(state: &mut LuaState, status: i32) -> i32 {
5742 state.ci = CallInfoIdx(0);
5743 let ci_idx = 0usize;
5744
5745 if !state.stack.is_empty() {
5747 state.stack[0].val = LuaValue::Nil;
5748 }
5749
5750 state.call_info[ci_idx].func = StackIdx(0);
5751 state.call_info[ci_idx].call_metamethods = 0;
5752 state.call_info[ci_idx].callstatus = CIST_C;
5753
5754 let mut status = if status == LuaStatus::Yield as i32 {
5755 LuaStatus::Ok as i32
5756 } else {
5757 status
5758 };
5759
5760 state.status = LuaStatus::Ok as u8;
5761
5762 let close_status = crate::do_::close_protected(state, StackIdx(1), LuaStatus::from_raw(status));
5763 status = close_status as i32;
5764
5765 if status != LuaStatus::Ok as i32 {
5766 crate::do_::set_error_obj(state, LuaStatus::from_raw(status), StackIdx(1));
5767 } else {
5768 state.top = StackIdx(1);
5769 }
5770
5771 let new_ci_top = StackIdx(state.top.0 + LUA_MINSTACK as u32);
5772 state.call_info[ci_idx].top = new_ci_top;
5773
5774 let needed = new_ci_top.0 as usize;
5777 if state.stack.len() < needed {
5778 state.stack.resize(needed, StackValue::default());
5779 }
5780
5781 status
5782}
5783
5784pub fn close_thread(state: &mut LuaState, from: Option<&LuaState>) -> i32 {
5798 state.n_ccalls = match from {
5800 Some(f) => f.c_calls(),
5801 None => 0,
5802 };
5803 let current_status = state.status as i32;
5804 let result = reset_thread(state, current_status);
5805 result
5806}
5807
5808pub fn reset_thread_api(state: &mut LuaState) -> i32 {
5817 close_thread(state, None)
5818}
5819
5820pub fn new_state() -> Option<LuaState> {
5856 let placeholder_str = GcRef::new(LuaString::placeholder());
5865
5866 let initial_white = 1u8 << WHITE0BIT;
5868
5869 let global = GlobalState {
5873 parser_hook: None,
5874 cli_argv: None,
5875 cli_preload: None,
5876 lua_version: lua_types::LuaVersion::default(),
5877 file_loader_hook: None,
5878 file_open_hook: None,
5879 stdout_hook: None,
5880 stderr_hook: None,
5881 stdin_hook: None,
5882 env_hook: None,
5883 unix_time_hook: None,
5884 cpu_clock_hook: None,
5885 local_offset_hook: None,
5886 entropy_hook: None,
5887 temp_name_hook: None,
5888 popen_hook: None,
5889 file_remove_hook: None,
5890 file_rename_hook: None,
5891 os_execute_hook: None,
5892 dynlib_load_hook: None,
5893 dynlib_symbol_hook: None,
5894 dynlib_unload_hook: None,
5895 sandbox: SandboxLimits::default(),
5896 gc_debt: 0,
5897 gc_estimate: 0,
5898 lastatomic: 0,
5899 strt: StringPool::default(),
5900 l_registry: LuaValue::Nil,
5901 external_roots: ExternalRootSet::default(),
5902 globals: LuaValue::Nil,
5903 loaded: LuaValue::Nil,
5904 nilvalue: LuaValue::Int(0),
5905 seed: make_seed(),
5906 currentwhite: initial_white,
5907 gcstate: GCS_PAUSE,
5908 gckind: GcKind::Incremental as u8,
5910 gcstopem: false,
5911 genminormul: LUAI_GENMINORMUL,
5912 genmajormul: (LUAI_GENMAJORMUL / 4) as u8,
5914 gcstp: GCSTPGC,
5915 gcemergency: false,
5916 gcpause: (LUAI_GCPAUSE / 4) as u8,
5917 gcstepmul: (LUAI_GCMUL / 4) as u8,
5918 gcstepsize: LUAI_GCSTEPSIZE,
5919 gc55_params: [20, 50, 68, 250, 200, 9600],
5922 sweepgc_cursor: 0,
5923 weak_tables_registry: lua_gc::WeakRegistry::default(),
5924 finalizers: lua_gc::FinalizerRegistry::default(),
5925 gc_finalizer_error: None,
5926 twups: Vec::new(),
5927 panic: None,
5928 mainthread: None,
5929 threads: std::collections::HashMap::new(),
5930 main_thread_value: GcRef::new(lua_types::value::LuaThread::new(0)),
5931 current_thread_id: 0,
5932 closing_thread_id: None,
5933 main_thread_id: 0,
5934 next_thread_id: 1,
5935 memerrmsg: placeholder_str.clone(),
5936 tmname: Vec::new(),
5937 mt: std::array::from_fn(|_| None),
5938 strcache: std::array::from_fn(|_| std::array::from_fn(|_| placeholder_str.clone())),
5939 interned_lt: InternedStringMap::default(),
5940 warnf: None,
5941 warn_mode: WarnMode::Off,
5942 test_warn_enabled: false,
5943 test_warn_on: false,
5944 test_warn_mode: TestWarnMode::Normal,
5945 test_warn_last_to_cont: false,
5946 test_warn_buffer: Vec::new(),
5947 c_functions: Vec::new(),
5948 heap: lua_gc::Heap::new(),
5949 cross_thread_upvals: std::collections::HashMap::new(),
5950 suspended_parent_stacks: Vec::new(),
5951 suspended_parent_open_upvals: Vec::new(),
5952 snapshot_stack_pool: Vec::new(),
5953 snapshot_upval_pool: Vec::new(),
5954 };
5955
5956 let global_rc = Rc::new(RefCell::new(global));
5957
5958 let initial_marked = initial_white;
5960
5961 let mut main_thread = LuaState {
5962 status: LuaStatus::Ok as u8,
5963 allowhook: true,
5964 nci: 0,
5965 top: StackIdx(0),
5966 stack_last: StackIdx(0),
5967 stack: Vec::new(),
5968 ci: CallInfoIdx(0),
5969 call_info: Vec::new(),
5970 openupval: Vec::new(),
5971 tbclist: Vec::new(),
5972 global: global_rc.clone(),
5973 hook: None,
5974 hookmask: 0,
5975 basehookcount: 0,
5976 hookcount: 0,
5977 errfunc: 0,
5978 n_ccalls: 0,
5979 oldpc: 0,
5980 marked: initial_marked,
5981 cached_thread_id: 0,
5982 gc_check_needed: false,
5983 };
5984
5985 preinit_thread(&mut main_thread, global_rc.clone());
5986
5987 main_thread.inc_nny();
5989
5990 match lua_open(&mut main_thread) {
6000 Ok(()) => {}
6001 Err(_) => {
6002 close_state(&mut main_thread);
6003 return None;
6004 }
6005 }
6006
6007 Some(main_thread)
6008}
6009
6010pub fn close(mut state: LuaState) {
6026 close_state(&mut state);
6031}
6032
6033pub(crate) fn warning(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6043 let test_warn_enabled = state.global().test_warn_enabled;
6044 if test_warn_enabled {
6045 test_warn(state, msg, to_cont);
6046 return;
6047 }
6048
6049 let has_warnf = state.global().warnf.is_some();
6058 if has_warnf {
6059 let mut warnf = state.global_mut().warnf.take();
6061 if let Some(ref mut f) = warnf {
6062 f(msg, to_cont);
6063 }
6064 state.global_mut().warnf = warnf;
6066 return;
6067 }
6068 default_warn(state, msg, to_cont);
6069}
6070
6071fn test_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6072 let is_control = {
6073 let g = state.global();
6074 !g.test_warn_last_to_cont && !to_cont && msg.first() == Some(&b'@')
6075 };
6076 if is_control {
6077 let mut g = state.global_mut();
6078 match &msg[1..] {
6079 b"off" => g.test_warn_on = false,
6080 b"on" => g.test_warn_on = true,
6081 b"normal" => g.test_warn_mode = TestWarnMode::Normal,
6082 b"allow" => g.test_warn_mode = TestWarnMode::Allow,
6083 b"store" => g.test_warn_mode = TestWarnMode::Store,
6084 _ => {}
6085 }
6086 return;
6087 }
6088
6089 let finished = {
6090 let mut g = state.global_mut();
6091 g.test_warn_last_to_cont = to_cont;
6092 g.test_warn_buffer.extend_from_slice(msg);
6093 if to_cont {
6094 None
6095 } else {
6096 Some((
6097 std::mem::take(&mut g.test_warn_buffer),
6098 g.test_warn_mode,
6099 g.test_warn_on,
6100 ))
6101 }
6102 };
6103
6104 let Some((message, mode, warn_on)) = finished else {
6105 return;
6106 };
6107 match mode {
6108 TestWarnMode::Normal => {
6109 if warn_on && message.first() == Some(&b'#') {
6110 write_warning_message(&message);
6111 }
6112 }
6113 TestWarnMode::Allow => {
6114 if warn_on {
6115 write_warning_message(&message);
6116 }
6117 }
6118 TestWarnMode::Store => {
6119 if let Ok(s) = state.intern_str(&message) {
6120 state.push(LuaValue::Str(s));
6121 let _ = crate::api::set_global(state, b"_WARN");
6122 }
6123 }
6124 }
6125}
6126
6127fn write_warning_message(message: &[u8]) {
6128 use std::io::Write;
6129 let stderr = std::io::stderr();
6130 let mut h = stderr.lock();
6131 let _ = h.write_all(b"Lua warning: ");
6132 let _ = h.write_all(message);
6133 let _ = h.write_all(b"\n");
6134}
6135
6136fn default_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
6141 use std::io::Write;
6142 if !to_cont && msg.first() == Some(&b'@') {
6144 match &msg[1..] {
6145 b"off" => state.global_mut().warn_mode = WarnMode::Off,
6146 b"on" => state.global_mut().warn_mode = WarnMode::On,
6147 _ => {}
6148 }
6149 return;
6150 }
6151 let mode = state.global().warn_mode;
6152 match mode {
6153 WarnMode::Off => {}
6154 WarnMode::On | WarnMode::Cont => {
6155 let stderr = std::io::stderr();
6156 let mut h = stderr.lock();
6157 if mode == WarnMode::On {
6158 let _ = h.write_all(b"Lua warning: ");
6159 }
6160 let _ = h.write_all(msg);
6161 if to_cont {
6162 state.global_mut().warn_mode = WarnMode::Cont;
6163 } else {
6164 let _ = h.write_all(b"\n");
6165 state.global_mut().warn_mode = WarnMode::On;
6166 }
6167 }
6168 }
6169}
6170
6171#[cfg(test)]
6172mod tests {
6173 use super::*;
6174
6175 fn test_noop_cclosure(_: &mut LuaState) -> Result<usize, LuaError> {
6176 Ok(0)
6177 }
6178
6179 #[test]
6180 fn external_root_keys_reject_stale_slot_after_reuse() {
6181 let mut roots = ExternalRootSet::default();
6182
6183 let first = roots.insert(LuaValue::Int(1));
6184 assert_eq!(roots.len(), 1);
6185 assert_eq!(roots.get(first), Some(&LuaValue::Int(1)));
6186
6187 assert_eq!(roots.remove(first), Some(LuaValue::Int(1)));
6188 assert!(roots.get(first).is_none());
6189 assert!(roots.remove(first).is_none());
6190 assert_eq!(roots.len(), 0);
6191 assert_eq!(roots.vacant_len(), 1);
6192 assert!(roots.replace(first, LuaValue::Int(9)).is_none());
6193 assert!(roots.is_empty());
6194
6195 let second = roots.insert(LuaValue::Int(2));
6196 assert_eq!(first.index, second.index);
6197 assert_ne!(first, second);
6198 assert!(roots.get(first).is_none());
6199 assert_eq!(roots.get(second), Some(&LuaValue::Int(2)));
6200 assert!(roots.replace(first, LuaValue::Int(3)).is_none());
6201 }
6202
6203 #[test]
6204 fn external_roots_keep_heap_value_alive_until_unrooted() {
6205 let mut state = new_state().expect("state should initialize");
6206 let _heap_guard = {
6207 let g = state.global();
6208 lua_gc::HeapGuard::push(&g.heap)
6209 };
6210
6211 let table = state.new_table();
6212 assert_eq!(state.global().heap.allgc_count(), 1);
6213
6214 let key = state.external_root_value(LuaValue::Table(table));
6215 state.gc().full_collect();
6216 assert_eq!(state.global().heap.allgc_count(), 1);
6217 assert_eq!(state.global().external_roots.len(), 1);
6218
6219 assert!(state.external_unroot_value(key).is_some());
6220 state.gc().full_collect();
6221 assert_eq!(state.global().heap.allgc_count(), 0);
6222 assert!(state.global().external_roots.is_empty());
6223 }
6224
6225 #[test]
6226 fn table_buffer_accounting_refunds_on_sweep() {
6227 let mut state = new_state().expect("state should initialize");
6228 let _heap_guard = {
6229 let g = state.global();
6230 lua_gc::HeapGuard::push(&g.heap)
6231 };
6232
6233 let table = state.new_table();
6234 let key = state.external_root_value(LuaValue::Table(table));
6235 let header_bytes = state.global().heap.bytes_used();
6236 assert!(header_bytes > 0);
6237
6238 for i in 1..=128 {
6239 table
6240 .raw_set_int(&mut state, i, LuaValue::Int(i))
6241 .expect("integer table insert should succeed");
6242 }
6243 let grown_bytes = state.global().heap.bytes_used();
6244 assert!(
6245 grown_bytes > header_bytes,
6246 "table array/hash buffer growth must be charged to the GC heap"
6247 );
6248
6249 state.gc().full_collect();
6250 assert_eq!(
6251 state.global().heap.bytes_used(),
6252 grown_bytes,
6253 "rooted table buffer bytes should remain charged after collection"
6254 );
6255
6256 assert!(state.external_unroot_value(key).is_some());
6257 state.gc().full_collect();
6258 assert_eq!(state.global().heap.bytes_used(), 0);
6259 assert_eq!(state.global().heap.allgc_count(), 0);
6260 }
6261
6262 #[test]
6263 fn userdata_buffer_accounting_refunds_on_sweep() {
6264 let mut state = new_state().expect("state should initialize");
6265 let _heap_guard = {
6266 let g = state.global();
6267 lua_gc::HeapGuard::push(&g.heap)
6268 };
6269
6270 let payload_len = 4096;
6271 let userdata = state
6272 .new_userdata_typed(b"accounting", payload_len, 3)
6273 .expect("userdata allocation should succeed");
6274 state.pop_n(1);
6275 let key = state.external_root_value(LuaValue::UserData(userdata));
6276 let allocated_bytes = state.global().heap.bytes_used();
6277 assert!(
6278 allocated_bytes > payload_len,
6279 "userdata payload bytes must be charged to the GC heap"
6280 );
6281
6282 state.gc().full_collect();
6283 assert_eq!(
6284 state.global().heap.bytes_used(),
6285 allocated_bytes,
6286 "rooted userdata payload bytes should remain charged after collection"
6287 );
6288
6289 assert!(state.external_unroot_value(key).is_some());
6290 state.gc().full_collect();
6291 assert_eq!(state.global().heap.bytes_used(), 0);
6292 assert_eq!(state.global().heap.allgc_count(), 0);
6293 }
6294
6295 #[test]
6296 fn cclosure_upvalue_accounting_refunds_on_sweep() {
6297 let mut state = new_state().expect("state should initialize");
6298 let _heap_guard = {
6299 let g = state.global();
6300 lua_gc::HeapGuard::push(&g.heap)
6301 };
6302
6303 let nupvalues = 64;
6304 for i in 0..nupvalues {
6305 state.push(LuaValue::Int(i as i64));
6306 }
6307 crate::api::push_cclosure(&mut state, test_noop_cclosure, nupvalues as i32)
6308 .expect("C closure creation should succeed");
6309 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6310 panic!("expected heavy C closure");
6311 };
6312 let expected_payload = ccl.buffer_bytes();
6313 let key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
6314 state.pop_n(1);
6315 let allocated_bytes = state.global().heap.bytes_used();
6316 assert!(
6317 allocated_bytes >= expected_payload,
6318 "C closure upvalue vector bytes must be charged to the GC heap"
6319 );
6320
6321 state.gc().full_collect();
6322 assert_eq!(
6323 state.global().heap.bytes_used(),
6324 allocated_bytes,
6325 "rooted C closure payload bytes should remain charged after collection"
6326 );
6327
6328 assert!(state.external_unroot_value(key).is_some());
6329 state.gc().full_collect();
6330 assert_eq!(state.global().heap.bytes_used(), 0);
6331 assert_eq!(state.global().heap.allgc_count(), 0);
6332 }
6333
6334 #[test]
6335 fn proto_and_lclosure_accounting_refunds_on_sweep() {
6336 let mut state = new_state().expect("state should initialize");
6337 let _heap_guard = {
6338 let g = state.global();
6339 lua_gc::HeapGuard::push(&g.heap)
6340 };
6341
6342 let mut proto = LuaProto::placeholder();
6343 proto.code = vec![lua_types::opcode::Instruction(0); 2048];
6344 proto.lineinfo = vec![0; 2048];
6345 proto.k = vec![LuaValue::Int(1); 512];
6346 let expected_proto_payload = proto.buffer_bytes();
6347 let proto = GcRef::new(proto);
6348 proto.account_buffer(expected_proto_payload as isize);
6349
6350 let closure = state.new_lclosure(proto, 16);
6351 let expected_closure_payload = closure.buffer_bytes();
6352 let key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6353 let allocated_bytes = state.global().heap.bytes_used();
6354 assert!(
6355 allocated_bytes >= expected_proto_payload + expected_closure_payload,
6356 "proto and Lua closure vector bytes must be charged to the GC heap"
6357 );
6358
6359 state.gc().full_collect();
6360 assert_eq!(
6361 state.global().heap.bytes_used(),
6362 allocated_bytes,
6363 "rooted proto and Lua closure payload bytes should remain charged after collection"
6364 );
6365
6366 assert!(state.external_unroot_value(key).is_some());
6367 state.gc().full_collect();
6368 assert_eq!(state.global().heap.bytes_used(), 0);
6369 assert_eq!(state.global().heap.allgc_count(), 0);
6370 }
6371
6372 #[test]
6373 fn string_buffer_accounting_refunds_on_sweep() {
6374 let mut state = new_state().expect("state should initialize");
6375 let _heap_guard = {
6376 let g = state.global();
6377 lua_gc::HeapGuard::push(&g.heap)
6378 };
6379
6380 let payload = vec![b'x'; crate::string::MAX_SHORT_LEN + 4096];
6381 let string = state
6382 .intern_str(&payload)
6383 .expect("long string should allocate");
6384 let key = state.external_root_value(LuaValue::Str(string));
6385 let allocated_bytes = state.global().heap.bytes_used();
6386 assert!(
6387 allocated_bytes > payload.len(),
6388 "long string backing bytes must be charged to the GC heap"
6389 );
6390
6391 state.gc().full_collect();
6392 assert_eq!(
6393 state.global().heap.bytes_used(),
6394 allocated_bytes,
6395 "rooted string buffer bytes should remain charged after collection"
6396 );
6397
6398 assert!(state.external_unroot_value(key).is_some());
6399 state.gc().full_collect();
6400 assert_eq!(state.global().heap.bytes_used(), 0);
6401 assert_eq!(state.global().heap.allgc_count(), 0);
6402 }
6403
6404 #[test]
6405 fn interned_short_string_cache_does_not_root_unreferenced_string() {
6406 let mut state = new_state().expect("state should initialize");
6407 let _heap_guard = {
6408 let g = state.global();
6409 lua_gc::HeapGuard::push(&g.heap)
6410 };
6411
6412 let payload = b"weak-cache-probe-a";
6413 let string = state
6414 .intern_str(payload)
6415 .expect("short string should intern");
6416 let id = string.identity();
6417 assert!(state.global().interned_lt.contains_key(&payload[..]));
6418 assert!(state.global().heap.allocation_token(id).is_some());
6419
6420 state.gc().full_collect();
6421 assert!(!state.global().interned_lt.contains_key(&payload[..]));
6422 assert_eq!(state.global().heap.allocation_token(id), None);
6423 }
6424
6425 #[test]
6426 fn interned_short_string_cache_keeps_reachable_string_until_unrooted() {
6427 let mut state = new_state().expect("state should initialize");
6428 let _heap_guard = {
6429 let g = state.global();
6430 lua_gc::HeapGuard::push(&g.heap)
6431 };
6432
6433 let payload = b"weak-cache-probe-b";
6434 let string = state
6435 .intern_str(payload)
6436 .expect("short string should intern");
6437 let id = string.identity();
6438 let key = state.external_root_value(LuaValue::Str(string));
6439
6440 state.gc().full_collect();
6441 assert!(state.global().interned_lt.contains_key(&payload[..]));
6442 assert!(state.global().heap.allocation_token(id).is_some());
6443
6444 assert!(state.external_unroot_value(key).is_some());
6445 state.gc().full_collect();
6446 assert!(!state.global().interned_lt.contains_key(&payload[..]));
6447 assert_eq!(state.global().heap.allocation_token(id), None);
6448 }
6449
6450 #[test]
6451 fn gc_phase_predicates_follow_heap_state() {
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 {
6459 let mut g = state.global_mut();
6460 g.gckind = GcKind::Incremental as u8;
6461 g.lastatomic = 0;
6462 assert!(!g.is_gen_mode());
6463 g.lastatomic = 1;
6464 assert!(g.is_gen_mode());
6465 g.lastatomic = 0;
6466 }
6467
6468 let mut roots = Vec::new();
6469 for _ in 0..16 {
6470 let table = state.new_table();
6471 roots.push(state.external_root_value(LuaValue::Table(table)));
6472 }
6473
6474 let mut saw_keep = false;
6475 let mut saw_sweep = false;
6476 for _ in 0..128 {
6477 state.gc().incremental_step(1);
6478 let g = state.global();
6479 let heap_state = g.heap.gc_state();
6480 assert_eq!(g.keep_invariant(), heap_state.is_invariant());
6481 assert_eq!(g.is_sweep_phase(), heap_state.is_sweep());
6482 saw_keep |= g.keep_invariant();
6483 saw_sweep |= g.is_sweep_phase();
6484 if heap_state.is_pause() && saw_keep && saw_sweep {
6485 break;
6486 }
6487 }
6488
6489 assert!(
6490 saw_keep,
6491 "incremental cycle should expose an invariant phase"
6492 );
6493 assert!(saw_sweep, "incremental cycle should expose a sweep phase");
6494
6495 for key in roots {
6496 assert!(state.external_unroot_value(key).is_some());
6497 }
6498 state.gc().full_collect();
6499 }
6500
6501 #[test]
6502 fn gc_barrier_keeps_new_child_stored_in_black_parent() {
6503 let mut state = new_state().expect("state should initialize");
6504 let _heap_guard = {
6505 let g = state.global();
6506 lua_gc::HeapGuard::push(&g.heap)
6507 };
6508
6509 let parent = state.new_table();
6510 let parent_key = state.external_root_value(LuaValue::Table(parent));
6511 state.gc().incremental_step(1);
6512 assert!(
6513 state.global().keep_invariant(),
6514 "test setup should leave the parent marked during an active cycle"
6515 );
6516
6517 let child = state.new_table();
6518 let parent_value = LuaValue::Table(parent);
6519 let child_value = LuaValue::Table(child);
6520 parent
6521 .raw_set_int(&mut state, 1, child_value)
6522 .expect("table store should succeed");
6523 state.gc_barrier_back(&parent_value, &child_value);
6524
6525 for _ in 0..128 {
6526 if state.gc().incremental_step(1) {
6527 break;
6528 }
6529 }
6530
6531 assert_eq!(state.global().heap.allgc_count(), 2);
6532 assert_eq!(
6533 parent.get_int(1).as_table().map(|t| t.identity()),
6534 Some(child.identity())
6535 );
6536
6537 assert!(state.external_unroot_value(parent_key).is_some());
6538 state.gc().full_collect();
6539 assert_eq!(state.global().heap.allgc_count(), 0);
6540 }
6541
6542 #[test]
6543 fn generational_mode_promotes_and_barriers_age_objects() {
6544 let mut state = new_state().expect("state should initialize");
6545 let _heap_guard = {
6546 let g = state.global();
6547 lua_gc::HeapGuard::push(&g.heap)
6548 };
6549
6550 let parent = state.new_table();
6551 let parent_key = state.external_root_value(LuaValue::Table(parent));
6552
6553 state.gc().change_mode(GcKind::Generational);
6554 assert_eq!(parent.0.age(), lua_gc::GcAge::Old);
6555 assert_eq!(parent.0.color(), lua_gc::Color::Black);
6556 let majorbase = state.global().gc_estimate;
6557 assert!(majorbase > 0);
6558 assert!(state.global().gc_debt() <= 0);
6559
6560 let child = state.new_table();
6561 let parent_value = LuaValue::Table(parent);
6562 let child_value = LuaValue::Table(child);
6563 parent
6564 .raw_set_int(&mut state, 1, child_value.clone())
6565 .expect("table store should succeed");
6566 state.gc_barrier_back(&parent_value, &child_value);
6567 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched1);
6568 assert_eq!(parent.0.color(), lua_gc::Color::Gray);
6569 assert_eq!(child.0.age(), lua_gc::GcAge::New);
6570
6571 let metatable = state.new_table();
6572 parent.set_metatable(Some(metatable));
6573 state.gc().obj_barrier(&parent, &metatable);
6574 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old0);
6575
6576 assert!(state.gc().generational_step_minor_only());
6577 assert_eq!(parent.0.age(), lua_gc::GcAge::Touched2);
6578 assert_eq!(child.0.age(), lua_gc::GcAge::Survival);
6579 assert_eq!(metatable.0.age(), lua_gc::GcAge::Old1);
6580 assert_eq!(state.global().gc_estimate, majorbase);
6581 assert!(state.global().gc_debt() <= 0);
6582
6583 state.gc().change_mode(GcKind::Incremental);
6584 assert_eq!(parent.0.age(), lua_gc::GcAge::New);
6585 assert_eq!(child.0.age(), lua_gc::GcAge::New);
6586 assert_eq!(metatable.0.age(), lua_gc::GcAge::New);
6587
6588 assert!(state.external_unroot_value(parent_key).is_some());
6589 state.gc().full_collect();
6590 }
6591
6592 #[test]
6593 fn generational_upvalue_write_barrier_marks_young_child_old0() {
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 proto = state.new_proto();
6601 let closure = state.new_lclosure(proto, 1);
6602 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6603 state.gc().change_mode(GcKind::Generational);
6604 let uv = closure.upval(0);
6605 assert_eq!(uv.0.age(), lua_gc::GcAge::Old);
6606
6607 let child = state.new_table();
6608 state
6609 .upvalue_set(&closure, 0, LuaValue::Table(child))
6610 .expect("closed upvalue write should succeed");
6611 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
6612
6613 assert!(state.external_unroot_value(closure_key).is_some());
6614 state.gc().full_collect();
6615 }
6616
6617 #[test]
6618 fn cclosure_setupvalue_replaces_upvalue() {
6619 let mut state = new_state().expect("state should initialize");
6620 let _heap_guard = {
6621 let g = state.global();
6622 lua_gc::HeapGuard::push(&g.heap)
6623 };
6624
6625 let first = state.new_table();
6626 state.push(LuaValue::Table(first));
6627 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
6628 .expect("C closure creation should succeed");
6629 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6630 panic!("expected heavy C closure");
6631 };
6632
6633 let second = state.new_table();
6634 state.push(LuaValue::Table(second));
6635 let name =
6636 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
6637
6638 assert!(name.is_empty());
6639 let upvalues = ccl.upvalues.borrow();
6640 let LuaValue::Table(actual) = upvalues[0].clone() else {
6641 panic!("expected table upvalue");
6642 };
6643 assert_eq!(actual.identity(), second.identity());
6644 }
6645
6646 #[test]
6647 fn generational_cclosure_setupvalue_barrier_marks_young_child_old0() {
6648 let mut state = new_state().expect("state should initialize");
6649 let _heap_guard = {
6650 let g = state.global();
6651 lua_gc::HeapGuard::push(&g.heap)
6652 };
6653
6654 state.push(LuaValue::Nil);
6655 crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
6656 .expect("C closure creation should succeed");
6657 let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
6658 panic!("expected heavy C closure");
6659 };
6660 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
6661
6662 state.gc().change_mode(GcKind::Generational);
6663 assert_eq!(ccl.0.age(), lua_gc::GcAge::Old);
6664
6665 let child = state.new_table();
6666 state.push(LuaValue::Table(child));
6667 crate::api::setup_value(&mut state, -2, 1).expect("C closure upvalue should exist");
6668
6669 assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
6670
6671 assert!(state.external_unroot_value(closure_key).is_some());
6672 state.gc().full_collect();
6673 }
6674
6675 #[test]
6676 fn generational_closure_upvalue_slot_barrier_marks_new_upval_old0() {
6677 let mut state = new_state().expect("state should initialize");
6678 let _heap_guard = {
6679 let g = state.global();
6680 lua_gc::HeapGuard::push(&g.heap)
6681 };
6682
6683 let proto = state.new_proto();
6684 let closure = state.new_lclosure(proto, 1);
6685 let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
6686 state.gc().change_mode(GcKind::Generational);
6687 assert_eq!(closure.0.age(), lua_gc::GcAge::Old);
6688
6689 let replacement = state.new_upval_closed(LuaValue::Nil);
6690 closure.set_upval(0, replacement);
6691 state.gc().obj_barrier(&closure, &replacement);
6692 assert_eq!(replacement.0.age(), lua_gc::GcAge::Old0);
6693
6694 assert!(state.external_unroot_value(closure_key).is_some());
6695 state.gc().full_collect();
6696 }
6697
6698 #[test]
6699 fn cross_thread_upvalue_mirror_traces_values_as_roots() {
6700 let mut state = new_state().expect("state should initialize");
6701 let _heap_guard = {
6702 let g = state.global();
6703 lua_gc::HeapGuard::push(&g.heap)
6704 };
6705
6706 let mirrored = state.new_table();
6707 state
6708 .global_mut()
6709 .cross_thread_upvals
6710 .insert((999, StackIdx(0)), LuaValue::Table(mirrored));
6711
6712 state.gc().full_collect();
6713 assert_eq!(state.global().heap.allgc_count(), 1);
6714
6715 state.global_mut().cross_thread_upvals.clear();
6716 state.gc().full_collect();
6717 assert_eq!(state.global().heap.allgc_count(), 0);
6718 }
6719
6720 #[test]
6721 fn generational_full_collect_promotes_new_survivors_to_old() {
6722 let mut state = new_state().expect("state should initialize");
6723 let _heap_guard = {
6724 let g = state.global();
6725 lua_gc::HeapGuard::push(&g.heap)
6726 };
6727
6728 state.gc().change_mode(GcKind::Generational);
6729 let table = state.new_table();
6730 let table_key = state.external_root_value(LuaValue::Table(table));
6731 assert_eq!(table.0.age(), lua_gc::GcAge::New);
6732
6733 state.gc().full_collect();
6734 assert_eq!(table.0.age(), lua_gc::GcAge::Old);
6735 assert_eq!(table.0.color(), lua_gc::Color::Black);
6736
6737 assert!(state.external_unroot_value(table_key).is_some());
6738 state.gc().full_collect();
6739 }
6740
6741 #[test]
6742 fn gc_packed_params_return_user_visible_values() {
6743 let mut state = new_state().expect("state should initialize");
6744 assert_eq!(
6745 crate::api::gc(&mut state, crate::api::GcArgs::SetPause { value: 200 }),
6746 200
6747 );
6748 assert_eq!(state.global().gc_pause_param(), 200);
6749 assert_eq!(
6750 crate::api::gc(&mut state, crate::api::GcArgs::SetStepMul { value: 200 }),
6751 100
6752 );
6753 assert_eq!(state.global().gc_stepmul_param(), 200);
6754
6755 crate::api::gc(
6756 &mut state,
6757 crate::api::GcArgs::Gen {
6758 minormul: 0,
6759 majormul: 200,
6760 },
6761 );
6762 assert_eq!(state.global().gc_genmajormul_param(), 200);
6763 }
6764
6765 #[test]
6766 fn generational_step_runs_bad_major_when_growth_exceeds_genmajormul() {
6767 let mut state = new_state().expect("state should initialize");
6768 let _heap_guard = {
6769 let g = state.global();
6770 lua_gc::HeapGuard::push(&g.heap)
6771 };
6772
6773 let root = state.new_table();
6774 let root_key = state.external_root_value(LuaValue::Table(root));
6775 state.gc().change_mode(GcKind::Generational);
6776
6777 let root_value = LuaValue::Table(root);
6778 for i in 1..=64 {
6779 let child = state.new_table();
6780 let child_value = LuaValue::Table(child);
6781 root.raw_set_int(&mut state, i, child_value.clone())
6782 .expect("table store should succeed");
6783 state.gc_barrier_back(&root_value, &child_value);
6784 }
6785
6786 {
6787 let mut g = state.global_mut();
6788 g.gc_estimate = 1;
6789 set_debt(&mut *g, 1);
6790 }
6791
6792 assert!(state.gc().generational_step());
6793 let g = state.global();
6794 assert!(g.is_gen_mode());
6795 assert!(
6796 g.lastatomic > 0,
6797 "bad major collection should arm stepgenfull"
6798 );
6799 assert!(g.gc_estimate > 1);
6800 assert!(g.gc_debt() <= 0);
6801 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
6802 drop(g);
6803
6804 assert!(state.external_unroot_value(root_key).is_some());
6805 state.gc().full_collect();
6806 }
6807
6808 #[test]
6809 fn generational_implicit_step_runs_major_when_heap_threshold_exceeded() {
6810 let mut state = new_state().expect("state should initialize");
6811 let _heap_guard = {
6812 let g = state.global();
6813 lua_gc::HeapGuard::push(&g.heap)
6814 };
6815
6816 let root = state.new_table();
6817 let root_key = state.external_root_value(LuaValue::Table(root));
6818 state.gc().change_mode(GcKind::Generational);
6819
6820 let root_value = LuaValue::Table(root);
6821 for i in 1..=64 {
6822 let child = state.new_table();
6823 let child_value = LuaValue::Table(child);
6824 root.raw_set_int(&mut state, i, child_value.clone())
6825 .expect("table store should succeed");
6826 state.gc_barrier_back(&root_value, &child_value);
6827 }
6828
6829 {
6830 let mut g = state.global_mut();
6831 g.gc_estimate = 1;
6832 set_debt(&mut *g, -1);
6833 g.heap.set_threshold_bytes(1);
6834 }
6835
6836 assert!(state.gc().generational_step());
6837 let g = state.global();
6838 assert!(g.is_gen_mode());
6839 assert!(
6840 g.lastatomic > 0,
6841 "implicit threshold-triggered growth should arm a bad major"
6842 );
6843 assert!(g.gc_debt() <= 0);
6844 drop(g);
6845
6846 assert!(state.external_unroot_value(root_key).is_some());
6847 state.gc().full_collect();
6848 }
6849
6850 #[test]
6851 fn generational_stepgenfull_returns_to_gen_after_good_collection() {
6852 let mut state = new_state().expect("state should initialize");
6853 let _heap_guard = {
6854 let g = state.global();
6855 lua_gc::HeapGuard::push(&g.heap)
6856 };
6857
6858 let root = state.new_table();
6859 let root_key = state.external_root_value(LuaValue::Table(root));
6860 state.gc().change_mode(GcKind::Generational);
6861 {
6862 let mut g = state.global_mut();
6863 g.lastatomic = 1024;
6864 }
6865
6866 assert!(state.gc().generational_step());
6867 let g = state.global();
6868 assert_eq!(g.gckind, GcKind::Generational as u8);
6869 assert_eq!(g.lastatomic, 0);
6870 assert!(g.gc_debt() <= 0);
6871 assert_eq!(root.0.age(), lua_gc::GcAge::Old);
6872 assert_eq!(root.0.color(), lua_gc::Color::Black);
6873 drop(g);
6874
6875 assert!(state.external_unroot_value(root_key).is_some());
6876 state.gc().full_collect();
6877 }
6878
6879 #[test]
6880 fn generational_step_zero_reports_false_without_positive_debt() {
6881 let mut state = new_state().expect("state should initialize");
6882 let _heap_guard = {
6883 let g = state.global();
6884 lua_gc::HeapGuard::push(&g.heap)
6885 };
6886
6887 state.gc().change_mode(GcKind::Generational);
6888 assert_eq!(
6889 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 0 }),
6890 0
6891 );
6892 assert_eq!(
6893 crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 1 }),
6894 1
6895 );
6896 }
6897}
6898
6899