Skip to content

Commit 2f6a365

Browse files
committed
Add a RecursiveSolver trait
1 parent 601568f commit 2f6a365

File tree

2 files changed

+129
-113
lines changed

2 files changed

+129
-113
lines changed

chalk-solve/src/recursive.rs

Lines changed: 96 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod fulfill;
22
mod search_graph;
33
mod stack;
44

5-
use self::fulfill::{Fulfill, RecursiveInferenceTable};
5+
use self::fulfill::{Fulfill, RecursiveInferenceTable, RecursiveSolver};
66
use self::search_graph::{DepthFirstNumber, SearchGraph};
77
use self::stack::{Stack, StackDepth};
88
use crate::clauses::program_clauses_for_goal;
@@ -144,7 +144,7 @@ pub(crate) struct Solver<'me, I: Interner> {
144144
/// The `minimums` struct is used while solving to track whether we encountered
145145
/// any cycles in the process.
146146
#[derive(Copy, Clone, Debug)]
147-
struct Minimums {
147+
pub struct Minimums {
148148
positive: DepthFirstNumber,
149149
}
150150

@@ -234,94 +234,6 @@ impl<'me, I: Interner> Solver<'me, I> {
234234
self.solve_goal(canonical_goal.clone(), minimums)
235235
}
236236

237-
/// Attempt to solve a goal that has been fully broken down into leaf form
238-
/// and canonicalized. This is where the action really happens, and is the
239-
/// place where we would perform caching in rustc (and may eventually do in Chalk).
240-
fn solve_goal(
241-
&mut self,
242-
goal: UCanonicalGoal<I>,
243-
minimums: &mut Minimums,
244-
) -> Fallible<Solution<I>> {
245-
info_heading!("solve_goal({:?})", goal);
246-
247-
// First check the cache.
248-
if let Some(value) = self.context.cache.get(&goal) {
249-
debug!("solve_reduced_goal: cache hit, value={:?}", value);
250-
return value.clone();
251-
}
252-
253-
// Next, check if the goal is in the search tree already.
254-
if let Some(dfn) = self.context.search_graph.lookup(&goal) {
255-
// Check if this table is still on the stack.
256-
if let Some(depth) = self.context.search_graph[dfn].stack_depth {
257-
// Is this a coinductive goal? If so, that is success,
258-
// so we can return normally. Note that this return is
259-
// not tabled.
260-
//
261-
// XXX how does caching with coinduction work?
262-
if self.context.stack.coinductive_cycle_from(depth) {
263-
let value = ConstrainedSubst {
264-
subst: goal.trivial_substitution(self.program.interner()),
265-
constraints: vec![],
266-
};
267-
debug!("applying coinductive semantics");
268-
return Ok(Solution::Unique(Canonical {
269-
value,
270-
binders: goal.canonical.binders,
271-
}));
272-
}
273-
274-
self.context.stack[depth].flag_cycle();
275-
}
276-
277-
minimums.update_from(self.context.search_graph[dfn].links);
278-
279-
// Return the solution from the table.
280-
let previous_solution = self.context.search_graph[dfn].solution.clone();
281-
let previous_solution_priority = self.context.search_graph[dfn].solution_priority;
282-
info!(
283-
"solve_goal: cycle detected, previous solution {:?} with prio {:?}",
284-
previous_solution, previous_solution_priority
285-
);
286-
previous_solution
287-
} else {
288-
// Otherwise, push the goal onto the stack and create a table.
289-
// The initial result for this table is error.
290-
let depth = self.context.stack.push(self.program, &goal);
291-
let dfn = self.context.search_graph.insert(&goal, depth);
292-
let subgoal_minimums = self.solve_new_subgoal(goal, depth, dfn);
293-
self.context.search_graph[dfn].links = subgoal_minimums;
294-
self.context.search_graph[dfn].stack_depth = None;
295-
self.context.stack.pop(depth);
296-
minimums.update_from(subgoal_minimums);
297-
298-
// Read final result from table.
299-
let result = self.context.search_graph[dfn].solution.clone();
300-
let priority = self.context.search_graph[dfn].solution_priority;
301-
302-
// If processing this subgoal did not involve anything
303-
// outside of its subtree, then we can promote it to the
304-
// cache now. This is a sort of hack to alleviate the
305-
// worst of the repeated work that we do during tabling.
306-
if subgoal_minimums.positive >= dfn {
307-
if self.context.caching_enabled {
308-
self.context
309-
.search_graph
310-
.move_to_cache(dfn, &mut self.context.cache);
311-
debug!("solve_reduced_goal: SCC head encountered, moving to cache");
312-
} else {
313-
debug!(
314-
"solve_reduced_goal: SCC head encountered, rolling back as caching disabled"
315-
);
316-
self.context.search_graph.rollback_to(dfn);
317-
}
318-
}
319-
320-
info!("solve_goal: solution = {:?} prio {:?}", result, priority);
321-
result
322-
}
323-
}
324-
325237
fn solve_new_subgoal(
326238
&mut self,
327239
canonical_goal: UCanonicalGoal<I>,
@@ -557,6 +469,100 @@ impl<'me, I: Interner> Solver<'me, I> {
557469
}
558470
}
559471

472+
impl<'me, I: Interner> RecursiveSolver<I> for Solver<'me, I> {
473+
/// Attempt to solve a goal that has been fully broken down into leaf form
474+
/// and canonicalized. This is where the action really happens, and is the
475+
/// place where we would perform caching in rustc (and may eventually do in Chalk).
476+
fn solve_goal(
477+
&mut self,
478+
goal: UCanonicalGoal<I>,
479+
minimums: &mut Minimums,
480+
) -> Fallible<Solution<I>> {
481+
info_heading!("solve_goal({:?})", goal);
482+
483+
// First check the cache.
484+
if let Some(value) = self.context.cache.get(&goal) {
485+
debug!("solve_reduced_goal: cache hit, value={:?}", value);
486+
return value.clone();
487+
}
488+
489+
// Next, check if the goal is in the search tree already.
490+
if let Some(dfn) = self.context.search_graph.lookup(&goal) {
491+
// Check if this table is still on the stack.
492+
if let Some(depth) = self.context.search_graph[dfn].stack_depth {
493+
// Is this a coinductive goal? If so, that is success,
494+
// so we can return normally. Note that this return is
495+
// not tabled.
496+
//
497+
// XXX how does caching with coinduction work?
498+
if self.context.stack.coinductive_cycle_from(depth) {
499+
let value = ConstrainedSubst {
500+
subst: goal.trivial_substitution(self.program.interner()),
501+
constraints: vec![],
502+
};
503+
debug!("applying coinductive semantics");
504+
return Ok(Solution::Unique(Canonical {
505+
value,
506+
binders: goal.canonical.binders,
507+
}));
508+
}
509+
510+
self.context.stack[depth].flag_cycle();
511+
}
512+
513+
minimums.update_from(self.context.search_graph[dfn].links);
514+
515+
// Return the solution from the table.
516+
let previous_solution = self.context.search_graph[dfn].solution.clone();
517+
let previous_solution_priority = self.context.search_graph[dfn].solution_priority;
518+
info!(
519+
"solve_goal: cycle detected, previous solution {:?} with prio {:?}",
520+
previous_solution, previous_solution_priority
521+
);
522+
previous_solution
523+
} else {
524+
// Otherwise, push the goal onto the stack and create a table.
525+
// The initial result for this table is error.
526+
let depth = self.context.stack.push(self.program, &goal);
527+
let dfn = self.context.search_graph.insert(&goal, depth);
528+
let subgoal_minimums = self.solve_new_subgoal(goal, depth, dfn);
529+
self.context.search_graph[dfn].links = subgoal_minimums;
530+
self.context.search_graph[dfn].stack_depth = None;
531+
self.context.stack.pop(depth);
532+
minimums.update_from(subgoal_minimums);
533+
534+
// Read final result from table.
535+
let result = self.context.search_graph[dfn].solution.clone();
536+
let priority = self.context.search_graph[dfn].solution_priority;
537+
538+
// If processing this subgoal did not involve anything
539+
// outside of its subtree, then we can promote it to the
540+
// cache now. This is a sort of hack to alleviate the
541+
// worst of the repeated work that we do during tabling.
542+
if subgoal_minimums.positive >= dfn {
543+
if self.context.caching_enabled {
544+
self.context
545+
.search_graph
546+
.move_to_cache(dfn, &mut self.context.cache);
547+
debug!("solve_reduced_goal: SCC head encountered, moving to cache");
548+
} else {
549+
debug!(
550+
"solve_reduced_goal: SCC head encountered, rolling back as caching disabled"
551+
);
552+
self.context.search_graph.rollback_to(dfn);
553+
}
554+
}
555+
556+
info!("solve_goal: solution = {:?} prio {:?}", result, priority);
557+
result
558+
}
559+
}
560+
561+
fn interner(&self) -> &I {
562+
&self.program.interner()
563+
}
564+
}
565+
560566
fn calculate_inputs<I: Interner>(
561567
interner: &I,
562568
domain_goal: &DomainGoal<I>,

0 commit comments

Comments
 (0)