feat: 重构for-in为预计算+隐藏局部变量模式
ForInInit: 改为预计算所有迭代元素为数组 ForInNext: 接收items_slot+idx_slot操作数,从局部变量读取 编译器: for-in迭代器使用隐藏局部变量(items, idx),隔离于value stack 修复: for-in在函数调用嵌套场景下栈对齐问题 修复: break路径在for-in中正确弹出循环变量 修复: ForInNext退出时push Nil以保持栈平衡 修复: 跳转偏移计算(移除emit_loop_jump中错误的-3) 通过: 17/18 benchmark测试 (全部for-in相关测试通过) 已知限制: 3层嵌套闭包(10c)需传递式upvalue支持
This commit is contained in:
@@ -84,6 +84,7 @@ struct Local {
|
|||||||
struct Upvalue {
|
struct Upvalue {
|
||||||
index: u8,
|
index: u8,
|
||||||
is_local: bool, // true = captured from enclosing fn's local; false = from upvalue
|
is_local: bool, // true = captured from enclosing fn's local; false = from upvalue
|
||||||
|
name: String, // variable name (for transitive upvalue resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoopContext {
|
struct LoopContext {
|
||||||
@@ -97,9 +98,9 @@ pub struct Compiler {
|
|||||||
function: FunctionProto,
|
function: FunctionProto,
|
||||||
locals: Vec<Local>,
|
locals: Vec<Local>,
|
||||||
upvalues: Vec<Upvalue>,
|
upvalues: Vec<Upvalue>,
|
||||||
/// Snapshot of enclosing compiler's locals (for upvalue resolution)
|
/// Direct parent's locals (for upvalue resolution)
|
||||||
enclosing_locals: Option<Vec<Local>>,
|
enclosing_locals: Option<Vec<Local>>,
|
||||||
/// Snapshot of enclosing compiler's upvalues (for transitive upvalues)
|
/// Direct parent's upvalues (for transitive upvalue resolution)
|
||||||
enclosing_upvalues: Option<Vec<Upvalue>>,
|
enclosing_upvalues: Option<Vec<Upvalue>>,
|
||||||
scope_depth: u8,
|
scope_depth: u8,
|
||||||
loop_stack: Vec<LoopContext>,
|
loop_stack: Vec<LoopContext>,
|
||||||
@@ -303,13 +304,39 @@ impl Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compile_for_in(&mut self, var_name: &str, iterable: &Expr, body: &Stmt) -> Result<(), RuntimeError> {
|
fn compile_for_in(&mut self, var_name: &str, iterable: &Expr, body: &Stmt) -> Result<(), RuntimeError> {
|
||||||
// Evaluate iterable, set up iterator
|
// 1. New scope for hidden iterator locals (items, idx)
|
||||||
self.compile_expr(iterable)?;
|
|
||||||
emit_op(&mut self.function.code, OpCode::ForInInit);
|
|
||||||
let exit_jump = emit_i16_placeholder(&mut self.function.code, OpCode::ForInNext);
|
|
||||||
|
|
||||||
// New scope for the loop variable
|
|
||||||
self.begin_scope();
|
self.begin_scope();
|
||||||
|
|
||||||
|
// 2. Evaluate iterable and compute items array
|
||||||
|
self.compile_expr(iterable)?;
|
||||||
|
emit_op(&mut self.function.code, OpCode::ForInInit); // pops iterable, pushes items[]
|
||||||
|
|
||||||
|
// 3. Store items array in a hidden local (peek, no Pop — cleaned by end_scope)
|
||||||
|
let items_slot = self.locals.len() as u8;
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: format!("__iter_items_{}", items_slot),
|
||||||
|
depth: self.scope_depth,
|
||||||
|
is_captured: false,
|
||||||
|
is_const: false,
|
||||||
|
});
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, items_slot);
|
||||||
|
|
||||||
|
// 4. Initialize index = 0 in hidden local
|
||||||
|
let idx_slot = self.locals.len() as u8;
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: format!("__iter_idx_{}", idx_slot),
|
||||||
|
depth: self.scope_depth,
|
||||||
|
is_captured: false,
|
||||||
|
is_const: false,
|
||||||
|
});
|
||||||
|
let zero_idx = self.add_constant(Value::Number(0.0));
|
||||||
|
emit_u16(&mut self.function.code, OpCode::LoadConst, zero_idx);
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, idx_slot);
|
||||||
|
|
||||||
|
// 5. ForInNext: reads items[idx_slot], idx[idx_slot], pushes element
|
||||||
|
let forin_loc = self.emit_forin_next(items_slot, idx_slot);
|
||||||
|
|
||||||
|
// 6. Store loop variable in a local (peek, value stays on stack)
|
||||||
let loop_var_slot = self.locals.len() as u8;
|
let loop_var_slot = self.locals.len() as u8;
|
||||||
self.locals.push(Local {
|
self.locals.push(Local {
|
||||||
name: var_name.to_string(),
|
name: var_name.to_string(),
|
||||||
@@ -318,13 +345,10 @@ impl Compiler {
|
|||||||
is_const: false,
|
is_const: false,
|
||||||
});
|
});
|
||||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, loop_var_slot);
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, loop_var_slot);
|
||||||
// Pop the ForInNext value from stack top (it's stored in the local)
|
|
||||||
self.emit_op(OpCode::Pop);
|
|
||||||
|
|
||||||
let start_ip = self.function.code.len();
|
|
||||||
|
|
||||||
|
// 7. Loop body (can resolve var_name to loop_var_slot)
|
||||||
self.loop_stack.push(LoopContext {
|
self.loop_stack.push(LoopContext {
|
||||||
start_ip,
|
start_ip: forin_loc,
|
||||||
break_patches: Vec::new(),
|
break_patches: Vec::new(),
|
||||||
continue_patches: Vec::new(),
|
continue_patches: Vec::new(),
|
||||||
scope_depth: self.scope_depth,
|
scope_depth: self.scope_depth,
|
||||||
@@ -332,28 +356,31 @@ impl Compiler {
|
|||||||
|
|
||||||
self.compile_stmt(body)?;
|
self.compile_stmt(body)?;
|
||||||
|
|
||||||
// Patch continue → loop back
|
// Patch continue
|
||||||
let continue_ip = self.function.code.len();
|
let continue_ip = self.function.code.len();
|
||||||
let loop_ctx = self.loop_stack.pop().unwrap();
|
let loop_ctx = self.loop_stack.pop().unwrap();
|
||||||
for patch in loop_ctx.continue_patches {
|
for patch in loop_ctx.continue_patches {
|
||||||
self.patch_jump_to(patch, continue_ip);
|
self.patch_jump_to(patch, continue_ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jump back to ForInNext
|
// 8. Pop loop var element and remove from tracking
|
||||||
self.emit_loop_jump(exit_jump);
|
self.emit_op(OpCode::Pop);
|
||||||
let exit_ip = self.function.code.len();
|
self.locals.pop(); // loop_var_slot
|
||||||
self.patch_jump_to(exit_jump, exit_ip);
|
|
||||||
|
|
||||||
|
// 9. Jump back to ForInNext (next iteration re-adds loop var via StoreLocal)
|
||||||
|
self.emit_loop_jump(forin_loc);
|
||||||
|
|
||||||
|
// 10. Exit target: patch ForInNext exit and break jumps
|
||||||
|
let exit_ip = self.function.code.len();
|
||||||
|
self.patch_forin_jump(forin_loc, exit_ip);
|
||||||
for patch in loop_ctx.break_patches {
|
for patch in loop_ctx.break_patches {
|
||||||
self.patch_jump(patch);
|
self.patch_jump_to(patch, exit_ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop the loop variable
|
// 11. Pop loop var (ForInNext pushes Nil on exit; break leaves element on stack)
|
||||||
self.emit_op(OpCode::Pop);
|
|
||||||
// Pop the iterator state (2 values: iterable ref + index)
|
|
||||||
self.emit_op(OpCode::Pop);
|
|
||||||
self.emit_op(OpCode::Pop);
|
self.emit_op(OpCode::Pop);
|
||||||
|
|
||||||
|
// 12. End scope: pops items_slot + idx_slot values (left by StoreLocal peek)
|
||||||
self.end_scope();
|
self.end_scope();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -658,7 +685,6 @@ impl Compiler {
|
|||||||
/// Compile a nested function (lambda or function declaration) and return its proto.
|
/// Compile a nested function (lambda or function declaration) and return its proto.
|
||||||
fn compile_nested_function(&mut self, name: Option<String>, params: &[String], body: &[Stmt]) -> Result<FunctionProto, RuntimeError> {
|
fn compile_nested_function(&mut self, name: Option<String>, params: &[String], body: &[Stmt]) -> Result<FunctionProto, RuntimeError> {
|
||||||
let mut child = Compiler::new(name);
|
let mut child = Compiler::new(name);
|
||||||
// Pass enclosing state for upvalue resolution
|
|
||||||
child.enclosing_locals = Some(self.locals.clone());
|
child.enclosing_locals = Some(self.locals.clone());
|
||||||
child.enclosing_upvalues = Some(self.upvalues.clone());
|
child.enclosing_upvalues = Some(self.upvalues.clone());
|
||||||
|
|
||||||
@@ -744,22 +770,32 @@ impl Compiler {
|
|||||||
/// Try to resolve a variable as an upvalue from enclosing functions.
|
/// Try to resolve a variable as an upvalue from enclosing functions.
|
||||||
/// Returns the upvalue index in this function's upvalues list.
|
/// Returns the upvalue index in this function's upvalues list.
|
||||||
fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
|
fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
|
||||||
|
// Check if already captured
|
||||||
|
for (j, uv) in self.upvalues.iter().enumerate() {
|
||||||
|
if uv.name == name {
|
||||||
|
return Some(j as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check enclosing function's locals
|
||||||
if let Some(ref enclosing_locals) = self.enclosing_locals {
|
if let Some(ref enclosing_locals) = self.enclosing_locals {
|
||||||
for (i, local) in enclosing_locals.iter().enumerate().rev() {
|
for (i, local) in enclosing_locals.iter().enumerate().rev() {
|
||||||
if local.name == name && local.depth > 0 {
|
if local.name == name && local.depth > 0 {
|
||||||
// Check if we already captured this upvalue
|
|
||||||
for (j, uv) in self.upvalues.iter().enumerate() {
|
|
||||||
if uv.is_local && uv.index == i as u8 {
|
|
||||||
return Some(j as u8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let idx = self.upvalues.len() as u8;
|
let idx = self.upvalues.len() as u8;
|
||||||
self.upvalues.push(Upvalue { index: i as u8, is_local: true });
|
self.upvalues.push(Upvalue { index: i as u8, is_local: true, name: name.to_string() });
|
||||||
|
return Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check enclosing function's upvalues (transitive)
|
||||||
|
if let Some(ref enclosing_upvalues) = self.enclosing_upvalues {
|
||||||
|
for uv in enclosing_upvalues.iter().rev() {
|
||||||
|
if uv.name == name {
|
||||||
|
let idx = self.upvalues.len() as u8;
|
||||||
|
self.upvalues.push(Upvalue { index: uv.index, is_local: false, name: name.to_string() });
|
||||||
return Some(idx);
|
return Some(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Transitive upvalues (from enclosing's upvalues) not yet implemented
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -788,6 +824,17 @@ impl Compiler {
|
|||||||
emit_i16(&mut self.function.code, OpCode::Jump, offset as i16);
|
emit_i16(&mut self.function.code, OpCode::Jump, offset as i16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_forin_next(&mut self, items_slot: u8, idx_slot: u8) -> usize {
|
||||||
|
let loc = self.function.code.len();
|
||||||
|
let code = &mut self.function.code;
|
||||||
|
code.push(OpCode::ForInNext as u8);
|
||||||
|
code.push(items_slot);
|
||||||
|
code.push(idx_slot);
|
||||||
|
code.push(0xFF); // placeholder offset low
|
||||||
|
code.push(0x7F); // placeholder offset high
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_closure(&mut self, proto_idx: u16, upvalues: &[(bool, u8)]) {
|
fn emit_closure(&mut self, proto_idx: u16, upvalues: &[(bool, u8)]) {
|
||||||
let code = &mut self.function.code;
|
let code = &mut self.function.code;
|
||||||
code.push(OpCode::Closure as u8);
|
code.push(OpCode::Closure as u8);
|
||||||
@@ -815,6 +862,18 @@ impl Compiler {
|
|||||||
code[jump_loc + 2] = ((offset >> 8) & 0xFF) as u8;
|
code[jump_loc + 2] = ((offset >> 8) & 0xFF) as u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Patch ForInNext's exit offset (at jump_loc + 3, +4)
|
||||||
|
fn patch_forin_jump(&mut self, forin_loc: usize, target: usize) {
|
||||||
|
let offset = target as isize - forin_loc as isize;
|
||||||
|
let code = &mut self.function.code;
|
||||||
|
code[forin_loc + 3] = (offset & 0xFF) as u8;
|
||||||
|
code[forin_loc + 4] = ((offset >> 8) & 0xFF) as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_constant(&mut self, val: Value) -> u16 {
|
||||||
|
self.function.add_constant(val)
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Constant pool helpers
|
// Constant pool helpers
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|||||||
+27
-18
@@ -511,32 +511,41 @@ impl Vm {
|
|||||||
// --- For-in ---
|
// --- For-in ---
|
||||||
OpCode::ForInInit => {
|
OpCode::ForInInit => {
|
||||||
let iterable = self.stack.pop().unwrap();
|
let iterable = self.stack.pop().unwrap();
|
||||||
// Push iterator state: (collection, index)
|
let items = self.for_in_items(&iterable);
|
||||||
let iter = Value::Number(0.0);
|
self.stack.push(Value::Array(Rc::new(RefCell::new(items))));
|
||||||
self.stack.push(iterable);
|
|
||||||
self.stack.push(iter);
|
|
||||||
self.advance_ip(SIZE_OP);
|
self.advance_ip(SIZE_OP);
|
||||||
}
|
}
|
||||||
OpCode::ForInNext => {
|
OpCode::ForInNext => {
|
||||||
let exit_offset = read_i16(code, ip) as isize;
|
// Encoding: opcode(1) + items_slot(1) + idx_slot(1) + exit_offset(2) = 5
|
||||||
let exit_ip = ((ip as isize) + exit_offset) as usize;
|
let items_slot = code[ip + 1] as usize;
|
||||||
let iter_idx = self.stack.pop().unwrap(); // current index
|
let idx_slot = code[ip + 2] as usize;
|
||||||
let collection = self.stack.pop().unwrap(); // the iterable
|
let exit_offset = ((code[ip + 3] as u16) | ((code[ip + 4] as u16) << 8)) as i16;
|
||||||
|
let exit_ip = ((ip as isize) + exit_offset as isize) as usize;
|
||||||
|
let base = self.frames.last().unwrap().stack_base;
|
||||||
|
|
||||||
|
// Read index from local slot
|
||||||
|
let idx_val = self.stack[base + idx_slot].clone();
|
||||||
|
let idx = self.as_usize(&idx_val, "for-in index")?;
|
||||||
|
let items_val = self.stack[base + items_slot].clone();
|
||||||
|
|
||||||
|
let items = match &items_val {
|
||||||
|
Value::Array(arr) => arr.borrow(),
|
||||||
|
_ => return Err(RuntimeError::RuntimeError {
|
||||||
|
message: "ForInNext: items is not an array".into(),
|
||||||
|
token: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
let idx = self.as_number(&iter_idx)? as usize;
|
|
||||||
let items = self.for_in_items(&collection);
|
|
||||||
if idx >= items.len() {
|
if idx >= items.len() {
|
||||||
// Done iterating — push back state and jump to exit
|
// Done: push Nil (keeps stack balanced for Pop at exit)
|
||||||
self.stack.push(collection);
|
self.stack.push(Value::Nil);
|
||||||
self.stack.push(iter_idx);
|
|
||||||
self.advance_ip_to(exit_ip);
|
self.advance_ip_to(exit_ip);
|
||||||
} else {
|
} else {
|
||||||
// Push back incremented state
|
// Push current element for loop body
|
||||||
self.stack.push(collection);
|
|
||||||
self.stack.push(Value::Number((idx + 1) as f64));
|
|
||||||
// Push the current value for the loop body
|
|
||||||
self.stack.push(items[idx].clone());
|
self.stack.push(items[idx].clone());
|
||||||
self.advance_ip(SIZE_U16);
|
// Increment index in local slot
|
||||||
|
self.stack[base + idx_slot] = Value::Number((idx + 1) as f64);
|
||||||
|
self.advance_ip(5); // SIZE_FORIN_NEXT = 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user