Browse Source

Prune collected shared shapes (#2941)

* Prune garbage collected `SharedShape`s

* Prune on insertion limit
pull/2977/head
Haled Odat 2 years ago committed by GitHub
parent
commit
c013caca22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 95
      boa_engine/src/object/shape/shared_shape/forward_transition.rs
  2. 9
      boa_engine/src/object/shape/shared_shape/mod.rs
  3. 109
      boa_engine/src/object/shape/shared_shape/tests.rs
  4. 7
      boa_gc/src/pointers/ephemeron.rs
  5. 5
      boa_gc/src/pointers/weak.rs

95
boa_engine/src/object/shape/shared_shape/forward_transition.rs

@ -1,3 +1,5 @@
use std::fmt::Debug;
use boa_gc::{Finalize, Gc, GcRefCell, Trace, WeakGc};
use rustc_hash::FxHashMap;
@ -6,7 +8,34 @@ use crate::object::JsPrototype;
use super::{Inner as SharedShapeInner, TransitionKey};
/// Maps transition key type to a [`SharedShapeInner`] transition.
type TransitionMap<T> = FxHashMap<T, WeakGc<SharedShapeInner>>;
#[derive(Debug, Trace, Finalize)]
struct TransitionMap<T: Debug + Trace + Finalize> {
map: FxHashMap<T, WeakGc<SharedShapeInner>>,
/// This counts the number of insertions after a prune operation.
insertion_count_since_prune: u8,
}
impl<T: Debug + Trace + Finalize> Default for TransitionMap<T> {
fn default() -> Self {
Self {
map: FxHashMap::default(),
insertion_count_since_prune: 0,
}
}
}
impl<T: Debug + Trace + Finalize> TransitionMap<T> {
fn get_and_increment_count(&mut self) -> u8 {
let result = self.insertion_count_since_prune;
// NOTE(HalidOdat): This is done so it overflows to 0, on every 256 insertion
// operations. Fulfills the prune condition every 256 insertions.
self.insertion_count_since_prune = self.insertion_count_since_prune.wrapping_add(1);
result
}
}
/// The internal representation of [`ForwardTransition`].
#[derive(Default, Debug, Trace, Finalize)]
@ -17,7 +46,7 @@ struct Inner {
/// Holds a forward reference to a previously created transition.
///
/// The reference is weak, therefore it can be garbage collected if it is not in use.
/// The reference is weak, therefore it can be garbage collected, if it's not in use.
#[derive(Default, Debug, Trace, Finalize)]
pub(super) struct ForwardTransition {
inner: GcRefCell<Inner>,
@ -28,14 +57,24 @@ impl ForwardTransition {
pub(super) fn insert_property(&self, key: TransitionKey, value: &Gc<SharedShapeInner>) {
let mut this = self.inner.borrow_mut();
let properties = this.properties.get_or_insert_with(Box::default);
properties.insert(key, WeakGc::new(value));
if properties.get_and_increment_count() == u8::MAX {
properties.map.retain(|_, v| v.is_upgradable());
}
properties.map.insert(key, WeakGc::new(value));
}
/// Insert a prototype transition.
pub(super) fn insert_prototype(&self, key: JsPrototype, value: &Gc<SharedShapeInner>) {
let mut this = self.inner.borrow_mut();
let prototypes = this.prototypes.get_or_insert_with(Box::default);
prototypes.insert(key, WeakGc::new(value));
if prototypes.get_and_increment_count() == u8::MAX {
prototypes.map.retain(|_, v| v.is_upgradable());
}
prototypes.map.insert(key, WeakGc::new(value));
}
/// Get a property transition, return [`None`] otherwise.
@ -44,7 +83,7 @@ impl ForwardTransition {
let Some(transitions) = this.properties.as_ref() else {
return None;
};
transitions.get(key).cloned()
transitions.map.get(key).cloned()
}
/// Get a prototype transition, return [`None`] otherwise.
@ -53,6 +92,50 @@ impl ForwardTransition {
let Some(transitions) = this.prototypes.as_ref() else {
return None;
};
transitions.get(key).cloned()
transitions.map.get(key).cloned()
}
/// Prunes the [`WeakGc`]s that have been garbage collected.
pub(super) fn prune_property_transitions(&self) {
let mut this = self.inner.borrow_mut();
let Some(transitions) = this.properties.as_deref_mut() else {
return;
};
transitions.insertion_count_since_prune = 0;
transitions.map.retain(|_, v| v.is_upgradable());
}
/// Prunes the [`WeakGc`]s that have been garbage collected.
pub(super) fn prune_prototype_transitions(&self) {
let mut this = self.inner.borrow_mut();
let Some(transitions) = this.prototypes.as_deref_mut() else {
return;
};
transitions.insertion_count_since_prune = 0;
transitions.map.retain(|_, v| v.is_upgradable());
}
#[cfg(test)]
pub(crate) fn property_transitions_count(&self) -> (usize, u8) {
let this = self.inner.borrow();
this.properties.as_ref().map_or((0, 0), |transitions| {
(
transitions.map.len(),
transitions.insertion_count_since_prune,
)
})
}
#[cfg(test)]
pub(crate) fn prototype_transitions_count(&self) -> (usize, u8) {
let this = self.inner.borrow();
this.prototypes.as_ref().map_or((0, 0), |transitions| {
(
transitions.map.len(),
transitions.insertion_count_since_prune,
)
})
}
}

9
boa_engine/src/object/shape/shared_shape/mod.rs

@ -1,6 +1,9 @@
mod forward_transition;
pub(crate) mod template;
#[cfg(test)]
mod tests;
use std::{collections::hash_map::RandomState, hash::Hash};
use bitflags::bitflags;
@ -192,6 +195,8 @@ impl SharedShape {
if let Some(inner) = shape.upgrade() {
return Self { inner };
}
self.forward_transitions().prune_prototype_transitions();
}
let new_inner_shape = Inner {
forward_transitions: ForwardTransition::default(),
@ -217,6 +222,8 @@ impl SharedShape {
if let Some(inner) = shape.upgrade() {
return Self { inner };
}
self.forward_transitions().prune_property_transitions();
}
let property_table = self.property_table().add_property_deep_clone_if_needed(
@ -266,6 +273,8 @@ impl SharedShape {
action,
};
}
self.forward_transitions().prune_property_transitions();
}
// The attribute change transitions, didn't change from accessor to data property or vice-versa.

109
boa_engine/src/object/shape/shared_shape/tests.rs

@ -0,0 +1,109 @@
use crate::{object::shape::slot::SlotAttributes, property::PropertyKey, JsObject, JsSymbol};
use super::{SharedShape, TransitionKey};
#[test]
fn test_prune_property_on_counter_limit() {
let shape = SharedShape::root();
for i in 0..255 {
assert_eq!(
shape.forward_transitions().property_transitions_count(),
(i, i as u8)
);
shape.insert_property_transition(TransitionKey {
property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()),
attributes: SlotAttributes::all(),
});
}
assert_eq!(
shape.forward_transitions().property_transitions_count(),
(255, 255)
);
boa_gc::force_collect();
{
shape.insert_property_transition(TransitionKey {
property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()),
attributes: SlotAttributes::all(),
});
}
assert_eq!(
shape.forward_transitions().property_transitions_count(),
(1, 0)
);
{
shape.insert_property_transition(TransitionKey {
property_key: PropertyKey::Symbol(JsSymbol::new(None).unwrap()),
attributes: SlotAttributes::all(),
});
}
assert_eq!(
shape.forward_transitions().property_transitions_count(),
(2, 1)
);
boa_gc::force_collect();
assert_eq!(
shape.forward_transitions().property_transitions_count(),
(2, 1)
);
}
#[test]
fn test_prune_prototype_on_counter_limit() {
let shape = SharedShape::root();
assert_eq!(
shape.forward_transitions().prototype_transitions_count(),
(0, 0)
);
for i in 0..255 {
assert_eq!(
shape.forward_transitions().prototype_transitions_count(),
(i, i as u8)
);
shape.change_prototype_transition(Some(JsObject::with_null_proto()));
}
boa_gc::force_collect();
assert_eq!(
shape.forward_transitions().prototype_transitions_count(),
(255, 255)
);
{
shape.change_prototype_transition(Some(JsObject::with_null_proto()));
}
assert_eq!(
shape.forward_transitions().prototype_transitions_count(),
(1, 0)
);
{
shape.change_prototype_transition(Some(JsObject::with_null_proto()));
}
assert_eq!(
shape.forward_transitions().prototype_transitions_count(),
(2, 1)
);
boa_gc::force_collect();
assert_eq!(
shape.forward_transitions().prototype_transitions_count(),
(2, 1)
);
}

7
boa_gc/src/pointers/ephemeron.rs

@ -31,6 +31,13 @@ impl<K: Trace + ?Sized, V: Trace + Clone> Ephemeron<K, V> {
// `inner_ptr`.
unsafe { self.inner_ptr.get().as_ref().value().cloned() }
}
/// Checks if the [`Ephemeron`] has a value.
pub fn has_value(&self) -> bool {
// SAFETY: this is safe because `Ephemeron` is tracked to always point to a valid pointer
// `inner_ptr`.
unsafe { self.inner_ptr.get().as_ref().value().is_some() }
}
}
impl<K: Trace + ?Sized, V: Trace> Ephemeron<K, V> {

5
boa_gc/src/pointers/weak.rs

@ -23,6 +23,11 @@ impl<T: Trace> WeakGc<T> {
pub fn upgrade(&self) -> Option<Gc<T>> {
self.inner.value()
}
/// Check if the [`WeakGc`] can be upgraded.
pub fn is_upgradable(&self) -> bool {
self.inner.has_value()
}
}
impl<T: Trace> Clone for WeakGc<T> {

Loading…
Cancel
Save