Skip to content

Commit 3afbf31

Browse files
committed
Optimize incompatibility contradiction
1 parent 4e34e80 commit 3afbf31

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<PackageId, 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)]
@@ -60,7 +55,6 @@ impl<DP: DependencyProvider> State<DP> {
6055
root_package_id,
6156
root_version_index,
6257
incompatibilities,
63-
contradicted_incompatibilities: Map::default(),
6458
partial_solution: PartialSolution::empty(),
6559
incompatibility_store,
6660
unit_propagation_buffer: SmallVec::Empty,
@@ -112,13 +106,10 @@ impl<DP: DependencyProvider> State<DP> {
112106
let mut conflict_id = None;
113107
// We only care about incompatibilities if it contains the current package.
114108
for &incompat_id in self.incompatibilities[&current_package].iter().rev() {
115-
if self
116-
.contradicted_incompatibilities
117-
.contains_key(&incompat_id)
118-
{
109+
let current_incompat = &mut self.incompatibility_store[incompat_id];
110+
if self.partial_solution.is_contradicted(current_incompat) {
119111
continue;
120112
}
121-
let current_incompat = &self.incompatibility_store[incompat_id];
122113
match self.partial_solution.relation(current_incompat) {
123114
// If the partial solution satisfies the incompatibility
124115
// we must perform conflict resolution.
@@ -142,15 +133,13 @@ impl<DP: DependencyProvider> State<DP> {
142133
self.partial_solution.add_derivation(
143134
package_almost,
144135
incompat_id,
145-
&self.incompatibility_store,
136+
current_incompat.get(package_almost).unwrap(),
146137
);
147138
// With the partial solution updated, the incompatibility is now contradicted.
148-
self.contradicted_incompatibilities
149-
.insert(incompat_id, self.partial_solution.current_decision_level());
139+
self.partial_solution.contradict(current_incompat);
150140
}
151141
Relation::Contradicted(_) => {
152-
self.contradicted_incompatibilities
153-
.insert(incompat_id, self.partial_solution.current_decision_level());
142+
self.partial_solution.contradict(current_incompat);
154143
}
155144
_ => {}
156145
}
@@ -163,16 +152,16 @@ impl<DP: DependencyProvider> State<DP> {
163152
})?;
164153
self.unit_propagation_buffer.clear();
165154
self.unit_propagation_buffer.push(package_almost);
155+
let root_incompat = &mut self.incompatibility_store[root_cause];
166156
// Add to the partial solution with incompat as cause.
167157
self.partial_solution.add_derivation(
168158
package_almost,
169159
root_cause,
170-
&self.incompatibility_store,
160+
root_incompat.get(package_almost).unwrap(),
171161
);
172162
// After conflict resolution and the partial solution update,
173163
// the root cause incompatibility is now contradicted.
174-
self.contradicted_incompatibilities
175-
.insert(root_cause, self.partial_solution.current_decision_level());
164+
self.partial_solution.contradict(root_incompat);
176165
}
177166
}
178167
// If there are no more changed packages, unit propagation is done.
@@ -241,9 +230,6 @@ impl<DP: DependencyProvider> State<DP> {
241230
decision_level: DecisionLevel,
242231
) {
243232
self.partial_solution.backtrack(decision_level);
244-
// Remove contradicted incompatibilities that depend on decisions we just backtracked away.
245-
self.contradicted_incompatibilities
246-
.retain(|_, dl| *dl <= decision_level);
247233
if incompat_changed {
248234
self.merge_incompatibility(incompat);
249235
}

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
PackageArena, PackageId, 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<PackageId, 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_id, 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_id, term)]),
111148
kind: Kind::NoVersions(package_id, 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_id, term)]),
125163
kind: Kind::Custom(package_id, 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_id, term)]),
139178
kind: Kind::Custom(package_id, set, metadata),
179+
contradiction_info: ContradicationInfo::not_contradicted(),
140180
}
141181
}
142182

@@ -157,6 +197,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
157197
])
158198
},
159199
kind: Kind::FromDependencyOf(package_id, vs, pid2, set2),
200+
contradiction_info: ContradicationInfo::not_contradicted(),
160201
}
161202
}
162203

@@ -232,6 +273,7 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
232273
Self {
233274
package_terms,
234275
kind,
276+
contradiction_info: ContradicationInfo::not_contradicted(),
235277
}
236278
}
237279

@@ -252,6 +294,21 @@ impl<M: Eq + Clone + Debug + Display> Incompatibility<M> {
252294
}
253295
}
254296

297+
/// Check if an incompatibility is contradicted.
298+
pub(crate) fn is_contradicted(&self, last_valid_decision_levels: &[DecisionLevel]) -> bool {
299+
self.contradiction_info
300+
.is_contradicted(last_valid_decision_levels)
301+
}
302+
303+
/// Set incompatibility contradication info.
304+
pub(crate) fn set_contradication_info(
305+
&mut self,
306+
decision_level: DecisionLevel,
307+
backtrack_generation: u32,
308+
) {
309+
self.contradiction_info = ContradicationInfo::new(decision_level, backtrack_generation);
310+
}
311+
255312
/// Get the term related to a given package (if it exists).
256313
pub(crate) fn get(&self, package_id: PackageId) -> Option<Term> {
257314
self.package_terms.get(&package_id).copied()
@@ -401,12 +458,14 @@ pub(crate) mod tests {
401458
let mut store = Arena::new();
402459
let i1 = store.alloc(Incompatibility {
403460
package_terms: SmallMap::Two([(PackageId(1), t1), (PackageId(2), t2.negate())]),
404-
kind: Kind::<String>::FromDependencyOf(PackageId(1), VersionSet::full(), PackageId(2), VersionSet::full())
461+
kind: Kind::<String>::FromDependencyOf(PackageId(1), VersionSet::full(), PackageId(2), VersionSet::full()),
462+
contradiction_info: ContradicationInfo::not_contradicted(),
405463
});
406464

407465
let i2 = store.alloc(Incompatibility {
408466
package_terms: SmallMap::Two([(PackageId(2), t2), (PackageId(3), t3)]),
409-
kind: Kind::<String>::FromDependencyOf(PackageId(2), VersionSet::full(), PackageId(3), VersionSet::full())
467+
kind: Kind::<String>::FromDependencyOf(PackageId(2), VersionSet::full(), PackageId(3), VersionSet::full()),
468+
contradiction_info: ContradicationInfo::not_contradicted(),
410469
});
411470

412471
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<PackageId, 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<PackageId, 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_id: PackageId, 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_id: PackageId,
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_id).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);
@@ -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.
@@ -547,10 +570,6 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
547570
.unwrap();
548571
decision_level.max(DecisionLevel(1))
549572
}
550-
551-
pub(crate) fn current_decision_level(&self) -> DecisionLevel {
552-
self.current_decision_level
553-
}
554573
}
555574

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

0 commit comments

Comments
 (0)