diff --git a/stack-graphs/src/arena.rs b/stack-graphs/src/arena.rs index 6e4747267..a6785292b 100644 --- a/stack-graphs/src/arena.rs +++ b/stack-graphs/src/arena.rs @@ -187,6 +187,7 @@ impl Arena { #[inline(always)] pub(crate) fn clear(&mut self) { self.items.clear(); + self.items.push(MaybeUninit::uninit()); } /// Adds a new instance to this arena, returning a stable handle to it. @@ -294,6 +295,7 @@ impl SupplementalArena { #[inline(always)] pub(crate) fn clear(&mut self) { self.items.clear(); + self.items.push(MaybeUninit::uninit()); } /// Creates a new, empty supplemental arena, preallocating enough space to store supplemental diff --git a/stack-graphs/src/cycles.rs b/stack-graphs/src/cycles.rs index d10679385..c18e6892e 100644 --- a/stack-graphs/src/cycles.rs +++ b/stack-graphs/src/cycles.rs @@ -52,6 +52,12 @@ pub struct SimilarPathDetector

{ paths: HashMap>, } +impl

SimilarPathDetector

{ + pub fn clear(&mut self) { + self.paths.clear(); + } +} + #[doc(hidden)] #[derive(Clone, Eq, Hash, PartialEq)] pub struct PathKey { @@ -163,6 +169,11 @@ impl Appendables { interned: Arena::new(), } } + + pub fn clear(&mut self) { + self.elements.clear(); + self.interned.clear(); + } } /// Enum that unifies handles to initial paths interned in the cycle detector, and appended diff --git a/stack-graphs/src/stitching.rs b/stack-graphs/src/stitching.rs index 3d02a14be..2caf19f8e 100644 --- a/stack-graphs/src/stitching.rs +++ b/stack-graphs/src/stitching.rs @@ -41,9 +41,6 @@ use std::collections::VecDeque; #[cfg(feature = "copious-debugging")] use std::fmt::Display; -use itertools::izip; -use itertools::Itertools; - use crate::arena::Arena; use crate::arena::Handle; use crate::arena::HandleSet; @@ -763,17 +760,13 @@ impl<'a> Display for DisplaySymbolStackKey<'a> { pub struct ForwardPartialPathStitcher { candidates: Vec, extensions: Vec<(PartialPath, AppendingCycleDetector)>, - queue: VecDeque<(PartialPath, AppendingCycleDetector, bool)>, + queue: VecDeque<(PartialPath, PathStitchingState)>, // tracks the number of initial paths in the queue because we do not want call // extend_until on those initial_paths: usize, // next_iteration is a tuple of queues instead of an queue of tuples so that the path queue // can be cheaply exposed through the C API as a continuous memory block - next_iteration: ( - VecDeque, - VecDeque>, - VecDeque, - ), + next_iteration: (VecDeque, VecDeque>), appended_paths: Appendables, similar_path_detector: Option>, check_only_join_nodes: bool, @@ -782,40 +775,83 @@ pub struct ForwardPartialPathStitcher { phase_number: usize, } +struct PathStitchingState { + cycle_detector: AppendingCycleDetector, + has_split: bool, +} + +impl Default for ForwardPartialPathStitcher { + fn default() -> Self { + Self { + candidates: Vec::new(), + extensions: Vec::new(), + queue: VecDeque::new(), + initial_paths: 0, + next_iteration: (VecDeque::new(), VecDeque::new()), + appended_paths: Appendables::new(), + similar_path_detector: Some(SimilarPathDetector::new()), + // By default, all nodes are checked for cycles and (if enabled) similarity + check_only_join_nodes: false, + // By default, there's no artificial bound on the amount of work done per phase + max_work_per_phase: usize::MAX, + #[cfg(feature = "copious-debugging")] + phase_number: 1, + } + } +} + impl ForwardPartialPathStitcher { /// Creates a new forward partial path stitcher that is "seeded" with a set of initial partial /// paths. If the sticher is used to find complete paths, it is the responsibility of the caller /// to ensure precondition variables are eliminated by calling [`PartialPath::eliminate_precondition_stack_variables`][]. pub fn from_partial_paths( - _graph: &StackGraph, - _partials: &mut PartialPaths, + graph: &StackGraph, + partials: &mut PartialPaths, initial_partial_paths: I, ) -> Self where I: IntoIterator, { - let mut appended_paths = Appendables::new(); - let next_iteration: (VecDeque<_>, VecDeque<_>, VecDeque<_>) = initial_partial_paths + let mut result = Self::default(); + result.reset(graph, partials, initial_partial_paths); + result + } + + pub fn reset( + &mut self, + _graph: &StackGraph, + _partials: &mut PartialPaths, + initial_partial_paths: I, + ) where + I: IntoIterator, + { + self.appended_paths.clear(); + self.next_iteration = initial_partial_paths .into_iter() .map(|p| { - let c = AppendingCycleDetector::from(&mut appended_paths, p.clone().into()); - (p, c, false) + let cycle_detector = + AppendingCycleDetector::from(&mut self.appended_paths, p.clone().into()); + ( + p, + PathStitchingState { + cycle_detector, + has_split: false, + }, + ) }) - .multiunzip(); - Self { - candidates: Vec::new(), - extensions: Vec::new(), - queue: VecDeque::new(), - initial_paths: next_iteration.0.len(), - next_iteration, - appended_paths, - similar_path_detector: Some(SimilarPathDetector::new()), - // By default, all nodes are checked for cycles and (if enabled) similarity - check_only_join_nodes: false, - // By default, there's no artificial bound on the amount of work done per phase - max_work_per_phase: usize::MAX, - #[cfg(feature = "copious-debugging")] - phase_number: 1, + .unzip(); + self.initial_paths = self.next_iteration.0.len(); + self.candidates.clear(); + self.extensions.clear(); + self.queue.clear(); + if let Some(similar_path_detector) = &mut self.similar_path_detector { + similar_path_detector.clear(); + } + // keep self.check_only_join_nodes + // keep self.max_work_per_phase + #[cfg(feature = "copious-debugging")] + { + self.phase_number = 1; } } } @@ -952,7 +988,6 @@ impl ForwardPartialPathStitcher { let new_has_split = has_split || self.extensions.len() > 1; self.next_iteration.0.reserve(extension_count); self.next_iteration.1.reserve(extension_count); - self.next_iteration.2.reserve(extension_count); for (new_partial_path, new_cycle_detector) in self.extensions.drain(..) { let check_similar_path = new_has_split && (!self.check_only_join_nodes @@ -990,8 +1025,10 @@ impl ForwardPartialPathStitcher { } self.next_iteration.0.push(new_partial_path); - self.next_iteration.1.push(new_cycle_detector); - self.next_iteration.2.push(new_has_split); + self.next_iteration.1.push(PathStitchingState { + cycle_detector: new_cycle_detector, + has_split: new_has_split, + }); } candidate_count @@ -1022,13 +1059,21 @@ impl ForwardPartialPathStitcher { E: Fn(&StackGraph, &mut PartialPaths, &PartialPath) -> bool, { copious_debugging!("==> Start phase {}", self.phase_number); - self.queue.extend(izip!( - self.next_iteration.0.drain(..), - self.next_iteration.1.drain(..), - self.next_iteration.2.drain(..), - )); + self.queue.extend( + self.next_iteration + .0 + .drain(..) + .zip(self.next_iteration.1.drain(..)), + ); let mut work_performed = 0; - while let Some((partial_path, cycle_detector, has_split)) = self.queue.pop_front() { + while let Some(( + partial_path, + PathStitchingState { + cycle_detector, + has_split, + }, + )) = self.queue.pop_front() + { let (graph, partials, _) = candidates.get_graph_partials_and_db(); copious_debugging!( "--> Candidate partial path {}", @@ -1101,18 +1146,22 @@ impl ForwardPartialPathStitcher { .filter(|node| graph[*node].is_endpoint()) .map(|node| PartialPath::from_node(graph, partials, node)) .collect::>(); - let mut stitcher = - ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths); + + let mut stitcher = ForwardPartialPathStitcher::default(); stitcher.set_check_only_join_nodes(true); - while !stitcher.is_complete() { - cancellation_flag.check("finding complete partial paths")?; - stitcher.process_next_phase( - &mut GraphEdgeCandidates::new(graph, partials, Some(file)), - |g, _ps, p| !as_complete_as_necessary(g, p), - ); - for path in stitcher.previous_phase_partial_paths() { - if as_complete_as_necessary(graph, path) { - visit(graph, partials, path); + + for initial_path in initial_paths { + stitcher.reset(graph, partials, std::iter::once(initial_path)); + while !stitcher.is_complete() { + cancellation_flag.check("finding complete partial paths")?; + stitcher.process_next_phase( + &mut GraphEdgeCandidates::new(graph, partials, Some(file)), + |g, _ps, p| !as_complete_as_necessary(g, p), + ); + for path in stitcher.previous_phase_partial_paths() { + if as_complete_as_necessary(graph, path) { + visit(graph, partials, path); + } } } } @@ -1146,33 +1195,38 @@ impl ForwardPartialPathStitcher { F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath), Err: std::convert::From, { - let mut stitcher = { - let (graph, partials, _) = candidates.get_graph_partials_and_db(); - let initial_paths = starting_nodes - .into_iter() - .filter(|n| graph[*n].is_reference()) - .map(|n| { - let mut p = PartialPath::from_node(graph, partials, n); - p.eliminate_precondition_stack_variables(partials); - p - }) - .collect::>(); - ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths) - }; + let (graph, partials, _) = candidates.get_graph_partials_and_db(); + let initial_paths = starting_nodes + .into_iter() + .filter(|n| graph[*n].is_reference()) + .map(|n| { + let mut p = PartialPath::from_node(graph, partials, n); + p.eliminate_precondition_stack_variables(partials); + p + }) + .collect::>(); + + let mut stitcher = ForwardPartialPathStitcher::default(); stitcher.set_check_only_join_nodes(true); - while !stitcher.is_complete() { - cancellation_flag.check("finding complete partial paths")?; - for path in stitcher.previous_phase_partial_paths() { - candidates.load_forward_candidates(path, cancellation_flag)?; - } - stitcher.process_next_phase(candidates, |_, _, _| true); + + for initial_path in initial_paths { let (graph, partials, _) = candidates.get_graph_partials_and_db(); - for path in stitcher.previous_phase_partial_paths() { - if path.is_complete(graph) { - visit(graph, partials, path); + stitcher.reset(graph, partials, std::iter::once(initial_path)); + while !stitcher.is_complete() { + cancellation_flag.check("finding complete partial paths")?; + for path in stitcher.previous_phase_partial_paths() { + candidates.load_forward_candidates(path, cancellation_flag)?; + } + stitcher.process_next_phase(candidates, |_, _, _| true); + let (graph, partials, _) = candidates.get_graph_partials_and_db(); + for path in stitcher.previous_phase_partial_paths() { + if path.is_complete(graph) { + visit(graph, partials, path); + } } } } + Ok(()) } } diff --git a/stack-graphs/tests/it/serde.rs b/stack-graphs/tests/it/serde.rs index 04679f353..fbff97946 100644 --- a/stack-graphs/tests/it/serde.rs +++ b/stack-graphs/tests/it/serde.rs @@ -996,55 +996,6 @@ fn can_serialize_partial_paths() { // formatted using: json_pp -json_opt utf8,canonical,pretty,indent_length=4 let expected = json!( [ - { - "edges" : [ - { - "precedence" : 0, - "source" : { - "file" : "test.py", - "local_id" : 3 - } - }, - { - "precedence" : 0, - "source" : { - "file" : "test.py", - "local_id" : 8 - } - } - ], - "end_node" : { - "file" : "test.py", - "local_id" : 9 - }, - "scope_stack_postcondition" : { - "scopes" : [], - "variable" : 1 - }, - "scope_stack_precondition" : { - "scopes" : [], - "variable" : 1 - }, - "start_node" : { - "file" : "test.py", - "local_id" : 3 - }, - "symbol_stack_postcondition" : { - "symbols" : [], - "variable" : 1 - }, - "symbol_stack_precondition" : { - "symbols" : [ - { - "symbol" : "." - }, - { - "symbol" : "x" - } - ], - "variable" : 1 - } - }, { "edges" : [ { @@ -1270,6 +1221,55 @@ fn can_serialize_partial_paths() { "symbols" : [], "variable" : 1 } + }, + { + "edges" : [ + { + "precedence" : 0, + "source" : { + "file" : "test.py", + "local_id" : 3 + } + }, + { + "precedence" : 0, + "source" : { + "file" : "test.py", + "local_id" : 8 + } + } + ], + "end_node" : { + "file" : "test.py", + "local_id" : 9 + }, + "scope_stack_postcondition" : { + "scopes" : [], + "variable" : 1 + }, + "scope_stack_precondition" : { + "scopes" : [], + "variable" : 1 + }, + "start_node" : { + "file" : "test.py", + "local_id" : 3 + }, + "symbol_stack_postcondition" : { + "symbols" : [], + "variable" : 1 + }, + "symbol_stack_precondition" : { + "symbols" : [ + { + "symbol" : "." + }, + { + "symbol" : "x" + } + ], + "variable" : 1 + } } ] );