Skip to main content

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}