use crate::{
CallInfo, Chunk, LUA_MASKCALL, LUA_MASKRET, LuaValue,
lua_vm::{
CFunction, LUA_HOOKCALL, LUA_HOOKRET, LuaResult, LuaState, TmKind, call_info::call_status,
execute::hook::hook_on_return, get_metamethod_event, lua_limits::EXTRA_STACK,
},
};
#[inline(always)]
fn insert_callable_before_args(
lua_state: &mut LuaState,
func_idx: usize,
arg_count: usize,
callable: LuaValue,
original_func: LuaValue,
) -> LuaResult<usize> {
let first_arg = func_idx + 1;
let new_top = first_arg + arg_count + 1;
if new_top > lua_state.stack_len() {
lua_state.grow_stack(new_top)?;
}
unsafe {
let stack = lua_state.stack_mut().as_mut_ptr();
std::ptr::copy(stack.add(first_arg), stack.add(first_arg + 1), arg_count);
*stack.add(first_arg) = original_func;
*stack.add(func_idx) = callable;
}
lua_state.set_top_raw(new_top);
Ok(arg_count + 1)
}
pub fn resolve_call_chain(
lua_state: &mut LuaState,
func_idx: usize,
arg_count: usize,
) -> LuaResult<(usize, u8)> {
let mut current_arg_count = arg_count;
let mut ccmt_depth = 0;
loop {
if func_idx >= lua_state.stack_len() {
return Err(lua_state.error("resolve_call_chain: function not found".to_string()));
}
let func = unsafe { *lua_state.stack().get_unchecked(func_idx) };
if func.is_c_callable() || func.is_lua_function() {
return Ok((current_arg_count, ccmt_depth));
}
if func.ttisfulluserdata()
&& let Some(ud) = func.as_userdata_mut()
&& let Some(call_fn) = ud.get_trait().lua_call()
{
current_arg_count = insert_callable_before_args(
lua_state,
func_idx,
current_arg_count,
LuaValue::cfunction(call_fn),
func,
)?;
return Ok((current_arg_count, ccmt_depth));
}
if let Some(mm) = get_metamethod_event(lua_state, &func, TmKind::Call) {
if ccmt_depth == 15 {
return Err(lua_state.error("'__call' chain too long".to_string()));
}
ccmt_depth += 1;
current_arg_count =
insert_callable_before_args(lua_state, func_idx, current_arg_count, mm, func)?;
if mm.is_c_callable() || mm.is_lua_function() {
return Ok((current_arg_count, ccmt_depth));
}
} else {
return Err(crate::stdlib::debug::typeerror(lua_state, &func, "call"));
}
}
}
pub fn call_c_function(
lua_state: &mut LuaState,
func_idx: usize,
nargs: usize,
nresults: i32,
) -> LuaResult<()> {
let func = lua_state.stack_mut()[func_idx];
let c_func: Option<CFunction> = if let Some(f) = func.as_cfunction() {
Some(f)
} else {
func.as_cclosure().map(|cc| cc.func())
};
let call_base = func_idx + 1;
lua_state.push_c_frame(call_base, nargs, nresults)?;
if lua_state.hook_mask & LUA_MASKCALL != 0 && lua_state.allow_hook {
let narg = (lua_state.get_top() - call_base) as i32;
lua_state.run_hook(LUA_HOOKCALL, -1, 1, narg)?;
}
let n = if let Some(c_func) = c_func {
c_func(lua_state)?
} else {
func.as_rclosure().unwrap().call(lua_state)?
};
let stack_top = lua_state.get_top();
let first_result = if stack_top >= n {
stack_top - n
} else {
call_base
};
if lua_state.hook_mask & LUA_MASKRET != 0 && lua_state.allow_hook {
let ftransfer = (first_result - call_base + 1) as i32;
lua_state.run_hook(LUA_HOOKRET, -1, ftransfer, n as i32)?;
}
lua_state.pop_c_frame();
debug_assert!(lua_state.call_depth() > 0);
unsafe {
let stack = lua_state.stack_mut();
match nresults {
0 => { }
1 => {
*stack.get_unchecked_mut(func_idx) = if n > 0 {
*stack.get_unchecked(first_result)
} else {
LuaValue::nil()
};
}
_ if nresults > 0 => {
let wanted = nresults as usize;
let copy_count = n.min(wanted);
for i in 0..copy_count {
*stack.get_unchecked_mut(func_idx + i) = *stack.get_unchecked(first_result + i);
}
for i in copy_count..wanted {
*stack.get_unchecked_mut(func_idx + i) = LuaValue::nil();
}
}
_ => {
for i in 0..n {
*stack.get_unchecked_mut(func_idx + i) = *stack.get_unchecked(first_result + i);
}
}
}
}
let ci_idx = lua_state.call_depth() - 1;
if nresults >= 0 {
let frame_top = lua_state.get_call_info(ci_idx).top as usize;
lua_state.set_top_raw(frame_top);
} else {
let new_top = func_idx + n;
let ci_top = lua_state.get_call_info(ci_idx).top as usize;
if ci_top < new_top {
lua_state.get_call_info_mut(ci_idx).top = new_top as u32;
}
lua_state.set_top_raw(new_top);
}
Ok(())
}
#[inline(never)]
pub fn pretailcall_lua(
lua_state: &mut LuaState,
ci: &mut CallInfo,
func_idx: usize,
base: usize,
nargs: usize,
numparams: usize,
max_stack_size: usize,
chunk_ptr: *const Chunk,
upvalue_ptrs: *const crate::gc::UpvaluePtr,
) -> LuaResult<*const Chunk> {
let new_chunk_ptr = chunk_ptr;
let func_offset = ci.func_offset;
let func_pos = base - func_offset as usize;
let narg1 = nargs + 1;
unsafe {
let stack_ptr = lua_state.stack_mut().as_mut_ptr();
std::ptr::copy(stack_ptr.add(func_idx), stack_ptr.add(func_pos), narg1);
}
let new_base = func_pos + 1;
if nargs < numparams {
let stack = lua_state.stack_mut();
for i in nargs..numparams {
unsafe { *stack.get_unchecked_mut(new_base + i) = LuaValue::nil() };
}
}
let actual_nargs = nargs.max(numparams);
let frame_top = func_pos + 1 + max_stack_size;
let needed_physical = frame_top + EXTRA_STACK;
if needed_physical > lua_state.stack_len() {
lua_state.grow_stack(needed_physical)?;
}
ci.base = new_base;
ci.func_offset = 1;
ci.top = frame_top as u32;
ci.pc = 0;
ci.nextraargs = if nargs > numparams {
(nargs - numparams) as i32
} else {
0
};
ci.call_status |= call_status::CIST_TAIL;
ci.chunk_ptr = new_chunk_ptr;
ci.upvalue_ptrs = upvalue_ptrs;
lua_state.set_top_raw(new_base + actual_nargs);
Ok(new_chunk_ptr)
}
fn call_c_function_tailcall(
lua_state: &mut LuaState,
func_idx: usize,
nargs: usize,
) -> LuaResult<()> {
let func = lua_state.stack_mut()[func_idx];
let c_func: Option<CFunction> = if let Some(c_func) = func.as_cfunction() {
Some(c_func)
} else if let Some(cclosure) = func.as_cclosure() {
Some(cclosure.func())
} else if func.is_rclosure() {
None
} else {
return Err(lua_state.error("Not a callable value".to_string()));
};
let call_base = func_idx + 1;
lua_state.push_c_frame(call_base, nargs, -1)?;
let n = if let Some(c_func) = c_func {
c_func(lua_state)?
} else {
let rclosure = func.as_rclosure().unwrap();
rclosure.call(lua_state)?
};
let stack_top = lua_state.get_top();
let first_result = if stack_top >= n {
stack_top - n
} else {
call_base
};
lua_state.pop_c_frame();
unsafe {
let stack = lua_state.stack_mut();
match n {
0 => {}
1 => {
*stack.get_unchecked_mut(func_idx) = *stack.get_unchecked(first_result);
}
2 => {
*stack.get_unchecked_mut(func_idx) = *stack.get_unchecked(first_result);
*stack.get_unchecked_mut(func_idx + 1) = *stack.get_unchecked(first_result + 1);
}
_ => {
for i in 0..n {
*stack.get_unchecked_mut(func_idx + i) = *stack.get_unchecked(first_result + i);
}
}
}
}
let new_top = func_idx + n;
lua_state.set_top_raw(new_top);
Ok(())
}
#[inline(always)]
pub fn precall(
lua_state: &mut LuaState,
func_idx: usize,
nargs: usize,
nresults: i32,
) -> LuaResult<bool> {
let func = unsafe { lua_state.stack().get_unchecked(func_idx) };
if func.is_lua_function() {
let (param_count, max_stack_size, chunk_ptr, upvalue_ptrs) = {
let lua_func = unsafe { func.as_lua_function_unchecked() };
let chunk = lua_func.chunk();
(
chunk.param_count,
chunk.max_stack_size,
chunk as *const _,
lua_func.upvalues().as_ptr(),
)
};
if nargs == param_count
&& lua_state.try_push_lua_frame_exact(
func_idx + 1,
nresults,
max_stack_size,
chunk_ptr,
upvalue_ptrs,
)?
{
return Ok(true);
}
lua_state.push_lua_frame(
func_idx + 1,
nargs,
nresults,
param_count,
max_stack_size,
chunk_ptr,
upvalue_ptrs,
)?;
return Ok(true);
} else if func.is_c_callable() {
call_c_function(lua_state, func_idx, nargs, nresults)?;
return Ok(false);
}
precall_meta(lua_state, func_idx, nargs, nresults)
}
#[cold]
fn precall_meta(
lua_state: &mut LuaState,
func_idx: usize,
nargs: usize,
nresults: i32,
) -> LuaResult<bool> {
let (nargs, ccmt_depth) = resolve_call_chain(lua_state, func_idx, nargs)?;
let func = unsafe { lua_state.stack().get_unchecked(func_idx) };
if func.is_lua_function() {
let (param_count, max_stack_size, chunk_ptr, upvalue_ptrs) = {
let lua_func = unsafe { func.as_lua_function_unchecked() };
let chunk = lua_func.chunk();
(
chunk.param_count,
chunk.max_stack_size,
chunk as *const _,
lua_func.upvalues().as_ptr(),
)
};
if nargs == param_count
&& lua_state.try_push_lua_frame_exact(
func_idx + 1,
nresults,
max_stack_size,
chunk_ptr,
upvalue_ptrs,
)?
{
if ccmt_depth > 0 {
let fi = lua_state.call_depth() - 1;
let status = call_status::set_ccmt_count(0, ccmt_depth);
lua_state.get_call_info_mut(fi).call_status |= status;
}
return Ok(true);
}
lua_state.push_lua_frame(
func_idx + 1,
nargs,
nresults,
param_count,
max_stack_size,
chunk_ptr,
upvalue_ptrs,
)?;
if ccmt_depth > 0 {
let fi = lua_state.call_depth() - 1;
let status = call_status::set_ccmt_count(0, ccmt_depth);
lua_state.get_call_info_mut(fi).call_status |= status;
}
return Ok(true);
}
if func.is_c_callable() {
call_c_function(lua_state, func_idx, nargs, nresults)?;
return Ok(false);
}
let func = unsafe { *lua_state.stack().get_unchecked(func_idx) };
Err(crate::stdlib::debug::typeerror(lua_state, &func, "call"))
}
pub fn pretailcall(
lua_state: &mut LuaState,
ci: &mut CallInfo,
func_idx: usize,
narg1: usize,
) -> LuaResult<bool> {
let func = unsafe { lua_state.stack().get_unchecked(func_idx) };
if func.is_lua_function() {
let (param_count, max_stack_size, chunk_ptr, upvalue_ptrs) = {
let lua_func = unsafe { func.as_lua_function_unchecked() };
let chunk = lua_func.chunk();
(
chunk.param_count,
chunk.max_stack_size,
chunk as *const _,
lua_func.upvalues().as_ptr(),
)
};
let base = ci.base;
pretailcall_lua(
lua_state,
ci,
func_idx,
base,
narg1 - 1,
param_count,
max_stack_size,
chunk_ptr,
upvalue_ptrs,
)?;
return Ok(true);
} else if func.is_c_callable() {
call_c_function_tailcall(lua_state, func_idx, narg1 - 1)?;
return Ok(false);
}
pretailcall_meta(lua_state, ci, func_idx, narg1)
}
fn pretailcall_meta(
lua_state: &mut LuaState,
ci: &mut CallInfo,
func_idx: usize,
narg1: usize,
) -> LuaResult<bool> {
let nargs = narg1 - 1;
let (actual_nargs, _) = resolve_call_chain(lua_state, func_idx, nargs)?;
let func = unsafe { lua_state.stack().get_unchecked(func_idx) };
if func.is_lua_function() {
let (param_count, max_stack_size, chunk_ptr, upvalue_ptrs) = {
let lua_func = unsafe { func.as_lua_function_unchecked() };
let chunk = lua_func.chunk();
(
chunk.param_count,
chunk.max_stack_size,
chunk as *const _,
lua_func.upvalues().as_ptr(),
)
};
let base = ci.base;
pretailcall_lua(
lua_state,
ci,
func_idx,
base,
actual_nargs,
param_count,
max_stack_size,
chunk_ptr,
upvalue_ptrs,
)?;
return Ok(true);
}
if func.is_c_callable() {
call_c_function_tailcall(lua_state, func_idx, actual_nargs)?;
return Ok(false);
}
let func = unsafe { *lua_state.stack().get_unchecked(func_idx) };
Err(crate::stdlib::debug::typeerror(lua_state, &func, "call"))
}
pub fn poscall(
lua_state: &mut LuaState,
ci: &mut CallInfo,
nres: usize,
pc: usize,
) -> LuaResult<()> {
let nresults = ci.nresults();
if lua_state.hook_mask & LUA_MASKRET != 0 && lua_state.allow_hook {
hook_on_return(lua_state, ci, pc, nres as i32)?;
}
let res = ci.base - ci.func_offset as usize;
moveresults(lua_state, res, nres, nresults);
lua_state.pop_call_frame();
Ok(())
}
#[inline(always)]
fn moveresults(lua_state: &mut LuaState, res: usize, nres: usize, wanted: i32) {
let top = lua_state.get_top();
match wanted {
0 => {
lua_state.set_top_raw(res);
}
1 => {
let first_result = top - nres;
unsafe {
let sp = lua_state.stack_mut().as_mut_ptr();
*sp.add(res) = if nres == 0 {
LuaValue::nil()
} else {
*sp.add(first_result)
};
}
lua_state.set_top_raw(res + 1);
}
-1 => {
genmoveresults(lua_state, res, top - nres, nres, nres);
}
_ => {
genmoveresults(lua_state, res, top - nres, nres, wanted as usize);
}
}
}
#[inline(always)]
fn genmoveresults(
lua_state: &mut LuaState,
res: usize,
first_result: usize,
nres: usize,
wanted: usize,
) {
let copy_count = nres.min(wanted);
unsafe {
let sp = lua_state.stack_mut().as_mut_ptr();
if copy_count != 0 && res != first_result {
std::ptr::copy(sp.add(first_result), sp.add(res), copy_count);
}
for i in copy_count..wanted {
*sp.add(res + i) = LuaValue::nil();
}
}
lua_state.set_top_raw(res + wanted);
}