Skip to content

Commit f4df326

Browse files
committed
support to be close
1 parent ceac3a0 commit f4df326

File tree

3 files changed

+53
-20
lines changed

3 files changed

+53
-20
lines changed

crates/luars/src/lua_vm/dispatcher/control_instructions.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use crate::lua_vm::{Instruction, LuaCallFrame, LuaError, LuaResult, LuaVM};
1111
pub fn exec_return(vm: &mut LuaVM, instr: u32) -> LuaResult<()> {
1212
let a = Instruction::get_a(instr) as usize;
1313
let b = Instruction::get_b(instr) as usize;
14-
let _c = Instruction::get_c(instr) as usize;
15-
let _k = Instruction::get_k(instr);
14+
// let _c = Instruction::get_c(instr) as usize;
15+
let k = Instruction::get_k(instr);
1616

1717
// Close upvalues before popping the frame
1818
let base_ptr = vm.current_frame().base_ptr;
@@ -117,9 +117,11 @@ pub fn exec_return(vm: &mut LuaVM, instr: u32) -> LuaResult<()> {
117117
}
118118

119119
// Handle upvalue closing (k bit)
120-
if _k {
120+
if k {
121121
let close_from = base_ptr + a;
122122
vm.close_upvalues_from(close_from);
123+
// Also call __close metamethods for to-be-closed variables
124+
vm.close_to_be_closed(close_from)?;
123125
}
124126

125127
Ok(())

crates/luars/src/lua_vm/dispatcher/upvalue_instructions.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -310,24 +310,14 @@ pub fn exec_tbc(vm: &mut LuaVM, instr: u32) -> LuaResult<()> {
310310
let a = Instruction::get_a(instr) as usize;
311311
let frame = vm.current_frame();
312312
let base_ptr = frame.base_ptr;
313+
let reg_idx = base_ptr + a;
313314

314-
// In a full implementation, we would:
315-
// 1. Mark the variable at R[A] as to-be-closed
316-
// 2. When the variable goes out of scope (block end, return, etc.),
317-
// call its __close metamethod if it exists
318-
//
319-
// For now, we just note the variable exists. The __close metamethod
320-
// should be called by:
321-
// - RETURN instruction (with k bit set)
322-
// - End of block (JMP with upvalue closing)
323-
// - Error unwinding
324-
325-
// Check if the value has a __close metamethod (optional validation)
326-
let value = vm.register_stack[base_ptr + a];
327-
if !value.is_nil() {
328-
// We could check for __close metamethod here, but Lua allows
329-
// any value to be marked as to-be-closed
330-
}
315+
// Get the value to be marked as to-be-closed
316+
let value = vm.register_stack[reg_idx];
317+
318+
// Add to to_be_closed stack (will be processed in LIFO order)
319+
// Store absolute register index for later closing
320+
vm.to_be_closed.push((reg_idx, value));
331321

332322
Ok(())
333323
}

crates/luars/src/lua_vm/mod.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ pub struct LuaVM {
4141
// Open upvalues list (for closing when frames exit)
4242
pub(crate) open_upvalues: Vec<Rc<LuaUpvalue>>,
4343

44+
// To-be-closed variables stack (for __close metamethod)
45+
// Stores (register_index, value) pairs that need __close called when they go out of scope
46+
pub(crate) to_be_closed: Vec<(usize, LuaValue)>,
47+
4448
// Next frame ID (for tracking frames)
4549
pub(crate) next_frame_id: usize,
4650

@@ -80,6 +84,7 @@ impl LuaVM {
8084
gc: GC::new(),
8185
return_values: Vec::new(),
8286
open_upvalues: Vec::new(),
87+
to_be_closed: Vec::new(),
8388
next_frame_id: 0,
8489
error_handler: None,
8590
#[cfg(feature = "loadlib")]
@@ -1129,6 +1134,42 @@ impl LuaVM {
11291134
self.open_upvalues.retain(|uv| uv.is_open());
11301135
}
11311136

1137+
/// Call __close metamethods for to-be-closed variables >= stack_pos
1138+
pub fn close_to_be_closed(&mut self, stack_pos: usize) -> LuaResult<()> {
1139+
// Process in reverse order (LIFO - last marked is closed first)
1140+
while let Some(&(reg_idx, value)) = self.to_be_closed.last() {
1141+
if reg_idx < stack_pos {
1142+
break;
1143+
}
1144+
1145+
self.to_be_closed.pop();
1146+
1147+
// Skip nil values
1148+
if value.is_nil() {
1149+
continue;
1150+
}
1151+
1152+
// Try to get __close metamethod
1153+
let close_key = self.create_string("__close");
1154+
let metamethod = if let Some(mt) = self.table_get_metatable(&value) {
1155+
self.table_get_with_meta(&mt, &close_key)
1156+
} else {
1157+
None
1158+
};
1159+
1160+
if let Some(mm) = metamethod {
1161+
if !mm.is_nil() {
1162+
// Call __close(value, error)
1163+
// error is nil in normal close, contains error object during unwinding
1164+
let args = vec![value, LuaValue::nil()];
1165+
// Ignore errors from __close to prevent infinite loops
1166+
let _ = self.call_metamethod(&mm, &args);
1167+
}
1168+
}
1169+
}
1170+
Ok(())
1171+
}
1172+
11321173
/// Create a new table and register it with GC
11331174
/// Create a string and register it with GC
11341175
/// For short strings (≤64 bytes), use interning (global deduplication)

0 commit comments

Comments
 (0)