1
Fork 0

add flag for disabling global cache and printing proof trees on error

This commit is contained in:
Boxy 2023-07-03 21:00:10 +01:00
parent 8931edf746
commit 040aa58d0a
8 changed files with 175 additions and 48 deletions

View file

@ -745,6 +745,14 @@ pub enum TraitSolver {
NextCoherence, NextCoherence,
} }
#[derive(Default, Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum SolverProofTreeCondition {
#[default]
Never,
Always,
OnError,
}
pub enum Input { pub enum Input {
/// Load source code from a file. /// Load source code from a file.
File(PathBuf), File(PathBuf),

View file

@ -418,6 +418,7 @@ mod desc {
"a `,` separated combination of `bti`, `b-key`, `pac-ret`, or `leaf`"; "a `,` separated combination of `bti`, `b-key`, `pac-ret`, or `leaf`";
pub const parse_proc_macro_execution_strategy: &str = pub const parse_proc_macro_execution_strategy: &str =
"one of supported execution strategies (`same-thread`, or `cross-thread`)"; "one of supported execution strategies (`same-thread`, or `cross-thread`)";
pub const parse_solver_proof_tree_condition: &str = "one of: `always`, `never`, `on_error`";
} }
mod parse { mod parse {
@ -1238,6 +1239,19 @@ mod parse {
}; };
true true
} }
pub(crate) fn parse_solver_proof_tree_condition(
slot: &mut SolverProofTreeCondition,
v: Option<&str>,
) -> bool {
match v {
None | Some("always") => *slot = SolverProofTreeCondition::Always,
Some("never") => *slot = SolverProofTreeCondition::Never,
Some("on-error") => *slot = SolverProofTreeCondition::OnError,
_ => return false,
};
true
}
} }
options! { options! {
@ -1463,8 +1477,10 @@ options! {
"output statistics about monomorphization collection"), "output statistics about monomorphization collection"),
dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED], dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED],
"the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"), "the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"),
dump_solver_proof_tree: bool = (false, parse_bool, [UNTRACKED], dump_solver_proof_tree: SolverProofTreeCondition = (SolverProofTreeCondition::Never, parse_solver_proof_tree_condition, [UNTRACKED],
"dump a proof tree for every goal evaluated by the new trait solver"), "dump a proof tree for every goal evaluated by the new trait solver. The default is `always`"),
dump_solver_proof_tree_uses_cache: Option<bool> = (None, parse_opt_bool, [UNTRACKED],
"determines whether proof tree generation uses 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

@ -19,7 +19,9 @@ use rustc_middle::ty::{
self, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, self, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
TypeVisitableExt, TypeVisitor, TypeVisitableExt, TypeVisitor,
}; };
use rustc_session::config::SolverProofTreeCondition;
use rustc_span::DUMMY_SP; use rustc_span::DUMMY_SP;
use std::io::Write;
use std::ops::ControlFlow; use std::ops::ControlFlow;
use crate::traits::specialization_graph; use crate::traits::specialization_graph;
@ -113,9 +115,23 @@ 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(DisableGlobalCache),
No,
}
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
pub enum DisableGlobalCache {
Yes, Yes,
No, No,
} }
impl DisableGlobalCache {
pub fn from_bool(disable_cache: bool) -> Self {
match disable_cache {
true => DisableGlobalCache::Yes,
false => DisableGlobalCache::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.
@ -164,6 +180,36 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
let mode = if infcx.intercrate { SolverMode::Coherence } else { SolverMode::Normal }; let mode = if infcx.intercrate { SolverMode::Coherence } else { SolverMode::Normal };
let mut search_graph = search_graph::SearchGraph::new(infcx.tcx, mode); let mut search_graph = search_graph::SearchGraph::new(infcx.tcx, mode);
let inspect = {
let generate_proof_tree = match (
infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree,
infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree_uses_cache,
generate_proof_tree,
) {
(_, Some(use_cache), GenerateProofTree::Yes(_)) => {
GenerateProofTree::Yes(DisableGlobalCache::from_bool(!use_cache))
}
(SolverProofTreeCondition::Always, use_cache, GenerateProofTree::No) => {
let use_cache = use_cache.unwrap_or(true);
GenerateProofTree::Yes(DisableGlobalCache::from_bool(!use_cache))
}
(_, None, GenerateProofTree::Yes(_)) => generate_proof_tree,
// `Never` is kind of weird- it doesn't actually force us to not generate proof trees
// its just the default setting for rustflags forced proof tree generation.
(SolverProofTreeCondition::Never, _, _) => generate_proof_tree,
(SolverProofTreeCondition::OnError, _, _) => generate_proof_tree,
};
match generate_proof_tree {
GenerateProofTree::No => ProofTreeBuilder::new_noop(),
GenerateProofTree::Yes(global_cache_disabled) => {
ProofTreeBuilder::new_root(global_cache_disabled)
}
}
};
let mut ecx = EvalCtxt { let mut ecx = EvalCtxt {
search_graph: &mut search_graph, search_graph: &mut search_graph,
infcx: infcx, infcx: infcx,
@ -177,17 +223,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
var_values: CanonicalVarValues::dummy(), var_values: CanonicalVarValues::dummy(),
nested_goals: NestedGoals::new(), nested_goals: NestedGoals::new(),
tainted: Ok(()), tainted: Ok(()),
inspect: (infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree inspect,
|| matches!(generate_proof_tree, GenerateProofTree::Yes))
.then(ProofTreeBuilder::new_root)
.unwrap_or_else(ProofTreeBuilder::new_noop),
}; };
let result = f(&mut ecx); let result = f(&mut ecx);
let tree = ecx.inspect.finalize(); let tree = ecx.inspect.finalize();
if let Some(tree) = &tree { if let (Some(tree), SolverProofTreeCondition::Always) =
// module to allow more granular RUSTC_LOG filtering to just proof tree output (&tree, infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree)
super::inspect::dump::print_tree(tree); {
let mut lock = std::io::stdout().lock();
let _ = lock.write_fmt(format_args!("{tree:?}"));
let _ = lock.flush();
} }
assert!( assert!(

View file

@ -5,7 +5,7 @@ use rustc_middle::traits::solve::{
}; };
use rustc_middle::ty; use rustc_middle::ty;
pub mod dump; use super::eval_ctxt::DisableGlobalCache;
#[derive(Eq, PartialEq, Debug, Hash, HashStable)] #[derive(Eq, PartialEq, Debug, Hash, HashStable)]
pub struct WipGoalEvaluation<'tcx> { pub struct WipGoalEvaluation<'tcx> {
@ -145,11 +145,15 @@ impl<'tcx> From<WipGoalCandidate<'tcx>> for DebugSolver<'tcx> {
pub struct ProofTreeBuilder<'tcx> { pub struct ProofTreeBuilder<'tcx> {
state: Option<Box<DebugSolver<'tcx>>>, state: Option<Box<DebugSolver<'tcx>>>,
disable_global_cache: DisableGlobalCache,
} }
impl<'tcx> ProofTreeBuilder<'tcx> { impl<'tcx> ProofTreeBuilder<'tcx> {
fn new(state: impl Into<DebugSolver<'tcx>>) -> ProofTreeBuilder<'tcx> { fn new(
ProofTreeBuilder { state: Some(Box::new(state.into())) } state: impl Into<DebugSolver<'tcx>>,
disable_global_cache: DisableGlobalCache,
) -> ProofTreeBuilder<'tcx> {
ProofTreeBuilder { state: Some(Box::new(state.into())), disable_global_cache }
} }
fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> { fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> {
@ -165,12 +169,16 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
} }
} }
pub fn new_root() -> ProofTreeBuilder<'tcx> { pub fn disable_global_cache(&self) -> DisableGlobalCache {
ProofTreeBuilder::new(DebugSolver::Root) self.disable_global_cache
}
pub fn new_root(disable_global_cache: DisableGlobalCache) -> ProofTreeBuilder<'tcx> {
ProofTreeBuilder::new(DebugSolver::Root, disable_global_cache)
} }
pub fn new_noop() -> ProofTreeBuilder<'tcx> { pub fn new_noop() -> ProofTreeBuilder<'tcx> {
ProofTreeBuilder { state: None } ProofTreeBuilder { state: None, disable_global_cache: DisableGlobalCache::No }
} }
pub fn is_noop(&self) -> bool { pub fn is_noop(&self) -> bool {
@ -183,10 +191,14 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
is_normalizes_to_hack: IsNormalizesToHack, is_normalizes_to_hack: IsNormalizesToHack,
) -> ProofTreeBuilder<'tcx> { ) -> ProofTreeBuilder<'tcx> {
if self.state.is_none() { if self.state.is_none() {
return ProofTreeBuilder { state: None }; return ProofTreeBuilder {
state: None,
disable_global_cache: self.disable_global_cache,
};
} }
ProofTreeBuilder::new(WipGoalEvaluation { ProofTreeBuilder::new(
WipGoalEvaluation {
uncanonicalized_goal: goal, uncanonicalized_goal: goal,
canonicalized_goal: None, canonicalized_goal: None,
evaluation_steps: vec![], evaluation_steps: vec![],
@ -194,7 +206,9 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
cache_hit: None, cache_hit: None,
returned_goals: vec![], returned_goals: vec![],
result: None, result: None,
}) },
self.disable_global_cache,
)
} }
pub fn canonicalized_goal(&mut self, canonical_goal: CanonicalInput<'tcx>) { pub fn canonicalized_goal(&mut self, canonical_goal: CanonicalInput<'tcx>) {
@ -250,15 +264,21 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>, instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
) -> ProofTreeBuilder<'tcx> { ) -> ProofTreeBuilder<'tcx> {
if self.state.is_none() { if self.state.is_none() {
return ProofTreeBuilder { state: None }; return ProofTreeBuilder {
state: None,
disable_global_cache: self.disable_global_cache,
};
} }
ProofTreeBuilder::new(WipGoalEvaluationStep { ProofTreeBuilder::new(
WipGoalEvaluationStep {
instantiated_goal, instantiated_goal,
nested_goal_evaluations: vec![], nested_goal_evaluations: vec![],
candidates: vec![], candidates: vec![],
result: None, result: None,
}) },
self.disable_global_cache,
)
} }
pub fn goal_evaluation_step(&mut self, goal_eval_step: ProofTreeBuilder<'tcx>) { pub fn goal_evaluation_step(&mut self, goal_eval_step: ProofTreeBuilder<'tcx>) {
if let Some(this) = self.as_mut() { if let Some(this) = self.as_mut() {
@ -273,14 +293,17 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
pub fn new_goal_candidate(&mut self) -> ProofTreeBuilder<'tcx> { pub fn new_goal_candidate(&mut self) -> ProofTreeBuilder<'tcx> {
if self.state.is_none() { if self.state.is_none() {
return ProofTreeBuilder { state: None }; return ProofTreeBuilder {
state: None,
disable_global_cache: self.disable_global_cache,
};
} }
ProofTreeBuilder::new(WipGoalCandidate { ProofTreeBuilder::new(
nested_goal_evaluations: vec![], WipGoalCandidate { nested_goal_evaluations: vec![], candidates: vec![], kind: None },
candidates: vec![], self.disable_global_cache,
kind: None, )
})
} }
pub fn candidate_kind(&mut self, candidate_kind: CandidateKind<'tcx>) { pub fn candidate_kind(&mut self, candidate_kind: CandidateKind<'tcx>) {
@ -309,10 +332,17 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> { pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> {
if self.state.is_none() { if self.state.is_none() {
return ProofTreeBuilder { state: None }; return ProofTreeBuilder {
state: None,
disable_global_cache: self.disable_global_cache,
};
} }
ProofTreeBuilder::new(WipAddedGoalsEvaluation { evaluations: vec![], result: None }) ProofTreeBuilder::new(
WipAddedGoalsEvaluation { evaluations: vec![], result: None },
self.disable_global_cache,
)
} }
pub fn evaluate_added_goals_loop_start(&mut self) { pub fn evaluate_added_goals_loop_start(&mut self) {

View file

@ -1,5 +0,0 @@
use rustc_middle::traits::solve::inspect::GoalEvaluation;
pub fn print_tree(tree: &GoalEvaluation<'_>) {
debug!(?tree);
}

View file

@ -33,7 +33,9 @@ mod search_graph;
mod trait_goals; mod trait_goals;
mod weak_types; mod weak_types;
pub use eval_ctxt::{EvalCtxt, InferCtxtEvalExt, InferCtxtSelectExt}; pub use eval_ctxt::{
DisableGlobalCache, EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt,
};
pub use fulfill::FulfillmentCtxt; pub use fulfill::FulfillmentCtxt;
pub(crate) use normalize::deeply_normalize; pub(crate) use normalize::deeply_normalize;

View file

@ -13,6 +13,7 @@ use rustc_middle::traits::solve::{CanonicalInput, Certainty, MaybeCause, QueryRe
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use std::{collections::hash_map::Entry, mem}; use std::{collections::hash_map::Entry, mem};
use super::eval_ctxt::DisableGlobalCache;
use super::inspect::ProofTreeBuilder; use super::inspect::ProofTreeBuilder;
use super::SolverMode; use super::SolverMode;
@ -213,7 +214,9 @@ impl<'tcx> SearchGraph<'tcx> {
inspect: &mut ProofTreeBuilder<'tcx>, inspect: &mut ProofTreeBuilder<'tcx>,
mut loop_body: impl FnMut(&mut Self, &mut ProofTreeBuilder<'tcx>) -> QueryResult<'tcx>, mut loop_body: impl FnMut(&mut Self, &mut ProofTreeBuilder<'tcx>) -> QueryResult<'tcx>,
) -> QueryResult<'tcx> { ) -> QueryResult<'tcx> {
if self.should_use_global_cache() { if self.should_use_global_cache()
&& inspect.disable_global_cache() == DisableGlobalCache::No
{
if let Some(result) = tcx.new_solver_evaluation_cache.get(&canonical_input, tcx) { if let Some(result) = tcx.new_solver_evaluation_cache.get(&canonical_input, tcx) {
debug!(?canonical_input, ?result, "cache hit"); debug!(?canonical_input, ?result, "cache hit");
inspect.cache_hit(CacheHit::Global); inspect.cache_hit(CacheHit::Global);

View file

@ -10,6 +10,7 @@ use super::{
use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode}; use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode};
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use crate::infer::{self, InferCtxt}; use crate::infer::{self, InferCtxt};
use crate::solve::{DisableGlobalCache, GenerateProofTree, InferCtxtEvalExt};
use crate::traits::query::evaluate_obligation::InferCtxtExt as _; use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
use crate::traits::query::normalize::QueryNormalizeExt as _; use crate::traits::query::normalize::QueryNormalizeExt as _;
use crate::traits::specialize::to_pretty_impl_header; use crate::traits::specialize::to_pretty_impl_header;
@ -28,6 +29,7 @@ use rustc_hir::{GenericParam, Item, Node};
use rustc_infer::infer::error_reporting::TypeErrCtxt; use rustc_infer::infer::error_reporting::TypeErrCtxt;
use rustc_infer::infer::{InferOk, TypeTrace}; use rustc_infer::infer::{InferOk, TypeTrace};
use rustc_middle::traits::select::OverflowError; use rustc_middle::traits::select::OverflowError;
use rustc_middle::traits::solve::Goal;
use rustc_middle::traits::SelectionOutputTypeParameterMismatch; use rustc_middle::traits::SelectionOutputTypeParameterMismatch;
use rustc_middle::ty::abstract_const::NotConstEvaluatable; use rustc_middle::ty::abstract_const::NotConstEvaluatable;
use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::error::{ExpectedFound, TypeError};
@ -37,13 +39,14 @@ use rustc_middle::ty::{
self, SubtypePredicate, ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt, TypeFoldable, self, SubtypePredicate, ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt, TypeFoldable,
TypeVisitable, TypeVisitableExt, TypeVisitable, TypeVisitableExt,
}; };
use rustc_session::config::TraitSolver; use rustc_session::config::{SolverProofTreeCondition, TraitSolver};
use rustc_session::Limit; use rustc_session::Limit;
use rustc_span::def_id::LOCAL_CRATE; use rustc_span::def_id::LOCAL_CRATE;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::{ExpnKind, Span, DUMMY_SP}; use rustc_span::{ExpnKind, Span, DUMMY_SP};
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::io::Write;
use std::iter; use std::iter;
use std::ops::ControlFlow; use std::ops::ControlFlow;
use suggestions::TypeErrCtxtExt as _; use suggestions::TypeErrCtxtExt as _;
@ -630,6 +633,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
error: &SelectionError<'tcx>, error: &SelectionError<'tcx>,
) { ) {
let tcx = self.tcx; let tcx = self.tcx;
if tcx.sess.opts.unstable_opts.dump_solver_proof_tree == SolverProofTreeCondition::OnError {
dump_proof_tree(root_obligation, self.infcx);
}
let mut span = obligation.cause.span; let mut span = obligation.cause.span;
// FIXME: statically guarantee this by tainting after the diagnostic is emitted // FIXME: statically guarantee this by tainting after the diagnostic is emitted
self.set_tainted_by_errors( self.set_tainted_by_errors(
@ -1529,6 +1537,12 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
#[instrument(skip(self), level = "debug")] #[instrument(skip(self), level = "debug")]
fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) { fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) {
if self.tcx.sess.opts.unstable_opts.dump_solver_proof_tree
== SolverProofTreeCondition::OnError
{
dump_proof_tree(&error.root_obligation, self.infcx);
}
match error.code { match error.code {
FulfillmentErrorCode::CodeSelectionError(ref selection_error) => { FulfillmentErrorCode::CodeSelectionError(ref selection_error) => {
self.report_selection_error( self.report_selection_error(
@ -3499,3 +3513,16 @@ pub enum DefIdOrName {
DefId(DefId), DefId(DefId),
Name(&'static str), Name(&'static str),
} }
pub fn dump_proof_tree<'tcx>(o: &Obligation<'tcx, ty::Predicate<'tcx>>, infcx: &InferCtxt<'tcx>) {
infcx.probe(|_| {
let goal = Goal { predicate: o.predicate, param_env: o.param_env };
let tree = infcx
.evaluate_root_goal(goal, GenerateProofTree::Yes(DisableGlobalCache::Yes))
.1
.expect("proof tree should have been generated");
let mut lock = std::io::stdout().lock();
let _ = lock.write_fmt(format_args!("{tree:?}"));
let _ = lock.flush();
});
}