@@ -30,36 +30,61 @@ impl DecisionLevel {
30
30
#[ derive( Clone , Debug ) ]
31
31
pub ( crate ) struct PartialSolution < DP : DependencyProvider > {
32
32
next_global_index : u32 ,
33
+ /// The number of decisions that have been made, equal to the number of packages with decisions.
33
34
current_decision_level : DecisionLevel ,
34
- /// `package_assignments` is primarily a HashMap from a package to its
35
- /// `PackageAssignments`. But it can also keep the items in an order.
36
- /// We maintain three sections in this order:
37
- /// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`.
38
- /// This makes it very efficient to extract the solution, And to backtrack to a particular decision level.
39
- /// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments
40
- /// changed since the last time `prioritize` has been called. Within this range there is no sorting.
41
- /// 3. `[changed_this_decision_level..]` Contains all packages that **have** had there assignments changed since
42
- /// the last time `prioritize` has been called. The inverse is not necessarily true, some packages in the range
43
- /// did not have a change. Within this range there is no sorting.
35
+ /// Store for all known package decisions and package derivations.
36
+ ///
37
+ /// "assignment" refers to both packages with decisions and package with only derivations and
38
+ /// no decision yet. We combine this in a single index map, where different sections (of
39
+ /// indexes) contain package with different level of information, and make a decision moves a
40
+ /// package from the derivations sections to the decisions section.
41
+ ///
42
+ /// `[..current_decision_level]`: Packages that have had a decision made, sorted by the
43
+ /// `decision_level`. The section is can be seen as the partial solution, it contains a
44
+ /// mapping from package name to decided version. The sorting makes it very efficient to
45
+ /// extract the solution, and to backtrack to a particular decision level. The
46
+ /// `AssignmentsIntersection` is always a `Decision`.
47
+ ///
48
+ /// `[prioritize_decision_level..]`: Packages that are dependencies of some other package,
49
+ /// but have not yet been decided. The `AssignmentsIntersection` is always a `Derivations`, the
50
+ /// derivations store the obligations from the decided packages. This section has two
51
+ /// subsections to optimize the number of `prioritize` calls:
52
+ ///
53
+ /// `[current_decision_level..prioritize_decision_level]`: The assignments of packages in this
54
+ /// range have not changed since the last time `prioritize` has been called, their
55
+ /// priority in `prioritized_potential_packages` is fresh. There is no sorting within this
56
+ /// range.
57
+ ///
58
+ /// `[prioritize_decision_level..]`: The assignments of packages in this range may have changed
59
+ /// since the last time `prioritize` has been called, their priority in
60
+ /// `prioritized_potential_packages` needs to be refreshed. There is no sorting within this
61
+ /// range.
44
62
#[ allow( clippy:: type_complexity) ]
45
63
package_assignments : FnvIndexMap < Id < DP :: P > , PackageAssignments < DP :: P , DP :: VS , DP :: M > > ,
46
- /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment
47
- /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order.
64
+ /// Index into `package_assignments` to decide which packages need to be re-prioritized.
65
+ prioritize_decision_level : usize ,
66
+ /// The undecided packages order by their `Priority`.
67
+ ///
68
+ /// The max heap allows quickly `pop`ing the highest priority package.
48
69
prioritized_potential_packages :
49
70
PriorityQueue < Id < DP :: P > , DP :: Priority , BuildHasherDefault < FxHasher > > ,
50
- changed_this_decision_level : usize ,
71
+ /// Whether we have never backtracked, to enable fast path optimizations.
51
72
has_ever_backtracked : bool ,
52
73
}
53
74
54
- /// Package assignments contain the potential decision and derivations
55
- /// that have already been made for a given package,
56
- /// as well as the intersection of terms by all of these.
75
+ /// A package assignment is either a decision or a list of (accumulated) derivations without a
76
+ /// decision.
57
77
#[ derive( Clone , Debug ) ]
58
78
struct PackageAssignments < P : Package , VS : VersionSet , M : Eq + Clone + Debug + Display > {
79
+ /// Whether the assigment is a decision or a derivation.
80
+ assignments_intersection : AssignmentsIntersection < VS > ,
81
+ /// All constraints on the package version from previous decisions, accumulated by decision
82
+ /// level.
83
+ dated_derivations : SmallVec < DatedDerivation < P , VS , M > > ,
84
+ /// Smallest [`DecisionLevel`] in `dated_derivations`.
59
85
smallest_decision_level : DecisionLevel ,
86
+ /// Highest [`DecisionLevel`] in `dated_derivations`.
60
87
highest_decision_level : DecisionLevel ,
61
- dated_derivations : SmallVec < DatedDerivation < P , VS , M > > ,
62
- assignments_intersection : AssignmentsIntersection < VS > ,
63
88
}
64
89
65
90
impl < P : Package , VS : VersionSet , M : Eq + Clone + Debug + Display > Display
@@ -85,8 +110,13 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Display
85
110
#[ derive( Clone , Debug ) ]
86
111
struct DatedDerivation < P : Package , VS : VersionSet , M : Eq + Clone + Debug + Display > {
87
112
global_index : u32 ,
113
+ /// Only decisions up this level has been used to compute the accumulated term.
88
114
decision_level : DecisionLevel ,
89
115
cause : IncompId < P , VS , M > ,
116
+ /// The intersection of all terms up to `decision_level`.
117
+ ///
118
+ /// It may not contain all terms of this `decision_level`, there may be more than one
119
+ /// `DatedDerivation` per decision level.
90
120
accumulated_intersection : Term < VS > ,
91
121
}
92
122
@@ -100,15 +130,25 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Display
100
130
101
131
#[ derive( Clone , Debug ) ]
102
132
enum AssignmentsIntersection < VS : VersionSet > {
103
- Decision ( ( u32 , VS :: V , Term < VS > ) ) ,
133
+ /// A decision on package for version has been made at the given level.
134
+ Decision {
135
+ decision_level : u32 ,
136
+ version : VS :: V ,
137
+ /// The version, but as positive singleton term.
138
+ term : Term < VS > ,
139
+ } ,
104
140
Derivations ( Term < VS > ) ,
105
141
}
106
142
107
143
impl < VS : VersionSet > Display for AssignmentsIntersection < VS > {
108
144
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
109
145
match self {
110
- Self :: Decision ( ( lvl, version, _) ) => {
111
- write ! ( f, "Decision: level {}, v = {}" , lvl, version)
146
+ Self :: Decision {
147
+ decision_level,
148
+ version,
149
+ term : _,
150
+ } => {
151
+ write ! ( f, "Decision: level {}, v = {}" , decision_level, version)
112
152
}
113
153
Self :: Derivations ( term) => write ! ( f, "Derivations term: {}" , term) ,
114
154
}
@@ -135,7 +175,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
135
175
current_decision_level : DecisionLevel ( 0 ) ,
136
176
package_assignments : FnvIndexMap :: default ( ) ,
137
177
prioritized_potential_packages : PriorityQueue :: default ( ) ,
138
- changed_this_decision_level : 0 ,
178
+ prioritize_decision_level : 0 ,
139
179
has_ever_backtracked : false ,
140
180
}
141
181
}
@@ -173,7 +213,9 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
173
213
None => panic ! ( "Derivations must already exist" ) ,
174
214
Some ( pa) => match & pa. assignments_intersection {
175
215
// Cannot be called when a decision has already been taken.
176
- AssignmentsIntersection :: Decision ( _) => panic ! ( "Already existing decision" ) ,
216
+ AssignmentsIntersection :: Decision { .. } => {
217
+ panic ! ( "Already existing decision" )
218
+ }
177
219
// Cannot be called if the versions is not contained in the terms' intersection.
178
220
AssignmentsIntersection :: Derivations ( term) => {
179
221
debug_assert ! (
@@ -187,7 +229,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
187
229
} ,
188
230
}
189
231
assert_eq ! (
190
- self . changed_this_decision_level ,
232
+ self . prioritize_decision_level ,
191
233
self . package_assignments. len( )
192
234
) ;
193
235
}
@@ -198,11 +240,11 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
198
240
. get_full_mut ( & package)
199
241
. expect ( "Derivations must already exist" ) ;
200
242
pa. highest_decision_level = self . current_decision_level ;
201
- pa. assignments_intersection = AssignmentsIntersection :: Decision ( (
202
- self . next_global_index ,
203
- version. clone ( ) ,
204
- Term :: exact ( version) ,
205
- ) ) ;
243
+ pa. assignments_intersection = AssignmentsIntersection :: Decision {
244
+ decision_level : self . next_global_index ,
245
+ version : version . clone ( ) ,
246
+ term : Term :: exact ( version) ,
247
+ } ;
206
248
// Maintain that the beginning of the `package_assignments` Have all decisions in sorted order.
207
249
if new_idx != old_idx {
208
250
self . package_assignments . swap_indices ( new_idx, old_idx) ;
@@ -233,17 +275,17 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
233
275
pa. highest_decision_level = self . current_decision_level ;
234
276
match & mut pa. assignments_intersection {
235
277
// Check that add_derivation is never called in the wrong context.
236
- AssignmentsIntersection :: Decision ( _ ) => {
278
+ AssignmentsIntersection :: Decision { .. } => {
237
279
panic ! ( "add_derivation should not be called after a decision" )
238
280
}
239
281
AssignmentsIntersection :: Derivations ( t) => {
240
282
* t = t. intersection ( & dated_derivation. accumulated_intersection ) ;
241
283
dated_derivation. accumulated_intersection = t. clone ( ) ;
242
284
if t. is_positive ( ) {
243
- // we can use `swap_indices` to make `changed_this_decision_level ` only go down by 1
285
+ // we can use `swap_indices` to make `prioritize_decision_level ` only go down by 1
244
286
// but the copying is slower then the larger search
245
- self . changed_this_decision_level =
246
- std:: cmp:: min ( self . changed_this_decision_level , idx) ;
287
+ self . prioritize_decision_level =
288
+ std:: cmp:: min ( self . prioritize_decision_level , idx) ;
247
289
}
248
290
}
249
291
}
@@ -252,8 +294,8 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
252
294
Entry :: Vacant ( v) => {
253
295
let term = dated_derivation. accumulated_intersection . clone ( ) ;
254
296
if term. is_positive ( ) {
255
- self . changed_this_decision_level =
256
- std:: cmp:: min ( self . changed_this_decision_level , pa_last_index) ;
297
+ self . prioritize_decision_level =
298
+ std:: cmp:: min ( self . prioritize_decision_level , pa_last_index) ;
257
299
}
258
300
v. insert ( PackageAssignments {
259
301
smallest_decision_level : self . current_decision_level ,
@@ -270,12 +312,12 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
270
312
& mut self ,
271
313
prioritizer : impl Fn ( Id < DP :: P > , & DP :: VS ) -> DP :: Priority ,
272
314
) -> Option < Id < DP :: P > > {
273
- let check_all = self . changed_this_decision_level
315
+ let check_all = self . prioritize_decision_level
274
316
== self . current_decision_level . 0 . saturating_sub ( 1 ) as usize ;
275
317
let current_decision_level = self . current_decision_level ;
276
318
let prioritized_potential_packages = & mut self . prioritized_potential_packages ;
277
319
self . package_assignments
278
- . get_range ( self . changed_this_decision_level ..)
320
+ . get_range ( self . prioritize_decision_level ..)
279
321
. unwrap ( )
280
322
. iter ( )
281
323
. filter ( |( _, pa) | {
@@ -290,7 +332,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
290
332
let priority = prioritizer ( p, r) ;
291
333
prioritized_potential_packages. push ( p, priority) ;
292
334
} ) ;
293
- self . changed_this_decision_level = self . package_assignments . len ( ) ;
335
+ self . prioritize_decision_level = self . package_assignments . len ( ) ;
294
336
prioritized_potential_packages. pop ( ) . map ( |( p, _) | p)
295
337
}
296
338
@@ -302,7 +344,11 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
302
344
. iter ( )
303
345
. take ( self . current_decision_level . 0 as usize )
304
346
. map ( |( & p, pa) | match & pa. assignments_intersection {
305
- AssignmentsIntersection :: Decision ( ( _, v, _) ) => ( p, v. clone ( ) ) ,
347
+ AssignmentsIntersection :: Decision {
348
+ decision_level : _,
349
+ version : v,
350
+ term : _,
351
+ } => ( p, v. clone ( ) ) ,
306
352
AssignmentsIntersection :: Derivations ( _) => {
307
353
panic ! ( "Derivations in the Decision part" )
308
354
}
@@ -347,7 +393,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
347
393
} ) ;
348
394
// Throw away all stored priority levels, And mark that they all need to be recomputed.
349
395
self . prioritized_potential_packages . clear ( ) ;
350
- self . changed_this_decision_level = self . current_decision_level . 0 . saturating_sub ( 1 ) as usize ;
396
+ self . prioritize_decision_level = self . current_decision_level . 0 . saturating_sub ( 1 ) as usize ;
351
397
self . has_ever_backtracked = true ;
352
398
}
353
399
@@ -484,7 +530,11 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
484
530
} else {
485
531
match & satisfier_pa. assignments_intersection {
486
532
AssignmentsIntersection :: Derivations ( _) => panic ! ( "must be a decision" ) ,
487
- AssignmentsIntersection :: Decision ( ( _, _, term) ) => term. clone ( ) ,
533
+ AssignmentsIntersection :: Decision {
534
+ decision_level : _,
535
+ version : _,
536
+ term,
537
+ } => term. clone ( ) ,
488
538
}
489
539
} ;
490
540
@@ -532,9 +582,11 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> PackageAssignm
532
582
// If it wasn't found in the derivations,
533
583
// it must be the decision which is last (if called in the right context).
534
584
match & self . assignments_intersection {
535
- AssignmentsIntersection :: Decision ( ( global_index, _, _) ) => {
536
- ( None , * global_index, self . highest_decision_level )
537
- }
585
+ AssignmentsIntersection :: Decision {
586
+ decision_level : global_index,
587
+ version : _,
588
+ term : _,
589
+ } => ( None , * global_index, self . highest_decision_level ) ,
538
590
AssignmentsIntersection :: Derivations ( accumulated_intersection) => {
539
591
unreachable ! (
540
592
concat!(
@@ -555,7 +607,11 @@ impl<VS: VersionSet> AssignmentsIntersection<VS> {
555
607
/// Returns the term intersection of all assignments (decision included).
556
608
fn term ( & self ) -> & Term < VS > {
557
609
match self {
558
- Self :: Decision ( ( _, _, term) ) => term,
610
+ Self :: Decision {
611
+ decision_level : _,
612
+ version : _,
613
+ term,
614
+ } => term,
559
615
Self :: Derivations ( term) => term,
560
616
}
561
617
}
@@ -566,7 +622,7 @@ impl<VS: VersionSet> AssignmentsIntersection<VS> {
566
622
/// in the partial solution.
567
623
fn potential_package_filter < P : Package > ( & self , package : Id < P > ) -> Option < ( Id < P > , & VS ) > {
568
624
match self {
569
- Self :: Decision ( _ ) => None ,
625
+ Self :: Decision { .. } => None ,
570
626
Self :: Derivations ( term_intersection) => {
571
627
if term_intersection. is_positive ( ) {
572
628
Some ( ( package, term_intersection. unwrap_positive ( ) ) )
0 commit comments