use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use cranelift::codegen::settings::{self, Configurable};
use cranelift::prelude::types;
use cranelift::prelude::{
AbiParam, Block, FloatCC, FunctionBuilder, FunctionBuilderContext, InstBuilder, IntCC, MemFlags,
Value as ClifValue, Variable,
};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{Linkage, Module};
use tishlang_ast::{BinOp, UnaryOp};
use tishlang_bytecode::{u8_to_binop, u8_to_unaryop, Chunk, Constant, Opcode, NO_REST_PARAM};
#[derive(Clone, Copy)]
pub struct NumericFn {
ptr: usize,
arity: u8,
result_bool: bool,
array_param_mask: u8,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ArrayHandle {
pub ptr: *const f64,
pub len: usize,
}
#[cfg(not(target_arch = "wasm32"))]
pub fn jit_arrays_enabled() -> bool {
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| std::env::var("TISH_JIT_ARRAYS").map(|v| v != "0").unwrap_or(true))
}
unsafe impl Send for NumericFn {}
unsafe impl Sync for NumericFn {}
impl NumericFn {
#[inline]
pub fn arity(&self) -> usize {
self.arity as usize
}
#[inline]
pub fn result_is_bool(&self) -> bool {
self.result_bool
}
#[inline]
pub fn call(&self, args: &[f64]) -> f64 {
unsafe {
match self.arity {
1 => {
let f: extern "C" fn(f64) -> f64 = std::mem::transmute(self.ptr);
f(args[0])
}
2 => {
let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(self.ptr);
f(args[0], args[1])
}
3 => {
let f: extern "C" fn(f64, f64, f64) -> f64 = std::mem::transmute(self.ptr);
f(args[0], args[1], args[2])
}
4 => {
let f: extern "C" fn(f64, f64, f64, f64) -> f64 = std::mem::transmute(self.ptr);
f(args[0], args[1], args[2], args[3])
}
5 => {
let f: extern "C" fn(f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(self.ptr);
f(args[0], args[1], args[2], args[3], args[4])
}
6 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(self.ptr);
f(args[0], args[1], args[2], args[3], args[4], args[5])
}
7 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(self.ptr);
f(args[0], args[1], args[2], args[3], args[4], args[5], args[6])
}
8 => {
let f: extern "C" fn(f64, f64, f64, f64, f64, f64, f64, f64) -> f64 =
std::mem::transmute(self.ptr);
f(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
}
_ => f64::NAN,
}
}
}
#[inline]
pub fn array_param_mask(&self) -> u8 {
self.array_param_mask
}
#[inline]
pub fn call_arrays(&self, numeric: &[f64], arrays: &[ArrayHandle]) -> (f64, bool) {
let mut deopt: u8 = 0;
let num_ptr = if numeric.is_empty() {
std::ptr::NonNull::<f64>::dangling().as_ptr() as *const f64
} else {
numeric.as_ptr()
};
let arr_ptr = if arrays.is_empty() {
std::ptr::NonNull::<ArrayHandle>::dangling().as_ptr() as *const ArrayHandle
} else {
arrays.as_ptr()
};
let res = unsafe {
let f: extern "C" fn(*const f64, *const ArrayHandle, *mut u8) -> f64 =
std::mem::transmute(self.ptr);
f(num_ptr, arr_ptr, &mut deopt as *mut u8)
};
(res, deopt != 0)
}
}
struct JitGlobal {
module: JITModule,
cache: HashMap<usize, Option<NumericFn>>,
counter: usize,
}
unsafe impl Send for JitGlobal {}
static JIT: OnceLock<Option<Mutex<JitGlobal>>> = OnceLock::new();
fn new_module() -> 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 builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
Some(JITModule::new(builder))
}
fn jit() -> Option<&'static Mutex<JitGlobal>> {
JIT.get_or_init(|| {
new_module().map(|module| {
Mutex::new(JitGlobal {
module,
cache: HashMap::new(),
counter: 0,
})
})
})
.as_ref()
}
#[inline]
fn read_u16(code: &[u8], ip: &mut usize) -> Option<u16> {
let a = *code.get(*ip)? as u16;
let b = *code.get(*ip + 1)? as u16;
*ip += 2;
Some((a << 8) | b)
}
pub fn try_compile_numeric(chunk: &Chunk) -> Option<NumericFn> {
if !chunk.slot_based
|| chunk.rest_param_index != NO_REST_PARAM
|| chunk.param_count == 0
|| chunk.param_count > 8
{
return None;
}
let key = chunk as *const Chunk as usize;
let lock = jit()?;
let mut g = lock.lock().ok()?;
if let Some(cached) = g.cache.get(&key).copied() {
return cached;
}
let result = compile_chunk(&mut g, chunk);
g.cache.insert(key, result);
result
}
fn fcmp_f64(bcx: &mut FunctionBuilder, cc: FloatCC, a: ClifValue, b: ClifValue) -> ClifValue {
let cond = bcx.ins().fcmp(cc, a, b);
let one = bcx.ins().f64const(1.0);
let zero = bcx.ins().f64const(0.0);
bcx.ins().select(cond, one, zero)
}
fn js_to_int32(bcx: &mut FunctionBuilder, x: ClifValue) -> ClifValue {
let sat = bcx.ins().fcvt_to_sint_sat(types::I64, x);
let red = bcx.ins().ireduce(types::I32, sat);
let absx = bcx.ins().fabs(x);
let inf = bcx.ins().f64const(f64::INFINITY);
let finite = bcx.ins().fcmp(FloatCC::LessThan, absx, inf);
let zero = bcx.ins().iconst(types::I32, 0);
bcx.ins().select(finite, red, zero)
}
fn compile_chunk(g: &mut JitGlobal, chunk: &Chunk) -> Option<NumericFn> {
let arity = chunk.param_count as usize;
#[cfg(not(target_arch = "wasm32"))]
{
let array_mask = if jit_arrays_enabled() {
classify_params(chunk, arity)
} else {
0
};
if array_mask != 0 {
return compile_chunk_arrays(g, chunk, arity, array_mask);
}
}
let mut sig = g.module.make_signature();
for _ in 0..arity {
sig.params.push(AbiParam::new(types::F64));
}
sig.returns.push(AbiParam::new(types::F64));
let name = format!("tish_num_{}", g.counter);
g.counter += 1;
let id = match g.module.declare_function(&name, Linkage::Export, &sig) {
Ok(id) => id,
Err(_) => return None,
};
let mut ctx = g.module.make_context();
ctx.func.signature = sig.clone();
let self_ref = g.module.declare_func_in_func(id, &mut ctx.func);
let mut fbctx = FunctionBuilderContext::new();
let result_bool = match build_body_cfg(&mut ctx.func, &mut fbctx, chunk, arity, Some(self_ref), 0)
{
Some(b) => b,
None => {
g.module.clear_context(&mut ctx);
ctx = g.module.make_context();
ctx.func.signature = sig.clone();
fbctx = FunctionBuilderContext::new();
match build_body(&mut ctx.func, &mut fbctx, chunk, arity) {
Some(b) => b,
None => {
g.module.clear_context(&mut ctx);
return None;
}
}
}
};
if g.module.define_function(id, &mut ctx).is_err() {
g.module.clear_context(&mut ctx);
return None;
}
g.module.clear_context(&mut ctx);
if g.module.finalize_definitions().is_err() {
return None;
}
let ptr = g.module.get_finalized_function(id);
Some(NumericFn {
ptr: ptr as usize,
arity: arity as u8,
result_bool,
array_param_mask: 0,
})
}
#[cfg(not(target_arch = "wasm32"))]
fn classify_params(chunk: &Chunk, arity: usize) -> u8 {
if arity == 0 || arity > 8 || (chunk.num_slots as usize) == 0 {
return 0;
}
let code = &chunk.code;
let mut used_numeric = [false; 8];
let mut used_array = [false; 8];
let mut ip = 0usize;
while ip < code.len() {
let op = match Opcode::from_u8(code[ip]) {
Some(o) => o,
None => return 0,
};
let size = match op_size(op) {
Some(s) => s,
None => return 0, };
match op {
Opcode::LoadLocal => {
let slot = match peek_u16(code, ip + 1) {
Some(s) => s as usize,
None => return 0,
};
let idx_then_getindex = matches!(
(
code.get(ip + 3).copied().and_then(Opcode::from_u8),
code.get(ip + 6).copied().and_then(Opcode::from_u8),
),
(Some(Opcode::LoadLocal), Some(Opcode::GetIndex))
| (Some(Opcode::LoadConst), Some(Opcode::GetIndex))
);
if idx_then_getindex && slot < arity {
used_array[slot] = true;
ip += 7; continue;
} else if slot < arity {
used_numeric[slot] = true;
}
}
Opcode::StoreLocal => {
let slot = match peek_u16(code, ip + 1) {
Some(s) => s as usize,
None => return 0,
};
if slot < arity {
used_numeric[slot] = true; }
}
Opcode::GetIndex => return 0,
_ => {}
}
ip += size;
}
let mut mask = 0u8;
for k in 0..arity {
if used_array[k] {
if used_numeric[k] {
return 0; }
mask |= 1u8 << k;
}
}
mask
}
#[cfg(not(target_arch = "wasm32"))]
fn compile_chunk_arrays(g: &mut JitGlobal, chunk: &Chunk, arity: usize, mask: u8) -> Option<NumericFn> {
let ptr_ty = g.module.target_config().pointer_type();
let mut sig = g.module.make_signature();
sig.params.push(AbiParam::new(ptr_ty)); sig.params.push(AbiParam::new(ptr_ty)); sig.params.push(AbiParam::new(ptr_ty)); sig.returns.push(AbiParam::new(types::F64));
let name = format!("tish_arr_{}", g.counter);
g.counter += 1;
let id = g.module.declare_function(&name, Linkage::Export, &sig).ok()?;
let mut ctx = g.module.make_context();
ctx.func.signature = sig.clone();
let mut fbctx = FunctionBuilderContext::new();
if build_body_cfg(&mut ctx.func, &mut fbctx, chunk, arity, None, mask).is_none() {
g.module.clear_context(&mut ctx);
return None;
}
if g.module.define_function(id, &mut ctx).is_err() {
g.module.clear_context(&mut ctx);
return None;
}
g.module.clear_context(&mut ctx);
if g.module.finalize_definitions().is_err() {
return None;
}
let ptr = g.module.get_finalized_function(id);
Some(NumericFn {
ptr: ptr as usize,
arity: arity as u8,
result_bool: false,
array_param_mask: mask,
})
}
enum SimpleOp {
#[allow(dead_code)] Handled(bool),
NotSimple,
Unsupported,
}
fn emit_simple_op(
bcx: &mut FunctionBuilder,
chunk: &Chunk,
code: &[u8],
ip: &mut usize,
stack: &mut Vec<(ClifValue, bool)>,
params: &[ClifValue],
arity: usize,
) -> SimpleOp {
let op = match code.get(*ip).copied().and_then(Opcode::from_u8) {
Some(o) => o,
None => return SimpleOp::NotSimple,
};
match op {
Opcode::Nop | Opcode::LoadLocal | Opcode::LoadConst | Opcode::BinOp | Opcode::UnaryOp => {}
_ => return SimpleOp::NotSimple,
}
*ip += 1;
match op {
Opcode::Nop => {}
Opcode::LoadLocal => {
let slot = match read_u16(code, ip) {
Some(s) => s as usize,
None => return SimpleOp::Unsupported,
};
if slot >= arity {
return SimpleOp::Unsupported;
}
stack.push((params[slot], false));
}
Opcode::LoadConst => {
let idx = match read_u16(code, ip) {
Some(i) => i as usize,
None => return SimpleOp::Unsupported,
};
match chunk.constants.get(idx) {
Some(Constant::Number(n)) => {
let v = bcx.ins().f64const(*n);
stack.push((v, false));
}
Some(Constant::Bool(b)) => {
let v = bcx.ins().f64const(if *b { 1.0 } else { 0.0 });
stack.push((v, true));
}
_ => return SimpleOp::Unsupported,
}
}
Opcode::BinOp => {
let bop = match read_u16(code, ip).map(|r| r as u8).and_then(u8_to_binop) {
Some(b) => b,
None => return SimpleOp::Unsupported,
};
if stack.len() < 2 {
return SimpleOp::Unsupported;
}
let (r, _) = stack.pop().unwrap();
let (l, _) = stack.pop().unwrap();
let is_cmp = matches!(
bop,
BinOp::Eq | BinOp::Ne | BinOp::StrictEq | BinOp::StrictNe
| BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge
);
let v = match bop {
BinOp::Add => bcx.ins().fadd(l, r),
BinOp::Sub => bcx.ins().fsub(l, r),
BinOp::Mul => bcx.ins().fmul(l, r),
BinOp::Div => bcx.ins().fdiv(l, r),
BinOp::Eq | BinOp::StrictEq => fcmp_f64(bcx, FloatCC::Equal, l, r),
BinOp::Ne | BinOp::StrictNe => fcmp_f64(bcx, FloatCC::NotEqual, l, r),
BinOp::Lt => fcmp_f64(bcx, FloatCC::LessThan, l, r),
BinOp::Le => fcmp_f64(bcx, FloatCC::LessThanOrEqual, l, r),
BinOp::Gt => fcmp_f64(bcx, FloatCC::GreaterThan, l, r),
BinOp::Ge => fcmp_f64(bcx, FloatCC::GreaterThanOrEqual, l, r),
BinOp::Mod => {
let q = bcx.ins().fdiv(l, r);
let t = bcx.ins().trunc(q);
let p = bcx.ins().fmul(t, r);
bcx.ins().fsub(l, p)
}
BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor => {
let li = js_to_int32(bcx, l);
let ri = js_to_int32(bcx, r);
let res = match bop {
BinOp::BitAnd => bcx.ins().band(li, ri),
BinOp::BitOr => bcx.ins().bor(li, ri),
BinOp::BitXor => bcx.ins().bxor(li, ri),
_ => unreachable!(),
};
bcx.ins().fcvt_from_sint(types::F64, res)
}
_ => return SimpleOp::Unsupported,
};
stack.push((v, is_cmp));
}
Opcode::UnaryOp => {
let uop = match read_u16(code, ip).map(|r| r as u8).and_then(u8_to_unaryop) {
Some(u) => u,
None => return SimpleOp::Unsupported,
};
let (o, _) = match stack.pop() {
Some(x) => x,
None => return SimpleOp::Unsupported,
};
let (v, is_bool) = match uop {
UnaryOp::Neg => (bcx.ins().fneg(o), false),
UnaryOp::Pos => (o, false),
UnaryOp::Not => {
let zero = bcx.ins().f64const(0.0);
(fcmp_f64(bcx, FloatCC::Equal, o, zero), true)
}
UnaryOp::BitNot => {
let oi = js_to_int32(bcx, o);
let res = bcx.ins().bnot(oi);
(bcx.ins().fcvt_from_sint(types::F64, res), false)
}
_ => return SimpleOp::Unsupported,
};
stack.push((v, is_bool));
}
_ => unreachable!("guarded above"),
}
SimpleOp::Handled(false)
}
fn falsy_flag(bcx: &mut FunctionBuilder, cond: ClifValue) -> ClifValue {
let zero = bcx.ins().f64const(0.0);
let eq_zero = bcx.ins().fcmp(FloatCC::Equal, cond, zero); let is_nan = bcx.ins().fcmp(FloatCC::NotEqual, cond, cond); bcx.ins().bor(eq_zero, is_nan)
}
fn op_size(op: Opcode) -> Option<usize> {
use Opcode::*;
Some(match op {
Nop | Pop | Dup | Return | LoopVarsEnd | EnterBlock | ExitBlock | GetIndex => 1,
LoadLocal | StoreLocal | LoadConst | BinOp | UnaryOp | Jump | JumpIfFalse | JumpBack
| LoopVarsBegin | SelfCall => 3,
_ => return None,
})
}
#[inline]
fn peek_u16(code: &[u8], off: usize) -> Option<u16> {
let a = *code.get(off)? as u16;
let b = *code.get(off + 1)? as u16;
Some((a << 8) | b)
}
fn build_body_cfg(
func: &mut cranelift::codegen::ir::Function,
fbctx: &mut FunctionBuilderContext,
chunk: &Chunk,
arity: usize,
self_ref: Option<cranelift::codegen::ir::FuncRef>,
array_mask: u8,
) -> Option<bool> {
let code = &chunk.code;
let num_slots = chunk.num_slots as usize;
if num_slots == 0 || num_slots > 256 {
return None;
}
let mut leaders: std::collections::BTreeSet<usize> = std::collections::BTreeSet::new();
leaders.insert(0);
let mut has_loop = false;
let mut has_self_call = false;
let mut ip = 0;
while ip < code.len() {
let op = Opcode::from_u8(code[ip])?;
let size = op_size(op)?;
if op == Opcode::SelfCall {
if self_ref.is_none() || peek_u16(code, ip + 1)? as usize != arity {
return None;
}
has_self_call = true;
}
match op {
Opcode::JumpIfFalse => {
let off = peek_u16(code, ip + 1)? as i16 as isize;
leaders.insert(((ip + 3) as isize + off).max(0) as usize);
leaders.insert(ip + 3);
}
Opcode::Jump => {
let off = peek_u16(code, ip + 1)? as i16 as isize;
leaders.insert(((ip + 3) as isize + off).max(0) as usize);
}
Opcode::JumpBack => {
let dist = peek_u16(code, ip + 1)? as usize;
leaders.insert((ip + 3).checked_sub(dist)?);
has_loop = true;
}
_ => {}
}
ip += size;
}
if !has_loop && !has_self_call {
return None;
}
let mut bcx = FunctionBuilder::new(func, fbctx);
let blocks: std::collections::BTreeMap<usize, Block> =
leaders.iter().map(|&o| (o, bcx.create_block())).collect();
let entry = *blocks.get(&0)?;
bcx.append_block_params_for_function_params(entry);
bcx.switch_to_block(entry);
let params: Vec<ClifValue> = bcx.block_params(entry).to_vec();
let vars: Vec<Variable> = (0..num_slots).map(|_| bcx.declare_var(types::F64)).collect();
let mut array_slots: HashMap<usize, (ClifValue, ClifValue)> = HashMap::new();
let mut deopt_block: Option<Block> = None;
let mut deopt_ptr: Option<ClifValue> = None;
if array_mask != 0 {
let numeric_ptr = *params.first()?;
let handles_ptr = *params.get(1)?;
deopt_ptr = Some(*params.get(2)?);
let mut numeric_i = 0i32;
let mut array_i = 0i64;
#[allow(clippy::needless_range_loop)] for slot in 0..num_slots {
let init = if slot < arity && (array_mask >> slot) & 1 == 1 {
let base = bcx.ins().iadd_imm(handles_ptr, array_i * 16);
let p = bcx.ins().load(types::I64, MemFlags::new(), base, 0);
let l = bcx.ins().load(types::I64, MemFlags::new(), base, 8);
array_slots.insert(slot, (p, l));
array_i += 1;
bcx.ins().f64const(0.0) } else if slot < arity {
let v = bcx
.ins()
.load(types::F64, MemFlags::new(), numeric_ptr, numeric_i * 8);
numeric_i += 1;
v
} else {
bcx.ins().f64const(0.0)
};
bcx.def_var(vars[slot], init);
}
deopt_block = Some(bcx.create_block());
} else {
for (i, &v) in vars.iter().enumerate() {
let init = if i < arity {
params[i]
} else {
bcx.ins().f64const(0.0)
};
bcx.def_var(v, init);
}
}
let mut stack: Vec<(ClifValue, bool)> = Vec::new();
let mut cur = entry;
let mut terminated = false;
let mut ip = 0usize;
while ip < code.len() {
if let Some(&blk) = blocks.get(&ip) {
if blk != cur {
if !terminated {
if !stack.is_empty() {
return None;
}
bcx.ins().jump(blk, &[]);
}
bcx.switch_to_block(blk);
cur = blk;
terminated = false;
stack.clear();
}
}
if terminated {
ip += op_size(Opcode::from_u8(code[ip])?)?; continue;
}
let op = Opcode::from_u8(code[ip])?;
match op {
Opcode::LoadLocal => {
let slot = peek_u16(code, ip + 1)? as usize;
if array_mask != 0 && slot < arity && (array_mask >> slot) & 1 == 1 {
let (aptr, alen) = *array_slots.get(&slot)?;
let idx_op = Opcode::from_u8(*code.get(ip + 3)?)?;
let idx_f64 = match idx_op {
Opcode::LoadLocal => {
let islot = peek_u16(code, ip + 4)? as usize;
bcx.use_var(*vars.get(islot)?)
}
Opcode::LoadConst => {
let ci = peek_u16(code, ip + 4)? as usize;
match chunk.constants.get(ci) {
Some(Constant::Number(n)) => bcx.ins().f64const(*n),
_ => return None,
}
}
_ => return None,
};
if Opcode::from_u8(*code.get(ip + 6)?)? != Opcode::GetIndex {
return None;
}
let i = bcx.ins().fcvt_to_uint_sat(types::I64, idx_f64);
let inb = bcx.ins().icmp(IntCC::UnsignedLessThan, i, alen);
let cont = bcx.create_block();
let db = deopt_block?;
bcx.ins().brif(inb, cont, &[], db, &[]);
bcx.switch_to_block(cont);
cur = cont; let off = bcx.ins().imul_imm(i, 8);
let addr = bcx.ins().iadd(aptr, off);
let val = bcx.ins().load(types::F64, MemFlags::new(), addr, 0);
stack.push((val, false));
ip += 7; continue;
}
let v = *vars.get(slot)?;
stack.push((bcx.use_var(v), false));
ip += 3;
}
Opcode::StoreLocal => {
let slot = peek_u16(code, ip + 1)? as usize;
let (val, is_bool) = stack.pop()?;
if is_bool {
return None; }
let v = *vars.get(slot)?;
bcx.def_var(v, val);
ip += 3;
}
Opcode::Pop => {
stack.pop()?;
ip += 1;
}
Opcode::Dup => {
let top = *stack.last()?;
stack.push(top);
ip += 1;
}
Opcode::Nop | Opcode::EnterBlock | Opcode::ExitBlock | Opcode::LoopVarsEnd => ip += 1,
Opcode::LoopVarsBegin => ip += 3,
Opcode::Return => {
let (v, is_bool) = stack.pop()?;
if is_bool {
return None;
}
bcx.ins().return_(&[v]);
terminated = true;
ip += 1;
}
Opcode::Jump => {
let off = peek_u16(code, ip + 1)? as i16 as isize;
let blk = *blocks.get(&(((ip + 3) as isize + off).max(0) as usize))?;
if !stack.is_empty() {
return None;
}
bcx.ins().jump(blk, &[]);
terminated = true;
ip += 3;
}
Opcode::JumpBack => {
let dist = peek_u16(code, ip + 1)? as usize;
let blk = *blocks.get(&((ip + 3).checked_sub(dist)?))?;
if !stack.is_empty() {
return None;
}
bcx.ins().jump(blk, &[]);
terminated = true;
ip += 3;
}
Opcode::JumpIfFalse => {
let off = peek_u16(code, ip + 1)? as i16 as isize;
let (cond, _) = stack.pop()?;
if !stack.is_empty() {
return None; }
let falsy = falsy_flag(&mut bcx, cond);
let target = *blocks.get(&(((ip + 3) as isize + off).max(0) as usize))?;
let fallthrough = *blocks.get(&(ip + 3))?;
bcx.ins().brif(falsy, target, &[], fallthrough, &[]);
terminated = true;
ip += 3;
}
Opcode::SelfCall => {
let sref = self_ref?; if stack.len() < arity {
return None;
}
let arg_start = stack.len() - arity;
let mut call_args = Vec::with_capacity(arity);
for (v, is_bool) in stack.drain(arg_start..) {
if is_bool {
return None; }
call_args.push(v);
}
let call = bcx.ins().call(sref, &call_args);
let result = bcx.inst_results(call)[0];
stack.push((result, false));
ip += 3;
}
_ => match emit_simple_op(&mut bcx, chunk, code, &mut ip, &mut stack, ¶ms, arity) {
SimpleOp::Handled(_) => {}
_ => return None, },
}
}
if !terminated {
return None;
}
if let (Some(db), Some(dp)) = (deopt_block, deopt_ptr) {
bcx.switch_to_block(db);
let one = bcx.ins().iconst(types::I8, 1);
bcx.ins().store(MemFlags::new(), one, dp, 0);
let zero = bcx.ins().f64const(0.0);
bcx.ins().return_(&[zero]);
}
bcx.seal_all_blocks();
bcx.finalize();
Some(false)
}
fn build_body(
func: &mut cranelift::codegen::ir::Function,
fbctx: &mut FunctionBuilderContext,
chunk: &Chunk,
arity: usize,
) -> Option<bool> {
let mut bcx = FunctionBuilder::new(func, fbctx);
let entry = bcx.create_block();
bcx.append_block_params_for_function_params(entry);
bcx.switch_to_block(entry);
bcx.seal_block(entry);
let params: Vec<ClifValue> = bcx.block_params(entry).to_vec();
let code = &chunk.code;
let mut stack: Vec<(ClifValue, bool)> = Vec::new();
let mut ip = 0usize;
let mut result: Option<bool> = None;
while ip < code.len() {
match emit_simple_op(&mut bcx, chunk, code, &mut ip, &mut stack, ¶ms, arity) {
SimpleOp::Handled(_) => continue,
SimpleOp::Unsupported => return None,
SimpleOp::NotSimple => {}
}
let op = Opcode::from_u8(code[ip])?;
match op {
Opcode::Return => {
let (v, is_bool) = stack.pop()?;
bcx.ins().return_(&[v]);
result = Some(is_bool); break;
}
Opcode::JumpIfFalse => {
let (cond, _) = stack.pop()?;
let mut p = ip + 1;
let off = read_u16(code, &mut p)? as i16 as isize; let else_target = (p as isize + off).max(0) as usize;
let base = stack.len();
let mut tip = p;
loop {
match emit_simple_op(&mut bcx, chunk, code, &mut tip, &mut stack, ¶ms, arity)
{
SimpleOp::Handled(_) => continue,
SimpleOp::Unsupported => return None,
SimpleOp::NotSimple => break,
}
}
if Opcode::from_u8(*code.get(tip)?)? != Opcode::Jump {
return None; }
let mut jp = tip + 1;
let joff = read_u16(code, &mut jp)? as i16 as isize;
let merge_target = (jp as isize + joff).max(0) as usize;
if else_target != jp || stack.len() != base + 1 {
return None;
}
let (then_v, then_b) = stack.pop()?;
let mut eip = jp;
while eip < merge_target {
match emit_simple_op(&mut bcx, chunk, code, &mut eip, &mut stack, ¶ms, arity)
{
SimpleOp::Handled(_) => continue,
_ => return None, }
}
if eip != merge_target || stack.len() != base + 1 {
return None;
}
let (else_v, else_b) = stack.pop()?;
if then_b != else_b {
return None;
}
let falsy = falsy_flag(&mut bcx, cond);
let sel = bcx.ins().select(falsy, else_v, then_v);
stack.push((sel, then_b));
ip = merge_target;
}
_ => return None,
}
}
bcx.finalize();
result
}