use crate::debug::DebugRuntime;
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::memory::MemoryRuntime;
use crate::state::ThreadState;
use crate::state::{CallInfoCursor, RawCallInfo};
use crate::thread::{LUAI_MAX_CALLS, LUAI_MAX_NATIVE_CALLS, Thread};
use crate::value::{RawTValue, TValueCursor};
use crate::{VmError, VmErrorResult};
pub(super) const MAX_STACK_SIZE: i32 =
((1024 / core::mem::size_of::<RawTValue>()) * 1024 * 1024) as i32;
pub(super) const MAX_CALLS_HARD: usize = LUAI_MAX_CALLS + (LUAI_MAX_CALLS >> 3);
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait ThreadStack: Sealed {
unsafe fn check_stack_internal(&self, needed: i32) -> VmErrorResult;
unsafe fn check_stack_for_new_ci(&self, needed: i32) -> VmErrorResult;
unsafe fn get_grown_stack_size(&self, needed: i32) -> i32;
unsafe fn stack_limit_reached(&self, needed: i32) -> bool;
unsafe fn save_stack(&self, pointer: TValueCursor) -> isize;
unsafe fn restore_stack(&self, offset: isize) -> TValueCursor;
unsafe fn save_ci(&self, pointer: CallInfoCursor) -> isize;
unsafe fn restore_ci(&self, offset: isize) -> CallInfoCursor;
unsafe fn expand_stack_limit(&self, pointer: TValueCursor);
unsafe fn realloc_stack(&self, new_size: i32, for_new_ci: bool) -> VmErrorResult;
unsafe fn realloc_ci(&self, new_size: i32) -> VmErrorResult;
unsafe fn grow_stack(&self, needed: i32) -> VmErrorResult;
unsafe fn grow_ci(&self) -> VmErrorResult<CallInfoCursor>;
unsafe fn incr_ci(&self) -> VmErrorResult<CallInfoCursor>;
unsafe fn check_c_stack(&self) -> VmErrorResult;
}
impl Thread {
unsafe fn correct_stack(&self, old_stack: TValueCursor) {
unsafe {
let new_stack_cursor = self.stack();
let top_offset = self.stack_top().addr_offset_from(old_stack) as usize;
self.set_stack_top(new_stack_cursor.add(top_offset));
let mut upvalue = self.open_upvalue();
while let Some(current_upvalue) = upvalue {
current_upvalue.rebase_value(old_stack, new_stack_cursor);
upvalue = current_upvalue.open_data().thread_next();
}
let mut call_info_cursor = self.base_call_info_cursor();
let current_call_info_cursor = self.current_call_info_cursor();
while call_info_cursor <= current_call_info_cursor {
call_info_cursor
.call_info_unchecked()
.rebase_stack(old_stack, new_stack_cursor);
call_info_cursor = call_info_cursor.add(1);
}
let base_offset = self.stack_base().addr_offset_from(old_stack) as usize;
self.set_stack_base(new_stack_cursor.add(base_offset));
}
}
}
impl ThreadStack for Thread {
unsafe fn check_stack_internal(&self, needed: i32) -> VmErrorResult {
if unsafe { self.stack_limit_reached(needed) } {
unsafe { self.grow_stack(needed)? };
}
Ok(())
}
unsafe fn check_stack_for_new_ci(&self, needed: i32) -> VmErrorResult {
if unsafe { self.stack_limit_reached(needed) } {
unsafe { self.realloc_stack(self.get_grown_stack_size(needed), true)? };
}
Ok(())
}
unsafe fn get_grown_stack_size(&self, needed: i32) -> i32 {
let stack_size = unsafe { self.as_ptr().as_ref().unwrap_unchecked().stack_size };
if needed <= stack_size {
2 * stack_size
} else {
stack_size + needed
}
}
unsafe fn stack_limit_reached(&self, needed: i32) -> bool {
let thread = unsafe { self.as_ptr().as_ref().unwrap_unchecked() };
let available_bytes = (thread.stack_last as usize).wrapping_sub(thread.top as usize);
available_bytes <= needed as usize * core::mem::size_of::<RawTValue>()
}
unsafe fn save_stack(&self, pointer: TValueCursor) -> isize {
let stack = unsafe { self.stack() };
unsafe {
pointer
.as_ptr()
.cast::<u8>()
.offset_from(stack.as_ptr().cast::<u8>())
}
}
unsafe fn restore_stack(&self, offset: isize) -> TValueCursor {
unsafe { TValueCursor::from_ptr(self.stack().as_ptr().cast::<u8>().offset(offset).cast()) }
}
unsafe fn save_ci(&self, pointer: CallInfoCursor) -> isize {
let base_ci = unsafe { self.as_ptr().as_ref().unwrap_unchecked().base_ci };
unsafe {
pointer
.as_ptr()
.cast::<u8>()
.offset_from(base_ci.cast::<u8>())
}
}
unsafe fn restore_ci(&self, offset: isize) -> CallInfoCursor {
unsafe {
CallInfoCursor::from_ptr(
self.as_ptr()
.as_ref()
.unwrap_unchecked()
.base_ci
.cast::<u8>()
.offset(offset)
.cast::<RawCallInfo>(),
)
}
}
unsafe fn expand_stack_limit(&self, pointer: TValueCursor) {
debug_assert!(pointer <= unsafe { self.stack_last() });
unsafe {
let current_call_info = self.current_call_info();
if current_call_info.top() < pointer {
current_call_info.set_top(pointer);
}
}
}
unsafe fn realloc_stack(&self, new_size: i32, for_new_ci: bool) -> VmErrorResult {
unsafe {
if new_size > MAX_STACK_SIZE {
if for_new_ci {
let parent_cursor = self.current_call_info_cursor().sub(1);
self.restore_call_frame(
parent_cursor,
parent_cursor.call_info_unchecked().top(),
);
}
return Err(VmError::Memory);
}
let old_stack = self.stack();
let real_size = new_size + crate::state::EXTRA_STACK as i32;
debug_assert!(
self.as_ptr()
.as_ref()
.unwrap_unchecked()
.stack_last
.offset_from(self.as_ptr().as_ref().unwrap_unchecked().stack)
== self.as_ptr().as_ref().unwrap_unchecked().stack_size as isize
- crate::state::EXTRA_STACK as isize
);
let old_stack_size = self.as_ptr().as_ref().unwrap_unchecked().stack_size as usize;
let new_stack_size = real_size as usize;
let memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
let new_stack = TValueCursor::from_ptr(self.realloc_array(
old_stack.as_ptr(),
old_stack_size,
new_stack_size,
memcat,
)?);
self.set_stack(new_stack);
for index in old_stack_size..new_stack_size {
new_stack.add(index).value_unchecked().set_nil();
}
let raw = self.as_ptr().as_mut().unwrap_unchecked();
raw.stack_size = real_size;
raw.stack_last = new_stack.add(new_size as usize).as_ptr();
self.correct_stack(old_stack);
}
Ok(())
}
unsafe fn realloc_ci(&self, new_size: i32) -> VmErrorResult {
unsafe {
let old_ci = self.as_ptr().as_ref().unwrap_unchecked().base_ci;
let old_size = self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize;
let new_size_usize = new_size as usize;
let memcat = self.as_ptr().as_ref().unwrap_unchecked().active_memcat;
let new_ci = self.realloc_array(old_ci, old_size, new_size_usize, memcat)?;
let current_offset =
self.current_call_info_cursor()
.offset_from(self.base_call_info_cursor()) as usize;
let raw = self.as_ptr().as_mut().unwrap_unchecked();
raw.base_ci = new_ci;
raw.size_ci = new_size;
raw.end_ci = new_ci.add(new_size_usize - 1);
self.set_current_call_info(self.base_call_info_cursor().add(current_offset));
}
Ok(())
}
unsafe fn grow_stack(&self, needed: i32) -> VmErrorResult {
unsafe { self.realloc_stack(self.get_grown_stack_size(needed), false) }
}
unsafe fn grow_ci(&self) -> VmErrorResult<CallInfoCursor> {
unsafe {
if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize >= MAX_CALLS_HARD {
return Err(VmError::ErrorHandler);
}
let current_size = self.as_ptr().as_ref().unwrap_unchecked().size_ci;
let request = current_size * 2;
let new_size = if current_size as usize >= LUAI_MAX_CALLS {
MAX_CALLS_HARD as i32
} else if request as usize >= LUAI_MAX_CALLS {
LUAI_MAX_CALLS as i32
} else {
request
};
self.realloc_ci(new_size)?;
if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize > LUAI_MAX_CALLS {
return crate::run_error!(self, "stack overflow");
}
let call_info = self.current_call_info_cursor().add(1);
self.set_current_call_info(call_info);
Ok(call_info)
}
}
unsafe fn incr_ci(&self) -> VmErrorResult<CallInfoCursor> {
unsafe {
if self.current_call_info_cursor()
== self
.base_call_info_cursor()
.add(self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize - 1)
{
self.grow_ci()
} else {
let call_info = self.current_call_info_cursor().add(1);
self.set_current_call_info(call_info);
Ok(call_info)
}
}
}
unsafe fn check_c_stack(&self) -> VmErrorResult {
unsafe {
let native_call_depth = self.as_ptr().as_ref().unwrap_unchecked().native_call_depth;
if native_call_depth == LUAI_MAX_NATIVE_CALLS {
return crate::run_error!(self, "C stack overflow");
} else if native_call_depth >= crate::state::MAX_NATIVE_CALLS_HARD {
return Err(VmError::ErrorHandler);
}
}
Ok(())
}
}