1
Fork 0

Rollup merge of #117394 - lcnr:proof-tree-cache4, r=compiler-errors

use global cache when computing proof trees

we're writing the solver while relying on the existence of the global cache to avoid exponential blowup. By disabling the global cache when building proof trees, it is easy to get hangs, e.g. when computing intercrate ambiguity causes.

Removes the unstable `-Zdump_solver_proof_tree_use_cache` option, as we now always return a full proof tree.

r? `@compiler-errors`
This commit is contained in:
Matthias Krüger 2023-11-02 15:31:20 +01:00 committed by GitHub
commit 298edd6d46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 138 additions and 126 deletions

View file

@ -69,6 +69,7 @@ macro_rules! arena_types {
[] dtorck_constraint: rustc_middle::traits::query::DropckConstraint<'tcx>, [] dtorck_constraint: rustc_middle::traits::query::DropckConstraint<'tcx>,
[] candidate_step: rustc_middle::traits::query::CandidateStep<'tcx>, [] candidate_step: rustc_middle::traits::query::CandidateStep<'tcx>,
[] autoderef_bad_ty: rustc_middle::traits::query::MethodAutoderefBadTy<'tcx>, [] autoderef_bad_ty: rustc_middle::traits::query::MethodAutoderefBadTy<'tcx>,
[] canonical_goal_evaluation: rustc_middle::traits::solve::inspect::GoalEvaluationStep<'tcx>,
[] query_region_constraints: rustc_middle::infer::canonical::QueryRegionConstraints<'tcx>, [] query_region_constraints: rustc_middle::infer::canonical::QueryRegionConstraints<'tcx>,
[] type_op_subtype: [] type_op_subtype:
rustc_middle::infer::canonical::Canonical<'tcx, rustc_middle::infer::canonical::Canonical<'tcx,

View file

@ -1,4 +1,4 @@
use super::{CanonicalInput, QueryResult}; use super::{inspect, CanonicalInput, QueryResult};
use crate::ty::TyCtxt; use crate::ty::TyCtxt;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::sync::Lock; use rustc_data_structures::sync::Lock;
@ -14,8 +14,10 @@ pub struct EvaluationCache<'tcx> {
map: Lock<FxHashMap<CanonicalInput<'tcx>, CacheEntry<'tcx>>>, map: Lock<FxHashMap<CanonicalInput<'tcx>, CacheEntry<'tcx>>>,
} }
#[derive(PartialEq, Eq)]
pub struct CacheData<'tcx> { pub struct CacheData<'tcx> {
pub result: QueryResult<'tcx>, pub result: QueryResult<'tcx>,
pub proof_tree: Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]>,
pub reached_depth: usize, pub reached_depth: usize,
pub encountered_overflow: bool, pub encountered_overflow: bool,
} }
@ -24,22 +26,33 @@ impl<'tcx> EvaluationCache<'tcx> {
/// Insert a final result into the global cache. /// Insert a final result into the global cache.
pub fn insert( pub fn insert(
&self, &self,
tcx: TyCtxt<'tcx>,
key: CanonicalInput<'tcx>, key: CanonicalInput<'tcx>,
proof_tree: Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]>,
reached_depth: usize, reached_depth: usize,
did_overflow: bool, encountered_overflow: bool,
cycle_participants: FxHashSet<CanonicalInput<'tcx>>, cycle_participants: FxHashSet<CanonicalInput<'tcx>>,
dep_node: DepNodeIndex, dep_node: DepNodeIndex,
result: QueryResult<'tcx>, result: QueryResult<'tcx>,
) { ) {
let mut map = self.map.borrow_mut(); let mut map = self.map.borrow_mut();
let entry = map.entry(key).or_default(); let entry = map.entry(key).or_default();
let data = WithDepNode::new(dep_node, result); let data = WithDepNode::new(dep_node, QueryData { result, proof_tree });
entry.cycle_participants.extend(cycle_participants); entry.cycle_participants.extend(cycle_participants);
if did_overflow { if encountered_overflow {
entry.with_overflow.insert(reached_depth, data); entry.with_overflow.insert(reached_depth, data);
} else { } else {
entry.success = Some(Success { data, reached_depth }); entry.success = Some(Success { data, reached_depth });
} }
if cfg!(debug_assertions) {
drop(map);
if Some(CacheData { result, proof_tree, reached_depth, encountered_overflow })
!= self.get(tcx, key, |_| false, Limit(reached_depth))
{
bug!("unable to retrieve inserted element from cache: {key:?}");
}
}
} }
/// Try to fetch a cached result, checking the recursion limit /// Try to fetch a cached result, checking the recursion limit
@ -62,27 +75,39 @@ impl<'tcx> EvaluationCache<'tcx> {
if let Some(ref success) = entry.success { if let Some(ref success) = entry.success {
if available_depth.value_within_limit(success.reached_depth) { if available_depth.value_within_limit(success.reached_depth) {
let QueryData { result, proof_tree } = success.data.get(tcx);
return Some(CacheData { return Some(CacheData {
result: success.data.get(tcx), result,
proof_tree,
reached_depth: success.reached_depth, reached_depth: success.reached_depth,
encountered_overflow: false, encountered_overflow: false,
}); });
} }
} }
entry.with_overflow.get(&available_depth.0).map(|e| CacheData { entry.with_overflow.get(&available_depth.0).map(|e| {
result: e.get(tcx), let QueryData { result, proof_tree } = e.get(tcx);
reached_depth: available_depth.0, CacheData {
encountered_overflow: true, result,
proof_tree,
reached_depth: available_depth.0,
encountered_overflow: true,
}
}) })
} }
} }
struct Success<'tcx> { struct Success<'tcx> {
data: WithDepNode<QueryResult<'tcx>>, data: WithDepNode<QueryData<'tcx>>,
reached_depth: usize, reached_depth: usize,
} }
#[derive(Clone, Copy)]
pub struct QueryData<'tcx> {
pub result: QueryResult<'tcx>,
pub proof_tree: Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]>,
}
/// The cache entry for a goal `CanonicalInput`. /// The cache entry for a goal `CanonicalInput`.
/// ///
/// This contains results whose computation never hit the /// This contains results whose computation never hit the
@ -96,5 +121,5 @@ struct CacheEntry<'tcx> {
/// See the doc comment of `StackEntry::cycle_participants` for more /// See the doc comment of `StackEntry::cycle_participants` for more
/// details. /// details.
cycle_participants: FxHashSet<CanonicalInput<'tcx>>, cycle_participants: FxHashSet<CanonicalInput<'tcx>>,
with_overflow: FxHashMap<usize, WithDepNode<QueryResult<'tcx>>>, with_overflow: FxHashMap<usize, WithDepNode<QueryData<'tcx>>>,
} }

View file

@ -42,12 +42,6 @@ pub struct State<'tcx, T> {
pub type CanonicalState<'tcx, T> = Canonical<'tcx, State<'tcx, T>>; pub type CanonicalState<'tcx, T> = Canonical<'tcx, State<'tcx, T>>;
#[derive(Debug, Eq, PartialEq)]
pub enum CacheHit {
Provisional,
Global,
}
/// When evaluating the root goals we also store the /// When evaluating the root goals we also store the
/// original values for the `CanonicalVarValues` of the /// original values for the `CanonicalVarValues` of the
/// canonicalized goal. We use this to map any [CanonicalState] /// canonicalized goal. We use this to map any [CanonicalState]
@ -78,8 +72,8 @@ pub struct CanonicalGoalEvaluation<'tcx> {
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
pub enum CanonicalGoalEvaluationKind<'tcx> { pub enum CanonicalGoalEvaluationKind<'tcx> {
Overflow, Overflow,
CacheHit(CacheHit), CycleInStack,
Uncached { revisions: Vec<GoalEvaluationStep<'tcx>> }, Evaluation { revisions: &'tcx [GoalEvaluationStep<'tcx>] },
} }
impl Debug for GoalEvaluation<'_> { impl Debug for GoalEvaluation<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View file

@ -74,13 +74,10 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
CanonicalGoalEvaluationKind::Overflow => { CanonicalGoalEvaluationKind::Overflow => {
writeln!(self.f, "OVERFLOW: {:?}", eval.result) writeln!(self.f, "OVERFLOW: {:?}", eval.result)
} }
CanonicalGoalEvaluationKind::CacheHit(CacheHit::Global) => { CanonicalGoalEvaluationKind::CycleInStack => {
writeln!(self.f, "GLOBAL CACHE HIT: {:?}", eval.result) writeln!(self.f, "CYCLE IN STACK: {:?}", eval.result)
} }
CanonicalGoalEvaluationKind::CacheHit(CacheHit::Provisional) => { CanonicalGoalEvaluationKind::Evaluation { revisions } => {
writeln!(self.f, "PROVISIONAL CACHE HIT: {:?}", eval.result)
}
CanonicalGoalEvaluationKind::Uncached { revisions } => {
for (n, step) in revisions.iter().enumerate() { for (n, step) in revisions.iter().enumerate() {
writeln!(self.f, "REVISION {n}")?; writeln!(self.f, "REVISION {n}")?;
self.nested(|this| this.format_evaluation_step(step))?; self.nested(|this| this.format_evaluation_step(step))?;

View file

@ -1529,8 +1529,6 @@ options! {
dump_solver_proof_tree: DumpSolverProofTree = (DumpSolverProofTree::Never, parse_dump_solver_proof_tree, [UNTRACKED], dump_solver_proof_tree: DumpSolverProofTree = (DumpSolverProofTree::Never, parse_dump_solver_proof_tree, [UNTRACKED],
"dump a proof tree for every goal evaluated by the new trait solver. If the flag is specified without any options after it "dump a proof tree for every goal evaluated by the new trait solver. If the flag is specified without any options after it
then it defaults to `always`. If the flag is not specified at all it defaults to `on-request`."), then it defaults to `always`. If the flag is not specified at all it defaults to `on-request`."),
dump_solver_proof_tree_use_cache: Option<bool> = (None, parse_opt_bool, [UNTRACKED],
"determines whether dumped proof trees use the global cache"),
dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED], dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED],
"version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"),
dylib_lto: bool = (false, parse_bool, [UNTRACKED], dylib_lto: bool = (false, parse_bool, [UNTRACKED],

View file

@ -119,25 +119,11 @@ impl NestedGoals<'_> {
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
pub enum GenerateProofTree { pub enum GenerateProofTree {
Yes(UseGlobalCache), Yes,
IfEnabled, IfEnabled,
Never, Never,
} }
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
pub enum UseGlobalCache {
Yes,
No,
}
impl UseGlobalCache {
pub fn from_bool(use_cache: bool) -> Self {
match use_cache {
true => UseGlobalCache::Yes,
false => UseGlobalCache::No,
}
}
}
pub trait InferCtxtEvalExt<'tcx> { pub trait InferCtxtEvalExt<'tcx> {
/// Evaluates a goal from **outside** of the trait solver. /// Evaluates a goal from **outside** of the trait solver.
/// ///

View file

@ -17,7 +17,7 @@ use rustc_middle::traits::solve::{Certainty, Goal};
use rustc_middle::ty; use rustc_middle::ty;
use crate::solve::inspect::ProofTreeBuilder; use crate::solve::inspect::ProofTreeBuilder;
use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache}; use crate::solve::{GenerateProofTree, InferCtxtEvalExt};
pub struct InspectGoal<'a, 'tcx> { pub struct InspectGoal<'a, 'tcx> {
infcx: &'a InferCtxt<'tcx>, infcx: &'a InferCtxt<'tcx>,
@ -82,8 +82,7 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
} }
for &goal in &instantiated_goals { for &goal in &instantiated_goals {
let (_, proof_tree) = let (_, proof_tree) = infcx.evaluate_root_goal(goal, GenerateProofTree::Yes);
infcx.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No));
let proof_tree = proof_tree.unwrap(); let proof_tree = proof_tree.unwrap();
visitor.visit_goal(&InspectGoal::new( visitor.visit_goal(&InspectGoal::new(
infcx, infcx,
@ -169,11 +168,11 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
let mut candidates = vec![]; let mut candidates = vec![];
let last_eval_step = match self.evaluation.evaluation.kind { let last_eval_step = match self.evaluation.evaluation.kind {
inspect::CanonicalGoalEvaluationKind::Overflow inspect::CanonicalGoalEvaluationKind::Overflow
| inspect::CanonicalGoalEvaluationKind::CacheHit(_) => { | inspect::CanonicalGoalEvaluationKind::CycleInStack => {
warn!("unexpected root evaluation: {:?}", self.evaluation); warn!("unexpected root evaluation: {:?}", self.evaluation);
return vec![]; return vec![];
} }
inspect::CanonicalGoalEvaluationKind::Uncached { ref revisions } => { inspect::CanonicalGoalEvaluationKind::Evaluation { ref revisions } => {
if let Some(last) = revisions.last() { if let Some(last) = revisions.last() {
last last
} else { } else {
@ -227,8 +226,7 @@ impl<'tcx> ProofTreeInferCtxtExt<'tcx> for InferCtxt<'tcx> {
goal: Goal<'tcx, ty::Predicate<'tcx>>, goal: Goal<'tcx, ty::Predicate<'tcx>>,
visitor: &mut V, visitor: &mut V,
) -> ControlFlow<V::BreakTy> { ) -> ControlFlow<V::BreakTy> {
let (_, proof_tree) = let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes);
self.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No));
let proof_tree = proof_tree.unwrap(); let proof_tree = proof_tree.unwrap();
visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree)) visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree))
} }

View file

@ -3,6 +3,8 @@
//! This code is *a bit* of a mess and can hopefully be //! This code is *a bit* of a mess and can hopefully be
//! mostly ignored. For a general overview of how it works, //! mostly ignored. For a general overview of how it works,
//! see the comment on [ProofTreeBuilder]. //! see the comment on [ProofTreeBuilder].
use std::mem;
use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::solve::{ use rustc_middle::traits::solve::{
CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult, CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult,
@ -10,7 +12,6 @@ use rustc_middle::traits::solve::{
use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::ty::{self, TyCtxt};
use rustc_session::config::DumpSolverProofTree; use rustc_session::config::DumpSolverProofTree;
use crate::solve::eval_ctxt::UseGlobalCache;
use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree}; use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree};
/// The core data structure when building proof trees. /// The core data structure when building proof trees.
@ -34,12 +35,7 @@ use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree};
/// is called to recursively convert the whole structure to a /// is called to recursively convert the whole structure to a
/// finished proof tree. /// finished proof tree.
pub(in crate::solve) struct ProofTreeBuilder<'tcx> { pub(in crate::solve) struct ProofTreeBuilder<'tcx> {
state: Option<Box<BuilderData<'tcx>>>, state: Option<Box<DebugSolver<'tcx>>>,
}
struct BuilderData<'tcx> {
tree: DebugSolver<'tcx>,
use_global_cache: UseGlobalCache,
} }
/// The current state of the proof tree builder, at most places /// The current state of the proof tree builder, at most places
@ -118,36 +114,46 @@ pub(in crate::solve) enum WipGoalEvaluationKind<'tcx> {
Nested { is_normalizes_to_hack: IsNormalizesToHack }, Nested { is_normalizes_to_hack: IsNormalizesToHack },
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq)]
pub(in crate::solve) enum WipCanonicalGoalEvaluationKind { pub(in crate::solve) enum WipCanonicalGoalEvaluationKind<'tcx> {
Overflow, Overflow,
CacheHit(inspect::CacheHit), CycleInStack,
Interned { revisions: &'tcx [inspect::GoalEvaluationStep<'tcx>] },
}
impl std::fmt::Debug for WipCanonicalGoalEvaluationKind<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Overflow => write!(f, "Overflow"),
Self::CycleInStack => write!(f, "CycleInStack"),
Self::Interned { revisions: _ } => f.debug_struct("Interned").finish_non_exhaustive(),
}
}
} }
#[derive(Eq, PartialEq, Debug)] #[derive(Eq, PartialEq, Debug)]
struct WipCanonicalGoalEvaluation<'tcx> { struct WipCanonicalGoalEvaluation<'tcx> {
goal: CanonicalInput<'tcx>, goal: CanonicalInput<'tcx>,
kind: Option<WipCanonicalGoalEvaluationKind>, kind: Option<WipCanonicalGoalEvaluationKind<'tcx>>,
/// Only used for uncached goals. After we finished evaluating
/// the goal, this is interned and moved into `kind`.
revisions: Vec<WipGoalEvaluationStep<'tcx>>, revisions: Vec<WipGoalEvaluationStep<'tcx>>,
result: Option<QueryResult<'tcx>>, result: Option<QueryResult<'tcx>>,
} }
impl<'tcx> WipCanonicalGoalEvaluation<'tcx> { impl<'tcx> WipCanonicalGoalEvaluation<'tcx> {
fn finalize(self) -> inspect::CanonicalGoalEvaluation<'tcx> { fn finalize(self) -> inspect::CanonicalGoalEvaluation<'tcx> {
let kind = match self.kind { assert!(self.revisions.is_empty());
Some(WipCanonicalGoalEvaluationKind::Overflow) => { let kind = match self.kind.unwrap() {
WipCanonicalGoalEvaluationKind::Overflow => {
inspect::CanonicalGoalEvaluationKind::Overflow inspect::CanonicalGoalEvaluationKind::Overflow
} }
Some(WipCanonicalGoalEvaluationKind::CacheHit(hit)) => { WipCanonicalGoalEvaluationKind::CycleInStack => {
inspect::CanonicalGoalEvaluationKind::CacheHit(hit) inspect::CanonicalGoalEvaluationKind::CycleInStack
}
WipCanonicalGoalEvaluationKind::Interned { revisions } => {
inspect::CanonicalGoalEvaluationKind::Evaluation { revisions }
} }
None => inspect::CanonicalGoalEvaluationKind::Uncached {
revisions: self
.revisions
.into_iter()
.map(WipGoalEvaluationStep::finalize)
.collect(),
},
}; };
inspect::CanonicalGoalEvaluation { goal: self.goal, kind, result: self.result.unwrap() } inspect::CanonicalGoalEvaluation { goal: self.goal, kind, result: self.result.unwrap() }
@ -226,33 +232,20 @@ impl<'tcx> WipProbeStep<'tcx> {
} }
impl<'tcx> ProofTreeBuilder<'tcx> { impl<'tcx> ProofTreeBuilder<'tcx> {
fn new( fn new(state: impl Into<DebugSolver<'tcx>>) -> ProofTreeBuilder<'tcx> {
state: impl Into<DebugSolver<'tcx>>, ProofTreeBuilder { state: Some(Box::new(state.into())) }
use_global_cache: UseGlobalCache,
) -> ProofTreeBuilder<'tcx> {
ProofTreeBuilder {
state: Some(Box::new(BuilderData { tree: state.into(), use_global_cache })),
}
} }
fn nested<T: Into<DebugSolver<'tcx>>>(&self, state: impl FnOnce() -> T) -> Self { fn nested<T: Into<DebugSolver<'tcx>>>(&self, state: impl FnOnce() -> T) -> Self {
match &self.state { ProofTreeBuilder { state: self.state.as_ref().map(|_| Box::new(state().into())) }
Some(prev_state) => Self {
state: Some(Box::new(BuilderData {
tree: state().into(),
use_global_cache: prev_state.use_global_cache,
})),
},
None => Self { state: None },
}
} }
fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> { fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> {
self.state.as_mut().map(|boxed| &mut boxed.tree) self.state.as_deref_mut()
} }
pub fn finalize(self) -> Option<inspect::GoalEvaluation<'tcx>> { pub fn finalize(self) -> Option<inspect::GoalEvaluation<'tcx>> {
match self.state?.tree { match *self.state? {
DebugSolver::GoalEvaluation(wip_goal_evaluation) => { DebugSolver::GoalEvaluation(wip_goal_evaluation) => {
Some(wip_goal_evaluation.finalize()) Some(wip_goal_evaluation.finalize())
} }
@ -260,13 +253,6 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
} }
} }
pub fn use_global_cache(&self) -> bool {
self.state
.as_ref()
.map(|state| matches!(state.use_global_cache, UseGlobalCache::Yes))
.unwrap_or(true)
}
pub fn new_maybe_root( pub fn new_maybe_root(
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
generate_proof_tree: GenerateProofTree, generate_proof_tree: GenerateProofTree,
@ -276,10 +262,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
GenerateProofTree::IfEnabled => { GenerateProofTree::IfEnabled => {
let opts = &tcx.sess.opts.unstable_opts; let opts = &tcx.sess.opts.unstable_opts;
match opts.dump_solver_proof_tree { match opts.dump_solver_proof_tree {
DumpSolverProofTree::Always => { DumpSolverProofTree::Always => ProofTreeBuilder::new_root(),
let use_cache = opts.dump_solver_proof_tree_use_cache.unwrap_or(true);
ProofTreeBuilder::new_root(UseGlobalCache::from_bool(use_cache))
}
// `OnError` is handled by reevaluating goals in error // `OnError` is handled by reevaluating goals in error
// reporting with `GenerateProofTree::Yes`. // reporting with `GenerateProofTree::Yes`.
DumpSolverProofTree::OnError | DumpSolverProofTree::Never => { DumpSolverProofTree::OnError | DumpSolverProofTree::Never => {
@ -287,12 +270,12 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
} }
} }
} }
GenerateProofTree::Yes(use_cache) => ProofTreeBuilder::new_root(use_cache), GenerateProofTree::Yes => ProofTreeBuilder::new_root(),
} }
} }
pub fn new_root(use_global_cache: UseGlobalCache) -> ProofTreeBuilder<'tcx> { pub fn new_root() -> ProofTreeBuilder<'tcx> {
ProofTreeBuilder::new(DebugSolver::Root, use_global_cache) ProofTreeBuilder::new(DebugSolver::Root)
} }
pub fn new_noop() -> ProofTreeBuilder<'tcx> { pub fn new_noop() -> ProofTreeBuilder<'tcx> {
@ -336,9 +319,27 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
}) })
} }
pub fn finalize_evaluation(
&mut self,
tcx: TyCtxt<'tcx>,
) -> Option<&'tcx [inspect::GoalEvaluationStep<'tcx>]> {
self.as_mut().map(|this| match this {
DebugSolver::CanonicalGoalEvaluation(evaluation) => {
let revisions = mem::take(&mut evaluation.revisions)
.into_iter()
.map(WipGoalEvaluationStep::finalize);
let revisions = &*tcx.arena.alloc_from_iter(revisions);
let kind = WipCanonicalGoalEvaluationKind::Interned { revisions };
assert_eq!(evaluation.kind.replace(kind), None);
revisions
}
_ => unreachable!(),
})
}
pub fn canonical_goal_evaluation(&mut self, canonical_goal_evaluation: ProofTreeBuilder<'tcx>) { pub fn canonical_goal_evaluation(&mut self, canonical_goal_evaluation: ProofTreeBuilder<'tcx>) {
if let Some(this) = self.as_mut() { if let Some(this) = self.as_mut() {
match (this, canonical_goal_evaluation.state.unwrap().tree) { match (this, *canonical_goal_evaluation.state.unwrap()) {
( (
DebugSolver::GoalEvaluation(goal_evaluation), DebugSolver::GoalEvaluation(goal_evaluation),
DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation), DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation),
@ -348,7 +349,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
} }
} }
pub fn goal_evaluation_kind(&mut self, kind: WipCanonicalGoalEvaluationKind) { pub fn goal_evaluation_kind(&mut self, kind: WipCanonicalGoalEvaluationKind<'tcx>) {
if let Some(this) = self.as_mut() { if let Some(this) = self.as_mut() {
match this { match this {
DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation) => { DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation) => {
@ -372,7 +373,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
} }
pub fn goal_evaluation(&mut self, goal_evaluation: ProofTreeBuilder<'tcx>) { pub fn goal_evaluation(&mut self, goal_evaluation: ProofTreeBuilder<'tcx>) {
if let Some(this) = self.as_mut() { if let Some(this) = self.as_mut() {
match (this, goal_evaluation.state.unwrap().tree) { match (this, *goal_evaluation.state.unwrap()) {
( (
DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation { DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation {
evaluations, .. evaluations, ..
@ -396,7 +397,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
} }
pub fn goal_evaluation_step(&mut self, goal_evaluation_step: ProofTreeBuilder<'tcx>) { pub fn goal_evaluation_step(&mut self, goal_evaluation_step: ProofTreeBuilder<'tcx>) {
if let Some(this) = self.as_mut() { if let Some(this) = self.as_mut() {
match (this, goal_evaluation_step.state.unwrap().tree) { match (this, *goal_evaluation_step.state.unwrap()) {
( (
DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluations), DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluations),
DebugSolver::GoalEvaluationStep(goal_evaluation_step), DebugSolver::GoalEvaluationStep(goal_evaluation_step),
@ -444,7 +445,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
pub fn finish_probe(&mut self, probe: ProofTreeBuilder<'tcx>) { pub fn finish_probe(&mut self, probe: ProofTreeBuilder<'tcx>) {
if let Some(this) = self.as_mut() { if let Some(this) = self.as_mut() {
match (this, probe.state.unwrap().tree) { match (this, *probe.state.unwrap()) {
( (
DebugSolver::Probe(WipProbe { steps, .. }) DebugSolver::Probe(WipProbe { steps, .. })
| DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { | DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
@ -486,7 +487,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
pub fn added_goals_evaluation(&mut self, added_goals_evaluation: ProofTreeBuilder<'tcx>) { pub fn added_goals_evaluation(&mut self, added_goals_evaluation: ProofTreeBuilder<'tcx>) {
if let Some(this) = self.as_mut() { if let Some(this) = self.as_mut() {
match (this, added_goals_evaluation.state.unwrap().tree) { match (this, *added_goals_evaluation.state.unwrap()) {
( (
DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
evaluation: WipProbe { steps, .. }, evaluation: WipProbe { steps, .. },

View file

@ -38,9 +38,7 @@ mod project_goals;
mod search_graph; mod search_graph;
mod trait_goals; mod trait_goals;
pub use eval_ctxt::{ pub use eval_ctxt::{EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt};
EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt, UseGlobalCache,
};
pub use fulfill::FulfillmentCtxt; pub use fulfill::FulfillmentCtxt;
pub(crate) use normalize::{deeply_normalize, deeply_normalize_with_skipped_universes}; pub(crate) use normalize::{deeply_normalize, deeply_normalize_with_skipped_universes};

View file

@ -6,7 +6,6 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_index::Idx; use rustc_index::Idx;
use rustc_index::IndexVec; use rustc_index::IndexVec;
use rustc_middle::dep_graph::dep_kinds; use rustc_middle::dep_graph::dep_kinds;
use rustc_middle::traits::solve::inspect::CacheHit;
use rustc_middle::traits::solve::CacheData; use rustc_middle::traits::solve::CacheData;
use rustc_middle::traits::solve::{CanonicalInput, Certainty, EvaluationCache, QueryResult}; use rustc_middle::traits::solve::{CanonicalInput, Certainty, EvaluationCache, QueryResult};
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
@ -191,8 +190,8 @@ impl<'tcx> SearchGraph<'tcx> {
}; };
// Try to fetch the goal from the global cache. // Try to fetch the goal from the global cache.
if inspect.use_global_cache() { 'global: {
if let Some(CacheData { result, reached_depth, encountered_overflow }) = let Some(CacheData { result, proof_tree, reached_depth, encountered_overflow }) =
self.global_cache(tcx).get( self.global_cache(tcx).get(
tcx, tcx,
input, input,
@ -201,13 +200,26 @@ impl<'tcx> SearchGraph<'tcx> {
}, },
available_depth, available_depth,
) )
{ else {
inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CacheHit( break 'global;
CacheHit::Global, };
));
self.on_cache_hit(reached_depth, encountered_overflow); // If we're building a proof tree and the current cache entry does not
return result; // contain a proof tree, we do not use the entry but instead recompute
// the goal. We simply overwrite the existing entry once we're done,
// caching the proof tree.
if !inspect.is_noop() {
if let Some(revisions) = proof_tree {
inspect.goal_evaluation_kind(
inspect::WipCanonicalGoalEvaluationKind::Interned { revisions },
);
} else {
break 'global;
}
} }
self.on_cache_hit(reached_depth, encountered_overflow);
return result;
} }
// Check whether we're in a cycle. // Check whether we're in a cycle.
@ -238,9 +250,7 @@ impl<'tcx> SearchGraph<'tcx> {
// Finally we can return either the provisional response for that goal if we have a // Finally we can return either the provisional response for that goal if we have a
// coinductive cycle or an ambiguous result if the cycle is inductive. // coinductive cycle or an ambiguous result if the cycle is inductive.
Entry::Occupied(entry) => { Entry::Occupied(entry) => {
inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CacheHit( inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CycleInStack);
CacheHit::Provisional,
));
let stack_depth = *entry.get(); let stack_depth = *entry.get();
debug!("encountered cycle with depth {stack_depth:?}"); debug!("encountered cycle with depth {stack_depth:?}");
@ -329,6 +339,8 @@ impl<'tcx> SearchGraph<'tcx> {
(current_entry, result) (current_entry, result)
}); });
let proof_tree = inspect.finalize_evaluation(tcx);
// We're now done with this goal. In case this goal is involved in a larger cycle // We're now done with this goal. In case this goal is involved in a larger cycle
// do not remove it from the provisional cache and update its provisional result. // do not remove it from the provisional cache and update its provisional result.
// We only add the root of cycles to the global cache. // We only add the root of cycles to the global cache.
@ -346,7 +358,9 @@ impl<'tcx> SearchGraph<'tcx> {
// more details. // more details.
let reached_depth = final_entry.reached_depth.as_usize() - self.stack.len(); let reached_depth = final_entry.reached_depth.as_usize() - self.stack.len();
self.global_cache(tcx).insert( self.global_cache(tcx).insert(
tcx,
input, input,
proof_tree,
reached_depth, reached_depth,
final_entry.encountered_overflow, final_entry.encountered_overflow,
final_entry.cycle_participants, final_entry.cycle_participants,

View file

@ -8,7 +8,7 @@ mod type_err_ctxt_ext;
use super::{Obligation, ObligationCause, ObligationCauseCode, PredicateObligation}; use super::{Obligation, ObligationCause, ObligationCauseCode, PredicateObligation};
use crate::infer::InferCtxt; use crate::infer::InferCtxt;
use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache}; use crate::solve::{GenerateProofTree, InferCtxtEvalExt};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor; use rustc_hir::intravisit::Visitor;
@ -173,7 +173,7 @@ pub fn dump_proof_tree<'tcx>(o: &Obligation<'tcx, ty::Predicate<'tcx>>, infcx: &
infcx.probe(|_| { infcx.probe(|_| {
let goal = Goal { predicate: o.predicate, param_env: o.param_env }; let goal = Goal { predicate: o.predicate, param_env: o.param_env };
let tree = infcx let tree = infcx
.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No)) .evaluate_root_goal(goal, GenerateProofTree::Yes)
.1 .1
.expect("proof tree should have been generated"); .expect("proof tree should have been generated");
let mut lock = std::io::stdout().lock(); let mut lock = std::io::stdout().lock();