Skip to content

Commit d0a5f4e

Browse files
committed
feat: move fulfillment into rustc_next_trait_solver
Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
1 parent 49c80b5 commit d0a5f4e

9 files changed

Lines changed: 636 additions & 465 deletions

File tree

compiler/rustc_infer/src/traits/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rustc_middle::traits::solve::Certainty;
1818
pub use rustc_middle::traits::*;
1919
use rustc_middle::ty::{self, Ty, TyCtxt, Upcast};
2020
use rustc_span::Span;
21+
use rustc_type_ir::solve::fulfill::FulfillmentObligation;
2122
use thin_vec::ThinVec;
2223

2324
pub use self::engine::{FromSolverError, ScrubbedTraitError, TraitEngine, TraitErrors};
@@ -92,6 +93,28 @@ pub type PolyTraitObligation<'tcx> = Obligation<'tcx, ty::PolyTraitPredicate<'tc
9293

9394
pub type PredicateObligations<'tcx> = ThinVec<PredicateObligation<'tcx>>;
9495

96+
impl<'tcx> FulfillmentObligation<TyCtxt<'tcx>> for PredicateObligation<'tcx> {
97+
fn as_goal(&self) -> solve::Goal<'tcx, ty::Predicate<'tcx>> {
98+
Obligation::as_goal(self)
99+
}
100+
101+
fn span(&self) -> Span {
102+
self.cause.span
103+
}
104+
105+
fn recursion_depth(&self) -> usize {
106+
self.recursion_depth
107+
}
108+
109+
fn set_recursion_depth(&mut self, depth: usize) {
110+
self.recursion_depth = depth;
111+
}
112+
113+
fn set_predicate(&mut self, predicate: ty::Predicate<'tcx>) {
114+
self.predicate = predicate;
115+
}
116+
}
117+
95118
impl<'tcx> PredicateObligation<'tcx> {
96119
/// Flips the polarity of the inner predicate.
97120
///
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
use rustc_type_ir::solve::fulfill::FulfillmentObligation;
2+
use rustc_type_ir::solve::{Certainty, Goal, NoSolution};
3+
use rustc_type_ir::{InferCtxtLike as _, Interner};
4+
use thin_vec::ThinVec;
5+
6+
use super::fast_path::compute_goal_fast_path;
7+
use super::{
8+
GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegate, SolverDelegateEvalExt as _,
9+
};
10+
11+
#[derive(Debug, Clone)]
12+
pub enum NextSolverError<O> {
13+
TrueError(O),
14+
Ambiguity(O),
15+
Overflow(O),
16+
}
17+
18+
// FIXME: Do we need to use a `ThinVec` here?
19+
type PendingObligations<I, O> = ThinVec<(O, Option<GoalStalledOn<I>>)>;
20+
21+
#[derive(Debug)]
22+
struct ObligationStorage<I: Interner, O> {
23+
/// Obligations which resulted in overflow in fulfillment itself.
24+
///
25+
/// We cannot eagerly return these as errors, so we instead store them here
26+
/// to avoid recomputing them each time `try_evaluate_obligations` is called.
27+
/// This also allows the frontend to construct the correct error for them.
28+
overflowed: Vec<O>,
29+
30+
pending: PendingObligations<I, O>,
31+
}
32+
33+
impl<I: Interner, O> Default for ObligationStorage<I, O> {
34+
fn default() -> Self {
35+
Self { overflowed: Vec::new(), pending: ThinVec::new() }
36+
}
37+
}
38+
39+
impl<I: Interner, O> ObligationStorage<I, O> {
40+
fn register(&mut self, obligation: O, stalled_on: Option<GoalStalledOn<I>>) {
41+
self.pending.push((obligation, stalled_on));
42+
}
43+
44+
fn has_pending_obligations(&self) -> bool {
45+
!self.pending.is_empty() || !self.overflowed.is_empty()
46+
}
47+
48+
fn clone_pending(&self) -> ThinVec<O>
49+
where
50+
O: Clone,
51+
{
52+
let mut obligations =
53+
self.pending.iter().map(|(obligation, _)| obligation.clone()).collect::<ThinVec<_>>();
54+
55+
obligations.extend(self.overflowed.iter().cloned());
56+
obligations
57+
}
58+
59+
fn clone_pending_filtered<F>(&self, mut filter: F) -> ThinVec<O>
60+
where
61+
O: Clone,
62+
F: FnMut(&O, &Option<GoalStalledOn<I>>) -> bool,
63+
{
64+
let mut obligations = self
65+
.pending
66+
.iter()
67+
.filter_map(|(obligation, stalled_on)| {
68+
filter(obligation, stalled_on).then(|| obligation.clone())
69+
})
70+
.collect::<ThinVec<_>>();
71+
72+
obligations.extend(self.overflowed.iter().cloned());
73+
obligations
74+
}
75+
76+
fn drain_pending<F>(&mut self, mut filter: F) -> ThinVec<O>
77+
where
78+
F: FnMut(&O, &Option<GoalStalledOn<I>>) -> bool,
79+
{
80+
let (drained, pending): (PendingObligations<I, O>, PendingObligations<I, O>) =
81+
std::mem::take(&mut self.pending)
82+
.into_iter()
83+
.partition(|(obligation, stalled_on)| filter(obligation, stalled_on));
84+
85+
self.pending = pending;
86+
87+
drained.into_iter().map(|(obligation, _)| obligation).collect()
88+
}
89+
90+
#[cold]
91+
#[inline(never)]
92+
fn collect_remaining_errors<E>(
93+
&mut self,
94+
map: impl FnMut(NextSolverError<O>) -> E,
95+
) -> ThinVec<E> {
96+
self.pending
97+
.drain(..)
98+
.map(|(obligation, _)| NextSolverError::Ambiguity(obligation))
99+
.chain(self.overflowed.drain(..).map(NextSolverError::Overflow))
100+
.map(map)
101+
.collect()
102+
}
103+
}
104+
105+
/// A fulfillment engine using the new trait solver.
106+
///
107+
/// This is mostly identical to how `evaluate_all` works inside of the solver,
108+
/// except that it is possible to add new obligations later and the frontend
109+
/// needs to retain its obligation representation for diagnostics.
110+
///
111+
/// It is also likely that we want to use different data structures here, as
112+
/// fulfillment deals with far more root goals than `evaluate_all`.
113+
#[derive(Debug)]
114+
pub struct FulfillmentCtxt<I: Interner, O: FulfillmentObligation<I>> {
115+
obligations: ObligationStorage<I, O>,
116+
}
117+
118+
impl<I: Interner, O: FulfillmentObligation<I>> Default for FulfillmentCtxt<I, O> {
119+
fn default() -> Self {
120+
Self { obligations: ObligationStorage::default() }
121+
}
122+
}
123+
124+
impl<I: Interner, O: FulfillmentObligation<I>> FulfillmentCtxt<I, O> {
125+
pub fn new() -> Self {
126+
Self { obligations: Default::default() }
127+
}
128+
129+
pub fn register<D>(&mut self, delegate: &D, obligation: O)
130+
where
131+
D: SolverDelegate<Interner = I>,
132+
{
133+
if let Some(GoalEvaluation { certainty, stalled_on, .. }) =
134+
compute_goal_fast_path(delegate, obligation.as_goal(), obligation.span())
135+
{
136+
// If we can take the fast path, do not add a successful goal to
137+
// the pending obligations. For `Certainty::Maybe`, retain the
138+
// precise `stalled_on` information for later re-evaluation.
139+
match certainty {
140+
Certainty::Yes => {}
141+
Certainty::Maybe(_) => {
142+
self.obligations.register(obligation, stalled_on);
143+
}
144+
}
145+
} else {
146+
self.obligations.register(obligation, None);
147+
}
148+
}
149+
150+
fn on_fulfillment_overflow<D>(&mut self, delegate: &D)
151+
where
152+
D: SolverDelegate<Interner = I>,
153+
{
154+
delegate.probe(|| {
155+
// IMPORTANT: we must not resolve any inference variables in the
156+
// obligations, as this is all happening inside of a probe. The
157+
// probe makes sure we collect every obligation involved in the
158+
// overflow. Conceptually, we check which goals would change if we
159+
// performed one more fulfillment iteration.
160+
let overflowed = self
161+
.obligations
162+
.pending
163+
.extract_if(.., |(obligation, stalled_on)| {
164+
let result = delegate.evaluate_root_goal(
165+
obligation.as_goal(),
166+
obligation.span(),
167+
stalled_on.take(),
168+
);
169+
170+
matches!(result, Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. }))
171+
})
172+
.map(|(obligation, _)| obligation)
173+
.collect::<Vec<_>>();
174+
175+
self.obligations.overflowed.extend(overflowed);
176+
});
177+
}
178+
179+
pub fn try_evaluate_obligations<D, Inspect, OnSuccess>(
180+
&mut self,
181+
delegate: &D,
182+
mut inspect: Inspect,
183+
mut on_success: OnSuccess,
184+
) -> ThinVec<NextSolverError<O>>
185+
where
186+
D: SolverDelegate<Interner = I>,
187+
Inspect: FnMut(&O, Goal<I, I::Predicate>, &Result<GoalEvaluation<I>, NoSolution>),
188+
OnSuccess: FnMut(&O),
189+
{
190+
let mut errors = ThinVec::new();
191+
192+
loop {
193+
let mut any_changed = false;
194+
let mut overflowed = false;
195+
196+
self.obligations.pending.retain_mut(|(obligation, opt_stalled_on)| {
197+
if overflowed {
198+
return false;
199+
}
200+
201+
// Common case: still stalled; keep the obligation. This path is extremely hot in
202+
// some cases; there can be thousands of pending obligations.
203+
if let Some(stalled_on) = opt_stalled_on
204+
&& let Some(certainty) = delegate.goal_remains_stalled(stalled_on)
205+
&& matches!(certainty, Certainty::Maybe(_))
206+
{
207+
return true;
208+
}
209+
210+
let goal = obligation.as_goal();
211+
let result =
212+
delegate.evaluate_root_goal(goal, obligation.span(), opt_stalled_on.take());
213+
214+
inspect(obligation, goal, &result);
215+
216+
let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result {
217+
Ok(result) => result,
218+
Err(NoSolution) => {
219+
errors.push(NextSolverError::TrueError(obligation.clone()));
220+
return false;
221+
}
222+
};
223+
224+
// We resolved the goal in `evaluate_root_goal`; retain the eagerly resolved
225+
// predicate to avoid repeating this work in the next iteration.
226+
obligation.set_predicate(goal.predicate);
227+
228+
if has_changed == HasChanged::Yes {
229+
// Track the number of times this root goal resulted in inference progress.
230+
let depth = obligation.recursion_depth() + 1;
231+
obligation.set_recursion_depth(depth);
232+
233+
if depth > delegate.cx().recursion_limit() {
234+
// We cannot break out of `retain_mut`, so use a flag and handle
235+
// fulfillment overflow after the iteration.
236+
overflowed = true;
237+
return false;
238+
}
239+
240+
any_changed = true;
241+
}
242+
243+
match certainty {
244+
Certainty::Yes => {
245+
on_success(obligation);
246+
false
247+
}
248+
Certainty::Maybe(_) => {
249+
*opt_stalled_on = stalled_on;
250+
true
251+
}
252+
}
253+
});
254+
255+
if overflowed {
256+
self.on_fulfillment_overflow(delegate);
257+
// Only return true errors accumulated while processing.
258+
return errors;
259+
}
260+
261+
if !any_changed {
262+
break;
263+
}
264+
}
265+
266+
errors
267+
}
268+
269+
pub fn has_pending_obligations(&self) -> bool {
270+
self.obligations.has_pending_obligations()
271+
}
272+
273+
pub fn pending_obligations(&self) -> ThinVec<O>
274+
where
275+
O: Clone,
276+
{
277+
self.obligations.clone_pending()
278+
}
279+
280+
pub fn pending_obligations_filtered<F>(&self, filter: F) -> ThinVec<O>
281+
where
282+
O: Clone,
283+
F: FnMut(&O, &Option<GoalStalledOn<I>>) -> bool,
284+
{
285+
self.obligations.clone_pending_filtered(filter)
286+
}
287+
288+
pub fn drain_pending_obligations<F>(&mut self, filter: F) -> ThinVec<O>
289+
where
290+
F: FnMut(&O, &Option<GoalStalledOn<I>>) -> bool,
291+
{
292+
self.obligations.drain_pending(filter)
293+
}
294+
295+
pub fn collect_remaining_errors<E>(
296+
&mut self,
297+
map: impl FnMut(NextSolverError<O>) -> E,
298+
) -> ThinVec<E> {
299+
self.obligations.collect_remaining_errors(map)
300+
}
301+
}

compiler/rustc_next_trait_solver/src/solve/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
mod assembly;
1515
mod effect_goals;
1616
mod eval_ctxt;
17+
pub mod fulfill;
1718
pub mod inspect;
1819
mod normalizes_to;
1920
mod project_goals;

compiler/rustc_trait_selection/src/solve.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
pub use rustc_next_trait_solver::solve::*;
22

33
mod delegate;
4-
mod fulfill;
54
pub mod inspect;
65
mod normalize;
6+
mod rustc_fulfill;
77
mod select;
88

99
pub(crate) use delegate::SolverDelegate;
10-
pub use fulfill::{FulfillmentCtxt, NextSolverError};
1110
pub(crate) use normalize::deeply_normalize_for_diagnostics;
1211
pub use normalize::{
1312
deeply_normalize, deeply_normalize_with_skipped_universes,
1413
deeply_normalize_with_skipped_universes_and_ambiguous_coroutine_goals, normalize,
1514
};
15+
pub use rustc_fulfill::{FulfillmentCtxt, NextSolverError};
1616
use rustc_middle::query::Providers;
1717
use rustc_middle::ty::TyCtxt;
1818
pub use select::InferCtxtSelectExt;

0 commit comments

Comments
 (0)