Skip to content

Commit 121ce9e

Browse files
committed
update
1 parent 5332c87 commit 121ce9e

File tree

3 files changed

+214
-44
lines changed

3 files changed

+214
-44
lines changed

crates/luars/src/compiler/expr.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,10 @@ fn compile_literal_expr_desc(c: &mut Compiler, expr: &LuaLiteralExpr) -> Result<
206206
LuaLiteralToken::Dots(_) => {
207207
// Variable arguments: ...
208208
// Allocate register and emit VARARG
209+
// VARARG A C: R(A), ..., R(A+C-2) = vararg
210+
// C=0 means all varargs, C>0 means C-1 values
209211
let reg = alloc_register(c);
210-
emit(c, Instruction::encode_abc(OpCode::Vararg, reg, 2, 0));
212+
emit(c, Instruction::encode_abc(OpCode::Vararg, reg, 0, 2));
211213
Ok(ExpDesc::new_nonreloc(reg))
212214
}
213215
_ => Err("Unsupported literal type".to_string()),
@@ -934,12 +936,10 @@ fn compile_literal_expr(
934936
}
935937
LuaLiteralToken::Dots(_) => {
936938
// Variable arguments: ...
937-
// VarArg instruction: R(A), ..., R(A+B-2) = vararg
938-
// B=1 means load 0 varargs (empty)
939-
// B=2 means load 1 vararg into R(A)
940-
// B=0 means load all varargs starting from R(A)
941-
// For expression context, we load 1 vararg into the register
942-
emit(c, Instruction::encode_abc(OpCode::Vararg, reg, 2, 0));
939+
// VARARG A C: R(A), ..., R(A+C-2) = vararg
940+
// C=0 means all varargs, C>0 means C-1 values
941+
// For expression context, we load 1 vararg into the register (C=2 means 1 value)
942+
emit(c, Instruction::encode_abc(OpCode::Vararg, reg, 0, 2));
943943
}
944944
_ => {}
945945
}

crates/luars/src/compiler/stmt.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,16 +242,16 @@ fn compile_local_stat(c: &mut Compiler, stat: &LuaLocalStat) -> Result<(), Strin
242242

243243
if is_dots && remaining_vars > 0 {
244244
// Varargs expansion: generate VarArg instruction into pre-allocated registers
245-
// VarArg instruction: R(target_base)..R(target_base+remaining_vars-1) = ...
246-
// B = remaining_vars + 1 (or 0 for all)
247-
let b_value = if remaining_vars == 1 {
245+
// VARARG A C: R(A), ..., R(A+C-2) = vararg
246+
// C = remaining_vars + 1 (or 0 for all)
247+
let c_value = if remaining_vars == 1 {
248248
2
249249
} else {
250250
(remaining_vars + 1) as u32
251251
};
252252
emit(
253253
c,
254-
Instruction::encode_abc(OpCode::Vararg, target_base, b_value, 0),
254+
Instruction::encode_abc(OpCode::Vararg, target_base, 0, c_value),
255255
);
256256

257257
// Add all registers

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

Lines changed: 203 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -955,50 +955,211 @@ pub fn exec_call(
955955
}
956956

957957
// Slow path: Check for __call metamethod (for Table and Userdata)
958-
let metatable_opt = match func.kind() {
959-
LuaValueKind::Table => func.as_table_id().and_then(|table_id| {
960-
vm.object_pool
961-
.get_table(table_id)
962-
.and_then(|t| t.get_metatable())
963-
}),
964-
LuaValueKind::Userdata => func.as_userdata_id().and_then(|ud_id| {
965-
vm.object_pool
966-
.get_userdata(ud_id)
967-
.map(|ud| ud.get_metatable())
968-
.filter(|mt| !mt.is_nil())
969-
}),
970-
_ => None,
971-
};
958+
// Need to handle chains of __call metamethods by inserting extra arguments
959+
let mut current_func = func;
960+
let mut extra_args: Vec<LuaValue> = Vec::new(); // Tables in the chain to insert as args
961+
let mut depth = 0;
962+
const MAX_CALL_CHAIN: usize = 200; // Prevent infinite loops
963+
964+
loop {
965+
let metatable_opt = match current_func.kind() {
966+
LuaValueKind::Table => current_func.as_table_id().and_then(|table_id| {
967+
vm.object_pool
968+
.get_table(table_id)
969+
.and_then(|t| t.get_metatable())
970+
}),
971+
LuaValueKind::Userdata => current_func.as_userdata_id().and_then(|ud_id| {
972+
vm.object_pool
973+
.get_userdata(ud_id)
974+
.map(|ud| ud.get_metatable())
975+
.filter(|mt| !mt.is_nil())
976+
}),
977+
_ => break, // Not a table/userdata, cannot have __call
978+
};
972979

973-
if let Some(metatable) = metatable_opt {
974-
// Use pre-cached __call StringId
975-
let call_key = LuaValue::string(vm.object_pool.tm_call);
976-
if let Some(call_func) = vm.table_get_with_meta(&metatable, &call_key) {
977-
if call_func.is_callable() {
978-
if call_func.is_cfunction() {
979-
exec_call_cfunction(vm, call_func, a, b, c, base, true, func, frame_ptr_ptr)?;
980+
if let Some(metatable) = metatable_opt {
981+
// Use pre-cached __call StringId
982+
let call_key = LuaValue::string(vm.object_pool.tm_call);
983+
if let Some(call_func) = vm.table_get_with_meta(&metatable, &call_key) {
984+
// Add current_func to the extra args (at the front)
985+
extra_args.insert(0, current_func);
986+
987+
if call_func.is_callable() {
988+
// Found a callable __call metamethod
989+
// Now we need to insert all extra_args before the original arguments
990+
exec_call_with_extra_args(vm, call_func, a, b, c, base, &extra_args, frame_ptr_ptr)?;
980991
return Ok(());
992+
} else if call_func.is_table() || call_func.is_userdata() {
993+
// __call is itself a table/userdata, follow the chain
994+
current_func = call_func;
995+
depth += 1;
996+
if depth >= MAX_CALL_CHAIN {
997+
return Err(vm.error("'__call' chain too long; possible loop".to_string()));
998+
}
999+
continue;
9811000
} else {
982-
exec_call_lua_function(
983-
vm,
984-
call_func,
985-
a,
986-
b,
987-
c,
988-
base,
989-
true,
990-
func,
991-
frame_ptr_ptr,
992-
)?;
993-
return Ok(());
1001+
break; // __call is not callable and not a table
9941002
}
1003+
} else {
1004+
break; // No __call metamethod
9951005
}
1006+
} else {
1007+
break; // No metatable
9961008
}
9971009
}
9981010

9991011
Err(vm.error(format!("attempt to call a {} value", func.type_name())))
10001012
}
10011013

1014+
/// Execute a call with extra arguments inserted at the front (for __call chains)
1015+
/// extra_args are inserted before the original arguments
1016+
#[inline(never)]
1017+
fn exec_call_with_extra_args(
1018+
vm: &mut LuaVM,
1019+
call_func: LuaValue,
1020+
a: usize,
1021+
b: usize,
1022+
c: usize,
1023+
base: usize,
1024+
extra_args: &[LuaValue],
1025+
frame_ptr_ptr: &mut *mut LuaCallFrame,
1026+
) -> LuaResult<()> {
1027+
// Calculate original argument count
1028+
let orig_arg_count = if b == 0 {
1029+
let frame = vm.current_frame();
1030+
if frame.top as usize > a + 1 {
1031+
(frame.top as usize) - (a + 1)
1032+
} else {
1033+
0
1034+
}
1035+
} else {
1036+
vm.current_frame_mut().top = (a + b) as u32;
1037+
b - 1
1038+
};
1039+
1040+
let extra_count = extra_args.len();
1041+
let total_arg_count = extra_count + orig_arg_count;
1042+
let return_count = if c == 0 { usize::MAX } else { c - 1 };
1043+
1044+
// New frame base = R[A+1] in caller's frame
1045+
let new_base = base + a + 1;
1046+
1047+
// Ensure we have enough stack space
1048+
// First, make room for the extra args by shifting original args
1049+
let required_capacity = new_base + total_arg_count + 10; // Extra buffer
1050+
vm.ensure_stack_capacity(required_capacity);
1051+
1052+
// Shift original arguments to make room for extra_args at the front
1053+
if orig_arg_count > 0 && extra_count > 0 {
1054+
// Move original args from [new_base..new_base+orig_arg_count]
1055+
// to [new_base+extra_count..new_base+extra_count+orig_arg_count]
1056+
unsafe {
1057+
let reg_ptr = vm.register_stack.as_mut_ptr();
1058+
// Copy in reverse to avoid overwriting
1059+
for i in (0..orig_arg_count).rev() {
1060+
*reg_ptr.add(new_base + extra_count + i) = *reg_ptr.add(new_base + i);
1061+
}
1062+
}
1063+
}
1064+
1065+
// Insert extra_args at the front
1066+
for (i, arg) in extra_args.iter().enumerate() {
1067+
vm.register_stack[new_base + i] = *arg;
1068+
}
1069+
1070+
// Update top to reflect new argument count
1071+
vm.current_frame_mut().top = (a + 1 + total_arg_count) as u32;
1072+
1073+
// Now call the function
1074+
if call_func.is_cfunction() {
1075+
let c_func = call_func.as_cfunction().unwrap();
1076+
// For C function, we need to set up the frame manually
1077+
let call_base = base + a;
1078+
vm.register_stack[call_base] = call_func;
1079+
1080+
let temp_frame = LuaCallFrame::new_c_function(call_base, total_arg_count + 1);
1081+
*frame_ptr_ptr = vm.push_frame(temp_frame);
1082+
1083+
let result = c_func(vm)?;
1084+
vm.pop_frame_discard();
1085+
1086+
// Restore frame_ptr to caller's frame
1087+
*frame_ptr_ptr = vm.current_frame_ptr();
1088+
1089+
// Write results
1090+
let vals = result.all_values();
1091+
let count = if return_count == usize::MAX {
1092+
vals.len()
1093+
} else {
1094+
vals.len().min(return_count)
1095+
};
1096+
1097+
for i in 0..count {
1098+
vm.register_stack[base + a + i] = vals[i];
1099+
}
1100+
1101+
// Fill remaining expected results with nil
1102+
if return_count != usize::MAX {
1103+
for i in count..return_count {
1104+
vm.register_stack[base + a + i] = LuaValue::nil();
1105+
}
1106+
}
1107+
1108+
// Update caller's top if variable return
1109+
if return_count == usize::MAX {
1110+
vm.current_frame_mut().top = (a + count) as u32;
1111+
}
1112+
1113+
Ok(())
1114+
} else {
1115+
// Lua function
1116+
let func_id = match call_func.as_function_id() {
1117+
Some(id) => id,
1118+
None => return Err(vm.error("not a function".to_string())),
1119+
};
1120+
let func_ref = match vm.object_pool.get_function(func_id) {
1121+
Some(f) => f,
1122+
None => return Err(vm.error("invalid function".to_string())),
1123+
};
1124+
let chunk = func_ref.lua_chunk();
1125+
1126+
let max_stack_size = chunk.max_stack_size;
1127+
let num_params = chunk.param_count;
1128+
let is_vararg = chunk.is_vararg;
1129+
let code_ptr = chunk.code.as_ptr();
1130+
let constants_ptr = chunk.constants.as_ptr();
1131+
let upvalues_ptr = func_ref.upvalues.as_ptr();
1132+
1133+
// Ensure stack capacity
1134+
let required_size = new_base + max_stack_size;
1135+
vm.ensure_stack_capacity(required_size);
1136+
1137+
// Fill missing arguments with nil
1138+
if total_arg_count < num_params && !is_vararg {
1139+
for i in total_arg_count..num_params {
1140+
vm.register_stack[new_base + i] = LuaValue::nil();
1141+
}
1142+
}
1143+
1144+
// Create and push new frame
1145+
let nresults = if return_count == usize::MAX { -1i16 } else { return_count as i16 };
1146+
let new_frame = LuaCallFrame::new_lua_function(
1147+
func_id,
1148+
code_ptr,
1149+
constants_ptr,
1150+
upvalues_ptr,
1151+
new_base,
1152+
total_arg_count,
1153+
a,
1154+
nresults,
1155+
max_stack_size,
1156+
);
1157+
1158+
*frame_ptr_ptr = vm.push_frame(new_frame);
1159+
Ok(())
1160+
}
1161+
}
1162+
10021163
/// Slow path for vararg Lua function calls
10031164
#[inline(never)]
10041165
fn exec_call_lua_vararg(
@@ -1698,6 +1859,7 @@ pub fn exec_tailcall(
16981859
};
16991860
let chunk = func_ref.lua_chunk();
17001861
let max_stack_size = chunk.max_stack_size;
1862+
let num_params = chunk.param_count;
17011863
let code_ptr = chunk.code.as_ptr();
17021864
let constants_ptr = chunk.constants.as_ptr();
17031865
let upvalues_ptr = func_ref.upvalues.as_ptr();
@@ -1719,6 +1881,14 @@ pub fn exec_tailcall(
17191881
vm.register_stack[old_base + i] = *arg;
17201882
}
17211883

1884+
// CRITICAL: Fill missing parameters with nil
1885+
// If fewer arguments were passed than parameters expected, fill with nil
1886+
if arg_count < num_params {
1887+
for i in arg_count..num_params {
1888+
vm.register_stack[old_base + i] = LuaValue::nil();
1889+
}
1890+
}
1891+
17221892
// Create new frame at same location
17231893
let nresults = if return_count == usize::MAX {
17241894
-1i16

0 commit comments

Comments
 (0)