diff --git a/rust/rubydex/src/resolution.rs b/rust/rubydex/src/resolution.rs index 9ee152af..d713bce4 100644 --- a/rust/rubydex/src/resolution.rs +++ b/rust/rubydex/src/resolution.rs @@ -99,6 +99,14 @@ pub struct Resolver<'a> { unit_queue: VecDeque, /// Whether we made any progress in the last pass of the resolution loop made_progress: bool, + /// Spare buffers for the parent ancestor chains. Linearization recurses, and each level needs + /// one buffer, thus the pool keeps the buffers alive between calls to avoid one heap + /// allocation for each linearized declaration. + chain_pool: Vec>, + /// Spare buffers for the collected mixins of a declaration, reused the same way as `chain_pool`. + mixin_pool: Vec>, + /// Spare deques for the linearized prepends and includes, reused the same way as `chain_pool`. + deque_pool: Vec>, /// The linearization context carried across recursion context: LinearizationContext, } @@ -109,10 +117,46 @@ impl<'a> Resolver<'a> { graph, unit_queue: VecDeque::new(), made_progress: false, + chain_pool: Vec::new(), + mixin_pool: Vec::new(), + deque_pool: Vec::new(), context: LinearizationContext::new(), } } + /// Take an empty chain buffer from the pool, or make a new one if the pool is empty. + fn take_chain_buffer(&mut self) -> Vec { + self.chain_pool.pop().unwrap_or_default() + } + + /// Give a chain buffer back to the pool for the next linearization to use. + fn return_chain_buffer(&mut self, mut buffer: Vec) { + buffer.clear(); + self.chain_pool.push(buffer); + } + + /// Take an empty mixin buffer from the pool, or make a new one if the pool is empty. + fn take_mixin_buffer(&mut self) -> Vec { + self.mixin_pool.pop().unwrap_or_default() + } + + /// Give a mixin buffer back to the pool. + fn return_mixin_buffer(&mut self, mut buffer: Vec) { + buffer.clear(); + self.mixin_pool.push(buffer); + } + + /// Take an empty deque from the pool, or make a new one if the pool is empty. + fn take_deque(&mut self) -> VecDeque { + self.deque_pool.pop().unwrap_or_default() + } + + /// Give a deque back to the pool. + fn return_deque(&mut self, mut deque: VecDeque) { + deque.clear(); + self.deque_pool.push(deque); + } + /// Runs the resolution phase on the graph. The resolution phase is when 4 main pieces of information are computed: /// /// 1. Declarations for all definitions @@ -1037,9 +1081,11 @@ impl<'a> Resolver<'a> { } let mut state = ChainState::default(); - let parent_ancestors = self.linearize_parent_ancestors(declaration_id, &mut state); + let mut parent_buffer = self.take_chain_buffer(); + let has_parent = self.linearize_parent_ancestors_into(declaration_id, &mut state, &mut parent_buffer); + + let mut mixins = self.take_mixin_buffer(); let declaration = self.graph.declarations().get(&declaration_id).unwrap(); - let mut mixins = Vec::new(); let is_singleton_class = matches!(declaration, Declaration::Namespace(Namespace::SingletonClass(_))); @@ -1074,18 +1120,34 @@ impl<'a> Resolver<'a> { self.get_or_create_singleton_class(declaration_id, SingletonAncestors::Enqueue); } - let (linearized_prepends, linearized_includes) = - self.linearize_mixins(mixins, parent_ancestors.as_ref(), &mut state); + let mut linearized_prepends = self.take_deque(); + let mut linearized_includes = self.take_deque(); + + self.linearize_mixins( + &mut state, + &mixins, + has_parent.then_some(parent_buffer.as_slice()), + &mut linearized_prepends, + &mut linearized_includes, + ); - // Build the final list - let mut ancestors = Vec::new(); - ancestors.extend(linearized_prepends); + // Build the final list with its final capacity. + let final_len = linearized_prepends.len() + 1 + linearized_includes.len() + parent_buffer.len(); + let mut ancestors = Vec::with_capacity(final_len); + ancestors.extend(linearized_prepends.drain(..)); ancestors.push(Ancestor::Complete(declaration_id)); - ancestors.extend(linearized_includes); - if let Some(parents) = parent_ancestors { - ancestors.extend(parents); + ancestors.extend(linearized_includes.drain(..)); + if has_parent { + ancestors.extend_from_slice(&parent_buffer); } + // The scratch buffers gave up their contents to the new list, thus they go back to the pool + // for the next linearization to use. + self.return_chain_buffer(parent_buffer); + self.return_mixin_buffer(mixins); + self.return_deque(linearized_prepends); + self.return_deque(linearized_includes); + let result = if state.cyclic { Ancestors::Cyclic(ancestors) } else if state.partial { @@ -1106,17 +1168,21 @@ impl<'a> Resolver<'a> { result } - fn linearize_parent_ancestors( + /// Copy the parent ancestor chain of a declaration into `buffer`. + /// + /// Returns `true` if the declaration has a parent. A module, or a class without a superclass, + /// has no parent and leaves `buffer` untouched. + fn linearize_parent_ancestors_into( &mut self, declaration_id: DeclarationId, state: &mut ChainState, - ) -> Option> { + buffer: &mut Vec, + ) -> bool { let declaration = self.graph.declarations().get(&declaration_id).unwrap(); match declaration { Declaration::Namespace(Namespace::Class(_)) => { - let superclass = self.linearize_superclass(declaration_id, state)?; - Some(state.fold(superclass)) + self.linearize_superclass_into(declaration_id, state, buffer) } Declaration::Namespace(Namespace::SingletonClass(_)) => { let owner_id = *declaration.owner_id(); @@ -1126,10 +1192,16 @@ impl<'a> Resolver<'a> { state.partial = true; } - let parent = self.linearize_ancestors(singleton_parent_id); - Some(state.fold(parent)) + let result = self.linearize_ancestors(singleton_parent_id); + match &result { + Ancestors::Complete(_) => {} + Ancestors::Cyclic(_) => state.cyclic = true, + Ancestors::Partial(_) => state.partial = true, + } + buffer.extend(result.iter()); + true } - _ => None, + _ => false, } } @@ -1137,13 +1209,12 @@ impl<'a> Resolver<'a> { /// modules are deduplicated against them fn linearize_mixins( &mut self, - mixins: Vec, - parent_ancestors: Option<&Vec>, state: &mut ChainState, - ) -> (VecDeque, VecDeque) { - let mut linearized_prepends = VecDeque::new(); - let mut linearized_includes = VecDeque::new(); - + mixins: &[Mixin], + parent_ancestors: Option<&[Ancestor]>, + linearized_prepends: &mut VecDeque, + linearized_includes: &mut VecDeque, + ) { // IMPORTANT! In the slice of mixins we receive, extends are the ones that occurred in the attached object, which we // collect ahead of time. This is the reason why we apparently treat an extend like an include, because an extend in // the attached object is equivalent to an include in the singleton class @@ -1199,9 +1270,7 @@ impl<'a> Resolver<'a> { ids.retain(|id| { !linearized_prepends.contains(id) && !linearized_includes.contains(id) - && parent_ancestors - .as_ref() - .is_none_or(|parent_ids| !parent_ids.contains(id)) + && parent_ancestors.is_none_or(|parent_ids| !parent_ids.contains(id)) }); for id in ids.into_iter().rev() { @@ -1218,8 +1287,6 @@ impl<'a> Resolver<'a> { } } } - - (linearized_prepends, linearized_includes) } /// Propagate descendants to all cached ancestors @@ -2063,24 +2130,41 @@ impl<'a> Resolver<'a> { )) } - fn linearize_superclass(&mut self, declaration_id: DeclarationId, state: &mut ChainState) -> Option { - let (superclass_id, unresolved_superclass) = self.get_superclass(declaration_id)?; - let mut result = self.linearize_ancestors(superclass_id); + /// Copy the linearized superclass chain of a class into `buffer`. Returns `true` if the class + /// has a superclass. + /// + /// An unresolved superclass reference means the chain must be partial, never cyclic. A cyclic + /// chain counts as settled (`has_complete_ancestors` is true for `Cyclic`), which would stop + /// retries for the unresolved reference. + fn linearize_superclass_into( + &mut self, + declaration_id: DeclarationId, + state: &mut ChainState, + buffer: &mut Vec, + ) -> bool { + let Some((superclass_id, unresolved_superclass)) = self.get_superclass(declaration_id) else { + return false; + }; + + let result = self.linearize_ancestors(superclass_id); if let Some(name_id) = unresolved_superclass { state.partial = true; - // Insert the unresolved superclass as a Partial ancestor at the front of the chain, so it + // Put the unresolved superclass as a Partial ancestor at the front of the chain, so it // appears before the default Object ancestors - let ancestors = match &mut result { - Ancestors::Complete(ids) | Ancestors::Cyclic(ids) | Ancestors::Partial(ids) => ids, - }; - ancestors.insert(0, Ancestor::Partial(name_id)); - - result = result.to_partial(); + buffer.push(Ancestor::Partial(name_id)); + buffer.extend(result.iter()); + } else { + match &result { + Ancestors::Complete(_) => {} + Ancestors::Cyclic(_) => state.cyclic = true, + Ancestors::Partial(_) => state.partial = true, + } + buffer.extend(result.iter()); } - Some(result) + true } fn mixins_of(&self, definition_id: DefinitionId) -> &[Mixin] {