use std::{marker::PhantomData, num::NonZeroU32};
use hashbrown::{HashTable, hash_table::Entry};
use crate::{
ecmascript::{Agent, InternalMethods, Object, PropertyKey, TryResult, Value},
engine::{Bindable, GcToken, HeapRootData, NoGcScope, bindable_handle},
heap::{
AtomicBits, BitRange, CompactionLists, HeapMarkAndSweep, HeapSweepWeakReference,
PropertyKeyHeap, WeakReference, WorkQueues, sweep_heap_vector_values,
},
};
use super::ObjectShape;
#[derive(Debug)]
pub(crate) struct Caches<'a> {
property_lookup_cache_lookup_table:
HashTable<(PropertyKey<'a>, WeakReference<PropertyLookupCache<'a>>)>,
property_lookup_caches: Vec<PropertyLookupCacheRecord<'a>>,
property_lookup_cache_prototypes: Vec<PropertyLookupCacheRecordPrototypes<'a>>,
current_cache_to_populate: Option<CacheToPopulate<'a>>,
}
#[derive(Debug)]
pub(crate) struct CacheToPopulate<'a> {
pub(crate) receiver: Value<'a>,
pub(crate) cache: PropertyLookupCache<'a>,
pub(crate) key: PropertyKey<'a>,
pub(crate) shape: ObjectShape<'a>,
}
impl<'a> Caches<'a> {
pub(crate) fn with_capacity(capacity: usize) -> Self {
Self {
property_lookup_cache_lookup_table: HashTable::with_capacity(capacity),
property_lookup_caches: Vec::with_capacity(capacity),
property_lookup_cache_prototypes: Vec::with_capacity(capacity),
current_cache_to_populate: None,
}
}
pub(crate) fn invalidate_caches_on_intrinsic_shape_property_addition(
agent: &mut Agent,
o: Object,
shape: ObjectShape,
key: PropertyKey,
addition_index: u32,
gc: NoGcScope,
) {
let o = o.unbind();
let shape = shape.unbind();
let key = key.unbind();
let self_index = PropertyOffset::new(addition_index);
let prototype_index = PropertyOffset::new_prototype(addition_index);
let hash = key.heap_hash(agent);
let Some((_, WeakReference(cache))) = agent
.heap
.caches
.property_lookup_cache_lookup_table
.find(hash, |(k, _)| k == &key)
else {
return;
};
let cache = *cache;
let mut prototype_chain = if let Some(proto) = shape.get_prototype(agent) {
let mut protos = vec![proto];
let mut proto = proto.try_get_prototype_of(agent, gc);
while let TryResult::Continue(Some(p)) = proto {
protos.push(p);
proto = p.try_get_prototype_of(agent, gc);
}
protos
} else {
vec![]
};
prototype_chain.sort();
let affected_shapes = {
let mut cache = cache;
let mut shapes: Vec<ObjectShape> = vec![];
let caches = &agent.heap.caches.property_lookup_caches;
loop {
let caches = &caches[cache.get_index()];
shapes.extend(caches.shapes.iter().filter_map(|s| s.as_ref()));
if let Some(next) = caches.next {
cache = next;
continue;
}
break;
}
shapes.sort();
shapes.dedup();
shapes.retain(|s| {
s == &shape || prototype_chain_includes_object(agent, s.get_prototype(agent), o, gc)
});
shapes
};
let mut cache = cache;
let caches = &mut agent.heap.caches.property_lookup_caches;
let prototypes = &mut agent.heap.caches.property_lookup_cache_prototypes;
loop {
let caches = &mut caches[cache.get_index()];
let prototypes = &mut prototypes[cache.get_index()];
for (s, offset, proto) in caches
.shapes
.iter_mut()
.zip(caches.offsets.iter_mut())
.zip(prototypes.prototypes.iter_mut())
.filter_map(|((s, offset), proto)| {
affected_shapes.binary_search(s.as_ref()?).ok()?;
if
offset.is_unset() ||
prototype_chain.binary_search(proto.as_ref()?).is_ok()
{
Some((s, offset, proto))
} else {
None
}
})
{
let self_cache = s.as_mut().unwrap() == &shape;
let addition_index = if self_cache {
self_index
} else {
prototype_index
};
let Some(addition_index) = addition_index else {
*s = None;
*offset = PropertyOffset(0);
*proto = None;
continue;
};
*offset = addition_index;
if s.as_mut().unwrap() == &shape {
*proto = None;
} else {
*proto = Some(o);
}
}
if let Some(next) = caches.next {
cache = next;
continue;
}
break;
}
}
pub(crate) fn invalidate_caches_on_intrinsic_shape_property_removal(
agent: &mut Agent,
o: Object,
shape: ObjectShape,
removal_index: u32,
) {
let caches = &mut agent.heap.caches;
let o = o.unbind();
let shape = shape.unbind();
let self_index = PropertyOffset::new(removal_index);
let proto_index = PropertyOffset::new_prototype(removal_index);
if self_index.is_none() && proto_index.is_none() {
return;
}
let removal_index = removal_index as u16;
for ((s, offset), proto) in caches
.property_lookup_caches
.iter_mut()
.zip(caches.property_lookup_cache_prototypes.iter_mut())
.flat_map(|(cache, prototypes)| {
cache
.shapes
.iter_mut()
.zip(cache.offsets.iter_mut())
.zip(prototypes.prototypes.iter_mut())
})
.filter(|((s, offset), proto)| {
!offset.is_unset()
&& (*s == &Some(shape)
&& self_index.as_ref().is_some_and(|i| {
offset.get_property_offset() >= i.get_property_offset()
})
|| *proto == &Some(o)
&& proto_index.as_ref().is_some_and(|i| {
offset.get_property_offset() >= i.get_property_offset()
}))
})
{
let index = if s == &Some(shape) {
self_index.unwrap()
} else {
proto_index.unwrap()
};
let property_offset = index.get_property_offset();
if property_offset == removal_index {
*s = None;
*offset = PropertyOffset(0);
*proto = None;
} else {
assert_ne!(property_offset, 0);
offset.0 -= 1;
}
}
}
pub(crate) fn invalidate_caches_on_intrinsic_shape_prototype_change(
agent: &mut Agent,
o: Object,
shape: ObjectShape,
old_prototype: Option<Object>,
new_prototype: Option<Object>,
gc: NoGcScope,
) {
let mut prototype_chain = if let Some(proto) = old_prototype {
let mut protos = vec![proto];
let mut proto = proto.try_get_prototype_of(agent, gc);
while let TryResult::Continue(Some(p)) = proto {
protos.push(p);
proto = p.try_get_prototype_of(agent, gc);
}
protos
} else {
vec![]
};
prototype_chain.sort();
let affected_shapes = {
let mut shapes = agent
.heap
.caches
.property_lookup_caches
.iter()
.flat_map(|c| c.shapes.iter())
.filter_map(|s| s.as_ref())
.cloned()
.collect::<Vec<_>>();
shapes.sort();
shapes.dedup();
shapes.retain(|s| {
s == &shape || prototype_chain_includes_object(agent, s.get_prototype(agent), o, gc)
});
shapes
};
for (s, offset, proto) in agent
.heap
.caches
.property_lookup_caches
.iter_mut()
.zip(
agent
.heap
.caches
.property_lookup_cache_prototypes
.iter_mut(),
)
.flat_map(|(caches, prototypes)| {
caches
.shapes
.iter_mut()
.zip(caches.offsets.iter_mut())
.zip(prototypes.prototypes.iter_mut())
.filter_map(|((s, offset), proto)| {
affected_shapes.binary_search(s.as_ref()?).ok()?;
if
offset.is_unset() && new_prototype.is_some() ||
prototype_chain.binary_search(proto.as_ref()?).is_ok()
{
Some((s, offset, proto))
} else {
None
}
})
})
{
if new_prototype.is_some() {
*s = None;
*offset = PropertyOffset(0);
*proto = None;
} else {
*offset = PropertyOffset::UNSET;
*proto = None;
}
}
}
pub(crate) fn take_current_cache_to_populate(
&mut self,
key: PropertyKey,
) -> Option<CacheToPopulate<'a>> {
if self
.current_cache_to_populate
.as_ref()
.is_some_and(|p| p.key == key)
{
self.current_cache_to_populate.take()
} else {
None
}
}
pub(crate) fn clear_current_cache_to_populate(&mut self) {
self.current_cache_to_populate = None;
}
pub(crate) fn set_current_cache(
&mut self,
shape: ObjectShape,
key: PropertyKey,
receiver: Value,
cache: PropertyLookupCache,
) {
let previous = self.current_cache_to_populate.replace(CacheToPopulate {
receiver: receiver.unbind(),
cache: cache.unbind(),
key: key.unbind(),
shape: shape.unbind(),
});
debug_assert!(previous.is_none());
}
pub(crate) fn len(&self) -> usize {
self.property_lookup_caches.len()
}
}
fn prototype_chain_includes_object(
agent: &mut Agent,
prototype: Option<Object>,
object: Object,
gc: NoGcScope,
) -> bool {
let Some(prototype) = prototype else {
return false;
};
if prototype == object {
true
} else if let TryResult::Continue(prototype) = prototype.try_get_prototype_of(agent, gc) {
prototype_chain_includes_object(agent, prototype, object, gc)
} else {
false
}
}
impl Caches<'static> {
pub(crate) fn mark_cache(&self, index: usize, queues: &mut WorkQueues) {
self.property_lookup_caches[index].mark_values(queues);
self.property_lookup_cache_prototypes[index].mark_values(queues);
}
pub(crate) fn sweep_cache(
&mut self,
compactions: &CompactionLists,
range: &BitRange,
bits: &[AtomicBits],
) {
sweep_heap_vector_values(&mut self.property_lookup_caches, compactions, range, bits);
sweep_heap_vector_values(
&mut self.property_lookup_cache_prototypes,
compactions,
range,
bits,
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct PropertyLookupCache<'a>(NonZeroU32, PhantomData<&'a GcToken>);
impl<'a> PropertyLookupCache<'a> {
pub(crate) fn get(agent: &Agent, key: PropertyKey<'a>) -> Option<PropertyLookupCache<'a>> {
let hash = key.heap_hash(agent);
agent
.heap
.caches
.property_lookup_cache_lookup_table
.find(hash, |(k, _)| *k == key)
.map(|(_, c)| c.0)
}
pub(crate) fn new(agent: &mut Agent, key: PropertyKey<'a>) -> PropertyLookupCache<'a> {
let hash = key.heap_hash(agent);
let caches = &mut agent.heap.caches;
let property_key_heap =
PropertyKeyHeap::new(&mut agent.heap.strings, &mut agent.heap.symbols);
let entry = caches.property_lookup_cache_lookup_table.entry(
hash,
|(k, _)| *k == key,
|(k, _)| k.heap_hash(&property_key_heap),
);
match entry {
Entry::Occupied(e) => e.get().1.0,
Entry::Vacant(e) => {
caches
.property_lookup_caches
.push(PropertyLookupCacheRecord::new());
caches
.property_lookup_cache_prototypes
.push(PropertyLookupCacheRecordPrototypes::new());
let cache = PropertyLookupCache::last(&caches.property_lookup_caches);
e.insert((key.unbind(), WeakReference(cache.unbind())));
cache
}
}
}
pub(crate) fn find_cached_property_offset(
self,
agent: &Agent,
shape: ObjectShape<'a>,
) -> Option<(PropertyOffset, Option<Object<'a>>)> {
let caches = &agent.heap.caches;
let record = &caches.property_lookup_caches[self.get_index()];
if let Some((i, offset)) = record.find(shape) {
let prototype = if !offset.is_unset() && offset.is_prototype_property() {
let prototype = caches.property_lookup_cache_prototypes[self.get_index()]
.prototypes[i as usize]
.unwrap();
Some(prototype)
} else {
debug_assert!(
caches.property_lookup_cache_prototypes[self.get_index()].prototypes
[i as usize]
.is_none()
);
None
};
Some((offset, prototype))
} else if let Some(next) = record.next {
next.find_cached_property_offset(agent, shape)
} else {
None
}
}
pub(crate) fn insert_unset(self, agent: &mut Agent, shape: ObjectShape<'a>) {
debug_assert!(self.find_cached_property_offset(agent, shape).is_none());
let offset = PropertyOffset::UNSET;
let caches = &mut agent.heap.caches;
let mut cache = self;
let next_to_create = PropertyLookupCache::from_index(caches.property_lookup_caches.len());
loop {
let (record, _) = cache.get_record_mut(caches);
if record.insert(shape, offset).is_some() {
return;
}
if let Some(next) = record.next {
cache = next;
continue;
}
record.next = Some(next_to_create);
caches
.property_lookup_caches
.push(PropertyLookupCacheRecord::with_shape_and_offset(shape, offset).unbind());
caches
.property_lookup_cache_prototypes
.push(PropertyLookupCacheRecordPrototypes::new());
let cache = PropertyLookupCache::last(&caches.property_lookup_caches);
debug_assert_eq!(cache, next_to_create);
break;
}
}
pub(crate) fn insert_lookup_offset(
self,
agent: &mut Agent,
shape: ObjectShape<'a>,
index: u32,
) {
debug_assert!(self.find_cached_property_offset(agent, shape).is_none());
let Some(offset) = PropertyOffset::new(index) else {
return;
};
let caches = &mut agent.heap.caches;
let mut cache = self;
let next_to_create = PropertyLookupCache::from_index(caches.property_lookup_caches.len());
loop {
let (record, _) = cache.get_record_mut(caches);
if record.insert(shape, offset).is_some() {
return;
}
if let Some(next) = record.next {
cache = next;
continue;
}
record.next = Some(next_to_create);
caches
.property_lookup_caches
.push(PropertyLookupCacheRecord::with_shape_and_offset(shape, offset).unbind());
caches
.property_lookup_cache_prototypes
.push(PropertyLookupCacheRecordPrototypes::new());
let cache = PropertyLookupCache::last(&caches.property_lookup_caches);
debug_assert_eq!(cache, next_to_create);
break;
}
}
pub(crate) fn insert_lookup_offset_if_not_found(
self,
agent: &mut Agent,
shape: ObjectShape<'a>,
index: u32,
) {
let Some(offset) = PropertyOffset::new(index) else {
return;
};
let caches = &mut agent.heap.caches;
let mut cache = self;
let next_to_create = PropertyLookupCache::from_index(caches.property_lookup_caches.len());
loop {
let (record, _) = cache.get_record_mut(caches);
if record.insert_if_not_found(shape, offset).is_some() {
return;
}
if let Some(next) = record.next {
cache = next;
continue;
}
record.next = Some(next_to_create);
caches
.property_lookup_caches
.push(PropertyLookupCacheRecord::with_shape_and_offset(shape, offset).unbind());
caches
.property_lookup_cache_prototypes
.push(PropertyLookupCacheRecordPrototypes::new());
let cache = PropertyLookupCache::last(&caches.property_lookup_caches);
debug_assert_eq!(cache, next_to_create);
break;
}
}
pub(crate) fn insert_prototype_lookup_offset(
self,
agent: &mut Agent,
shape: ObjectShape<'a>,
index: u32,
prototype: Object<'a>,
) {
debug_assert!(self.find_cached_property_offset(agent, shape).is_none());
let Some(offset) = PropertyOffset::new_prototype(index) else {
return;
};
let caches = &mut agent.heap.caches;
let mut cache = self;
let next_to_create = PropertyLookupCache::from_index(caches.property_lookup_caches.len());
loop {
let (record, prototypes) = cache.get_record_mut(caches);
if let Some(i) = record.insert(shape, offset) {
debug_assert!(offset.is_prototype_property());
let previous = prototypes.prototypes[i as usize].replace(prototype.unbind());
debug_assert!(previous.is_none());
return;
}
if let Some(next) = record.next {
cache = next;
continue;
}
record.next = Some(next_to_create);
caches
.property_lookup_caches
.push(PropertyLookupCacheRecord::with_shape_and_offset(shape, offset).unbind());
caches
.property_lookup_cache_prototypes
.push(PropertyLookupCacheRecordPrototypes::with_prototype(prototype).unbind());
let cache = PropertyLookupCache::last(&caches.property_lookup_caches);
debug_assert_eq!(cache, next_to_create);
break;
}
}
#[inline(always)]
fn from_index(index: usize) -> Self {
Self(
NonZeroU32::new(u32::try_from(index).unwrap().checked_add(1).unwrap()).unwrap(),
PhantomData,
)
}
#[inline(always)]
fn last(caches: &[PropertyLookupCacheRecord<'a>]) -> Self {
Self(
NonZeroU32::new(u32::try_from(caches.len()).unwrap()).unwrap(),
PhantomData,
)
}
#[inline(always)]
pub(crate) fn get_index(self) -> usize {
(self.0.get() - 1) as usize
}
#[inline(always)]
fn get_record_mut<'c>(
self,
caches: &'c mut Caches<'static>,
) -> (
&'c mut PropertyLookupCacheRecord<'static>,
&'c mut PropertyLookupCacheRecordPrototypes<'static>,
) {
let index = self.get_index();
(
&mut caches.property_lookup_caches[index],
&mut caches.property_lookup_cache_prototypes[index],
)
}
}
bindable_handle!(PropertyLookupCache);
impl From<PropertyLookupCache<'_>> for HeapRootData {
fn from(value: PropertyLookupCache<'_>) -> Self {
Self::PropertyLookupCache(value.unbind())
}
}
impl TryFrom<HeapRootData> for PropertyLookupCache<'_> {
type Error = ();
fn try_from(value: HeapRootData) -> Result<Self, Self::Error> {
match value {
HeapRootData::PropertyLookupCache(p) => Ok(p),
_ => Err(()),
}
}
}
const N: usize = 4;
#[derive(Debug)]
pub(crate) struct PropertyLookupCacheRecord<'a> {
shapes: [Option<ObjectShape<'a>>; N],
offsets: [PropertyOffset; N],
next: Option<PropertyLookupCache<'a>>,
}
impl<'a> PropertyLookupCacheRecord<'a> {
const fn new() -> Self {
Self {
shapes: [None; N],
offsets: [PropertyOffset(0); N],
next: None,
}
}
fn with_shape_and_offset(shape: ObjectShape<'a>, offset: PropertyOffset) -> Self {
Self {
shapes: [Some(shape), None, None, None],
offsets: [
offset,
PropertyOffset(0),
PropertyOffset(0),
PropertyOffset(0),
],
next: None,
}
}
fn find(&self, shape: ObjectShape<'a>) -> Option<(u8, PropertyOffset)> {
self.shapes
.iter()
.enumerate()
.find(|(_, s)| **s == Some(shape))
.map(|(i, _)| (i as u8, self.offsets[i]))
}
fn insert(&mut self, shape: ObjectShape, offset: PropertyOffset) -> Option<u8> {
if let Some((i, slot)) = self
.shapes
.iter_mut()
.enumerate()
.find(|(_, s)| s.is_none())
{
*slot = Some(shape.unbind());
self.offsets[i] = offset;
Some(i as u8)
} else {
None
}
}
fn insert_if_not_found(&mut self, shape: ObjectShape, offset: PropertyOffset) -> Option<u8> {
if let Some((i, slot)) = self
.shapes
.iter_mut()
.enumerate()
.find(|(_, s)| s.is_none_or(|s| s == shape))
{
if slot == &Some(shape.unbind()) {
debug_assert_eq!(self.offsets[i], offset);
return Some(i as u8);
}
*slot = Some(shape.unbind());
self.offsets[i] = offset;
Some(i as u8)
} else {
None
}
}
}
bindable_handle!(PropertyLookupCacheRecord);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct PropertyOffset(u16);
impl PropertyOffset {
const PROTOTYPE_BIT_MASK: u16 = 0x8000;
const CUSTOM_STORAGE_BIT_MASK: u16 = 0x4000;
const UNSET_BIT_MASK: u16 = 0x2000;
const OFFSET_BIT_MASK: u16 = 0x1FFF;
const UNSET: Self = Self(Self::UNSET_BIT_MASK);
#[inline(always)]
pub(crate) const fn new(offset: u32) -> Option<Self> {
let masked = offset & Self::OFFSET_BIT_MASK as u32;
if masked == offset {
Some(Self(masked as u16))
} else {
None
}
}
#[inline(always)]
pub(crate) const fn new_prototype(offset: u32) -> Option<Self> {
let masked = offset & Self::OFFSET_BIT_MASK as u32;
if masked == offset {
Some(Self(masked as u16 | Self::PROTOTYPE_BIT_MASK))
} else {
None
}
}
#[inline(always)]
pub(crate) const fn new_custom(offset: u32) -> Option<Self> {
let masked = offset & Self::OFFSET_BIT_MASK as u32;
if masked == offset {
Some(Self(masked as u16 | Self::CUSTOM_STORAGE_BIT_MASK))
} else {
None
}
}
#[inline(always)]
pub(crate) const fn is_unset(self) -> bool {
(self.0 & Self::UNSET_BIT_MASK) > 0
}
#[inline(always)]
pub(crate) const fn is_prototype_property(self) -> bool {
(self.0 & Self::PROTOTYPE_BIT_MASK) > 0
}
#[inline(always)]
pub(crate) const fn is_custom_property(self) -> bool {
(self.0 & Self::CUSTOM_STORAGE_BIT_MASK) > 0
}
#[inline(always)]
pub(crate) const fn get_property_offset(self) -> u16 {
debug_assert!(!self.is_unset());
self.0 & Self::OFFSET_BIT_MASK
}
}
#[derive(Debug)]
#[repr(transparent)]
pub(crate) struct PropertyLookupCacheRecordPrototypes<'a> {
prototypes: [Option<Object<'a>>; N],
}
impl<'a> PropertyLookupCacheRecordPrototypes<'a> {
pub(crate) const fn new() -> Self {
Self {
prototypes: [None; N],
}
}
pub(crate) const fn with_prototype(prototype: Object<'a>) -> Self {
Self {
prototypes: [Some(prototype), None, None, None],
}
}
}
bindable_handle!(PropertyLookupCacheRecordPrototypes);
impl HeapMarkAndSweep for Caches<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
property_lookup_cache_lookup_table,
property_lookup_caches: _,
property_lookup_cache_prototypes: _,
current_cache_to_populate: current_property_lookup_cache,
} = self;
for (key, _) in property_lookup_cache_lookup_table.iter() {
key.mark_values(queues);
}
current_property_lookup_cache.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
property_lookup_cache_lookup_table,
property_lookup_caches: _,
property_lookup_cache_prototypes: _,
current_cache_to_populate: current_property_lookup_cache,
} = self;
property_lookup_cache_lookup_table.retain(|(key, cache)| {
let Some(new_cache) = cache.sweep_weak_reference(compactions) else {
return false;
};
key.sweep_values(compactions);
*cache = new_cache;
true
});
current_property_lookup_cache.sweep_values(compactions);
}
}
impl HeapMarkAndSweep for CacheToPopulate<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
receiver,
cache,
key,
shape,
} = self;
receiver.mark_values(queues);
cache.mark_values(queues);
key.mark_values(queues);
shape.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
receiver,
cache,
key,
shape,
} = self;
receiver.sweep_values(compactions);
cache.sweep_values(compactions);
key.sweep_values(compactions);
shape.sweep_values(compactions);
}
}
impl HeapSweepWeakReference for PropertyLookupCache<'_> {
fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
compactions
.caches
.shift_weak_non_zero_u32_index(self.0)
.map(|i| Self(i, PhantomData))
}
}
impl HeapMarkAndSweep for PropertyLookupCache<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.caches.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.caches.shift_non_zero_u32_index(&mut self.0);
}
}
impl HeapMarkAndSweep for PropertyLookupCacheRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
shapes,
offsets: _,
next: next_cache,
} = self;
shapes.as_slice().mark_values(queues);
next_cache.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
shapes,
offsets: _,
next: next_cache,
} = self;
shapes.as_mut_slice().sweep_values(compactions);
next_cache.sweep_values(compactions);
}
}
impl HeapMarkAndSweep for PropertyLookupCacheRecordPrototypes<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self { prototypes } = self;
prototypes.as_slice().mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self { prototypes } = self;
prototypes.as_mut_slice().sweep_values(compactions);
}
}