use std::cell::RefCell;
use std::rc::Rc;
use crate::bytecode::bytecode_array::BytecodeArray;
use crate::bytecode::feedback::FeedbackVector;
use crate::compiler::baseline::compiler::jit_to_jsvalue;
use crate::compiler::maglev::deopt::{DeoptInfo, DeoptReason, FrameState, deoptimize};
use crate::compiler::turbofan::DeoptPoint;
use crate::error::{StatorError, StatorResult};
use crate::interpreter::GlobalEnv;
use crate::objects::value::JsValue;
#[derive(Debug, Clone, PartialEq)]
pub enum ValueRecovery {
InRegister {
index: u32,
},
Constant {
value: JsValue,
},
Materialized {
map: u32,
fields: Vec<(u32, Box<ValueRecovery>)>,
},
}
#[derive(Debug, Clone)]
pub struct DeoptEntry {
pub bytecode_offset: u32,
pub register_map: Vec<ValueRecovery>,
pub reason: DeoptReason,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeoptKind {
Eager,
Lazy,
}
#[derive(Debug, Clone)]
pub struct TurbofanFrameState {
pub registers: Vec<i64>,
pub deopt_index: u32,
}
pub fn deoptimize_turbofan(
bytecode_array: BytecodeArray,
feedback: &mut FeedbackVector,
deopt_points: &[DeoptPoint],
frame: TurbofanFrameState,
global_env: Rc<RefCell<GlobalEnv>>,
) -> StatorResult<JsValue> {
let point = deopt_points
.iter()
.find(|p| p.index == frame.deopt_index)
.ok_or_else(|| {
StatorError::Internal(format!(
"turbofan deopt: no deopt point with index {}",
frame.deopt_index
))
})?;
let registers: Vec<JsValue> = frame
.registers
.iter()
.map(|&r| jit_to_jsvalue(r).unwrap_or(JsValue::Undefined))
.collect();
let reason_lower = point.reason.to_ascii_lowercase();
let reason = if reason_lower.contains("overflow") {
DeoptReason::ArithmeticOverflow
} else if reason_lower.contains("typecheck") || reason_lower.contains("type") {
DeoptReason::TypeCheckFailure
} else {
DeoptReason::UnsupportedOperation
};
let info = DeoptInfo {
reason,
bytecode_offset: point.bytecode_offset,
frame_state: FrameState {
registers,
accumulator: JsValue::Undefined,
},
};
deoptimize(bytecode_array, feedback, info, global_env)
}
pub fn deoptimize_with_entry(
bytecode_array: BytecodeArray,
feedback: &mut FeedbackVector,
entry: &DeoptEntry,
raw_regs: &[i64],
global_env: Rc<RefCell<GlobalEnv>>,
) -> StatorResult<JsValue> {
let registers: Vec<JsValue> = entry
.register_map
.iter()
.map(|recovery| recover_value(recovery, raw_regs))
.collect();
let info = DeoptInfo {
reason: entry.reason,
bytecode_offset: entry.bytecode_offset,
frame_state: FrameState {
registers,
accumulator: JsValue::Undefined,
},
};
deoptimize(bytecode_array, feedback, info, global_env)
}
fn recover_value(recovery: &ValueRecovery, raw_regs: &[i64]) -> JsValue {
match recovery {
ValueRecovery::InRegister { index } => {
let idx = *index as usize;
if idx < raw_regs.len() {
jit_to_jsvalue(raw_regs[idx]).unwrap_or(JsValue::Undefined)
} else {
JsValue::Undefined
}
}
ValueRecovery::Constant { value } => value.clone(),
ValueRecovery::Materialized { .. } => {
JsValue::Undefined
}
}
}
pub fn reconstruct_frame(raw_regs: &[i64]) -> FrameState {
let registers: Vec<JsValue> = raw_regs
.iter()
.map(|&r| jit_to_jsvalue(r).unwrap_or(JsValue::Undefined))
.collect();
FrameState {
registers,
accumulator: JsValue::Undefined,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::bytecodes::{Instruction, Opcode, Operand, encode};
use crate::bytecode::feedback::{
FeedbackMetadata, FeedbackSlotKind, FeedbackVector, InlineCacheState,
};
use crate::compiler::maglev::ir::{BasicBlock, ControlNode, MaglevGraph, ValueNode};
use crate::compiler::turbofan::compile;
fn empty_globals() -> Rc<RefCell<GlobalEnv>> {
Rc::new(RefCell::new(GlobalEnv::new()))
}
fn make_lda_return(value: i32) -> BytecodeArray {
let instrs = vec![
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(value)]),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let bytes = encode(&instrs);
BytecodeArray::new(
bytes,
vec![],
0,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
)
}
#[test]
fn test_turbofan_frame_state_construction() {
let fs = TurbofanFrameState {
registers: vec![1, 2, 3],
deopt_index: 0,
};
assert_eq!(fs.registers, vec![1, 2, 3]);
assert_eq!(fs.deopt_index, 0);
}
#[test]
fn test_deoptimize_turbofan_missing_deopt_point() {
let ba = make_lda_return(0);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let frame = TurbofanFrameState {
registers: vec![],
deopt_index: 99, };
let result = deoptimize_turbofan(ba, &mut fv, &[], frame, empty_globals());
assert!(
result.is_err(),
"expected error for missing deopt point, got {result:?}"
);
}
#[test]
fn test_deoptimize_turbofan_trivial_resume() {
let ba = make_lda_return(55);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let deopt_points = vec![DeoptPoint {
index: 0,
bytecode_offset: 0,
reason: "UnsupportedOperation",
}];
let frame = TurbofanFrameState {
registers: vec![],
deopt_index: 0,
};
let result =
deoptimize_turbofan(ba, &mut fv, &deopt_points, frame, empty_globals()).unwrap();
assert_eq!(result, JsValue::Smi(55));
}
#[test]
fn test_deoptimize_turbofan_overflow_reason() {
let ba = make_lda_return(0);
let meta = FeedbackMetadata::new(vec![FeedbackSlotKind::BinaryOp]);
let mut fv = FeedbackVector::new(&meta);
fv.set_state(0, InlineCacheState::Monomorphic);
let deopt_points = vec![DeoptPoint {
index: 0,
bytecode_offset: 0,
reason: "CheckedSmiAdd overflow",
}];
let frame = TurbofanFrameState {
registers: vec![],
deopt_index: 0,
};
deoptimize_turbofan(ba, &mut fv, &deopt_points, frame, empty_globals())
.expect("should succeed");
assert_eq!(fv.get_state(0), Some(InlineCacheState::Megamorphic));
}
#[test]
fn test_execute_with_deopt_interpreter_resume() {
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: 0,
reason: 0,
});
graph.add_block(block);
let compiled = compile(&graph, 0).expect("turbofan compile failed");
assert_eq!(compiled.deopt_points.len(), 1);
let ba = make_lda_return(77);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let result = unsafe { compiled.execute_with_deopt(ba, &mut fv, empty_globals()) }
.expect("interpreter fallback must succeed");
assert_eq!(result, JsValue::Smi(77));
}
#[test]
fn test_execute_with_deopt_checked_smi_overflow() {
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("turbofan compile failed");
let ba = make_lda_return(42);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let result = unsafe { compiled.execute_with_deopt(ba, &mut fv, empty_globals()) }
.expect("interpreter fallback must succeed");
assert_eq!(result, JsValue::Smi(42));
}
#[test]
fn test_value_recovery_in_register() {
let raw_regs: Vec<i64> = vec![10, 20, 30];
let recovery = ValueRecovery::InRegister { index: 1 };
let val = recover_value(&recovery, &raw_regs);
assert_eq!(val, JsValue::Smi(20));
}
#[test]
fn test_value_recovery_constant() {
let recovery = ValueRecovery::Constant {
value: JsValue::Smi(99),
};
let val = recover_value(&recovery, &[]);
assert_eq!(val, JsValue::Smi(99));
}
#[test]
fn test_value_recovery_materialized_fallback() {
let recovery = ValueRecovery::Materialized {
map: 0,
fields: vec![],
};
let val = recover_value(&recovery, &[]);
assert_eq!(val, JsValue::Undefined);
}
#[test]
fn test_value_recovery_in_register_oob() {
let recovery = ValueRecovery::InRegister { index: 100 };
let val = recover_value(&recovery, &[1, 2]);
assert_eq!(val, JsValue::Undefined);
}
#[test]
fn test_deoptimize_with_entry_trivial() {
let ba = make_lda_return(33);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let entry = DeoptEntry {
bytecode_offset: 0,
register_map: vec![],
reason: DeoptReason::UnsupportedOperation,
};
let result = deoptimize_with_entry(ba, &mut fv, &entry, &[], empty_globals()).unwrap();
assert_eq!(result, JsValue::Smi(33));
}
#[test]
fn test_deoptimize_with_entry_register_recovery() {
let ba = make_lda_return(11);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let entry = DeoptEntry {
bytecode_offset: 0,
register_map: vec![
ValueRecovery::Constant {
value: JsValue::Smi(5),
},
ValueRecovery::InRegister { index: 0 },
],
reason: DeoptReason::TypeCheckFailure,
};
let result = deoptimize_with_entry(ba, &mut fv, &entry, &[20], empty_globals()).unwrap();
assert_eq!(result, JsValue::Smi(11));
}
#[test]
fn test_reconstruct_frame_basic() {
let frame = reconstruct_frame(&[10, 20]);
assert_eq!(frame.registers.len(), 2);
assert_eq!(frame.registers[0], JsValue::Smi(10));
assert_eq!(frame.registers[1], JsValue::Smi(20));
assert_eq!(frame.accumulator, JsValue::Undefined);
}
#[test]
fn test_reconstruct_frame_empty() {
let frame = reconstruct_frame(&[]);
assert!(frame.registers.is_empty());
}
#[test]
fn test_deopt_kind_variants() {
assert_ne!(DeoptKind::Eager, DeoptKind::Lazy);
assert_eq!(DeoptKind::Eager, DeoptKind::Eager);
}
}