From 85fe615036d246aa1dbfac764c5071289cc3faa8 Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 13 Jun 2025 15:19:32 +0200 Subject: [PATCH] move stack into submodule --- .../rustc_type_ir/src/search_graph/mod.rs | 70 ++--------- .../rustc_type_ir/src/search_graph/stack.rs | 114 ++++++++++++++++++ 2 files changed, 127 insertions(+), 57 deletions(-) create mode 100644 compiler/rustc_type_ir/src/search_graph/stack.rs diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 1acd5d5c2af4..6302ebbb9aed 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -26,6 +26,8 @@ use tracing::debug; use crate::data_structures::HashMap; +mod stack; +use stack::{Stack, StackDepth, StackEntry}; mod global_cache; use global_cache::CacheData; pub use global_cache::GlobalCache; @@ -225,9 +227,9 @@ impl AvailableDepth { /// in case there is exponential blowup. fn allowed_depth_for_nested( root_depth: AvailableDepth, - stack: &IndexVec>, + stack: &Stack, ) -> Option { - if let Some(last) = stack.raw.last() { + if let Some(last) = stack.last() { if last.available_depth.0 == 0 { return None; } @@ -433,50 +435,6 @@ impl NestedGoals { } } -rustc_index::newtype_index! { - #[orderable] - #[gate_rustc_only] - pub struct StackDepth {} -} - -/// Stack entries of the evaluation stack. Its fields tend to be lazily -/// when popping a child goal or completely immutable. -#[derive_where(Debug; X: Cx)] -struct StackEntry { - input: X::Input, - - /// Whether proving this goal is a coinductive step. - /// - /// This is used when encountering a trait solver cycle to - /// decide whether the initial provisional result of the cycle. - step_kind_from_parent: PathKind, - - /// The available depth of a given goal, immutable. - available_depth: AvailableDepth, - - /// The maximum depth reached by this stack entry, only up-to date - /// for the top of the stack and lazily updated for the rest. - reached_depth: StackDepth, - - /// All cycle heads this goal depends on. Lazily updated and only - /// up-to date for the top of the stack. - heads: CycleHeads, - - /// Whether evaluating this goal encountered overflow. Lazily updated. - encountered_overflow: bool, - - /// Whether this goal has been used as the root of a cycle. This gets - /// eagerly updated when encountering a cycle. - has_been_used: Option, - - /// The nested goals of this goal, see the doc comment of the type. - nested_goals: NestedGoals, - - /// Starts out as `None` and gets set when rerunning this - /// goal in case we encounter a cycle. - provisional_result: Option, -} - /// A provisional result of an already computed goals which depends on other /// goals still on the stack. #[derive_where(Debug; X: Cx)] @@ -498,7 +456,7 @@ pub struct SearchGraph, X: Cx = ::Cx> { /// The stack of goals currently being computed. /// /// An element is *deeper* in the stack if its index is *lower*. - stack: IndexVec>, + stack: Stack, /// The provisional cache contains entries for already computed goals which /// still depend on goals higher-up in the stack. We don't move them to the /// global cache and track them locally instead. A provisional cache entry @@ -537,7 +495,7 @@ impl, X: Cx> SearchGraph { /// and using existing global cache entries to make sure they /// have the same impact on the remaining evaluation. fn update_parent_goal( - stack: &mut IndexVec>, + stack: &mut Stack, step_kind_from_parent: PathKind, reached_depth: StackDepth, heads: &CycleHeads, @@ -588,13 +546,11 @@ impl, X: Cx> SearchGraph { /// the stack which completes the cycle. This given an inductive step AB which then cycles /// coinductively with A, we need to treat this cycle as coinductive. fn cycle_path_kind( - stack: &IndexVec>, + stack: &Stack, step_kind_to_head: PathKind, head: StackDepth, ) -> PathKind { - stack.raw[head.index() + 1..] - .iter() - .fold(step_kind_to_head, |curr, entry| curr.extend(entry.step_kind_from_parent)) + stack.cycle_step_kinds(head).fold(step_kind_to_head, |curr, step| curr.extend(step)) } /// Probably the most involved method of the whole solver. @@ -728,7 +684,7 @@ impl, X: Cx> SearchGraph { input: X::Input, inspect: &mut D::ProofTreeBuilder, ) -> X::Result { - if let Some(last) = self.stack.raw.last_mut() { + if let Some(last) = self.stack.last_mut() { last.encountered_overflow = true; // If computing a goal `B` depends on another goal `A` and // `A` has a nested goal which overflows, then computing `B` @@ -859,7 +815,7 @@ impl, X: Cx> SearchGraph { // apply provisional cache entries which encountered overflow once the // current goal is already part of the same cycle. This check could be // improved but seems to be good enough for now. - let last = self.stack.raw.last().unwrap(); + let last = self.stack.last().unwrap(); if last.heads.opt_lowest_cycle_head().is_none_or(|lowest| lowest > head) { continue; } @@ -893,7 +849,7 @@ impl, X: Cx> SearchGraph { /// evaluating this entry would not have ended up depending on either a goal /// already on the stack or a provisional cache entry. fn candidate_is_applicable( - stack: &IndexVec>, + stack: &Stack, step_kind_from_parent: PathKind, provisional_cache: &HashMap>>, nested_goals: &NestedGoals, @@ -1028,7 +984,7 @@ impl, X: Cx> SearchGraph { input: X::Input, step_kind_from_parent: PathKind, ) -> Option { - let (head, _stack_entry) = self.stack.iter_enumerated().find(|(_, e)| e.input == input)?; + let head = self.stack.find(input)?; // We have a nested goal which directly relies on a goal deeper in the stack. // // We start by tagging all cycle participants, as that's necessary for caching. @@ -1095,7 +1051,7 @@ impl, X: Cx> SearchGraph { let mut i = 0; loop { let result = evaluate_goal(self, inspect); - let stack_entry = self.stack.pop().unwrap(); + let stack_entry = self.stack.pop(); debug_assert_eq!(stack_entry.input, input); // If the current goal is not the root of a cycle, we are done. diff --git a/compiler/rustc_type_ir/src/search_graph/stack.rs b/compiler/rustc_type_ir/src/search_graph/stack.rs new file mode 100644 index 000000000000..1dea818d9e39 --- /dev/null +++ b/compiler/rustc_type_ir/src/search_graph/stack.rs @@ -0,0 +1,114 @@ +use std::ops::{Index, IndexMut}; + +use derive_where::derive_where; +use rustc_index::IndexVec; + +use super::{AvailableDepth, Cx, CycleHeads, NestedGoals, PathKind, UsageKind}; + +rustc_index::newtype_index! { + #[orderable] + #[gate_rustc_only] + pub(super) struct StackDepth {} +} + +/// Stack entries of the evaluation stack. Its fields tend to be lazily +/// when popping a child goal or completely immutable. +#[derive_where(Debug; X: Cx)] +pub(super) struct StackEntry { + pub input: X::Input, + + /// Whether proving this goal is a coinductive step. + /// + /// This is used when encountering a trait solver cycle to + /// decide whether the initial provisional result of the cycle. + pub step_kind_from_parent: PathKind, + + /// The available depth of a given goal, immutable. + pub available_depth: AvailableDepth, + + /// The maximum depth reached by this stack entry, only up-to date + /// for the top of the stack and lazily updated for the rest. + pub reached_depth: StackDepth, + + /// All cycle heads this goal depends on. Lazily updated and only + /// up-to date for the top of the stack. + pub heads: CycleHeads, + + /// Whether evaluating this goal encountered overflow. Lazily updated. + pub encountered_overflow: bool, + + /// Whether this goal has been used as the root of a cycle. This gets + /// eagerly updated when encountering a cycle. + pub has_been_used: Option, + + /// The nested goals of this goal, see the doc comment of the type. + pub nested_goals: NestedGoals, + + /// Starts out as `None` and gets set when rerunning this + /// goal in case we encounter a cycle. + pub provisional_result: Option, +} + +#[derive_where(Default; X: Cx)] +pub(super) struct Stack { + entries: IndexVec>, +} + +impl Stack { + pub(super) fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub(super) fn len(&self) -> usize { + self.entries.len() + } + + pub(super) fn last_index(&self) -> Option { + self.entries.last_index() + } + + pub(super) fn last(&self) -> Option<&StackEntry> { + self.entries.raw.last() + } + + pub(super) fn last_mut(&mut self) -> Option<&mut StackEntry> { + self.entries.raw.last_mut() + } + + pub(super) fn next_index(&self) -> StackDepth { + self.entries.next_index() + } + + pub(super) fn push(&mut self, entry: StackEntry) -> StackDepth { + self.entries.push(entry) + } + + pub(super) fn pop(&mut self) -> StackEntry { + self.entries.pop().unwrap() + } + + pub(super) fn cycle_step_kinds(&self, head: StackDepth) -> impl Iterator { + self.entries.raw[head.index() + 1..].iter().map(|entry| entry.step_kind_from_parent) + } + + pub(super) fn iter(&self) -> impl Iterator> { + self.entries.iter() + } + + pub(super) fn find(&self, input: X::Input) -> Option { + self.entries.iter_enumerated().find(|(_, e)| e.input == input).map(|(idx, _)| idx) + } +} + +impl Index for Stack { + type Output = StackEntry; + fn index(&self, index: StackDepth) -> &StackEntry { + &self.entries[index] + } +} + +impl IndexMut for Stack { + fn index_mut(&mut self, index: StackDepth) -> &mut Self::Output { + &mut self.entries[index] + } +}