Skip to content

Commit af48f30

Browse files
authored
Merge pull request #322 from jackh726/truncate_flounder
Flounder if truncation would happen
2 parents a329f5d + 30f9d12 commit af48f30

File tree

3 files changed

+54
-158
lines changed

3 files changed

+54
-158
lines changed

chalk-engine/src/logic.rs

Lines changed: 30 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::context::{
2-
Context, ContextOps, Floundered, InferenceTable, ResolventOps, UnificationOps,
2+
Context, ContextOps, Floundered, InferenceTable, ResolventOps, TruncateOps, UnificationOps,
33
};
44
use crate::fallible::NoSolution;
55
use crate::forest::Forest;
@@ -9,7 +9,6 @@ use crate::table::AnswerIndex;
99
use crate::{
1010
Answer, CompleteAnswer, ExClause, FlounderedSubgoal, Literal, Minimums, TableIndex, TimeStamp,
1111
};
12-
use std::mem;
1312

1413
type RootSearchResult<T> = Result<T, RootSearchFail>;
1514

@@ -284,7 +283,7 @@ impl<C: Context> Forest<C> {
284283
) {
285284
Ok(()) => {
286285
let Strand {
287-
infer,
286+
infer: _,
288287
ex_clause,
289288
selected_subgoal: _,
290289
last_pursued_time: _,
@@ -302,9 +301,6 @@ impl<C: Context> Forest<C> {
302301
// subgoals may be eligble to be pursued again.
303302
ex_clause.answer_time.increment();
304303

305-
// Apply answer abstraction.
306-
self.truncate_returned(ex_clause, infer);
307-
308304
// Ok, we've applied the answer to this Strand.
309305
return Ok(());
310306
}
@@ -1040,6 +1036,28 @@ impl<C: Context> Forest<C> {
10401036
assert!(subgoals.is_empty());
10411037
assert!(floundered_subgoals.is_empty());
10421038

1039+
// If the answer gets too large, mark the table as floundered.
1040+
// This is the *most conservative* course. There are a few alternatives:
1041+
// 1) Replace the answer with a truncated version of it. (This was done
1042+
// previously, but turned out to be more complicated than we wanted and
1043+
// and a source of multiple bugs.)
1044+
// 2) Mark this *strand* as floundered. We don't currently have a mechanism
1045+
// for this (only floundered subgoals), so implementing this is more
1046+
// difficult because we don't want to just *remove* this strand from the
1047+
// table, because that might make the table give `NoMoreSolutions`, which
1048+
// is *wrong*.
1049+
// 3) Do something fancy with delayed subgoals, effectively delayed the
1050+
// truncated bits to a different strand (and a more "refined" answer).
1051+
// (This one probably needs more thought, but is here for "completeness")
1052+
//
1053+
// Ultimately, the current decision to flounder the entire table mostly boils
1054+
// down to "it works as we expect for the current tests". And, we likely don't
1055+
// even *need* the added complexity just for potentially more answers.
1056+
if infer.truncate_answer(&subst).is_some() {
1057+
self.tables[table].mark_floundered();
1058+
return None;
1059+
}
1060+
10431061
let subst = infer.canonicalize_answer_subst(subst, constraints, delayed_subgoals);
10441062
debug!("answer: table={:?}, subst={:?}", table, subst);
10451063

@@ -1160,7 +1178,7 @@ impl<C: Context> Forest<C> {
11601178

11611179
// Subgoal abstraction:
11621180
let (ucanonical_subgoal, universe_map) = match subgoal {
1163-
Literal::Positive(subgoal) => self.abstract_positive_literal(infer, subgoal),
1181+
Literal::Positive(subgoal) => self.abstract_positive_literal(infer, subgoal)?,
11641182
Literal::Negative(subgoal) => self.abstract_negative_literal(infer, subgoal)?,
11651183
};
11661184

@@ -1292,44 +1310,16 @@ impl<C: Context> Forest<C> {
12921310
/// abstraction function to yield the canonical form that will be
12931311
/// used to pick a table. Typically, this abstraction has no
12941312
/// effect, and hence we are simply returning the canonical form
1295-
/// of `subgoal`, but if the subgoal is getting too big, we may
1296-
/// truncate the goal to ensure termination.
1297-
///
1298-
/// This technique is described in the SA paper.
1313+
/// of `subgoal`; but if the subgoal is getting too big, we return
1314+
/// `None`, which causes the subgoal to flounder.
12991315
fn abstract_positive_literal(
13001316
&mut self,
13011317
infer: &mut dyn InferenceTable<C>,
13021318
subgoal: &C::GoalInEnvironment,
1303-
) -> (C::UCanonicalGoalInEnvironment, C::UniverseMap) {
1304-
// Subgoal abstraction: Rather than looking up the table for
1305-
// `selected_goal` directly, first apply the truncation
1306-
// function. This may introduce fresh variables, making the
1307-
// goal that we are looking up more general, and forcing us to
1308-
// reuse an existing table. For example, if we had a selected
1309-
// goal of
1310-
//
1311-
// // Vec<Vec<Vec<Vec<i32>>>>: Sized
1312-
//
1313-
// we might now produce a truncated goal of
1314-
//
1315-
// // Vec<Vec<?T>>: Sized
1316-
//
1317-
// Obviously, the answer we are looking for -- if it exists -- will be
1318-
// found amongst the answers of this new, truncated goal.
1319-
//
1320-
// Subtle point: Note that the **selected goal** remains
1321-
// unchanged and will be carried over into the "pending
1322-
// clause" for the positive link on the new subgoal. This
1323-
// means that if our new, truncated subgoal produces
1324-
// irrelevant answers (e.g., `Vec<Vec<u32>>: Sized`), they
1325-
// will fail to unify with our selected goal, producing no
1326-
// resolvent.
1319+
) -> Option<(C::UCanonicalGoalInEnvironment, C::UniverseMap)> {
13271320
match infer.truncate_goal(subgoal) {
1328-
None => infer.fully_canonicalize_goal(subgoal),
1329-
Some(truncated_subgoal) => {
1330-
debug!("truncated={:?}", truncated_subgoal);
1331-
infer.fully_canonicalize_goal(&truncated_subgoal)
1332-
}
1321+
Some(_) => None,
1322+
None => Some(infer.fully_canonicalize_goal(subgoal)),
13331323
}
13341324
}
13351325

@@ -1384,57 +1374,6 @@ impl<C: Context> Forest<C> {
13841374
// affect completeness when it comes to subgoal abstraction.
13851375
let inverted_subgoal = infer.invert_goal(subgoal)?;
13861376

1387-
// DIVERGENCE
1388-
//
1389-
// If the negative subgoal has grown so large that we would have
1390-
// to truncate it, we currently just abort the computation
1391-
// entirely. This is not necessary -- the SA paper makes no
1392-
// such distinction, for example, and applies truncation equally
1393-
// for positive/negative literals. However, there are some complications
1394-
// that arise that I do not wish to deal with right now.
1395-
//
1396-
// Let's work through an example to show you what I
1397-
// mean. Imagine we have this (negative) selected literal;
1398-
// hence `selected_subgoal` will just be the inner part:
1399-
//
1400-
// // not { Vec<Vec<Vec<Vec<i32>>>>: Sized }
1401-
// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1402-
// // `selected_goal`
1403-
//
1404-
// (In this case, the `inverted_subgoal` would be the same,
1405-
// since there are no free universal variables.)
1406-
//
1407-
// If truncation **doesn't apply**, we would go and lookup the
1408-
// table for the selected goal (`Vec<Vec<..>>: Sized`) and see
1409-
// whether it has any answers. If it does, and they are
1410-
// definite, then this negative literal is false. We don't
1411-
// really care even how many answers there are and so forth
1412-
// (if the goal is ground, as in this case, there can be at
1413-
// most one definite answer, but if there are universals, then
1414-
// the inverted goal would have variables; even so, a single
1415-
// definite answer suffices to show that the `not { .. }` goal
1416-
// is false).
1417-
//
1418-
// Truncation muddies the water, because the table may
1419-
// generate answers that are not relevant to our original,
1420-
// untruncated literal. Suppose that we truncate the selected
1421-
// goal to:
1422-
//
1423-
// // Vec<Vec<T>>: Sized
1424-
//
1425-
// Clearly this table will have some solutions that don't
1426-
// apply to us. e.g., `Vec<Vec<u32>>: Sized` is a solution to
1427-
// this table, but that doesn't imply that `not {
1428-
// Vec<Vec<Vec<..>>>: Sized }` is false.
1429-
//
1430-
// This can be made to work -- we carry along the original
1431-
// selected goal when we establish links between tables, and
1432-
// we could use that to screen the resulting answers. (There
1433-
// are some further complications around the fact that
1434-
// selected goal may contain universally quantified free
1435-
// variables that have been inverted, as discussed in the
1436-
// prior paragraph above.) I just didn't feel like dealing
1437-
// with it yet.
14381377
match infer.truncate_goal(&inverted_subgoal) {
14391378
Some(_) => None,
14401379
None => Some(infer.fully_canonicalize_goal(&inverted_subgoal)),
@@ -1458,50 +1397,4 @@ impl<C: Context> Forest<C> {
14581397
});
14591398
debug!("flounder_subgoal: ex_clause={:#?}", ex_clause);
14601399
}
1461-
1462-
/// Used whenever we process an answer (whether new or cached) on
1463-
/// a positive edge (the SLG POSITIVE RETURN operation). Truncates
1464-
/// the resolvent (or factor) if it has grown too large.
1465-
fn truncate_returned(&self, ex_clause: &mut ExClause<C>, infer: &mut dyn InferenceTable<C>) {
1466-
// DIVERGENCE
1467-
//
1468-
// In the original RR paper, truncation is only applied
1469-
// when the result of resolution is a new answer (i.e.,
1470-
// `ex_clause.subgoals.is_empty()`). I've chosen to be
1471-
// more aggressive here, precisely because or our extended
1472-
// semantics for unification. In particular, unification
1473-
// can insert new goals, so I fear that positive feedback
1474-
// loops could still run indefinitely in the original
1475-
// formulation. I would like to revise our unification
1476-
// mechanism to avoid that problem, in which case this could
1477-
// be tightened up to be more like the original RR paper.
1478-
//
1479-
// Still, I *believe* this more aggressive approx. should
1480-
// not interfere with any of the properties of the
1481-
// original paper. In particular, applying truncation only
1482-
// when the resolvent has no subgoals seems like it is
1483-
// aimed at giving us more times to eliminate this
1484-
// ambiguous answer.
1485-
1486-
match infer.truncate_answer(&ex_clause.subst) {
1487-
// No need to truncate
1488-
None => {}
1489-
1490-
// Resolvent got too large. Have to introduce approximation.
1491-
Some(truncated_subst) => {
1492-
mem::replace(
1493-
ex_clause,
1494-
ExClause {
1495-
subst: truncated_subst,
1496-
ambiguous: true,
1497-
constraints: vec![],
1498-
subgoals: vec![],
1499-
delayed_subgoals: vec![],
1500-
answer_time: TimeStamp::default(),
1501-
floundered_subgoals: vec![],
1502-
},
1503-
);
1504-
}
1505-
}
1506-
}
15071400
}

tests/test/cycle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ fn overflow() {
164164
goal {
165165
S<Z>: Q
166166
} yields {
167-
"No possible solution"
167+
"Ambiguous; no inference guidance"
168168
}
169169
}
170170
}

tests/test/misc.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ fn subgoal_abstraction() {
7272

7373
goal {
7474
exists<T> { T: Foo }
75-
} yields_all[SolverChoice::slg(50, None)] {
75+
} yields_first[SolverChoice::slg(50, None)] {
76+
"Floundered"
7677
}
7778
}
7879
}
@@ -169,25 +170,25 @@ fn subgoal_cycle_uninhabited() {
169170
impl<T> Foo for Box<T> where Box<Vec<T>>: Foo { }
170171
}
171172

172-
// There is no solution here with a finite proof, so we get
173-
// back: 0 answer(s) found.
173+
// Infinite recursion -> we flounder
174174
goal {
175175
exists<T> { T: Foo }
176-
} yields_all[SolverChoice::slg(2, None)] {
176+
} yields_first[SolverChoice::slg(2, None)] {
177+
"Floundered"
177178
}
178179

179-
// Unsurprisingly, applying negation succeeds then.
180+
// Unsurprisingly, applying negation also flounders.
180181
goal {
181182
not { exists<T> { T: Foo } }
182-
} yields_all[SolverChoice::slg(2, None)] {
183-
"substitution [], lifetime constraints []"
183+
} yields_first[SolverChoice::slg(2, None)] {
184+
"Floundered"
184185
}
185186

186187
// Equivalent to the previous.
187188
goal {
188189
forall<T> { not { T: Foo } }
189-
} yields_all[SolverChoice::slg(2, None)] {
190-
"substitution [], lifetime constraints []"
190+
} yields_first[SolverChoice::slg(2, None)] {
191+
"Floundered"
191192
}
192193

193194
// However, if we come across a negative goal that exceeds our
@@ -208,8 +209,9 @@ fn subgoal_cycle_uninhabited() {
208209
// Here, due to the hypothesis, there does indeed exist a suitable T, `U`.
209210
goal {
210211
forall<U> { if (U: Foo) { exists<T> { T: Foo } } }
211-
} yields_all[SolverChoice::slg(2, None)] {
212-
"substitution [?0 := !1_0], lifetime constraints []"
212+
} yields_first[SolverChoice::slg(2, None)] {
213+
"substitution [?0 := !1_0], lifetime constraints []",
214+
"Floundered"
213215
}
214216
}
215217
}
@@ -226,10 +228,12 @@ fn subgoal_cycle_inhabited() {
226228
impl Foo for u32 { }
227229
}
228230

231+
// Exceeds size threshold -> flounder
229232
goal {
230233
exists<T> { T: Foo }
231-
} yields_all[SolverChoice::slg(3, None)] {
232-
"substitution [?0 := u32], lifetime constraints []"
234+
} yields_first[SolverChoice::slg(3, None)] {
235+
"substitution [?0 := u32], lifetime constraints []",
236+
"Floundered"
233237
}
234238
}
235239
}
@@ -308,12 +312,12 @@ fn cached_answers_1() {
308312

309313
goal {
310314
exists<T> { T: Sour }
311-
} yields_all[SolverChoice::slg(2, None)] {
315+
} yields_first[SolverChoice::slg(2, None)] {
312316
"substitution [?0 := Lemon], lifetime constraints []",
313317
"substitution [?0 := Vinegar], lifetime constraints []",
314318
"substitution [?0 := HotSauce<Lemon>], lifetime constraints []",
315319
"substitution [?0 := HotSauce<Vinegar>], lifetime constraints []",
316-
"Ambiguous(for<?U0> { substitution [?0 := HotSauce<^0>], lifetime constraints [] })"
320+
"Floundered"
317321
}
318322
}
319323
}
@@ -335,12 +339,12 @@ fn cached_answers_2() {
335339

336340
goal {
337341
exists<T> { T: Sour }
338-
} yields_all[SolverChoice::slg(2, None)] {
342+
} yields_first[SolverChoice::slg(2, None)] {
339343
"substitution [?0 := Lemon], lifetime constraints []",
340344
"substitution [?0 := Vinegar], lifetime constraints []",
341345
"substitution [?0 := HotSauce<Lemon>], lifetime constraints []",
342346
"substitution [?0 := HotSauce<Vinegar>], lifetime constraints []",
343-
"Ambiguous(for<?U0> { substitution [?0 := HotSauce<^0>], lifetime constraints [] })"
347+
"Floundered"
344348
}
345349
}
346350
}
@@ -362,12 +366,11 @@ fn cached_answers_3() {
362366

363367
goal {
364368
exists<T> { T: Sour }
365-
} yields_all[SolverChoice::slg(2, None)] {
369+
} yields_first[SolverChoice::slg(2, None)] {
366370
"substitution [?0 := Lemon], lifetime constraints []",
367371
"substitution [?0 := HotSauce<Lemon>], lifetime constraints []",
368372
"substitution [?0 := Vinegar], lifetime constraints []",
369-
"Ambiguous(for<?U0> { substitution [?0 := HotSauce<^0>], lifetime constraints [] })",
370-
"substitution [?0 := HotSauce<Vinegar>], lifetime constraints []"
373+
"Floundered"
371374
}
372375
}
373376
}

0 commit comments

Comments
 (0)