Skip to content

Commit a0f3240

Browse files
committed
update
1 parent 73e546c commit a0f3240

File tree

5 files changed

+87
-68
lines changed

5 files changed

+87
-68
lines changed

crates/luars/src/gc/mod.rs

Lines changed: 36 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,12 @@ impl GC {
158158
total_bytes: 0,
159159
state: GcState::Pause,
160160
current_white: 0,
161-
gc_kind: GcKind::Generational, // Default to generational mode like Lua 5.4
161+
gc_kind: GcKind::Generational, // Use generational mode like Lua 5.4
162162
sweep_index: 0,
163163
propagate_work: 0,
164-
gc_pause: 200, // Like Lua: 200 = wait until memory doubles
165-
gen_minor_mul: 25, // Minor GC when memory grows 25%
166-
gen_major_mul: 100, // Major GC when memory grows 100% since last major
164+
gc_pause: 200,
165+
gen_minor_mul: 20, // Minor GC when memory grows 20%
166+
gen_major_mul: 50, // Major GC when memory grows 50% (降低以更频繁触发major GC清除weak tables)
167167
last_atomic: 0,
168168
gc_estimate: 0,
169169
young_list: Vec::with_capacity(1024),
@@ -299,63 +299,36 @@ impl GC {
299299
}
300300
}
301301

302-
/// Incremental GC step - original incremental mode
302+
/// Incremental GC step - complete one full cycle
303303
fn inc_step(&mut self, roots: &[LuaValue], pool: &mut ObjectPool) {
304-
const WORK_PER_STEP: usize = 4096;
305-
let mut work = 0;
306-
307-
// State machine for incremental GC
308-
loop {
309-
match self.state {
310-
GcState::Pause => {
311-
// Start new cycle: mark roots and transition to propagate
312-
self.start_cycle(roots, pool);
313-
self.state = GcState::Propagate;
314-
work += 100; // Small fixed cost
315-
}
316-
317-
GcState::Propagate => {
318-
// Incremental marking: process some gray objects
319-
let marked = self.propagate_step(pool, WORK_PER_STEP - work);
320-
work += marked;
321-
322-
if self.gray.is_empty() && self.grayagain.is_empty() {
323-
// All marking done, go to atomic phase
324-
self.state = GcState::Atomic;
325-
}
326-
}
327-
328-
GcState::Atomic => {
329-
// Atomic phase - must finish marking (like Lua's atomic)
330-
// Process any grayagain objects
331-
while let Some(gc_id) = self.grayagain.pop() {
332-
self.mark_one(gc_id, pool);
333-
}
334-
// Clear weak table entries before sweep
335-
self.clear_weak_tables(pool);
336-
// Start sweep
337-
self.sweep_index = 0;
338-
self.state = GcState::Sweep;
339-
work += 50;
340-
}
341-
342-
GcState::Sweep => {
343-
// Complete sweep in one step (pools are iterated directly)
344-
let swept = self.sweep_step(pool, WORK_PER_STEP - work);
345-
work += swept;
346-
// sweep_step handles state transition and finish_cycle
347-
break;
348-
}
349-
}
350-
351-
// Check if we've done enough work for this step
352-
if work >= WORK_PER_STEP {
353-
break;
354-
}
304+
// Pause -> Start cycle and mark roots
305+
if self.state == GcState::Pause {
306+
self.start_cycle(roots, pool);
307+
self.state = GcState::Propagate;
308+
}
309+
310+
// Propagate -> Mark all gray objects
311+
if self.state == GcState::Propagate {
312+
while let Some(gc_id) = self.gray.pop() {
313+
self.mark_one(gc_id, pool);
314+
}
315+
self.state = GcState::Atomic;
316+
}
317+
318+
// Atomic -> Process grayagain and clear weak tables
319+
if self.state == GcState::Atomic {
320+
while let Some(gc_id) = self.grayagain.pop() {
321+
self.mark_one(gc_id, pool);
322+
}
323+
self.clear_weak_tables(pool);
324+
self.sweep_index = 0;
325+
self.state = GcState::Sweep;
326+
}
327+
328+
// Sweep -> Clean up dead objects
329+
if self.state == GcState::Sweep {
330+
self.sweep_step(pool, usize::MAX);
355331
}
356-
357-
// Reduce debt by work done (convert work to "bytes paid off")
358-
self.gc_debt -= (work as isize) * 2;
359332
}
360333

361334
/// Start a new GC cycle - mark roots and build gray list
@@ -664,6 +637,7 @@ impl GC {
664637
}
665638
for id in dead_tables {
666639
pool.tables.free(id);
640+
self.total_bytes = self.total_bytes.saturating_sub(256);
667641
self.record_deallocation(256);
668642
collected += 1;
669643
}
@@ -677,6 +651,7 @@ impl GC {
677651
}
678652
for id in dead_funcs {
679653
pool.functions.free(id);
654+
self.total_bytes = self.total_bytes.saturating_sub(128);
680655
self.record_deallocation(128);
681656
collected += 1;
682657
}
@@ -703,6 +678,7 @@ impl GC {
703678
}
704679
for id in dead_strings {
705680
pool.strings.free(id);
681+
self.total_bytes = self.total_bytes.saturating_sub(64);
706682
self.record_deallocation(64);
707683
collected += 1;
708684
}
@@ -1372,14 +1348,14 @@ impl GC {
13721348
if let Some(mt_id) = table.data.get_metatable().and_then(|v| v.as_table_id()) {
13731349
if let Some(mt) = pool.tables.get(mt_id.0) {
13741350
// Look for __mode key in metatable
1375-
// We need to find the string "__mode" in the metatable
13761351
let mode = self.get_weak_mode(mt, pool);
13771352
if let Some((weak_keys, weak_values)) = mode {
13781353
// Collect keys to remove
13791354
let mut keys_to_remove = Vec::new();
13801355
for (key, value) in table.data.iter_all() {
13811356
let key_dead = weak_keys && self.is_value_dead(&key, pool);
13821357
let value_dead = weak_values && self.is_value_dead(&value, pool);
1358+
13831359
if key_dead || value_dead {
13841360
keys_to_remove.push(key);
13851361
}

crates/luars/src/lua_vm/execute/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@ pub fn luavm_execute(vm: &mut LuaVM) -> LuaResult<LuaValue> {
8686

8787
let opcode = get_op!(instr);
8888

89+
// CRITICAL: Implement Lua 5.4's top management (lvm.c line 1183)
90+
// lua_assert(isIT(i) || (cast_void(L->top.p = base), 1));
91+
// For non-IT instructions, reset top to base to prevent temporary values
92+
// from being kept alive by GC.
93+
// In Lua 5.4: base = ci->func.p + 1 (first register of current function)
94+
// In our VM: base_ptr is already the first register position
95+
// So we set top = 0 (relative to base_ptr), meaning no registers are "active"
96+
// for GC purposes. IT instructions will set top correctly when needed.
97+
unsafe {
98+
if !opcode.uses_top() {
99+
(*frame_ptr).top = 0;
100+
}
101+
}
102+
89103
match opcode {
90104
// ============ HOT PATH: Inline simple instructions (< 10 lines) ============
91105

crates/luars/src/lua_vm/mod.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,7 +1722,9 @@ impl LuaVM {
17221722
pub fn create_string_owned(&mut self, s: String) -> LuaValue {
17231723
let len = s.len();
17241724
let id = self.object_pool.create_string_owned(s);
1725-
self.gc_debt_local += (32 + len) as isize;
1725+
let size = (32 + len) as isize;
1726+
self.gc_debt_local += size;
1727+
self.gc.total_bytes = self.gc.total_bytes.saturating_add(size as usize);
17261728
LuaValue::string(id)
17271729
}
17281730

@@ -1807,6 +1809,7 @@ impl LuaVM {
18071809
pub fn create_table(&mut self, array_size: usize, hash_size: usize) -> LuaValue {
18081810
let id = self.object_pool.create_table(array_size, hash_size);
18091811
self.gc_debt_local += 256;
1812+
self.gc.total_bytes = self.gc.total_bytes.saturating_add(256);
18101813
LuaValue::table(id)
18111814
}
18121815

@@ -2113,12 +2116,9 @@ impl LuaVM {
21132116
/// OPTIMIZATION: Fast path is inlined, slow path is separate function
21142117
#[inline(always)]
21152118
fn check_gc(&mut self) {
2116-
// Fast path: check if gc_debt_local > threshold
2117-
// Use a larger threshold to reduce GC frequency
2118-
// 1MB threshold = about 16000 small object allocations before GC
2119-
// The incremental GC will catch up during collection anyway
2120-
const GC_THRESHOLD: isize = 1024 * 1024;
2121-
if self.gc_debt_local <= GC_THRESHOLD {
2119+
// Fast path: check if gc_debt_local > 0
2120+
// Once debt becomes positive, trigger GC step
2121+
if self.gc_debt_local <= 0 {
21222122
return;
21232123
}
21242124
// Slow path: actual GC work
@@ -2164,7 +2164,11 @@ impl LuaVM {
21642164
let top = frame.top as usize;
21652165
for i in 0..top {
21662166
if base_ptr + i < self.register_stack.len() {
2167-
self.gc_roots_buffer.push(self.register_stack[base_ptr + i]);
2167+
let value = self.register_stack[base_ptr + i];
2168+
// Skip nil values - they don't need to be roots
2169+
if !value.is_nil() {
2170+
self.gc_roots_buffer.push(value);
2171+
}
21682172
}
21692173
}
21702174
}

crates/luars/src/lua_vm/opcode/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,30 @@ impl OpCode {
160160
unsafe { std::mem::transmute(byte) }
161161
}
162162

163+
/// Check if instruction uses "top" (IT mode - In Top)
164+
/// These instructions depend on the value of 'top' from previous instruction
165+
/// For all other instructions, top should be reset to base + nactvar
166+
#[inline(always)]
167+
/// Check if this opcode uses "top" from previous instruction (isIT in Lua 5.4)
168+
/// These instructions expect top to be set correctly by previous instruction.
169+
/// For all other instructions, Lua 5.4 resets top = base before execution.
170+
///
171+
/// From Lua 5.4 lopcodes.c:
172+
/// - CALL: IT=1 (uses top for vararg count)
173+
/// - TAILCALL: IT=1
174+
/// - RETURN: IT=1 (uses top for return count)
175+
/// - SETLIST: IT=1 (uses top for list size)
176+
/// - VARARGPREP: IT=1 (sets up varargs)
177+
///
178+
/// Note: RETURN0, RETURN1, Vararg, Concat are NOT IT instructions in Lua 5.4!
179+
pub fn uses_top(self) -> bool {
180+
use OpCode::*;
181+
matches!(
182+
self,
183+
Call | TailCall | Return | SetList | VarargPrep
184+
)
185+
}
186+
163187
/// Get the instruction format mode for this opcode
164188
/// Based on Lua 5.4 lopcodes.c luaP_opmodes table
165189
pub fn get_mode(self) -> OpMode {

crates/luars/src/stdlib/basic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ fn lua_print(vm: &mut LuaVM) -> LuaResult<MultiValue> {
5858
} else {
5959
println!();
6060
}
61+
println!(); // Extra blank line for visibility
6162

6263
Ok(MultiValue::empty())
6364
}

0 commit comments

Comments
 (0)