diff --git a/core/engine/src/vm/inline_cache/mod.rs b/core/engine/src/vm/inline_cache/mod.rs index c55aae8f767..aaa59f24fa5 100644 --- a/core/engine/src/vm/inline_cache/mod.rs +++ b/core/engine/src/vm/inline_cache/mod.rs @@ -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::{ @@ -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, + /// The property that is accessed. pub(crate) name: JsString, /// Multiple cached shape-to-slot entries. - pub(crate) entries: GcRefCell>, + pub(crate) entries: Cell>, +} - /// Whether this access site has seen too many shapes and should no longer be cached. - #[unsafe_ignore_trace] - pub(crate) megamorphic: Cell, +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 { @@ -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 { + 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 @@ -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. @@ -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(); @@ -109,6 +151,7 @@ impl InlineCache { } } + self.entries.set(entries); result } } diff --git a/core/engine/src/vm/inline_cache/tests.rs b/core/engine/src/vm/inline_cache/tests.rs index 1f93754b4b3..65135ca1a07 100644 --- a/core/engine/src/vm/inline_cache/tests.rs +++ b/core/engine/src/vm/inline_cache/tests.rs @@ -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()) @@ -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() @@ -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()) @@ -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() @@ -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![ @@ -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(()) @@ -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 @@ -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(())