Skip to content

Commit 5e26056

Browse files
authored
Merge pull request #93 from aatxe/issue-81
Implement early stop for solver when no strands can invalidate the current substitution.
2 parents 6ab7717 + ca57c48 commit 5e26056

File tree

7 files changed

+303
-32
lines changed

7 files changed

+303
-32
lines changed

chalk-engine/src/context/mod.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ crate mod prelude;
88

99
/// The "context" in which the SLG solver operates.
1010
pub trait Context: Sized + Clone + Debug + ContextOps<Self> + AggregateOps<Self> {
11-
type CanonicalExClause: Debug;
11+
type CanonicalExClause: CanonicalExClause<Self>;
1212

1313
/// A map between universes. These are produced when
1414
/// u-canonicalizing something; they map canonical results back to
@@ -21,6 +21,10 @@ pub trait Context: Sized + Clone + Debug + ContextOps<Self> + AggregateOps<Self>
2121
/// [the rustc-guide]: https://rust-lang-nursery.github.io/rustc-guide/traits-canonicalization.html#canonicalizing-the-query-result
2222
type CanonicalConstrainedSubst: CanonicalConstrainedSubst<Self>;
2323

24+
/// Extracted from a canonicalized substitution or canonicalized ex clause, this is the type of
25+
/// substitution that is fully normalized with respect to inference variables.
26+
type InferenceNormalizedSubst: Debug;
27+
2428
/// A canonicalized `GoalInEnvironment` -- that is, one where all
2529
/// free inference variables have been bound into the canonical
2630
/// binder. See [the rustc-guide] for more information.
@@ -157,7 +161,7 @@ pub trait AggregateOps<C: Context> {
157161
fn make_solution(
158162
&self,
159163
root_goal: &C::CanonicalGoalInEnvironment,
160-
simplified_answers: impl IntoIterator<Item = SimplifiedAnswer<C>>,
164+
simplified_answers: impl AnswerStream<C>,
161165
) -> Option<C::Solution>;
162166
}
163167

@@ -285,9 +289,17 @@ pub trait Environment<C: Context, I: InferenceContext<C>>: Debug + Clone {
285289
fn add_clauses(&self, clauses: impl IntoIterator<Item = I::ProgramClause>) -> Self;
286290
}
287291

292+
pub trait CanonicalExClause<C: Context>: Debug {
293+
/// Extracts the inner normalized substitution.
294+
fn inference_normalized_subst(&self) -> &C::InferenceNormalizedSubst;
295+
}
296+
288297
pub trait CanonicalConstrainedSubst<C: Context>: Clone + Debug + Eq + Hash + Ord {
289298
/// True if this solution has no region constraints.
290299
fn empty_constraints(&self) -> bool;
300+
301+
/// Extracts the inner normalized substitution.
302+
fn inference_normalized_subst(&self) -> &C::InferenceNormalizedSubst;
291303
}
292304

293305
pub trait DomainGoal<C: Context, I: InferenceContext<C>>: Debug {
@@ -337,3 +349,15 @@ pub trait UnificationResult<C: Context, I: InferenceContext<C>> {
337349
/// Also add region constraints.
338350
fn into_ex_clause(self, ex_clause: &mut ExClause<C, I>);
339351
}
352+
353+
pub trait AnswerStream<C: Context> {
354+
fn peek_answer(&mut self) -> Option<SimplifiedAnswer<C>>;
355+
fn next_answer(&mut self) -> Option<SimplifiedAnswer<C>>;
356+
357+
/// Invokes `test` with each possible future answer, returning true immediately
358+
/// if we find any answer for which `test` returns true.
359+
fn any_future_answer(
360+
&mut self,
361+
test: impl FnMut(&C::InferenceNormalizedSubst) -> bool,
362+
) -> bool;
363+
}

chalk-engine/src/context/prelude.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ crate use super::UnificationResult;
1111
crate use super::GoalInEnvironment;
1212
crate use super::UCanonicalGoalInEnvironment;
1313
crate use super::UniverseMap;
14+
crate use super::CanonicalExClause;
1415
crate use super::CanonicalConstrainedSubst;
1516
crate use super::Goal;
1617
crate use super::DomainGoal;

chalk-engine/src/forest.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{DepthFirstNumber, SimplifiedAnswer, TableIndex};
22
use crate::context::prelude::*;
3+
use crate::context::AnswerStream;
34
use crate::logic::RootSearchFail;
45
use crate::stack::{Stack, StackIndex};
56
use crate::tables::Tables;
@@ -64,7 +65,7 @@ impl<C: Context> Forest<C> {
6465
fn iter_answers<'f>(
6566
&'f mut self,
6667
goal: &C::UCanonicalGoalInEnvironment,
67-
) -> impl Iterator<Item = SimplifiedAnswer<C>> + 'f {
68+
) -> impl AnswerStream<C> + 'f {
6869
let table = self.get_or_create_table_for_ucanonical_goal(goal.clone());
6970
let answer = AnswerIndex::ZERO;
7071
ForestSolver {
@@ -127,13 +128,11 @@ struct ForestSolver<'forest, C: Context + 'forest> {
127128
answer: AnswerIndex,
128129
}
129130

130-
impl<'forest, C> Iterator for ForestSolver<'forest, C>
131+
impl<'forest, C> AnswerStream<C> for ForestSolver<'forest, C>
131132
where
132133
C: Context,
133134
{
134-
type Item = SimplifiedAnswer<C>;
135-
136-
fn next(&mut self) -> Option<SimplifiedAnswer<C>> {
135+
fn peek_answer(&mut self) -> Option<SimplifiedAnswer<C>> {
137136
loop {
138137
match self.forest.ensure_root_answer(self.table, self.answer) {
139138
Ok(()) => {
@@ -154,8 +153,6 @@ where
154153
ambiguous: !answer.delayed_literals.is_empty(),
155154
};
156155

157-
self.answer.increment();
158-
159156
return Some(simplified_answer);
160157
}
161158

@@ -167,4 +164,18 @@ where
167164
}
168165
}
169166
}
167+
168+
fn next_answer(&mut self) -> Option<SimplifiedAnswer<C>> {
169+
self.peek_answer().map(|answer| {
170+
self.answer.increment();
171+
answer
172+
})
173+
}
174+
175+
fn any_future_answer(
176+
&mut self,
177+
test: impl FnMut(&C::InferenceNormalizedSubst) -> bool,
178+
) -> bool {
179+
self.forest.any_future_answer(self.table, self.answer, test)
180+
}
170181
}

chalk-engine/src/logic.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,22 @@ impl<C: Context> Forest<C> {
103103
}
104104
}
105105

106+
pub(super) fn any_future_answer(
107+
&mut self,
108+
table: TableIndex,
109+
answer: AnswerIndex,
110+
mut test: impl FnMut(&C::InferenceNormalizedSubst) -> bool,
111+
) -> bool {
112+
if let Some(answer) = self.tables[table].answer(answer) {
113+
info!("answer cached = {:?}", answer);
114+
return test(answer.subst.inference_normalized_subst());
115+
}
116+
117+
self.tables[table].strands_mut().any(|strand| {
118+
test(strand.canonical_ex_clause.inference_normalized_subst())
119+
})
120+
}
121+
106122
/// Ensures that answer with the given index is available from the
107123
/// given table. Returns `Ok` if there is an answer:
108124
///

src/solve/slg/implementation/aggregate.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,16 @@ impl context::AggregateOps<SlgContext> for SlgContext {
1515
fn make_solution(
1616
&self,
1717
root_goal: &Canonical<InEnvironment<Goal>>,
18-
simplified_answers: impl IntoIterator<Item = SimplifiedAnswer<SlgContext>>,
18+
mut simplified_answers: impl context::AnswerStream<SlgContext>,
1919
) -> Option<Solution> {
20-
let mut simplified_answers = simplified_answers.into_iter().peekable();
21-
2220
// No answers at all?
23-
if simplified_answers.peek().is_none() {
21+
if simplified_answers.peek_answer().is_none() {
2422
return None;
2523
}
26-
let SimplifiedAnswer { subst, ambiguous } = simplified_answers.next().unwrap();
24+
let SimplifiedAnswer { subst, ambiguous } = simplified_answers.next_answer().unwrap();
2725

2826
// Exactly 1 unconditional answer?
29-
if simplified_answers.peek().is_none() && !ambiguous {
27+
if simplified_answers.peek_answer().is_none() && !ambiguous {
3028
return Some(Solution::Unique(subst));
3129
}
3230

@@ -57,7 +55,13 @@ impl context::AggregateOps<SlgContext> for SlgContext {
5755
break Guidance::Unknown;
5856
}
5957

60-
match simplified_answers.next() {
58+
if !simplified_answers.any_future_answer(|ref mut new_subst| {
59+
new_subst.may_invalidate(&subst)
60+
}) {
61+
break Guidance::Definite(subst);
62+
}
63+
64+
match simplified_answers.next_answer() {
6165
Some(answer1) => {
6266
subst = merge_into_guidance(root_goal, subst, &answer1.subst);
6367
}

src/solve/slg/implementation/mod.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ impl context::Context for SlgContext {
5555
type UCanonicalGoalInEnvironment = UCanonical<InEnvironment<Goal>>;
5656
type UniverseMap = UniverseMap;
5757
type CanonicalConstrainedSubst = Canonical<ConstrainedSubst>;
58+
type InferenceNormalizedSubst = Substitution;
5859
type Solution = Solution;
5960
}
6061

@@ -238,6 +239,157 @@ impl context::Environment<SlgContext, SlgContext> for Arc<Environment> {
238239
}
239240
}
240241

242+
impl Substitution {
243+
fn may_invalidate(&self, subst: &Canonical<Substitution>) -> bool {
244+
self.parameters
245+
.iter()
246+
.zip(&subst.value.parameters)
247+
.any(|(new, current)| MayInvalidate.aggregate_parameters(new, current))
248+
}
249+
}
250+
251+
// This is a struct in case we need to add state at any point like in AntiUnifier
252+
struct MayInvalidate;
253+
254+
impl MayInvalidate {
255+
fn aggregate_parameters(&mut self, new: &Parameter, current: &Parameter) -> bool {
256+
match (new, current) {
257+
(ParameterKind::Ty(ty1), ParameterKind::Ty(ty2)) => {
258+
self.aggregate_tys(ty1, ty2)
259+
}
260+
(ParameterKind::Lifetime(l1), ParameterKind::Lifetime(l2)) => {
261+
self.aggregate_lifetimes(l1, l2)
262+
}
263+
(ParameterKind::Ty(_), _) | (ParameterKind::Lifetime(_), _) => {
264+
panic!("mismatched parameter kinds: new={:?} current={:?}", new, current)
265+
}
266+
}
267+
}
268+
269+
// Returns true if the two types could be unequal.
270+
fn aggregate_tys(&mut self, new: &Ty, current: &Ty) -> bool {
271+
match (new, current) {
272+
(_, Ty::Var(_)) => {
273+
// If the aggregate solution already has an inference variable here, then no matter
274+
// what type we produce, the aggregate cannot get 'more generalized' than it already
275+
// is. So return false, we cannot invalidate.
276+
false
277+
}
278+
279+
(Ty::Var(_), _) => {
280+
// If we see a type variable in the potential future solution, we have to be
281+
// conservative. We don't know what type variable will wind up being! Remember that
282+
// the future solution could be any instantiation of `ty0` -- or it could leave this
283+
// variable unbound, if the result is true for all types.
284+
true
285+
}
286+
287+
// Aggregating universally-quantified types seems hard according to Niko. ;)
288+
// Since this is the case, we are conservative here and just say we may invalidate.
289+
(Ty::ForAll(_), Ty::ForAll(_)) => true,
290+
291+
(Ty::Apply(apply1), Ty::Apply(apply2)) => {
292+
self.aggregate_application_tys(apply1, apply2)
293+
}
294+
295+
(Ty::Projection(apply1), Ty::Projection(apply2)) => {
296+
self.aggregate_projection_tys(apply1, apply2)
297+
}
298+
299+
(Ty::UnselectedProjection(apply1), Ty::UnselectedProjection(apply2)) => {
300+
self.aggregate_unselected_projection_tys(apply1, apply2)
301+
}
302+
303+
(Ty::ForAll(_), _)
304+
| (Ty::Apply(_), _)
305+
| (Ty::Projection(_), _)
306+
| (Ty::UnselectedProjection(_), _) => true,
307+
}
308+
}
309+
310+
fn aggregate_lifetimes(&mut self, _: &Lifetime, _: &Lifetime) -> bool {
311+
true
312+
}
313+
314+
fn aggregate_application_tys(
315+
&mut self,
316+
new: &ApplicationTy,
317+
current: &ApplicationTy
318+
) -> bool {
319+
let ApplicationTy {
320+
name: new_name,
321+
parameters: new_parameters,
322+
} = new;
323+
let ApplicationTy {
324+
name: current_name,
325+
parameters: current_parameters,
326+
} = current;
327+
328+
self.aggregate_name_and_substs(new_name, new_parameters, current_name, current_parameters)
329+
}
330+
331+
fn aggregate_projection_tys(&mut self, new: &ProjectionTy, current: &ProjectionTy) -> bool {
332+
let ProjectionTy {
333+
associated_ty_id: new_name,
334+
parameters: new_parameters,
335+
} = new;
336+
let ProjectionTy {
337+
associated_ty_id: current_name,
338+
parameters: current_parameters,
339+
} = current;
340+
341+
self.aggregate_name_and_substs(new_name, new_parameters, current_name, current_parameters)
342+
}
343+
344+
fn aggregate_unselected_projection_tys(
345+
&mut self,
346+
new: &UnselectedProjectionTy,
347+
current: &UnselectedProjectionTy,
348+
) -> bool {
349+
let UnselectedProjectionTy {
350+
type_name: new_name,
351+
parameters: new_parameters,
352+
} = new;
353+
let UnselectedProjectionTy {
354+
type_name: current_name,
355+
parameters: current_parameters,
356+
} = current;
357+
358+
self.aggregate_name_and_substs(new_name, new_parameters, current_name, current_parameters)
359+
}
360+
361+
fn aggregate_name_and_substs<N>(
362+
&mut self,
363+
new_name: N,
364+
new_parameters: &[Parameter],
365+
current_name: N,
366+
current_parameters: &[Parameter],
367+
) -> bool
368+
where
369+
N: Copy + Eq + Debug,
370+
{
371+
if new_name != current_name {
372+
return true;
373+
}
374+
375+
let name = new_name;
376+
377+
assert_eq!(
378+
new_parameters.len(),
379+
current_parameters.len(),
380+
"does {:?} take {} parameters or {}? can't both be right",
381+
name,
382+
new_parameters.len(),
383+
current_parameters.len()
384+
);
385+
386+
new_parameters
387+
.iter()
388+
.zip(current_parameters)
389+
.any(|(new, current)| self.aggregate_parameters(new, current))
390+
}
391+
}
392+
241393
impl context::UniverseMap<SlgContext> for ::crate::solve::infer::ucanonicalize::UniverseMap {
242394
fn map_goal_from_canonical(
243395
&self,
@@ -260,10 +412,20 @@ impl context::DomainGoal<SlgContext, SlgContext> for DomainGoal {
260412
}
261413
}
262414

415+
impl context::CanonicalExClause<SlgContext> for Canonical<ExClause<SlgContext, SlgContext>> {
416+
fn inference_normalized_subst(&self) -> &Substitution {
417+
&self.value.subst
418+
}
419+
}
420+
263421
impl context::CanonicalConstrainedSubst<SlgContext> for Canonical<ConstrainedSubst> {
264422
fn empty_constraints(&self) -> bool {
265423
self.value.constraints.is_empty()
266424
}
425+
426+
fn inference_normalized_subst(&self) -> &Substitution {
427+
&self.value.subst
428+
}
267429
}
268430

269431
impl context::UCanonicalGoalInEnvironment<SlgContext> for UCanonical<InEnvironment<Goal>> {

0 commit comments

Comments
 (0)