@@ -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
0 commit comments