From 34d5330ff53f9d7b5b6da778c19e03ad290e679f Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 14:01:39 -0700 Subject: [PATCH 01/14] wip --- Cargo.toml | 5 + nova_vm/src/ecmascript/builtins/array.rs | 40 +------ nova_vm/src/heap.rs | 17 ++- nova_vm/src/heap/subspace.rs | 144 +++++++++++++++++++++++ tests/test262_runner.rs | 30 +++-- 5 files changed, 190 insertions(+), 46 deletions(-) create mode 100644 nova_vm/src/heap/subspace.rs diff --git a/Cargo.toml b/Cargo.toml index 5b540c283..f99668105 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,11 @@ libraries = [{ path = "nova_lint" }] [profile.release] lto = true +[profile.dev] +debug = false +[profile.test] +debug = false + # This profile has all the same safety checks as dev builds. It trades slightly # longer compile times for faster runs, which is worth it when running test262. [profile.dev-fast] diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index 813af6377..a4698b4c6 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -36,29 +36,24 @@ use crate::{ }, heap::{ CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - WellKnownSymbolIndexes, WorkQueues, + WellKnownSymbolIndexes, WorkQueues, declare_subspace_resident, element_array::{ ElementArrays, ElementDescriptor, ElementStorageMut, ElementStorageRef, ElementsVector, }, - indexes::ArrayIndex, + indexes::{ArrayIndex, 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; 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()) diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 4cbe92a9f..f678ca2ca 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -8,6 +8,8 @@ mod heap_constants; pub(crate) mod heap_gc; pub mod indexes; mod object_entry; +// mod subspace_old; +mod subspace; use core::{cell::RefCell, ops::Index}; use std::ops::Deref; @@ -22,12 +24,16 @@ pub(crate) use self::heap_constants::{ LAST_INTRINSIC_CONSTRUCTOR_INDEX, LAST_INTRINSIC_FUNCTION_INDEX, LAST_INTRINSIC_OBJECT_INDEX, }; pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; +// pub(crate) use self::subspace::{ +// HeapResident, IsoSubspace, Subspace, SubspaceResident, declare_nominal_heap_resident, +// }; +pub(crate) use subspace::*; use self::{ element_array::{ ElementArray2Pow8, ElementArray2Pow10, ElementArray2Pow12, ElementArray2Pow16, ElementArray2Pow24, ElementArray2Pow32, ElementArrays, }, - indexes::NumberIndex, + indexes::{BaseIndex, NumberIndex}, }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::data::DateHeapData; @@ -365,6 +371,15 @@ impl Heap { Script::last(&self.scripts) } + // pub(crate) fn alloc<'a, T, U>(&'a mut self, data: T) -> U + // where + // T: subspace::SubspaceResident, + // U: From>, + // { + // let subspace = T::subspace_for_mut(self); + // subspace.alloc(data).into() + // } + /// Allocate a borrowed string onto the Agent heap /// /// This method will hash the input and look for a matching string on the diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs new file mode 100644 index 000000000..1f2be4067 --- /dev/null +++ b/nova_vm/src/heap/subspace.rs @@ -0,0 +1,144 @@ +use std::{cell::Cell, fmt, marker::PhantomData, ops}; + +use super::*; +// type Ptr<'a, T: ?Sized> = BaseIndex<'a, T>; + +pub trait SubspaceResident<'a, HeapRepr: ?Sized> { + type Space: Subspace<'a, HeapRepr, Self>; + fn subspace_for(heap: &Heap) -> &Self::Space; + fn subspace_for_mut(heap: &mut Heap) -> &mut Self::Space; +} + +pub trait IsoSubspaceResident { + type Data<'a>: Bindable = Self::Data<'a>>; +} + +macro_rules! declare_subspace_resident { + (iso; struct $Nominal:ident, $Data:ident) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + pub struct $Nominal<'a>(BaseIndex<'a, $Data<'static>>); + + impl<'a> $Nominal<'a> { + /// # Do not use this + /// This is only for Value discriminant creation. + pub(crate) const fn _def() -> Self { + Self(BaseIndex::from_u32_index(0)) + } + pub(crate) fn get_index(self) -> usize { + self.0.into_index() + } + } + + impl<'a> From>> for $Nominal<'a> { + fn from(value: BaseIndex<'a, $Data<'static>>) -> Self { + $Nominal(value) + } + } + + // 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: NoGcScope<'a, '_>) -> Self::Of<'a> { + unsafe { core::mem::transmute::>(self) } + } + } + + impl crate::heap::IsoSubspaceResident for $Nominal<'_> { + type Data<'a> = $Data<'a>; + } + }; +} +pub(crate) use declare_subspace_resident; + +pub trait Subspace<'a, T: ?Sized, Ptr: ?Sized> { + fn alloc(&'a mut self, data: T) -> Ptr; +} + +pub struct IsoSubspace { + name: &'static str, + alloc_count: usize, + data: Vec>, + _marker: PhantomData, +} + +impl IsoSubspace { + fn new(name: &'static str) -> Self { + Self::with_capacity(name, 0) + } + + pub fn with_capacity(name: &'static str, capacity: usize) -> Self { + Self { + name, + alloc_count: 0, + data: Vec::with_capacity(capacity), + _marker: PhantomData, + } + } +} + +impl IsoSubspace +where + T: Into, +{ + pub fn get(&self, key: T) -> Option<&D> { + self.data.get(key.into()).map(Option::as_ref).flatten() + } + pub fn get_mut(&mut self, key: T) -> Option<&mut D> { + self.data.get_mut(key.into()).map(Option::as_mut).flatten() + } +} + +impl ops::Index for IsoSubspace +where + T: Into, +{ + type Output = D; + fn index(&self, index: T) -> &Self::Output { + self.data + .get(index.into()) + .expect("subspace index out of bounds") + .as_ref() + .expect("subspace slot is empty") + } +} +impl ops::IndexMut for IsoSubspace +where + T: Into, +{ + fn index_mut(&mut self, index: T) -> &mut Self::Output { + self.data + .get_mut(index.into()) + .expect("subspace index out of bounds") + .as_mut() + .expect("subspace slot is empty") + } +} + +// impl Subspace for IsoSubspace +// { +// fn alloc(&mut self, data: D) -> Ptr<'_, D> { +// self.data.push(Some(data)); +// self.alloc_count += core::mem::size_of::(); +// return Ptr::from_usize(self.data.len()); +// } +// } + +impl<'a, R> Subspace<'a, R::Data<'a>, R> for IsoSubspace> +where + R: IsoSubspaceResident + From>>, +{ + fn alloc(&mut self, data: R::Data<'a>) -> R { + // SAFETY: this is not safe. fixme. + let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; + self.data.push(Some(d)); + self.alloc_count += core::mem::size_of::>(); + return R::from(BaseIndex::from_usize(self.data.len())); + } +} 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) { From 6dee5c0bcfccec458f0811754262a9029f891aa8 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 14:01:58 -0700 Subject: [PATCH 02/14] mark + sweep --- nova_vm/src/heap.rs | 41 +++------- nova_vm/src/heap/heap_gc.rs | 25 +++--- nova_vm/src/heap/subspace.rs | 144 ++++++++++++++++++++++++++--------- 3 files changed, 133 insertions(+), 77 deletions(-) diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index f678ca2ca..e0d0b3e3f 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -58,53 +58,31 @@ use crate::ecmascript::builtins::{ weak_set::data::WeakSetHeapData, }; use crate::{ - SmallInteger, ecmascript::{ builtins::{ - ArrayBuffer, ArrayHeapData, - array_buffer::DetachKey, - async_generator_objects::AsyncGeneratorHeapData, - control_abstraction_objects::{ + array_buffer::DetachKey, async_generator_objects::AsyncGeneratorHeapData, control_abstraction_objects::{ async_function_objects::await_reaction::AwaitReactionRecord, generator_objects::GeneratorHeapData, promise_objects::promise_abstract_operations::{ promise_reaction_records::PromiseReactionRecord, promise_resolving_functions::PromiseResolvingFunctionHeapData, }, - }, - embedder_object::data::EmbedderObjectHeapData, - error::ErrorHeapData, - finalization_registry::data::FinalizationRegistryHeapData, - indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, - keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, - map::data::MapHeapData, - module::{Module, data::ModuleHeapData}, - primitive_objects::PrimitiveObjectHeapData, - promise::data::PromiseHeapData, - proxy::data::ProxyHeapData, - text_processing::string_objects::string_iterator_objects::StringIteratorHeapData, + }, embedder_object::data::EmbedderObjectHeapData, error::ErrorHeapData, finalization_registry::data::FinalizationRegistryHeapData, indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, map::data::MapHeapData, module::{data::ModuleHeapData, Module}, primitive_objects::PrimitiveObjectHeapData, promise::data::PromiseHeapData, proxy::data::ProxyHeapData, text_processing::string_objects::string_iterator_objects::StringIteratorHeapData, Array, ArrayBuffer, ArrayHeapData }, execution::{Agent, Environments, Realm, RealmRecord}, scripts_and_modules::{ module::module_semantics::{ - ModuleRequestRecord, source_text_module_records::SourceTextModuleHeap, + source_text_module_records::SourceTextModuleHeap, ModuleRequestRecord }, script::{Script, ScriptRecord}, source_code::SourceCodeHeapData, }, types::{ - BUILTIN_STRINGS_LIST, BigIntHeapData, BoundFunctionHeapData, - BuiltinConstructorHeapData, BuiltinFunctionHeapData, ECMAScriptFunctionHeapData, - HeapNumber, HeapString, NumberHeapData, Object, ObjectHeapData, OrdinaryObject, - PropertyKey, String, StringHeapData, Symbol, SymbolHeapData, Value, bigint::HeapBigInt, + bigint::HeapBigInt, BigIntHeapData, BoundFunctionHeapData, BuiltinConstructorHeapData, BuiltinFunctionHeapData, ECMAScriptFunctionHeapData, HeapNumber, HeapString, NumberHeapData, Object, ObjectHeapData, OrdinaryObject, PropertyKey, String, StringHeapData, Symbol, SymbolHeapData, Value, BUILTIN_STRINGS_LIST }, - }, - engine::{ - ExecutableHeapData, - context::{Bindable, NoGcScope}, - rootable::HeapRootData, - small_f64::SmallF64, - }, + }, engine::{ + context::{Bindable, NoGcScope}, rootable::HeapRootData, small_f64::SmallF64, ExecutableHeapData + }, SmallInteger }; #[cfg(feature = "array-buffer")] use ahash::AHashMap; @@ -126,7 +104,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, ArrayHeapData<'static>>, pub array_iterators: Vec>>, pub async_generators: Vec>>, pub(crate) await_reactions: Vec>>, @@ -251,7 +230,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("array", 1024), array_iterators: Vec::with_capacity(256), async_generators: Vec::with_capacity(0), await_reactions: Vec::with_capacity(1024), diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index ab3580961..5d965d739 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -325,17 +325,18 @@ 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); + // 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); + // } + // }); #[cfg(feature = "array-buffer")] { let mut array_buffer_marks: Box<[ArrayBuffer]> = @@ -1492,7 +1493,7 @@ fn sweep( } if !arrays.is_empty() { s.spawn(|| { - sweep_heap_vector_values(arrays, &compactions, &bits.arrays); + arrays.sweep(&compactions, &bits.arrays) }); } if !array_iterators.is_empty() { diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index 1f2be4067..3706610e4 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -1,4 +1,4 @@ -use std::{cell::Cell, fmt, marker::PhantomData, ops}; +use std::{borrow::Borrow, cell::Cell, fmt, marker::PhantomData, ops}; use super::*; // type Ptr<'a, T: ?Sized> = BaseIndex<'a, T>; @@ -8,9 +8,31 @@ pub trait SubspaceResident<'a, HeapRepr: ?Sized> { 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: ?Sized>: From> + HeapIndexable { + /// # Do not use this + /// This is only for Value discriminant creation. + const _DEF: Self; + // const fn _def() -> Self { + // Self(BaseIndex::from_u32_index(0)) + // } + // fn get_index(self) -> usize; + // { + // self.0.into_index() + // } + // fn id(self) -> BaseIndex<'a, T>; + // fn get_index(self) -> usize { + + // } +} +// pub trait IsoSubspaceResident { +// type Data<'a>: Bindable = Self::Data<'a>>; +// } pub trait IsoSubspaceResident { - type Data<'a>: Bindable = Self::Data<'a>>; + type Key<'a>: SubspaceIndex<'a, Self>; } macro_rules! declare_subspace_resident { @@ -18,21 +40,21 @@ macro_rules! declare_subspace_resident { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct $Nominal<'a>(BaseIndex<'a, $Data<'static>>); - impl<'a> $Nominal<'a> { - /// # Do not use this - /// This is only for Value discriminant creation. - pub(crate) const fn _def() -> Self { - Self(BaseIndex::from_u32_index(0)) + impl<'a> From>> for $Nominal<'a> { + fn from(value: BaseIndex<'a, $Data<'static>>) -> Self { + $Nominal(value) } - pub(crate) fn get_index(self) -> usize { + } + + impl crate::heap::HeapIndexable for $Nominal<'_> { + #[inline] + fn get_index(self) -> usize { self.0.into_index() } } - impl<'a> From>> for $Nominal<'a> { - fn from(value: BaseIndex<'a, $Data<'static>>) -> Self { - $Nominal(value) - } + impl<'a> crate::heap::SubspaceIndex<'a, $Data<'static>> for $Nominal<'a> { + const _DEF: Self = Self(BaseIndex::from_u32_index(0)); } // SAFETY: Property implemented as a lifetime transmute. @@ -50,8 +72,8 @@ macro_rules! declare_subspace_resident { } } - impl crate::heap::IsoSubspaceResident for $Nominal<'_> { - type Data<'a> = $Data<'a>; + impl crate::heap::IsoSubspaceResident for $Data<'static> { + type Key<'a> = $Nominal<'a>; } }; } @@ -81,6 +103,18 @@ impl IsoSubspace { _marker: PhantomData, } } + + #[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(super) fn len(&self) -> usize { + self.data.len() + } } impl IsoSubspace @@ -97,12 +131,12 @@ where impl ops::Index for IsoSubspace where - T: Into, + T: HeapIndexable, { type Output = D; fn index(&self, index: T) -> &Self::Output { self.data - .get(index.into()) + .get(index.get_index()) .expect("subspace index out of bounds") .as_ref() .expect("subspace slot is empty") @@ -110,35 +144,77 @@ where } impl ops::IndexMut for IsoSubspace where - T: Into, + T: HeapIndexable { fn index_mut(&mut self, index: T) -> &mut Self::Output { self.data - .get_mut(index.into()) + .get_mut(index.get_index()) .expect("subspace index out of bounds") .as_mut() .expect("subspace slot is empty") } } -// impl Subspace for IsoSubspace -// { -// fn alloc(&mut self, data: D) -> Ptr<'_, D> { -// self.data.push(Some(data)); -// self.alloc_count += core::mem::size_of::(); -// return Ptr::from_usize(self.data.len()); -// } -// } - -impl<'a, R> Subspace<'a, R::Data<'a>, R> for IsoSubspace> +impl<'a, R> Subspace<'a, R, R::Key<'a>> for IsoSubspace, R> where - R: IsoSubspaceResident + From>>, + // R: IsoSubspaceResident + From>>, + R: IsoSubspaceResident, { - fn alloc(&mut self, data: R::Data<'a>) -> R { + fn alloc(&'a mut self, data: R) -> R::Key<'a> { // SAFETY: this is not safe. fixme. - let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; - self.data.push(Some(d)); - self.alloc_count += core::mem::size_of::>(); - return R::from(BaseIndex::from_usize(self.data.len())); + // let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; + self.data.push(Some(data)); + self.alloc_count += core::mem::size_of::(); + return R::Key::from(BaseIndex::from_usize(self.data.len())); + } +} + +impl<'a, T, D> IsoSubspace +where + T: SubspaceIndex<'a, D>, + D: HeapMarkAndSweep, +{ + pub(crate) fn mark( + &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(); + self.data.retain_mut(|item| { + let do_retain = iter.next().unwrap(); + if *do_retain { + item.sweep_values(compactions); + true + } else { + false + } + }); + } +} + +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() } } From f4d0fe7bb97d55566226af6a374c3b8e68d61d68 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 14:57:27 -0700 Subject: [PATCH 03/14] move to new file --- nova_vm/src/heap/subspace.rs | 161 ++----------------- nova_vm/src/heap/subspace/iso_subspace.rs | 185 ++++++++++++++++++++++ 2 files changed, 199 insertions(+), 147 deletions(-) create mode 100644 nova_vm/src/heap/subspace/iso_subspace.rs diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index 3706610e4..18df76143 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -1,8 +1,14 @@ -use std::{borrow::Borrow, cell::Cell, fmt, marker::PhantomData, ops}; +mod iso_subspace; + +pub(crate) use iso_subspace::{IsoSubspace, IsoSubspaceResident}; use super::*; // type Ptr<'a, T: ?Sized> = BaseIndex<'a, T>; +pub trait Subspace<'a, T: ?Sized, Ptr: ?Sized> { + fn alloc(&'a mut self, data: T) -> Ptr; +} + pub trait SubspaceResident<'a, HeapRepr: ?Sized> { type Space: Subspace<'a, HeapRepr, Self>; fn subspace_for(heap: &Heap) -> &Self::Space; @@ -11,7 +17,11 @@ pub trait SubspaceResident<'a, HeapRepr: ?Sized> { pub(crate) trait HeapIndexable { fn get_index(self) -> usize; } -pub(crate) trait SubspaceIndex<'a, T: ?Sized>: From> + HeapIndexable { + + +pub(crate) trait SubspaceIndex<'a, T: Bindable>: + From>> + HeapIndexable +{ /// # Do not use this /// This is only for Value discriminant creation. const _DEF: Self; @@ -31,9 +41,6 @@ pub(crate) trait SubspaceIndex<'a, T: ?Sized>: From> + HeapInde // pub trait IsoSubspaceResident { // type Data<'a>: Bindable = Self::Data<'a>>; // } -pub trait IsoSubspaceResident { - type Key<'a>: SubspaceIndex<'a, Self>; -} macro_rules! declare_subspace_resident { (iso; struct $Nominal:ident, $Data:ident) => { @@ -53,7 +60,7 @@ macro_rules! declare_subspace_resident { } } - impl<'a> crate::heap::SubspaceIndex<'a, $Data<'static>> for $Nominal<'a> { + impl<'a> crate::heap::SubspaceIndex<'a, $Data<'a>> for $Nominal<'a> { const _DEF: Self = Self(BaseIndex::from_u32_index(0)); } @@ -72,149 +79,9 @@ macro_rules! declare_subspace_resident { } } - impl crate::heap::IsoSubspaceResident for $Data<'static> { + impl crate::heap::IsoSubspaceResident for $Data<'_> { type Key<'a> = $Nominal<'a>; } }; } pub(crate) use declare_subspace_resident; - -pub trait Subspace<'a, T: ?Sized, Ptr: ?Sized> { - fn alloc(&'a mut self, data: T) -> Ptr; -} - -pub struct IsoSubspace { - name: &'static str, - alloc_count: usize, - data: Vec>, - _marker: PhantomData, -} - -impl IsoSubspace { - fn new(name: &'static str) -> Self { - Self::with_capacity(name, 0) - } - - pub fn with_capacity(name: &'static str, capacity: usize) -> Self { - Self { - name, - alloc_count: 0, - data: Vec::with_capacity(capacity), - _marker: PhantomData, - } - } - - #[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(super) fn len(&self) -> usize { - self.data.len() - } -} - -impl IsoSubspace -where - T: Into, -{ - pub fn get(&self, key: T) -> Option<&D> { - self.data.get(key.into()).map(Option::as_ref).flatten() - } - pub fn get_mut(&mut self, key: T) -> Option<&mut D> { - self.data.get_mut(key.into()).map(Option::as_mut).flatten() - } -} - -impl ops::Index for IsoSubspace -where - T: HeapIndexable, -{ - type Output = D; - fn index(&self, index: T) -> &Self::Output { - self.data - .get(index.get_index()) - .expect("subspace index out of bounds") - .as_ref() - .expect("subspace slot is empty") - } -} -impl ops::IndexMut for IsoSubspace -where - T: HeapIndexable -{ - fn index_mut(&mut self, index: T) -> &mut Self::Output { - self.data - .get_mut(index.get_index()) - .expect("subspace index out of bounds") - .as_mut() - .expect("subspace slot is empty") - } -} - -impl<'a, R> Subspace<'a, R, R::Key<'a>> for IsoSubspace, R> -where - // R: IsoSubspaceResident + From>>, - R: IsoSubspaceResident, -{ - fn alloc(&'a mut self, data: R) -> R::Key<'a> { - // SAFETY: this is not safe. fixme. - // let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; - self.data.push(Some(data)); - self.alloc_count += core::mem::size_of::(); - return R::Key::from(BaseIndex::from_usize(self.data.len())); - } -} - -impl<'a, T, D> IsoSubspace -where - T: SubspaceIndex<'a, D>, - D: HeapMarkAndSweep, -{ - pub(crate) fn mark( - &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(); - self.data.retain_mut(|item| { - let do_retain = iter.next().unwrap(); - if *do_retain { - item.sweep_values(compactions); - true - } else { - false - } - }); - } -} - -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/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs new file mode 100644 index 000000000..0c98c3df1 --- /dev/null +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -0,0 +1,185 @@ +use std::{borrow::Borrow, cell::Cell, fmt, marker::PhantomData, ops}; +use super::{SubspaceResident, Subspace, SubspaceIndex, HeapIndexable}; +// use crate::{engine::context::Bindable, heap::indexes::BaseIndex}; +use crate::heap::*; + +pub struct IsoSubspace { + name: &'static str, + alloc_count: usize, + data: Vec>, + _marker: PhantomData, +} + +pub trait IsoSubspaceResident : Bindable { + type Key<'a>: SubspaceIndex<'a, Self>; +} + +impl IsoSubspace { + fn new(name: &'static str) -> Self { + Self::with_capacity(name, 0) + } + + pub fn with_capacity(name: &'static str, capacity: usize) -> Self { + Self { + name, + alloc_count: 0, + data: Vec::with_capacity(capacity), + _marker: PhantomData, + } + } + + #[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(super) fn len(&self) -> usize { + self.data.len() + } +} + +impl IsoSubspace +where + T: HeapIndexable +{ + pub fn get(&self, key: T) -> Option<&D> { + self.data.get(key.get_index()).map(Option::as_ref).flatten() + } + pub fn get_mut(&mut self, key: T) -> Option<&mut D> { + self.data.get_mut(key.get_index()).map(Option::as_mut).flatten() + } +} + +// impl ops::Index for IsoSubspace +// where +// T: HeapIndexable, +// { +// type Output = D; +// fn index(&self, index: T) -> &Self::Output { +// self.data +// .get(index.get_index()) +// .expect("subspace index out of bounds") +// .as_ref() +// .expect("subspace slot is empty") +// } +// } + +// impl ops::IndexMut for IsoSubspace +// where +// T: HeapIndexable, +// { +// fn index_mut(&mut self, index: T) -> &mut Self::Output { +// self.data +// .get_mut(index.get_index()) +// .expect("subspace index out of bounds") +// .as_mut() +// .expect("subspace slot is empty") +// } +// } + +impl ops::Index> for IsoSubspace, R> +where + R: IsoSubspaceResident, +{ + type Output = R; + + fn index(&self, index: R::Key<'_>) -> &Self::Output { + self.data + .get(index.get_index()) + .expect("subspace index out of bounds") + .as_ref() + .expect("subspace slot is empty") + } +} + +impl ops::IndexMut> for IsoSubspace, R> +where + R: IsoSubspaceResident, +{ + fn index_mut(&mut self, index: R::Key<'_>) -> &mut Self::Output { + self.data + .get_mut(index.get_index()) + .expect("subspace index out of bounds") + .as_mut() + .expect("subspace slot is empty") + } +} + +impl<'a, R> Subspace<'a, R, R::Key<'a>> for IsoSubspace, R> +where + // R: IsoSubspaceResident + From>>, + R: IsoSubspaceResident, +{ + fn alloc(&'a mut self, data: R) -> R::Key<'a> { + // SAFETY: this is not safe. fixme. + // let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; + self.data.push(Some(data)); + self.alloc_count += core::mem::size_of::(); + return R::Key::from(BaseIndex::from_usize(self.data.len())); + } +} + +impl<'a, R> IsoSubspace, R> where + R: IsoSubspaceResident + Bindable, + R::Of<'static> : Into +{ + pub fn create(&mut self, data: R) -> R::Key<'a> { + self.data.push(Some(data.unbind().into())); + self.alloc_count += core::mem::size_of::(); + return R::Key::from(BaseIndex::from_usize(self.data.len())); + } +} + +impl<'a, T, D> IsoSubspace +where + T: HeapIndexable, //SubspaceIndex<'a, D>, + D: HeapMarkAndSweep + // D: HeapMarkAndSweep + Bindable, +{ + pub(crate) fn mark( + &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(); + self.data.retain_mut(|item| { + let do_retain = iter.next().unwrap(); + if *do_retain { + item.sweep_values(compactions); + true + } else { + false + } + }); + } +} + +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() + } +} From 72710dfd7fc19f9ea9259c58618693b0bfd00a5c Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 15:52:46 -0700 Subject: [PATCH 04/14] by god it compiles --- nova_vm/src/ecmascript/builtins/array.rs | 66 +++++----- .../array_objects/array_prototype.rs | 4 +- .../ecmascript/execution/realm/intrinsics.rs | 2 +- .../src/ecmascript/types/language/object.rs | 2 +- .../src/ecmascript/types/language/value.rs | 35 ++---- nova_vm/src/heap.rs | 3 +- nova_vm/src/heap/subspace.rs | 7 +- nova_vm/src/heap/subspace/iso_subspace.rs | 115 ++++++++++++------ 8 files changed, 123 insertions(+), 111 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index a4698b4c6..35648b978 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -24,23 +24,16 @@ use crate::{ }, execution::{Agent, JsResult, ProtoIntrinsics}, types::{ - BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, - IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, + Function, InternalMethods, InternalSlots, IntoFunction, IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, BUILTIN_STRING_MEMORY }, }, engine::{ - TryResult, - context::{Bindable, GcScope, NoGcScope}, - rootable::{HeapRootData, HeapRootRef, Rootable}, - unwrap_try, + context::{Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}, unwrap_try, TryResult }, heap::{ - CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - WellKnownSymbolIndexes, WorkQueues, declare_subspace_resident, - element_array::{ + declare_subspace_resident, element_array::{ ElementArrays, ElementDescriptor, ElementStorageMut, ElementStorageRef, ElementsVector, - }, - indexes::{ArrayIndex, BaseIndex}, + }, indexes::{ArrayIndex, BaseIndex}, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IsoSubspace, Subspace as _, WellKnownSymbolIndexes, WorkQueues }, }; @@ -739,25 +732,25 @@ 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 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; @@ -784,9 +777,12 @@ 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)) + // self.arrays.alloc(data.unbind()) + let arr: Array<'a> = self.arrays.create(data); + arr + // self.arrays.push(Some(data.unbind())); + // self.alloc_counter += core::mem::size_of::>>(); + // Array::from(ArrayIndex::last(&self.arrays)) } } @@ -1097,13 +1093,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/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/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 46780c41c..4802f26af 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -259,7 +259,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..7f463eabf 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -19,43 +19,22 @@ use crate::ecmascript::builtins::{ #[cfg(feature = "weak-refs")] use crate::ecmascript::builtins::{weak_map::WeakMap, weak_ref::WeakRef, weak_set::WeakSet}; use crate::{ - SmallInteger, SmallString, ecmascript::{ abstract_operations::type_conversion::{ to_big_int, to_int16, to_int32, to_number, to_numeric, to_string, to_uint16, to_uint32, try_to_string, }, builtins::{ - Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction, - async_generator_objects::AsyncGenerator, - bound_function::BoundFunction, - control_abstraction_objects::{ + async_generator_objects::AsyncGenerator, bound_function::BoundFunction, control_abstraction_objects::{ generator_objects::Generator, promise_objects::promise_abstract_operations::promise_resolving_functions::BuiltinPromiseResolvingFunction, - }, - embedder_object::EmbedderObject, - error::Error, - finalization_registry::FinalizationRegistry, - indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIterator, - keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIterator, - map::Map, - module::Module, - primitive_objects::PrimitiveObject, - promise::Promise, - proxy::Proxy, - text_processing::string_objects::string_iterator_objects::StringIterator, + }, embedder_object::EmbedderObject, error::Error, finalization_registry::FinalizationRegistry, indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIterator, keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIterator, map::Map, module::Module, primitive_objects::PrimitiveObject, promise::Promise, proxy::Proxy, text_processing::string_objects::string_iterator_objects::StringIterator, Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction }, execution::{Agent, JsResult}, - types::{BUILTIN_STRING_MEMORY, Object}, - }, - engine::{ - Scoped, TryResult, - context::{Bindable, GcScope, NoGcScope}, - rootable::{HeapRootData, HeapRootRef, Rootable}, - small_bigint::SmallBigInt, - small_f64::SmallF64, - }, - heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, + types::{Object, BUILTIN_STRING_MEMORY}, + }, engine::{ + context::{Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}, small_bigint::SmallBigInt, small_f64::SmallF64, Scoped, TryResult + }, heap::{CompactionLists, HeapIndexable as _, HeapMarkAndSweep, SubspaceIndex, WorkQueues}, SmallInteger, SmallString }; #[cfg(feature = "array-buffer")] use crate::{ @@ -258,7 +237,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 e0d0b3e3f..f22d3ba74 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -8,7 +8,6 @@ mod heap_constants; pub(crate) mod heap_gc; pub mod indexes; mod object_entry; -// mod subspace_old; mod subspace; use core::{cell::RefCell, ops::Index}; @@ -105,7 +104,7 @@ pub struct Heap { #[cfg(feature = "array-buffer")] pub array_buffer_detach_keys: AHashMap, DetachKey>, // pub arrays: Vec>>, - pub arrays: IsoSubspace, ArrayHeapData<'static>>, + pub arrays: IsoSubspace>, pub array_iterators: Vec>>, pub async_generators: Vec>>, pub(crate) await_reactions: Vec>>, diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index 18df76143..f09bf959b 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -20,7 +20,7 @@ pub(crate) trait HeapIndexable { pub(crate) trait SubspaceIndex<'a, T: Bindable>: - From>> + HeapIndexable + From> + HeapIndexable { /// # Do not use this /// This is only for Value discriminant creation. @@ -60,7 +60,7 @@ macro_rules! declare_subspace_resident { } } - impl<'a> crate::heap::SubspaceIndex<'a, $Data<'a>> for $Nominal<'a> { + impl<'a> crate::heap::SubspaceIndex<'a, $Data<'static>> for $Nominal<'a> { const _DEF: Self = Self(BaseIndex::from_u32_index(0)); } @@ -79,8 +79,9 @@ macro_rules! declare_subspace_resident { } } - impl crate::heap::IsoSubspaceResident for $Data<'_> { + impl crate::heap::IsoSubspaceResident for $Data<'static> { type Key<'a> = $Nominal<'a>; + type X<'a> = $Data<'a>; } }; } diff --git a/nova_vm/src/heap/subspace/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs index 0c98c3df1..abd2511f9 100644 --- a/nova_vm/src/heap/subspace/iso_subspace.rs +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -1,20 +1,21 @@ +use super::{HeapIndexable, Subspace, SubspaceIndex, SubspaceResident}; use std::{borrow::Borrow, cell::Cell, fmt, marker::PhantomData, ops}; -use super::{SubspaceResident, Subspace, SubspaceIndex, HeapIndexable}; // use crate::{engine::context::Bindable, heap::indexes::BaseIndex}; use crate::heap::*; -pub struct IsoSubspace { +pub struct IsoSubspace { name: &'static str, alloc_count: usize, data: Vec>, - _marker: PhantomData, + // _marker: PhantomData, } -pub trait IsoSubspaceResident : Bindable { +pub trait IsoSubspaceResident: Bindable { type Key<'a>: SubspaceIndex<'a, Self>; + type X<'a>: Bindable = Self>; } -impl IsoSubspace { +impl IsoSubspace { fn new(name: &'static str) -> Self { Self::with_capacity(name, 0) } @@ -24,7 +25,7 @@ impl IsoSubspace { name, alloc_count: 0, data: Vec::with_capacity(capacity), - _marker: PhantomData, + // _marker: PhantomData, } } @@ -36,20 +37,31 @@ impl IsoSubspace { /// TODO: do not rely on len(). subspace will eventually store data across /// various blocks to avoid massive re-allocations #[inline] - pub(super) fn len(&self) -> usize { + pub(crate) fn len(&self) -> usize { self.data.len() } } -impl IsoSubspace +impl IsoSubspace where - T: HeapIndexable + T: IsoSubspaceResident, { - pub fn get(&self, key: T) -> Option<&D> { + pub fn get(&self, key: T::Key<'_>) -> Option<&T> { self.data.get(key.get_index()).map(Option::as_ref).flatten() } - pub fn get_mut(&mut self, key: T) -> Option<&mut D> { - self.data.get_mut(key.get_index()).map(Option::as_mut).flatten() + pub fn get_mut(&mut self, key: T::Key<'_>) -> Option<&mut T> { + self.data + .get_mut(key.get_index()) + .map(Option::as_mut) + .flatten() + } + 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") } } @@ -80,13 +92,13 @@ where // } // } -impl ops::Index> for IsoSubspace, R> +impl ops::Index> for IsoSubspace where - R: IsoSubspaceResident, + T: IsoSubspaceResident, { - type Output = R; + type Output = T; - fn index(&self, index: R::Key<'_>) -> &Self::Output { + fn index(&self, index: T::Key<'_>) -> &Self::Output { self.data .get(index.get_index()) .expect("subspace index out of bounds") @@ -95,11 +107,11 @@ where } } -impl ops::IndexMut> for IsoSubspace, R> +impl ops::IndexMut> for IsoSubspace where - R: IsoSubspaceResident, + T: IsoSubspaceResident, { - fn index_mut(&mut self, index: R::Key<'_>) -> &mut Self::Output { + fn index_mut(&mut self, index: T::Key<'_>) -> &mut Self::Output { self.data .get_mut(index.get_index()) .expect("subspace index out of bounds") @@ -108,44 +120,69 @@ where } } -impl<'a, R> Subspace<'a, R, R::Key<'a>> for IsoSubspace, R> +impl<'a, T> Subspace<'a, T, T::Key<'a>> for IsoSubspace where // R: IsoSubspaceResident + From>>, - R: IsoSubspaceResident, + T: IsoSubspaceResident, { - fn alloc(&'a mut self, data: R) -> R::Key<'a> { + fn alloc(&'a mut self, data: T) -> T::Key<'a> { // SAFETY: this is not safe. fixme. // let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; self.data.push(Some(data)); - self.alloc_count += core::mem::size_of::(); - return R::Key::from(BaseIndex::from_usize(self.data.len())); + self.alloc_count += core::mem::size_of::(); + return T::Key::from(BaseIndex::from_usize(self.data.len())); } } -impl<'a, R> IsoSubspace, R> where - R: IsoSubspaceResident + Bindable, - R::Of<'static> : Into +impl IsoSubspace +where + T: IsoSubspaceResident, { - pub fn create(&mut self, data: R) -> R::Key<'a> { - self.data.push(Some(data.unbind().into())); - self.alloc_count += core::mem::size_of::(); - return R::Key::from(BaseIndex::from_usize(self.data.len())); + pub (crate) fn reserve_intrinsic(&mut self) -> T::Key<'static> { + self.data.push(None); + // note: not from_index b/c len is now +1 + return T::Key::from(BaseIndex::from_usize(self.len())) + + } + pub(crate) fn create<'a>(&mut self, data: T::X<'a>) -> T::Key<'a> + // for<'a> U: IsoSubspaceResident = T::Key<'a>, Of<'a> = T::Of<'a>>, + { + let d: T = unsafe { core::mem::transmute(data.unbind()) }; + + self.data.push(Some(d)); + self.alloc_count += core::mem::size_of::(); + return T::Key::from(BaseIndex::from_usize(self.data.len())); } + // fn create(&mut self, data: U) -> T::Key<'_> + // where + // for<'a> U: IsoSubspaceResident = T::Key<'a>, Of<'a> = T::Of<'a>>, + // { } -impl<'a, T, D> IsoSubspace +// impl<'a, /* static */ T, /* gc bound*/ U> IsoSubspace +// where +// T: IsoSubspaceResident = U>, +// U: IsoSubspaceResident = T>, +// // T::Of<'static> +// { +// pub fn create(&mut self, data: U) -> T::Key<'a> { +// self.data.push(Some(data.unbind())); +// self.alloc_count += core::mem::size_of::(); +// return T::Key::from(BaseIndex::from_usize(self.data.len())); +// } +// } + +impl IsoSubspace where - T: HeapIndexable, //SubspaceIndex<'a, D>, - D: HeapMarkAndSweep - // D: HeapMarkAndSweep + Bindable, + T: IsoSubspaceResident + HeapMarkAndSweep, { - pub(crate) fn mark( - &self, // + pub(crate) fn mark<'a, M>( + &'a self, // marks: M, bits: &mut [bool], queues: &mut WorkQueues, ) where - M: IntoIterator, + M: IntoIterator>, { marks.into_iter().for_each(|idx| { let index = idx.get_index(); @@ -174,7 +211,7 @@ where } } -impl fmt::Debug for IsoSubspace { +impl fmt::Debug for IsoSubspace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IsoSubspace") .field("name", &self.name) From 0b810e100a8b6e577f7f79bb2ecc1edfae5b6757 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 15:56:43 -0700 Subject: [PATCH 05/14] clippy fix --- nova_vm/src/ecmascript/builtins/array.rs | 2 +- nova_vm/src/heap.rs | 2 +- nova_vm/src/heap/subspace/iso_subspace.rs | 15 +++++++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index 35648b978..44c55e5bb 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -33,7 +33,7 @@ use crate::{ heap::{ declare_subspace_resident, element_array::{ ElementArrays, ElementDescriptor, ElementStorageMut, ElementStorageRef, ElementsVector, - }, indexes::{ArrayIndex, BaseIndex}, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IsoSubspace, Subspace as _, WellKnownSymbolIndexes, WorkQueues + }, indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IsoSubspace, WellKnownSymbolIndexes, WorkQueues }, }; diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index f22d3ba74..4e2521ef3 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -66,7 +66,7 @@ use crate::{ promise_reaction_records::PromiseReactionRecord, promise_resolving_functions::PromiseResolvingFunctionHeapData, }, - }, embedder_object::data::EmbedderObjectHeapData, error::ErrorHeapData, finalization_registry::data::FinalizationRegistryHeapData, indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, map::data::MapHeapData, module::{data::ModuleHeapData, Module}, primitive_objects::PrimitiveObjectHeapData, promise::data::PromiseHeapData, proxy::data::ProxyHeapData, text_processing::string_objects::string_iterator_objects::StringIteratorHeapData, Array, ArrayBuffer, ArrayHeapData + }, embedder_object::data::EmbedderObjectHeapData, error::ErrorHeapData, finalization_registry::data::FinalizationRegistryHeapData, indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, map::data::MapHeapData, module::{data::ModuleHeapData, Module}, primitive_objects::PrimitiveObjectHeapData, promise::data::PromiseHeapData, proxy::data::ProxyHeapData, text_processing::string_objects::string_iterator_objects::StringIteratorHeapData, ArrayBuffer, ArrayHeapData }, execution::{Agent, Environments, Realm, RealmRecord}, scripts_and_modules::{ diff --git a/nova_vm/src/heap/subspace/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs index abd2511f9..8603a79f5 100644 --- a/nova_vm/src/heap/subspace/iso_subspace.rs +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -1,5 +1,5 @@ -use super::{HeapIndexable, Subspace, SubspaceIndex, SubspaceResident}; -use std::{borrow::Borrow, cell::Cell, fmt, marker::PhantomData, ops}; +use super::{HeapIndexable, Subspace, SubspaceIndex}; +use std::{fmt, ops}; // use crate::{engine::context::Bindable, heap::indexes::BaseIndex}; use crate::heap::*; @@ -47,13 +47,12 @@ where T: IsoSubspaceResident, { pub fn get(&self, key: T::Key<'_>) -> Option<&T> { - self.data.get(key.get_index()).map(Option::as_ref).flatten() + 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()) - .map(Option::as_mut) - .flatten() + .and_then(Option::as_mut) } pub fn slot(&self, key: T::Key<'_>) -> &Option { self.data.get(key.get_index()).expect("Slot out of bounds") @@ -130,7 +129,7 @@ where // let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; self.data.push(Some(data)); self.alloc_count += core::mem::size_of::(); - return T::Key::from(BaseIndex::from_usize(self.data.len())); + T::Key::from(BaseIndex::from_usize(self.data.len())) } } @@ -141,7 +140,7 @@ where pub (crate) fn reserve_intrinsic(&mut self) -> T::Key<'static> { self.data.push(None); // note: not from_index b/c len is now +1 - return T::Key::from(BaseIndex::from_usize(self.len())) + T::Key::from(BaseIndex::from_usize(self.len())) } pub(crate) fn create<'a>(&mut self, data: T::X<'a>) -> T::Key<'a> @@ -151,7 +150,7 @@ where self.data.push(Some(d)); self.alloc_count += core::mem::size_of::(); - return T::Key::from(BaseIndex::from_usize(self.data.len())); + T::Key::from(BaseIndex::from_usize(self.data.len())) } // fn create(&mut self, data: U) -> T::Key<'_> // where From 9d58ee972ec7dda1a8179f72b433716c8bd07402 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 16:49:47 -0700 Subject: [PATCH 06/14] fn alloc --- nova_vm/src/ecmascript/builtins/array.rs | 41 +++++---- .../builtins/array/abstract_operations.rs | 2 +- nova_vm/src/ecmascript/builtins/ordinary.rs | 5 +- .../src/ecmascript/types/language/value.rs | 33 +++++-- nova_vm/src/heap.rs | 53 ++++++++++-- nova_vm/src/heap/heap_gc.rs | 15 +--- nova_vm/src/heap/subspace.rs | 49 +++++------ nova_vm/src/heap/subspace/iso_subspace.rs | 85 +++---------------- 8 files changed, 138 insertions(+), 145 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index 44c55e5bb..ab48ed0a0 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -24,16 +24,23 @@ use crate::{ }, execution::{Agent, JsResult, ProtoIntrinsics}, types::{ - Function, InternalMethods, InternalSlots, IntoFunction, IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, BUILTIN_STRING_MEMORY + BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, + IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, }, }, engine::{ - context::{Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}, unwrap_try, TryResult + TryResult, + context::{Bindable, GcScope, NoGcScope}, + rootable::{HeapRootData, HeapRootRef, Rootable}, + unwrap_try, }, heap::{ - declare_subspace_resident, element_array::{ + CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + IsoSubspace, WellKnownSymbolIndexes, WorkQueues, declare_subspace_resident, + element_array::{ ElementArrays, ElementDescriptor, ElementStorageMut, ElementStorageRef, ElementsVector, - }, indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IsoSubspace, WellKnownSymbolIndexes, WorkQueues + }, + indexes::BaseIndex, }, }; @@ -42,7 +49,7 @@ pub use data::ArrayHeapData; // #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] // pub struct Array<'a>(ArrayIndex<'a>); -declare_subspace_resident!(iso; struct Array, ArrayHeapData); +declare_subspace_resident!(iso = arrays; struct Array, ArrayHeapData); pub(crate) static ARRAY_INDEX_RANGE: RangeInclusive = 0..=(i64::pow(2, 32) - 2); @@ -156,7 +163,7 @@ impl<'a> Array<'a> { object_index: None, elements: cloned_elements, }; - agent.heap.create(data) + agent.heap.alloc::>(data) } #[inline] @@ -775,16 +782,16 @@ impl Rootable for Array<'_> { } } -impl<'a> CreateHeapData, Array<'a>> for Heap { - fn create(&mut self, data: ArrayHeapData<'a>) -> Array<'a> { - // self.arrays.alloc(data.unbind()) - let arr: Array<'a> = self.arrays.create(data); - arr - // self.arrays.push(Some(data.unbind())); - // self.alloc_counter += core::mem::size_of::>>(); - // Array::from(ArrayIndex::last(&self.arrays)) - } -} +// impl<'a> CreateHeapData, Array<'a>> for Heap { +// fn create(&mut self, data: ArrayHeapData<'a>) -> Array<'a> { +// // self.arrays.alloc(data.unbind()) +// let arr: Array<'a> = self.arrays.create(data); +// arr +// // 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) { @@ -1093,7 +1100,7 @@ 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 IsoSubspace> + arrays: &'a IsoSubspace>, } impl ArrayHeap<'_> { diff --git a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs index c648ce3d8..016d0253a 100644 --- a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs @@ -75,7 +75,7 @@ pub(crate) fn array_create<'a>( }; // 7. Return A. - Ok(agent.heap.create(data)) + Ok(agent.heap.alloc::>(data)) } /// ### [10.4.2.3 ArraySpeciesCreate ( originalArray, length )](https://tc39.es/ecma262/#sec-arrayspeciescreate) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 3fdc85efd..23588f12f 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 + .alloc::>(ArrayHeapData::default()) + .into_object(), #[cfg(feature = "array-buffer")] ProtoIntrinsics::ArrayBuffer => agent .heap diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 7f463eabf..e52cd737e 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -19,22 +19,43 @@ use crate::ecmascript::builtins::{ #[cfg(feature = "weak-refs")] use crate::ecmascript::builtins::{weak_map::WeakMap, weak_ref::WeakRef, weak_set::WeakSet}; use crate::{ + SmallInteger, SmallString, ecmascript::{ abstract_operations::type_conversion::{ to_big_int, to_int16, to_int32, to_number, to_numeric, to_string, to_uint16, to_uint32, try_to_string, }, builtins::{ - async_generator_objects::AsyncGenerator, bound_function::BoundFunction, control_abstraction_objects::{ + Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction, + async_generator_objects::AsyncGenerator, + bound_function::BoundFunction, + control_abstraction_objects::{ generator_objects::Generator, promise_objects::promise_abstract_operations::promise_resolving_functions::BuiltinPromiseResolvingFunction, - }, embedder_object::EmbedderObject, error::Error, finalization_registry::FinalizationRegistry, indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIterator, keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIterator, map::Map, module::Module, primitive_objects::PrimitiveObject, promise::Promise, proxy::Proxy, text_processing::string_objects::string_iterator_objects::StringIterator, Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction + }, + embedder_object::EmbedderObject, + error::Error, + finalization_registry::FinalizationRegistry, + indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIterator, + keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIterator, + map::Map, + module::Module, + primitive_objects::PrimitiveObject, + promise::Promise, + proxy::Proxy, + text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{Agent, JsResult}, - types::{Object, BUILTIN_STRING_MEMORY}, - }, engine::{ - context::{Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}, small_bigint::SmallBigInt, small_f64::SmallF64, Scoped, TryResult - }, heap::{CompactionLists, HeapIndexable as _, HeapMarkAndSweep, SubspaceIndex, WorkQueues}, SmallInteger, SmallString + types::{BUILTIN_STRING_MEMORY, Object}, + }, + engine::{ + Scoped, TryResult, + context::{Bindable, GcScope, NoGcScope}, + rootable::{HeapRootData, HeapRootRef, Rootable}, + small_bigint::SmallBigInt, + small_f64::SmallF64, + }, + heap::{CompactionLists, HeapIndexable as _, HeapMarkAndSweep, SubspaceIndex, WorkQueues}, }; #[cfg(feature = "array-buffer")] use crate::{ diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 4e2521ef3..947843892 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -26,7 +26,6 @@ pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; // pub(crate) use self::subspace::{ // HeapResident, IsoSubspace, Subspace, SubspaceResident, declare_nominal_heap_resident, // }; -pub(crate) use subspace::*; use self::{ element_array::{ ElementArray2Pow8, ElementArray2Pow10, ElementArray2Pow12, ElementArray2Pow16, @@ -57,31 +56,53 @@ use crate::ecmascript::builtins::{ weak_set::data::WeakSetHeapData, }; use crate::{ + SmallInteger, ecmascript::{ builtins::{ - array_buffer::DetachKey, async_generator_objects::AsyncGeneratorHeapData, control_abstraction_objects::{ + ArrayBuffer, ArrayHeapData, + array_buffer::DetachKey, + async_generator_objects::AsyncGeneratorHeapData, + control_abstraction_objects::{ async_function_objects::await_reaction::AwaitReactionRecord, generator_objects::GeneratorHeapData, promise_objects::promise_abstract_operations::{ promise_reaction_records::PromiseReactionRecord, promise_resolving_functions::PromiseResolvingFunctionHeapData, }, - }, embedder_object::data::EmbedderObjectHeapData, error::ErrorHeapData, finalization_registry::data::FinalizationRegistryHeapData, indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, map::data::MapHeapData, module::{data::ModuleHeapData, Module}, primitive_objects::PrimitiveObjectHeapData, promise::data::PromiseHeapData, proxy::data::ProxyHeapData, text_processing::string_objects::string_iterator_objects::StringIteratorHeapData, ArrayBuffer, ArrayHeapData + }, + embedder_object::data::EmbedderObjectHeapData, + error::ErrorHeapData, + finalization_registry::data::FinalizationRegistryHeapData, + indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, + keyed_collections::map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, + map::data::MapHeapData, + module::{Module, data::ModuleHeapData}, + primitive_objects::PrimitiveObjectHeapData, + promise::data::PromiseHeapData, + proxy::data::ProxyHeapData, + text_processing::string_objects::string_iterator_objects::StringIteratorHeapData, }, execution::{Agent, Environments, Realm, RealmRecord}, scripts_and_modules::{ module::module_semantics::{ - source_text_module_records::SourceTextModuleHeap, ModuleRequestRecord + ModuleRequestRecord, source_text_module_records::SourceTextModuleHeap, }, script::{Script, ScriptRecord}, source_code::SourceCodeHeapData, }, types::{ - bigint::HeapBigInt, BigIntHeapData, BoundFunctionHeapData, BuiltinConstructorHeapData, BuiltinFunctionHeapData, ECMAScriptFunctionHeapData, HeapNumber, HeapString, NumberHeapData, Object, ObjectHeapData, OrdinaryObject, PropertyKey, String, StringHeapData, Symbol, SymbolHeapData, Value, BUILTIN_STRINGS_LIST + BUILTIN_STRINGS_LIST, BigIntHeapData, BoundFunctionHeapData, + BuiltinConstructorHeapData, BuiltinFunctionHeapData, ECMAScriptFunctionHeapData, + HeapNumber, HeapString, NumberHeapData, Object, ObjectHeapData, OrdinaryObject, + PropertyKey, String, StringHeapData, Symbol, SymbolHeapData, Value, bigint::HeapBigInt, }, - }, engine::{ - context::{Bindable, NoGcScope}, rootable::HeapRootData, small_f64::SmallF64, ExecutableHeapData - }, SmallInteger + }, + engine::{ + ExecutableHeapData, + context::{Bindable, NoGcScope}, + rootable::HeapRootData, + small_f64::SmallF64, + }, }; #[cfg(feature = "array-buffer")] use ahash::AHashMap; @@ -95,6 +116,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)] @@ -349,6 +371,13 @@ impl Heap { Script::last(&self.scripts) } + 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) + } + // pub(crate) fn alloc<'a, T, U>(&'a mut self, data: T) -> U // where // T: subspace::SubspaceResident, @@ -614,6 +643,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 5d965d739..cd19d8be3 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -326,17 +326,6 @@ 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(); arrays.mark(array_marks, &mut bits.arrays, &mut queues); - // 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); - // } - // }); #[cfg(feature = "array-buffer")] { let mut array_buffer_marks: Box<[ArrayBuffer]> = @@ -1492,9 +1481,7 @@ fn sweep( }); } if !arrays.is_empty() { - s.spawn(|| { - arrays.sweep(&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 index f09bf959b..2f60ca0a4 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -1,49 +1,37 @@ mod iso_subspace; -pub(crate) use iso_subspace::{IsoSubspace, IsoSubspaceResident}; +pub(crate) use iso_subspace::IsoSubspace; use super::*; -// type Ptr<'a, T: ?Sized> = BaseIndex<'a, T>; -pub trait Subspace<'a, T: ?Sized, Ptr: ?Sized> { - fn alloc(&'a mut self, data: T) -> Ptr; +pub trait Subspace { + fn alloc<'a>(&mut self, data: T::Bound<'a>) -> T::Key<'a>; } - -pub trait SubspaceResident<'a, HeapRepr: ?Sized> { - type Space: Subspace<'a, HeapRepr, Self>; +pub(crate) trait SubspaceResident: Bindable { + type Key<'a>: SubspaceIndex<'a, Self>; + type Bound<'a>: Bindable = Self>; +} +pub trait WithSubspace { + type Space: Subspace; + // type Bound<'a> : Bindable = Self>; 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; - // const fn _def() -> Self { - // Self(BaseIndex::from_u32_index(0)) - // } - // fn get_index(self) -> usize; - // { - // self.0.into_index() - // } - // fn id(self) -> BaseIndex<'a, T>; - // fn get_index(self) -> usize { - - // } } -// pub trait IsoSubspaceResident { -// type Data<'a>: Bindable = Self::Data<'a>>; -// } - macro_rules! declare_subspace_resident { - (iso; struct $Nominal:ident, $Data:ident) => { + (iso = $space:ident; struct $Nominal:ident, $Data:ident) => { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct $Nominal<'a>(BaseIndex<'a, $Data<'static>>); @@ -79,9 +67,18 @@ macro_rules! declare_subspace_resident { } } - impl crate::heap::IsoSubspaceResident for $Data<'static> { + impl crate::heap::SubspaceResident for $Data<'static> { type Key<'a> = $Nominal<'a>; - type X<'a> = $Data<'a>; + type Bound<'a> = $Data<'a>; + } + impl crate::heap::WithSubspace<$Data<'static>> for $Nominal<'_> { + type Space = 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 + } } }; } diff --git a/nova_vm/src/heap/subspace/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs index 8603a79f5..9171e594c 100644 --- a/nova_vm/src/heap/subspace/iso_subspace.rs +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -1,4 +1,4 @@ -use super::{HeapIndexable, Subspace, SubspaceIndex}; +use super::{HeapIndexable, Subspace}; use std::{fmt, ops}; // use crate::{engine::context::Bindable, heap::indexes::BaseIndex}; use crate::heap::*; @@ -7,12 +7,6 @@ pub struct IsoSubspace { name: &'static str, alloc_count: usize, data: Vec>, - // _marker: PhantomData, -} - -pub trait IsoSubspaceResident: Bindable { - type Key<'a>: SubspaceIndex<'a, Self>; - type X<'a>: Bindable = Self>; } impl IsoSubspace { @@ -25,7 +19,6 @@ impl IsoSubspace { name, alloc_count: 0, data: Vec::with_capacity(capacity), - // _marker: PhantomData, } } @@ -44,15 +37,13 @@ impl IsoSubspace { impl IsoSubspace where - T: IsoSubspaceResident, + 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) + 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") @@ -64,36 +55,9 @@ where } } -// impl ops::Index for IsoSubspace -// where -// T: HeapIndexable, -// { -// type Output = D; -// fn index(&self, index: T) -> &Self::Output { -// self.data -// .get(index.get_index()) -// .expect("subspace index out of bounds") -// .as_ref() -// .expect("subspace slot is empty") -// } -// } - -// impl ops::IndexMut for IsoSubspace -// where -// T: HeapIndexable, -// { -// fn index_mut(&mut self, index: T) -> &mut Self::Output { -// self.data -// .get_mut(index.get_index()) -// .expect("subspace index out of bounds") -// .as_mut() -// .expect("subspace slot is empty") -// } -// } - impl ops::Index> for IsoSubspace where - T: IsoSubspaceResident, + T: SubspaceResident, { type Output = T; @@ -108,7 +72,7 @@ where impl ops::IndexMut> for IsoSubspace where - T: IsoSubspaceResident, + T: SubspaceResident, { fn index_mut(&mut self, index: T::Key<'_>) -> &mut Self::Output { self.data @@ -119,15 +83,12 @@ where } } -impl<'a, T> Subspace<'a, T, T::Key<'a>> for IsoSubspace +impl Subspace for IsoSubspace where - // R: IsoSubspaceResident + From>>, - T: IsoSubspaceResident, + T: SubspaceResident, { - fn alloc(&'a mut self, data: T) -> T::Key<'a> { - // SAFETY: this is not safe. fixme. - // let d: R::Data<'static> = unsafe { std::mem::transmute(data) }; - self.data.push(Some(data)); + 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())) } @@ -135,45 +96,25 @@ where impl IsoSubspace where - T: IsoSubspaceResident, + T: SubspaceResident, { - pub (crate) fn reserve_intrinsic(&mut self) -> T::Key<'static> { + 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())) - } - pub(crate) fn create<'a>(&mut self, data: T::X<'a>) -> T::Key<'a> - // for<'a> U: IsoSubspaceResident = T::Key<'a>, Of<'a> = T::Of<'a>>, - { + pub(crate) fn create<'a>(&mut self, data: T::Bound<'a>) -> T::Key<'a> { let d: T = unsafe { core::mem::transmute(data.unbind()) }; self.data.push(Some(d)); self.alloc_count += core::mem::size_of::(); T::Key::from(BaseIndex::from_usize(self.data.len())) } - // fn create(&mut self, data: U) -> T::Key<'_> - // where - // for<'a> U: IsoSubspaceResident = T::Key<'a>, Of<'a> = T::Of<'a>>, - // { } -// impl<'a, /* static */ T, /* gc bound*/ U> IsoSubspace -// where -// T: IsoSubspaceResident = U>, -// U: IsoSubspaceResident = T>, -// // T::Of<'static> -// { -// pub fn create(&mut self, data: U) -> T::Key<'a> { -// self.data.push(Some(data.unbind())); -// self.alloc_count += core::mem::size_of::(); -// return T::Key::from(BaseIndex::from_usize(self.data.len())); -// } -// } - impl IsoSubspace where - T: IsoSubspaceResident + HeapMarkAndSweep, + T: SubspaceResident + HeapMarkAndSweep, { pub(crate) fn mark<'a, M>( &'a self, // From 37486845e22a40ee58d51549acb81637bac4e589 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 17:33:34 -0700 Subject: [PATCH 07/14] docs + cleanup --- .../ecmascript/execution/realm/intrinsics.rs | 1 - nova_vm/src/heap/subspace.rs | 13 +++++++++++-- nova_vm/src/heap/subspace/iso_subspace.rs | 17 +++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 4802f26af..71ad49c93 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -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 diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index 2f60ca0a4..cd0e713c0 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -4,16 +4,25 @@ pub(crate) use iso_subspace::IsoSubspace; use super::*; +/// 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 { + // note: please be very selective when expanding this API. + // when possible, prefer expanding APIs for concrete subspaces. fn alloc<'a>(&mut self, data: T::Bound<'a>) -> T::Key<'a>; } -pub(crate) trait SubspaceResident: Bindable { +/// A thing that can live within a [`Subspace`]. +pub trait SubspaceResident: Bindable { type Key<'a>: SubspaceIndex<'a, Self>; type Bound<'a>: Bindable = Self>; } pub trait WithSubspace { type Space: Subspace; - // type Bound<'a> : Bindable = Self>; fn subspace_for(heap: &Heap) -> &Self::Space; fn subspace_for_mut(heap: &mut Heap) -> &mut Self::Space; } diff --git a/nova_vm/src/heap/subspace/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs index 9171e594c..64692e82e 100644 --- a/nova_vm/src/heap/subspace/iso_subspace.rs +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -62,11 +62,12 @@ where type Output = T; fn index(&self, index: T::Key<'_>) -> &Self::Output { + let i = index.get_index(); self.data - .get(index.get_index()) - .expect("subspace index out of bounds") + .get(i) + .unwrap_or_else(|| panic!("subspace {}: index out of bounds", self.name)) .as_ref() - .expect("subspace slot is empty") + .unwrap_or_else(|| panic!("subspace {}: slot {i} is empty", self.name)) } } @@ -75,11 +76,12 @@ where T: SubspaceResident, { fn index_mut(&mut self, index: T::Key<'_>) -> &mut Self::Output { + let i = index.get_index(); self.data - .get_mut(index.get_index()) - .expect("subspace index out of bounds") + .get_mut(i) + .unwrap_or_else(|| panic!("subspace {}: index out of bounds", self.name)) .as_mut() - .expect("subspace slot is empty") + .unwrap_or_else(|| panic!("subspace {}: slot {i} is empty", self.name)) } } @@ -139,6 +141,7 @@ where 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 { @@ -148,6 +151,8 @@ where false } }); + let items_dropped = items_before.saturating_sub(self.data.len()); + self.alloc_count -= items_dropped * core::mem::size_of::() } } From b423f172a9f6f3782d17f939b103bdbf4637797a Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 22 Jul 2025 23:05:40 -0400 Subject: [PATCH 08/14] smol names --- nova_vm/src/heap.rs | 2 +- nova_vm/src/heap/subspace.rs | 18 +++++--- nova_vm/src/heap/subspace/iso_subspace.rs | 52 +++++++++++++++++++++-- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 947843892..5d8838fca 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -251,7 +251,7 @@ impl Heap { array_buffers: Vec::with_capacity(1024), #[cfg(feature = "array-buffer")] array_buffer_detach_keys: AHashMap::with_capacity(0), - arrays: IsoSubspace::with_capacity("array", 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), diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index cd0e713c0..3f70ddcf1 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -4,6 +4,9 @@ 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 @@ -12,8 +15,9 @@ use super::*; /// 2. Subspaces should, but are not required to, store homogenous data. /// Subspaces _may_ choose to upgrade that suggestion to a requirement. pub trait Subspace { - // note: please be very selective when expanding this API. - // when possible, prefer expanding APIs for concrete subspaces. + fn name(&self) -> Option<&str> { + None + } fn alloc<'a>(&mut self, data: T::Bound<'a>) -> T::Key<'a>; } /// A thing that can live within a [`Subspace`]. @@ -42,10 +46,10 @@ pub(crate) trait SubspaceIndex<'a, T: Bindable>: 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>(BaseIndex<'a, $Data<'static>>); + pub struct $Nominal<'a>(crate::heap::indexes::BaseIndex<'a, $Data<'static>>); impl<'a> From>> for $Nominal<'a> { - fn from(value: BaseIndex<'a, $Data<'static>>) -> Self { + fn from(value: crate::heap::indexes::BaseIndex<'a, $Data<'static>>) -> Self { $Nominal(value) } } @@ -58,7 +62,7 @@ macro_rules! declare_subspace_resident { } impl<'a> crate::heap::SubspaceIndex<'a, $Data<'static>> for $Nominal<'a> { - const _DEF: Self = Self(BaseIndex::from_u32_index(0)); + const _DEF: Self = Self(crate::heap::indexes::BaseIndex::from_u32_index(0)); } // SAFETY: Property implemented as a lifetime transmute. @@ -71,7 +75,7 @@ macro_rules! declare_subspace_resident { } #[inline(always)] - fn bind<'a>(self, _gc: NoGcScope<'a, '_>) -> Self::Of<'a> { + fn bind<'a>(self, _gc: crate::engine::context::NoGcScope<'a, '_>) -> Self::Of<'a> { unsafe { core::mem::transmute::>(self) } } } @@ -81,7 +85,7 @@ macro_rules! declare_subspace_resident { type Bound<'a> = $Data<'a>; } impl crate::heap::WithSubspace<$Data<'static>> for $Nominal<'_> { - type Space = IsoSubspace<$Data<'static>>; + type Space = crate::heap::IsoSubspace<$Data<'static>>; fn subspace_for(heap: &Heap) -> &Self::Space { &heap.$space } diff --git a/nova_vm/src/heap/subspace/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs index 64692e82e..0b4bde628 100644 --- a/nova_vm/src/heap/subspace/iso_subspace.rs +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -1,22 +1,62 @@ use super::{HeapIndexable, Subspace}; +use core::ffi::CStr; use std::{fmt, ops}; // use crate::{engine::context::Bindable, heap::indexes::BaseIndex}; use crate::heap::*; +/// A [`Subspace`] storing data of a single [`Sized`] type. pub struct IsoSubspace { - name: &'static str, + /// display name for debugging purposes. + /// + /// We use this instead of &'static str to save a word in IsoSubspace's size. + name: Name, alloc_count: usize, data: Vec>, } +/// 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. +#[derive(Clone, Copy)] +struct Name(*const core::ffi::c_char); +// SAFETY: pointer is &'static and never mutable. +unsafe impl Send for Name {} +unsafe impl Sync for Name {} + +impl Name { + const fn new(s: &'static CStr) -> Self { + assert!(s.to_str().is_ok()); + Self(s.as_ptr()) + } + 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 { CStr::from_ptr(self.0).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 IsoSubspace { - fn new(name: &'static str) -> Self { + fn new(name: &'static CStr) -> Self { Self::with_capacity(name, 0) } - pub fn with_capacity(name: &'static str, capacity: usize) -> Self { + pub fn with_capacity(name: &'static CStr, capacity: usize) -> Self { Self { - name, + name: Name::new(name), alloc_count: 0, data: Vec::with_capacity(capacity), } @@ -89,6 +129,10 @@ 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::(); From c7bb685179f8fa76cfc0273da5cd9105e2d1d898 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 23 Jul 2025 12:58:08 -0400 Subject: [PATCH 09/14] go back to using CreateHeapData --- nova_vm/src/ecmascript/builtins/array.rs | 17 +++++------------ .../builtins/array/abstract_operations.rs | 4 ++-- .../ecmascript/execution/realm/intrinsics.rs | 2 +- nova_vm/src/heap.rs | 2 +- nova_vm/src/heap/subspace.rs | 6 ++++++ 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index ab48ed0a0..970528113 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -24,23 +24,16 @@ use crate::{ }, execution::{Agent, JsResult, ProtoIntrinsics}, types::{ - BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, - IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, + Function, InternalMethods, InternalSlots, IntoFunction, IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, BUILTIN_STRING_MEMORY }, }, engine::{ - TryResult, - context::{Bindable, GcScope, NoGcScope}, - rootable::{HeapRootData, HeapRootRef, Rootable}, - unwrap_try, + context::{Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}, unwrap_try, TryResult }, heap::{ - CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - IsoSubspace, WellKnownSymbolIndexes, WorkQueues, declare_subspace_resident, - element_array::{ + declare_subspace_resident, element_array::{ ElementArrays, ElementDescriptor, ElementStorageMut, ElementStorageRef, ElementsVector, - }, - indexes::BaseIndex, + }, indexes::BaseIndex, CompactionLists, CreateHeapData as _, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IsoSubspace, WellKnownSymbolIndexes, WorkQueues }, }; @@ -163,7 +156,7 @@ impl<'a> Array<'a> { object_index: None, elements: cloned_elements, }; - agent.heap.alloc::>(data) + agent.heap.create(data) } #[inline] diff --git a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs index 016d0253a..19ca31794 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::{Heap, WellKnownSymbolIndexes}, }; /// ### [10.4.2.2 ArrayCreate ( length \[ , proto \] )](https://tc39.es/ecma262/#sec-arraycreate) @@ -75,7 +75,7 @@ pub(crate) fn array_create<'a>( }; // 7. Return A. - Ok(agent.heap.alloc::>(data)) + Ok(agent.heap.create(data)) } /// ### [10.4.2.3 ArraySpeciesCreate ( originalArray, length )](https://tc39.es/ecma262/#sec-arrayspeciescreate) diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 71ad49c93..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, }, }; diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 5d8838fca..65bd545fa 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -373,7 +373,7 @@ impl Heap { pub(crate) fn alloc<'a, T: SubspaceResident>(&mut self, value: T::Bound<'a>) -> T::Key<'a> where - T::Key<'a>: WithSubspace, + T::Key<'a>: WithSubspace { T::Key::subspace_for_mut(self).alloc(value) } diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index 3f70ddcf1..66ad8d473 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -93,6 +93,12 @@ macro_rules! declare_subspace_resident { &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; From b444489136c1a56afc712d586c40b3e863ba6075 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 23 Jul 2025 13:28:28 -0400 Subject: [PATCH 10/14] move name to separate file + add more docs --- nova_vm/src/ecmascript/builtins/array.rs | 15 ++-- .../builtins/array/abstract_operations.rs | 2 +- nova_vm/src/heap.rs | 2 +- nova_vm/src/heap/subspace.rs | 42 +++++++++++ nova_vm/src/heap/subspace/iso_subspace.rs | 71 ++++--------------- nova_vm/src/heap/subspace/name.rs | 46 ++++++++++++ 6 files changed, 114 insertions(+), 64 deletions(-) create mode 100644 nova_vm/src/heap/subspace/name.rs diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index 970528113..113965df8 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -24,16 +24,23 @@ use crate::{ }, execution::{Agent, JsResult, ProtoIntrinsics}, types::{ - Function, InternalMethods, InternalSlots, IntoFunction, IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, BUILTIN_STRING_MEMORY + BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, + IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value, }, }, engine::{ - context::{Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}, unwrap_try, TryResult + TryResult, + context::{Bindable, GcScope, NoGcScope}, + rootable::{HeapRootData, HeapRootRef, Rootable}, + unwrap_try, }, heap::{ - declare_subspace_resident, element_array::{ + CompactionLists, CreateHeapData as _, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + IsoSubspace, WellKnownSymbolIndexes, WorkQueues, declare_subspace_resident, + element_array::{ ElementArrays, ElementDescriptor, ElementStorageMut, ElementStorageRef, ElementsVector, - }, indexes::BaseIndex, CompactionLists, CreateHeapData as _, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IsoSubspace, WellKnownSymbolIndexes, WorkQueues + }, + indexes::BaseIndex, }, }; diff --git a/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs b/nova_vm/src/ecmascript/builtins/array/abstract_operations.rs index 19ca31794..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::{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/heap.rs b/nova_vm/src/heap.rs index 65bd545fa..5d8838fca 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -373,7 +373,7 @@ impl Heap { pub(crate) fn alloc<'a, T: SubspaceResident>(&mut self, value: T::Bound<'a>) -> T::Key<'a> where - T::Key<'a>: WithSubspace + T::Key<'a>: WithSubspace, { T::Key::subspace_for_mut(self).alloc(value) } diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index 66ad8d473..38c37300a 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -1,4 +1,10 @@ +//! Regions of semi-isolated heap-managed memory. +//! +//! ## Notes +//! +//! There a mod iso_subspace; +mod name; pub(crate) use iso_subspace::IsoSubspace; @@ -15,16 +21,52 @@ use super::*; /// 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; diff --git a/nova_vm/src/heap/subspace/iso_subspace.rs b/nova_vm/src/heap/subspace/iso_subspace.rs index 0b4bde628..ca08d6062 100644 --- a/nova_vm/src/heap/subspace/iso_subspace.rs +++ b/nova_vm/src/heap/subspace/iso_subspace.rs @@ -1,54 +1,18 @@ -use super::{HeapIndexable, Subspace}; +use super::{HeapIndexable, Subspace, SubspaceResident, name::Name}; +use crate::heap::{BaseIndex, Bindable, CompactionLists, HeapMarkAndSweep, WorkQueues}; use core::ffi::CStr; use std::{fmt, ops}; -// use crate::{engine::context::Bindable, heap::indexes::BaseIndex}; -use crate::heap::*; /// A [`Subspace`] storing data of a single [`Sized`] type. pub struct IsoSubspace { - /// display name for debugging purposes. + /// Display name for debugging purposes. /// - /// We use this instead of &'static str to save a word in IsoSubspace's size. + /// We use [`Name`] over `&'static str` to save 1 word in memory layout. name: Name, alloc_count: usize, data: Vec>, } -/// 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. -#[derive(Clone, Copy)] -struct Name(*const core::ffi::c_char); -// SAFETY: pointer is &'static and never mutable. -unsafe impl Send for Name {} -unsafe impl Sync for Name {} - -impl Name { - const fn new(s: &'static CStr) -> Self { - assert!(s.to_str().is_ok()); - Self(s.as_ptr()) - } - 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 { CStr::from_ptr(self.0).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 IsoSubspace { fn new(name: &'static CStr) -> Self { Self::with_capacity(name, 0) @@ -82,17 +46,26 @@ where 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 @@ -140,24 +113,6 @@ where } } -impl IsoSubspace -where - T: SubspaceResident, -{ - 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())) - } - pub(crate) fn create<'a>(&mut self, data: T::Bound<'a>) -> T::Key<'a> { - let d: T = unsafe { core::mem::transmute(data.unbind()) }; - - self.data.push(Some(d)); - self.alloc_count += core::mem::size_of::(); - T::Key::from(BaseIndex::from_usize(self.data.len())) - } -} - impl IsoSubspace where T: SubspaceResident + HeapMarkAndSweep, diff --git a/nova_vm/src/heap/subspace/name.rs b/nova_vm/src/heap/subspace/name.rs new file mode 100644 index 000000000..e3360a87e --- /dev/null +++ b/nova_vm/src/heap/subspace/name.rs @@ -0,0 +1,46 @@ +use core::{ffi, fmt}; + +/// 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(*const ffi::c_char); +// 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()); + Self(s.as_ptr()) + } + 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).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() + } +} From 7d54a856f66af65dc9acd3af9a84a1649485caf4 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 23 Jul 2025 13:51:51 -0400 Subject: [PATCH 11/14] more docs --- nova_vm/src/heap/subspace.rs | 16 ++++++++++++++++ nova_vm/src/heap/subspace/name.rs | 14 ++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index 38c37300a..b37f34760 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -85,6 +85,22 @@ pub(crate) trait SubspaceIndex<'a, T: Bindable>: 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)] diff --git a/nova_vm/src/heap/subspace/name.rs b/nova_vm/src/heap/subspace/name.rs index e3360a87e..69475b2bb 100644 --- a/nova_vm/src/heap/subspace/name.rs +++ b/nova_vm/src/heap/subspace/name.rs @@ -1,4 +1,4 @@ -use core::{ffi, fmt}; +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 @@ -7,7 +7,12 @@ use core::{ffi, fmt}; /// /// Do not expose this outside of the `subspace` module. #[derive(Clone, Copy)] -pub(super) struct Name(*const ffi::c_char); +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 {} @@ -15,12 +20,13 @@ unsafe impl Sync for Name {} impl Name { pub const fn new(s: &'static ffi::CStr) -> Self { assert!(s.to_str().is_ok()); - Self(s.as_ptr()) + 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).to_str() } { + match unsafe { ffi::CStr::from_ptr(self.0.as_ref() as *const _).to_str() } { Ok(s) => s, Err(_) => unreachable!(), } From b38fc519f5506756df1605ef78686b77d2d99e68 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 23 Jul 2025 14:08:52 -0400 Subject: [PATCH 12/14] docs + cleanup --- nova_vm/src/ecmascript/builtins/array.rs | 31 ------------------------ nova_vm/src/heap.rs | 13 +--------- nova_vm/src/heap/subspace.rs | 29 +++++++++++++++++++++- 3 files changed, 29 insertions(+), 44 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/array.rs b/nova_vm/src/ecmascript/builtins/array.rs index 113965df8..8dd210f7b 100644 --- a/nova_vm/src/ecmascript/builtins/array.rs +++ b/nova_vm/src/ecmascript/builtins/array.rs @@ -739,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; @@ -782,17 +762,6 @@ impl Rootable for Array<'_> { } } -// impl<'a> CreateHeapData, Array<'a>> for Heap { -// fn create(&mut self, data: ArrayHeapData<'a>) -> Array<'a> { -// // self.arrays.alloc(data.unbind()) -// let arr: Array<'a> = self.arrays.create(data); -// arr -// // 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); diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 5d8838fca..bb76da9f0 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -23,9 +23,6 @@ pub(crate) use self::heap_constants::{ LAST_INTRINSIC_CONSTRUCTOR_INDEX, LAST_INTRINSIC_FUNCTION_INDEX, LAST_INTRINSIC_OBJECT_INDEX, }; pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; -// pub(crate) use self::subspace::{ -// HeapResident, IsoSubspace, Subspace, SubspaceResident, declare_nominal_heap_resident, -// }; use self::{ element_array::{ ElementArray2Pow8, ElementArray2Pow10, ElementArray2Pow12, ElementArray2Pow16, @@ -371,6 +368,7 @@ 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, @@ -378,15 +376,6 @@ impl Heap { T::Key::subspace_for_mut(self).alloc(value) } - // pub(crate) fn alloc<'a, T, U>(&'a mut self, data: T) -> U - // where - // T: subspace::SubspaceResident, - // U: From>, - // { - // let subspace = T::subspace_for_mut(self); - // subspace.alloc(data).into() - // } - /// Allocate a borrowed string onto the Agent heap /// /// This method will hash the input and look for a matching string on the diff --git a/nova_vm/src/heap/subspace.rs b/nova_vm/src/heap/subspace.rs index b37f34760..52f832282 100644 --- a/nova_vm/src/heap/subspace.rs +++ b/nova_vm/src/heap/subspace.rs @@ -2,7 +2,34 @@ //! //! ## Notes //! -//! There a +//! 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; From a92f89b7d0c304516ba44aa8d252a1caf3475781 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 23 Jul 2025 14:30:02 -0400 Subject: [PATCH 13/14] undo debug=false --- Cargo.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f99668105..5b540c283 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,11 +58,6 @@ libraries = [{ path = "nova_lint" }] [profile.release] lto = true -[profile.dev] -debug = false -[profile.test] -debug = false - # This profile has all the same safety checks as dev builds. It trades slightly # longer compile times for faster runs, which is worth it when running test262. [profile.dev-fast] From de3b3b7b8880a258352af0cb5988e74447917057 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 23 Jul 2025 14:34:34 -0400 Subject: [PATCH 14/14] cleanup --- nova_vm/src/ecmascript/builtins/ordinary.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 23588f12f..4d7801aa5 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -1213,7 +1213,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( let object = match proto_intrinsics { ProtoIntrinsics::Array => agent .heap - .alloc::>(ArrayHeapData::default()) + .create(ArrayHeapData::default()) .into_object(), #[cfg(feature = "array-buffer")] ProtoIntrinsics::ArrayBuffer => agent