Skip to content

Commit 1a40b9d

Browse files
committed
support __gc and __mode
1 parent f4df326 commit 1a40b9d

File tree

3 files changed

+463
-30
lines changed

3 files changed

+463
-30
lines changed

crates/luars/src/gc.rs

Lines changed: 209 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -74,28 +74,27 @@ pub struct GC {
7474
// Lua 5.4 GC debt mechanism
7575
// GC runs when: GCdebt > 0
7676
// totalbytes = actual_bytes - GCdebt
77-
pub(crate) gc_debt: isize, // Bytes allocated not yet compensated by collector
78-
pub(crate) total_bytes: usize, // Number of bytes currently allocated - GCdebt
79-
gc_estimate: usize, // Estimate of non-garbage memory
80-
77+
pub(crate) gc_debt: isize, // Bytes allocated not yet compensated by collector
78+
pub(crate) total_bytes: usize, // Number of bytes currently allocated - GCdebt
79+
gc_estimate: usize, // Estimate of non-garbage memory
80+
8181
// GC parameters (Lua 5.4 style)
82-
gc_pause: usize, // Pause parameter (default 200 = 200%)
83-
gc_step_mul: usize, // Step multiplier (default 100)
84-
gen_minor_mul: u8, // Minor generational control (default 20)
85-
gen_major_mul: u8, // Major generational control (default 100)
86-
82+
gc_pause: usize, // Pause parameter (default 200 = 200%)
83+
8784
// GC mode
88-
gc_kind: GCKind, // KGC_INC or KGC_GEN
89-
last_atomic: usize, // For generational mode
90-
85+
gc_kind: GCKind, // KGC_INC or KGC_GEN
86+
9187
// Shrink optimization - avoid frequent shrinking
92-
shrink_cooldown: u32, // Shrink 冷却计数器
93-
shrink_threshold: u32, // Shrink 阈值
94-
88+
shrink_cooldown: u32, // Shrink 冷却计数器
89+
shrink_threshold: u32, // Shrink 阈值
90+
9591
// Generational GC state
9692
allocations_since_minor_gc: usize,
9793
minor_gc_count: usize,
98-
94+
95+
// Finalization support (__gc metamethod)
96+
finalize_queue: Vec<(GcObjectType, u32)>, // Objects awaiting finalization
97+
9998
// Statistics
10099
collection_count: usize,
101100
stats: GCStats,
@@ -125,19 +124,16 @@ impl GC {
125124
GC {
126125
objects: HashMap::new(),
127126
next_gc_id: 1,
128-
gc_debt: -(200 * 1024), // Start with negative debt (can allocate 200KB)
127+
gc_debt: -(200 * 1024), // Start with negative debt (can allocate 200KB)
129128
total_bytes: 0,
130129
gc_estimate: 0,
131130
gc_pause: 200, // 200% pause (LUAI_GCPAUSE)
132-
gc_step_mul: 100, // 100 step mul (LUAI_GCMUL)
133-
gen_minor_mul: 20, // LUAI_GENMINORMUL
134-
gen_major_mul: 100, // LUAI_GENMAJORMUL
135131
gc_kind: GCKind::Generational, // Default to generational (like Lua 5.4)
136-
last_atomic: 0,
137132
shrink_cooldown: 0,
138-
shrink_threshold: 10, // 每 10 次 GC 才 shrink 一次
133+
shrink_threshold: 10, // 每 10 次 GC 才 shrink 一次
139134
allocations_since_minor_gc: 0,
140135
minor_gc_count: 0,
136+
finalize_queue: Vec::new(), // __gc finalizer queue
141137
collection_count: 0,
142138
stats: GCStats::default(),
143139
}
@@ -180,18 +176,19 @@ impl GC {
180176
pub fn should_collect(&self) -> bool {
181177
self.gc_debt > 0
182178
}
183-
179+
184180
/// Perform one step of GC work (like luaC_step in Lua 5.4)
185181
pub fn step(&mut self, roots: &[LuaValue], object_pool: &mut crate::object_pool::ObjectPool) {
186182
if !self.should_collect() {
187183
return;
188184
}
189-
185+
190186
// Determine which collection to run
191187
match self.gc_kind {
192188
GCKind::Generational => {
193189
// Simple generational: check if we should do minor or major
194-
if self.minor_gc_count >= 10 { // Every 10 minor GCs, do a major
190+
if self.minor_gc_count >= 10 {
191+
// Every 10 minor GCs, do a major
195192
self.major_collect_internal(roots, object_pool);
196193
} else {
197194
self.minor_collect_internal(roots, object_pool);
@@ -203,11 +200,11 @@ impl GC {
203200
self.major_collect_internal(roots, object_pool);
204201
}
205202
}
206-
203+
207204
// Reset debt based on estimate
208205
self.set_debt();
209206
}
210-
207+
211208
/// Set GC debt based on current memory and pause parameter
212209
fn set_debt(&mut self) {
213210
let estimate = self.gc_estimate.max(1024);
@@ -267,6 +264,14 @@ impl GC {
267264

268265
survivors.push((key, obj));
269266
} else {
267+
// Object is unreachable - check for __gc finalizer
268+
if self.check_finalizer(obj_type, obj_id, object_pool) {
269+
// Has finalizer, keep alive for one more cycle
270+
// (Finalization happens externally by VM)
271+
survivors.push((key, obj));
272+
continue;
273+
}
274+
270275
// Collect garbage - remove from object pool!
271276
collected += 1;
272277
match obj_type {
@@ -299,6 +304,20 @@ impl GC {
299304
self.objects.insert(key, obj);
300305
}
301306

307+
// Clean up weak tables after GC
308+
let all_tables: Vec<_> = self
309+
.objects
310+
.iter()
311+
.filter(|((obj_type, _), _)| *obj_type == GcObjectType::Table)
312+
.map(|((_, obj_id), _)| crate::object_pool::TableId(*obj_id))
313+
.collect();
314+
315+
for table_id in all_tables {
316+
if let Some(weak_mode) = self.get_weak_mode(table_id, object_pool) {
317+
self.clear_weak_entries(table_id, &weak_mode, object_pool);
318+
}
319+
}
320+
302321
self.stats.objects_collected += collected;
303322
self.stats.promoted_objects += promoted;
304323
self.update_generation_sizes();
@@ -335,6 +354,12 @@ impl GC {
335354

336355
for key @ (obj_type, obj_id) in keys {
337356
if !reachable.contains(&key) {
357+
// Check for __gc finalizer before collecting
358+
if self.check_finalizer(obj_type, obj_id, object_pool) {
359+
// Has finalizer, keep alive for one more cycle
360+
continue;
361+
}
362+
338363
self.objects.remove(&key);
339364
collected += 1;
340365

@@ -368,6 +393,20 @@ impl GC {
368393
obj.unmark();
369394
}
370395

396+
// Clean up weak tables after GC
397+
let all_tables: Vec<_> = self
398+
.objects
399+
.iter()
400+
.filter(|((obj_type, _), _)| *obj_type == GcObjectType::Table)
401+
.map(|((_, obj_id), _)| crate::object_pool::TableId(*obj_id))
402+
.collect();
403+
404+
for table_id in all_tables {
405+
if let Some(weak_mode) = self.get_weak_mode(table_id, object_pool) {
406+
self.clear_weak_entries(table_id, &weak_mode, object_pool);
407+
}
408+
}
409+
371410
self.stats.objects_collected += collected;
372411
self.update_generation_sizes();
373412

@@ -484,9 +523,9 @@ impl GC {
484523
crate::lua_value::LuaValueKind::Table => {
485524
value.as_table_id().map(|id| (GcObjectType::Table, id.0))
486525
}
487-
crate::lua_value::LuaValueKind::Function => {
488-
value.as_function_id().map(|id| (GcObjectType::Function, id.0))
489-
}
526+
crate::lua_value::LuaValueKind::Function => value
527+
.as_function_id()
528+
.map(|id| (GcObjectType::Function, id.0)),
490529
_ => None,
491530
};
492531

@@ -541,6 +580,146 @@ impl GC {
541580
// Adjust debt based on current memory
542581
self.adjust_threshold();
543582
}
583+
584+
/// Check if object has __gc metamethod that needs to be called
585+
/// This should be called during sweep phase before removing object
586+
pub fn check_finalizer(
587+
&mut self,
588+
obj_type: GcObjectType,
589+
obj_id: u32,
590+
object_pool: &crate::object_pool::ObjectPool,
591+
) -> bool {
592+
if obj_type != GcObjectType::Table {
593+
return false; // Only tables can have metatables with __gc
594+
}
595+
596+
let table_id = crate::object_pool::TableId(obj_id);
597+
if let Some(table_rc) = object_pool.get_table(table_id) {
598+
let table = table_rc.borrow();
599+
if let Some(meta_value) = table.get_metatable() {
600+
if let Some(meta_id) = meta_value.as_table_id() {
601+
if let Some(meta_table_rc) = object_pool.get_table(meta_id) {
602+
let meta_table = meta_table_rc.borrow();
603+
// Check for __gc key in metatable
604+
// We need to look for the string "__gc" in the metatable
605+
for (key, value) in meta_table.iter_all() {
606+
if let Some(string_id) = key.as_string_id() {
607+
if let Some(string_rc) = object_pool.get_string(string_id) {
608+
if string_rc.as_str() == "__gc" && !value.is_nil() {
609+
// Has __gc metamethod, add to finalize queue
610+
self.finalize_queue.push((obj_type, obj_id));
611+
return true;
612+
}
613+
}
614+
}
615+
}
616+
}
617+
}
618+
}
619+
}
620+
false
621+
}
622+
623+
/// Get finalization queue (for external processing by VM)
624+
/// VM should call __gc metamethods on these objects
625+
pub fn take_finalize_queue(&mut self) -> Vec<(GcObjectType, u32)> {
626+
std::mem::take(&mut self.finalize_queue)
627+
}
628+
629+
/// Check if table has weak mode (__mode metamethod)
630+
/// Returns: None if no weak mode, Some("k") for weak keys, Some("v") for weak values, Some("kv") for both
631+
pub fn get_weak_mode(
632+
&self,
633+
table_id: crate::object_pool::TableId,
634+
object_pool: &crate::object_pool::ObjectPool,
635+
) -> Option<String> {
636+
if let Some(table_rc) = object_pool.get_table(table_id) {
637+
let table = table_rc.borrow();
638+
if let Some(meta_value) = table.get_metatable() {
639+
if let Some(meta_id) = meta_value.as_table_id() {
640+
if let Some(meta_table_rc) = object_pool.get_table(meta_id) {
641+
let meta_table = meta_table_rc.borrow();
642+
// Look for __mode key
643+
for (key, value) in meta_table.iter_all() {
644+
if let Some(string_id) = key.as_string_id() {
645+
if let Some(string_rc) = object_pool.get_string(string_id) {
646+
if string_rc.as_str() == "__mode" {
647+
if let Some(mode_string_id) = value.as_string_id() {
648+
if let Some(mode_string_rc) =
649+
object_pool.get_string(mode_string_id)
650+
{
651+
return Some(mode_string_rc.as_str().to_string());
652+
}
653+
}
654+
}
655+
}
656+
}
657+
}
658+
}
659+
}
660+
}
661+
}
662+
None
663+
}
664+
665+
/// Clear weak references from a weak table after GC
666+
/// This removes entries where the key or value was collected
667+
pub fn clear_weak_entries(
668+
&self,
669+
table_id: crate::object_pool::TableId,
670+
weak_mode: &str,
671+
object_pool: &crate::object_pool::ObjectPool,
672+
) {
673+
if let Some(table_rc) = object_pool.get_table(table_id) {
674+
let mut table = table_rc.borrow_mut();
675+
let has_weak_keys = weak_mode.contains('k');
676+
let has_weak_values = weak_mode.contains('v');
677+
678+
if !has_weak_keys && !has_weak_values {
679+
return;
680+
}
681+
682+
// Collect keys to remove
683+
let mut keys_to_remove = Vec::new();
684+
685+
for (key, value) in table.iter_all() {
686+
let mut should_remove = false;
687+
688+
// Check if weak key was collected
689+
if has_weak_keys {
690+
if let Some(key_table_id) = key.as_table_id() {
691+
if !self
692+
.objects
693+
.contains_key(&(GcObjectType::Table, key_table_id.0))
694+
{
695+
should_remove = true;
696+
}
697+
}
698+
}
699+
700+
// Check if weak value was collected
701+
if has_weak_values && !should_remove {
702+
if let Some(val_table_id) = value.as_table_id() {
703+
if !self
704+
.objects
705+
.contains_key(&(GcObjectType::Table, val_table_id.0))
706+
{
707+
should_remove = true;
708+
}
709+
}
710+
}
711+
712+
if should_remove {
713+
keys_to_remove.push(key);
714+
}
715+
}
716+
717+
// Remove collected weak references (set to nil removes from table)
718+
for key in keys_to_remove {
719+
table.raw_set(key, LuaValue::nil());
720+
}
721+
}
722+
}
544723
}
545724

546725
/// Memory pool for small object allocation

crates/luars/src/test/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ pub mod test_xpcall_debug;
1515
// pub mod test_closures; // Disabled - upvalue handling needs work
1616
pub mod test_advanced_calls;
1717
pub mod test_functions;
18+
pub mod test_gc_metamethods;

0 commit comments

Comments
 (0)