Skip to content

Commit 76c8047

Browse files
committed
Optimize incompatibility contradiction
1 parent f688e72 commit 76c8047

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
@@ -16,6 +16,7 @@ use crate::{
1616
};
1717

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

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

5354
impl<DP: DependencyProvider> PartialSolution<DP> {
@@ -170,10 +171,23 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
170171
package_assignments: FxIndexMap::default(),
171172
prioritized_potential_packages: PriorityQueue::default(),
172173
changed_this_decision_level: 0,
173-
has_ever_backtracked: false,
174+
last_valid_decision_levels: vec![DecisionLevel(0)],
174175
}
175176
}
176177

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

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

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

0 commit comments

Comments
 (0)