Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions core/engine/src/vm/inline_cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use arrayvec::ArrayVec;
use itertools::Itertools;
use std::{cell::Cell, fmt};

use boa_gc::GcRefCell;
use boa_macros::{Finalize, Trace};

use crate::{
Expand All @@ -25,17 +24,48 @@ pub(crate) struct CacheEntry {
}

/// An inline cache entry for a property access.
#[derive(Clone, Debug, Trace, Finalize)]
#[repr(C)]
#[derive(Trace, Finalize)]
pub(crate) struct InlineCache {
/// Whether this access site has seen too many shapes and should no longer be cached.
#[unsafe_ignore_trace]
pub(crate) megamorphic: Cell<bool>,

/// The property that is accessed.
pub(crate) name: JsString,

/// Multiple cached shape-to-slot entries.
pub(crate) entries: GcRefCell<ArrayVec<CacheEntry, PIC_CAPACITY>>,
pub(crate) entries: Cell<ArrayVec<CacheEntry, PIC_CAPACITY>>,
}

/// Whether this access site has seen too many shapes and should no longer be cached.
#[unsafe_ignore_trace]
pub(crate) megamorphic: Cell<bool>,
impl Clone for InlineCache {
fn clone(&self) -> Self {
// SAFETY: `entries` is only ever accessed through `&self`/`&mut self`
// on this single-threaded cache, and cloning `CacheEntry` doesn't
// reenter this `Cell`, so it's safe to read through the raw pointer
// for the duration of this borrow without disturbing the cell's contents.
let cloned_entries = unsafe { (*self.entries.as_ptr()).clone() };

Self {
megamorphic: self.megamorphic.clone(),
name: self.name.clone(),
entries: Cell::new(cloned_entries),
}
}
}

impl fmt::Debug for InlineCache {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let entries = self.entries.take();
let result = f
.debug_struct("InlineCache")
.field("name", &self.name)
.field("entries", &entries)
.field("megamorphic", &self.megamorphic)
.finish();
self.entries.set(entries);
result
}
}

impl fmt::Display for InlineCache {
Expand All @@ -46,28 +76,38 @@ impl fmt::Display for InlineCache {
return write!(f, "(megamorphic))");
}

let entries = self.entries.borrow();
let entries = entries.iter().map(|e| e.shape.to_addr_usize()).format(", ");
let entries = self.entries.take();
let formatted = entries.iter().map(|e| e.shape.to_addr_usize()).format(", ");
let result = write!(f, "({formatted:#x}))");
self.entries.set(entries);

write!(f, "({entries:#x}))")
result
}
}

impl InlineCache {
pub(crate) fn new(name: JsString) -> Self {
Self {
name,
entries: GcRefCell::new(ArrayVec::new()),
megamorphic: Cell::new(false),
name,
entries: Cell::new(ArrayVec::new()),
}
}

#[cfg(test)]
pub(crate) fn entries(&self) -> ArrayVec<CacheEntry, PIC_CAPACITY> {
let entries = self.entries.take();
let cloned = entries.clone();
self.entries.set(entries);
cloned
}

pub(crate) fn set(&self, shape: &Shape, slot: Slot) {
if self.megamorphic.get() {
return;
}

let mut entries = self.entries.borrow_mut();
let mut entries = self.entries.take();

// Add a new entry if there's space.
if entries
Expand All @@ -81,6 +121,8 @@ impl InlineCache {
self.megamorphic.set(true);
entries.clear();
}

self.entries.set(entries);
}

/// Returns the cached `(Shape, Slot)` if a matching shape exists in the inline cache.
Expand All @@ -91,7 +133,7 @@ impl InlineCache {
return None;
}

let mut entries = self.entries.borrow_mut();
let mut entries = self.entries.take();
let mut i = 0;
let mut result = None;
let shape_addr = shape.to_addr_usize();
Expand All @@ -109,6 +151,7 @@ impl InlineCache {
}
}

self.entries.set(entries);
result
}
}
20 changes: 10 additions & 10 deletions core/engine/src/vm/inline_cache/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ fn set_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {
let (function, code) = get_codeblock(&function).unwrap();

assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert_eq!(code.ic[0].entries().len(), 0);

let o = ObjectInitializer::new(context)
.property(js_string!("test"), 0, Attribute::all())
Expand All @@ -339,9 +339,9 @@ fn set_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {

function.call(&JsValue::undefined(), &[o.clone().into()], context)?;

assert_eq!(code.ic[0].entries.borrow().len(), 1);
assert_eq!(code.ic[0].entries().len(), 1);
assert_eq!(
code.ic[0].entries.borrow()[0]
code.ic[0].entries()[0]
.shape
.upgrade()
.unwrap()
Expand All @@ -359,7 +359,7 @@ fn get_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {
let (function, code) = get_codeblock(&function).unwrap();

assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert_eq!(code.ic[0].entries().len(), 0);

let o = ObjectInitializer::new(context)
.property(js_string!("test"), 0, Attribute::all())
Expand All @@ -368,9 +368,9 @@ fn get_property_by_name_set_inline_cache_on_property_load() -> JsResult<()> {

function.call(&JsValue::undefined(), &[o.clone().into()], context)?;

assert_eq!(code.ic[0].entries.borrow().len(), 1);
assert_eq!(code.ic[0].entries().len(), 1);
assert_eq!(
code.ic[0].entries.borrow()[0]
code.ic[0].entries()[0]
.shape
.upgrade()
.unwrap()
Expand All @@ -388,7 +388,7 @@ fn test_polymorphic_inline_cache() -> JsResult<()> {
let (function, code) = get_codeblock(&function).unwrap();

assert_eq!(code.ic.len(), 1);
assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert_eq!(code.ic[0].entries().len(), 0);
assert!(!code.ic[0].megamorphic.get());

let shapes = vec![
Expand All @@ -413,7 +413,7 @@ fn test_polymorphic_inline_cache() -> JsResult<()> {
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
}

assert_eq!(code.ic[0].entries.borrow().len(), 4);
assert_eq!(code.ic[0].entries().len(), 4);
assert!(!code.ic[0].megamorphic.get());

Ok(())
Expand Down Expand Up @@ -451,7 +451,7 @@ fn test_megamorphic_inline_cache() -> JsResult<()> {
function.call(&JsValue::undefined(), &[o.clone().into()], context)?;
}

assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert_eq!(code.ic[0].entries().len(), 0);
assert!(code.ic[0].megamorphic.get());

// Regression check: repeated miss should remain empty
Expand All @@ -460,7 +460,7 @@ fn test_megamorphic_inline_cache() -> JsResult<()> {
.property(js_string!("test"), 1, Attribute::all())
.build();
function.call(&JsValue::undefined(), &[o6.clone().into()], context)?;
assert_eq!(code.ic[0].entries.borrow().len(), 0);
assert_eq!(code.ic[0].entries().len(), 0);
assert!(code.ic[0].megamorphic.get());

Ok(())
Expand Down