Skip to content

Commit 09103b2

Browse files
committed
move fixed-point logic code into fixed_point module
1 parent 9cbd071 commit 09103b2

File tree

3 files changed

+218
-210
lines changed

3 files changed

+218
-210
lines changed

chalk-recursive/src/fixed_point.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use crate::cache::Cache;
2+
use crate::search_graph::DepthFirstNumber;
3+
use crate::search_graph::SearchGraph;
4+
use crate::stack::{Stack, StackDepth};
5+
use crate::Minimums;
6+
use std::fmt::Debug;
7+
use std::hash::Hash;
8+
use tracing::debug;
9+
use tracing::{info, instrument};
10+
11+
pub(super) struct RecursiveContext<K, V>
12+
where
13+
K: Hash + Eq + Debug + Clone,
14+
V: Debug + Clone,
15+
{
16+
stack: Stack,
17+
18+
/// The "search graph" stores "in-progress results" that are still being
19+
/// solved.
20+
search_graph: SearchGraph<K, V>,
21+
22+
/// The "cache" stores results for goals that we have completely solved.
23+
/// Things are added to the cache when we have completely processed their
24+
/// result.
25+
cache: Option<Cache<K, V>>,
26+
27+
/// The maximum size for goals.
28+
max_size: usize,
29+
}
30+
31+
pub(super) trait SolverStuff<K, V>: Copy
32+
where
33+
K: Hash + Eq + Debug + Clone,
34+
V: Debug + Clone,
35+
{
36+
fn is_coinductive_goal(self, goal: &K) -> bool;
37+
fn initial_value(self, goal: &K, coinductive_goal: bool) -> V;
38+
fn solve_iteration(
39+
self,
40+
context: &mut RecursiveContext<K, V>,
41+
goal: &K,
42+
minimums: &mut Minimums,
43+
) -> V;
44+
fn reached_fixed_point(self, old_value: &V, new_value: &V) -> bool;
45+
fn error_value(self) -> V;
46+
}
47+
48+
impl<K, V> RecursiveContext<K, V>
49+
where
50+
K: Hash + Eq + Debug + Clone,
51+
V: Debug + Clone,
52+
{
53+
pub fn new(overflow_depth: usize, max_size: usize, cache: Option<Cache<K, V>>) -> Self {
54+
RecursiveContext {
55+
stack: Stack::new(overflow_depth),
56+
search_graph: SearchGraph::new(),
57+
cache,
58+
max_size,
59+
}
60+
}
61+
62+
pub fn max_size(&self) -> usize {
63+
self.max_size
64+
}
65+
66+
/// Solves a canonical goal. The substitution returned in the
67+
/// solution will be for the fully decomposed goal. For example, given the
68+
/// program
69+
///
70+
/// ```ignore
71+
/// struct u8 { }
72+
/// struct SomeType<T> { }
73+
/// trait Foo<T> { }
74+
/// impl<U> Foo<u8> for SomeType<U> { }
75+
/// ```
76+
///
77+
/// and the goal `exists<V> { forall<U> { SomeType<U>: Foo<V> }
78+
/// }`, `into_peeled_goal` can be used to create a canonical goal
79+
/// `SomeType<!1>: Foo<?0>`. This function will then return a
80+
/// solution with the substitution `?0 := u8`.
81+
pub fn solve_root_goal(
82+
&mut self,
83+
canonical_goal: &K,
84+
solver_stuff: impl SolverStuff<K, V>,
85+
) -> V {
86+
debug!("solve_root_goal(canonical_goal={:?})", canonical_goal);
87+
assert!(self.stack.is_empty());
88+
let minimums = &mut Minimums::new();
89+
self.solve_goal(canonical_goal, minimums, solver_stuff)
90+
}
91+
92+
/// Attempt to solve a goal that has been fully broken down into leaf form
93+
/// and canonicalized. This is where the action really happens, and is the
94+
/// place where we would perform caching in rustc (and may eventually do in Chalk).
95+
#[instrument(level = "info", skip(self, minimums, solver_stuff,))]
96+
pub fn solve_goal(
97+
&mut self,
98+
goal: &K,
99+
minimums: &mut Minimums,
100+
solver_stuff: impl SolverStuff<K, V>,
101+
) -> V {
102+
// First check the cache.
103+
if let Some(cache) = &self.cache {
104+
if let Some(value) = cache.get(&goal) {
105+
debug!("solve_reduced_goal: cache hit, value={:?}", value);
106+
return value.clone();
107+
}
108+
}
109+
110+
// Next, check if the goal is in the search tree already.
111+
if let Some(dfn) = self.search_graph.lookup(&goal) {
112+
// Check if this table is still on the stack.
113+
if let Some(depth) = self.search_graph[dfn].stack_depth {
114+
self.stack[depth].flag_cycle();
115+
// Mixed cycles are not allowed. For more information about this
116+
// see the corresponding section in the coinduction chapter:
117+
// https://rust-lang.github.io/chalk/book/recursive/coinduction.html#mixed-co-inductive-and-inductive-cycles
118+
if self.stack.mixed_inductive_coinductive_cycle_from(depth) {
119+
return solver_stuff.error_value();
120+
}
121+
}
122+
123+
minimums.update_from(self.search_graph[dfn].links);
124+
125+
// Return the solution from the table.
126+
let previous_solution = self.search_graph[dfn].solution.clone();
127+
info!(
128+
"solve_goal: cycle detected, previous solution {:?}",
129+
previous_solution,
130+
);
131+
previous_solution
132+
} else {
133+
// Otherwise, push the goal onto the stack and create a table.
134+
// The initial result for this table depends on whether the goal is coinductive.
135+
let coinductive_goal = solver_stuff.is_coinductive_goal(goal);
136+
let initial_solution = solver_stuff.initial_value(goal, coinductive_goal);
137+
let depth = self.stack.push(coinductive_goal);
138+
let dfn = self.search_graph.insert(&goal, depth, initial_solution);
139+
140+
let subgoal_minimums = self.solve_new_subgoal(&goal, depth, dfn, solver_stuff);
141+
142+
self.search_graph[dfn].links = subgoal_minimums;
143+
self.search_graph[dfn].stack_depth = None;
144+
self.stack.pop(depth);
145+
minimums.update_from(subgoal_minimums);
146+
147+
// Read final result from table.
148+
let result = self.search_graph[dfn].solution.clone();
149+
150+
// If processing this subgoal did not involve anything
151+
// outside of its subtree, then we can promote it to the
152+
// cache now. This is a sort of hack to alleviate the
153+
// worst of the repeated work that we do during tabling.
154+
if subgoal_minimums.positive >= dfn {
155+
if let Some(cache) = &mut self.cache {
156+
self.search_graph.move_to_cache(dfn, cache);
157+
debug!("solve_reduced_goal: SCC head encountered, moving to cache");
158+
} else {
159+
debug!(
160+
"solve_reduced_goal: SCC head encountered, rolling back as caching disabled"
161+
);
162+
self.search_graph.rollback_to(dfn);
163+
}
164+
}
165+
166+
info!("solve_goal: solution = {:?}", result);
167+
result
168+
}
169+
}
170+
171+
#[instrument(level = "debug", skip(self, solver_stuff))]
172+
fn solve_new_subgoal(
173+
&mut self,
174+
canonical_goal: &K,
175+
depth: StackDepth,
176+
dfn: DepthFirstNumber,
177+
solver_stuff: impl SolverStuff<K, V>,
178+
) -> Minimums {
179+
// We start with `answer = None` and try to solve the goal. At the end of the iteration,
180+
// `answer` will be updated with the result of the solving process. If we detect a cycle
181+
// during the solving process, we cache `answer` and try to solve the goal again. We repeat
182+
// until we reach a fixed point for `answer`.
183+
// Considering the partial order:
184+
// - None < Some(Unique) < Some(Ambiguous)
185+
// - None < Some(CannotProve)
186+
// the function which maps the loop iteration to `answer` is a nondecreasing function
187+
// so this function will eventually be constant and the loop terminates.
188+
loop {
189+
let minimums = &mut Minimums::new();
190+
let current_answer = solver_stuff.solve_iteration(self, &canonical_goal, minimums);
191+
192+
debug!(
193+
"solve_new_subgoal: loop iteration result = {:?} with minimums {:?}",
194+
current_answer, minimums
195+
);
196+
197+
if !self.stack[depth].read_and_reset_cycle_flag() {
198+
// None of our subgoals depended on us directly.
199+
// We can return.
200+
self.search_graph[dfn].solution = current_answer;
201+
return *minimums;
202+
}
203+
204+
let old_answer =
205+
std::mem::replace(&mut self.search_graph[dfn].solution, current_answer);
206+
207+
if solver_stuff.reached_fixed_point(&old_answer, &self.search_graph[dfn].solution) {
208+
return *minimums;
209+
}
210+
211+
// Otherwise: rollback the search tree and try again.
212+
self.search_graph.rollback_to(dfn + 1);
213+
}
214+
}
215+
}

chalk-recursive/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub type UCanonicalGoal<I> = UCanonical<InEnvironment<Goal<I>>>;
55

66
mod cache;
77
mod combine;
8+
mod fixed_point;
89
mod fulfill;
910
mod recursive;
1011
mod search_graph;

0 commit comments

Comments
 (0)