diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index 813af6377..8dd210f7b 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -35,30 +35,25 @@ use crate::{ unwrap_try, }, heap::{ - CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - WellKnownSymbolIndexes, WorkQueues, + CompactionLists, CreateHeapData as _, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + IsoSubspace, WellKnownSymbolIndexes, WorkQueues, declare_subspace_resident, element_array::{ ElementArrays, ElementDescriptor, ElementStorageMut, ElementStorageRef, ElementsVector, }, - indexes::ArrayIndex, + indexes::BaseIndex, }, }; use ahash::AHashMap; pub use data::ArrayHeapData; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Array<'a>(ArrayIndex<'a>); +// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +// pub struct Array<'a>(ArrayIndex<'a>); +declare_subspace_resident!(iso = arrays; struct Array, ArrayHeapData); pub(crate) static ARRAY_INDEX_RANGE: RangeInclusive = 0..=(i64::pow(2, 32) - 2); impl<'a> Array<'a> { - /// # Do not use this - /// This is only for Value discriminant creation. - pub(crate) const fn _def() -> Self { - Self(ArrayIndex::from_u32_index(0)) - } - /// Creates a new array with the given elements. /// /// This is equal to the [CreateArrayFromList](https://tc39.es/ecma262/#sec-createarrayfromlist) @@ -68,10 +63,6 @@ impl<'a> Array<'a> { create_array_from_list(agent, elements, gc) } - pub(crate) fn get_index(self) -> usize { - self.0.into_index() - } - pub fn len(&self, agent: &impl Index, Output = ArrayHeapData<'static>>) -> u32 { agent[*self].elements.len() } @@ -231,27 +222,6 @@ impl<'a> Array<'a> { } } -// SAFETY: Property implemented as a lifetime transmute. -unsafe impl Bindable for Array<'_> { - type Of<'a> = Array<'a>; - - #[inline(always)] - fn unbind(self) -> Self::Of<'static> { - unsafe { core::mem::transmute::>(self) } - } - - #[inline(always)] - fn bind<'a>(self, _gc: NoGcScope<'a, '_>) -> Self::Of<'a> { - unsafe { core::mem::transmute::>(self) } - } -} - -impl<'a> From> for Array<'a> { - fn from(value: ArrayIndex<'a>) -> Self { - Array(value) - } -} - impl<'a> From> for Object<'a> { fn from(value: Array) -> Self { Self::Array(value.unbind()) @@ -769,26 +739,6 @@ impl IndexMut> for Agent { } } -impl Index> for Vec>> { - type Output = ArrayHeapData<'static>; - - fn index(&self, index: Array) -> &Self::Output { - self.get(index.get_index()) - .expect("Array out of bounds") - .as_ref() - .expect("Array slot empty") - } -} - -impl IndexMut> for Vec>> { - fn index_mut(&mut self, index: Array) -> &mut Self::Output { - self.get_mut(index.get_index()) - .expect("Array out of bounds") - .as_mut() - .expect("Array slot empty") - } -} - impl Rootable for Array<'_> { type RootRepr = HeapRootRef; @@ -812,14 +762,6 @@ impl Rootable for Array<'_> { } } -impl<'a> CreateHeapData, Array<'a>> for Heap { - fn create(&mut self, data: ArrayHeapData<'a>) -> Array<'a> { - self.arrays.push(Some(data.unbind())); - self.alloc_counter += core::mem::size_of::>>(); - Array::from(ArrayIndex::last(&self.arrays)) - } -} - impl HeapMarkAndSweep for Array<'static> { fn mark_values(&self, queues: &mut WorkQueues) { queues.arrays.push(*self); @@ -1127,13 +1069,13 @@ fn insert_element_descriptor( /// A partial view to the Agent's Heap that allows accessing array heap data. pub(crate) struct ArrayHeap<'a> { elements: &'a ElementArrays, - arrays: &'a Vec>>, + arrays: &'a IsoSubspace>, } impl ArrayHeap<'_> { pub(crate) fn new<'a>( elements: &'a ElementArrays, - arrays: &'a Vec>>, + arrays: &'a IsoSubspace>, ) -> ArrayHeap<'a> { ArrayHeap { elements, arrays } } diff --git a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs index c648ce3d8..dc3f71294 100644 --- a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs @@ -21,7 +21,7 @@ use crate::{ context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, }, - heap::{CreateHeapData, Heap, WellKnownSymbolIndexes}, + heap::{CreateHeapData as _, Heap, WellKnownSymbolIndexes}, }; /// ### [10.4.2.2 ArrayCreate ( length \[ , proto \] )](https://tc39.es/ecma262/#sec-arraycreate) diff --git a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs index eb93e2db5..f2a52204e 100644 --- a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_prototype.rs @@ -4204,9 +4204,9 @@ impl ArrayPrototype { }) .build(); - let slot = agent.heap.arrays.get_mut(this.get_index()).unwrap(); + let slot = agent.heap.arrays.slot_mut(this); assert!(slot.is_none()); - *slot = Some(ArrayHeapData { + slot.replace(ArrayHeapData { object_index: Some(this_base_object), // has a "length" property whose initial value is +0𝔽 and whose // attributes are { [[Writable]]: true, [[Enumerable]]: false, diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 3fdc85efd..4d7801aa5 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -1211,7 +1211,10 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( }; let object = match proto_intrinsics { - ProtoIntrinsics::Array => agent.heap.create(ArrayHeapData::default()).into_object(), + ProtoIntrinsics::Array => agent + .heap + .create(ArrayHeapData::default()) + .into_object(), #[cfg(feature = "array-buffer")] ProtoIntrinsics::ArrayBuffer => agent .heap diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 46780c41c..e294756b4 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -145,7 +145,7 @@ use crate::{ heap::{ CompactionLists, HeapMarkAndSweep, IntrinsicConstructorIndexes, IntrinsicFunctionIndexes, IntrinsicObjectIndexes, IntrinsicPrimitiveObjectIndexes, WorkQueues, - indexes::{ArrayIndex, BuiltinFunctionIndex, ObjectIndex, PrimitiveObjectIndex}, + indexes::{BuiltinFunctionIndex, ObjectIndex, PrimitiveObjectIndex}, intrinsic_function_count, intrinsic_object_count, intrinsic_primitive_object_count, }, }; @@ -245,7 +245,6 @@ impl Intrinsics { PrimitiveObjectIndex::from_index(agent.heap.primitive_objects.len()); let builtin_function_index_base = BuiltinFunctionIndex::from_index(agent.heap.builtin_functions.len()); - let array_prototype = Array::from(ArrayIndex::from_index(agent.heap.arrays.len())); agent .heap @@ -259,7 +258,7 @@ impl Intrinsics { .heap .builtin_functions .extend((0..intrinsic_function_count()).map(|_| None)); - agent.heap.arrays.push(None); + let array_prototype = agent.heap.arrays.reserve_intrinsic(); Self { object_index_base, diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 5d9217f2e..6036fa5a7 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -66,7 +66,7 @@ use crate::ecmascript::{ use crate::{ ecmascript::builtins::{ArrayBuffer, data_view::DataView, typed_array::TypedArray}, engine::context::NoGcScope, - heap::indexes::TypedArrayIndex, + heap::{HeapIndexable, indexes::TypedArrayIndex}, }; use crate::{ ecmascript::{ diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 3f425620e..e52cd737e 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -55,7 +55,7 @@ use crate::{ small_bigint::SmallBigInt, small_f64::SmallF64, }, - heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, + heap::{CompactionLists, HeapIndexable as _, HeapMarkAndSweep, SubspaceIndex, WorkQueues}, }; #[cfg(feature = "array-buffer")] use crate::{ @@ -258,7 +258,7 @@ pub(crate) const SMALL_BIGINT_DISCRIMINANT: u8 = value_discriminant(Value::SmallBigInt(SmallBigInt::zero())); pub(crate) const OBJECT_DISCRIMINANT: u8 = value_discriminant(Value::Object(OrdinaryObject::_def())); -pub(crate) const ARRAY_DISCRIMINANT: u8 = value_discriminant(Value::Array(Array::_def())); +pub(crate) const ARRAY_DISCRIMINANT: u8 = value_discriminant(Value::Array(Array::_DEF)); #[cfg(feature = "array-buffer")] pub(crate) const ARRAY_BUFFER_DISCRIMINANT: u8 = value_discriminant(Value::ArrayBuffer(ArrayBuffer::_def())); diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 4cbe92a9f..bb76da9f0 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -8,6 +8,7 @@ mod heap_constants; pub(crate) mod heap_gc; pub mod indexes; mod object_entry; +mod subspace; use core::{cell::RefCell, ops::Index}; use std::ops::Deref; @@ -27,7 +28,7 @@ use self::{ ElementArray2Pow8, ElementArray2Pow10, ElementArray2Pow12, ElementArray2Pow16, ElementArray2Pow24, ElementArray2Pow32, ElementArrays, }, - indexes::NumberIndex, + indexes::{BaseIndex, NumberIndex}, }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::data::DateHeapData; @@ -112,6 +113,7 @@ pub(crate) use heap_bits::{ CompactionLists, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, sweep_side_set, }; use indexes::TypedArrayIndex; +pub(crate) use subspace::*; use wtf8::{Wtf8, Wtf8Buf}; #[derive(Debug)] @@ -120,7 +122,8 @@ pub struct Heap { pub array_buffers: Vec>>, #[cfg(feature = "array-buffer")] pub array_buffer_detach_keys: AHashMap, DetachKey>, - pub arrays: Vec>>, + // pub arrays: Vec>>, + pub arrays: IsoSubspace>, pub array_iterators: Vec>>, pub async_generators: Vec>>, pub(crate) await_reactions: Vec>>, @@ -245,7 +248,7 @@ impl Heap { array_buffers: Vec::with_capacity(1024), #[cfg(feature = "array-buffer")] array_buffer_detach_keys: AHashMap::with_capacity(0), - arrays: Vec::with_capacity(1024), + arrays: IsoSubspace::with_capacity(c"array", 1024), array_iterators: Vec::with_capacity(256), async_generators: Vec::with_capacity(0), await_reactions: Vec::with_capacity(1024), @@ -365,6 +368,14 @@ impl Heap { Script::last(&self.scripts) } + /// Allocate a value within the heap. + pub(crate) fn alloc<'a, T: SubspaceResident>(&mut self, value: T::Bound<'a>) -> T::Key<'a> + where + T::Key<'a>: WithSubspace, + { + T::Key::subspace_for_mut(self).alloc(value) + } + /// Allocate a borrowed string onto the Agent heap /// /// This method will hash the input and look for a matching string on the @@ -621,6 +632,14 @@ pub(crate) trait PropertyKeyHeapIndexable: impl PropertyKeyHeapIndexable for PropertyKeyHeap<'_> {} impl PropertyKeyHeapIndexable for Agent {} +// impl<'a, T> CreateHeapData, T::Key<'a>> for T::Key<'a> +// where +// T: SubspaceResident, +// { +// fn create(&mut self, data: T::Bound<'a>) -> T::Key<'a> { +// self.alloc(data) +// } +// } #[test] fn init_heap() { let heap = Heap::new(); diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index ab3580961..cd19d8be3 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -325,17 +325,7 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc let mut array_marks: Box<[Array]> = queues.arrays.drain(..).collect(); array_marks.sort(); - array_marks.iter().for_each(|&idx| { - let index = idx.get_index(); - if let Some(marked) = bits.arrays.get_mut(index) { - if *marked { - // Already marked, ignore - return; - } - *marked = true; - arrays.get(index).mark_values(&mut queues); - } - }); + arrays.mark(array_marks, &mut bits.arrays, &mut queues); #[cfg(feature = "array-buffer")] { let mut array_buffer_marks: Box<[ArrayBuffer]> = @@ -1491,9 +1481,7 @@ fn sweep( }); } if !arrays.is_empty() { - s.spawn(|| { - sweep_heap_vector_values(arrays, &compactions, &bits.arrays); - }); + s.spawn(|| arrays.sweep(&compactions, &bits.arrays)); } if !array_iterators.is_empty() { s.spawn(|| { diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs new file mode 100644 index 000000000..52f832282 --- /dev/null +++ b/nova_vm/src/heap/subspace.rs @@ -0,0 +1,189 @@ +//! Regions of semi-isolated heap-managed memory. +//! +//! ## Notes +//! +//! Subspaces are designed around two primary types: a _resident_ that allocated +//! on the heap (and in a subspace) and a pointer-like _key_ newtype that looks it +//! up. both types are generic over the lifetime of the heap. They may be +//! bound/unbound to a garbage collection scope. This means there are actually 4 types: +//! `Key<'a>`, `Key<'static>`, `Resident<'a>`, `Resident<'static>`. +//! +//! Since trait impls may not pass lifetimes to the types they're being implemented on, +//! we're forced to use associated types. [`SubspaceResident`], which should be +//! implemented on `Resident<'static>` holds those types via +//! `SubspaceResident::Key<'a>` and `SubspaceResident::Bound<'a>`. +//! +//! The `Key<'a>` is effectively a pointer to a `Resident<'a>`, but uses a [`BaseIndex`] +//! to make it smaller. Note that this API does not use [`BaseIndex`] directly, preferring +//! a newtype wrapper around it to prevent indexing into other subspaces. +//! +//! > note: I originally designed this with the goal of having `&'a Resident<'static>` +//! > as a valid implementation of `Key<'a>`. The `From>>` +//! > type constraint currently prevents this. This would allow things like storing +//! > strings in a subspace, and using a straight-up pointer as a key. +//! +//! The `Bound<'a>` type must be +//! - the same exact type as `Resident<'a>`, but accepting a lifetime parameter. +//! - the type bound/unbound to a garbage collection scope via [`bind`] /[`unbind`] +//! Note that this is actually the same restriction since [`Bindable`] requires those +//! methods are semantically equivalent to a lifetime transmute. +//! +//! [`bind`]: crate::engine::context::Bindable::bind +//! [`unbind`]: crate::engine::context::Bindable::unbind +mod iso_subspace; +mod name; + +pub(crate) use iso_subspace::IsoSubspace; + +use super::*; + +// NOTE: please be very selective when expanding this API. +// when possible, prefer expanding APIs for concrete subspaces. +// +/// An isolated region of heap-managed memory. +/// +/// 1. Subspaces choose how to allocate their residents, as well as the +/// best way to store those allocations. It may be a [`Vec`]. It may be a +/// map. It may be whatever. +/// 2. Subspaces should, but are not required to, store homogenous data. +/// Subspaces _may_ choose to upgrade that suggestion to a requirement. +pub trait Subspace { + /// Display name for debugging purposes. + /// + /// Names are not guaranteed to be unique. Do not rely on subspaces + /// returning the same name in all cases; for example, a subspace may + /// provide a `name` in debug builds but not in release builds. + fn name(&self) -> Option<&str> { + None + } + /// Store `data` into this subspace, returning a handle to the allocation. + /// `data` may contain references to, or ownership of, other heap-allocated + /// values. + /// + /// ## Safety + /// - The lifetime parameter `'a` must be of the currently active [`NoGcScope`]. + /// - The subspace have the exact same lifetime as the [`Heap`] it belongs to, + /// which obviously must live longer than `'a`. + /// + /// The latter point is easy to enforce for subspaces put directly onto the + /// [`Heap`], but if we decide to allow external subspaces, this could + /// become more challenging + fn alloc<'a>(&mut self, data: T::Bound<'a>) -> T::Key<'a>; + // TODO: drop? len? +} + +/// A thing that can live within a [`Subspace`]. +pub trait SubspaceResident: Bindable { + type Key<'a>: SubspaceIndex<'a, Self>; + type Bound<'a>: Bindable = Self>; +} + +/// Ties a type to a specific [`Subspace`]. Implementing this trait +/// allows for `T`s to be created using [`Heap::alloc`]. +/// +/// ## Notes +/// - Eventually this will be used to support [`EmbedderObject`]s +/// - Ideally (and hopefully in the future) this will take the [`Agent`] instead of the [`Heap`] as an argument. +/// This would allow external consumers (e.g. runtimes) to put custom subspaces +/// into [`HostHooks`]. +/// - Another possible alternative to the above option is storing a dynamic list +/// of subspaces, possibly an [`IndexVec`]. This introduces the challenge of +/// statically storing/knowing which index a data structure is stored in. +/// +/// [`EmbedderObject`]: crate::ecmascript::builtins::embedder_object::EmbedderObject +/// [`HostHooks`]: crate::ecmascript::execution::agent::HostHooks +/// [`Agent`]: crate::ecmascript::execution::Agent +/// [`IndexVec`]: https://docs.rs/indexvec/latest/indexvec/struct.IndexVec.html +pub trait WithSubspace { + type Space: Subspace; + fn subspace_for(heap: &Heap) -> &Self::Space; + fn subspace_for_mut(heap: &mut Heap) -> &mut Self::Space; +} + +pub(crate) trait HeapIndexable { + fn get_index(self) -> usize; +} + +pub(crate) trait SubspaceIndex<'a, T: Bindable>: + From> + HeapIndexable +{ + /// # Do not use this + /// This is only for Value discriminant creation. + const _DEF: Self; +} + +/// Declare a newtype backed by a heap allocation. +/// +/// There is currently a single variant of this macro, that takes the form +/// ```rust,nocompile +/// declare_subspace_resident(iso = foospace; struct Foo, FooHeapData); +/// ``` +/// where +/// - `foospace` is a property on [`Heap`] that is an [`IsoSubspace`] storing `FooHeapData<'static>`s. +/// - `Foo` is a newtime that wraps a [heap index](crate::heap::indexes::BaseIndex) +/// - `FooHeapData` is a struct that stores data in the heap. +/// +/// This form is intended for declaring intrinsics within Nova. It should not be +/// used externally. +/// +/// This macro creates `Foo<'a>` and attaches traits to it. It also implements +/// [`SubspaceResident`] for `FooHeapData<'static>`. +macro_rules! declare_subspace_resident { + (iso = $space:ident; struct $Nominal:ident, $Data:ident) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + pub struct $Nominal<'a>(crate::heap::indexes::BaseIndex<'a, $Data<'static>>); + + impl<'a> From>> for $Nominal<'a> { + fn from(value: crate::heap::indexes::BaseIndex<'a, $Data<'static>>) -> Self { + $Nominal(value) + } + } + + impl crate::heap::HeapIndexable for $Nominal<'_> { + #[inline] + fn get_index(self) -> usize { + self.0.into_index() + } + } + + impl<'a> crate::heap::SubspaceIndex<'a, $Data<'static>> for $Nominal<'a> { + const _DEF: Self = Self(crate::heap::indexes::BaseIndex::from_u32_index(0)); + } + + // SAFETY: Property implemented as a lifetime transmute. + unsafe impl crate::engine::context::Bindable for $Nominal<'_> { + type Of<'a> = $Nominal<'a>; + + #[inline(always)] + fn unbind(self) -> Self::Of<'static> { + unsafe { core::mem::transmute::>(self) } + } + + #[inline(always)] + fn bind<'a>(self, _gc: crate::engine::context::NoGcScope<'a, '_>) -> Self::Of<'a> { + unsafe { core::mem::transmute::>(self) } + } + } + + impl crate::heap::SubspaceResident for $Data<'static> { + type Key<'a> = $Nominal<'a>; + type Bound<'a> = $Data<'a>; + } + impl crate::heap::WithSubspace<$Data<'static>> for $Nominal<'_> { + type Space = crate::heap::IsoSubspace<$Data<'static>>; + fn subspace_for(heap: &Heap) -> &Self::Space { + &heap.$space + } + fn subspace_for_mut(heap: &mut Heap) -> &mut Self::Space { + &mut heap.$space + } + } + + impl<'a> crate::heap::CreateHeapData<$Data<'a>, $Nominal<'a>> for Heap { + fn create(&mut self, data: ArrayHeapData<'a>) -> Array<'a> { + self.alloc::<$Data<'static>>(data) + } + } + }; +} +pub(crate) use declare_subspace_resident; diff --git a/nova_vm/src/heap/subspace/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs new file mode 100644 index 000000000..ca08d6062 --- /dev/null +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -0,0 +1,166 @@ +use super::{HeapIndexable, Subspace, SubspaceResident, name::Name}; +use crate::heap::{BaseIndex, Bindable, CompactionLists, HeapMarkAndSweep, WorkQueues}; +use core::ffi::CStr; +use std::{fmt, ops}; + +/// A [`Subspace`] storing data of a single [`Sized`] type. +pub struct IsoSubspace { + /// Display name for debugging purposes. + /// + /// We use [`Name`] over `&'static str` to save 1 word in memory layout. + name: Name, + alloc_count: usize, + data: Vec>, +} + +impl IsoSubspace { + fn new(name: &'static CStr) -> Self { + Self::with_capacity(name, 0) + } + + pub fn with_capacity(name: &'static CStr, capacity: usize) -> Self { + Self { + name: Name::new(name), + alloc_count: 0, + data: Vec::with_capacity(capacity), + } + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// TODO: do not rely on len(). subspace will eventually store data across + /// various blocks to avoid massive re-allocations + #[inline] + pub(crate) fn len(&self) -> usize { + self.data.len() + } +} + +impl IsoSubspace +where + T: SubspaceResident, +{ + pub fn get(&self, key: T::Key<'_>) -> Option<&T> { + self.data.get(key.get_index()).and_then(Option::as_ref) + } + + pub fn get_mut(&mut self, key: T::Key<'_>) -> Option<&mut T> { + self.data.get_mut(key.get_index()).and_then(Option::as_mut) + } + + pub fn slot(&self, key: T::Key<'_>) -> &Option { + self.data.get(key.get_index()).expect("Slot out of bounds") + } + + pub fn slot_mut(&mut self, key: T::Key<'_>) -> &mut Option { + self.data + .get_mut(key.get_index()) + .expect("Slot out of bounds") + } + + pub(crate) fn reserve_intrinsic(&mut self) -> T::Key<'static> { + self.data.push(None); + // note: not from_index b/c len is now +1 + T::Key::from(BaseIndex::from_usize(self.len())) + } +} + +impl ops::Index> for IsoSubspace +where + T: SubspaceResident, +{ + type Output = T; + + fn index(&self, index: T::Key<'_>) -> &Self::Output { + let i = index.get_index(); + self.data + .get(i) + .unwrap_or_else(|| panic!("subspace {}: index out of bounds", self.name)) + .as_ref() + .unwrap_or_else(|| panic!("subspace {}: slot {i} is empty", self.name)) + } +} + +impl ops::IndexMut> for IsoSubspace +where + T: SubspaceResident, +{ + fn index_mut(&mut self, index: T::Key<'_>) -> &mut Self::Output { + let i = index.get_index(); + self.data + .get_mut(i) + .unwrap_or_else(|| panic!("subspace {}: index out of bounds", self.name)) + .as_mut() + .unwrap_or_else(|| panic!("subspace {}: slot {i} is empty", self.name)) + } +} + +impl Subspace for IsoSubspace +where + T: SubspaceResident, +{ + fn name(&self) -> Option<&str> { + Some(self.name.as_str()) + } + + fn alloc<'a>(&mut self, data: T::Bound<'a>) -> T::Key<'a> { + self.data.push(Some(data.unbind())); + self.alloc_count += core::mem::size_of::(); + T::Key::from(BaseIndex::from_usize(self.data.len())) + } +} + +impl IsoSubspace +where + T: SubspaceResident + HeapMarkAndSweep, +{ + pub(crate) fn mark<'a, M>( + &'a self, // + marks: M, + bits: &mut [bool], + queues: &mut WorkQueues, + ) where + M: IntoIterator>, + { + marks.into_iter().for_each(|idx| { + let index = idx.get_index(); + if let Some(marked) = bits.get_mut(index) { + if *marked { + // Already marked, ignore + return; + } + *marked = true; + self.data.get(index).mark_values(queues); + } + }); + } + pub(crate) fn sweep(&mut self, compactions: &CompactionLists, bits: &[bool]) { + assert_eq!(self.data.len(), bits.len()); + let mut iter = bits.iter(); + let items_before = self.data.len(); + self.data.retain_mut(|item| { + let do_retain = iter.next().unwrap(); + if *do_retain { + item.sweep_values(compactions); + true + } else { + false + } + }); + let items_dropped = items_before.saturating_sub(self.data.len()); + self.alloc_count -= items_dropped * core::mem::size_of::() + } +} + +impl fmt::Debug for IsoSubspace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IsoSubspace") + .field("name", &self.name) + .field("alloc_count", &self.alloc_count) + .field("data", &"") + .finish() + } +} diff --git a/nova_vm/src/heap/subspace/name.rs b/nova_vm/src/heap/subspace/name.rs new file mode 100644 index 000000000..69475b2bb --- /dev/null +++ b/nova_vm/src/heap/subspace/name.rs @@ -0,0 +1,52 @@ +use core::{ffi, fmt, ptr::NonNull}; + +/// This is a &'static CStr converted into a pointer to avoid storying a +/// word for the string's length. this comes at the cost of O(n) casts into +/// &'static str, which is fine because if we're doing so its either for +/// debugging or because Nova is about to panic. +/// +/// Do not expose this outside of the `subspace` module. +#[derive(Clone, Copy)] +pub(super) struct Name( + // invariant: never dereference a `*mut`, or create a `&mut`, from this pointer. + // NonNull is used over *const c_char for non-null optimization. + NonNull, +); +// pub(super) struct Name(ptr::NonNull); +// SAFETY: pointer is &'static and never mutable. +unsafe impl Send for Name {} +unsafe impl Sync for Name {} + +impl Name { + pub const fn new(s: &'static ffi::CStr) -> Self { + assert!(s.to_str().is_ok()); + let p = NonNull::new(s.as_ptr() as *mut _).unwrap(); + Self(p) + } + pub const fn as_str(self) -> &'static str { + // SAFETY: inner string is always created from a &'static CStr that is + // known to be valid utf-8 + match unsafe { ffi::CStr::from_ptr(self.0.as_ref() as *const _).to_str() } { + Ok(s) => s, + Err(_) => unreachable!(), + } + } +} + +impl fmt::Debug for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (*self).as_str().fmt(f) + } +} + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl From for &'static str { + fn from(name: Name) -> Self { + name.as_str() + } +} diff --git a/tests/test262_runner.rs b/tests/test262_runner.rs index 6d156e6f0..caacdcb73 100644 --- a/tests/test262_runner.rs +++ b/tests/test262_runner.rs @@ -432,6 +432,12 @@ impl Test262Runner { builder.use_current_thread().build().unwrap() }; + if !self.inner.tests_base.exists() { + let dir = self.inner.tests_base.display(); + panic!( + "Test262 directory at {dir} does not exist. Did you forget to run `git submodule update --init`?" + ); + } thread_pool.install(|| { self.walk_dir(&self.inner.tests_base.clone(), filters); }); @@ -464,19 +470,23 @@ impl Test262Runner { /// directory) will be run. fn walk_dir(&self, path: &PathBuf, filters: &TestFilters) { // Iterate through every entry in this directory in parallel. - read_dir(path).unwrap().par_bridge().for_each(|entry| { - let entry = entry.unwrap(); + read_dir(path) + .map_err(|e| format!("Failed to open directory {}: {e}", path.display())) + .unwrap() + .par_bridge() + .for_each(|entry| { + let entry = entry.unwrap(); - if entry.file_type().unwrap().is_dir() { - if let Some(child_filters) = filters.filter_dir(&entry.file_name()) { - self.walk_dir(&entry.path(), &child_filters); + if entry.file_type().unwrap().is_dir() { + if let Some(child_filters) = filters.filter_dir(&entry.file_name()) { + self.walk_dir(&entry.path(), &child_filters); + } } - } - if entry.file_type().unwrap().is_file() && filters.filter_file(&entry.file_name()) { - self.run_test(&entry.path()); - } - }) + if entry.file_type().unwrap().is_file() && filters.filter_file(&entry.file_name()) { + self.run_test(&entry.path()); + } + }) } fn run_test(&self, path: &PathBuf) {