Skip to content

Commit 5332c87

Browse files
committed
update
1 parent 0342671 commit 5332c87

File tree

3 files changed

+238
-5
lines changed

3 files changed

+238
-5
lines changed

crates/luars/src/compiler/expr.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2567,8 +2567,13 @@ pub fn compile_call_expr_with_returns_and_dest(
25672567
// Handle method call with SELF instruction
25682568
let call_reg = if inner_is_method {
25692569
if let LuaExpr::IndexExpr(index_expr) = &inner_prefix {
2570-
let func_reg = alloc_register(c);
2571-
alloc_register(c); // Reserve A+1 for self
2570+
// CRITICAL FIX: For method call as last argument, we need to place
2571+
// the function at arg_dest so the call result ends up at the correct position.
2572+
// SELF A B C: R[A+1] = R[B]; R[A] = R[B][C]
2573+
// So we want A = arg_dest, and need to reserve arg_dest+1 for self
2574+
let func_reg = arg_dest;
2575+
ensure_register(c, func_reg);
2576+
ensure_register(c, func_reg + 1); // Reserve for self
25722577

25732578
let obj_expr = index_expr
25742579
.get_prefix_expr()
@@ -3495,6 +3500,7 @@ pub fn compile_closure_expr_to(
34953500
func_compiler.chunk.param_count = regular_param_count + param_offset;
34963501
func_compiler.chunk.is_vararg = has_vararg;
34973502
func_compiler.freereg = (regular_param_count + param_offset) as u32;
3503+
func_compiler.peak_freereg = func_compiler.freereg; // CRITICAL: Initialize peak_freereg with parameters!
34983504
func_compiler.nactvar = (regular_param_count + param_offset) as usize;
34993505

35003506
// Emit VarargPrep instruction if function accepts varargs

crates/luars/src/compiler/stmt.rs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,21 +627,127 @@ fn compile_return_stat(c: &mut Compiler, stat: &LuaReturnStat) -> Result<(), Str
627627
return Err("Tail call missing function expression".to_string());
628628
};
629629

630+
// Check if this is a method call (obj:method syntax)
631+
let is_method = if let LuaExpr::IndexExpr(index_expr) = &func_expr {
632+
index_expr
633+
.get_index_token()
634+
.map(|t| t.is_colon())
635+
.unwrap_or(false)
636+
} else {
637+
false
638+
};
639+
630640
// Get arguments first to know how many we have
631641
let args = if let Some(args_list) = call_expr.get_args_list() {
632642
args_list.get_args().collect::<Vec<_>>()
633643
} else {
634644
Vec::new()
635645
};
636646

637-
// Reserve all registers we'll need (func + args)
638-
let num_total = 1 + args.len();
647+
// For method calls, we need an extra slot for self
648+
let num_args = args.len();
649+
let num_total = if is_method {
650+
2 + num_args // func + self + explicit args
651+
} else {
652+
1 + num_args // func + args
653+
};
654+
655+
// Reserve all registers we'll need
639656
let mut reserved_regs = Vec::new();
640657
for _ in 0..num_total {
641658
reserved_regs.push(alloc_register(c));
642659
}
643660
let base_reg = reserved_regs[0];
644661

662+
// Handle method call with SELF instruction
663+
if is_method {
664+
if let LuaExpr::IndexExpr(index_expr) = &func_expr {
665+
// Method call: obj:method(args) → SELF instruction
666+
// SELF A B C: R(A+1) = R(B); R(A) = R(B)[C]
667+
668+
// Compile object (table)
669+
let obj_expr = index_expr
670+
.get_prefix_expr()
671+
.ok_or("Method call missing object")?;
672+
let obj_reg = compile_expr(c, &obj_expr)?;
673+
674+
// Get method name
675+
let method_name = if let Some(emmylua_parser::LuaIndexKey::Name(name_token)) =
676+
index_expr.get_index_key()
677+
{
678+
name_token.get_name_text().to_string()
679+
} else {
680+
return Err("Method call requires name index".to_string());
681+
};
682+
683+
// Add method name to constants
684+
let lua_str = create_string_value(c, &method_name);
685+
let key_idx = add_constant_dedup(c, lua_str);
686+
687+
// Emit SELF instruction: R(base_reg+1) = R(obj_reg); R(base_reg) = R(obj_reg)[key]
688+
emit(
689+
c,
690+
Instruction::create_abck(
691+
OpCode::Self_,
692+
base_reg,
693+
obj_reg,
694+
key_idx,
695+
true, // k=1: C is constant index
696+
),
697+
);
698+
699+
// Compile explicit arguments to consecutive registers after self (base_reg+2, ...)
700+
let mut last_is_vararg_all_out = false;
701+
for (i, arg) in args.iter().enumerate() {
702+
let target_reg = base_reg + 2 + i as u32; // +2 for func and self
703+
let is_last_arg = i == num_args - 1;
704+
705+
// Check if last argument is ... (vararg)
706+
if is_last_arg {
707+
if let LuaExpr::LiteralExpr(lit) = arg {
708+
if matches!(
709+
lit.get_literal(),
710+
Some(emmylua_parser::LuaLiteralToken::Dots(_))
711+
) {
712+
emit(
713+
c,
714+
Instruction::encode_abc(OpCode::Vararg, target_reg, 0, 0),
715+
);
716+
last_is_vararg_all_out = true;
717+
continue;
718+
}
719+
}
720+
}
721+
722+
let arg_reg = compile_expr(c, &arg)?;
723+
if arg_reg != target_reg {
724+
emit_move(c, target_reg, arg_reg);
725+
}
726+
}
727+
728+
// Emit TailCall instruction
729+
// For method call, B includes implicit self parameter
730+
let b_param = if last_is_vararg_all_out {
731+
0
732+
} else {
733+
(num_args + 2) as u32 // +1 for self, +1 for Lua convention
734+
};
735+
emit(
736+
c,
737+
Instruction::encode_abc(OpCode::TailCall, base_reg, b_param, 0),
738+
);
739+
740+
// After TAILCALL, emit RETURN
741+
emit(
742+
c,
743+
Instruction::create_abck(OpCode::Return, base_reg, 0, 0, false),
744+
);
745+
746+
return Ok(());
747+
}
748+
}
749+
750+
// Regular function call (not method)
645751
// Compile function to the first reserved register
646752
let func_reg = compile_expr(c, &func_expr)?;
647753
if func_reg != base_reg {
@@ -677,7 +783,6 @@ fn compile_return_stat(c: &mut Compiler, stat: &LuaReturnStat) -> Result<(), Str
677783

678784
// Emit TailCall instruction
679785
// A = function register, B = num_args + 1 (or 0 if last arg is vararg "all out")
680-
let num_args = args.len();
681786
let b_param = if last_is_vararg_all_out {
682787
0 // B=0: all in (variable number of args from vararg)
683788
} else {

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,128 @@ pub fn exec_tailcall(
18141814

18151815
Ok(())
18161816
}
1817+
LuaValueKind::Table => {
1818+
// Table with __call metamethod
1819+
// Need to find __call and call it with the table as first argument
1820+
1821+
// Get __call metamethod
1822+
let call_mm = match vm.get_metamethod(&func, "__call") {
1823+
Some(mm) => mm,
1824+
None => return Err(vm.error("attempt to call a table value".to_string())),
1825+
};
1826+
1827+
// Prepare arguments: table (self) + original args
1828+
let mut new_args = Vec::with_capacity(args.len() + 1);
1829+
new_args.push(func); // The table itself as first argument
1830+
new_args.extend(args.iter().copied());
1831+
1832+
// Check if metamethod is a Lua function or C function
1833+
match call_mm.kind() {
1834+
LuaValueKind::Function => {
1835+
let Some(func_id) = call_mm.as_function_id() else {
1836+
return Err(vm.error("invalid __call metamethod".to_string()));
1837+
};
1838+
1839+
// Extract all info from func_ref before calling mutable methods
1840+
let (max_stack_size, code_ptr, constants_ptr, upvalues_ptr, num_params) = {
1841+
let Some(func_ref) = vm.object_pool.get_function(func_id) else {
1842+
return Err(vm.error("Invalid function ID".to_string()));
1843+
};
1844+
let chunk = func_ref.lua_chunk();
1845+
(
1846+
chunk.max_stack_size,
1847+
chunk.code.as_ptr(),
1848+
chunk.constants.as_ptr(),
1849+
func_ref.upvalues.as_ptr(),
1850+
chunk.param_count,
1851+
)
1852+
};
1853+
1854+
vm.close_upvalues_from(base);
1855+
let old_base = base;
1856+
vm.pop_frame_discard();
1857+
1858+
vm.ensure_stack_capacity(old_base + max_stack_size);
1859+
1860+
// Copy arguments to frame base
1861+
for (i, arg) in new_args.iter().enumerate() {
1862+
vm.register_stack[old_base + i] = *arg;
1863+
}
1864+
1865+
// Fill remaining parameters with nil
1866+
for i in new_args.len()..num_params {
1867+
vm.register_stack[old_base + i] = LuaValue::nil();
1868+
}
1869+
1870+
let nresults = if return_count == usize::MAX {
1871+
-1i16
1872+
} else {
1873+
return_count as i16
1874+
};
1875+
let new_frame = LuaCallFrame::new_lua_function(
1876+
func_id,
1877+
code_ptr,
1878+
constants_ptr,
1879+
upvalues_ptr,
1880+
old_base,
1881+
new_args.len(),
1882+
result_reg,
1883+
nresults,
1884+
max_stack_size,
1885+
);
1886+
1887+
*frame_ptr_ptr = vm.push_frame(new_frame);
1888+
Ok(())
1889+
}
1890+
LuaValueKind::CFunction => {
1891+
let Some(c_func) = call_mm.as_cfunction() else {
1892+
return Err(vm.error("invalid __call metamethod".to_string()));
1893+
};
1894+
1895+
let call_base = base;
1896+
vm.ensure_stack_capacity(call_base + new_args.len() + 1);
1897+
1898+
vm.register_stack[call_base] = call_mm;
1899+
for (i, arg) in new_args.iter().enumerate() {
1900+
vm.register_stack[call_base + 1 + i] = *arg;
1901+
}
1902+
1903+
let temp_frame = LuaCallFrame::new_c_function(call_base, new_args.len() + 1);
1904+
vm.push_frame(temp_frame);
1905+
let result = c_func(vm)?;
1906+
vm.pop_frame_discard();
1907+
1908+
vm.pop_frame_discard();
1909+
1910+
if !vm.frames_is_empty() {
1911+
*frame_ptr_ptr = vm.current_frame_ptr();
1912+
1913+
let parent_base = vm.current_frame().base_ptr as usize;
1914+
let vals = result.all_values();
1915+
let count = if return_count == usize::MAX {
1916+
vals.len()
1917+
} else {
1918+
vals.len().min(return_count)
1919+
};
1920+
1921+
for i in 0..count {
1922+
vm.register_stack[parent_base + result_reg + i] = vals[i];
1923+
}
1924+
1925+
if return_count != usize::MAX {
1926+
for i in count..return_count {
1927+
vm.register_stack[parent_base + result_reg + i] = LuaValue::nil();
1928+
}
1929+
}
1930+
1931+
vm.current_frame_mut().top = (result_reg + count) as u32;
1932+
}
1933+
1934+
Ok(())
1935+
}
1936+
_ => Err(vm.error("attempt to call a table value (invalid __call)".to_string())),
1937+
}
1938+
}
18171939
_ => Err(vm.error(format!("attempt to call a {} value", func.type_name()))),
18181940
}
18191941
}

0 commit comments

Comments
 (0)