use std::cell::RefCell;
use std::rc::Rc;
use crate::string::StringPool;
pub use lua_types::error::LuaError;
pub use lua_types::{CallInfoIdx, StackIdx};
pub struct StackIdxConv(pub StackIdx);
#[inline(always)]
pub fn stack_idx_to_i32(i: StackIdx) -> i32 { i.0 as i32 }
impl From<u32> for StackIdxConv {
#[inline(always)]
fn from(v: u32) -> Self { StackIdxConv(StackIdx(v)) }
}
impl From<i32> for StackIdxConv {
#[inline(always)]
fn from(v: i32) -> Self { StackIdxConv(StackIdx(v.max(0) as u32)) }
}
impl From<usize> for StackIdxConv {
#[inline(always)]
fn from(v: usize) -> Self { StackIdxConv(StackIdx(v as u32)) }
}
impl From<StackIdx> for StackIdxConv {
#[inline(always)]
fn from(v: StackIdx) -> Self { StackIdxConv(v) }
}
pub use lua_types::value::{LuaTable, LuaValue, F2Imod};
pub use lua_types::string::LuaString;
pub use lua_types::userdata::LuaUserData;
pub use lua_types::closure::{LuaCFnPtr, LuaClosure, LuaLClosure as LuaClosureLua, LuaCClosure as LuaClosureC};
pub use lua_types::proto::LuaProto;
pub use lua_types::upval::{UpVal, UpValState};
pub use lua_types::gc::GcRef;
pub type LuaCFunction = fn(&mut LuaState) -> Result<usize, LuaError>;
pub type LuaRustFunction = Rc<dyn Fn(&mut LuaState) -> Result<usize, LuaError>>;
#[derive(Clone)]
pub enum LuaCallable {
Bare(LuaCFunction),
Rust(LuaRustFunction),
}
impl std::fmt::Debug for LuaCallable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LuaCallable::Bare(_) => f.write_str("LuaCallable::Bare(..)"),
LuaCallable::Rust(_) => f.write_str("LuaCallable::Rust(..)"),
}
}
}
impl LuaCallable {
pub fn bare(f: LuaCFunction) -> Self {
LuaCallable::Bare(f)
}
pub fn rust(f: LuaRustFunction) -> Self {
LuaCallable::Rust(f)
}
pub fn as_bare(&self) -> Option<LuaCFunction> {
match self {
LuaCallable::Bare(f) => Some(*f),
LuaCallable::Rust(_) => None,
}
}
pub fn call(&self, state: &mut LuaState) -> Result<usize, LuaError> {
match self {
LuaCallable::Bare(f) => f(state),
LuaCallable::Rust(f) => f(state),
}
}
}
#[derive(Clone, Debug)]
pub enum FinalizerObject {
Table(GcRef<LuaTable>),
UserData(GcRef<LuaUserData>),
}
impl FinalizerObject {
pub fn identity(&self) -> usize {
match self {
FinalizerObject::Table(t) => t.identity(),
FinalizerObject::UserData(u) => u.identity(),
}
}
pub fn metatable(&self) -> Option<GcRef<LuaTable>> {
match self {
FinalizerObject::Table(t) => t.metatable(),
FinalizerObject::UserData(u) => u.metatable(),
}
}
pub fn as_lua_value(&self) -> LuaValue {
match self {
FinalizerObject::Table(t) => LuaValue::Table(t.clone()),
FinalizerObject::UserData(u) => LuaValue::UserData(u.clone()),
}
}
pub fn mark(&self, marker: &mut lua_gc::Marker) {
match self {
FinalizerObject::Table(t) => marker.mark(t.0),
FinalizerObject::UserData(u) => marker.mark(u.0),
}
}
pub fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
Some(match self {
FinalizerObject::Table(t) => t.0.as_trace_ptr(),
FinalizerObject::UserData(u) => u.0.as_trace_ptr(),
})
}
pub fn age(&self) -> lua_gc::GcAge {
match self {
FinalizerObject::Table(t) => t.0.age(),
FinalizerObject::UserData(u) => u.0.age(),
}
}
pub fn is_finalized(&self) -> bool {
match self {
FinalizerObject::Table(t) => t.0.is_finalized(),
FinalizerObject::UserData(u) => u.0.is_finalized(),
}
}
pub fn set_finalized(&self, finalized: bool) {
match self {
FinalizerObject::Table(t) => t.0.set_finalized(finalized),
FinalizerObject::UserData(u) => u.0.set_finalized(finalized),
}
}
}
impl lua_gc::FinalizerEntry for FinalizerObject {
fn identity(&self) -> usize {
FinalizerObject::identity(self)
}
fn heap_ptr(&self) -> Option<std::ptr::NonNull<lua_gc::GcBox<dyn lua_gc::Trace>>> {
FinalizerObject::heap_ptr(self)
}
fn age(&self) -> lua_gc::GcAge {
FinalizerObject::age(self)
}
fn is_finalized(&self) -> bool {
FinalizerObject::is_finalized(self)
}
fn set_finalized(&self, finalized: bool) {
FinalizerObject::set_finalized(self, finalized);
}
}
#[derive(Clone, Debug)]
pub struct WeakTableEntry {
table: lua_types::gc::GcWeak<LuaTable>,
kind: lua_gc::WeakListKind,
}
impl WeakTableEntry {
pub fn new(table: &GcRef<LuaTable>) -> Self {
let mode = table.weak_mode();
let weak_keys = (mode & (1 << 0)) != 0;
let weak_values = (mode & (1 << 1)) != 0;
let kind = match (weak_keys, weak_values) {
(true, true) => lua_gc::WeakListKind::AllWeak,
(true, false) => lua_gc::WeakListKind::Ephemeron,
(false, true) => lua_gc::WeakListKind::WeakValues,
(false, false) => lua_gc::WeakListKind::WeakValues,
};
Self { table: table.downgrade(), kind }
}
}
impl lua_gc::WeakEntry for WeakTableEntry {
type Strong = GcRef<LuaTable>;
fn identity(&self) -> usize {
self.table.identity()
}
fn list_kind(&self) -> lua_gc::WeakListKind {
self.kind
}
fn upgrade(&self) -> Option<Self::Strong> {
self.table.upgrade()
}
}
pub(crate) const EXTRA_STACK: usize = 5;
pub(crate) const LUA_MINSTACK: usize = 20;
pub(crate) const BASIC_STACK_SIZE: usize = 2 * LUA_MINSTACK;
pub(crate) const LUAI_MAXCCALLS: u32 = 200;
pub(crate) const CIST_C: u16 = 1 << 1;
pub(crate) const CIST_OAH: u16 = 1 << 0;
pub(crate) const CIST_FRESH: u16 = 1 << 2;
pub(crate) const CIST_HOOKED: u16 = 1 << 3;
pub(crate) const CIST_YPCALL: u16 = 1 << 4;
pub(crate) const CIST_TAIL: u16 = 1 << 5;
pub(crate) const CIST_HOOKYIELD: u16 = 1 << 6;
pub(crate) const CIST_FIN: u16 = 1 << 7;
pub(crate) const CIST_TRAN: u16 = 1 << 8;
pub(crate) const CIST_RECST: u32 = 10;
pub(crate) const CIST_LEQ: u16 = 1 << 13;
const LUA_NUMTYPES: usize = 9;
const GCSTPUSR: u8 = 1;
const GCSTPGC: u8 = 2;
const GCS_PAUSE: u8 = 0;
const LUAI_GCPAUSE: u32 = 200;
const LUAI_GCMUL: u32 = 100;
const LUAI_GCSTEPSIZE: u8 = 13;
const LUAI_GENMAJORMUL: u32 = 100;
const LUAI_GENMINORMUL: u8 = 20;
const WHITE0BIT: u8 = 0;
const STRCACHE_N: usize = 53;
const STRCACHE_M: usize = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GcKind {
Incremental = 0,
Generational = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WarnMode {
Off,
On,
Cont,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TestWarnMode {
Normal,
Allow,
Store,
}
pub use lua_types::status::LuaStatus;
#[derive(Clone)]
pub struct StackValue {
pub val: LuaValue,
pub tbc_delta: u16,
}
impl Default for StackValue {
fn default() -> Self {
StackValue {
val: LuaValue::Nil,
tbc_delta: 0,
}
}
}
#[derive(Clone)]
pub struct CallInfo {
pub func: StackIdx,
pub top: StackIdx,
pub previous: Option<CallInfoIdx>,
pub next: Option<CallInfoIdx>,
pub u: CallInfoFrame,
pub u2: CallInfoExtra,
pub nresults: i16,
pub callstatus: u16,
}
#[derive(Clone, Copy)]
pub enum CallInfoFrame {
Lua {
savedpc: u32,
trap: bool,
nextraargs: i32,
},
C {
k: Option<LuaKFunction>,
old_errfunc: isize,
ctx: isize,
},
}
pub type LuaKFunction = fn(&mut LuaState, status: i32, ctx: isize) -> Result<usize, LuaError>;
#[derive(Default, Clone, Copy)]
pub struct CallInfoExtra {
pub value: i32,
pub ftransfer: u16,
pub ntransfer: u16,
}
impl CallInfoFrame {
pub fn c_default() -> Self {
CallInfoFrame::C {
k: None,
old_errfunc: 0,
ctx: 0,
}
}
pub fn lua_default() -> Self {
CallInfoFrame::Lua {
savedpc: 0,
trap: false,
nextraargs: 0,
}
}
}
impl Default for CallInfo {
fn default() -> Self {
CallInfo {
func: StackIdx(0),
top: StackIdx(0),
previous: None,
next: None,
u: CallInfoFrame::c_default(),
u2: CallInfoExtra::default(),
nresults: 0,
callstatus: 0,
}
}
}
impl CallInfo {
pub fn is_lua(&self) -> bool { (self.callstatus & CIST_C) == 0 }
pub fn is_lua_code(&self) -> bool { self.is_lua() }
pub fn is_vararg_func(&self) -> bool { false }
pub fn saved_pc(&self) -> u32 {
if let CallInfoFrame::Lua { savedpc, .. } = self.u { savedpc } else { 0 }
}
pub fn set_saved_pc(&mut self, pc: u32) {
if let CallInfoFrame::Lua { ref mut savedpc, .. } = self.u { *savedpc = pc; }
}
pub fn nextra_args(&self) -> i32 {
if let CallInfoFrame::Lua { nextraargs, .. } = self.u { nextraargs } else { 0 }
}
pub fn transfer_ftransfer(&self) -> u16 { self.u2.ftransfer }
pub fn transfer_ntransfer(&self) -> u16 { self.u2.ntransfer }
pub fn set_trap(&mut self, t: bool) {
if let CallInfoFrame::Lua { ref mut trap, .. } = self.u { *trap = t; }
}
pub fn recover_status(&self) -> i32 {
((self.callstatus >> CIST_RECST) & 7) as i32
}
pub fn set_recover_status<T: Into<i32>>(&mut self, status: T) {
let st = (status.into() & 7) as u16;
self.callstatus = (self.callstatus & !(7u16 << CIST_RECST)) | (st << CIST_RECST);
}
pub fn get_oah(&self) -> bool { (self.callstatus & CIST_OAH) != 0 }
pub fn set_oah(&mut self, allow: bool) {
self.callstatus = (self.callstatus & !CIST_OAH) | (if allow { CIST_OAH } else { 0 });
}
pub fn u_c_old_errfunc(&self) -> isize {
if let CallInfoFrame::C { old_errfunc, .. } = self.u { old_errfunc } else { 0 }
}
pub fn u_c_ctx(&self) -> isize {
if let CallInfoFrame::C { ctx, .. } = self.u { ctx } else { 0 }
}
pub fn u_c_k(&self) -> Option<LuaKFunction> {
if let CallInfoFrame::C { k, .. } = self.u { k } else { None }
}
pub fn set_u_c_k(&mut self, k: Option<LuaKFunction>) {
if let CallInfoFrame::C { k: ref mut slot, .. } = self.u {
*slot = k;
}
}
pub fn set_u_c_ctx(&mut self, ctx: isize) {
if let CallInfoFrame::C { ctx: ref mut slot, .. } = self.u {
*slot = ctx;
}
}
pub fn set_u_c_old_errfunc(&mut self, old_errfunc: isize) {
if let CallInfoFrame::C { old_errfunc: ref mut slot, .. } = self.u {
*slot = old_errfunc;
}
}
pub fn set_u2_funcidx(&mut self, idx: i32) {
self.u2.value = idx;
}
}
pub trait LuaValueExt {
fn base_type(&self) -> lua_types::LuaType;
fn to_number_no_strconv(&self) -> Option<f64>;
fn to_number_with_strconv(&self) -> Option<f64>;
fn to_integer_no_strconv(&self) -> Option<i64>;
fn to_integer_with_strconv(&self) -> Option<i64>;
fn full_type_tag(&self) -> u8;
}
impl LuaValueExt for LuaValue {
fn base_type(&self) -> lua_types::LuaType { self.type_tag() }
fn to_number_no_strconv(&self) -> Option<f64> {
match self {
LuaValue::Float(f) => Some(*f),
LuaValue::Int(i) => Some(*i as f64),
_ => None,
}
}
fn to_number_with_strconv(&self) -> Option<f64> {
if let Some(n) = self.to_number_no_strconv() { return Some(n); }
if let LuaValue::Str(s) = self {
let mut tmp = LuaValue::Nil;
let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
if sz == 0 { return None; }
return match tmp {
LuaValue::Int(i) => Some(i as f64),
LuaValue::Float(f) => Some(f),
_ => None,
};
}
None
}
fn to_integer_no_strconv(&self) -> Option<i64> {
match self {
LuaValue::Int(i) => Some(*i),
LuaValue::Float(f) if f.fract() == 0.0 && f.is_finite() => {
let min_f = i64::MIN as f64;
let max_plus1_f = -(i64::MIN as f64);
if *f >= min_f && *f < max_plus1_f {
Some(*f as i64)
} else {
None
}
}
_ => None,
}
}
fn to_integer_with_strconv(&self) -> Option<i64> {
if let Some(i) = self.to_integer_no_strconv() { return Some(i); }
if let LuaValue::Str(s) = self {
let mut tmp = LuaValue::Nil;
let sz = crate::object::str2num(s.as_bytes(), &mut tmp);
if sz == 0 { return None; }
return tmp.to_integer_no_strconv();
}
None
}
fn full_type_tag(&self) -> u8 {
match self {
LuaValue::Nil => 0x00,
LuaValue::Bool(false) => 0x01,
LuaValue::Bool(true) => 0x11,
LuaValue::Int(_) => 0x03,
LuaValue::Float(_) => 0x13,
LuaValue::Str(s) if s.is_short() => 0x04,
LuaValue::Str(_) => 0x14,
LuaValue::LightUserData(_) => 0x02,
LuaValue::Table(_) => 0x05,
LuaValue::Function(LuaClosure::Lua(_)) => 0x06,
LuaValue::Function(LuaClosure::LightC(_)) => 0x16,
LuaValue::Function(LuaClosure::C(_)) => 0x26,
LuaValue::UserData(_) => 0x07,
LuaValue::Thread(_) => 0x08,
}
}
}
pub trait LuaTypeExt {
fn type_name(&self) -> &'static [u8];
}
impl LuaTypeExt for lua_types::LuaType {
fn type_name(&self) -> &'static [u8] {
use lua_types::LuaType::*;
match self {
None => b"no value",
Nil => b"nil",
Boolean => b"boolean",
LightUserData => b"userdata",
Number => b"number",
String => b"string",
Table => b"table",
Function => b"function",
UserData => b"userdata",
Thread => b"thread",
}
}
}
pub trait StackIdxExt {
fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32;
fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32;
fn raw(self) -> u32;
}
impl StackIdxExt for StackIdx {
#[inline(always)]
fn saturating_sub(self, n: impl Into<StackIdxConv>) -> u32 { self.0.saturating_sub(n.into().0.0) }
#[inline(always)]
fn wrapping_sub(self, n: impl Into<StackIdxConv>) -> u32 { self.0.wrapping_sub(n.into().0.0) }
#[inline(always)]
fn raw(self) -> u32 { self.0 }
}
pub trait LuaTableRefExt {
fn metatable(&self) -> Option<GcRef<LuaTable>>;
fn as_ptr(&self) -> *const ();
fn get(&self, _k: &LuaValue) -> LuaValue;
fn get_int(&self, _k: i64) -> LuaValue;
fn get_short_str(&self, _k: &GcRef<LuaString>) -> LuaValue;
fn raw_set(&self, _state: &mut LuaState, _k: LuaValue, _v: LuaValue) -> Result<(), LuaError>;
fn raw_set_int(&self, _state: &mut LuaState, _k: i64, _v: LuaValue) -> Result<(), LuaError>;
fn invalidate_tm_cache(&self);
fn resize(&self, _state: &mut LuaState, _na: usize, _nh: usize) -> Result<(), LuaError>;
fn next(&self, _k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError>;
}
impl LuaTableRefExt for GcRef<LuaTable> {
#[inline]
fn metatable(&self) -> Option<GcRef<LuaTable>> { (**self).metatable() }
#[inline]
fn as_ptr(&self) -> *const () { GcRef::identity(self) as *const () }
#[inline]
fn get(&self, k: &LuaValue) -> LuaValue { (**self).get(k) }
#[inline]
fn get_int(&self, k: i64) -> LuaValue { (**self).get_int(k) }
#[inline]
fn get_short_str(&self, k: &GcRef<LuaString>) -> LuaValue { (**self).get_short_str(k) }
#[inline]
fn raw_set(&self, _state: &mut LuaState, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
let before = (**self).buffer_bytes();
let result = (**self).try_raw_set(k, v);
if result.is_ok() {
account_table_buffer_delta(self, before);
}
result
}
#[inline]
fn raw_set_int(&self, _state: &mut LuaState, k: i64, v: LuaValue) -> Result<(), LuaError> {
let before = (**self).buffer_bytes();
let result = (**self).try_raw_set_int(k, v);
if result.is_ok() {
account_table_buffer_delta(self, before);
}
result
}
fn invalidate_tm_cache(&self) {}
fn resize(&self, _state: &mut LuaState, na: usize, nh: usize) -> Result<(), LuaError> {
let before = (**self).buffer_bytes();
let na32 = na.min(u32::MAX as usize) as u32;
let nh32 = nh.min(u32::MAX as usize) as u32;
let result = (**self).resize(na32, nh32);
if result.is_ok() {
account_table_buffer_delta(self, before);
}
result
}
fn next(&self, k: LuaValue) -> Result<Option<(LuaValue, LuaValue)>, LuaError> {
(**self).try_next_pair(&k)
}
}
#[inline]
fn account_table_buffer_delta(t: &GcRef<LuaTable>, before: usize) {
let after = (**t).buffer_bytes();
if after > before {
t.account_buffer((after - before) as isize);
} else if before > after {
t.account_buffer(-((before - after) as isize));
}
}
pub trait LuaUserDataRefExt {
fn metatable(&self) -> Option<GcRef<LuaTable>>;
fn set_metatable(&self, mt: Option<GcRef<LuaTable>>);
fn as_ptr(&self) -> *const ();
fn len(&self) -> usize;
}
impl LuaUserDataRefExt for GcRef<LuaUserData> {
fn metatable(&self) -> Option<GcRef<LuaTable>> { (**self).metatable() }
fn set_metatable(&self, mt: Option<GcRef<LuaTable>>) { (**self).set_metatable(mt); }
fn as_ptr(&self) -> *const () { GcRef::identity(self) as *const () }
fn len(&self) -> usize { self.0.data.len() }
}
pub trait LuaStringRefExt {
fn is_white(&self) -> bool;
fn hash(&self) -> u32;
fn as_gc_ref(&self) -> GcRef<LuaString>;
}
impl LuaStringRefExt for GcRef<LuaString> {
fn is_white(&self) -> bool { false }
fn hash(&self) -> u32 { self.0.hash() }
fn as_gc_ref(&self) -> GcRef<LuaString> { self.clone() }
}
pub trait LuaLClosureRefExt {
fn proto(&self) -> &GcRef<LuaProto>;
fn nupvalues(&self) -> usize;
}
impl LuaLClosureRefExt for GcRef<lua_types::closure::LuaLClosure> {
fn proto(&self) -> &GcRef<LuaProto> { &self.0.proto }
fn nupvalues(&self) -> usize { self.0.upvals.len() }
}
pub trait LuaClosureExt {
fn nupvalues(&self) -> usize;
}
impl LuaClosureExt for LuaClosure {
fn nupvalues(&self) -> usize {
match self {
LuaClosure::Lua(l) => l.0.upvals.len(),
LuaClosure::C(c) => c.0.upvalues.borrow().len(),
LuaClosure::LightC(_) => 0,
}
}
}
pub trait LuaProtoExt {
fn source_bytes(&self) -> &[u8];
fn source_string(&self) -> Option<&GcRef<LuaString>>;
}
impl LuaProtoExt for LuaProto {
fn source_bytes(&self) -> &[u8] {
match &self.source { Some(s) => s.0.as_bytes(), None => &[] }
}
fn source_string(&self) -> Option<&GcRef<LuaString>> { self.source.as_ref() }
}
pub trait Collectable: std::fmt::Debug {}
impl std::fmt::Debug for LuaState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "LuaState")
}
}
impl Collectable for LuaState {}
pub type ParserHook = fn(
state: &mut LuaState,
source: &[u8],
name: &[u8],
firstchar: i32,
) -> Result<GcRef<lua_types::closure::LuaLClosure>, LuaError>;
pub type FileLoaderHook = fn(filename: &[u8]) -> Result<Vec<u8>, LuaError>;
pub type FileOpenHook =
fn(filename: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
pub type OutputHook = fn(bytes: &[u8]) -> std::io::Result<()>;
pub type InputHook = fn(buf: &mut [u8]) -> std::io::Result<usize>;
pub type EnvHook = fn(name: &[u8]) -> Option<Vec<u8>>;
pub type UnixTimeHook = fn() -> i64;
pub type CpuClockHook = fn() -> f64;
pub type LocalOffsetHook = fn(timestamp: i64) -> i64;
pub type EntropyHook = fn() -> u64;
pub type TempNameHook = fn() -> Result<Vec<u8>, LuaError>;
pub type PopenHook =
fn(cmd: &[u8], mode: &[u8]) -> Result<Box<dyn lua_types::LuaFileHandle>, LuaError>;
pub type FileRemoveHook = fn(filename: &[u8]) -> Result<(), LuaError>;
pub type FileRenameHook = fn(from: &[u8], to: &[u8]) -> Result<(), LuaError>;
#[derive(Clone, Copy, Debug)]
pub enum OsExecuteReason {
Exit,
Signal,
}
#[derive(Debug)]
pub struct OsExecuteResult {
pub success: bool,
pub reason: OsExecuteReason,
pub code: i32,
}
pub type OsExecuteHook = fn(cmd: &[u8]) -> Result<OsExecuteResult, LuaError>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct DynLibId(pub u64);
pub enum DynamicSymbol {
RustNative(LuaCFunction),
LuaCAbi(*const ()),
Unsupported { reason: Vec<u8> },
}
pub type DynLibLoadHook =
fn(state: &mut LuaState, path: &[u8], see_global: bool) -> Result<DynLibId, LuaError>;
pub type DynLibSymbolHook =
fn(state: &mut LuaState, handle: DynLibId, symbol: &[u8]) -> Result<DynamicSymbol, LuaError>;
pub type DynLibUnloadHook = fn(handle: DynLibId);
pub struct ThreadRegistryEntry {
pub state: Rc<RefCell<LuaState>>,
pub value: GcRef<lua_types::value::LuaThread>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExternalRootKey {
index: usize,
generation: u64,
}
#[derive(Debug)]
struct ExternalRootSlot {
value: Option<LuaValue>,
generation: u64,
}
#[derive(Debug, Default)]
pub struct ExternalRootSet {
slots: Vec<ExternalRootSlot>,
free: Vec<usize>,
live: usize,
}
impl ExternalRootSet {
pub fn insert(&mut self, value: LuaValue) -> ExternalRootKey {
if let Some(index) = self.free.pop() {
let slot = &mut self.slots[index];
debug_assert!(
slot.value.is_none(),
"free external-root slot is occupied"
);
slot.generation = slot.generation.wrapping_add(1).max(1);
slot.value = Some(value);
self.live += 1;
ExternalRootKey {
index,
generation: slot.generation,
}
} else {
let index = self.slots.len();
self.slots.push(ExternalRootSlot {
value: Some(value),
generation: 1,
});
self.live += 1;
ExternalRootKey {
index,
generation: 1,
}
}
}
pub fn get(&self, key: ExternalRootKey) -> Option<&LuaValue> {
let slot = self.slots.get(key.index)?;
if slot.generation == key.generation {
slot.value.as_ref()
} else {
None
}
}
pub fn replace(&mut self, key: ExternalRootKey, value: LuaValue) -> Option<LuaValue> {
let slot = self.slots.get_mut(key.index)?;
if slot.generation != key.generation || slot.value.is_none() {
return None;
}
slot.value.replace(value)
}
pub fn remove(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
let slot = self.slots.get_mut(key.index)?;
if slot.generation != key.generation {
return None;
}
let old = slot.value.take()?;
self.free.push(key.index);
self.live -= 1;
Some(old)
}
pub fn iter_values(&self) -> impl Iterator<Item = &LuaValue> {
self.slots.iter().filter_map(|slot| slot.value.as_ref())
}
pub fn len(&self) -> usize {
self.live
}
pub fn is_empty(&self) -> bool {
self.live == 0
}
pub fn vacant_len(&self) -> usize {
self.free.len()
}
}
pub struct GlobalState {
pub parser_hook: Option<ParserHook>,
pub cli_argv: Option<Vec<Vec<u8>>>,
pub cli_preload: Option<fn(&mut LuaState) -> Result<(), LuaError>>,
pub lua_version: lua_types::LuaVersion,
pub file_loader_hook: Option<FileLoaderHook>,
pub file_open_hook: Option<FileOpenHook>,
pub stdout_hook: Option<OutputHook>,
pub stderr_hook: Option<OutputHook>,
pub stdin_hook: Option<InputHook>,
pub env_hook: Option<EnvHook>,
pub unix_time_hook: Option<UnixTimeHook>,
pub cpu_clock_hook: Option<CpuClockHook>,
pub local_offset_hook: Option<LocalOffsetHook>,
pub entropy_hook: Option<EntropyHook>,
pub temp_name_hook: Option<TempNameHook>,
pub popen_hook: Option<PopenHook>,
pub file_remove_hook: Option<FileRemoveHook>,
pub file_rename_hook: Option<FileRenameHook>,
pub os_execute_hook: Option<OsExecuteHook>,
pub dynlib_load_hook: Option<DynLibLoadHook>,
pub dynlib_symbol_hook: Option<DynLibSymbolHook>,
pub dynlib_unload_hook: Option<DynLibUnloadHook>,
pub sandbox: SandboxLimits,
pub gc_debt: isize,
pub gc_estimate: usize,
pub lastatomic: usize,
pub strt: StringPool,
pub l_registry: LuaValue,
pub external_roots: ExternalRootSet,
pub globals: LuaValue,
pub loaded: LuaValue,
pub nilvalue: LuaValue,
pub seed: u32,
pub currentwhite: u8,
pub gcstate: u8,
pub gckind: u8,
pub gcstopem: bool,
pub genminormul: u8,
pub genmajormul: u8,
pub gcstp: u8,
pub gcemergency: bool,
pub gcpause: u8,
pub gcstepmul: u8,
pub gcstepsize: u8,
pub gc55_params: [i64; 6],
pub sweepgc_cursor: usize,
pub weak_tables_registry: lua_gc::WeakRegistry<WeakTableEntry>,
pub finalizers: lua_gc::FinalizerRegistry<FinalizerObject>,
pub gc_finalizer_error: Option<LuaValue>,
pub twups: Vec<GcRef<LuaState>>,
pub panic: Option<LuaCFunction>,
pub mainthread: Option<GcRef<LuaState>>,
pub threads: std::collections::HashMap<u64, ThreadRegistryEntry>,
pub main_thread_value: GcRef<lua_types::value::LuaThread>,
pub current_thread_id: u64,
pub main_thread_id: u64,
pub next_thread_id: u64,
pub memerrmsg: GcRef<LuaString>,
pub tmname: Vec<GcRef<LuaString>>,
pub mt: [Option<GcRef<LuaTable>>; LUA_NUMTYPES],
pub strcache: [[GcRef<LuaString>; STRCACHE_M]; STRCACHE_N],
pub interned_lt: std::collections::HashMap<Box<[u8]>, GcRef<LuaString>>,
pub warnf: Option<Box<dyn FnMut(&[u8], bool)>>,
pub warn_mode: WarnMode,
pub test_warn_enabled: bool,
pub test_warn_on: bool,
pub test_warn_mode: TestWarnMode,
pub test_warn_last_to_cont: bool,
pub test_warn_buffer: Vec<u8>,
pub c_functions: Vec<LuaCallable>,
pub heap: lua_gc::Heap,
pub cross_thread_upvals: std::collections::HashMap<(u64, StackIdx), LuaValue>,
pub suspended_parent_stacks: Vec<Vec<LuaValue>>,
pub suspended_parent_open_upvals: Vec<Vec<GcRef<UpVal>>>,
}
const SANDBOX_COUNT_MASK: u8 = 1 << 3;
pub const SANDBOX_TRIP_NONE: u8 = 0;
pub const SANDBOX_TRIP_INSTRUCTIONS: u8 = 1;
pub const SANDBOX_TRIP_MEMORY: u8 = 2;
#[derive(Default)]
pub struct SandboxLimits {
pub interval: std::cell::Cell<i32>,
pub instr_limited: std::cell::Cell<bool>,
pub instr_remaining: std::cell::Cell<u64>,
pub instr_limit: std::cell::Cell<u64>,
pub mem_limit: std::cell::Cell<Option<usize>>,
pub tripped: std::cell::Cell<u8>,
pub aborting: std::cell::Cell<bool>,
}
impl GlobalState {
pub fn sandbox_active(&self) -> bool {
self.sandbox.interval.get() != 0
}
pub fn total_bytes(&self) -> usize {
self.heap.bytes_used().max(1)
}
pub fn get_thread(&self, id: u64) -> Option<&ThreadRegistryEntry> {
self.threads.get(&id)
}
pub fn thread_value_for(&self, id: u64) -> Option<GcRef<lua_types::value::LuaThread>> {
if id == self.main_thread_id {
Some(self.main_thread_value.clone())
} else {
self.threads.get(&id).map(|e| e.value.clone())
}
}
pub fn is_complete(&self) -> bool {
matches!(self.nilvalue, LuaValue::Nil)
}
pub fn current_white(&self) -> u8 {
self.currentwhite
}
pub fn other_white(&self) -> u8 {
self.currentwhite ^ 0x03
}
pub fn is_gen_mode(&self) -> bool {
self.gckind == GcKind::Generational as u8 || self.lastatomic != 0
}
pub fn gc_running(&self) -> bool {
self.gcstp == 0
}
pub fn keep_invariant(&self) -> bool {
self.heap.gc_state().is_invariant()
}
pub fn is_sweep_phase(&self) -> bool {
self.heap.gc_state().is_sweep()
}
pub fn gc_debt(&self) -> isize { self.gc_debt }
pub fn set_gc_debt(&mut self, d: isize) { self.gc_debt = d; }
pub fn gc_at_pause(&self) -> bool { self.heap.gc_state().is_pause() }
fn get_gc_param(p: u8) -> i32 { (p as i32) * 4 }
fn set_gc_param_slot(slot: &mut u8, p: i32) { *slot = (p / 4) as u8; }
pub fn gc_pause_param(&self) -> i32 { Self::get_gc_param(self.gcpause) }
pub fn set_gc_pause_param(&mut self, p: i32) { Self::set_gc_param_slot(&mut self.gcpause, p); }
pub fn gc_stepmul_param(&self) -> i32 { Self::get_gc_param(self.gcstepmul) }
pub fn set_gc_stepmul_param(&mut self, p: i32) { Self::set_gc_param_slot(&mut self.gcstepmul, p); }
pub fn gc_genmajormul_param(&self) -> i32 { Self::get_gc_param(self.genmajormul) }
pub fn set_gc_genmajormul(&mut self, p: i32) { Self::set_gc_param_slot(&mut self.genmajormul, p); }
pub fn gc55_param(&mut self, idx: usize, value: i64) -> i64 {
let old = self.gc55_params[idx];
if value >= 0 {
self.gc55_params[idx] = value;
}
old
}
pub fn gc_stop_flags(&self) -> u8 { self.gcstp }
pub fn set_gc_stop_flags(&mut self, f: u8) { self.gcstp = f; }
pub fn stop_gc_internal(&mut self) -> u8 {
let old = self.gcstp;
self.gcstp |= GCSTPGC;
old
}
pub fn set_gc_stop_user(&mut self) {
self.gcstp = GCSTPUSR;
}
pub fn clear_gc_stop(&mut self) { self.gcstp = 0; }
pub fn is_gc_running(&self) -> bool { self.gcstp == 0 }
pub fn is_gc_stopped_internally(&self) -> bool { (self.gcstp & GCSTPGC) != 0 }
pub fn tm_name<T: TmIndex>(&self, tm: T) -> Option<GcRef<LuaString>> {
self.tmname.get(tm.tm_index()).cloned()
}
}
pub trait TmIndex: Copy {
fn tm_index(self) -> usize;
}
impl TmIndex for lua_types::tagmethod::TagMethod {
fn tm_index(self) -> usize { self as u8 as usize }
}
impl TmIndex for crate::tagmethods::TagMethod {
fn tm_index(self) -> usize { self as u8 as usize }
}
impl TmIndex for usize {
fn tm_index(self) -> usize { self }
}
impl TmIndex for u8 {
fn tm_index(self) -> usize { self as usize }
}
use lua_types::tagmethod::TagMethod;
pub struct LuaState {
pub status: u8,
pub allowhook: bool,
pub nci: u32,
pub top: StackIdx,
pub stack_last: StackIdx,
pub stack: Vec<StackValue>,
pub ci: CallInfoIdx,
pub call_info: Vec<CallInfo>,
pub openupval: Vec<GcRef<UpVal>>,
pub tbclist: Vec<StackIdx>,
pub(crate) global: Rc<RefCell<GlobalState>>,
pub hook: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>,
pub hookmask: u8,
pub basehookcount: i32,
pub hookcount: i32,
pub errfunc: isize,
pub n_ccalls: u32,
pub oldpc: u32,
pub marked: u8,
pub cached_thread_id: u64,
pub gc_check_needed: bool,
}
impl LuaState {
pub fn global(&self) -> std::cell::Ref<'_, GlobalState> {
self.global.borrow()
}
pub fn global_mut(&self) -> std::cell::RefMut<'_, GlobalState> {
self.global.borrow_mut()
}
pub fn global_rc(&self) -> Rc<RefCell<GlobalState>> {
Rc::clone(&self.global)
}
pub fn enable_test_warning_handler(&mut self) -> Result<(), LuaError> {
{
let mut g = self.global_mut();
g.test_warn_enabled = true;
g.test_warn_on = false;
g.test_warn_mode = TestWarnMode::Normal;
g.test_warn_last_to_cont = false;
g.test_warn_buffer.clear();
}
self.push(LuaValue::Bool(false));
crate::api::set_global(self, b"_WARN")
}
pub fn c_calls(&self) -> u32 {
self.n_ccalls & 0xffff
}
pub fn inc_nny(&mut self) {
self.n_ccalls += 0x10000;
}
pub fn dec_nny(&mut self) {
self.n_ccalls -= 0x10000;
}
pub fn is_yieldable(&self) -> bool {
(self.n_ccalls & 0xffff0000) == 0
}
pub fn reset_hook_count(&mut self) {
self.hookcount = self.basehookcount;
}
pub fn install_sandbox_limits(
&mut self,
interval: i32,
instr_limit: Option<u64>,
mem_limit: Option<usize>,
) {
let interval = interval.max(1);
{
let g = self.global();
g.sandbox.interval.set(interval);
g.sandbox.instr_limited.set(instr_limit.is_some());
g.sandbox.instr_remaining.set(instr_limit.unwrap_or(0));
g.sandbox.instr_limit.set(instr_limit.unwrap_or(0));
g.sandbox.mem_limit.set(mem_limit);
g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
}
self.hookmask |= SANDBOX_COUNT_MASK;
self.basehookcount = interval;
self.hookcount = interval;
crate::debug::arm_traps(self);
}
pub fn sandbox_charge_interval(&self) -> Option<LuaError> {
let interval = self.global().sandbox.interval.get();
self.sandbox_charge(interval as u64)
}
pub fn sandbox_charge(&self, amount: u64) -> Option<LuaError> {
let g = self.global();
if g.sandbox.interval.get() == 0 {
return None;
}
if g.sandbox.instr_limited.get() {
let rem = g.sandbox.instr_remaining.get().saturating_sub(amount);
g.sandbox.instr_remaining.set(rem);
if rem == 0 {
g.sandbox.tripped.set(SANDBOX_TRIP_INSTRUCTIONS);
g.sandbox.aborting.set(true);
return Some(LuaError::runtime(format_args!(
"sandbox: instruction budget exhausted"
)));
}
}
if let Some(limit) = g.sandbox.mem_limit.get() {
if g.total_bytes() > limit {
g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
g.sandbox.aborting.set(true);
return Some(LuaError::runtime(format_args!(
"sandbox: memory limit exceeded"
)));
}
}
None
}
pub fn sandbox_reserve(&self, additional: usize) -> Option<LuaError> {
let g = self.global();
if g.sandbox.interval.get() == 0 {
return None;
}
if let Some(limit) = g.sandbox.mem_limit.get() {
let projected = g.total_bytes().saturating_add(additional);
if projected > limit {
g.sandbox.tripped.set(SANDBOX_TRIP_MEMORY);
g.sandbox.aborting.set(true);
return Some(LuaError::runtime(format_args!(
"sandbox: memory limit exceeded"
)));
}
}
None
}
pub fn sandbox_match_step_limit(&self) -> u64 {
let g = self.global();
if g.sandbox.interval.get() != 0 && g.sandbox.instr_limited.get() {
g.sandbox.instr_remaining.get()
} else {
0
}
}
pub fn sandbox_aborting(&self) -> bool {
self.global().sandbox.aborting.get()
}
pub fn sandbox_instr_limited(&self) -> bool {
self.global().sandbox.instr_limited.get()
}
pub fn sandbox_instr_remaining(&self) -> u64 {
self.global().sandbox.instr_remaining.get()
}
pub fn sandbox_instr_limit(&self) -> u64 {
self.global().sandbox.instr_limit.get()
}
pub fn sandbox_tripped_code(&self) -> u8 {
self.global().sandbox.tripped.get()
}
pub fn sandbox_reset(&self) {
let g = self.global();
if g.sandbox.instr_limited.get() {
g.sandbox.instr_remaining.set(g.sandbox.instr_limit.get());
}
g.sandbox.tripped.set(SANDBOX_TRIP_NONE);
g.sandbox.aborting.set(false);
}
pub fn stack_size(&self) -> usize {
self.stack_last.0 as usize
}
#[inline(always)]
pub fn push(&mut self, val: LuaValue) {
let top = self.top.0 as usize;
if top < self.stack.len() {
self.stack[top] = StackValue { val, tbc_delta: 0 };
} else {
self.stack.push(StackValue { val, tbc_delta: 0 });
}
self.top = StackIdx(self.top.0 + 1);
}
#[inline(always)]
pub fn pop(&mut self) -> LuaValue {
if self.top.0 == 0 {
return LuaValue::Nil;
}
self.top = StackIdx(self.top.0 - 1);
self.stack[self.top.0 as usize].val.clone()
}
#[inline(always)]
pub fn stack_val(&self, idx: StackIdx) -> &LuaValue {
&self.stack[idx.0 as usize].val
}
#[inline(always)]
pub fn set_stack_val(&mut self, idx: StackIdx, val: LuaValue) {
self.stack[idx.0 as usize].val = val;
}
pub fn gc(&mut self) -> GcHandle<'_> {
GcHandle { _state: self }
}
pub fn external_root_value(&mut self, value: LuaValue) -> ExternalRootKey {
self.global_mut().external_roots.insert(value)
}
pub fn external_rooted_value(&self, key: ExternalRootKey) -> Option<LuaValue> {
self.global().external_roots.get(key).cloned()
}
pub fn external_replace_root(
&mut self,
key: ExternalRootKey,
value: LuaValue,
) -> Option<LuaValue> {
self.global_mut().external_roots.replace(key, value)
}
pub fn external_unroot_value(&mut self, key: ExternalRootKey) -> Option<LuaValue> {
self.global_mut().external_roots.remove(key)
}
pub fn try_external_unroot_value(
&mut self,
key: ExternalRootKey,
) -> std::result::Result<Option<LuaValue>, std::cell::BorrowMutError> {
self.global
.try_borrow_mut()
.map(|mut global| global.external_roots.remove(key))
}
pub fn new_table(&mut self) -> GcRef<LuaTable> {
self.mark_gc_check_needed();
GcRef::new(LuaTable::placeholder())
}
pub fn new_table_with_sizes(
&mut self,
array_size: u32,
hash_size: u32,
) -> Result<GcRef<LuaTable>, LuaError> {
self.mark_gc_check_needed();
let t = GcRef::new(LuaTable::placeholder());
self.table_resize(&t, array_size as usize, hash_size as usize)?;
Ok(t)
}
pub fn intern_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
if bytes.len() <= crate::string::MAX_SHORT_LEN {
if let Some(existing) = self.global().interned_lt.get(bytes) {
return Ok(existing.clone());
}
self.mark_gc_check_needed();
let new_ref = GcRef::new(LuaString::from_bytes(bytes.to_vec()));
new_ref.account_buffer(new_ref.buffer_bytes() as isize);
self.global_mut()
.interned_lt
.insert(bytes.to_vec().into_boxed_slice(), new_ref.clone());
Ok(new_ref)
} else {
self.mark_gc_check_needed();
let new_ref = GcRef::new(LuaString::from_bytes(bytes.to_vec()));
new_ref.account_buffer(new_ref.buffer_bytes() as isize);
Ok(new_ref)
}
}
#[inline(always)]
pub fn top_idx(&self) -> StackIdx {
self.top
}
}
impl LuaState {
#[inline(always)]
pub fn get_at(&self, idx: impl Into<StackIdxConv>) -> LuaValue {
let i: StackIdx = idx.into().0;
match self.stack.get(i.0 as usize) {
Some(slot) => slot.val.clone(),
None => LuaValue::Nil,
}
}
#[inline(always)]
pub fn set_at(&mut self, idx: impl Into<StackIdxConv>, v: LuaValue) {
let i: StackIdx = idx.into().0;
self.stack[i.0 as usize].val = v;
}
pub fn clear_stack_range(&mut self, start: StackIdx, end: StackIdx) {
if end.0 <= start.0 {
return;
}
let end_u = end.0 as usize;
if end_u > self.stack.len() {
self.stack.resize_with(end_u, StackValue::default);
}
for i in start.0..end.0 {
self.stack[i as usize].val = LuaValue::Nil;
self.stack[i as usize].tbc_delta = 0;
}
}
#[inline(always)]
pub fn get_int_at(&self, idx: impl Into<StackIdxConv>) -> Option<i64> {
let i: StackIdx = idx.into().0;
match self.stack.get(i.0 as usize) {
Some(slot) => match &slot.val {
LuaValue::Int(v) => Some(*v),
_ => None,
},
None => None,
}
}
#[inline(always)]
pub fn get_int_pair_at(
&self,
rb: impl Into<StackIdxConv>,
rc: impl Into<StackIdxConv>,
) -> Option<(i64, i64)> {
let rb: StackIdx = rb.into().0;
let rc: StackIdx = rc.into().0;
match (
self.stack[rb.0 as usize].val,
self.stack[rc.0 as usize].val,
) {
(LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib, ic)),
_ => None,
}
}
#[inline(always)]
pub fn get_num_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
let i: StackIdx = idx.into().0;
match self.stack.get(i.0 as usize) {
Some(slot) => match &slot.val {
LuaValue::Float(f) => Some(*f),
LuaValue::Int(v) => Some(*v as f64),
_ => None,
},
None => None,
}
}
#[inline(always)]
pub fn get_float_at(&self, idx: impl Into<StackIdxConv>) -> Option<f64> {
let i: StackIdx = idx.into().0;
match self.stack.get(i.0 as usize) {
Some(slot) => match &slot.val {
LuaValue::Float(f) => Some(*f),
_ => None,
},
None => None,
}
}
#[inline(always)]
pub fn get_num_pair_at(
&self,
rb: impl Into<StackIdxConv>,
rc: impl Into<StackIdxConv>,
) -> Option<(f64, f64)> {
let rb: StackIdx = rb.into().0;
let rc: StackIdx = rc.into().0;
match (
self.stack[rb.0 as usize].val,
self.stack[rc.0 as usize].val,
) {
(LuaValue::Float(nb), LuaValue::Float(nc)) => Some((nb, nc)),
(LuaValue::Int(ib), LuaValue::Int(ic)) => Some((ib as f64, ic as f64)),
(LuaValue::Int(ib), LuaValue::Float(nc)) => Some((ib as f64, nc)),
(LuaValue::Float(nb), LuaValue::Int(ic)) => Some((nb, ic as f64)),
_ => None,
}
}
#[inline(always)]
pub fn set_top(&mut self, idx: impl Into<StackIdxConv>) {
let new_top: StackIdx = idx.into().0;
let new_top_u = new_top.0 as usize;
if new_top_u > self.stack.len() {
self.stack.resize_with(new_top_u, StackValue::default);
}
self.top = new_top;
}
#[inline(always)]
pub fn set_top_idx(&mut self, idx: impl Into<StackIdxConv>) {
let new_top: StackIdx = idx.into().0;
self.top = new_top;
}
#[inline(always)]
pub fn dec_top(&mut self) {
if self.top.0 > 0 {
self.top = StackIdx(self.top.0 - 1);
}
}
#[inline(always)]
pub fn pop_n(&mut self, n: usize) {
let cur = self.top.0 as usize;
let new = cur.saturating_sub(n);
self.top = StackIdx(new as u32);
}
#[inline(always)]
pub fn peek_at(&mut self, idx: impl Into<StackIdxConv>) -> LuaValue {
let i: StackIdx = idx.into().0;
match self.stack.get(i.0 as usize) {
Some(slot) => slot.val.clone(),
None => LuaValue::Nil,
}
}
#[inline(always)]
pub fn peek_top(&mut self) -> LuaValue {
if self.top.0 == 0 {
return LuaValue::Nil;
}
self.stack[(self.top.0 - 1) as usize].val.clone()
}
pub fn peek_string_at_top(&mut self) -> GcRef<LuaString> {
match self.peek_top() {
LuaValue::Str(s) => s,
_ => panic!("peek_string_at_top: top of stack is not a string"),
}
}
pub fn stack_at(&mut self, idx: impl Into<StackIdxConv>) -> &mut LuaValue {
let i: StackIdx = idx.into().0;
&mut self.stack[i.0 as usize].val
}
pub fn stack_set_nil(&mut self, idx: impl Into<StackIdxConv>) {
let i: StackIdx = idx.into().0;
let slot = i.0 as usize;
if slot < self.stack.len() {
self.stack[slot].val = LuaValue::Nil;
}
}
pub fn stack_resize(&mut self, size: usize) -> Result<(), LuaError> {
self.stack.resize_with(size, StackValue::default);
Ok(())
}
pub fn stack_available(&mut self) -> usize {
(self.stack_last.0 as usize).saturating_sub(self.top.0 as usize)
}
pub fn check_stack(&mut self, n: i32) -> Result<(), LuaError> {
let free = (self.stack_last.0 as i32) - (self.top.0 as i32);
if free <= n {
self.grow_stack(n, true)?;
}
Ok(())
}
pub fn grow_stack(&mut self, n: i32, raise_error: bool) -> Result<(), LuaError> {
crate::do_::grow_stack(self, n, raise_error).map(|_| ())
}
#[inline(always)]
pub fn get_ci(&self, idx: CallInfoIdx) -> &CallInfo { &self.call_info[idx.as_usize()] }
#[inline(always)]
pub fn get_ci_mut(&mut self, idx: CallInfoIdx) -> &mut CallInfo { &mut self.call_info[idx.as_usize()] }
#[inline(always)]
pub fn current_call_info(&self) -> &CallInfo { &self.call_info[self.ci.as_usize()] }
#[inline(always)]
pub fn current_call_info_mut(&mut self) -> &mut CallInfo { let i = self.ci.as_usize(); &mut self.call_info[i] }
#[inline(always)]
pub fn current_ci_idx(&self) -> CallInfoIdx { self.ci }
pub fn call_stack_mut(&mut self) -> &mut Vec<CallInfo> { &mut self.call_info }
#[inline(always)]
pub fn next_ci(&mut self) -> Result<CallInfoIdx, LuaError> {
match self.call_info[self.ci.as_usize()].next {
Some(idx) => Ok(idx),
None => Ok(extend_ci(self)),
}
}
#[inline(always)]
pub fn prev_ci(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> { self.call_info[idx.as_usize()].previous }
pub fn get_prev_ci(&self, idx: CallInfoIdx) -> Option<&CallInfo> {
self.call_info[idx.as_usize()]
.previous
.map(|p| &self.call_info[p.as_usize()])
}
#[inline(always)]
pub fn is_base_ci(&self, idx: CallInfoIdx) -> bool { idx.as_usize() == 0 }
#[inline(always)]
pub fn is_current_ci(&self, idx: CallInfoIdx) -> bool { idx == self.ci }
pub fn ci_next_func(&self, idx: CallInfoIdx) -> StackIdx {
let next = self.call_info[idx.as_usize()]
.next
.expect("ci_next_func: no next CallInfo");
self.call_info[next.as_usize()].func
}
#[inline(always)]
pub fn ci_top(&self, idx: CallInfoIdx) -> StackIdx { self.call_info[idx.as_usize()].top }
#[inline(always)]
pub fn ci_trap(&mut self, idx: CallInfoIdx) -> bool {
if let CallInfoFrame::Lua { trap, .. } = self.call_info[idx.as_usize()].u {
trap
} else {
false
}
}
#[inline(always)]
pub fn ci_savedpc(&self, idx: CallInfoIdx) -> u32 { self.call_info[idx.as_usize()].saved_pc() }
#[inline(always)]
pub fn set_ci_savedpc(&mut self, idx: CallInfoIdx, pc: u32) {
self.call_info[idx.as_usize()].set_saved_pc(pc);
}
#[inline(always)]
pub fn set_ci_previous(&mut self, idx: CallInfoIdx) {
self.ci = self.call_info[idx.as_usize()]
.previous
.expect("set_ci_previous: returning frame has no previous CallInfo");
}
#[inline(always)]
pub fn ci_previous(&self, idx: CallInfoIdx) -> Option<CallInfoIdx> { self.call_info[idx.as_usize()].previous }
#[inline(always)]
pub fn ci_adjust_func(&mut self, idx: CallInfoIdx, delta: i32) {
let ci = &mut self.call_info[idx.as_usize()];
ci.func = StackIdx((ci.func.0 as i32 - delta) as u32);
}
#[inline(always)]
pub fn ci_base(&self, idx: CallInfoIdx) -> StackIdx { self.call_info[idx.as_usize()].func + 1 }
#[inline(always)]
pub fn ci_is_fresh(&self, idx: CallInfoIdx) -> bool {
(self.call_info[idx.as_usize()].callstatus & CIST_FRESH) != 0
}
#[inline(always)]
pub fn ci_lua_closure(&self, idx: CallInfoIdx) -> Option<GcRef<lua_types::closure::LuaLClosure>> {
let func_idx = self.call_info[idx.as_usize()].func;
match self.get_at(func_idx) {
LuaValue::Function(lua_types::closure::LuaClosure::Lua(cl)) => Some(cl),
_ => None,
}
}
#[inline(always)]
pub fn ci_nextraargs(&self, idx: CallInfoIdx) -> i32 {
self.call_info[idx.as_usize()].nextra_args()
}
#[inline(always)]
pub fn ci_nres(&self, idx: CallInfoIdx) -> i32 {
self.call_info[idx.as_usize()].u2.value
}
#[inline(always)]
pub fn ci_nres_set(&mut self, idx: CallInfoIdx, n: i32) {
self.call_info[idx.as_usize()].u2.value = n;
}
#[inline(always)]
pub fn ci_nresults(&self, idx: CallInfoIdx) -> i32 { self.call_info[idx.as_usize()].nresults as i32 }
pub fn ci_prev_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
let pc = self.call_info[idx.as_usize()].saved_pc();
let cl = self.ci_lua_closure(idx)
.expect("ci_prev_instruction: CallInfo does not hold a Lua closure");
cl.proto.code[(pc - 1) as usize]
}
pub fn ci_prev2_instruction(&self, idx: CallInfoIdx) -> lua_types::opcode::Instruction {
let pc = self.call_info[idx.as_usize()].saved_pc();
let cl = self.ci_lua_closure(idx)
.expect("ci_prev2_instruction: CallInfo does not hold a Lua closure");
cl.proto.code[(pc - 2) as usize]
}
pub fn ci_skip_next_instruction(&mut self, idx: CallInfoIdx) {
let pc = self.call_info[idx.as_usize()].saved_pc();
self.call_info[idx.as_usize()].set_saved_pc(pc + 1);
}
pub fn ci_step_pc_back(&mut self, idx: CallInfoIdx) {
let pc = self.call_info[idx.as_usize()].saved_pc();
self.call_info[idx.as_usize()].set_saved_pc(pc - 1);
}
pub fn get_ci_pcrel(&mut self, idx: CallInfoIdx) -> u32 {
self.call_info[idx.as_usize()].saved_pc().saturating_sub(1)
}
pub fn get_ci_u2_funcidx(&mut self, idx: CallInfoIdx) -> i32 {
self.call_info[idx.as_usize()].u2.value
}
pub fn get_ci_u2_nres(&mut self, idx: CallInfoIdx) -> i32 {
self.call_info[idx.as_usize()].u2.value
}
pub fn get_ci_u2_nyield(&mut self, idx: CallInfoIdx) -> i32 {
self.call_info[idx.as_usize()].u2.value
}
pub fn get_ci_vararg_info(&mut self, idx: CallInfoIdx) -> (bool, i32, i32) {
let nextraargs = self.call_info[idx.as_usize()].nextra_args();
match self.ci_lua_closure(idx) {
Some(cl) => (cl.proto.is_vararg, nextraargs, cl.proto.numparams as i32),
None => (false, nextraargs, 0),
}
}
pub fn get_ci_lua_proto_numparams(&mut self, idx: CallInfoIdx) -> u8 {
self.ci_lua_closure(idx)
.map(|cl| cl.proto.numparams)
.unwrap_or(0)
}
pub fn set_ci_u2_nres(&mut self, idx: CallInfoIdx, n: i32) {
self.call_info[idx.as_usize()].u2.value = n;
}
pub fn set_ci_u2_nyield(&mut self, idx: CallInfoIdx, n: i32) {
self.call_info[idx.as_usize()].u2.value = n;
}
pub fn set_ci_transfer_info(&mut self, idx: CallInfoIdx, ftransfer: u16, ntransfer: u16) {
let ci = &mut self.call_info[idx.as_usize()];
ci.u2.ftransfer = ftransfer;
ci.u2.ntransfer = ntransfer;
}
pub fn shrink_ci(&mut self) { shrink_ci(self) }
pub fn check_c_stack(&mut self) -> Result<(), LuaError> { check_c_stack(self) }
pub fn status(&mut self) -> LuaStatus { LuaStatus::from_raw(self.status as i32) }
pub fn errfunc(&mut self) -> isize { self.errfunc }
pub fn old_pc(&mut self) -> u32 { self.oldpc }
pub fn set_old_pc(&mut self, pc: u32) { self.oldpc = pc; }
pub fn set_oldpc(&mut self, pc: u32) { self.oldpc = pc; }
pub fn _hook_call_noargs(&mut self) {}
pub fn hook(&self) -> Option<&Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>> {
self.hook.as_ref()
}
pub fn has_hook(&mut self) -> bool { self.hook.is_some() }
pub fn hook_count(&mut self) -> i32 { self.hookcount }
pub fn set_hook_count(&mut self, n: i32) { self.hookcount = n; }
pub fn hook_mask(&self) -> u8 { self.hookmask }
pub fn set_hook_mask(&mut self, m: u8) { self.hookmask = m; }
pub fn base_hook_count(&self) -> i32 { self.basehookcount }
pub fn set_base_hook_count(&mut self, n: i32) { self.basehookcount = n; }
pub fn set_hook(&mut self, h: Option<Box<dyn FnMut(&mut LuaState, &crate::debug::LuaDebug)>>) {
self.hook = h;
}
pub fn call_hook_event(&mut self, event: i32, line: i32) -> Result<(), LuaError> {
crate::do_::hook(self, event, line, 0, 0)
}
pub fn registry_value(&self) -> LuaValue { self.global().l_registry.clone() }
pub fn registry_get(&self, key: usize) -> LuaValue {
let reg = self.global().l_registry.clone();
match reg {
LuaValue::Table(t) => t.get(&LuaValue::Int(key as i64)),
_ => LuaValue::Nil,
}
}
pub fn new_string(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> { self.intern_or_create_str(bytes) }
pub fn new_proto(&mut self) -> GcRef<LuaProto> {
self.mark_gc_check_needed();
GcRef::new(LuaProto::placeholder())
}
pub fn new_lclosure(&mut self, proto: GcRef<LuaProto>, nupvals: usize) -> GcRef<LuaClosureLua> {
self.mark_gc_check_needed();
let mut upvals = Vec::with_capacity(nupvals);
for _ in 0..nupvals {
upvals.push(std::cell::Cell::new(self.new_upval_closed(LuaValue::Nil)));
}
let closure = GcRef::new(LuaClosureLua { proto, upvals });
closure.account_buffer(closure.buffer_bytes() as isize);
closure
}
pub fn new_upval_closed(&mut self, v: LuaValue) -> GcRef<UpVal> {
self.mark_gc_check_needed();
GcRef::new(UpVal::closed(v))
}
pub fn new_upval_open(&mut self, thread_id: usize, level: StackIdx) -> GcRef<UpVal> {
self.mark_gc_check_needed();
GcRef::new(UpVal::open(thread_id, level))
}
pub fn intern_or_create_str(&mut self, bytes: &[u8]) -> Result<GcRef<LuaString>, LuaError> {
self.intern_str(bytes)
}
pub fn new_userdata(&mut self, _size: usize, _nuvalue: usize) -> Result<GcRef<LuaUserData>, LuaError> {
Err(LuaError::runtime(format_args!("new_userdata not implemented in this Phase-B build; use new_userdata_typed instead")))
}
pub fn new_c_closure(&mut self, _f: LuaCFunction, _n: i32) -> Result<LuaClosure, LuaError> {
Err(LuaError::runtime(format_args!("new_c_closure not implemented in this Phase-B build; use push_cclosure in lua_vm::api instead")))
}
pub fn push_closure(
&mut self,
proto_idx: usize,
ci: CallInfoIdx,
base: StackIdx,
ra: StackIdx,
) -> Result<(), LuaError> {
let parent_cl = self.ci_lua_closure(ci).expect(
"push_closure: current frame is not a Lua closure",
);
let child_proto = parent_cl.proto.p[proto_idx].clone();
let nup = child_proto.upvalues.len();
let mut upvals: Vec<std::cell::Cell<GcRef<UpVal>>> = Vec::with_capacity(nup);
for i in 0..nup {
let desc = &child_proto.upvalues[i];
let uv = if desc.instack {
let level = base + desc.idx as i32;
crate::func::find_upval(self, level)
} else {
parent_cl.upval(desc.idx as usize)
};
upvals.push(std::cell::Cell::new(uv));
}
let cache_enabled = matches!(
self.global().lua_version,
lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
);
if cache_enabled {
if let Some(cached) = child_proto.cache.borrow().as_ref() {
if cached.upvals.len() == nup
&& (0..nup).all(|i| GcRef::ptr_eq(&cached.upvals[i].get(), &upvals[i].get()))
{
let reused = cached.clone();
self.set_at(ra, LuaValue::Function(LuaClosure::Lua(reused)));
return Ok(());
}
}
}
self.mark_gc_check_needed();
let new_cl = GcRef::new(LuaClosureLua {
proto: child_proto.clone(),
upvals,
});
new_cl.account_buffer(new_cl.buffer_bytes() as isize);
if cache_enabled {
*child_proto.cache.borrow_mut() = Some(new_cl.clone());
}
self.set_at(ra, LuaValue::Function(LuaClosure::Lua(new_cl)));
Ok(())
}
pub fn new_tbc_upval(&mut self, idx: StackIdx) -> Result<(), LuaError> {
crate::func::new_tbc_upval(self, idx)
}
#[inline(always)]
pub fn upvalue_get(&self, cl: &GcRef<LuaClosureLua>, n: usize) -> LuaValue {
let uv = cl.upval(n);
let (thread_id, idx) = match uv.try_open_payload() {
Some(p) => p,
None => return *uv.closed_value(),
};
let current = self.cached_thread_id;
let tid = thread_id as u64;
if tid == current {
return self.stack[idx.0 as usize].val;
}
self.upvalue_get_cross_thread(tid, idx)
}
#[cold]
#[inline(never)]
fn upvalue_get_cross_thread(&self, tid: u64, idx: StackIdx) -> LuaValue {
let entry_rc = {
let g = self.global();
g.threads.get(&tid).map(|e| e.state.clone())
};
if let Some(rc) = entry_rc {
if let Ok(home_state) = rc.try_borrow() {
return home_state.get_at(idx);
}
}
let g = self.global();
match g.cross_thread_upvals.get(&(tid, idx)) {
Some(v) => *v,
None => LuaValue::Nil,
}
}
#[inline(always)]
pub fn upvalue_set(&mut self, cl: &GcRef<LuaClosureLua>, n: usize, val: LuaValue) -> Result<(), LuaError> {
let uv = cl.upval(n);
match uv.try_open_payload() {
Some((thread_id, idx)) => {
let tid = thread_id as u64;
let current = self.cached_thread_id;
if tid == current {
self.stack[idx.0 as usize].val = val;
} else {
self.upvalue_set_cross_thread(tid, idx, val)?;
}
}
None => {
uv.set_closed_value(val);
}
}
self.gc_barrier_upval(&uv, &val);
Ok(())
}
#[cold]
#[inline(never)]
fn upvalue_set_cross_thread(
&mut self,
tid: u64,
idx: StackIdx,
val: LuaValue,
) -> Result<(), LuaError> {
let entry_rc = {
let g = self.global();
g.threads.get(&tid).map(|e| e.state.clone())
};
if let Some(rc) = entry_rc {
if let Ok(mut home_state) = rc.try_borrow_mut() {
home_state.set_at(idx, val);
return Ok(());
}
}
let mut g = self.global_mut();
g.cross_thread_upvals.insert((tid, idx), val);
Ok(())
}
pub fn protected_call_raw(&mut self, func: StackIdx, nresults: i32, errfunc: StackIdx) -> Result<(), LuaError> {
let ef = errfunc.0 as isize;
let status = crate::do_::pcall(
self,
|s| s.call_no_yield(func, nresults),
func,
ef,
);
match status {
LuaStatus::Ok => Ok(()),
LuaStatus::ErrSyntax => {
let err_val = self.get_at(func);
self.set_top(func);
Err(LuaError::Syntax(err_val))
}
LuaStatus::Yield => {
self.set_top(func);
Err(LuaError::Yield)
}
_ => {
let err_val = self.get_at(func);
self.set_top(func);
Err(LuaError::Runtime(err_val))
}
}
}
pub fn protected_parser(&mut self, z: crate::zio::ZIO, name: &[u8], mode: Option<&[u8]>) -> LuaStatus {
crate::do_::protected_parser(self, z, name, mode)
}
pub fn do_call(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
crate::do_::call(self, func, nresults)
}
pub fn do_call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
crate::do_::callnoyield(self, func, nresults)
}
pub fn call_no_yield(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
crate::do_::callnoyield(self, func, nresults)
}
pub fn call_at(&mut self, func: StackIdx, nresults: i32) -> Result<(), LuaError> {
crate::do_::call(self, func, nresults)
}
#[inline(always)]
pub fn precall(&mut self, func: StackIdx, nresults: i32) -> Result<Option<CallInfoIdx>, LuaError> {
crate::do_::precall(self, func, nresults)
}
#[inline(always)]
pub fn pretailcall(
&mut self,
ci: CallInfoIdx,
func: StackIdx,
narg1: i32,
delta: i32,
) -> Result<i32, LuaError> {
crate::do_::pretailcall(self, ci, func, narg1, delta)
}
#[inline(always)]
pub fn poscall<N: TryInto<i32>>(&mut self, ci: CallInfoIdx, nres: N) -> Result<(), LuaError>
where
<N as TryInto<i32>>::Error: std::fmt::Debug,
{
let n = nres.try_into().expect("poscall: nres out of i32 range");
crate::do_::poscall(self, ci, n)
}
pub fn adjust_results(&mut self, nresults: i32) {
const LUA_MULTRET: i32 = -1;
if nresults <= LUA_MULTRET {
let ci_idx = self.ci.as_usize();
if self.call_info[ci_idx].top.0 < self.top.0 {
self.call_info[ci_idx].top = self.top;
}
}
}
pub fn adjust_varargs(
&mut self,
ci: CallInfoIdx,
nfixparams: i32,
cl: &GcRef<lua_types::closure::LuaLClosure>,
) -> Result<(), LuaError> {
crate::tagmethods::adjust_varargs(self, nfixparams, ci, &cl.0.proto)
}
pub fn get_varargs(
&mut self,
ci: CallInfoIdx,
ra: StackIdx,
n: i32,
) -> Result<i32, LuaError> {
crate::tagmethods::get_varargs(self, ci, ra, n)?;
Ok(0)
}
pub fn close_upvals(&mut self, level: StackIdx) -> Result<(), LuaError> {
crate::func::close_upval(self, level);
Ok(())
}
pub fn close_upvals_status(&mut self, level: StackIdx, _status: i32) -> Result<(), LuaError> {
crate::func::close_upval(self, level);
Ok(())
}
pub fn close_upvals_from_base(&mut self, ci: CallInfoIdx) -> Result<(), LuaError> {
let base = self.ci_base(ci);
crate::func::close_upval(self, base);
Ok(())
}
pub fn arith_op(&mut self, op: i32, p1: &LuaValue, p2: &LuaValue) -> Result<LuaValue, LuaError> {
let arith_op = match op {
0 => lua_types::arith::ArithOp::Add,
1 => lua_types::arith::ArithOp::Sub,
2 => lua_types::arith::ArithOp::Mul,
3 => lua_types::arith::ArithOp::Mod,
4 => lua_types::arith::ArithOp::Pow,
5 => lua_types::arith::ArithOp::Div,
6 => lua_types::arith::ArithOp::Idiv,
7 => lua_types::arith::ArithOp::Band,
8 => lua_types::arith::ArithOp::Bor,
9 => lua_types::arith::ArithOp::Bxor,
10 => lua_types::arith::ArithOp::Shl,
11 => lua_types::arith::ArithOp::Shr,
12 => lua_types::arith::ArithOp::Unm,
13 => lua_types::arith::ArithOp::Bnot,
_ => return Err(LuaError::runtime(format_args!("invalid arith op {}", op))),
};
let mut res = LuaValue::Nil;
if crate::object::raw_arith(self, arith_op, p1, p2, &mut res)? {
Ok(res)
} else {
Err(LuaError::arith_error(p1, p2, "perform arithmetic on"))
}
}
pub fn concat(&mut self, n: i32) -> Result<(), LuaError> {
crate::vm::concat(self, n)
}
pub fn less_than(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
crate::vm::less_than(self, l, r)
}
pub fn less_equal(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
crate::vm::less_equal(self, l, r)
}
pub fn equal_obj(&self, _ctx: Option<&LuaValue>, l: &LuaValue, r: &LuaValue) -> bool {
crate::vm::equal_obj(None, l, r).unwrap_or(false)
}
pub fn equal_obj_with_tm(&mut self, l: &LuaValue, r: &LuaValue) -> Result<bool, LuaError> {
crate::vm::equal_obj(Some(self), l, r)
}
pub fn obj_len(&mut self, v: &LuaValue) -> Result<LuaValue, LuaError> {
match v {
LuaValue::Table(_) => {
let consult_len_tm =
!matches!(self.global().lua_version, lua_types::LuaVersion::V51);
let tm = if consult_len_tm {
let mt = self.table_metatable(v);
self.fast_tm_table(mt.as_ref(), TagMethod::Len)
} else {
LuaValue::Nil
};
if matches!(tm, LuaValue::Nil) {
let n = self.table_length(v)?;
return Ok(LuaValue::Int(n));
}
self.push(LuaValue::Nil);
let slot = StackIdx(self.top.0 - 1);
crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
Ok(self.pop())
}
LuaValue::Str(s) => Ok(LuaValue::Int(s.len() as i64)),
other => {
let tm = crate::tagmethods::get_tm_by_obj(self, other, crate::tagmethods::TagMethod::Len);
if matches!(tm, LuaValue::Nil) {
let mut msg = b"attempt to get length of a ".to_vec();
msg.extend_from_slice(&self.obj_type_name(other));
msg.extend_from_slice(b" value");
return Err(crate::debug::prefixed_runtime_pub(self, msg));
}
self.push(LuaValue::Nil);
let slot = StackIdx(self.top.0 - 1);
crate::tagmethods::call_tm_res(self, tm, v.clone(), v.clone(), slot)?;
Ok(self.pop())
}
}
}
pub fn obj_to_string(&mut self, idx: i32) -> Result<GcRef<LuaString>, LuaError> {
let slot: StackIdx = if idx > 0 {
let ci_func = self.current_call_info().func;
ci_func + idx
} else {
debug_assert!(idx != 0, "invalid index");
StackIdx((self.top_idx().0 as i32 + idx) as u32)
};
let val = self.get_at(slot);
match val {
LuaValue::Str(s) => Ok(s),
LuaValue::Int(_) | LuaValue::Float(_) => {
let s = crate::object::num_to_string(self, &val)?;
self.set_at(slot, LuaValue::Str(s.clone()));
Ok(s)
}
_ => Err(LuaError::type_error(&val, "convert to string")),
}
}
pub fn coerce_to_string(&mut self, idx: StackIdx) -> Result<GcRef<LuaString>, LuaError> {
let val = self.get_at(idx);
match val {
LuaValue::Str(s) => Ok(s),
LuaValue::Int(_) | LuaValue::Float(_) => {
let s = crate::object::num_to_string(self, &val)?;
self.set_at(idx, LuaValue::Str(s.clone()));
Ok(s)
}
_ => Err(LuaError::type_error(&val, "convert to string")),
}
}
pub fn str_to_num(&mut self, s: &[u8]) -> Option<(LuaValue, usize)> {
let mut out = LuaValue::Nil;
let sz = crate::object::str2num(s, &mut out);
if sz == 0 { None } else { Some((out, sz)) }
}
pub fn fast_get(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
let LuaValue::Table(tbl) = t else { return Ok(None); };
let v = tbl.get(k);
if matches!(v, LuaValue::Nil) { Ok(None) } else { Ok(Some(v)) }
}
pub fn fast_get_int(&mut self, t: &LuaValue, k: i64) -> Result<Option<LuaValue>, LuaError> {
let LuaValue::Table(tbl) = t else { return Ok(None); };
let v = tbl.get_int(k);
if matches!(v, LuaValue::Nil) { Ok(None) } else { Ok(Some(v)) }
}
pub fn fast_get_short_str(&mut self, t: &LuaValue, k: &LuaValue) -> Result<Option<LuaValue>, LuaError> {
let LuaValue::Table(tbl) = t else { return Ok(None); };
let LuaValue::Str(s) = k else { return Ok(None); };
let v = tbl.get_short_str(s);
if matches!(v, LuaValue::Nil) { Ok(None) } else { Ok(Some(v)) }
}
pub fn fast_tm_table(&mut self, t: Option<&GcRef<LuaTable>>, tm: TagMethod) -> LuaValue {
let Some(mt) = t else { return LuaValue::Nil; };
debug_assert!((tm as u8) <= TagMethod::Eq as u8);
let ename = self.global().tmname[tm as usize].clone();
mt.get_short_str(&ename)
}
pub fn fast_tm_ud(&mut self, u: &GcRef<LuaUserData>, tm: TagMethod) -> LuaValue {
let mt = u.metatable();
self.fast_tm_table(mt.as_ref(), tm)
}
pub fn table_get_with_tm(&mut self, t: &LuaValue, k: &LuaValue) -> Result<LuaValue, LuaError> {
if let LuaValue::Table(tbl) = t {
if tbl.metatable().is_none() {
return Ok(tbl.get(k));
}
}
if let Some(v) = self.fast_get(t, k)? {
return Ok(v);
}
let res = self.top_idx();
self.push(LuaValue::Nil);
crate::vm::finish_get(self, t.clone(), k.clone(), res, true, None)?;
let value = self.get_at(res);
self.pop();
Ok(value)
}
#[inline]
pub fn table_set_with_tm(&mut self, t: &LuaValue, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
if let LuaValue::Table(tbl) = t {
if tbl.metatable().is_none() {
self.gc_barrier_back(t, &v);
return self.table_raw_set(t, k, v);
}
}
if self.fast_get(t, &k)?.is_some() {
self.gc_barrier_back(t, &v);
return self.table_raw_set(t, k, v);
}
crate::vm::finish_set(self, t.clone(), k, v, true, None, None)
}
#[inline]
pub fn table_raw_set(&mut self, t: &LuaValue, k: LuaValue, v: LuaValue) -> Result<(), LuaError> {
let LuaValue::Table(tbl) = t else {
return Err(LuaError::type_error(t, "index"));
};
let tbl = tbl.clone();
tbl.raw_set(self, k, v)
}
#[inline]
pub fn table_array_set(&mut self, t: &LuaValue, idx: usize, v: LuaValue) -> Result<(), LuaError> {
let LuaValue::Table(tbl) = t else {
return Err(LuaError::type_error(t, "index"));
};
let tbl = tbl.clone();
tbl.raw_set_int(self, idx as i64 + 1, v)
}
pub fn table_ensure_array(&mut self, t: &LuaValue, n: usize) -> Result<(), LuaError> {
let LuaValue::Table(tbl) = t else {
return Err(LuaError::type_error(t, "index"));
};
if n > tbl.array_len() {
tbl.resize(self, n, 0)?;
}
Ok(())
}
pub fn table_length(&mut self, t: &LuaValue) -> Result<i64, LuaError> {
let LuaValue::Table(tbl) = t else {
return Err(LuaError::type_error(t, "get length of"));
};
Ok(tbl.getn() as i64)
}
pub fn table_metatable(&mut self, v: &LuaValue) -> Option<GcRef<LuaTable>> {
match v {
LuaValue::Table(t) => t.metatable(),
LuaValue::UserData(u) => u.metatable(),
other => {
let idx = other.base_type() as usize;
self.global().mt[idx].clone()
}
}
}
pub fn table_resize(&mut self, t: &GcRef<LuaTable>, na: usize, nh: usize) -> Result<(), LuaError> {
self.mark_gc_check_needed();
t.resize(self, na, nh)
}
pub fn table_getn(&self, t: &GcRef<LuaTable>) -> i64 {
let mut i: i64 = 1;
loop {
let v = t.get_int(i);
if matches!(v, LuaValue::Nil) {
return i - 1;
}
i += 1;
}
}
pub fn try_bin_tm(&mut self, p1: &LuaValue, p1_idx: Option<StackIdx>, p2: &LuaValue, p2_idx: Option<StackIdx>, res: StackIdx, tm: lua_types::tagmethod::TagMethod) -> Result<(), LuaError> {
let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
crate::tagmethods::try_bin_tm(self, p1, p1_idx, p2, p2_idx, res, event)
}
pub fn try_bin_i_tm(&mut self, p1: &LuaValue, p1_idx: Option<StackIdx>, imm: i64, flip: bool, res: StackIdx, tm: lua_types::tagmethod::TagMethod) -> Result<(), LuaError> {
let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
crate::tagmethods::try_bini_tm(self, p1, p1_idx, imm, flip, res, event)
}
pub fn try_bin_assoc_tm(&mut self, p1: &LuaValue, p1_idx: Option<StackIdx>, p2: &LuaValue, p2_idx: Option<StackIdx>, flip: bool, res: StackIdx, tm: lua_types::tagmethod::TagMethod) -> Result<(), LuaError> {
let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
crate::tagmethods::try_bin_assoc_tm(self, p1, p1_idx, p2, p2_idx, flip, res, event)
}
pub fn try_concat_tm(&mut self, _p1: &LuaValue, _p2: &LuaValue) -> Result<(), LuaError> {
crate::tagmethods::try_concat_tm(self)
}
pub fn call_tm(&mut self, f: LuaValue, p1: &LuaValue, p2: &LuaValue, p3: &LuaValue) -> Result<(), LuaError> {
crate::tagmethods::call_tm(self, f, p1.clone(), p2.clone(), p3.clone())
}
pub fn call_tm_res(&mut self, f: LuaValue, p1: &LuaValue, p2: &LuaValue, res: StackIdx) -> Result<(), LuaError> {
crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)
}
pub fn call_tm_res_bool(&mut self, f: LuaValue, p1: &LuaValue, p2: &LuaValue) -> Result<bool, LuaError> {
let res = self.top_idx();
self.push(LuaValue::Nil);
crate::tagmethods::call_tm_res(self, f, p1.clone(), p2.clone(), res)?;
let result = self.get_at(res).clone();
self.pop();
Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)))
}
pub fn call_order_tm(&mut self, p1: &LuaValue, p2: &LuaValue, tm: lua_types::tagmethod::TagMethod) -> Result<bool, LuaError> {
let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
crate::tagmethods::call_order_tm(self, p1, p2, event)
}
pub fn call_order_i_tm(&mut self, p1: &LuaValue, v2: i64, flip: bool, isfloat: bool, tm: lua_types::tagmethod::TagMethod) -> Result<bool, LuaError> {
let event = crate::tagmethods::TagMethod::from_u8(tm as u8);
crate::tagmethods::call_orderi_tm(self, p1, v2 as i32, flip, isfloat, event)
}
#[inline(always)]
pub fn proto_code(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, pc: u32) -> lua_types::opcode::Instruction {
cl.proto.code[pc as usize]
}
#[inline(always)]
pub fn proto_const(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> LuaValue {
cl.proto.k[idx].clone()
}
#[inline(always)]
pub fn proto_const_int(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> Option<i64> {
match &cl.proto.k[idx] {
LuaValue::Int(v) => Some(*v),
_ => None,
}
}
#[inline(always)]
pub fn proto_const_num(&self, cl: &GcRef<lua_types::closure::LuaLClosure>, idx: usize) -> Option<f64> {
match &cl.proto.k[idx] {
LuaValue::Float(f) => Some(*f),
LuaValue::Int(v) => Some(*v as f64),
_ => None,
}
}
pub fn get_proto_instr(&self, ci: CallInfoIdx, pc: u32) -> lua_types::opcode::Instruction {
let cl = self.ci_lua_closure(ci)
.expect("get_proto_instr: CallInfo does not hold a Lua closure");
cl.proto.code[pc as usize]
}
pub fn trace_call(&mut self, _idx: CallInfoIdx) -> Result<bool, LuaError> {
Ok(crate::debug::trace_call(self)? != 0)
}
pub fn trace_exec(&mut self, _idx: CallInfoIdx, pc: u32) -> Result<bool, LuaError> {
Ok(crate::debug::trace_exec(self, pc)? != 0)
}
pub fn hook_call(&mut self, idx: CallInfoIdx) -> Result<(), LuaError> {
crate::do_::hookcall(self, idx)
}
#[inline(always)]
fn gc_step_flags(&self) -> Option<(bool, bool)> {
let g = self.global();
if !g.is_gc_running() {
return None;
}
let should_collect = g.heap.would_collect();
let has_finalizers = g.finalizers.has_to_be_finalized();
if should_collect || has_finalizers {
Some((should_collect, has_finalizers))
} else {
None
}
}
#[inline(always)]
fn should_check_gc(&mut self) -> bool {
if self.gc_check_needed {
return true;
}
if self.global().finalizers.has_to_be_finalized() {
self.gc_check_needed = true;
return true;
}
false
}
#[inline(always)]
pub(crate) fn mark_gc_check_needed(&mut self) {
self.gc_check_needed = true;
}
#[inline(always)]
pub fn gc_check_step(&mut self) {
if !self.allowhook {
return;
}
if !self.should_check_gc() {
return;
}
let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
self.gc_check_needed = false;
return;
};
if should_collect || has_finalizers {
if should_collect {
self.gc().check_step();
}
crate::api::run_pending_finalizers(self);
self.gc_check_needed = true;
}
let should_keep_checking = {
let g = self.global();
g.heap.would_collect() || g.finalizers.has_to_be_finalized()
};
self.gc_check_needed = should_keep_checking;
}
#[inline(always)]
pub fn gc_cond_step(&mut self) {
if !self.allowhook {
return;
}
if !self.should_check_gc() {
return;
}
let Some((should_collect, has_finalizers)) = self.gc_step_flags() else {
self.gc_check_needed = false;
return;
};
if should_collect || has_finalizers {
if should_collect {
self.gc().check_step();
}
crate::api::run_pending_finalizers(self);
self.gc_check_needed = true;
}
let should_keep_checking = {
let g = self.global();
g.heap.would_collect() || g.finalizers.has_to_be_finalized()
};
self.gc_check_needed = should_keep_checking;
}
pub fn gc_barrier_back(&mut self, t: &dyn std::any::Any, v: &LuaValue) {
self.gc().barrier_back(t, v);
}
pub fn gc_barrier_upval(&mut self, uv: &GcRef<UpVal>, v: &LuaValue) {
self.gc().barrier(uv, v);
}
pub fn is_main_thread(&mut self) -> bool {
let g = self.global();
g.current_thread_id == g.main_thread_id
}
pub fn obj_type_name<'v>(&self, v: &'v LuaValue) -> std::borrow::Cow<'static, [u8]> {
match v {
LuaValue::LightUserData(_) => std::borrow::Cow::Borrowed(b"light userdata"),
LuaValue::Table(t) => {
if let Some(mt) = t.metatable() {
if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
return std::borrow::Cow::Owned(s.as_bytes().to_vec());
}
}
std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
}
LuaValue::UserData(u) => {
if let Some(mt) = u.metatable() {
if let LuaValue::Str(s) = mt.get_str_bytes(b"__name") {
return std::borrow::Cow::Owned(s.as_bytes().to_vec());
}
}
std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type()))
}
_ => std::borrow::Cow::Borrowed(crate::tagmethods::type_name(v.base_type())),
}
}
pub fn full_type_name(&mut self, v: &LuaValue) -> Result<Vec<u8>, LuaError> {
crate::tagmethods::obj_type_name(self, v)
}
pub fn emit_warning(&mut self, _msg: &[u8], _to_cont: bool) { warning(self, _msg, _to_cont) }
}
pub struct GcHandle<'a> {
_state: &'a mut LuaState,
}
struct CollectRoots<'a> {
global: &'a GlobalState,
thread: &'a LuaState,
}
#[derive(Clone, Copy)]
enum HeapCollectMode {
Full,
Step,
Minor,
}
impl<'a> lua_gc::Trace for CollectRoots<'a> {
fn trace(&self, m: &mut lua_gc::Marker) {
self.global.trace(m);
self.thread.trace(m);
}
}
#[derive(Clone, Copy)]
enum BarrierKind {
Forward,
Backward,
}
fn barrier_lua_value<P>(
heap: &lua_gc::Heap,
parent: GcRef<P>,
child: &LuaValue,
generational: bool,
kind: BarrierKind,
)
where
P: lua_gc::Trace + 'static,
{
if generational && matches!(kind, BarrierKind::Backward) {
heap.generational_backward_barrier(parent.0);
}
match child {
LuaValue::Str(c) => barrier_gc_child(heap, parent, *c, generational, kind),
LuaValue::Table(c) => barrier_gc_child(heap, parent, *c, generational, kind),
LuaValue::Function(LuaClosure::Lua(c)) => barrier_gc_child(heap, parent, *c, generational, kind),
LuaValue::Function(LuaClosure::C(c)) => barrier_gc_child(heap, parent, *c, generational, kind),
LuaValue::UserData(c) => barrier_gc_child(heap, parent, *c, generational, kind),
LuaValue::Thread(c) => barrier_gc_child(heap, parent, *c, generational, kind),
LuaValue::Nil
| LuaValue::Bool(_)
| LuaValue::Int(_)
| LuaValue::Float(_)
| LuaValue::LightUserData(_)
| LuaValue::Function(LuaClosure::LightC(_)) => {}
}
}
fn barrier_gc_child<P, C>(
heap: &lua_gc::Heap,
parent: GcRef<P>,
child: GcRef<C>,
generational: bool,
kind: BarrierKind,
)
where
P: lua_gc::Trace + 'static,
C: lua_gc::Trace + 'static,
{
if generational && matches!(kind, BarrierKind::Forward) {
heap.generational_forward_barrier(parent.0, child.0);
} else if matches!(kind, BarrierKind::Backward) {
heap.barrier_back(parent.0, child.0);
} else {
heap.barrier(parent.0, child.0);
}
}
fn barrier_child_any<P>(
heap: &lua_gc::Heap,
parent: GcRef<P>,
child: &dyn std::any::Any,
generational: bool,
kind: BarrierKind,
)
where
P: lua_gc::Trace + 'static,
{
if let Some(v) = child.downcast_ref::<LuaValue>() {
barrier_lua_value(heap, parent, v, generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<LuaString>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<LuaTable>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureLua>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<LuaClosureC>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<LuaUserData>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<LuaProto>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
} else if let Some(c) = child.downcast_ref::<GcRef<UpVal>>() {
barrier_gc_child(heap, parent, c.clone(), generational, kind);
}
}
fn barrier_any(
heap: &lua_gc::Heap,
parent: &dyn std::any::Any,
child: &dyn std::any::Any,
generational: bool,
kind: BarrierKind,
) {
if let Some(v) = parent.downcast_ref::<LuaValue>() {
match v {
LuaValue::Str(p) => barrier_child_any(heap, *p, child, generational, kind),
LuaValue::Table(p) => barrier_child_any(heap, *p, child, generational, kind),
LuaValue::Function(LuaClosure::Lua(p)) => barrier_child_any(heap, *p, child, generational, kind),
LuaValue::Function(LuaClosure::C(p)) => barrier_child_any(heap, *p, child, generational, kind),
LuaValue::UserData(p) => barrier_child_any(heap, *p, child, generational, kind),
LuaValue::Thread(p) => barrier_child_any(heap, *p, child, generational, kind),
LuaValue::Nil
| LuaValue::Bool(_)
| LuaValue::Int(_)
| LuaValue::Float(_)
| LuaValue::LightUserData(_)
| LuaValue::Function(LuaClosure::LightC(_)) => {}
}
} else if let Some(p) = parent.downcast_ref::<GcRef<LuaString>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
} else if let Some(p) = parent.downcast_ref::<GcRef<LuaTable>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
} else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureLua>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
} else if let Some(p) = parent.downcast_ref::<GcRef<LuaClosureC>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
} else if let Some(p) = parent.downcast_ref::<GcRef<LuaUserData>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
} else if let Some(p) = parent.downcast_ref::<GcRef<lua_types::value::LuaThread>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
} else if let Some(p) = parent.downcast_ref::<GcRef<LuaProto>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
} else if let Some(p) = parent.downcast_ref::<GcRef<UpVal>>() {
barrier_child_any(heap, p.clone(), child, generational, kind);
}
}
fn trace_reachable_threads(
global: &GlobalState,
_current_thread_id: u64,
marker: &mut lua_gc::Marker,
) {
use lua_gc::Trace;
loop {
let visited_before = marker.visited_count();
for (id, entry) in global.threads.iter() {
if thread_entry_marked_alive(marker, *id, entry) {
if let Ok(thread) = entry.state.try_borrow() {
thread.trace(marker);
}
}
}
marker.drain_gray_queue();
if marker.visited_count() == visited_before {
break;
}
}
}
fn thread_entry_marked_alive(
marker: &lua_gc::Marker,
id: u64,
entry: &ThreadRegistryEntry,
) -> bool {
marker.is_marked_or_old(entry.value.0) && entry.value.id == id
}
fn lua_value_marked_or_old(marker: &lua_gc::Marker, value: &LuaValue) -> bool {
match value {
LuaValue::Str(v) => marker.is_marked_or_old(v.0),
LuaValue::Table(v) => marker.is_marked_or_old(v.0),
LuaValue::Function(LuaClosure::Lua(v)) => marker.is_marked_or_old(v.0),
LuaValue::Function(LuaClosure::C(v)) => marker.is_marked_or_old(v.0),
LuaValue::UserData(v) => marker.is_marked_or_old(v.0),
LuaValue::Thread(v) => marker.is_marked_or_old(v.0),
LuaValue::Nil
| LuaValue::Bool(_)
| LuaValue::Int(_)
| LuaValue::Float(_)
| LuaValue::LightUserData(_)
| LuaValue::Function(LuaClosure::LightC(_)) => true,
}
}
fn lua_value_identity(value: &LuaValue) -> Option<usize> {
match value {
LuaValue::Str(v) => Some(v.identity()),
LuaValue::Table(v) => Some(v.identity()),
LuaValue::Function(LuaClosure::Lua(v)) => Some(v.identity()),
LuaValue::Function(LuaClosure::C(v)) => Some(v.identity()),
LuaValue::UserData(v) => Some(v.identity()),
LuaValue::Thread(v) => Some(v.identity()),
LuaValue::Nil
| LuaValue::Bool(_)
| LuaValue::Int(_)
| LuaValue::Float(_)
| LuaValue::LightUserData(_)
| LuaValue::Function(LuaClosure::LightC(_)) => None,
}
}
fn finalizer_marked_or_old(marker: &lua_gc::Marker, object: &FinalizerObject) -> bool {
match object {
FinalizerObject::Table(t) => marker.is_marked_or_old(t.0),
FinalizerObject::UserData(u) => marker.is_marked_or_old(u.0),
}
}
fn weak_snapshot_tables<'a>(
snapshot: &'a lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>>,
) -> impl Iterator<Item = &'a GcRef<LuaTable>> {
snapshot
.weak_values
.iter()
.chain(snapshot.ephemeron.iter())
.chain(snapshot.all_weak.iter())
}
fn close_open_upvalues_for_unreachable_threads(
global: &GlobalState,
marker: &mut lua_gc::Marker,
) {
use lua_gc::Trace;
let mut closed_values = Vec::<LuaValue>::new();
for (id, entry) in global.threads.iter() {
if entry.value.id != *id {
continue;
}
if thread_entry_marked_alive(marker, *id, entry) {
continue;
}
let Ok(thread) = entry.state.try_borrow() else {
continue;
};
for uv in thread.openupval.iter() {
if !marker.is_visited(uv.identity()) {
continue;
}
let Some((thread_id, idx)) = uv.try_open_payload() else {
continue;
};
if thread_id as u64 != *id {
continue;
}
let value = thread.get_at(idx);
uv.close_with(value.clone());
closed_values.push(value);
}
}
for value in closed_values {
value.trace(marker);
}
marker.drain_gray_queue();
}
fn record_live_interned_strings(
global: &GlobalState,
marker: &lua_gc::Marker,
live_ids: &std::cell::RefCell<std::collections::HashSet<usize>>,
) {
let mut live = live_ids.borrow_mut();
for s in global.interned_lt.values() {
let id = s.identity();
if marker.is_visited(id) {
live.insert(id);
}
}
}
fn retain_live_interned_strings(
global: &mut GlobalState,
live_ids: &std::collections::HashSet<usize>,
) {
global
.interned_lt
.retain(|_, s| live_ids.contains(&s.identity()));
}
impl<'a> GcHandle<'a> {
pub fn check_step(&self) {
if !self._state.global().is_gc_running() {
return;
}
if self._state.global().is_gen_mode() {
let should_collect = {
let g = self._state.global();
g.heap.would_collect() || g.gc_debt() > 0
};
if should_collect {
self.generational_step();
}
} else {
self.collect_via_heap( false);
}
}
pub fn full_collect(&self) {
if self._state.global().is_gen_mode() {
self.fullgen();
} else {
self.collect_via_heap( true);
}
}
fn negative_debt(bytes: usize) -> isize {
-(bytes.min(isize::MAX as usize) as isize)
}
fn set_minor_debt(&self) {
let mut g = self._state.global_mut();
let total = g.total_bytes();
let growth = (total / 100).saturating_mul(g.genminormul as usize);
g.heap
.set_threshold_bytes(total.saturating_add(growth.max(1)));
set_debt(&mut *g, Self::negative_debt(growth));
}
fn set_pause_debt(&self) {
let mut g = self._state.global_mut();
let total = g.total_bytes();
let pause = g.gc_pause_param().max(0) as usize;
let threshold = g.gc_estimate.max(1).saturating_mul(pause) / 100;
let debt = if threshold > total {
Self::negative_debt(threshold - total)
} else {
0
};
let heap_threshold = if threshold > total {
threshold
} else {
total.saturating_add(1)
};
g.heap.set_threshold_bytes(heap_threshold);
set_debt(&mut *g, debt);
}
fn enter_incremental_mode(&self) {
let mut g = self._state.global_mut();
g.heap.reset_all_ages();
g.finalizers.reset_generation_boundaries();
g.gckind = GcKind::Incremental as u8;
g.lastatomic = 0;
}
fn enter_generational_mode(&self) -> usize {
self.collect_via_heap_mode(HeapCollectMode::Full);
let numobjs = {
let mut g = self._state.global_mut();
g.heap.promote_all_to_old();
g.finalizers.promote_all_pending_to_old();
g.heap.allgc_count()
};
let total = self._state.global().total_bytes();
{
let mut g = self._state.global_mut();
g.gckind = GcKind::Generational as u8;
g.lastatomic = 0;
g.gc_estimate = total;
}
self.set_minor_debt();
numobjs
}
fn fullgen(&self) -> usize {
self.enter_incremental_mode();
self.enter_generational_mode()
}
fn stepgenfull(&self, lastatomic: usize) {
if self._state.global().gckind == GcKind::Generational as u8 {
self.enter_incremental_mode();
}
self.collect_via_heap_mode(HeapCollectMode::Full);
let newatomic = self._state.global().heap.allgc_count().max(1);
if newatomic < lastatomic.saturating_add(lastatomic >> 3) {
{
let mut g = self._state.global_mut();
g.heap.promote_all_to_old();
g.finalizers.promote_all_pending_to_old();
}
let total = self._state.global().total_bytes();
{
let mut g = self._state.global_mut();
g.gckind = GcKind::Generational as u8;
g.lastatomic = 0;
g.gc_estimate = total;
}
self.set_minor_debt();
} else {
{
let mut g = self._state.global_mut();
g.heap.reset_all_ages();
g.finalizers.reset_generation_boundaries();
}
let total = self._state.global().total_bytes();
{
let mut g = self._state.global_mut();
g.gckind = GcKind::Incremental as u8;
g.lastatomic = newatomic;
g.gc_estimate = total;
}
self.set_pause_debt();
}
}
fn collect_via_heap(&self, force: bool) {
self.collect_via_heap_mode(if force {
HeapCollectMode::Full
} else {
HeapCollectMode::Step
});
}
fn collect_via_heap_mode(&self, mode: HeapCollectMode) {
use lua_gc::Trace;
let state_ref: &LuaState = &*self._state;
if matches!(mode, HeapCollectMode::Step) {
let g = state_ref.global.borrow();
if !g.heap.would_collect() {
return;
}
}
let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
let mut g = state_ref.global.borrow_mut();
g.weak_tables_registry.live_snapshot_by_kind()
};
let pending_snapshot: Vec<FinalizerObject> = {
let g = state_ref.global.borrow();
match mode {
HeapCollectMode::Minor => g.finalizers.pending_minor_snapshot(),
HeapCollectMode::Full | HeapCollectMode::Step => g.finalizers.pending_snapshot(),
}
};
let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
std::cell::RefCell::new(Vec::new());
let finalizing_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let live_interned_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let collect_ran = std::cell::Cell::new(false);
{
let global = state_ref.global.borrow();
global.heap.unpause();
let roots = CollectRoots { global: &*global, thread: state_ref };
let hook = |marker: &mut lua_gc::Marker| {
collect_ran.set(true);
trace_reachable_threads(&*global, global.current_thread_id, marker);
close_open_upvalues_for_unreachable_threads(&*global, marker);
loop {
let visited_before = marker.visited_count();
for t in &weak_tables_snapshot.ephemeron {
if !marker.is_marked_or_old(t.0) {
continue;
}
let to_mark = t.ephemeron_values_to_mark_with_value(
&|v| lua_value_marked_or_old(marker, v),
);
for v in &to_mark {
v.trace(marker);
}
}
marker.drain_gray_queue();
if marker.visited_count() == visited_before {
break;
}
}
for pf in &pending_snapshot {
if !finalizer_marked_or_old(marker, pf) {
pf.mark(marker);
finalizing_ids.borrow_mut().insert(pf.identity());
newly_unreachable.borrow_mut().push(pf.clone());
}
}
marker.drain_gray_queue();
loop {
let visited_before = marker.visited_count();
for t in &weak_tables_snapshot.ephemeron {
if !marker.is_marked_or_old(t.0) {
continue;
}
let to_mark = t.ephemeron_values_to_mark_with_value(
&|v| lua_value_marked_or_old(marker, v),
);
for v in &to_mark {
v.trace(marker);
}
}
marker.drain_gray_queue();
if marker.visited_count() == visited_before {
break;
}
}
for t in weak_snapshot_tables(&weak_tables_snapshot) {
let id = t.identity();
if marker.is_marked_or_old(t.0) {
let to_mark = {
let finalizing = finalizing_ids.borrow();
t.prune_weak_dead_with_value(
&|v| lua_value_marked_or_old(marker, v),
&|v| {
lua_value_marked_or_old(marker, v)
&& lua_value_identity(v)
.map_or(true, |id| !finalizing.contains(&id))
},
)
};
for v in &to_mark {
v.trace(marker);
}
alive_ids.borrow_mut().insert(id);
}
}
marker.drain_gray_queue();
{
let mut alive = alive_thread_ids.borrow_mut();
for (id, entry) in global.threads.iter() {
if thread_entry_marked_alive(marker, *id, entry) {
alive.insert(*id);
}
}
}
record_live_interned_strings(&*global, marker, &live_interned_ids);
};
match mode {
HeapCollectMode::Full => global.heap.full_collect_with_post_mark(&roots, hook),
HeapCollectMode::Step => global.heap.step_with_post_mark(&roots, hook),
HeapCollectMode::Minor => global.heap.minor_collect_with_post_mark(&roots, hook),
}
}
if !collect_ran.get() {
return;
}
let alive_set = alive_ids.into_inner();
let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
let alive_thread_ids = alive_thread_ids.into_inner();
let live_interned_ids = live_interned_ids.into_inner();
let mut g = state_ref.global.borrow_mut();
retain_live_interned_strings(&mut *g, &live_interned_ids);
g.weak_tables_registry.retain_identities(&alive_set);
let main_thread_id = g.main_thread_id;
g.threads.retain(|id, _| alive_thread_ids.contains(id));
g.cross_thread_upvals
.retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
let promoted = g.finalizers.promote_pending_to_finalized(promote);
for object in &promoted {
if let Some(ptr) = object.heap_ptr() {
g.heap.move_finobj_to_tobefnz(ptr);
}
}
if matches!(mode, HeapCollectMode::Minor) {
g.finalizers.finish_minor_collection();
}
}
pub fn generational_step(&self) -> bool {
self.generational_step_with_major(true)
}
pub fn generational_step_minor_only(&self) -> bool {
self.generational_step_with_major(false)
}
fn generational_step_with_major(&self, allow_major: bool) -> bool {
let (lastatomic, majorbase, majorinc, should_major) = {
let g = self._state.global();
let majorbase = if g.gc_estimate == 0 {
g.total_bytes()
} else {
g.gc_estimate
};
let majormul = g.gc_genmajormul_param().max(0) as usize;
let majorinc = (majorbase / 100).saturating_mul(majormul);
let debt_due = g.gc_debt() > 0 || g.heap.would_collect();
let should_major = allow_major
&& debt_due
&& g.total_bytes() > majorbase.saturating_add(majorinc);
(g.lastatomic, majorbase, majorinc, should_major)
};
if lastatomic != 0 {
self.stepgenfull(lastatomic);
debug_assert!(self._state.global().is_gen_mode());
return true;
}
if should_major {
let numobjs = self.fullgen();
let after = self._state.global().total_bytes();
if after < majorbase.saturating_add(majorinc / 2) {
self.set_minor_debt();
} else {
{
let mut g = self._state.global_mut();
g.lastatomic = numobjs.max(1);
}
self.set_pause_debt();
}
} else {
self.collect_via_heap_mode(HeapCollectMode::Minor);
self.set_minor_debt();
self._state.global_mut().gc_estimate = majorbase;
}
debug_assert!(self._state.global().is_gen_mode());
true
}
pub fn step(&self) { }
pub fn incremental_step(&self, work_units: isize) -> bool {
self.incremental_step_to_state(work_units, None)
}
pub fn run_until_gc_state_for_test(&self, target: lua_gc::GcState) -> bool {
self.incremental_step_to_state(isize::MAX / 4, Some(target));
self._state.global().heap.gc_state() == target
}
fn incremental_step_to_state(
&self,
work_units: isize,
target: Option<lua_gc::GcState>,
) -> bool {
use lua_gc::{StepBudget, StepOutcome, Trace};
let state_ref: &LuaState = &*self._state;
let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
let mut g = state_ref.global.borrow_mut();
g.weak_tables_registry.live_snapshot_by_kind()
};
let pending_snapshot: Vec<FinalizerObject> = {
let g = state_ref.global.borrow();
g.finalizers.pending_snapshot()
};
let alive_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let newly_unreachable: std::cell::RefCell<Vec<FinalizerObject>> =
std::cell::RefCell::new(Vec::new());
let finalizing_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let alive_thread_ids: std::cell::RefCell<std::collections::HashSet<u64>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let live_interned_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
std::cell::RefCell::new(std::collections::HashSet::new());
let atomic_ran = std::cell::Cell::new(false);
let stop_target = {
let g = state_ref.global.borrow();
match (target, g.heap.gc_state()) {
(Some(target), _) => Some(target),
(None, lua_gc::GcState::CallFin) => None,
(None, _) => Some(lua_gc::GcState::CallFin),
}
};
let outcome = {
let global = state_ref.global.borrow();
global.heap.unpause();
let roots = CollectRoots { global: &*global, thread: state_ref };
let hook = |marker: &mut lua_gc::Marker| {
atomic_ran.set(true);
trace_reachable_threads(&*global, global.current_thread_id, marker);
close_open_upvalues_for_unreachable_threads(&*global, marker);
loop {
let visited_before = marker.visited_count();
for t in &weak_tables_snapshot.ephemeron {
let t_id = t.identity();
if !marker.is_visited(t_id) {
continue;
}
let to_mark = t.ephemeron_values_to_mark(
&|id| marker.is_visited(id),
);
for v in &to_mark {
v.trace(marker);
}
}
marker.drain_gray_queue();
if marker.visited_count() == visited_before {
break;
}
}
for pf in &pending_snapshot {
if !marker.is_visited(pf.identity()) {
pf.mark(marker);
finalizing_ids.borrow_mut().insert(pf.identity());
newly_unreachable.borrow_mut().push(pf.clone());
}
}
marker.drain_gray_queue();
loop {
let visited_before = marker.visited_count();
for t in &weak_tables_snapshot.ephemeron {
let t_id = t.identity();
if !marker.is_visited(t_id) {
continue;
}
let to_mark = t.ephemeron_values_to_mark(
&|id| marker.is_visited(id),
);
for v in &to_mark {
v.trace(marker);
}
}
marker.drain_gray_queue();
if marker.visited_count() == visited_before {
break;
}
}
for t in weak_snapshot_tables(&weak_tables_snapshot) {
let id = t.identity();
if marker.is_visited(id) {
let to_mark = {
let finalizing = finalizing_ids.borrow();
t.prune_weak_dead_with(
&|id| marker.is_visited(id),
&|id| marker.is_visited(id) && !finalizing.contains(&id),
)
};
for v in &to_mark {
v.trace(marker);
}
alive_ids.borrow_mut().insert(id);
}
}
marker.drain_gray_queue();
{
let mut alive = alive_thread_ids.borrow_mut();
for (id, entry) in global.threads.iter() {
if thread_entry_marked_alive(marker, *id, entry) {
alive.insert(*id);
}
}
}
record_live_interned_strings(&*global, marker, &live_interned_ids);
};
let budget = StepBudget::from_work(work_units);
if let Some(target) = stop_target {
global.heap.incremental_run_until_state_with_post_mark(
&roots,
target,
work_units,
hook,
)
} else {
global.heap.incremental_step_with_post_mark(&roots, budget, hook)
}
};
if atomic_ran.get() {
let alive_set = alive_ids.into_inner();
let promote: Vec<FinalizerObject> = newly_unreachable.into_inner();
let alive_thread_ids = alive_thread_ids.into_inner();
let live_interned_ids = live_interned_ids.into_inner();
let mut g = state_ref.global.borrow_mut();
retain_live_interned_strings(&mut *g, &live_interned_ids);
g.weak_tables_registry.retain_identities(&alive_set);
let main_thread_id = g.main_thread_id;
g.threads.retain(|id, _| alive_thread_ids.contains(id));
g.cross_thread_upvals
.retain(|(id, _), _| *id == main_thread_id || alive_thread_ids.contains(id));
let promoted = g.finalizers.promote_pending_to_finalized(promote);
for object in &promoted {
if let Some(ptr) = object.heap_ptr() {
g.heap.move_finobj_to_tobefnz(ptr);
}
}
}
let mut paused = matches!(outcome, StepOutcome::Paused);
if target.is_none()
&& self._state.global().heap.gc_state() == lua_gc::GcState::CallFin
&& !self._state.global().finalizers.has_to_be_finalized()
{
paused = self._state.global().heap.finish_callfin_phase();
}
paused
}
pub fn prune_weak_tables_mark_only(&self) {
use lua_gc::Trace;
let state_ref: &LuaState = &*self._state;
let weak_tables_snapshot: lua_gc::WeakRegistrySnapshot<GcRef<LuaTable>> = {
let mut g = state_ref.global.borrow_mut();
g.weak_tables_registry.live_snapshot_by_kind()
};
let live_interned_ids: std::cell::RefCell<std::collections::HashSet<usize>> =
std::cell::RefCell::new(std::collections::HashSet::new());
{
let global = state_ref.global.borrow();
global.heap.unpause();
let roots = CollectRoots { global: &*global, thread: state_ref };
let hook = |marker: &mut lua_gc::Marker| {
trace_reachable_threads(&*global, global.current_thread_id, marker);
loop {
let visited_before = marker.visited_count();
for t in &weak_tables_snapshot.ephemeron {
let t_id = t.identity();
if !marker.is_visited(t_id) {
continue;
}
let to_mark = t.ephemeron_values_to_mark(
&|id| marker.is_visited(id),
);
for v in &to_mark {
v.trace(marker);
}
}
marker.drain_gray_queue();
if marker.visited_count() == visited_before {
break;
}
}
for t in weak_snapshot_tables(&weak_tables_snapshot) {
if marker.is_visited(t.identity()) {
let to_mark = t.prune_weak_dead(&|id| marker.is_visited(id));
for v in &to_mark {
v.trace(marker);
}
}
}
marker.drain_gray_queue();
record_live_interned_strings(&*global, marker, &live_interned_ids);
};
global.heap.mark_only_with_post_mark(&roots, hook);
}
let live_interned_ids = live_interned_ids.into_inner();
let mut g = state_ref.global.borrow_mut();
retain_live_interned_strings(&mut *g, &live_interned_ids);
}
pub fn change_mode(&self, mode: GcKind) {
let old = self._state.global().gckind;
if old == mode as u8 {
self._state.global_mut().lastatomic = 0;
return;
}
match mode {
GcKind::Generational => {
self.enter_generational_mode();
}
GcKind::Incremental => {
self.enter_incremental_mode();
}
}
}
pub fn fix_object<T: lua_gc::Trace + 'static>(&self, _o: &GcRef<T>) { }
pub fn free_all_objects(&self) {
}
pub fn barrier(&self, p: &dyn std::any::Any, v: &LuaValue) {
let g = self._state.global();
barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Forward);
}
pub fn barrier_back(&self, p: &dyn std::any::Any, v: &LuaValue) {
let g = self._state.global();
barrier_any(&g.heap, p, v, g.is_gen_mode(), BarrierKind::Backward);
}
pub fn obj_barrier(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
let g = self._state.global();
barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Forward);
}
pub fn obj_barrier_back(&self, p: &dyn std::any::Any, o: &dyn std::any::Any) {
let g = self._state.global();
barrier_any(&g.heap, p, o, g.is_gen_mode(), BarrierKind::Backward);
}
}
fn make_seed() -> u32 {
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
{
return crate::string::hash_bytes(b"lua-rs-wasm-seed", 0x9e37_79b9);
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
{
use std::time::{SystemTime, UNIX_EPOCH};
let t = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as u32)
.unwrap_or(0);
crate::string::hash_bytes(&t.to_le_bytes(), t)
}
}
pub(crate) fn set_debt(g: &mut GlobalState, mut debt: isize) {
let tb = g.total_bytes() as isize;
debug_assert!(tb > 0);
if debt < tb.saturating_sub(isize::MAX) {
debt = tb - isize::MAX;
}
g.gc_debt = debt;
}
pub fn set_c_stack_limit(_state: &mut LuaState, _limit: u32) -> i32 {
let _ = (_state, _limit);
LUAI_MAXCCALLS as i32
}
pub(crate) fn extend_ci(state: &mut LuaState) -> CallInfoIdx {
debug_assert!(
state.call_info[state.ci.0 as usize].next.is_none(),
"extend_ci: current ci already has a cached next frame"
);
let current_idx = state.ci;
let new_idx = CallInfoIdx(state.call_info.len() as u32);
state.call_info.push(CallInfo {
previous: Some(current_idx),
next: None,
u: CallInfoFrame::lua_default(),
..CallInfo::default()
});
state.call_info[current_idx.0 as usize].next = Some(new_idx);
state.nci += 1;
new_idx
}
fn free_ci(state: &mut LuaState) {
let ci_idx = state.ci.0 as usize;
let mut next_opt = state.call_info[ci_idx].next.take();
while let Some(idx) = next_opt {
next_opt = state.call_info[idx.0 as usize].next;
state.nci = state.nci.saturating_sub(1);
}
state.call_info.truncate(ci_idx + 1);
}
pub(crate) fn shrink_ci(state: &mut LuaState) {
let ci_idx = state.ci.0 as usize;
if state.call_info[ci_idx].next.is_none() {
return;
}
let free_count = state.call_info.len().saturating_sub(ci_idx + 1);
if free_count <= 1 {
return;
}
let keep = free_count / 2;
let removed = free_count - keep;
let new_len = ci_idx + 1 + keep;
state.call_info.truncate(new_len);
state.nci = state.nci.saturating_sub(removed as u32);
if let Some(last) = state.call_info.last_mut() {
last.next = None;
}
}
pub(crate) fn check_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
if state.c_calls() == LUAI_MAXCCALLS {
return Err(LuaError::runtime(format_args!("C stack overflow")));
}
if state.c_calls() >= (LUAI_MAXCCALLS / 10 * 11) {
return Err(LuaError::runtime(format_args!(
"error while handling stack overflow (C stack overflow)"
)));
}
Ok(())
}
pub fn inc_c_stack(state: &mut LuaState) -> Result<(), LuaError> {
state.n_ccalls += 1;
if state.c_calls() >= LUAI_MAXCCALLS {
check_c_stack(state)?;
}
Ok(())
}
fn stack_init(thread: &mut LuaState) {
let total_slots = BASIC_STACK_SIZE + EXTRA_STACK;
thread.stack = vec![StackValue::default(); total_slots];
thread.tbclist = Vec::new();
thread.top = StackIdx(0);
thread.stack_last = StackIdx(BASIC_STACK_SIZE as u32);
let base_ci = CallInfo {
func: StackIdx(0),
top: StackIdx(1 + LUA_MINSTACK as u32),
previous: None,
next: None,
callstatus: CIST_C,
nresults: 0,
u: CallInfoFrame::c_default(),
u2: CallInfoExtra::default(),
};
if thread.call_info.is_empty() {
thread.call_info.push(base_ci);
} else {
thread.call_info[0] = base_ci;
thread.call_info.truncate(1);
}
thread.stack[0] = StackValue { val: LuaValue::Nil, tbc_delta: 0 };
thread.top = StackIdx(1);
thread.ci = CallInfoIdx(0);
}
fn free_stack(state: &mut LuaState) {
if state.stack.is_empty() {
return;
}
state.ci = CallInfoIdx(0);
free_ci(state);
debug_assert_eq!(state.nci, 0, "nci should be 0 after free_ci");
state.stack.clear();
state.stack.shrink_to_fit();
}
fn init_registry(state: &mut LuaState) -> Result<(), LuaError> {
let registry = state.new_table();
state.global_mut().l_registry = LuaValue::Table(registry.clone());
let globals = state.new_table();
state.global_mut().globals = LuaValue::Table(globals);
let loaded = state.new_table();
state.global_mut().loaded = LuaValue::Table(loaded);
Ok(())
}
fn lua_open(state: &mut LuaState) -> Result<(), LuaError> {
stack_init(state);
init_registry(state)?;
crate::string::init(state)?;
crate::tagmethods::init(state)?;
state.global_mut().gcstp = 0;
state.global().heap.unpause();
state.global_mut().nilvalue = LuaValue::Nil;
Ok(())
}
fn preinit_thread(thread: &mut LuaState, global: Rc<RefCell<GlobalState>>) {
thread.global = global;
thread.stack = Vec::new();
thread.call_info = Vec::new();
thread.ci = CallInfoIdx(0);
thread.nci = 0;
thread.n_ccalls = 0;
thread.hook = None;
thread.hookmask = 0;
thread.basehookcount = 0;
thread.allowhook = true;
thread.hookcount = thread.basehookcount;
{
let (active, interval) = {
let g = thread.global.borrow();
(g.sandbox_active(), g.sandbox.interval.get())
};
if active {
thread.hookmask = SANDBOX_COUNT_MASK;
thread.basehookcount = interval;
thread.hookcount = interval;
}
}
thread.openupval = Vec::new();
thread.status = LuaStatus::Ok as u8;
thread.errfunc = 0;
thread.oldpc = 0;
thread.gc_check_needed = true;
}
fn close_state(state: &mut LuaState) {
let is_complete = state.global().is_complete();
if !is_complete {
state.gc().free_all_objects();
} else {
state.ci = CallInfoIdx(0);
state.gc().free_all_objects();
}
state.global_mut().strt = StringPool::default();
free_stack(state);
}
pub fn new_thread(state: &mut LuaState, initial_body: Option<LuaValue>) -> Result<(), LuaError> {
state.gc().check_step();
let global_rc = state.global_rc();
let hookmask = state.hookmask;
let basehookcount = state.basehookcount;
let reserved_id = {
let mut g = state.global_mut();
let id = g.next_thread_id;
g.next_thread_id += 1;
id
};
let mut new_thread = LuaState {
status: LuaStatus::Ok as u8,
allowhook: true,
nci: 0,
top: StackIdx(0),
stack_last: StackIdx(0),
stack: Vec::new(),
ci: CallInfoIdx(0),
call_info: Vec::new(),
openupval: Vec::new(),
tbclist: Vec::new(),
global: global_rc.clone(),
hook: None,
hookmask: 0,
basehookcount: 0,
hookcount: 0,
errfunc: 0,
n_ccalls: 0,
oldpc: 0,
marked: 0,
cached_thread_id: reserved_id,
gc_check_needed: false,
};
preinit_thread(&mut new_thread, global_rc);
new_thread.hookmask = hookmask;
new_thread.basehookcount = basehookcount;
new_thread.reset_hook_count();
stack_init(&mut new_thread);
if let Some(body) = initial_body {
new_thread.push(body);
}
let thread_ref: Rc<RefCell<LuaState>> = Rc::new(RefCell::new(new_thread));
let value = {
let mut g = state.global_mut();
let id = reserved_id;
let value = GcRef::new(lua_types::value::LuaThread::new(id));
g.threads.insert(
id,
ThreadRegistryEntry { state: thread_ref, value: value.clone() },
);
value
};
state.push(LuaValue::Thread(value));
Ok(())
}
pub fn reset_thread(state: &mut LuaState, status: i32) -> i32 {
state.ci = CallInfoIdx(0);
let ci_idx = 0usize;
if !state.stack.is_empty() {
state.stack[0].val = LuaValue::Nil;
}
state.call_info[ci_idx].func = StackIdx(0);
state.call_info[ci_idx].callstatus = CIST_C;
let mut status = if status == LuaStatus::Yield as i32 {
LuaStatus::Ok as i32
} else {
status
};
state.status = LuaStatus::Ok as u8;
let close_status = crate::do_::close_protected(
state,
StackIdx(1),
LuaStatus::from_raw(status),
);
status = close_status as i32;
if status != LuaStatus::Ok as i32 {
crate::do_::set_error_obj(state, LuaStatus::from_raw(status), StackIdx(1));
} else {
state.top = StackIdx(1);
}
let new_ci_top = StackIdx(state.top.0 + LUA_MINSTACK as u32);
state.call_info[ci_idx].top = new_ci_top;
let needed = new_ci_top.0 as usize;
if state.stack.len() < needed {
state.stack.resize(needed, StackValue::default());
}
status
}
pub fn close_thread(state: &mut LuaState, from: Option<&LuaState>) -> i32 {
state.n_ccalls = match from {
Some(f) => f.c_calls(),
None => 0,
};
let current_status = state.status as i32;
let result = reset_thread(state, current_status);
result
}
pub fn reset_thread_api(state: &mut LuaState) -> i32 {
close_thread(state, None)
}
pub fn new_state() -> Option<LuaState> {
let placeholder_str = GcRef::new(LuaString::placeholder());
let initial_white = 1u8 << WHITE0BIT;
let global = GlobalState {
parser_hook: None,
cli_argv: None,
cli_preload: None,
lua_version: lua_types::LuaVersion::default(),
file_loader_hook: None,
file_open_hook: None,
stdout_hook: None,
stderr_hook: None,
stdin_hook: None,
env_hook: None,
unix_time_hook: None,
cpu_clock_hook: None,
local_offset_hook: None,
entropy_hook: None,
temp_name_hook: None,
popen_hook: None,
file_remove_hook: None,
file_rename_hook: None,
os_execute_hook: None,
dynlib_load_hook: None,
dynlib_symbol_hook: None,
dynlib_unload_hook: None,
sandbox: SandboxLimits::default(),
gc_debt: 0,
gc_estimate: 0,
lastatomic: 0,
strt: StringPool::default(),
l_registry: LuaValue::Nil,
external_roots: ExternalRootSet::default(),
globals: LuaValue::Nil,
loaded: LuaValue::Nil,
nilvalue: LuaValue::Int(0),
seed: make_seed(),
currentwhite: initial_white,
gcstate: GCS_PAUSE,
gckind: GcKind::Incremental as u8,
gcstopem: false,
genminormul: LUAI_GENMINORMUL,
genmajormul: (LUAI_GENMAJORMUL / 4) as u8,
gcstp: GCSTPGC,
gcemergency: false,
gcpause: (LUAI_GCPAUSE / 4) as u8,
gcstepmul: (LUAI_GCMUL / 4) as u8,
gcstepsize: LUAI_GCSTEPSIZE,
gc55_params: [20, 50, 68, 250, 200, 9600],
sweepgc_cursor: 0,
weak_tables_registry: lua_gc::WeakRegistry::default(),
finalizers: lua_gc::FinalizerRegistry::default(),
gc_finalizer_error: None,
twups: Vec::new(),
panic: None,
mainthread: None,
threads: std::collections::HashMap::new(),
main_thread_value: GcRef::new(lua_types::value::LuaThread::new(0)),
current_thread_id: 0,
main_thread_id: 0,
next_thread_id: 1,
memerrmsg: placeholder_str.clone(),
tmname: Vec::new(),
mt: std::array::from_fn(|_| None),
strcache: std::array::from_fn(|_| {
std::array::from_fn(|_| placeholder_str.clone())
}),
interned_lt: std::collections::HashMap::new(),
warnf: None,
warn_mode: WarnMode::Off,
test_warn_enabled: false,
test_warn_on: false,
test_warn_mode: TestWarnMode::Normal,
test_warn_last_to_cont: false,
test_warn_buffer: Vec::new(),
c_functions: Vec::new(),
heap: lua_gc::Heap::new(),
cross_thread_upvals: std::collections::HashMap::new(),
suspended_parent_stacks: Vec::new(),
suspended_parent_open_upvals: Vec::new(),
};
let global_rc = Rc::new(RefCell::new(global));
let initial_marked = initial_white;
let mut main_thread = LuaState {
status: LuaStatus::Ok as u8,
allowhook: true,
nci: 0,
top: StackIdx(0),
stack_last: StackIdx(0),
stack: Vec::new(),
ci: CallInfoIdx(0),
call_info: Vec::new(),
openupval: Vec::new(),
tbclist: Vec::new(),
global: global_rc.clone(),
hook: None,
hookmask: 0,
basehookcount: 0,
hookcount: 0,
errfunc: 0,
n_ccalls: 0,
oldpc: 0,
marked: initial_marked,
cached_thread_id: 0,
gc_check_needed: false,
};
preinit_thread(&mut main_thread, global_rc.clone());
main_thread.inc_nny();
match lua_open(&mut main_thread) {
Ok(()) => {}
Err(_) => {
close_state(&mut main_thread);
return None;
}
}
Some(main_thread)
}
pub fn close(mut state: LuaState) {
close_state(&mut state);
}
pub(crate) fn warning(state: &mut LuaState, msg: &[u8], to_cont: bool) {
let test_warn_enabled = state.global().test_warn_enabled;
if test_warn_enabled {
test_warn(state, msg, to_cont);
return;
}
let has_warnf = state.global().warnf.is_some();
if has_warnf {
let mut warnf = state.global_mut().warnf.take();
if let Some(ref mut f) = warnf {
f(msg, to_cont);
}
state.global_mut().warnf = warnf;
return;
}
default_warn(state, msg, to_cont);
}
fn test_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
let is_control = {
let g = state.global();
!g.test_warn_last_to_cont && !to_cont && msg.first() == Some(&b'@')
};
if is_control {
let mut g = state.global_mut();
match &msg[1..] {
b"off" => g.test_warn_on = false,
b"on" => g.test_warn_on = true,
b"normal" => g.test_warn_mode = TestWarnMode::Normal,
b"allow" => g.test_warn_mode = TestWarnMode::Allow,
b"store" => g.test_warn_mode = TestWarnMode::Store,
_ => {}
}
return;
}
let finished = {
let mut g = state.global_mut();
g.test_warn_last_to_cont = to_cont;
g.test_warn_buffer.extend_from_slice(msg);
if to_cont {
None
} else {
Some((
std::mem::take(&mut g.test_warn_buffer),
g.test_warn_mode,
g.test_warn_on,
))
}
};
let Some((message, mode, warn_on)) = finished else {
return;
};
match mode {
TestWarnMode::Normal => {
if warn_on && message.first() == Some(&b'#') {
write_warning_message(&message);
}
}
TestWarnMode::Allow => {
if warn_on {
write_warning_message(&message);
}
}
TestWarnMode::Store => {
if let Ok(s) = state.intern_str(&message) {
state.push(LuaValue::Str(s));
let _ = crate::api::set_global(state, b"_WARN");
}
}
}
}
fn write_warning_message(message: &[u8]) {
use std::io::Write;
let stderr = std::io::stderr();
let mut h = stderr.lock();
let _ = h.write_all(b"Lua warning: ");
let _ = h.write_all(message);
let _ = h.write_all(b"\n");
}
fn default_warn(state: &mut LuaState, msg: &[u8], to_cont: bool) {
use std::io::Write;
if !to_cont && msg.first() == Some(&b'@') {
match &msg[1..] {
b"off" => state.global_mut().warn_mode = WarnMode::Off,
b"on" => state.global_mut().warn_mode = WarnMode::On,
_ => {}
}
return;
}
let mode = state.global().warn_mode;
match mode {
WarnMode::Off => {}
WarnMode::On | WarnMode::Cont => {
let stderr = std::io::stderr();
let mut h = stderr.lock();
if mode == WarnMode::On {
let _ = h.write_all(b"Lua warning: ");
}
let _ = h.write_all(msg);
if to_cont {
state.global_mut().warn_mode = WarnMode::Cont;
} else {
let _ = h.write_all(b"\n");
state.global_mut().warn_mode = WarnMode::On;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_noop_cclosure(_: &mut LuaState) -> Result<usize, LuaError> {
Ok(0)
}
#[test]
fn external_root_keys_reject_stale_slot_after_reuse() {
let mut roots = ExternalRootSet::default();
let first = roots.insert(LuaValue::Int(1));
assert_eq!(roots.len(), 1);
assert_eq!(roots.get(first), Some(&LuaValue::Int(1)));
assert_eq!(roots.remove(first), Some(LuaValue::Int(1)));
assert!(roots.get(first).is_none());
assert!(roots.remove(first).is_none());
assert_eq!(roots.len(), 0);
assert_eq!(roots.vacant_len(), 1);
assert!(roots.replace(first, LuaValue::Int(9)).is_none());
assert!(roots.is_empty());
let second = roots.insert(LuaValue::Int(2));
assert_eq!(first.index, second.index);
assert_ne!(first, second);
assert!(roots.get(first).is_none());
assert_eq!(roots.get(second), Some(&LuaValue::Int(2)));
assert!(roots.replace(first, LuaValue::Int(3)).is_none());
}
#[test]
fn external_roots_keep_heap_value_alive_until_unrooted() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let table = state.new_table();
assert_eq!(state.global().heap.allgc_count(), 1);
let key = state.external_root_value(LuaValue::Table(table));
state.gc().full_collect();
assert_eq!(state.global().heap.allgc_count(), 1);
assert_eq!(state.global().external_roots.len(), 1);
assert!(state.external_unroot_value(key).is_some());
state.gc().full_collect();
assert_eq!(state.global().heap.allgc_count(), 0);
assert!(state.global().external_roots.is_empty());
}
#[test]
fn table_buffer_accounting_refunds_on_sweep() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let table = state.new_table();
let key = state.external_root_value(LuaValue::Table(table));
let header_bytes = state.global().heap.bytes_used();
assert!(header_bytes > 0);
for i in 1..=128 {
table
.raw_set_int(&mut state, i, LuaValue::Int(i))
.expect("integer table insert should succeed");
}
let grown_bytes = state.global().heap.bytes_used();
assert!(
grown_bytes > header_bytes,
"table array/hash buffer growth must be charged to the GC heap"
);
state.gc().full_collect();
assert_eq!(
state.global().heap.bytes_used(),
grown_bytes,
"rooted table buffer bytes should remain charged after collection"
);
assert!(state.external_unroot_value(key).is_some());
state.gc().full_collect();
assert_eq!(state.global().heap.bytes_used(), 0);
assert_eq!(state.global().heap.allgc_count(), 0);
}
#[test]
fn userdata_buffer_accounting_refunds_on_sweep() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let payload_len = 4096;
let userdata = state
.new_userdata_typed(b"accounting", payload_len, 3)
.expect("userdata allocation should succeed");
state.pop_n(1);
let key = state.external_root_value(LuaValue::UserData(userdata));
let allocated_bytes = state.global().heap.bytes_used();
assert!(
allocated_bytes > payload_len,
"userdata payload bytes must be charged to the GC heap"
);
state.gc().full_collect();
assert_eq!(
state.global().heap.bytes_used(),
allocated_bytes,
"rooted userdata payload bytes should remain charged after collection"
);
assert!(state.external_unroot_value(key).is_some());
state.gc().full_collect();
assert_eq!(state.global().heap.bytes_used(), 0);
assert_eq!(state.global().heap.allgc_count(), 0);
}
#[test]
fn cclosure_upvalue_accounting_refunds_on_sweep() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let nupvalues = 64;
for i in 0..nupvalues {
state.push(LuaValue::Int(i as i64));
}
crate::api::push_cclosure(&mut state, test_noop_cclosure, nupvalues as i32)
.expect("C closure creation should succeed");
let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
panic!("expected heavy C closure");
};
let expected_payload = ccl.buffer_bytes();
let key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
state.pop_n(1);
let allocated_bytes = state.global().heap.bytes_used();
assert!(
allocated_bytes >= expected_payload,
"C closure upvalue vector bytes must be charged to the GC heap"
);
state.gc().full_collect();
assert_eq!(
state.global().heap.bytes_used(),
allocated_bytes,
"rooted C closure payload bytes should remain charged after collection"
);
assert!(state.external_unroot_value(key).is_some());
state.gc().full_collect();
assert_eq!(state.global().heap.bytes_used(), 0);
assert_eq!(state.global().heap.allgc_count(), 0);
}
#[test]
fn proto_and_lclosure_accounting_refunds_on_sweep() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let mut proto = LuaProto::placeholder();
proto.code = vec![lua_types::opcode::Instruction(0); 2048];
proto.lineinfo = vec![0; 2048];
proto.k = vec![LuaValue::Int(1); 512];
let expected_proto_payload = proto.buffer_bytes();
let proto = GcRef::new(proto);
proto.account_buffer(expected_proto_payload as isize);
let closure = state.new_lclosure(proto, 16);
let expected_closure_payload = closure.buffer_bytes();
let key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
let allocated_bytes = state.global().heap.bytes_used();
assert!(
allocated_bytes >= expected_proto_payload + expected_closure_payload,
"proto and Lua closure vector bytes must be charged to the GC heap"
);
state.gc().full_collect();
assert_eq!(
state.global().heap.bytes_used(),
allocated_bytes,
"rooted proto and Lua closure payload bytes should remain charged after collection"
);
assert!(state.external_unroot_value(key).is_some());
state.gc().full_collect();
assert_eq!(state.global().heap.bytes_used(), 0);
assert_eq!(state.global().heap.allgc_count(), 0);
}
#[test]
fn string_buffer_accounting_refunds_on_sweep() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let payload = vec![b'x'; crate::string::MAX_SHORT_LEN + 4096];
let string = state.intern_str(&payload).expect("long string should allocate");
let key = state.external_root_value(LuaValue::Str(string));
let allocated_bytes = state.global().heap.bytes_used();
assert!(
allocated_bytes > payload.len(),
"long string backing bytes must be charged to the GC heap"
);
state.gc().full_collect();
assert_eq!(
state.global().heap.bytes_used(),
allocated_bytes,
"rooted string buffer bytes should remain charged after collection"
);
assert!(state.external_unroot_value(key).is_some());
state.gc().full_collect();
assert_eq!(state.global().heap.bytes_used(), 0);
assert_eq!(state.global().heap.allgc_count(), 0);
}
#[test]
fn interned_short_string_cache_does_not_root_unreferenced_string() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let payload = b"weak-cache-probe-a";
let string = state
.intern_str(payload)
.expect("short string should intern");
let id = string.identity();
assert!(state.global().interned_lt.contains_key(&payload[..]));
assert!(state.global().heap.allocation_token(id).is_some());
state.gc().full_collect();
assert!(!state.global().interned_lt.contains_key(&payload[..]));
assert_eq!(state.global().heap.allocation_token(id), None);
}
#[test]
fn interned_short_string_cache_keeps_reachable_string_until_unrooted() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let payload = b"weak-cache-probe-b";
let string = state
.intern_str(payload)
.expect("short string should intern");
let id = string.identity();
let key = state.external_root_value(LuaValue::Str(string));
state.gc().full_collect();
assert!(state.global().interned_lt.contains_key(&payload[..]));
assert!(state.global().heap.allocation_token(id).is_some());
assert!(state.external_unroot_value(key).is_some());
state.gc().full_collect();
assert!(!state.global().interned_lt.contains_key(&payload[..]));
assert_eq!(state.global().heap.allocation_token(id), None);
}
#[test]
fn gc_phase_predicates_follow_heap_state() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
{
let mut g = state.global_mut();
g.gckind = GcKind::Incremental as u8;
g.lastatomic = 0;
assert!(!g.is_gen_mode());
g.lastatomic = 1;
assert!(g.is_gen_mode());
g.lastatomic = 0;
}
let mut roots = Vec::new();
for _ in 0..16 {
let table = state.new_table();
roots.push(state.external_root_value(LuaValue::Table(table)));
}
let mut saw_keep = false;
let mut saw_sweep = false;
for _ in 0..128 {
state.gc().incremental_step(1);
let g = state.global();
let heap_state = g.heap.gc_state();
assert_eq!(
g.keep_invariant(),
heap_state.is_invariant()
);
assert_eq!(g.is_sweep_phase(), heap_state.is_sweep());
saw_keep |= g.keep_invariant();
saw_sweep |= g.is_sweep_phase();
if heap_state.is_pause() && saw_keep && saw_sweep {
break;
}
}
assert!(saw_keep, "incremental cycle should expose an invariant phase");
assert!(saw_sweep, "incremental cycle should expose a sweep phase");
for key in roots {
assert!(state.external_unroot_value(key).is_some());
}
state.gc().full_collect();
}
#[test]
fn gc_barrier_keeps_new_child_stored_in_black_parent() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let parent = state.new_table();
let parent_key = state.external_root_value(LuaValue::Table(parent));
state.gc().incremental_step(1);
assert!(
state.global().keep_invariant(),
"test setup should leave the parent marked during an active cycle"
);
let child = state.new_table();
let parent_value = LuaValue::Table(parent);
let child_value = LuaValue::Table(child);
parent
.raw_set_int(&mut state, 1, child_value)
.expect("table store should succeed");
state.gc_barrier_back(&parent_value, &child_value);
for _ in 0..128 {
if state.gc().incremental_step(1) {
break;
}
}
assert_eq!(state.global().heap.allgc_count(), 2);
assert_eq!(
parent.get_int(1).as_table().map(|t| t.identity()),
Some(child.identity())
);
assert!(state.external_unroot_value(parent_key).is_some());
state.gc().full_collect();
assert_eq!(state.global().heap.allgc_count(), 0);
}
#[test]
fn generational_mode_promotes_and_barriers_age_objects() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let parent = state.new_table();
let parent_key = state.external_root_value(LuaValue::Table(parent));
state.gc().change_mode(GcKind::Generational);
assert_eq!(parent.0.age(), lua_gc::GcAge::Old);
assert_eq!(parent.0.color(), lua_gc::Color::Black);
let majorbase = state.global().gc_estimate;
assert!(majorbase > 0);
assert!(state.global().gc_debt() <= 0);
let child = state.new_table();
let parent_value = LuaValue::Table(parent);
let child_value = LuaValue::Table(child);
parent
.raw_set_int(&mut state, 1, child_value.clone())
.expect("table store should succeed");
state.gc_barrier_back(&parent_value, &child_value);
assert_eq!(parent.0.age(), lua_gc::GcAge::Touched1);
assert_eq!(parent.0.color(), lua_gc::Color::Gray);
assert_eq!(child.0.age(), lua_gc::GcAge::New);
let metatable = state.new_table();
parent.set_metatable(Some(metatable));
state.gc().obj_barrier(&parent, &metatable);
assert_eq!(metatable.0.age(), lua_gc::GcAge::Old0);
assert!(state.gc().generational_step_minor_only());
assert_eq!(parent.0.age(), lua_gc::GcAge::Touched2);
assert_eq!(child.0.age(), lua_gc::GcAge::Survival);
assert_eq!(metatable.0.age(), lua_gc::GcAge::Old1);
assert_eq!(state.global().gc_estimate, majorbase);
assert!(state.global().gc_debt() <= 0);
state.gc().change_mode(GcKind::Incremental);
assert_eq!(parent.0.age(), lua_gc::GcAge::New);
assert_eq!(child.0.age(), lua_gc::GcAge::New);
assert_eq!(metatable.0.age(), lua_gc::GcAge::New);
assert!(state.external_unroot_value(parent_key).is_some());
state.gc().full_collect();
}
#[test]
fn generational_upvalue_write_barrier_marks_young_child_old0() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let proto = state.new_proto();
let closure = state.new_lclosure(proto, 1);
let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
state.gc().change_mode(GcKind::Generational);
let uv = closure.upval(0);
assert_eq!(uv.0.age(), lua_gc::GcAge::Old);
let child = state.new_table();
state
.upvalue_set(&closure, 0, LuaValue::Table(child))
.expect("closed upvalue write should succeed");
assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
assert!(state.external_unroot_value(closure_key).is_some());
state.gc().full_collect();
}
#[test]
fn cclosure_setupvalue_replaces_upvalue() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let first = state.new_table();
state.push(LuaValue::Table(first));
crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
.expect("C closure creation should succeed");
let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
panic!("expected heavy C closure");
};
let second = state.new_table();
state.push(LuaValue::Table(second));
let name = crate::api::setup_value(&mut state, -2, 1)
.expect("C closure upvalue should exist");
assert!(name.is_empty());
let upvalues = ccl.upvalues.borrow();
let LuaValue::Table(actual) = upvalues[0].clone() else {
panic!("expected table upvalue");
};
assert_eq!(actual.identity(), second.identity());
}
#[test]
fn generational_cclosure_setupvalue_barrier_marks_young_child_old0() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
state.push(LuaValue::Nil);
crate::api::push_cclosure(&mut state, test_noop_cclosure, 1)
.expect("C closure creation should succeed");
let LuaValue::Function(LuaClosure::C(ccl)) = state.get_at(state.top_idx() - 1) else {
panic!("expected heavy C closure");
};
let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::C(ccl)));
state.gc().change_mode(GcKind::Generational);
assert_eq!(ccl.0.age(), lua_gc::GcAge::Old);
let child = state.new_table();
state.push(LuaValue::Table(child));
crate::api::setup_value(&mut state, -2, 1)
.expect("C closure upvalue should exist");
assert_eq!(child.0.age(), lua_gc::GcAge::Old0);
assert!(state.external_unroot_value(closure_key).is_some());
state.gc().full_collect();
}
#[test]
fn generational_closure_upvalue_slot_barrier_marks_new_upval_old0() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let proto = state.new_proto();
let closure = state.new_lclosure(proto, 1);
let closure_key = state.external_root_value(LuaValue::Function(LuaClosure::Lua(closure)));
state.gc().change_mode(GcKind::Generational);
assert_eq!(closure.0.age(), lua_gc::GcAge::Old);
let replacement = state.new_upval_closed(LuaValue::Nil);
closure.set_upval(0, replacement);
state.gc().obj_barrier(&closure, &replacement);
assert_eq!(replacement.0.age(), lua_gc::GcAge::Old0);
assert!(state.external_unroot_value(closure_key).is_some());
state.gc().full_collect();
}
#[test]
fn cross_thread_upvalue_mirror_traces_values_as_roots() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let mirrored = state.new_table();
state
.global_mut()
.cross_thread_upvals
.insert((999, StackIdx(0)), LuaValue::Table(mirrored));
state.gc().full_collect();
assert_eq!(state.global().heap.allgc_count(), 1);
state.global_mut().cross_thread_upvals.clear();
state.gc().full_collect();
assert_eq!(state.global().heap.allgc_count(), 0);
}
#[test]
fn generational_full_collect_promotes_new_survivors_to_old() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
state.gc().change_mode(GcKind::Generational);
let table = state.new_table();
let table_key = state.external_root_value(LuaValue::Table(table));
assert_eq!(table.0.age(), lua_gc::GcAge::New);
state.gc().full_collect();
assert_eq!(table.0.age(), lua_gc::GcAge::Old);
assert_eq!(table.0.color(), lua_gc::Color::Black);
assert!(state.external_unroot_value(table_key).is_some());
state.gc().full_collect();
}
#[test]
fn gc_packed_params_return_user_visible_values() {
let mut state = new_state().expect("state should initialize");
assert_eq!(
crate::api::gc(&mut state, crate::api::GcArgs::SetPause { value: 200 }),
200
);
assert_eq!(state.global().gc_pause_param(), 200);
assert_eq!(
crate::api::gc(&mut state, crate::api::GcArgs::SetStepMul { value: 200 }),
100
);
assert_eq!(state.global().gc_stepmul_param(), 200);
crate::api::gc(
&mut state,
crate::api::GcArgs::Gen {
minormul: 0,
majormul: 200,
},
);
assert_eq!(state.global().gc_genmajormul_param(), 200);
}
#[test]
fn generational_step_runs_bad_major_when_growth_exceeds_genmajormul() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let root = state.new_table();
let root_key = state.external_root_value(LuaValue::Table(root));
state.gc().change_mode(GcKind::Generational);
let root_value = LuaValue::Table(root);
for i in 1..=64 {
let child = state.new_table();
let child_value = LuaValue::Table(child);
root
.raw_set_int(&mut state, i, child_value.clone())
.expect("table store should succeed");
state.gc_barrier_back(&root_value, &child_value);
}
{
let mut g = state.global_mut();
g.gc_estimate = 1;
set_debt(&mut *g, 1);
}
assert!(state.gc().generational_step());
let g = state.global();
assert!(g.is_gen_mode());
assert!(g.lastatomic > 0, "bad major collection should arm stepgenfull");
assert!(g.gc_estimate > 1);
assert!(g.gc_debt() <= 0);
assert_eq!(root.0.age(), lua_gc::GcAge::Old);
drop(g);
assert!(state.external_unroot_value(root_key).is_some());
state.gc().full_collect();
}
#[test]
fn generational_implicit_step_runs_major_when_heap_threshold_exceeded() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let root = state.new_table();
let root_key = state.external_root_value(LuaValue::Table(root));
state.gc().change_mode(GcKind::Generational);
let root_value = LuaValue::Table(root);
for i in 1..=64 {
let child = state.new_table();
let child_value = LuaValue::Table(child);
root
.raw_set_int(&mut state, i, child_value.clone())
.expect("table store should succeed");
state.gc_barrier_back(&root_value, &child_value);
}
{
let mut g = state.global_mut();
g.gc_estimate = 1;
set_debt(&mut *g, -1);
g.heap.set_threshold_bytes(1);
}
assert!(state.gc().generational_step());
let g = state.global();
assert!(g.is_gen_mode());
assert!(
g.lastatomic > 0,
"implicit threshold-triggered growth should arm a bad major"
);
assert!(g.gc_debt() <= 0);
drop(g);
assert!(state.external_unroot_value(root_key).is_some());
state.gc().full_collect();
}
#[test]
fn generational_stepgenfull_returns_to_gen_after_good_collection() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
let root = state.new_table();
let root_key = state.external_root_value(LuaValue::Table(root));
state.gc().change_mode(GcKind::Generational);
{
let mut g = state.global_mut();
g.lastatomic = 1024;
}
assert!(state.gc().generational_step());
let g = state.global();
assert_eq!(g.gckind, GcKind::Generational as u8);
assert_eq!(g.lastatomic, 0);
assert!(g.gc_debt() <= 0);
assert_eq!(root.0.age(), lua_gc::GcAge::Old);
assert_eq!(root.0.color(), lua_gc::Color::Black);
drop(g);
assert!(state.external_unroot_value(root_key).is_some());
state.gc().full_collect();
}
#[test]
fn generational_step_zero_reports_false_without_positive_debt() {
let mut state = new_state().expect("state should initialize");
let _heap_guard = {
let g = state.global();
lua_gc::HeapGuard::push(&g.heap)
};
state.gc().change_mode(GcKind::Generational);
assert_eq!(
crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 0 }),
0
);
assert_eq!(
crate::api::gc(&mut state, crate::api::GcArgs::Step { data: 1 }),
1
);
}
}