11// SPDX-License-Identifier: MPL-2.0
22
33//! A Memory acts like a structured partial solution
4- //! where terms are regrouped by package in a [Map].
4+ //! where terms are regrouped by package in a [Map](crate::type_aliases::Map) .
55
66use std:: fmt:: Display ;
7+ use std:: hash:: BuildHasherDefault ;
8+
9+ use priority_queue:: PriorityQueue ;
10+ use rustc_hash:: FxHasher ;
711
812use crate :: internal:: arena:: Arena ;
913use crate :: internal:: incompatibility:: { IncompId , Incompatibility , Relation } ;
1014use crate :: internal:: small_map:: SmallMap ;
1115use crate :: package:: Package ;
1216use crate :: term:: Term ;
13- use crate :: type_aliases:: { Map , SelectedDependencies } ;
17+ use crate :: type_aliases:: SelectedDependencies ;
1418use crate :: version_set:: VersionSet ;
1519
1620use super :: small_vec:: SmallVec ;
1721
22+ type FnvIndexMap < K , V > = indexmap:: IndexMap < K , V , BuildHasherDefault < rustc_hash:: FxHasher > > ;
23+
1824#[ derive( Debug , Copy , Clone , Ord , PartialOrd , Eq , PartialEq ) ]
1925pub struct DecisionLevel ( pub u32 ) ;
2026
@@ -27,13 +33,29 @@ impl DecisionLevel {
2733/// The partial solution contains all package assignments,
2834/// organized by package and historically ordered.
2935#[ derive( Clone , Debug ) ]
30- pub struct PartialSolution < P : Package , VS : VersionSet > {
36+ pub struct PartialSolution < P : Package , VS : VersionSet , Priority : Ord + Clone > {
3137 next_global_index : u32 ,
3238 current_decision_level : DecisionLevel ,
33- package_assignments : Map < P , PackageAssignments < P , VS > > ,
39+ /// `package_assignments` is primarily a HashMap from a package to its
40+ /// `PackageAssignments`. But it can also keep the items in an order.
41+ /// We maintain three sections in this order:
42+ /// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`.
43+ /// This makes it very efficient to extract the solution, And to backtrack to a particular decision level.
44+ /// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments
45+ /// changed since the last time `prioritize` has bean called. Within this range there is no sorting.
46+ /// 3. `[changed_this_decision_level..]` Containes all packages that **have** had there assignments changed since
47+ /// the last time `prioritize` has bean called. The inverse is not necessarily true, some packages in the range
48+ /// did not have a change. Within this range there is no sorting.
49+ package_assignments : FnvIndexMap < P , PackageAssignments < P , VS > > ,
50+ /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment
51+ /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order.
52+ prioritized_potential_packages : PriorityQueue < P , Priority , BuildHasherDefault < FxHasher > > ,
53+ changed_this_decision_level : usize ,
3454}
3555
36- impl < P : Package , VS : VersionSet > Display for PartialSolution < P , VS > {
56+ impl < P : Package , VS : VersionSet , Priority : Ord + Clone > Display
57+ for PartialSolution < P , VS , Priority >
58+ {
3759 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
3860 let mut assignments: Vec < _ > = self
3961 . package_assignments
@@ -120,13 +142,15 @@ pub enum SatisfierSearch<P: Package, VS: VersionSet> {
120142 } ,
121143}
122144
123- impl < P : Package , VS : VersionSet > PartialSolution < P , VS > {
145+ impl < P : Package , VS : VersionSet , Priority : Ord + Clone > PartialSolution < P , VS , Priority > {
124146 /// Initialize an empty PartialSolution.
125147 pub fn empty ( ) -> Self {
126148 Self {
127149 next_global_index : 0 ,
128150 current_decision_level : DecisionLevel ( 0 ) ,
129- package_assignments : Map :: default ( ) ,
151+ package_assignments : FnvIndexMap :: default ( ) ,
152+ prioritized_potential_packages : PriorityQueue :: default ( ) ,
153+ changed_this_decision_level : 0 ,
130154 }
131155 }
132156
@@ -151,18 +175,27 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
151175 }
152176 } ,
153177 }
178+ assert_eq ! (
179+ self . changed_this_decision_level,
180+ self . package_assignments. len( )
181+ ) ;
154182 }
183+ let new_idx = self . current_decision_level . 0 as usize ;
155184 self . current_decision_level = self . current_decision_level . increment ( ) ;
156- let pa = self
185+ let ( old_idx , _ , pa ) = self
157186 . package_assignments
158- . get_mut ( & package)
187+ . get_full_mut ( & package)
159188 . expect ( "Derivations must already exist" ) ;
160189 pa. highest_decision_level = self . current_decision_level ;
161190 pa. assignments_intersection = AssignmentsIntersection :: Decision ( (
162191 self . next_global_index ,
163192 version. clone ( ) ,
164193 Term :: exact ( version) ,
165194 ) ) ;
195+ // Maintain that the beginning of the `package_assignments` Have all decisions in sorted order.
196+ if new_idx != old_idx {
197+ self . package_assignments . swap_indices ( new_idx, old_idx) ;
198+ }
166199 self . next_global_index += 1 ;
167200 }
168201
@@ -173,16 +206,18 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
173206 cause : IncompId < P , VS > ,
174207 store : & Arena < Incompatibility < P , VS > > ,
175208 ) {
176- use std :: collections :: hash_map :: Entry ;
209+ use indexmap :: map :: Entry ;
177210 let term = store[ cause] . get ( & package) . unwrap ( ) . negate ( ) ;
178211 let dated_derivation = DatedDerivation {
179212 global_index : self . next_global_index ,
180213 decision_level : self . current_decision_level ,
181214 cause,
182215 } ;
183216 self . next_global_index += 1 ;
217+ let pa_last_index = self . package_assignments . len ( ) . saturating_sub ( 1 ) ;
184218 match self . package_assignments . entry ( package) {
185219 Entry :: Occupied ( mut occupied) => {
220+ let idx = occupied. index ( ) ;
186221 let pa = occupied. get_mut ( ) ;
187222 pa. highest_decision_level = self . current_decision_level ;
188223 match & mut pa. assignments_intersection {
@@ -192,11 +227,21 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
192227 }
193228 AssignmentsIntersection :: Derivations ( t) => {
194229 * t = t. intersection ( & term) ;
230+ if t. is_positive ( ) {
231+ // we can use `swap_indices` to make `changed_this_decision_level` only go down by 1
232+ // but the copying is slower then the larger search
233+ self . changed_this_decision_level =
234+ std:: cmp:: min ( self . changed_this_decision_level , idx) ;
235+ }
195236 }
196237 }
197238 pa. dated_derivations . push ( dated_derivation) ;
198239 }
199240 Entry :: Vacant ( v) => {
241+ if term. is_positive ( ) {
242+ self . changed_this_decision_level =
243+ std:: cmp:: min ( self . changed_this_decision_level , pa_last_index) ;
244+ }
200245 v. insert ( PackageAssignments {
201246 smallest_decision_level : self . current_decision_level ,
202247 highest_decision_level : self . current_decision_level ,
@@ -207,43 +252,48 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
207252 }
208253 }
209254
210- /// Extract potential packages for the next iteration of unit propagation.
211- /// Return `None` if there is no suitable package anymore, which stops the algorithm.
212- /// A package is a potential pick if there isn't an already
213- /// selected version (no "decision")
214- /// and if it contains at least one positive derivation term
215- /// in the partial solution.
216- pub fn potential_packages ( & self ) -> Option < impl Iterator < Item = ( & P , & VS ) > > {
217- let mut iter = self
218- . package_assignments
255+ pub fn pick_highest_priority_pkg (
256+ & mut self ,
257+ prioritizer : impl Fn ( & P , & VS ) -> Priority ,
258+ ) -> Option < P > {
259+ let check_all = self . changed_this_decision_level
260+ == self . current_decision_level . 0 . saturating_sub ( 1 ) as usize ;
261+ let current_decision_level = self . current_decision_level ;
262+ let prioritized_potential_packages = & mut self . prioritized_potential_packages ;
263+ self . package_assignments
264+ . get_range ( self . changed_this_decision_level ..)
265+ . unwrap ( )
219266 . iter ( )
267+ . filter ( |( _, pa) | {
268+ // We only actually need to update the package if its Been changed
269+ // since the last time we called prioritize.
270+ // Which means it's highest decision level is the current decision level,
271+ // or if we backtracked in the mean time.
272+ check_all || pa. highest_decision_level == current_decision_level
273+ } )
220274 . filter_map ( |( p, pa) | pa. assignments_intersection . potential_package_filter ( p) )
221- . peekable ( ) ;
222- if iter . peek ( ) . is_some ( ) {
223- Some ( iter )
224- } else {
225- None
226- }
275+ . for_each ( | ( p , r ) | {
276+ let priority = prioritizer ( p , r ) ;
277+ prioritized_potential_packages . push ( p . clone ( ) , priority ) ;
278+ } ) ;
279+ self . changed_this_decision_level = self . package_assignments . len ( ) ;
280+ prioritized_potential_packages . pop ( ) . map ( | ( p , _ ) | p )
227281 }
228282
229283 /// If a partial solution has, for every positive derivation,
230284 /// a corresponding decision that satisfies that assignment,
231285 /// it's a total solution and version solving has succeeded.
232- pub fn extract_solution ( & self ) -> Option < SelectedDependencies < P , VS :: V > > {
233- let mut solution = Map :: default ( ) ;
234- for ( p, pa) in & self . package_assignments {
235- match & pa. assignments_intersection {
236- AssignmentsIntersection :: Decision ( ( _, v, _) ) => {
237- solution. insert ( p. clone ( ) , v. clone ( ) ) ;
238- }
239- AssignmentsIntersection :: Derivations ( term) => {
240- if term. is_positive ( ) {
241- return None ;
242- }
286+ pub fn extract_solution ( & self ) -> SelectedDependencies < P , VS :: V > {
287+ self . package_assignments
288+ . iter ( )
289+ . take ( self . current_decision_level . 0 as usize )
290+ . map ( |( p, pa) | match & pa. assignments_intersection {
291+ AssignmentsIntersection :: Decision ( ( _, v, _) ) => ( p. clone ( ) , v. clone ( ) ) ,
292+ AssignmentsIntersection :: Derivations ( _) => {
293+ panic ! ( "Derivations in the Decision part" )
243294 }
244- }
245- }
246- Some ( solution)
295+ } )
296+ . collect ( )
247297 }
248298
249299 /// Backtrack the partial solution to a given decision level.
@@ -290,6 +340,9 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
290340 true
291341 }
292342 } ) ;
343+ // Throw away all stored priority levels, And mark that they all need to be recomputed.
344+ self . prioritized_potential_packages . clear ( ) ;
345+ self . changed_this_decision_level = self . current_decision_level . 0 . saturating_sub ( 1 ) as usize ;
293346 }
294347
295348 /// We can add the version to the partial solution as a decision
@@ -386,7 +439,7 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
386439 /// to return a coherent previous_satisfier_level.
387440 fn find_satisfier (
388441 incompat : & Incompatibility < P , VS > ,
389- package_assignments : & Map < P , PackageAssignments < P , VS > > ,
442+ package_assignments : & FnvIndexMap < P , PackageAssignments < P , VS > > ,
390443 store : & Arena < Incompatibility < P , VS > > ,
391444 ) -> SmallMap < P , ( usize , u32 , DecisionLevel ) > {
392445 let mut satisfied = SmallMap :: Empty ;
@@ -407,7 +460,7 @@ impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
407460 incompat : & Incompatibility < P , VS > ,
408461 satisfier_package : & P ,
409462 mut satisfied_map : SmallMap < P , ( usize , u32 , DecisionLevel ) > ,
410- package_assignments : & Map < P , PackageAssignments < P , VS > > ,
463+ package_assignments : & FnvIndexMap < P , PackageAssignments < P , VS > > ,
411464 store : & Arena < Incompatibility < P , VS > > ,
412465 ) -> DecisionLevel {
413466 // First, let's retrieve the previous derivations and the initial accum_term.
0 commit comments