use std::cell::RefCell;
use std::rc::Rc;
use crate::bytecode::bytecode_array::BytecodeArray;
use crate::bytecode::bytecodes::decode_with_byte_offsets;
use crate::bytecode::feedback::{FeedbackSlotKind, FeedbackVector, InlineCacheState};
use crate::error::StatorResult;
use crate::interpreter::{GlobalEnv, Interpreter, InterpreterFrame};
use crate::objects::value::JsValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeoptReason {
ArithmeticOverflow,
TypeCheckFailure,
UnsupportedOperation,
}
#[derive(Debug, Clone)]
pub struct FrameState {
pub registers: Vec<JsValue>,
pub accumulator: JsValue,
}
#[derive(Debug, Clone)]
pub struct DeoptInfo {
pub reason: DeoptReason,
pub bytecode_offset: u32,
pub frame_state: FrameState,
}
pub fn deoptimize(
bytecode_array: BytecodeArray,
feedback: &mut FeedbackVector,
deopt_info: DeoptInfo,
global_env: Rc<RefCell<GlobalEnv>>,
) -> StatorResult<JsValue> {
for slot in 0..feedback.slot_count() {
if let Some(
FeedbackSlotKind::BinaryOp | FeedbackSlotKind::BinaryOpInc | FeedbackSlotKind::Compare,
) = feedback.kind_of(slot)
{
feedback.set_state(slot, InlineCacheState::Megamorphic);
}
}
let (_, byte_offsets) = decode_with_byte_offsets(bytecode_array.bytecodes())?;
let target_pc = byte_offsets
.iter()
.position(|&o| o as u32 >= deopt_info.bytecode_offset)
.unwrap_or(0);
let mut frame = InterpreterFrame::new_with_globals(Rc::new(bytecode_array), vec![], global_env);
for (i, v) in deopt_info.frame_state.registers.iter().enumerate() {
if i < frame.registers.len() {
frame.registers[i] = v.clone();
}
}
frame.accumulator = deopt_info.frame_state.accumulator;
frame.pc = target_pc;
Interpreter::run(&mut frame)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::bytecode_array::BytecodeArray;
use crate::bytecode::bytecodes::{Instruction, Opcode, Operand, encode};
use crate::bytecode::feedback::{
FeedbackMetadata, FeedbackSlotKind, FeedbackVector, InlineCacheState,
};
use crate::objects::value::JsValue;
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![],
)
}
fn make_add_params(feedback_meta: FeedbackMetadata) -> BytecodeArray {
let p0 = (-1_i32) as u32;
let p1 = (-2_i32) as u32;
let instrs = vec![
Instruction::new_unchecked(Opcode::Ldar, vec![Operand::Register(p0)]),
Instruction::new_unchecked(Opcode::Star, vec![Operand::Register(0)]),
Instruction::new_unchecked(Opcode::Ldar, vec![Operand::Register(p1)]),
Instruction::new_unchecked(
Opcode::Add,
vec![Operand::Register(0), Operand::FeedbackSlot(0)],
),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let bytes = encode(&instrs);
BytecodeArray::new(bytes, vec![], 1, 2, vec![], feedback_meta, vec![])
}
fn empty_globals() -> Rc<RefCell<GlobalEnv>> {
Rc::new(RefCell::new(GlobalEnv::new()))
}
#[test]
fn test_deopt_info_construction() {
let info = DeoptInfo {
reason: DeoptReason::ArithmeticOverflow,
bytecode_offset: 4,
frame_state: FrameState {
registers: vec![JsValue::Smi(1), JsValue::Smi(2)],
accumulator: JsValue::Smi(3),
},
};
assert_eq!(info.reason, DeoptReason::ArithmeticOverflow);
assert_eq!(info.bytecode_offset, 4);
assert_eq!(info.frame_state.accumulator, JsValue::Smi(3));
}
#[test]
fn test_deoptimize_updates_feedback_to_megamorphic() {
let meta = FeedbackMetadata::new(vec![FeedbackSlotKind::BinaryOp, FeedbackSlotKind::Call]);
let ba = make_lda_return(0);
let mut fv = FeedbackVector::new(&meta);
fv.set_state(0, InlineCacheState::Monomorphic);
let info = DeoptInfo {
reason: DeoptReason::ArithmeticOverflow,
bytecode_offset: 0,
frame_state: FrameState {
registers: vec![],
accumulator: JsValue::Undefined,
},
};
deoptimize(ba, &mut fv, info, empty_globals()).expect("deoptimize should succeed");
assert_eq!(fv.get_state(0), Some(InlineCacheState::Megamorphic));
assert_eq!(fv.get_state(1), Some(InlineCacheState::Uninitialized));
}
#[test]
fn test_deoptimize_updates_compare_and_inc_slots() {
let meta = FeedbackMetadata::new(vec![
FeedbackSlotKind::Compare,
FeedbackSlotKind::BinaryOpInc,
]);
let ba = make_lda_return(0);
let mut fv = FeedbackVector::new(&meta);
let info = DeoptInfo {
reason: DeoptReason::TypeCheckFailure,
bytecode_offset: 0,
frame_state: FrameState {
registers: vec![],
accumulator: JsValue::Undefined,
},
};
deoptimize(ba, &mut fv, info, empty_globals()).expect("deoptimize should succeed");
assert_eq!(fv.get_state(0), Some(InlineCacheState::Megamorphic));
assert_eq!(fv.get_state(1), Some(InlineCacheState::Megamorphic));
}
#[test]
fn test_deoptimize_interpreter_resume_trivial() {
let ba = make_lda_return(42);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let info = DeoptInfo {
reason: DeoptReason::UnsupportedOperation,
bytecode_offset: 0,
frame_state: FrameState {
registers: vec![],
accumulator: JsValue::Undefined,
},
};
let result =
deoptimize(ba, &mut fv, info, empty_globals()).expect("interpreter must succeed");
assert_eq!(result, JsValue::Smi(42));
}
#[cfg(all(target_arch = "x86_64", unix))]
#[test]
#[ignore = "narrow-Int32 analysis is disabled (SIGSEGV); 64-bit add does not overflow at 2^32"]
fn test_deoptimize_via_type_change() {
use crate::compiler::baseline::compiler::jit_to_jsvalue;
use crate::compiler::maglev::codegen::compile;
use crate::compiler::maglev::ir::{BasicBlock, ControlNode, MaglevGraph, ValueNode};
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::CheckedSmiAdd {
left: p0,
right: p1,
});
block.set_control(ControlNode::Return { value: sum });
graph.add_block(block);
let cc = compile(&graph, 2).expect("maglev compile failed");
let ok = unsafe { cc.execute(&[3, 4]) };
assert_eq!(ok.ok().and_then(jit_to_jsvalue), Some(JsValue::Smi(7)));
let large: i64 = 1_i64 << 31; let deopt_result = unsafe { cc.execute(&[large, large]) };
assert!(
deopt_result.is_err(),
"expected JIT_DEOPT when sum has bits above bit 31, got {deopt_result:?}"
);
let meta = FeedbackMetadata::new(vec![FeedbackSlotKind::BinaryOp]);
let ba = make_add_params(meta);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
fv.set_state(0, InlineCacheState::Monomorphic);
let a = JsValue::Smi(i32::MAX);
let b = JsValue::Smi(i32::MAX);
let info = DeoptInfo {
reason: DeoptReason::ArithmeticOverflow,
bytecode_offset: 0,
frame_state: FrameState {
registers: vec![a, b],
accumulator: JsValue::Undefined,
},
};
let result = deoptimize(ba, &mut fv, info, empty_globals())
.expect("interpreter must handle large Smi addition");
let expected = f64::from(i32::MAX) + f64::from(i32::MAX);
match result {
JsValue::HeapNumber(v) => assert!(
(v - expected).abs() < f64::EPSILON,
"expected {expected}, got {v}"
),
other => panic!("expected HeapNumber({expected}), got {other:?}"),
}
assert_eq!(fv.get_state(0), Some(InlineCacheState::Megamorphic));
}
#[test]
fn test_deoptimize_resumes_from_correct_pc() {
let instrs = vec![
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(10)]),
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(99)]),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let bytes = encode(&instrs);
let ba = BytecodeArray::new(
bytes,
vec![],
0,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
);
let mut fv = FeedbackVector::new(ba.feedback_metadata());
let info = DeoptInfo {
reason: DeoptReason::UnsupportedOperation,
bytecode_offset: 0,
frame_state: FrameState {
registers: vec![],
accumulator: JsValue::Undefined,
},
};
let result =
deoptimize(ba, &mut fv, info, empty_globals()).expect("interpreter must succeed");
assert_eq!(result, JsValue::Smi(99));
}
}