Skip to content

Commit 97dafd6

Browse files
committed
update
1 parent 8908876 commit 97dafd6

File tree

4 files changed

+213
-49
lines changed

4 files changed

+213
-49
lines changed

crates/luars/src/compiler/expr.rs

Lines changed: 130 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -403,41 +403,79 @@ fn compile_binary_expr_desc(c: &mut Compiler, expr: &LuaBinaryExpr) -> Result<Ex
403403
} else {
404404
alloc_register(c)
405405
};
406-
emit(
407-
c,
408-
Instruction::encode_abc(OpCode::Add, result_reg, left_reg, right_reg),
409-
);
410-
emit(
411-
c,
412-
Instruction::create_abck(
413-
OpCode::MmBin,
414-
left_reg,
415-
right_reg,
416-
TagMethod::Add.as_u32(),
417-
false,
418-
),
419-
);
406+
if can_use_rk {
407+
// Right is constant, use ADDK
408+
let const_idx = ensure_constant(c, &saved_right_desc)?;
409+
emit(
410+
c,
411+
Instruction::encode_abc(OpCode::AddK, result_reg, left_reg, const_idx),
412+
);
413+
emit(
414+
c,
415+
Instruction::create_abck(
416+
OpCode::MmBinK,
417+
left_reg,
418+
const_idx,
419+
TagMethod::Add.as_u32(),
420+
false,
421+
),
422+
);
423+
} else {
424+
emit(
425+
c,
426+
Instruction::encode_abc(OpCode::Add, result_reg, left_reg, right_reg),
427+
);
428+
emit(
429+
c,
430+
Instruction::create_abck(
431+
OpCode::MmBin,
432+
left_reg,
433+
right_reg,
434+
TagMethod::Add.as_u32(),
435+
false,
436+
),
437+
);
438+
}
420439
}
421440
BinaryOperator::OpSub => {
422441
result_reg = if can_reuse_left {
423442
left_reg
424443
} else {
425444
alloc_register(c)
426445
};
427-
emit(
428-
c,
429-
Instruction::encode_abc(OpCode::Sub, result_reg, left_reg, right_reg),
430-
);
431-
emit(
432-
c,
433-
Instruction::create_abck(
434-
OpCode::MmBin,
435-
left_reg,
436-
right_reg,
437-
TagMethod::Sub.as_u32(),
438-
false,
439-
),
440-
);
446+
if can_use_rk {
447+
// Right is constant, use SUBK
448+
let const_idx = ensure_constant(c, &saved_right_desc)?;
449+
emit(
450+
c,
451+
Instruction::encode_abc(OpCode::SubK, result_reg, left_reg, const_idx),
452+
);
453+
emit(
454+
c,
455+
Instruction::create_abck(
456+
OpCode::MmBinK,
457+
left_reg,
458+
const_idx,
459+
TagMethod::Sub.as_u32(),
460+
false,
461+
),
462+
);
463+
} else {
464+
emit(
465+
c,
466+
Instruction::encode_abc(OpCode::Sub, result_reg, left_reg, right_reg),
467+
);
468+
emit(
469+
c,
470+
Instruction::create_abck(
471+
OpCode::MmBin,
472+
left_reg,
473+
right_reg,
474+
TagMethod::Sub.as_u32(),
475+
false,
476+
),
477+
);
478+
}
441479
}
442480
BinaryOperator::OpMul => {
443481
result_reg = if can_reuse_left {
@@ -2642,33 +2680,68 @@ pub fn compile_call_expr_with_returns_and_dest(
26422680
alloc_register(c);
26432681
}
26442682

2683+
let mut inner_last_arg_is_vararg_or_call = false;
26452684
for (j, call_arg) in call_arg_exprs.iter().enumerate() {
26462685
let call_arg_dest = call_args_start + j as u32;
2686+
let is_last_inner_arg = j == call_arg_exprs.len() - 1;
2687+
26472688
// Reset freereg before each argument to protect argument slots
26482689
if c.freereg < call_args_end {
26492690
c.freereg = call_args_end;
26502691
}
2692+
2693+
// Check if last inner argument is vararg
2694+
if is_last_inner_arg {
2695+
if let LuaExpr::LiteralExpr(lit_expr) = call_arg {
2696+
if matches!(lit_expr.get_literal(), Some(LuaLiteralToken::Dots(_))) {
2697+
// Vararg as last argument: VARARG with C=0 (all out)
2698+
emit(c, Instruction::encode_abc(OpCode::Vararg, call_arg_dest, 0, 0));
2699+
call_arg_regs.push(call_arg_dest);
2700+
inner_last_arg_is_vararg_or_call = true;
2701+
continue;
2702+
}
2703+
}
2704+
// Check if last arg is a call expression - compile it with "all out"
2705+
if let LuaExpr::CallExpr(inner_call_expr) = call_arg {
2706+
// Recursively compile this call with returns=usize::MAX (all out)
2707+
let inner_call_reg = compile_call_expr_with_returns_and_dest(c, inner_call_expr, usize::MAX, Some(call_arg_dest))?;
2708+
if inner_call_reg != call_arg_dest {
2709+
while c.freereg <= call_arg_dest {
2710+
alloc_register(c);
2711+
}
2712+
emit_move(c, call_arg_dest, inner_call_reg);
2713+
}
2714+
call_arg_regs.push(call_arg_dest);
2715+
inner_last_arg_is_vararg_or_call = true;
2716+
continue;
2717+
}
2718+
}
2719+
26512720
let arg_reg = compile_expr_to(c, call_arg, Some(call_arg_dest))?;
26522721
call_arg_regs.push(arg_reg);
26532722
}
26542723

2655-
// Move call arguments if needed
2656-
for (j, &reg) in call_arg_regs.iter().enumerate() {
2657-
let target = call_args_start + j as u32;
2658-
if reg != target {
2659-
while c.freereg <= target {
2660-
alloc_register(c);
2724+
// Move call arguments if needed (skip if we emitted VARARG directly)
2725+
if !inner_last_arg_is_vararg_or_call {
2726+
for (j, &reg) in call_arg_regs.iter().enumerate() {
2727+
let target = call_args_start + j as u32;
2728+
if reg != target {
2729+
while c.freereg <= target {
2730+
alloc_register(c);
2731+
}
2732+
emit_move(c, target, reg);
26612733
}
2662-
emit_move(c, target, reg);
26632734
}
26642735
}
26652736

26662737
// Emit call with "all out" (C=0)
2667-
let inner_arg_count = call_arg_exprs.len();
2668-
let inner_b_param = if inner_is_method {
2669-
(inner_arg_count + 2) as u32 // +1 for self, +1 for Lua convention
2738+
// B = number of arguments + 1, or 0 if last arg was vararg/call
2739+
let inner_b_param = if inner_last_arg_is_vararg_or_call {
2740+
0 // B=0: variable number of args
2741+
} else if inner_is_method {
2742+
(num_call_args + 2) as u32 // +1 for self, +1 for Lua convention
26702743
} else {
2671-
(inner_arg_count + 1) as u32
2744+
(num_call_args + 1) as u32
26722745
};
26732746
emit(
26742747
c,
@@ -2743,6 +2816,7 @@ pub fn compile_call_expr_with_returns_and_dest(
27432816
// B = number of arguments + 1, or 0 if last arg was "all out" call
27442817
// For method calls, B includes the implicit self parameter
27452818
// C = number of expected return values + 1 (1 means 0 returns, 2 means 1 return, 0 means all returns)
2819+
// SPECIAL: when num_returns = usize::MAX, it means "all out" mode (C=0)
27462820
let arg_count = arg_exprs.len();
27472821
let b_param = if last_arg_is_call_all_out {
27482822
0 // B=0: all in
@@ -2751,7 +2825,13 @@ pub fn compile_call_expr_with_returns_and_dest(
27512825
let total_args = if is_method { arg_count + 1 } else { arg_count };
27522826
(total_args + 1) as u32
27532827
};
2754-
let c_param = (num_returns + 1) as u32;
2828+
// C=0 means "all out", C=1 means 0 returns, C=2 means 1 return, etc.
2829+
// When caller passes num_returns=usize::MAX, they mean "all out" (C=0)
2830+
let c_param = if num_returns == usize::MAX {
2831+
0 // C=0: all out (take all return values)
2832+
} else {
2833+
(num_returns + 1) as u32
2834+
};
27552835

27562836
emit(
27572837
c,
@@ -2996,6 +3076,16 @@ fn compile_table_expr_to(
29963076
let mut array_idx = 0;
29973077
let values_start = reg + 1;
29983078
let mut has_vararg_at_end = false;
3079+
3080+
// CRITICAL: Pre-reserve registers for array elements BEFORE processing any fields.
3081+
// This prevents hash field value expressions (like `select('#', ...)`) from
3082+
// allocating temporary registers that conflict with array element positions.
3083+
// Without this, `{n = select('#', ...), ...}` would have `select` use reg+1,
3084+
// which should be reserved for the first vararg element.
3085+
let array_values_end = values_start + array_count as u32;
3086+
while c.freereg < array_values_end {
3087+
alloc_register(c);
3088+
}
29993089
let mut has_call_at_end = false;
30003090

30013091
// Process all fields in source order

crates/luars/src/compiler/stmt.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,7 @@ fn compile_return_stat(c: &mut Compiler, stat: &LuaReturnStat) -> Result<(), Str
755755
}
756756

757757
// Compile arguments to consecutive registers after function
758-
let mut last_is_vararg_all_out = false;
758+
let mut last_is_vararg_or_call_all_out = false;
759759
for (i, arg) in args.iter().enumerate() {
760760
let target_reg = reserved_regs[i + 1];
761761
let is_last_arg = i == args.len() - 1;
@@ -769,10 +769,17 @@ fn compile_return_stat(c: &mut Compiler, stat: &LuaReturnStat) -> Result<(), Str
769769
) {
770770
// Vararg as last argument: use "all out" mode
771771
emit(c, Instruction::encode_abc(OpCode::Vararg, target_reg, 0, 0));
772-
last_is_vararg_all_out = true;
772+
last_is_vararg_or_call_all_out = true;
773773
continue;
774774
}
775775
}
776+
// Check if last argument is a function call
777+
if let LuaExpr::CallExpr(inner_call) = arg {
778+
// Compile the inner call with "all out" mode (num_returns = usize::MAX)
779+
compile_call_expr_with_returns_and_dest(c, inner_call, usize::MAX, Some(target_reg))?;
780+
last_is_vararg_or_call_all_out = true;
781+
continue;
782+
}
776783
}
777784

778785
let arg_reg = compile_expr(c, &arg)?;
@@ -782,8 +789,8 @@ fn compile_return_stat(c: &mut Compiler, stat: &LuaReturnStat) -> Result<(), Str
782789
}
783790

784791
// Emit TailCall instruction
785-
// A = function register, B = num_args + 1 (or 0 if last arg is vararg "all out")
786-
let b_param = if last_is_vararg_all_out {
792+
// A = function register, B = num_args + 1 (or 0 if last arg is vararg/call "all out")
793+
let b_param = if last_is_vararg_or_call_all_out {
787794
0 // B=0: all in (variable number of args from vararg)
788795
} else {
789796
(num_args + 1) as u32
@@ -858,7 +865,7 @@ fn compile_return_stat(c: &mut Compiler, stat: &LuaReturnStat) -> Result<(), Str
858865
} else if let LuaExpr::CallExpr(call_expr) = last_expr {
859866
// Call expression: compile with "all out" mode
860867
let last_target_reg = base_reg + (num_exprs - 1) as u32;
861-
compile_call_expr_with_returns_and_dest(c, call_expr, 0, Some(last_target_reg))?;
868+
compile_call_expr_with_returns_and_dest(c, call_expr, usize::MAX, Some(last_target_reg))?;
862869
// Return with B=0 (all out)
863870
emit(
864871
c,

crates/luars/src/lua_vm/execute/upvalue_instructions.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,71 @@ pub fn exec_vararg(
169169
};
170170

171171
let dest_base = base_ptr + a;
172+
173+
// Calculate actual copy count
174+
let actual_copy_count = if c == 0 {
175+
vararg_count
176+
} else {
177+
(c - 1).min(vararg_count)
178+
};
179+
180+
// Check for overlap between source (vararg_start..vararg_start+actual_copy_count)
181+
// and destination (dest_base..dest_base+actual_copy_count)
182+
// If there's overlap, we need to relocate vararg source data first to prevent corruption
183+
let dest_end = dest_base + actual_copy_count;
184+
let src_end = vararg_start + actual_copy_count;
185+
let has_overlap = actual_copy_count > 0 && !(dest_end <= vararg_start || src_end <= dest_base);
186+
187+
// If there's overlap, relocate vararg source data to a safe location
188+
// This prevents subsequent VARARG instructions from reading corrupted data
189+
if has_overlap {
190+
// Allocate a new safe location beyond any potential destination
191+
// Use the end of current stack + some buffer
192+
let safe_location = vm.register_stack.len().max(dest_end + vararg_count);
193+
vm.ensure_stack_capacity(safe_location + vararg_count);
194+
if vm.register_stack.len() < safe_location + vararg_count {
195+
vm.register_stack.resize(safe_location + vararg_count, LuaValue::nil());
196+
}
197+
198+
// Copy ALL vararg data to safe location (not just actual_copy_count)
199+
// This ensures subsequent VARARG instructions can read correct data
200+
for i in 0..vararg_count {
201+
vm.register_stack[safe_location + i] = vm.register_stack[vararg_start + i];
202+
}
203+
204+
// Update frame's vararg_start to point to new location
205+
unsafe {
206+
(*frame_ptr).set_vararg(safe_location, vararg_count);
207+
}
208+
209+
// Now copy from safe location to destination (no overlap possible)
210+
if c == 0 {
211+
for i in 0..vararg_count {
212+
vm.register_stack[dest_base + i] = vm.register_stack[safe_location + i];
213+
}
214+
// Update frame top
215+
let new_top = a + vararg_count;
216+
unsafe {
217+
let current_top = (*frame_ptr).top as usize;
218+
(*frame_ptr).top = (new_top.max(current_top)) as u32;
219+
}
220+
} else {
221+
// Fixed count
222+
let count = c - 1;
223+
let copy_count = count.min(vararg_count);
224+
for i in 0..copy_count {
225+
vm.register_stack[dest_base + i] = vm.register_stack[safe_location + i];
226+
}
227+
// Fill remaining with nil
228+
let nil_val = LuaValue::nil();
229+
for i in copy_count..count {
230+
vm.register_stack[dest_base + i] = nil_val;
231+
}
232+
}
233+
234+
return Ok(());
235+
}
236+
172237
let reg_ptr = vm.register_stack.as_mut_ptr();
173238

174239
if c == 0 {
@@ -179,8 +244,8 @@ pub fn exec_vararg(
179244
(*frame_ptr).top = (new_top.max(top)) as u32;
180245
}
181246

182-
// OPTIMIZED: Use ptr::copy for bulk transfer when possible
183247
if vararg_count > 0 && vararg_start + vararg_count <= vm.register_stack.len() {
248+
// No overlap (handled above) - use fast copy
184249
unsafe {
185250
std::ptr::copy(
186251
reg_ptr.add(vararg_start),
@@ -204,11 +269,11 @@ pub fn exec_vararg(
204269
}
205270
} else {
206271
// Fixed number of results (c-1 values)
272+
// Overlap case was already handled above
207273
let count = c - 1;
208274
let copy_count = count.min(vararg_count);
209275
let nil_count = count.saturating_sub(vararg_count);
210276

211-
// OPTIMIZED: Bulk copy available varargs
212277
if copy_count > 0 && vararg_start + copy_count <= vm.register_stack.len() {
213278
unsafe {
214279
std::ptr::copy(

crates/luars_interpreter/src/bin/main.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,10 @@ fn main() {
307307
for code in &opts.execute_strings {
308308
match vm.compile(code) {
309309
Ok(chunk) => {
310-
if let Err(e) = vm.execute(Rc::new(chunk)) {
311-
eprintln!("lua: {}", e);
310+
if let Err(_) = vm.execute(Rc::new(chunk)) {
311+
let error_msg = vm.get_error_message();
312+
let traceback = vm.generate_traceback(error_msg);
313+
eprintln!("lua: Runtime Error: {}", traceback);
312314
std::process::exit(1);
313315
}
314316
}

0 commit comments

Comments
 (0)