use cranelift::prelude::*;
use cranelift_codegen::ir::{BlockArg, UserFuncName};
use cranelift_frontend::FunctionBuilderContext;
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{FuncId, Linkage, Module};
use luna_core::jit::trace_types::{CompileOptions, CompiledTrace, TraceRecord};
use luna_core::jit::{
CompileResult, IntChunkCompiler, IntChunkFn, IntFn1, IntFn2, IntFn3, IntFn4, JitVmGuard,
MAX_JIT_ARITY, TraceCompiler,
};
use luna_core::runtime::Gc;
use luna_core::runtime::Value as LuaValue;
use luna_core::runtime::function::Proto;
use luna_core::vm::isa::{Inst, Op};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum RegKind {
Unset,
Int,
Float,
Table,
}
impl RegKind {
#[inline]
fn unify(slot: &mut RegKind, incoming: RegKind) -> bool {
match (*slot, incoming) {
(_, RegKind::Unset) => true,
(RegKind::Unset, _) => {
*slot = incoming;
true
}
(a, b) if a == b => true,
(RegKind::Int, RegKind::Table) | (RegKind::Table, RegKind::Int) => true,
(RegKind::Float, RegKind::Table) | (RegKind::Table, RegKind::Float) => true,
_ => false,
}
}
}
pub mod trace;
mod send_jit_module;
#[allow(unused_imports)] pub use send_jit_module::SendJitModule;
pub use luna_jit_helpers::*;
pub(crate) mod storage;
#[cfg(test)]
fn test_vm_new(version: luna_core::version::LuaVersion) -> luna_core::vm::Vm {
let mut vm = luna_core::vm::Vm::new(version);
vm.install_jit_backend(CraneliftBackend, CraneliftBackend);
vm.install_jit_storage(storage::CraneliftJitStorage::default());
vm
}
#[cfg(test)]
#[allow(dead_code)]
fn test_vm_new_minimal(version: luna_core::version::LuaVersion) -> luna_core::vm::Vm {
let mut vm = luna_core::vm::Vm::new_minimal(version);
vm.install_jit_backend(CraneliftBackend, CraneliftBackend);
vm.install_jit_storage(storage::CraneliftJitStorage::default());
vm
}
pub fn cache_lookup_or_compile(
storage: &mut dyn luna_core::jit::JitStorage,
proto: luna_core::runtime::Gc<Proto>,
pre53: bool,
float_only: bool,
) -> Option<(*const u8, u8, bool, u8, u8, bool, bool)> {
let key = proto_cache_key(&proto, pre53, float_only);
let cs = storage::from_storage(storage).ok()?;
let cached = cs.cache.get(&key).copied();
if let Some(hit) = cached {
return match hit {
CacheEntry::Failed => None,
CacheEntry::Compiled {
entry,
num_args,
returns_one,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
} => Some((
entry,
num_args,
returns_one,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
)),
};
}
let entry = match try_compile_int_chunk(proto, pre53, float_only) {
Some(handle) => {
let raw = handle.entry_raw();
let num_args = handle.num_args();
let returns_one = handle.returns_one();
let arg_float_mask = handle.arg_float_mask();
let arg_table_mask = handle.arg_table_mask();
let ret_is_float = handle.ret_is_float();
let ret_is_table = handle.ret_is_table();
match storage::from_storage(storage) {
Ok(cs) => cs.cache_handles.push(handle),
Err(_) => return None,
}
CacheEntry::Compiled {
entry: raw,
num_args,
returns_one,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
}
}
None => CacheEntry::Failed,
};
storage::from_storage(storage)
.ok()?
.cache
.insert(key, entry);
match entry {
CacheEntry::Failed => None,
CacheEntry::Compiled {
entry,
num_args,
returns_one,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
} => Some((
entry,
num_args,
returns_one,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
)),
}
}
#[derive(Clone, Copy)]
pub(crate) enum CacheEntry {
Failed,
Compiled {
entry: *const u8,
num_args: u8,
returns_one: bool,
arg_float_mask: u8,
arg_table_mask: u8,
ret_is_float: bool,
ret_is_table: bool,
},
}
fn determine_getupval_roles(proto: &Proto) -> Vec<bool> {
const WINDOW: usize = 8;
let n = proto.code.len();
let mut roles = vec![false; n];
for pc in 0..n {
let ins = proto.code[pc];
if !matches!(ins.op(), Op::GetUpval) {
continue;
}
let target_a = ins.a() as usize;
let end = (pc + 1 + WINDOW).min(n);
for q in (pc + 1)..end {
let q_ins = proto.code[q];
if matches!(q_ins.op(), Op::Call) && q_ins.a() as usize == target_a {
break;
}
if reads_register_a_arith(q_ins, target_a) {
roles[pc] = true;
break;
}
if writes_register_a(q_ins, target_a) {
break;
}
}
}
roles
}
fn reads_register_a_arith(ins: Inst, target_a: usize) -> bool {
let b = ins.b() as usize;
let c = ins.c() as usize;
let a = ins.a() as usize;
match ins.op() {
Op::Add | Op::Sub | Op::Mul | Op::Div | Op::Mod | Op::Pow | Op::IDiv => {
b == target_a || c == target_a
}
Op::Lt | Op::Le | Op::Eq => a == target_a || b == target_a,
Op::Unm | Op::BNot | Op::Not => b == target_a,
_ => false,
}
}
fn writes_register_a(ins: Inst, target_a: usize) -> bool {
let a = ins.a() as usize;
match ins.op() {
Op::LoadI
| Op::LoadF
| Op::LoadK
| Op::LoadKx
| Op::LoadFalse
| Op::LFalseSkip
| Op::LoadTrue
| Op::Move
| Op::Add
| Op::Sub
| Op::Mul
| Op::Mod
| Op::Pow
| Op::Div
| Op::IDiv
| Op::BAnd
| Op::BOr
| Op::BXor
| Op::Shl
| Op::Shr
| Op::Unm
| Op::BNot
| Op::Not
| Op::Len
| Op::Concat
| Op::Call
| Op::GetUpval
| Op::GetTabUp
| Op::GetTable
| Op::GetI
| Op::GetField
| Op::NewTable
| Op::SelfOp => a == target_a,
Op::LoadNil => target_a >= a && target_a <= a + ins.b() as usize,
Op::ForPrep | Op::ForLoop => target_a >= a && target_a <= a + 3,
_ => false,
}
}
pub fn cache_entry_count(vm: &luna_core::vm::Vm) -> usize {
let storage = vm.jit.storage.as_ref().as_any();
let cs = storage
.downcast_ref::<storage::CraneliftJitStorage>()
.expect("vm storage not CraneliftJitStorage");
cs.cache
.values()
.filter(|e| matches!(e, CacheEntry::Compiled { .. }))
.count()
}
pub fn cache_clear(vm: &mut luna_core::vm::Vm) {
let storage = vm.jit.storage.as_mut().as_any_mut();
if let Some(cs) = storage.downcast_mut::<storage::CraneliftJitStorage>() {
cs.cache.clear();
cs.cache_handles.clear();
}
}
fn proto_cache_key(proto: &Proto, pre53: bool, float_only: bool) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
for inst in proto.code.iter() {
inst.0.hash(&mut h);
}
for c in proto.consts.iter() {
match c {
luna_core::runtime::Value::Int(i) => {
0u8.hash(&mut h);
i.hash(&mut h);
}
luna_core::runtime::Value::Float(f) => {
1u8.hash(&mut h);
f.to_bits().hash(&mut h);
}
luna_core::runtime::Value::Str(s) => {
3u8.hash(&mut h);
s.as_bytes().hash(&mut h);
}
other => {
2u8.hash(&mut h);
std::mem::discriminant(other).hash(&mut h);
}
}
}
proto.num_params.hash(&mut h);
proto.upvals.len().hash(&mut h);
proto.max_stack.hash(&mut h);
pre53.hash(&mut h);
float_only.hash(&mut h);
h.finish()
}
const MATH_LIBM_FNS: &[(&[u8], &str)] = &[
(b"sin", "sin"),
(b"cos", "cos"),
(b"tan", "tan"),
(b"asin", "asin"),
(b"acos", "acos"),
(b"atan", "atan"),
(b"exp", "exp"),
(b"log", "log"),
(b"sqrt", "sqrt"),
(b"floor", "floor"),
(b"ceil", "ceil"),
];
pub(crate) const TABLE_ARRAY_PTR_OFFSET: usize =
std::mem::offset_of!(luna_core::runtime::Table, array_ptr);
pub(crate) const TABLE_ASIZE_OFFSET: usize = std::mem::offset_of!(luna_core::runtime::Table, asize);
pub(crate) const TABLE_METATABLE_OFFSET: usize =
std::mem::offset_of!(luna_core::runtime::Table, metatable);
#[allow(dead_code)]
pub(crate) const TABLE_NODES_PTR_OFFSET: usize =
luna_core::runtime::table::jit_layout::TABLE_NODES_OFFSET;
#[allow(dead_code)]
pub(crate) const TABLE_NODES_LEN_OFFSET: usize = TABLE_NODES_PTR_OFFSET + 8;
#[allow(dead_code)]
pub(crate) const NODE_KEY_OFFSET: usize = luna_core::runtime::table::jit_layout::NODE_KEY_OFFSET;
#[allow(dead_code)]
pub(crate) const NODE_VAL_OFFSET: usize = luna_core::runtime::table::jit_layout::NODE_VAL_OFFSET;
#[allow(dead_code)]
pub(crate) const SIZEOF_NODE: usize = luna_core::runtime::table::jit_layout::SIZEOF_NODE;
#[allow(dead_code)]
pub(crate) const NODE_VAL_TAG_OFFSET: usize = NODE_VAL_OFFSET;
#[allow(dead_code)]
pub(crate) const NODE_VAL_RAW_OFFSET: usize = NODE_VAL_OFFSET + 8;
#[allow(dead_code)]
pub(crate) const NODE_KEY_RAW_OFFSET: usize = NODE_KEY_OFFSET + 8;
#[allow(dead_code)]
pub(crate) const NODE_KEY_TAG_OFFSET: usize = NODE_KEY_OFFSET;
const RAW_TAG_INT: i64 = luna_core::runtime::value::raw::INT as i64;
const RAW_TAG_FLOAT: i64 = luna_core::runtime::value::raw::FLOAT as i64;
const RAW_TAG_TABLE: i64 = luna_core::runtime::value::raw::TABLE as i64;
const RAW_TAG_NIL: i64 = luna_core::runtime::value::raw::NIL as i64;
const _: () = {
assert!(std::mem::size_of::<*mut u8>() == 8);
assert!(std::mem::size_of::<luna_core::runtime::value::RawVal>() == 8);
assert!(std::mem::align_of::<luna_core::runtime::value::RawVal>() == 8);
assert!(std::mem::size_of::<u64>() == 8);
};
#[derive(Clone, Copy)]
struct MathFold {
start_pc: usize,
fn_name: &'static str,
arg_reg: u32,
dst_reg: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct ChunkMeta {
pub num_args: u8,
pub returns_one: bool,
pub arg_float_mask: u8,
pub arg_table_mask: u8,
pub ret_is_float: bool,
pub ret_is_table: bool,
}
fn build_jit_module_with_helpers() -> Option<JITModule> {
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").ok();
flag_builder.set("is_pic", "false").ok();
flag_builder.set("opt_level", "speed").ok();
let isa = cranelift_native::builder()
.ok()?
.finish(settings::Flags::new(flag_builder))
.ok()?;
let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
builder.symbol("luna_jit_new_table", luna_jit_new_table as *const u8);
builder.symbol(
"luna_jit_new_table_sized",
luna_jit_new_table_sized as *const u8,
);
builder.symbol(
"luna_jit_table_set_int",
luna_jit_table_set_int as *const u8,
);
builder.symbol(
"luna_jit_table_set_float_float",
luna_jit_table_set_float_float as *const u8,
);
builder.symbol(
"luna_jit_table_get_int",
luna_jit_table_get_int as *const u8,
);
builder.symbol(
"luna_jit_table_get_float",
luna_jit_table_get_float as *const u8,
);
builder.symbol("luna_jit_table_len", luna_jit_table_len as *const u8);
builder.symbol("luna_jit_upval_get", luna_jit_upval_get as *const u8);
Some(JITModule::new(builder))
}
pub fn try_compile_int_chunk(proto: Gc<Proto>, pre53: bool, float_only: bool) -> Option<JitHandle> {
let mut module = build_jit_module_with_helpers()?;
let (fn_id, meta) = lower_int_chunk_into(&mut module, proto, pre53, float_only)?;
module.finalize_definitions().ok()?;
if std::env::var_os("LUNA_JIT_TRACE").is_some() {
let src_bytes = proto.source.as_bytes();
let src = std::str::from_utf8(src_bytes).unwrap_or("<non-utf8 source>");
let line_start = proto.line_defined;
let line_end = proto.last_line_defined;
let ChunkMeta {
num_args,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
..
} = meta;
eprintln!(
"[luna jit] {src}:{line_start}-{line_end} params={} code_len={} num_args={num_args} arg_float_mask={arg_float_mask:#x} arg_table_mask={arg_table_mask:#x} ret_is_float={ret_is_float} ret_is_table={ret_is_table}",
proto.num_params,
proto.code.len(),
);
}
let ptr = module.get_finalized_function(fn_id);
Some(JitHandle {
_module: SendJitModule::new(module),
entry_raw: ptr,
num_args: meta.num_args,
returns_one: meta.returns_one,
arg_float_mask: meta.arg_float_mask,
arg_table_mask: meta.arg_table_mask,
ret_is_float: meta.ret_is_float,
ret_is_table: meta.ret_is_table,
})
}
pub fn lower_int_chunk_into<M: Module>(
module: &mut M,
proto: Gc<Proto>,
pre53: bool,
float_only: bool,
) -> Option<(FuncId, ChunkMeta)> {
if proto.num_params > MAX_JIT_ARITY {
return None;
}
let num_params = proto.num_params as usize;
let allows_self_recursion = !proto.upvals.is_empty() && proto.upvals.len() <= 4;
let mut self_upval_idx: Option<u32> = None;
let n = proto.code.len();
let mut bb_starts = vec![false; n];
if n == 0 {
return None;
}
bb_starts[0] = true;
let mut sees_return1 = false;
let max_stack = (proto.max_stack as usize).max(num_params);
let mut self_upval: Vec<bool> = vec![false; max_stack];
let is_upval_value_read: Vec<bool> = determine_getupval_roles(&proto);
let mut self_call_pcs: Vec<bool> = vec![false; n];
let mut step_const: Vec<Option<i64>> = vec![None; max_stack];
let mut for_loops: Vec<(usize, usize, i64)> = Vec::new();
let mut defines_table: Vec<bool> = vec![false; max_stack];
for i in 0..num_params {
if let Some(slot) = defines_table.get_mut(i) {
*slot = true;
}
}
let mut presize_for_newtable: std::collections::HashMap<usize, i64> =
std::collections::HashMap::new();
let mut folded_math: Vec<bool> = vec![false; n];
let mut math_folds: Vec<MathFold> = Vec::new();
let env_upval_present = proto
.upvals
.first()
.map(|u| &*u.name == "_ENV")
.unwrap_or(false);
if env_upval_present {
let mut try_pc = 0usize;
while try_pc + 3 < n {
if let Some(fold) = try_match_math_fold(&proto, try_pc) {
folded_math[try_pc] = true;
folded_math[try_pc + 1] = true;
folded_math[try_pc + 2] = true;
folded_math[try_pc + 3] = true;
math_folds.push(fold);
try_pc += 4;
} else {
try_pc += 1;
}
}
}
let mut pc = 0;
while pc < n {
let ins = proto.code[pc];
match ins.op() {
Op::LoadI => {
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = Some(ins.sbx() as i64);
}
}
Op::LoadF => {
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
}
Op::LoadK => {
let bx = ins.bx() as usize;
let k = proto.consts.get(bx).copied();
if !matches!(k, Some(LuaValue::Float(_)) | Some(LuaValue::Int(_))) {
return None;
}
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = match k {
Some(LuaValue::Int(v)) => Some(v),
_ => None,
};
}
}
Op::LoadNil => {
let a = ins.a() as usize;
let b = ins.b() as usize;
for off in 0..=b {
let r = a + off;
if let Some(slot) = self_upval.get_mut(r) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(r) {
*slot = None;
}
if let Some(slot) = defines_table.get_mut(r) {
*slot = false;
}
}
}
Op::Move => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let tag = self_upval.get(b).copied().unwrap_or(false);
if let Some(slot) = self_upval.get_mut(a) {
*slot = tag;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
let src_def = defines_table.get(b).copied().unwrap_or(false);
if let Some(slot) = defines_table.get_mut(a) {
*slot = src_def;
}
}
Op::Add | Op::Sub | Op::Mul | Op::Div => {
let b = ins.b() as usize;
let c = ins.c() as usize;
if self_upval.get(b).copied().unwrap_or(false)
|| self_upval.get(c).copied().unwrap_or(false)
{
return None;
}
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
}
Op::GetUpval => {
let b = ins.b();
if (b as usize) >= proto.upvals.len() {
return None;
}
if is_upval_value_read[pc] {
if !float_only {
return None;
}
if let Some(slot) = step_const.get_mut(ins.a() as usize) {
*slot = None;
}
if let Some(slot) = defines_table.get_mut(ins.a() as usize) {
*slot = false;
}
pc += 1;
continue;
}
if !allows_self_recursion {
return None;
}
match self_upval_idx {
Some(idx) if idx != b => return None,
Some(_) => {}
None => self_upval_idx = Some(b),
}
if let Some(slot) = self_upval.get_mut(ins.a() as usize) {
*slot = true;
}
if let Some(slot) = step_const.get_mut(ins.a() as usize) {
*slot = None;
}
}
Op::Call => {
let a = ins.a() as usize;
let nargs = ins.b().checked_sub(1)?;
let c = ins.c();
let next_is_variadic_setlist = c == 0
&& pc + 1 < n
&& matches!(proto.code[pc + 1].op(), Op::SetList)
&& proto.code[pc + 1].b() == 0;
let nresults = if next_is_variadic_setlist {
1
} else {
c.checked_sub(1)?
};
if nargs > MAX_JIT_ARITY as u32 || nresults != 1 {
return None;
}
if folded_math[pc] {
} else if self_upval.get(a).copied().unwrap_or(false) {
self_call_pcs[pc] = true;
} else {
return None;
}
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
}
Op::GetTabUp | Op::GetField => {
if !folded_math[pc] {
return None;
}
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
}
Op::Return1 => {
if self_upval.get(ins.a() as usize).copied().unwrap_or(false) {
return None;
}
sees_return1 = true;
if pc + 1 < n {
bb_starts[pc + 1] = true;
}
}
Op::Return0 => {
if pc + 1 < n {
bb_starts[pc + 1] = true;
}
}
Op::Jmp => {
let tgt = jmp_target(pc, ins);
if tgt >= n {
return None;
}
bb_starts[tgt] = true;
if pc + 1 < n {
bb_starts[pc + 1] = true;
}
}
Op::Lt | Op::Le | Op::Eq => {
if self_upval.get(ins.a() as usize).copied().unwrap_or(false)
|| self_upval.get(ins.b() as usize).copied().unwrap_or(false)
{
return None;
}
let &jmp = proto.code.get(pc + 1)?;
if !matches!(jmp.op(), Op::Jmp) {
return None;
}
let jmp_pc = pc + 1;
let tgt = jmp_target(jmp_pc, jmp);
if tgt >= n {
return None;
}
bb_starts[tgt] = true;
if jmp_pc + 1 < n {
bb_starts[jmp_pc + 1] = true;
}
pc = jmp_pc; }
Op::ForPrep => {
let a = ins.a() as usize;
let step_imm = step_const.get(a + 2).copied().flatten()?;
if step_imm == 0 {
return None;
}
let loop_pc = pc + ins.bx() as usize;
if loop_pc >= n {
return None;
}
let loop_ins = proto.code[loop_pc];
if !matches!(loop_ins.op(), Op::ForLoop) || loop_ins.a() as usize != a {
return None;
}
bb_starts[pc + 1] = true;
if loop_pc + 1 < n {
bb_starts[loop_pc + 1] = true;
}
bb_starts[loop_pc] = true; for_loops.push((pc, loop_pc, step_imm));
for off in 0..=3 {
if let Some(slot) = step_const.get_mut(a + off) {
*slot = None;
}
if let Some(slot) = self_upval.get_mut(a + off) {
*slot = false;
}
}
}
Op::ForLoop => {
let a = ins.a() as usize;
if !for_loops.iter().any(|&(_, lp, _)| lp == pc) {
return None;
}
for off in [0usize, 1, 3] {
if let Some(slot) = step_const.get_mut(a + off) {
*slot = None;
}
if let Some(slot) = self_upval.get_mut(a + off) {
*slot = false;
}
}
}
Op::NewTable => {
if ins.c() != 0 {
return None;
}
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
if let Some(slot) = defines_table.get_mut(a) {
*slot = true;
}
}
Op::SetTable => {
}
Op::SetList => {
let b = ins.b();
if ins.c() != 0 {
return None;
}
if b == 0 {
if pc == 0 {
return None;
}
let prev = proto.code[pc - 1];
if !matches!(prev.op(), Op::Call) || prev.c() != 0 {
return None;
}
let a_call = prev.a() as i64;
let a_list = ins.a() as i64;
if a_call <= a_list {
return None;
}
}
}
Op::GetI => {
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
if let Some(slot) = defines_table.get_mut(a) {
*slot = false;
}
}
Op::GetTable => {
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
if let Some(slot) = defines_table.get_mut(a) {
*slot = false;
}
}
Op::Len => {
let a = ins.a() as usize;
if let Some(slot) = self_upval.get_mut(a) {
*slot = false;
}
if let Some(slot) = step_const.get_mut(a) {
*slot = None;
}
if let Some(slot) = defines_table.get_mut(a) {
*slot = false;
}
}
_ => return None,
}
pc += 1;
}
let bb_pcs: Vec<usize> = (0..n)
.filter(|&p| bb_starts.get(p).copied().unwrap_or(false))
.collect();
let num_bbs = bb_pcs.len();
if num_bbs == 0 {
return None;
}
let mut pc_to_bb: Vec<usize> = vec![0; n];
for (idx, &start) in bb_pcs.iter().enumerate() {
let end = bb_pcs.get(idx + 1).copied().unwrap_or(n);
for p in start..end {
pc_to_bb[p] = idx;
}
}
let mut bb_successors: Vec<Vec<usize>> = vec![Vec::new(); num_bbs];
for bb_idx in 0..num_bbs {
let bb_start = bb_pcs[bb_idx];
let bb_end = bb_pcs.get(bb_idx + 1).copied().unwrap_or(n);
let mut found_terminator = false;
let mut p = bb_start;
while p < bb_end {
let ins = proto.code[p];
match ins.op() {
Op::Jmp => {
let tgt = jmp_target(p, ins);
if tgt < n {
let s = pc_to_bb[tgt];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
found_terminator = true;
break;
}
Op::Lt | Op::Le | Op::Eq => {
let jmp = proto.code[p + 1];
let tgt = jmp_target(p + 1, jmp);
if tgt < n {
let s = pc_to_bb[tgt];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
let fall = p + 2;
if fall < n {
let s = pc_to_bb[fall];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
found_terminator = true;
break;
}
Op::Return0 | Op::Return1 => {
found_terminator = true;
break;
}
Op::ForPrep => {
let fall = p + 1;
if fall < n {
let s = pc_to_bb[fall];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
if let Some(&(_, lp, _)) = for_loops.iter().find(|&&(pp, _, _)| pp == p) {
let exit_pc = lp + 1;
if exit_pc < n {
let s = pc_to_bb[exit_pc];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
}
found_terminator = true;
break;
}
Op::ForLoop => {
let exit_pc = p + 1;
if exit_pc < n {
let s = pc_to_bb[exit_pc];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
if let Some(&(prep, _, _)) = for_loops.iter().find(|&&(_, lp, _)| lp == p) {
let body = prep + 1;
if body < n {
let s = pc_to_bb[body];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
}
found_terminator = true;
break;
}
_ => {
p += 1;
}
}
}
if !found_terminator && bb_end < n {
let s = pc_to_bb[bb_end];
if !bb_successors[bb_idx].contains(&s) {
bb_successors[bb_idx].push(s);
}
}
}
let mut bb_predecessors: Vec<Vec<usize>> = vec![Vec::new(); num_bbs];
for src in 0..num_bbs {
for &dst in &bb_successors[src] {
if !bb_predecessors[dst].contains(&src) {
bb_predecessors[dst].push(src);
}
}
}
let body_apply = |bb_idx: usize, state: &mut Vec<bool>| {
let bb_start = bb_pcs[bb_idx];
let bb_end = bb_pcs.get(bb_idx + 1).copied().unwrap_or(n);
for p in bb_start..bb_end {
let ins = proto.code[p];
match ins.op() {
Op::NewTable => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = true;
}
}
Op::Move => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let src_def = state.get(b).copied().unwrap_or(false);
if let Some(slot) = state.get_mut(a) {
*slot = src_def;
}
}
Op::GetI | Op::GetTable | Op::Len => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = false;
}
}
Op::LoadI | Op::LoadF | Op::LoadK | Op::Add | Op::Sub | Op::Mul | Op::Div => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = false;
}
}
Op::LoadNil => {
let a = ins.a() as usize;
for off in 0..=(ins.b() as usize) {
if let Some(slot) = state.get_mut(a + off) {
*slot = false;
}
}
}
Op::Call => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = false;
}
}
Op::GetUpval | Op::GetTabUp | Op::GetField => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = false;
}
}
Op::ForPrep | Op::ForLoop => {
let a = ins.a() as usize;
for off in 0..=3 {
if let Some(slot) = state.get_mut(a + off) {
*slot = false;
}
}
}
_ => {}
}
}
};
let mut bb_entry: Vec<Vec<bool>> = (0..num_bbs).map(|i| vec![i != 0; max_stack]).collect();
let mut bb_exit: Vec<Vec<bool>> = vec![vec![true; max_stack]; num_bbs];
for i in 0..max_stack {
if let Some(slot) = bb_entry[0].get_mut(i) {
*slot = i < num_params;
}
}
let mut iters = 0;
let max_iters = num_bbs * (max_stack + 2);
let mut changed = true;
while changed && iters < max_iters {
changed = false;
iters += 1;
for bb_idx in 0..num_bbs {
let new_entry = if bb_predecessors[bb_idx].is_empty() {
bb_entry[bb_idx].clone()
} else {
let mut e = bb_exit[bb_predecessors[bb_idx][0]].clone();
for &pred in &bb_predecessors[bb_idx][1..] {
for (i, val) in bb_exit[pred].iter().enumerate() {
e[i] &= val;
}
}
if bb_idx == 0 {
for i in 0..num_params {
if let Some(slot) = e.get_mut(i) {
*slot = true;
}
}
}
e
};
let mut state = new_entry.clone();
body_apply(bb_idx, &mut state);
if state != bb_exit[bb_idx] {
bb_exit[bb_idx] = state;
changed = true;
}
if new_entry != bb_entry[bb_idx] {
bb_entry[bb_idx] = new_entry;
changed = true;
}
}
}
for p in 0..n {
let ins = proto.code[p];
let check_reg = match ins.op() {
Op::SetTable | Op::SetList => Some(ins.a() as usize),
Op::GetI | Op::GetTable | Op::Len => Some(ins.b() as usize),
_ => None,
};
if let Some(reg) = check_reg {
let bb_idx = pc_to_bb[p];
let bb_start = bb_pcs[bb_idx];
let mut state = bb_entry[bb_idx].clone();
for q in bb_start..p {
let prev = proto.code[q];
match prev.op() {
Op::NewTable => {
if let Some(slot) = state.get_mut(prev.a() as usize) {
*slot = true;
}
}
Op::Move => {
let a = prev.a() as usize;
let b = prev.b() as usize;
let src_def = state.get(b).copied().unwrap_or(false);
if let Some(slot) = state.get_mut(a) {
*slot = src_def;
}
}
Op::GetI | Op::GetTable | Op::Len => {
if let Some(slot) = state.get_mut(prev.a() as usize) {
*slot = false;
}
}
Op::LoadI | Op::LoadF | Op::LoadK | Op::Add | Op::Sub | Op::Mul | Op::Div => {
if let Some(slot) = state.get_mut(prev.a() as usize) {
*slot = false;
}
}
Op::LoadNil => {
let a = prev.a() as usize;
for off in 0..=(prev.b() as usize) {
if let Some(slot) = state.get_mut(a + off) {
*slot = false;
}
}
}
Op::Call => {
if let Some(slot) = state.get_mut(prev.a() as usize) {
*slot = false;
}
}
Op::GetUpval | Op::GetTabUp | Op::GetField => {
if let Some(slot) = state.get_mut(prev.a() as usize) {
*slot = false;
}
}
Op::ForPrep | Op::ForLoop => {
let a = prev.a() as usize;
for off in 0..=3 {
if let Some(slot) = state.get_mut(a + off) {
*slot = false;
}
}
}
_ => {}
}
}
if !state.get(reg).copied().unwrap_or(false) {
return None;
}
}
}
for &(prep_pc, _, step_imm) in &for_loops {
if step_imm != 1 || prep_pc < 4 {
continue;
}
let nt_pc = prep_pc - 4;
let init_pc = prep_pc - 3;
let limit_pc = prep_pc - 2;
let step_pc = prep_pc - 1;
let nt = proto.code[nt_pc];
let init = proto.code[init_pc];
let limit = proto.code[limit_pc];
let step = proto.code[step_pc];
let fp = proto.code[prep_pc];
if !matches!(nt.op(), Op::NewTable) {
continue;
}
if nt.b() != 0 || nt.c() != 0 {
continue;
}
let fp_base = fp.a() as i64;
if (nt.a() as i64) + 1 != fp_base {
continue;
}
if !matches!(init.op(), Op::LoadI) || init.a() as i64 != fp_base || init.sbx() != 1 {
continue;
}
let limit_val: i64 = match limit.op() {
Op::LoadI if limit.a() as i64 == fp_base + 1 => limit.sbx() as i64,
Op::LoadK if limit.a() as i64 == fp_base + 1 => {
let bx = limit.bx() as usize;
match proto.consts.get(bx).copied() {
Some(LuaValue::Int(v)) => v,
_ => continue,
}
}
_ => continue,
};
if !matches!(step.op(), Op::LoadI) || step.a() as i64 != fp_base + 2 || step.sbx() != 1 {
continue;
}
if limit_val <= 0 || limit_val > (1 << 27) {
continue;
}
presize_for_newtable.insert(nt_pc, limit_val);
}
for fold in &math_folds {
for off in 1..=3 {
if bb_starts.get(fold.start_pc + off).copied().unwrap_or(false) {
return None;
}
}
}
let any_self_call = self_call_pcs.iter().any(|&b| b);
if any_self_call {
let mut visited = vec![false; n];
let mut stack = vec![0usize];
visited[0] = true;
let mut safe_return_reached = false;
while let Some(pc) = stack.pop() {
let ins = proto.code[pc];
match ins.op() {
Op::Return0 | Op::Return1 => {
safe_return_reached = true;
break;
}
Op::Jmp => {
let tgt = jmp_target(pc, ins);
if tgt < n && !visited[tgt] {
visited[tgt] = true;
stack.push(tgt);
}
}
Op::Lt | Op::Le | Op::Eq => {
let jmp = proto.code[pc + 1];
let jmp_tgt = jmp_target(pc + 1, jmp);
if jmp_tgt < n && !visited[jmp_tgt] {
visited[jmp_tgt] = true;
stack.push(jmp_tgt);
}
let fall = pc + 2;
if fall < n && !visited[fall] {
visited[fall] = true;
stack.push(fall);
}
}
Op::Call if self_call_pcs[pc] => {
}
Op::ForPrep => {
let fall = pc + 1;
if fall < n && !visited[fall] {
visited[fall] = true;
stack.push(fall);
}
if let Some(&(_, lp, _)) = for_loops.iter().find(|&&(p, _, _)| p == pc) {
let exit = lp + 1;
if exit < n && !visited[exit] {
visited[exit] = true;
stack.push(exit);
}
}
}
_ => {
let fall = pc + 1;
if fall < n && !visited[fall] {
visited[fall] = true;
stack.push(fall);
}
}
}
}
if !safe_return_reached {
return None;
}
}
let mut reg_kinds: Vec<RegKind> = vec![RegKind::Unset; max_stack];
let mut ret_kind: RegKind = RegKind::Unset;
let mut latest_writer_kind: Vec<RegKind>;
let mut maybe_table: Vec<bool>;
let mut is_nil_writer: Vec<bool>;
for _ in 0..4 {
let pre_regs = reg_kinds.clone();
let pre_ret = ret_kind;
latest_writer_kind = vec![RegKind::Unset; max_stack];
maybe_table = vec![false; max_stack];
is_nil_writer = vec![false; max_stack];
for r in num_params..max_stack {
is_nil_writer[r] = true;
}
let mut pc = 0;
while pc < n {
let ins = proto.code[pc];
match ins.op() {
Op::LoadI => {
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], RegKind::Int) {
return None;
}
latest_writer_kind[ins.a() as usize] = RegKind::Int;
maybe_table[ins.a() as usize] = false;
is_nil_writer[ins.a() as usize] = false;
}
Op::LoadF => {
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], RegKind::Float) {
return None;
}
latest_writer_kind[ins.a() as usize] = RegKind::Float;
maybe_table[ins.a() as usize] = false;
is_nil_writer[ins.a() as usize] = false;
}
Op::LoadK => {
let bx = ins.bx() as usize;
let kind = match proto.consts[bx] {
LuaValue::Float(_) => RegKind::Float,
LuaValue::Int(_) => RegKind::Int,
_ => unreachable!("whitelist gates non-numeric consts"),
};
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], kind) {
return None;
}
latest_writer_kind[ins.a() as usize] = kind;
maybe_table[ins.a() as usize] = false;
is_nil_writer[ins.a() as usize] = false;
}
Op::LoadNil => {
let a = ins.a() as usize;
let b = ins.b() as usize;
for off in 0..=b {
let r = a + off;
maybe_table[r] = false;
is_nil_writer[r] = true;
}
}
Op::Move => {
if folded_math[pc] {
} else {
let src_kind = reg_kinds[ins.b() as usize];
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], src_kind) {
return None;
}
let lwk = latest_writer_kind[ins.b() as usize];
latest_writer_kind[ins.a() as usize] = lwk;
maybe_table[ins.a() as usize] = maybe_table[ins.b() as usize];
is_nil_writer[ins.a() as usize] = is_nil_writer[ins.b() as usize];
}
}
Op::Add | Op::Sub | Op::Mul | Op::Div => {
let b = ins.b() as usize;
let c = ins.c() as usize;
if matches!(reg_kinds[b], RegKind::Table)
|| matches!(reg_kinds[c], RegKind::Table)
|| matches!(latest_writer_kind[b], RegKind::Table)
|| matches!(latest_writer_kind[c], RegKind::Table)
|| maybe_table[b]
|| maybe_table[c]
{
return None;
}
if is_nil_writer[b] || is_nil_writer[c] {
return None;
}
let kb = reg_kinds[b];
let kc = reg_kinds[c];
if !RegKind::unify(&mut reg_kinds[b], kc) {
return None;
}
if !RegKind::unify(&mut reg_kinds[c], kb) {
return None;
}
let merged = reg_kinds[b];
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], merged) {
return None;
}
if matches!(ins.op(), Op::Div)
&& !RegKind::unify(&mut reg_kinds[ins.a() as usize], RegKind::Float)
{
return None;
}
maybe_table[ins.a() as usize] = false;
is_nil_writer[ins.a() as usize] = false;
let lwk_b = latest_writer_kind[b];
let lwk_c = latest_writer_kind[c];
let arith_kind = if matches!(lwk_b, RegKind::Float)
|| matches!(lwk_c, RegKind::Float)
|| matches!(ins.op(), Op::Div)
{
RegKind::Float
} else {
RegKind::Int
};
latest_writer_kind[ins.a() as usize] = arith_kind;
}
Op::Lt | Op::Le | Op::Eq => {
let a = ins.a() as usize;
let b = ins.b() as usize;
if matches!(ins.op(), Op::Lt | Op::Le)
&& (matches!(reg_kinds[a], RegKind::Table)
|| matches!(reg_kinds[b], RegKind::Table)
|| matches!(latest_writer_kind[a], RegKind::Table)
|| matches!(latest_writer_kind[b], RegKind::Table)
|| maybe_table[a]
|| maybe_table[b])
{
return None;
}
if is_nil_writer[a] || is_nil_writer[b] {
return None;
}
let ka = reg_kinds[a];
let kb = reg_kinds[b];
if !RegKind::unify(&mut reg_kinds[a], kb) {
return None;
}
if !RegKind::unify(&mut reg_kinds[b], ka) {
return None;
}
}
Op::GetUpval => {
if is_upval_value_read[pc] {
let a = ins.a() as usize;
if !RegKind::unify(&mut reg_kinds[a], RegKind::Float) {
return None;
}
latest_writer_kind[a] = RegKind::Float;
maybe_table[a] = false;
is_nil_writer[a] = false;
}
}
Op::Call => {
if folded_math[pc] {
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], RegKind::Float) {
return None;
}
latest_writer_kind[ins.a() as usize] = RegKind::Float;
maybe_table[ins.a() as usize] = false;
is_nil_writer[ins.a() as usize] = false;
} else {
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], ret_kind) {
return None;
}
if !matches!(ret_kind, RegKind::Unset) {
latest_writer_kind[ins.a() as usize] = ret_kind;
}
maybe_table[ins.a() as usize] = false;
is_nil_writer[ins.a() as usize] = false;
}
}
Op::GetTabUp | Op::GetField => {
if !folded_math[pc] {
return None;
}
}
Op::Return1 => {
if is_nil_writer[ins.a() as usize] {
return None;
}
let a_kind = latest_writer_kind[ins.a() as usize];
let a_kind = if matches!(a_kind, RegKind::Unset) {
reg_kinds[ins.a() as usize]
} else {
a_kind
};
if !RegKind::unify(&mut ret_kind, a_kind) {
return None;
}
if matches!(reg_kinds[ins.a() as usize], RegKind::Unset)
&& !RegKind::unify(&mut reg_kinds[ins.a() as usize], ret_kind)
{
return None;
}
}
Op::Return0 | Op::Jmp => {}
Op::ForPrep | Op::ForLoop => {
let a = ins.a() as usize;
let loop_kind = match reg_kinds[a] {
RegKind::Float => RegKind::Float,
RegKind::Int | RegKind::Unset => RegKind::Int,
RegKind::Table => return None,
};
for off in [0usize, 1, 2, 3] {
if matches!(latest_writer_kind[a + off], RegKind::Table)
|| maybe_table[a + off]
{
return None;
}
}
for off in [0usize, 1, 3] {
if !RegKind::unify(&mut reg_kinds[a + off], loop_kind) {
return None;
}
latest_writer_kind[a + off] = loop_kind;
maybe_table[a + off] = false;
is_nil_writer[a + off] = false;
}
if !RegKind::unify(&mut reg_kinds[a + 2], RegKind::Int) {
return None;
}
latest_writer_kind[a + 2] = RegKind::Int;
maybe_table[a + 2] = false;
is_nil_writer[a + 2] = false;
}
Op::NewTable => {
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], RegKind::Table) {
return None;
}
latest_writer_kind[ins.a() as usize] = RegKind::Table;
maybe_table[ins.a() as usize] = false;
is_nil_writer[ins.a() as usize] = false;
}
Op::SetList => {
if !RegKind::unify(&mut reg_kinds[ins.a() as usize], RegKind::Table) {
return None;
}
}
Op::SetTable => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let c = ins.c() as usize;
if !RegKind::unify(&mut reg_kinds[a], RegKind::Table) {
return None;
}
if is_nil_writer[b] || is_nil_writer[c] {
return None;
}
let kb = reg_kinds[b];
let kc = reg_kinds[c];
if !RegKind::unify(&mut reg_kinds[b], kc) {
return None;
}
if !RegKind::unify(&mut reg_kinds[c], kb) {
return None;
}
let resolved = reg_kinds[b];
if matches!(resolved, RegKind::Unset) {
if !RegKind::unify(&mut reg_kinds[b], RegKind::Int) {
return None;
}
if !RegKind::unify(&mut reg_kinds[c], RegKind::Int) {
return None;
}
} else if !matches!(resolved, RegKind::Int | RegKind::Float) {
return None;
}
}
Op::GetI => {
let a = ins.a() as usize;
let b = ins.b() as usize;
if !RegKind::unify(&mut reg_kinds[b], RegKind::Table) {
return None;
}
if !RegKind::unify(&mut reg_kinds[a], RegKind::Int) {
return None;
}
maybe_table[a] = true;
is_nil_writer[a] = false;
}
Op::GetTable => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let c = ins.c() as usize;
if !RegKind::unify(&mut reg_kinds[b], RegKind::Table) {
return None;
}
if matches!(reg_kinds[c], RegKind::Table)
|| matches!(latest_writer_kind[c], RegKind::Table)
|| maybe_table[c]
{
return None;
}
if is_nil_writer[c] {
return None;
}
let default_kind = if float_only {
RegKind::Float
} else {
RegKind::Int
};
if matches!(reg_kinds[a], RegKind::Unset) {
reg_kinds[a] = default_kind;
}
latest_writer_kind[a] = default_kind;
maybe_table[a] = true;
is_nil_writer[a] = false;
}
Op::Len => {
let a = ins.a() as usize;
let b = ins.b() as usize;
if !RegKind::unify(&mut reg_kinds[b], RegKind::Table) {
return None;
}
match reg_kinds[a] {
RegKind::Int | RegKind::Float => { }
RegKind::Unset => {
reg_kinds[a] = RegKind::Int;
}
RegKind::Table => return None,
}
latest_writer_kind[a] = RegKind::Int;
maybe_table[a] = false;
is_nil_writer[a] = false;
}
_ => return None,
}
pc += 1;
}
if reg_kinds == pre_regs && ret_kind == pre_ret {
break;
}
}
let mut arg_float_mask: u8 = 0;
let mut arg_table_mask: u8 = 0;
for i in 0..num_params {
match reg_kinds[i] {
RegKind::Float => arg_float_mask |= 1 << i,
RegKind::Table => arg_table_mask |= 1 << i,
_ => {}
}
}
let ret_is_float = matches!(ret_kind, RegKind::Float);
let ret_is_table = matches!(ret_kind, RegKind::Table);
let init_kind_for_reg = |i: usize| -> RegKind {
if i < num_params {
if (arg_float_mask >> i) & 1 == 1 {
RegKind::Float
} else if (arg_table_mask >> i) & 1 == 1 {
RegKind::Table
} else {
RegKind::Int
}
} else {
RegKind::Unset
}
};
let meet_kind = |a: RegKind, b: RegKind| -> RegKind {
match (a, b) {
(RegKind::Unset, x) | (x, RegKind::Unset) => x,
(x, y) if x == y => x,
_ => RegKind::Unset,
}
};
let body_apply_kinds = |bb_idx: usize, state: &mut Vec<RegKind>| {
let bb_start = bb_pcs[bb_idx];
let bb_end = bb_pcs.get(bb_idx + 1).copied().unwrap_or(n);
for p in bb_start..bb_end {
let ins = proto.code[p];
if folded_math[p] {
if let Some(fold) = math_folds.iter().find(|f| f.start_pc == p) {
if let Some(slot) = state.get_mut(fold.dst_reg as usize) {
*slot = RegKind::Float;
}
}
continue;
}
match ins.op() {
Op::LoadI => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = RegKind::Int;
}
}
Op::LoadF => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = RegKind::Float;
}
}
Op::LoadK => {
let k = match proto.consts.get(ins.bx() as usize) {
Some(LuaValue::Float(_)) => RegKind::Float,
Some(LuaValue::Int(_)) => RegKind::Int,
_ => RegKind::Unset,
};
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = k;
}
}
Op::Move => {
let src_kind = state
.get(ins.b() as usize)
.copied()
.unwrap_or(RegKind::Unset);
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = src_kind;
}
}
Op::Add | Op::Sub | Op::Mul | Op::Div => {
let k = reg_kinds
.get(ins.a() as usize)
.copied()
.unwrap_or(RegKind::Unset);
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = k;
}
}
Op::Call => {
if !matches!(ret_kind, RegKind::Unset) {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = ret_kind;
}
}
}
Op::ForPrep => {
let a = ins.a() as usize;
let is_float = matches!(
reg_kinds.get(a).copied().unwrap_or(RegKind::Unset),
RegKind::Float
);
match (pre53, is_float) {
(true, false) => {
if let Some(s) = state.get_mut(a) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 1) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 2) {
*s = RegKind::Int;
}
}
(false, false) => {
if let Some(s) = state.get_mut(a) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 1) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 2) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 3) {
*s = RegKind::Int;
}
}
(true, true) => {
if let Some(s) = state.get_mut(a) {
*s = RegKind::Float;
}
if let Some(s) = state.get_mut(a + 1) {
*s = RegKind::Float;
}
if let Some(s) = state.get_mut(a + 2) {
*s = RegKind::Int;
}
}
(false, true) => {
if let Some(s) = state.get_mut(a) {
*s = RegKind::Float;
}
if let Some(s) = state.get_mut(a + 1) {
*s = RegKind::Float;
}
if let Some(s) = state.get_mut(a + 2) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 3) {
*s = RegKind::Float;
}
}
}
}
Op::ForLoop => {
let a = ins.a() as usize;
let is_float = matches!(
reg_kinds.get(a).copied().unwrap_or(RegKind::Unset),
RegKind::Float
);
if is_float {
if let Some(s) = state.get_mut(a) {
*s = RegKind::Float;
}
if let Some(s) = state.get_mut(a + 3) {
*s = RegKind::Float;
}
} else if pre53 {
if let Some(s) = state.get_mut(a) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 3) {
*s = RegKind::Int;
}
} else {
if let Some(s) = state.get_mut(a) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 1) {
*s = RegKind::Int;
}
if let Some(s) = state.get_mut(a + 3) {
*s = RegKind::Int;
}
}
}
Op::NewTable => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = RegKind::Table;
}
}
Op::GetI | Op::GetTable => {
let k = reg_kinds
.get(ins.a() as usize)
.copied()
.unwrap_or(RegKind::Unset);
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = k;
}
}
Op::LoadNil => {
let k = reg_kinds
.get(ins.a() as usize)
.copied()
.unwrap_or(RegKind::Int);
let a = ins.a() as usize;
for off in 0..=(ins.b() as usize) {
if let Some(slot) = state.get_mut(a + off) {
*slot = k;
}
}
}
Op::Len => {
if let Some(slot) = state.get_mut(ins.a() as usize) {
*slot = RegKind::Int;
}
}
_ => {}
}
}
};
let mut bb_entry_kinds: Vec<Vec<RegKind>> = (0..num_bbs)
.map(|_| vec![RegKind::Unset; max_stack])
.collect();
let mut bb_exit_kinds: Vec<Vec<RegKind>> = (0..num_bbs)
.map(|_| vec![RegKind::Unset; max_stack])
.collect();
for i in 0..max_stack {
bb_entry_kinds[0][i] = init_kind_for_reg(i);
}
let max_iters_kinds = num_bbs * (max_stack + 2);
let mut iters_kinds = 0;
let mut changed_kinds = true;
while changed_kinds && iters_kinds < max_iters_kinds {
changed_kinds = false;
iters_kinds += 1;
for bb_idx in 0..num_bbs {
let new_entry = if bb_predecessors[bb_idx].is_empty() {
bb_entry_kinds[bb_idx].clone()
} else {
let mut e = bb_exit_kinds[bb_predecessors[bb_idx][0]].clone();
for &pred in &bb_predecessors[bb_idx][1..] {
for (i, val) in bb_exit_kinds[pred].iter().enumerate() {
e[i] = meet_kind(e[i], *val);
}
}
if bb_idx == 0 {
for i in 0..max_stack {
e[i] = init_kind_for_reg(i);
}
}
e
};
let mut state = new_entry.clone();
body_apply_kinds(bb_idx, &mut state);
if state != bb_exit_kinds[bb_idx] {
bb_exit_kinds[bb_idx] = state;
changed_kinds = true;
}
if new_entry != bb_entry_kinds[bb_idx] {
bb_entry_kinds[bb_idx] = new_entry;
changed_kinds = true;
}
}
}
let mut sig = module.make_signature();
for _ in 0..num_params {
sig.params.push(AbiParam::new(types::I64));
}
sig.returns.push(AbiParam::new(types::I64));
let fn_id = module
.declare_function("luna_jit_chunk", Linkage::Local, &sig)
.ok()?;
let mut ctx = module.make_context();
ctx.func.signature = sig;
ctx.func.name = UserFuncName::user(0, fn_id.as_u32());
let mut fbc = FunctionBuilderContext::new();
let mut bcx = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let mut pc_to_block: Vec<Option<Block>> = vec![None; n];
for pc_i in 0..n {
if bb_starts[pc_i] {
pc_to_block[pc_i] = Some(bcx.create_block());
}
}
let entry = pc_to_block[0].expect("entry block exists");
bcx.append_block_params_for_function_params(entry);
bcx.switch_to_block(entry);
let max_stack = (proto.max_stack as usize).max(num_params);
let mut regs: Vec<Variable> = Vec::with_capacity(max_stack);
let entry_block_params: Vec<_> = bcx.block_params(entry).to_vec();
for i in 0..max_stack {
let cl_ty = match reg_kinds.get(i).copied().unwrap_or(RegKind::Unset) {
RegKind::Float => types::F64,
RegKind::Int | RegKind::Unset | RegKind::Table => types::I64,
};
let v = bcx.declare_var(cl_ty);
let init = if i < num_params {
let raw = entry_block_params[i];
if cl_ty == types::F64 {
bcx.ins().bitcast(types::F64, MemFlags::new(), raw)
} else {
raw
}
} else if cl_ty == types::F64 {
bcx.ins().f64const(0.0)
} else {
bcx.ins().iconst(types::I64, 0)
};
bcx.def_var(v, init);
regs.push(v);
}
let mut current_kinds: Vec<RegKind> = vec![RegKind::Unset; max_stack];
for i in 0..num_params {
current_kinds[i] = if (arg_float_mask >> i) & 1 == 1 {
RegKind::Float
} else if (arg_table_mask >> i) & 1 == 1 {
RegKind::Table
} else {
RegKind::Int
};
}
let mut current_is_nil: Vec<bool> = vec![false; max_stack];
let mut current_block = entry;
let mut terminated = false;
let mut pc = 0;
while pc < n {
if pc != 0 && bb_starts[pc] {
let next_blk = pc_to_block[pc].expect("BB present");
if !terminated {
bcx.ins().jump(next_blk, &[]);
}
bcx.switch_to_block(next_blk);
current_block = next_blk;
terminated = false;
let new_bb_idx = pc_to_bb[pc];
current_kinds = bb_entry_kinds[new_bb_idx].clone();
for slot in current_is_nil.iter_mut() {
*slot = false;
}
}
let _ = current_block; let ins = proto.code[pc];
let a_kind = |k: &[RegKind], idx: u32| k.get(idx as usize).copied().unwrap_or(RegKind::Int);
match ins.op() {
Op::LoadI => {
let imm = ins.sbx() as i64;
let v = bcx.ins().iconst(types::I64, imm);
aligned_def(&mut bcx, ®s, ®_kinds, ins.a() as usize, v);
current_kinds[ins.a() as usize] = RegKind::Int;
current_is_nil[ins.a() as usize] = false;
}
Op::LoadF => {
let f = ins.sbx() as f64;
let v = bcx.ins().f64const(f);
aligned_def(&mut bcx, ®s, ®_kinds, ins.a() as usize, v);
current_kinds[ins.a() as usize] = RegKind::Float;
current_is_nil[ins.a() as usize] = false;
}
Op::LoadK => {
let bx = ins.bx() as usize;
let (v, k) = match proto.consts[bx] {
LuaValue::Float(f) => (bcx.ins().f64const(f), RegKind::Float),
LuaValue::Int(i) => (bcx.ins().iconst(types::I64, i), RegKind::Int),
_ => unreachable!("scanner rejects non-numeric LoadK"),
};
aligned_def(&mut bcx, ®s, ®_kinds, ins.a() as usize, v);
current_kinds[ins.a() as usize] = k;
current_is_nil[ins.a() as usize] = false;
}
Op::LoadNil => {
let zero = bcx.ins().iconst(types::I64, 0);
let a = ins.a() as usize;
for off in 0..=(ins.b() as usize) {
let r = a + off;
aligned_def(&mut bcx, ®s, ®_kinds, r, zero);
current_is_nil[r] = true;
}
}
Op::Move => {
let src = bcx.use_var(regs[ins.b() as usize]);
aligned_def(&mut bcx, ®s, ®_kinds, ins.a() as usize, src);
current_kinds[ins.a() as usize] = current_kinds[ins.b() as usize];
current_is_nil[ins.a() as usize] = current_is_nil[ins.b() as usize];
}
Op::Add | Op::Sub | Op::Mul | Op::Div => {
let lhs = bcx.use_var(regs[ins.b() as usize]);
let rhs = bcx.use_var(regs[ins.c() as usize]);
let k = a_kind(®_kinds, ins.a());
let r = match (ins.op(), k) {
(Op::Add, RegKind::Float) => bcx.ins().fadd(lhs, rhs),
(Op::Sub, RegKind::Float) => bcx.ins().fsub(lhs, rhs),
(Op::Mul, RegKind::Float) => bcx.ins().fmul(lhs, rhs),
(Op::Div, RegKind::Float) => bcx.ins().fdiv(lhs, rhs),
(Op::Add, _) => bcx.ins().iadd(lhs, rhs),
(Op::Sub, _) => bcx.ins().isub(lhs, rhs),
(Op::Mul, _) => bcx.ins().imul(lhs, rhs),
(Op::Div, _) => unreachable!("Op::Div scan pins result to Float"),
_ => unreachable!(),
};
aligned_def(&mut bcx, ®s, ®_kinds, ins.a() as usize, r);
current_kinds[ins.a() as usize] = k;
current_is_nil[ins.a() as usize] = false;
}
Op::Return1 => {
let v = bcx.use_var(regs[ins.a() as usize]);
let out = if matches!(a_kind(®_kinds, ins.a()), RegKind::Float) {
bcx.ins().bitcast(types::I64, MemFlags::new(), v)
} else {
v
};
bcx.ins().return_(&[out]);
terminated = true;
}
Op::Return0 => {
let zero = bcx.ins().iconst(types::I64, 0);
bcx.ins().return_(&[zero]);
terminated = true;
}
Op::Jmp => {
let tgt = jmp_target(pc, ins);
let tgt_blk = pc_to_block[tgt].expect("Jmp target is BB start");
bcx.ins().jump(tgt_blk, &[]);
terminated = true;
}
Op::GetTabUp => {
debug_assert!(
folded_math[pc],
"scanner accepts GetTabUp only inside a math fold"
);
let fold = math_folds
.iter()
.find(|f| f.start_pc == pc)
.copied()
.expect("math fold for this PC");
let mut libm_sig = module.make_signature();
libm_sig.params.push(AbiParam::new(types::F64));
libm_sig.returns.push(AbiParam::new(types::F64));
let libm_id = module
.declare_function(fold.fn_name, Linkage::Import, &libm_sig)
.ok()?;
let libm_ref = module.declare_func_in_func(libm_id, bcx.func);
let arg_kind = a_kind(®_kinds, fold.arg_reg);
let arg_var = bcx.use_var(regs[fold.arg_reg as usize]);
let arg_f64 = match arg_kind {
RegKind::Float => arg_var,
RegKind::Int | RegKind::Unset => bcx.ins().fcvt_from_sint(types::F64, arg_var),
RegKind::Table => unreachable!("math fold arg can't be Table"),
};
let call_inst = bcx.ins().call(libm_ref, &[arg_f64]);
let result_f64 = bcx.inst_results(call_inst)[0];
aligned_def(
&mut bcx,
®s,
®_kinds,
fold.dst_reg as usize,
result_f64,
);
current_kinds[fold.dst_reg as usize] = RegKind::Float;
current_is_nil[fold.dst_reg as usize] = false;
pc += 3; }
Op::GetField if folded_math[pc] => {
unreachable!("GetTabUp emit advances pc past the rest of the fold");
}
Op::GetUpval => {
let a = ins.a() as usize;
if is_upval_value_read[pc] {
let idx_arg = bcx.ins().iconst(types::I64, ins.b() as i64);
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let id = module
.declare_function("luna_jit_upval_get", Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let call_inst = bcx.ins().call(r, &[idx_arg]);
let v = bcx.inst_results(call_inst)[0];
aligned_def(&mut bcx, ®s, ®_kinds, a, v);
current_kinds[a] = reg_kinds[a];
current_is_nil[a] = false;
} else {
let zero = if matches!(a_kind(®_kinds, ins.a()), RegKind::Float) {
bcx.ins().f64const(0.0)
} else {
bcx.ins().iconst(types::I64, 0)
};
aligned_def(&mut bcx, ®s, ®_kinds, a, zero);
}
}
Op::Call => {
debug_assert!(
self_call_pcs[pc],
"scanner accepts only self-recursive Calls"
);
let a = ins.a() as usize;
let nargs = (ins.b() - 1) as usize;
let mut arg_vals: Vec<Value> = Vec::with_capacity(nargs);
for i in 0..nargs {
let slot_idx = a + 1 + i;
let v = bcx.use_var(regs[slot_idx]);
let v_i64 = if matches!(a_kind(®_kinds, slot_idx as u32), RegKind::Float) {
bcx.ins().bitcast(types::I64, MemFlags::new(), v)
} else {
v
};
arg_vals.push(v_i64);
}
let self_ref = module.declare_func_in_func(fn_id, bcx.func);
let call_inst = bcx.ins().call(self_ref, &arg_vals);
let result_i64 = bcx.inst_results(call_inst)[0];
let result = if matches!(ret_kind, RegKind::Float) {
bcx.ins().bitcast(types::F64, MemFlags::new(), result_i64)
} else {
result_i64
};
aligned_def(&mut bcx, ®s, ®_kinds, a, result);
if !matches!(ret_kind, RegKind::Unset) {
current_kinds[a] = ret_kind;
}
current_is_nil[a] = false;
}
Op::ForPrep => {
let &(_, loop_pc, step_imm) = for_loops
.iter()
.find(|&&(p, _, _)| p == pc)
.expect("scanner recorded this ForPrep");
let a = ins.a() as usize;
let is_float = matches!(a_kind(®_kinds, ins.a()), RegKind::Float);
let step_i = bcx.ins().iconst(types::I64, step_imm);
match (pre53, is_float) {
(true, false) => {
let init = bcx.use_var(regs[a]);
let limit = bcx.use_var(regs[a + 1]);
let pre = bcx.ins().isub(init, step_i);
aligned_def(&mut bcx, ®s, ®_kinds, a, pre);
aligned_def(&mut bcx, ®s, ®_kinds, a + 1, limit);
aligned_def(&mut bcx, ®s, ®_kinds, a + 2, step_i);
current_kinds[a] = RegKind::Int;
current_kinds[a + 1] = RegKind::Int;
current_kinds[a + 2] = RegKind::Int;
let loop_blk = pc_to_block[loop_pc].expect("ForLoop BB");
bcx.ins().jump(loop_blk, &[]);
terminated = true;
}
(false, false) => {
let init = bcx.use_var(regs[a]);
let limit = bcx.use_var(regs[a + 1]);
let empty = if step_imm > 0 {
bcx.ins().icmp(IntCC::SignedGreaterThan, init, limit)
} else {
bcx.ins().icmp(IntCC::SignedLessThan, init, limit)
};
let span = if step_imm > 0 {
bcx.ins().isub(limit, init)
} else {
bcx.ins().isub(init, limit)
};
let abs_step = bcx.ins().iconst(types::I64, step_imm.abs());
let count = bcx.ins().sdiv(span, abs_step);
aligned_def(&mut bcx, ®s, ®_kinds, a, init);
aligned_def(&mut bcx, ®s, ®_kinds, a + 1, count);
aligned_def(&mut bcx, ®s, ®_kinds, a + 2, step_i);
aligned_def(&mut bcx, ®s, ®_kinds, a + 3, init);
current_kinds[a] = RegKind::Int;
current_kinds[a + 1] = RegKind::Int;
current_kinds[a + 2] = RegKind::Int;
current_kinds[a + 3] = RegKind::Int;
let body_blk = pc_to_block[pc + 1].expect("body BB start");
let exit_blk = pc_to_block[loop_pc + 1].expect("exit BB start");
bcx.ins().brif(empty, exit_blk, &[], body_blk, &[]);
terminated = true;
}
(true, true) => {
let init = bcx.use_var(regs[a]);
let limit = bcx.use_var(regs[a + 1]);
let step_f = bcx.ins().f64const(step_imm as f64);
let pre = bcx.ins().fsub(init, step_f);
aligned_def(&mut bcx, ®s, ®_kinds, a, pre);
aligned_def(&mut bcx, ®s, ®_kinds, a + 1, limit);
aligned_def(&mut bcx, ®s, ®_kinds, a + 2, step_i);
current_kinds[a] = RegKind::Float;
current_kinds[a + 1] = RegKind::Float;
current_kinds[a + 2] = RegKind::Int;
let loop_blk = pc_to_block[loop_pc].expect("ForLoop BB");
bcx.ins().jump(loop_blk, &[]);
terminated = true;
}
(false, true) => {
let init = bcx.use_var(regs[a]);
let limit = bcx.use_var(regs[a + 1]);
let step_f = bcx.ins().f64const(step_imm as f64);
let empty = if step_imm > 0 {
bcx.ins().fcmp(FloatCC::GreaterThan, init, limit)
} else {
bcx.ins().fcmp(FloatCC::LessThan, init, limit)
};
let set_blk = bcx.create_block();
let exit_blk = pc_to_block[loop_pc + 1].expect("exit BB start");
bcx.ins().brif(empty, exit_blk, &[], set_blk, &[]);
terminated = true;
bcx.switch_to_block(set_blk);
bcx.seal_block(set_blk);
aligned_def(&mut bcx, ®s, ®_kinds, a, init);
aligned_def(&mut bcx, ®s, ®_kinds, a + 1, limit);
aligned_def(&mut bcx, ®s, ®_kinds, a + 2, step_i);
aligned_def(&mut bcx, ®s, ®_kinds, a + 3, init);
current_kinds[a] = RegKind::Float;
current_kinds[a + 1] = RegKind::Float;
current_kinds[a + 2] = RegKind::Int;
current_kinds[a + 3] = RegKind::Float;
let _ = step_f;
let body_blk = pc_to_block[pc + 1].expect("body BB start");
bcx.ins().jump(body_blk, &[]);
}
}
}
Op::ForLoop => {
let prep_pc = for_loops
.iter()
.find(|&&(_, lp, _)| lp == pc)
.map(|&(p, _, _)| p)
.expect("scanner paired this ForLoop");
let &(_, _, step_imm) = for_loops
.iter()
.find(|&&(p, _, _)| p == prep_pc)
.expect("step_const recorded");
let a = ins.a() as usize;
let is_float = matches!(a_kind(®_kinds, ins.a()), RegKind::Float);
if is_float {
let cur = bcx.use_var(regs[a]);
let step_f = bcx.ins().f64const(step_imm as f64);
let next = bcx.ins().fadd(cur, step_f);
let limit = bcx.use_var(regs[a + 1]);
let cont = if step_imm > 0 {
bcx.ins().fcmp(FloatCC::LessThanOrEqual, next, limit)
} else {
bcx.ins().fcmp(FloatCC::GreaterThanOrEqual, next, limit)
};
let continue_blk = bcx.create_block();
let body_blk = pc_to_block[prep_pc + 1].expect("body BB");
let exit_blk = pc_to_block[pc + 1].expect("exit BB");
bcx.ins().brif(cont, continue_blk, &[], exit_blk, &[]);
terminated = true;
bcx.switch_to_block(continue_blk);
bcx.seal_block(continue_blk);
aligned_def(&mut bcx, ®s, ®_kinds, a, next);
aligned_def(&mut bcx, ®s, ®_kinds, a + 3, next);
current_kinds[a] = RegKind::Float;
current_kinds[a + 3] = RegKind::Float;
bcx.ins().jump(body_blk, &[]);
} else if pre53 {
let cur = bcx.use_var(regs[a]);
let step_v = bcx.ins().iconst(types::I64, step_imm);
let next = bcx.ins().iadd(cur, step_v);
let limit = bcx.use_var(regs[a + 1]);
let cont = if step_imm > 0 {
bcx.ins().icmp(IntCC::SignedLessThanOrEqual, next, limit)
} else {
bcx.ins().icmp(IntCC::SignedGreaterThanOrEqual, next, limit)
};
let continue_blk = bcx.create_block();
let body_blk = pc_to_block[prep_pc + 1].expect("body BB");
let exit_blk = pc_to_block[pc + 1].expect("exit BB");
bcx.ins().brif(cont, continue_blk, &[], exit_blk, &[]);
terminated = true;
bcx.switch_to_block(continue_blk);
bcx.seal_block(continue_blk);
aligned_def(&mut bcx, ®s, ®_kinds, a, next);
aligned_def(&mut bcx, ®s, ®_kinds, a + 3, next);
current_kinds[a] = RegKind::Int;
current_kinds[a + 3] = RegKind::Int;
bcx.ins().jump(body_blk, &[]);
} else {
let count = bcx.use_var(regs[a + 1]);
let zero_i = bcx.ins().iconst(types::I64, 0);
let cont = bcx.ins().icmp(IntCC::SignedGreaterThan, count, zero_i);
let continue_blk = bcx.create_block();
let body_blk = pc_to_block[prep_pc + 1].expect("body BB");
let exit_blk = pc_to_block[pc + 1].expect("exit BB");
bcx.ins().brif(cont, continue_blk, &[], exit_blk, &[]);
terminated = true;
bcx.switch_to_block(continue_blk);
bcx.seal_block(continue_blk);
let cur = bcx.use_var(regs[a]);
let step_v = bcx.ins().iconst(types::I64, step_imm);
let next = bcx.ins().iadd(cur, step_v);
let one = bcx.ins().iconst(types::I64, 1);
let new_count = bcx.ins().isub(count, one);
aligned_def(&mut bcx, ®s, ®_kinds, a, next);
aligned_def(&mut bcx, ®s, ®_kinds, a + 1, new_count);
aligned_def(&mut bcx, ®s, ®_kinds, a + 3, next);
current_kinds[a] = RegKind::Int;
current_kinds[a + 1] = RegKind::Int;
current_kinds[a + 3] = RegKind::Int;
bcx.ins().jump(body_blk, &[]);
}
}
Op::Lt | Op::Le | Op::Eq => {
let jmp = proto.code[pc + 1];
debug_assert!(matches!(jmp.op(), Op::Jmp), "scanner enforces pairing");
let lhs = bcx.use_var(regs[ins.a() as usize]);
let rhs = bcx.use_var(regs[ins.b() as usize]);
let lhs_kind = a_kind(®_kinds, ins.a());
let rhs_kind = a_kind(®_kinds, ins.b());
let cond =
if matches!(lhs_kind, RegKind::Float) || matches!(rhs_kind, RegKind::Float) {
let fcc = match ins.op() {
Op::Lt => FloatCC::LessThan,
Op::Le => FloatCC::LessThanOrEqual,
Op::Eq => FloatCC::Equal,
_ => unreachable!(),
};
bcx.ins().fcmp(fcc, lhs, rhs)
} else {
let icc = match ins.op() {
Op::Lt => IntCC::SignedLessThan,
Op::Le => IntCC::SignedLessThanOrEqual,
Op::Eq => IntCC::Equal,
_ => unreachable!(),
};
bcx.ins().icmp(icc, lhs, rhs)
};
let fall_blk = pc_to_block[pc + 2].expect("fallthrough BB");
let jmp_blk = pc_to_block[jmp_target(pc + 1, jmp)].expect("Jmp target BB");
if ins.k() {
bcx.ins().brif(cond, jmp_blk, &[], fall_blk, &[]);
} else {
bcx.ins().brif(cond, fall_blk, &[], jmp_blk, &[]);
}
terminated = true;
pc += 1; }
Op::NewTable => {
let presize = presize_for_newtable.get(&pc).copied().or_else(|| {
let b = ins.b();
if b > 0 { Some(b as i64) } else { None }
});
let g = if let Some(n) = presize {
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let id = module
.declare_function("luna_jit_new_table_sized", Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let n_v = bcx.ins().iconst(types::I64, n);
let call_inst = bcx.ins().call(r, &[n_v]);
bcx.inst_results(call_inst)[0]
} else {
let mut sig = module.make_signature();
sig.returns.push(AbiParam::new(types::I64));
let id = module
.declare_function("luna_jit_new_table", Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let call_inst = bcx.ins().call(r, &[]);
bcx.inst_results(call_inst)[0]
};
aligned_def(&mut bcx, ®s, ®_kinds, ins.a() as usize, g);
current_kinds[ins.a() as usize] = RegKind::Table;
current_is_nil[ins.a() as usize] = false;
}
Op::SetTable => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let c = ins.c() as usize;
let t_raw = bcx.use_var(regs[a]);
let t = if matches!(
reg_kinds.get(a).copied().unwrap_or(RegKind::Int),
RegKind::Float
) {
bcx.ins().bitcast(types::I64, MemFlags::new(), t_raw)
} else {
t_raw
};
let key = bcx.use_var(regs[b]);
let val = bcx.use_var(regs[c]);
let is_float = matches!(a_kind(®_kinds, b as u32), RegKind::Float);
if !is_float {
let asize = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ASIZE_OFFSET as i32,
);
let one = bcx.ins().iconst(types::I64, 1);
let key_minus_1 = bcx.ins().isub(key, one);
let in_range = bcx.ins().icmp(IntCC::UnsignedLessThan, key_minus_1, asize);
let fast_blk = bcx.create_block();
let slow_blk = bcx.create_block();
let merge_blk = bcx.create_block();
bcx.ins().brif(in_range, fast_blk, &[], slow_blk, &[]);
bcx.switch_to_block(fast_blk);
bcx.seal_block(fast_blk);
let avals_ptr = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ARRAY_PTR_OFFSET as i32,
);
let three = bcx.ins().iconst(types::I64, 3);
let avals_bytes = bcx.ins().ishl(asize, three);
let atags_ptr = bcx.ins().iadd(avals_ptr, avals_bytes);
let tag_dst = bcx.ins().iadd(atags_ptr, key_minus_1);
let tag_byte = bcx.ins().iconst(types::I8, RAW_TAG_INT);
bcx.ins().store(MemFlags::trusted(), tag_byte, tag_dst, 0);
let val_off = bcx.ins().ishl(key_minus_1, three); let val_dst = bcx.ins().iadd(avals_ptr, val_off);
bcx.ins().store(MemFlags::trusted(), val, val_dst, 0);
bcx.ins().jump(merge_blk, &[]);
bcx.switch_to_block(slow_blk);
bcx.seal_block(slow_blk);
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
let id = module
.declare_function("luna_jit_table_set_int", Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let _ = bcx.ins().call(r, &[t, key, val]);
bcx.ins().jump(merge_blk, &[]);
bcx.switch_to_block(merge_blk);
bcx.seal_block(merge_blk);
} else {
let key_i = bcx.ins().bitcast(types::I64, MemFlags::new(), key);
let val_i = bcx.ins().bitcast(types::I64, MemFlags::new(), val);
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
let id = module
.declare_function("luna_jit_table_set_float_float", Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let _ = bcx.ins().call(r, &[t, key_i, val_i]);
}
}
Op::SetList => {
let a = ins.a() as usize;
let b_field = ins.b();
let b = if b_field == 0 {
let prev = proto.code[pc - 1];
(prev.a() as usize).saturating_sub(a)
} else {
b_field as usize
};
let t_raw = bcx.use_var(regs[a]);
let t = if matches!(
reg_kinds.get(a).copied().unwrap_or(RegKind::Int),
RegKind::Float
) {
bcx.ins().bitcast(types::I64, MemFlags::new(), t_raw)
} else {
t_raw
};
let avals_ptr = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ARRAY_PTR_OFFSET as i32,
);
let asize = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ASIZE_OFFSET as i32,
);
let three_imm = bcx.ins().iconst(types::I64, 3);
let avals_bytes = bcx.ins().ishl(asize, three_imm);
let atags_ptr = bcx.ins().iadd(avals_ptr, avals_bytes);
for i in 0..b {
let src = a + 1 + i;
let v = bcx.use_var(regs[src]);
let kind = current_kinds.get(src).copied().unwrap_or(RegKind::Int);
let is_nil_src = current_is_nil.get(src).copied().unwrap_or(false);
let (tag, bits) = if is_nil_src {
let zero = bcx.ins().iconst(types::I64, 0);
(RAW_TAG_NIL, zero)
} else {
let bits = if matches!(
reg_kinds.get(src).copied().unwrap_or(RegKind::Int),
RegKind::Float
) {
bcx.ins().bitcast(types::I64, MemFlags::new(), v)
} else {
v
};
let tag = match kind {
RegKind::Int | RegKind::Unset => RAW_TAG_INT,
RegKind::Float => RAW_TAG_FLOAT,
RegKind::Table => RAW_TAG_TABLE,
};
(tag, bits)
};
let idx_const = bcx.ins().iconst(types::I64, i as i64);
let tag_dst = bcx.ins().iadd(atags_ptr, idx_const);
let tag_byte = bcx.ins().iconst(types::I8, tag);
bcx.ins().store(MemFlags::trusted(), tag_byte, tag_dst, 0);
let val_off = bcx.ins().iconst(types::I64, (i as i64) * 8);
let val_dst = bcx.ins().iadd(avals_ptr, val_off);
bcx.ins().store(MemFlags::trusted(), bits, val_dst, 0);
}
}
Op::GetI => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let t_raw = bcx.use_var(regs[b]);
let t = if matches!(
reg_kinds.get(b).copied().unwrap_or(RegKind::Int),
RegKind::Float
) {
bcx.ins().bitcast(types::I64, MemFlags::new(), t_raw)
} else {
t_raw
};
let key_imm = ins.c() as i64;
let asize = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ASIZE_OFFSET as i32,
);
let key_minus_1 = bcx.ins().iconst(types::I64, key_imm - 1);
let in_range = bcx.ins().icmp(IntCC::UnsignedLessThan, key_minus_1, asize);
let metatable = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_METATABLE_OFFSET as i32,
);
let zero_i64 = bcx.ins().iconst(types::I64, 0);
let no_meta = bcx.ins().icmp(IntCC::Equal, metatable, zero_i64);
let fast_ok = bcx.ins().band(in_range, no_meta);
let fast_blk = bcx.create_block();
let slow_blk = bcx.create_block();
let merge_blk = bcx.create_block();
bcx.append_block_param(merge_blk, types::I64);
bcx.ins().brif(fast_ok, fast_blk, &[], slow_blk, &[]);
bcx.switch_to_block(fast_blk);
bcx.seal_block(fast_blk);
let avals_ptr = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ARRAY_PTR_OFFSET as i32,
);
let three = bcx.ins().iconst(types::I64, 3);
let val_off = bcx.ins().ishl(key_minus_1, three);
let val_addr = bcx.ins().iadd(avals_ptr, val_off);
let fast_bits = bcx.ins().load(types::I64, MemFlags::trusted(), val_addr, 0);
bcx.ins().jump(merge_blk, &[BlockArg::Value(fast_bits)]);
bcx.switch_to_block(slow_blk);
bcx.seal_block(slow_blk);
let key = bcx.ins().iconst(types::I64, key_imm);
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let id = module
.declare_function("luna_jit_table_get_int", Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let call_inst = bcx.ins().call(r, &[t, key]);
let slow_bits = bcx.inst_results(call_inst)[0];
bcx.ins().jump(merge_blk, &[BlockArg::Value(slow_bits)]);
bcx.switch_to_block(merge_blk);
bcx.seal_block(merge_blk);
let v = bcx.block_params(merge_blk)[0];
aligned_def(&mut bcx, ®s, ®_kinds, a, v);
current_kinds[a] = reg_kinds[a];
current_is_nil[a] = false;
}
Op::GetTable => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let c = ins.c() as usize;
let t_raw = bcx.use_var(regs[b]);
let t = if matches!(
reg_kinds.get(b).copied().unwrap_or(RegKind::Int),
RegKind::Float
) {
bcx.ins().bitcast(types::I64, MemFlags::new(), t_raw)
} else {
t_raw
};
let key_raw = bcx.use_var(regs[c]);
let key_kind = a_kind(®_kinds, c as u32);
let is_float_key = matches!(key_kind, RegKind::Float);
let (key_i64, key_ok) = if is_float_key {
let key_int = bcx.ins().fcvt_to_sint(types::I64, key_raw);
let key_back = bcx.ins().fcvt_from_sint(types::F64, key_int);
let exact = bcx.ins().fcmp(FloatCC::Equal, key_raw, key_back);
(key_int, exact)
} else {
let always = bcx.ins().iconst(types::I8, 1);
(key_raw, always)
};
let asize = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ASIZE_OFFSET as i32,
);
let one = bcx.ins().iconst(types::I64, 1);
let key_minus_1 = bcx.ins().isub(key_i64, one);
let in_range = bcx.ins().icmp(IntCC::UnsignedLessThan, key_minus_1, asize);
let metatable = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_METATABLE_OFFSET as i32,
);
let zero_i64 = bcx.ins().iconst(types::I64, 0);
let no_meta = bcx.ins().icmp(IntCC::Equal, metatable, zero_i64);
let bounds_ok = bcx.ins().band(in_range, no_meta);
let fast_ok = bcx.ins().band(bounds_ok, key_ok);
let fast_blk = bcx.create_block();
let slow_blk = bcx.create_block();
let merge_blk = bcx.create_block();
bcx.append_block_param(merge_blk, types::I64);
bcx.ins().brif(fast_ok, fast_blk, &[], slow_blk, &[]);
bcx.switch_to_block(fast_blk);
bcx.seal_block(fast_blk);
let avals_ptr = bcx.ins().load(
types::I64,
MemFlags::trusted(),
t,
TABLE_ARRAY_PTR_OFFSET as i32,
);
let three = bcx.ins().iconst(types::I64, 3);
let val_off = bcx.ins().ishl(key_minus_1, three);
let val_addr = bcx.ins().iadd(avals_ptr, val_off);
let fast_bits = bcx.ins().load(types::I64, MemFlags::trusted(), val_addr, 0);
bcx.ins().jump(merge_blk, &[BlockArg::Value(fast_bits)]);
bcx.switch_to_block(slow_blk);
bcx.seal_block(slow_blk);
let (helper_name, key_arg) = if is_float_key {
let key_bits = bcx.ins().bitcast(types::I64, MemFlags::new(), key_raw);
("luna_jit_table_get_float", key_bits)
} else {
("luna_jit_table_get_int", key_raw)
};
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let id = module
.declare_function(helper_name, Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let call_inst = bcx.ins().call(r, &[t, key_arg]);
let slow_bits = bcx.inst_results(call_inst)[0];
bcx.ins().jump(merge_blk, &[BlockArg::Value(slow_bits)]);
bcx.switch_to_block(merge_blk);
bcx.seal_block(merge_blk);
let v = bcx.block_params(merge_blk)[0];
aligned_def(&mut bcx, ®s, ®_kinds, a, v);
current_kinds[a] = reg_kinds[a];
current_is_nil[a] = false;
}
Op::Len => {
let a = ins.a() as usize;
let b = ins.b() as usize;
let t_raw = bcx.use_var(regs[b]);
let t = if matches!(
reg_kinds.get(b).copied().unwrap_or(RegKind::Int),
RegKind::Float
) {
bcx.ins().bitcast(types::I64, MemFlags::new(), t_raw)
} else {
t_raw
};
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let id = module
.declare_function("luna_jit_table_len", Linkage::Import, &sig)
.ok()?;
let r = module.declare_func_in_func(id, bcx.func);
let call_inst = bcx.ins().call(r, &[t]);
let v = bcx.inst_results(call_inst)[0];
aligned_def(&mut bcx, ®s, ®_kinds, a, v);
current_kinds[a] = RegKind::Int;
}
_ => return None,
}
pc += 1;
}
if !terminated {
let zero = bcx.ins().iconst(types::I64, 0);
bcx.ins().return_(&[zero]);
}
bcx.seal_all_blocks();
bcx.finalize();
module.define_function(fn_id, &mut ctx).ok()?;
module.clear_context(&mut ctx);
let _ = ret_kind;
Some((
fn_id,
ChunkMeta {
num_args: num_params as u8,
returns_one: sees_return1,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
},
))
}
#[inline]
fn aligned_def(
bcx: &mut FunctionBuilder<'_>,
regs: &[Variable],
kinds: &[RegKind],
idx: usize,
value: Value,
) {
let want = match kinds.get(idx).copied().unwrap_or(RegKind::Unset) {
RegKind::Float => types::F64,
RegKind::Int | RegKind::Unset | RegKind::Table => types::I64,
};
let got = bcx.func.dfg.value_type(value);
let aligned = if got == want {
value
} else {
bcx.ins().bitcast(want, MemFlags::new(), value)
};
bcx.def_var(regs[idx], aligned);
}
fn try_match_math_fold(proto: &Proto, start_pc: usize) -> Option<MathFold> {
let code = &proto.code;
let i0 = *code.get(start_pc)?;
let i1 = *code.get(start_pc + 1)?;
let i2 = *code.get(start_pc + 2)?;
let i3 = *code.get(start_pc + 3)?;
if !matches!(i0.op(), Op::GetTabUp) {
return None;
}
if !matches!(i1.op(), Op::GetField) {
return None;
}
if !matches!(i2.op(), Op::Move) {
return None;
}
if !matches!(i3.op(), Op::Call) {
return None;
}
let a = i0.a();
if i0.b() != 0 {
return None;
}
let k_math = proto.consts.get(i0.c() as usize).copied()?;
let LuaValue::Str(s) = k_math else {
return None;
};
if s.as_bytes() != b"math" {
return None;
}
if i1.a() != a || i1.b() != a {
return None;
}
let k_fn = proto.consts.get(i1.c() as usize).copied()?;
let LuaValue::Str(fname) = k_fn else {
return None;
};
let fn_name = MATH_LIBM_FNS
.iter()
.find_map(|&(needle, name)| (needle == fname.as_bytes()).then_some(name))?;
if i2.a() != a + 1 {
return None;
}
let arg_reg = i2.b();
if i3.a() != a || i3.b() != 2 || i3.c() != 2 {
return None;
}
Some(MathFold {
start_pc,
fn_name,
arg_reg,
dst_reg: a,
})
}
#[inline]
fn jmp_target(pc: usize, inst: Inst) -> usize {
let new_pc = pc as i64 + 1 + inst.sj() as i64;
new_pc as usize
}
pub struct JitHandle {
_module: SendJitModule,
entry_raw: *const u8,
num_args: u8,
returns_one: bool,
arg_float_mask: u8,
arg_table_mask: u8,
ret_is_float: bool,
ret_is_table: bool,
}
unsafe impl Send for JitHandle {}
impl JitHandle {
#[inline]
pub fn call(&self) -> i64 {
debug_assert_eq!(
self.num_args, 0,
"JitHandle::call() is the zero-arg form; use call_with for higher arity"
);
let f: IntChunkFn = unsafe { std::mem::transmute(self.entry_raw) };
unsafe { f() }
}
pub fn call_with(&self, args: &[i64]) -> i64 {
debug_assert_eq!(args.len(), self.num_args as usize);
unsafe {
match self.num_args {
0 => (std::mem::transmute::<*const u8, IntChunkFn>(self.entry_raw))(),
1 => (std::mem::transmute::<*const u8, IntFn1>(self.entry_raw))(args[0]),
2 => (std::mem::transmute::<*const u8, IntFn2>(self.entry_raw))(args[0], args[1]),
3 => (std::mem::transmute::<*const u8, IntFn3>(self.entry_raw))(
args[0], args[1], args[2],
),
4 => (std::mem::transmute::<*const u8, IntFn4>(self.entry_raw))(
args[0], args[1], args[2], args[3],
),
_ => unreachable!("MAX_JIT_ARITY enforces num_args <= 4"),
}
}
}
#[inline]
pub fn entry_raw(&self) -> *const u8 {
self.entry_raw
}
#[doc(hidden)]
#[inline]
pub fn __j_d_module(&self) -> &SendJitModule {
&self._module
}
#[inline]
pub fn num_args(&self) -> u8 {
self.num_args
}
#[inline]
pub fn returns_one(&self) -> bool {
self.returns_one
}
#[inline]
pub fn arg_float_mask(&self) -> u8 {
self.arg_float_mask
}
#[inline]
pub fn ret_is_float(&self) -> bool {
self.ret_is_float
}
#[inline]
pub fn arg_table_mask(&self) -> u8 {
self.arg_table_mask
}
#[inline]
pub fn ret_is_table(&self) -> bool {
self.ret_is_table
}
}
#[cfg(test)]
mod smoke {
use cranelift::prelude::*;
use cranelift_codegen::ir::UserFuncName;
use cranelift_frontend::FunctionBuilderContext;
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{Linkage, Module};
#[test]
fn cranelift_jit_fib28_returns_317811() {
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
flag_builder.set("is_pic", "false").unwrap();
flag_builder.set("opt_level", "speed").unwrap();
let isa = cranelift_native::builder()
.expect("host isa builder")
.finish(settings::Flags::new(flag_builder))
.unwrap();
let builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(builder);
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let fib_id = module
.declare_function("fib", Linkage::Local, &sig)
.expect("declare fib");
let mut ctx = module.make_context();
ctx.func.signature = sig.clone();
ctx.func.name = UserFuncName::user(0, fib_id.as_u32());
let mut fbc = FunctionBuilderContext::new();
let mut bcx = FunctionBuilder::new(&mut ctx.func, &mut fbc);
let entry = bcx.create_block();
let then_blk = bcx.create_block();
let else_blk = bcx.create_block();
bcx.append_block_params_for_function_params(entry);
let n = bcx.block_params(entry)[0];
bcx.switch_to_block(entry);
bcx.seal_block(entry);
let two = bcx.ins().iconst(types::I64, 2);
let cmp = bcx.ins().icmp(IntCC::SignedLessThan, n, two);
bcx.ins().brif(cmp, then_blk, &[], else_blk, &[]);
bcx.switch_to_block(then_blk);
bcx.seal_block(then_blk);
bcx.ins().return_(&[n]);
bcx.switch_to_block(else_blk);
bcx.seal_block(else_blk);
let one = bcx.ins().iconst(types::I64, 1);
let n_minus_1 = bcx.ins().isub(n, one);
let n_minus_2 = bcx.ins().isub(n, two);
let fib_ref = module.declare_func_in_func(fib_id, bcx.func);
let call1 = bcx.ins().call(fib_ref, &[n_minus_1]);
let r1 = bcx.inst_results(call1)[0];
let call2 = bcx.ins().call(fib_ref, &[n_minus_2]);
let r2 = bcx.inst_results(call2)[0];
let sum = bcx.ins().iadd(r1, r2);
bcx.ins().return_(&[sum]);
bcx.finalize();
module.define_function(fib_id, &mut ctx).expect("define");
module.clear_context(&mut ctx);
module.finalize_definitions().expect("finalize");
let fib_ptr = module.get_finalized_function(fib_id);
let fib_fn: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(fib_ptr) };
assert_eq!(fib_fn(0), 0);
assert_eq!(fib_fn(1), 1);
assert_eq!(fib_fn(10), 55);
assert_eq!(fib_fn(28), 317811, "fib(28)");
}
}
#[cfg(test)]
mod s1 {
use super::try_compile_int_chunk;
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn jit_int(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let handle = try_compile_int_chunk(cl.proto, false, false)
.expect("S1 lowerer should accept this chunk");
handle.call()
}
fn interp_int(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn return_const_int() {
assert_eq!(jit_int("return 42"), 42);
}
#[test]
fn const_folded_arith() {
assert_eq!(jit_int("return 1 + 2 + 3"), 6);
}
#[test]
fn locals_add_runtime() {
assert_eq!(jit_int("local a = 5; local b = 7; return a + b"), 12);
}
#[test]
fn mul_then_add_runtime() {
assert_eq!(jit_int("local a = 5; return a * a + 1"), 26);
}
#[test]
fn matches_interpreter() {
for src in [
"return 0",
"return 42",
"return -7",
"return 1 + 2",
"local a = 5; local b = 7; return a + b",
"local a = 5; return a * a + 1",
"local x = 100; return x - 1",
] {
assert_eq!(jit_int(src), interp_int(src), "src = {src}");
}
}
#[test]
fn bails_out_on_unsupported_op() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(b"return 'hello'", b"=t").unwrap();
assert!(try_compile_int_chunk(cl.proto, false, false).is_none());
}
}
#[cfg(test)]
mod s2 {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn eval_int(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn eval_int_chunk_goes_through_jit() {
assert_eq!(eval_int("return 42"), 42);
}
#[test]
fn eval_local_arith_goes_through_jit() {
assert_eq!(eval_int("local a = 5; local b = 7; return a + b"), 12);
assert_eq!(eval_int("local a = 5; return a * a + 1"), 26);
}
#[test]
fn second_call_hits_cached_native() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm
.load(b"local a = 5; local b = 7; return a + b", b"=t")
.expect("compile");
let v1 = vm.call_value(Value::Closure(cl), &[]).unwrap();
let v2 = vm.call_value(Value::Closure(cl), &[]).unwrap();
assert!(matches!(v1.first(), Some(Value::Int(12))));
assert!(matches!(v2.first(), Some(Value::Int(12))));
assert_eq!(
crate::jit_backend::cache_entry_count(&vm),
1,
"one compiled Proto"
);
}
#[test]
fn unsupported_chunk_falls_back_cleanly() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval("return 'hello'").unwrap();
match v.first() {
Some(Value::Str(s)) => assert_eq!(s.as_bytes(), b"hello"),
other => panic!("expected 'hello', got {other:?}"),
}
}
#[test]
fn args_disable_jit_path() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(b"local a = 5; return a + 1", b"=t").unwrap();
let v = vm
.call_value(Value::Closure(cl), &[Value::Int(99)])
.unwrap();
assert!(matches!(v.first(), Some(Value::Int(6))));
}
}
#[cfg(test)]
mod s2b {
use super::try_compile_int_chunk;
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn jit_int(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let handle = try_compile_int_chunk(cl.proto, false, false)
.expect("S2b lowerer should accept this chunk");
handle.call()
}
fn interp_int(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
fn parity(src: &str) {
assert_eq!(jit_int(src), interp_int(src), "src = {src}");
}
#[test]
fn if_lt_true_branch() {
parity("local x = 2; if x < 3 then return 1 else return 0 end");
}
#[test]
fn if_lt_false_branch() {
parity("local x = 5; if x < 3 then return 1 else return 0 end");
}
#[test]
fn if_le_boundary() {
for src in [
"local x = 3; if x <= 3 then return 1 else return 0 end",
"local x = 4; if x <= 3 then return 1 else return 0 end",
"local x = 2; if x <= 3 then return 1 else return 0 end",
] {
parity(src);
}
}
#[test]
fn if_eq() {
parity("local x = 5; if x == 5 then return 1 else return 0 end");
parity("local x = 5; if x == 4 then return 1 else return 0 end");
}
#[test]
fn if_no_else() {
parity("local x = 5; if x < 3 then return 1 end; return 0");
parity("local x = 2; if x < 3 then return 1 end; return 0");
}
#[test]
fn nested_if_else() {
parity(
"local x = 5; local y = 7; \
if x < 10 then \
if y < 5 then return 1 else return 2 end \
else return 3 end",
);
parity(
"local x = 5; local y = 3; \
if x < 10 then \
if y < 5 then return 1 else return 2 end \
else return 3 end",
);
parity(
"local x = 15; \
if x < 10 then return 1 else return 3 end",
);
}
#[test]
fn arith_inside_branches() {
parity(
"local a = 5; local b = 7; \
if a < b then return a * b + 1 else return a - b end",
);
parity(
"local a = 50; local b = 7; \
if a < b then return a * b + 1 else return a - b end",
);
}
}
#[cfg(test)]
mod s2c_a {
use super::{IntFn1, IntFn2, try_compile_int_chunk};
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn with_inner<F: FnOnce(&luna_core::runtime::function::Proto)>(src: &str, f: F) {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile main");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run main");
let inner = match r.first() {
Some(&Value::Closure(inner)) => inner,
other => panic!("expected the chunk to return one closure, got {other:?}"),
};
f(&inner.proto);
drop(vm); }
#[test]
fn add1_compiles_and_runs() {
with_inner("local function f(n) return n + 1 end; return f", |proto| {
let handle = try_compile_int_chunk(
luna_core::runtime::Gc::from_ptr(proto as *const _ as *mut _),
false,
false,
)
.expect("S2c.A accepts num_params == 1");
assert_eq!(handle.num_args(), 1);
assert!(handle.returns_one());
let f: IntFn1 = unsafe { std::mem::transmute(handle.entry_raw()) };
assert_eq!(unsafe { f(41) }, 42);
assert_eq!(unsafe { f(0) }, 1);
assert_eq!(unsafe { f(-1) }, 0);
assert_eq!(handle.call_with(&[100]), 101);
});
}
#[test]
fn two_param_arith() {
with_inner(
"local function f(a, b) return a * b + 1 end; return f",
|proto| {
let handle = try_compile_int_chunk(
luna_core::runtime::Gc::from_ptr(proto as *const _ as *mut _),
false,
false,
)
.expect("S2c.A accepts num_params == 2");
assert_eq!(handle.num_args(), 2);
let f: IntFn2 = unsafe { std::mem::transmute(handle.entry_raw()) };
assert_eq!(unsafe { f(3, 4) }, 13);
assert_eq!(handle.call_with(&[5, 6]), 31);
},
);
}
#[test]
fn param_with_branch() {
with_inner(
"local function clip(n) if n < 0 then return 0 end; return n end; return clip",
|proto| {
let handle = try_compile_int_chunk(
luna_core::runtime::Gc::from_ptr(proto as *const _ as *mut _),
false,
false,
)
.expect("S2c.A accepts param + branch");
assert_eq!(handle.num_args(), 1);
let f: IntFn1 = unsafe { std::mem::transmute(handle.entry_raw()) };
assert_eq!(unsafe { f(5) }, 5);
assert_eq!(unsafe { f(-5) }, 0);
assert_eq!(unsafe { f(0) }, 0);
},
);
}
#[test]
fn high_arity_bails() {
with_inner(
"local function f(a, b, c, d, e) return a + b + c + d + e end; return f",
|proto| {
assert!(
try_compile_int_chunk(
luna_core::runtime::Gc::from_ptr(proto as *const _ as *mut _),
false,
false,
)
.is_none(),
"5 params is above MAX_JIT_ARITY (4)"
);
},
);
}
}
#[cfg(test)]
mod s2c_b {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn eval_int(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn calls_jit_inner_one_arg() {
assert_eq!(
eval_int("local function add1(n) return n + 1 end; return add1(41)"),
42
);
}
#[test]
fn calls_jit_inner_two_args() {
assert_eq!(
eval_int("local function f(a, b) return a * b + 1 end; return f(5, 7)"),
36
);
}
#[test]
fn calls_jit_inner_with_branch() {
let src = "local function clip(n) if n < 0 then return 0 end; return n end; \
return clip(-3) + clip(7)";
assert_eq!(eval_int(src), 7);
}
#[test]
fn multiple_calls_share_cache() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm
.eval(
"local function add1(n) return n + 1 end; \
return add1(10) + add1(20) + add1(30)",
)
.unwrap();
assert!(matches!(v.first(), Some(Value::Int(63))));
assert_eq!(
crate::jit_backend::cache_entry_count(&vm),
1,
"Proto compiled exactly once"
);
}
#[test]
fn jit_failed_state_falls_through() {
assert!(
crate::jit_backend::test_vm_new(LuaVersion::Lua55)
.eval("local function f(n) return tostring(n) end; return #f(42)")
.unwrap()
.first()
.map(|v| matches!(v, Value::Int(_)))
.unwrap_or(false),
);
}
}
#[cfg(test)]
mod s2c_c {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn eval_int(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn fib_recursive_small() {
let fib = "local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib";
assert_eq!(
eval_int(
&format!("{fib} return fib(0)")
.replace("return fib return fib(0)", "; return fib(0)")
),
0,
);
}
#[test]
fn fib_10_matches_interpreter() {
let src = "local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib(10)";
assert_eq!(eval_int(src), 55);
}
#[test]
fn fib_15_matches_interpreter() {
let src = "local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib(15)";
assert_eq!(eval_int(src), 610);
}
#[test]
fn fib_28_matches_interpreter() {
let src = "local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib(28)";
assert_eq!(eval_int(src), 317811);
}
#[test]
fn recursive_one_compile_per_proto() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm
.eval(
"local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib(10)",
)
.unwrap();
assert!(matches!(v.first(), Some(Value::Int(55))));
assert_eq!(
crate::jit_backend::cache_entry_count(&vm),
1,
"fib's Proto compiled exactly once"
);
}
}
#[cfg(test)]
mod s2c_c_perf_check {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
#[test]
fn fib28_bench_source_flips_proto_to_compiled() {
let src = "local function f(n) \
if n < 2 then return n end \
return f(n - 1) + f(n - 2) \
end \
return f(28)";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).unwrap();
assert!(matches!(v.first(), Some(Value::Int(317811))));
assert_eq!(
crate::jit_backend::cache_entry_count(&vm),
1,
"fib's Proto should compile exactly once",
);
}
}
#[cfg(test)]
mod s3 {
use super::try_compile_int_chunk;
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn eval_float_55(src: &str) -> f64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Float(f)) => f,
other => panic!("expected float return, got {other:?}"),
}
}
fn eval_with(version: LuaVersion, src: &str) -> Value {
let mut vm = crate::jit_backend::test_vm_new(version);
vm.eval(src)
.expect("eval")
.into_iter()
.next()
.expect("one value")
}
#[test]
fn float_const_return() {
assert_eq!(eval_float_55("return 1.5"), 1.5);
}
#[test]
fn float_runtime_arith() {
assert_eq!(
eval_float_55("local a = 1.5; local b = 2.5; return a + b"),
4.0,
);
assert_eq!(
eval_float_55("local a = 1.5; local b = 2.5; return a * b"),
3.75,
);
}
#[test]
fn float_branch() {
assert_eq!(
eval_float_55("local x = 2.5; if x < 3.0 then return 1.0 else return 0.0 end"),
1.0,
);
assert_eq!(
eval_float_55("local x = 4.5; if x < 3.0 then return 1.0 else return 0.0 end"),
0.0,
);
}
#[test]
fn mixed_int_float_bails_cleanly() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm
.load(b"local a = 1; local b = 0.5; return a + b", b"=t")
.expect("compile");
assert!(try_compile_int_chunk(cl.proto, false, false).is_none());
let v = vm
.eval("local a = 1; local b = 0.5; return a + b")
.expect("eval");
assert!(matches!(v.first(), Some(&Value::Float(1.5))));
}
#[test]
fn fib28_5_2_matches_interpreter() {
let src = "local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib(28)";
match eval_with(LuaVersion::Lua52, src) {
Value::Float(f) => assert_eq!(f, 317811.0),
other => panic!("expected Float(317811.0), got {other:?}"),
}
}
#[test]
fn fib28_5_1_matches_interpreter() {
let src = "local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib(28)";
match eval_with(LuaVersion::Lua51, src) {
Value::Float(f) => assert_eq!(f, 317811.0),
other => panic!("expected Float(317811.0), got {other:?}"),
}
}
#[test]
fn fib28_5_5_still_int() {
let src = "local function fib(n) \
if n < 2 then return n end \
return fib(n - 1) + fib(n - 2) \
end; return fib(28)";
match eval_with(LuaVersion::Lua55, src) {
Value::Int(i) => assert_eq!(i, 317811),
other => panic!("expected Int(317811), got {other:?}"),
}
}
#[test]
fn cache_key_includes_consts() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v1 = vm.eval("return 1.5").unwrap();
assert!(matches!(v1.first(), Some(&Value::Float(f)) if f == 1.5));
let v2 = vm.eval("return 2.5").unwrap();
assert!(matches!(v2.first(), Some(&Value::Float(f)) if f == 2.5));
assert_eq!(crate::jit_backend::cache_entry_count(&vm), 2);
}
#[test]
fn float_div() {
assert_eq!(
eval_float_55("local a = 3.0; local b = 2.0; return a / b"),
1.5,
);
}
}
#[cfg(test)]
mod s5a {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn eval_int_55(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
fn eval_int_with(version: LuaVersion, src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(version);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn for_1_to_1000_sums_to_500500() {
assert_eq!(
eval_int_55("local s = 0 for i = 1, 1000 do s = s + i end return s"),
500500,
);
}
#[test]
fn for_descending_step_minus_1() {
assert_eq!(
eval_int_55("local s = 0 for i = 10, 1, -1 do s = s + i end return s"),
55,
);
}
#[test]
fn for_empty_loop_skips_body() {
assert_eq!(
eval_int_55("local s = 0 for i = 10, 1 do s = s + 1 end return s"),
0,
);
}
#[test]
fn for_single_iter() {
assert_eq!(
eval_int_55("local s = 0 for i = 1, 1 do s = s + i end return s"),
1,
);
}
#[test]
fn for_body_references_control_var() {
assert_eq!(
eval_int_55("local s = 0 for i = 1, 100 do s = s + i * 2 end return s"),
10100,
);
}
#[test]
fn loop_int_1m_5_5_matches_interpreter() {
let src = "local s = 0 for i = 1, 1000000 do s = s + i end return s";
assert_eq!(eval_int_55(src), 500000500000);
}
#[test]
fn loop_int_1m_5_4_matches_interpreter() {
let src = "local s = 0 for i = 1, 1000000 do s = s + i end return s";
assert_eq!(eval_int_with(LuaVersion::Lua54, src), 500000500000);
}
#[test]
fn loop_int_1k_pre53_runs_through_interpreter() {
let src = "local s = 0 for i = 1, 1000 do s = s + i end return s";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua51);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Float(f)) => assert_eq!(f, 500500.0),
other => panic!("expected Float(500500.0) under 5.1, got {other:?}"),
}
assert_eq!(eval_int_with(LuaVersion::Lua53, src), 500500);
}
#[test]
fn cache_pre53_post53_distinct() {
use luna_core::runtime::function::JitProtoState;
let src = b"local s = 0 for i = 1, 100 do s = s + i end return s";
let mut vm55 = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl55 = vm55.load(src, b"=t").expect("compile");
let r55 = vm55.call_value(Value::Closure(cl55), &[]).expect("run");
assert!(matches!(
cl55.proto.jit.get(),
JitProtoState::Compiled { .. }
));
assert!(matches!(r55.first(), Some(&Value::Int(5050))));
let mut vm53 = crate::jit_backend::test_vm_new(LuaVersion::Lua53);
let cl53 = vm53.load(src, b"=t").expect("compile");
let r53 = vm53.call_value(Value::Closure(cl53), &[]).expect("run");
assert!(matches!(
cl53.proto.jit.get(),
JitProtoState::Compiled { .. }
));
assert!(matches!(r53.first(), Some(&Value::Int(5050))));
assert_eq!(crate::jit_backend::cache_entry_count(&vm55), 1);
assert_eq!(crate::jit_backend::cache_entry_count(&vm53), 1);
}
#[test]
fn non_immediate_step_runs_through_interpreter() {
let src = "local function f(s) local sum = 0 for i = 1, 10, s do sum = sum + i end return sum end; return f(2)";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => assert_eq!(i, 25), other => panic!("expected Int(25), got {other:?}"),
}
}
}
#[cfg(test)]
mod s5a_b {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn eval_int_53(src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua53);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn pre53_for_1_to_1000_sums_under_5_3() {
assert_eq!(
eval_int_53("local s = 0 for i = 1, 1000 do s = s + i end return s"),
500500,
);
}
#[test]
fn pre53_descending_step_minus_1_under_5_3() {
assert_eq!(
eval_int_53("local s = 0 for i = 10, 1, -1 do s = s + i end return s"),
55,
);
}
#[test]
fn pre53_empty_loop_skips_body_under_5_3() {
assert_eq!(
eval_int_53("local s = 0 for i = 10, 1 do s = s + 1 end return s"),
0,
);
}
#[test]
fn pre53_single_iter_under_5_3() {
assert_eq!(
eval_int_53("local s = 0 for i = 5, 5 do s = s + i end return s"),
5,
);
}
#[test]
fn loop_int_1m_5_3_matches_interpreter() {
let src = "local s = 0 for i = 1, 1000000 do s = s + i end return s";
assert_eq!(eval_int_53(src), 500000500000);
}
#[test]
fn loop_int_1m_5_3_jit_state_compiled() {
use luna_core::runtime::function::JitProtoState;
let src = b"local s = 0 for i = 1, 1000000 do s = s + i end return s";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua53);
let cl = vm.load(src, b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
assert!(matches!(r.first(), Some(&Value::Int(500000500000))));
}
}
#[cfg(test)]
mod s5a_c {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
fn eval_float_with(version: LuaVersion, src: &str) -> f64 {
let mut vm = crate::jit_backend::test_vm_new(version);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Float(f)) => f,
other => panic!("expected float return, got {other:?}"),
}
}
#[test]
fn post53_float_for_1_to_1000_sums_under_5_5() {
assert_eq!(
eval_float_with(
LuaVersion::Lua55,
"local s = 0.0 for i = 1.0, 1000.0 do s = s + i end return s",
),
500500.0,
);
}
#[test]
fn pre53_float_for_1_to_1000_sums_under_5_3() {
assert_eq!(
eval_float_with(
LuaVersion::Lua53,
"local s = 0.0 for i = 1.0, 1000.0 do s = s + i end return s",
),
500500.0,
);
}
#[test]
fn loop_int_1m_5_1_matches_interpreter() {
assert_eq!(
eval_float_with(
LuaVersion::Lua51,
"local s = 0 for i = 1, 1000000 do s = s + i end return s",
),
500000500000.0,
);
}
#[test]
fn loop_int_1m_5_2_matches_interpreter() {
assert_eq!(
eval_float_with(
LuaVersion::Lua52,
"local s = 0 for i = 1, 1000000 do s = s + i end return s",
),
500000500000.0,
);
}
#[test]
fn loop_int_1m_5_1_jit_state_compiled() {
use luna_core::runtime::function::JitProtoState;
let src = b"local s = 0 for i = 1, 1000000 do s = s + i end return s";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua51);
let cl = vm.load(src, b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
assert!(matches!(r.first(), Some(&Value::Float(f)) if f == 500000500000.0));
}
#[test]
fn loop_int_1m_5_5_still_jit_int_path() {
use luna_core::runtime::function::JitProtoState;
let src = b"local s = 0 for i = 1, 1000000 do s = s + i end return s";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src, b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
assert!(matches!(r.first(), Some(&Value::Int(500000500000))));
}
#[test]
fn pre53_float_descending_step_minus_1_under_5_3() {
assert_eq!(
eval_float_with(
LuaVersion::Lua53,
"local s = 0.0 for i = 1000.0, 1.0, -1 do s = s + i end return s",
),
500500.0,
);
}
#[test]
fn pre53_float_empty_loop_skips_body_under_5_3() {
assert_eq!(
eval_float_with(
LuaVersion::Lua53,
"local s = 7.0 for i = 10.0, 1.0 do s = s + 1.0 end return s",
),
7.0,
);
}
#[test]
fn post53_float_empty_loop_skips_body_under_5_5() {
assert_eq!(
eval_float_with(
LuaVersion::Lua55,
"local s = 7.0 for i = 10.0, 1.0 do s = s + 1.0 end return s",
),
7.0,
);
}
}
#[cfg(test)]
mod s5b {
use luna_core::runtime::Value;
use luna_core::runtime::function::JitProtoState;
use luna_core::version::LuaVersion;
fn eval_float_with(version: LuaVersion, src: &str) -> f64 {
let mut vm = crate::jit_backend::test_vm_new(version);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Float(f)) => f,
other => panic!("expected float return, got {other:?}"),
}
}
fn interp_only_float_with(version: LuaVersion, src: &str) -> f64 {
let _ = version;
let _ = src;
unreachable!("references are precomputed in each test")
}
#[test]
fn math_sin_5_5_matches_libm() {
let _ = interp_only_float_with;
let src = "local s = 0.0 for i = 1, 100 do s = s + math.sin(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua55, src);
let r_ref: f64 = (1..=100).map(|i| (i as f64).sin()).sum();
assert!(
(r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 16.0).max(1e-12),
"math.sin sum mismatch: jit={r_jit} ref={r_ref}"
);
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let _ = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
}
#[test]
fn math_cos_5_5_matches_libm() {
let src = "local s = 0.0 for i = 1, 100 do s = s + math.cos(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua55, src);
let r_ref: f64 = (1..=100).map(|i| (i as f64).cos()).sum();
assert!(
(r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 16.0).max(1e-12),
"math.cos sum mismatch: jit={r_jit} ref={r_ref}"
);
}
#[test]
fn math_sin_cos_product_5_5_matches_libm() {
let src = "local s = 0.0 for i = 1, 1000 do s = s + math.sin(i) * math.cos(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua55, src);
let r_ref: f64 = (1..=1000)
.map(|i| (i as f64).sin() * (i as f64).cos())
.sum();
assert!(
(r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 32.0).max(1e-9),
"sin*cos sum mismatch: jit={r_jit} ref={r_ref}"
);
}
#[test]
fn math_loop_100k_5_5_matches_libm() {
let src =
"local s = 0.0 for i = 1, 100000 do s = s + math.sin(i) * math.cos(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua55, src);
let r_ref: f64 = (1..=100_000)
.map(|i| (i as f64).sin() * (i as f64).cos())
.sum();
assert!(
(r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 1024.0).max(1e-6),
"100k sin*cos sum mismatch: jit={r_jit} ref={r_ref}"
);
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let _ = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
}
#[test]
fn math_loop_100k_5_4_matches_libm() {
let src =
"local s = 0.0 for i = 1, 100000 do s = s + math.sin(i) * math.cos(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua54, src);
let r_ref: f64 = (1..=100_000)
.map(|i| (i as f64).sin() * (i as f64).cos())
.sum();
assert!((r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 1024.0).max(1e-6),);
}
#[test]
fn math_loop_100k_5_3_matches_libm() {
let src =
"local s = 0.0 for i = 1, 100000 do s = s + math.sin(i) * math.cos(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua53, src);
let r_ref: f64 = (1..=100_000)
.map(|i| (i as f64).sin() * (i as f64).cos())
.sum();
assert!((r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 1024.0).max(1e-6),);
}
#[test]
fn math_loop_100k_5_2_matches_libm() {
let src =
"local s = 0.0 for i = 1, 100000 do s = s + math.sin(i) * math.cos(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua52, src);
let r_ref: f64 = (1..=100_000)
.map(|i| (i as f64).sin() * (i as f64).cos())
.sum();
assert!((r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 1024.0).max(1e-6),);
}
#[test]
fn math_loop_100k_5_1_matches_libm() {
let src =
"local s = 0.0 for i = 1, 100000 do s = s + math.sin(i) * math.cos(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua51, src);
let r_ref: f64 = (1..=100_000)
.map(|i| (i as f64).sin() * (i as f64).cos())
.sum();
assert!((r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 1024.0).max(1e-6),);
}
#[test]
fn math_libcall_distinct_fns_distinct_cache() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let _ = vm
.eval("local s = 0.0 for i = 1, 4 do s = s + math.sin(i) end return s")
.expect("sin eval");
let n_after_sin = crate::jit_backend::cache_entry_count(&vm);
let _ = vm
.eval("local s = 0.0 for i = 1, 4 do s = s + math.cos(i) end return s")
.expect("cos eval");
let n_after_cos = crate::jit_backend::cache_entry_count(&vm);
assert!(
n_after_cos > n_after_sin,
"sin/cos chunks must hash to distinct cache slots (sin={n_after_sin} cos={n_after_cos})"
);
}
#[test]
fn math_sqrt_5_5_matches_libm() {
let src = "local s = 0.0 for i = 1, 100 do s = s + math.sqrt(i) end return s";
let r_jit = eval_float_with(LuaVersion::Lua55, src);
let r_ref: f64 = (1..=100).map(|i| (i as f64).sqrt()).sum();
assert!((r_jit - r_ref).abs() <= (r_ref.abs() * f64::EPSILON * 16.0).max(1e-12),);
}
#[test]
fn math_constant_access_bails_to_interp() {
let src = "local s = 0.0 for i = 1, 4 do s = s + math.pi end return s";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Failed));
match r.first() {
Some(&Value::Float(f)) => {
assert!((f - 4.0 * std::f64::consts::PI).abs() < 1e-12);
}
other => panic!("expected float, got {other:?}"),
}
}
#[test]
fn math_two_arg_atan_bails() {
let src = "local s = 0.0 for i = 1, 4 do s = s + math.atan(i, 2) end return s";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let _ = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Failed));
}
}
#[cfg(test)]
mod s5c {
use luna_core::runtime::Value;
use luna_core::runtime::function::JitProtoState;
use luna_core::version::LuaVersion;
fn eval_int_with(version: LuaVersion, src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(version);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn table_alloc_10_matches_interpreter_5_5() {
assert_eq!(
eval_int_with(
LuaVersion::Lua55,
"local t = {} for i = 1, 10 do t[i] = i end return #t",
),
10,
);
}
#[test]
fn table_alloc_10k_5_5_jit_state_compiled() {
let src = "local t = {} for i = 1, 10000 do t[i] = i end return #t";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
assert!(matches!(r.first(), Some(&Value::Int(10000))));
}
#[test]
fn table_alloc_10k_5_4_matches_interpreter() {
assert_eq!(
eval_int_with(
LuaVersion::Lua54,
"local t = {} for i = 1, 10000 do t[i] = i end return #t",
),
10000,
);
}
#[test]
fn table_alloc_10k_5_3_matches_interpreter() {
assert_eq!(
eval_int_with(
LuaVersion::Lua53,
"local t = {} for i = 1, 10000 do t[i] = i end return #t",
),
10000,
);
}
#[test]
fn table_get_int_5_5() {
assert_eq!(
eval_int_with(
LuaVersion::Lua55,
"local t = {} for i = 1, 100 do t[i] = i * 2 end return t[50]",
),
100,
);
}
#[test]
fn table_len_5_5() {
assert_eq!(
eval_int_with(
LuaVersion::Lua55,
"local t = {} for i = 1, 42 do t[i] = i end return #t",
),
42,
);
}
#[test]
fn table_alloc_10k_5_2_jit_compiles() {
table_alloc_10k_jit_compiles_for_version(LuaVersion::Lua52);
}
#[test]
fn table_alloc_10k_5_1_jit_compiles() {
table_alloc_10k_jit_compiles_for_version(LuaVersion::Lua51);
}
fn table_alloc_10k_jit_compiles_for_version(ver: LuaVersion) {
let src = "local t = {} for i = 1, 10000 do t[i] = i end return #t";
let mut vm = crate::jit_backend::test_vm_new(ver);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(
matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }),
"{ver:?} table_alloc Proto did not JIT-compile (state: {:?})",
cl.proto.jit.get()
);
assert!(matches!(r.first(), Some(&Value::Int(10000))));
}
#[test]
fn table_with_string_key_bails() {
let src = "local t = {} t['a'] = 1 return t['a']";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let _ = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Failed));
}
#[test]
fn presized_newtable_now_jits() {
let src = "local t = {10, 20, 30} return t[2]";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
assert!(matches!(r.first(), Some(&Value::Int(20))));
}
}
#[cfg(test)]
mod s5c_b {
use luna_core::runtime::Value;
use luna_core::runtime::function::JitProtoState;
use luna_core::version::LuaVersion;
fn eval_int_with(version: LuaVersion, src: &str) -> i64 {
let mut vm = crate::jit_backend::test_vm_new(version);
let v = vm.eval(src).expect("eval");
match v.first() {
Some(&Value::Int(i)) => i,
other => panic!("expected int return, got {other:?}"),
}
}
#[test]
fn table_alloc_10k_5_5_presized() {
let src = "local t = {} for i = 1, 10000 do t[i] = i end return #t";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
assert!(matches!(r.first(), Some(&Value::Int(10000))));
}
#[test]
fn table_alloc_10k_5_4_presized() {
assert_eq!(
eval_int_with(
LuaVersion::Lua54,
"local t = {} for i = 1, 10000 do t[i] = i end return #t",
),
10000,
);
}
#[test]
fn table_alloc_10k_5_3_presized() {
assert_eq!(
eval_int_with(
LuaVersion::Lua53,
"local t = {} for i = 1, 10000 do t[i] = i end return #t",
),
10000,
);
}
#[test]
fn table_alloc_loadk_limit_5_5() {
let src = "local t = {} for i = 1, 65536 do t[i] = i end return #t";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src.as_bytes(), b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
assert!(matches!(cl.proto.jit.get(), JitProtoState::Compiled { .. }));
assert!(matches!(r.first(), Some(&Value::Int(65536))));
}
#[test]
fn table_alloc_4_small_presize() {
assert_eq!(
eval_int_with(
LuaVersion::Lua55,
"local t = {} for i = 1, 4 do t[i] = i end return #t",
),
4,
);
}
#[test]
fn step_ne_1_falls_back_to_empty_helper() {
assert_eq!(
eval_int_with(
LuaVersion::Lua55,
"local t = {} for i = 1, 10, 2 do t[i] = i end return #t",
),
1,
);
}
#[test]
fn inline_aset_payload_round_trip_5_5() {
assert_eq!(
eval_int_with(
LuaVersion::Lua55,
"local t = {}
for i = 1, 5 do t[i] = i * i end
return t[1] + t[2] + t[3] + t[4] + t[5]",
),
55,
);
}
#[test]
fn inline_aset_payload_round_trip_5_3() {
assert_eq!(
eval_int_with(
LuaVersion::Lua53,
"local t = {}
for i = 1, 5 do t[i] = i * i end
return t[1] + t[2] + t[3] + t[4] + t[5]",
),
55,
);
}
}
#[cfg(test)]
mod s5d_a {
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
#[test]
fn table_param_int_return_round_trip_5_5() {
use luna_core::runtime::function::JitProtoState;
let src = b"local function f(t) return t[1] end return f";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src, b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
let inner = match r.first() {
Some(&Value::Closure(c)) => c,
other => panic!("expected closure, got {other:?}"),
};
let t_chunk = b"local t = {} t[1] = 42 return t";
let t_cl = vm.load(t_chunk, b"=u").expect("compile");
let t_v = vm.call_value(Value::Closure(t_cl), &[]).expect("run");
let tv = *t_v.first().expect("t");
let r2 = vm
.call_value(Value::Closure(inner), &[tv])
.expect("call f(t)");
assert!(matches!(r2.first(), Some(&Value::Int(42))));
assert!(matches!(
inner.proto.jit.get(),
JitProtoState::Compiled { .. }
));
}
#[test]
fn table_param_len_5_5() {
let src = "local function f(t) return #t end
local t = {} t[1] = 1 t[2] = 1 t[3] = 1 return f(t)";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let r = vm.eval(src).expect("eval");
assert!(matches!(r.first(), Some(&Value::Int(3))));
}
}
#[cfg(test)]
mod s5d_b {
use luna_core::runtime::Value;
use luna_core::runtime::function::JitProtoState;
use luna_core::version::LuaVersion;
#[test]
fn newtable_b3_setlist_int_5_5() {
let src = b"local function f() return {10, 20, 30} end return f";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let cl = vm.load(src, b"=t").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
let inner = match r.first() {
Some(&Value::Closure(c)) => c,
other => panic!("expected closure, got {other:?}"),
};
let r2 = vm.call_value(Value::Closure(inner), &[]).expect("call f()");
let t = match r2.first() {
Some(&Value::Table(t)) => t,
other => panic!("expected table, got {other:?}"),
};
assert_eq!(t.len(), 3);
assert!(matches!(t.get_int(2), Value::Int(20)));
assert!(matches!(
inner.proto.jit.get(),
JitProtoState::Compiled { .. }
));
}
#[test]
fn setlist_then_geti_round_trip_5_5() {
let src = "local function get(t, i) return t[i] end
local function make() return {7, 11, 13} end
return get(make(), 2)";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let r = vm.eval(src).expect("eval");
assert!(matches!(r.first(), Some(&Value::Int(11))));
}
#[test]
fn conditional_both_branches_new_table_5_5() {
let src = "local function f(flag)
if flag == 1 then return {1, 2}
else return {3, 4} end
end
return f(1)[2] + f(0)[1]";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let r = vm.eval(src).expect("eval");
assert!(matches!(r.first(), Some(&Value::Int(5))));
}
#[test]
fn make_proto_5_5_round_trip() {
let src = "local function make(d)
if d == 0 then return {1, 1}
else return {make(d-1), make(d-1)} end
end
local t = make(3)
return t[1][1][1][1] + t[2][2][2][2]";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let r = vm.eval(src).expect("eval");
assert!(matches!(r.first(), Some(&Value::Int(2))));
}
#[test]
fn binary_trees_n10_round_trip_5_5() {
let src = "local function make(d)
if d == 0 then return {1, 1}
else return {make(d-1), make(d-1)} end
end
local function check(t)
if t[1] == 1 then return 1 end
return 1 + check(t[1]) + check(t[2])
end
local sum = 0
for i = 1, 16 do sum = sum + check(make(10)) end
return sum";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let r = vm.eval(src).expect("eval");
assert!(matches!(r.first(), Some(&Value::Int(32752))));
}
fn make_proto_jit_compiles_for_version(ver: LuaVersion) {
let src = b"local function make(d)
if d == 0 then return {1, 1}
else return {make(d-1), make(d-1)} end
end
return make";
let mut vm = crate::jit_backend::test_vm_new(ver);
let cl = vm.load(src, b"=make").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
let make_cl = match r.first() {
Some(&Value::Closure(c)) => c,
other => panic!("expected closure, got {other:?}"),
};
for d in 0..3 {
vm.call_value(Value::Closure(make_cl), &[Value::Int(d)])
.expect("call make(d)");
}
assert!(
matches!(make_cl.proto.jit.get(), JitProtoState::Compiled { .. }),
"{ver:?} make Proto did not JIT-compile (state: {:?})",
make_cl.proto.jit.get()
);
}
#[test]
fn make_proto_jit_compiles_5_1() {
make_proto_jit_compiles_for_version(LuaVersion::Lua51);
}
#[test]
fn make_proto_jit_compiles_5_2() {
make_proto_jit_compiles_for_version(LuaVersion::Lua52);
}
fn make_nil_proto_jit_compiles_for_version(ver: LuaVersion) {
let src = b"local function make(d)
if d == 0 then return {nil, nil}
else return {make(d-1), make(d-1)} end
end
return make";
let mut vm = crate::jit_backend::test_vm_new(ver);
let cl = vm.load(src, b"=make").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
let make_cl = match r.first() {
Some(&Value::Closure(c)) => c,
other => panic!("expected closure, got {other:?}"),
};
for d in 0..3 {
vm.call_value(Value::Closure(make_cl), &[Value::Int(d)])
.expect("call make(d)");
}
assert!(
matches!(make_cl.proto.jit.get(), JitProtoState::Compiled { .. }),
"{ver:?} make {{nil,nil}} Proto did not JIT-compile (state: {:?})",
make_cl.proto.jit.get()
);
}
#[test]
fn make_nil_proto_jit_compiles_5_1() {
make_nil_proto_jit_compiles_for_version(LuaVersion::Lua51);
}
#[test]
fn make_nil_proto_jit_compiles_5_2() {
make_nil_proto_jit_compiles_for_version(LuaVersion::Lua52);
}
#[test]
fn make_nil_proto_jit_compiles_5_3() {
make_nil_proto_jit_compiles_for_version(LuaVersion::Lua53);
}
#[test]
fn make_nil_proto_jit_compiles_5_5() {
make_nil_proto_jit_compiles_for_version(LuaVersion::Lua55);
}
#[test]
fn return_nil_does_not_miscompile_5_5() {
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let r = vm
.eval("local function f() return nil end return f()")
.expect("eval");
assert!(
matches!(r.first(), Some(&Value::Nil)),
"expected Nil, got {r:?}"
);
}
#[test]
fn check_proto_jit_compiles_5_5() {
let src = "local function check(t)
if t[1] == 1 then return 1 end
return 1 + check(t[1]) + check(t[2])
end
local leaf = {1, 1}
local node = {leaf, leaf}
return check(node)";
let mut vm = crate::jit_backend::test_vm_new(LuaVersion::Lua55);
let r = vm.eval(src).expect("eval");
assert!(matches!(r.first(), Some(&Value::Int(3))));
}
fn get_table_simple_jit_for_version(ver: LuaVersion) {
let src = b"local function get(t, k) return t[k] end
return get";
let mut vm = crate::jit_backend::test_vm_new(ver);
let cl = vm.load(src, b"=get").expect("compile");
let r = vm.call_value(Value::Closure(cl), &[]).expect("run");
let get_cl = match r.first() {
Some(&Value::Closure(c)) => c,
other => panic!("expected closure, got {other:?}"),
};
let table = vm.heap.new_table();
let _ = unsafe { table.as_mut() }.set_int(&mut vm.heap, 1, Value::Float(42.0));
for _ in 0..3 {
let _ = vm
.call_value(
Value::Closure(get_cl),
&[Value::Table(table), Value::Float(1.0)],
)
.expect("call get(t, 1.0)");
}
assert!(
matches!(get_cl.proto.jit.get(), JitProtoState::Compiled { .. }),
"{ver:?} get Proto did not JIT-compile (state: {:?})",
get_cl.proto.jit.get()
);
}
#[test]
fn get_table_simple_jit_5_1() {
get_table_simple_jit_for_version(LuaVersion::Lua51);
}
#[test]
fn get_table_simple_jit_5_2() {
get_table_simple_jit_for_version(LuaVersion::Lua52);
}
fn check_proto_jit_compiles_pre53(ver: LuaVersion) {
let src = "local function check(t)
if t[1] == 1 then return 1 end
return 1 + check(t[1]) + check(t[2])
end
local leaf = {1, 1}
local node = {leaf, leaf}
return check(node)";
let mut vm = crate::jit_backend::test_vm_new(ver);
let r = vm.eval(src).expect("eval");
match r.first() {
Some(&Value::Float(f)) if (f - 3.0).abs() < 1e-9 => {}
other => panic!("{ver:?} check(node) expected Float(3.0), got {other:?}"),
}
}
#[test]
fn check_proto_jit_compiles_5_1() {
check_proto_jit_compiles_pre53(LuaVersion::Lua51);
}
#[test]
fn check_proto_jit_compiles_5_2() {
check_proto_jit_compiles_pre53(LuaVersion::Lua52);
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct CraneliftBackend;
impl IntChunkCompiler for CraneliftBackend {
fn try_compile(
&self,
storage: &mut dyn luna_core::jit::JitStorage,
proto: luna_core::runtime::Gc<luna_core::runtime::function::Proto>,
pre53: bool,
float_only: bool,
) -> CompileResult {
match cache_lookup_or_compile(storage, proto, pre53, float_only) {
Some((
entry,
num_args,
returns_one,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
)) => CompileResult::Compiled {
entry,
num_args,
returns_one,
arg_float_mask,
arg_table_mask,
ret_is_float,
ret_is_table,
},
None => CompileResult::Skipped,
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] fn enter(
&self,
vm: *mut luna_core::vm::Vm,
cl: Option<luna_core::runtime::Gc<luna_core::runtime::LuaClosure>>,
) -> JitVmGuard {
let vm_ref: &mut luna_core::vm::Vm = unsafe { &mut *vm };
enter_jit(vm_ref, cl)
}
}
impl TraceCompiler for CraneliftBackend {
fn try_compile_trace(
&self,
storage: &mut dyn luna_core::jit::JitStorage,
record: &TraceRecord,
opts: CompileOptions,
) -> Option<CompiledTrace> {
trace::try_compile_trace_with_options(storage, record, opts)
}
fn last_compile_checkpoint(&self) -> &'static str {
trace::last_compile_checkpoint()
}
}