Skip to content

Commit ade94fe

Browse files
committed
update
1 parent 0ed5036 commit ade94fe

File tree

4 files changed

+75
-5
lines changed

4 files changed

+75
-5
lines changed

crates/luars/src/compiler/expr.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3333,6 +3333,7 @@ pub fn compile_closure_expr_to(
33333333
register: 0,
33343334
is_const: false,
33353335
is_to_be_closed: false,
3336+
needs_close: false,
33363337
});
33373338
func_compiler.chunk.locals.push("self".to_string());
33383339
param_offset = 1;
@@ -3367,6 +3368,7 @@ pub fn compile_closure_expr_to(
33673368
register: reg_index,
33683369
is_const: false,
33693370
is_to_be_closed: false,
3371+
needs_close: false,
33703372
});
33713373
func_compiler.chunk.locals.push(param_name);
33723374
regular_param_count += 1;

crates/luars/src/compiler/helpers.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ pub fn add_local_with_attrs(
199199
register,
200200
is_const,
201201
is_to_be_closed,
202+
needs_close: false,
202203
};
203204
c.scope_chain.borrow_mut().locals.push(local);
204205

@@ -271,9 +272,11 @@ pub fn resolve_upvalue_from_chain(c: &mut Compiler, name: &str) -> Option<usize>
271272
fn resolve_in_parent_scope(scope: &Rc<RefCell<ScopeChain>>, name: &str) -> Option<(bool, u32)> {
272273
// First, search in this scope's locals
273274
{
274-
let scope_ref = scope.borrow();
275-
if let Some(local) = scope_ref.locals.iter().rev().find(|l| l.name == name) {
275+
let mut scope_ref = scope.borrow_mut();
276+
if let Some(local) = scope_ref.locals.iter_mut().rev().find(|l| l.name == name) {
276277
let register = local.register;
278+
// Mark this local as captured by a closure - needs CLOSE on scope exit
279+
local.needs_close = true;
277280
// Found as local - return (true, register)
278281
return Some((true, register));
279282
}
@@ -568,8 +571,15 @@ pub fn try_expr_as_constant(c: &mut Compiler, expr: &emmylua_parser::LuaExpr) ->
568571

569572
/// Begin a new loop (for break statement support)
570573
pub fn begin_loop(c: &mut Compiler) {
574+
begin_loop_with_register(c, c.freereg);
575+
}
576+
577+
/// Begin a new loop with a specific first register for CLOSE
578+
pub fn begin_loop_with_register(c: &mut Compiler, first_reg: u32) {
571579
c.loop_stack.push(super::LoopInfo {
572580
break_jumps: Vec::new(),
581+
scope_depth: c.scope_depth,
582+
first_local_register: first_reg,
573583
});
574584
}
575585

@@ -589,6 +599,35 @@ pub fn emit_break(c: &mut Compiler) -> Result<(), String> {
589599
return Err("break statement outside loop".to_string());
590600
}
591601

602+
// Before breaking, check if we need to close any captured upvalues
603+
// This is needed when break jumps past the CLOSE instruction that would
604+
// normally be executed at the end of each iteration
605+
let loop_info = c.loop_stack.last().unwrap();
606+
let loop_scope_depth = loop_info.scope_depth;
607+
let first_reg = loop_info.first_local_register;
608+
609+
// Find minimum register of captured locals in the loop scope
610+
let mut min_close_reg: Option<u32> = None;
611+
{
612+
let scope = c.scope_chain.borrow();
613+
for local in scope.locals.iter().rev() {
614+
if local.depth < loop_scope_depth {
615+
break; // Only check loop scope and nested scopes
616+
}
617+
if local.needs_close && local.register >= first_reg {
618+
min_close_reg = Some(match min_close_reg {
619+
None => local.register,
620+
Some(min_reg) => min_reg.min(local.register),
621+
});
622+
}
623+
}
624+
}
625+
626+
// Emit CLOSE if needed
627+
if let Some(reg) = min_close_reg {
628+
emit(c, Instruction::encode_abc(OpCode::Close, reg, 0, 0));
629+
}
630+
592631
let jump_pos = emit_jump(c, OpCode::Jmp);
593632
c.loop_stack.last_mut().unwrap().break_jumps.push(jump_pos);
594633
Ok(())

crates/luars/src/compiler/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,14 @@ pub(crate) struct Local {
8383
pub register: u32,
8484
pub is_const: bool, // <const> attribute
8585
pub is_to_be_closed: bool, // <close> attribute
86+
pub needs_close: bool, // True if captured by a closure (needs CLOSE on scope exit)
8687
}
8788

8889
/// Loop information for break statements
8990
pub(crate) struct LoopInfo {
90-
pub break_jumps: Vec<usize>, // Positions of break statements to patch
91+
pub break_jumps: Vec<usize>, // Positions of break statements to patch
92+
pub scope_depth: usize, // Scope depth at loop start
93+
pub first_local_register: u32, // First register of loop-local variables (for CLOSE on break)
9194
}
9295

9396
/// Label definition

crates/luars/src/compiler/stmt.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,9 +1223,12 @@ fn compile_for_stat(c: &mut Compiler, stat: &LuaForStat) -> Result<(), String> {
12231223
// Begin new scope for loop body
12241224
begin_scope(c);
12251225

1226+
// Begin loop with var_reg as first register, so break can close it
1227+
// Using var_reg instead of c.freereg because var_reg is the loop variable
1228+
begin_loop_with_register(c, var_reg);
1229+
12261230
// The loop variable is at R(base+3)
12271231
add_local(c, var_name, var_reg);
1228-
begin_loop(c);
12291232

12301233
// Loop body starts here
12311234
let loop_body_start = c.chunk.code.len();
@@ -1235,7 +1238,29 @@ fn compile_for_stat(c: &mut Compiler, stat: &LuaForStat) -> Result<(), String> {
12351238
compile_block(c, &body)?;
12361239
}
12371240

1238-
// FORLOOP comes AFTER the body
1241+
// Before FORLOOP, emit CLOSE if any local in the loop body was captured
1242+
// Find the minimum register of captured locals in the current scope (loop body)
1243+
{
1244+
let scope = c.scope_chain.borrow();
1245+
let mut min_close_reg: Option<u32> = None;
1246+
for local in scope.locals.iter().rev() {
1247+
if local.depth < c.scope_depth {
1248+
break; // Only check current scope (loop body)
1249+
}
1250+
if local.depth == c.scope_depth && local.needs_close {
1251+
min_close_reg = Some(match min_close_reg {
1252+
None => local.register,
1253+
Some(min_reg) => min_reg.min(local.register),
1254+
});
1255+
}
1256+
}
1257+
drop(scope);
1258+
if let Some(reg) = min_close_reg {
1259+
emit(c, Instruction::encode_abc(OpCode::Close, reg, 0, 0));
1260+
}
1261+
}
1262+
1263+
// FORLOOP comes AFTER the body (and CLOSE if needed)
12391264
let forloop_pc = c.chunk.code.len();
12401265
// Emit FORLOOP: increments index, checks condition, copies to var, jumps back to body
12411266
// Bx is the backward jump distance. Since PC is incremented before dispatch,
@@ -1516,6 +1541,7 @@ fn compile_local_function_stat(
15161541
register: func_reg,
15171542
is_const: false,
15181543
is_to_be_closed: false,
1544+
needs_close: false,
15191545
});
15201546
c.chunk.locals.push(func_name.clone());
15211547

0 commit comments

Comments
 (0)