luaur_rt/gc.rs
1//! Garbage-collector control. Mirrors `mlua::Lua`'s `gc_*` surface and the
2//! `mlua::state::{GcMode, GcIncParams, GcGenParams}` types.
3//!
4//! Luau ships a single **incremental** GC (no generational mode). The control
5//! ops map onto luaur's `lua_gc`:
6//!
7//! | mlua | `lua_gc` op |
8//! |---------------------|---------------------|
9//! | `gc_collect` | `LUA_GCCOLLECT` |
10//! | `gc_stop` | `LUA_GCSTOP` |
11//! | `gc_restart` | `LUA_GCRESTART` |
12//! | `gc_is_running` | `LUA_GCISRUNNING` |
13//! | `gc_count` | `LUA_GCCOUNT`/`..B` |
14//! | `gc_step` | `LUA_GCSTEP` |
15//! | `gc_inc(goal,mul,sz)`| `LUA_GCSETGOAL/..` |
16//!
17//! **DEVIATION:** Luau has no generational GC, so `gc_gen` and
18//! `GcMode::Generational` are not backed by the VM. `gc_set_mode` accepts the
19//! incremental mode (applying its params) and *reports* the previous mode as
20//! incremental; passing `Generational` is a no-op that returns the current
21//! (incremental) mode, matching the only behavior Luau can honor.
22
23use crate::error::Result;
24use crate::state::Lua;
25use crate::sys::*;
26
27/// Parameters for Luau's incremental GC, mirroring `mlua::state::GcIncParams`.
28///
29/// On Luau the tunables are the **goal** (heap-growth target percentage),
30/// the **step multiplier**, and the **step size** (KB). (Lua 5.x's `pause` is
31/// replaced by `goal` here — see [`GcIncParams::goal`].)
32#[derive(Debug, Clone, Copy, Default)]
33pub struct GcIncParams {
34 pub(crate) goal: Option<core::ffi::c_int>,
35 pub(crate) step_multiplier: Option<core::ffi::c_int>,
36 pub(crate) step_size: Option<core::ffi::c_int>,
37}
38
39impl GcIncParams {
40 /// The heap-growth goal (percentage). Luau's analog of Lua's `pause`.
41 /// Mirrors `mlua::state::GcIncParams::goal`.
42 pub fn goal(mut self, goal: u32) -> Self {
43 self.goal = Some(goal as core::ffi::c_int);
44 self
45 }
46
47 /// The GC step multiplier (percentage of allocation to collect per step).
48 /// Mirrors `mlua::state::GcIncParams::step_multiplier`.
49 pub fn step_multiplier(mut self, mul: u32) -> Self {
50 self.step_multiplier = Some(mul as core::ffi::c_int);
51 self
52 }
53
54 /// The GC step size in KB. Mirrors `mlua::state::GcIncParams::step_size`.
55 pub fn step_size(mut self, size: u32) -> Self {
56 self.step_size = Some(size as core::ffi::c_int);
57 self
58 }
59}
60
61/// Parameters for a generational GC, mirroring `mlua::state::GcGenParams`.
62///
63/// **DEVIATION:** Luau has no generational GC; this exists only for signature
64/// parity with mlua's Lua 5.4/5.5 surface and is never honored by the VM.
65#[derive(Debug, Clone, Copy, Default)]
66pub struct GcGenParams {
67 pub minor_multiplier: u32,
68 pub major_multiplier: u32,
69}
70
71/// The GC operating mode, mirroring `mlua::state::GcMode`.
72///
73/// Luau only supports [`GcMode::Incremental`]; [`GcMode::Generational`] is
74/// provided for signature parity and is treated as a no-op by [`Lua::gc_set_mode`].
75#[derive(Debug, Clone, Copy)]
76pub enum GcMode {
77 /// Incremental GC (the only mode Luau supports).
78 Incremental(GcIncParams),
79 /// Generational GC — **not supported by Luau** (see the module note).
80 Generational(GcGenParams),
81}
82
83impl Lua {
84 /// The number of bytes currently used by the VM. Mirrors
85 /// `mlua::Lua::used_memory` (luaur's `totalbytes`).
86 pub fn used_memory(&self) -> usize {
87 unsafe {
88 let g = (*self.state()).global;
89 (*g).totalbytes
90 }
91 }
92
93 /// Whether the GC is currently running. Mirrors `mlua::Lua::gc_is_running`.
94 pub fn gc_is_running(&self) -> bool {
95 lua_gc(self.state(), lua_GCOp::LUA_GCISRUNNING as c_int, 0) != 0
96 }
97
98 /// Stop the GC. Mirrors `mlua::Lua::gc_stop`.
99 pub fn gc_stop(&self) {
100 lua_gc(self.state(), lua_GCOp::LUA_GCSTOP as c_int, 0);
101 }
102
103 /// Restart the GC. Mirrors `mlua::Lua::gc_restart`.
104 pub fn gc_restart(&self) {
105 lua_gc(self.state(), lua_GCOp::LUA_GCRESTART as c_int, 0);
106 }
107
108 /// The total memory in use, in KB (the `LUA_GCCOUNT` op). Mirrors
109 /// `mlua::Lua::gc_count`.
110 pub fn gc_count(&self) -> usize {
111 let kb = lua_gc(self.state(), lua_GCOp::LUA_GCCOUNT as c_int, 0).max(0) as usize;
112 kb
113 }
114
115 /// Run one incremental GC step over `kbytes` of work. Returns whether a full
116 /// collection cycle finished. Mirrors `mlua::Lua::gc_step_kbytes`/`gc_step`.
117 pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result<bool> {
118 let finished = lua_gc(self.state(), lua_GCOp::LUA_GCSTEP as c_int, kbytes) != 0;
119 Ok(finished)
120 }
121
122 /// Run a default-size incremental GC step. Mirrors `mlua::Lua::gc_step`.
123 pub fn gc_step(&self) -> Result<bool> {
124 self.gc_step_kbytes(0)
125 }
126
127 /// Apply incremental-GC parameters (goal / step multiplier / step size).
128 /// Mirrors `mlua::Lua::gc_inc`; returns the previous [`GcMode`] (always
129 /// incremental on Luau).
130 pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GcMode {
131 let state = self.state();
132 // mlua maps `pause` -> goal on the Luau backend.
133 if pause > 0 {
134 lua_gc(state, lua_GCOp::LUA_GCSETGOAL as c_int, pause);
135 }
136 if step_multiplier > 0 {
137 lua_gc(state, lua_GCOp::LUA_GCSETSTEPMUL as c_int, step_multiplier);
138 }
139 if step_size > 0 {
140 lua_gc(state, lua_GCOp::LUA_GCSETSTEPSIZE as c_int, step_size);
141 }
142 GcMode::Incremental(GcIncParams::default())
143 }
144
145 /// Set the GC mode, returning the previous mode. Mirrors
146 /// `mlua::Lua::gc_set_mode`.
147 ///
148 /// **DEVIATION:** Luau is always incremental. Passing
149 /// [`GcMode::Incremental`] applies its params and returns the prior
150 /// (incremental) mode; passing [`GcMode::Generational`] is a no-op that
151 /// returns the current incremental mode.
152 pub fn gc_set_mode(&self, mode: GcMode) -> GcMode {
153 let state = self.state();
154 match mode {
155 GcMode::Incremental(p) => {
156 if let Some(goal) = p.goal {
157 lua_gc(state, lua_GCOp::LUA_GCSETGOAL as c_int, goal);
158 }
159 if let Some(mul) = p.step_multiplier {
160 lua_gc(state, lua_GCOp::LUA_GCSETSTEPMUL as c_int, mul);
161 }
162 if let Some(sz) = p.step_size {
163 lua_gc(state, lua_GCOp::LUA_GCSETSTEPSIZE as c_int, sz);
164 }
165 }
166 GcMode::Generational(_) => {
167 // Luau has no generational GC; nothing to apply.
168 }
169 }
170 // Luau's only real mode is incremental.
171 GcMode::Incremental(GcIncParams::default())
172 }
173}