Skip to main content

luaur_vm/functions/
lua_gc.rs

1use crate::enums::lua_gc_op::lua_GCOp;
2use crate::functions::lua_c_fullgc::lua_c_fullgc;
3use crate::functions::lua_c_step::luaC_step;
4use crate::functions::lua_c_validate::lua_c_validate;
5use crate::macros::cast_int::cast_int;
6use crate::macros::condhardmemtests::condhardmemtests;
7use crate::records::gc_cycle_metrics::GCCycleMetrics;
8use crate::records::global_state::global_State;
9use crate::type_aliases::lua_state::lua_State;
10use luaur_common::macros::luau_assert::LUAU_ASSERT;
11
12pub const GCSpause: core::ffi::c_int = 0;
13pub const GCSpropagate: core::ffi::c_int = 1;
14pub const GCSpropagateagain: core::ffi::c_int = 2;
15pub const GCSatomic: core::ffi::c_int = 3;
16pub const GCSsweep: core::ffi::c_int = 4;
17
18#[allow(non_snake_case)]
19pub fn lua_gc(
20    L: *mut lua_State,
21    what: core::ffi::c_int,
22    data: core::ffi::c_int,
23) -> core::ffi::c_int {
24    let mut res: core::ffi::c_int = 0;
25    unsafe {
26        condhardmemtests!(lua_c_validate(L), 1);
27        let g: *mut global_State = (*L).global;
28        match what as i32 {
29            x if x == lua_GCOp::LUA_GCSTOP as i32 => {
30                (*g).GCthreshold = usize::MAX;
31            }
32            x if x == lua_GCOp::LUA_GCRESTART as i32 => {
33                (*g).GCthreshold = (*g).totalbytes;
34            }
35            x if x == lua_GCOp::LUA_GCCOLLECT as i32 => {
36                lua_c_fullgc(L);
37            }
38            x if x == lua_GCOp::LUA_GCCOUNT as i32 => {
39                res = cast_int!((*g).totalbytes >> 10);
40            }
41            x if x == lua_GCOp::LUA_GCCOUNTB as i32 => {
42                res = cast_int!((*g).totalbytes & 1023);
43            }
44            x if x == lua_GCOp::LUA_GCISRUNNING as i32 => {
45                res = if (*g).GCthreshold != usize::MAX { 1 } else { 0 };
46            }
47            x if x == lua_GCOp::LUA_GCSTEP as i32 => {
48                let amount: usize = (data as usize) << 10;
49                let gcstate_i32: i32 = i32::from((*g).gcstate);
50
51                let oldcredit: ptrdiff_t = if gcstate_i32 == GCSpause {
52                    0
53                } else {
54                    (*g).GCthreshold as ptrdiff_t - (*g).totalbytes as ptrdiff_t
55                };
56
57                // temporarily adjust the threshold so that we can perform GC work
58                if amount <= (*g).totalbytes {
59                    (*g).GCthreshold = (*g).totalbytes - amount;
60                } else {
61                    (*g).GCthreshold = 0;
62                }
63
64                #[cfg(feature = "luai_gcmetrics")]
65                let startmarktime = (*g).gcmetrics.currcycle.marktime;
66                #[cfg(feature = "luai_gcmetrics")]
67                let startsweeptime = (*g).gcmetrics.currcycle.sweeptime;
68
69                // track how much work the loop will actually perform
70                let mut actualwork: usize = 0;
71
72                while (*g).GCthreshold <= (*g).totalbytes {
73                    let stepsize = luaC_step(L, false);
74                    actualwork += stepsize;
75
76                    let gcstate_i32 = i32::from((*g).gcstate);
77                    if gcstate_i32 == GCSpause {
78                        res = 1; // signal it
79                        break;
80                    }
81                }
82
83                #[cfg(feature = "luai_gcmetrics")]
84                {
85                    // record explicit step statistics
86                    let cyclemetrics: &mut GCCycleMetrics = if i32::from((*g).gcstate) == GCSpause {
87                        &mut (*g).gcmetrics.lastcycle
88                    } else {
89                        &mut (*g).gcmetrics.currcycle
90                    };
91
92                    let totalmarktime = cyclemetrics.marktime - startmarktime;
93                    let totalsweeptime = cyclemetrics.sweeptime - startsweeptime;
94
95                    if totalmarktime > 0.0 {
96                        cyclemetrics.markexplicitsteps += 1;
97
98                        if totalmarktime > cyclemetrics.markmaxexplicittime {
99                            cyclemetrics.markmaxexplicittime = totalmarktime;
100                        }
101                    }
102
103                    if totalsweeptime > 0.0 {
104                        cyclemetrics.sweepexplicitsteps += 1;
105
106                        if totalsweeptime > cyclemetrics.sweepmaxexplicittime {
107                            cyclemetrics.sweepmaxexplicittime = totalsweeptime;
108                        }
109                    }
110                }
111
112                // if cycle hasn't finished, advance threshold forward for the amount of extra work performed
113                let gcstate_i32 = i32::from((*g).gcstate);
114                if gcstate_i32 != GCSpause {
115                    // if a new cycle was triggered by explicit step, old 'credit' of GC work is 0
116                    let newthreshold =
117                        (*g).totalbytes as ptrdiff_t + actualwork as ptrdiff_t + oldcredit;
118                    (*g).GCthreshold = if newthreshold < 0 {
119                        0
120                    } else {
121                        newthreshold as usize
122                    };
123                }
124            }
125            x if x == lua_GCOp::LUA_GCSETGOAL as i32 => {
126                res = (*g).gcgoal;
127                (*g).gcgoal = data;
128            }
129            x if x == lua_GCOp::LUA_GCSETSTEPMUL as i32 => {
130                res = (*g).gcstepmul;
131                (*g).gcstepmul = data;
132            }
133            x if x == lua_GCOp::LUA_GCSETSTEPSIZE as i32 => {
134                // GC values are expressed in Kbytes: #bytes/2^10
135                res = (*g).gcstepsize >> 10;
136                (*g).gcstepsize = data << 10;
137            }
138            _ => {
139                res = -1; // invalid option
140            }
141        }
142    }
143    res
144}
145
146type ptrdiff_t = core::ffi::c_long;