Skip to content

Commit 0ed5036

Browse files
committed
update
1 parent c7fa53f commit 0ed5036

File tree

2 files changed

+130
-104
lines changed

2 files changed

+130
-104
lines changed

crates/luars/src/compiler/expr.rs

Lines changed: 114 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2328,117 +2328,82 @@ pub fn compile_call_expr_with_returns_and_dest(
23282328
}
23292329
} else {
23302330
// Regular call: compile function expression
2331-
let temp_func_reg = compile_expr(c, &prefix_expr)?;
2332-
2333-
// CRITICAL FIX: When function call returns values and will be used in an expression,
2334-
// we need to avoid overwriting the function value itself.
2335-
//
2336-
// Example: `f()` where f is in R[0] - if we call directly, return value overwrites f!
2337-
// Solution: If temp_func_reg is an "old" register (< freereg when we started),
2338-
// move the function to a new register first.
2339-
//
2340-
// However, we need to distinguish:
2341-
// - `f = load(...)` - first assignment, can reuse register ✓
2342-
// - `assert(f() == 30)` - f exists, must preserve it ✓
2343-
//
2344-
// The key insight: If dest is specified, caller wants a specific target.
2345-
// If dest is NOT specified AND we need returns, allocate fresh register to be safe.
2346-
2331+
// OPTIMIZATION: If dest is specified and safe (>= nactvar), compile function directly to dest
2332+
// This avoids unnecessary MOVE instructions
2333+
let nactvar = c.nactvar as u32;
2334+
23472335
let func_reg = if let Some(d) = dest {
2348-
// Caller specified a destination - use it
2349-
// CRITICAL CHECK: Verify that arguments won't overwrite active local variables!
2350-
// Arguments will be placed at R[d+1], R[d+2], etc.
2351-
// If any of these overlap with active locals (< nactvar), we need to use a different register
2352-
let nactvar = c.nactvar as u32;
2336+
// Check if we can safely use dest for function
23532337
let args_start = d + 1;
2354-
2355-
// Check if dest or args would overwrite active locals
2356-
// Active locals occupy R[0] through R[nactvar-1]
2357-
// - If d < nactvar: moving function to d would overwrite a local before args are compiled!
2358-
// - If args_start < nactvar: arguments would overwrite locals!
2359-
//
2360-
// Example: `x = tonumber(x)` where x is in R[0]:
2361-
// - d = 0, nactvar = 1, args_start = 1
2362-
// - If we move tonumber to R[0] first, we overwrite x before reading it as argument!
2363-
if d < nactvar || args_start < nactvar {
2364-
// Conflict! Either dest or arguments would overwrite active locals
2365-
// Solution: Place function at freereg (after all locals) and move result back later
2338+
if d >= nactvar && args_start >= nactvar {
2339+
// Safe to compile directly to dest
2340+
let temp_func_reg = compile_expr_to(c, &prefix_expr, Some(d))?;
2341+
// Ensure we got the register we asked for (or move if needed)
2342+
if temp_func_reg != d {
2343+
ensure_register(c, d);
2344+
emit_move(c, d, temp_func_reg);
2345+
}
2346+
// Reset freereg to just past func_reg
2347+
c.freereg = d + 1;
2348+
d
2349+
} else {
2350+
// dest < nactvar: we need to use a safe temporary register
2351+
let temp_func_reg = compile_expr(c, &prefix_expr)?;
23662352
let new_func_reg = if c.freereg < nactvar {
2367-
// Ensure freereg is at least nactvar
23682353
c.freereg = nactvar;
23692354
alloc_register(c)
23702355
} else {
23712356
alloc_register(c)
23722357
};
2373-
emit(
2374-
c,
2375-
Instruction::encode_abc(OpCode::Move, new_func_reg, temp_func_reg, 0),
2376-
);
2377-
need_move_to_dest = true; // Remember to move result back to original dest
2378-
new_func_reg
2379-
} else {
2380-
// No conflict - safe to use dest
2381-
if d != temp_func_reg {
2382-
emit(
2383-
c,
2384-
Instruction::encode_abc(OpCode::Move, d, temp_func_reg, 0),
2385-
);
2358+
if temp_func_reg != new_func_reg {
2359+
emit_move(c, new_func_reg, temp_func_reg);
23862360
}
2387-
// CRITICAL: Reset freereg to just past func_reg
2388-
// This ensures arguments compile into consecutive registers starting from func_reg+1
2389-
c.freereg = d + 1;
2390-
d
2391-
}
2392-
} else if num_returns > 0 {
2393-
// No dest specified, but need return values - this is expression context!
2394-
// CRITICAL: Must preserve local variables!
2395-
// Check if temp_func_reg is a local variable (< nactvar)
2396-
let nactvar = c.nactvar as u32;
2397-
if temp_func_reg < nactvar {
2398-
// Function is a local variable - must preserve it!
2399-
let new_reg = alloc_register(c);
2400-
emit(
2401-
c,
2402-
Instruction::encode_abc(OpCode::Move, new_reg, temp_func_reg, 0),
2403-
);
2404-
new_reg
2405-
} else if temp_func_reg + 1 == c.freereg {
2406-
// Function was just loaded into a fresh temporary register - safe to reuse
2407-
temp_func_reg
2408-
} else {
2409-
// Function is in an "old" temporary register - must preserve it!
2410-
let new_reg = alloc_register(c);
2411-
emit(
2412-
c,
2413-
Instruction::encode_abc(OpCode::Move, new_reg, temp_func_reg, 0),
2414-
);
2415-
new_reg
2361+
need_move_to_dest = true;
2362+
new_func_reg
24162363
}
24172364
} else {
2418-
// num_returns == 0: Statement context, no return values needed
2419-
// BUT we still need to ensure arguments don't overwrite local variables!
2420-
// Arguments will go to temp_func_reg + 1, temp_func_reg + 2, etc.
2421-
// If these overlap with local variables (< nactvar), we must relocate!
2422-
let nactvar = c.nactvar as u32;
2423-
let args_would_start_at = temp_func_reg + 1;
2424-
2425-
if args_would_start_at < nactvar || temp_func_reg < nactvar {
2426-
// Arguments would overwrite local variables!
2427-
// Move function to a safe register (at or after nactvar)
2428-
let new_reg = if c.freereg < nactvar {
2429-
c.freereg = nactvar;
2430-
alloc_register(c)
2365+
// No dest specified - use default behavior
2366+
let temp_func_reg = compile_expr(c, &prefix_expr)?;
2367+
2368+
if num_returns > 0 {
2369+
// Expression context - need return values
2370+
// CRITICAL: Must preserve local variables!
2371+
let nactvar = c.nactvar as u32;
2372+
if temp_func_reg < nactvar {
2373+
// Function is a local variable - must preserve it!
2374+
let new_reg = alloc_register(c);
2375+
emit_move(c, new_reg, temp_func_reg);
2376+
new_reg
2377+
} else if temp_func_reg + 1 == c.freereg {
2378+
// Function was just loaded into a fresh temporary register - safe to reuse
2379+
temp_func_reg
24312380
} else {
2432-
alloc_register(c)
2433-
};
2434-
emit(
2435-
c,
2436-
Instruction::encode_abc(OpCode::Move, new_reg, temp_func_reg, 0),
2437-
);
2438-
new_reg
2381+
// Function is in an "old" temporary register - must preserve it!
2382+
let new_reg = alloc_register(c);
2383+
emit_move(c, new_reg, temp_func_reg);
2384+
new_reg
2385+
}
24392386
} else {
2440-
// Safe - neither function nor arguments overlap with locals
2441-
temp_func_reg
2387+
// num_returns == 0: Statement context, no return values needed
2388+
// BUT we still need to ensure arguments don't overwrite local variables!
2389+
let nactvar = c.nactvar as u32;
2390+
let args_would_start_at = temp_func_reg + 1;
2391+
2392+
if args_would_start_at < nactvar || temp_func_reg < nactvar {
2393+
// Arguments would overwrite local variables!
2394+
// Move function to a safe register (at or after nactvar)
2395+
let new_reg = if c.freereg < nactvar {
2396+
c.freereg = nactvar;
2397+
alloc_register(c)
2398+
} else {
2399+
alloc_register(c)
2400+
};
2401+
emit_move(c, new_reg, temp_func_reg);
2402+
new_reg
2403+
} else {
2404+
// Safe - neither function nor arguments overlap with locals
2405+
temp_func_reg
2406+
}
24422407
}
24432408
};
24442409

@@ -2765,9 +2730,36 @@ fn compile_table_expr_to(
27652730
expr: &LuaTableExpr,
27662731
dest: Option<u32>,
27672732
) -> Result<u32, String> {
2768-
let reg = get_result_reg(c, dest);
2733+
// Get all fields first to check if we need to use a temporary register
2734+
let fields: Vec<_> = expr.get_fields().collect();
2735+
2736+
// CRITICAL FIX: When dest is a local variable register (< nactvar) and we have
2737+
// non-empty table constructor, we must NOT use dest directly. This is because
2738+
// table elements will be compiled into consecutive registers starting from reg+1,
2739+
// which could overwrite other local variables.
2740+
//
2741+
// Example: `local a,b,c; a = {f()}` where a=R0, b=R1, c=R2
2742+
// If we create table at R0, function and args go to R1, R2... overwriting b, c!
2743+
//
2744+
// Solution: When dest < nactvar AND table is non-empty, ignore dest and use
2745+
// a fresh temporary register. At the end, we move the result to dest.
2746+
let original_dest = dest;
2747+
let need_move_to_dest = if let Some(d) = dest {
2748+
!fields.is_empty() && d < c.nactvar as u32
2749+
} else {
2750+
false
2751+
};
2752+
2753+
// If we need to protect locals, ignore dest and allocate a fresh register
2754+
let effective_dest = if need_move_to_dest {
2755+
None
2756+
} else {
2757+
dest
2758+
};
2759+
2760+
let reg = get_result_reg(c, effective_dest);
27692761

2770-
// Get all fields
2762+
// Fields already collected above
27712763
let fields: Vec<_> = expr.get_fields().collect();
27722764

27732765
// Separate array part from hash part to count sizes
@@ -3093,6 +3085,13 @@ fn compile_table_expr_to(
30933085
emit(c, Instruction::encode_abc(OpCode::SetList, reg, 0, c_param));
30943086

30953087
c.freereg = reg + 1;
3088+
// Move result to original destination if needed
3089+
if need_move_to_dest {
3090+
if let Some(d) = original_dest {
3091+
emit_move(c, d, reg);
3092+
return Ok(d);
3093+
}
3094+
}
30963095
return Ok(reg);
30973096
}
30983097

@@ -3118,6 +3117,13 @@ fn compile_table_expr_to(
31183117
emit(c, Instruction::encode_abc(OpCode::SetList, reg, 0, c_param));
31193118

31203119
c.freereg = reg + 1;
3120+
// Move result to original destination if needed
3121+
if need_move_to_dest {
3122+
if let Some(d) = original_dest {
3123+
emit_move(c, d, reg);
3124+
return Ok(d);
3125+
}
3126+
}
31213127
return Ok(reg);
31223128
}
31233129

@@ -3145,6 +3151,14 @@ fn compile_table_expr_to(
31453151
// Reset to table_reg + 1 to match luac's register allocation behavior
31463152
c.freereg = reg + 1;
31473153

3154+
// Move result to original destination if needed
3155+
if need_move_to_dest {
3156+
if let Some(d) = original_dest {
3157+
emit_move(c, d, reg);
3158+
return Ok(d);
3159+
}
3160+
}
3161+
31483162
Ok(reg)
31493163
}
31503164

crates/luars/src/compiler/stmt.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,17 +262,29 @@ fn compile_local_stat(c: &mut Compiler, stat: &LuaLocalStat) -> Result<(), Strin
262262
// Check if last expression is a function call (which might return multiple values)
263263
else if let LuaExpr::CallExpr(call_expr) = last_expr {
264264
if remaining_vars > 1 {
265-
// Multi-return call: compile with dest = target_base
265+
// Multi-return call: DON'T pass dest to avoid overwriting pre-allocated registers
266+
// Let the call compile into safe temporary position, results will be in target_base
267+
// because we've pre-allocated the registers
268+
//
269+
// CRITICAL: We need to compile without dest, then the results will naturally
270+
// end up in sequential registers starting from wherever the function was placed.
271+
// Since we pre-allocated target_base..target_base+remaining_vars, freereg points
272+
// past them, so the call will compile into fresh registers.
266273
let result_base = compile_call_expr_with_returns_and_dest(
267274
c,
268275
call_expr,
269276
remaining_vars,
270-
Some(target_base),
277+
None, // Don't specify dest - let call choose safe location
271278
)?;
272279

273-
// Add all return registers
280+
// Move results to target registers if needed
274281
for i in 0..remaining_vars {
275-
regs.push(result_base + i as u32);
282+
let src = result_base + i as u32;
283+
let dst = target_base + i as u32;
284+
if src != dst {
285+
emit_move(c, dst, src);
286+
}
287+
regs.push(dst);
276288
}
277289

278290
// Define locals and return

0 commit comments

Comments
 (0)