use crate::LuaValue;
use crate::compiler::expression::{ExpDesc, ExpKind};
use crate::compiler::func_state::FuncState;
use crate::compiler::parser::BinaryOperator;
use crate::compiler::{ExpUnion, IndVars};
use crate::lua_value::LuaValueKind;
use crate::lua_vm::lua_limits::{MAXINDEXRK, NO_REG};
use crate::lua_vm::{Instruction, OpCode, TmKind};
fn int2sc(i: i32) -> u32 {
((i as u32).wrapping_add(127)) & 0xFF
}
fn fits_c(i: i64) -> bool {
let offset_sc = 127i64;
let max_arg_c = 255u32; (i.wrapping_add(offset_sc) as u64) <= (max_arg_c as u64)
}
fn is_scnumber(e: &ExpDesc, pi: &mut i32, isfloat: &mut bool) -> bool {
let i = match e.kind {
ExpKind::VKINT => e.u.ival(),
ExpKind::VKFLT => {
let fval = e.u.nval();
let ival = fval as i64;
if (ival as f64) == fval {
*isfloat = true;
ival
} else {
return false; }
}
_ => return false, };
if !e.has_jumps() && fits_c(i) {
*pi = int2sc(i as i32) as i32;
true
} else {
false
}
}
pub fn const_to_exp(value: LuaValue, e: &mut ExpDesc) {
match value.kind() {
LuaValueKind::Integer => {
e.kind = ExpKind::VKINT;
e.u = ExpUnion::IVal(value.as_integer().unwrap_or(0));
}
LuaValueKind::Float => {
e.kind = ExpKind::VKFLT;
e.u = ExpUnion::NVal(value.as_float().unwrap_or(0.0));
}
LuaValueKind::Boolean => {
if value.as_boolean().unwrap_or(false) {
e.kind = ExpKind::VTRUE;
} else {
e.kind = ExpKind::VFALSE;
}
}
LuaValueKind::Nil => {
e.kind = ExpKind::VNIL;
}
LuaValueKind::String => {
e.kind = ExpKind::VKSTR;
e.u = ExpUnion::Str(value);
}
_ => {
e.kind = ExpKind::VNIL;
}
}
}
pub fn code_abc(fs: &mut FuncState, op: OpCode, a: u32, b: u32, c: u32) -> usize {
let instr = Instruction::create_abc(op, a, b, c);
let pc = fs.pc;
fs.chunk.code.push(instr);
fs.chunk.line_info.push(fs.lexer.lastline as u32); fs.pc += 1;
pc
}
pub fn code_abx(fs: &mut FuncState, op: OpCode, a: u32, bx: u32) -> usize {
let instr = Instruction::create_abx(op, a, bx);
let pc = fs.pc;
fs.chunk.code.push(instr);
fs.chunk.line_info.push(fs.lexer.lastline as u32); fs.pc += 1;
pc
}
pub fn code_asbx(fs: &mut FuncState, op: OpCode, a: u32, sbx: i32) -> usize {
let instr = Instruction::create_asbx(op, a, sbx);
let pc = fs.pc;
fs.chunk.code.push(instr);
fs.chunk.line_info.push(fs.lexer.lastline as u32); fs.pc += 1;
pc
}
pub fn code_abck(fs: &mut FuncState, op: OpCode, a: u32, b: u32, c: u32, k: bool) -> usize {
let instr = Instruction::create_abck(op, a, b, c, k);
let pc = fs.pc;
fs.chunk.code.push(instr);
fs.chunk.line_info.push(fs.lexer.lastline as u32); fs.pc += 1;
pc
}
pub fn code_vabck(fs: &mut FuncState, op: OpCode, a: u32, b: u32, c: u32, k: bool) -> usize {
let instr = Instruction::create_vabck(op, a, b, c, k);
let pc = fs.pc;
fs.chunk.code.push(instr);
fs.chunk.line_info.push(fs.lexer.lastline as u32);
fs.pc += 1;
pc
}
pub fn code_asj(fs: &mut FuncState, op: OpCode, sj: i32) -> usize {
let instr = Instruction::create_sj(op, sj);
let pc = fs.pc;
fs.chunk.code.push(instr);
fs.chunk.line_info.push(fs.lexer.lastline as u32); fs.pc += 1;
pc
}
pub fn ret(fs: &mut FuncState, first: u8, nret: u8) -> usize {
let op = match nret {
0 => OpCode::Return0,
1 => OpCode::Return1,
_ => OpCode::Return,
};
code_abc(fs, op, first as u32, nret.wrapping_add(1) as u32, 0)
}
pub fn finish(fs: &mut FuncState) {
let needclose = fs.needclose;
let needs_vararg_table = fs.chunk.needs_vararg_table;
let num_params = fs.chunk.param_count;
if needs_vararg_table {
fs.chunk.use_hidden_vararg = false;
}
let use_hidden_vararg = fs.chunk.use_hidden_vararg;
for i in 0..fs.pc {
let instr = &mut fs.chunk.code[i];
let opcode = instr.get_opcode();
match opcode {
OpCode::Return0 | OpCode::Return1 => {
if needclose || use_hidden_vararg {
let a = instr.get_a();
let b = if opcode == OpCode::Return0 { 1 } else { 2 };
let mut new_instr = Instruction::create_abck(OpCode::Return, a, b, 0, false);
if needclose {
new_instr.set_k(true);
}
if use_hidden_vararg {
new_instr.set_c((num_params + 1) as u32);
}
*instr = new_instr;
}
}
OpCode::Return | OpCode::TailCall => {
if needclose {
instr.set_k(true);
}
if use_hidden_vararg {
instr.set_c((num_params + 1) as u32);
}
}
OpCode::GetVarg => {
if needs_vararg_table {
let pc = &mut fs.chunk.code[i];
pc.set_opcode(OpCode::GetTable);
}
}
OpCode::Vararg => {
if needs_vararg_table {
let pc = &mut fs.chunk.code[i];
pc.set_k(true);
}
}
OpCode::Jmp => {
let target = finaltarget(&fs.chunk.code, i);
fixjump_at(fs, i, target);
}
_ => {}
}
}
}
fn finaltarget(code: &[Instruction], mut pc: usize) -> usize {
let mut count = 0;
while count < 100 {
if pc >= code.len() {
break;
}
let instr = code[pc];
if instr.get_opcode() != OpCode::Jmp {
break;
}
let offset = instr.get_sj() as isize;
if offset == -1 {
break;
}
let next_pc = (pc as isize) + 1 + offset;
if next_pc < 0 || next_pc >= code.len() as isize {
break;
}
pc = next_pc as usize;
count += 1;
}
pc
}
fn fixjump_at(fs: &mut FuncState, pc: usize, target: usize) {
let offset = (target as isize) - (pc as isize) - 1;
if offset < i32::MIN as isize || offset > i32::MAX as isize {
return;
}
let instr = &mut fs.chunk.code[pc];
instr.set_sj(offset as i32);
}
pub fn jump(fs: &mut FuncState) -> usize {
code_asj(fs, OpCode::Jmp, -1)
}
pub fn jumpto(fs: &mut FuncState, target: usize) {
let jmp = jump(fs);
patchlist(fs, jmp as isize, target as isize);
}
pub fn get_label(fs: &mut FuncState) -> usize {
fs.last_target = fs.pc;
fs.pc
}
pub fn patchtohere(fs: &mut FuncState, list: isize) {
let here = get_label(fs) as isize;
patchlist(fs, list, here);
}
pub fn concat(fs: &mut FuncState, l1: &mut isize, l2: isize) {
if l2 == -1 {
return;
}
if *l1 == -1 {
*l1 = l2;
} else {
let mut list = *l1;
let mut next = get_jump(fs, list as usize);
while next != -1 {
list = next;
next = get_jump(fs, list as usize);
}
fix_jump(fs, list as usize, l2 as usize);
}
}
pub fn getlabel(fs: &mut FuncState) -> usize {
fs.last_target = fs.pc;
fs.pc
}
pub fn patchlist(fs: &mut FuncState, list: isize, target: isize) {
if target < 0 {
return;
}
patchlistaux(fs, list, target, NO_REG as u8, target);
}
fn get_jump(fs: &FuncState, pc: usize) -> isize {
if pc >= fs.chunk.code.len() {
return -1;
}
let instr = fs.chunk.code[pc];
let offset = instr.get_sj();
if offset == -1 {
-1
} else {
let target = (pc as isize) + 1 + (offset as isize);
if target < 0 { -1 } else { target }
}
}
pub fn fix_jump(fs: &mut FuncState, pc: usize, target: usize) {
if pc >= fs.chunk.code.len() {
return;
}
let offset = (target as isize) - (pc as isize) - 1;
let max_sj = (Instruction::MAX_SJ >> 1) as isize;
if offset < -max_sj || offset > max_sj {
return;
}
let instr = &mut fs.chunk.code[pc];
instr.set_sj(offset as i32);
}
fn need_value(fs: &FuncState, mut list: isize) -> bool {
const NO_JUMP: isize = -1;
while list != NO_JUMP {
let control_pc = get_jump_control(fs, list as usize);
let i = fs.chunk.code[control_pc];
let op = i.get_opcode();
if op != OpCode::TestSet {
return true;
}
list = get_jump(fs, list as usize);
}
false
}
fn code_loadbool(fs: &mut FuncState, a: u8, op: OpCode) -> usize {
get_label(fs); code_abc(fs, op, a as u32, 0, 0)
}
fn patchtestreg(fs: &mut FuncState, node: usize, reg: u8) -> bool {
let pc = get_jump_control(fs, node);
if pc >= fs.chunk.code.len() {
return false;
}
let instr = fs.chunk.code[pc];
if Instruction::get_opcode(instr) != OpCode::TestSet {
return false; }
let b = Instruction::get_b(instr);
if reg != NO_REG as u8 && reg != b as u8 {
Instruction::set_a(&mut fs.chunk.code[pc], reg as u32);
} else {
let k = Instruction::get_k(instr);
fs.chunk.code[pc] = Instruction::create_abck(OpCode::Test, b, 0, 0, k);
}
true
}
fn patchlistaux(fs: &mut FuncState, mut list: isize, vtarget: isize, reg: u8, dtarget: isize) {
const NO_JUMP: isize = -1;
while list != NO_JUMP {
let next = get_jump(fs, list as usize);
if patchtestreg(fs, list as usize, reg) {
fix_jump(fs, list as usize, vtarget as usize);
} else {
fix_jump(fs, list as usize, dtarget as usize);
}
list = next;
}
}
pub fn exp2nextreg(fs: &mut FuncState, e: &mut ExpDesc) -> u8 {
discharge_vars(fs, e);
free_exp(fs, e);
reserve_regs(fs, 1);
let reg = fs.freereg - 1;
exp2reg(fs, e, reg);
reg
}
pub fn exp2anyreg(fs: &mut FuncState, e: &mut ExpDesc) -> u8 {
discharge_vars(fs, e);
if e.kind == ExpKind::VNONRELOC {
if !e.has_jumps() {
return e.u.info() as u8;
}
if e.u.info() >= fs.nactvar as i32 {
exp2reg(fs, e, e.u.info() as u8);
return e.u.info() as u8;
}
}
exp2nextreg(fs, e)
}
pub fn exp2reg(fs: &mut FuncState, e: &mut ExpDesc, reg: u8) {
let was_vjmp = e.kind == ExpKind::VJMP;
let vjmp_info = if was_vjmp { e.u.info() as isize } else { -1 };
discharge2reg(fs, e, reg);
if was_vjmp {
concat(fs, &mut e.t, vjmp_info);
}
if e.has_jumps() {
let mut p_f = -1_isize; let mut p_t = -1_isize;
if need_value(fs, e.t) || need_value(fs, e.f) {
let fj = if was_vjmp { -1 } else { jump(fs) as isize };
p_f = code_loadbool(fs, reg, OpCode::LFalseSkip) as isize;
p_t = code_loadbool(fs, reg, OpCode::LoadTrue) as isize;
patchtohere(fs, fj);
}
let final_label = get_label(fs) as isize;
patchlistaux(fs, e.f, final_label, reg, p_f);
patchlistaux(fs, e.t, final_label, reg, p_t);
}
e.f = -1;
e.t = -1;
e.u = ExpUnion::Info(reg as i32);
e.kind = ExpKind::VNONRELOC;
}
pub fn vapar_to_local(fs: &mut FuncState, var: &mut ExpDesc) {
fs.chunk.needs_vararg_table = true;
var.kind = ExpKind::VLOCAL;
}
pub fn discharge_vars(fs: &mut FuncState, e: &mut ExpDesc) {
match e.kind {
ExpKind::VCONST => {
let vidx = e.u.info() as usize;
if let Some(var_desc) = fs.actvar.get(vidx)
&& let Some(value) = var_desc.const_value
{
const_to_exp(value, e);
}
}
ExpKind::VVARGVAR => {
vapar_to_local(fs, e);
e.u = ExpUnion::Info(e.u.var().ridx as i32);
e.kind = ExpKind::VNONRELOC;
}
ExpKind::VLOCAL => {
e.u = ExpUnion::Info(e.u.var().ridx as i32);
e.kind = ExpKind::VNONRELOC;
}
ExpKind::VUPVAL => {
e.u = ExpUnion::Info(code_abc(fs, OpCode::GetUpval, 0, e.u.info() as u32, 0) as i32);
e.kind = ExpKind::VRELOC;
}
ExpKind::VINDEXUP => {
e.u = ExpUnion::Info(code_abc(
fs,
OpCode::GetTabUp,
0,
e.u.ind().t as u32,
e.u.ind().idx as u32,
) as i32);
e.kind = ExpKind::VRELOC;
}
ExpKind::VINDEXI => {
free_reg(fs, e.u.ind().t as u8);
e.u = ExpUnion::Info(code_abc(
fs,
OpCode::GetI,
0,
e.u.ind().t as u32,
e.u.ind().idx as u32,
) as i32);
e.kind = ExpKind::VRELOC;
}
ExpKind::VINDEXSTR => {
free_reg(fs, e.u.ind().t as u8);
e.u = ExpUnion::Info(code_abc(
fs,
OpCode::GetField,
0,
e.u.ind().t as u32,
e.u.ind().idx as u32,
) as i32);
e.kind = ExpKind::VRELOC;
}
ExpKind::VINDEXED => {
free_regs(fs, e.u.ind().t as u8, e.u.ind().idx as u8);
e.u = ExpUnion::Info(code_abc(
fs,
OpCode::GetTable,
0,
e.u.ind().t as u32,
e.u.ind().idx as u32,
) as i32);
e.kind = ExpKind::VRELOC;
}
ExpKind::VVARGIND => {
free_regs(fs, e.u.ind().t as u8, e.u.ind().idx as u8);
e.u = ExpUnion::Info(code_abc(
fs,
OpCode::GetVarg,
0,
e.u.ind().t as u32,
e.u.ind().idx as u32,
) as i32);
e.kind = ExpKind::VRELOC;
}
ExpKind::VVARARG | ExpKind::VCALL => {
setoneret(fs, e);
}
_ => {}
}
}
pub fn discharge2reg(fs: &mut FuncState, e: &mut ExpDesc, reg: u8) {
discharge_vars(fs, e);
match e.kind {
ExpKind::VNIL => {
nil(fs, reg, 1); }
ExpKind::VFALSE => {
code_abc(fs, OpCode::LoadFalse, reg as u32, 0, 0);
}
ExpKind::VTRUE => {
code_abc(fs, OpCode::LoadTrue, reg as u32, 0, 0);
}
ExpKind::VKSTR => {
str2k(fs, e);
code_k(fs, reg as u32, e.u.info() as u32);
}
ExpKind::VK => {
code_k(fs, reg as u32, e.u.info() as u32);
}
ExpKind::VKFLT => {
let val = e.u.nval();
if val.fract() == 0.0 && val.is_finite() {
let int_val = val as i64;
let max_sbx = (Instruction::MAX_BX - (Instruction::OFFSET_SBX as u32)) as i64;
let min_sbx = -(Instruction::OFFSET_SBX as i64);
if int_val >= min_sbx && int_val <= max_sbx {
code_asbx(fs, OpCode::LoadF, reg as u32, int_val as i32);
} else {
let k_idx = number_k(fs, val);
code_k(fs, reg as u32, k_idx as u32);
}
} else {
let k_idx = number_k(fs, val);
code_k(fs, reg as u32, k_idx as u32);
}
}
ExpKind::VKINT => {
let ival = e.u.ival();
let max_sbx = (Instruction::MAX_BX - (Instruction::OFFSET_SBX as u32)) as i64;
let min_sbx = -(Instruction::OFFSET_SBX as i64);
if ival >= min_sbx && ival <= max_sbx {
code_asbx(fs, OpCode::LoadI, reg as u32, ival as i32);
} else {
let k_idx = integer_k(fs, ival);
code_k(fs, reg as u32, k_idx as u32);
}
}
ExpKind::VNONRELOC => {
if e.u.info() != reg as i32 {
code_abc(fs, OpCode::Move, reg as u32, e.u.info() as u32, 0);
}
}
ExpKind::VVARGVAR => {
let vreg = e.u.var().ridx;
if vreg as u32 != reg as u32 {
code_abc(fs, OpCode::Move, reg as u32, vreg as u32, 0);
}
}
ExpKind::VRELOC => {
let pc = e.u.info() as usize;
Instruction::set_a(&mut fs.chunk.code[pc], reg as u32);
}
_ => {}
}
e.kind = ExpKind::VNONRELOC;
e.u = ExpUnion::Info(reg as i32);
}
pub fn free_exp(fs: &mut FuncState, e: &ExpDesc) {
if e.kind == ExpKind::VNONRELOC {
free_reg(fs, e.u.info() as u8);
}
}
fn free_exps(fs: &mut FuncState, e1: &ExpDesc, e2: &ExpDesc) {
let r1 = if e1.kind == ExpKind::VNONRELOC {
e1.u.info()
} else {
-1
};
let r2 = if e2.kind == ExpKind::VNONRELOC {
e2.u.info()
} else {
-1
};
if r1 > r2 {
if r1 >= 0 {
free_reg(fs, r1 as u8);
}
if r2 >= 0 {
free_reg(fs, r2 as u8);
}
} else {
if r2 >= 0 {
free_reg(fs, r2 as u8);
}
if r1 >= 0 {
free_reg(fs, r1 as u8);
}
}
}
pub fn free_reg(fs: &mut FuncState, reg: u8) {
let nvars = fs.nvarstack();
if reg >= nvars && reg < fs.freereg {
fs.freereg -= 1;
debug_assert_eq!(
reg, fs.freereg,
"freereg mismatch: expected reg {} to equal freereg {}",
reg, fs.freereg
);
}
}
pub fn free_regs(fs: &mut FuncState, r1: u8, r2: u8) {
if r1 > r2 {
free_reg(fs, r1);
free_reg(fs, r2);
} else {
free_reg(fs, r2);
free_reg(fs, r1);
}
}
pub fn checkstack(fs: &mut FuncState, n: u8) {
let newstack = (fs.freereg as usize) + (n as usize);
if newstack > MAXINDEXRK {
if fs.checklimit_error.is_none() {
fs.checklimit_error = Some(fs.errorlimit(MAXINDEXRK, "registers"));
}
}
if newstack > fs.chunk.max_stack_size {
fs.chunk.max_stack_size = newstack;
}
}
pub fn reserve_regs(fs: &mut FuncState, n: u8) {
checkstack(fs, n);
fs.freereg = fs.freereg.saturating_add(n);
}
pub fn nil(fs: &mut FuncState, from: u8, n: u8) {
if n == 0 {
return;
}
let pc = fs.pc;
if pc > 0 && pc > fs.last_target {
let prev_pc = pc - 1;
let prev_instr = fs.chunk.code[prev_pc];
if Instruction::get_opcode(prev_instr) == OpCode::LoadNil {
let pfrom = Instruction::get_a(prev_instr) as u8;
let pl = pfrom + Instruction::get_b(prev_instr) as u8; let l = from + n - 1;
if (pfrom <= from && from <= pl + 1) || (from <= pfrom && pfrom <= l + 1) {
let new_from = pfrom.min(from);
let new_l = pl.max(l);
let new_b = new_l - new_from;
Instruction::set_a(&mut fs.chunk.code[prev_pc], new_from as u32);
Instruction::set_b(&mut fs.chunk.code[prev_pc], new_b as u32);
return;
}
}
}
code_abc(fs, OpCode::LoadNil, from as u32, (n - 1) as u32, 0);
}
pub fn setoneret(fs: &mut FuncState, e: &mut ExpDesc) {
if e.kind == ExpKind::VCALL {
let pc = e.u.info() as usize;
debug_assert_eq!(Instruction::get_c(fs.chunk.code[pc]), 2);
e.kind = ExpKind::VNONRELOC;
e.u = ExpUnion::Info(Instruction::get_a(fs.chunk.code[pc]) as i32);
} else if e.kind == ExpKind::VVARARG {
let pc = e.u.info() as usize;
Instruction::set_c(&mut fs.chunk.code[pc], 2);
e.kind = ExpKind::VRELOC;
}
}
pub fn setreturns(fs: &mut FuncState, e: &mut ExpDesc, nresults: u8) {
let pc = e.u.info() as usize;
if e.kind == ExpKind::VCALL {
Instruction::set_c(&mut fs.chunk.code[pc], (nresults as u32) + 1);
} else {
Instruction::set_c(&mut fs.chunk.code[pc], (nresults as u32) + 1);
Instruction::set_a(&mut fs.chunk.code[pc], fs.freereg as u32);
reserve_regs(fs, 1);
}
}
fn tonumeral(e: &ExpDesc, _v: Option<&mut f64>) -> bool {
if e.has_jumps() {
return false;
}
matches!(e.kind, ExpKind::VKINT | ExpKind::VKFLT)
}
fn bool_t(fs: &mut FuncState) -> usize {
let val = LuaValue::boolean(true);
add_constant(fs, val)
}
fn bool_f(fs: &mut FuncState) -> usize {
let val = LuaValue::boolean(false);
add_constant(fs, val)
}
fn nil_k(fs: &mut FuncState) -> usize {
let key = fs.kcache;
let val = LuaValue::nil();
add_constant_with_key(fs, key, val)
}
fn int_k(fs: &mut FuncState, i: i64) -> usize {
let val = LuaValue::integer(i);
add_constant(fs, val)
}
fn number_k(fs: &mut FuncState, n: f64) -> usize {
let val = LuaValue::float(n);
if n == 0.0 {
let fs_ptr = fs as *const FuncState as *mut std::ffi::c_void;
let key = LuaValue::lightuserdata(fs_ptr);
return add_constant_with_key(fs, key, val);
}
const MANT_DIG: i32 = 53; let q = 2.0_f64.powi(-MANT_DIG + 1); let k = n * (1.0 + q);
if k.floor() == k && k >= i64::MIN as f64 && k < -(i64::MIN as f64) {
let idx = fs.chunk.constants.len();
fs.chunk.constants.push(val);
return idx;
}
let key = LuaValue::float(k);
let idx = add_constant_with_key(fs, key, val);
if let Some(existing) = fs.chunk.constants.get(idx)
&& val == *existing
{
return idx;
}
let idx = fs.chunk.constants.len();
fs.chunk.constants.push(val);
idx
}
fn integer_k(fs: &mut FuncState, i: i64) -> usize {
let val = LuaValue::integer(i);
add_constant(fs, val)
}
pub fn string_k(fs: &mut FuncState, s: String) -> usize {
let string = fs.vm.create_string(&s).unwrap();
add_constant(fs, string)
}
pub fn binary_k(fs: &mut FuncState, v: Vec<u8>) -> usize {
let binary = fs.vm.create_binary(v).unwrap();
add_constant(fs, binary)
}
fn str2k(fs: &mut FuncState, e: &mut ExpDesc) -> usize {
debug_assert!(e.kind == ExpKind::VKSTR);
e.kind = ExpKind::VK;
let string = e.u.str();
let k = add_constant(fs, *string);
e.u = ExpUnion::Info(k as i32);
k
}
fn add_constant_with_key(fs: &mut FuncState, key: LuaValue, value: LuaValue) -> usize {
let found_idx: Option<usize> = {
if let Some(kcache_table) = fs.kcache.as_table_mut() {
if let Some(idx_value) = kcache_table.raw_get(&key) {
idx_value.as_integer().map(|i| i as usize)
} else {
None
}
} else {
None
}
};
if let Some(idx) = found_idx {
if idx < fs.chunk.constants.len()
&& let Some(existing) = fs.chunk.constants.get(idx)
{
if value == *existing {
return idx;
}
}
}
let idx = fs.chunk.constants.len();
fs.chunk.constants.push(value);
fs.vm
.raw_set(&fs.kcache, key, LuaValue::integer(idx as i64));
idx
}
fn add_constant(fs: &mut FuncState, value: LuaValue) -> usize {
add_constant_with_key(fs, value, value)
}
fn exp2k(fs: &mut FuncState, e: &mut ExpDesc) -> bool {
if !e.has_jumps() {
if e.kind == ExpKind::VCONST {
let vidx = e.u.info() as usize;
if let Some(var) = fs.actvar.get(vidx) {
if let Some(value) = var.const_value {
const_to_exp(value, e);
} else {
return false;
}
} else {
return false;
}
}
let info = match e.kind {
ExpKind::VTRUE => bool_t(fs),
ExpKind::VFALSE => bool_f(fs),
ExpKind::VNIL => nil_k(fs),
ExpKind::VKINT => int_k(fs, e.u.ival()),
ExpKind::VKFLT => number_k(fs, e.u.nval()),
ExpKind::VKSTR => str2k(fs, e),
ExpKind::VK => e.u.info() as usize, _ => return false,
};
if info <= MAXINDEXRK {
e.kind = ExpKind::VK;
e.u = ExpUnion::Info(info as i32);
return true;
}
}
false
}
fn exp2rk(fs: &mut FuncState, e: &mut ExpDesc) -> bool {
if exp2k(fs, e) {
true
} else {
exp2anyreg(fs, e);
false
}
}
fn get_jump_control(fs: &FuncState, pc: usize) -> usize {
if pc >= 1 && pc < fs.chunk.code.len() {
let prev_instr = fs.chunk.code[pc - 1];
let prev_op = Instruction::get_opcode(prev_instr);
if matches!(
prev_op,
OpCode::Test
| OpCode::TestSet
| OpCode::Eq
| OpCode::Lt
| OpCode::Le
| OpCode::EqK
| OpCode::EqI
| OpCode::LtI
| OpCode::LeI
| OpCode::GtI
| OpCode::GeI
) {
return pc - 1;
}
}
pc
}
fn negatecondition(fs: &mut FuncState, e: &mut ExpDesc) {
let pc = get_jump_control(fs, e.u.info() as usize);
if pc < fs.chunk.code.len() {
let instr = &mut fs.chunk.code[pc];
let k = Instruction::get_k(*instr);
Instruction::set_k(instr, !k);
}
}
fn condjump(fs: &mut FuncState, op: OpCode, a: u32, b: u32, c: u32, k: bool) -> isize {
code_abck(fs, op, a, b, c, k);
jump(fs) as isize
}
fn discharge2anyreg(fs: &mut FuncState, e: &mut ExpDesc) {
if e.kind != ExpKind::VNONRELOC {
reserve_regs(fs, 1);
discharge2reg(fs, e, fs.freereg - 1);
}
}
fn remove_last_instruction(fs: &mut FuncState) {
if fs.pc > 0 {
fs.pc -= 1;
fs.chunk.code.pop();
fs.chunk.line_info.pop(); }
}
fn jumponcond(fs: &mut FuncState, e: &mut ExpDesc, cond: bool) -> isize {
if e.kind == ExpKind::VRELOC {
let ie = fs.chunk.code[e.u.info() as usize];
if Instruction::get_opcode(ie) == OpCode::Not {
remove_last_instruction(fs);
let b = Instruction::get_b(ie);
return condjump(fs, OpCode::Test, b, 0, 0, !cond);
}
}
discharge2anyreg(fs, e);
free_exp(fs, e);
condjump(fs, OpCode::TestSet, NO_REG, e.u.info() as u32, 0, cond)
}
pub fn goiftrue(fs: &mut FuncState, e: &mut ExpDesc) {
discharge_vars(fs, e);
let pc = match e.kind {
ExpKind::VJMP => {
negatecondition(fs, e);
e.u.info() as isize
}
ExpKind::VK | ExpKind::VKFLT | ExpKind::VKINT | ExpKind::VKSTR | ExpKind::VTRUE => {
-1 }
_ => jumponcond(fs, e, false),
};
concat(fs, &mut e.f, pc);
patchtohere(fs, e.t);
e.t = -1;
}
pub fn goiffalse(fs: &mut FuncState, e: &mut ExpDesc) {
discharge_vars(fs, e);
let pc = match e.kind {
ExpKind::VJMP => e.u.info() as isize,
ExpKind::VNIL | ExpKind::VFALSE => -1, _ => jumponcond(fs, e, true),
};
concat(fs, &mut e.t, pc);
patchtohere(fs, e.f);
e.f = -1;
}
pub fn infix(fs: &mut FuncState, op: BinaryOperator, v: &mut ExpDesc) {
discharge_vars(fs, v);
match op {
BinaryOperator::OpAnd => {
goiftrue(fs, v);
}
BinaryOperator::OpOr => {
goiffalse(fs, v);
}
BinaryOperator::OpConcat => {
exp2nextreg(fs, v);
}
BinaryOperator::OpAdd
| BinaryOperator::OpSub
| BinaryOperator::OpMul
| BinaryOperator::OpDiv
| BinaryOperator::OpIDiv
| BinaryOperator::OpMod
| BinaryOperator::OpPow
| BinaryOperator::OpBAnd
| BinaryOperator::OpBOr
| BinaryOperator::OpBXor
| BinaryOperator::OpShl
| BinaryOperator::OpShr => {
if !tonumeral(v, None) {
exp2anyreg(fs, v);
}
}
BinaryOperator::OpEq | BinaryOperator::OpNe => {
if !tonumeral(v, None) {
exp2rk(fs, v);
}
}
BinaryOperator::OpLt
| BinaryOperator::OpLe
| BinaryOperator::OpGt
| BinaryOperator::OpGe => {
let mut im: i32 = 0;
let mut isfloat: bool = false;
if !is_scnumber(v, &mut im, &mut isfloat) {
exp2anyreg(fs, v);
}
}
BinaryOperator::OpNop => {}
}
}
fn swapexps(e1: &mut ExpDesc, e2: &mut ExpDesc) {
std::mem::swap(e1, e2);
}
fn codeconcat(fs: &mut FuncState, e1: &mut ExpDesc, e2: &mut ExpDesc, line: usize) {
if fs.pc > 0 {
let prev_pc = fs.pc - 1;
let prev_instr = fs.chunk.code[prev_pc];
if Instruction::get_opcode(prev_instr) == OpCode::Concat {
let n = Instruction::get_b(prev_instr);
let a = Instruction::get_a(prev_instr);
if e1.u.info() as u32 + 1 == a {
free_exp(fs, e2);
Instruction::set_a(&mut fs.chunk.code[prev_pc], e1.u.info() as u32);
Instruction::set_b(&mut fs.chunk.code[prev_pc], n + 1);
return;
}
}
}
code_abc(fs, OpCode::Concat, e1.u.info() as u32, 2, 0);
free_exp(fs, e2);
fixline(fs, line);
}
pub fn code_abrk(fs: &mut FuncState, opcode: OpCode, a: u32, b: u32, ec: &mut ExpDesc) {
let k = exp2rk(fs, ec);
let c = ec.u.info() as u32;
code_abck(fs, opcode, a, b, c, k);
}
fn codeeq(fs: &mut FuncState, op: BinaryOperator, e1: &mut ExpDesc, e2: &mut ExpDesc, line: usize) {
let r2: u32;
let mut im: i32 = 0;
let mut isfloat: bool = false;
let opcode: OpCode;
if e1.kind != ExpKind::VNONRELOC {
swapexps(e1, e2);
}
let r1: u32 = exp2anyreg(fs, e1) as u32;
if is_scnumber(e2, &mut im, &mut isfloat) {
opcode = OpCode::EqI;
r2 = im as u32; }
else if exp2rk(fs, e2) {
opcode = OpCode::EqK;
r2 = e2.u.info() as u32; }
else {
opcode = OpCode::Eq;
r2 = exp2anyreg(fs, e2) as u32;
}
free_exps(fs, e1, e2);
let k = op == BinaryOperator::OpEq;
let pc = condjump(fs, opcode, r1, r2, isfloat as u32, k);
fixline(fs, line);
e1.u = ExpUnion::Info(pc as i32);
e1.kind = ExpKind::VJMP;
}
fn codeorder(
fs: &mut FuncState,
op: BinaryOperator,
e1: &mut ExpDesc,
e2: &mut ExpDesc,
line: usize,
) {
let r1: u32;
let r2: u32;
let mut im: i32 = 0;
let mut isfloat: bool = false;
let opcode: OpCode;
if is_scnumber(e2, &mut im, &mut isfloat) {
r1 = exp2anyreg(fs, e1) as u32;
r2 = im as u32;
opcode = match op {
BinaryOperator::OpLt => OpCode::LtI,
BinaryOperator::OpLe => OpCode::LeI,
_ => unreachable!(),
};
}
else if is_scnumber(e1, &mut im, &mut isfloat) {
r1 = exp2anyreg(fs, e2) as u32;
r2 = im as u32;
opcode = match op {
BinaryOperator::OpLt => OpCode::GtI, BinaryOperator::OpLe => OpCode::GeI, _ => unreachable!(),
};
}
else {
r1 = exp2anyreg(fs, e1) as u32;
r2 = exp2anyreg(fs, e2) as u32;
opcode = match op {
BinaryOperator::OpLt => OpCode::Lt,
BinaryOperator::OpLe => OpCode::Le,
_ => unreachable!(),
};
}
free_exps(fs, e1, e2);
let pc = condjump(fs, opcode, r1, r2, isfloat as u32, true);
fixline(fs, line);
e1.u = ExpUnion::Info(pc as i32);
e1.kind = ExpKind::VJMP;
}
fn foldbinop(op: BinaryOperator) -> bool {
use BinaryOperator::*;
matches!(
op,
OpAdd
| OpSub
| OpMul
| OpDiv
| OpIDiv
| OpMod
| OpPow
| OpBAnd
| OpBOr
| OpBXor
| OpShl
| OpShr
)
}
fn validop(op: BinaryOperator, e1: &ExpDesc, e2: &ExpDesc) -> bool {
use BinaryOperator::*;
use ExpKind::{VKFLT, VKINT};
match op {
OpBAnd | OpBOr | OpBXor | OpShl | OpShr => {
let ok1 = match e1.kind {
VKINT => true,
VKFLT => {
let v = e1.u.nval();
v.is_finite()
&& v.fract() == 0.0
&& v >= i64::MIN as f64
&& v < -(i64::MIN as f64)
}
_ => false,
};
let ok2 = match e2.kind {
VKINT => true,
VKFLT => {
let v = e2.u.nval();
v.is_finite()
&& v.fract() == 0.0
&& v >= i64::MIN as f64
&& v < -(i64::MIN as f64)
}
_ => false,
};
ok1 && ok2
}
OpDiv | OpIDiv | OpMod => match e2.kind {
VKINT => e2.u.ival() != 0,
VKFLT => e2.u.nval() != 0.0,
_ => false,
},
_ => true, }
}
#[inline]
fn fold_shiftl(x: i64, y: i64) -> i64 {
if y < 0 {
if y <= -64 {
0
} else {
((x as u64) >> ((-y) as u32)) as i64
}
} else if y >= 64 {
0
} else {
((x as u64) << (y as u32)) as i64
}
}
fn constfolding(_fs: &FuncState, op: BinaryOperator, e1: &mut ExpDesc, e2: &ExpDesc) -> bool {
use BinaryOperator::*;
use ExpKind::{VKFLT, VKINT};
if e1.has_jumps() || e2.has_jumps() {
return false;
}
let e1_is_int_type = matches!(e1.kind, VKINT);
let e2_is_int_type = matches!(e2.kind, VKINT);
let (v1, i1) = match e1.kind {
VKINT => {
let iv = e1.u.ival();
(iv as f64, iv)
}
VKFLT => {
let f = e1.u.nval();
let as_int = f as i64;
(f, as_int)
}
_ => return false,
};
let (v2, i2) = match e2.kind {
VKINT => {
let iv = e2.u.ival();
(iv as f64, iv)
}
VKFLT => {
let f = e2.u.nval();
let as_int = f as i64;
(f, as_int)
}
_ => return false,
};
if !validop(op, e1, e2) {
return false;
}
match op {
OpBAnd | OpBOr | OpBXor => {
let v1_can_be_int = if e1_is_int_type {
true
} else {
(i1 as f64) == v1
};
let v2_can_be_int = if e2_is_int_type {
true
} else {
(i2 as f64) == v2
};
if !v1_can_be_int || !v2_can_be_int {
return false;
}
e1.kind = VKINT;
let u1 = i1 as u64;
let u2 = i2 as u64;
e1.u = ExpUnion::IVal(match op {
OpBAnd => (u1 & u2) as i64,
OpBOr => (u1 | u2) as i64,
OpBXor => (u1 ^ u2) as i64,
_ => unreachable!(),
});
return true;
}
OpShl | OpShr => {
let v1_can_be_int = if e1_is_int_type {
true
} else {
(i1 as f64) == v1
};
let v2_can_be_int = if e2_is_int_type {
true
} else {
(i2 as f64) == v2
};
if !v1_can_be_int || !v2_can_be_int {
return false;
}
e1.kind = VKINT;
let shift_y = if op == OpShr { i2.wrapping_neg() } else { i2 };
e1.u = ExpUnion::IVal(fold_shiftl(i1, shift_y));
return true;
}
_ => {}
}
match op {
OpDiv | OpPow => {
let result = match op {
OpDiv => v1 / v2,
OpPow => v1.powf(v2),
_ => unreachable!(),
};
if result.is_nan() || result == 0.0 {
return false;
}
e1.kind = VKFLT;
e1.u = ExpUnion::NVal(result);
true
}
OpAdd | OpSub | OpMul | OpIDiv | OpMod => {
if e1_is_int_type && e2_is_int_type {
let int_result = match op {
OpAdd => i1.checked_add(i2),
OpSub => i1.checked_sub(i2),
OpMul => i1.checked_mul(i2),
OpIDiv => {
if i2 == -1 {
i1.checked_neg() } else {
let q = i1 / i2;
if (i1 ^ i2) < 0 && i1 % i2 != 0 {
Some(q - 1)
} else {
Some(q)
}
}
}
OpMod => {
if i2 == -1 {
Some(0) } else {
let m = i1 % i2;
if m != 0 && (m ^ i2) < 0 {
Some(m + i2)
} else {
Some(m)
}
}
}
_ => unreachable!(),
};
if let Some(res) = int_result {
e1.kind = VKINT;
e1.u = ExpUnion::IVal(res);
return true;
}
}
let result = match op {
OpAdd => v1 + v2,
OpSub => v1 - v2,
OpMul => v1 * v2,
OpIDiv => (v1 / v2).floor(),
OpMod => v1 - (v1 / v2).floor() * v2,
_ => unreachable!(),
};
if result.is_nan() || result == 0.0 {
return false;
}
e1.kind = VKFLT;
e1.u = ExpUnion::NVal(result);
true
}
_ => false,
}
}
pub fn posfix(
fs: &mut FuncState,
op: BinaryOperator,
e1: &mut ExpDesc,
e2: &mut ExpDesc,
line: usize,
) {
use BinaryOperator;
discharge_vars(fs, e2);
if foldbinop(op) && constfolding(fs, op, e1, e2) {
return; }
match op {
BinaryOperator::OpAnd => {
concat(fs, &mut e2.f, e1.f);
*e1 = e2.clone();
}
BinaryOperator::OpOr => {
concat(fs, &mut e2.t, e1.t);
*e1 = e2.clone();
}
BinaryOperator::OpConcat => {
if e2.kind == ExpKind::VKSTR
&& e1.kind == ExpKind::VNONRELOC
&& !e1.has_jumps()
&& fs.pc > 0
{
let prev_pc = fs.pc - 1;
let prev_instr = fs.chunk.code[prev_pc];
if Instruction::get_opcode(prev_instr) == OpCode::LoadK {
let reg_a = Instruction::get_a(prev_instr) as i32;
let k_idx = Instruction::get_bx(prev_instr) as usize;
if reg_a == e1.u.info()
&& k_idx < fs.chunk.constants.len()
&& let Some(s1) = fs.chunk.constants[k_idx].as_str()
&& let Some(s2) = e2.u.str().as_str()
{
let mut combined = String::with_capacity(s1.len() + s2.len());
combined.push_str(s1);
combined.push_str(s2);
let combined_value = fs.vm.create_string(&combined).unwrap();
fs.chunk.code.pop();
fs.chunk.line_info.pop();
fs.pc -= 1;
free_exp(fs, e1);
*e1 = ExpDesc::new_vkstr(combined_value);
return;
}
}
}
exp2nextreg(fs, e2);
codeconcat(fs, e1, e2, line);
}
BinaryOperator::OpEq | BinaryOperator::OpNe => {
codeeq(fs, op, e1, e2, line);
}
BinaryOperator::OpGt | BinaryOperator::OpGe => {
swapexps(e1, e2);
let new_op = if op == BinaryOperator::OpGt {
BinaryOperator::OpLt
} else {
BinaryOperator::OpLe
};
codeorder(fs, new_op, e1, e2, line);
}
BinaryOperator::OpLt | BinaryOperator::OpLe => {
codeorder(fs, op, e1, e2, line);
}
_ => {
let mut flip = false;
if matches!(op, BinaryOperator::OpAdd | BinaryOperator::OpMul) {
if matches!(e1.kind, ExpKind::VKINT | ExpKind::VKFLT) {
swapexps(e1, e2);
flip = true;
}
} else if matches!(
op,
BinaryOperator::OpBAnd | BinaryOperator::OpBOr | BinaryOperator::OpBXor
) {
if matches!(e1.kind, ExpKind::VKINT) {
swapexps(e1, e2);
flip = true;
}
}
if op == BinaryOperator::OpAdd {
if let ExpKind::VKINT = e2.kind
&& !e2.has_jumps()
{
let imm_val = e2.u.ival();
if (-127..=128).contains(&imm_val) {
let r1 = exp2anyreg(fs, e1);
let enc_imm = ((imm_val + 127) & 0xff) as u32;
let pc = code_abc(fs, OpCode::AddI, 0, r1 as u32, enc_imm);
free_exp(fs, e1);
free_exp(fs, e2);
e1.kind = ExpKind::VRELOC;
e1.u = ExpUnion::Info(pc as i32);
fixline(fs, line);
let mm_imm = ((imm_val + 127) & 0xff) as u32;
code_abck(
fs,
OpCode::MmBinI,
r1 as u32,
mm_imm,
TmKind::Add as u32,
flip,
);
fixline(fs, line);
return;
}
}
} else if op == BinaryOperator::OpSub {
if let ExpKind::VKINT = e2.kind
&& !e2.has_jumps()
{
let imm_val = e2.u.ival();
let neg_imm = -imm_val;
if (-127..=128).contains(&imm_val) && (-127..=128).contains(&neg_imm) {
let r1 = exp2anyreg(fs, e1);
let enc_imm = ((neg_imm + 127) & 0xff) as u32; let pc = code_abc(fs, OpCode::AddI, 0, r1 as u32, enc_imm);
free_exp(fs, e1);
free_exp(fs, e2);
e1.kind = ExpKind::VRELOC;
e1.u = ExpUnion::Info(pc as i32);
fixline(fs, line);
let mm_imm = ((imm_val + 127) & 0xff) as u32;
code_abck(
fs,
OpCode::MmBinI,
r1 as u32,
mm_imm,
TmKind::Sub as u32,
flip,
);
fixline(fs, line);
return;
}
}
}
if op == BinaryOperator::OpShl {
if let ExpKind::VKINT = e1.kind {
let imm_val = e1.u.ival();
if (-127..=128).contains(&imm_val) {
swapexps(e1, e2); let r1 = exp2anyreg(fs, e1);
let enc_imm = ((imm_val + 127) & 0xff) as u32;
let pc = code_abc(fs, OpCode::ShlI, 0, r1 as u32, enc_imm);
free_exp(fs, e1);
free_exp(fs, e2);
e1.kind = ExpKind::VRELOC;
e1.u = ExpUnion::Info(pc as i32);
fixline(fs, line);
let mm_imm = ((imm_val + 127) & 0xff) as u32;
code_abck(
fs,
OpCode::MmBinI,
r1 as u32,
mm_imm,
TmKind::Shl as u32,
true,
);
fixline(fs, line);
return;
}
} else if let ExpKind::VKINT = e2.kind {
let imm_val = e2.u.ival();
let neg_imm = -imm_val;
if (-127..=128).contains(&imm_val) && (-127..=128).contains(&neg_imm) {
let r1 = exp2anyreg(fs, e1);
let enc_imm = ((neg_imm + 127) & 0xff) as u32;
let pc = code_abc(fs, OpCode::ShrI, 0, r1 as u32, enc_imm);
free_exp(fs, e1);
free_exp(fs, e2);
e1.kind = ExpKind::VRELOC;
e1.u = ExpUnion::Info(pc as i32);
fixline(fs, line);
let mm_imm = ((imm_val + 127) & 0xff) as u32;
code_abck(
fs,
OpCode::MmBinI,
r1 as u32,
mm_imm,
TmKind::Shl as u32,
flip,
);
fixline(fs, line);
return;
}
}
} else if op == BinaryOperator::OpShr {
if let ExpKind::VKINT = e2.kind
&& !e2.has_jumps()
{
let imm_val = e2.u.ival();
if (-127..=128).contains(&imm_val) {
let r1 = exp2anyreg(fs, e1);
let enc_imm = ((imm_val + 127) & 0xff) as u32;
let pc = code_abc(fs, OpCode::ShrI, 0, r1 as u32, enc_imm);
free_exp(fs, e1);
free_exp(fs, e2);
e1.kind = ExpKind::VRELOC;
e1.u = ExpUnion::Info(pc as i32);
let mm_imm = ((imm_val + 127) & 0xff) as u32;
code_abck(
fs,
OpCode::MmBinI,
r1 as u32,
mm_imm,
TmKind::Shr as u32,
flip,
);
fixline(fs, line);
return;
}
}
}
let use_k_instruction = match op {
BinaryOperator::OpBAnd | BinaryOperator::OpBOr | BinaryOperator::OpBXor => {
matches!(e2.kind, ExpKind::VKINT) && exp2k(fs, e2)
}
BinaryOperator::OpAdd
| BinaryOperator::OpSub
| BinaryOperator::OpMul
| BinaryOperator::OpDiv
| BinaryOperator::OpIDiv
| BinaryOperator::OpMod
| BinaryOperator::OpPow => {
matches!(e2.kind, ExpKind::VKINT | ExpKind::VKFLT) && exp2k(fs, e2)
}
_ => false,
};
if use_k_instruction {
let k_idx = e2.u.info();
let r1 = exp2anyreg(fs, e1);
let opcode = match op {
BinaryOperator::OpAdd => OpCode::AddK,
BinaryOperator::OpSub => OpCode::SubK,
BinaryOperator::OpMul => OpCode::MulK,
BinaryOperator::OpDiv => OpCode::DivK,
BinaryOperator::OpIDiv => OpCode::IDivK,
BinaryOperator::OpMod => OpCode::ModK,
BinaryOperator::OpPow => OpCode::PowK,
BinaryOperator::OpBAnd => OpCode::BAndK,
BinaryOperator::OpBOr => OpCode::BOrK,
BinaryOperator::OpBXor => OpCode::BXorK,
_ => {
let o2 = exp2anyreg(fs, e2);
free_reg(fs, o2);
free_reg(fs, r1);
let res = fs.freereg;
reserve_regs(fs, 1);
let opcode = match op {
BinaryOperator::OpShl => OpCode::Shl,
BinaryOperator::OpShr => OpCode::Shr,
_ => unreachable!("Invalid operator"),
};
code_abc(fs, opcode, res as u32, r1 as u32, o2 as u32);
e1.kind = ExpKind::VNONRELOC;
e1.u = ExpUnion::Info(res as i32);
return;
}
};
let pc = code_abc(fs, opcode, 0, r1 as u32, k_idx as u32);
free_exps(fs, e1, e2);
e1.kind = ExpKind::VRELOC;
e1.u = ExpUnion::Info(pc as i32);
fixline(fs, line);
let tm_event = match op {
BinaryOperator::OpAdd => TmKind::Add, BinaryOperator::OpSub => TmKind::Sub, BinaryOperator::OpMul => TmKind::Mul, BinaryOperator::OpMod => TmKind::Mod, BinaryOperator::OpPow => TmKind::Pow, BinaryOperator::OpDiv => TmKind::Div, BinaryOperator::OpIDiv => TmKind::IDiv, BinaryOperator::OpBAnd => TmKind::Band, BinaryOperator::OpBOr => TmKind::Bor, BinaryOperator::OpBXor => TmKind::Bxor, _ => TmKind::N, };
code_abck(
fs,
OpCode::MmBinK,
r1 as u32,
k_idx as u32,
tm_event as u32,
flip,
);
fixline(fs, line);
} else {
if flip {
swapexps(e1, e2);
}
let o2 = exp2anyreg(fs, e2);
let opcode = match op {
BinaryOperator::OpAdd => OpCode::Add,
BinaryOperator::OpSub => OpCode::Sub,
BinaryOperator::OpMul => OpCode::Mul,
BinaryOperator::OpDiv => OpCode::Div,
BinaryOperator::OpIDiv => OpCode::IDiv,
BinaryOperator::OpMod => OpCode::Mod,
BinaryOperator::OpPow => OpCode::Pow,
BinaryOperator::OpShl => OpCode::Shl,
BinaryOperator::OpShr => OpCode::Shr,
BinaryOperator::OpBAnd => OpCode::BAnd,
BinaryOperator::OpBOr => OpCode::BOr,
BinaryOperator::OpBXor => OpCode::BXor,
_ => unreachable!("Invalid operator for opcode generation"),
};
let o1 = exp2anyreg(fs, e1);
let pc = code_abc(fs, opcode, 0, o1 as u32, o2 as u32);
free_exps(fs, e1, e2);
e1.kind = ExpKind::VRELOC;
e1.u = ExpUnion::Info(pc as i32);
fixline(fs, line);
let tm_event = match op {
BinaryOperator::OpAdd => TmKind::Add,
BinaryOperator::OpSub => TmKind::Sub,
BinaryOperator::OpMul => TmKind::Mul,
BinaryOperator::OpMod => TmKind::Mod,
BinaryOperator::OpPow => TmKind::Pow,
BinaryOperator::OpDiv => TmKind::Div,
BinaryOperator::OpIDiv => TmKind::IDiv,
BinaryOperator::OpBAnd => TmKind::Band,
BinaryOperator::OpBOr => TmKind::Bor,
BinaryOperator::OpBXor => TmKind::Bxor,
BinaryOperator::OpShl => TmKind::Shl,
BinaryOperator::OpShr => TmKind::Shr,
_ => TmKind::N,
};
code_abc(fs, OpCode::MmBin, o1 as u32, o2 as u32, tm_event as u32);
fixline(fs, line);
}
}
}
}
fn removevalues(fs: &mut FuncState, mut list: isize) {
const NO_JUMP: isize = -1;
while list != NO_JUMP {
patchtestreg(fs, list as usize, NO_REG as u8);
list = get_jump(fs, list as usize);
}
}
fn codenot(fs: &mut FuncState, e: &mut ExpDesc) {
discharge_vars(fs, e);
match e.kind {
ExpKind::VNIL | ExpKind::VFALSE => {
e.kind = ExpKind::VTRUE;
}
ExpKind::VK | ExpKind::VKFLT | ExpKind::VKINT | ExpKind::VKSTR | ExpKind::VTRUE => {
e.kind = ExpKind::VFALSE;
}
ExpKind::VJMP => {
negatecondition(fs, e);
}
ExpKind::VRELOC | ExpKind::VNONRELOC => {
discharge2anyreg(fs, e);
free_exp(fs, e);
let pc = code_abc(fs, OpCode::Not, 0, e.u.info() as u32, 0);
e.u = ExpUnion::Info(pc as i32);
e.kind = ExpKind::VRELOC;
}
_ => {} }
std::mem::swap(&mut e.t, &mut e.f);
removevalues(fs, e.f);
removevalues(fs, e.t);
}
fn codeunexpval(fs: &mut FuncState, op: OpCode, e: &mut ExpDesc, line: usize) {
let r = exp2anyreg(fs, e); free_exp(fs, e);
let pc = code_abc(fs, op, 0, r as u32, 0); e.u = ExpUnion::Info(pc as i32);
e.kind = ExpKind::VRELOC; fixline(fs, line);
}
pub fn prefix(fs: &mut FuncState, op: OpCode, e: &mut ExpDesc, line: usize) {
discharge_vars(fs, e);
match op {
OpCode::Unm => {
if !e.has_jumps() {
match e.kind {
ExpKind::VKINT => {
let val = e.u.ival();
e.u = ExpUnion::IVal(val.wrapping_neg());
return;
}
ExpKind::VKFLT => {
let val = e.u.nval();
let negated = -val;
if negated == 0.0 {
codeunexpval(fs, op, e, line);
return;
}
e.u = ExpUnion::NVal(negated);
return;
}
_ => {}
}
}
codeunexpval(fs, op, e, line);
}
OpCode::BNot => {
if !e.has_jumps() {
match e.kind {
ExpKind::VKINT => {
let val = e.u.ival();
e.u = ExpUnion::IVal(!val);
return;
}
ExpKind::VKFLT => {
let fval = e.u.nval();
let ival = fval as i64;
if (ival as f64) == fval {
e.kind = ExpKind::VKINT;
e.u = ExpUnion::IVal(!ival);
return;
}
}
_ => {}
}
}
codeunexpval(fs, op, e, line);
}
OpCode::Len => {
codeunexpval(fs, op, e, line);
}
OpCode::Not => {
codenot(fs, e);
}
_ => unreachable!(),
}
}
pub fn exp2anyregup(fs: &mut FuncState, e: &mut ExpDesc) {
if (e.kind != ExpKind::VUPVAL && e.kind != ExpKind::VVARGVAR) || e.has_jumps() {
exp2anyreg(fs, e);
}
}
pub fn exp2val(fs: &mut FuncState, e: &mut ExpDesc) {
if e.kind == ExpKind::VJMP || e.has_jumps() {
exp2anyreg(fs, e);
} else {
discharge_vars(fs, e);
}
}
pub fn indexed(fs: &mut FuncState, t: &mut ExpDesc, k: &mut ExpDesc) {
let mut keystr: i32 = -1;
if k.kind == ExpKind::VKSTR {
str2k(fs, k);
keystr = k.u.info();
}
if t.kind == ExpKind::VUPVAL && !is_kstr(fs, k) {
exp2anyreg(fs, t);
}
if t.kind == ExpKind::VUPVAL {
let temp = t.u.info();
t.u = ExpUnion::Ind(IndVars {
t: temp as i16,
idx: k.u.info() as i16,
ro: false,
keystr, });
t.kind = ExpKind::VINDEXUP;
} else if t.kind == ExpKind::VVARGVAR {
let kreg = exp2anyreg(fs, k); let vreg = t.u.var().ridx; t.u = ExpUnion::Ind(IndVars {
t: vreg,
idx: kreg as i16,
ro: false,
keystr,
});
t.kind = ExpKind::VVARGIND; } else {
t.u.ind_mut().t = if t.kind == ExpKind::VLOCAL {
t.u.var().ridx
} else {
t.u.info() as i16
};
if is_kstr(fs, k) {
t.u.ind_mut().idx = k.u.info() as i16;
t.kind = ExpKind::VINDEXSTR;
} else if is_cint(k) {
t.u.ind_mut().idx = k.u.ival() as i16;
t.kind = ExpKind::VINDEXI;
} else {
t.u.ind_mut().idx = exp2anyreg(fs, k) as i16;
t.kind = ExpKind::VINDEXED;
}
t.u.ind_mut().keystr = keystr;
}
}
#[inline]
fn hasjumps(e: &ExpDesc) -> bool {
e.t != e.f
}
fn is_kstr(fs: &FuncState, e: &ExpDesc) -> bool {
let ok1 = e.kind == ExpKind::VK && !hasjumps(e) && e.u.info() <= Instruction::MAX_B as i32;
if !ok1 {
return false;
}
let k_idx = e.u.info() as usize;
if k_idx >= fs.chunk.constants.len() {
return false;
}
let const_val = &fs.chunk.constants[k_idx];
if let Some(str) = const_val.as_str() {
return str.len() <= 40;
}
false
}
fn is_kint(e: &ExpDesc) -> bool {
e.kind == ExpKind::VKINT && !hasjumps(e)
}
fn is_cint(e: &ExpDesc) -> bool {
is_kint(e) && e.u.ival() as u64 <= Instruction::MAX_C as u64
}
pub fn self_op(fs: &mut FuncState, e: &mut ExpDesc, key: &mut ExpDesc) {
let ereg = exp2anyreg(fs, e);
free_exp(fs, e);
let base = fs.freereg;
e.u = ExpUnion::Info(base as i32);
e.kind = ExpKind::VNONRELOC;
reserve_regs(fs, 2);
let can_use_self = if key.kind == ExpKind::VKSTR {
key.u.str().is_short_string()
} else {
false };
if can_use_self {
if exp2k(fs, key) {
code_abck(
fs,
OpCode::Self_,
base as u32,
ereg as u32,
key.u.info() as u32,
false,
);
} else {
exp2anyreg(fs, key);
code_abc(fs, OpCode::Move, (base + 1) as u32, ereg as u32, 0);
let kc = key.u.info() as u32;
code_abc(fs, OpCode::GetTable, base as u32, ereg as u32, kc);
}
} else {
exp2anyreg(fs, key);
code_abc(fs, OpCode::Move, (base + 1) as u32, ereg as u32, 0);
let kc = key.u.info() as u32;
code_abc(fs, OpCode::GetTable, base as u32, ereg as u32, kc);
}
free_exp(fs, key);
}
pub fn setmultret(fs: &mut FuncState, e: &mut ExpDesc) {
setreturns(fs, e, 255);
}
pub fn fixline(fs: &mut FuncState, line: usize) {
if !fs.chunk.line_info.is_empty() {
let last_idx = fs.chunk.line_info.len() - 1;
fs.chunk.line_info[last_idx] = line as u32;
}
}
pub fn exp2const(fs: &FuncState, e: &ExpDesc) -> Option<LuaValue> {
if e.has_jumps() {
return None;
}
match e.kind {
ExpKind::VFALSE => Some(LuaValue::boolean(false)),
ExpKind::VTRUE => Some(LuaValue::boolean(true)),
ExpKind::VNIL => Some(LuaValue::nil()),
ExpKind::VKSTR => {
let string = e.u.str();
Some(*string)
}
ExpKind::VK => {
let idx = e.u.info() as usize;
if idx < fs.chunk.constants.len() {
Some(fs.chunk.constants[idx])
} else {
None
}
}
ExpKind::VKINT => Some(LuaValue::integer(e.u.ival())),
ExpKind::VKFLT => {
let val = e.u.nval();
if val.to_bits() == 0x8000000000000000 {
return None;
}
Some(LuaValue::float(val))
}
ExpKind::VCONST => {
let vidx = e.u.info() as usize;
if let Some(var_desc) = fs.actvar.get(vidx) {
var_desc.const_value
} else {
None
}
}
_ => None,
}
}
pub const LUA_MULTRET: u32 = u32::MAX;
pub fn hasmultret(e: &ExpDesc) -> bool {
matches!(e.kind, ExpKind::VCALL | ExpKind::VVARARG)
}
pub fn setlist(fs: &mut FuncState, base: u8, nelems: u32, tostore: u32) {
debug_assert!(tostore != 0);
let c = if tostore == LUA_MULTRET { 0 } else { tostore };
if nelems <= Instruction::MAX_V_C {
code_vabck(fs, OpCode::SetList, base as u32, c, nelems, false);
} else {
let extra = nelems / (Instruction::MAX_V_C + 1);
let c_arg = nelems % (Instruction::MAX_V_C + 1);
code_vabck(fs, OpCode::SetList, base as u32, c, c_arg, true);
code_extraarg(fs, extra);
}
fs.freereg = base + 1; }
pub fn settablesize(fs: &mut FuncState, pc: usize, ra: u8, asize: u32, hsize: u32) {
let rb = if hsize != 0 {
let bits = 32 - (hsize - 1).leading_zeros();
bits + 1
} else {
0
};
let rc = asize % (Instruction::MAX_V_C + 1);
let extra = asize / (Instruction::MAX_V_C + 1);
let k = extra > 0;
let inst = &mut fs.chunk.code[pc];
let opcode = Instruction::get_opcode(*inst);
debug_assert_eq!(opcode, OpCode::NewTable);
*inst = Instruction::create_vabck(OpCode::NewTable, ra as u32, rb, rc, k);
if pc + 1 < fs.chunk.code.len() {
fs.chunk.code[pc + 1] = Instruction::create_ax(OpCode::ExtraArg, extra);
}
}
pub fn code_k(fs: &mut FuncState, reg: u32, k: u32) -> usize {
if k <= Instruction::MAX_BX {
code_abx(fs, OpCode::LoadK, reg, k)
} else {
let p = code_abx(fs, OpCode::LoadKX, reg, 0);
code_extraarg(fs, k);
p
}
}
pub fn code_extraarg(fs: &mut FuncState, a: u32) -> usize {
let inst = Instruction::create_ax(OpCode::ExtraArg, a);
let pc = fs.pc;
fs.chunk.code.push(inst);
fs.chunk.line_info.push(fs.lexer.lastline as u32); fs.pc += 1;
pc
}
pub fn codecheckglobal(fs: &mut FuncState, var: &mut ExpDesc, mut k: i32, line: usize) {
exp2anyreg(fs, var);
fixline(fs, line);
const MAX_BX: i32 = (1 << 17) - 1; k = if k >= MAX_BX { 0 } else { k + 1 };
code_abx(fs, OpCode::ErrNNil, var.u.info() as u32, k as u32);
fixline(fs, line);
if var.kind == ExpKind::VNONRELOC {
let reg = var.u.info() as u8;
if fs.freereg > reg {
fs.freereg = reg;
}
}
}