use super::super::compiler::UpvalueDesc;
use super::super::compiler::{RuntimeCaches, TforCursorEntry, TforCursorSlot};
use super::super::error::TypeError;
use super::Result;
use super::State;
use super::Val;
use super::frame::Frame;
use super::lua_val::RustFunc;
use crate::instr::{ArgCount, RetCount};
use crate::lua_std::{base_ipairs_iter, base_next};
impl State {
#[hotpath::measure]
pub(super) fn instr_branch(
&mut self,
frame: &mut Frame,
cond: bool,
offset: i16,
keep_cond: bool,
) -> Result<()> {
let val = self.pop_val();
let truthy = val.truthy();
if cond == truthy {
frame.jump(offset)?;
}
if keep_cond {
self.push_unchecked(val); }
Ok(())
}
#[hotpath::measure]
pub(super) fn instr_closure(&mut self, frame: &mut Frame, i: u8) -> Result<()> {
let bytecode = frame.get_nested_bytecode(i);
let mut captured_upvalues = Vec::with_capacity(bytecode.upvalues.len());
for desc in &bytecode.upvalues {
let uv_ref = match desc {
UpvalueDesc::Local(idx) => {
let stack_idx = frame.stack_bottom + *idx as usize;
self.find_or_create_upvalue(stack_idx)
}
UpvalueDesc::Upvalue(idx) => {
frame.upvalues[*idx as usize]
}
};
captured_upvalues.push(uv_ref);
}
self.push_closure(bytecode, captured_upvalues)
}
#[hotpath::measure]
pub(super) fn instr_for_prep(
&mut self,
frame: &mut Frame,
local: u8,
body_len: i16,
) -> Result<()> {
let step_val = self.pop_val();
let end_val = self.pop_val();
let start_val = self.pop_val();
let step = step_val
.as_num()
.ok_or_else(|| self.type_error(TypeError::Arithmetic(step_val.typ(&self.heap))))?;
let end = end_val
.as_num()
.ok_or_else(|| self.type_error(TypeError::Arithmetic(end_val.typ(&self.heap))))?;
let start = start_val
.as_num()
.ok_or_else(|| self.type_error(TypeError::Arithmetic(start_val.typ(&self.heap))))?;
if check_numeric_for_condition(start, end, step) {
for (local_slot, n) in
(local as usize + self.stack_bottom..).zip([start, end, step, start])
{
self.stack[local_slot] = Val::Num(n);
}
} else {
frame.jump(body_len)?;
}
Ok(())
}
#[hotpath::measure]
pub(super) fn instr_for_loop(
&mut self,
frame: &mut Frame,
local_slot: u8,
offset: i16,
) -> Result<()> {
let slot = local_slot as usize + self.stack_bottom;
let mut var = self.stack[slot].as_num().ok_or_else(|| {
self.type_error(TypeError::Arithmetic(self.stack[slot].typ(&self.heap)))
})?;
let limit = self.stack[slot + 1].as_num().ok_or_else(|| {
self.type_error(TypeError::Arithmetic(self.stack[slot + 1].typ(&self.heap)))
})?;
let step = self.stack[slot + 2].as_num().ok_or_else(|| {
self.type_error(TypeError::Arithmetic(self.stack[slot + 2].typ(&self.heap)))
})?;
var += step;
if check_numeric_for_condition(var, limit, step) {
self.stack[slot] = Val::Num(var);
self.stack[slot + 3] = Val::Num(var);
frame.jump(offset)?;
}
Ok(())
}
#[hotpath::measure]
pub(super) fn instr_tfor_prep(&mut self, local_slot: u8) {
let base = local_slot as usize + self.stack_bottom;
let control = self.pop_val();
let state = self.pop_val();
let iterator = self.pop_val();
self.stack[base] = iterator;
self.stack[base + 1] = state;
self.stack[base + 2] = control;
}
#[hotpath::measure]
pub(super) fn instr_tfor_call(
&mut self,
local_slot: u8,
num_vars: u8,
cursor_operand: u8,
caches: &RuntimeCaches,
) -> Result<()> {
let base = local_slot as usize + self.stack_bottom;
let iterator = self.stack[base];
let state = self.stack[base + 1];
let control = self.stack[base + 2];
if let Val::RustFn(f) = iterator {
let base_next_fn: RustFunc = base_next;
let base_ipairs_iter_fn: RustFunc = base_ipairs_iter;
if std::ptr::fn_addr_eq(f, base_next_fn)
&& self.instr_tfor_call_next(base, state, control, num_vars, cursor_operand, caches)
{
return Ok(());
}
if std::ptr::fn_addr_eq(f, base_ipairs_iter_fn)
&& self.instr_tfor_call_ipairs(base, state, control, num_vars)
{
return Ok(());
}
return self.instr_tfor_call_rust_fn(f, base, state, control, num_vars);
}
self.check_stack_space(3)?;
self.push_unchecked(iterator);
self.push_unchecked(state);
self.push_unchecked(control);
self.call(ArgCount::Fixed(2), RetCount::Fixed(num_vars))?;
let results_start = self.stack.len() - num_vars as usize;
for i in 0..num_vars as usize {
self.stack[base + 3 + i] = self.stack[results_start + i];
}
self.stack.truncate(results_start);
Ok(())
}
#[inline(always)]
pub(super) fn write_tfor_results(
&mut self,
base: usize,
num_vars: u8,
first: Val,
second: Option<Val>,
) {
for i in 0..num_vars as usize {
self.stack[base + 3 + i] = match i {
0 => first,
1 => second.unwrap_or(Val::Nil),
_ => Val::Nil,
};
}
}
pub(super) fn instr_tfor_call_next(
&mut self,
base: usize,
state: Val,
control: Val,
num_vars: u8,
cursor_operand: u8,
caches: &RuntimeCaches,
) -> bool {
let Some(table_ptr) = state.as_object_ptr() else {
return false;
};
let Some(tbl) = self.heap.as_table_ref(table_ptr) else {
return false;
};
let cursor = cursor_operand
.checked_sub(1)
.and_then(|index| caches.tfor_cursor.get(index as usize));
let next = match cursor.and_then(TforCursorSlot::get) {
Some(entry) if entry.table == table_ptr => {
match tbl.next_from_matching_index(entry.index, &control) {
super::table::TableNextWithIndex::InvalidKey => tbl.next_with_index(&control),
next => next,
}
}
_ => tbl.next_with_index(&control),
};
match next {
super::table::TableNextWithIndex::Pair { index, key, value } => {
if let Some(slot) = cursor {
slot.set(TforCursorEntry {
table: table_ptr,
index,
});
}
self.write_tfor_results(base, num_vars, key, Some(value));
}
super::table::TableNextWithIndex::End => {
self.write_tfor_results(base, num_vars, Val::Nil, None);
}
super::table::TableNextWithIndex::InvalidKey => return false,
}
true
}
pub(super) fn instr_tfor_call_ipairs(
&mut self,
base: usize,
state: Val,
control: Val,
num_vars: u8,
) -> bool {
let Some(old_index) = control.as_num() else {
return false;
};
let Some(tbl) = state
.as_object_ptr()
.and_then(|ptr| self.heap.as_table_ref(ptr))
else {
return false;
};
let new_index = old_index + 1.0;
let key = Val::Num(new_index);
let val = tbl.get(&key);
if matches!(val, Val::Nil) && tbl.get_metatable().is_some() {
return false;
}
if matches!(val, Val::Nil) {
self.write_tfor_results(base, num_vars, Val::Nil, None);
} else {
self.write_tfor_results(base, num_vars, key, Some(val));
}
true
}
pub(super) fn instr_tfor_call_rust_fn(
&mut self,
f: RustFunc,
base: usize,
state: Val,
control: Val,
num_vars: u8,
) -> Result<()> {
let old_stack_bottom = self.stack_bottom;
let call_base = self.stack.len();
self.check_stack_space(2)?;
self.push_unchecked(state);
self.push_unchecked(control);
self.stack_bottom = call_base;
let result = f(self);
let num_ret_reported = match result {
Ok(n) => n,
Err(e) => {
self.stack.truncate(call_base);
self.stack_bottom = old_stack_bottom;
return Err(e);
}
};
let num_ret_actual = self.get_top();
let reported = usize::from(num_ret_reported);
match reported.cmp(&num_ret_actual) {
std::cmp::Ordering::Greater => {
if let Err(e) = self.check_stack_space(reported - num_ret_actual) {
self.stack.truncate(call_base);
self.stack_bottom = old_stack_bottom;
return Err(e);
}
for _ in num_ret_actual..reported {
self.push_unchecked(Val::Nil);
}
}
std::cmp::Ordering::Less => {
let slc = &mut self.stack[self.stack_bottom..];
slc.rotate_right(reported);
let new_len = self.stack.len() - num_ret_actual + reported;
self.stack.truncate(new_len);
}
std::cmp::Ordering::Equal => (),
}
self.stack_bottom = old_stack_bottom;
if let Err(e) = self.balance_stack(num_vars as usize, num_ret_reported as usize) {
self.stack.truncate(call_base);
return Err(e);
}
let results_start = self.stack.len() - num_vars as usize;
for i in 0..num_vars as usize {
self.stack[base + 3 + i] = self.stack[results_start + i];
}
self.stack.truncate(results_start);
Ok(())
}
#[hotpath::measure]
pub(super) fn instr_tfor_loop(
&mut self,
frame: &mut Frame,
local_slot: u8,
offset: i16,
) -> Result<()> {
let base = local_slot as usize + self.stack_bottom;
let first_var = &self.stack[base + 3];
if matches!(first_var, Val::Nil) {
frame.jump(offset)?;
} else {
self.stack[base + 2] = self.stack[base + 3];
}
Ok(())
}
}
fn check_numeric_for_condition(var: f64, limit: f64, step: f64) -> bool {
if step == 0.0 {
false
} else if step > 0.0 {
var <= limit
} else {
var >= limit
}
}