#![allow(rustdoc::private_intra_doc_links)]
use crate::ast::{BinOp, IncDecOp};
use crate::bytecode::{CompiledProgram, GetlineSource, Op, SubTarget};
use crate::vm::{JIT_FIELD_SENTINEL_FNR, JIT_FIELD_SENTINEL_NF, JIT_FIELD_SENTINEL_NR};
use cranelift_codegen::ir::FuncRef;
use cranelift_codegen::ir::{types, AbiParam, Block, InstBuilder, MemFlags, UserFuncName};
use cranelift_codegen::settings::{self, Configurable};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{default_libcall_names, Linkage, Module};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::ffi::c_void;
use std::mem;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Default)]
pub struct JitCompileOptions {
pub direct_field_calls: bool,
}
impl JitCompileOptions {
#[inline]
pub fn vm_default() -> Self {
Self {
direct_field_calls: true,
}
}
}
#[inline]
pub fn jit_min_invocations_before_compile() -> u32 {
std::env::var("AWKRS_JIT_MIN_INVOCATIONS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(3)
}
pub const JIT_VAR_OP_GET: u32 = 0;
pub const JIT_VAR_OP_SET: u32 = 1;
pub const JIT_VAR_OP_INCR: u32 = 2;
pub const JIT_VAR_OP_DECR: u32 = 3;
pub const JIT_VAR_OP_COMPOUND_ADD: u32 = 4;
pub const JIT_VAR_OP_COMPOUND_SUB: u32 = 5;
pub const JIT_VAR_OP_COMPOUND_MUL: u32 = 6;
pub const JIT_VAR_OP_COMPOUND_DIV: u32 = 7;
pub const JIT_VAR_OP_COMPOUND_MOD: u32 = 8;
pub const JIT_VAR_OP_INCDEC_PRE_INC: u32 = 9;
pub const JIT_VAR_OP_INCDEC_POST_INC: u32 = 10;
pub const JIT_VAR_OP_INCDEC_PRE_DEC: u32 = 11;
pub const JIT_VAR_OP_INCDEC_POST_DEC: u32 = 12;
pub const JIT_FIELD_OP_SET_NUM: u32 = 13;
pub const JIT_IO_PRINT_FIELD: u32 = 0;
pub const JIT_IO_PRINT_FIELD_SEP_FIELD: u32 = 1;
pub const JIT_IO_PRINT_THREE_FIELDS: u32 = 2;
pub const JIT_IO_PRINT_RECORD: u32 = 3;
pub const JIT_VAL_MATCH_REGEXP: u32 = 0;
pub const JIT_VAL_SIGNAL_NEXT: u32 = 1;
pub const JIT_VAL_SIGNAL_NEXT_FILE: u32 = 2;
pub const JIT_VAL_SIGNAL_EXIT_DEFAULT: u32 = 3;
pub const JIT_VAL_SIGNAL_EXIT_CODE: u32 = 4;
pub const JIT_VAL_ARRAY_GET: u32 = 5;
pub const JIT_VAL_ARRAY_SET: u32 = 6;
pub const JIT_VAL_ARRAY_IN: u32 = 7;
pub const JIT_VAL_ARRAY_DELETE_ELEM: u32 = 8;
pub const JIT_VAL_ARRAY_DELETE_ALL: u32 = 9;
pub const JIT_VAL_ARRAY_COMPOUND_ADD: u32 = 10;
pub const JIT_VAL_ARRAY_COMPOUND_SUB: u32 = 11;
pub const JIT_VAL_ARRAY_COMPOUND_MUL: u32 = 12;
pub const JIT_VAL_ARRAY_COMPOUND_DIV: u32 = 13;
pub const JIT_VAL_ARRAY_COMPOUND_MOD: u32 = 14;
pub const JIT_VAL_ARRAY_INCDEC_PRE_INC: u32 = 15;
pub const JIT_VAL_ARRAY_INCDEC_POST_INC: u32 = 16;
pub const JIT_VAL_ARRAY_INCDEC_PRE_DEC: u32 = 17;
pub const JIT_VAL_ARRAY_INCDEC_POST_DEC: u32 = 18;
pub const JIT_VAL_SIGNAL_RETURN_VAL: u32 = 19;
pub const JIT_VAL_SIGNAL_RETURN_EMPTY: u32 = 20;
pub const JIT_VAL_FORIN_START: u32 = 21;
pub const JIT_VAL_FORIN_NEXT: u32 = 22;
pub const JIT_VAL_FORIN_END: u32 = 23;
pub const JIT_VAL_ASORT: u32 = 24;
pub const JIT_VAL_ASORTI: u32 = 25;
pub const JIT_VAL_FDIV_CHECKED: u32 = 26;
pub const NAN_STR_TAG_HI32: u32 = 0x7FFC_0000;
pub const NAN_STR_TAG: u64 = (NAN_STR_TAG_HI32 as u64) << 32;
pub const NAN_STR_DYN_BIT: u64 = 1 << 47;
#[inline]
pub fn is_nan_str(bits: u64) -> bool {
let hi = (bits >> 32) as u32;
let dyn_in_hi = (NAN_STR_DYN_BIT >> 32) as u32;
(hi & !dyn_in_hi) == NAN_STR_TAG_HI32
}
#[inline]
pub fn nan_str_pool(pool_idx: u32) -> f64 {
f64::from_bits(NAN_STR_TAG | pool_idx as u64)
}
#[inline]
pub fn nan_str_dyn(dyn_idx: u32) -> f64 {
f64::from_bits(NAN_STR_TAG | NAN_STR_DYN_BIT | dyn_idx as u64)
}
#[inline]
pub fn decode_nan_str_bits(bits: u64) -> Option<(bool, u32)> {
if !is_nan_str(bits) {
return None;
}
let is_dyn = (bits & NAN_STR_DYN_BIT) != 0;
let idx = (bits & 0xffff_ffff) as u32;
Some((is_dyn, idx))
}
pub const NAN_UNINIT_HI32: u32 = 0x7FFD_0000;
pub const NAN_UNINIT_TAG: u64 = (NAN_UNINIT_HI32 as u64) << 32;
#[inline]
pub fn nan_uninit() -> f64 {
f64::from_bits(NAN_UNINIT_TAG)
}
#[inline]
pub fn is_nan_uninit(bits: u64) -> bool {
bits == NAN_UNINIT_TAG
}
#[inline]
pub fn mixed_encode_array_compound(arr: u32, bop: BinOp) -> u32 {
let b: u32 = match bop {
BinOp::Add => 0,
BinOp::Sub => 1,
BinOp::Mul => 2,
BinOp::Div => 3,
BinOp::Mod => 4,
_ => 0,
};
arr | (b << 16)
}
#[inline]
pub fn mixed_encode_array_incdec(arr: u32, kind: IncDecOp) -> u32 {
let k: u32 = match kind {
IncDecOp::PreInc => 0,
IncDecOp::PostInc => 1,
IncDecOp::PreDec => 2,
IncDecOp::PostDec => 3,
};
arr | (k << 16)
}
pub const MIXED_ADD: u32 = 100;
pub const MIXED_SUB: u32 = 101;
pub const MIXED_MUL: u32 = 102;
pub const MIXED_DIV: u32 = 103;
pub const MIXED_MOD: u32 = 104;
pub const MIXED_NEG: u32 = 105;
pub const MIXED_POS: u32 = 106;
pub const MIXED_NOT: u32 = 107;
pub const MIXED_TO_BOOL: u32 = 108;
pub const MIXED_POW: u32 = 109;
pub const MIXED_CMP_EQ: u32 = 110;
pub const MIXED_CMP_NE: u32 = 111;
pub const MIXED_CMP_LT: u32 = 112;
pub const MIXED_CMP_LE: u32 = 113;
pub const MIXED_CMP_GT: u32 = 114;
pub const MIXED_CMP_GE: u32 = 115;
pub const MIXED_TRUTHINESS: u32 = 116;
pub const MIXED_PUSH_STR: u32 = 120;
pub const MIXED_CONCAT: u32 = 121;
pub const MIXED_CONCAT_POOL: u32 = 122;
pub const MIXED_GET_FIELD: u32 = 123;
pub const MIXED_GET_VAR: u32 = 124;
pub const MIXED_SET_VAR: u32 = 125;
pub const MIXED_GET_SLOT: u32 = 126;
pub const MIXED_REGEX_MATCH: u32 = 130;
pub const MIXED_REGEX_NOT_MATCH: u32 = 131;
pub const MIXED_PRINT_ARG: u32 = 140;
pub const MIXED_PRINT_FLUSH: u32 = 141;
pub const MIXED_ARRAY_GET: u32 = 150;
pub const MIXED_ARRAY_SET: u32 = 151;
pub const MIXED_ARRAY_IN: u32 = 152;
pub const MIXED_ARRAY_DELETE_ELEM: u32 = 153;
pub const MIXED_ARRAY_DELETE_ALL: u32 = 154;
pub const MIXED_ARRAY_COMPOUND: u32 = 155;
pub const MIXED_ARRAY_INCDEC: u32 = 156;
pub const MIXED_INCDEC_SLOT: u32 = 157;
pub const MIXED_INCR_SLOT: u32 = 158;
pub const MIXED_DECR_SLOT: u32 = 159;
pub const MIXED_ADD_SLOT_TO_SLOT: u32 = 160;
pub const MIXED_ADD_FIELD_TO_SLOT: u32 = 161;
pub const MIXED_ADD_MUL_FIELDS_TO_SLOT: u32 = 162;
pub const MIXED_SLOT_AS_NUMBER: u32 = 163;
pub const MIXED_SET_FIELD: u32 = 164;
pub const MIXED_COMPOUND_ASSIGN_FIELD: u32 = 165;
pub const MIXED_JOIN_KEY_ARG: u32 = 167;
pub const MIXED_JOIN_ARRAY_KEY: u32 = 168;
pub const MIXED_TYPEOF_VAR: u32 = 169;
pub const MIXED_TYPEOF_SLOT: u32 = 170;
pub const MIXED_TYPEOF_ARRAY_ELEM: u32 = 171;
pub const MIXED_TYPEOF_FIELD: u32 = 172;
pub const MIXED_TYPEOF_VALUE: u32 = 173;
pub const MIXED_BUILTIN_ARG: u32 = 174;
pub const MIXED_BUILTIN_CALL: u32 = 175;
pub const MIXED_PRINTF_FLUSH: u32 = 176;
pub const MIXED_SPLIT: u32 = 177;
pub const MIXED_SPLIT_WITH_FS: u32 = 178;
pub const MIXED_PATSPLIT: u32 = 179;
pub const MIXED_PATSPLIT_SEP: u32 = 180;
pub const MIXED_PATSPLIT_FP: u32 = 181;
pub const MIXED_PATSPLIT_FP_SEP: u32 = 182;
pub const MIXED_PATSPLIT_STASH_SEPS: u32 = 204;
pub const MIXED_PATSPLIT_FP_SEP_WIDE: u32 = 205;
pub const MIXED_MATCH_BUILTIN: u32 = 183;
pub const MIXED_MATCH_BUILTIN_ARR: u32 = 184;
pub const MIXED_PRINT_FLUSH_REDIR: u32 = 185;
pub const MIXED_PRINTF_FLUSH_REDIR: u32 = 186;
pub const MIXED_GETLINE_PRIMARY: u32 = 187;
pub const MIXED_GETLINE_FILE: u32 = 188;
pub const MIXED_GETLINE_COPROC: u32 = 189;
pub const MIXED_GETLINE_INTO_RECORD: u32 = u32::MAX;
pub const MIXED_CALL_USER_ARG: u32 = 190;
pub const MIXED_CALL_USER_CALL: u32 = 191;
pub const MIXED_SUB_RECORD: u32 = 192;
pub const MIXED_GSUB_RECORD: u32 = 193;
pub const MIXED_SUB_VAR: u32 = 194;
pub const MIXED_GSUB_VAR: u32 = 195;
pub const MIXED_SUB_SLOT: u32 = 196;
pub const MIXED_GSUB_SLOT: u32 = 197;
pub const MIXED_SUB_FIELD: u32 = 198;
pub const MIXED_GSUB_FIELD: u32 = 199;
pub const MIXED_SUB_INDEX_STASH: u32 = 200;
pub const MIXED_SUB_INDEX: u32 = 201;
pub const MIXED_GSUB_INDEX_STASH: u32 = 202;
pub const MIXED_GSUB_INDEX: u32 = 203;
pub const MIXED_ADD_FIELDNUM_TO_SLOT: u32 = 206;
pub const MIXED_ADD_MUL_FIELDNUMS_TO_SLOT: u32 = 207;
#[inline]
pub fn pack_print_redir(argc: u16, redir: crate::bytecode::RedirKind) -> u32 {
let rk: u32 = match redir {
crate::bytecode::RedirKind::Stdout => 0,
crate::bytecode::RedirKind::Overwrite => 1,
crate::bytecode::RedirKind::Append => 2,
crate::bytecode::RedirKind::Pipe => 3,
crate::bytecode::RedirKind::Coproc => 4,
};
u32::from(argc) | (rk << 16)
}
#[inline]
fn mixed_encode_field_compound_binop(bop: BinOp) -> u32 {
match bop {
BinOp::Add => 0,
BinOp::Sub => 1,
BinOp::Mul => 2,
BinOp::Div => 3,
BinOp::Mod => 4,
_ => 0,
}
}
#[inline]
pub fn mixed_encode_slot_incdec(slot: u16, kind: IncDecOp) -> u32 {
let k: u32 = match kind {
IncDecOp::PreInc => 0,
IncDecOp::PostInc => 1,
IncDecOp::PreDec => 2,
IncDecOp::PostDec => 3,
};
u32::from(slot) | (k << 16)
}
#[inline]
pub fn mixed_encode_slot_pair(src: u16, dst: u16) -> u32 {
u32::from(src) | (u32::from(dst) << 16)
}
#[inline]
pub fn mixed_encode_field_slot(field: u16, slot: u16) -> u32 {
u32::from(field) | (u32::from(slot) << 16)
}
#[inline]
fn mixed_op_for_binop(bop: BinOp) -> u32 {
match bop {
BinOp::Add => MIXED_ADD,
BinOp::Sub => MIXED_SUB,
BinOp::Mul => MIXED_MUL,
BinOp::Div => MIXED_DIV,
BinOp::Mod => MIXED_MOD,
_ => unreachable!("filtered by is_jit_eligible"),
}
}
#[inline]
fn mixed_op_for_cmp(op: &Op) -> u32 {
match op {
Op::CmpEq => MIXED_CMP_EQ,
Op::CmpNe => MIXED_CMP_NE,
Op::CmpLt => MIXED_CMP_LT,
Op::CmpLe => MIXED_CMP_LE,
Op::CmpGt => MIXED_CMP_GT,
Op::CmpGe => MIXED_CMP_GE,
_ => unreachable!(),
}
}
#[inline]
fn jit_val_op_for_array_compound(bop: BinOp) -> u32 {
match bop {
BinOp::Add => JIT_VAL_ARRAY_COMPOUND_ADD,
BinOp::Sub => JIT_VAL_ARRAY_COMPOUND_SUB,
BinOp::Mul => JIT_VAL_ARRAY_COMPOUND_MUL,
BinOp::Div => JIT_VAL_ARRAY_COMPOUND_DIV,
BinOp::Mod => JIT_VAL_ARRAY_COMPOUND_MOD,
_ => unreachable!("filtered by is_jit_eligible"),
}
}
#[inline]
fn jit_val_op_for_array_incdec(kind: IncDecOp) -> u32 {
match kind {
IncDecOp::PreInc => JIT_VAL_ARRAY_INCDEC_PRE_INC,
IncDecOp::PostInc => JIT_VAL_ARRAY_INCDEC_POST_INC,
IncDecOp::PreDec => JIT_VAL_ARRAY_INCDEC_PRE_DEC,
IncDecOp::PostDec => JIT_VAL_ARRAY_INCDEC_POST_DEC,
}
}
#[inline]
fn jit_var_op_for_compound(bop: BinOp) -> u32 {
match bop {
BinOp::Add => JIT_VAR_OP_COMPOUND_ADD,
BinOp::Sub => JIT_VAR_OP_COMPOUND_SUB,
BinOp::Mul => JIT_VAR_OP_COMPOUND_MUL,
BinOp::Div => JIT_VAR_OP_COMPOUND_DIV,
BinOp::Mod => JIT_VAR_OP_COMPOUND_MOD,
_ => unreachable!("filtered by is_jit_eligible"),
}
}
#[inline]
fn jit_var_op_for_incdec(kind: IncDecOp) -> u32 {
match kind {
IncDecOp::PreInc => JIT_VAR_OP_INCDEC_PRE_INC,
IncDecOp::PostInc => JIT_VAR_OP_INCDEC_POST_INC,
IncDecOp::PreDec => JIT_VAR_OP_INCDEC_PRE_DEC,
IncDecOp::PostDec => JIT_VAR_OP_INCDEC_POST_DEC,
}
}
thread_local! {
static JIT_COMPILE_CACHE: RefCell<HashMap<u64, Option<Arc<JitChunk>>>> =
RefCell::new(HashMap::new());
}
fn ops_hash(ops: &[Op]) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
let bytes = unsafe {
std::slice::from_raw_parts(ops.as_ptr() as *const u8, std::mem::size_of_val(ops))
};
bytes.hash(&mut h);
h.finish()
}
pub struct JitRuntimeState<'a> {
pub vmctx: *mut c_void,
pub slots: &'a mut [f64],
pub field_fn: extern "C" fn(*mut c_void, i32) -> f64,
pub array_field_add: extern "C" fn(*mut c_void, u32, i32, f64),
pub var_dispatch: extern "C" fn(*mut c_void, u32, u32, f64) -> f64,
pub field_dispatch: extern "C" fn(*mut c_void, u32, i32, f64) -> f64,
pub io_dispatch: extern "C" fn(*mut c_void, u32, i32, i32, i32),
pub val_dispatch: extern "C" fn(*mut c_void, u32, u32, f64, f64) -> f64,
}
impl<'a> JitRuntimeState<'a> {
#[allow(clippy::too_many_arguments)]
#[inline]
pub fn new(
vmctx: *mut c_void,
slots: &'a mut [f64],
field_fn: extern "C" fn(*mut c_void, i32) -> f64,
array_field_add: extern "C" fn(*mut c_void, u32, i32, f64),
var_dispatch: extern "C" fn(*mut c_void, u32, u32, f64) -> f64,
field_dispatch: extern "C" fn(*mut c_void, u32, i32, f64) -> f64,
io_dispatch: extern "C" fn(*mut c_void, u32, i32, i32, i32),
val_dispatch: extern "C" fn(*mut c_void, u32, u32, f64, f64) -> f64,
) -> Self {
Self {
vmctx,
slots,
field_fn,
array_field_add,
var_dispatch,
field_dispatch,
io_dispatch,
val_dispatch,
}
}
}
pub struct JitChunk {
_module: JITModule,
fn_ptr: *const u8,
#[allow(dead_code)]
slot_count: u16,
#[allow(dead_code)]
needs_fields: bool,
}
unsafe impl Send for JitChunk {}
unsafe impl Sync for JitChunk {}
type JitFn = extern "C" fn(
*mut c_void,
*mut f64,
extern "C" fn(*mut c_void, i32) -> f64,
extern "C" fn(*mut c_void, u32, i32, f64),
extern "C" fn(*mut c_void, u32, u32, f64) -> f64,
extern "C" fn(*mut c_void, u32, i32, f64) -> f64,
extern "C" fn(*mut c_void, u32, i32, i32, i32),
extern "C" fn(*mut c_void, u32, u32, f64, f64) -> f64,
) -> f64;
impl JitChunk {
pub fn execute(&self, state: &mut JitRuntimeState<'_>) -> f64 {
let f: JitFn = unsafe { mem::transmute(self.fn_ptr) };
f(
state.vmctx,
state.slots.as_mut_ptr(),
state.field_fn,
state.array_field_add,
state.var_dispatch,
state.field_dispatch,
state.io_dispatch,
state.val_dispatch,
)
}
}
pub fn needs_mixed_mode(ops: &[Op]) -> bool {
ops.iter().any(|op| {
matches!(
op,
Op::PushStr(_)
| Op::PushRegexp(_)
| Op::Concat
| Op::ConcatPoolStr(_)
| Op::RegexMatch
| Op::RegexNotMatch
| Op::GetArrayElem(_)
| Op::SetArrayElem(_)
| Op::InArray(_)
| Op::DeleteElem(_)
| Op::DeleteArray(_)
| Op::CompoundAssignIndex(_, _)
| Op::IncDecIndex(_, _)
| Op::JoinArrayKey(_)
| Op::TypeofVar(_)
| Op::TypeofSlot(_)
| Op::TypeofArrayElem(_)
| Op::TypeofField
| Op::TypeofValue
| Op::CallBuiltin(_, _)
| Op::Split { .. }
| Op::Patsplit { .. }
| Op::MatchBuiltin { .. }
) || matches!(
op,
Op::Print {
argc,
redir: crate::bytecode::RedirKind::Stdout,
} if *argc > 0
) || matches!(
op,
Op::Printf {
argc,
redir: crate::bytecode::RedirKind::Stdout,
} if *argc > 0
) || matches!(
op,
Op::Print { redir, .. } if *redir != crate::bytecode::RedirKind::Stdout
) || matches!(
op,
Op::Printf { redir, .. } if *redir != crate::bytecode::RedirKind::Stdout
) || matches!(op, Op::GetLine { .. })
|| matches!(op, Op::CallIndirect(_))
|| matches!(op, Op::CallUser(_, _))
|| matches!(op, Op::SubFn(_) | Op::GsubFn(_))
|| matches!(op, Op::SetField)
|| matches!(op, Op::GetVar(_))
|| matches!(op, Op::Pow)
})
}
pub fn jit_call_builtins_ok(ops: &[Op], cp: &CompiledProgram) -> bool {
const MAX_CALL_ARGS: u16 = 64;
for op in ops {
match op {
Op::CallBuiltin(name_idx, argc) => {
let name = cp.strings.get(*name_idx);
if cp.functions.contains_key(name) {
return false;
}
if !builtin_supported_for_jit(name, *argc) {
return false;
}
}
Op::CallUser(name_idx, argc) => {
if *argc > MAX_CALL_ARGS {
return false;
}
let name = cp.strings.get(*name_idx);
if !cp.functions.contains_key(name) {
return false;
}
}
_ => {}
}
}
true
}
fn builtin_supported_for_jit(name: &str, argc: u16) -> bool {
const MAX_CALL_ARGS: u16 = 64;
match name {
"length" => argc == 0,
"index" => argc == 2,
"substr" => argc == 2 || argc == 3,
"tolower" | "toupper" => argc == 1,
"int" | "sqrt" | "strtonum" | "mkbool" => argc == 1,
"intdiv" => argc == 2,
"sin" | "cos" | "exp" | "log" | "compl" => argc == 1,
"atan2" => argc == 2,
"and" | "or" | "xor" | "lshift" | "rshift" => argc == 2,
"systime" => argc == 0,
"mktime" => argc == 1,
"rand" => argc == 0,
"srand" => argc <= 1,
"sprintf" => (1..=MAX_CALL_ARGS).contains(&argc),
"printf" => (1..=MAX_CALL_ARGS).contains(&argc),
"strftime" => argc <= 3,
"fflush" => argc <= 1,
"close" => argc == 1,
"system" => argc == 1,
"typeof" => argc == 1,
_ => false,
}
}
fn empty_compiled_program() -> CompiledProgram {
use crate::bytecode::StringPool;
CompiledProgram {
begin_chunks: vec![],
end_chunks: vec![],
beginfile_chunks: vec![],
endfile_chunks: vec![],
record_rules: vec![],
functions: HashMap::new(),
strings: StringPool::default(),
slot_count: 0,
slot_names: vec![],
slot_map: HashMap::new(),
array_var_names: vec![],
}
}
pub fn is_jit_eligible(ops: &[Op]) -> bool {
if ops.is_empty() {
return false;
}
let mut depth: i32 = 0;
for op in ops {
match op {
Op::PushNum(_) => depth += 1,
Op::GetSlot(_) => depth += 1,
Op::SetSlot(_) => { }
Op::Add | Op::Sub | Op::Mul | Op::Div | Op::Mod | Op::Pow => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::CmpEq | Op::CmpNe | Op::CmpLt | Op::CmpLe | Op::CmpGt | Op::CmpGe => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::Neg | Op::Pos | Op::Not | Op::ToBool => {
if depth < 1 {
return false;
}
}
Op::Jump(_) | Op::JumpIfFalsePop(_) | Op::JumpIfTruePop(_) => {
}
Op::Pop => {
if depth < 1 {
return false;
}
depth -= 1;
}
Op::Dup => {
if depth < 1 {
return false;
}
depth += 1;
}
Op::CompoundAssignSlot(_, bop) => {
if depth < 1 {
return false;
}
match bop {
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => {}
_ => return false,
}
}
Op::CompoundAssignVar(_, bop) => {
if depth < 1 {
return false;
}
match bop {
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => {}
_ => return false,
}
}
Op::GetVar(_) => depth += 1,
Op::SetVar(_) => {}
Op::IncrVar(_) | Op::DecrVar(_) => {}
Op::IncDecVar(_, _) => depth += 1,
Op::IncDecSlot(_, _) => depth += 1,
Op::IncrSlot(_) | Op::DecrSlot(_) => {}
Op::AddSlotToSlot { .. } => {}
Op::PushFieldNum(_) => depth += 1,
Op::GetField => {
if depth < 1 {
return false;
}
}
Op::GetNR | Op::GetFNR | Op::GetNF => depth += 1,
Op::AddFieldToSlot { .. } => {}
Op::AddMulFieldsToSlot { .. } => {}
Op::JumpIfSlotGeNum { .. } => {}
Op::ArrayFieldAddConst { .. } => {}
Op::CompoundAssignField(bop) => {
if depth < 2 {
return false;
}
match bop {
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => {}
_ => return false,
}
depth -= 1;
}
Op::IncDecField(_) => {
if depth < 1 {
return false;
}
}
Op::SetField => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::PrintFieldStdout(_) => {}
Op::PrintFieldSepField { .. } => {}
Op::PrintThreeFieldsStdout { .. } => {}
Op::Print {
argc: 0,
redir: crate::bytecode::RedirKind::Stdout,
} => {}
Op::MatchRegexp(_) => depth += 1,
Op::Next | Op::NextFile | Op::ExitDefault => {}
Op::ExitWithCode => {
if depth < 1 {
return false;
}
depth -= 1;
}
Op::PushStr(_) => depth += 1,
Op::Concat => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::ConcatPoolStr(_) => {
if depth < 1 {
return false;
}
}
Op::RegexMatch | Op::RegexNotMatch => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::GetArrayElem(_) => {
if depth < 1 {
return false;
}
}
Op::SetArrayElem(_) => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::InArray(_) => {
if depth < 1 {
return false;
}
}
Op::DeleteElem(_) => {
if depth < 1 {
return false;
}
depth -= 1;
}
Op::DeleteArray(_) => {}
Op::CompoundAssignIndex(_, bop) => {
if depth < 2 {
return false;
}
match bop {
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => {}
_ => return false,
}
depth -= 1;
}
Op::IncDecIndex(_, _) => {
if depth < 1 {
return false;
}
}
Op::JoinArrayKey(n) => {
let n = *n as i32;
if depth < n {
return false;
}
depth -= n - 1;
}
Op::TypeofVar(_) | Op::TypeofSlot(_) => {
depth += 1;
}
Op::TypeofArrayElem(_) => {
if depth < 1 {
return false;
}
}
Op::TypeofField | Op::TypeofValue => {
if depth < 1 {
return false;
}
}
Op::Print {
argc,
redir: crate::bytecode::RedirKind::Stdout,
} if *argc > 0 => {
let n = *argc as i32;
if depth < n {
return false;
}
depth -= n;
}
Op::Printf {
argc,
redir: crate::bytecode::RedirKind::Stdout,
} if *argc > 0 => {
let n = *argc as i32;
if depth < n {
return false;
}
depth -= n;
}
Op::Print { argc, redir } if *redir != crate::bytecode::RedirKind::Stdout => {
let n = *argc as i32;
if *argc == 0 {
if depth < 1 {
return false;
}
depth -= 1;
} else {
if depth < n + 1 {
return false;
}
depth -= n + 1;
}
}
Op::Printf { argc, redir } if *redir != crate::bytecode::RedirKind::Stdout => {
if *argc == 0 {
return false;
}
let n = *argc as i32;
if depth < n + 1 {
return false;
}
depth -= n + 1;
}
Op::ReturnVal => {
if depth < 1 {
return false;
}
depth -= 1;
}
Op::ReturnEmpty => {}
Op::ForInStart(_) => {}
Op::ForInNext { .. } => {}
Op::ForInEnd => {}
Op::Asort { .. } | Op::Asorti { .. } => depth += 1,
Op::CallBuiltin(_, argc) => {
let n = *argc as i32;
if depth < n {
return false;
}
depth -= n - 1;
}
Op::Split { has_fs, .. } => {
if *has_fs {
if depth < 2 {
return false;
}
depth -= 1;
} else if depth < 1 {
return false;
}
}
Op::Patsplit { has_fp, .. } => {
if *has_fp {
if depth < 2 {
return false;
}
depth -= 1;
} else if depth < 1 {
return false;
}
}
Op::MatchBuiltin { .. } => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::GetLine {
source,
push_result,
..
} => {
if *push_result {
return false;
}
match source {
GetlineSource::Primary => {}
GetlineSource::File | GetlineSource::Coproc => {
if depth < 1 {
return false;
}
depth -= 1;
}
GetlineSource::Pipe => return false,
}
}
Op::CallUser(_, argc) => {
let n = *argc as i32;
if depth < n {
return false;
}
depth -= n - 1;
}
Op::SubFn(t) | Op::GsubFn(t) => {
let need = match t {
SubTarget::Record | SubTarget::Var(_) | SubTarget::SlotVar(_) => 2,
SubTarget::Field | SubTarget::Index(_) => 3,
};
if depth < need {
return false;
}
depth -= need - 1;
}
_ => return false,
}
}
true
}
fn new_jit_module(opts: &JitCompileOptions) -> Option<JITModule> {
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").ok()?;
flag_builder.set("is_pic", "false").ok()?;
flag_builder.set("opt_level", "speed").ok()?;
let isa_builder = cranelift_native::builder().ok()?;
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.ok()?;
let mut builder = JITBuilder::with_isa(isa, default_libcall_names());
if opts.direct_field_calls {
builder.symbol(
"awkrs_jit_field_load",
crate::vm::jit_field_callback as *const u8,
);
}
Some(JITModule::new(builder))
}
#[inline]
fn emit_field_call(
builder: &mut FunctionBuilder,
field_sig_ir: cranelift_codegen::ir::SigRef,
field_fn_ptr: cranelift_codegen::ir::Value,
vmctx: cranelift_codegen::ir::Value,
arg: cranelift_codegen::ir::Value,
field_direct: Option<FuncRef>,
) -> cranelift_codegen::ir::Value {
let call = if let Some(fr) = field_direct {
builder.ins().call(fr, &[vmctx, arg])
} else {
builder
.ins()
.call_indirect(field_sig_ir, field_fn_ptr, &[vmctx, arg])
};
builder.inst_results(call)[0]
}
fn collect_jump_targets(ops: &[Op]) -> HashSet<usize> {
let mut targets = HashSet::new();
for op in ops {
match op {
Op::Jump(t) | Op::JumpIfFalsePop(t) | Op::JumpIfTruePop(t) => {
targets.insert(*t);
}
Op::JumpIfSlotGeNum { target, .. } => {
targets.insert(*target);
}
Op::ForInNext { end_jump, .. } => {
targets.insert(*end_jump);
}
_ => {}
}
}
targets
}
fn ops_have_early_return_or_signal(ops: &[Op]) -> bool {
ops.iter().any(|op| {
matches!(
op,
Op::ReturnVal
| Op::ReturnEmpty
| Op::ExitWithCode
| Op::ExitDefault
| Op::Next
| Op::NextFile
)
})
}
fn emit_slot_ssa_flush_to_mem(
builder: &mut FunctionBuilder,
use_slot_ssa: bool,
slot_vars: &[Variable],
slots_ptr: cranelift_codegen::ir::Value,
) {
if !use_slot_ssa {
return;
}
for (i, &slot_var) in slot_vars.iter().enumerate() {
let v = builder.use_var(slot_var);
builder
.ins()
.store(MemFlags::trusted(), v, slots_ptr, (i as i32) * 8);
}
}
fn needs_field_callback(ops: &[Op]) -> bool {
ops.iter().any(|op| {
matches!(
op,
Op::PushFieldNum(_)
| Op::GetField
| Op::AddFieldToSlot { .. }
| Op::AddMulFieldsToSlot { .. }
| Op::GetNR
| Op::GetFNR
| Op::GetNF
| Op::ArrayFieldAddConst { .. }
| Op::SetField
| Op::CompoundAssignField(_)
| Op::IncDecField(_)
)
})
}
fn max_slot(ops: &[Op]) -> u16 {
let mut m: u16 = 0;
for op in ops {
let s = match op {
Op::GetSlot(s) | Op::SetSlot(s) | Op::IncrSlot(s) | Op::DecrSlot(s) => *s,
Op::CompoundAssignSlot(s, _) | Op::IncDecSlot(s, _) => *s,
Op::AddSlotToSlot { src, dst } => (*src).max(*dst),
Op::AddFieldToSlot { slot, .. } => *slot,
Op::AddMulFieldsToSlot { slot, .. } => *slot,
Op::JumpIfSlotGeNum { slot, .. } => *slot,
_ => continue,
};
m = m.max(s);
}
m
}
pub fn try_compile(ops: &[Op], cp: &CompiledProgram) -> Option<JitChunk> {
try_compile_with_options(ops, cp, JitCompileOptions::default())
}
pub fn try_compile_with_options(
ops: &[Op],
cp: &CompiledProgram,
opts: JitCompileOptions,
) -> Option<JitChunk> {
if !is_jit_eligible(ops) || !jit_call_builtins_ok(ops, cp) {
return None;
}
let mixed = needs_mixed_mode(ops);
use crate::jit::{
mixed_encode_array_compound, mixed_encode_array_incdec, mixed_encode_slot_incdec,
mixed_encode_slot_pair, pack_print_redir, MIXED_ADD, MIXED_ADD_FIELDNUM_TO_SLOT,
MIXED_ADD_MUL_FIELDNUMS_TO_SLOT, MIXED_ADD_SLOT_TO_SLOT, MIXED_ARRAY_COMPOUND,
MIXED_ARRAY_DELETE_ALL, MIXED_ARRAY_DELETE_ELEM, MIXED_ARRAY_GET, MIXED_ARRAY_IN,
MIXED_ARRAY_INCDEC, MIXED_ARRAY_SET, MIXED_BUILTIN_ARG, MIXED_BUILTIN_CALL,
MIXED_CALL_USER_ARG, MIXED_CALL_USER_CALL, MIXED_COMPOUND_ASSIGN_FIELD, MIXED_CONCAT,
MIXED_CONCAT_POOL, MIXED_DECR_SLOT, MIXED_DIV, MIXED_GETLINE_COPROC, MIXED_GETLINE_FILE,
MIXED_GETLINE_INTO_RECORD, MIXED_GETLINE_PRIMARY, MIXED_GET_FIELD, MIXED_GET_SLOT,
MIXED_GET_VAR, MIXED_GSUB_FIELD, MIXED_GSUB_INDEX, MIXED_GSUB_INDEX_STASH,
MIXED_GSUB_RECORD, MIXED_GSUB_SLOT, MIXED_GSUB_VAR, MIXED_INCDEC_SLOT, MIXED_INCR_SLOT,
MIXED_JOIN_ARRAY_KEY, MIXED_JOIN_KEY_ARG, MIXED_MATCH_BUILTIN, MIXED_MATCH_BUILTIN_ARR,
MIXED_MOD, MIXED_MUL, MIXED_NEG, MIXED_NOT, MIXED_PATSPLIT, MIXED_PATSPLIT_FP,
MIXED_PATSPLIT_FP_SEP, MIXED_PATSPLIT_FP_SEP_WIDE, MIXED_PATSPLIT_SEP,
MIXED_PATSPLIT_STASH_SEPS, MIXED_POS, MIXED_POW, MIXED_PRINTF_FLUSH,
MIXED_PRINTF_FLUSH_REDIR, MIXED_PRINT_ARG, MIXED_PRINT_FLUSH, MIXED_PRINT_FLUSH_REDIR,
MIXED_PUSH_STR, MIXED_REGEX_MATCH, MIXED_REGEX_NOT_MATCH, MIXED_SET_FIELD, MIXED_SET_VAR,
MIXED_SLOT_AS_NUMBER, MIXED_SPLIT, MIXED_SPLIT_WITH_FS, MIXED_SUB, MIXED_SUB_FIELD,
MIXED_SUB_INDEX, MIXED_SUB_INDEX_STASH, MIXED_SUB_RECORD, MIXED_SUB_SLOT, MIXED_SUB_VAR,
MIXED_TO_BOOL, MIXED_TRUTHINESS, MIXED_TYPEOF_ARRAY_ELEM, MIXED_TYPEOF_FIELD,
MIXED_TYPEOF_SLOT, MIXED_TYPEOF_VALUE, MIXED_TYPEOF_VAR,
};
let mut module = new_jit_module(&opts)?;
let mut ctx = module.make_context();
let mut func_ctx = FunctionBuilderContext::new();
let ptr_type = module.target_config().pointer_type();
let mut sig = module.make_signature();
sig.params.push(AbiParam::new(ptr_type)); sig.params.push(AbiParam::new(ptr_type)); sig.params.push(AbiParam::new(ptr_type)); sig.params.push(AbiParam::new(ptr_type)); sig.params.push(AbiParam::new(ptr_type)); sig.params.push(AbiParam::new(ptr_type)); sig.params.push(AbiParam::new(ptr_type)); sig.params.push(AbiParam::new(ptr_type)); sig.returns.push(AbiParam::new(types::F64));
let mut field_sig = module.make_signature();
field_sig.params.push(AbiParam::new(ptr_type)); field_sig.params.push(AbiParam::new(types::I32));
field_sig.returns.push(AbiParam::new(types::F64));
let _field_sig_ref = module.declare_anonymous_function(&field_sig).ok()?;
let field_import_id = if opts.direct_field_calls {
Some(
module
.declare_function("awkrs_jit_field_load", Linkage::Import, &field_sig)
.ok()?,
)
} else {
None
};
let mut array_sig = module.make_signature();
array_sig.params.push(AbiParam::new(ptr_type)); array_sig.params.push(AbiParam::new(types::I32));
array_sig.params.push(AbiParam::new(types::I32));
array_sig.params.push(AbiParam::new(types::F64));
let _array_sig_ref = module.declare_anonymous_function(&array_sig).ok()?;
let mut var_sig = module.make_signature();
var_sig.params.push(AbiParam::new(ptr_type)); var_sig.params.push(AbiParam::new(types::I32));
var_sig.params.push(AbiParam::new(types::I32));
var_sig.params.push(AbiParam::new(types::F64));
var_sig.returns.push(AbiParam::new(types::F64));
let _var_sig_ref = module.declare_anonymous_function(&var_sig).ok()?;
let mut field_mut_sig = module.make_signature();
field_mut_sig.params.push(AbiParam::new(ptr_type)); field_mut_sig.params.push(AbiParam::new(types::I32));
field_mut_sig.params.push(AbiParam::new(types::I32));
field_mut_sig.params.push(AbiParam::new(types::F64));
field_mut_sig.returns.push(AbiParam::new(types::F64));
let _field_mut_sig_ref = module.declare_anonymous_function(&field_mut_sig).ok()?;
let mut io_sig = module.make_signature();
io_sig.params.push(AbiParam::new(ptr_type)); io_sig.params.push(AbiParam::new(types::I32));
io_sig.params.push(AbiParam::new(types::I32));
io_sig.params.push(AbiParam::new(types::I32));
io_sig.params.push(AbiParam::new(types::I32));
let _io_sig_ref = module.declare_anonymous_function(&io_sig).ok()?;
let mut val_sig = module.make_signature();
val_sig.params.push(AbiParam::new(ptr_type)); val_sig.params.push(AbiParam::new(types::I32));
val_sig.params.push(AbiParam::new(types::I32));
val_sig.params.push(AbiParam::new(types::F64));
val_sig.params.push(AbiParam::new(types::F64));
val_sig.returns.push(AbiParam::new(types::F64));
let _val_sig_ref = module.declare_anonymous_function(&val_sig).ok()?;
let func_id = module
.declare_function("awkrs_jit_chunk", Linkage::Export, &sig)
.ok()?;
ctx.func.signature = sig;
ctx.func.name = UserFuncName::user(0, func_id.as_u32());
let field_direct_ref = if let Some(fid) = field_import_id {
Some(module.declare_func_in_func(fid, &mut ctx.func))
} else {
None
};
let slot_count: usize = if ops.iter().any(|op| {
matches!(
op,
Op::GetSlot(_)
| Op::SetSlot(_)
| Op::IncrSlot(_)
| Op::DecrSlot(_)
| Op::CompoundAssignSlot(_, _)
| Op::IncDecSlot(_, _)
| Op::AddSlotToSlot { .. }
| Op::AddFieldToSlot { .. }
| Op::AddMulFieldsToSlot { .. }
| Op::JumpIfSlotGeNum { .. }
)
}) {
(max_slot(ops) + 1) as usize
} else {
0
};
let has_fields = needs_field_callback(ops);
let jump_targets = collect_jump_targets(ops);
let use_slot_ssa = !mixed && slot_count > 0 && !ops_have_early_return_or_signal(ops);
{
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx);
let field_sig_ir = builder.import_signature(field_sig);
let field_direct = field_direct_ref;
let array_sig_ir = builder.import_signature(array_sig);
let var_sig_ir = builder.import_signature(var_sig);
let field_mut_sig_ir = builder.import_signature(field_mut_sig);
let io_sig_ir = builder.import_signature(io_sig);
let val_sig_ir = builder.import_signature(val_sig);
let var_vmctx = builder.declare_var(ptr_type);
let var_slots_ptr = builder.declare_var(ptr_type);
let var_field_fn = builder.declare_var(ptr_type);
let var_array_fn = builder.declare_var(ptr_type);
let var_var_fn = builder.declare_var(ptr_type);
let var_field_mut_fn = builder.declare_var(ptr_type);
let var_io_fn = builder.declare_var(ptr_type);
let var_val_fn = builder.declare_var(ptr_type);
let entry_block = builder.create_block();
builder.append_block_params_for_function_params(entry_block);
let mut block_map: HashMap<usize, Block> = HashMap::new();
for &target in &jump_targets {
block_map.insert(target, builder.create_block());
}
for (i, op) in ops.iter().enumerate() {
if matches!(op, Op::Jump(_)) && i + 1 < ops.len() && !block_map.contains_key(&(i + 1)) {
block_map.insert(i + 1, builder.create_block());
}
}
builder.switch_to_block(entry_block);
let vmctx_val = builder.block_params(entry_block)[0];
let slots_ptr_val = builder.block_params(entry_block)[1];
let field_fn_val = builder.block_params(entry_block)[2];
let array_fn_val = builder.block_params(entry_block)[3];
let var_dispatch_val = builder.block_params(entry_block)[4];
let field_mut_dispatch_val = builder.block_params(entry_block)[5];
let io_dispatch_val = builder.block_params(entry_block)[6];
let val_dispatch_val = builder.block_params(entry_block)[7];
builder.def_var(var_vmctx, vmctx_val);
builder.def_var(var_slots_ptr, slots_ptr_val);
builder.def_var(var_field_fn, field_fn_val);
builder.def_var(var_array_fn, array_fn_val);
builder.def_var(var_var_fn, var_dispatch_val);
builder.def_var(var_field_mut_fn, field_mut_dispatch_val);
builder.def_var(var_io_fn, io_dispatch_val);
builder.def_var(var_val_fn, val_dispatch_val);
builder.seal_block(entry_block);
let slot_vars: Vec<Variable> = if use_slot_ssa {
(0..slot_count)
.map(|_| builder.declare_var(types::F64))
.collect()
} else {
Vec::new()
};
if use_slot_ssa {
let slots_ptr_init = builder.use_var(var_slots_ptr);
for (i, &slot_var) in slot_vars.iter().enumerate() {
let v = builder.ins().load(
types::F64,
MemFlags::trusted(),
slots_ptr_init,
(i as i32) * 8,
);
builder.def_var(slot_var, v);
}
}
let mut stack: Vec<cranelift_codegen::ir::Value> = Vec::new();
let mut block_terminated = false;
let mut pc: usize = 0;
while pc < ops.len() {
if let Some(&target_block) = block_map.get(&pc) {
if !block_terminated {
builder.ins().jump(target_block, &[]);
}
builder.switch_to_block(target_block);
block_terminated = false;
stack.clear();
}
if block_terminated {
pc += 1;
continue;
}
let vmctx = builder.use_var(var_vmctx);
let slots_ptr = builder.use_var(var_slots_ptr);
let field_fn_ptr = builder.use_var(var_field_fn);
let array_fn_ptr = builder.use_var(var_array_fn);
let var_fn_ptr = builder.use_var(var_var_fn);
let field_mut_fn_ptr = builder.use_var(var_field_mut_fn);
let io_fn_ptr = builder.use_var(var_io_fn);
let val_fn_ptr = builder.use_var(var_val_fn);
match ops[pc] {
Op::PushNum(n) => {
stack.push(builder.ins().f64const(n));
}
Op::PushStr(idx) => {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_PUSH_STR));
let a1 = builder.ins().iconst(types::I32, i64::from(idx));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::Concat => {
let b = stack.pop().expect("Concat");
let a = stack.pop().expect("Concat");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_CONCAT));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
}
Op::ConcatPoolStr(idx) => {
let a = stack.pop().expect("ConcatPoolStr");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_CONCAT_POOL));
let a1 = builder.ins().iconst(types::I32, i64::from(idx));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, a, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::RegexMatch => {
let pat = stack.pop().expect("RegexMatch pat");
let s = stack.pop().expect("RegexMatch s");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_REGEX_MATCH));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, s, pat],
);
stack.push(builder.inst_results(call)[0]);
}
Op::RegexNotMatch => {
let pat = stack.pop().expect("RegexNotMatch pat");
let s = stack.pop().expect("RegexNotMatch s");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_REGEX_NOT_MATCH));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, s, pat],
);
stack.push(builder.inst_results(call)[0]);
}
Op::TypeofVar(idx) => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_TYPEOF_VAR));
let a1 = builder.ins().iconst(types::I32, i64::from(idx));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::TypeofSlot(slot) => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_TYPEOF_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::TypeofArrayElem(arr) => {
let key = stack.pop().expect("TypeofArrayElem");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_TYPEOF_ARRAY_ELEM));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::TypeofField => {
let idx = stack.pop().expect("TypeofField");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_TYPEOF_FIELD));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z32, idx, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::TypeofValue => {
let v = stack.pop().expect("TypeofValue");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_TYPEOF_VALUE));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z32, v, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::Split { arr, has_fs } => {
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let zf = builder.ins().f64const(0.0);
if has_fs {
let fs = stack.pop().expect("Split fs");
let s = stack.pop().expect("Split s");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_SPLIT_WITH_FS));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, s, fs],
);
stack.push(builder.inst_results(call)[0]);
} else {
let s = stack.pop().expect("Split s");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SPLIT));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, s, zf],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::GetLine {
var,
source,
push_result,
} => {
debug_assert!(!push_result, "expression getline is not JIT-compiled");
let a1_enc = if let Some(v) = var {
builder.ins().iconst(types::I32, i64::from(v))
} else {
builder
.ins()
.iconst(types::I32, i64::from(MIXED_GETLINE_INTO_RECORD))
};
let zf = builder.ins().f64const(0.0);
match source {
GetlineSource::Primary => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_GETLINE_PRIMARY));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1_enc, zf, zf],
);
}
GetlineSource::File => {
let path = stack.pop().expect("GetLine file path");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_GETLINE_FILE));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1_enc, path, zf],
);
}
GetlineSource::Coproc => {
let path = stack.pop().expect("GetLine coproc");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_GETLINE_COPROC));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1_enc, path, zf],
);
}
GetlineSource::Pipe => return None,
}
}
Op::Patsplit { arr, has_fp, seps } => {
let zf = builder.ins().f64const(0.0);
match (has_fp, seps) {
(false, None) => {
let s = stack.pop().expect("Patsplit s");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_PATSPLIT));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, s, zf],
);
stack.push(builder.inst_results(call)[0]);
}
(false, Some(sepi)) => {
let s = stack.pop().expect("Patsplit s");
let op_ps = builder.ins().iconst(types::I32, i64::from(MIXED_PUSH_STR));
let a1s = builder.ins().iconst(types::I32, i64::from(sepi));
let seps_box = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_ps, a1s, zf, zf],
);
let seps_val = builder.inst_results(seps_box)[0];
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PATSPLIT_SEP));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, s, seps_val],
);
stack.push(builder.inst_results(call)[0]);
}
(true, None) => {
let fp = stack.pop().expect("Patsplit fp");
let s = stack.pop().expect("Patsplit s");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PATSPLIT_FP));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, s, fp],
);
stack.push(builder.inst_results(call)[0]);
}
(true, Some(sepi)) => {
let fp = stack.pop().expect("Patsplit fp");
let s = stack.pop().expect("Patsplit s");
if arr < 65536 && sepi < 65536 {
let packed = arr | (sepi << 16);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PATSPLIT_FP_SEP));
let a1 = builder.ins().iconst(types::I32, i64::from(packed));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, s, fp],
);
stack.push(builder.inst_results(call)[0]);
} else {
let op_stash = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PATSPLIT_STASH_SEPS));
let a1_seps = builder.ins().iconst(types::I32, i64::from(sepi));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_stash, a1_seps, zf, zf],
);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PATSPLIT_FP_SEP_WIDE));
let a1_arr = builder.ins().iconst(types::I32, i64::from(arr));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1_arr, s, fp],
);
stack.push(builder.inst_results(call)[0]);
}
}
}
}
Op::MatchBuiltin { arr } => {
let re = stack.pop().expect("MatchBuiltin re");
let s = stack.pop().expect("MatchBuiltin s");
let z = builder.ins().iconst(types::I32, 0);
let (op_c, a1) = if let Some(ai) = arr {
(
builder
.ins()
.iconst(types::I32, i64::from(MIXED_MATCH_BUILTIN_ARR)),
builder.ins().iconst(types::I32, i64::from(ai)),
)
} else {
(
builder
.ins()
.iconst(types::I32, i64::from(MIXED_MATCH_BUILTIN)),
z,
)
};
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, s, re],
);
stack.push(builder.inst_results(call)[0]);
}
Op::CallBuiltin(name_idx, argc_u) => {
let argc = argc_u as usize;
let mut arg_vals: Vec<_> = (0..argc)
.map(|_| stack.pop().expect("CallBuiltin"))
.collect();
arg_vals.reverse();
let zf = builder.ins().f64const(0.0);
for (i, v) in arg_vals.iter().enumerate() {
let op_arg = builder
.ins()
.iconst(types::I32, i64::from(MIXED_BUILTIN_ARG));
let a1 = builder.ins().iconst(types::I32, i64::from(i as u32));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_arg, a1, *v, zf],
);
}
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_BUILTIN_CALL));
let a1 = builder.ins().iconst(types::I32, i64::from(name_idx));
let a2 = builder.ins().f64const(argc as f64);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, a2, zf],
);
stack.push(builder.inst_results(call)[0]);
}
Op::CallUser(name_idx, argc_u) => {
let argc = argc_u as usize;
let mut arg_vals: Vec<_> =
(0..argc).map(|_| stack.pop().expect("CallUser")).collect();
arg_vals.reverse();
let zf = builder.ins().f64const(0.0);
for (i, v) in arg_vals.iter().enumerate() {
let op_arg = builder
.ins()
.iconst(types::I32, i64::from(MIXED_CALL_USER_ARG));
let a1 = builder.ins().iconst(types::I32, i64::from(i as u32));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_arg, a1, *v, zf],
);
}
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_CALL_USER_CALL));
let a1 = builder.ins().iconst(types::I32, i64::from(name_idx));
let a2 = builder.ins().f64const(argc as f64);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, a2, zf],
);
stack.push(builder.inst_results(call)[0]);
}
Op::SubFn(target) => {
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
match target {
SubTarget::Record => {
let repl = stack.pop().expect("SubFn repl");
let re = stack.pop().expect("SubFn re");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_SUB_RECORD));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::Var(name_idx) => {
let repl = stack.pop().expect("SubFn repl");
let re = stack.pop().expect("SubFn re");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SUB_VAR));
let a1 = builder.ins().iconst(types::I32, i64::from(name_idx));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::SlotVar(slot) => {
let repl = stack.pop().expect("SubFn repl");
let re = stack.pop().expect("SubFn re");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SUB_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::Field => {
let fi = stack.pop().expect("SubFn field");
let repl = stack.pop().expect("SubFn repl");
let re = stack.pop().expect("SubFn re");
let fi_i = builder.ins().fcvt_to_sint(types::I32, fi);
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SUB_FIELD));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, fi_i, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::Index(arr_idx) => {
let key = stack.pop().expect("SubFn key");
let repl = stack.pop().expect("SubFn repl");
let re = stack.pop().expect("SubFn re");
let st_op = builder
.ins()
.iconst(types::I32, i64::from(MIXED_SUB_INDEX_STASH));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, st_op, z, key, zf],
);
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SUB_INDEX));
let a1 = builder.ins().iconst(types::I32, i64::from(arr_idx));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
}
}
Op::GsubFn(target) => {
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
match target {
SubTarget::Record => {
let repl = stack.pop().expect("GsubFn repl");
let re = stack.pop().expect("GsubFn re");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_GSUB_RECORD));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::Var(name_idx) => {
let repl = stack.pop().expect("GsubFn repl");
let re = stack.pop().expect("GsubFn re");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_GSUB_VAR));
let a1 = builder.ins().iconst(types::I32, i64::from(name_idx));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::SlotVar(slot) => {
let repl = stack.pop().expect("GsubFn repl");
let re = stack.pop().expect("GsubFn re");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_GSUB_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::Field => {
let fi = stack.pop().expect("GsubFn field");
let repl = stack.pop().expect("GsubFn repl");
let re = stack.pop().expect("GsubFn re");
let fi_i = builder.ins().fcvt_to_sint(types::I32, fi);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_GSUB_FIELD));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, fi_i, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
SubTarget::Index(arr_idx) => {
let key = stack.pop().expect("GsubFn key");
let repl = stack.pop().expect("GsubFn repl");
let re = stack.pop().expect("GsubFn re");
let st_op = builder
.ins()
.iconst(types::I32, i64::from(MIXED_GSUB_INDEX_STASH));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, st_op, z, key, zf],
);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_GSUB_INDEX));
let a1 = builder.ins().iconst(types::I32, i64::from(arr_idx));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, re, repl],
);
stack.push(builder.inst_results(call)[0]);
}
}
}
Op::GetSlot(slot) => {
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_GET_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
stack.push(builder.inst_results(call)[0]);
} else if use_slot_ssa {
stack.push(builder.use_var(slot_vars[slot as usize]));
} else {
let offset = (slot as i32) * 8;
let v =
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset);
stack.push(v);
}
}
Op::SetSlot(slot) => {
let v = *stack.last().expect("SetSlot: empty stack");
if use_slot_ssa {
builder.def_var(slot_vars[slot as usize], v);
} else {
let offset = (slot as i32) * 8;
builder
.ins()
.store(MemFlags::trusted(), v, slots_ptr, offset);
}
}
Op::GetVar(idx) => {
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_GET_VAR));
let a1 = builder.ins().iconst(types::I32, i64::from(idx));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
stack.push(builder.inst_results(call)[0]);
} else {
let opv = builder.ins().iconst(types::I32, i64::from(JIT_VAR_OP_GET));
let ni = builder.ins().iconst(types::I32, idx as i64);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
var_sig_ir,
var_fn_ptr,
&[vmctx, opv, ni, z],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::SetVar(idx) => {
let v = *stack.last().expect("SetVar: empty stack");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SET_VAR));
let a1 = builder.ins().iconst(types::I32, i64::from(idx));
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, v, z],
);
} else {
let opv = builder.ins().iconst(types::I32, i64::from(JIT_VAR_OP_SET));
let ni = builder.ins().iconst(types::I32, idx as i64);
builder
.ins()
.call_indirect(var_sig_ir, var_fn_ptr, &[vmctx, opv, ni, v]);
}
}
Op::IncrVar(idx) => {
let opv = builder.ins().iconst(types::I32, i64::from(JIT_VAR_OP_INCR));
let ni = builder.ins().iconst(types::I32, idx as i64);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(var_sig_ir, var_fn_ptr, &[vmctx, opv, ni, z]);
}
Op::DecrVar(idx) => {
let opv = builder.ins().iconst(types::I32, i64::from(JIT_VAR_OP_DECR));
let ni = builder.ins().iconst(types::I32, idx as i64);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(var_sig_ir, var_fn_ptr, &[vmctx, opv, ni, z]);
}
Op::CompoundAssignVar(idx, bop) => {
let rhs = stack.pop().expect("CompoundAssignVar");
let ni = builder.ins().iconst(types::I32, idx as i64);
let z = builder.ins().f64const(0.0);
let new_val = if mixed {
let op_get = builder.ins().iconst(types::I32, i64::from(MIXED_GET_VAR));
let call_old = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_get, ni, z, z],
);
let old = builder.inst_results(call_old)[0];
let mop = mixed_op_for_binop(bop);
let op_b = builder.ins().iconst(types::I32, i64::from(mop));
let z32 = builder.ins().iconst(types::I32, 0);
let call_op = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_b, z32, old, rhs],
);
let computed = builder.inst_results(call_op)[0];
let op_set = builder.ins().iconst(types::I32, i64::from(MIXED_SET_VAR));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_set, ni, computed, z],
);
computed
} else {
let cop = jit_var_op_for_compound(bop);
let opv = builder.ins().iconst(types::I32, i64::from(cop));
let call = builder.ins().call_indirect(
var_sig_ir,
var_fn_ptr,
&[vmctx, opv, ni, rhs],
);
builder.inst_results(call)[0]
};
stack.push(new_val);
}
Op::IncDecVar(idx, kind) => {
let cop = jit_var_op_for_incdec(kind);
let opv = builder.ins().iconst(types::I32, i64::from(cop));
let ni = builder.ins().iconst(types::I32, idx as i64);
let z = builder.ins().f64const(0.0);
let call =
builder
.ins()
.call_indirect(var_sig_ir, var_fn_ptr, &[vmctx, opv, ni, z]);
stack.push(builder.inst_results(call)[0]);
}
Op::CompoundAssignField(bop) => {
let rhs = stack.pop().expect("CompoundAssignField");
let idx_f = stack.pop().expect("CompoundAssignField idx");
if mixed {
let bc = mixed_encode_field_compound_binop(bop);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_COMPOUND_ASSIGN_FIELD));
let a1 = builder.ins().iconst(types::I32, i64::from(bc));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, idx_f, rhs],
);
stack.push(builder.inst_results(call)[0]);
} else {
let idx_i32 = builder.ins().fcvt_to_sint_sat(types::I32, idx_f);
let cop = jit_var_op_for_compound(bop);
let opv = builder.ins().iconst(types::I32, i64::from(cop));
let call = builder.ins().call_indirect(
field_mut_sig_ir,
field_mut_fn_ptr,
&[vmctx, opv, idx_i32, rhs],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::IncDecField(kind) => {
let idx_f = stack.pop().expect("IncDecField");
let idx_i32 = builder.ins().fcvt_to_sint_sat(types::I32, idx_f);
let cop = jit_var_op_for_incdec(kind);
let opv = builder.ins().iconst(types::I32, i64::from(cop));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
field_mut_sig_ir,
field_mut_fn_ptr,
&[vmctx, opv, idx_i32, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::SetField => {
let val = stack.pop().expect("SetField val");
let idx_f = stack.pop().expect("SetField idx");
if mixed {
let idx_i32 = builder.ins().fcvt_to_sint_sat(types::I32, idx_f);
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SET_FIELD));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, idx_i32, val, z],
);
stack.push(builder.inst_results(call)[0]);
} else {
let idx_i32 = builder.ins().fcvt_to_sint_sat(types::I32, idx_f);
let opv = builder
.ins()
.iconst(types::I32, i64::from(JIT_FIELD_OP_SET_NUM));
let call = builder.ins().call_indirect(
field_mut_sig_ir,
field_mut_fn_ptr,
&[vmctx, opv, idx_i32, val],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::Add => {
let b = stack.pop().expect("Add");
let a = stack.pop().expect("Add");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_ADD));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
} else {
stack.push(builder.ins().fadd(a, b));
}
}
Op::Sub => {
let b = stack.pop().expect("Sub");
let a = stack.pop().expect("Sub");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_SUB));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
} else {
stack.push(builder.ins().fsub(a, b));
}
}
Op::Mul => {
let b = stack.pop().expect("Mul");
let a = stack.pop().expect("Mul");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_MUL));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
} else {
stack.push(builder.ins().fmul(a, b));
}
}
Op::Div => {
let b = stack.pop().expect("Div");
let a = stack.pop().expect("Div");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_DIV));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
} else {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_FDIV_CHECKED));
let z32 = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z32, a, b],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::Mod => {
let b = stack.pop().expect("Mod");
let a = stack.pop().expect("Mod");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_MOD));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
} else {
let div = builder.ins().fdiv(a, b);
let trunc = builder.ins().trunc(div);
let prod = builder.ins().fmul(trunc, b);
stack.push(builder.ins().fsub(a, prod));
}
}
Op::Pow => {
let b = stack.pop().expect("Pow");
let a = stack.pop().expect("Pow");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_POW));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
} else {
return None;
}
}
Op::CmpEq | Op::CmpNe | Op::CmpLt | Op::CmpLe | Op::CmpGt | Op::CmpGe => {
let b = stack.pop().expect("cmp");
let a = stack.pop().expect("cmp");
if mixed {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(mixed_op_for_cmp(&ops[pc])));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, b],
);
stack.push(builder.inst_results(call)[0]);
} else {
use cranelift_codegen::ir::condcodes::FloatCC;
let cc = match ops[pc] {
Op::CmpEq => FloatCC::Equal,
Op::CmpNe => FloatCC::NotEqual,
Op::CmpLt => FloatCC::LessThan,
Op::CmpLe => FloatCC::LessThanOrEqual,
Op::CmpGt => FloatCC::GreaterThan,
Op::CmpGe => FloatCC::GreaterThanOrEqual,
_ => unreachable!(),
};
let cmp = builder.ins().fcmp(cc, a, b);
let i = builder.ins().uextend(types::I32, cmp);
stack.push(builder.ins().fcvt_from_uint(types::F64, i));
}
}
Op::Neg => {
let a = stack.pop().expect("Neg");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_NEG));
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, zf],
);
stack.push(builder.inst_results(call)[0]);
} else {
stack.push(builder.ins().fneg(a));
}
}
Op::Pos => {
if mixed {
let a = stack.pop().expect("Pos");
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_POS));
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, zf],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::Not => {
let a = stack.pop().expect("Not");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_NOT));
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, zf],
);
stack.push(builder.inst_results(call)[0]);
} else {
let zero = builder.ins().f64const(0.0);
let is_zero = builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::Equal,
a,
zero,
);
let i = builder.ins().uextend(types::I32, is_zero);
stack.push(builder.ins().fcvt_from_uint(types::F64, i));
}
}
Op::ToBool => {
let a = stack.pop().expect("ToBool");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_TO_BOOL));
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, a, zf],
);
stack.push(builder.inst_results(call)[0]);
} else {
let zero = builder.ins().f64const(0.0);
let ne = builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::NotEqual,
a,
zero,
);
let i = builder.ins().uextend(types::I32, ne);
stack.push(builder.ins().fcvt_from_uint(types::F64, i));
}
}
Op::Jump(target) => {
let target_block = block_map[&target];
builder.ins().jump(target_block, &[]);
block_terminated = true;
}
Op::JumpIfFalsePop(target) => {
let v = stack.pop().expect("JumpIfFalsePop");
let cond = if mixed {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_TRUTHINESS));
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, v, zf],
);
let truth = builder.inst_results(call)[0];
let zero = builder.ins().f64const(0.0);
builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::Equal,
truth,
zero,
)
} else {
let zero = builder.ins().f64const(0.0);
builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::Equal,
v,
zero,
)
};
let target_block = block_map[&target];
let fall_through = builder.create_block();
builder
.ins()
.brif(cond, target_block, &[], fall_through, &[]);
builder.switch_to_block(fall_through);
stack.clear(); }
Op::JumpIfTruePop(target) => {
let v = stack.pop().expect("JumpIfTruePop");
let cond = if mixed {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_TRUTHINESS));
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, v, zf],
);
let truth = builder.inst_results(call)[0];
let zero = builder.ins().f64const(0.0);
builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::NotEqual,
truth,
zero,
)
} else {
let zero = builder.ins().f64const(0.0);
builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::NotEqual,
v,
zero,
)
};
let target_block = block_map[&target];
let fall_through = builder.create_block();
builder
.ins()
.brif(cond, target_block, &[], fall_through, &[]);
builder.switch_to_block(fall_through);
stack.clear(); }
Op::Pop => {
stack.pop();
}
Op::Dup => {
let v = *stack.last().expect("Dup");
stack.push(v);
}
Op::CompoundAssignSlot(slot, bop) => {
let rhs = stack.pop().expect("CompoundAssignSlot");
let offset = (slot as i32) * 8;
let old = if use_slot_ssa {
builder.use_var(slot_vars[slot as usize])
} else {
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset)
};
let new_val = if mixed {
let mop = mixed_op_for_binop(bop);
let op_c = builder.ins().iconst(types::I32, i64::from(mop));
let z = builder.ins().iconst(types::I32, 0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, old, rhs],
);
builder.inst_results(call)[0]
} else {
match bop {
BinOp::Add => builder.ins().fadd(old, rhs),
BinOp::Sub => builder.ins().fsub(old, rhs),
BinOp::Mul => builder.ins().fmul(old, rhs),
BinOp::Div => builder.ins().fdiv(old, rhs),
BinOp::Mod => {
let div = builder.ins().fdiv(old, rhs);
let trunc = builder.ins().trunc(div);
let prod = builder.ins().fmul(trunc, rhs);
builder.ins().fsub(old, prod)
}
_ => unreachable!("filtered by is_jit_eligible"),
}
};
if use_slot_ssa {
builder.def_var(slot_vars[slot as usize], new_val);
} else {
builder
.ins()
.store(MemFlags::trusted(), new_val, slots_ptr, offset);
}
stack.push(new_val);
}
Op::IncDecSlot(slot, kind) => {
if mixed {
let enc = mixed_encode_slot_incdec(slot, kind);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_INCDEC_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(enc));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
stack.push(builder.inst_results(call)[0]);
} else if use_slot_ssa {
let old = builder.use_var(slot_vars[slot as usize]);
let delta = match kind {
IncDecOp::PreInc | IncDecOp::PostInc => builder.ins().f64const(1.0),
IncDecOp::PreDec | IncDecOp::PostDec => builder.ins().f64const(-1.0),
};
let new_val = builder.ins().fadd(old, delta);
builder.def_var(slot_vars[slot as usize], new_val);
let push_val = match kind {
IncDecOp::PreInc | IncDecOp::PreDec => new_val,
IncDecOp::PostInc | IncDecOp::PostDec => old,
};
stack.push(push_val);
} else {
let offset = (slot as i32) * 8;
let old =
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset);
let delta = match kind {
IncDecOp::PreInc | IncDecOp::PostInc => builder.ins().f64const(1.0),
IncDecOp::PreDec | IncDecOp::PostDec => builder.ins().f64const(-1.0),
};
let new_val = builder.ins().fadd(old, delta);
builder
.ins()
.store(MemFlags::trusted(), new_val, slots_ptr, offset);
let push_val = match kind {
IncDecOp::PreInc | IncDecOp::PreDec => new_val,
IncDecOp::PostInc | IncDecOp::PostDec => old,
};
stack.push(push_val);
}
}
Op::IncrSlot(slot) => {
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_INCR_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
} else if use_slot_ssa {
let old = builder.use_var(slot_vars[slot as usize]);
let one = builder.ins().f64const(1.0);
let new_val = builder.ins().fadd(old, one);
builder.def_var(slot_vars[slot as usize], new_val);
} else {
let offset = (slot as i32) * 8;
let old =
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset);
let one = builder.ins().f64const(1.0);
let new_val = builder.ins().fadd(old, one);
builder
.ins()
.store(MemFlags::trusted(), new_val, slots_ptr, offset);
}
}
Op::DecrSlot(slot) => {
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_DECR_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
} else if use_slot_ssa {
let old = builder.use_var(slot_vars[slot as usize]);
let one = builder.ins().f64const(1.0);
let new_val = builder.ins().fsub(old, one);
builder.def_var(slot_vars[slot as usize], new_val);
} else {
let offset = (slot as i32) * 8;
let old =
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset);
let one = builder.ins().f64const(1.0);
let new_val = builder.ins().fsub(old, one);
builder
.ins()
.store(MemFlags::trusted(), new_val, slots_ptr, offset);
}
}
Op::AddSlotToSlot { src, dst } => {
if mixed {
let enc = mixed_encode_slot_pair(src, dst);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_ADD_SLOT_TO_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(enc));
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
} else if use_slot_ssa {
let sv = builder.use_var(slot_vars[src as usize]);
let dv = builder.use_var(slot_vars[dst as usize]);
let sum = builder.ins().fadd(dv, sv);
builder.def_var(slot_vars[dst as usize], sum);
} else {
let sv = builder.ins().load(
types::F64,
MemFlags::trusted(),
slots_ptr,
(src as i32) * 8,
);
let dv = builder.ins().load(
types::F64,
MemFlags::trusted(),
slots_ptr,
(dst as i32) * 8,
);
let sum = builder.ins().fadd(dv, sv);
builder
.ins()
.store(MemFlags::trusted(), sum, slots_ptr, (dst as i32) * 8);
}
}
Op::PushFieldNum(field) => {
let arg = builder.ins().iconst(types::I32, field as i64);
let result = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
arg,
field_direct,
);
stack.push(result);
}
Op::GetField => {
let fv = stack.pop().expect("GetField");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_GET_FIELD));
let z = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z, fv, zf],
);
stack.push(builder.inst_results(call)[0]);
} else {
let idx_i32 = builder.ins().fcvt_to_sint_sat(types::I32, fv);
let result = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
idx_i32,
field_direct,
);
stack.push(result);
}
}
Op::GetNR => {
let arg = builder
.ins()
.iconst(types::I32, JIT_FIELD_SENTINEL_NR as i64);
let result = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
arg,
field_direct,
);
stack.push(result);
}
Op::GetFNR => {
let arg = builder
.ins()
.iconst(types::I32, JIT_FIELD_SENTINEL_FNR as i64);
let result = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
arg,
field_direct,
);
stack.push(result);
}
Op::GetNF => {
let arg = builder
.ins()
.iconst(types::I32, JIT_FIELD_SENTINEL_NF as i64);
let result = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
arg,
field_direct,
);
stack.push(result);
}
Op::AddFieldToSlot { field, slot } => {
if mixed {
let arg = builder.ins().iconst(types::I32, field as i64);
let fv = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
arg,
field_direct,
);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_ADD_FIELDNUM_TO_SLOT));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, fv, z],
);
} else if use_slot_ssa {
let arg = builder.ins().iconst(types::I32, field as i64);
let fv = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
arg,
field_direct,
);
let old = builder.use_var(slot_vars[slot as usize]);
let sum = builder.ins().fadd(old, fv);
builder.def_var(slot_vars[slot as usize], sum);
} else {
let arg = builder.ins().iconst(types::I32, field as i64);
let fv = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
arg,
field_direct,
);
let offset = (slot as i32) * 8;
let old =
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset);
let sum = builder.ins().fadd(old, fv);
builder
.ins()
.store(MemFlags::trusted(), sum, slots_ptr, offset);
}
}
Op::AddMulFieldsToSlot { f1, f2, slot } => {
if mixed {
let a1 = builder.ins().iconst(types::I32, f1 as i64);
let v1 = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
a1,
field_direct,
);
let a2f = builder.ins().iconst(types::I32, f2 as i64);
let v2 = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
a2f,
field_direct,
);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_ADD_MUL_FIELDNUMS_TO_SLOT));
let slot_a1 = builder.ins().iconst(types::I32, i64::from(slot));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, slot_a1, v1, v2],
);
} else if use_slot_ssa {
let a1 = builder.ins().iconst(types::I32, f1 as i64);
let v1 = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
a1,
field_direct,
);
let a2 = builder.ins().iconst(types::I32, f2 as i64);
let v2 = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
a2,
field_direct,
);
let prod = builder.ins().fmul(v1, v2);
let old = builder.use_var(slot_vars[slot as usize]);
let sum = builder.ins().fadd(old, prod);
builder.def_var(slot_vars[slot as usize], sum);
} else {
let a1 = builder.ins().iconst(types::I32, f1 as i64);
let v1 = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
a1,
field_direct,
);
let a2 = builder.ins().iconst(types::I32, f2 as i64);
let v2 = emit_field_call(
&mut builder,
field_sig_ir,
field_fn_ptr,
vmctx,
a2,
field_direct,
);
let prod = builder.ins().fmul(v1, v2);
let offset = (slot as i32) * 8;
let old =
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset);
let sum = builder.ins().fadd(old, prod);
builder
.ins()
.store(MemFlags::trusted(), sum, slots_ptr, offset);
}
}
Op::ArrayFieldAddConst { arr, field, delta } => {
let arr_idx = builder.ins().iconst(types::I32, i64::from(arr));
let field_idx = builder.ins().iconst(types::I32, i64::from(field));
let d = builder.ins().f64const(delta);
builder.ins().call_indirect(
array_sig_ir,
array_fn_ptr,
&[vmctx, arr_idx, field_idx, d],
);
}
Op::JumpIfSlotGeNum {
slot,
limit,
target,
} => {
let v = if mixed {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_SLOT_AS_NUMBER));
let a1 = builder.ins().iconst(types::I32, i64::from(slot));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
builder.inst_results(call)[0]
} else if use_slot_ssa {
builder.use_var(slot_vars[slot as usize])
} else {
let offset = (slot as i32) * 8;
builder
.ins()
.load(types::F64, MemFlags::trusted(), slots_ptr, offset)
};
let lim = builder.ins().f64const(limit);
let ge = builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::GreaterThanOrEqual,
v,
lim,
);
let target_block = block_map[&target];
let fall_through = builder.create_block();
builder.ins().brif(ge, target_block, &[], fall_through, &[]);
builder.switch_to_block(fall_through);
stack.clear();
}
Op::PrintFieldStdout(field) => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_IO_PRINT_FIELD));
let a1 = builder.ins().iconst(types::I32, field as i64);
let z = builder.ins().iconst(types::I32, 0);
builder
.ins()
.call_indirect(io_sig_ir, io_fn_ptr, &[vmctx, op_c, a1, z, z]);
}
Op::PrintFieldSepField { f1, sep, f2 } => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_IO_PRINT_FIELD_SEP_FIELD));
let a1 = builder.ins().iconst(types::I32, f1 as i64);
let a2 = builder.ins().iconst(types::I32, sep as i64);
let a3 = builder.ins().iconst(types::I32, f2 as i64);
builder
.ins()
.call_indirect(io_sig_ir, io_fn_ptr, &[vmctx, op_c, a1, a2, a3]);
}
Op::PrintThreeFieldsStdout { f1, f2, f3 } => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_IO_PRINT_THREE_FIELDS));
let a1 = builder.ins().iconst(types::I32, f1 as i64);
let a2 = builder.ins().iconst(types::I32, f2 as i64);
let a3 = builder.ins().iconst(types::I32, f3 as i64);
builder
.ins()
.call_indirect(io_sig_ir, io_fn_ptr, &[vmctx, op_c, a1, a2, a3]);
}
Op::Print {
argc: 0,
redir: crate::bytecode::RedirKind::Stdout,
} => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_IO_PRINT_RECORD));
let z = builder.ins().iconst(types::I32, 0);
builder
.ins()
.call_indirect(io_sig_ir, io_fn_ptr, &[vmctx, op_c, z, z, z]);
}
Op::Print {
argc,
redir: crate::bytecode::RedirKind::Stdout,
} if argc > 0 => {
let n = argc as usize;
if stack.len() < n {
return None;
}
let mut vals: Vec<cranelift_codegen::ir::Value> = Vec::with_capacity(n);
for _ in 0..n {
vals.push(stack.pop().expect("Print argc"));
}
vals.reverse();
for (i, v) in vals.iter().enumerate() {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_PRINT_ARG));
let a1 = builder.ins().iconst(types::I32, i64::try_from(i).unwrap());
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, *v, z],
);
}
let op_f = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PRINT_FLUSH));
let a1 = builder.ins().iconst(types::I32, i64::from(argc));
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_f, a1, z, z]);
}
Op::Printf {
argc,
redir: crate::bytecode::RedirKind::Stdout,
} if argc > 0 => {
let n = argc as usize;
if stack.len() < n {
return None;
}
let mut vals: Vec<cranelift_codegen::ir::Value> = Vec::with_capacity(n);
for _ in 0..n {
vals.push(stack.pop().expect("Printf argc"));
}
vals.reverse();
for (i, v) in vals.iter().enumerate() {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_PRINT_ARG));
let a1 = builder.ins().iconst(types::I32, i64::try_from(i).unwrap());
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, *v, z],
);
}
let op_pf = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PRINTF_FLUSH));
let a1 = builder.ins().iconst(types::I32, i64::from(argc));
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_pf, a1, z, z]);
}
Op::Printf { argc, redir }
if argc > 0 && redir != crate::bytecode::RedirKind::Stdout =>
{
let n = argc as usize;
if stack.len() < n + 1 {
return None;
}
let path = stack.pop().expect("Printf redir path");
let mut vals: Vec<_> =
(0..n).map(|_| stack.pop().expect("Printf arg")).collect();
vals.reverse();
let zf = builder.ins().f64const(0.0);
for (i, v) in vals.iter().enumerate() {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_PRINT_ARG));
let a1 = builder.ins().iconst(types::I32, i64::try_from(i).unwrap());
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, *v, zf],
);
}
let op_pf = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PRINTF_FLUSH_REDIR));
let a1 = builder
.ins()
.iconst(types::I32, i64::from(pack_print_redir(argc, redir)));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_pf, a1, path, zf],
);
}
Op::Print { argc, redir } if redir != crate::bytecode::RedirKind::Stdout => {
let n = argc as usize;
if stack.len() < n + 1 {
return None;
}
let path = stack.pop().expect("Print redir path");
let zf = builder.ins().f64const(0.0);
if n > 0 {
let mut vals: Vec<_> =
(0..n).map(|_| stack.pop().expect("Print arg")).collect();
vals.reverse();
for (i, v) in vals.iter().enumerate() {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_PRINT_ARG));
let a1 = builder.ins().iconst(types::I32, i64::try_from(i).unwrap());
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, *v, zf],
);
}
}
let op_fr = builder
.ins()
.iconst(types::I32, i64::from(MIXED_PRINT_FLUSH_REDIR));
let a1 = builder
.ins()
.iconst(types::I32, i64::from(pack_print_redir(argc, redir)));
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_fr, a1, path, zf],
);
}
Op::MatchRegexp(idx) => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_MATCH_REGEXP));
let a1 = builder.ins().iconst(types::I32, idx as i64);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::Next => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_SIGNAL_NEXT));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_c, z32, z, z]);
builder.ins().return_(&[z]);
block_terminated = true;
}
Op::NextFile => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_SIGNAL_NEXT_FILE));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_c, z32, z, z]);
builder.ins().return_(&[z]);
block_terminated = true;
}
Op::ExitDefault => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_SIGNAL_EXIT_DEFAULT));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_c, z32, z, z]);
builder.ins().return_(&[z]);
block_terminated = true;
}
Op::ExitWithCode => {
let code = stack.pop().expect("ExitWithCode");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_SIGNAL_EXIT_CODE));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z32, code, z],
);
builder.ins().return_(&[z]);
block_terminated = true;
}
Op::GetArrayElem(arr) => {
let key = stack.pop().expect("GetArrayElem key");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_ARRAY_GET));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
stack.push(builder.inst_results(call)[0]);
} else {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_ARRAY_GET));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::SetArrayElem(arr) => {
let val = stack.pop().expect("SetArrayElem val");
let key = stack.pop().expect("SetArrayElem key");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_ARRAY_SET));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, val],
);
stack.push(builder.inst_results(call)[0]);
} else {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_ARRAY_SET));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, val],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::InArray(arr) => {
let key = stack.pop().expect("InArray key");
if mixed {
let op_c = builder.ins().iconst(types::I32, i64::from(MIXED_ARRAY_IN));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
stack.push(builder.inst_results(call)[0]);
} else {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_ARRAY_IN));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::DeleteElem(arr) => {
let key = stack.pop().expect("DeleteElem key");
if mixed {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_ARRAY_DELETE_ELEM));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
} else {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_ARRAY_DELETE_ELEM));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
}
}
Op::DeleteArray(arr) => {
if mixed {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_ARRAY_DELETE_ALL));
let a1 = builder.ins().iconst(types::I32, i64::from(arr));
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
} else {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_ARRAY_DELETE_ALL));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
}
}
Op::CompoundAssignIndex(arr, bop) => {
let rhs = stack.pop().expect("CompoundAssignIndex rhs");
let key = stack.pop().expect("CompoundAssignIndex key");
if mixed {
let enc = mixed_encode_array_compound(arr, bop);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_ARRAY_COMPOUND));
let a1 = builder.ins().iconst(types::I32, i64::from(enc));
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, rhs],
);
stack.push(builder.inst_results(call)[0]);
} else {
let cop = jit_val_op_for_array_compound(bop);
let op_c = builder.ins().iconst(types::I32, i64::from(cop));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, rhs],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::IncDecIndex(arr, kind) => {
let key = stack.pop().expect("IncDecIndex key");
if mixed {
let enc = mixed_encode_array_incdec(arr, kind);
let op_c = builder
.ins()
.iconst(types::I32, i64::from(MIXED_ARRAY_INCDEC));
let a1 = builder.ins().iconst(types::I32, i64::from(enc));
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
stack.push(builder.inst_results(call)[0]);
} else {
let cop = jit_val_op_for_array_incdec(kind);
let op_c = builder.ins().iconst(types::I32, i64::from(cop));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, key, z],
);
stack.push(builder.inst_results(call)[0]);
}
}
Op::JoinArrayKey(n) => {
let n = n as usize;
let mut vals: Vec<cranelift_codegen::ir::Value> = Vec::with_capacity(n);
for _ in 0..n {
vals.push(stack.pop().expect("JoinArrayKey"));
}
vals.reverse();
let z0 = builder.ins().iconst(types::I32, 0);
let zf = builder.ins().f64const(0.0);
let op_arg = builder
.ins()
.iconst(types::I32, i64::from(MIXED_JOIN_KEY_ARG));
for v in vals {
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_arg, z0, v, zf],
);
}
let op_join = builder
.ins()
.iconst(types::I32, i64::from(MIXED_JOIN_ARRAY_KEY));
let a1n = builder.ins().iconst(types::I32, i64::try_from(n).unwrap());
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_join, a1n, zf, zf],
);
stack.push(builder.inst_results(call)[0]);
}
Op::ReturnVal => {
let val = stack.pop().expect("ReturnVal");
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_SIGNAL_RETURN_VAL));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, z32, val, z],
);
builder.ins().return_(&[z]);
block_terminated = true;
}
Op::ReturnEmpty => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_SIGNAL_RETURN_EMPTY));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_c, z32, z, z]);
builder.ins().return_(&[z]);
block_terminated = true;
}
Op::ForInStart(arr) => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_FORIN_START));
let a1 = builder.ins().iconst(types::I32, arr as i64);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_c, a1, z, z]);
}
Op::ForInNext { var, end_jump } => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_FORIN_NEXT));
let a1 = builder.ins().iconst(types::I32, var as i64);
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, z, z],
);
let result = builder.inst_results(call)[0];
let zero = builder.ins().f64const(0.0);
let exhausted = builder.ins().fcmp(
cranelift_codegen::ir::condcodes::FloatCC::Equal,
result,
zero,
);
let end_block = block_map[&end_jump];
let fall_through = builder.create_block();
builder
.ins()
.brif(exhausted, end_block, &[], fall_through, &[]);
builder.switch_to_block(fall_through);
stack.clear();
}
Op::ForInEnd => {
let op_c = builder
.ins()
.iconst(types::I32, i64::from(JIT_VAL_FORIN_END));
let z32 = builder.ins().iconst(types::I32, 0);
let z = builder.ins().f64const(0.0);
builder
.ins()
.call_indirect(val_sig_ir, val_fn_ptr, &[vmctx, op_c, z32, z, z]);
}
Op::Asort { src, dest } => {
let op_c = builder.ins().iconst(types::I32, i64::from(JIT_VAL_ASORT));
let a1 = builder.ins().iconst(types::I32, src as i64);
let d = match dest {
Some(d) => builder.ins().f64const(d as f64),
None => builder.ins().f64const(-1.0),
};
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, d, z],
);
stack.push(builder.inst_results(call)[0]);
}
Op::Asorti { src, dest } => {
let op_c = builder.ins().iconst(types::I32, i64::from(JIT_VAL_ASORTI));
let a1 = builder.ins().iconst(types::I32, src as i64);
let d = match dest {
Some(d) => builder.ins().f64const(d as f64),
None => builder.ins().f64const(-1.0),
};
let z = builder.ins().f64const(0.0);
let call = builder.ins().call_indirect(
val_sig_ir,
val_fn_ptr,
&[vmctx, op_c, a1, d, z],
);
stack.push(builder.inst_results(call)[0]);
}
_ => unreachable!("filtered by is_jit_eligible"),
}
pc += 1;
}
if !block_terminated {
let slots_ptr_ret = builder.use_var(var_slots_ptr);
emit_slot_ssa_flush_to_mem(&mut builder, use_slot_ssa, &slot_vars, slots_ptr_ret);
let result = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
builder.ins().return_(&[result]);
}
for &t in &jump_targets {
if t >= ops.len() {
if let Some(&blk) = block_map.get(&t) {
builder.switch_to_block(blk);
let slots_ptr_exit = builder.use_var(var_slots_ptr);
emit_slot_ssa_flush_to_mem(
&mut builder,
use_slot_ssa,
&slot_vars,
slots_ptr_exit,
);
let z = builder.ins().f64const(0.0);
builder.ins().return_(&[z]);
}
}
}
builder.seal_all_blocks();
builder.finalize();
}
module.define_function(func_id, &mut ctx).ok()?;
module.clear_context(&mut ctx);
module.finalize_definitions().ok()?;
let fn_ptr = module.get_finalized_function(func_id);
Some(JitChunk {
_module: module,
fn_ptr,
slot_count: slot_count as u16,
needs_fields: has_fields,
})
}
#[inline]
pub(crate) fn jit_disabled_by_env() -> bool {
matches!(
std::env::var_os("AWKRS_JIT").as_deref(),
Some(s) if s == "0"
)
}
pub(crate) fn try_jit_execute_cached(
chunk: &Arc<JitChunk>,
state: &mut JitRuntimeState<'_>,
) -> Option<f64> {
if jit_disabled_by_env() {
return None;
}
Some(chunk.execute(state))
}
pub fn try_jit_execute(
ops: &[Op],
state: &mut JitRuntimeState<'_>,
cp: &CompiledProgram,
) -> Option<f64> {
if jit_disabled_by_env() {
return None;
}
let hash = ops_hash(ops);
let cached = JIT_COMPILE_CACHE.with(|c| c.borrow().get(&hash).cloned());
if let Some(entry) = cached {
return entry.as_ref().map(|chunk| chunk.execute(state));
}
let chunk = try_compile(ops, cp).map(Arc::new);
let result = chunk.as_ref().map(|c| c.execute(state));
JIT_COMPILE_CACHE.with(|c| {
c.borrow_mut().insert(hash, chunk);
});
result
}
pub fn is_numeric_stack_eligible(ops: &[Op]) -> bool {
let mut depth: i32 = 0;
for op in ops {
match op {
Op::PushNum(_) => depth += 1,
Op::Add | Op::Sub | Op::Mul | Op::Div | Op::Mod | Op::Pow => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::CmpEq | Op::CmpNe | Op::CmpLt | Op::CmpLe | Op::CmpGt | Op::CmpGe => {
if depth < 2 {
return false;
}
depth -= 1;
}
Op::Neg | Op::Pos | Op::Not | Op::ToBool => {
if depth < 1 {
return false;
}
}
Op::Dup => {
if depth < 1 {
return false;
}
depth += 1;
}
Op::Pop => {
if depth < 1 {
return false;
}
depth -= 1;
}
Op::GetSlot(_) => depth += 1,
Op::SetSlot(_) => {}
Op::CompoundAssignSlot(_, bop) => {
if depth < 1 {
return false;
}
match bop {
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => {}
_ => return false,
}
}
Op::IncDecSlot(_, _) => depth += 1,
Op::IncrSlot(_) | Op::DecrSlot(_) => {}
Op::AddSlotToSlot { .. } => {}
Op::AddFieldToSlot { .. } | Op::AddMulFieldsToSlot { .. } => {}
Op::GetField => {
if depth < 1 {
return false;
}
}
Op::PushFieldNum(_) | Op::GetNR | Op::GetFNR | Op::GetNF => depth += 1,
_ => return false,
}
}
depth == 1
}
pub fn numeric_stack_slot_words(ops: &[Op]) -> usize {
let mut m = 0usize;
for op in ops {
match op {
Op::GetSlot(s) | Op::SetSlot(s) => m = m.max(*s as usize + 1),
Op::CompoundAssignSlot(s, _)
| Op::IncDecSlot(s, _)
| Op::IncrSlot(s)
| Op::DecrSlot(s) => {
m = m.max(*s as usize + 1);
}
Op::AddSlotToSlot { src, dst, .. } => {
m = m.max(*src as usize + 1).max(*dst as usize + 1);
}
Op::AddFieldToSlot { slot, .. } | Op::AddMulFieldsToSlot { slot, .. } => {
m = m.max(*slot as usize + 1);
}
_ => {}
}
}
m
}
pub fn try_compile_numeric_expr(ops: &[Op]) -> Option<JitNumericChunk> {
if !is_numeric_stack_eligible(ops) {
return None;
}
let chunk = try_compile(ops, &empty_compiled_program())?;
Some(JitNumericChunk {
inner: chunk,
slot_words: numeric_stack_slot_words(ops),
})
}
pub struct JitNumericChunk {
inner: JitChunk,
slot_words: usize,
}
impl JitNumericChunk {
pub fn call_f64(&self) -> f64 {
extern "C" fn dummy_field(_: *mut c_void, _: i32) -> f64 {
0.0
}
extern "C" fn dummy_array(_: *mut c_void, _: u32, _: i32, _: f64) {}
extern "C" fn dummy_var(_: *mut c_void, _: u32, _: u32, _: f64) -> f64 {
0.0
}
extern "C" fn dummy_field_dispatch(_: *mut c_void, _: u32, _: i32, _: f64) -> f64 {
0.0
}
extern "C" fn dummy_io_dispatch(_: *mut c_void, _: u32, _: i32, _: i32, _: i32) {}
extern "C" fn dummy_val_dispatch(_: *mut c_void, op: u32, _: u32, a2: f64, a3: f64) -> f64 {
if op == JIT_VAL_FDIV_CHECKED {
return if a3 == 0.0 { f64::NAN } else { a2 / a3 };
}
0.0
}
let mut slots: Vec<f64> = if self.slot_words == 0 {
Vec::new()
} else {
vec![0.0; self.slot_words]
};
let mut state = JitRuntimeState::new(
std::ptr::null_mut(),
&mut slots,
dummy_field,
dummy_array,
dummy_var,
dummy_field_dispatch,
dummy_io_dispatch,
dummy_val_dispatch,
);
self.inner.execute(&mut state)
}
}
pub fn try_jit_dispatch_numeric_chunk(ops: &[Op]) -> Option<f64> {
if jit_disabled_by_env() {
return None;
}
let jit = try_compile_numeric_expr(ops)?;
Some(jit.call_f64())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::BinOp;
use crate::bytecode::GetlineSource;
use crate::test_sync::ENV_LOCK;
use cranelift_codegen::settings::OptLevel;
use cranelift_module::Module;
use std::cell::RefCell;
thread_local! {
static TEST_JIT_VARS: RefCell<Vec<f64>> = const { RefCell::new(Vec::new()) };
}
thread_local! {
static TEST_JIT_FIELDS: RefCell<Vec<f64>> = const { RefCell::new(Vec::new()) };
}
#[test]
fn is_nan_str_recognizes_dyn_bit() {
let h = nan_str_dyn(0);
assert!(is_nan_str(h.to_bits()));
}
#[test]
fn nan_uninit_distinct_from_string_handles() {
let u = nan_uninit();
assert!(is_nan_uninit(u.to_bits()));
assert!(!is_nan_str(u.to_bits()));
}
#[test]
fn new_jit_module_uses_speed_opt_level() {
let m = new_jit_module(&JitCompileOptions::default()).expect("jit module");
assert_eq!(m.isa().flags().opt_level(), OptLevel::Speed);
}
#[test]
fn jit_min_invocations_before_compile_defaults_and_env() {
let _g = ENV_LOCK.lock().expect("env test lock");
std::env::remove_var("AWKRS_JIT_MIN_INVOCATIONS");
assert_eq!(jit_min_invocations_before_compile(), 3);
std::env::set_var("AWKRS_JIT_MIN_INVOCATIONS", "11");
assert_eq!(jit_min_invocations_before_compile(), 11);
std::env::remove_var("AWKRS_JIT_MIN_INVOCATIONS");
}
extern "C" fn test_var_dispatch(_: *mut c_void, op: u32, name_idx: u32, arg: f64) -> f64 {
use crate::ast::BinOp;
use crate::jit::{
JIT_VAR_OP_COMPOUND_ADD, JIT_VAR_OP_COMPOUND_DIV, JIT_VAR_OP_COMPOUND_MOD,
JIT_VAR_OP_COMPOUND_MUL, JIT_VAR_OP_COMPOUND_SUB, JIT_VAR_OP_DECR, JIT_VAR_OP_GET,
JIT_VAR_OP_INCDEC_POST_DEC, JIT_VAR_OP_INCDEC_POST_INC, JIT_VAR_OP_INCDEC_PRE_DEC,
JIT_VAR_OP_INCDEC_PRE_INC, JIT_VAR_OP_INCR, JIT_VAR_OP_SET,
};
TEST_JIT_VARS.with(|cell| {
let mut v = cell.borrow_mut();
let i = name_idx as usize;
if v.len() <= i {
v.resize(i + 1, 0.0);
}
match op {
JIT_VAR_OP_GET => v[i],
JIT_VAR_OP_SET => {
v[i] = arg;
arg
}
JIT_VAR_OP_INCR => {
v[i] += 1.0;
0.0
}
JIT_VAR_OP_DECR => {
v[i] -= 1.0;
0.0
}
JIT_VAR_OP_COMPOUND_ADD
| JIT_VAR_OP_COMPOUND_SUB
| JIT_VAR_OP_COMPOUND_MUL
| JIT_VAR_OP_COMPOUND_DIV
| JIT_VAR_OP_COMPOUND_MOD => {
let bop = match op {
JIT_VAR_OP_COMPOUND_ADD => BinOp::Add,
JIT_VAR_OP_COMPOUND_SUB => BinOp::Sub,
JIT_VAR_OP_COMPOUND_MUL => BinOp::Mul,
JIT_VAR_OP_COMPOUND_DIV => BinOp::Div,
JIT_VAR_OP_COMPOUND_MOD => BinOp::Mod,
_ => unreachable!(),
};
let a = v[i];
let b = arg;
let n = match bop {
BinOp::Add => a + b,
BinOp::Sub => a - b,
BinOp::Mul => a * b,
BinOp::Div => a / b,
BinOp::Mod => a % b,
_ => 0.0,
};
v[i] = n;
n
}
JIT_VAR_OP_INCDEC_PRE_INC
| JIT_VAR_OP_INCDEC_POST_INC
| JIT_VAR_OP_INCDEC_PRE_DEC
| JIT_VAR_OP_INCDEC_POST_DEC => {
let kind = match op {
JIT_VAR_OP_INCDEC_PRE_INC => IncDecOp::PreInc,
JIT_VAR_OP_INCDEC_POST_INC => IncDecOp::PostInc,
JIT_VAR_OP_INCDEC_PRE_DEC => IncDecOp::PreDec,
JIT_VAR_OP_INCDEC_POST_DEC => IncDecOp::PostDec,
_ => unreachable!(),
};
let old_n = v[i];
let delta = match kind {
IncDecOp::PreInc | IncDecOp::PostInc => 1.0,
IncDecOp::PreDec | IncDecOp::PostDec => -1.0,
};
let new_n = old_n + delta;
v[i] = new_n;
match kind {
IncDecOp::PreInc | IncDecOp::PreDec => new_n,
IncDecOp::PostInc | IncDecOp::PostDec => old_n,
}
}
_ => 0.0,
}
})
}
extern "C" fn test_field_dispatch(_: *mut c_void, op: u32, field_idx: i32, arg: f64) -> f64 {
use crate::ast::BinOp;
use crate::jit::{
JIT_FIELD_OP_SET_NUM, JIT_VAR_OP_COMPOUND_ADD, JIT_VAR_OP_COMPOUND_DIV,
JIT_VAR_OP_COMPOUND_MOD, JIT_VAR_OP_COMPOUND_MUL, JIT_VAR_OP_COMPOUND_SUB,
JIT_VAR_OP_INCDEC_POST_DEC, JIT_VAR_OP_INCDEC_POST_INC, JIT_VAR_OP_INCDEC_PRE_DEC,
JIT_VAR_OP_INCDEC_PRE_INC,
};
let i = field_idx.max(0) as usize;
TEST_JIT_FIELDS.with(|cell| {
let mut v = cell.borrow_mut();
if v.len() <= i {
v.resize(i + 1, 0.0);
}
match op {
JIT_FIELD_OP_SET_NUM => {
v[i] = arg;
arg
}
JIT_VAR_OP_COMPOUND_ADD
| JIT_VAR_OP_COMPOUND_SUB
| JIT_VAR_OP_COMPOUND_MUL
| JIT_VAR_OP_COMPOUND_DIV
| JIT_VAR_OP_COMPOUND_MOD => {
let bop = match op {
JIT_VAR_OP_COMPOUND_ADD => BinOp::Add,
JIT_VAR_OP_COMPOUND_SUB => BinOp::Sub,
JIT_VAR_OP_COMPOUND_MUL => BinOp::Mul,
JIT_VAR_OP_COMPOUND_DIV => BinOp::Div,
JIT_VAR_OP_COMPOUND_MOD => BinOp::Mod,
_ => unreachable!(),
};
let a = v[i];
let b = arg;
let n = match bop {
BinOp::Add => a + b,
BinOp::Sub => a - b,
BinOp::Mul => a * b,
BinOp::Div => a / b,
BinOp::Mod => a % b,
_ => 0.0,
};
v[i] = n;
n
}
JIT_VAR_OP_INCDEC_PRE_INC
| JIT_VAR_OP_INCDEC_POST_INC
| JIT_VAR_OP_INCDEC_PRE_DEC
| JIT_VAR_OP_INCDEC_POST_DEC => {
let kind = match op {
JIT_VAR_OP_INCDEC_PRE_INC => IncDecOp::PreInc,
JIT_VAR_OP_INCDEC_POST_INC => IncDecOp::PostInc,
JIT_VAR_OP_INCDEC_PRE_DEC => IncDecOp::PreDec,
JIT_VAR_OP_INCDEC_POST_DEC => IncDecOp::PostDec,
_ => unreachable!(),
};
let old_n = v[i];
let delta = match kind {
IncDecOp::PreInc | IncDecOp::PostInc => 1.0,
IncDecOp::PreDec | IncDecOp::PostDec => -1.0,
};
let new_n = old_n + delta;
v[i] = new_n;
match kind {
IncDecOp::PreInc | IncDecOp::PreDec => new_n,
IncDecOp::PostInc | IncDecOp::PostDec => old_n,
}
}
_ => 0.0,
}
})
}
fn setup_test_vars(initial: &[f64]) {
TEST_JIT_VARS.with(|c| {
*c.borrow_mut() = initial.to_vec();
});
}
fn snapshot_test_vars() -> Vec<f64> {
TEST_JIT_VARS.with(|c| c.borrow().clone())
}
fn setup_test_fields(initial: &[(i32, f64)]) {
TEST_JIT_FIELDS.with(|c| {
let mut v = c.borrow_mut();
v.clear();
for &(idx, val) in initial {
let i = idx.max(0) as usize;
if v.len() <= i {
v.resize(i + 1, 0.0);
}
v[i] = val;
}
});
}
fn field_at(idx: i32) -> f64 {
let i = idx.max(0) as usize;
TEST_JIT_FIELDS.with(|c| {
let v = c.borrow();
v.get(i).copied().unwrap_or(0.0)
})
}
fn exec_with_test_var(ops: &[Op]) -> f64 {
let chunk = try_compile(ops, &super::empty_compiled_program()).expect("compile failed");
let mut slots = [0.0f64; 0];
let mut state = JitRuntimeState::new(
std::ptr::null_mut(),
&mut slots,
dummy_field,
dummy_array,
test_var_dispatch,
dummy_field_dispatch,
dummy_io_dispatch,
dummy_val_dispatch,
);
chunk.execute(&mut state)
}
fn exec_with_test_field(ops: &[Op]) -> f64 {
let chunk = try_compile(ops, &super::empty_compiled_program()).expect("compile failed");
let mut slots = [0.0f64; 0];
let mut state = JitRuntimeState::new(
std::ptr::null_mut(),
&mut slots,
dummy_field,
dummy_array,
dummy_var,
test_field_dispatch,
dummy_io_dispatch,
test_field_mixed_val_dispatch,
);
chunk.execute(&mut state)
}
extern "C" fn dummy_field(_: *mut c_void, _: i32) -> f64 {
0.0
}
extern "C" fn dummy_array(_: *mut c_void, _: u32, _: i32, _: f64) {}
extern "C" fn dummy_var(_: *mut c_void, _: u32, _: u32, _: f64) -> f64 {
0.0
}
extern "C" fn dummy_field_dispatch(_: *mut c_void, _: u32, _: i32, _: f64) -> f64 {
0.0
}
extern "C" fn dummy_io_dispatch(_: *mut c_void, _: u32, _: i32, _: i32, _: i32) {}
extern "C" fn dummy_val_dispatch(_: *mut c_void, op: u32, _: u32, a2: f64, a3: f64) -> f64 {
if op == JIT_VAL_FDIV_CHECKED {
return if a3 == 0.0 { f64::NAN } else { a2 / a3 };
}
0.0
}
extern "C" fn test_field_mixed_val_dispatch(
vmctx: *mut c_void,
op: u32,
a1: u32,
a2: f64,
a3: f64,
) -> f64 {
if op == MIXED_SET_FIELD {
let field_idx = a1 as i32;
let i = field_idx.max(0) as usize;
TEST_JIT_FIELDS.with(|cell| {
let mut v = cell.borrow_mut();
if v.len() <= i {
v.resize(i + 1, 0.0);
}
v[i] = a2;
a2
})
} else {
dummy_val_dispatch(vmctx, op, a1, a2, a3)
}
}
fn exec(ops: &[Op]) -> f64 {
let chunk = try_compile(ops, &super::empty_compiled_program()).expect("compile failed");
let mut slots = [0.0f64; 0];
let mut state = JitRuntimeState::new(
std::ptr::null_mut(),
&mut slots,
dummy_field,
dummy_array,
dummy_var,
dummy_field_dispatch,
dummy_io_dispatch,
dummy_val_dispatch,
);
chunk.execute(&mut state)
}
fn exec_with_slots(ops: &[Op], slots: &mut [f64]) -> f64 {
let chunk = try_compile(ops, &super::empty_compiled_program()).expect("compile failed");
let mut state = JitRuntimeState::new(
std::ptr::null_mut(),
slots,
dummy_field,
dummy_array,
dummy_var,
dummy_field_dispatch,
dummy_io_dispatch,
dummy_val_dispatch,
);
chunk.execute(&mut state)
}
fn exec_with_fields(
ops: &[Op],
slots: &mut [f64],
field_fn: extern "C" fn(*mut c_void, i32) -> f64,
) -> f64 {
let chunk = try_compile(ops, &super::empty_compiled_program()).expect("compile failed");
let mut state = JitRuntimeState::new(
std::ptr::null_mut(),
slots,
field_fn,
dummy_array,
dummy_var,
dummy_field_dispatch,
dummy_io_dispatch,
dummy_val_dispatch,
);
chunk.execute(&mut state)
}
#[test]
fn jit_adds_constants() {
let ops = [Op::PushNum(3.0), Op::PushNum(4.0), Op::Add];
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!((j.call_f64() - 7.0).abs() < 1e-15);
}
#[test]
fn jit_complex_expr() {
let ops = [
Op::PushNum(10.0),
Op::PushNum(2.0),
Op::Sub,
Op::PushNum(3.0),
Op::Mul,
Op::PushNum(4.0),
Op::Div,
];
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!((j.call_f64() - 6.0).abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_mod_dup_pos() {
let ops_mod = [Op::PushNum(10.0), Op::PushNum(3.0), Op::Mod];
let j = try_compile_numeric_expr(&ops_mod).expect("compile mod");
assert!((j.call_f64() - 1.0).abs() < 1e-15);
let ops_pos = [Op::PushNum(7.0), Op::Pos];
let j = try_compile_numeric_expr(&ops_pos).expect("compile pos");
assert!((j.call_f64() - 7.0).abs() < 1e-15);
let ops_dup = [Op::PushNum(5.0), Op::Dup, Op::Mul];
let j = try_compile_numeric_expr(&ops_dup).expect("compile dup mul");
assert!((j.call_f64() - 25.0).abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_cmp_not_to_bool() {
let ops_lt = [Op::PushNum(1.0), Op::PushNum(2.0), Op::CmpLt];
let j = try_compile_numeric_expr(&ops_lt).expect("compile CmpLt");
assert!((j.call_f64() - 1.0).abs() < 1e-15);
let ops_not = [Op::PushNum(0.0), Op::Not];
let j = try_compile_numeric_expr(&ops_not).expect("compile Not");
assert!((j.call_f64() - 1.0).abs() < 1e-15);
let ops_tb = [Op::PushNum(0.0), Op::ToBool];
let j = try_compile_numeric_expr(&ops_tb).expect("compile ToBool");
assert!(j.call_f64().abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_slots_get_set() {
let ops = [Op::PushNum(5.0), Op::SetSlot(0), Op::GetSlot(0), Op::Add];
assert_eq!(numeric_stack_slot_words(&ops), 1);
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!((j.call_f64() - 10.0).abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_set_pop_get() {
let ops = [Op::PushNum(5.0), Op::SetSlot(0), Op::Pop, Op::GetSlot(0)];
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!((j.call_f64() - 5.0).abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_get_slot_zero() {
let ops = [Op::GetSlot(0)];
assert_eq!(numeric_stack_slot_words(&ops), 1);
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!(j.call_f64().abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_get_field() {
let ops = [Op::PushNum(2.0), Op::GetField];
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!(j.call_f64().abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_compound_assign_slot() {
let ops = [Op::PushNum(5.0), Op::CompoundAssignSlot(0, BinOp::Add)];
assert_eq!(numeric_stack_slot_words(&ops), 1);
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!((j.call_f64() - 5.0).abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_add_slot_to_slot() {
let ops = [
Op::PushNum(7.0),
Op::SetSlot(0),
Op::Pop,
Op::PushNum(0.0),
Op::SetSlot(1),
Op::Pop,
Op::AddSlotToSlot { src: 0, dst: 1 },
Op::GetSlot(1),
];
assert_eq!(numeric_stack_slot_words(&ops), 2);
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!((j.call_f64() - 7.0).abs() < 1e-15);
}
#[test]
fn jit_numeric_expr_incr_slot_then_get() {
let ops = [
Op::PushNum(0.0),
Op::SetSlot(0),
Op::Pop,
Op::IncrSlot(0),
Op::GetSlot(0),
];
assert_eq!(numeric_stack_slot_words(&ops), 1);
let j = try_compile_numeric_expr(&ops).expect("compile");
assert!((j.call_f64() - 1.0).abs() < 1e-15);
}
#[test]
fn jit_rejects_non_numeric_ops() {
let ops = [Op::PushNum(1.0), Op::PushStr(0)];
assert!(try_compile_numeric_expr(&ops).is_none());
}
#[test]
fn jit_modulo() {
let r = exec(&[Op::PushNum(10.0), Op::PushNum(3.0), Op::Mod]);
assert!((r - 1.0).abs() < 1e-10);
}
#[test]
fn jit_negation() {
let r = exec(&[Op::PushNum(42.0), Op::Neg]);
assert!((r - (-42.0)).abs() < 1e-15);
}
#[test]
fn jit_not_zero_is_one() {
let r = exec(&[Op::PushNum(0.0), Op::Not]);
assert!((r - 1.0).abs() < 1e-15);
}
#[test]
fn jit_not_nonzero_is_zero() {
let r = exec(&[Op::PushNum(5.0), Op::Not]);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_to_bool() {
assert!((exec(&[Op::PushNum(0.0), Op::ToBool])).abs() < 1e-15);
assert!((exec(&[Op::PushNum(std::f64::consts::PI), Op::ToBool]) - 1.0).abs() < 1e-15);
}
#[test]
fn jit_cmp_eq() {
assert!((exec(&[Op::PushNum(5.0), Op::PushNum(5.0), Op::CmpEq]) - 1.0).abs() < 1e-15);
assert!(exec(&[Op::PushNum(5.0), Op::PushNum(6.0), Op::CmpEq]).abs() < 1e-15);
}
#[test]
fn jit_cmp_ne() {
assert!((exec(&[Op::PushNum(5.0), Op::PushNum(6.0), Op::CmpNe]) - 1.0).abs() < 1e-15);
assert!(exec(&[Op::PushNum(5.0), Op::PushNum(5.0), Op::CmpNe]).abs() < 1e-15);
}
#[test]
fn jit_cmp_lt() {
assert!((exec(&[Op::PushNum(3.0), Op::PushNum(5.0), Op::CmpLt]) - 1.0).abs() < 1e-15);
assert!(exec(&[Op::PushNum(5.0), Op::PushNum(3.0), Op::CmpLt]).abs() < 1e-15);
}
#[test]
fn jit_cmp_le() {
assert!((exec(&[Op::PushNum(5.0), Op::PushNum(5.0), Op::CmpLe]) - 1.0).abs() < 1e-15);
assert!(exec(&[Op::PushNum(6.0), Op::PushNum(5.0), Op::CmpLe]).abs() < 1e-15);
}
#[test]
fn jit_cmp_gt() {
assert!((exec(&[Op::PushNum(5.0), Op::PushNum(3.0), Op::CmpGt]) - 1.0).abs() < 1e-15);
assert!(exec(&[Op::PushNum(3.0), Op::PushNum(5.0), Op::CmpGt]).abs() < 1e-15);
}
#[test]
fn jit_cmp_ge() {
assert!((exec(&[Op::PushNum(5.0), Op::PushNum(5.0), Op::CmpGe]) - 1.0).abs() < 1e-15);
assert!(exec(&[Op::PushNum(3.0), Op::PushNum(5.0), Op::CmpGe]).abs() < 1e-15);
}
#[test]
fn jit_get_set_slot() {
let mut slots = [0.0, 42.0];
let r = exec_with_slots(&[Op::GetSlot(1)], &mut slots);
assert!((r - 42.0).abs() < 1e-15);
}
#[test]
fn jit_set_slot_stores() {
let mut slots = [0.0, 0.0];
exec_with_slots(&[Op::PushNum(99.0), Op::SetSlot(1)], &mut slots);
assert!((slots[1] - 99.0).abs() < 1e-15);
}
#[test]
fn jit_compound_assign_slot_add() {
let mut slots = [10.0];
let r = exec_with_slots(
&[Op::PushNum(5.0), Op::CompoundAssignSlot(0, BinOp::Add)],
&mut slots,
);
assert!((r - 15.0).abs() < 1e-15);
assert!((slots[0] - 15.0).abs() < 1e-15);
}
#[test]
fn jit_incr_slot() {
let mut slots = [10.0];
exec_with_slots(&[Op::IncrSlot(0), Op::PushNum(0.0)], &mut slots);
assert!((slots[0] - 11.0).abs() < 1e-15);
}
#[test]
fn jit_decr_slot() {
let mut slots = [10.0];
exec_with_slots(&[Op::DecrSlot(0), Op::PushNum(0.0)], &mut slots);
assert!((slots[0] - 9.0).abs() < 1e-15);
}
#[test]
fn jit_add_slot_to_slot() {
let mut slots = [3.0, 7.0];
exec_with_slots(
&[Op::AddSlotToSlot { src: 0, dst: 1 }, Op::PushNum(0.0)],
&mut slots,
);
assert!((slots[1] - 10.0).abs() < 1e-15);
}
#[test]
fn jit_incdec_slot_pre_inc() {
let mut slots = [10.0];
let r = exec_with_slots(&[Op::IncDecSlot(0, IncDecOp::PreInc)], &mut slots);
assert!((r - 11.0).abs() < 1e-15);
assert!((slots[0] - 11.0).abs() < 1e-15);
}
#[test]
fn jit_incdec_slot_post_inc() {
let mut slots = [10.0];
let r = exec_with_slots(&[Op::IncDecSlot(0, IncDecOp::PostInc)], &mut slots);
assert!((r - 10.0).abs() < 1e-15); assert!((slots[0] - 11.0).abs() < 1e-15);
}
#[test]
fn jit_simple_jump() {
let mut slots = [0.0];
let ops = [
Op::PushNum(1.0),
Op::SetSlot(0), Op::Pop, Op::Jump(5), Op::PushNum(99.0), Op::GetSlot(0), ];
let r = exec_with_slots(&ops, &mut slots);
assert!((r - 1.0).abs() < 1e-15);
}
#[test]
fn jit_conditional_jump_false() {
let ops = [
Op::PushNum(0.0), Op::JumpIfFalsePop(3), Op::PushNum(99.0), Op::PushNum(42.0), ];
let r = exec(&ops);
assert!((r - 42.0).abs() < 1e-15);
}
#[test]
fn jit_conditional_jump_true_no_jump() {
let ops = [
Op::PushNum(1.0), Op::JumpIfFalsePop(3), Op::PushNum(42.0), ];
let r = exec(&ops);
assert!((r - 42.0).abs() < 1e-15);
}
#[test]
fn jit_loop_sum_1_to_10() {
let ops = [
Op::PushNum(0.0), Op::SetSlot(0), Op::Pop, Op::PushNum(1.0), Op::SetSlot(1), Op::Pop, Op::JumpIfSlotGeNum {
slot: 1,
limit: 11.0,
target: 10,
}, Op::AddSlotToSlot { src: 1, dst: 0 }, Op::IncrSlot(1), Op::Jump(6), Op::GetSlot(0), ];
let mut slots = [0.0, 0.0];
let r = exec_with_slots(&ops, &mut slots);
assert!((r - 55.0).abs() < 1e-15);
}
extern "C" fn test_field_fn(_: *mut c_void, i: i32) -> f64 {
match i {
1 => 10.0,
2 => 20.0,
3 => 30.0,
JIT_FIELD_SENTINEL_NR => 100.0,
JIT_FIELD_SENTINEL_FNR => 50.0,
JIT_FIELD_SENTINEL_NF => 3.0,
_ => 0.0,
}
}
#[test]
fn jit_push_field_num() {
let mut slots = [0.0; 0];
let r = exec_with_fields(
&[Op::PushFieldNum(1), Op::PushFieldNum(2), Op::Add],
&mut slots,
test_field_fn,
);
assert!((r - 30.0).abs() < 1e-15);
}
#[test]
fn jit_get_field_dynamic() {
let mut slots = [0.0; 0];
let r = exec_with_fields(&[Op::PushNum(2.0), Op::GetField], &mut slots, test_field_fn);
assert!((r - 20.0).abs() < 1e-15);
}
#[test]
fn jit_loop_sum_fields_dynamic() {
extern "C" fn fields(_: *mut c_void, i: i32) -> f64 {
match i {
1 => 100.0,
2 => 200.0,
3 => 300.0,
JIT_FIELD_SENTINEL_NF => 3.0, _ => 0.0,
}
}
let ops = [
Op::PushNum(0.0),
Op::SetSlot(0),
Op::Pop,
Op::PushNum(1.0),
Op::SetSlot(1),
Op::Pop,
Op::JumpIfSlotGeNum {
slot: 1,
limit: 4.0,
target: 13,
},
Op::GetSlot(1),
Op::GetField,
Op::CompoundAssignSlot(0, BinOp::Add),
Op::Pop,
Op::IncrSlot(1),
Op::Jump(6),
Op::GetSlot(0),
];
let mut slots = [0.0, 0.0];
let r = exec_with_fields(&ops, &mut slots, fields);
assert!((r - 600.0).abs() < 1e-15);
}
#[test]
fn jit_add_field_to_slot() {
let mut slots = [5.0];
exec_with_fields(
&[Op::AddFieldToSlot { field: 2, slot: 0 }, Op::PushNum(0.0)],
&mut slots,
test_field_fn,
);
assert!((slots[0] - 25.0).abs() < 1e-15);
}
#[test]
fn jit_add_mul_fields_to_slot() {
let mut slots = [0.0];
exec_with_fields(
&[
Op::AddMulFieldsToSlot {
f1: 1,
f2: 2,
slot: 0,
},
Op::PushNum(0.0),
],
&mut slots,
test_field_fn,
);
assert!((slots[0] - 200.0).abs() < 1e-15);
}
#[test]
fn jit_get_nr_fnr_nf() {
let mut slots = [0.0; 0];
let nr = exec_with_fields(&[Op::GetNR], &mut slots, test_field_fn);
assert!((nr - 100.0).abs() < 1e-15);
let fnr = exec_with_fields(&[Op::GetFNR], &mut slots, test_field_fn);
assert!((fnr - 50.0).abs() < 1e-15);
let nf = exec_with_fields(&[Op::GetNF], &mut slots, test_field_fn);
assert!((nf - 3.0).abs() < 1e-15);
}
#[test]
fn jit_jump_if_slot_ge_num() {
let mut slots = [15.0];
let r = exec_with_slots(
&[
Op::JumpIfSlotGeNum {
slot: 0,
limit: 10.0,
target: 2,
},
Op::PushNum(99.0), Op::PushNum(1.0), ],
&mut slots,
);
assert!((r - 1.0).abs() < 1e-15);
}
#[test]
fn jit_eligible_with_slots_and_control_flow() {
let ops = [
Op::PushNum(0.0),
Op::SetSlot(0),
Op::Pop,
Op::GetSlot(0),
Op::PushNum(10.0),
Op::CmpLt,
Op::JumpIfFalsePop(9),
Op::IncrSlot(0),
Op::Jump(3),
Op::GetSlot(0),
];
assert!(is_jit_eligible(&ops));
}
#[test]
fn jit_eligible_join_array_key() {
let ops = [
Op::PushNum(1.0),
Op::PushNum(2.0),
Op::JoinArrayKey(2),
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_eligible_typeof() {
let t = [Op::PushNum(1.0), Op::TypeofValue, Op::ReturnVal];
assert!(is_jit_eligible(&t));
assert!(needs_mixed_mode(&t));
assert!(is_jit_eligible(&[Op::TypeofVar(0), Op::ReturnVal]));
assert!(needs_mixed_mode(&[Op::TypeofVar(0), Op::ReturnVal]));
}
#[test]
fn jit_mixed_string_ops_eligible() {
assert!(is_jit_eligible(&[Op::PushStr(0)]));
assert!(needs_mixed_mode(&[Op::PushStr(0)]));
let concat_ops = [
Op::PushNum(1.0),
Op::PushStr(0),
Op::Concat,
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&concat_ops));
assert!(needs_mixed_mode(&concat_ops));
}
#[test]
fn jit_mixed_print_stdout_eligible() {
let ops = [
Op::PushNum(1.0),
Op::Print {
argc: 1,
redir: crate::bytecode::RedirKind::Stdout,
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_eligible_printf_stdout() {
let ops = [
Op::PushStr(0),
Op::PushNum(42.0),
Op::Printf {
argc: 2,
redir: crate::bytecode::RedirKind::Stdout,
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_eligible_array_field_add_const() {
assert!(is_jit_eligible(&[
Op::ArrayFieldAddConst {
arr: 0,
field: 1,
delta: 1.0,
},
Op::PushNum(0.0),
]));
}
#[test]
fn jit_eligible_hashmap_var_ops() {
assert!(is_jit_eligible(&[
Op::GetVar(0),
Op::PushNum(1.0),
Op::Add,
Op::IncrVar(1),
Op::PushNum(0.0),
]));
}
#[test]
fn jit_eligible_incdec_var() {
assert!(is_jit_eligible(&[
Op::IncDecVar(0, IncDecOp::PostInc),
Op::PushNum(0.0),
]));
}
#[test]
fn jit_incdec_var_post_inc() {
setup_test_vars(&[10.0]);
let r = exec_with_test_var(&[Op::IncDecVar(0, IncDecOp::PostInc)]);
assert!((r - 10.0).abs() < 1e-15);
assert!((snapshot_test_vars()[0] - 11.0).abs() < 1e-15);
}
#[test]
fn jit_incdec_var_pre_inc() {
setup_test_vars(&[10.0]);
let r = exec_with_test_var(&[Op::IncDecVar(0, IncDecOp::PreInc)]);
assert!((r - 11.0).abs() < 1e-15);
assert!((snapshot_test_vars()[0] - 11.0).abs() < 1e-15);
}
#[test]
fn jit_incdec_var_post_dec() {
setup_test_vars(&[10.0]);
let r = exec_with_test_var(&[Op::IncDecVar(0, IncDecOp::PostDec)]);
assert!((r - 10.0).abs() < 1e-15);
assert!((snapshot_test_vars()[0] - 9.0).abs() < 1e-15);
}
#[test]
fn jit_incdec_var_pre_dec() {
setup_test_vars(&[10.0]);
let r = exec_with_test_var(&[Op::IncDecVar(0, IncDecOp::PreDec)]);
assert!((r - 9.0).abs() < 1e-15);
assert!((snapshot_test_vars()[0] - 9.0).abs() < 1e-15);
}
#[test]
fn jit_eligible_compound_assign_field() {
assert!(is_jit_eligible(&[
Op::PushNum(1.0),
Op::PushNum(5.0),
Op::CompoundAssignField(BinOp::Add),
Op::PushNum(0.0),
]));
}
#[test]
fn jit_compound_assign_field_add() {
setup_test_fields(&[(1, 100.0)]);
let r = exec_with_test_field(&[
Op::PushNum(1.0),
Op::PushNum(5.0),
Op::CompoundAssignField(BinOp::Add),
]);
assert!((r - 105.0).abs() < 1e-15);
assert!((field_at(1) - 105.0).abs() < 1e-15);
}
#[test]
fn jit_incdec_field_post_inc() {
setup_test_fields(&[(1, 10.0)]);
let r = exec_with_test_field(&[Op::PushNum(1.0), Op::IncDecField(IncDecOp::PostInc)]);
assert!((r - 10.0).abs() < 1e-15);
assert!((field_at(1) - 11.0).abs() < 1e-15);
}
#[test]
fn jit_eligible_set_field() {
assert!(is_jit_eligible(&[
Op::PushNum(1.0),
Op::PushNum(42.0),
Op::SetField,
Op::PushNum(0.0),
]));
}
#[test]
fn jit_set_field_numeric() {
setup_test_fields(&[(1, 0.0)]);
let r = exec_with_test_field(&[Op::PushNum(1.0), Op::PushNum(42.0), Op::SetField]);
assert!((r - 42.0).abs() < 1e-15);
assert!((field_at(1) - 42.0).abs() < 1e-15);
}
#[test]
fn jit_array_field_add_const_straight_line() {
let ops = [
Op::ArrayFieldAddConst {
arr: 0,
field: 1,
delta: 2.0,
},
Op::PushNum(0.0),
];
assert!((exec(&ops)).abs() < 1e-15);
}
#[test]
fn jit_dup() {
let r = exec(&[Op::PushNum(7.0), Op::Dup, Op::Add]);
assert!((r - 14.0).abs() < 1e-15);
}
#[test]
fn jit_sum_fields_loop() {
extern "C" fn fields(_: *mut c_void, i: i32) -> f64 {
match i {
1 => 100.0,
2 => 200.0,
3 => 300.0,
JIT_FIELD_SENTINEL_NF => 3.0, _ => 0.0,
}
}
let ops = [
Op::PushNum(0.0), Op::SetSlot(0), Op::Pop, Op::AddFieldToSlot { field: 1, slot: 0 }, Op::AddFieldToSlot { field: 2, slot: 0 }, Op::AddFieldToSlot { field: 3, slot: 0 }, Op::GetSlot(0), ];
let mut slots = [0.0, 0.0];
let r = exec_with_fields(&ops, &mut slots, fields);
assert!((r - 600.0).abs() < 1e-15);
}
#[test]
fn jit_eligible_print_field_stdout() {
assert!(is_jit_eligible(&[
Op::PrintFieldStdout(1),
Op::PushNum(0.0),
]));
}
#[test]
fn jit_eligible_print_field_sep_field() {
assert!(is_jit_eligible(&[
Op::PrintFieldSepField {
f1: 1,
sep: 0,
f2: 2
},
Op::PushNum(0.0),
]));
}
#[test]
fn jit_eligible_print_three_fields() {
assert!(is_jit_eligible(&[
Op::PrintThreeFieldsStdout {
f1: 1,
f2: 2,
f3: 3
},
Op::PushNum(0.0),
]));
}
#[test]
fn jit_eligible_print_record() {
assert!(is_jit_eligible(&[
Op::Print {
argc: 0,
redir: crate::bytecode::RedirKind::Stdout
},
Op::PushNum(0.0),
]));
}
#[test]
fn jit_mixed_print_with_args_eligible() {
let ops = [
Op::PushNum(1.0),
Op::Print {
argc: 1,
redir: crate::bytecode::RedirKind::Stdout,
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_print_field_compiles_and_runs() {
let ops = [Op::PrintFieldStdout(1), Op::PushNum(0.0)];
let r = exec(&ops);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_print_three_fields_compiles() {
let ops = [
Op::PrintThreeFieldsStdout {
f1: 1,
f2: 2,
f3: 3,
},
Op::PushNum(0.0),
];
let r = exec(&ops);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_eligible_match_regexp() {
assert!(is_jit_eligible(&[Op::MatchRegexp(0)]));
}
#[test]
fn jit_match_regexp_compiles() {
let r = exec(&[Op::MatchRegexp(0)]);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_eligible_next() {
assert!(is_jit_eligible(&[Op::Next]));
}
#[test]
fn jit_eligible_exit_default() {
assert!(is_jit_eligible(&[Op::ExitDefault]));
}
#[test]
fn jit_eligible_exit_with_code() {
assert!(is_jit_eligible(&[Op::PushNum(1.0), Op::ExitWithCode]));
}
#[test]
fn jit_next_compiles() {
let r = exec(&[Op::Next]);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_exit_default_compiles() {
let r = exec(&[Op::ExitDefault]);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_exit_with_code_compiles() {
let r = exec(&[Op::PushNum(42.0), Op::ExitWithCode]);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_mixed_array_get_eligible() {
let ops = [Op::PushNum(1.0), Op::GetArrayElem(0), Op::PushNum(0.0)];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_mixed_array_set_eligible() {
let ops = [
Op::PushNum(1.0),
Op::PushNum(42.0),
Op::SetArrayElem(0),
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_mixed_in_array_eligible() {
let ops = [Op::PushNum(1.0), Op::InArray(0), Op::PushNum(0.0)];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_mixed_compound_assign_index_eligible() {
let ops = [
Op::PushNum(1.0),
Op::PushNum(5.0),
Op::CompoundAssignIndex(0, BinOp::Add),
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_mixed_split_eligible() {
let ops_default_fs = [
Op::PushStr(0),
Op::Split {
arr: 1,
has_fs: false,
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops_default_fs));
assert!(needs_mixed_mode(&ops_default_fs));
let ops_explicit_fs = [
Op::PushStr(0),
Op::PushStr(0),
Op::Split {
arr: 1,
has_fs: true,
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops_explicit_fs));
assert!(needs_mixed_mode(&ops_explicit_fs));
}
#[test]
fn jit_eligible_call_user_and_gsub() {
use crate::bytecode::SubTarget;
let ops_user = [Op::PushNum(1.0), Op::CallUser(0, 1), Op::PushNum(0.0)];
assert!(is_jit_eligible(&ops_user));
assert!(needs_mixed_mode(&ops_user));
let ops_gsub = [
Op::PushStr(0),
Op::PushStr(1),
Op::GsubFn(SubTarget::Record),
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops_gsub));
assert!(needs_mixed_mode(&ops_gsub));
}
#[test]
fn jit_mixed_patsplit_eligible() {
let ops = [
Op::PushStr(0),
Op::Patsplit {
arr: 1,
has_fp: false,
seps: None,
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
let ops_fp_sep = [
Op::PushStr(0),
Op::PushStr(0),
Op::Patsplit {
arr: 1,
has_fp: true,
seps: Some(2),
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops_fp_sep));
assert!(needs_mixed_mode(&ops_fp_sep));
let ops_fp_sep_large = [
Op::PushStr(0),
Op::PushStr(0),
Op::Patsplit {
arr: 70000,
has_fp: true,
seps: Some(2),
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops_fp_sep_large));
}
#[test]
fn jit_mixed_match_builtin_eligible() {
let ops = [
Op::PushStr(0),
Op::PushStr(0),
Op::MatchBuiltin { arr: None },
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
let ops_arr = [
Op::PushStr(0),
Op::PushStr(0),
Op::MatchBuiltin { arr: Some(1) },
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops_arr));
assert!(needs_mixed_mode(&ops_arr));
}
#[test]
fn jit_mixed_print_redir_eligible() {
use crate::bytecode::RedirKind;
let ops = [
Op::PushStr(0),
Op::PushStr(0),
Op::Print {
argc: 1,
redir: RedirKind::Overwrite,
},
Op::PushNum(0.0),
];
assert!(is_jit_eligible(&ops));
assert!(needs_mixed_mode(&ops));
}
#[test]
fn jit_mixed_getline_eligible() {
let ops_primary = [
Op::GetLine {
var: None,
source: GetlineSource::Primary,
push_result: false,
},
Op::ReturnEmpty,
];
assert!(is_jit_eligible(&ops_primary));
assert!(needs_mixed_mode(&ops_primary));
let ops_file = [
Op::PushStr(0),
Op::GetLine {
var: Some(0),
source: GetlineSource::File,
push_result: false,
},
Op::ReturnEmpty,
];
assert!(is_jit_eligible(&ops_file));
assert!(needs_mixed_mode(&ops_file));
}
#[test]
fn jit_conditional_next() {
let ops = [
Op::PushNum(1.0),
Op::JumpIfFalsePop(3),
Op::Next, Op::PushNum(99.0), ];
let r = exec(&ops);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_print_then_arithmetic() {
extern "C" fn fields(_: *mut c_void, i: i32) -> f64 {
match i {
1 => 10.0,
2 => 20.0,
_ => 0.0,
}
}
let ops = [
Op::PrintFieldStdout(1), Op::AddFieldToSlot { field: 2, slot: 0 }, Op::GetSlot(0), ];
let mut slots = [5.0];
let chunk = try_compile(&ops, &super::empty_compiled_program()).expect("compile");
let mut state = JitRuntimeState::new(
std::ptr::null_mut(),
&mut slots,
fields,
dummy_array,
dummy_var,
dummy_field_dispatch,
dummy_io_dispatch,
dummy_val_dispatch,
);
let r = chunk.execute(&mut state);
assert!((r - 25.0).abs() < 1e-15); }
#[test]
fn jit_eligible_return_val() {
assert!(is_jit_eligible(&[Op::PushNum(42.0), Op::ReturnVal]));
}
#[test]
fn jit_eligible_return_empty() {
assert!(is_jit_eligible(&[Op::ReturnEmpty]));
}
#[test]
fn jit_return_val_compiles() {
let r = exec(&[Op::PushNum(42.0), Op::ReturnVal]);
assert!(r.abs() < 1e-15); }
#[test]
fn jit_return_empty_compiles() {
let r = exec(&[Op::ReturnEmpty]);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_eligible_forin() {
assert!(is_jit_eligible(&[
Op::ForInStart(0),
Op::ForInNext {
var: 1,
end_jump: 4
},
Op::IncrSlot(0),
Op::Jump(1),
Op::ForInEnd,
Op::GetSlot(0),
]));
}
#[test]
fn jit_forin_compiles() {
let ops = [
Op::ForInStart(0),
Op::ForInNext {
var: 1,
end_jump: 4,
},
Op::IncrSlot(0),
Op::Jump(1),
Op::ForInEnd,
Op::GetSlot(0),
];
let mut slots = [0.0];
let r = exec_with_slots(&ops, &mut slots);
assert!(r.abs() < 1e-15);
}
#[test]
fn jit_eligible_asort() {
assert!(is_jit_eligible(&[Op::Asort { src: 0, dest: None }]));
}
#[test]
fn jit_eligible_asorti() {
assert!(is_jit_eligible(&[Op::Asorti {
src: 0,
dest: Some(1),
}]));
}
#[test]
fn jit_asort_compiles() {
let r = exec(&[Op::Asort { src: 0, dest: None }]);
assert!(r.abs() < 1e-15); }
#[test]
fn jit_asorti_compiles() {
let r = exec(&[Op::Asorti {
src: 0,
dest: Some(1),
}]);
assert!(r.abs() < 1e-15);
}
}