Skip to content

Commit

Permalink
Prune on insertion limit
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed May 19, 2023
1 parent 8ef2583 commit e2b203b
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 8 deletions.
79 changes: 71 additions & 8 deletions boa_engine/src/object/shape/shared_shape/forward_transition.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Debug;

use boa_gc::{Finalize, Gc, GcRefCell, Trace, WeakGc};
use rustc_hash::FxHashMap;

Expand All @@ -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)]
Expand All @@ -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>,
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -53,7 +92,7 @@ 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.
Expand All @@ -63,7 +102,8 @@ impl ForwardTransition {
return;
};

transitions.retain(|_, v| v.is_upgradable());
transitions.insertion_count_since_prune = 0;
transitions.map.retain(|_, v| v.is_upgradable());
}

/// Prunes the [`WeakGc`]s that have been garbage collected.
Expand All @@ -73,6 +113,29 @@ impl ForwardTransition {
return;
};

transitions.retain(|_, v| v.is_upgradable());
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,
)
})
}
}
3 changes: 3 additions & 0 deletions boa_engine/src/object/shape/shared_shape/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
109 changes: 109 additions & 0 deletions boa_engine/src/object/shape/shared_shape/tests.rs
Original file line number Diff line number Diff line change
@@ -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)
);
}

0 comments on commit e2b203b

Please sign in to comment.