feat: VM函数调用、闭包、upvalue捕获支持
- 实现用户定义函数调用 (CallFrame创建、参数传递、返回) - 实现闭包/upvalue捕获 (编译器解析、VM捕获、LoadUpvalue/StoreUpvalue) - 修复栈管理: StoreLocal peek语义、局部变量槽位分配 - 修复跳转偏移计算 (移除错误的-3偏置) - 修复对象字面量编译 (每字段后Pop) - 修复函数声明栈泄漏 (DefineGlobal后Pop) - 添加ForInNext值Pop (避免栈累积) - 添加OpCode::from_u8对非连续值的支持 通过: 算术、循环、条件、数组、对象、字符串、递归、闭包、upvalue 部分通过: for-in迭代器 (基本工作,复杂嵌套场景待修复) 未实现: VM版require()、复合upvalue赋值 测试: 327/327 通过
This commit is contained in:
+103
-51
@@ -63,8 +63,8 @@ pub struct Vm {
|
||||
/// Open upvalues (tracked so closures share the same upvalue object)
|
||||
pub open_upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
|
||||
|
||||
/// Allocated function protos (indexed by the compiler's constant pool index)
|
||||
protos: Vec<Rc<FunctionProto>>,
|
||||
/// VM-compiled closures: maps Function Rc pointer → Closure data
|
||||
closures: RefCell<HashMap<*const crate::interpreter::Function, Rc<Closure>>>,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
@@ -85,7 +85,7 @@ impl Vm {
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| ".".to_string()),
|
||||
open_upvalues: Vec::new(),
|
||||
protos: Vec::new(),
|
||||
closures: RefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +133,13 @@ impl Vm {
|
||||
|
||||
if ip >= code_len {
|
||||
// End of function — implicit return nil
|
||||
self.frames.pop();
|
||||
let frame = self.frames.pop().unwrap();
|
||||
self.close_upvalues(frame.stack_base);
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
self.stack.truncate(base);
|
||||
// Remove callee + args + function locals, push nil result
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
self.stack.push(Value::Nil);
|
||||
continue;
|
||||
}
|
||||
@@ -193,15 +194,51 @@ impl Vm {
|
||||
// --- Locals ---
|
||||
OpCode::LoadLocal => {
|
||||
let slot = read_u8(code, ip) as usize;
|
||||
let val = self.stack[self.frame().stack_base + slot].clone();
|
||||
let idx = self.frames.last().unwrap().stack_base + slot;
|
||||
if idx >= self.stack.len() {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("LoadLocal: slot {} uninitialized", slot),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
let val = self.stack[idx].clone();
|
||||
self.stack.push(val);
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
OpCode::StoreLocal => {
|
||||
let slot = read_u8(code, ip) as usize;
|
||||
let val = self.stack.last().unwrap().clone();
|
||||
let val = self.stack.last().unwrap().clone(); // peek
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
self.stack[base + slot] = val;
|
||||
let idx = base + slot;
|
||||
if idx >= self.stack.len() {
|
||||
self.stack.resize(idx + 1, Value::Nil);
|
||||
}
|
||||
self.stack[idx] = val;
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
OpCode::LoadUpvalue => {
|
||||
let idx = read_u8(code, ip) as usize;
|
||||
let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[idx]);
|
||||
let uv_ref = uv.borrow();
|
||||
let val = if let Some(ref closed) = uv_ref.closed {
|
||||
closed.clone()
|
||||
} else {
|
||||
self.stack[uv_ref.location].clone()
|
||||
};
|
||||
drop(uv_ref);
|
||||
self.stack.push(val);
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
OpCode::StoreUpvalue => {
|
||||
let idx = read_u8(code, ip) as usize;
|
||||
let val = self.stack.last().unwrap().clone(); // peek
|
||||
let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[idx]);
|
||||
let mut uv_ref = uv.borrow_mut();
|
||||
if let Some(ref mut closed) = uv_ref.closed {
|
||||
*closed = val;
|
||||
} else {
|
||||
self.stack[uv_ref.location] = val;
|
||||
}
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
|
||||
@@ -402,31 +439,34 @@ impl Vm {
|
||||
let result = self.stack.pop().unwrap();
|
||||
let frame = self.frames.pop().unwrap();
|
||||
self.close_upvalues(frame.stack_base);
|
||||
self.stack.truncate(frame.stack_base);
|
||||
// Remove callee + args + function locals, push return value
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
self.stack.push(result);
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
// IP of caller is unchanged (already at next instruction)
|
||||
}
|
||||
OpCode::Closure => {
|
||||
let proto_idx = read_u16(code, ip) as usize;
|
||||
let upvalue_count = code[ip + 3] as usize;
|
||||
let proto = Rc::clone(&self.protos[proto_idx]);
|
||||
let proto = proto.protos.get(proto_idx).ok_or_else(|| RuntimeError::RuntimeError {
|
||||
message: format!("Closure proto index {} out of bounds", proto_idx),
|
||||
token: None,
|
||||
})?.clone();
|
||||
|
||||
// Collect upvalue capture info from bytecode first
|
||||
// Collect upvalue capture info
|
||||
struct UpCapture { is_local: bool, index: usize }
|
||||
let mut captures: Vec<UpCapture> = Vec::new();
|
||||
let mut offset = ip + 4;
|
||||
let mut off = ip + 4;
|
||||
for _ in 0..upvalue_count {
|
||||
let is_local = code[offset] != 0;
|
||||
offset += 1;
|
||||
let index = code[offset] as usize;
|
||||
offset += 1;
|
||||
let is_local = code[off] != 0;
|
||||
off += 1;
|
||||
let index = code[off] as usize;
|
||||
off += 1;
|
||||
captures.push(UpCapture { is_local, index });
|
||||
}
|
||||
|
||||
// Now do the actual captures (no conflicting borrows)
|
||||
// Do the actual captures
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
let parent_upvalues = self.frames.last().unwrap().closure.upvalues.clone();
|
||||
let mut upvalues = Vec::new();
|
||||
@@ -439,17 +479,17 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
|
||||
// Store closure (stub for now)
|
||||
let _closure = Rc::new(Closure { proto, upvalues });
|
||||
self.stack.push(Value::Function(Rc::new(
|
||||
crate::interpreter::Function {
|
||||
params: Vec::new(),
|
||||
body: Vec::new(),
|
||||
env: Rc::new(RefCell::new(crate::interpreter::Env::new(None))),
|
||||
name: None,
|
||||
}
|
||||
)));
|
||||
self.advance_ip_to(offset);
|
||||
let closure = Rc::new(Closure { proto, upvalues });
|
||||
let func = Rc::new(crate::interpreter::Function {
|
||||
params: Vec::new(),
|
||||
body: Vec::new(),
|
||||
env: Rc::new(RefCell::new(crate::interpreter::Env::new(None))),
|
||||
name: None,
|
||||
});
|
||||
let key = Rc::as_ptr(&func) as *const crate::interpreter::Function;
|
||||
self.closures.borrow_mut().insert(key, closure);
|
||||
self.stack.push(Value::Function(func));
|
||||
self.advance_ip_to(off);
|
||||
}
|
||||
|
||||
// --- Object/Array ---
|
||||
@@ -507,9 +547,16 @@ impl Vm {
|
||||
let compound_op = CompoundOp::from_u8(op_tag).unwrap();
|
||||
let rhs = self.stack.pop().unwrap();
|
||||
let base = self.frames.last().unwrap().stack_base;
|
||||
let lhs = self.stack[base + slot].clone();
|
||||
let idx = base + slot;
|
||||
if idx >= self.stack.len() {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("CompoundAssignLocal to uninitialized local {}", slot),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
let lhs = self.stack[idx].clone();
|
||||
let result = self.apply_compound_op(lhs, rhs, compound_op)?;
|
||||
self.stack[base + slot] = result.clone();
|
||||
self.stack[idx] = result.clone();
|
||||
self.stack.push(result);
|
||||
self.advance_ip(SIZE_U8 + 1);
|
||||
}
|
||||
@@ -570,8 +617,13 @@ impl Vm {
|
||||
let callee_idx = self.stack.len() - 1 - arg_count;
|
||||
let callee = self.stack[callee_idx].clone();
|
||||
|
||||
match callee {
|
||||
Value::NativeFunction(native_fn) => {
|
||||
match &callee {
|
||||
Value::NativeFunction(_) => {
|
||||
// Get the native function
|
||||
let native_fn = match self.stack[callee_idx].clone() {
|
||||
Value::NativeFunction(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Pop arguments from stack
|
||||
let mut args = Vec::new();
|
||||
for _ in 0..arg_count {
|
||||
@@ -583,27 +635,27 @@ impl Vm {
|
||||
self.stack.push(result);
|
||||
self.advance_ip(SIZE_U8);
|
||||
}
|
||||
Value::Function(f) => {
|
||||
// User-defined function: create new call frame
|
||||
let base = callee_idx; // where the new frame's locals start
|
||||
let new_closure = Rc::new(Closure {
|
||||
proto: Rc::new(FunctionProto::new(f.name.clone())),
|
||||
upvalues: Vec::new(),
|
||||
});
|
||||
Value::Function(_) => {
|
||||
// Look up the VM-compiled closure
|
||||
let func_ptr = match &self.stack[callee_idx] {
|
||||
Value::Function(f) => Rc::as_ptr(f) as *const crate::interpreter::Function,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let closure = self.closures.borrow().get(&func_ptr).cloned().ok_or_else(|| RuntimeError::RuntimeError {
|
||||
message: "VM: call to non-VM function (tree-walker function not supported in VM)".into(),
|
||||
token: None,
|
||||
})?;
|
||||
|
||||
// Advance caller's IP past the Call instruction before pushing new frame
|
||||
self.frame_mut().ip += SIZE_U8;
|
||||
|
||||
// Callee is at callee_idx, args start at callee_idx+1
|
||||
let base = callee_idx + 1; // first param is here
|
||||
self.frames.push(CallFrame {
|
||||
closure: new_closure,
|
||||
closure,
|
||||
ip: 0,
|
||||
stack_base: base,
|
||||
});
|
||||
// Arguments are already on the stack at the right position
|
||||
// (they become locals 0..arg_count in the new frame)
|
||||
// The callee itself becomes slot 0 (TODO: handle properly)
|
||||
// For now, this is a stub — full function support needs
|
||||
// proper closure/proto integration
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: "VM user-defined function calls not yet fully implemented".into(),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
|
||||
Reference in New Issue
Block a user