use crate::runtime::Value;
use crate::version::LuaVersion;
use crate::vm::argcheck::{self, Args};
use crate::vm::error::LuaError;
use crate::vm::exec::Vm;
const GC_STEP_OBJS: usize = 32;
const LUNA_PAUSE: i64 = 200;
const LUNA_STEPMUL: i64 = 100;
const LUNA_STEPSIZE: i64 = 13;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Param {
Pause,
StepMul,
StepSize,
MajorInc,
MinorMul,
MajorMul,
MajorMinor,
MinorMajor,
}
const PARAMS: [Param; 8] = [
Param::Pause,
Param::StepMul,
Param::StepSize,
Param::MajorInc,
Param::MinorMul,
Param::MajorMul,
Param::MajorMinor,
Param::MinorMajor,
];
pub(crate) struct GcParams {
version: LuaVersion,
stored: [i64; PARAMS.len()],
}
impl GcParams {
pub(crate) fn new(version: LuaVersion) -> Self {
let mut p = GcParams {
version,
stored: [0; PARAMS.len()],
};
for param in PARAMS {
if let Some(v) = p.default(param) {
p.set(param, v);
}
}
p
}
fn slot(param: Param) -> usize {
PARAMS
.iter()
.position(|&q| q == param)
.expect("every Param is listed in PARAMS")
}
fn default(&self, param: Param) -> Option<i32> {
use LuaVersion::*;
use Param::*;
match (self.version, param) {
(Lua51 | Lua52 | Lua53, Pause | StepMul) => Some(200),
(Lua52, MajorInc) => Some(200),
(Lua54 | MacroLua, Pause) => Some(200),
(Lua54 | MacroLua, StepMul) => Some(100),
(Lua54 | MacroLua, StepSize) => Some(13),
(Lua54 | MacroLua, MinorMul) => Some(20),
(Lua54 | MacroLua, MajorMul) => Some(100),
(Lua55, Pause) => Some(250),
(Lua55, StepMul) => Some(200),
(Lua55, StepSize) => Some(9600),
(Lua55, MinorMul) => Some(20),
(Lua55, MajorMinor) => Some(50),
(Lua55, MinorMajor) => Some(70),
_ => None,
}
}
fn set(&mut self, param: Param, v: i32) {
use LuaVersion::*;
let stored = match self.version {
Lua51 | Lua52 => i64::from(v),
Lua53 if param == Param::StepMul => i64::from(v.max(40)),
Lua53 => i64::from(v),
Lua54 | MacroLua => match param {
Param::Pause | Param::StepMul | Param::MajorMul => i64::from((v / 4) as u8),
_ => i64::from(v as u8),
},
Lua55 => i64::from(code_param(v as u32)),
};
self.stored[Self::slot(param)] = stored;
}
fn get(&self, param: Param) -> i64 {
let s = self.stored[Self::slot(param)];
match self.version {
LuaVersion::Lua54 | LuaVersion::MacroLua => match param {
Param::Pause | Param::StepMul | Param::MajorMul => s * 4,
_ => s,
},
LuaVersion::Lua55 => apply_param(s as u8, 100),
_ => s,
}
}
fn luna_scaled(&self, param: Param, luna_default: i64) -> i64 {
match self.default(param) {
Some(d) => self.get(param) * luna_default / i64::from(d),
None => luna_default,
}
}
}
fn code_param(p: u32) -> u8 {
if u64::from(p) >= (0x1F_u64 << (0xF - 7 - 1)) * 100 {
return 0xFF;
}
let p = (p * 128).div_ceil(100);
if p < 0x10 {
return p as u8;
}
let log = ceil_log2(p + 1) - 5;
(((p >> log) - 0x10) | ((log + 1) << 4)) as u8
}
fn ceil_log2(x: u32) -> u32 {
32 - (x - 1).leading_zeros()
}
fn apply_param(p: u8, x: i64) -> i64 {
let mut m = i64::from(p & 0xF);
let mut e = i32::from(p >> 4);
if e > 0 {
e -= 1;
m += 0x10;
}
e -= 7;
if e >= 0 { (x * m) << e } else { (x * m) >> -e }
}
pub(crate) fn nat_collectgarbage(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let a = Args::new(fs, nargs);
let v = vm.version();
let opt = argcheck::check_option(vm, a, 0, Some("collect"), options(v))?;
let opt = options(v)[opt];
let ex = if v <= LuaVersion::Lua53 {
argcheck::opt_integer(vm, a, 1, 0)? as i32
} else {
0
};
if vm.gc_is_finalizing() && v >= LuaVersion::Lua54 {
return Ok(vm.nat_return(fs, &[Value::Nil]));
}
let out: Vec<Value> = match opt {
"collect" => {
if !vm.gc_is_finalizing() {
if v <= LuaVersion::Lua53 {
vm.collect_garbage_propagating()?;
} else {
vm.collect_garbage();
}
}
vec![Value::Int(0)]
}
"count" => {
let bytes = vm.heap.bytes();
let kb = Value::Float(bytes as f64 / 1024.0);
if v == LuaVersion::Lua52 {
vec![kb, Value::Int((bytes % 1024) as i64)]
} else {
vec![kb]
}
}
"step" => {
let n = if v <= LuaVersion::Lua53 {
i64::from(ex)
} else {
argcheck::opt_integer(vm, a, 1, 0)?
};
vec![Value::Bool(step(vm, n))]
}
"stop" => {
vm.heap.gc_set_stopped(true);
vec![Value::Int(0)]
}
"restart" => {
vm.heap.gc_set_stopped(false);
vec![Value::Int(0)]
}
"isrunning" => vec![Value::Bool(
!vm.heap.gc_is_stopped() && !vm.gc_is_finalizing(),
)],
"setpause" => vec![Value::Int(set_param(vm, a, Param::Pause, ex)?)],
"setstepmul" => vec![Value::Int(set_param(vm, a, Param::StepMul, ex)?)],
"setmajorinc" => vec![Value::Int(set_param(vm, a, Param::MajorInc, ex)?)],
"incremental" | "generational" => switch_mode(vm, a, opt)?,
"param" => vec![param(vm, a)?],
_ => unreachable!("check_option returned an index into options()"),
};
Ok(vm.nat_return(fs, &out))
}
fn options(v: LuaVersion) -> &'static [&'static str] {
match v {
LuaVersion::Lua51 => &[
"stop",
"restart",
"collect",
"count",
"step",
"setpause",
"setstepmul",
],
LuaVersion::Lua52 => &[
"stop",
"restart",
"collect",
"count",
"step",
"setpause",
"setstepmul",
"setmajorinc",
"isrunning",
"generational",
"incremental",
],
LuaVersion::Lua53 => &[
"stop",
"restart",
"collect",
"count",
"step",
"setpause",
"setstepmul",
"isrunning",
],
LuaVersion::Lua54 | LuaVersion::MacroLua => &[
"stop",
"restart",
"collect",
"count",
"step",
"setpause",
"setstepmul",
"isrunning",
"generational",
"incremental",
],
LuaVersion::Lua55 => &[
"stop",
"restart",
"collect",
"count",
"step",
"isrunning",
"generational",
"incremental",
"param",
],
}
}
fn step(vm: &mut Vm, n: i64) -> bool {
if vm.gc_mode_is_generational() {
vm.collect_garbage();
return false;
}
let budget = if n > 0 {
(n as usize).saturating_mul(GC_STEP_OBJS)
} else if vm.version() <= LuaVersion::Lua53 {
GC_STEP_OBJS
} else {
match vm.gc_stepsize() {
0 => usize::MAX,
ss => (ss.max(1) as usize).saturating_mul(GC_STEP_OBJS),
}
};
vm.gc_step(budget)
}
fn set_param(vm: &mut Vm, a: Args, param: Param, ex: i32) -> Result<i64, LuaError> {
let new = if vm.version() <= LuaVersion::Lua53 {
ex
} else {
argcheck::opt_integer(vm, a, 1, 0)? as i32
};
let prev = vm.gc_params.get(param);
vm.gc_params.set(param, new);
sync_pacing(vm);
Ok(prev)
}
fn switch_mode(vm: &mut Vm, a: Args, opt: &'static str) -> Result<Vec<Value>, LuaError> {
let v = vm.version();
if v == LuaVersion::Lua54 || v == LuaVersion::MacroLua {
let params: &[Param] = if opt == "incremental" {
&[Param::Pause, Param::StepMul, Param::StepSize]
} else {
&[Param::MinorMul, Param::MajorMul]
};
let mut values = Vec::with_capacity(params.len());
for i in 0..params.len() as u32 {
values.push(argcheck::opt_integer(vm, a, 1 + i, 0)? as i32);
}
for (¶m, value) in params.iter().zip(values) {
if value != 0 {
vm.gc_params.set(param, value);
}
}
sync_pacing(vm);
}
let prev = vm.gc_switch_mode(opt);
Ok(vec![if v == LuaVersion::Lua52 {
Value::Int(0)
} else {
Value::Str(vm.heap.intern(prev.as_bytes()))
}])
}
fn param(vm: &mut Vm, a: Args) -> Result<Value, LuaError> {
const NAMES: [&str; 6] = [
"minormul",
"majorminor",
"minormajor",
"pause",
"stepmul",
"stepsize",
];
const MAP: [Param; 6] = [
Param::MinorMul,
Param::MajorMinor,
Param::MinorMajor,
Param::Pause,
Param::StepMul,
Param::StepSize,
];
let which = MAP[argcheck::check_option(vm, a, 1, None, &NAMES)?];
let value = argcheck::opt_integer(vm, a, 2, -1)? as i32;
let prev = vm.gc_params.get(which);
if value >= 0 {
vm.gc_params.set(which, value);
sync_pacing(vm);
}
Ok(Value::Int(prev))
}
fn sync_pacing(vm: &mut Vm) {
let p = &vm.gc_params;
let pause = p.luna_scaled(Param::Pause, LUNA_PAUSE);
let stepmul = p.luna_scaled(Param::StepMul, LUNA_STEPMUL);
let stepsize = match p.default(Param::StepSize) {
Some(_) if p.get(Param::StepSize) == 0 => 0,
Some(_) => p.luna_scaled(Param::StepSize, LUNA_STEPSIZE).max(1),
None => LUNA_STEPSIZE,
};
vm.set_gc_pacing(pause, stepmul, stepsize);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn param_encoding_reads_back_like_puc_5_5() {
for (v, back) in [
(1, 1),
(7, 7),
(15, 15),
(16, 16),
(99, 96),
(100, 100),
(101, 100),
(123, 118),
(128, 125),
(1000, 1000),
(12345, 12000),
(1_000_000, 396_800),
] {
assert_eq!(apply_param(code_param(v), 100), back, "param {v}");
}
}
#[test]
fn defaults_read_back_like_puc_5_5() {
let p = GcParams::new(LuaVersion::Lua55);
let got: Vec<i64> = [
Param::MinorMul,
Param::MajorMinor,
Param::MinorMajor,
Param::Pause,
Param::StepMul,
Param::StepSize,
]
.iter()
.map(|&q| p.get(q))
.collect();
assert_eq!(got, [20, 50, 68, 250, 200, 9600]);
}
#[test]
fn five_four_stores_a_quarter_in_a_byte() {
let mut p = GcParams::new(LuaVersion::Lua54);
p.set(Param::Pause, 150);
assert_eq!(p.get(Param::Pause), 148);
p.set(Param::Pause, 2000); assert_eq!(p.get(Param::Pause), 976);
}
#[test]
fn defaults_leave_luna_pacing_unchanged() {
for v in [
LuaVersion::Lua51,
LuaVersion::Lua52,
LuaVersion::Lua53,
LuaVersion::Lua54,
LuaVersion::Lua55,
] {
let p = GcParams::new(v);
assert_eq!(p.luna_scaled(Param::Pause, LUNA_PAUSE), LUNA_PAUSE, "{v:?}");
assert_eq!(
p.luna_scaled(Param::StepMul, LUNA_STEPMUL),
LUNA_STEPMUL,
"{v:?}"
);
}
}
}