use super::*;
use cranelift_codegen::Context;
use cranelift_codegen::ir::types::{F64, I64};
use cranelift_codegen::ir::{AbiParam, InstBuilder};
use cranelift_codegen::settings::{self, Configurable};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{FuncId, Linkage, Module, default_libcall_names};
use std::collections::HashMap;
pub struct JitFunction {
_module: JITModule,
func_ptr: *const u8,
param_count: usize,
}
unsafe impl Send for JitFunction {}
#[allow(dead_code)]
struct HelperFuncs {
add: FuncId,
concat: FuncId,
sub: FuncId,
mul: FuncId,
div: FuncId,
eq: FuncId,
ne: FuncId,
gt: FuncId,
lt: FuncId,
ge: FuncId,
le: FuncId,
not: FuncId,
neg: FuncId,
truthy: FuncId,
wrapok: FuncId,
wraperr: FuncId,
isok: FuncId,
iserr: FuncId,
unwrap: FuncId,
jit_move: FuncId,
drop_rc: FuncId,
len: FuncId,
str_fn: FuncId,
num: FuncId,
abs: FuncId,
mod_fn: FuncId,
clamp: FuncId,
min: FuncId,
max: FuncId,
flr: FuncId,
cel: FuncId,
rou: FuncId,
pow: FuncId,
sqrt: FuncId,
log: FuncId,
exp: FuncId,
sin: FuncId,
cos: FuncId,
tan: FuncId,
log10: FuncId,
log2: FuncId,
atan2: FuncId,
transpose: FuncId,
matmul: FuncId,
dot: FuncId,
rnd0: FuncId,
rnd2: FuncId,
rndn: FuncId,
now: FuncId,
env: FuncId,
get: FuncId,
spl: FuncId,
cat: FuncId,
has: FuncId,
hd: FuncId,
at: FuncId,
fmt2: FuncId,
zip: FuncId,
enumerate: FuncId,
range: FuncId,
window: FuncId,
chunks: FuncId,
setunion: FuncId,
setinter: FuncId,
setdiff: FuncId,
tl: FuncId,
rev: FuncId,
srt: FuncId,
rsrt: FuncId,
fft: FuncId,
ifft: FuncId,
cumsum: FuncId,
median: FuncId,
quantile: FuncId,
stdev: FuncId,
variance: FuncId,
slc: FuncId,
lst: FuncId,
rgxsub: FuncId,
take: FuncId,
drop_fn: FuncId,
listappend: FuncId,
index: FuncId,
recfld: FuncId,
recfld_name: FuncId,
recnew: FuncId,
recwith: FuncId,
recwith_arena: FuncId,
listnew: FuncId,
listget: FuncId,
jpth: FuncId,
jdmp: FuncId,
jpar: FuncId,
rdjl: FuncId,
call: FuncId,
isnum: FuncId,
istext: FuncId,
isbool: FuncId,
islist: FuncId,
mapnew: FuncId,
mget: FuncId,
mset: FuncId,
mhas: FuncId,
mkeys: FuncId,
mvals: FuncId,
mdel: FuncId,
prt: FuncId,
trm: FuncId,
upr: FuncId,
lwr: FuncId,
cap: FuncId,
padl: FuncId,
padr: FuncId,
ord: FuncId,
chr: FuncId,
unq: FuncId,
uniqby: FuncId,
partition: FuncId,
frq: FuncId,
rd: FuncId,
rdl: FuncId,
wr: FuncId,
wrl: FuncId,
post: FuncId,
geth: FuncId,
posth: FuncId,
getmany: FuncId,
solve: FuncId,
inv: FuncId,
det: FuncId,
dtfmt: FuncId,
dtparse: FuncId,
}
fn declare_helper(module: &mut JITModule, name: &str, n_params: usize, n_returns: usize) -> FuncId {
let mut sig = module.make_signature();
for _ in 0..n_params {
sig.params.push(AbiParam::new(I64));
}
for _ in 0..n_returns {
sig.returns.push(AbiParam::new(I64));
}
module
.declare_function(name, Linkage::Import, &sig)
.unwrap()
}
fn register_helpers(builder: &mut JITBuilder) {
let helpers: &[(&str, *const u8)] = &[
("jit_add", jit_add as *const u8),
("jit_concat", jit_concat as *const u8),
("jit_sub", jit_sub as *const u8),
("jit_mul", jit_mul as *const u8),
("jit_div", jit_div as *const u8),
("jit_eq", jit_eq as *const u8),
("jit_ne", jit_ne as *const u8),
("jit_gt", jit_gt as *const u8),
("jit_lt", jit_lt as *const u8),
("jit_ge", jit_ge as *const u8),
("jit_le", jit_le as *const u8),
("jit_not", jit_not as *const u8),
("jit_neg", jit_neg as *const u8),
("jit_truthy", jit_truthy as *const u8),
("jit_wrapok", jit_wrapok as *const u8),
("jit_wraperr", jit_wraperr as *const u8),
("jit_isok", jit_isok as *const u8),
("jit_iserr", jit_iserr as *const u8),
("jit_unwrap", jit_unwrap as *const u8),
("jit_move", jit_move as *const u8),
("jit_drop_rc", jit_drop_rc as *const u8),
("jit_len", jit_len as *const u8),
("jit_str", jit_str as *const u8),
("jit_num", jit_num as *const u8),
("jit_abs", jit_abs as *const u8),
("jit_mod", jit_mod as *const u8),
("jit_clamp", jit_clamp as *const u8),
("jit_min", jit_min as *const u8),
("jit_max", jit_max as *const u8),
("jit_flr", jit_flr as *const u8),
("jit_cel", jit_cel as *const u8),
("jit_rou", jit_rou as *const u8),
("jit_pow", jit_pow as *const u8),
("jit_sqrt", jit_sqrt as *const u8),
("jit_log", jit_log as *const u8),
("jit_exp", jit_exp as *const u8),
("jit_sin", jit_sin as *const u8),
("jit_cos", jit_cos as *const u8),
("jit_tan", jit_tan as *const u8),
("jit_log10", jit_log10 as *const u8),
("jit_log2", jit_log2 as *const u8),
("jit_atan2", jit_atan2 as *const u8),
("jit_transpose", jit_transpose as *const u8),
("jit_matmul", jit_matmul as *const u8),
("jit_dot", jit_dot as *const u8),
("jit_rnd0", jit_rnd0 as *const u8),
("jit_rnd2", jit_rnd2 as *const u8),
("jit_rndn", jit_rndn as *const u8),
("jit_now", jit_now as *const u8),
("jit_env", jit_env as *const u8),
("jit_get", jit_get as *const u8),
("jit_spl", jit_spl as *const u8),
("jit_cat", jit_cat as *const u8),
("jit_has", jit_has as *const u8),
("jit_hd", jit_hd as *const u8),
("jit_at", jit_at as *const u8),
("jit_fmt2", jit_fmt2 as *const u8),
("jit_zip", jit_zip as *const u8),
("jit_enumerate", jit_enumerate as *const u8),
("jit_range", jit_range as *const u8),
("jit_window", jit_window as *const u8),
("jit_chunks", jit_chunks as *const u8),
("jit_setunion", jit_setunion as *const u8),
("jit_setinter", jit_setinter as *const u8),
("jit_setdiff", jit_setdiff as *const u8),
("jit_tl", jit_tl as *const u8),
("jit_rev", jit_rev as *const u8),
("jit_srt", jit_srt as *const u8),
("jit_rsrt", jit_rsrt as *const u8),
("jit_fft", jit_fft as *const u8),
("jit_ifft", jit_ifft as *const u8),
("jit_cumsum", jit_cumsum as *const u8),
("jit_median", jit_median as *const u8),
("jit_quantile", jit_quantile as *const u8),
("jit_stdev", jit_stdev as *const u8),
("jit_variance", jit_variance as *const u8),
("jit_slc", jit_slc as *const u8),
("jit_lst", jit_lst as *const u8),
("jit_rgxsub", jit_rgxsub as *const u8),
("jit_take", jit_take as *const u8),
("jit_drop", jit_drop as *const u8),
("jit_listappend", jit_listappend as *const u8),
("jit_index", jit_index as *const u8),
("jit_recfld", jit_recfld as *const u8),
("jit_recfld_name", jit_recfld_name as *const u8),
("jit_recnew", jit_recnew as *const u8),
("jit_recwith", jit_recwith as *const u8),
("jit_recwith_arena", jit_recwith_arena as *const u8),
("jit_listnew", jit_listnew as *const u8),
("jit_listget", jit_listget as *const u8),
("jit_jpth", jit_jpth as *const u8),
("jit_jdmp", jit_jdmp as *const u8),
("jit_jpar", jit_jpar as *const u8),
("jit_rdjl", jit_rdjl as *const u8),
("jit_call", jit_call as *const u8),
("jit_isnum", jit_isnum as *const u8),
("jit_istext", jit_istext as *const u8),
("jit_isbool", jit_isbool as *const u8),
("jit_islist", jit_islist as *const u8),
("jit_mapnew", jit_mapnew as *const u8),
("jit_mget", jit_mget as *const u8),
("jit_mset", jit_mset as *const u8),
("jit_mhas", jit_mhas as *const u8),
("jit_mkeys", jit_mkeys as *const u8),
("jit_mvals", jit_mvals as *const u8),
("jit_mdel", jit_mdel as *const u8),
("jit_prt", jit_prt as *const u8),
("jit_trm", jit_trm as *const u8),
("jit_upr", jit_upr as *const u8),
("jit_lwr", jit_lwr as *const u8),
("jit_cap", jit_cap as *const u8),
("jit_padl", jit_padl as *const u8),
("jit_padr", jit_padr as *const u8),
("jit_ord", jit_ord as *const u8),
("jit_chr", jit_chr as *const u8),
("jit_unq", jit_unq as *const u8),
("jit_uniqby", jit_uniqby as *const u8),
("jit_partition", jit_partition as *const u8),
("jit_frq", jit_frq as *const u8),
("jit_rd", jit_rd as *const u8),
("jit_rdl", jit_rdl as *const u8),
("jit_wr", jit_wr as *const u8),
("jit_wrl", jit_wrl as *const u8),
("jit_post", jit_post as *const u8),
("jit_geth", jit_geth as *const u8),
("jit_posth", jit_posth as *const u8),
("jit_getmany", jit_getmany as *const u8),
("jit_solve", jit_solve as *const u8),
("jit_inv", jit_inv as *const u8),
("jit_det", jit_det as *const u8),
("jit_dtfmt", jit_dtfmt as *const u8),
("jit_dtparse", jit_dtparse as *const u8),
];
for &(name, ptr) in helpers {
builder.symbol(name, ptr);
}
}
fn declare_all_helpers(module: &mut JITModule) -> HelperFuncs {
HelperFuncs {
add: declare_helper(module, "jit_add", 2, 1),
concat: declare_helper(module, "jit_concat", 2, 1),
sub: declare_helper(module, "jit_sub", 2, 1),
mul: declare_helper(module, "jit_mul", 2, 1),
div: declare_helper(module, "jit_div", 2, 1),
eq: declare_helper(module, "jit_eq", 2, 1),
ne: declare_helper(module, "jit_ne", 2, 1),
gt: declare_helper(module, "jit_gt", 2, 1),
lt: declare_helper(module, "jit_lt", 2, 1),
ge: declare_helper(module, "jit_ge", 2, 1),
le: declare_helper(module, "jit_le", 2, 1),
not: declare_helper(module, "jit_not", 1, 1),
neg: declare_helper(module, "jit_neg", 1, 1),
truthy: declare_helper(module, "jit_truthy", 1, 1),
wrapok: declare_helper(module, "jit_wrapok", 1, 1),
wraperr: declare_helper(module, "jit_wraperr", 1, 1),
isok: declare_helper(module, "jit_isok", 1, 1),
iserr: declare_helper(module, "jit_iserr", 1, 1),
unwrap: declare_helper(module, "jit_unwrap", 1, 1),
jit_move: declare_helper(module, "jit_move", 1, 1),
drop_rc: declare_helper(module, "jit_drop_rc", 1, 0),
len: declare_helper(module, "jit_len", 1, 1),
str_fn: declare_helper(module, "jit_str", 1, 1),
num: declare_helper(module, "jit_num", 1, 1),
abs: declare_helper(module, "jit_abs", 1, 1),
mod_fn: declare_helper(module, "jit_mod", 2, 1),
clamp: declare_helper(module, "jit_clamp", 3, 1),
min: declare_helper(module, "jit_min", 2, 1),
max: declare_helper(module, "jit_max", 2, 1),
flr: declare_helper(module, "jit_flr", 1, 1),
cel: declare_helper(module, "jit_cel", 1, 1),
rou: declare_helper(module, "jit_rou", 1, 1),
pow: declare_helper(module, "jit_pow", 2, 1),
sqrt: declare_helper(module, "jit_sqrt", 1, 1),
log: declare_helper(module, "jit_log", 1, 1),
exp: declare_helper(module, "jit_exp", 1, 1),
sin: declare_helper(module, "jit_sin", 1, 1),
cos: declare_helper(module, "jit_cos", 1, 1),
tan: declare_helper(module, "jit_tan", 1, 1),
log10: declare_helper(module, "jit_log10", 1, 1),
log2: declare_helper(module, "jit_log2", 1, 1),
atan2: declare_helper(module, "jit_atan2", 2, 1),
transpose: declare_helper(module, "jit_transpose", 1, 1),
matmul: declare_helper(module, "jit_matmul", 2, 1),
dot: declare_helper(module, "jit_dot", 2, 1),
rnd0: declare_helper(module, "jit_rnd0", 0, 1),
rnd2: declare_helper(module, "jit_rnd2", 2, 1),
rndn: declare_helper(module, "jit_rndn", 2, 1),
now: declare_helper(module, "jit_now", 0, 1),
env: declare_helper(module, "jit_env", 1, 1),
get: declare_helper(module, "jit_get", 1, 1),
spl: declare_helper(module, "jit_spl", 2, 1),
cat: declare_helper(module, "jit_cat", 2, 1),
has: declare_helper(module, "jit_has", 2, 1),
hd: declare_helper(module, "jit_hd", 1, 1),
at: declare_helper(module, "jit_at", 2, 1),
fmt2: declare_helper(module, "jit_fmt2", 2, 1),
zip: declare_helper(module, "jit_zip", 2, 1),
enumerate: declare_helper(module, "jit_enumerate", 1, 1),
range: declare_helper(module, "jit_range", 2, 1),
window: declare_helper(module, "jit_window", 2, 1),
chunks: declare_helper(module, "jit_chunks", 2, 1),
setunion: declare_helper(module, "jit_setunion", 2, 1),
setinter: declare_helper(module, "jit_setinter", 2, 1),
setdiff: declare_helper(module, "jit_setdiff", 2, 1),
tl: declare_helper(module, "jit_tl", 1, 1),
rev: declare_helper(module, "jit_rev", 1, 1),
srt: declare_helper(module, "jit_srt", 1, 1),
rsrt: declare_helper(module, "jit_rsrt", 1, 1),
fft: declare_helper(module, "jit_fft", 1, 1),
ifft: declare_helper(module, "jit_ifft", 1, 1),
cumsum: declare_helper(module, "jit_cumsum", 1, 1),
median: declare_helper(module, "jit_median", 1, 1),
quantile: declare_helper(module, "jit_quantile", 2, 1),
stdev: declare_helper(module, "jit_stdev", 1, 1),
variance: declare_helper(module, "jit_variance", 1, 1),
slc: declare_helper(module, "jit_slc", 3, 1),
lst: declare_helper(module, "jit_lst", 3, 1),
rgxsub: declare_helper(module, "jit_rgxsub", 3, 1),
take: declare_helper(module, "jit_take", 2, 1),
drop_fn: declare_helper(module, "jit_drop", 2, 1),
listappend: declare_helper(module, "jit_listappend", 2, 1),
index: declare_helper(module, "jit_index", 2, 1),
recfld: declare_helper(module, "jit_recfld", 2, 1),
recfld_name: declare_helper(module, "jit_recfld_name", 3, 1),
recnew: declare_helper(module, "jit_recnew", 4, 1),
recwith: declare_helper(module, "jit_recwith", 4, 1),
recwith_arena: declare_helper(module, "jit_recwith_arena", 5, 1),
listnew: declare_helper(module, "jit_listnew", 2, 1),
listget: declare_helper(module, "jit_listget", 2, 1),
jpth: declare_helper(module, "jit_jpth", 2, 1),
jdmp: declare_helper(module, "jit_jdmp", 1, 1),
jpar: declare_helper(module, "jit_jpar", 1, 1),
rdjl: declare_helper(module, "jit_rdjl", 1, 1),
call: declare_helper(module, "jit_call", 4, 1),
isnum: declare_helper(module, "jit_isnum", 1, 1),
istext: declare_helper(module, "jit_istext", 1, 1),
isbool: declare_helper(module, "jit_isbool", 1, 1),
islist: declare_helper(module, "jit_islist", 1, 1),
mapnew: declare_helper(module, "jit_mapnew", 0, 1),
mget: declare_helper(module, "jit_mget", 2, 1),
mset: declare_helper(module, "jit_mset", 3, 1),
mhas: declare_helper(module, "jit_mhas", 2, 1),
mkeys: declare_helper(module, "jit_mkeys", 1, 1),
mvals: declare_helper(module, "jit_mvals", 1, 1),
mdel: declare_helper(module, "jit_mdel", 2, 1),
prt: declare_helper(module, "jit_prt", 1, 1),
trm: declare_helper(module, "jit_trm", 1, 1),
upr: declare_helper(module, "jit_upr", 1, 1),
lwr: declare_helper(module, "jit_lwr", 1, 1),
cap: declare_helper(module, "jit_cap", 1, 1),
padl: declare_helper(module, "jit_padl", 2, 1),
padr: declare_helper(module, "jit_padr", 2, 1),
ord: declare_helper(module, "jit_ord", 1, 1),
chr: declare_helper(module, "jit_chr", 1, 1),
unq: declare_helper(module, "jit_unq", 1, 1),
uniqby: declare_helper(module, "jit_uniqby", 2, 1),
partition: declare_helper(module, "jit_partition", 2, 1),
frq: declare_helper(module, "jit_frq", 1, 1),
rd: declare_helper(module, "jit_rd", 1, 1),
rdl: declare_helper(module, "jit_rdl", 1, 1),
wr: declare_helper(module, "jit_wr", 2, 1),
wrl: declare_helper(module, "jit_wrl", 2, 1),
post: declare_helper(module, "jit_post", 2, 1),
geth: declare_helper(module, "jit_geth", 2, 1),
posth: declare_helper(module, "jit_posth", 3, 1),
getmany: declare_helper(module, "jit_getmany", 1, 1),
solve: declare_helper(module, "jit_solve", 2, 1),
inv: declare_helper(module, "jit_inv", 1, 1),
det: declare_helper(module, "jit_det", 1, 1),
dtfmt: declare_helper(module, "jit_dtfmt", 2, 1),
dtparse: declare_helper(module, "jit_dtparse", 2, 1),
}
}
fn is_inlinable(chunk: &Chunk, nan_consts: &[NanVal]) -> bool {
if !chunk.all_regs_numeric {
return false;
}
if chunk.reg_count as usize > 16 {
return false;
}
if chunk.code.is_empty() {
return false;
}
let mut has_ret = false;
for &inst in &chunk.code {
let op = (inst >> 24) as u8;
match op {
op if op == OP_CMPK_GE_N
|| op == OP_CMPK_GT_N
|| op == OP_CMPK_LT_N
|| op == OP_CMPK_LE_N
|| op == OP_CMPK_EQ_N
|| op == OP_CMPK_NE_N => {}
OP_JMP => {}
OP_RET => {
has_ret = true;
}
OP_LOADK => {
let bx = (inst & 0xFFFF) as usize;
if bx >= nan_consts.len() || !nan_consts[bx].is_number() {
return false;
}
}
OP_ADD_NN | OP_SUB_NN | OP_MUL_NN | OP_DIV_NN | OP_ADDK_N | OP_SUBK_N | OP_MULK_N
| OP_DIVK_N => {}
_ => return false,
}
}
has_ret
}
#[allow(clippy::too_many_arguments)]
fn inline_chunk(
builder: &mut FunctionBuilder<'_>,
callee_chunk: &Chunk,
callee_consts: &[NanVal],
arg_vars: &[Variable],
result_var: Variable,
extra_vars: &[Variable],
f64_arg_vars: &[Variable],
merge_block: cranelift_codegen::ir::Block,
) -> bool {
let n_params = callee_chunk.param_count as usize;
let mf = cranelift_codegen::ir::MemFlags::new();
let reg_var = |r: usize| -> Variable {
if r < n_params {
arg_vars[r]
} else {
extra_vars[r - n_params]
}
};
let f64_val_for =
|r: usize, builder: &mut FunctionBuilder<'_>| -> cranelift_codegen::ir::Value {
if r < n_params {
builder.use_var(f64_arg_vars[r])
} else {
let iv = builder.use_var(extra_vars[r - n_params]);
builder.ins().bitcast(F64, mf, iv)
}
};
let leaders = find_block_leaders(&callee_chunk.code);
let mut imap: HashMap<usize, cranelift_codegen::ir::Block> = HashMap::new();
for &l in &leaders {
imap.insert(l, builder.create_block());
}
builder.ins().jump(imap[&0], &[]);
let mut terminated = true;
for (ip, &inst) in callee_chunk.code.iter().enumerate() {
if let Some(&blk) = imap.get(&ip) {
if !terminated {
builder.ins().jump(blk, &[]);
}
builder.switch_to_block(blk);
terminated = false;
}
if terminated {
continue;
}
let op = (inst >> 24) as u8;
let a_raw = ((inst >> 16) & 0xFF) as usize;
match op {
OP_RET => {
let rv = builder.use_var(reg_var(a_raw));
builder.def_var(result_var, rv);
builder.ins().jump(merge_block, &[]);
terminated = true;
}
OP_JMP => {
let sbx = (inst & 0xFFFF) as i16;
let t = (ip as isize + 1 + sbx as isize) as usize;
if let Some(&tb) = imap.get(&t) {
builder.ins().jump(tb, &[]);
terminated = true;
} else {
return false;
}
}
OP_LOADK => {
let bx = (inst & 0xFFFF) as usize;
let bits = callee_consts[bx].0;
let kval = builder.ins().iconst(I64, bits as i64);
builder.def_var(reg_var(a_raw), kval);
}
op if op == OP_CMPK_GE_N
|| op == OP_CMPK_GT_N
|| op == OP_CMPK_LT_N
|| op == OP_CMPK_LE_N
|| op == OP_CMPK_EQ_N
|| op == OP_CMPK_NE_N =>
{
let ki = (inst & 0xFF) as usize;
let lhs_f64 = f64_val_for(a_raw, builder);
let rhs_k = if ki < callee_consts.len() {
callee_consts[ki].as_number()
} else {
0.0
};
let rhs_f64 = builder.ins().f64const(rhs_k);
use cranelift_codegen::ir::condcodes::FloatCC;
let cc = match op {
op if op == OP_CMPK_GE_N => FloatCC::GreaterThanOrEqual,
op if op == OP_CMPK_GT_N => FloatCC::GreaterThan,
op if op == OP_CMPK_LT_N => FloatCC::LessThan,
op if op == OP_CMPK_LE_N => FloatCC::LessThanOrEqual,
op if op == OP_CMPK_EQ_N => FloatCC::Equal,
_ => FloatCC::NotEqual,
};
let cmp = builder.ins().fcmp(cc, lhs_f64, rhs_f64);
let body = imap.get(&(ip + 2)).copied();
let miss = callee_chunk
.code
.get(ip + 1)
.and_then(|&j| {
if (j >> 24) as u8 == OP_JMP {
let sbx = (j & 0xFFFF) as i16;
imap.get(&((ip as isize + 2 + sbx as isize) as usize))
.copied()
} else {
None
}
})
.or_else(|| imap.get(&(ip + 1)).copied());
match (miss, body) {
(Some(fb), Some(bb)) => {
builder.ins().brif(cmp, bb, &[], fb, &[]);
terminated = true;
}
_ => return false,
}
}
OP_ADD_NN => {
let b = ((inst >> 8) & 0xFF) as usize;
let c = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let cv = f64_val_for(c, builder);
let rf = builder.ins().fadd(bv, cv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
OP_SUB_NN => {
let b = ((inst >> 8) & 0xFF) as usize;
let c = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let cv = f64_val_for(c, builder);
let rf = builder.ins().fsub(bv, cv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
OP_MUL_NN => {
let b = ((inst >> 8) & 0xFF) as usize;
let c = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let cv = f64_val_for(c, builder);
let rf = builder.ins().fmul(bv, cv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
OP_DIV_NN => {
let b = ((inst >> 8) & 0xFF) as usize;
let c = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let cv = f64_val_for(c, builder);
let rf = builder.ins().fdiv(bv, cv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
OP_ADDK_N => {
let b = ((inst >> 8) & 0xFF) as usize;
let ki = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let kv = builder
.ins()
.f64const(callee_consts.get(ki).map(|c| c.as_number()).unwrap_or(0.0));
let rf = builder.ins().fadd(bv, kv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
OP_SUBK_N => {
let b = ((inst >> 8) & 0xFF) as usize;
let ki = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let kv = builder
.ins()
.f64const(callee_consts.get(ki).map(|c| c.as_number()).unwrap_or(0.0));
let rf = builder.ins().fsub(bv, kv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
OP_MULK_N => {
let b = ((inst >> 8) & 0xFF) as usize;
let ki = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let kv = builder
.ins()
.f64const(callee_consts.get(ki).map(|c| c.as_number()).unwrap_or(0.0));
let rf = builder.ins().fmul(bv, kv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
OP_DIVK_N => {
let b = ((inst >> 8) & 0xFF) as usize;
let ki = (inst & 0xFF) as usize;
let bv = f64_val_for(b, builder);
let kv = builder
.ins()
.f64const(callee_consts.get(ki).map(|c| c.as_number()).unwrap_or(0.0));
let rf = builder.ins().fdiv(bv, kv);
let ri = builder.ins().bitcast(I64, mf, rf);
builder.def_var(reg_var(a_raw), ri);
}
_ => return false,
}
}
if !terminated {
let nil = builder.ins().iconst(I64, TAG_NIL as i64);
builder.def_var(result_var, nil);
builder.ins().jump(merge_block, &[]);
}
true
}
fn compile_function_body(
module: &mut JITModule,
chunk: &Chunk,
nan_consts: &[NanVal],
func_id: FuncId,
helpers: &HelperFuncs,
all_func_ids: &[FuncId],
program: &CompiledProgram,
) -> Option<()> {
let mut sig = module.make_signature();
for _ in 0..chunk.param_count {
sig.params.push(AbiParam::new(I64));
}
sig.returns.push(AbiParam::new(I64));
let mut ctx = Context::new();
ctx.func.signature = sig;
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fn_builder_ctx);
let reg_count = chunk.reg_count.max(chunk.param_count) as usize;
let mut vars: Vec<Variable> = Vec::with_capacity(reg_count);
for i in 0..reg_count {
let var = Variable::from_u32(i as u32);
builder.declare_var(var, I64);
vars.push(var);
}
let f64_var_offset = reg_count;
let mut f64_vars: Vec<Variable> = Vec::with_capacity(reg_count);
for i in 0..reg_count {
let var = Variable::from_u32((f64_var_offset + i) as u32);
builder.declare_var(var, F64);
f64_vars.push(var);
}
let foreach_var_base = 2 * reg_count;
let mut foreach_loop_map: HashMap<(usize, usize), usize> = HashMap::new();
for &inst in &chunk.code {
let op = (inst >> 24) as u8;
if op == OP_FOREACHPREP {
let b = ((inst >> 8) & 0xFF) as usize;
let c = (inst & 0xFF) as usize;
let next_idx = foreach_loop_map.len();
foreach_loop_map.entry((b, c)).or_insert(next_idx);
}
}
let num_foreach_loops = foreach_loop_map.len();
for i in 0..(num_foreach_loops * 4) {
let var = Variable::from_u32((foreach_var_base + i) as u32);
builder.declare_var(var, I64);
}
let fe_ptr_var = |loop_idx: usize| Variable::from_u32((foreach_var_base + loop_idx * 4) as u32);
let fe_data_ptr_var =
|loop_idx: usize| Variable::from_u32((foreach_var_base + loop_idx * 4 + 1) as u32);
let fe_len_var =
|loop_idx: usize| Variable::from_u32((foreach_var_base + loop_idx * 4 + 2) as u32);
let fe_idx_var =
|loop_idx: usize| Variable::from_u32((foreach_var_base + loop_idx * 4 + 3) as u32);
let inline_var_base = foreach_var_base + num_foreach_loops * 4;
let mut inline_var_map: HashMap<usize, Vec<Variable>> = HashMap::new();
{
let mut next_var_idx = inline_var_base;
for (ip, &inst) in chunk.code.iter().enumerate() {
let op = (inst >> 24) as u8;
if op != OP_CALL {
continue;
}
let bx = (inst & 0xFFFF) as usize;
let func_idx = bx >> 8;
if func_idx >= program.chunks.len() {
continue;
}
let callee_chunk = &program.chunks[func_idx];
let callee_consts = &program.nan_constants[func_idx];
if !is_inlinable(callee_chunk, callee_consts) {
continue;
}
let n_extra =
(callee_chunk.reg_count as usize).saturating_sub(callee_chunk.param_count as usize);
let mut slot_vars = Vec::with_capacity(n_extra);
for _ in 0..n_extra {
let v = Variable::from_u32(next_var_idx as u32);
builder.declare_var(v, I64);
slot_vars.push(v);
next_var_idx += 1;
}
inline_var_map.insert(ip, slot_vars);
}
}
let total_inline_i64 = inline_var_map.values().map(|v| v.len()).sum::<usize>();
let inline_f64_var_base = foreach_var_base + num_foreach_loops * 4 + total_inline_i64;
let mut inline_f64_var_map: HashMap<usize, Vec<Variable>> = HashMap::new();
{
let mut next_f64_idx = inline_f64_var_base;
for &ip in inline_var_map.keys() {
let bx = (chunk.code[ip] & 0xFFFF) as usize;
let func_idx = bx >> 8;
let callee = &program.chunks[func_idx];
let np = callee.param_count as usize;
let mut fvs = Vec::with_capacity(np);
for _ in 0..np {
let v = Variable::from_u32(next_f64_idx as u32);
builder.declare_var(v, F64);
fvs.push(v);
next_f64_idx += 1;
}
inline_f64_var_map.insert(ip, fvs);
}
}
let leaders = find_block_leaders(&chunk.code);
let mut block_map: HashMap<usize, cranelift_codegen::ir::Block> = HashMap::new();
for &leader in &leaders {
let block = builder.create_block();
block_map.insert(leader, block);
}
let entry_block = block_map[&0];
builder.append_block_params_for_function_params(entry_block);
builder.switch_to_block(entry_block);
for (i, var) in vars.iter().enumerate().take(chunk.param_count as usize) {
let val = builder.block_params(entry_block)[i];
builder.def_var(*var, val);
}
let nil_bits = TAG_NIL;
for var in vars.iter().take(reg_count).skip(chunk.param_count as usize) {
let zero = builder.ins().iconst(I64, nil_bits as i64);
builder.def_var(*var, zero);
}
let mut func_refs: HashMap<FuncId, cranelift_codegen::ir::FuncRef> = HashMap::new();
let mut get_func_ref = |builder: &mut FunctionBuilder<'_>,
module: &mut JITModule,
id: FuncId|
-> cranelift_codegen::ir::FuncRef {
*func_refs
.entry(id)
.or_insert_with(|| module.declare_func_in_func(id, builder.func))
};
let program_ptr_val = program as *const CompiledProgram as u64;
let mut reg_always_num = vec![false; reg_count];
let mut reg_always_bool = vec![false; reg_count];
{
let mut non_num_write = vec![false; reg_count];
let mut num_write = vec![false; reg_count];
let mut bool_write = vec![false; reg_count];
let mut non_bool_write = vec![false; reg_count];
if chunk.all_regs_numeric {
for slot in num_write.iter_mut().take(chunk.param_count as usize) {
*slot = true;
}
}
for &inst in &chunk.code {
let op = (inst >> 24) as u8;
let a = ((inst >> 16) & 0xFF) as usize;
if a >= reg_count {
continue;
}
match op {
OP_ADD_NN | OP_SUB_NN | OP_MUL_NN | OP_DIV_NN
| OP_ADDK_N | OP_SUBK_N | OP_MULK_N | OP_DIVK_N
| OP_LEN | OP_ABS | OP_MIN | OP_MAX
| OP_FLR | OP_CEL | OP_ROU | OP_RND0 | OP_RND2 | OP_RNDN | OP_NOW
| OP_MOD | OP_CLAMP | OP_POW | OP_SQRT | OP_LOG | OP_EXP | OP_SIN | OP_COS
| OP_TAN | OP_LOG10 | OP_LOG2 | OP_ATAN2
| OP_MEDIAN | OP_QUANTILE | OP_STDEV | OP_VARIANCE | OP_DOT | OP_DET
| OP_ORD => {
num_write[a] = true;
}
OP_LOADK => {
let bx = (inst & 0xFFFF) as usize;
if bx < nan_consts.len() && nan_consts[bx].is_number() {
num_write[a] = true;
} else {
non_num_write[a] = true;
}
}
OP_LT | OP_GT | OP_LE | OP_GE | OP_EQ | OP_NE
| OP_NOT | OP_HAS | OP_ISNUM | OP_ISTEXT | OP_ISBOOL | OP_ISLIST
| OP_MHAS | OP_ISOK | OP_ISERR => {
bool_write[a] = true;
non_num_write[a] = true;
}
OP_MOVE => {}
OP_ADD | OP_SUB | OP_MUL | OP_DIV | OP_ADD_SS | OP_NEG
| OP_WRAPOK | OP_WRAPERR | OP_UNWRAP
| OP_RECFLD | OP_RECFLD_NAME | OP_LISTGET | OP_INDEX
| OP_STR | OP_HD | OP_AT | OP_FMT2 | OP_TL | OP_REV | OP_SRT | OP_SRTDESC
| OP_FFT | OP_IFFT
| OP_SLC | OP_LST | OP_ZIP | OP_TAKE | OP_DROP | OP_ENUMERATE | OP_RANGE
| OP_WINDOW | OP_CHUNKS | OP_CUMSUM
| OP_SETUNION | OP_SETINTER | OP_SETDIFF
| OP_INV | OP_SOLVE
| OP_SPL | OP_CAT | OP_GET | OP_POST | OP_GETH | OP_POSTH | OP_GETMANY
| OP_ENV | OP_JPTH | OP_JDMP | OP_JPAR | OP_RDJL
| OP_MAPNEW | OP_MGET | OP_MSET | OP_MDEL | OP_MKEYS | OP_MVALS
| OP_LISTNEW | OP_LISTAPPEND
| OP_RECNEW | OP_RECWITH
| OP_PRT | OP_RD | OP_RDL | OP_WR | OP_WRL | OP_TRM | OP_UPR | OP_LWR | OP_CAP
| OP_PADL | OP_PADR | OP_CHR | OP_UNQ | OP_UNIQBY | OP_PARTITION | OP_FRQ | OP_NUM
| OP_RGXSUB | OP_TRANSPOSE | OP_MATMUL | OP_DTFMT | OP_DTPARSE => {
non_num_write[a] = true;
non_bool_write[a] = true;
}
OP_CALL => {
let bx = (inst & 0xFFFF) as usize;
let func_idx = bx >> 8;
if func_idx < program.chunks.len()
&& program.chunks[func_idx].all_regs_numeric
{
num_write[a] = true;
} else {
non_num_write[a] = true;
non_bool_write[a] = true;
}
}
_ => {}
}
}
loop {
let mut changed = false;
for &inst in &chunk.code {
let op = (inst >> 24) as u8;
if op != OP_MOVE {
continue;
}
let a = ((inst >> 16) & 0xFF) as usize;
let b = ((inst >> 8) & 0xFF) as usize;
if a >= reg_count || b >= reg_count {
continue;
}
let b_always_num = num_write[b] && !non_num_write[b];
let b_always_bool = bool_write[b] && !non_bool_write[b];
if b_always_num {
if !num_write[a] {
num_write[a] = true;
changed = true;
}
} else if !non_num_write[a] {
non_num_write[a] = true;
changed = true;
}
if b_always_bool {
if !bool_write[a] {
bool_write[a] = true;
changed = true;
}
} else if !non_bool_write[a] {
non_bool_write[a] = true;
changed = true;
}
}
if !changed {
break;
}
}
for i in 0..reg_count {
reg_always_num[i] = num_write[i] && !non_num_write[i];
reg_always_bool[i] = bool_write[i] && !non_bool_write[i];
}
}
{
let mf = cranelift_codegen::ir::MemFlags::new();
for i in 0..(chunk.param_count as usize) {
if i < reg_count && reg_always_num[i] {
let iv = builder.use_var(vars[i]);
let fv = builder.ins().bitcast(F64, mf, iv);
builder.def_var(f64_vars[i], fv);
}
}
}
let mut block_terminated = false;
let mut skip_next = false;
for (ip, &inst) in chunk.code.iter().enumerate() {
if skip_next {
skip_next = false;
continue;
}
if ip > 0 && block_map.contains_key(&ip) {
let block = block_map[&ip];
if !block_terminated {
builder.ins().jump(block, &[]);
}
builder.switch_to_block(block);
block_terminated = false;
}
if block_terminated {
continue;
}
let op = (inst >> 24) as u8;
let a_idx = ((inst >> 16) & 0xFF) as usize;
let b_idx = ((inst >> 8) & 0xFF) as usize;
let c_idx = (inst & 0xFF) as usize;
match op {
OP_ADD_NN => {
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let cf = if c_idx < reg_count && reg_always_num[c_idx] {
builder.use_var(f64_vars[c_idx])
} else {
let cv = builder.use_var(vars[c_idx]);
builder.ins().bitcast(F64, mf, cv)
};
let result_f = builder.ins().fadd(bf, cf);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_SUB_NN => {
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let cf = if c_idx < reg_count && reg_always_num[c_idx] {
builder.use_var(f64_vars[c_idx])
} else {
let cv = builder.use_var(vars[c_idx]);
builder.ins().bitcast(F64, mf, cv)
};
let result_f = builder.ins().fsub(bf, cf);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_MUL_NN => {
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let cf = if c_idx < reg_count && reg_always_num[c_idx] {
builder.use_var(f64_vars[c_idx])
} else {
let cv = builder.use_var(vars[c_idx]);
builder.ins().bitcast(F64, mf, cv)
};
let result_f = builder.ins().fmul(bf, cf);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_DIV_NN => {
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let cf = if c_idx < reg_count && reg_always_num[c_idx] {
builder.use_var(f64_vars[c_idx])
} else {
let cv = builder.use_var(vars[c_idx]);
builder.ins().bitcast(F64, mf, cv)
};
let result_f = builder.ins().fdiv(bf, cf);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_ADDK_N => {
let kv = nan_consts.get(c_idx)?.as_number();
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let kval = builder.ins().f64const(kv);
let result_f = builder.ins().fadd(bf, kval);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_SUBK_N => {
let kv = nan_consts.get(c_idx)?.as_number();
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let kval = builder.ins().f64const(kv);
let result_f = builder.ins().fsub(bf, kval);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_MULK_N => {
let kv = nan_consts.get(c_idx)?.as_number();
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let kval = builder.ins().f64const(kv);
let result_f = builder.ins().fmul(bf, kval);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_DIVK_N => {
let kv = nan_consts.get(c_idx)?.as_number();
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let kval = builder.ins().f64const(kv);
let result_f = builder.ins().fdiv(bf, kval);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
}
OP_ADD | OP_SUB | OP_MUL | OP_DIV => {
let both_always_num = b_idx < reg_always_num.len()
&& reg_always_num[b_idx]
&& c_idx < reg_always_num.len()
&& reg_always_num[c_idx];
if both_always_num {
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
let bv = builder.use_var(vars[b_idx]);
builder.ins().bitcast(F64, mf, bv)
};
let cf = if c_idx < reg_count && reg_always_num[c_idx] {
builder.use_var(f64_vars[c_idx])
} else {
let cv = builder.use_var(vars[c_idx]);
builder.ins().bitcast(F64, mf, cv)
};
let result_f = match op {
OP_ADD => builder.ins().fadd(bf, cf),
OP_SUB => builder.ins().fsub(bf, cf),
OP_MUL => builder.ins().fmul(bf, cf),
OP_DIV => builder.ins().fdiv(bf, cf),
_ => unreachable!(),
};
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
} else {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let qnan_val = builder.ins().iconst(I64, QNAN as i64);
let b_masked = builder.ins().band(bv, qnan_val);
let c_masked = builder.ins().band(cv, qnan_val);
let b_or_c = builder.ins().bor(b_masked, c_masked);
let both_num = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::NotEqual,
b_or_c,
qnan_val,
);
let num_block = builder.create_block();
let slow_block = builder.create_block();
let merge_block = builder.create_block();
builder.append_block_param(merge_block, I64);
builder
.ins()
.brif(both_num, num_block, &[], slow_block, &[]);
builder.switch_to_block(num_block);
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = builder.ins().bitcast(F64, mf, bv);
let cf = builder.ins().bitcast(F64, mf, cv);
let result_f = match op {
OP_ADD => builder.ins().fadd(bf, cf),
OP_SUB => builder.ins().fsub(bf, cf),
OP_MUL => builder.ins().fmul(bf, cf),
OP_DIV => builder.ins().fdiv(bf, cf),
_ => unreachable!(),
};
let fast_result = builder.ins().bitcast(I64, mf, result_f);
builder.ins().jump(merge_block, &[fast_result]);
builder.switch_to_block(slow_block);
let helper = match op {
OP_ADD => helpers.add,
OP_SUB => helpers.sub,
OP_MUL => helpers.mul,
OP_DIV => helpers.div,
_ => unreachable!(),
};
let fref = get_func_ref(&mut builder, module, helper);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let slow_result = builder.inst_results(call_inst)[0];
builder.ins().jump(merge_block, &[slow_result]);
builder.switch_to_block(merge_block);
let result = builder.block_params(merge_block)[0];
builder.def_var(vars[a_idx], result);
}
}
OP_ADD_SS => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.concat);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_LT | OP_GT | OP_LE | OP_GE | OP_EQ | OP_NE => {
use cranelift_codegen::ir::condcodes::FloatCC;
let cc = match op {
OP_LT => FloatCC::LessThan,
OP_GT => FloatCC::GreaterThan,
OP_LE => FloatCC::LessThanOrEqual,
OP_GE => FloatCC::GreaterThanOrEqual,
OP_EQ => FloatCC::Equal,
OP_NE => FloatCC::NotEqual,
_ => unreachable!(),
};
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let both_always_num = b_idx < reg_always_num.len()
&& reg_always_num[b_idx]
&& c_idx < reg_always_num.len()
&& reg_always_num[c_idx];
if both_always_num {
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = if b_idx < reg_count && reg_always_num[b_idx] {
builder.use_var(f64_vars[b_idx])
} else {
builder.ins().bitcast(F64, mf, bv)
};
let cf = if c_idx < reg_count && reg_always_num[c_idx] {
builder.use_var(f64_vars[c_idx])
} else {
builder.ins().bitcast(F64, mf, cv)
};
let next_inst = chunk.code.get(ip + 1).copied();
let fused = if let Some(next) = next_inst {
let next_op = (next >> 24) as u8;
let next_a = ((next >> 16) & 0xFF) as usize;
(next_op == OP_JMPF || next_op == OP_JMPT)
&& next_a == a_idx
&& !block_map.contains_key(&(ip + 1)) } else {
false
};
if fused {
let next = chunk.code[ip + 1];
let next_op = (next >> 24) as u8;
let sbx = (next & 0xFFFF) as i16;
let target = (ip as isize + 2 + sbx as isize) as usize;
let fallthrough = ip + 2;
let cmp = builder.ins().fcmp(cc, bf, cf);
let (true_dest, false_dest) = if next_op == OP_JMPF {
(fallthrough, target)
} else {
(target, fallthrough)
};
let true_block = block_map.get(&true_dest).copied();
let false_block = block_map.get(&false_dest).copied();
if let (Some(tb), Some(fb)) = (true_block, false_block) {
builder.ins().brif(cmp, tb, &[], fb, &[]);
block_terminated = true;
skip_next = true;
} else {
let true_val = builder.ins().iconst(I64, TAG_TRUE as i64);
let false_val = builder.ins().iconst(I64, TAG_FALSE as i64);
let result = builder.ins().select(cmp, true_val, false_val);
builder.def_var(vars[a_idx], result);
}
} else {
let cmp = builder.ins().fcmp(cc, bf, cf);
let true_val = builder.ins().iconst(I64, TAG_TRUE as i64);
let false_val = builder.ins().iconst(I64, TAG_FALSE as i64);
let result = builder.ins().select(cmp, true_val, false_val);
builder.def_var(vars[a_idx], result);
}
} else {
let qnan_val = builder.ins().iconst(I64, QNAN as i64);
let b_masked = builder.ins().band(bv, qnan_val);
let c_masked = builder.ins().band(cv, qnan_val);
let b_or_c = builder.ins().bor(b_masked, c_masked);
let both_num = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::NotEqual,
b_or_c,
qnan_val,
);
let num_block = builder.create_block();
let slow_block = builder.create_block();
let merge_block = builder.create_block();
builder.append_block_param(merge_block, I64);
builder
.ins()
.brif(both_num, num_block, &[], slow_block, &[]);
builder.switch_to_block(num_block);
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = builder.ins().bitcast(F64, mf, bv);
let cf = builder.ins().bitcast(F64, mf, cv);
let cmp = builder.ins().fcmp(cc, bf, cf);
let true_val = builder.ins().iconst(I64, TAG_TRUE as i64);
let false_val = builder.ins().iconst(I64, TAG_FALSE as i64);
let fast_result = builder.ins().select(cmp, true_val, false_val);
builder.ins().jump(merge_block, &[fast_result]);
builder.switch_to_block(slow_block);
let helper = match op {
OP_LT => helpers.lt,
OP_GT => helpers.gt,
OP_LE => helpers.le,
OP_GE => helpers.ge,
OP_EQ => helpers.eq,
OP_NE => helpers.ne,
_ => unreachable!(),
};
let fref = get_func_ref(&mut builder, module, helper);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let slow_result = builder.inst_results(call_inst)[0];
builder.ins().jump(merge_block, &[slow_result]);
builder.switch_to_block(merge_block);
let result = builder.block_params(merge_block)[0];
builder.def_var(vars[a_idx], result);
}
}
OP_MOVE => {
if a_idx != b_idx {
let bv = builder.use_var(vars[b_idx]);
let src_always_num = b_idx < reg_always_num.len() && reg_always_num[b_idx];
let src_always_bool = b_idx < reg_always_bool.len() && reg_always_bool[b_idx];
if src_always_num || src_always_bool {
builder.def_var(vars[a_idx], bv);
if src_always_num && a_idx < reg_always_num.len() && reg_always_num[a_idx] {
let bf = builder.use_var(f64_vars[b_idx]);
builder.def_var(f64_vars[a_idx], bf);
}
} else {
let qnan_val = builder.ins().iconst(I64, QNAN as i64);
let masked = builder.ins().band(bv, qnan_val);
let is_heap = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
masked,
qnan_val,
);
let clone_block = builder.create_block();
let after_block = builder.create_block();
builder
.ins()
.brif(is_heap, clone_block, &[], after_block, &[]);
builder.switch_to_block(clone_block);
let fref = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref, &[bv]);
builder.ins().jump(after_block, &[]);
builder.switch_to_block(after_block);
builder.def_var(vars[a_idx], bv);
}
}
}
OP_NOT => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.not);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_NEG => {
if b_idx < reg_always_num.len() && reg_always_num[b_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let bf = builder.use_var(f64_vars[b_idx]);
let result_f = builder.ins().fneg(bf);
let result = builder.ins().bitcast(I64, mf, result_f);
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
builder.def_var(f64_vars[a_idx], result_f);
}
} else {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.neg);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
}
OP_WRAPOK => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.wrapok);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_WRAPERR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.wraperr);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ISOK => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.isok);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ISERR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.iserr);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_UNWRAP => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.unwrap);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_LOADK => {
let bx = (inst & 0xFFFF) as usize;
let bits = nan_consts.get(bx)?.0;
let kval = builder.ins().iconst(I64, bits as i64);
let nv = NanVal(bits);
if nv.is_heap() {
let fref = get_func_ref(&mut builder, module, helpers.jit_move);
let call_inst = builder.ins().call(fref, &[kval]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
} else {
builder.def_var(vars[a_idx], kval);
if nv.is_number() && a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let fv = builder.ins().bitcast(F64, mf, kval);
builder.def_var(f64_vars[a_idx], fv);
}
}
}
OP_JMP => {
let sbx = (inst & 0xFFFF) as i16;
let target = (ip as isize + 1 + sbx as isize) as usize;
if let Some(&target_block) = block_map.get(&target) {
builder.ins().jump(target_block, &[]);
block_terminated = true;
}
}
OP_JMPF | OP_JMPT => {
let sbx = (inst & 0xFFFF) as i16;
let target = (ip as isize + 1 + sbx as isize) as usize;
let fallthrough = ip + 1;
let av = builder.use_var(vars[a_idx]);
if let (Some(&target_block), Some(&fall_block)) =
(block_map.get(&target), block_map.get(&fallthrough))
{
if a_idx < reg_always_bool.len() && reg_always_bool[a_idx] {
let false_val = builder.ins().iconst(I64, TAG_FALSE as i64);
let is_false = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
av,
false_val,
);
if op == OP_JMPF {
builder
.ins()
.brif(is_false, target_block, &[], fall_block, &[]);
} else {
builder
.ins()
.brif(is_false, fall_block, &[], target_block, &[]);
}
block_terminated = true;
} else {
let qnan_val = builder.ins().iconst(I64, QNAN as i64);
let masked = builder.ins().band(av, qnan_val);
let is_num = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::NotEqual,
masked,
qnan_val,
);
let num_truthy_block = builder.create_block();
let tag_truthy_block = builder.create_block();
let merge_truthy = builder.create_block();
builder.append_block_param(merge_truthy, I64);
builder
.ins()
.brif(is_num, num_truthy_block, &[], tag_truthy_block, &[]);
builder.switch_to_block(num_truthy_block);
let mf = cranelift_codegen::ir::MemFlags::new();
let af = builder.ins().bitcast(F64, mf, av);
let zero = builder.ins().f64const(0.0);
let cmp = builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::NotEqual,
af,
zero,
);
let num_result = builder.ins().uextend(I64, cmp);
builder.ins().jump(merge_truthy, &[num_result]);
builder.switch_to_block(tag_truthy_block);
let nil_val = builder.ins().iconst(I64, TAG_NIL as i64);
let false_val2 = builder.ins().iconst(I64, TAG_FALSE as i64);
let not_nil = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::NotEqual,
av,
nil_val,
);
let not_false = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::NotEqual,
av,
false_val2,
);
let tag_truthy = builder.ins().band(not_nil, not_false);
let tag_result = builder.ins().uextend(I64, tag_truthy);
builder.ins().jump(merge_truthy, &[tag_result]);
builder.switch_to_block(merge_truthy);
let truthy_val = builder.block_params(merge_truthy)[0];
if op == OP_JMPF {
builder
.ins()
.brif(truthy_val, fall_block, &[], target_block, &[]);
} else {
builder
.ins()
.brif(truthy_val, target_block, &[], fall_block, &[]);
}
block_terminated = true;
}
}
}
OP_JMPNN => {
let sbx = (inst & 0xFFFF) as i16;
let target = (ip as isize + 1 + sbx as isize) as usize;
let fallthrough = ip + 1;
let av = builder.use_var(vars[a_idx]);
let nil_const = builder.ins().iconst(I64, TAG_NIL as i64);
let is_nil = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
av,
nil_const,
);
if let (Some(&target_block), Some(&fall_block)) =
(block_map.get(&target), block_map.get(&fallthrough))
{
builder
.ins()
.brif(is_nil, fall_block, &[], target_block, &[]);
block_terminated = true;
}
}
OP_RET => {
let av = builder.use_var(vars[a_idx]);
builder.ins().return_(&[av]);
block_terminated = true;
}
OP_LEN => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.len);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_STR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.str_fn);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_NUM => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.num);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ABS => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.abs);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_MIN => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.min);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_MAX => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.max);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_MOD => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.mod_fn);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_CLAMP => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let data_inst = chunk.code[ip + 1];
skip_next = true;
let d_idx = ((data_inst >> 16) & 0xFF) as usize;
let dv = builder.use_var(vars[d_idx]);
let fref = get_func_ref(&mut builder, module, helpers.clamp);
let call_inst = builder.ins().call(fref, &[bv, cv, dv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_FLR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.flr);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_CEL => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.cel);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_ROU => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rou);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_POW => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.pow);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_ATAN2 => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.atan2);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_TRANSPOSE => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.transpose);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MATMUL => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.matmul);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_DOT => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.dot);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_DET => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.det);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_INV => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.inv);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SOLVE => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.solve);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SQRT | OP_LOG | OP_EXP | OP_SIN | OP_COS | OP_TAN | OP_LOG10 | OP_LOG2 => {
let bv = builder.use_var(vars[b_idx]);
let fref = match op {
OP_SQRT => helpers.sqrt,
OP_LOG => helpers.log,
OP_EXP => helpers.exp,
OP_SIN => helpers.sin,
OP_COS => helpers.cos,
OP_TAN => helpers.tan,
OP_LOG10 => helpers.log10,
_ => helpers.log2,
};
let fref = get_func_ref(&mut builder, module, fref);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_RND0 => {
let fref = get_func_ref(&mut builder, module, helpers.rnd0);
let call_inst = builder.ins().call(fref, &[]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_RND2 => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rnd2);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_RNDN => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rndn);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_NOW => {
let fref = get_func_ref(&mut builder, module, helpers.now);
let call_inst = builder.ins().call(fref, &[]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
if a_idx < reg_count && reg_always_num[a_idx] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rf = builder.ins().bitcast(F64, mf, result);
builder.def_var(f64_vars[a_idx], rf);
}
}
OP_ENV => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.env);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_GET => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.get);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SPL => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.spl);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_CAT => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.cat);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_HAS => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.has);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_HD => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.hd);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_AT => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.at);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_FMT2 => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.fmt2);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ZIP => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.zip);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ENUMERATE => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.enumerate);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_RANGE => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.range);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_WINDOW => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.window);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_CHUNKS => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.chunks);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SETUNION => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.setunion);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SETINTER => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.setinter);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SETDIFF => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.setdiff);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_TL => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.tl);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_REV => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rev);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SRT => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.srt);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SRTDESC => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rsrt);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_FFT => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.fft);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_IFFT => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.ifft);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_CUMSUM => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.cumsum);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MEDIAN => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.median);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_QUANTILE => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.quantile);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_STDEV => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.stdev);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_VARIANCE => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.variance);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_SLC => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let data_inst = chunk.code[ip + 1];
skip_next = true;
let d_idx = ((data_inst >> 16) & 0xFF) as usize;
let dv = builder.use_var(vars[d_idx]);
let fref = get_func_ref(&mut builder, module, helpers.slc);
let call_inst = builder.ins().call(fref, &[bv, cv, dv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_LST => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let data_inst = chunk.code[ip + 1];
skip_next = true;
let d_idx = ((data_inst >> 16) & 0xFF) as usize;
let dv = builder.use_var(vars[d_idx]);
let fref = get_func_ref(&mut builder, module, helpers.lst);
let call_inst = builder.ins().call(fref, &[bv, cv, dv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_RGXSUB => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let data_inst = chunk.code[ip + 1];
skip_next = true;
let d_idx = ((data_inst >> 16) & 0xFF) as usize;
let dv = builder.use_var(vars[d_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rgxsub);
let call_inst = builder.ins().call(fref, &[bv, cv, dv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_TAKE => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.take);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_DROP => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.drop_fn);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_LISTAPPEND => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.listappend);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_INDEX => {
let bv = builder.use_var(vars[b_idx]);
let idx_val = builder.ins().iconst(I64, c_idx as i64);
let fref = get_func_ref(&mut builder, module, helpers.index);
let call_inst = builder.ins().call(fref, &[bv, idx_val]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_RECFLD => {
let bv = builder.use_var(vars[b_idx]);
let tag_mask_val = builder.ins().iconst(I64, TAG_MASK as i64);
let tag = builder.ins().band(bv, tag_mask_val);
let arena_tag_val = builder.ins().iconst(I64, TAG_ARENA_REC as i64);
let is_arena = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
tag,
arena_tag_val,
);
let arena_block = builder.create_block();
let heap_block = builder.create_block();
let merge_block = builder.create_block();
builder.append_block_param(merge_block, I64);
builder
.ins()
.brif(is_arena, arena_block, &[], heap_block, &[]);
builder.switch_to_block(arena_block);
let ptr_mask_val = builder.ins().iconst(I64, PTR_MASK as i64);
let ptr = builder.ins().band(bv, ptr_mask_val);
let field_offset = builder.ins().iconst(I64, (8 + c_idx * 8) as i64);
let field_addr = builder.ins().iadd(ptr, field_offset);
let field_val = builder.ins().load(
I64,
cranelift_codegen::ir::MemFlags::trusted(),
field_addr,
0,
);
let qnan_val = builder.ins().iconst(I64, QNAN as i64);
let masked = builder.ins().band(field_val, qnan_val);
let is_nan_tagged = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
masked,
qnan_val,
);
let clone_block = builder.create_block();
let skip_clone_block = builder.create_block();
builder
.ins()
.brif(is_nan_tagged, clone_block, &[], skip_clone_block, &[]);
builder.switch_to_block(clone_block);
let fref_move = get_func_ref(&mut builder, module, helpers.jit_move);
let move_inst = builder.ins().call(fref_move, &[field_val]);
let _cloned = builder.inst_results(move_inst)[0];
builder.ins().jump(skip_clone_block, &[]);
builder.switch_to_block(skip_clone_block);
builder.ins().jump(merge_block, &[field_val]);
builder.switch_to_block(heap_block);
let field_idx_val = builder.ins().iconst(I64, c_idx as i64);
let fref = get_func_ref(&mut builder, module, helpers.recfld);
let call_inst = builder.ins().call(fref, &[bv, field_idx_val]);
let heap_result = builder.inst_results(call_inst)[0];
builder.ins().jump(merge_block, &[heap_result]);
builder.switch_to_block(merge_block);
let result = builder.block_params(merge_block)[0];
builder.def_var(vars[a_idx], result);
}
OP_RECFLD_NAME => {
let b_idx = ((inst >> 8) & 0xFF) as usize;
let c_idx = (inst & 0xFF) as usize;
let bv = builder.use_var(vars[b_idx]);
let cstring = match &chunk.constants[c_idx] {
crate::interpreter::Value::Text(s) => {
std::ffi::CString::new(s.as_bytes()).ok()?
}
_ => return None,
};
let leaked = Box::leak(Box::new(cstring));
let field_name_ptr = leaked.as_ptr() as u64;
let field_name_val = builder.ins().iconst(I64, field_name_ptr as i64);
let registry_ptr = ACTIVE_REGISTRY.with(|r| r.get() as u64);
let registry_val = builder.ins().iconst(I64, registry_ptr as i64);
let fref = get_func_ref(&mut builder, module, helpers.recfld_name);
let call_inst = builder
.ins()
.call(fref, &[bv, field_name_val, registry_val]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_RECNEW => {
let bx = (inst & 0xFFFF) as usize;
let type_id = (bx >> 8) as u16;
let n_fields = bx & 0xFF;
let record_size = 8 + n_fields * 8;
let arena_ptr = jit_arena_ptr();
let arena_ptr_val = builder.ins().iconst(I64, arena_ptr as i64);
let cur_offset = builder.ins().load(
I64,
cranelift_codegen::ir::MemFlags::trusted(),
arena_ptr_val,
16,
);
let seven = builder.ins().iconst(I64, 7);
let off_plus_7 = builder.ins().iadd(cur_offset, seven);
let neg8 = builder.ins().iconst(I64, !7i64);
let aligned = builder.ins().band(off_plus_7, neg8);
let size_val = builder.ins().iconst(I64, record_size as i64);
let new_offset = builder.ins().iadd(aligned, size_val);
let buf_cap = builder.ins().load(
I64,
cranelift_codegen::ir::MemFlags::trusted(),
arena_ptr_val,
8,
);
let has_space = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::UnsignedLessThanOrEqual,
new_offset,
buf_cap,
);
let alloc_block = builder.create_block();
let fallback_block = builder.create_block();
let merge_block = builder.create_block();
builder.append_block_param(merge_block, I64);
builder
.ins()
.brif(has_space, alloc_block, &[], fallback_block, &[]);
builder.switch_to_block(alloc_block);
let buf_ptr = builder.ins().load(
I64,
cranelift_codegen::ir::MemFlags::trusted(),
arena_ptr_val,
0,
);
let rec_ptr = builder.ins().iadd(buf_ptr, aligned);
let header = ((n_fields as u64) << 16) | (type_id as u64);
let header_val = builder.ins().iconst(I64, header as i64);
builder.ins().store(
cranelift_codegen::ir::MemFlags::trusted(),
header_val,
rec_ptr,
0,
);
for i in 0..n_fields {
let field_v = builder.use_var(vars[a_idx + 1 + i]);
let field_off = (8 + i * 8) as i32;
builder.ins().store(
cranelift_codegen::ir::MemFlags::trusted(),
field_v,
rec_ptr,
field_off,
);
let qnan_val = builder.ins().iconst(I64, QNAN as i64);
let masked = builder.ins().band(field_v, qnan_val);
let is_heap = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
masked,
qnan_val,
);
let do_clone = builder.create_block();
let after_clone = builder.create_block();
builder.ins().brif(is_heap, do_clone, &[], after_clone, &[]);
builder.switch_to_block(do_clone);
let fref_move = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref_move, &[field_v]);
builder.ins().jump(after_clone, &[]);
builder.switch_to_block(after_clone);
}
builder.ins().store(
cranelift_codegen::ir::MemFlags::trusted(),
new_offset,
arena_ptr_val,
16,
);
let tag_val = builder.ins().iconst(I64, TAG_ARENA_REC as i64);
let result_val = builder.ins().bor(rec_ptr, tag_val);
builder.ins().jump(merge_block, &[result_val]);
builder.switch_to_block(fallback_block);
let slot =
builder.create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
(n_fields * 8) as u32,
0,
));
for i in 0..n_fields {
let v = builder.use_var(vars[a_idx + 1 + i]);
builder.ins().stack_store(v, slot, (i * 8) as i32);
}
let regs_ptr = builder.ins().stack_addr(I64, slot, 0);
let type_id_and_nfields = ((type_id as u64) << 16) | (n_fields as u64);
let type_id_nfields_val = builder.ins().iconst(I64, type_id_and_nfields as i64);
let registry_ptr_val = builder
.ins()
.iconst(I64, &program.type_registry as *const TypeRegistry as i64);
let fref = get_func_ref(&mut builder, module, helpers.recnew);
let call_inst = builder.ins().call(
fref,
&[
arena_ptr_val,
type_id_nfields_val,
regs_ptr,
registry_ptr_val,
],
);
let fb_result = builder.inst_results(call_inst)[0];
builder.ins().jump(merge_block, &[fb_result]);
builder.switch_to_block(merge_block);
let result = builder.block_params(merge_block)[0];
builder.def_var(vars[a_idx], result);
}
OP_RECWITH => {
let bx = (inst & 0xFFFF) as usize;
let indices_idx = bx >> 8;
let n_updates = bx & 0xFF;
let (update_indices, all_resolved) = match &chunk.constants[indices_idx] {
Value::List(items) => {
let resolved = items.iter().all(|v| matches!(v, Value::Number(_)));
let indices: Vec<u8> = items
.iter()
.map(|v| match v {
Value::Number(n) => *n as u8,
_ => 0,
})
.collect();
(indices, resolved)
}
_ => return None,
};
let old_rec = builder.use_var(vars[a_idx]);
if all_resolved {
let arena_ptr_rw = jit_arena_ptr();
let arena_ptr_rw_val = builder.ins().iconst(I64, arena_ptr_rw as i64);
let mf_t = cranelift_codegen::ir::MemFlags::trusted();
let tag_mask_rw = builder.ins().iconst(I64, TAG_MASK as i64);
let tag_rw = builder.ins().band(old_rec, tag_mask_rw);
let arena_tag_rw = builder.ins().iconst(I64, TAG_ARENA_REC as i64);
let is_arena_rw = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
tag_rw,
arena_tag_rw,
);
let arena_inline_block = builder.create_block();
let fallback_rw_block = builder.create_block();
let merge_rw_block = builder.create_block();
builder.append_block_param(merge_rw_block, I64);
builder.ins().brif(
is_arena_rw,
arena_inline_block,
&[],
fallback_rw_block,
&[],
);
builder.switch_to_block(arena_inline_block);
builder.seal_block(arena_inline_block);
let ptr_mask_rw = builder.ins().iconst(I64, PTR_MASK as i64);
let old_ptr_rw = builder.ins().band(old_rec, ptr_mask_rw);
let header_rw = builder.ins().load(I64, mf_t, old_ptr_rw, 0);
let n_fields_rt = {
let shifted = builder.ins().ushr_imm(header_rw, 16);
let mask16 = builder.ins().iconst(I64, 0xFFFFi64);
builder.ins().band(shifted, mask16)
};
let eight_rw = builder.ins().iconst(I64, 8i64);
let fields_bytes_rw = builder.ins().imul(n_fields_rt, eight_rw);
let record_size_rw = builder.ins().iadd(fields_bytes_rw, eight_rw);
let cur_off_rw = builder.ins().load(I64, mf_t, arena_ptr_rw_val, 16);
let seven_rw = builder.ins().iconst(I64, 7i64);
let neg8_rw = builder.ins().iconst(I64, !7i64);
let off_plus_7_rw = builder.ins().iadd(cur_off_rw, seven_rw);
let aligned_rw = builder.ins().band(off_plus_7_rw, neg8_rw);
let new_off_rw = builder.ins().iadd(aligned_rw, record_size_rw);
let buf_cap_rw = builder.ins().load(I64, mf_t, arena_ptr_rw_val, 8);
let has_space_rw = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::UnsignedLessThanOrEqual,
new_off_rw,
buf_cap_rw,
);
let alloc_rw_block = builder.create_block();
let alloc_fallback_rw_block = builder.create_block();
builder.ins().brif(
has_space_rw,
alloc_rw_block,
&[],
alloc_fallback_rw_block,
&[],
);
builder.switch_to_block(alloc_rw_block);
builder.seal_block(alloc_rw_block);
let buf_ptr_rw = builder.ins().load(I64, mf_t, arena_ptr_rw_val, 0);
let new_ptr_rw = builder.ins().iadd(buf_ptr_rw, aligned_rw);
builder.ins().store(mf_t, header_rw, new_ptr_rw, 0);
let loop_copy_hdr = builder.create_block();
builder.append_block_param(loop_copy_hdr, I64); let copy_body = builder.create_block();
builder.append_block_param(copy_body, I64); let clone_rw_block = builder.create_block();
builder.append_block_param(clone_rw_block, I64); let after_clone_rw = builder.create_block();
builder.append_block_param(after_clone_rw, I64); let copy_done = builder.create_block();
let zero_rw = builder.ins().iconst(I64, 0i64);
builder.ins().jump(loop_copy_hdr, &[zero_rw]);
builder.switch_to_block(loop_copy_hdr);
let ci_hdr = builder.block_params(loop_copy_hdr)[0];
let loop_done_rw = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::UnsignedGreaterThanOrEqual,
ci_hdr,
n_fields_rt,
);
builder
.ins()
.brif(loop_done_rw, copy_done, &[], copy_body, &[ci_hdr]);
builder.switch_to_block(copy_body);
builder.seal_block(copy_body);
let ci = builder.block_params(copy_body)[0];
let ci_bytes_rw = builder.ins().imul(ci, eight_rw);
let ci_off_rw = builder.ins().iadd(ci_bytes_rw, eight_rw);
let src_addr_rw = builder.ins().iadd(old_ptr_rw, ci_off_rw);
let fv_rw = builder.ins().load(I64, mf_t, src_addr_rw, 0);
let dst_addr_rw = builder.ins().iadd(new_ptr_rw, ci_off_rw);
builder.ins().store(mf_t, fv_rw, dst_addr_rw, 0);
let qnan_rw = builder.ins().iconst(I64, QNAN as i64);
let fv_masked_rw = builder.ins().band(fv_rw, qnan_rw);
let fv_is_heap_rw = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
fv_masked_rw,
qnan_rw,
);
builder
.ins()
.brif(fv_is_heap_rw, clone_rw_block, &[ci], after_clone_rw, &[ci]);
builder.switch_to_block(clone_rw_block);
builder.seal_block(clone_rw_block);
let ci_in_clone = builder.block_params(clone_rw_block)[0];
let fref_move_rw = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref_move_rw, &[fv_rw]);
builder.ins().jump(after_clone_rw, &[ci_in_clone]);
builder.switch_to_block(after_clone_rw);
builder.seal_block(after_clone_rw);
let ci_cont = builder.block_params(after_clone_rw)[0];
let ci_next_rw = builder.ins().iadd_imm(ci_cont, 1);
builder.ins().jump(loop_copy_hdr, &[ci_next_rw]);
builder.seal_block(loop_copy_hdr);
builder.switch_to_block(copy_done);
builder.seal_block(copy_done);
let qnan_upd = builder.ins().iconst(I64, QNAN as i64);
for (upd_i, &field_slot) in update_indices.iter().enumerate() {
let new_val_rw = builder.use_var(vars[a_idx + 1 + upd_i]);
let slot_off = (8 + field_slot as i64 * 8) as i32;
let old_fv = builder.ins().load(I64, mf_t, new_ptr_rw, slot_off);
let old_masked_rw = builder.ins().band(old_fv, qnan_upd);
let old_is_heap_rw = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
old_masked_rw,
qnan_upd,
);
let drop_rw_block = builder.create_block();
let after_drop_rw = builder.create_block();
builder
.ins()
.brif(old_is_heap_rw, drop_rw_block, &[], after_drop_rw, &[]);
builder.switch_to_block(drop_rw_block);
builder.seal_block(drop_rw_block);
let fref_drop_rw = get_func_ref(&mut builder, module, helpers.drop_rc);
builder.ins().call(fref_drop_rw, &[old_fv]);
builder.ins().jump(after_drop_rw, &[]);
builder.switch_to_block(after_drop_rw);
builder.seal_block(after_drop_rw);
builder.ins().store(mf_t, new_val_rw, new_ptr_rw, slot_off);
let nv_masked_rw = builder.ins().band(new_val_rw, qnan_upd);
let nv_is_heap_rw = builder.ins().icmp(
cranelift_codegen::ir::condcodes::IntCC::Equal,
nv_masked_rw,
qnan_upd,
);
let clone_nv_block = builder.create_block();
let after_nv_clone = builder.create_block();
builder
.ins()
.brif(nv_is_heap_rw, clone_nv_block, &[], after_nv_clone, &[]);
builder.switch_to_block(clone_nv_block);
builder.seal_block(clone_nv_block);
let fref_move_nv = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref_move_nv, &[new_val_rw]);
builder.ins().jump(after_nv_clone, &[]);
builder.switch_to_block(after_nv_clone);
builder.seal_block(after_nv_clone);
}
builder.ins().store(mf_t, new_off_rw, arena_ptr_rw_val, 16);
let tag_arena_rw = builder.ins().iconst(I64, TAG_ARENA_REC as i64);
let result_rw = builder.ins().bor(new_ptr_rw, tag_arena_rw);
builder.ins().jump(merge_rw_block, &[result_rw]);
builder.switch_to_block(alloc_fallback_rw_block);
builder.seal_block(alloc_fallback_rw_block);
{
let indices_bytes_fb: &'static [u8] =
Box::leak(update_indices.clone().into_boxed_slice());
let slot_fb = builder.create_sized_stack_slot(
cranelift_codegen::ir::StackSlotData::new(
cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
(n_updates * 8) as u32,
0,
),
);
for i in 0..n_updates {
let v = builder.use_var(vars[a_idx + 1 + i]);
builder.ins().stack_store(v, slot_fb, (i * 8) as i32);
}
let regs_ptr_fb = builder.ins().stack_addr(I64, slot_fb, 0);
let indices_ptr_fb =
builder.ins().iconst(I64, indices_bytes_fb.as_ptr() as i64);
let n_upd_fb = builder.ins().iconst(I64, n_updates as i64);
let fref_fb = get_func_ref(&mut builder, module, helpers.recwith_arena);
let call_fb = builder.ins().call(
fref_fb,
&[
old_rec,
arena_ptr_rw_val,
indices_ptr_fb,
n_upd_fb,
regs_ptr_fb,
],
);
let fb_res = builder.inst_results(call_fb)[0];
builder.ins().jump(merge_rw_block, &[fb_res]);
}
builder.switch_to_block(fallback_rw_block);
builder.seal_block(fallback_rw_block);
{
let indices_bytes_hp: &'static [u8] =
Box::leak(update_indices.into_boxed_slice());
let slot_hp = builder.create_sized_stack_slot(
cranelift_codegen::ir::StackSlotData::new(
cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
(n_updates * 8) as u32,
0,
),
);
for i in 0..n_updates {
let v = builder.use_var(vars[a_idx + 1 + i]);
builder.ins().stack_store(v, slot_hp, (i * 8) as i32);
}
let regs_ptr_hp = builder.ins().stack_addr(I64, slot_hp, 0);
let indices_ptr_hp =
builder.ins().iconst(I64, indices_bytes_hp.as_ptr() as i64);
let n_upd_hp = builder.ins().iconst(I64, n_updates as i64);
let fref_hp = get_func_ref(&mut builder, module, helpers.recwith);
let call_hp = builder
.ins()
.call(fref_hp, &[old_rec, indices_ptr_hp, n_upd_hp, regs_ptr_hp]);
let hp_res = builder.inst_results(call_hp)[0];
builder.ins().jump(merge_rw_block, &[hp_res]);
}
builder.switch_to_block(merge_rw_block);
builder.seal_block(merge_rw_block);
let result_rw_final = builder.block_params(merge_rw_block)[0];
builder.def_var(vars[a_idx], result_rw_final);
} else {
let indices_bytes: &'static [u8] = Box::leak(update_indices.into_boxed_slice());
let slot =
builder.create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
(n_updates * 8) as u32,
0,
));
for i in 0..n_updates {
let v = builder.use_var(vars[a_idx + 1 + i]);
builder.ins().stack_store(v, slot, (i * 8) as i32);
}
let regs_ptr = builder.ins().stack_addr(I64, slot, 0);
let indices_ptr_val = builder.ins().iconst(I64, indices_bytes.as_ptr() as i64);
let n_updates_val = builder.ins().iconst(I64, n_updates as i64);
let fref = get_func_ref(&mut builder, module, helpers.recwith);
let call_inst = builder
.ins()
.call(fref, &[old_rec, indices_ptr_val, n_updates_val, regs_ptr]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
}
OP_LISTNEW => {
let n = (inst & 0xFFFF) as usize;
if n == 0 {
let null_ptr = builder.ins().iconst(I64, 0i64);
let n_val = builder.ins().iconst(I64, 0i64);
let fref = get_func_ref(&mut builder, module, helpers.listnew);
let call_inst = builder.ins().call(fref, &[null_ptr, n_val]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
} else {
let slot =
builder.create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
(n * 8) as u32,
0,
));
for i in 0..n {
let v = builder.use_var(vars[a_idx + 1 + i]);
builder.ins().stack_store(v, slot, (i * 8) as i32);
}
let regs_ptr = builder.ins().stack_addr(I64, slot, 0);
let n_val = builder.ins().iconst(I64, n as i64);
let fref = get_func_ref(&mut builder, module, helpers.listnew);
let call_inst = builder.ins().call(fref, &[regs_ptr, n_val]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
}
OP_LISTGET => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let mf_plain = cranelift_codegen::ir::MemFlags::new();
let mf_trusted = cranelift_codegen::ir::MemFlags::trusted();
let ic_eq = cranelift_codegen::ir::condcodes::IntCC::Equal;
let ic_ult = cranelift_codegen::ir::condcodes::IntCC::UnsignedLessThan;
let jmp_block = block_map.get(&(ip + 1)).copied();
let body_block = block_map.get(&(ip + 2)).copied();
if let (Some(jb), Some(bb)) = (jmp_block, body_block) {
let tag_mask_c = builder.ins().iconst(I64, TAG_MASK as i64);
let tag = builder.ins().band(bv, tag_mask_c);
let list_tag_c = builder.ins().iconst(I64, TAG_LIST as i64);
let is_list = builder.ins().icmp(ic_eq, tag, list_tag_c);
let check_num_block = builder.create_block();
builder.ins().brif(is_list, check_num_block, &[], jb, &[]);
builder.switch_to_block(check_num_block);
builder.seal_block(check_num_block);
let qnan_c = builder.ins().iconst(I64, QNAN as i64);
let cv_masked = builder.ins().band(cv, qnan_c);
let is_not_num = builder.ins().icmp(ic_eq, cv_masked, qnan_c);
let load_block = builder.create_block();
builder.ins().brif(is_not_num, jb, &[], load_block, &[]);
builder.switch_to_block(load_block);
builder.seal_block(load_block);
let ptr_mask_c = builder.ins().iconst(I64, PTR_MASK as i64);
let ptr = builder.ins().band(bv, ptr_mask_c);
let vec_len = builder.ins().load(I64, mf_trusted, ptr, 24);
let cv_f = builder.ins().bitcast(F64, mf_plain, cv);
let idx_u = builder.ins().fcvt_to_uint_sat(I64, cv_f);
let in_bounds = builder.ins().icmp(ic_ult, idx_u, vec_len);
let in_bounds_block = builder.create_block();
builder.ins().brif(in_bounds, in_bounds_block, &[], jb, &[]);
builder.switch_to_block(in_bounds_block);
builder.seal_block(in_bounds_block);
let data_ptr = builder.ins().load(I64, mf_trusted, ptr, 16);
let eight = builder.ins().iconst(I64, 8i64);
let byte_off = builder.ins().imul(idx_u, eight);
let elem_addr = builder.ins().iadd(data_ptr, byte_off);
let elem = builder.ins().load(I64, mf_trusted, elem_addr, 0);
let elem_masked = builder.ins().band(elem, qnan_c);
let elem_is_heap = builder.ins().icmp(ic_eq, elem_masked, qnan_c);
let clone_block = builder.create_block();
let after_clone_block = builder.create_block();
builder
.ins()
.brif(elem_is_heap, clone_block, &[], after_clone_block, &[]);
builder.switch_to_block(clone_block);
builder.seal_block(clone_block);
let fref_move = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref_move, &[elem]);
builder.ins().jump(after_clone_block, &[]);
builder.switch_to_block(after_clone_block);
builder.seal_block(after_clone_block);
builder.def_var(vars[a_idx], elem);
builder.ins().jump(bb, &[]);
block_terminated = true;
} else {
let fref = get_func_ref(&mut builder, module, helpers.listget);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
}
OP_FOREACHPREP => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let mf_trusted = cranelift_codegen::ir::MemFlags::trusted();
let ic_eq = cranelift_codegen::ir::condcodes::IntCC::Equal;
let ic_ult = cranelift_codegen::ir::condcodes::IntCC::UnsignedLessThan;
let jmp_block = block_map.get(&(ip + 1)).copied();
let body_block = block_map.get(&(ip + 2)).copied();
if let (Some(jb), Some(bb)) = (jmp_block, body_block) {
let tag_mask_c = builder.ins().iconst(I64, TAG_MASK as i64);
let tag = builder.ins().band(bv, tag_mask_c);
let list_tag_c = builder.ins().iconst(I64, TAG_LIST as i64);
let is_list = builder.ins().icmp(ic_eq, tag, list_tag_c);
let check_num_block = builder.create_block();
builder.ins().brif(is_list, check_num_block, &[], jb, &[]);
builder.switch_to_block(check_num_block);
builder.seal_block(check_num_block);
let qnan_c = builder.ins().iconst(I64, QNAN as i64);
let cv_masked = builder.ins().band(cv, qnan_c);
let is_not_num = builder.ins().icmp(ic_eq, cv_masked, qnan_c);
let load_block = builder.create_block();
builder.ins().brif(is_not_num, jb, &[], load_block, &[]);
builder.switch_to_block(load_block);
builder.seal_block(load_block);
let ptr_mask_c = builder.ins().iconst(I64, PTR_MASK as i64);
let ptr = builder.ins().band(bv, ptr_mask_c);
let vec_len = builder.ins().load(I64, mf_trusted, ptr, 24);
let idx_u = builder.ins().iconst(I64, 0i64);
let in_bounds = builder.ins().icmp(ic_ult, idx_u, vec_len);
let in_bounds_block = builder.create_block();
builder.ins().brif(in_bounds, in_bounds_block, &[], jb, &[]);
builder.switch_to_block(in_bounds_block);
builder.seal_block(in_bounds_block);
let data_ptr = builder.ins().load(I64, mf_trusted, ptr, 16);
if let Some(&loop_idx) = foreach_loop_map.get(&(b_idx, c_idx)) {
builder.def_var(fe_ptr_var(loop_idx), ptr);
builder.def_var(fe_data_ptr_var(loop_idx), data_ptr);
builder.def_var(fe_len_var(loop_idx), vec_len);
builder.def_var(fe_idx_var(loop_idx), idx_u);
}
let elem = builder.ins().load(I64, mf_trusted, data_ptr, 0);
let elem_masked = builder.ins().band(elem, qnan_c);
let elem_is_heap = builder.ins().icmp(ic_eq, elem_masked, qnan_c);
let clone_block = builder.create_block();
let after_clone_block = builder.create_block();
builder
.ins()
.brif(elem_is_heap, clone_block, &[], after_clone_block, &[]);
builder.switch_to_block(clone_block);
builder.seal_block(clone_block);
let fref_move = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref_move, &[elem]);
builder.ins().jump(after_clone_block, &[]);
builder.switch_to_block(after_clone_block);
builder.seal_block(after_clone_block);
builder.def_var(vars[a_idx], elem);
builder.ins().jump(bb, &[]);
block_terminated = true;
} else {
let fref = get_func_ref(&mut builder, module, helpers.listget);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
}
OP_FOREACHNEXT => {
let mf_plain = cranelift_codegen::ir::MemFlags::new();
let mf_trusted = cranelift_codegen::ir::MemFlags::trusted();
let ic_eq = cranelift_codegen::ir::condcodes::IntCC::Equal;
let ic_ult = cranelift_codegen::ir::condcodes::IntCC::UnsignedLessThan;
let jmp_block = block_map.get(&(ip + 1)).copied();
let body_block = block_map.get(&(ip + 2)).copied();
if let (Some(jb), Some(bb)) = (jmp_block, body_block) {
if let Some(&loop_idx) = foreach_loop_map.get(&(b_idx, c_idx)) {
let int_idx = builder.use_var(fe_idx_var(loop_idx));
let one_i = builder.ins().iconst(I64, 1i64);
let new_int_idx = builder.ins().iadd(int_idx, one_i);
builder.def_var(fe_idx_var(loop_idx), new_int_idx);
let new_idx_f64 = builder.ins().fcvt_from_uint(F64, new_int_idx);
let new_idx_nanval = builder.ins().bitcast(I64, mf_plain, new_idx_f64);
builder.def_var(vars[c_idx], new_idx_nanval);
let vec_len = builder.use_var(fe_len_var(loop_idx));
let in_bounds = builder.ins().icmp(ic_ult, new_int_idx, vec_len);
let in_bounds_block = builder.create_block();
builder.ins().brif(in_bounds, in_bounds_block, &[], jb, &[]);
builder.switch_to_block(in_bounds_block);
builder.seal_block(in_bounds_block);
let data_ptr = builder.use_var(fe_data_ptr_var(loop_idx));
let eight = builder.ins().iconst(I64, 8i64);
let byte_off = builder.ins().imul(new_int_idx, eight);
let elem_addr = builder.ins().iadd(data_ptr, byte_off);
let elem = builder.ins().load(I64, mf_trusted, elem_addr, 0);
let qnan_c = builder.ins().iconst(I64, QNAN as i64);
let elem_masked = builder.ins().band(elem, qnan_c);
let elem_is_heap = builder.ins().icmp(ic_eq, elem_masked, qnan_c);
let clone_block = builder.create_block();
let after_clone_block = builder.create_block();
builder
.ins()
.brif(elem_is_heap, clone_block, &[], after_clone_block, &[]);
builder.switch_to_block(clone_block);
builder.seal_block(clone_block);
let fref_move = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref_move, &[elem]);
builder.ins().jump(after_clone_block, &[]);
builder.switch_to_block(after_clone_block);
builder.seal_block(after_clone_block);
builder.def_var(vars[a_idx], elem);
builder.ins().jump(bb, &[]);
block_terminated = true;
} else {
let cv = builder.use_var(vars[c_idx]);
let cv_f64 = builder.ins().bitcast(F64, mf_plain, cv);
let one_f64 = builder.ins().f64const(1.0);
let new_idx_f64 = builder.ins().fadd(cv_f64, one_f64);
let new_idx = builder.ins().bitcast(I64, mf_plain, new_idx_f64);
builder.def_var(vars[c_idx], new_idx);
let bv = builder.use_var(vars[b_idx]);
let ptr_mask_c = builder.ins().iconst(I64, PTR_MASK as i64);
let ptr = builder.ins().band(bv, ptr_mask_c);
let vec_len = builder.ins().load(I64, mf_trusted, ptr, 24);
let idx_u = builder.ins().fcvt_to_uint_sat(I64, new_idx_f64);
let in_bounds = builder.ins().icmp(ic_ult, idx_u, vec_len);
let in_bounds_block = builder.create_block();
builder.ins().brif(in_bounds, in_bounds_block, &[], jb, &[]);
builder.switch_to_block(in_bounds_block);
builder.seal_block(in_bounds_block);
let data_ptr = builder.ins().load(I64, mf_trusted, ptr, 16);
let eight = builder.ins().iconst(I64, 8i64);
let byte_off = builder.ins().imul(idx_u, eight);
let elem_addr = builder.ins().iadd(data_ptr, byte_off);
let elem = builder.ins().load(I64, mf_trusted, elem_addr, 0);
let qnan_c = builder.ins().iconst(I64, QNAN as i64);
let elem_masked = builder.ins().band(elem, qnan_c);
let elem_is_heap = builder.ins().icmp(ic_eq, elem_masked, qnan_c);
let clone_block = builder.create_block();
let after_clone_block = builder.create_block();
builder
.ins()
.brif(elem_is_heap, clone_block, &[], after_clone_block, &[]);
builder.switch_to_block(clone_block);
builder.seal_block(clone_block);
let fref_move = get_func_ref(&mut builder, module, helpers.jit_move);
builder.ins().call(fref_move, &[elem]);
builder.ins().jump(after_clone_block, &[]);
builder.switch_to_block(after_clone_block);
builder.seal_block(after_clone_block);
builder.def_var(vars[a_idx], elem);
builder.ins().jump(bb, &[]);
block_terminated = true;
}
} else {
let cv = builder.use_var(vars[c_idx]);
let cv_f64 = builder.ins().bitcast(F64, mf_plain, cv);
let one_f64 = builder.ins().f64const(1.0);
let new_idx_f64 = builder.ins().fadd(cv_f64, one_f64);
let new_idx = builder.ins().bitcast(I64, mf_plain, new_idx_f64);
builder.def_var(vars[c_idx], new_idx);
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.listget);
let call_inst = builder.ins().call(fref, &[bv, new_idx]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
}
op if op == OP_CMPK_GE_N
|| op == OP_CMPK_GT_N
|| op == OP_CMPK_LT_N
|| op == OP_CMPK_LE_N
|| op == OP_CMPK_EQ_N
|| op == OP_CMPK_NE_N =>
{
let ki = (inst & 0xFF) as usize;
let lhs_f64 = if a_idx < reg_count && reg_always_num[a_idx] {
builder.use_var(f64_vars[a_idx])
} else {
let lhs = builder.use_var(vars[a_idx]);
let mf = cranelift_codegen::ir::MemFlags::new();
builder.ins().bitcast(F64, mf, lhs)
};
let rhs_f64 = if ki < nan_consts.len() {
builder.ins().f64const(nan_consts[ki].as_number())
} else {
builder.ins().f64const(0.0)
};
use cranelift_codegen::ir::condcodes::FloatCC;
let cc = match op {
op if op == OP_CMPK_GE_N => FloatCC::GreaterThanOrEqual,
op if op == OP_CMPK_GT_N => FloatCC::GreaterThan,
op if op == OP_CMPK_LT_N => FloatCC::LessThan,
op if op == OP_CMPK_LE_N => FloatCC::LessThanOrEqual,
op if op == OP_CMPK_EQ_N => FloatCC::Equal,
_ => FloatCC::NotEqual, };
let cmp = builder.ins().fcmp(cc, lhs_f64, rhs_f64);
let body_block = block_map.get(&(ip + 2)).copied();
let false_dest_block = chunk
.code
.get(ip + 1)
.and_then(|&jmp_inst| {
let jmp_op = (jmp_inst >> 24) as u8;
if jmp_op == OP_JMP {
let sbx = (jmp_inst & 0xFFFF) as i16;
let jmp_target = (ip as isize + 2 + sbx as isize) as usize;
block_map.get(&jmp_target).copied()
} else {
None
}
})
.or_else(|| block_map.get(&(ip + 1)).copied());
if let (Some(false_block), Some(bb)) = (false_dest_block, body_block) {
builder.ins().brif(cmp, bb, &[], false_block, &[]);
block_terminated = true;
}
}
OP_CALL => {
let a = ((inst >> 16) & 0xFF) as u8;
let bx = (inst & 0xFFFF) as usize;
let func_idx = bx >> 8;
let n_args = bx & 0xFF;
let a_idx_call = a as usize;
let call_result: cranelift_codegen::ir::Value;
if func_idx < all_func_ids.len() {
let can_inline = func_idx < program.chunks.len()
&& is_inlinable(
&program.chunks[func_idx],
&program.nan_constants[func_idx],
)
&& n_args == program.chunks[func_idx].param_count as usize
&& inline_var_map.contains_key(&ip);
if can_inline {
let callee_chunk = &program.chunks[func_idx];
let callee_consts = &program.nan_constants[func_idx];
let result_var = vars[a as usize];
let arg_var_list: Vec<Variable> =
(0..n_args).map(|i| vars[a as usize + 1 + i]).collect();
let f64_arg_list: Vec<Variable> = {
let mf = cranelift_codegen::ir::MemFlags::new();
let f64_slots = inline_f64_var_map
.get(&ip)
.map(|v| v.as_slice())
.unwrap_or(&[]);
for (i, &av) in arg_var_list.iter().enumerate() {
if i < f64_slots.len() {
let iv = builder.use_var(av);
let fv = builder.ins().bitcast(F64, mf, iv);
builder.def_var(f64_slots[i], fv);
}
}
f64_slots.to_vec()
};
let extra_var_list = inline_var_map
.get(&ip)
.map(|v| v.as_slice())
.unwrap_or(&[])
.to_vec();
let merge_blk = builder.create_block();
let ok = inline_chunk(
&mut builder,
callee_chunk,
callee_consts,
&arg_var_list,
result_var,
&extra_var_list,
&f64_arg_list,
merge_blk,
);
if ok {
builder.switch_to_block(merge_blk);
block_terminated = false;
} else {
builder.ins().jump(merge_blk, &[]);
builder.switch_to_block(merge_blk);
let target_fid = all_func_ids[func_idx];
let target_fref = get_func_ref(&mut builder, module, target_fid);
let call_args: Vec<_> = (0..n_args)
.map(|i| builder.use_var(vars[a as usize + 1 + i]))
.collect();
let call_inst = builder.ins().call(target_fref, &call_args);
let result = builder.inst_results(call_inst)[0];
builder.def_var(result_var, result);
}
} else {
let target_fid = all_func_ids[func_idx];
let target_fref = get_func_ref(&mut builder, module, target_fid);
let mut call_args = Vec::with_capacity(n_args);
for i in 0..n_args {
call_args.push(builder.use_var(vars[a_idx_call + 1 + i]));
}
let call_inst = builder.ins().call(target_fref, &call_args);
call_result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx_call], call_result);
} } else {
if n_args > 0 {
let slot = builder.create_sized_stack_slot(
cranelift_codegen::ir::StackSlotData::new(
cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
(n_args * 8) as u32,
0,
),
);
for i in 0..n_args {
let v = builder.use_var(vars[a_idx_call + 1 + i]);
builder.ins().stack_store(v, slot, (i * 8) as i32);
}
let args_ptr = builder.ins().stack_addr(I64, slot, 0);
let prog_ptr = builder.ins().iconst(I64, program_ptr_val as i64);
let func_idx_val = builder.ins().iconst(I64, func_idx as i64);
let n_args_val = builder.ins().iconst(I64, n_args as i64);
let fref = get_func_ref(&mut builder, module, helpers.call);
let call_inst = builder
.ins()
.call(fref, &[prog_ptr, func_idx_val, args_ptr, n_args_val]);
call_result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx_call], call_result);
} else {
let null_ptr = builder.ins().iconst(I64, 0i64);
let prog_ptr = builder.ins().iconst(I64, program_ptr_val as i64);
let func_idx_val = builder.ins().iconst(I64, func_idx as i64);
let n_args_val = builder.ins().iconst(I64, 0i64);
let fref = get_func_ref(&mut builder, module, helpers.call);
let call_inst = builder
.ins()
.call(fref, &[prog_ptr, func_idx_val, null_ptr, n_args_val]);
call_result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx_call], call_result);
}
}
if a_idx_call < reg_count && reg_always_num[a_idx_call] {
let mf = cranelift_codegen::ir::MemFlags::new();
let rv = builder.use_var(vars[a_idx_call]);
let rf = builder.ins().bitcast(F64, mf, rv);
builder.def_var(f64_vars[a_idx_call], rf);
}
}
OP_JPTH => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.jpth);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_JDMP => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.jdmp);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_JPAR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.jpar);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_RDJL => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rdjl);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_DTFMT => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.dtfmt);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_DTPARSE => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.dtparse);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ISNUM => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.isnum);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ISTEXT => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.istext);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ISBOOL => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.isbool);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ISLIST => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.islist);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MAPNEW => {
let fref = get_func_ref(&mut builder, module, helpers.mapnew);
let call_inst = builder.ins().call(fref, &[]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MGET => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.mget);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MSET => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let data_inst = chunk.code[ip + 1];
skip_next = true;
let d_idx = ((data_inst >> 16) & 0xFF) as usize;
let dv = builder.use_var(vars[d_idx]);
let fref = get_func_ref(&mut builder, module, helpers.mset);
let call_inst = builder.ins().call(fref, &[bv, cv, dv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MHAS => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.mhas);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MKEYS => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.mkeys);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MVALS => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.mvals);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_MDEL => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.mdel);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_PRT => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.prt);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_TRM => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.trm);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_UPR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.upr);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_LWR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.lwr);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_CAP => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.cap);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_PADL => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.padl);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_PADR => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.padr);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_ORD => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.ord);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_CHR => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.chr);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_UNQ => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.unq);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_UNIQBY => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.uniqby);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_PARTITION => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.partition);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_FRQ => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.frq);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_RD => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rd);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_RDL => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.rdl);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_WR => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.wr);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_WRL => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.wrl);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_POST => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.post);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_GETH => {
let bv = builder.use_var(vars[b_idx]);
let cv = builder.use_var(vars[c_idx]);
let fref = get_func_ref(&mut builder, module, helpers.geth);
let call_inst = builder.ins().call(fref, &[bv, cv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_GETMANY => {
let bv = builder.use_var(vars[b_idx]);
let fref = get_func_ref(&mut builder, module, helpers.getmany);
let call_inst = builder.ins().call(fref, &[bv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_POSTH => {
let bv = builder.use_var(vars[b_idx]); let cv = builder.use_var(vars[c_idx]); let data_inst = chunk.code[ip + 1];
skip_next = true;
let d_idx = ((data_inst >> 16) & 0xFF) as usize; let dv = builder.use_var(vars[d_idx]);
let fref = get_func_ref(&mut builder, module, helpers.posth);
let call_inst = builder.ins().call(fref, &[bv, cv, dv]);
let result = builder.inst_results(call_inst)[0];
builder.def_var(vars[a_idx], result);
}
OP_FLATMAP => {
return None;
}
_ => {
return None;
}
}
}
if !block_terminated {
let nil = builder.ins().iconst(I64, TAG_NIL as i64);
builder.ins().return_(&[nil]);
}
builder.seal_all_blocks();
builder.finalize();
module.define_function(func_id, &mut ctx).ok()?;
Some(())
}
pub fn compile(
chunk: &Chunk,
_nan_consts: &[NanVal],
program: &CompiledProgram,
) -> Option<JitFunction> {
let entry_idx = program.chunks.iter().position(|c| std::ptr::eq(c, chunk))?;
compile_program(program, entry_idx)
}
fn compile_program(program: &CompiledProgram, entry_idx: usize) -> Option<JitFunction> {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed").ok()?;
let isa_builder = cranelift_native::builder().ok()?;
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.ok()?;
let mut jit_builder = JITBuilder::with_isa(isa, default_libcall_names());
register_helpers(&mut jit_builder);
let mut module = JITModule::new(jit_builder);
let helpers = declare_all_helpers(&mut module);
let mut func_ids = Vec::with_capacity(program.chunks.len());
for (i, chunk) in program.chunks.iter().enumerate() {
let name = format!("ilo_{}", program.func_names[i]);
let mut sig = module.make_signature();
for _ in 0..chunk.param_count {
sig.params.push(AbiParam::new(I64));
}
sig.returns.push(AbiParam::new(I64));
let fid = module.declare_function(&name, Linkage::Local, &sig).ok()?;
func_ids.push(fid);
}
for (i, (chunk, nan_consts)) in program
.chunks
.iter()
.zip(program.nan_constants.iter())
.enumerate()
{
compile_function_body(
&mut module,
chunk,
nan_consts,
func_ids[i],
&helpers,
&func_ids,
program,
)?;
}
module.finalize_definitions().ok()?;
let entry_func_id = func_ids[entry_idx];
let func_ptr = module.get_finalized_function(entry_func_id);
let param_count = program.chunks[entry_idx].param_count as usize;
Some(JitFunction {
_module: module,
func_ptr,
param_count,
})
}
fn call_raw(func: &JitFunction, args: &[u64]) -> Option<u64> {
if args.len() != func.param_count {
return None;
}
Some(match args.len() {
0 => {
let f: extern "C" fn() -> u64 = unsafe { std::mem::transmute(func.func_ptr) };
f()
}
1 => {
let f: extern "C" fn(u64) -> u64 = unsafe { std::mem::transmute(func.func_ptr) };
f(args[0])
}
2 => {
let f: extern "C" fn(u64, u64) -> u64 = unsafe { std::mem::transmute(func.func_ptr) };
f(args[0], args[1])
}
3 => {
let f: extern "C" fn(u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(func.func_ptr) };
f(args[0], args[1], args[2])
}
4 => {
let f: extern "C" fn(u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(func.func_ptr) };
f(args[0], args[1], args[2], args[3])
}
5 => {
let f: extern "C" fn(u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(func.func_ptr) };
f(args[0], args[1], args[2], args[3], args[4])
}
6 => {
let f: extern "C" fn(u64, u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(func.func_ptr) };
f(args[0], args[1], args[2], args[3], args[4], args[5])
}
7 => {
let f: extern "C" fn(u64, u64, u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(func.func_ptr) };
f(
args[0], args[1], args[2], args[3], args[4], args[5], args[6],
)
}
8 => {
let f: extern "C" fn(u64, u64, u64, u64, u64, u64, u64, u64) -> u64 =
unsafe { std::mem::transmute(func.func_ptr) };
f(
args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
)
}
_ => return None,
})
}
pub fn call(func: &JitFunction, args: &[u64]) -> Option<u64> {
let mut result = call_raw(func, args)?;
let rv = NanVal(result);
if rv.is_arena_record() {
let registry_ptr = ACTIVE_REGISTRY.with(|r| r.get());
if !registry_ptr.is_null() {
let promoted = rv.promote_arena_to_heap(unsafe { &*registry_ptr });
result = promoted.0;
}
}
jit_arena_reset();
Some(result)
}
pub fn compile_and_call(
chunk: &Chunk,
nan_consts: &[NanVal],
args: &[u64],
program: &CompiledProgram,
) -> Option<u64> {
with_active_registry(program, || {
let func = compile(chunk, nan_consts, program)?;
call(&func, args)
})
}
#[cfg(test)]
#[allow(clippy::approx_constant)]
mod tests {
use super::*;
use crate::lexer;
use crate::parser;
fn jit_run(source: &str, func_name: &str, args: &[Value]) -> Option<Value> {
let tokens: Vec<crate::lexer::Token> = lexer::lex(source)
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == func_name)?;
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let nan_args: Vec<u64> = args.iter().map(|v| NanVal::from_value(v).0).collect();
let result = compile_and_call(chunk, nan_consts, &nan_args, &compiled)?;
Some(NanVal(result).to_value())
}
fn jit_run_numeric(source: &str, func_name: &str, args: &[f64]) -> Option<f64> {
let val_args: Vec<Value> = args.iter().map(|n| Value::Number(*n)).collect();
match jit_run(source, func_name, &val_args)? {
Value::Number(n) => Some(n),
_ => None,
}
}
#[test]
fn cranelift_sub_nn() {
let result = jit_run_numeric("f a:n b:n>n;-a b", "f", &[10.0, 3.0]);
assert_eq!(result, Some(7.0));
}
#[test]
fn cranelift_div_nn() {
let result = jit_run_numeric("f a:n b:n>n;/a b", "f", &[10.0, 2.0]);
assert_eq!(result, Some(5.0));
}
#[test]
fn cranelift_subk_n() {
let result = jit_run_numeric("f x:n>n;-x 3", "f", &[10.0]);
assert_eq!(result, Some(7.0));
}
#[test]
fn cranelift_divk_n() {
let result = jit_run_numeric("f x:n>n;/x 4", "f", &[20.0]);
assert_eq!(result, Some(5.0));
}
#[test]
fn cranelift_neg() {
let result = jit_run_numeric("f x:n>n;-x", "f", &[5.0]);
assert_eq!(result, Some(-5.0));
}
#[test]
fn cranelift_zero_arg_function() {
let result = jit_run_numeric("f>n;42", "f", &[]);
assert_eq!(result, Some(42.0));
}
#[test]
fn cranelift_add_k_n() {
let result = jit_run_numeric("f x:n>n;+x 10", "f", &[5.0]);
assert_eq!(result, Some(15.0));
}
#[test]
fn cranelift_move_op() {
let result = jit_run_numeric("f x:n>n;x", "f", &[7.0]);
assert_eq!(result, Some(7.0));
}
#[test]
fn cranelift_arg_count_mismatch() {
let result = jit_run_numeric("f x:n y:n>n;+x y", "f", &[1.0]);
assert_eq!(result, None);
}
#[test]
fn cranelift_move_a_ne_b() {
let result = jit_run_numeric("f x:n>n;y=x;y", "f", &[7.0]);
assert_eq!(result, Some(7.0));
}
#[test]
fn cranelift_4_args() {
let result = jit_run_numeric("f a:n b:n c:n d:n>n;+a +b +c d", "f", &[1.0, 2.0, 3.0, 4.0]);
assert_eq!(result, Some(10.0));
}
#[test]
fn cranelift_5_args() {
let result = jit_run_numeric(
"f a:n b:n c:n d:n e:n>n;+a +b +c +d e",
"f",
&[1.0, 2.0, 3.0, 4.0, 5.0],
);
assert_eq!(result, Some(15.0));
}
#[test]
fn cranelift_6_args() {
let result = jit_run_numeric(
"f a:n b:n c:n d:n e:n f0:n>n;+a +b +c +d +e f0",
"f",
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
);
assert_eq!(result, Some(21.0));
}
#[test]
fn cranelift_7_args() {
let result = jit_run_numeric(
"f a:n b:n c:n d:n e:n f0:n g0:n>n;+a +b +c +d +e +f0 g0",
"f",
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0],
);
assert_eq!(result, Some(28.0));
}
#[test]
fn cranelift_8_args() {
let result = jit_run_numeric(
"f a:n b:n c:n d:n e:n f0:n g0:n h:n>n;+a +b +c +d +e +f0 +g0 h",
"f",
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
);
assert_eq!(result, Some(36.0));
}
#[test]
fn cranelift_9_args_hits_fallback() {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex(
"f a:n b:n c:n d:n e:n f0:n g0:n h:n i:n>n;+a +b +c +d +e +f0 +g0 +h i",
)
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
if let Some(func) = compile(chunk, nan_consts, &compiled) {
let args: Vec<u64> = (1..=9).map(|i| NanVal::number(i as f64).0).collect();
let result = call(&func, &args);
assert_eq!(result, None);
}
}
#[test]
fn cranelift_string_concat() {
let result = jit_run(
r#"f a:t b:t>t;+ a b"#,
"f",
&[Value::Text("hello".into()), Value::Text(" world".into())],
);
assert_eq!(result, Some(Value::Text("hello world".into())));
}
#[test]
fn cranelift_string_constant() {
let result = jit_run(r#"f>t;"hello""#, "f", &[]);
assert_eq!(result, Some(Value::Text("hello".into())));
}
#[test]
fn cranelift_bool_true() {
let result = jit_run("f>b;true", "f", &[]);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_bool_false() {
let result = jit_run("f>b;false", "f", &[]);
assert_eq!(result, Some(Value::Bool(false)));
}
#[test]
fn cranelift_equality() {
let result = jit_run(
"f a:n b:n>b;= a b",
"f",
&[Value::Number(5.0), Value::Number(5.0)],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_inequality() {
let result = jit_run(
"f a:n b:n>b;!= a b",
"f",
&[Value::Number(5.0), Value::Number(3.0)],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_guard_ternary() {
let result = jit_run("f x:n>n;>x 0{x}{0}", "f", &[Value::Number(5.0)]);
assert_eq!(result, Some(Value::Number(5.0)));
let result2 = jit_run("f x:n>n;>x 0{x}{0}", "f", &[Value::Number(-1.0)]);
assert_eq!(result2, Some(Value::Number(0.0)));
}
#[test]
fn cranelift_wrapok() {
let result = jit_run("f x:n>R n t;~x", "f", &[Value::Number(42.0)]);
assert_eq!(result, Some(Value::Ok(Box::new(Value::Number(42.0)))));
}
#[test]
fn cranelift_wraperr() {
let result = jit_run(r#"f x:t>R n t;^"bad""#, "f", &[Value::Text("bad".into())]);
assert_eq!(
result,
Some(Value::Err(Box::new(Value::Text("bad".into()))))
);
}
#[test]
fn cranelift_len_string() {
let result = jit_run(r#"f s:t>n;len s"#, "f", &[Value::Text("hello".into())]);
assert_eq!(result, Some(Value::Number(5.0)));
}
#[test]
fn cranelift_len_list() {
let result = jit_run(
"f xs:L n>n;len xs",
"f",
&[Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0),
])],
);
assert_eq!(result, Some(Value::Number(3.0)));
}
#[test]
fn cranelift_not() {
let result = jit_run("f x:b>b;! x", "f", &[Value::Bool(true)]);
assert_eq!(result, Some(Value::Bool(false)));
}
#[test]
fn cranelift_comparison_gt() {
let result = jit_run(
"f a:n b:n>b;> a b",
"f",
&[Value::Number(5.0), Value::Number(3.0)],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_comparison_lt() {
let result = jit_run(
"f a:n b:n>b;< a b",
"f",
&[Value::Number(3.0), Value::Number(5.0)],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_while_loop() {
let result = jit_run(
"f n:n>n;s=0;i=1;wh <= i n{s=+s i;i=+i 1};s",
"f",
&[Value::Number(10.0)],
);
assert_eq!(result, Some(Value::Number(55.0)));
}
#[test]
fn cranelift_str_builtin() {
let result = jit_run("f x:n>t;str x", "f", &[Value::Number(42.0)]);
assert_eq!(result, Some(Value::Text("42".into())));
}
#[test]
fn cranelift_abs_builtin() {
let result = jit_run("f x:n>n;abs x", "f", &[Value::Number(-5.0)]);
assert_eq!(result, Some(Value::Number(5.0)));
}
#[test]
fn cranelift_function_call() {
let result = jit_run(
"double x:n>n;* x 2\nf x:n>n;double x",
"f",
&[Value::Number(5.0)],
);
assert_eq!(result, Some(Value::Number(10.0)));
}
#[test]
fn cranelift_num_builtin() {
let result = jit_run(
r#"f s:t>n;r=num s;?r{~v:v;^_:0}"#,
"f",
&[Value::Text("3.14".into())],
);
assert_eq!(result, Some(Value::Number(3.14)));
}
#[test]
fn cranelift_flr_builtin() {
let result = jit_run("f x:n>n;flr x", "f", &[Value::Number(4.7)]);
assert_eq!(result, Some(Value::Number(4.0)));
}
#[test]
fn cranelift_cel_builtin() {
let result = jit_run("f x:n>n;cel x", "f", &[Value::Number(4.1)]);
assert_eq!(result, Some(Value::Number(5.0)));
}
#[test]
fn cranelift_min_builtin() {
let result = jit_run(
"f a:n b:n>n;min a b",
"f",
&[Value::Number(3.0), Value::Number(7.0)],
);
assert_eq!(result, Some(Value::Number(3.0)));
}
#[test]
fn cranelift_max_builtin() {
let result = jit_run(
"f a:n b:n>n;max a b",
"f",
&[Value::Number(3.0), Value::Number(7.0)],
);
assert_eq!(result, Some(Value::Number(7.0)));
}
#[test]
fn cranelift_rnd0_returns_number() {
let result = jit_run("f>n;rnd", "f", &[]);
assert!(matches!(result, Some(Value::Number(_))));
}
#[test]
fn cranelift_rnd2_range_returns_number() {
let result = jit_run("f>n;rnd 1 10", "f", &[]);
assert!(matches!(result, Some(Value::Number(_))));
}
#[test]
fn cranelift_env_builtin() {
unsafe {
std::env::set_var("ILO_JIT_TEST_VAR", "hello");
}
let result = jit_run(
r#"f k:t>R t t;env k"#,
"f",
&[Value::Text("ILO_JIT_TEST_VAR".into())],
);
assert_eq!(
result,
Some(Value::Ok(Box::new(Value::Text("hello".into()))))
);
}
#[test]
fn cranelift_spl_builtin() {
let result = jit_run(
r#"f s:t sep:t>L t;spl s sep"#,
"f",
&[Value::Text("a,b,c".into()), Value::Text(",".into())],
);
assert_eq!(
result,
Some(Value::List(vec![
Value::Text("a".into()),
Value::Text("b".into()),
Value::Text("c".into()),
]))
);
}
#[test]
fn cranelift_cat_builtin() {
let result = jit_run(
r#"f xs:L t sep:t>t;cat xs sep"#,
"f",
&[
Value::List(vec![Value::Text("x".into()), Value::Text("y".into())]),
Value::Text("-".into()),
],
);
assert_eq!(result, Some(Value::Text("x-y".into())));
}
#[test]
fn cranelift_has_list() {
let result = jit_run(
"f xs:L n v:n>b;has xs v",
"f",
&[
Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0),
]),
Value::Number(2.0),
],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_hd_builtin() {
let result = jit_run(
"f xs:L n>n;hd xs",
"f",
&[Value::List(vec![Value::Number(10.0), Value::Number(20.0)])],
);
assert_eq!(result, Some(Value::Number(10.0)));
}
#[test]
fn cranelift_tl_builtin() {
let result = jit_run(
"f xs:L n>L n;tl xs",
"f",
&[Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0),
])],
);
assert_eq!(
result,
Some(Value::List(vec![Value::Number(2.0), Value::Number(3.0)]))
);
}
#[test]
fn cranelift_rev_builtin() {
let result = jit_run(
"f xs:L n>L n;rev xs",
"f",
&[Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0),
])],
);
assert_eq!(
result,
Some(Value::List(vec![
Value::Number(3.0),
Value::Number(2.0),
Value::Number(1.0)
]))
);
}
#[test]
fn cranelift_srt_builtin() {
let result = jit_run(
"f xs:L n>L n;srt xs",
"f",
&[Value::List(vec![
Value::Number(3.0),
Value::Number(1.0),
Value::Number(2.0),
])],
);
assert_eq!(
result,
Some(Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0)
]))
);
}
#[test]
fn cranelift_slc_builtin() {
let result = jit_run(
"f xs:L n a:n b:n>L n;slc xs a b",
"f",
&[
Value::List(vec![
Value::Number(10.0),
Value::Number(20.0),
Value::Number(30.0),
Value::Number(40.0),
]),
Value::Number(1.0),
Value::Number(3.0),
],
);
assert_eq!(
result,
Some(Value::List(vec![Value::Number(20.0), Value::Number(30.0)]))
);
}
#[test]
fn cranelift_listappend() {
let result = jit_run(
"f xs:L n v:n>L n;r=+=xs v;r",
"f",
&[
Value::List(vec![Value::Number(1.0), Value::Number(2.0)]),
Value::Number(3.0),
],
);
assert_eq!(
result,
Some(Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0)
]))
);
}
#[test]
fn cranelift_listnew() {
let result = jit_run(
"f a:n b:n>L n;[a, b]",
"f",
&[Value::Number(5.0), Value::Number(6.0)],
);
assert_eq!(
result,
Some(Value::List(vec![Value::Number(5.0), Value::Number(6.0)]))
);
}
#[test]
fn cranelift_index_literal() {
let result = jit_run(
"f xs:L n>n;xs.0",
"f",
&[Value::List(vec![
Value::Number(10.0),
Value::Number(20.0),
Value::Number(30.0),
])],
);
assert_eq!(result, Some(Value::Number(10.0)));
}
#[test]
fn cranelift_recnew_and_field() {
let src = "type pt{x:n;y:n} f a:n b:n>n;p=pt x:a y:b;p.x";
let result = jit_run(src, "f", &[Value::Number(3.0), Value::Number(4.0)]);
assert_eq!(result, Some(Value::Number(3.0)));
}
#[test]
fn cranelift_recwith() {
let src = "type pt{x:n;y:n} f a:n b:n>n;p=pt x:a y:b;q=p with x:99;q.x";
let result = jit_run(src, "f", &[Value::Number(3.0), Value::Number(4.0)]);
assert_eq!(result, Some(Value::Number(99.0)));
}
#[test]
fn cranelift_jdmp_number() {
let result = jit_run("f x:n>t;jdmp x", "f", &[Value::Number(42.0)]);
assert_eq!(result, Some(Value::Text("42".into())));
}
#[test]
fn cranelift_jpar_ok() {
let result = jit_run(
r#"f s:t>R t t;jpar s"#,
"f",
&[Value::Text(r#"{"k":"v"}"#.into())],
);
assert!(matches!(result, Some(Value::Ok(_))));
}
#[test]
fn cranelift_jpth_ok() {
let result = jit_run(
r#"f j:t p:t>R t t;jpth j p"#,
"f",
&[
Value::Text(r#"{"name":"alice"}"#.into()),
Value::Text("name".into()),
],
);
assert_eq!(
result,
Some(Value::Ok(Box::new(Value::Text("alice".into()))))
);
}
#[test]
fn cranelift_isok_via_match() {
let result = jit_run(
"f x:R n t>n;?x{~v:v;^_:0}",
"f",
&[Value::Ok(Box::new(Value::Number(7.0)))],
);
assert_eq!(result, Some(Value::Number(7.0)));
}
#[test]
fn cranelift_iserr_via_match() {
let result = jit_run(
r#"f x:R n t>n;?x{~_:1;^_:99}"#,
"f",
&[Value::Err(Box::new(Value::Text("bad".into())))],
);
assert_eq!(result, Some(Value::Number(99.0)));
}
#[test]
fn cranelift_unwrap_via_match() {
let src = "f x:R n t>n;?x{~v:v;^_:0}";
let result = jit_run(src, "f", &[Value::Ok(Box::new(Value::Number(42.0)))]);
assert_eq!(result, Some(Value::Number(42.0)));
}
#[test]
fn cranelift_now_returns_number() {
let result = jit_run("f>n;now", "f", &[]);
assert!(matches!(result, Some(Value::Number(_))));
}
#[test]
fn cranelift_nil_coalesce_with_value() {
let result = jit_run("f x:O n>n;x??42", "f", &[Value::Number(7.0)]);
assert_eq!(result, Some(Value::Number(7.0)));
}
#[test]
fn cranelift_nil_coalesce_with_nil() {
let result = jit_run("f x:O n>n;x??42", "f", &[Value::Nil]);
assert_eq!(result, Some(Value::Number(42.0)));
}
#[test]
fn cranelift_empty_list_literal() {
let result = jit_run("f>L n;[]", "f", &[]);
assert_eq!(result, Some(Value::List(vec![])));
}
#[test]
fn jit_run_numeric_non_number_returns_none() {
let result = jit_run_numeric("f>b;true", "f", &[]);
assert_eq!(result, None);
}
#[test]
fn cranelift_recfld_name_works() {
let source = r#"f x:t>R t t;r=jpar! x;r.score"#;
let tokens: Vec<(crate::lexer::Token, crate::ast::Span)> = lexer::lex(source)
.unwrap()
.into_iter()
.map(|(t, r)| {
(
t,
crate::ast::Span {
start: r.start,
end: r.end,
},
)
})
.collect();
let (prog, errors) = parser::parse(tokens);
assert!(errors.is_empty(), "parse errors: {:?}", errors);
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let nan_args: Vec<u64> = [Value::Text(r#"{"score":42}"#.to_string())]
.iter()
.map(|v| NanVal::from_value(v).0)
.collect();
let result = compile_and_call(chunk, nan_consts, &nan_args, &compiled);
assert!(result.is_some(), "JIT should handle OP_RECFLD_NAME");
let val = NanVal(result.unwrap()).to_value();
match val {
Value::Number(n) => assert_eq!(n, 42.0),
other => panic!("expected Number(42), got {:?}", other),
}
}
#[test]
fn cranelift_function_ends_without_explicit_terminator() {
let result = jit_run("f x:n>n;wh > x 0{x=-x};x", "f", &[Value::Number(5.0)]);
assert_eq!(result, Some(Value::Number(-5.0)));
}
#[test]
fn cranelift_rou_builtin() {
let result = jit_run("f x:n>n;rou x", "f", &[Value::Number(4.5)]);
assert_eq!(result, Some(Value::Number(5.0)));
}
#[test]
fn cranelift_rou_down() {
let result = jit_run("f x:n>n;rou x", "f", &[Value::Number(4.4)]);
assert_eq!(result, Some(Value::Number(4.0)));
}
#[test]
fn cranelift_call_zero_args_injected() {
use crate::vm::{OP_CALL, OP_RET, compile as vm_compile};
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex("g>n;42\nf>n;42")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let mut compiled = vm_compile(&prog).unwrap();
let f_idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let g_idx = compiled.func_names.iter().position(|n| n == "g").unwrap();
let call_inst = (OP_CALL as u32) << 24 | ((g_idx as u32) << 8);
let ret_inst = (OP_RET as u32) << 24;
compiled.chunks[f_idx].code = vec![call_inst, ret_inst];
let chunk = &compiled.chunks[f_idx];
let nan_consts = &compiled.nan_constants[f_idx];
let func = compile(chunk, nan_consts, &compiled);
if let Some(f) = func {
let result = call(&f, &[]);
assert_eq!(result, Some(NanVal::number(42.0).0));
}
}
#[test]
fn cranelift_record_return_promotes_arena() {
let src = "type pt{x:n;y:n} f a:n b:n>pt;pt x:a y:b";
let result = jit_run(src, "f", &[Value::Number(10.0), Value::Number(20.0)]);
match result {
Some(Value::Record { type_name, fields }) => {
assert_eq!(type_name, "pt");
assert_eq!(fields.get("x"), Some(&Value::Number(10.0)));
assert_eq!(fields.get("y"), Some(&Value::Number(20.0)));
}
other => panic!("expected Record, got {:?}", other),
}
}
#[test]
fn cranelift_recwith_update() {
let src = "type pt{x:n;y:n} f>pt;p=pt x:1 y:2;p with x:99";
let result = jit_run(src, "f", &[]);
match result {
Some(Value::Record { type_name, fields }) => {
assert_eq!(type_name, "pt");
assert_eq!(fields.get("x"), Some(&Value::Number(99.0)));
assert_eq!(fields.get("y"), Some(&Value::Number(2.0)));
}
other => panic!("expected Record, got {:?}", other),
}
}
#[test]
fn cranelift_foreach_loop() {
let result = jit_run(
"f xs:L n>n;s=0;@x xs{s=+s x};s",
"f",
&[Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(3.0),
])],
);
assert_eq!(result, Some(Value::Number(6.0)));
}
#[test]
fn cranelift_foreach_loop_heap_elements() {
let result = jit_run(
"f xs:L n>n;s=0;@x xs{s=+s 1};s",
"f",
&[Value::List(vec![
Value::Text("a".to_string()),
Value::Text("b".to_string()),
Value::Text("c".to_string()),
])],
);
assert_eq!(result, Some(Value::Number(3.0)));
}
#[test]
fn cranelift_foreach_empty_list() {
let result = jit_run(
"f xs:L n>n;s=0;@x xs{s=+s x};s",
"f",
&[Value::List(vec![])],
);
assert_eq!(result, Some(Value::Number(0.0)));
}
#[test]
fn cranelift_sequential_cross_function_calls() {
let result = jit_run(
"dbl x:n>n;*x 2\ntriple x:n>n;*x 3\nf n:n>n;a=dbl n;triple a",
"f",
&[Value::Number(5.0)],
);
assert_eq!(result, Some(Value::Number(30.0)));
}
#[test]
fn cranelift_pipe_chain() {
let result = jit_run(
"dbl x:n>n;*x 2\ninc x:n>n;+x 1\nf n:n>n;n>>dbl>>inc>>dbl>>inc",
"f",
&[Value::Number(5.0)],
);
assert_eq!(result, Some(Value::Number(23.0)));
}
#[test]
fn cranelift_foreach_listbuild_many_calls() {
let source = "f n:n>n;xs=[];i=0;wh <i n{xs=+=xs i;i=+i 1};s=0;@x xs{s=+s x};s";
let prog = {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex(source)
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
crate::parser::parse_tokens(tokens).unwrap()
};
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let n_val = crate::vm::NanVal::from_value(&Value::Number(100.0)).0;
let nan_args = vec![n_val];
crate::vm::with_active_registry(&compiled, || {
if let Some(jit_func) = compile(chunk, nan_consts, &compiled) {
for i in 0..10_100u32 {
let result = call(&jit_func, &nan_args).expect("JIT call failed");
let val = crate::vm::NanVal(result).to_value();
assert_eq!(val, Value::Number(4950.0), "failed on iteration {}", i);
}
} else {
panic!("JIT compilation failed");
}
});
}
#[test]
fn cranelift_cov_divk_n() {
let result = jit_run_numeric("f x:n>n;/x 2", "f", &[10.0]);
assert_eq!(result, Some(5.0));
}
#[test]
fn cranelift_cov_gt_comparison() {
let result = jit_run_numeric("f x:n>n;>x 5 1;0", "f", &[10.0]);
assert_eq!(result, Some(1.0));
}
#[test]
fn cranelift_cov_gt_comparison_false() {
let result = jit_run_numeric("f x:n>n;>x 5 1;0", "f", &[3.0]);
assert_eq!(result, Some(0.0));
}
#[test]
fn cranelift_cov_gte() {
let result = jit_run_numeric("f x:n>n;>=x 5 1;0", "f", &[5.0]);
assert_eq!(result, Some(1.0));
}
#[test]
fn cranelift_cov_lte() {
let result = jit_run_numeric("f x:n>n;<=x 5 1;0", "f", &[5.0]);
assert_eq!(result, Some(1.0));
}
#[test]
fn cranelift_cov_modulo() {
let result = jit_run_numeric("f a:n b:n>n;mod a b", "f", &[10.0, 3.0]);
assert_eq!(result, Some(1.0));
}
#[test]
fn cranelift_cov_record_field() {
let result = jit_run_numeric("type pt{x:n;y:n}\nf>n;p=pt x:3 y:4;p.x", "f", &[]);
assert_eq!(result, Some(3.0));
}
#[test]
fn cranelift_cov_record_with() {
let result = jit_run_numeric(
"type pt{x:n;y:n}\nf>n;p=pt x:1 y:2;q=p with x:10;+q.x q.y",
"f",
&[],
);
assert_eq!(result, Some(12.0));
}
#[test]
fn cranelift_cov_eq() {
let result = jit_run_numeric("f a:n b:n>n;=a b 1;0", "f", &[5.0, 5.0]);
assert_eq!(result, Some(1.0));
}
#[test]
fn cranelift_cov_neq() {
let result = jit_run_numeric("f a:n b:n>n;!=a b 1;0", "f", &[5.0, 3.0]);
assert_eq!(result, Some(1.0));
}
#[test]
fn cranelift_cov_deep_call() {
let result = jit_run_numeric("a x:n>n;+x 1\nb x:n>n;a x\nf x:n>n;b x", "f", &[10.0]);
assert_eq!(result, Some(11.0));
}
#[test]
fn cranelift_cov_nil_guard() {
let result = jit_run("f x:n>n;>x 0{x}", "f", &[Value::Number(-1.0)]);
match result {
Some(Value::Nil) => {} None => {} other => panic!("expected Nil or None, got {:?}", other),
}
}
#[test]
fn cranelift_is_inlinable_too_many_regs() {
let result = jit_run_numeric(
"sum17 a:n b:n c:n d:n e:n f:n g:n h:n i:n j:n k:n l:n m:n nn:n o:n p:n q:n>n;\
+a +b +c +d +e +f +g +h +i +j +k +l +m +nn +o +p q\n\
caller>n;sum17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17",
"caller",
&[],
);
assert_eq!(result, Some(153.0));
}
#[test]
fn cranelift_is_inlinable_non_numeric_regs_not_inlined() {
let result = jit_run(
"strjoin a:t b:t>t;+a b\nf a:t b:t>t;strjoin a b",
"f",
&[Value::Text("hello".into()), Value::Text(" world".into())],
);
assert_eq!(result, Some(Value::Text("hello world".into())));
}
#[test]
fn cranelift_non_numeric_call_result_non_num_branch() {
let result = jit_run(
"greet x:t>t;+\"hi \" x\nf name:t>t;greet name",
"f",
&[Value::Text("alice".into())],
);
assert_eq!(result, Some(Value::Text("hi alice".into())));
}
#[test]
fn cranelift_isnum_true() {
let result = jit_run(
r#"f x:t>b;?x{n _:true;_:false}"#,
"f",
&[Value::Number(5.0)],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_istext_true() {
let result = jit_run(
r#"f x:t>b;?x{t _:true;_:false}"#,
"f",
&[Value::Text("hi".into())],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_isbool_true() {
let result = jit_run("f x:b>b;?x{b _:true;_:false}", "f", &[Value::Bool(true)]);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_islist_true() {
let result = jit_run(
"f x:t>b;?x{l _:true;_:false}",
"f",
&[Value::List(vec![Value::Number(1.0)])],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_map_new_set_get() {
let result = jit_run(r#"f>n;m=mset mmap "k" 42;mget m "k""#, "f", &[]);
match result {
Some(Value::Number(n)) => assert_eq!(n, 42.0),
Some(Value::Ok(v)) => assert_eq!(*v, Value::Number(42.0)),
other => panic!("expected 42, got {:?}", other),
}
}
#[test]
fn cranelift_map_has() {
let result = jit_run(r#"f>b;m=mset mmap "a" 1;mhas m "a""#, "f", &[]);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_map_keys() {
let result = jit_run(r#"f>n;m=mset mmap "x" 1;k=mkeys m;len k"#, "f", &[]);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_map_vals() {
let result = jit_run(r#"f>n;m=mset mmap "x" 99;v=mvals m;len v"#, "f", &[]);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_map_del() {
let result = jit_run(
r#"f>n;m=mset mmap "a" 1;m=mdel m "a";k=mkeys m;len k"#,
"f",
&[],
);
assert_eq!(result, Some(Value::Number(0.0)));
}
#[test]
fn cranelift_prt_builtin() {
let result = jit_run("f x:n>n;prnt x;x", "f", &[Value::Number(7.0)]);
assert_eq!(result, Some(Value::Number(7.0)));
}
#[test]
fn cranelift_trm_builtin() {
let result = jit_run(r#"f s:t>t;trm s"#, "f", &[Value::Text(" hello ".into())]);
assert_eq!(result, Some(Value::Text("hello".into())));
}
#[test]
fn cranelift_unq_builtin() {
let result = jit_run(
"f xs:L n>n;u=unq xs;len u",
"f",
&[Value::List(vec![
Value::Number(1.0),
Value::Number(2.0),
Value::Number(1.0),
Value::Number(3.0),
])],
);
assert_eq!(result, Some(Value::Number(3.0)));
}
#[test]
fn cranelift_eq_mixed_type_operands() {
let result = jit_run(
r#"f x:t y:t>b;= x y"#,
"f",
&[Value::Text("hello".into()), Value::Text("hello".into())],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_lt_mixed_non_numeric() {
let result = jit_run(
r#"f x:t y:t>b;< x y"#,
"f",
&[Value::Text("a".into()), Value::Text("b".into())],
);
assert_eq!(result, Some(Value::Bool(true)));
}
#[test]
fn cranelift_jmpt_numeric_truthy() {
let result = jit_run_numeric("f x:n>n;x{1}{0}", "f", &[5.0]);
assert_eq!(result, Some(1.0));
}
#[test]
fn cranelift_jmpf_numeric_falsy_zero() {
let result = jit_run_numeric("f x:n>n;x{1}{0}", "f", &[0.0]);
assert_eq!(result, Some(0.0));
}
#[test]
fn cranelift_wr_compiles_and_runs() {
use std::env::temp_dir;
let path = temp_dir().join("ilo_jit_wr_test.txt");
let path_str = path.to_str().unwrap().to_string();
let result = jit_run(
"f p:t c:t>t;wr p c",
"f",
&[Value::Text(path_str), Value::Text("hello jit\n".into())],
);
let _ = std::fs::remove_file(&path);
assert!(result.is_some());
}
#[test]
fn cranelift_rd_compiles() {
let bytes = {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex("f p:t>R t t;rd p")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
compile(chunk, nan_consts, &compiled).is_some()
};
assert!(bytes, "OP_RD JIT compilation should succeed");
}
#[test]
fn cranelift_get_compiles() {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex("f url:t>R t t;get url")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let compiled_fn = compile(chunk, nan_consts, &compiled);
assert!(
compiled_fn.is_some(),
"OP_GET JIT compilation should succeed"
);
}
#[test]
fn cranelift_inline_sub_nn_callee() {
let result = jit_run_numeric(
"diff a:n b:n>n;-a b\nf a:n b:n>n;diff a b",
"f",
&[10.0, 3.0],
);
assert_eq!(result, Some(7.0));
}
#[test]
fn cranelift_inline_div_nn_callee() {
let result = jit_run_numeric(
"div2 a:n b:n>n;/a b\nf a:n b:n>n;div2 a b",
"f",
&[20.0, 4.0],
);
assert_eq!(result, Some(5.0));
}
#[test]
fn cranelift_inline_mulk_n_callee() {
let result = jit_run_numeric("triple x:n>n;*x 3\nf x:n>n;triple x", "f", &[7.0]);
assert_eq!(result, Some(21.0));
}
#[test]
fn cranelift_inline_divk_n_callee() {
let result = jit_run_numeric("half x:n>n;/x 2\nf x:n>n;half x", "f", &[14.0]);
assert_eq!(result, Some(7.0));
}
#[test]
fn cranelift_inline_addk_n_callee() {
let result = jit_run_numeric("inc x:n>n;+x 1\nf x:n>n;inc x", "f", &[9.0]);
assert_eq!(result, Some(10.0));
}
#[test]
fn cranelift_inline_subk_n_callee() {
let result = jit_run_numeric("dec x:n>n;-x 1\nf x:n>n;dec x", "f", &[5.0]);
assert_eq!(result, Some(4.0));
}
#[test]
fn cranelift_num_text_to_number() {
let result = jit_run(r#"f s:t>R n t;num s"#, "f", &[Value::Text("3.14".into())]);
assert_eq!(result, Some(Value::Ok(Box::new(Value::Number(3.14)))));
}
#[test]
fn cranelift_map_set_and_get_string_value() {
let result = jit_run(r#"f>t;m=mset mmap "key" "val";mget m "key""#, "f", &[]);
match result {
Some(Value::Text(s)) => assert_eq!(s, "val"),
Some(Value::Ok(v)) => assert_eq!(*v, Value::Text("val".into())),
other => panic!("expected 'val', got {:?}", other),
}
}
#[test]
fn cranelift_wrl_compiles() {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex("f p:t c:t>t;wrl p c")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let compiled_fn = compile(chunk, nan_consts, &compiled);
assert!(
compiled_fn.is_some(),
"OP_WRL JIT compilation should succeed"
);
}
#[test]
fn cranelift_rdl_compiles() {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex("f p:t>R t t;rdl p")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let compiled_fn = compile(chunk, nan_consts, &compiled);
assert!(
compiled_fn.is_some(),
"OP_RDL JIT compilation should succeed"
);
}
#[test]
fn cranelift_move_string_value() {
let result = jit_run(r#"f s:t>t;t=s;t"#, "f", &[Value::Text("hello".into())]);
assert_eq!(result, Some(Value::Text("hello".into())));
}
#[test]
fn cranelift_neg_string_slow_path() {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex("f x:t>n;-x")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let compiled_fn = compile(chunk, nan_consts, &compiled);
assert!(
compiled_fn.is_some(),
"OP_NEG with text type should compile"
);
}
#[test]
fn cranelift_post_compiles() {
let tokens: Vec<crate::lexer::Token> =
crate::lexer::lex("f url:t body:t>R t t;post url body")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
assert!(
compile(chunk, nan_consts, &compiled).is_some(),
"OP_POST should compile"
);
}
#[test]
fn cranelift_geth_compiles() {
let tokens: Vec<crate::lexer::Token> =
crate::lexer::lex("f url:t hdrs:M t t>R t t;get url hdrs")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
assert!(
compile(chunk, nan_consts, &compiled).is_some(),
"OP_GETH should compile"
);
}
#[test]
fn cranelift_mul_nn() {
let result = jit_run_numeric("f a:n b:n>n;*a b", "f", &[3.0, 4.0]);
assert_eq!(result, Some(12.0));
}
#[test]
fn cranelift_add_nn() {
let result = jit_run_numeric("f a:n b:n>n;+a b", "f", &[5.0, 7.0]);
assert_eq!(result, Some(12.0));
}
#[test]
fn cranelift_inline_add_nn_callee() {
let result = jit_run_numeric(
"mysum a:n b:n>n;+a b\nf a:n b:n>n;mysum a b",
"f",
&[6.0, 9.0],
);
assert_eq!(result, Some(15.0));
}
#[test]
fn cranelift_inline_mul_nn_callee() {
let result = jit_run_numeric(
"myprod a:n b:n>n;*a b\nf a:n b:n>n;myprod a b",
"f",
&[4.0, 5.0],
);
assert_eq!(result, Some(20.0));
}
#[test]
fn cranelift_inline_cmpk_guard_callee() {
let result = jit_run_numeric("pos x:n>n;>x 0 x;0\nf x:n>n;pos x", "f", &[5.0]);
assert_eq!(result, Some(5.0));
let result2 = jit_run_numeric("pos x:n>n;>x 0 x;0\nf x:n>n;pos x", "f", &[-3.0]);
assert_eq!(result2, Some(0.0));
}
#[test]
fn cranelift_foreach_numeric_list() {
let result = jit_run(
"f xs:L n>n;@x xs{*x x}",
"f",
&[Value::List(vec![
Value::Number(3.0),
Value::Number(4.0),
Value::Number(5.0),
])],
);
assert_eq!(result, Some(Value::Number(25.0)));
}
#[test]
fn cranelift_foreach_text_list() {
let tokens: Vec<crate::lexer::Token> = crate::lexer::lex("f xs:L t>t;@x xs{x}")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
let compiled_fn = compile(chunk, nan_consts, &compiled);
assert!(
compiled_fn.is_some(),
"foreach text list JIT compilation should succeed"
);
}
#[test]
fn cranelift_sub_nn_non_always_num() {
let result = jit_run(
"f x:n y:t>n;-x x",
"f",
&[Value::Number(8.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(0.0)));
}
#[test]
fn cranelift_mul_nn_non_always_num() {
let result = jit_run(
"f x:n y:t>n;*x x",
"f",
&[Value::Number(3.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(9.0)));
}
#[test]
fn cranelift_div_nn_non_always_num() {
let result = jit_run(
"f x:n y:t>n;/x x",
"f",
&[Value::Number(6.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_generic_sub() {
let result = jit_run_numeric("f x:v y:v>n;-x y", "f", &[10.0, 3.0]);
assert_eq!(result, Some(7.0));
}
#[test]
fn cranelift_generic_mul() {
let result = jit_run_numeric("f x:v y:v>n;*x y", "f", &[4.0, 5.0]);
assert_eq!(result, Some(20.0));
}
#[test]
fn cranelift_generic_div() {
let result = jit_run_numeric("f x:v y:v>n;/x y", "f", &[12.0, 4.0]);
assert_eq!(result, Some(3.0));
}
#[test]
fn cranelift_inline_callee_with_extra_reg() {
let result = jit_run_numeric("double x:n>n;d=*x 2;+d 1\nf x:n>n;double x", "f", &[4.0]);
assert_eq!(result, Some(9.0)); }
#[test]
fn cranelift_addk_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;+x 1",
"f",
&[Value::Number(5.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(6.0)));
}
#[test]
fn cranelift_subk_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;-x 1",
"f",
&[Value::Number(8.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(7.0)));
}
#[test]
fn cranelift_mulk_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;*x 3",
"f",
&[Value::Number(4.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(12.0)));
}
#[test]
fn cranelift_divk_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;/x 2",
"f",
&[Value::Number(10.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(5.0)));
}
#[test]
fn cranelift_jmpt_always_bool_via_or() {
let result = jit_run("f x:n>b;|>x 3 >x 5", "f", &[Value::Number(4.0)]);
assert_eq!(result, Some(Value::Bool(true)));
let result2 = jit_run("f x:n>b;|>x 3 >x 5", "f", &[Value::Number(2.0)]);
assert_eq!(result2, Some(Value::Bool(false)));
}
#[test]
fn cranelift_posth_compiles() {
let tokens: Vec<crate::lexer::Token> =
crate::lexer::lex("f url:t body:t hdrs:M t t>R t t;post url body hdrs")
.unwrap()
.into_iter()
.map(|(t, _)| t)
.collect();
let prog = crate::parser::parse_tokens(tokens).unwrap();
let compiled = crate::vm::compile(&prog).unwrap();
let idx = compiled.func_names.iter().position(|n| n == "f").unwrap();
let chunk = &compiled.chunks[idx];
let nan_consts = &compiled.nan_constants[idx];
assert!(
compile(chunk, nan_consts, &compiled).is_some(),
"OP_POSTH should compile"
);
}
#[test]
fn cranelift_cmpk_gt_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;>x 5 1;0",
"f",
&[Value::Number(10.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_cmpk_lt_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;<x 5 1;0",
"f",
&[Value::Number(3.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_cmpk_le_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;<=x 5 1;0",
"f",
&[Value::Number(5.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_cmpk_ge_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;>=x 5 1;0",
"f",
&[Value::Number(5.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_cmpk_eq_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;==x 5 1;0",
"f",
&[Value::Number(5.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_cmpk_ne_n_non_always_num() {
let result = jit_run(
"f x:n y:t>n;!=x 5 1;0",
"f",
&[Value::Number(3.0), Value::Text("dummy".into())],
);
assert_eq!(result, Some(Value::Number(1.0)));
}
#[test]
fn cranelift_generic_le() {
let result = jit_run_numeric("f x:v y:v>n;b=<=x y;b", "f", &[3.0, 5.0]);
let _ = result; let r2 = jit_run(
"f x:v y:v>b;<=x y",
"f",
&[Value::Number(3.0), Value::Number(5.0)],
);
assert_eq!(r2, Some(Value::Bool(true)));
}
#[test]
fn cranelift_generic_ge() {
let r = jit_run(
"f x:v y:v>b;>=x y",
"f",
&[Value::Number(5.0), Value::Number(3.0)],
);
assert_eq!(r, Some(Value::Bool(true)));
}
#[test]
fn cranelift_generic_eq() {
let r = jit_run(
"f x:v y:v>b;==x y",
"f",
&[Value::Number(5.0), Value::Number(5.0)],
);
assert_eq!(r, Some(Value::Bool(true)));
}
#[test]
fn cranelift_generic_ne() {
let r = jit_run(
"f x:v y:v>b;!=x y",
"f",
&[Value::Number(3.0), Value::Number(5.0)],
);
assert_eq!(r, Some(Value::Bool(true)));
}
#[test]
fn cranelift_inline_cmpk_lt_callee() {
let result = jit_run_numeric("negone x:n>n;<x 0 -1;0\nf x:n>n;negone x", "f", &[-5.0]);
assert_eq!(result, Some(-1.0));
let result2 = jit_run_numeric("negone x:n>n;<x 0 -1;0\nf x:n>n;negone x", "f", &[3.0]);
assert_eq!(result2, Some(0.0));
}
#[test]
fn cranelift_inline_cmpk_le_callee() {
let result = jit_run_numeric("atmost5 x:n>n;<=x 5 x;5\nf x:n>n;atmost5 x", "f", &[3.0]);
assert_eq!(result, Some(3.0));
let result2 = jit_run_numeric("atmost5 x:n>n;<=x 5 x;5\nf x:n>n;atmost5 x", "f", &[10.0]);
assert_eq!(result2, Some(5.0));
}
#[test]
fn cranelift_inline_cmpk_eq_callee() {
let result = jit_run_numeric("exact5 x:n>n;==x 5 1;0\nf x:n>n;exact5 x", "f", &[5.0]);
assert_eq!(result, Some(1.0));
let result2 = jit_run_numeric("exact5 x:n>n;==x 5 1;0\nf x:n>n;exact5 x", "f", &[3.0]);
assert_eq!(result2, Some(0.0));
}
#[test]
fn cranelift_inline_cmpk_ne_callee() {
let result = jit_run_numeric("noteq5 x:n>n;!=x 5 1;0\nf x:n>n;noteq5 x", "f", &[3.0]);
assert_eq!(result, Some(1.0));
let result2 = jit_run_numeric("noteq5 x:n>n;!=x 5 1;0\nf x:n>n;noteq5 x", "f", &[5.0]);
assert_eq!(result2, Some(0.0));
}
#[test]
fn cranelift_recwith_unresolved_field_names() {
let result = jit_run(
"type pt{x:n;y:n}\ntype qt{y:n;x:n}\ng p:v>v;p with x:99\nf>v;p=pt x:1 y:2;g p",
"f",
&[],
);
match result {
Some(Value::Record { fields, .. }) => {
let x_val = fields.get("x").expect("field x missing");
assert_eq!(*x_val, Value::Number(99.0), "x field should be 99");
}
other => panic!("expected a Record, got {:?}", other),
}
}
#[test]
fn cranelift_recwith_resolved_single_type() {
let result = jit_run(
"type pt{x:n;y:n}\nf>v;p=pt x:1 y:2;p with x:99 y:88",
"f",
&[],
);
match result {
Some(Value::Record { fields, .. }) => {
assert_eq!(fields.get("x"), Some(&Value::Number(99.0)));
assert_eq!(fields.get("y"), Some(&Value::Number(88.0)));
}
other => panic!("expected a Record, got {:?}", other),
}
}
#[test]
fn cranelift_recwith_three_fields() {
let result = jit_run(
"type tri{a:n;b:n;c:n}\nf>v;r=tri a:1 b:2 c:3;r with a:10 b:20",
"f",
&[],
);
match result {
Some(Value::Record { fields, .. }) => {
assert_eq!(fields.get("a"), Some(&Value::Number(10.0)));
assert_eq!(fields.get("b"), Some(&Value::Number(20.0)));
assert_eq!(fields.get("c"), Some(&Value::Number(3.0)));
}
other => panic!("expected a Record, got {:?}", other),
}
}
}