use std::rc::Rc;
use crate::error::{LuaError, LuaResult, RuntimeError};
use crate::check_interrupted;
use super::callinfo::{CallInfo, LUA_MULTRET};
use super::closure::{Closure, LuaClosure, Upvalue};
use super::debug_info;
use super::gc::arena::GcRef;
use super::instructions::{Instruction, LFIELDS_PER_FLUSH, OpCode, index_k, is_k};
use super::metatable::{MAXTAGLOOP, TMS, get_comp_tm, gettmbyobj, val_raw_equal};
use super::proto::{Proto, VARARG_ISVARARG, VARARG_NEEDSARG};
use super::state::{
Gc, LUA_MINSTACK, LuaState, MASK_CALL, MASK_COUNT, MASK_LINE, MASK_RET, MAXCALLS, MAXCCALLS,
};
use super::table::Table;
use super::value::{Userdata, Val};
use crate::platform::{localeconv, strcoll, strtod};
#[allow(unsafe_code)]
pub(crate) fn libc_strtod(s: &[u8]) -> Option<(f64, usize)> {
let mut buf = Vec::with_capacity(s.len() + 1);
buf.extend_from_slice(s);
buf.push(0);
let mut endptr: *mut u8 = std::ptr::null_mut();
let result = unsafe { strtod(buf.as_ptr(), &raw mut endptr) };
let consumed = endptr as usize - buf.as_ptr() as usize;
if consumed == 0 {
return None;
}
Some((result, consumed))
}
#[allow(unsafe_code)]
pub(crate) fn locale_decimal_point() -> u8 {
let lc = unsafe { localeconv() };
if lc.is_null() {
return b'.';
}
let dp = unsafe { (*lc).decimal_point };
if dp.is_null() {
return b'.';
}
let ch = unsafe { *dp };
if ch == 0 { b'.' } else { ch }
}
#[inline]
fn rk(stack: &[Val], base: usize, constants: &[Val], field: u32) -> Val {
if is_k(field) {
constants[index_k(field) as usize]
} else {
stack[base + field as usize]
}
}
pub(crate) fn coerce_to_number(val: Val, gc: &Gc) -> Option<f64> {
match val {
Val::Num(n) => Some(n),
Val::Str(r) => {
let s = gc.string_arena.get(r)?;
str_to_number(s.data())
}
_ => None,
}
}
pub(crate) fn str_to_number(data: &[u8]) -> Option<f64> {
let start = data.iter().position(|&b| !b.is_ascii_whitespace())?;
let trimmed = &data[start..];
if trimmed.is_empty() {
return None;
}
let (result, consumed) = libc_strtod(trimmed)?;
let rest = &trimmed[consumed..];
if !rest.is_empty() && (rest[0] == b'x' || rest[0] == b'X') {
let hex_str = std::str::from_utf8(trimmed).ok()?;
let hex_trimmed = hex_str.trim();
let (sign, hex_part) = if let Some(rest) = hex_trimmed.strip_prefix('-') {
(-1.0_f64, rest)
} else if let Some(rest) = hex_trimmed.strip_prefix('+') {
(1.0, rest)
} else {
(1.0, hex_trimmed)
};
let hex_part = hex_part
.strip_prefix("0x")
.or_else(|| hex_part.strip_prefix("0X"))?;
let val = u64::from_str_radix(hex_part, 16).ok()?;
return Some(sign * val as f64);
}
if rest.iter().all(u8::is_ascii_whitespace) {
Some(result)
} else {
None
}
}
#[allow(dead_code)] fn coerce_to_string(val: Val, gc: &mut Gc) -> Option<Val> {
match val {
Val::Str(_) => Some(val),
Val::Num(_) => {
let formatted = format!("{val}");
let r = gc.intern_string(formatted.as_bytes());
Some(Val::Str(r))
}
_ => None,
}
}
fn runtime_error(proto: &Proto, pc: usize, message: &str) -> LuaError {
let line = if pc > 0 && pc <= proto.line_info.len() {
proto.line_info[pc - 1]
} else if !proto.line_info.is_empty() {
proto.line_info[0]
} else {
0
};
let source = chunkid(&proto.source);
LuaError::Runtime(RuntimeError {
message: format!("{source}:{line}: {message}"),
level: 0,
traceback: vec![],
})
}
fn type_error(
state: &LuaState,
proto: &Proto,
pc: usize,
base: usize,
reg: usize,
opname: &str,
) -> LuaError {
let val = state.stack_get(base + reg);
let type_name = val.type_name();
let current_pc = pc.saturating_sub(1);
let name_info = debug_info::getobjname(
proto,
current_pc,
#[allow(clippy::cast_possible_truncation)]
(reg as u32),
&state.gc.string_arena,
);
let message = if let Some((kind, name)) = name_info {
format!("attempt to {opname} {kind} '{name}' (a {type_name} value)")
} else {
format!("attempt to {opname} a {type_name} value")
};
runtime_error(proto, pc, &message)
}
#[allow(clippy::too_many_arguments)]
fn arith_error(
state: &LuaState,
proto: &Proto,
pc: usize,
base: usize,
lhs: Val,
rhs: Val,
b_rk: u32,
c_rk: u32,
) -> LuaError {
let rk = if coerce_to_number(lhs, &state.gc).is_none() {
b_rk
} else {
c_rk
};
if is_k(rk) {
let val = if coerce_to_number(lhs, &state.gc).is_none() {
lhs
} else {
rhs
};
runtime_error(
proto,
pc,
&format!(
"attempt to perform arithmetic on a {} value",
val.type_name()
),
)
} else {
type_error(state, proto, pc, base, rk as usize, "perform arithmetic on")
}
}
fn compare_error(proto: &Proto, pc: usize, left: Val, right: Val) -> LuaError {
let message = if left.type_name() == right.type_name() {
format!("attempt to compare two {} values", left.type_name())
} else {
format!(
"attempt to compare {} with {}",
left.type_name(),
right.type_name()
)
};
runtime_error(proto, pc, &message)
}
pub(crate) fn fb2int(x: u32) -> u32 {
let e = (x >> 3) & 31;
if e == 0 { x } else { ((x & 7) + 8) << (e - 1) }
}
pub enum CallResult {
Lua,
Rust,
}
impl LuaState {
fn check_stack_overflow(&self) -> LuaResult<()> {
if self.call_depth >= MAXCCALLS {
let hard_limit = MAXCCALLS + (MAXCCALLS >> 3);
if self.call_depth >= hard_limit {
return Err(runtime_error_simple("stack overflow"));
}
if self.call_depth == MAXCCALLS {
let where_prefix = get_where(self, 0);
return Err(LuaError::Runtime(RuntimeError {
message: format!("{where_prefix}stack overflow"),
level: 0,
traceback: vec![],
}));
}
}
Ok(())
}
pub fn call_function(&mut self, func_idx: usize, num_results: i32) -> LuaResult<()> {
self.n_ccalls += 1;
self.call_depth += 1;
let result = (|| {
self.check_stack_overflow()?;
match self.precall(func_idx, num_results)? {
CallResult::Lua => execute(self),
CallResult::Rust => Ok(()),
}
})();
self.call_depth -= 1;
self.n_ccalls -= 1;
result
}
pub fn callhook(&mut self, event: &str, line: i32) -> LuaResult<()> {
if !self.hook.allow_hook {
return Ok(());
}
let hook_func = self.hook.hook_func;
if hook_func.is_nil() {
return Ok(());
}
let saved_top = self.top;
let saved_ci_top = self.call_stack[self.ci].top;
self.ensure_stack(self.top + LUA_MINSTACK);
self.call_stack[self.ci].top = self.top + LUA_MINSTACK;
self.hook.allow_hook = false;
let call_base = self.top;
let event_ref = self.gc.intern_string(event.as_bytes());
self.stack_set(call_base, hook_func);
self.stack_set(call_base + 1, Val::Str(event_ref));
if line >= 0 {
#[allow(clippy::cast_precision_loss)]
self.stack_set(call_base + 2, Val::Num(f64::from(line)));
} else {
self.stack_set(call_base + 2, Val::Nil);
}
self.top = call_base + 3;
let result = self.call_function(call_base, 0);
self.hook.allow_hook = true;
self.call_stack[self.ci].top = saved_ci_top;
self.top = saved_top;
result
}
pub fn precall(&mut self, func_idx: usize, num_results: i32) -> LuaResult<CallResult> {
let next_ci = self.ci + 1;
if self.ci_overflow {
if next_ci >= MAXCALLS * 2 {
return Err(runtime_error_simple("stack overflow"));
}
} else if next_ci >= MAXCALLS {
self.ci_overflow = true;
let where_prefix = get_where(self, 0);
return Err(LuaError::Runtime(RuntimeError {
message: format!("{where_prefix}stack overflow"),
level: 0,
traceback: vec![],
}));
}
let func_val = self.stack_get(func_idx);
let closure_ref = if let Val::Function(r) = func_val {
r
} else {
let tm = get_tm_for_val(&self.gc, func_val, TMS::Call);
match tm {
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
let top = self.top;
self.ensure_stack(top + 1);
let mut p = top;
while p > func_idx {
let v = self.stack_get(p - 1);
self.stack_set(p, v);
p -= 1;
}
self.top = top + 1;
self.stack_set(func_idx, tm_val);
match tm_val {
Val::Function(r) => r,
_ => unreachable!(),
}
}
_ => {
let ci = &self.call_stack[self.ci];
let caller_func = self.stack_get(ci.func);
let err = if let Val::Function(r) = caller_func {
if let Some(Closure::Lua(lcl)) = self.gc.closures.get(r) {
let reg = func_idx - ci.base;
type_error(self, &lcl.proto, ci.saved_pc, ci.base, reg, "call")
} else {
LuaError::Runtime(RuntimeError {
message: format!(
"attempt to call a {} value",
func_val.type_name()
),
level: 0,
traceback: vec![],
})
}
} else {
LuaError::Runtime(RuntimeError {
message: format!("attempt to call a {} value", func_val.type_name()),
level: 0,
traceback: vec![],
})
};
return Err(err);
}
}
};
let saved_pc = self.call_stack[self.ci].saved_pc;
let _ = saved_pc;
let closure = self
.gc
.closures
.get(closure_ref)
.ok_or_else(|| runtime_error_simple("invalid function reference"))?;
match closure {
Closure::Lua(lua_cl) => {
let proto = Rc::clone(&lua_cl.proto);
let num_params = proto.num_params as usize;
let max_stack = proto.max_stack_size as usize;
let is_vararg = proto.is_vararg & VARARG_ISVARARG != 0;
self.ensure_stack(func_idx + max_stack + 1);
let nargs = self.get_nargs(func_idx);
let new_base;
if is_vararg {
new_base = self.adjust_varargs(&proto, nargs, func_idx);
} else {
new_base = func_idx + 1;
if nargs > num_params {
self.top = new_base + num_params;
} else {
while self.top < new_base + num_params {
self.push(Val::Nil);
}
}
}
let ci_top = new_base + max_stack;
for i in self.top..ci_top {
self.stack_set(i, Val::Nil);
}
self.top = ci_top;
let mut ci = CallInfo::new(func_idx, new_base, ci_top, num_results);
ci.is_lua = true;
self.push_ci(ci);
self.base = new_base;
if self.hook.hook_mask & MASK_CALL != 0 {
self.call_stack[self.ci].saved_pc += 1;
self.callhook("call", -1)?;
self.call_stack[self.ci].saved_pc =
self.call_stack[self.ci].saved_pc.saturating_sub(1);
}
Ok(CallResult::Lua)
}
Closure::Rust(rust_cl) => {
let func = rust_cl.func;
self.ensure_stack(self.top + LUA_MINSTACK);
let ci_top = self.top + LUA_MINSTACK;
let ci = CallInfo::new(func_idx, func_idx + 1, ci_top, num_results);
self.push_ci(ci);
self.base = func_idx + 1;
if self.hook.hook_mask & MASK_CALL != 0 {
self.callhook("call", -1)?;
}
let n_results = func(self)?;
let first_result = self.top - n_results as usize;
self.poscall(first_result);
Ok(CallResult::Rust)
}
}
}
pub fn poscall(&mut self, mut first_result: usize) -> bool {
if self.hook.hook_mask & MASK_RET != 0 {
let fr_offset = first_result;
let _ = self.callhook("return", -1);
let tail_calls = self.call_stack[self.ci].tail_calls;
for _ in 0..tail_calls {
let _ = self.callhook("tail return", -1);
}
first_result = fr_offset;
}
let ci_func = self.call_stack[self.ci].func;
let wanted = self.call_stack[self.ci].num_results;
self.pop_ci();
self.base = self.call_stack[self.ci].base;
let mut res = ci_func;
if wanted == LUA_MULTRET {
let mut src = first_result;
while src < self.top {
self.stack_set(res, self.stack_get(src));
res += 1;
src += 1;
}
} else {
let mut moved = 0i32;
let mut src = first_result;
while moved < wanted && src < self.top {
self.stack_set(res, self.stack_get(src));
res += 1;
src += 1;
moved += 1;
}
while moved < wanted {
self.stack_set(res, Val::Nil);
res += 1;
moved += 1;
}
}
self.top = res;
wanted != LUA_MULTRET
}
fn adjust_varargs(&mut self, proto: &Proto, nargs: usize, func_idx: usize) -> usize {
let num_params = proto.num_params as usize;
let mut actual = nargs;
while actual < num_params {
self.push(Val::Nil);
actual += 1;
}
let arg_table = if proto.is_vararg & VARARG_NEEDSARG != 0 {
let nvar = actual - num_params; let fixed = self.top - actual;
let mut tbl = Table::new();
for i in 0..nvar {
let val = self.stack_get(fixed + num_params + i);
#[allow(clippy::cast_precision_loss)]
let _ = tbl.raw_set(Val::Num((i + 1) as f64), val, &self.gc.string_arena);
}
let n_key = self.gc.intern_string(b"n");
#[allow(clippy::cast_precision_loss)]
let _ = tbl.raw_set(
Val::Str(n_key),
Val::Num(nvar as f64),
&self.gc.string_arena,
);
Some(self.gc.alloc_table(tbl))
} else {
None
};
let fixed = self.top - actual;
let new_base = self.top;
self.ensure_stack(new_base + num_params + 1);
for i in 0..num_params {
let val = self.stack_get(fixed + i);
self.stack_set(self.top, val);
self.top += 1;
self.stack_set(fixed + i, Val::Nil);
}
if let Some(tbl_ref) = arg_table {
self.stack_set(self.top, Val::Table(tbl_ref));
self.top += 1;
}
let _ = func_idx;
new_base
}
pub fn find_upvalue(&mut self, stack_index: usize) -> super::gc::arena::GcRef<Upvalue> {
for &uv_ref in &self.open_upvalues {
if let Some(uv) = self.gc.upvalues.get(uv_ref)
&& let Some(idx) = uv.stack_index()
{
if idx == stack_index {
return uv_ref;
}
if idx < stack_index {
break; }
}
}
let uv = Upvalue::new_open(stack_index);
let uv_ref = self.gc.alloc_upvalue(uv);
let pos = self
.open_upvalues
.iter()
.position(|&r| {
self.gc
.upvalues
.get(r)
.and_then(super::closure::Upvalue::stack_index)
.is_none_or(|idx| idx < stack_index)
})
.unwrap_or(self.open_upvalues.len());
self.open_upvalues.insert(pos, uv_ref);
uv_ref
}
pub fn close_upvalues(&mut self, level: usize) {
while let Some(&uv_ref) = self.open_upvalues.first() {
let should_close = self
.gc
.upvalues
.get(uv_ref)
.and_then(super::closure::Upvalue::stack_index)
.is_some_and(|idx| idx >= level);
if !should_close {
break;
}
if let Some(uv) = self.gc.upvalues.get_mut(uv_ref) {
uv.close(&self.stack);
}
let captured_val = self
.gc
.upvalues
.get(uv_ref)
.map_or(Val::Nil, |uv| uv.get(&self.stack));
let uv_color = self
.gc
.upvalues
.color(uv_ref)
.unwrap_or(crate::vm::gc::Color::White0);
self.gc.barrier_forward_val(uv_color, captured_val);
self.open_upvalues.remove(0);
}
}
}
fn runtime_error_simple(message: &str) -> LuaError {
LuaError::Runtime(RuntimeError {
message: message.to_string(),
level: 0,
traceback: vec![],
})
}
use crate::error::chunkid;
pub(crate) fn get_where(state: &LuaState, level: u32) -> String {
let level = level as usize;
if state.ci < level {
return String::new();
}
let target_ci = state.ci - level;
let ci = &state.call_stack[target_ci];
let func_val = state.stack_get(ci.func);
if let Val::Function(r) = func_val
&& let Some(Closure::Lua(lcl)) = state.gc.closures.get(r)
{
let proto = &lcl.proto;
let pc = ci.saved_pc;
let line = if pc > 0 && pc <= proto.line_info.len() {
proto.line_info[pc - 1]
} else if !proto.line_info.is_empty() {
proto.line_info[0]
} else {
return String::new();
};
let short_src = chunkid(&proto.source);
return format!("{short_src}:{line}: ");
}
String::new()
}
fn get_tm_for_val(gc: &Gc, val: Val, event: TMS) -> Option<Val> {
gettmbyobj(
val,
event,
&gc.tables,
&gc.string_arena,
&gc.type_metatables,
&gc.tm_names,
&gc.userdata,
)
}
fn call_tm_res(
state: &mut LuaState,
tm: Val,
arg1: Val,
arg2: Val,
result_reg: usize,
) -> LuaResult<()> {
let call_base = state.top;
state.ensure_stack(call_base + 4);
state.stack_set(call_base, tm);
state.stack_set(call_base + 1, arg1);
state.stack_set(call_base + 2, arg2);
state.top = call_base + 3;
state.call_function(call_base, 1)?;
let result = state.stack_get(call_base);
state.stack_set(result_reg, result);
Ok(())
}
fn call_tm_void(state: &mut LuaState, tm: Val, arg1: Val, arg2: Val, arg3: Val) -> LuaResult<()> {
let call_base = state.top;
state.ensure_stack(call_base + 5);
state.stack_set(call_base, tm);
state.stack_set(call_base + 1, arg1);
state.stack_set(call_base + 2, arg2);
state.stack_set(call_base + 3, arg3);
state.top = call_base + 4;
state.call_function(call_base, 0)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn call_bin_tm(
state: &mut LuaState,
lhs: Val,
rhs: Val,
result_reg: usize,
event: TMS,
proto: &Proto,
pc: usize,
base: usize,
b_rk: u32,
c_rk: u32,
) -> LuaResult<()> {
let tm =
get_tm_for_val(&state.gc, lhs, event).or_else(|| get_tm_for_val(&state.gc, rhs, event));
match tm {
Some(tm_val) => call_tm_res(state, tm_val, lhs, rhs, result_reg),
None => Err(arith_error(state, proto, pc, base, lhs, rhs, b_rk, c_rk)),
}
}
fn call_order_tm(state: &mut LuaState, lhs: Val, rhs: Val, event: TMS) -> LuaResult<Option<bool>> {
let tm1 = get_tm_for_val(&state.gc, lhs, event);
let Some(tm1_val) = tm1 else {
return Ok(None);
};
let tm2 = get_tm_for_val(&state.gc, rhs, event);
let tm2_val = tm2.unwrap_or(Val::Nil);
if !val_raw_equal(tm1_val, tm2_val, &state.gc.tables, &state.gc.string_arena) {
return Ok(None);
}
let call_base = state.top;
state.ensure_stack(call_base + 4);
state.stack_set(call_base, tm1_val);
state.stack_set(call_base + 1, lhs);
state.stack_set(call_base + 2, rhs);
state.top = call_base + 3;
state.call_depth += 1;
let cmp_result = (|| {
state.check_stack_overflow()?;
match state.precall(call_base, 1)? {
CallResult::Lua => execute(state),
CallResult::Rust => Ok(()),
}
})();
state.call_depth -= 1;
cmp_result?;
let result = state.stack_get(call_base);
Ok(Some(result.is_truthy()))
}
fn vm_concat(
state: &mut LuaState,
base: usize,
b: usize,
c: usize,
proto: &Proto,
pc: usize,
) -> LuaResult<()> {
let mut total = c - b + 1; let mut last = c;
while total > 1 {
let top = base + last + 1;
let lhs = state.stack_get(top - 2);
let rhs = state.stack_get(top - 1);
if !is_string_or_number(lhs, &state.gc) || !is_string_or_number(rhs, &state.gc) {
let tm = get_tm_for_val(&state.gc, lhs, TMS::Concat)
.or_else(|| get_tm_for_val(&state.gc, rhs, TMS::Concat));
if let Some(tm_val) = tm {
call_tm_res(state, tm_val, lhs, rhs, top - 2)?;
} else {
let reg = if is_string_or_number(lhs, &state.gc) {
last
} else {
last - 1
};
return Err(type_error(state, proto, pc, base, reg, "concatenate"));
}
total -= 1;
last -= 1;
} else {
let mut n = 2;
while n < total && is_string_or_number(state.stack_get(top - n - 1), &state.gc) {
n += 1;
}
let mut tl: usize = 0;
for i in (0..n).rev() {
let l = val_string_len(state.stack_get(top - 1 - i), &state.gc);
if l >= MAX_STRING_SIZE - tl {
return Err(runtime_error(proto, pc, "string length overflow"));
}
tl += l;
}
let mut buffer = Vec::with_capacity(tl);
for i in (0..n).rev() {
let val = state.stack_get(top - 1 - i);
val_to_string_bytes(val, &state.gc, &mut buffer);
}
let r = state.gc.intern_string(&buffer);
state.stack_set(top - n, Val::Str(r));
total -= n - 1;
last -= n - 1;
}
}
Ok(())
}
const MAX_STRING_SIZE: usize = (u32::MAX - 2) as usize;
fn is_string_or_number(val: Val, gc: &Gc) -> bool {
matches!(val, Val::Num(_)) || {
if let Val::Str(r) = val {
gc.string_arena.get(r).is_some()
} else {
false
}
}
}
fn val_string_len(val: Val, gc: &Gc) -> usize {
match val {
Val::Str(r) => gc.string_arena.get(r).map_or(0, |s| s.data().len()),
Val::Num(_) => format!("{val}").len(),
_ => 0,
}
}
fn val_to_string_bytes(val: Val, gc: &Gc, buffer: &mut Vec<u8>) {
match val {
Val::Str(r) => {
if let Some(s) = gc.string_arena.get(r) {
buffer.extend_from_slice(s.data());
}
}
Val::Num(_) => {
let formatted = format!("{val}");
buffer.extend_from_slice(formatted.as_bytes());
}
_ => {}
}
}
pub fn execute(state: &mut LuaState) -> LuaResult<()> {
let mut nexeccalls: u32 = 1;
loop {
let ci_func = state.call_stack[state.ci].func;
let mut base = state.base;
let Val::Function(closure_ref) = state.stack_get(ci_func) else {
return Err(runtime_error_simple("not a function"));
};
let (proto, env) = {
let cl = state
.gc
.closures
.get(closure_ref)
.ok_or_else(|| runtime_error_simple("invalid closure"))?;
match cl {
Closure::Lua(lcl) => (Rc::clone(&lcl.proto), lcl.env),
Closure::Rust(_) => {
return Err(runtime_error_simple("expected Lua closure in execute"));
}
}
};
let mut pc = state.call_stack[state.ci].saved_pc;
loop {
if pc >= proto.code.len() {
return Err(runtime_error(&proto, pc, "bytecode overrun"));
}
let instr = Instruction::from_raw(proto.code[pc]);
pc += 1;
if check_interrupted() {
return Err(runtime_error(&proto, pc, "interrupted!"));
}
if (state.hook.hook_mask & (MASK_LINE | MASK_COUNT)) != 0 {
state.hook.hook_count -= 1;
if state.hook.hook_count == 0 || (state.hook.hook_mask & MASK_LINE) != 0 {
let npc = pc - 1;
let old_pc = state.call_stack[state.ci].saved_pc;
state.call_stack[state.ci].saved_pc = pc;
if state.hook.hook_mask > MASK_LINE && state.hook.hook_count == 0 {
state.hook.hook_count = state.hook.base_hook_count;
if state.hook.yield_on_hook {
state.call_stack[state.ci].saved_pc = npc;
state.yielded_in_hook = true;
return Err(LuaError::Yield(0));
}
state.callhook("count", -1)?;
}
if (state.hook.hook_mask & MASK_LINE) != 0 {
#[allow(clippy::cast_possible_wrap)]
let newline = proto.line_info.get(npc).copied().unwrap_or(0) as i32;
let should_fire = npc == 0 || pc <= old_pc || {
let old_npc = old_pc.wrapping_sub(1);
#[allow(clippy::cast_possible_wrap)]
let oldline = proto.line_info.get(old_npc).copied().unwrap_or(0) as i32;
newline != oldline
};
if should_fire {
if state.hook.yield_on_hook {
state.call_stack[state.ci].saved_pc = npc;
state.yielded_in_hook = true;
return Err(LuaError::Yield(0));
}
state.callhook("line", newline)?;
}
}
base = state.base;
}
}
let op = instr.opcode();
let a = instr.a() as usize;
let ra = base + a;
if option_env!("LUA_DEBUG_VM").is_some() {
eprintln!(
" [{pc:>4}] {op:<12} A={a} B={} C={} (base={base}, top={})",
instr.b(),
instr.c(),
state.top
);
}
match op {
OpCode::Move => {
let b = instr.b() as usize;
let val = state.stack_get(base + b);
state.stack_set(ra, val);
}
OpCode::LoadK => {
let bx = instr.bx() as usize;
let val = proto.constants[bx];
state.stack_set(ra, val);
}
OpCode::LoadBool => {
let b = instr.b();
state.stack_set(ra, Val::Bool(b != 0));
if instr.c() != 0 {
pc += 1; }
}
OpCode::LoadNil => {
let b = instr.b() as usize;
for i in a..=b {
state.stack_set(base + i, Val::Nil);
}
}
OpCode::GetGlobal => {
let bx = instr.bx() as usize;
let key = proto.constants[bx];
state.call_stack[state.ci].saved_pc = pc;
vm_gettable(state, Val::Table(env), key, ra, &proto, pc, base, None)?;
}
OpCode::SetGlobal => {
let bx = instr.bx() as usize;
let key = proto.constants[bx];
let val = state.stack_get(ra);
state.call_stack[state.ci].saved_pc = pc;
vm_settable(state, Val::Table(env), key, val, &proto, pc, base, None)?;
}
OpCode::GetTable => {
let b = instr.b() as usize;
let table_val = state.stack_get(base + b);
let key = rk(&state.stack, base, &proto.constants, instr.c());
state.call_stack[state.ci].saved_pc = pc;
vm_gettable(state, table_val, key, ra, &proto, pc, base, Some(b))?;
}
OpCode::SetTable => {
let table_val = state.stack_get(ra);
let key = rk(&state.stack, base, &proto.constants, instr.b());
let val = rk(&state.stack, base, &proto.constants, instr.c());
state.call_stack[state.ci].saved_pc = pc;
vm_settable(state, table_val, key, val, &proto, pc, base, Some(a))?;
}
OpCode::NewTable => {
state.gc_check()?;
let narray = fb2int(instr.b()) as usize;
let nhash = fb2int(instr.c()) as usize;
let t = state.gc.alloc_table(Table::with_sizes(narray, nhash));
state.stack_set(ra, Val::Table(t));
}
OpCode::OpSelf => {
let b = instr.b() as usize;
let table_val = state.stack_get(base + b);
state.stack_set(ra + 1, table_val);
let key = rk(&state.stack, base, &proto.constants, instr.c());
state.call_stack[state.ci].saved_pc = pc;
vm_gettable(state, table_val, key, ra, &proto, pc, base, Some(b))?;
}
OpCode::Add => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
if let (Some(nb), Some(nc)) = (
coerce_to_number(b_val, &state.gc),
coerce_to_number(c_val, &state.gc),
) {
state.stack_set(ra, Val::Num(nb + nc));
} else {
state.call_stack[state.ci].saved_pc = pc;
call_bin_tm(
state,
b_val,
c_val,
ra,
TMS::Add,
&proto,
pc,
base,
instr.b(),
instr.c(),
)?;
}
}
OpCode::Sub => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
if let (Some(nb), Some(nc)) = (
coerce_to_number(b_val, &state.gc),
coerce_to_number(c_val, &state.gc),
) {
state.stack_set(ra, Val::Num(nb - nc));
} else {
state.call_stack[state.ci].saved_pc = pc;
call_bin_tm(
state,
b_val,
c_val,
ra,
TMS::Sub,
&proto,
pc,
base,
instr.b(),
instr.c(),
)?;
}
}
OpCode::Mul => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
if let (Some(nb), Some(nc)) = (
coerce_to_number(b_val, &state.gc),
coerce_to_number(c_val, &state.gc),
) {
state.stack_set(ra, Val::Num(nb * nc));
} else {
state.call_stack[state.ci].saved_pc = pc;
call_bin_tm(
state,
b_val,
c_val,
ra,
TMS::Mul,
&proto,
pc,
base,
instr.b(),
instr.c(),
)?;
}
}
OpCode::Div => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
if let (Some(nb), Some(nc)) = (
coerce_to_number(b_val, &state.gc),
coerce_to_number(c_val, &state.gc),
) {
state.stack_set(ra, Val::Num(nb / nc));
} else {
state.call_stack[state.ci].saved_pc = pc;
call_bin_tm(
state,
b_val,
c_val,
ra,
TMS::Div,
&proto,
pc,
base,
instr.b(),
instr.c(),
)?;
}
}
OpCode::Mod => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
if let (Some(nb), Some(nc)) = (
coerce_to_number(b_val, &state.gc),
coerce_to_number(c_val, &state.gc),
) {
state.stack_set(ra, Val::Num((nb / nc).floor().mul_add(-nc, nb)));
} else {
state.call_stack[state.ci].saved_pc = pc;
call_bin_tm(
state,
b_val,
c_val,
ra,
TMS::Mod,
&proto,
pc,
base,
instr.b(),
instr.c(),
)?;
}
}
OpCode::Pow => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
if let (Some(nb), Some(nc)) = (
coerce_to_number(b_val, &state.gc),
coerce_to_number(c_val, &state.gc),
) {
state.stack_set(ra, Val::Num(nb.powf(nc)));
} else {
state.call_stack[state.ci].saved_pc = pc;
call_bin_tm(
state,
b_val,
c_val,
ra,
TMS::Pow,
&proto,
pc,
base,
instr.b(),
instr.c(),
)?;
}
}
OpCode::Unm => {
let b = instr.b() as usize;
let b_val = state.stack_get(base + b);
if let Some(nb) = coerce_to_number(b_val, &state.gc) {
state.stack_set(ra, Val::Num(-nb));
} else {
let tm = get_tm_for_val(&state.gc, b_val, TMS::Unm);
match tm {
Some(tm_val) => {
state.call_stack[state.ci].saved_pc = pc;
call_tm_res(state, tm_val, b_val, b_val, ra)?;
}
None => {
return Err(type_error(
state,
&proto,
pc,
base,
b,
"perform arithmetic on",
));
}
}
}
}
OpCode::Not => {
let b = instr.b() as usize;
let b_val = state.stack_get(base + b);
state.stack_set(ra, Val::Bool(!b_val.is_truthy()));
}
OpCode::Len => {
let b = instr.b() as usize;
let b_val = state.stack_get(base + b);
match b_val {
Val::Str(r) => {
let s =
state.gc.string_arena.get(r).ok_or_else(|| {
runtime_error_simple("invalid string reference")
})?;
#[allow(clippy::cast_precision_loss)]
state.stack_set(ra, Val::Num(s.len() as f64));
}
Val::Table(r) => {
let t =
state.gc.tables.get(r).ok_or_else(|| {
runtime_error_simple("invalid table reference")
})?;
#[allow(clippy::cast_precision_loss)]
let len = t.len(&state.gc.string_arena) as f64;
state.stack_set(ra, Val::Num(len));
}
_ => {
let tm = get_tm_for_val(&state.gc, b_val, TMS::Len);
if let Some(tm_val) = tm {
state.call_stack[state.ci].saved_pc = pc;
call_tm_res(state, tm_val, b_val, Val::Nil, ra)?;
} else {
return Err(type_error(
state,
&proto,
pc,
base,
b,
"get length of",
));
}
}
}
}
OpCode::Concat => {
state.gc_check()?;
let b = instr.b() as usize;
let c = instr.c() as usize;
state.call_stack[state.ci].saved_pc = pc;
vm_concat(state, base, b, c, &proto, pc)?;
if ra != base + b {
let val = state.stack_get(base + b);
state.stack_set(ra, val);
}
}
OpCode::Eq => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
let equal = if val_equal(b_val, c_val, &state.gc) {
true
} else if std::mem::discriminant(&b_val) != std::mem::discriminant(&c_val) {
false
} else {
let tm = match (b_val, c_val) {
(Val::Table(r1), Val::Table(r2)) => {
let mt1 = state.gc.tables.get(r1).and_then(Table::metatable);
let mt2 = state.gc.tables.get(r2).and_then(Table::metatable);
get_comp_tm(
&state.gc.tables,
&state.gc.string_arena,
mt1,
mt2,
TMS::Eq,
&state.gc.tm_names,
)
}
(Val::Userdata(r1), Val::Userdata(r2)) => {
let mt1 = state.gc.userdata.get(r1).and_then(Userdata::metatable);
let mt2 = state.gc.userdata.get(r2).and_then(Userdata::metatable);
get_comp_tm(
&state.gc.tables,
&state.gc.string_arena,
mt1,
mt2,
TMS::Eq,
&state.gc.tm_names,
)
}
_ => None,
};
if let Some(tm_val) = tm {
state.call_stack[state.ci].saved_pc = pc;
let res = state.top;
call_tm_res(state, tm_val, b_val, c_val, res)?;
state.stack_get(res).is_truthy()
} else {
false
}
};
let expected = a != 0;
if equal == expected {
let jump_instr = Instruction::from_raw(proto.code[pc]);
pc = ((pc as i64) + i64::from(jump_instr.sbx()) + 1) as usize;
} else {
pc += 1;
}
}
OpCode::Lt => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
state.call_stack[state.ci].saved_pc = pc;
let result = val_less_than(b_val, c_val, state, &proto, pc)?;
let expected = a != 0;
if result == expected {
let jump_instr = Instruction::from_raw(proto.code[pc]);
pc = ((pc as i64) + i64::from(jump_instr.sbx()) + 1) as usize;
} else {
pc += 1;
}
}
OpCode::Le => {
let b_val = rk(&state.stack, base, &proto.constants, instr.b());
let c_val = rk(&state.stack, base, &proto.constants, instr.c());
state.call_stack[state.ci].saved_pc = pc;
let result = val_less_equal(b_val, c_val, state, &proto, pc)?;
let expected = a != 0;
if result == expected {
let jump_instr = Instruction::from_raw(proto.code[pc]);
pc = ((pc as i64) + i64::from(jump_instr.sbx()) + 1) as usize;
} else {
pc += 1;
}
}
OpCode::Test => {
let val = state.stack_get(ra);
let c = instr.c() != 0;
if val.is_truthy() == c {
let jump_instr = Instruction::from_raw(proto.code[pc]);
pc = ((pc as i64) + i64::from(jump_instr.sbx()) + 1) as usize;
} else {
pc += 1;
}
}
OpCode::TestSet => {
let b = instr.b() as usize;
let rb = state.stack_get(base + b);
let c = instr.c() != 0;
if rb.is_truthy() == c {
state.stack_set(ra, rb);
let jump_instr = Instruction::from_raw(proto.code[pc]);
pc = ((pc as i64) + i64::from(jump_instr.sbx()) + 1) as usize;
} else {
pc += 1;
}
}
OpCode::Jmp => {
let sbx = instr.sbx();
pc = ((pc as i64) + i64::from(sbx)) as usize;
}
OpCode::ForPrep => {
let init = state.stack_get(ra);
let limit = state.stack_get(ra + 1);
let step = state.stack_get(ra + 2);
let n_init = coerce_to_number(init, &state.gc).ok_or_else(|| {
runtime_error(&proto, pc, "'for' initial value must be a number")
})?;
let n_limit = coerce_to_number(limit, &state.gc)
.ok_or_else(|| runtime_error(&proto, pc, "'for' limit must be a number"))?;
let n_step = coerce_to_number(step, &state.gc)
.ok_or_else(|| runtime_error(&proto, pc, "'for' step must be a number"))?;
state.stack_set(ra, Val::Num(n_init - n_step));
state.stack_set(ra + 1, Val::Num(n_limit));
state.stack_set(ra + 2, Val::Num(n_step));
let sbx = instr.sbx();
pc = ((pc as i64) + i64::from(sbx)) as usize;
}
OpCode::ForLoop => {
let Val::Num(step) = state.stack_get(ra + 2) else {
return Err(runtime_error(&proto, pc, "'for' step is not a number"));
};
let Val::Num(idx) = state.stack_get(ra) else {
return Err(runtime_error(&proto, pc, "'for' index is not a number"));
};
let idx = idx + step;
let Val::Num(limit) = state.stack_get(ra + 1) else {
return Err(runtime_error(&proto, pc, "'for' limit is not a number"));
};
let continue_loop = if step > 0.0 {
idx <= limit
} else {
limit <= idx
};
if continue_loop {
let sbx = instr.sbx();
pc = ((pc as i64) + i64::from(sbx)) as usize;
state.stack_set(ra, Val::Num(idx)); state.stack_set(ra + 3, Val::Num(idx)); }
}
OpCode::TForLoop => {
let cb = ra + 3;
state.stack_set(cb + 2, state.stack_get(ra + 2));
state.stack_set(cb + 1, state.stack_get(ra + 1));
state.stack_set(cb, state.stack_get(ra));
state.top = cb + 3;
state.call_stack[state.ci].saved_pc = pc;
let c = instr.c() as i32;
state.call_depth += 1;
let tfor_result = (|| {
state.check_stack_overflow()?;
match state.precall(cb, c)? {
CallResult::Lua => execute(state),
CallResult::Rust => Ok(()),
}
})();
state.call_depth -= 1;
tfor_result?;
state.top = state.call_stack[state.ci].top;
let result_base = ra + 3;
let control = state.stack_get(result_base);
if !control.is_nil() {
state.stack_set(ra + 2, control);
let jump_instr = Instruction::from_raw(proto.code[pc]);
pc = ((pc as i64) + i64::from(jump_instr.sbx())) as usize;
}
pc += 1;
}
OpCode::Call => {
let b = instr.b();
let c = instr.c();
if b != 0 {
state.top = ra + b as usize;
}
let num_results = if c == 0 { LUA_MULTRET } else { c as i32 - 1 };
state.call_stack[state.ci].saved_pc = pc;
match state.precall(ra, num_results)? {
CallResult::Lua => {
nexeccalls += 1;
}
CallResult::Rust => {
if c != 0 {
state.top = state.call_stack[state.ci].top;
}
}
}
break;
}
OpCode::TailCall => {
let b = instr.b();
if b != 0 {
state.top = ra + b as usize;
}
state.call_stack[state.ci].saved_pc = pc;
match state.precall(ra, LUA_MULTRET)? {
CallResult::Lua => {
let prev_ci = state.ci - 1;
let old_func = state.call_stack[prev_ci].func;
let new_func = state.call_stack[state.ci].func;
state.close_upvalues(state.call_stack[prev_ci].base);
let base_offset = state.call_stack[state.ci].base - new_func;
state.call_stack[prev_ci].base = old_func + base_offset;
state.base = state.call_stack[prev_ci].base;
let mut aux = 0;
while new_func + aux < state.top {
state.stack_set(old_func + aux, state.stack_get(new_func + aux));
aux += 1;
}
state.top = old_func + aux;
state.call_stack[prev_ci].top = state.top;
state.call_stack[prev_ci].saved_pc =
state.call_stack[state.ci].saved_pc;
state.call_stack[prev_ci].tail_calls += 1;
state.pop_ci();
}
CallResult::Rust => {
base = state.call_stack[state.ci].base;
continue;
}
}
break; }
OpCode::Return => {
let b = instr.b();
let first_result = ra;
state.close_upvalues(base);
if b != 0 {
state.top = first_result + (b as usize) - 1;
}
let fixed_results = state.poscall(first_result);
nexeccalls -= 1;
if nexeccalls == 0 {
return Ok(());
}
if fixed_results {
state.top = state.call_stack[state.ci].top;
}
break; }
OpCode::GetUpval => {
let b = instr.b() as usize;
let cl = state
.gc
.closures
.get(closure_ref)
.ok_or_else(|| runtime_error_simple("invalid closure"))?;
if let Closure::Lua(lcl) = cl {
if b < lcl.upvalues.len() {
let uv_ref = lcl.upvalues[b];
let val = state
.gc
.upvalues
.get(uv_ref)
.map_or(Val::Nil, |uv| uv.get(&state.stack));
state.stack_set(ra, val);
} else {
state.stack_set(ra, Val::Nil);
}
}
}
OpCode::SetUpval => {
let b = instr.b() as usize;
let val = state.stack_get(ra);
let cl = state
.gc
.closures
.get(closure_ref)
.ok_or_else(|| runtime_error_simple("invalid closure"))?;
if let Closure::Lua(lcl) = cl
&& b < lcl.upvalues.len()
{
let uv_ref = lcl.upvalues[b];
let uv_color = state.gc.upvalues.color(uv_ref);
if let Some(uv) = state.gc.upvalues.get_mut(uv_ref) {
uv.set(&mut state.stack, val);
}
if let Some(color) = uv_color {
state.gc.barrier_forward_val(color, val);
}
}
}
OpCode::Closure => {
state.gc_check()?;
let bx = instr.bx() as usize;
let child_proto = Rc::clone(&proto.protos[bx]);
let nups = child_proto.num_upvalues as usize;
let mut new_cl = LuaClosure::new(child_proto, env);
for _ in 0..nups {
let pseudo = Instruction::from_raw(proto.code[pc]);
pc += 1;
match pseudo.opcode() {
OpCode::Move => {
let local_reg = pseudo.b() as usize;
let stack_slot = base + local_reg;
let uv_ref = state.find_upvalue(stack_slot);
new_cl.upvalues.push(uv_ref);
}
OpCode::GetUpval => {
let parent_uv_idx = pseudo.b() as usize;
let cl = state
.gc
.closures
.get(closure_ref)
.ok_or_else(|| runtime_error_simple("invalid closure"))?;
if let Closure::Lua(lcl) = cl
&& parent_uv_idx < lcl.upvalues.len()
{
new_cl.upvalues.push(lcl.upvalues[parent_uv_idx]);
}
}
_ => {
return Err(runtime_error(
&proto,
pc,
"invalid pseudo-instruction after CLOSURE",
));
}
}
}
let cl_ref = state.gc.alloc_closure(Closure::Lua(new_cl));
state.stack_set(ra, Val::Function(cl_ref));
}
OpCode::Close => {
state.close_upvalues(ra);
}
OpCode::VarArg => {
let b = instr.b() as i32;
let ci_func = state.call_stack[state.ci].func;
let vararg_start = ci_func + 1 + proto.num_params as usize;
let num_varargs = base.saturating_sub(vararg_start);
let wanted = if b == 0 {
num_varargs
} else {
(b - 1) as usize
};
state.ensure_stack(ra + wanted);
for i in 0..wanted {
if i < num_varargs {
let val = state.stack_get(vararg_start + i);
state.stack_set(ra + i, val);
} else {
state.stack_set(ra + i, Val::Nil);
}
}
if b == 0 {
state.top = ra + wanted;
}
}
OpCode::SetList => {
let mut n = instr.b() as usize;
let mut c = instr.c() as usize;
if n == 0 {
n = state.top - ra - 1;
state.top = state.call_stack[state.ci].top;
}
if c == 0 {
c = proto.code[pc] as usize;
pc += 1;
}
let Val::Table(table_ref) = state.stack_get(ra) else {
return Err(type_error(state, &proto, pc, base, a, "index"));
};
let offset = (c - 1) * LFIELDS_PER_FLUSH as usize;
let last = offset + n;
if let Some(table) = state.gc.tables.get_mut(table_ref) {
let mem_before = table.estimated_memory();
table.ensure_array_capacity(last);
let mem_after = table.estimated_memory();
if mem_after > mem_before {
state.gc.gc_state.total_bytes += mem_after - mem_before;
}
}
if state.gc.gc_state.total_bytes > state.gc.gc_state.alloc_limit {
return Err(crate::LuaError::Memory);
}
for i in 1..=n {
let val = state.stack_get(ra + i);
let key = Val::Num((offset + i) as f64);
table_set(state, table_ref, key, val)?;
}
}
}
}
}
}
#[allow(dead_code)] fn table_get(state: &LuaState, table_ref: GcRef<Table>, key: Val) -> LuaResult<Val> {
let table = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| runtime_error_simple("invalid table reference"))?;
Ok(table.get(key, &state.gc.string_arena))
}
fn table_set(state: &mut LuaState, table_ref: GcRef<Table>, key: Val, value: Val) -> LuaResult<()> {
let table = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| runtime_error_simple("invalid table reference"))?;
let mem_before = table.estimated_memory();
table.raw_set(key, value, &state.gc.string_arena)?;
let mem_after = table.estimated_memory();
if mem_after > mem_before {
state.gc.gc_state.total_bytes += mem_after - mem_before;
}
if state.gc.gc_state.total_bytes > state.gc.gc_state.alloc_limit {
return Err(crate::LuaError::Memory);
}
state.gc.barrier_back(table_ref);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn vm_gettable(
state: &mut LuaState,
t: Val,
key: Val,
result_reg: usize,
proto: &Proto,
pc: usize,
base: usize,
obj_reg: Option<usize>,
) -> LuaResult<()> {
let mut current = t;
for _ in 0..MAXTAGLOOP {
if let Val::Table(table_ref) = current {
let table = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| runtime_error_simple("invalid table reference"))?;
let result = table.get(key, &state.gc.string_arena);
if !result.is_nil() {
state.stack_set(result_reg, result);
return Ok(());
}
let tm = {
let mt = table.metatable();
match mt {
Some(mt_ref) => get_tm_for_table(&state.gc, mt_ref, TMS::Index),
None => None,
}
};
match tm {
None => {
state.stack_set(result_reg, Val::Nil);
return Ok(());
}
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
call_tm_res(state, tm_val, current, key, result_reg)?;
return Ok(());
}
Some(tm_val) => {
current = tm_val;
}
}
} else {
let tm = get_tm_for_val(&state.gc, current, TMS::Index);
match tm {
None => {
if let Some(reg) = obj_reg {
return Err(type_error(state, proto, pc, base, reg, "index"));
}
return Err(runtime_error(
proto,
pc,
&format!("attempt to index a {} value", current.type_name()),
));
}
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
call_tm_res(state, tm_val, current, key, result_reg)?;
return Ok(());
}
Some(tm_val) => {
current = tm_val;
}
}
}
}
Err(runtime_error_simple("loop in gettable"))
}
#[allow(clippy::too_many_arguments)]
fn vm_settable(
state: &mut LuaState,
t: Val,
key: Val,
value: Val,
proto: &Proto,
pc: usize,
base: usize,
obj_reg: Option<usize>,
) -> LuaResult<()> {
let mut current = t;
for _ in 0..MAXTAGLOOP {
if let Val::Table(table_ref) = current {
let existing = {
let table = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| runtime_error_simple("invalid table reference"))?;
table.get(key, &state.gc.string_arena)
};
if !existing.is_nil() {
let table = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| runtime_error_simple("invalid table reference"))?;
table.raw_set(key, value, &state.gc.string_arena)?;
state.gc.barrier_back(table_ref);
return Ok(());
}
let tm = {
let table = state
.gc
.tables
.get(table_ref)
.ok_or_else(|| runtime_error_simple("invalid table reference"))?;
let mt = table.metatable();
match mt {
Some(mt_ref) => get_tm_for_table(&state.gc, mt_ref, TMS::NewIndex),
None => None,
}
};
match tm {
None => {
let table = state
.gc
.tables
.get_mut(table_ref)
.ok_or_else(|| runtime_error_simple("invalid table reference"))?;
let mem_before = table.estimated_memory();
table.raw_set(key, value, &state.gc.string_arena)?;
let mem_after = table.estimated_memory();
if mem_after > mem_before {
state.gc.gc_state.total_bytes += mem_after - mem_before;
}
if state.gc.gc_state.total_bytes > state.gc.gc_state.alloc_limit {
return Err(crate::LuaError::Memory);
}
state.gc.barrier_back(table_ref);
return Ok(());
}
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
call_tm_void(state, tm_val, current, key, value)?;
return Ok(());
}
Some(tm_val) => {
current = tm_val;
}
}
} else {
let tm = get_tm_for_val(&state.gc, current, TMS::NewIndex);
match tm {
None => {
if let Some(reg) = obj_reg {
return Err(type_error(state, proto, pc, base, reg, "index"));
}
return Err(runtime_error(
proto,
pc,
&format!("attempt to index a {} value", current.type_name()),
));
}
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
call_tm_void(state, tm_val, current, key, value)?;
return Ok(());
}
Some(tm_val) => {
current = tm_val;
}
}
}
}
Err(runtime_error_simple("loop in settable"))
}
fn get_tm_for_table(gc: &Gc, mt_ref: GcRef<Table>, event: TMS) -> Option<Val> {
use super::metatable::fasttm;
fasttm(&gc.tables, &gc.string_arena, mt_ref, event, &gc.tm_names)
}
fn val_equal(a: Val, b: Val, gc: &Gc) -> bool {
val_raw_equal(a, b, &gc.tables, &gc.string_arena)
}
#[allow(unsafe_code)]
pub(crate) fn l_strcmp(left: &[u8], right: &[u8]) -> std::cmp::Ordering {
let mut l = left;
let mut r = right;
loop {
let l_nul = l.iter().position(|&b| b == 0).unwrap_or(l.len());
let r_nul = r.iter().position(|&b| b == 0).unwrap_or(r.len());
let mut l_buf = Vec::with_capacity(l_nul + 1);
l_buf.extend_from_slice(&l[..l_nul]);
l_buf.push(0);
let mut r_buf = Vec::with_capacity(r_nul + 1);
r_buf.extend_from_slice(&r[..r_nul]);
r_buf.push(0);
let temp = unsafe { strcoll(l_buf.as_ptr(), r_buf.as_ptr()) };
if temp != 0 {
return if temp < 0 {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
};
}
if r_nul >= r.len() {
return if l_nul >= l.len() {
std::cmp::Ordering::Equal
} else {
std::cmp::Ordering::Greater
};
} else if l_nul >= l.len() {
return std::cmp::Ordering::Less;
}
let skip = l_nul + 1;
l = &l[skip..];
r = &r[skip..];
}
}
fn val_less_than(
a: Val,
b: Val,
state: &mut LuaState,
proto: &Proto,
pc: usize,
) -> LuaResult<bool> {
match (&a, &b) {
(Val::Num(x), Val::Num(y)) => Ok(x < y),
(Val::Str(x), Val::Str(y)) => {
let sx = state
.gc
.string_arena
.get(*x)
.ok_or_else(|| compare_error(proto, pc, a, b))?;
let sy = state
.gc
.string_arena
.get(*y)
.ok_or_else(|| compare_error(proto, pc, a, b))?;
Ok(l_strcmp(sx.data(), sy.data()) == std::cmp::Ordering::Less)
}
_ => {
if std::mem::discriminant(&a) != std::mem::discriminant(&b) {
return Err(compare_error(proto, pc, a, b));
}
match call_order_tm(state, a, b, TMS::Lt)? {
Some(result) => Ok(result),
None => Err(compare_error(proto, pc, a, b)),
}
}
}
}
fn val_less_equal(
a: Val,
b: Val,
state: &mut LuaState,
proto: &Proto,
pc: usize,
) -> LuaResult<bool> {
match (&a, &b) {
(Val::Num(x), Val::Num(y)) => Ok(x <= y),
(Val::Str(x), Val::Str(y)) => {
let sx = state
.gc
.string_arena
.get(*x)
.ok_or_else(|| compare_error(proto, pc, a, b))?;
let sy = state
.gc
.string_arena
.get(*y)
.ok_or_else(|| compare_error(proto, pc, a, b))?;
Ok(l_strcmp(sx.data(), sy.data()) != std::cmp::Ordering::Greater)
}
_ => {
if std::mem::discriminant(&a) != std::mem::discriminant(&b) {
return Err(compare_error(proto, pc, a, b));
}
if let Some(result) = call_order_tm(state, a, b, TMS::Le)? {
return Ok(result);
}
if let Some(result) = call_order_tm(state, b, a, TMS::Lt)? {
return Ok(!result);
}
Err(compare_error(proto, pc, a, b))
}
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::float_cmp,
clippy::approx_constant
)]
mod tests {
use super::*;
use crate::vm::instructions::{Instruction, OpCode, rk_as_k};
fn setup_state(proto: Proto) -> LuaState {
let mut state = LuaState::new();
let proto_rc = Rc::new(proto);
let env = state.global;
let cl = LuaClosure::new(proto_rc, env);
let cl_ref = state.gc.alloc_closure(Closure::Lua(cl));
state.stack_set(0, Val::Function(cl_ref));
state.base = 1;
state.top = 1;
state.call_stack[0] = CallInfo::new(0, 1, 41, LUA_MULTRET);
state
}
fn make_proto(code: Vec<u32>, constants: Vec<Val>) -> Proto {
let mut p = Proto::new("test");
let n = code.len();
p.code = code;
p.constants = constants;
p.line_info = vec![1; n];
p.max_stack_size = 20;
p.is_vararg = VARARG_ISVARARG;
p
}
#[test]
fn op_move() {
let code = vec![
Instruction::a_bx(OpCode::LoadK, 0, 0).raw(), Instruction::abc(OpCode::Move, 1, 0, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(42.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base + 1), Val::Num(42.0));
}
#[test]
fn op_loadk() {
let code = vec![
Instruction::a_bx(OpCode::LoadK, 0, 0).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(3.14)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Num(3.14));
}
#[test]
fn op_loadbool() {
let code = vec![
Instruction::abc(OpCode::LoadBool, 0, 1, 0).raw(), Instruction::abc(OpCode::LoadBool, 1, 0, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let mut state = setup_state(make_proto(code, vec![]));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Bool(true));
assert_eq!(state.stack_get(state.base + 1), Val::Bool(false));
}
#[test]
fn op_loadbool_skip() {
let code = vec![
Instruction::abc(OpCode::LoadBool, 0, 1, 1).raw(), Instruction::abc(OpCode::LoadBool, 0, 0, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let mut state = setup_state(make_proto(code, vec![]));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Bool(true));
}
#[test]
fn op_loadnil() {
let code = vec![
Instruction::a_bx(OpCode::LoadK, 0, 0).raw(), Instruction::a_bx(OpCode::LoadK, 1, 0).raw(), Instruction::a_bx(OpCode::LoadK, 2, 0).raw(), Instruction::abc(OpCode::LoadNil, 0, 2, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(1.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert!(state.stack_get(state.base).is_nil());
assert!(state.stack_get(state.base + 1).is_nil());
assert!(state.stack_get(state.base + 2).is_nil());
}
#[test]
fn op_add() {
let code = vec![
Instruction::abc(OpCode::Add, 0, rk_as_k(0), rk_as_k(1)).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(10.0), Val::Num(20.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Num(30.0));
}
#[test]
fn op_sub() {
let code = vec![
Instruction::abc(OpCode::Sub, 0, rk_as_k(0), rk_as_k(1)).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(50.0), Val::Num(30.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Num(20.0));
}
#[test]
fn op_mul() {
let code = vec![
Instruction::abc(OpCode::Mul, 0, rk_as_k(0), rk_as_k(1)).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(6.0), Val::Num(7.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Num(42.0));
}
#[test]
fn op_div() {
let code = vec![
Instruction::abc(OpCode::Div, 0, rk_as_k(0), rk_as_k(1)).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(10.0), Val::Num(4.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Num(2.5));
}
#[test]
fn op_mod() {
let code = vec![
Instruction::abc(OpCode::Mod, 0, rk_as_k(0), rk_as_k(1)).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(10.0), Val::Num(3.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Num(1.0));
}
#[test]
fn op_pow() {
let code = vec![
Instruction::abc(OpCode::Pow, 0, rk_as_k(0), rk_as_k(1)).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(2.0), Val::Num(10.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Num(1024.0));
}
#[test]
fn op_unm() {
let code = vec![
Instruction::a_bx(OpCode::LoadK, 0, 0).raw(),
Instruction::abc(OpCode::Unm, 1, 0, 0).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(42.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base + 1), Val::Num(-42.0));
}
#[test]
fn op_not() {
let code = vec![
Instruction::abc(OpCode::LoadBool, 0, 1, 0).raw(), Instruction::abc(OpCode::Not, 1, 0, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let mut state = setup_state(make_proto(code, vec![]));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base + 1), Val::Bool(false));
}
#[test]
fn op_eq_numbers_equal() {
let code = vec![
Instruction::abc(OpCode::Eq, 1, rk_as_k(0), rk_as_k(0)).raw(), Instruction::a_sbx(OpCode::Jmp, 0, 1).raw(), Instruction::a_sbx(OpCode::Jmp, 0, 1).raw(), Instruction::abc(OpCode::LoadBool, 0, 1, 1).raw(), Instruction::abc(OpCode::LoadBool, 0, 0, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(1.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Bool(true));
}
#[test]
fn op_jmp() {
let code = vec![
Instruction::a_sbx(OpCode::Jmp, 0, 1).raw(), Instruction::abc(OpCode::LoadBool, 0, 0, 0).raw(), Instruction::abc(OpCode::LoadBool, 0, 1, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let mut state = setup_state(make_proto(code, vec![]));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base), Val::Bool(true));
}
#[test]
fn op_forloop() {
let code = vec![
Instruction::a_bx(OpCode::LoadK, 0, 0).raw(), Instruction::a_bx(OpCode::LoadK, 1, 1).raw(), Instruction::a_bx(OpCode::LoadK, 2, 0).raw(), Instruction::a_sbx(OpCode::ForPrep, 0, 0).raw(), Instruction::a_sbx(OpCode::ForLoop, 0, -1).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Num(1.0), Val::Num(3.0)];
let mut state = setup_state(make_proto(code, constants));
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base + 3), Val::Num(3.0));
}
#[test]
fn op_newtable_and_settable_gettable() {
let mut state = LuaState::new();
let key_ref = state.gc.intern_string(b"x");
let code = vec![
Instruction::abc(OpCode::NewTable, 0, 0, 0).raw(), Instruction::abc(OpCode::SetTable, 0, rk_as_k(0), rk_as_k(1)).raw(), Instruction::abc(OpCode::GetTable, 1, 0, rk_as_k(0)).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Str(key_ref), Val::Num(42.0)];
let proto_rc = Rc::new(make_proto(code, constants));
let env = state.global;
let cl = LuaClosure::new(proto_rc, env);
let cl_ref = state.gc.alloc_closure(Closure::Lua(cl));
state.stack_set(0, Val::Function(cl_ref));
state.base = 1;
state.top = 1;
state.call_stack[0] = CallInfo::new(0, 1, 41, LUA_MULTRET);
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base + 1), Val::Num(42.0));
}
#[test]
fn op_setglobal_getglobal() {
let mut state = LuaState::new();
let key_ref = state.gc.intern_string(b"myvar");
let code = vec![
Instruction::a_bx(OpCode::LoadK, 0, 1).raw(), Instruction::a_bx(OpCode::SetGlobal, 0, 0).raw(), Instruction::a_bx(OpCode::GetGlobal, 1, 0).raw(), Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Str(key_ref), Val::Num(99.0)];
let proto_rc = Rc::new(make_proto(code, constants));
let env = state.global;
let cl = LuaClosure::new(proto_rc, env);
let cl_ref = state.gc.alloc_closure(Closure::Lua(cl));
state.stack_set(0, Val::Function(cl_ref));
state.base = 1;
state.top = 1;
state.call_stack[0] = CallInfo::new(0, 1, 41, LUA_MULTRET);
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base + 1), Val::Num(99.0));
}
#[test]
fn op_len_string() {
let mut state = LuaState::new();
let s_ref = state.gc.intern_string(b"hello");
let code = vec![
Instruction::a_bx(OpCode::LoadK, 0, 0).raw(),
Instruction::abc(OpCode::Len, 1, 0, 0).raw(),
Instruction::abc(OpCode::Return, 0, 1, 0).raw(),
];
let constants = vec![Val::Str(s_ref)];
let proto_rc = Rc::new(make_proto(code, constants));
let env = state.global;
let cl = LuaClosure::new(proto_rc, env);
let cl_ref = state.gc.alloc_closure(Closure::Lua(cl));
state.stack_set(0, Val::Function(cl_ref));
state.base = 1;
state.top = 1;
state.call_stack[0] = CallInfo::new(0, 1, 41, LUA_MULTRET);
execute(&mut state).ok();
assert_eq!(state.stack_get(state.base + 1), Val::Num(5.0));
}
#[test]
fn str_to_number_basic() {
assert_eq!(str_to_number(b"42"), Some(42.0));
assert_eq!(str_to_number(b"3.14"), Some(3.14));
assert_eq!(str_to_number(b" 42 "), Some(42.0));
assert_eq!(str_to_number(b""), None);
assert_eq!(str_to_number(b"abc"), None);
}
#[test]
fn str_to_number_hex() {
assert_eq!(str_to_number(b"0xff"), Some(255.0));
assert_eq!(str_to_number(b"0xFF"), Some(255.0));
assert_eq!(str_to_number(b"0x10"), Some(16.0));
}
#[test]
fn fb2int_values() {
assert_eq!(fb2int(0), 0);
assert_eq!(fb2int(1), 1);
assert_eq!(fb2int(7), 7); assert_eq!(fb2int(8), 8); }
#[test]
fn val_equal_basics() {
let gc = Gc::default();
assert!(val_equal(Val::Nil, Val::Nil, &gc));
assert!(!val_equal(Val::Nil, Val::Bool(false), &gc));
assert!(val_equal(Val::Num(1.0), Val::Num(1.0), &gc));
assert!(!val_equal(Val::Num(1.0), Val::Num(2.0), &gc));
assert!(val_equal(Val::Bool(true), Val::Bool(true), &gc));
}
}