diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index 969ebb311..b07d6f017 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -948,7 +948,7 @@ fn perform_promise_group<'gc>( #[cfg(feature = "set")] Object::Map(map) => agent[map].size(), #[cfg(feature = "set")] - Object::Set(set) => agent[set].size(), + Object::Set(set) => set.get(agent).set_data.borrow().len() as u32, _ => 0, }; diff --git a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_constructor.rs b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_constructor.rs index c4b2bc7be..0f9e937bc 100644 --- a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_constructor.rs @@ -142,7 +142,7 @@ impl SetConstructor { let array_heap = ArrayHeap::new(elements, arrays); let primitive_heap = PrimitiveHeap::new(bigints, numbers, strings); - let set_heap_data = &mut sets[set].borrow_mut(&primitive_heap); + let mut set_heap_data = set.get_direct_mut(sets); let values = &mut set_heap_data.values; let set_data = set_heap_data.set_data.get_mut(); @@ -181,7 +181,7 @@ impl SetConstructor { } hashbrown::hash_table::Entry::Vacant(vacant) => { vacant.insert(next_index); - values.push(Some(value.unbind())); + values.push(Some(value)); } } }); diff --git a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs index 04cbd2ddf..5bc08d6fd 100644 --- a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator_prototype.rs @@ -60,14 +60,14 @@ impl SetIteratorPrototype { // b. Let entries be set.[[SetData]]. // c. Let numEntries be the number of elements in entries. // d. Repeat, while index < numEntries, - while agent[iterator].next_index < agent[set].values(gc).len() { + while agent[iterator].next_index < set.get(agent).values.len() { // i. Let e be entries[index]. // ii. Set index to index + 1. let index = agent[iterator].next_index; agent[iterator].next_index += 1; // iii. if e is not EMPTY, then - let Some(e) = agent[set].values(gc)[index] else { + let Some(e) = set.get(agent).values[index] else { continue; }; @@ -91,7 +91,7 @@ impl SetIteratorPrototype { .map(|o| o.into_value()); } - debug_assert_eq!(agent[iterator].next_index, agent[set].values(gc).len()); + debug_assert_eq!(agent[iterator].next_index, set.get(agent).values.len()); // e. Return undefined. agent[iterator].set = None; diff --git a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_prototype.rs b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_prototype.rs index 6507ce952..1a2aa7080 100644 --- a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_prototype.rs @@ -115,8 +115,8 @@ impl SetPrototype { // 3. Set value to CanonicalizeKeyedCollectionKey(value). let value = canonicalize_keyed_collection_key(numbers, value); - let set_heap_data = &mut sets[s].borrow_mut(&primitive_heap); - let values = &mut set_heap_data.values; + let set_heap_data = s.get_direct_mut(sets); + let values = set_heap_data.values; let set_data = set_heap_data.set_data.get_mut(); let hasher = |value: Value| { let mut hasher = AHasher::default(); @@ -140,7 +140,7 @@ impl SetPrototype { // 5. Append value to S.[[SetData]]. let index = u32::try_from(values.len()).unwrap(); entry.insert(index); - values.push(Some(value.unbind())); + values.push(Some(value)); } // i. Return S. // 6. Return S. @@ -166,7 +166,9 @@ impl SetPrototype { // 3. For each element e of S.[[SetData]], do // a. Replace the element of S.[[SetData]] whose value is e with an // element whose value is EMPTY. - agent[s].clear(); + let data = s.get_mut(agent); + data.set_data.borrow_mut().clear(); + data.values.clear(); // 4. Return undefined. Ok(Value::Undefined) } @@ -206,8 +208,8 @@ impl SetPrototype { value.hash(&primitive_heap, &mut hasher); hasher.finish() }; - let set_heap_data = &mut sets[s].borrow_mut(&primitive_heap); - let values = &mut set_heap_data.values; + let set_heap_data = s.get_direct_mut(sets); + let values = set_heap_data.values; let set_data = set_heap_data.set_data.get_mut(); // 4. For each element e of S.[[SetData]], do if let Ok(entry) = set_data.find_entry(value_hash, |hash_equal_index| { @@ -309,7 +311,7 @@ impl SetPrototype { // 5. Let numEntries be the number of elements in entries. // Note: We must use the values vector length, not the size. The size // does not contain empty slots. - let mut num_entries = agent[s].values(gc.nogc()).len() as u32; + let mut num_entries = s.get(agent).values.len() as u32; let callback_fn = callback_fn.scope(agent, nogc); let scoped_s = s.scope(agent, nogc); @@ -320,7 +322,7 @@ impl SetPrototype { // 7. Repeat, while index < numEntries, while index < num_entries { // a. Let e be entries[index]. - let e = agent[s].values(gc.nogc())[index as usize]; + let e = s.get(agent).values[index as usize]; // b. Set index to index + 1. index += 1; // c. If e is not EMPTY, then @@ -341,7 +343,7 @@ impl SetPrototype { // ii. NOTE: The number of elements in entries may have increased during execution of callbackfn. // iii. Set numEntries to the number of elements in entries. s = scoped_s.get(agent).bind(gc.nogc()); - num_entries = agent[s].values(gc.nogc()).len() as u32; + num_entries = s.get(agent).values.len() as u32; } } // 8. Return undefined. @@ -370,8 +372,8 @@ impl SetPrototype { .. } = &agent.heap; let primitive_heap = PrimitiveHeap::new(bigints, numbers, strings); - let set_heap_data = &sets[s].borrow(&primitive_heap); - let values = &set_heap_data.values; + let set_heap_data = s.get_direct(sets); + let values = set_heap_data.values; let set_data = set_heap_data.set_data.borrow(); // 3. Set value to CanonicalizeKeyedCollectionKey(value). @@ -410,7 +412,7 @@ impl SetPrototype { // 2. Perform ? RequireInternalSlot(S, [[SetData]]). let s = require_set_data_internal_slot(agent, this_value, gc)?; // 3. Let size be SetDataSize(S.[[SetData]]). - let size = agent[s].size(); + let size = s.get(agent).set_data.borrow().len() as u32; // 4. Return 𝔽(size). Ok(Number::from(size).into_value()) } diff --git a/nova_vm/src/ecmascript/builtins/set.rs b/nova_vm/src/ecmascript/builtins/set.rs index dc1ee66f8..c55aeef8e 100644 --- a/nova_vm/src/ecmascript/builtins/set.rs +++ b/nova_vm/src/ecmascript/builtins/set.rs @@ -2,8 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use core::ops::{Index, IndexMut}; - use crate::{ Heap, ecmascript::{ @@ -20,7 +18,8 @@ use crate::{ }, }; -use self::data::SetHeapData; +use self::data::{SetHeapData, SetHeapDataMut, SetHeapDataRef}; +use soavec::SoAVec; pub mod data; @@ -28,7 +27,7 @@ pub mod data; #[repr(transparent)] pub struct Set<'a>(BaseIndex<'a, SetHeapData<'static>>); -impl Set<'_> { +impl<'gc> Set<'gc> { pub(crate) const fn _def() -> Self { Self(BaseIndex::from_u32_index(0)) } @@ -36,6 +35,40 @@ impl Set<'_> { pub(crate) const fn get_index(self) -> usize { self.0.into_index() } + + #[inline(always)] + pub(crate) fn get<'a>(self, agent: &'a Agent) -> SetHeapDataRef<'a, 'gc> { + self.get_direct(&agent.heap.sets) + } + + #[inline(always)] + pub(crate) fn get_mut<'a>(self, agent: &'a mut Agent) -> SetHeapDataMut<'a, 'gc> { + self.get_direct_mut(&mut agent.heap.sets) + } + + #[inline(always)] + pub(crate) fn get_direct<'a>( + self, + sets: &'a SoAVec>, + ) -> SetHeapDataRef<'a, 'gc> { + sets.get(self.0.into_u32_index()) + .expect("Invalid Set reference") + } + + #[inline(always)] + pub(crate) fn get_direct_mut<'a>( + self, + sets: &'a mut SoAVec>, + ) -> SetHeapDataMut<'a, 'gc> { + // SAFETY: Lifetime transmute to thread GC lifetime to temporary heap + // reference. + unsafe { + core::mem::transmute::, SetHeapDataMut<'a, 'gc>>( + sets.get_mut(self.0.into_u32_index()) + .expect("Invalid Set reference"), + ) + } + } } bindable_handle!(Set); @@ -81,12 +114,12 @@ impl<'a> InternalSlots<'a> for Set<'a> { #[inline(always)] fn get_backing_object(self, agent: &Agent) -> Option> { - agent[self].object_index + self.get(agent).object_index.unbind() } fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { assert!( - agent[self] + self.get_mut(agent) .object_index .replace(backing_object.unbind()) .is_none() @@ -112,34 +145,6 @@ impl HeapSweepWeakReference for Set<'static> { } } -impl Index> for Agent { - type Output = SetHeapData<'static>; - - fn index(&self, index: Set) -> &Self::Output { - &self.heap.sets[index] - } -} - -impl IndexMut> for Agent { - fn index_mut(&mut self, index: Set) -> &mut Self::Output { - &mut self.heap.sets[index] - } -} - -impl Index> for Vec> { - type Output = SetHeapData<'static>; - - fn index(&self, index: Set) -> &Self::Output { - self.get(index.get_index()).expect("Set out of bounds") - } -} - -impl IndexMut> for Vec> { - fn index_mut(&mut self, index: Set) -> &mut Self::Output { - self.get_mut(index.get_index()).expect("Set out of bounds") - } -} - impl TryFrom for Set<'_> { type Error = (); @@ -155,8 +160,52 @@ impl TryFrom for Set<'_> { impl<'a> CreateHeapData, Set<'a>> for Heap { fn create(&mut self, data: SetHeapData<'a>) -> Set<'a> { - self.sets.push(data.unbind()); + let i = self.sets.len(); + self.sets + .push(data.unbind()) + .expect("Failed to allocate Set"); self.alloc_counter += core::mem::size_of::>(); - Set(BaseIndex::last(&self.sets)) + Set(BaseIndex::from_u32_index(i)) + } +} + +impl HeapMarkAndSweep for SetHeapDataRef<'_, 'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + set_data: _, + values, + object_index, + needs_primitive_rehashing: _, + } = self; + values.mark_values(queues); + object_index.mark_values(queues); + } + + fn sweep_values(&mut self, _: &CompactionLists) { + unreachable!() + } +} + +impl HeapMarkAndSweep for SetHeapDataMut<'_, 'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + set_data: _, + values, + object_index, + needs_primitive_rehashing: _, + } = self; + values.mark_values(queues); + object_index.mark_values(queues); + } + + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + set_data: _, + values, + object_index, + needs_primitive_rehashing: _, + } = self; + values.sweep_values(compactions); + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/set/data.rs b/nova_vm/src/ecmascript/builtins/set/data.rs index 5efc024a9..f10448d6f 100644 --- a/nova_vm/src/ecmascript/builtins/set/data.rs +++ b/nova_vm/src/ecmascript/builtins/set/data.rs @@ -3,12 +3,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::{ - ecmascript::types::{ - BIGINT_DISCRIMINANT, HeapNumber, HeapString, NUMBER_DISCRIMINANT, OrdinaryObject, - STRING_DISCRIMINANT, Value, bigint::HeapBigInt, - }, - engine::context::{Bindable, NoGcScope, bindable_handle}, - heap::{CompactionLists, HeapMarkAndSweep, PrimitiveHeapIndexable, WorkQueues}, + ecmascript::types::{OrdinaryObject, Value}, + engine::context::bindable_handle, + heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; use ahash::AHasher; use core::{ @@ -16,7 +13,7 @@ use core::{ hash::{Hash, Hasher}, sync::atomic::{AtomicBool, Ordering}, }; -use hashbrown::{HashTable, hash_table::Entry}; +use hashbrown::HashTable; use soavec_derive::SoAble; #[derive(Debug, Default, SoAble)] @@ -38,115 +35,6 @@ pub struct SetHeapData<'a> { // pub(crate) observed: bool; } -impl<'a> SetHeapData<'a> { - /// ### [24.2.1.5 SetDataSize ( setData )](https://tc39.es/ecma262/#sec-setdatasize) - /// - /// The abstract operation SetDataSize takes argument setData (a List of either - /// ECMAScript language values or EMPTY) and returns a non-negative integer. - #[inline(always)] - pub fn size(&self) -> u32 { - // 1. Let count be 0. - // 2. For each element e of setData, do - // a. If e is not EMPTY, set count to count + 1. - // 3. Return count. - self.set_data.borrow().len() as u32 - } - - pub fn values(&self, _gc: NoGcScope<'a, '_>) -> &[Option>] { - &self.values - } - - pub fn clear(&mut self) { - // 3. For each element e of S.[[SetData]], do - // a. Replace the element of S.[[SetData]] whose value is e with an - // element whose value is EMPTY. - self.set_data.get_mut().clear(); - self.values.fill(None); - } - - pub(crate) fn borrow(&self, arena: &impl PrimitiveHeapIndexable) -> &Self { - self.rehash_if_needed(arena); - self - } - - pub(crate) fn borrow_mut(&mut self, arena: &impl PrimitiveHeapIndexable) -> &mut Self { - self.rehash_if_needed(arena); - self - } -} - -impl SetHeapData<'_> { - fn rehash_if_needed(&self, arena: &impl PrimitiveHeapIndexable) { - if !self.needs_primitive_rehashing.load(Ordering::Relaxed) { - return; - } - let mut set_data = self.set_data.borrow_mut(); - - rehash_set_data(&self.values, &mut set_data, arena); - self.needs_primitive_rehashing - .store(false, Ordering::Relaxed); - } -} - -fn rehash_set_data( - values: &[Option], - set_data: &mut HashTable, - arena: &impl PrimitiveHeapIndexable, -) { - let hasher = |value: Value| { - let mut hasher = AHasher::default(); - value.unbind().hash(arena, &mut hasher); - hasher.finish() - }; - let hashes = { - let hasher = |discriminant: u8| { - let mut hasher = AHasher::default(); - discriminant.hash(&mut hasher); - hasher.finish() - }; - [ - (0u8, hasher(STRING_DISCRIMINANT)), - (1u8, hasher(NUMBER_DISCRIMINANT)), - (2u8, hasher(BIGINT_DISCRIMINANT)), - ] - }; - for (id, hash) in hashes { - let eq = |equal_hash_index: &u32| { - let value = values[*equal_hash_index as usize].unwrap(); - match id { - 0 => HeapString::try_from(value).is_ok(), - 1 => HeapNumber::try_from(value).is_ok(), - 2 => HeapBigInt::try_from(value).is_ok(), - _ => unreachable!(), - } - }; - let mut entries = Vec::new(); - while let Ok(entry) = set_data.find_entry(hash, eq) { - entries.push(*entry.get()); - entry.remove(); - } - entries.iter().for_each(|entry| { - let key = values[*entry as usize].unwrap(); - let key_hash = hasher(key); - let result = set_data.entry( - key_hash, - |equal_hash_index| { - // It should not be possible for there to be an equal item - // in the Set already. - debug_assert_ne!(values[*equal_hash_index as usize].unwrap(), key); - false - }, - |index_to_hash| hasher(values[*index_to_hash as usize].unwrap()), - ); - - let Entry::Vacant(result) = result else { - unreachable!(); - }; - result.insert(*entry); - }); - } -} - bindable_handle!(SetHeapData); impl HeapMarkAndSweep for SetHeapData<'static> { diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 102abb0f6..b93a43a8e 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -178,7 +178,7 @@ pub(crate) struct Heap { #[cfg(feature = "regexp")] pub(crate) regexp_string_iterators: Vec>, #[cfg(feature = "set")] - pub(crate) sets: Vec>, + pub(crate) sets: SoAVec>, #[cfg(feature = "set")] pub(crate) set_iterators: Vec>, #[cfg(feature = "shared-array-buffer")] @@ -348,7 +348,7 @@ impl Heap { regexp_string_iterators: Vec::with_capacity(0), scripts: Vec::with_capacity(1), #[cfg(feature = "set")] - sets: Vec::with_capacity(128), + sets: SoAVec::with_capacity(128).expect("Failed to allocate Heap"), #[cfg(feature = "set")] set_iterators: Vec::with_capacity(128), #[cfg(feature = "shared-array-buffer")] diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index 7bd339d5f..378b64b84 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -735,7 +735,7 @@ impl HeapBits { BitRange::from_bit_count_and_len(&mut bit_count, heap.regexp_string_iterators.len()); let scripts = BitRange::from_bit_count_and_len(&mut bit_count, heap.scripts.len()); #[cfg(feature = "set")] - let sets = BitRange::from_bit_count_and_len(&mut bit_count, heap.sets.len()); + let sets = BitRange::from_bit_count_and_len(&mut bit_count, heap.sets.len() as usize); #[cfg(feature = "set")] let set_iterators = BitRange::from_bit_count_and_len(&mut bit_count, heap.set_iterators.len()); @@ -1092,7 +1092,7 @@ impl<'a> WorkQueues<'a> { regexp_string_iterators: Vec::with_capacity(heap.regexp_string_iterators.len() / 4), scripts: Vec::with_capacity(heap.scripts.len() / 4), #[cfg(feature = "set")] - sets: Vec::with_capacity(heap.sets.len() / 4), + sets: Vec::with_capacity(heap.sets.len() as usize / 4), #[cfg(feature = "set")] set_iterators: Vec::with_capacity(heap.set_iterators.len() / 4), #[cfg(feature = "shared-array-buffer")] diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index ae4d79203..6acdd03e8 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -782,7 +782,7 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc let index = idx.get_index(); if bits.sets.set_bit(index, &bits.bits) { // Did mark. - sets.get(index).mark_values(&mut queues); + sets.get(index as u32).mark_values(&mut queues); } }); } @@ -1955,7 +1955,7 @@ fn sweep( #[cfg(feature = "set")] if !sets.is_empty() { s.spawn(|| { - sweep_heap_vector_values(sets, &compactions, &bits.sets, &bits.bits); + sweep_heap_soa_vector_values(sets, &compactions, &bits.sets, &bits.bits); }); } #[cfg(feature = "set")]