use std::cell::Cell;
use boa_jit::NumericLeafFn;
use boa_jit::ir::{BinOp, CmpOp, Insn, MathFn, Op, Rel, Unary, When};
use crate::{
Context, JsValue,
builtins::function::OrdinaryFunction,
vm::{CodeBlock, opcode::Instruction},
};
const THRESHOLD: u32 = 1000;
#[derive(Debug, Default)]
pub(crate) struct JitSlot {
calls: Cell<u32>,
state: Cell<JitState>,
}
impl Clone for JitSlot {
fn clone(&self) -> Self {
Self::default()
}
}
#[derive(Debug, Default, Clone, Copy)]
enum JitState {
#[default]
Cold,
Failed,
Compiled(NumericLeafFn, bool),
}
pub(crate) fn try_call(
function: &OrdinaryFunction,
argument_count: usize,
context: &mut Context,
) -> bool {
let code = &*function.code;
let (compiled, uses_math) = match code.jit.state.get() {
JitState::Compiled(compiled, uses_math) => (compiled, uses_math),
JitState::Failed => return false,
JitState::Cold => {
let calls = code.jit.calls.get().saturating_add(1);
code.jit.calls.set(calls);
if calls < THRESHOLD || !code.is_ordinary() {
return false;
}
code.jit.state.set(JitState::Failed);
let Some((insns, uses_math)) = translate(code) else {
return false;
};
let Some(compiled) =
boa_jit::compile_numeric_leaf(insns, code.register_count, code.parameter_length)
else {
return false;
};
code.jit.state.set(JitState::Compiled(compiled, uses_math));
(compiled, uses_math)
}
};
if uses_math {
let realm = function.realm();
let global = realm.environment();
let static_global = function
.environments
.current_declarative_ref(global)
.is_some_and(|env| !env.with() && !env.poisoned());
if !realm.math_pristine() || !static_global {
return false;
}
}
if argument_count != code.parameter_length as usize {
return false;
}
let mut numbers = [0.0f64; 8];
let mut many = Vec::new();
let args: &mut [f64] = if argument_count <= numbers.len() {
&mut numbers[..argument_count]
} else {
many.resize(argument_count, 0.0);
&mut many
};
if !context
.vm
.stack
.calling_convention_numbers(argument_count, args)
{
return false;
}
let result = unsafe { compiled(args.as_ptr()) };
context
.vm
.stack
.calling_convention_replace_call(argument_count, JsValue::from(result));
true
}
fn translate(code: &CodeBlock) -> Option<(Vec<Insn>, bool)> {
let bytes = &code.bytecode.bytes;
let mut insns = Vec::new();
let mut uses_math = false;
let mut pc = 0;
while pc < bytes.len() {
let (instruction, next) = code.bytecode.next_instruction(pc);
let op = translate_op(&instruction)?;
uses_math |= matches!(op, Op::Math { .. });
insns.push(Insn {
pc: u32::try_from(pc).ok()?,
op,
});
pc = next;
}
Some((insns, uses_math))
}
fn translate_op(instruction: &Instruction) -> Option<Op> {
let r = |operand| u32::from(operand);
let bin = |op, dst, lhs, rhs| Op::Bin {
op,
dst: r(dst),
lhs: r(lhs),
rhs: r(rhs),
};
let cmp = |op, dst, lhs, rhs| Op::Cmp {
op,
dst: r(dst),
lhs: r(lhs),
rhs: r(rhs),
};
let rel = |rel, address, lhs, rhs| Op::JumpRel {
rel,
target: u32::from(address),
lhs: r(lhs),
rhs: r(rhs),
};
let constant = |dst, value| Op::Const { dst: r(dst), value };
Some(match *instruction {
Instruction::StoreZero { dst } => constant(dst, 0.0),
Instruction::StoreOne { dst } => constant(dst, 1.0),
Instruction::StoreInt8 { dst, value } => constant(dst, f64::from(value)),
Instruction::StoreInt16 { dst, value } => constant(dst, f64::from(value)),
Instruction::StoreInt32 { dst, value } => constant(dst, f64::from(value)),
Instruction::StoreFloat { dst, value } => constant(dst, f64::from(value)),
Instruction::StoreDouble { dst, value } => constant(dst, value),
Instruction::StoreNan { dst } => constant(dst, f64::NAN),
Instruction::StorePositiveInfinity { dst } => constant(dst, f64::INFINITY),
Instruction::StoreNegativeInfinity { dst } => constant(dst, f64::NEG_INFINITY),
Instruction::Add { dst, lhs, rhs } => bin(BinOp::Add, dst, lhs, rhs),
Instruction::Sub { dst, lhs, rhs } => bin(BinOp::Sub, dst, lhs, rhs),
Instruction::Mul { dst, lhs, rhs } => bin(BinOp::Mul, dst, lhs, rhs),
Instruction::Div { dst, lhs, rhs } => bin(BinOp::Div, dst, lhs, rhs),
Instruction::Mod { dst, lhs, rhs } => bin(BinOp::Mod, dst, lhs, rhs),
Instruction::BitAnd { dst, lhs, rhs } => bin(BinOp::BitAnd, dst, lhs, rhs),
Instruction::BitOr { dst, lhs, rhs } => bin(BinOp::BitOr, dst, lhs, rhs),
Instruction::BitXor { dst, lhs, rhs } => bin(BinOp::BitXor, dst, lhs, rhs),
Instruction::ShiftLeft { dst, lhs, rhs } => bin(BinOp::Shl, dst, lhs, rhs),
Instruction::ShiftRight { dst, lhs, rhs } => bin(BinOp::Shr, dst, lhs, rhs),
Instruction::UnsignedShiftRight { dst, lhs, rhs } => bin(BinOp::Ushr, dst, lhs, rhs),
Instruction::Eq { dst, lhs, rhs } => cmp(CmpOp::Eq, dst, lhs, rhs),
Instruction::NotEq { dst, lhs, rhs } => cmp(CmpOp::Ne, dst, lhs, rhs),
Instruction::StrictEq { dst, lhs, rhs } => cmp(CmpOp::StrictEq, dst, lhs, rhs),
Instruction::StrictNotEq { dst, lhs, rhs } => cmp(CmpOp::StrictNe, dst, lhs, rhs),
Instruction::LessThan { dst, lhs, rhs } => cmp(CmpOp::Lt, dst, lhs, rhs),
Instruction::LessThanOrEq { dst, lhs, rhs } => cmp(CmpOp::Le, dst, lhs, rhs),
Instruction::GreaterThan { dst, lhs, rhs } => cmp(CmpOp::Gt, dst, lhs, rhs),
Instruction::GreaterThanOrEq { dst, lhs, rhs } => cmp(CmpOp::Ge, dst, lhs, rhs),
Instruction::Pos { value } => Op::Unary {
op: Unary::Pos,
reg: r(value),
},
Instruction::Neg { value } => Op::Unary {
op: Unary::Neg,
reg: r(value),
},
Instruction::BitNot { value } => Op::BitNot { reg: r(value) },
Instruction::Inc { dst, src } => Op::Inc {
dst: r(dst),
src: r(src),
dec: false,
},
Instruction::Dec { dst, src } => Op::Inc {
dst: r(dst),
src: r(src),
dec: true,
},
Instruction::GetArgument { index, dst } => Op::GetArg {
index: u32::from(index),
dst: r(dst),
},
Instruction::Move { dst, src } => Op::Move {
dst: r(dst),
src: r(src),
},
Instruction::Jump { address } => Op::Jump {
target: u32::from(address),
},
Instruction::JumpIfTrue { address, value } => Op::JumpIf {
when: When::True,
target: u32::from(address),
value: r(value),
},
Instruction::JumpIfFalse { address, value } => Op::JumpIf {
when: When::False,
target: u32::from(address),
value: r(value),
},
Instruction::JumpIfNotLessThan { address, lhs, rhs } => rel(Rel::Lt, address, lhs, rhs),
Instruction::JumpIfNotLessThanOrEqual { address, lhs, rhs } => {
rel(Rel::Le, address, lhs, rhs)
}
Instruction::JumpIfNotGreaterThan { address, lhs, rhs } => rel(Rel::Gt, address, lhs, rhs),
Instruction::JumpIfNotGreaterThanOrEqual { address, lhs, rhs } => {
rel(Rel::Ge, address, lhs, rhs)
}
Instruction::JumpIfNotEqual { address, lhs, rhs } => Op::JumpNe {
target: u32::from(address),
lhs: r(lhs),
rhs: r(rhs),
},
Instruction::SetAccumulator { src } => Op::SetAcc { src: r(src) },
Instruction::CheckReturn => Op::CheckReturn,
Instruction::Return => Op::Return,
Instruction::IncrementLoopIteration => Op::LoopIter,
Instruction::PrepareMath { .. } => Op::Nop,
Instruction::CallMath { operands } => {
let name = crate::builtins::math::Math::pure_name(operands.function);
let function = match name.to_std_string_escaped().as_str() {
"max" => MathFn::Max,
"min" => MathFn::Min,
"floor" => MathFn::Floor,
"ceil" => MathFn::Ceil,
"abs" => MathFn::Abs,
"sqrt" => MathFn::Sqrt,
"trunc" => MathFn::Trunc,
_ => return None,
};
Op::Math {
f: function,
dst: r(operands.dst),
args: operands.args.map(r),
argc: operands.argc,
}
}
_ => return None,
})
}