Skip to content

Commit 6005953

Browse files
committed
Optimize incompatibility contradiction
1 parent 6b61cbd commit 6005953

File tree

4 files changed

+113
-37
lines changed

4 files changed

+113
-37
lines changed

src/internal/arena.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt;
22
use std::hash::{Hash, Hasher};
33
use std::marker::PhantomData;
4-
use std::ops::{Index, Range};
4+
use std::ops::{Index, IndexMut, Range};
55

66
/// The index of a value allocated in an arena that holds `T`s.
77
///
@@ -118,9 +118,21 @@ impl<T> Index<Id<T>> for Arena<T> {
118118
}
119119
}
120120

121+
impl<T> IndexMut<Id<T>> for Arena<T> {
122+
fn index_mut(&mut self, id: Id<T>) -> &mut Self::Output {
123+
&mut self.data[id.raw as usize]
124+
}
125+
}
126+
121127
impl<T> Index<Range<Id<T>>> for Arena<T> {
122128
type Output = [T];
123129
fn index(&self, id: Range<Id<T>>) -> &[T] {
124130
&self.data[(id.start.raw as usize)..(id.end.raw as usize)]
125131
}
126132
}
133+
134+
impl<T> IndexMut<Range<Id<T>>> for Arena<T> {
135+
fn index_mut(&mut self, id: Range<Id<T>>) -> &mut Self::Output {
136+
&mut self.data[(id.start.raw as usize)..(id.end.raw as usize)]
137+
}
138+
}

src/internal/core.rs

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ pub(crate) struct State<DP: DependencyProvider> {
2323
#[allow(clippy::type_complexity)]
2424
incompatibilities: Map<Package, Vec<IncompDpId<DP>>>,
2525

26-
/// Store the ids of incompatibilities that are already contradicted.
27-
/// For each one keep track of the decision level when it was found to be contradicted.
28-
/// These will stay contradicted until we have backtracked beyond its associated decision level.
29-
contradicted_incompatibilities: Map<IncompDpId<DP>, DecisionLevel>,
30-
3126
/// All incompatibilities expressing dependencies,
3227
/// with common dependents merged.
3328
#[allow(clippy::type_complexity)]
@@ -58,7 +53,6 @@ impl<DP: DependencyProvider> State<DP> {
5853
root_package,
5954
root_version_index,
6055
incompatibilities,
61-
contradicted_incompatibilities: Map::default(),
6256
partial_solution: PartialSolution::empty(),
6357
incompatibility_store,
6458
unit_propagation_buffer: SmallVec::Empty,
@@ -108,13 +102,10 @@ impl<DP: DependencyProvider> State<DP> {
108102
let mut conflict_id = None;
109103
// We only care about incompatibilities if it contains the current package.
110104
for &incompat_id in self.incompatibilities[&current_package].iter().rev() {
111-
if self
112-
.contradicted_incompatibilities
113-
.contains_key(&incompat_id)
114-
{
105+
let current_incompat = &mut self.incompatibility_store[incompat_id];
106+
if self.partial_solution.is_contradicted(current_incompat) {
115107
continue;
116108
}
117-
let current_incompat = &self.incompatibility_store[incompat_id];
118109
match self.partial_solution.relation(current_incompat) {
119110
// If the partial solution satisfies the incompatibility
120111
// we must perform conflict resolution.
@@ -138,15 +129,13 @@ impl<DP: DependencyProvider> State<DP> {
138129
self.partial_solution.add_derivation(
139130
package_almost,
140131
incompat_id,
141-
&self.incompatibility_store,
132+
current_incompat.get(package_almost).unwrap(),
142133
);
143134
// With the partial solution updated, the incompatibility is now contradicted.
144-
self.contradicted_incompatibilities
145-
.insert(incompat_id, self.partial_solution.current_decision_level());
135+
self.partial_solution.contradict(current_incompat);
146136
}
147137
Relation::Contradicted(_) => {
148-
self.contradicted_incompatibilities
149-
.insert(incompat_id, self.partial_solution.current_decision_level());
138+
self.partial_solution.contradict(current_incompat);
150139
}
151140
_ => {}
152141
}
@@ -159,16 +148,16 @@ impl<DP: DependencyProvider> State<DP> {
159148
})?;
160149
self.unit_propagation_buffer.clear();
161150
self.unit_propagation_buffer.push(package_almost);
151+
let root_incompat = &mut self.incompatibility_store[root_cause];
162152
// Add to the partial solution with incompat as cause.
163153
self.partial_solution.add_derivation(
164154
package_almost,
165155
root_cause,
166-
&self.incompatibility_store,
156+
root_incompat.get(package_almost).unwrap(),
167157
);
168158
// After conflict resolution and the partial solution update,
169159
// the root cause incompatibility is now contradicted.
170-
self.contradicted_incompatibilities
171-
.insert(root_cause, self.partial_solution.current_decision_level());
160+
self.partial_solution.contradict(root_incompat);
172161
}
173162
}
174163
// If there are no more changed packages, unit propagation is done.
@@ -236,9 +225,6 @@ impl<DP: DependencyProvider> State<DP> {
236225
decision_level: DecisionLevel,
237226
) {
238227
self.partial_solution.backtrack(decision_level);
239-
// Remove contradicted incompatibilities that depend on decisions we just backtracked away.
240-
self.contradicted_incompatibilities
241-
.retain(|_, dl| *dl <= decision_level);
242228
if incompat_changed {
243229
self.merge_incompatibility(incompat);
244230
}

src/internal/incompatibility.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,46 @@ use std::fmt::{self, Debug, Display};
77
use std::sync::Arc;
88

99
use crate::{
10-
internal::{Arena, Id, SmallMap},
10+
internal::{Arena, DecisionLevel, Id, SmallMap},
1111
term, DefaultStringReportFormatter, DependencyProvider, DerivationTree, Derived, External, Map,
1212
Package, PackageArena, ReportFormatter, Set, Term, VersionIndex, VersionSet,
1313
};
1414

15+
#[derive(Debug, Clone)]
16+
struct ContradicationInfo {
17+
/// Store the decision level when the incompatibility was found to be contradicted.
18+
/// These will stay contradicted until we have backtracked beyond its associated decision level.
19+
decision_level: DecisionLevel,
20+
/// Store the backtrack generation for the decision level.
21+
backtrack_generation: u32,
22+
}
23+
24+
impl ContradicationInfo {
25+
/// Construct a new value.
26+
fn new(decision_level: DecisionLevel, backtrack_generation: u32) -> Self {
27+
Self {
28+
decision_level,
29+
backtrack_generation,
30+
}
31+
}
32+
33+
/// Construct a value interpreted as not contradicted.
34+
fn not_contradicted() -> Self {
35+
Self {
36+
decision_level: DecisionLevel(u32::MAX),
37+
backtrack_generation: 0,
38+
}
39+
}
40+
41+
/// Check for contradiction.
42+
fn is_contradicted(&self, last_valid_decision_levels: &[DecisionLevel]) -> bool {
43+
last_valid_decision_levels
44+
.get(self.backtrack_generation as usize)
45+
.map(|&l| self.decision_level <= l)
46+
.unwrap_or(true)
47+
}
48+
}
49+
1550
/// An incompatibility is a set of terms for different packages
1651
/// that should never be satisfied all together.
1752
/// An incompatibility usually originates from a package dependency.
@@ -31,6 +66,7 @@ use crate::{
3166
pub(crate) struct Incompatibility<M: Eq + Clone + Debug + Display> {
3267
package_terms: SmallMap<Package, Term>,
3368
kind: Kind<M>,
69+
contradiction_info: ContradicationInfo,
3470
}
3571

3672
/// Type alias of unique identifiers for incompatibilities.
@@ -96,6 +132,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
96132
Term::negative(VersionSet::singleton(version_index)),
97133
)]),
98134
kind: Kind::NotRoot(package, version_index),
135+
contradiction_info: ContradicationInfo::not_contradicted(),
99136
}
100137
}
101138

@@ -109,6 +146,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
109146
Self {
110147
package_terms: SmallMap::One([(package, term)]),
111148
kind: Kind::NoVersions(package, set),
149+
contradiction_info: ContradicationInfo::not_contradicted(),
112150
}
113151
}
114152

@@ -123,6 +161,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
123161
Self {
124162
package_terms: SmallMap::One([(package, term)]),
125163
kind: Kind::Custom(package, set, metadata),
164+
contradiction_info: ContradicationInfo::not_contradicted(),
126165
}
127166
}
128167

@@ -137,6 +176,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
137176
Self {
138177
package_terms: SmallMap::One([(package, term)]),
139178
kind: Kind::Custom(package, set, metadata),
179+
contradiction_info: ContradicationInfo::not_contradicted(),
140180
}
141181
}
142182

@@ -154,6 +194,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
154194
SmallMap::Two([(package, Term::positive(vs)), (p2, Term::negative(set2))])
155195
},
156196
kind: Kind::FromDependencyOf(package, vs, p2, set2),
197+
contradiction_info: ContradicationInfo::not_contradicted(),
157198
}
158199
}
159200

@@ -227,6 +268,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
227268
Self {
228269
package_terms,
229270
kind,
271+
contradiction_info: ContradicationInfo::not_contradicted(),
230272
}
231273
}
232274

@@ -247,6 +289,21 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
247289
}
248290
}
249291

292+
/// Check if an incompatibility is contradicted.
293+
pub(crate) fn is_contradicted(&self, last_valid_decision_levels: &[DecisionLevel]) -> bool {
294+
self.contradiction_info
295+
.is_contradicted(last_valid_decision_levels)
296+
}
297+
298+
/// Set incompatibility contradication info.
299+
pub(crate) fn set_contradication_info(
300+
&mut self,
301+
decision_level: DecisionLevel,
302+
backtrack_generation: u32,
303+
) {
304+
self.contradiction_info = ContradicationInfo::new(decision_level, backtrack_generation);
305+
}
306+
250307
/// Get the term related to a given package (if it exists).
251308
pub(crate) fn get(&self, package: Package) -> Option<Term> {
252309
self.package_terms.get(&package).copied()
@@ -391,12 +448,14 @@ pub(crate) mod tests {
391448
let mut store = Arena::new();
392449
let i1 = store.alloc(Incompatibility {
393450
package_terms: SmallMap::Two([(Package(1), t1), (Package(2), t2.negate())]),
394-
kind: Kind::<String>::FromDependencyOf(Package(1), VersionSet::full(), Package(2), VersionSet::full())
451+
kind: Kind::<String>::FromDependencyOf(Package(1), VersionSet::full(), Package(2), VersionSet::full()),
452+
contradiction_info: ContradicationInfo::not_contradicted(),
395453
});
396454

397455
let i2 = store.alloc(Incompatibility {
398456
package_terms: SmallMap::Two([(Package(2), t2), (Package(3), t3)]),
399-
kind: Kind::<String>::FromDependencyOf(Package(2), VersionSet::full(), Package(3), VersionSet::full())
457+
kind: Kind::<String>::FromDependencyOf(Package(2), VersionSet::full(), Package(3), VersionSet::full()),
458+
contradiction_info: ContradicationInfo::not_contradicted(),
400459
});
401460

402461
let mut i3 = Map::default();

src/internal/partial_solution.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717
};
1818

1919
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
20+
#[repr(transparent)]
2021
pub(crate) struct DecisionLevel(pub(crate) u32);
2122

2223
impl DecisionLevel {
@@ -43,12 +44,12 @@ pub(crate) struct PartialSolution<DP: DependencyProvider> {
4344
/// did not have a change. Within this range there is no sorting.
4445
#[allow(clippy::type_complexity)]
4546
package_assignments: FxIndexMap<Package, PackageAssignments<DP::M>>,
46-
/// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment
47+
/// `prioritized_potential_packages` is primarily a HashMap from a package with no decision and a positive assignment
4748
/// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order.
4849
prioritized_potential_packages:
4950
PriorityQueue<Package, DP::Priority, BuildHasherDefault<FxHasher>>,
5051
changed_this_decision_level: usize,
51-
has_ever_backtracked: bool,
52+
last_valid_decision_levels: Vec<DecisionLevel>,
5253
}
5354

5455
impl<DP: DependencyProvider> PartialSolution<DP> {
@@ -171,10 +172,23 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
171172
package_assignments: FxIndexMap::default(),
172173
prioritized_potential_packages: PriorityQueue::default(),
173174
changed_this_decision_level: 0,
174-
has_ever_backtracked: false,
175+
last_valid_decision_levels: vec![DecisionLevel(0)],
175176
}
176177
}
177178

179+
/// Check if an incompatibility is contradicted.
180+
pub(crate) fn is_contradicted(&self, incompat: &Incompatibility<DP::M>) -> bool {
181+
incompat.is_contradicted(&self.last_valid_decision_levels)
182+
}
183+
184+
/// Contradict an incompatibility.
185+
pub(crate) fn contradict(&self, incompat: &mut Incompatibility<DP::M>) {
186+
incompat.set_contradication_info(
187+
self.current_decision_level,
188+
self.last_valid_decision_levels.len() as u32,
189+
);
190+
}
191+
178192
/// Add a decision.
179193
pub(crate) fn add_decision(&mut self, package: Package, version_index: VersionIndex) {
180194
// Check that add_decision is never used in the wrong context.
@@ -224,14 +238,14 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
224238
&mut self,
225239
package: Package,
226240
cause: IncompDpId<DP>,
227-
store: &Arena<Incompatibility<DP::M>>,
241+
incompat_term: Term,
228242
) {
229243
use indexmap::map::Entry;
230244
let mut dated_derivation = DatedDerivation {
231245
global_index: self.next_global_index,
232246
decision_level: self.current_decision_level,
233247
cause,
234-
accumulated_intersection: store[cause].get(package).unwrap().negate(),
248+
accumulated_intersection: incompat_term.negate(),
235249
};
236250
self.next_global_index += 1;
237251
let pa_last_index = self.package_assignments.len().saturating_sub(1);
@@ -367,7 +381,16 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
367381
// Throw away all stored priority levels, And mark that they all need to be recomputed.
368382
self.prioritized_potential_packages.clear();
369383
self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize;
370-
self.has_ever_backtracked = true;
384+
385+
// Update list of last valid contradicted decision levels
386+
self.last_valid_decision_levels
387+
.push(DecisionLevel(u32::MAX));
388+
389+
let index = self
390+
.last_valid_decision_levels
391+
.partition_point(|&l| l <= decision_level);
392+
393+
self.last_valid_decision_levels[index..].fill(decision_level);
371394
}
372395

373396
/// We can add the version to the partial solution as a decision
@@ -384,7 +407,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
384407
package_store: &PackageArena<DP::P>,
385408
dependency_provider: &DP,
386409
) {
387-
if !self.has_ever_backtracked {
410+
if self.last_valid_decision_levels.len() == 1 {
388411
// Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem.
389412
// So let's live with a little bit of risk and add the decision without checking the dependencies.
390413
// The worst that can happen is we will have to do a full backtrack which only removes this one decision.
@@ -543,10 +566,6 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
543566
.unwrap();
544567
decision_level.max(DecisionLevel(1))
545568
}
546-
547-
pub(crate) fn current_decision_level(&self) -> DecisionLevel {
548-
self.current_decision_level
549-
}
550569
}
551570

552571
impl<M: Eq + Clone + Debug + Display> PackageAssignments<M> {

0 commit comments

Comments
 (0)