use std::cell::{Cell, OnceCell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use crate::bytecode::feedback::{FeedbackMetadata, FeedbackVector, InlineCacheState};
use crate::bytecode::{
bytecodes::{self, Instruction, Operand},
peephole,
};
use crate::compiler::turbofan::TurbofanCompiledCode;
use crate::error::{StatorError, StatorResult};
use crate::objects::property_map::{
ObjectLiteralTemplate, PropertyMap, acquire_object_rc_from_template,
acquire_object_rc_from_template_with_values,
};
use crate::objects::value::{JsContext, JsValue};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandlerTableEntry {
pub try_start: u32,
pub try_end: u32,
pub handler: u32,
pub is_finally: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConstantPoolEntry {
Number(f64),
String(String),
Boolean(bool),
Null,
Undefined,
BigInt(i128),
Function(Rc<BytecodeArray>),
TemplateObject {
cooked: Vec<Option<String>>,
raw: Vec<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourcePosition {
pub bytecode_offset: u32,
pub line: u32,
pub column: u32,
}
impl SourcePosition {
pub fn new(bytecode_offset: u32, line: u32, column: u32) -> Self {
Self {
bytecode_offset,
line,
column,
}
}
}
#[cfg(all(target_arch = "x86_64", unix))]
type JitCodeCache = Rc<RefCell<Option<crate::compiler::baseline::compiler::CachedExecutableCode>>>;
#[cfg(not(all(target_arch = "x86_64", unix)))]
type JitCodeCache = Rc<RefCell<Option<(Vec<u8>, usize)>>>;
#[cfg(all(target_arch = "x86_64", unix))]
#[derive(Debug)]
pub struct JitExecutableCode {
ptr: *mut u8,
size: usize,
pub register_file_slots: usize,
}
#[cfg(all(target_arch = "x86_64", unix))]
impl JitExecutableCode {
pub unsafe fn new(code: &[u8], register_file_slots: usize) -> Option<Self> {
use std::ptr;
if code.is_empty() {
return None;
}
let size = code.len();
let mem = unsafe {
libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
if mem == libc::MAP_FAILED {
return None;
}
unsafe {
ptr::copy_nonoverlapping(code.as_ptr(), mem.cast::<u8>(), size);
}
Some(Self {
ptr: mem.cast::<u8>(),
size,
register_file_slots,
})
}
pub unsafe fn execute(&self, args: &[i64], ctx_ptr: i64) -> i64 {
let n = self.register_file_slots;
if n <= 16 {
let mut regs = [0i64; 16];
for (i, &v) in args.iter().enumerate().take(n) {
regs[i] = v;
}
let f: extern "C" fn(*mut i64, i64) -> i64 = unsafe { std::mem::transmute(self.ptr) };
return f(regs.as_mut_ptr(), ctx_ptr);
}
thread_local! {
static REG_FILE: std::cell::RefCell<Vec<i64>> = const {
std::cell::RefCell::new(Vec::new())
};
}
REG_FILE.with(|pool| {
let mut regs = pool.borrow_mut();
regs.clear();
regs.resize(n, 0);
for (i, &v) in args.iter().enumerate().take(n) {
regs[i] = v;
}
let f: extern "C" fn(*mut i64, i64) -> i64 = unsafe { std::mem::transmute(self.ptr) };
f(regs.as_mut_ptr(), ctx_ptr)
})
}
}
#[cfg(all(target_arch = "x86_64", unix))]
impl Drop for JitExecutableCode {
fn drop(&mut self) {
if !self.ptr.is_null() && self.size > 0 {
unsafe {
libc::munmap(self.ptr.cast(), self.size);
}
}
}
}
#[cfg(all(target_arch = "x86_64", unix))]
unsafe impl Send for JitExecutableCode {}
#[cfg(all(target_arch = "x86_64", unix))]
unsafe impl Sync for JitExecutableCode {}
#[cfg(all(target_arch = "x86_64", unix))]
pub type JitExecutableCache = Rc<RefCell<Option<JitExecutableCode>>>;
#[cfg(not(all(target_arch = "x86_64", unix)))]
pub type JitExecutableCache = Rc<RefCell<Option<()>>>;
pub(crate) type JumpTargetMap = Vec<Option<usize>>;
type DecodedBytecode = (Vec<Instruction>, Vec<usize>, JumpTargetMap);
type DecodedBytecodeRef<'a> = (&'a [Instruction], &'a [usize], &'a [Option<usize>]);
type DecodedBytecodeCache = Rc<OnceCell<Rc<DecodedBytecode>>>;
type SharedFeedbackVector = Rc<RefCell<FeedbackVector>>;
type FusionPatternCache = Rc<OnceCell<Option<(usize, i64)>>>;
#[cfg(all(target_arch = "x86_64", unix))]
pub type MaglevJitCodeCache =
Arc<Mutex<Option<crate::compiler::baseline::compiler::CachedExecutableCode>>>;
#[cfg(not(all(target_arch = "x86_64", unix)))]
pub type MaglevJitCodeCache = Arc<Mutex<Option<(Vec<u8>, usize)>>>;
#[cfg(all(target_arch = "x86_64", unix))]
pub type MaglevExecutableCache =
Rc<RefCell<Option<crate::compiler::maglev::codegen::CachedMaglevCode>>>;
#[cfg(not(all(target_arch = "x86_64", unix)))]
pub type MaglevExecutableCache = Rc<RefCell<Option<()>>>;
pub type TurbofanJitCodeCache = Arc<Mutex<Option<TurbofanCompiledCode>>>;
pub const TIERING_THRESHOLD: u32 = 10;
pub const MAGLEV_TIERING_THRESHOLD: u32 = 5;
pub const TURBOFAN_TIERING_THRESHOLD: u32 = 50;
type SharedMegamorphicIc = Rc<RefCell<Option<Box<crate::interpreter::MegamorphicIc>>>>;
type SharedProtoLoadIc = Rc<RefCell<Option<Box<crate::interpreter::ProtoIcCache>>>>;
type GlobalIcEntry = Option<(usize, u64)>;
type SharedGlobalIc = Rc<RefCell<Option<Box<Vec<GlobalIcEntry>>>>>;
#[derive(Debug)]
struct SharedBytecodeTemplate {
bytecodes: Rc<[u8]>,
constant_pool: Rc<[ConstantPoolEntry]>,
frame_size: u32,
parameter_count: u32,
function_length: u32,
function_name: Rc<str>,
source_text: Option<Rc<str>>,
binding_registers: Rc<HashMap<String, i32>>,
source_positions: Rc<[SourcePosition]>,
feedback_metadata: Rc<FeedbackMetadata>,
feedback_vector: SharedFeedbackVector,
handler_table: Rc<Vec<HandlerTableEntry>>,
handler_table_remapped: Rc<OnceCell<Rc<Vec<HandlerTableEntry>>>>,
cached_decode: DecodedBytecodeCache,
fusion_pattern_cache: FusionPatternCache,
template_cache: Rc<RefCell<HashMap<u32, crate::objects::value::JsValue>>>,
is_generator: bool,
is_async: bool,
is_module: bool,
is_strict: bool,
is_arrow: bool,
is_top_level: bool,
invocation_count: Rc<Cell<u32>>,
jit_code: JitCodeCache,
has_jit_code: Rc<Cell<bool>>,
jit_executable: JitExecutableCache,
maglev_jit_code: MaglevJitCodeCache,
maglev_executable: MaglevExecutableCache,
has_maglev_jit_code_flag: Arc<AtomicBool>,
maglev_compile_started: Arc<AtomicBool>,
turbofan_jit_code: TurbofanJitCodeCache,
has_turbofan_jit_code_flag: Arc<AtomicBool>,
turbofan_compile_started: Arc<AtomicBool>,
jit_baseline_deopted: Rc<Cell<bool>>,
jit_maglev_deopt_count: Rc<Cell<u32>>,
maglev_next_try_at: Rc<Cell<u32>>,
writes_closure_vars: bool,
self_name_register: Option<i32>,
construct_proto_cache: Rc<RefCell<Option<crate::objects::value::JsValue>>>,
construct_boilerplate: Rc<RefCell<Option<ConstructBoilerplate>>>,
object_literal_templates: Rc<RefCell<HashMap<u32, ObjectLiteralCacheEntry>>>,
shared_mega_load_ic: SharedMegamorphicIc,
shared_mega_store_ic: SharedMegamorphicIc,
shared_proto_load_ic: SharedProtoLoadIc,
shared_global_ic: SharedGlobalIc,
}
#[derive(Debug)]
pub struct BytecodeArray {
inner: Rc<SharedBytecodeTemplate>,
closure_context: Option<Rc<RefCell<JsContext>>>,
has_fn_props: Cell<bool>,
}
impl Clone for BytecodeArray {
fn clone(&self) -> Self {
Self {
inner: Rc::clone(&self.inner),
closure_context: self.closure_context.clone(),
has_fn_props: self.has_fn_props.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct ConstructBoilerplate {
pub keys: Vec<Rc<str>>,
pub attrs: Vec<crate::objects::map::PropertyAttributes>,
}
#[derive(Debug)]
pub(crate) enum ObjectLiteralCacheEntry {
Pending(Rc<RefCell<PropertyMap>>),
Cached(Box<ObjectLiteralTemplate>),
}
impl PartialEq for BytecodeArray {
fn eq(&self, other: &Self) -> bool {
self.inner.bytecodes == other.inner.bytecodes
&& self.inner.constant_pool == other.inner.constant_pool
&& self.inner.frame_size == other.inner.frame_size
&& self.inner.parameter_count == other.inner.parameter_count
&& self.inner.function_length == other.inner.function_length
&& self.inner.function_name == other.inner.function_name
&& self.inner.source_text == other.inner.source_text
&& self.inner.binding_registers == other.inner.binding_registers
&& self.inner.source_positions == other.inner.source_positions
&& self.inner.feedback_metadata == other.inner.feedback_metadata
&& self.inner.handler_table == other.inner.handler_table
&& self.inner.is_generator == other.inner.is_generator
&& self.inner.is_async == other.inner.is_async
&& self.inner.is_module == other.inner.is_module
&& self.inner.is_strict == other.inner.is_strict
&& self.inner.is_arrow == other.inner.is_arrow
&& self.inner.is_top_level == other.inner.is_top_level
&& self.inner.self_name_register == other.inner.self_name_register
&& self.inner.writes_closure_vars == other.inner.writes_closure_vars
}
}
impl BytecodeArray {
#[allow(clippy::too_many_arguments)]
pub fn new(
bytecodes: Vec<u8>,
constant_pool: Vec<ConstantPoolEntry>,
frame_size: u32,
parameter_count: u32,
source_positions: Vec<SourcePosition>,
feedback_metadata: FeedbackMetadata,
handler_table: Vec<HandlerTableEntry>,
) -> Self {
let feedback_vector = FeedbackVector::new(&feedback_metadata);
Self {
inner: Rc::new(SharedBytecodeTemplate {
bytecodes: bytecodes.into(),
constant_pool: constant_pool.into(),
frame_size,
parameter_count,
function_length: parameter_count,
function_name: Rc::from(""),
source_text: None,
binding_registers: Rc::new(HashMap::new()),
source_positions: source_positions.into(),
feedback_metadata: Rc::new(feedback_metadata),
feedback_vector: Rc::new(RefCell::new(feedback_vector)),
handler_table: Rc::new(handler_table),
handler_table_remapped: Rc::new(OnceCell::new()),
cached_decode: Rc::new(OnceCell::new()),
fusion_pattern_cache: Rc::new(OnceCell::new()),
template_cache: Rc::new(RefCell::new(HashMap::new())),
is_generator: false,
is_async: false,
is_module: false,
is_strict: false,
is_arrow: false,
is_top_level: false,
invocation_count: Rc::new(Cell::new(0)),
jit_code: Rc::new(RefCell::new(None)),
has_jit_code: Rc::new(Cell::new(false)),
jit_executable: Rc::new(RefCell::new(None)),
maglev_jit_code: Arc::new(Mutex::new(None)),
maglev_executable: Rc::new(RefCell::new(None)),
has_maglev_jit_code_flag: Arc::new(AtomicBool::new(false)),
maglev_compile_started: Arc::new(AtomicBool::new(false)),
turbofan_jit_code: Arc::new(Mutex::new(None)),
has_turbofan_jit_code_flag: Arc::new(AtomicBool::new(false)),
turbofan_compile_started: Arc::new(AtomicBool::new(false)),
jit_baseline_deopted: Rc::new(Cell::new(false)),
jit_maglev_deopt_count: Rc::new(Cell::new(0)),
maglev_next_try_at: Rc::new(Cell::new(0)),
writes_closure_vars: false,
self_name_register: None,
construct_proto_cache: Rc::new(RefCell::new(None)),
construct_boilerplate: Rc::new(RefCell::new(None)),
object_literal_templates: Rc::new(RefCell::new(HashMap::new())),
shared_mega_load_ic: Rc::new(RefCell::new(None)),
shared_mega_store_ic: Rc::new(RefCell::new(None)),
shared_proto_load_ic: Rc::new(RefCell::new(None)),
shared_global_ic: Rc::new(RefCell::new(None)),
}),
closure_context: None,
has_fn_props: Cell::new(false),
}
}
pub fn cached_template_object(
&self,
bytecode_offset: u32,
) -> Option<crate::objects::value::JsValue> {
self.inner
.template_cache
.borrow()
.get(&bytecode_offset)
.cloned()
}
pub fn cache_template_object(
&self,
bytecode_offset: u32,
value: crate::objects::value::JsValue,
) {
self.inner
.template_cache
.borrow_mut()
.insert(bytecode_offset, value);
}
#[cfg(test)]
pub(crate) fn template_cache_len(&self) -> usize {
self.inner.template_cache.borrow().len()
}
pub fn with_generator_flag(mut self, flag: bool) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_generator_flag called after sharing")
.is_generator = flag;
self
}
pub fn is_generator(&self) -> bool {
self.inner.is_generator
}
pub fn with_async_flag(mut self, flag: bool) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_async_flag called after sharing")
.is_async = flag;
self
}
pub fn is_async(&self) -> bool {
self.inner.is_async
}
pub fn with_module_flag(mut self, flag: bool) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_module_flag called after sharing")
.is_module = flag;
self
}
pub fn is_module(&self) -> bool {
self.inner.is_module
}
pub fn with_strict_flag(mut self, flag: bool) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_strict_flag called after sharing")
.is_strict = flag;
self
}
pub fn is_strict(&self) -> bool {
self.inner.is_strict
}
pub fn with_arrow_flag(mut self, flag: bool) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_arrow_flag called after sharing")
.is_arrow = flag;
self
}
pub fn is_arrow(&self) -> bool {
self.inner.is_arrow
}
pub fn has_trivial_body(&self) -> bool {
use super::bytecodes::{Opcode, decode};
let instrs = match decode(&self.inner.bytecodes) {
Ok(v) => v,
Err(_) => return false,
};
for instr in &instrs {
match instr.opcode {
Opcode::CreateMappedArguments
| Opcode::CreateUnmappedArguments
| Opcode::Star
| Opcode::Mov
| Opcode::LdaUndefined
| Opcode::LdaTheHole
| Opcode::Return => {}
_ => return false,
}
}
instrs.last().is_some_and(|i| i.opcode == Opcode::Return)
}
pub fn set_top_level(&mut self, flag: bool) {
Rc::get_mut(&mut self.inner)
.expect("set_top_level called after sharing")
.is_top_level = flag;
}
pub fn is_top_level(&self) -> bool {
self.inner.is_top_level
}
pub fn with_top_level_flag(mut self, flag: bool) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_top_level_flag called after sharing")
.is_top_level = flag;
self
}
pub fn closure_context(&self) -> Option<&Rc<RefCell<JsContext>>> {
self.closure_context.as_ref()
}
pub fn fusion_pattern_cache(&self) -> &OnceCell<Option<(usize, i64)>> {
&self.inner.fusion_pattern_cache
}
pub fn set_closure_context(&mut self, ctx: Rc<RefCell<JsContext>>) {
self.closure_context = Some(ctx);
}
pub fn writes_closure_vars(&self) -> bool {
self.inner.writes_closure_vars
}
#[inline(always)]
pub fn has_fn_props(&self) -> bool {
self.has_fn_props.get()
}
#[inline(always)]
pub fn mark_has_fn_props(&self) {
self.has_fn_props.set(true);
}
pub fn with_writes_closure_vars(mut self, flag: bool) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_writes_closure_vars called after sharing")
.writes_closure_vars = flag;
self
}
pub fn clone_for_closure(&self, ctx: Option<Rc<RefCell<JsContext>>>) -> Self {
Self {
inner: Rc::clone(&self.inner),
closure_context: ctx,
has_fn_props: Cell::new(false),
}
}
pub fn self_name_register(&self) -> Option<i32> {
self.inner.self_name_register
}
pub fn with_self_name_register(mut self, reg: i32) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_self_name_register called after sharing")
.self_name_register = Some(reg);
self
}
pub fn cached_construct_proto(&self) -> Option<crate::objects::value::JsValue> {
self.inner.construct_proto_cache.borrow().clone()
}
pub fn set_construct_proto_cache(&self, proto: crate::objects::value::JsValue) {
*self.inner.construct_proto_cache.borrow_mut() = Some(proto);
}
pub fn cached_construct_boilerplate(&self) -> Option<ConstructBoilerplate> {
self.inner.construct_boilerplate.borrow().clone()
}
pub fn set_construct_boilerplate(&self, bp: ConstructBoilerplate) {
*self.inner.construct_boilerplate.borrow_mut() = Some(bp);
}
pub fn clone_object_literal_template(&self, slot: u32) -> Option<PropertyMap> {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => Some(template.instantiate()),
_ => None,
}
}
pub fn promote_object_literal_template(&self, slot: u32) -> Option<PropertyMap> {
let pending_rc = {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Pending(rc)) => Some(Rc::clone(rc)),
_ => None,
}
};
let first_rc = pending_rc?;
let first_borrow = first_rc.borrow();
let template = ObjectLiteralTemplate::capture(&first_borrow)?;
let cloned = template.instantiate();
drop(first_borrow);
self.inner
.object_literal_templates
.borrow_mut()
.insert(slot, ObjectLiteralCacheEntry::Cached(Box::new(template)));
Some(cloned)
}
pub fn clone_object_literal_template_with_values(
&self,
slot: u32,
values: Vec<JsValue>,
) -> Option<PropertyMap> {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => {
Some(template.instantiate_with_values(values))
}
_ => None,
}
}
pub fn promote_object_literal_template_with_values(
&self,
slot: u32,
values: Vec<JsValue>,
) -> Option<PropertyMap> {
let pending_rc = {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Pending(rc)) => Some(Rc::clone(rc)),
_ => None,
}
};
let first_rc = pending_rc?;
let first_borrow = first_rc.borrow();
let template = ObjectLiteralTemplate::capture(&first_borrow)?;
let cloned = template.instantiate_with_values(values);
drop(first_borrow);
self.inner
.object_literal_templates
.borrow_mut()
.insert(slot, ObjectLiteralCacheEntry::Cached(Box::new(template)));
Some(cloned)
}
pub fn set_object_literal_pending(&self, slot: u32, map: Rc<RefCell<PropertyMap>>) {
self.inner
.object_literal_templates
.borrow_mut()
.insert(slot, ObjectLiteralCacheEntry::Pending(map));
}
#[cfg(all(target_arch = "x86_64", unix))]
pub(crate) fn get_cached_template_ptr(&self, slot: u32) -> *const ObjectLiteralTemplate {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => {
&**template as *const ObjectLiteralTemplate
}
_ => std::ptr::null(),
}
}
pub fn clone_object_literal_template_pooled(
&self,
slot: u32,
) -> Option<Rc<RefCell<PropertyMap>>> {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => {
Some(acquire_object_rc_from_template(template))
}
_ => None,
}
}
pub unsafe fn clone_object_literal_template_pooled_unchecked(
&self,
slot: u32,
) -> Option<Rc<RefCell<PropertyMap>>> {
let map = unsafe { &*self.inner.object_literal_templates.as_ptr() };
match map.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => {
Some(acquire_object_rc_from_template(template))
}
_ => None,
}
}
pub(crate) unsafe fn get_cached_template_unchecked(
&self,
slot: u32,
) -> Option<&ObjectLiteralTemplate> {
let map = unsafe { &*self.inner.object_literal_templates.as_ptr() };
match map.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => Some(template),
_ => None,
}
}
pub unsafe fn clone_object_literal_template_with_values_pooled_unchecked(
&self,
slot: u32,
values: &[JsValue],
) -> Option<Rc<RefCell<PropertyMap>>> {
let map = unsafe { &*self.inner.object_literal_templates.as_ptr() };
match map.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => Some(
acquire_object_rc_from_template_with_values(template, values),
),
_ => None,
}
}
pub unsafe fn set_object_literal_pending_unchecked(
&self,
slot: u32,
map_rc: Rc<RefCell<PropertyMap>>,
) {
let cache = unsafe { &mut *self.inner.object_literal_templates.as_ptr() };
cache.insert(slot, ObjectLiteralCacheEntry::Pending(map_rc));
}
pub fn promote_object_literal_template_pooled(
&self,
slot: u32,
) -> Option<Rc<RefCell<PropertyMap>>> {
let pending_rc = {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Pending(rc)) => Some(Rc::clone(rc)),
_ => None,
}
};
let first_rc = pending_rc?;
let first_borrow = first_rc.borrow();
let template = ObjectLiteralTemplate::capture(&first_borrow)?;
let rc = acquire_object_rc_from_template(&template);
drop(first_borrow);
template.pre_warm_pool();
self.inner
.object_literal_templates
.borrow_mut()
.insert(slot, ObjectLiteralCacheEntry::Cached(Box::new(template)));
Some(rc)
}
pub fn clone_object_literal_template_with_values_pooled(
&self,
slot: u32,
values: &[JsValue],
) -> Option<Rc<RefCell<PropertyMap>>> {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Cached(template)) => Some(
acquire_object_rc_from_template_with_values(template, values),
),
_ => None,
}
}
pub fn promote_object_literal_template_with_values_pooled(
&self,
slot: u32,
values: &[JsValue],
) -> Option<Rc<RefCell<PropertyMap>>> {
let pending_rc = {
let borrow = self.inner.object_literal_templates.borrow();
match borrow.get(&slot) {
Some(ObjectLiteralCacheEntry::Pending(rc)) => Some(Rc::clone(rc)),
_ => None,
}
};
let first_rc = pending_rc?;
let first_borrow = first_rc.borrow();
let template = ObjectLiteralTemplate::capture(&first_borrow)?;
let rc = acquire_object_rc_from_template_with_values(&template, values);
drop(first_borrow);
template.pre_warm_pool();
self.inner
.object_literal_templates
.borrow_mut()
.insert(slot, ObjectLiteralCacheEntry::Cached(Box::new(template)));
Some(rc)
}
pub fn shared_mega_load_ic(&self) -> Option<Box<crate::interpreter::MegamorphicIc>> {
self.inner.shared_mega_load_ic.borrow().clone()
}
pub fn set_shared_mega_load_ic(&self, ic: Box<crate::interpreter::MegamorphicIc>) {
*self.inner.shared_mega_load_ic.borrow_mut() = Some(ic);
}
pub fn shared_mega_store_ic(&self) -> Option<Box<crate::interpreter::MegamorphicIc>> {
self.inner.shared_mega_store_ic.borrow().clone()
}
pub fn set_shared_mega_store_ic(&self, ic: Box<crate::interpreter::MegamorphicIc>) {
*self.inner.shared_mega_store_ic.borrow_mut() = Some(ic);
}
pub fn shared_proto_load_ic(&self) -> Option<Box<crate::interpreter::ProtoIcCache>> {
self.inner.shared_proto_load_ic.borrow().clone()
}
pub fn set_shared_proto_load_ic(&self, ic: Box<crate::interpreter::ProtoIcCache>) {
*self.inner.shared_proto_load_ic.borrow_mut() = Some(ic);
}
pub fn shared_global_ic(&self) -> Option<Box<Vec<GlobalIcEntry>>> {
self.inner.shared_global_ic.borrow().clone()
}
pub fn set_shared_global_ic(&self, ic: Box<Vec<GlobalIcEntry>>) {
*self.inner.shared_global_ic.borrow_mut() = Some(ic);
}
pub fn bytecodes(&self) -> &[u8] {
&self.inner.bytecodes
}
pub fn constant_pool(&self) -> &[ConstantPoolEntry] {
&self.inner.constant_pool
}
pub fn frame_size(&self) -> u32 {
self.inner.frame_size
}
pub fn parameter_count(&self) -> u32 {
self.inner.parameter_count
}
pub fn bytecode_count(&self) -> usize {
self.ensure_decoded_instructions()
.map_or(usize::MAX, |decoded| decoded.0.len())
}
pub fn has_exception_handler(&self) -> bool {
!self.inner.handler_table.is_empty()
}
pub fn function_length(&self) -> u32 {
self.inner.function_length
}
pub fn with_function_length(mut self, length: u32) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_function_length called after sharing")
.function_length = length;
self
}
pub fn function_name(&self) -> &str {
&self.inner.function_name
}
pub fn with_function_name(mut self, name: impl Into<String>) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_function_name called after sharing")
.function_name = Rc::from(name.into());
self
}
pub fn source_text(&self) -> Option<&str> {
self.inner.source_text.as_deref()
}
pub fn with_source_text(mut self, source_text: impl Into<String>) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_source_text called after sharing")
.source_text = Some(Rc::from(source_text.into()));
self
}
pub fn binding_registers(&self) -> &HashMap<String, i32> {
&self.inner.binding_registers
}
pub fn with_binding_registers(mut self, binding_registers: HashMap<String, i32>) -> Self {
Rc::get_mut(&mut self.inner)
.expect("with_binding_registers called after sharing")
.binding_registers = Rc::new(binding_registers);
self
}
pub fn source_positions(&self) -> &[SourcePosition] {
&self.inner.source_positions
}
pub fn feedback_metadata(&self) -> &FeedbackMetadata {
&self.inner.feedback_metadata
}
pub fn feedback_vector_snapshot(&self) -> FeedbackVector {
self.inner.feedback_vector.borrow().clone()
}
pub fn feedback_state(&self, slot: u32) -> Option<InlineCacheState> {
self.inner.feedback_vector.borrow().get_state(slot)
}
pub fn feedback_transition(&self, slot: u32, new_state: InlineCacheState) -> bool {
self.inner
.feedback_vector
.borrow_mut()
.transition(slot, new_state)
}
pub fn set_feedback_state(&self, slot: u32, state: InlineCacheState) -> bool {
self.inner
.feedback_vector
.borrow_mut()
.set_state(slot, state)
}
pub fn handler_table(&self) -> &[HandlerTableEntry] {
let _ = self.ensure_decoded_instructions();
self.inner.handler_table.as_slice()
}
pub(crate) fn shared_handler_table(&self) -> Rc<Vec<HandlerTableEntry>> {
if let Some(remapped) = self.inner.handler_table_remapped.get() {
Rc::clone(remapped)
} else {
Rc::clone(&self.inner.handler_table)
}
}
pub fn instructions(&self) -> StatorResult<Vec<Instruction>> {
bytecodes::decode(&self.inner.bytecodes)
}
fn ensure_decoded_instructions(&self) -> StatorResult<&Rc<DecodedBytecode>> {
if self.inner.cached_decode.get().is_none() {
let (mut instructions, mut byte_offsets) =
bytecodes::decode_with_byte_offsets(&self.inner.bytecodes)?;
let remap = peephole::fuse_instructions_with_remap(
&mut instructions,
&mut byte_offsets,
Some(&self.inner.constant_pool),
);
let remap_index = |idx: u32, fallback: usize| -> u32 {
remap
.iter()
.skip(idx as usize)
.find_map(|mapped| *mapped)
.unwrap_or(fallback) as u32
};
let remapped_handlers: Vec<HandlerTableEntry> = self
.inner
.handler_table
.iter()
.map(|entry| HandlerTableEntry {
try_start: remap_index(entry.try_start, 0),
try_end: remap_index(entry.try_end, instructions.len()),
handler: remap_index(entry.handler, 0),
is_finally: entry.is_finally,
})
.collect();
let _ = self
.inner
.handler_table_remapped
.set(Rc::new(remapped_handlers));
let mut jump_targets = vec![None; instructions.len()];
for (instruction_index, instruction) in instructions.iter().enumerate() {
for operand in instruction.operands() {
let Operand::JumpOffset(delta) = operand else {
continue;
};
let pc_after_jump = instruction_index + 1;
let end_byte = *byte_offsets.get(pc_after_jump).ok_or_else(|| {
StatorError::Internal(format!(
"missing post-jump byte offset for instruction {instruction_index}"
))
})?;
let target_byte = (end_byte as i64 + i64::from(*delta)) as usize;
let target_index = byte_offsets
.binary_search(&target_byte)
.map_err(|_| {
StatorError::Internal(format!(
"jump target byte offset {target_byte} is not at an instruction boundary"
))
})?;
jump_targets[instruction_index] = Some(target_index);
}
}
let decoded = Rc::new((instructions, byte_offsets, jump_targets));
let _ = self.inner.cached_decode.set(decoded);
}
Ok(self
.inner
.cached_decode
.get()
.expect("decoded bytecode cache must be initialized"))
}
pub fn decoded_instructions(&self) -> StatorResult<DecodedBytecodeRef<'_>> {
let decoded = self.ensure_decoded_instructions()?;
Ok((
decoded.0.as_slice(),
decoded.1.as_slice(),
decoded.2.as_slice(),
))
}
pub(crate) fn shared_decoded_instructions(&self) -> StatorResult<Rc<DecodedBytecode>> {
Ok(Rc::clone(self.ensure_decoded_instructions()?))
}
pub fn get_constant(&self, index: u32) -> Option<&ConstantPoolEntry> {
self.inner.constant_pool.get(index as usize)
}
pub fn source_position_for(&self, bytecode_offset: u32) -> Option<&SourcePosition> {
let idx = self
.inner
.source_positions
.partition_point(|sp| sp.bytecode_offset <= bytecode_offset);
idx.checked_sub(1).map(|i| &self.inner.source_positions[i])
}
#[inline(always)]
pub fn increment_invocation_count(&self) -> u32 {
let old = self.inner.invocation_count.get();
let new = old.saturating_add(1);
self.inner.invocation_count.set(new);
new
}
pub fn invocation_count(&self) -> u32 {
self.inner.invocation_count.get()
}
#[cfg(all(target_arch = "x86_64", unix))]
pub fn store_jit_code(
&self,
cached: crate::compiler::baseline::compiler::CachedExecutableCode,
) {
*self.inner.jit_code.borrow_mut() = Some(cached);
self.inner.has_jit_code.set(true);
}
#[cfg(not(all(target_arch = "x86_64", unix)))]
pub fn store_jit_code(&self, code: Vec<u8>, register_file_slots: usize) {
*self.inner.jit_code.borrow_mut() = Some((code, register_file_slots));
self.inner.has_jit_code.set(true);
}
#[inline(always)]
pub fn has_any_jit_code(&self) -> bool {
self.inner.has_jit_code.get()
|| self.inner.has_maglev_jit_code_flag.load(Ordering::Relaxed)
|| self
.inner
.has_turbofan_jit_code_flag
.load(Ordering::Relaxed)
}
#[cfg(all(target_arch = "x86_64", unix))]
pub fn try_get_jit_code(
&self,
) -> std::cell::Ref<'_, Option<crate::compiler::baseline::compiler::CachedExecutableCode>> {
self.inner.jit_code.borrow()
}
#[cfg(not(all(target_arch = "x86_64", unix)))]
pub fn try_get_jit_code(&self) -> Option<(Vec<u8>, usize)> {
self.inner.jit_code.borrow().clone()
}
pub fn has_maglev_jit_code(&self) -> bool {
self.inner
.maglev_jit_code
.lock()
.ok()
.map(|g| g.is_some())
.unwrap_or(false)
}
pub fn has_all_maglev_jit_code(&self) -> bool {
if !self.has_maglev_jit_code() {
return false;
}
for entry in self.inner.constant_pool.iter() {
if let ConstantPoolEntry::Function(nested_ba) = entry {
if !nested_ba.has_all_maglev_jit_code() && !nested_ba.maglev_compile_attempted() {
return false;
}
}
}
true
}
pub fn maglev_jit_cache_arc(&self) -> MaglevJitCodeCache {
Arc::clone(&self.inner.maglev_jit_code)
}
pub fn maglev_jit_code_flag(&self) -> Arc<AtomicBool> {
Arc::clone(&self.inner.has_maglev_jit_code_flag)
}
pub fn try_start_maglev_compile(&self) -> bool {
self.inner
.maglev_compile_started
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
pub fn maglev_compile_attempted(&self) -> bool {
self.inner.maglev_compile_started.load(Ordering::Acquire)
}
pub fn has_turbofan_jit_code(&self) -> bool {
self.inner
.turbofan_jit_code
.lock()
.ok()
.map(|g| g.is_some())
.unwrap_or(false)
}
pub fn turbofan_jit_cache_arc(&self) -> TurbofanJitCodeCache {
Arc::clone(&self.inner.turbofan_jit_code)
}
pub fn turbofan_jit_code_flag(&self) -> Arc<AtomicBool> {
Arc::clone(&self.inner.has_turbofan_jit_code_flag)
}
pub fn try_start_turbofan_compile(&self) -> bool {
self.inner
.turbofan_compile_started
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
pub fn jit_executable_cache(&self) -> &JitExecutableCache {
&self.inner.jit_executable
}
pub fn maglev_executable_cache(&self) -> &MaglevExecutableCache {
&self.inner.maglev_executable
}
pub fn jit_baseline_has_deopted(&self) -> bool {
self.inner.jit_baseline_deopted.get()
}
pub fn mark_jit_baseline_deopted(&self) {
self.inner.jit_baseline_deopted.set(true);
}
const MAX_MAGLEV_DEOPT_RETRIES: u32 = 15;
pub fn jit_maglev_has_deopted(&self) -> bool {
let count = self.inner.jit_maglev_deopt_count.get();
if count >= Self::MAX_MAGLEV_DEOPT_RETRIES {
return true;
}
let next = self.inner.maglev_next_try_at.get();
if next > 0 && self.inner.invocation_count.get() < next {
return true;
}
false
}
pub fn maglev_deopt_count(&self) -> u32 {
self.inner.jit_maglev_deopt_count.get()
}
pub fn mark_jit_maglev_deopted(&self) {
let count = self.inner.jit_maglev_deopt_count.get();
self.inner
.jit_maglev_deopt_count
.set(count.saturating_add(1));
let next_try = self.inner.invocation_count.get().saturating_add(1);
self.inner.maglev_next_try_at.set(next_try);
}
pub fn reset_maglev_deopt_count(&self) {
self.inner.jit_maglev_deopt_count.set(0);
self.inner.maglev_next_try_at.set(0);
for entry in self.inner.constant_pool.iter() {
if let ConstantPoolEntry::Function(nested_ba) = entry {
nested_ba.reset_maglev_deopt_count();
}
}
}
pub fn maglev_next_try_at(&self) -> u32 {
self.inner.maglev_next_try_at.get()
}
pub fn set_maglev_next_try_at(&self, val: u32) {
self.inner.maglev_next_try_at.set(val);
}
pub fn has_maglev_executable_cached(&self) -> bool {
self.inner.maglev_executable.borrow().is_some()
}
#[cfg(not(all(target_arch = "x86_64", unix)))]
pub fn try_get_maglev_jit_code(&self) -> Option<(Vec<u8>, usize)> {
self.inner.maglev_jit_code.lock().ok()?.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::bytecodes::{Instruction, Opcode, Operand, encode};
use crate::bytecode::feedback::FeedbackMetadata;
use crate::objects::property_map::PropertyMap;
use crate::objects::value::JsValue;
fn make_simple_array() -> BytecodeArray {
let instructions = vec![
Instruction::new_unchecked(Opcode::LdaSmi, vec![Operand::Immediate(7)]),
Instruction::new_unchecked(Opcode::Star, vec![Operand::Register(0)]),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let bytes = encode(&instructions);
BytecodeArray::new(
bytes,
vec![],
1,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
)
}
fn make_jump_array() -> BytecodeArray {
let instructions = vec![
Instruction::new_unchecked(Opcode::Jump, vec![Operand::JumpOffset(0)]),
Instruction::new_unchecked(Opcode::LdaZero, vec![]),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let bytes = encode(&instructions);
let (_, offsets) = bytecodes::decode_with_byte_offsets(&bytes).expect("valid bytecode");
let target_byte = offsets[2];
let jump_end_byte = offsets[1];
let mut resolved = instructions;
*resolved[0].operand_mut(0) =
Operand::JumpOffset(target_byte as i32 - jump_end_byte as i32);
BytecodeArray::new(
encode(&resolved),
vec![],
1,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
)
}
#[test]
fn test_create_bytecode_array() {
let array = make_simple_array();
assert_eq!(array.frame_size(), 1);
assert_eq!(array.parameter_count(), 0);
assert!(array.constant_pool().is_empty());
assert!(array.source_positions().is_empty());
assert!(!array.bytecodes().is_empty());
}
#[test]
fn test_iterate_instructions() {
let array = make_simple_array();
let instrs = array.instructions().expect("valid bytecode");
assert_eq!(instrs.len(), 3);
assert_eq!(instrs[0].opcode, Opcode::LdaSmi);
assert_eq!(instrs[1].opcode, Opcode::Star);
assert_eq!(instrs[2].opcode, Opcode::Return);
}
#[test]
fn test_constant_pool() {
let instructions = vec![
Instruction::new_unchecked(Opcode::LdaConstant, vec![Operand::ConstantPoolIdx(0)]),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let bytes = encode(&instructions);
let pool = vec![
ConstantPoolEntry::Number(3.14),
ConstantPoolEntry::String("hello".to_owned()),
ConstantPoolEntry::Boolean(true),
ConstantPoolEntry::Null,
ConstantPoolEntry::Undefined,
];
let array =
BytecodeArray::new(bytes, pool, 0, 1, vec![], FeedbackMetadata::empty(), vec![]);
assert_eq!(array.constant_pool().len(), 5);
assert_eq!(
array.get_constant(0),
Some(&ConstantPoolEntry::Number(3.14))
);
assert_eq!(
array.get_constant(1),
Some(&ConstantPoolEntry::String("hello".to_owned()))
);
assert_eq!(
array.get_constant(2),
Some(&ConstantPoolEntry::Boolean(true))
);
assert_eq!(array.get_constant(3), Some(&ConstantPoolEntry::Null));
assert_eq!(array.get_constant(4), Some(&ConstantPoolEntry::Undefined));
assert_eq!(array.get_constant(5), None);
}
#[test]
fn test_object_literal_template_cache_stores_shape_only() {
let array = make_simple_array();
let mut first = PropertyMap::with_capacity(4);
first.insert("x".to_string(), JsValue::Smi(1));
first.insert("y".to_string(), JsValue::Smi(2));
let first = Rc::new(RefCell::new(first));
array.set_object_literal_pending(7, Rc::clone(&first));
let promoted = array
.promote_object_literal_template(7)
.expect("template should be promoted");
assert_eq!(promoted.layout_id(), first.borrow().layout_id());
assert_eq!(promoted.get("x"), Some(&JsValue::Undefined));
assert_eq!(promoted.get("y"), Some(&JsValue::Undefined));
first.borrow_mut().insert("x".to_string(), JsValue::Smi(99));
let cached = array
.clone_object_literal_template(7)
.expect("cached template should instantiate");
assert_eq!(cached.layout_id(), promoted.layout_id());
assert_eq!(cached.get("x"), Some(&JsValue::Undefined));
assert_eq!(cached.get("y"), Some(&JsValue::Undefined));
}
#[test]
fn test_source_positions() {
let array = BytecodeArray::new(
vec![],
vec![],
0,
0,
vec![
SourcePosition::new(0, 1, 1),
SourcePosition::new(4, 2, 5),
SourcePosition::new(8, 3, 1),
],
FeedbackMetadata::empty(),
vec![],
);
assert_eq!(
array.source_position_for(0),
Some(&SourcePosition::new(0, 1, 1))
);
assert_eq!(
array.source_position_for(2),
Some(&SourcePosition::new(0, 1, 1))
);
assert_eq!(
array.source_position_for(4),
Some(&SourcePosition::new(4, 2, 5))
);
assert_eq!(
array.source_position_for(10),
Some(&SourcePosition::new(8, 3, 1))
);
}
#[test]
fn test_source_position_empty_table() {
let array = make_simple_array();
assert_eq!(array.source_position_for(0), None);
}
#[test]
fn test_instructions_decode_error() {
let array = BytecodeArray::new(
vec![Opcode::LdaSmi as u8],
vec![],
0,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
);
assert!(array.instructions().is_err());
}
#[test]
fn test_feedback_metadata_stored_in_array() {
use crate::bytecode::feedback::FeedbackSlotKind;
let metadata =
FeedbackMetadata::new(vec![FeedbackSlotKind::Call, FeedbackSlotKind::LoadProperty]);
let array = BytecodeArray::new(vec![], vec![], 0, 0, vec![], metadata, vec![]);
assert_eq!(array.feedback_metadata().slot_count(), 2);
assert_eq!(
array.feedback_metadata().kind_of(0),
Some(FeedbackSlotKind::Call)
);
assert_eq!(
array.feedback_metadata().kind_of(1),
Some(FeedbackSlotKind::LoadProperty)
);
}
#[test]
fn test_decoded_instructions_are_cached() {
let mut array = make_simple_array();
let expected_offsets = bytecodes::decode_with_byte_offsets(array.bytecodes())
.expect("valid bytecode")
.1;
{
let (instructions, offsets, jump_targets) =
array.decoded_instructions().expect("valid bytecode");
assert_eq!(instructions.len(), 2);
assert_eq!(instructions[0].opcode, Opcode::LdaSmiStar);
assert_eq!(instructions[1].opcode, Opcode::Return);
assert_eq!(offsets.len(), instructions.len() + 1);
assert!(jump_targets.iter().all(|t| t.is_none()));
}
let first = array
.shared_decoded_instructions()
.expect("cached bytecode");
let second = array
.shared_decoded_instructions()
.expect("cached bytecode 2");
assert!(std::ptr::eq(first.0.as_ptr(), second.0.as_ptr()));
assert!(std::ptr::eq(first.1.as_ptr(), second.1.as_ptr()));
assert!(std::ptr::eq(&first.2, &second.2));
}
#[test]
fn test_decoded_instructions_cache_is_shared_across_clones() {
let array = make_simple_array();
let clone = array.clone();
let decoded_orig = array.shared_decoded_instructions().expect("valid bytecode");
let decoded_clone = clone
.shared_decoded_instructions()
.expect("shared cached bytecode");
assert!(std::ptr::eq(
decoded_orig.0.as_ptr(),
decoded_clone.0.as_ptr()
));
assert!(std::ptr::eq(
decoded_orig.1.as_ptr(),
decoded_clone.1.as_ptr()
));
assert!(std::ptr::eq(&decoded_orig.2, &decoded_clone.2));
}
#[test]
fn test_decoded_instructions_cache_includes_jump_targets() {
let mut array = make_jump_array();
let (instructions, _offsets, jump_targets) =
array.decoded_instructions().expect("valid bytecode");
assert_eq!(instructions[0].opcode, Opcode::Jump);
assert_eq!(jump_targets[0], Some(2));
}
#[test]
fn test_decoded_instructions_apply_fusion_before_jump_resolution() {
let unresolved = vec![
Instruction::new_unchecked(Opcode::Ldar, vec![Operand::Register(0)]),
Instruction::new_unchecked(
Opcode::Add,
vec![Operand::Register(1), Operand::FeedbackSlot(2)],
),
Instruction::new_unchecked(Opcode::Star, vec![Operand::Register(2)]),
Instruction::new_unchecked(
Opcode::TestLessThan,
vec![Operand::Register(3), Operand::FeedbackSlot(4)],
),
Instruction::new_unchecked(Opcode::JumpIfTrue, vec![Operand::JumpOffset(0)]),
Instruction::new_unchecked(Opcode::LdaZero, vec![]),
Instruction::new_unchecked(Opcode::Return, vec![]),
];
let bytes = encode(&unresolved);
let (_, offsets) = bytecodes::decode_with_byte_offsets(&bytes).expect("valid bytecode");
let mut resolved = unresolved;
let target_byte = offsets[6];
let jump_end_byte = offsets[5];
*resolved[4].operand_mut(0) =
Operand::JumpOffset(target_byte as i32 - jump_end_byte as i32);
let mut array = BytecodeArray::new(
encode(&resolved),
vec![],
4,
0,
vec![],
FeedbackMetadata::empty(),
vec![],
);
let (instructions, _offsets, jump_targets) =
array.decoded_instructions().expect("valid bytecode");
assert_eq!(
instructions
.iter()
.map(|instr| instr.opcode)
.collect::<Vec<_>>(),
vec![
Opcode::LdarAddStar,
Opcode::TestLessThanJump,
Opcode::LdaZero,
Opcode::Return,
]
);
assert_eq!(jump_targets[1], Some(3));
}
}