8080 }
8181 }
8282
83+ /// Attempt to solve a goal that has been fully broken down into leaf form
84+ /// and canonicalized. This is where the action really happens, and is the
85+ /// place where we would perform caching in rustc (and may eventually do in Chalk).
86+ #[ instrument(
87+ level = "info" ,
88+ skip(
89+ self ,
90+ minimums,
91+ initial_value,
92+ solve_iteration,
93+ reached_fixed_point,
94+ error_value
95+ )
96+ ) ]
97+ fn solve_goal (
98+ & mut self ,
99+ goal : & K ,
100+ minimums : & mut Minimums ,
101+ initial_value : impl Fn ( & K ) -> ( bool , V ) ,
102+ solve_iteration : impl FnMut ( & mut Self , & K , & mut Minimums ) -> V ,
103+ reached_fixed_point : impl Fn ( & V , & V ) -> bool ,
104+ error_value : impl Fn ( ) -> V ,
105+ ) -> V {
106+ // First check the cache.
107+ if let Some ( cache) = & self . cache {
108+ if let Some ( value) = cache. get ( & goal) {
109+ debug ! ( "solve_reduced_goal: cache hit, value={:?}" , value) ;
110+ return value. clone ( ) ;
111+ }
112+ }
113+
114+ // Next, check if the goal is in the search tree already.
115+ if let Some ( dfn) = self . search_graph . lookup ( & goal) {
116+ // Check if this table is still on the stack.
117+ if let Some ( depth) = self . search_graph [ dfn] . stack_depth {
118+ self . stack [ depth] . flag_cycle ( ) ;
119+ // Mixed cycles are not allowed. For more information about this
120+ // see the corresponding section in the coinduction chapter:
121+ // https://rust-lang.github.io/chalk/book/recursive/coinduction.html#mixed-co-inductive-and-inductive-cycles
122+ if self . stack . mixed_inductive_coinductive_cycle_from ( depth) {
123+ return error_value ( ) ;
124+ }
125+ }
126+
127+ minimums. update_from ( self . search_graph [ dfn] . links ) ;
128+
129+ // Return the solution from the table.
130+ let previous_solution = self . search_graph [ dfn] . solution . clone ( ) ;
131+ info ! (
132+ "solve_goal: cycle detected, previous solution {:?}" ,
133+ previous_solution,
134+ ) ;
135+ previous_solution
136+ } else {
137+ // Otherwise, push the goal onto the stack and create a table.
138+ // The initial result for this table depends on whether the goal is coinductive.
139+ let ( coinductive_goal, initial_solution) = initial_value ( goal) ;
140+ let depth = self . stack . push ( coinductive_goal) ;
141+ let dfn = self . search_graph . insert ( & goal, depth, initial_solution) ;
142+
143+ let subgoal_minimums =
144+ self . solve_new_subgoal ( & goal, depth, dfn, solve_iteration, reached_fixed_point) ;
145+
146+ self . search_graph [ dfn] . links = subgoal_minimums;
147+ self . search_graph [ dfn] . stack_depth = None ;
148+ self . stack . pop ( depth) ;
149+ minimums. update_from ( subgoal_minimums) ;
150+
151+ // Read final result from table.
152+ let result = self . search_graph [ dfn] . solution . clone ( ) ;
153+
154+ // If processing this subgoal did not involve anything
155+ // outside of its subtree, then we can promote it to the
156+ // cache now. This is a sort of hack to alleviate the
157+ // worst of the repeated work that we do during tabling.
158+ if subgoal_minimums. positive >= dfn {
159+ if let Some ( cache) = & mut self . cache {
160+ self . search_graph . move_to_cache ( dfn, cache) ;
161+ debug ! ( "solve_reduced_goal: SCC head encountered, moving to cache" ) ;
162+ } else {
163+ debug ! (
164+ "solve_reduced_goal: SCC head encountered, rolling back as caching disabled"
165+ ) ;
166+ self . search_graph . rollback_to ( dfn) ;
167+ }
168+ }
169+
170+ info ! ( "solve_goal: solution = {:?}" , result) ;
171+ result
172+ }
173+ }
174+
83175 #[ instrument( level = "debug" , skip( self , solve_iteration, reached_fixed_point) ) ]
84176 fn solve_new_subgoal (
85177 & mut self ,
@@ -159,122 +251,6 @@ impl<'me, I: Interner> Solver<'me, I> {
159251 let minimums = & mut Minimums :: new ( ) ;
160252 self . solve_goal ( canonical_goal. clone ( ) , minimums)
161253 }
162-
163- /// Attempt to solve a goal that has been fully broken down into leaf form
164- /// and canonicalized. This is where the action really happens, and is the
165- /// place where we would perform caching in rustc (and may eventually do in Chalk).
166- #[ instrument( level = "info" , skip( self , minimums) ) ]
167- fn solve_goal (
168- & mut self ,
169- goal : UCanonicalGoal < I > ,
170- minimums : & mut Minimums ,
171- ) -> Fallible < Solution < I > > {
172- // First check the cache.
173- if let Some ( cache) = & self . context . cache {
174- if let Some ( value) = cache. get ( & goal) {
175- debug ! ( "solve_reduced_goal: cache hit, value={:?}" , value) ;
176- return value. clone ( ) ;
177- }
178- }
179-
180- // Next, check if the goal is in the search tree already.
181- if let Some ( dfn) = self . context . search_graph . lookup ( & goal) {
182- // Check if this table is still on the stack.
183- if let Some ( depth) = self . context . search_graph [ dfn] . stack_depth {
184- self . context . stack [ depth] . flag_cycle ( ) ;
185- // Mixed cycles are not allowed. For more information about this
186- // see the corresponding section in the coinduction chapter:
187- // https://rust-lang.github.io/chalk/book/recursive/coinduction.html#mixed-co-inductive-and-inductive-cycles
188- if self
189- . context
190- . stack
191- . mixed_inductive_coinductive_cycle_from ( depth)
192- {
193- return Err ( NoSolution ) ;
194- }
195- }
196-
197- minimums. update_from ( self . context . search_graph [ dfn] . links ) ;
198-
199- // Return the solution from the table.
200- let previous_solution = self . context . search_graph [ dfn] . solution . clone ( ) ;
201- info ! (
202- "solve_goal: cycle detected, previous solution {:?}" ,
203- previous_solution,
204- ) ;
205- previous_solution
206- } else {
207- // Otherwise, push the goal onto the stack and create a table.
208- // The initial result for this table depends on whether the goal is coinductive.
209- let coinductive_goal = goal. is_coinductive ( self . program ) ;
210- let depth = self . context . stack . push ( coinductive_goal) ;
211- let initial_solution = if coinductive_goal {
212- Ok ( Solution :: Unique ( Canonical {
213- value : ConstrainedSubst {
214- subst : goal. trivial_substitution ( self . interner ( ) ) ,
215- constraints : Constraints :: empty ( self . interner ( ) ) ,
216- } ,
217- binders : goal. canonical . binders . clone ( ) ,
218- } ) )
219- } else {
220- Err ( NoSolution )
221- } ;
222- let dfn = self
223- . context
224- . search_graph
225- . insert ( & goal, depth, initial_solution) ;
226-
227- let program = self . program ;
228- let subgoal_minimums = self . context . solve_new_subgoal (
229- & goal,
230- depth,
231- dfn,
232- |context, goal, minimums| {
233- Solver :: new ( context, program) . solve_iteration ( goal, minimums)
234- } ,
235- |old_answer, current_answer| {
236- // Some of our subgoals depended on us. We need to re-run
237- // with the current answer.
238- old_answer == current_answer || {
239- // Subtle: if our current answer is ambiguous, we can just stop, and
240- // in fact we *must* -- otherwise, we sometimes fail to reach a
241- // fixed point. See `multiple_ambiguous_cycles` for more.
242- match & current_answer {
243- Ok ( s) => s. is_ambig ( ) ,
244- Err ( _) => false ,
245- }
246- }
247- } ,
248- ) ;
249-
250- self . context . search_graph [ dfn] . links = subgoal_minimums;
251- self . context . search_graph [ dfn] . stack_depth = None ;
252- self . context . stack . pop ( depth) ;
253- minimums. update_from ( subgoal_minimums) ;
254-
255- // Read final result from table.
256- let result = self . context . search_graph [ dfn] . solution . clone ( ) ;
257-
258- // If processing this subgoal did not involve anything
259- // outside of its subtree, then we can promote it to the
260- // cache now. This is a sort of hack to alleviate the
261- // worst of the repeated work that we do during tabling.
262- if subgoal_minimums. positive >= dfn {
263- if let Some ( cache) = & mut self . context . cache {
264- self . context . search_graph . move_to_cache ( dfn, cache) ;
265- debug ! ( "solve_reduced_goal: SCC head encountered, moving to cache" ) ;
266- } else {
267- debug ! (
268- "solve_reduced_goal: SCC head encountered, rolling back as caching disabled"
269- ) ;
270- self . context . search_graph . rollback_to ( dfn) ;
271- }
272- }
273-
274- info ! ( "solve_goal: solution = {:?}" , result) ;
275- result
276- }
277- }
278254}
279255
280256impl < ' me , I : Interner > SolveDatabase < I > for Solver < ' me , I > {
@@ -283,7 +259,42 @@ impl<'me, I: Interner> SolveDatabase<I> for Solver<'me, I> {
283259 goal : UCanonicalGoal < I > ,
284260 minimums : & mut Minimums ,
285261 ) -> Fallible < Solution < I > > {
286- self . solve_goal ( goal, minimums)
262+ let program = self . program ;
263+ let interner = program. interner ( ) ;
264+ self . context . solve_goal (
265+ & goal,
266+ minimums,
267+ |goal| {
268+ let coinductive_goal = goal. is_coinductive ( program) ;
269+ let initial_solution = if coinductive_goal {
270+ Ok ( Solution :: Unique ( Canonical {
271+ value : ConstrainedSubst {
272+ subst : goal. trivial_substitution ( interner) ,
273+ constraints : Constraints :: empty ( interner) ,
274+ } ,
275+ binders : goal. canonical . binders . clone ( ) ,
276+ } ) )
277+ } else {
278+ Err ( NoSolution )
279+ } ;
280+ ( coinductive_goal, initial_solution)
281+ } ,
282+ |context, goal, minimums| Solver :: new ( context, program) . solve_iteration ( goal, minimums) ,
283+ |old_answer, current_answer| {
284+ // Some of our subgoals depended on us. We need to re-run
285+ // with the current answer.
286+ old_answer == current_answer || {
287+ // Subtle: if our current answer is ambiguous, we can just stop, and
288+ // in fact we *must* -- otherwise, we sometimes fail to reach a
289+ // fixed point. See `multiple_ambiguous_cycles` for more.
290+ match & current_answer {
291+ Ok ( s) => s. is_ambig ( ) ,
292+ Err ( _) => false ,
293+ }
294+ }
295+ } ,
296+ || Err ( NoSolution ) ,
297+ )
287298 }
288299
289300 fn interner ( & self ) -> & I {
0 commit comments