use std::collections::HashMap;
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::types::{F64, I8, I32, I64};
use cranelift_codegen::ir::{AbiParam, BlockArg, Function, InstBuilder, MemFlags, Signature};
use cranelift_codegen::ir::{Block, Value};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{FuncId, Linkage, Module};
use crate::bytecode::feedback::FeedbackVector;
use crate::compiler::baseline::compiler::JIT_DEOPT;
use crate::compiler::maglev::ir::{ControlNode, MaglevGraph, NodeId, ValueNode};
use crate::error::{StatorError, StatorResult};
pub mod specialize;
pub mod deopt;
const REGISTER_SLOT_BYTES: i32 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JsType {
Tagged,
Int32,
Uint32,
Float64,
Bool,
}
impl JsType {
pub fn to_cranelift(self) -> cranelift_codegen::ir::Type {
match self {
JsType::Tagged => I64,
JsType::Int32 | JsType::Uint32 => I32,
JsType::Float64 => F64,
JsType::Bool => I8,
}
}
}
#[derive(Debug, Clone)]
pub struct DeoptPoint {
pub index: u32,
pub bytecode_offset: u32,
pub reason: &'static str,
}
pub struct TurbofanCompiledCode {
module: JITModule,
func_id: FuncId,
pub register_file_slots: usize,
pub deopt_points: Vec<DeoptPoint>,
pub code_size: usize,
}
unsafe impl Send for TurbofanCompiledCode {}
impl std::fmt::Debug for TurbofanCompiledCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TurbofanCompiledCode")
.field("register_file_slots", &self.register_file_slots)
.field("deopt_points", &self.deopt_points.len())
.field("code_size", &self.code_size)
.finish_non_exhaustive()
}
}
impl TurbofanCompiledCode {
pub unsafe fn execute(&self, args: &[i64]) -> StatorResult<i64> {
use smallvec::{SmallVec, smallvec};
let fn_ptr = self.module.get_finalized_function(self.func_id);
let total = self.register_file_slots + 1;
let mut regs: SmallVec<[i64; 32]> = smallvec![0i64; total];
for (i, &v) in args.iter().enumerate().take(self.register_file_slots) {
regs[i] = v;
}
let result = unsafe {
let f: extern "C" fn(*mut i64) -> i64 = std::mem::transmute(fn_ptr);
f(regs.as_mut_ptr())
};
if result == JIT_DEOPT {
Err(StatorError::Internal("turbofan deopt".into()))
} else {
Ok(result)
}
}
pub unsafe fn execute_with_deopt(
&self,
bytecode_array: crate::bytecode::bytecode_array::BytecodeArray,
feedback: &mut FeedbackVector,
global_env: std::rc::Rc<std::cell::RefCell<crate::interpreter::GlobalEnv>>,
) -> StatorResult<crate::objects::value::JsValue> {
use smallvec::{SmallVec, smallvec};
let fn_ptr = self.module.get_finalized_function(self.func_id);
let total = self.register_file_slots + 1;
let mut regs: SmallVec<[i64; 32]> = smallvec![0i64; total];
let result = unsafe {
let f: extern "C" fn(*mut i64) -> i64 = std::mem::transmute(fn_ptr);
f(regs.as_mut_ptr())
};
if result == JIT_DEOPT {
let deopt_index = regs[self.register_file_slots] as u32;
let frame = deopt::TurbofanFrameState {
registers: regs[..self.register_file_slots].to_vec(),
deopt_index,
};
deopt::deoptimize_turbofan(
bytecode_array,
feedback,
&self.deopt_points,
frame,
global_env,
)
} else {
use crate::compiler::baseline::compiler::jit_to_jsvalue;
jit_to_jsvalue(result).ok_or_else(|| {
StatorError::Internal(format!("turbofan: unrecognised return value {result}"))
})
}
}
}
pub fn compile(graph: &MaglevGraph, param_count: u32) -> StatorResult<TurbofanCompiledCode> {
TurbofanCodegen::new(param_count)?.compile(graph)
}
pub fn compile_with_feedback(
graph: &MaglevGraph,
param_count: u32,
fv: Option<&FeedbackVector>,
) -> StatorResult<TurbofanCompiledCode> {
if fv.is_some() {
let mut specialised = graph.clone();
specialize::run_pre_clif_passes(&mut specialised, fv);
TurbofanCodegen::new(param_count)?.compile(&specialised)
} else {
TurbofanCodegen::new(param_count)?.compile(graph)
}
}
struct TurbofanCodegen {
module: JITModule,
param_count: u32,
}
impl TurbofanCodegen {
fn new(param_count: u32) -> StatorResult<Self> {
let module = JITModule::new(
JITBuilder::with_flags(
&[("opt_level", "speed")],
cranelift_module::default_libcall_names(),
)
.map_err(|e| StatorError::Internal(format!("cranelift JITBuilder: {e}")))?,
);
Ok(Self {
module,
param_count,
})
}
fn compile(mut self, graph: &MaglevGraph) -> StatorResult<TurbofanCompiledCode> {
let pointer_type = self.module.target_config().pointer_type();
let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(call_conv);
sig.params.push(AbiParam::new(pointer_type));
sig.returns.push(AbiParam::new(I64));
let func_id = self
.module
.declare_function("turbofan_fn", Linkage::Local, &sig)
.map_err(|e| StatorError::Internal(format!("declare_function: {e}")))?;
let mut ctx = self.module.make_context();
ctx.func = Function::with_name_signature(
cranelift_codegen::ir::UserFuncName::user(0, func_id.as_u32()),
sig,
);
let mut fb_ctx = FunctionBuilderContext::new();
let mut deopt_points: Vec<DeoptPoint> = Vec::new();
{
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fb_ctx);
let lowering = Lowering::new(
&mut builder,
graph,
self.param_count,
&mut deopt_points,
pointer_type,
);
lowering.lower()?;
builder.finalize();
}
self.module
.define_function(func_id, &mut ctx)
.map_err(|e| StatorError::Internal(format!("define_function: {e}")))?;
let code_size = ctx
.compiled_code()
.map(|c| c.code_buffer().len())
.unwrap_or(0);
self.module.clear_context(&mut ctx);
self.module
.finalize_definitions()
.map_err(|e| StatorError::Internal(format!("finalize_definitions: {e}")))?;
Ok(TurbofanCompiledCode {
module: self.module,
func_id,
register_file_slots: self.param_count as usize,
deopt_points,
code_size,
})
}
}
struct Lowering<'a, 'b> {
builder: &'b mut FunctionBuilder<'a>,
graph: &'a MaglevGraph,
deopt_points: &'b mut Vec<DeoptPoint>,
value_map: HashMap<NodeId, Value>,
block_map: HashMap<u32, Block>,
regs_var: cranelift_frontend::Variable,
deopt_block: Option<Block>,
deopt_index: u32,
register_file_slots: u32,
}
impl<'a, 'b> Lowering<'a, 'b> {
fn new(
builder: &'b mut FunctionBuilder<'a>,
graph: &'a MaglevGraph,
param_count: u32,
deopt_points: &'b mut Vec<DeoptPoint>,
pointer_type: cranelift_codegen::ir::Type,
) -> Self {
let regs_var = builder.declare_var(pointer_type);
Self {
builder,
graph,
deopt_points,
value_map: HashMap::new(),
block_map: HashMap::new(),
regs_var,
deopt_block: None,
deopt_index: 0,
register_file_slots: param_count,
}
}
fn lower(mut self) -> StatorResult<()> {
let graph = self.graph;
for block in graph.blocks() {
let clif_block = self.builder.create_block();
self.block_map.insert(block.id, clif_block);
}
let entry_clif = self.block_map[&0];
self.builder.switch_to_block(entry_clif);
self.builder
.append_block_params_for_function_params(entry_clif);
let regs_ptr = self.builder.block_params(entry_clif)[0];
self.builder.def_var(self.regs_var, regs_ptr);
for block_idx in 0..graph.blocks().len() {
let maglev_block = &graph.blocks()[block_idx];
let clif_block = self.block_map[&maglev_block.id];
if block_idx > 0 {
self.builder.switch_to_block(clif_block);
}
for (node_id, node) in &maglev_block.nodes {
self.lower_value_node(*node_id, node)?;
}
if let Some(ctrl) = &maglev_block.control {
self.lower_control_node(ctrl)?;
}
self.builder.seal_block(clif_block);
}
if let Some(deopt_block) = self.deopt_block {
self.builder.switch_to_block(deopt_block);
self.builder.seal_block(deopt_block);
let idx_param = self.builder.block_params(deopt_block)[0];
let regs = self.builder.use_var(self.regs_var);
let slot_offset = (self.register_file_slots as i32) * REGISTER_SLOT_BYTES;
let idx_i64 = self.builder.ins().sextend(I64, idx_param);
self.builder
.ins()
.store(MemFlags::new(), idx_i64, regs, slot_offset);
let sentinel = self.builder.ins().iconst(I64, JIT_DEOPT);
self.builder.ins().return_(&[sentinel]);
}
Ok(())
}
fn lower_value_node(&mut self, id: NodeId, node: &ValueNode) -> StatorResult<()> {
let val = match node {
ValueNode::SmiConstant { value } => {
let tagged = (*value as i64) << 1;
self.builder.ins().iconst(I64, tagged)
}
ValueNode::Int32Constant { value } => self.builder.ins().iconst(I32, *value as i64),
ValueNode::Uint32Constant { value } => self.builder.ins().iconst(I32, *value as i64),
ValueNode::Float64Constant { value } => self.builder.ins().f64const(*value),
ValueNode::TrueConstant => self.builder.ins().iconst(I64, 1),
ValueNode::FalseConstant => self.builder.ins().iconst(I64, 0),
ValueNode::NullConstant => self.builder.ins().iconst(I64, 0),
ValueNode::UndefinedConstant => self.builder.ins().iconst(I64, 0),
ValueNode::Parameter { index } => {
let regs = self.builder.use_var(self.regs_var);
let byte_offset = (*index as i32) * REGISTER_SLOT_BYTES;
self.builder
.ins()
.load(I64, MemFlags::new(), regs, byte_offset)
}
ValueNode::Int32Add { left, right } => {
let l = self.use_i32(*left)?;
let r = self.use_i32(*right)?;
self.builder.ins().iadd(l, r)
}
ValueNode::Int32Subtract { left, right } => {
let l = self.use_i32(*left)?;
let r = self.use_i32(*right)?;
self.builder.ins().isub(l, r)
}
ValueNode::Int32Multiply { left, right } => {
let l = self.use_i32(*left)?;
let r = self.use_i32(*right)?;
self.builder.ins().imul(l, r)
}
ValueNode::Int32Divide { left, right } => {
let l = self.use_i32(*left)?;
let r = self.use_i32(*right)?;
self.builder.ins().sdiv(l, r)
}
ValueNode::Int32Modulus { left, right } => {
let l = self.use_i32(*left)?;
let r = self.use_i32(*right)?;
self.builder.ins().srem(l, r)
}
ValueNode::Int32Negate { value } => {
let v = self.use_i32(*value)?;
self.builder.ins().ineg(v)
}
ValueNode::Int32Increment { value } => {
let v = self.use_i32(*value)?;
let one = self.builder.ins().iconst(I32, 1);
self.builder.ins().iadd(v, one)
}
ValueNode::Int32Decrement { value } => {
let v = self.use_i32(*value)?;
let one = self.builder.ins().iconst(I32, 1);
self.builder.ins().isub(v, one)
}
ValueNode::Float64Add { left, right } => {
let l = self.use_f64(*left)?;
let r = self.use_f64(*right)?;
self.builder.ins().fadd(l, r)
}
ValueNode::Float64Subtract { left, right } => {
let l = self.use_f64(*left)?;
let r = self.use_f64(*right)?;
self.builder.ins().fsub(l, r)
}
ValueNode::Float64Multiply { left, right } => {
let l = self.use_f64(*left)?;
let r = self.use_f64(*right)?;
self.builder.ins().fmul(l, r)
}
ValueNode::Float64Divide { left, right } => {
let l = self.use_f64(*left)?;
let r = self.use_f64(*right)?;
self.builder.ins().fdiv(l, r)
}
ValueNode::Float64Negate { value } => {
let v = self.use_f64(*value)?;
self.builder.ins().fneg(v)
}
ValueNode::CheckedSmiAdd { left, right } => {
self.lower_checked_smi_add(*left, *right)?
}
ValueNode::CheckedSmiSubtract { left, right } => {
self.lower_checked_smi_sub(*left, *right)?
}
ValueNode::CheckedSmiMultiply { left, right } => {
self.lower_checked_smi_mul(*left, *right)?
}
ValueNode::ChangeInt32ToFloat64 { input } => {
let v = self.use_i32(*input)?;
self.builder.ins().fcvt_from_sint(F64, v)
}
ValueNode::ChangeFloat64ToInt32 { input } => {
let v = self.use_f64(*input)?;
self.builder.ins().fcvt_to_sint_sat(I32, v)
}
ValueNode::ChangeInt32ToTagged { input } => {
let v = self.use_i32(*input)?;
let shifted = self.builder.ins().ishl_imm(v, 1);
self.builder.ins().sextend(I64, shifted)
}
ValueNode::ChangeTaggedToInt32 { input } => {
let v = self.use_i64(*input)?;
let shifted = self.builder.ins().sshr_imm(v, 1);
self.builder.ins().ireduce(I32, shifted)
}
ValueNode::ChangeFloat64ToTagged { input } => {
let v = self.use_f64(*input)?;
self.builder.ins().bitcast(I64, MemFlags::new(), v)
}
ValueNode::ChangeTaggedToFloat64 { input } => {
let v = self.use_i64(*input)?;
self.builder.ins().bitcast(F64, MemFlags::new(), v)
}
ValueNode::Phi { .. } => {
let current_block = self
.builder
.current_block()
.ok_or_else(|| StatorError::Internal("phi outside block".into()))?;
self.builder.append_block_param(current_block, I64)
}
_ => {
self.builder.ins().iconst(I64, JIT_DEOPT)
}
};
self.value_map.insert(id, val);
Ok(())
}
fn lower_control_node(&mut self, ctrl: &ControlNode) -> StatorResult<()> {
match ctrl {
ControlNode::Return { value } => {
let v = self.use_value(*value)?;
let ret_val = self.coerce_to_i64(v)?;
self.builder.ins().return_(&[ret_val]);
}
ControlNode::Jump { target } => {
let target_block = self.block_for(*target)?;
self.builder.ins().jump(target_block, &[]);
}
ControlNode::Branch {
condition,
if_true,
if_false,
} => {
let cond_val = self.use_value(*condition)?;
let cond8 = self.coerce_to_bool(cond_val)?;
let true_block = self.block_for(*if_true)?;
let false_block = self.block_for(*if_false)?;
self.builder
.ins()
.brif(cond8, true_block, &[], false_block, &[]);
}
ControlNode::Deoptimize {
bytecode_offset,
reason: _,
} => {
let site_index = self.deopt_index;
self.deopt_points.push(DeoptPoint {
index: site_index,
bytecode_offset: *bytecode_offset,
reason: "Deoptimize",
});
self.deopt_index += 1;
let deopt_block = self.get_or_create_deopt_block();
let idx_val = self.builder.ins().iconst(I32, site_index as i64);
self.builder
.ins()
.jump(deopt_block, &[BlockArg::from(idx_val)]);
}
}
Ok(())
}
fn block_for(&self, idx: u32) -> StatorResult<Block> {
self.block_map
.get(&idx)
.copied()
.ok_or_else(|| StatorError::Internal(format!("unknown block index {idx}")))
}
fn use_value(&self, id: NodeId) -> StatorResult<Value> {
self.value_map
.get(&id)
.copied()
.ok_or_else(|| StatorError::Internal(format!("undefined node {:?}", id)))
}
fn use_i32(&mut self, id: NodeId) -> StatorResult<Value> {
let v = self.use_value(id)?;
let ty = self.builder.func.dfg.value_type(v);
if ty == I64 {
Ok(self.builder.ins().ireduce(I32, v))
} else {
Ok(v)
}
}
fn use_i64(&mut self, id: NodeId) -> StatorResult<Value> {
let v = self.use_value(id)?;
let ty = self.builder.func.dfg.value_type(v);
if ty == I32 {
Ok(self.builder.ins().sextend(I64, v))
} else {
Ok(v)
}
}
fn use_f64(&mut self, id: NodeId) -> StatorResult<Value> {
let v = self.use_value(id)?;
let ty = self.builder.func.dfg.value_type(v);
if ty == I64 {
Ok(self.builder.ins().bitcast(F64, MemFlags::new(), v))
} else {
Ok(v)
}
}
fn coerce_to_i64(&mut self, v: Value) -> StatorResult<Value> {
let ty = self.builder.func.dfg.value_type(v);
if ty == I32 {
Ok(self.builder.ins().sextend(I64, v))
} else if ty == I8 {
Ok(self.builder.ins().uextend(I64, v))
} else if ty == F64 {
Ok(self.builder.ins().bitcast(I64, MemFlags::new(), v))
} else {
Ok(v)
}
}
fn coerce_to_bool(&mut self, v: Value) -> StatorResult<Value> {
let ty = self.builder.func.dfg.value_type(v);
if ty == I8 {
Ok(v)
} else if ty == I32 || ty == I64 {
let zero = self.builder.ins().iconst(ty, 0);
Ok(self.builder.ins().icmp(IntCC::NotEqual, v, zero))
} else if ty == F64 {
let zero = self.builder.ins().f64const(0.0);
Ok(self.builder.ins().fcmp(FloatCC::NotEqual, v, zero))
} else {
Ok(v)
}
}
fn get_or_create_deopt_block(&mut self) -> Block {
if let Some(b) = self.deopt_block {
return b;
}
let b = self.builder.create_block();
self.builder.append_block_param(b, I32);
self.deopt_block = Some(b);
b
}
fn lower_checked_smi_add(&mut self, left: NodeId, right: NodeId) -> StatorResult<Value> {
let (l_i32, r_i32) = self.untag_smi_pair(left, right)?;
let (result_i32, overflow) = self.builder.ins().sadd_overflow(l_i32, r_i32);
self.emit_overflow_deopt(overflow, "CheckedSmiAdd overflow")?;
Ok(self.retag_smi(result_i32))
}
fn lower_checked_smi_sub(&mut self, left: NodeId, right: NodeId) -> StatorResult<Value> {
let (l_i32, r_i32) = self.untag_smi_pair(left, right)?;
let (result_i32, overflow) = self.builder.ins().ssub_overflow(l_i32, r_i32);
self.emit_overflow_deopt(overflow, "CheckedSmiSubtract overflow")?;
Ok(self.retag_smi(result_i32))
}
fn lower_checked_smi_mul(&mut self, left: NodeId, right: NodeId) -> StatorResult<Value> {
let (l_i32, r_i32) = self.untag_smi_pair(left, right)?;
let (result_i32, overflow) = self.builder.ins().smul_overflow(l_i32, r_i32);
self.emit_overflow_deopt(overflow, "CheckedSmiMultiply overflow")?;
Ok(self.retag_smi(result_i32))
}
fn untag_smi_pair(&mut self, left: NodeId, right: NodeId) -> StatorResult<(Value, Value)> {
let l_i64 = self.use_i64(left)?;
let r_i64 = self.use_i64(right)?;
let l_i32 = {
let shifted = self.builder.ins().sshr_imm(l_i64, 1);
self.builder.ins().ireduce(I32, shifted)
};
let r_i32 = {
let shifted = self.builder.ins().sshr_imm(r_i64, 1);
self.builder.ins().ireduce(I32, shifted)
};
Ok((l_i32, r_i32))
}
fn emit_overflow_deopt(&mut self, overflow: Value, reason: &'static str) -> StatorResult<()> {
let ok_block = self.builder.create_block();
let deopt_block = self.get_or_create_deopt_block();
let site_index = self.deopt_index;
let idx_val = self.builder.ins().iconst(I32, site_index as i64);
self.builder.ins().brif(
overflow,
deopt_block,
&[BlockArg::from(idx_val)],
ok_block,
&[],
);
self.deopt_points.push(DeoptPoint {
index: site_index,
bytecode_offset: 0,
reason,
});
self.deopt_index += 1;
self.builder.switch_to_block(ok_block);
self.builder.seal_block(ok_block);
Ok(())
}
fn retag_smi(&mut self, v: Value) -> Value {
let shifted = self.builder.ins().ishl_imm(v, 1);
self.builder.ins().sextend(I64, shifted)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compiler::maglev::ir::{BasicBlock, ControlNode, MaglevGraph, ValueNode};
fn run(graph: &MaglevGraph, param_count: u32, args: &[i64]) -> i64 {
let compiled = compile(graph, param_count).expect("turbofan compile failed");
unsafe { compiled.execute(args) }.expect("execute failed")
}
#[test]
fn test_return_int32_constant() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let c = block.push_value(ValueNode::Int32Constant { value: 42 });
block.set_control(ControlNode::Return { value: c });
graph.add_block(block);
assert_eq!(run(&graph, 0, &[]), 42);
}
#[test]
fn test_return_smi_constant() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let c = block.push_value(ValueNode::SmiConstant { value: 7 });
block.set_control(ControlNode::Return { value: c });
graph.add_block(block);
assert_eq!(run(&graph, 0, &[]), 14); }
#[test]
fn test_return_float64_constant() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let c = block.push_value(ValueNode::Float64Constant { value: 3.14 });
let tagged = block.push_value(ValueNode::ChangeFloat64ToTagged { input: c });
block.set_control(ControlNode::Return { value: tagged });
graph.add_block(block);
let raw = run(&graph, 0, &[]);
let f: f64 = f64::from_bits(raw as u64);
assert!((f - 3.14).abs() < 1e-10);
}
#[test]
fn test_int32_add() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::Int32Constant { value: 10 });
let b = block.push_value(ValueNode::Int32Constant { value: 32 });
let sum = block.push_value(ValueNode::Int32Add { left: a, right: b });
block.set_control(ControlNode::Return { value: sum });
graph.add_block(block);
assert_eq!(run(&graph, 0, &[]), 42);
}
#[test]
fn test_int32_subtract() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::Int32Constant { value: 100 });
let b = block.push_value(ValueNode::Int32Constant { value: 58 });
let diff = block.push_value(ValueNode::Int32Subtract { left: a, right: b });
block.set_control(ControlNode::Return { value: diff });
graph.add_block(block);
assert_eq!(run(&graph, 0, &[]), 42);
}
#[test]
fn test_int32_multiply() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::Int32Constant { value: 6 });
let b = block.push_value(ValueNode::Int32Constant { value: 7 });
let prod = block.push_value(ValueNode::Int32Multiply { left: a, right: b });
block.set_control(ControlNode::Return { value: prod });
graph.add_block(block);
assert_eq!(run(&graph, 0, &[]), 42);
}
#[test]
fn test_return_parameter() {
let mut graph = MaglevGraph::new(1);
let mut block = BasicBlock::new(0);
let p = block.push_value(ValueNode::Parameter { index: 0 });
block.set_control(ControlNode::Return { value: p });
graph.add_block(block);
assert_eq!(run(&graph, 1, &[99]), 99);
}
#[test]
fn test_add_two_parameters() {
let mut graph = MaglevGraph::new(2);
let mut block = BasicBlock::new(0);
let p0 = block.push_value(ValueNode::Parameter { index: 0 });
let p1 = block.push_value(ValueNode::Parameter { index: 1 });
let sum = block.push_value(ValueNode::Int32Add {
left: p0,
right: p1,
});
block.set_control(ControlNode::Return { value: sum });
graph.add_block(block);
assert_eq!(run(&graph, 2, &[20, 22]) as i32, 42);
}
#[test]
fn test_float64_add() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::Float64Constant { value: 1.5 });
let b = block.push_value(ValueNode::Float64Constant { value: 2.5 });
let sum = block.push_value(ValueNode::Float64Add { left: a, right: b });
let tagged = block.push_value(ValueNode::ChangeFloat64ToTagged { input: sum });
block.set_control(ControlNode::Return { value: tagged });
graph.add_block(block);
let raw = run(&graph, 0, &[]);
let f: f64 = f64::from_bits(raw as u64);
assert!((f - 4.0).abs() < 1e-10);
}
#[test]
fn test_int32_to_float64() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let c = block.push_value(ValueNode::Int32Constant { value: 10 });
let f = block.push_value(ValueNode::ChangeInt32ToFloat64 { input: c });
let tagged = block.push_value(ValueNode::ChangeFloat64ToTagged { input: f });
block.set_control(ControlNode::Return { value: tagged });
graph.add_block(block);
let raw = run(&graph, 0, &[]);
let fv: f64 = f64::from_bits(raw as u64);
assert!((fv - 10.0).abs() < 1e-10);
}
#[test]
fn test_checked_smi_add_no_overflow() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::SmiConstant { value: 3 });
let b = block.push_value(ValueNode::SmiConstant { value: 4 });
let sum = block.push_value(ValueNode::CheckedSmiAdd { left: a, right: b });
block.set_control(ControlNode::Return { value: sum });
graph.add_block(block);
assert_eq!(run(&graph, 0, &[]), 14); }
#[test]
fn test_checked_smi_add_overflow_deopt() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let a = block.push_value(ValueNode::SmiConstant { value: i32::MAX });
let b = block.push_value(ValueNode::SmiConstant { value: 1 });
let sum = block.push_value(ValueNode::CheckedSmiAdd { left: a, right: b });
block.set_control(ControlNode::Return { value: sum });
graph.add_block(block);
let compiled = compile(&graph, 0).expect("compile ok");
let result = unsafe { compiled.execute(&[]) };
assert!(result.is_err(), "expected deopt error, got: {result:?}");
}
#[test]
fn test_deoptimize_node_returns_error() {
let mut graph = MaglevGraph::new(0);
let mut block = BasicBlock::new(0);
let _ = block.push_value(ValueNode::UndefinedConstant);
block.set_control(ControlNode::Deoptimize {
bytecode_offset: 4,
reason: 1,
});
graph.add_block(block);
let compiled = compile(&graph, 0).expect("compile ok");
assert_eq!(compiled.deopt_points.len(), 1);
assert_eq!(compiled.deopt_points[0].bytecode_offset, 4);
let result = unsafe { compiled.execute(&[]) };
assert!(result.is_err());
}
#[test]
fn test_js_type_to_cranelift() {
assert_eq!(JsType::Tagged.to_cranelift(), I64);
assert_eq!(JsType::Int32.to_cranelift(), I32);
assert_eq!(JsType::Uint32.to_cranelift(), I32);
assert_eq!(JsType::Float64.to_cranelift(), F64);
assert_eq!(JsType::Bool.to_cranelift(), I8);
}
}