use crate::VmResult;
use crate::call::ThreadStack;
use crate::debug::DebugRuntime;
use crate::debug::LuaDebug;
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::metamethod::{MetamethodRuntime, TmEvent};
use crate::native::NativeCallContext;
use crate::state::ThreadState;
use crate::state::{THREAD_STATUS_BREAK, THREAD_STATUS_OK, THREAD_STATUS_YIELD};
use crate::thread::{LUA_MULTRET, Thread};
use crate::value::TValueCursor;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PreCallResult {
Lua,
Native,
}
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait VmCallFrame: Sealed {
unsafe fn try_func_tm(&self, function: TValueCursor) -> VmResult;
unsafe fn pre_call(&self, function: TValueCursor, n_results: i32) -> VmResult<PreCallResult>;
unsafe fn pos_call(&self, first: TValueCursor);
unsafe fn call_hook<F>(&self, hook: F, userdata: *mut ()) -> VmResult
where
F: FnOnce(&Thread, &mut LuaDebug) -> VmResult;
}
impl VmCallFrame for Thread {
unsafe fn try_func_tm(&self, function: TValueCursor) -> VmResult {
unsafe {
let function_value = function.value_unchecked();
let Some(tm) = self.get_tm_by_obj(function_value, TmEvent::Call) else {
return self.type_error(function_value, "call").map_err(Into::into);
};
if !tm.is_function() {
return self.type_error(function_value, "call").map_err(Into::into);
}
let top = self.stack_top();
let mut slot = top;
while slot > function {
slot.value_unchecked()
.set_obj(slot.sub(1).value_unchecked());
slot = slot.sub(1);
}
self.set_stack_top(top.add(1));
function.value_unchecked().set_obj(tm);
}
Ok(())
}
unsafe fn pre_call(&self, function: TValueCursor, n_results: i32) -> VmResult<PreCallResult> {
unsafe {
if !function.value_unchecked().is_function() {
self.try_func_tm(function)?;
}
let closure = function.value_unchecked().closure_value();
let call_info = self.incr_ci()?.call_info_unchecked();
call_info.init_call(
function,
self.stack_top()
.add(closure.as_ptr().as_ref().unwrap_unchecked().stack_size as usize),
n_results,
closure.proto(),
);
self.set_stack_base(call_info.base());
self.check_stack_for_new_ci(
closure.as_ptr().as_ref().unwrap_unchecked().stack_size as i32,
)?;
debug_assert!(call_info.top() <= self.stack_last());
if closure.is_lua() {
let proto = closure.proto().unwrap_unchecked();
let mut arg = self.stack_top();
let arg_end = self
.stack_base()
.add(proto.as_ptr().as_ref().unwrap_unchecked().num_params as usize);
while arg < arg_end {
arg.value_unchecked().set_nil();
arg = arg.add(1);
}
self.set_stack_top(
if proto.as_ptr().as_ref().unwrap_unchecked().is_vararg != 0 {
arg
} else {
call_info.top()
},
);
call_info.set_saved_pc(proto.as_ptr().as_ref().unwrap_unchecked().code);
if proto.as_ptr().as_ref().unwrap_unchecked().exec_target != 0
&& !proto
.as_ptr()
.as_ref()
.unwrap_unchecked()
.exec_data
.is_null()
{
call_info.as_ptr().as_mut().unwrap_unchecked().flags =
crate::state::LUA_CALLINFO_NATIVE;
}
Ok(PreCallResult::Lua)
} else {
let native_function = closure.native_data().function.unwrap_unchecked();
let count = native_function(NativeCallContext::new(self))?;
let call_info = self.current_call_info();
let parent_cursor = self.current_call_info_cursor().sub(1);
let mut result = call_info.function();
let mut value = self.stack_top().sub(count);
let value_end = self.stack_top();
let mut remaining = n_results;
while remaining != 0 && value < value_end {
result.value_unchecked().set_obj(value.value_unchecked());
result = result.add(1);
value = value.add(1);
remaining -= 1;
}
while remaining > 0 {
result.value_unchecked().set_nil();
result = result.add(1);
remaining -= 1;
}
self.restore_call_frame(parent_cursor, result);
Ok(PreCallResult::Native)
}
}
}
unsafe fn pos_call(&self, first: TValueCursor) {
unsafe {
let call_info = self.current_call_info();
let parent_cursor = self.current_call_info_cursor().sub(1);
let parent = parent_cursor.call_info_unchecked();
let mut result = call_info.function();
let mut value = first;
let value_end = self.stack_top();
let mut remaining = call_info.as_ptr().as_ref().unwrap_unchecked().n_results;
while remaining != 0 && value < value_end {
result.value_unchecked().set_obj(value.value_unchecked());
result = result.add(1);
value = value.add(1);
remaining -= 1;
}
while remaining > 0 {
result.value_unchecked().set_nil();
result = result.add(1);
remaining -= 1;
}
self.restore_call_frame(
parent_cursor,
if call_info.as_ptr().as_ref().unwrap_unchecked().n_results == LUA_MULTRET {
result
} else {
parent.top()
},
);
}
}
unsafe fn call_hook<F>(&self, hook: F, userdata: *mut ()) -> VmResult
where
F: FnOnce(&Thread, &mut LuaDebug) -> VmResult,
{
let (base_offset, top_offset, ci_top_offset, status, old_saved_pc, mut ar) = unsafe {
let base_offset = self.save_stack(self.stack_base());
let top_offset = self.save_stack(self.stack_top());
let ci_top_offset = self.save_stack(self.current_call_info().top());
let status = self.as_ptr().as_ref().unwrap_unchecked().status;
if status == THREAD_STATUS_YIELD || status == THREAD_STATUS_BREAK {
self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_OK;
self.set_stack_base(self.current_call_info().base());
}
let closure = self.current_call_info().function_closure();
let old_saved_pc = self.current_call_info().saved_pc();
if closure.is_lua() {
let proto = closure.proto().unwrap_unchecked();
let code_end = proto
.as_ptr()
.as_ref()
.unwrap_unchecked()
.code
.add(proto.as_ptr().as_ref().unwrap_unchecked().size_code as usize);
if !old_saved_pc.is_null() && old_saved_pc != code_end {
self.current_call_info().set_saved_pc(old_saved_pc.add(1));
}
}
self.check_stack_internal(crate::thread::LUA_MIN_STACK as i32)?;
self.current_call_info()
.set_top(self.stack_top().add(crate::thread::LUA_MIN_STACK));
debug_assert!(self.current_call_info().top() <= self.stack_last());
let ar = LuaDebug {
currentline: if closure.is_native() {
-1
} else {
let proto = closure.proto().unwrap_unchecked();
proto.get_line(proto.pc_rel(old_saved_pc))
},
userdata,
..LuaDebug::default()
};
(
base_offset,
top_offset,
ci_top_offset,
status,
old_saved_pc,
ar,
)
};
let result = hook(self, &mut ar);
unsafe {
self.current_call_info().set_saved_pc(old_saved_pc);
self.current_call_info()
.set_top(self.restore_stack(ci_top_offset));
self.set_stack_top(self.restore_stack(top_offset));
if status == THREAD_STATUS_YIELD
&& self.as_ptr().as_ref().unwrap_unchecked().status != THREAD_STATUS_YIELD
{
self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_YIELD;
self.set_stack_base(self.restore_stack(base_offset));
} else if status == THREAD_STATUS_BREAK {
debug_assert_ne!(
self.as_ptr().as_ref().unwrap_unchecked().status,
THREAD_STATUS_BREAK
);
self.as_ptr().as_mut().unwrap_unchecked().status = THREAD_STATUS_BREAK;
self.set_stack_base(self.restore_stack(base_offset));
}
}
result
}
}