From d2d4cd231348adfdedf10d8744909dc687e1399a Mon Sep 17 00:00:00 2001 From: mansiverma897993 Date: Wed, 17 Jun 2026 22:14:39 +0530 Subject: [PATCH 1/3] perf: remove GcRefCell from inline cache to avoid borrow checking overhead --- core/engine/src/vm/inline_cache/mod.rs | 56 ++++++++++++++++++++---- core/engine/src/vm/inline_cache/tests.rs | 20 ++++----- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/core/engine/src/vm/inline_cache/mod.rs b/core/engine/src/vm/inline_cache/mod.rs index c55aae8f767..b4b86f27765 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,19 +24,45 @@ pub(crate) struct CacheEntry { } /// An inline cache entry for a property access. -#[derive(Clone, Debug, Trace, Finalize)] +#[derive(Trace, Finalize)] pub(crate) struct InlineCache { /// 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 { + let entries = self.entries.take(); + let cloned_entries = entries.clone(); + self.entries.set(entries); + Self { + name: self.name.clone(), + entries: Cell::new(cloned_entries), + megamorphic: self.megamorphic.clone(), + } + } +} + +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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "(name:{} entries:", self.name.display_escaped())?; @@ -46,10 +71,12 @@ 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 } } @@ -57,17 +84,25 @@ impl InlineCache { pub(crate) fn new(name: JsString) -> Self { Self { name, - entries: GcRefCell::new(ArrayVec::new()), + entries: Cell::new(ArrayVec::new()), megamorphic: Cell::new(false), } } + #[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 +116,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 +128,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 +146,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(()) From 8c317428d243f2e8d371fe2342963186505c16a4 Mon Sep 17 00:00:00 2001 From: mansiverma897993 Date: Thu, 18 Jun 2026 10:39:30 +0530 Subject: [PATCH 2/3] style: format inline_cache/mod.rs --- core/engine/src/vm/inline_cache/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/engine/src/vm/inline_cache/mod.rs b/core/engine/src/vm/inline_cache/mod.rs index b4b86f27765..395c50f49dd 100644 --- a/core/engine/src/vm/inline_cache/mod.rs +++ b/core/engine/src/vm/inline_cache/mod.rs @@ -53,7 +53,8 @@ impl Clone for InlineCache { 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") + let result = f + .debug_struct("InlineCache") .field("name", &self.name) .field("entries", &entries) .field("megamorphic", &self.megamorphic) From bbe3e96c9646bb2b7515eca2f33794c802df962c Mon Sep 17 00:00:00 2001 From: mansiverma897993 Date: Thu, 18 Jun 2026 11:12:35 +0530 Subject: [PATCH 3/3] perf: optimize inline cache cloning and struct layout --- core/engine/src/vm/inline_cache/mod.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/core/engine/src/vm/inline_cache/mod.rs b/core/engine/src/vm/inline_cache/mod.rs index 395c50f49dd..aaa59f24fa5 100644 --- a/core/engine/src/vm/inline_cache/mod.rs +++ b/core/engine/src/vm/inline_cache/mod.rs @@ -24,28 +24,32 @@ pub(crate) struct CacheEntry { } /// An inline cache entry for a property access. +#[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: 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 { - let entries = self.entries.take(); - let cloned_entries = entries.clone(); - self.entries.set(entries); + // 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), - megamorphic: self.megamorphic.clone(), } } } @@ -84,9 +88,9 @@ impl fmt::Display for InlineCache { impl InlineCache { pub(crate) fn new(name: JsString) -> Self { Self { + megamorphic: Cell::new(false), name, entries: Cell::new(ArrayVec::new()), - megamorphic: Cell::new(false), } }