Skip to content

Commit 5ce70ea

Browse files
committed
Optimize incompatibility contradiction
1 parent 1477e83 commit 5ce70ea

File tree

4 files changed

+115
-37
lines changed

4 files changed

+115
-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_id);
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::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: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ use crate::{
1515
};
1616

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

2021
impl DecisionLevel {
22+
pub(crate) const MAX: Self = Self(u32::MAX);
23+
2124
fn increment(self) -> Self {
2225
Self(self.0 + 1)
2326
}
@@ -41,12 +44,12 @@ pub(crate) struct PartialSolution<DP: DependencyProvider> {
4144
/// did not have a change. Within this range there is no sorting.
4245
#[allow(clippy::type_complexity)]
4346
package_assignments: FxIndexMap<PackageId, PackageAssignments<DP::M>>,
44-
/// `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
4548
/// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order.
4649
prioritized_potential_packages:
4750
PriorityQueue<PackageId, DP::Priority, BuildHasherDefault<FxHasher>>,
4851
changed_this_decision_level: usize,
49-
has_ever_backtracked: bool,
52+
last_valid_decision_levels: Vec<DecisionLevel>,
5053
}
5154

5255
impl<DP: DependencyProvider> PartialSolution<DP> {
@@ -169,10 +172,23 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
169172
package_assignments: FxIndexMap::default(),
170173
prioritized_potential_packages: PriorityQueue::default(),
171174
changed_this_decision_level: 0,
172-
has_ever_backtracked: false,
175+
last_valid_decision_levels: vec![DecisionLevel(0)],
173176
}
174177
}
175178

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+
176192
/// Add a decision.
177193
pub(crate) fn add_decision(&mut self, package_id: PackageId, version_index: VersionIndex) {
178194
// Check that add_decision is never used in the wrong context.
@@ -222,14 +238,14 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
222238
&mut self,
223239
package_id: PackageId,
224240
cause: IncompDpId<DP>,
225-
store: &Arena<Incompatibility<DP::M>>,
241+
incompat_term: Term,
226242
) {
227243
use indexmap::map::Entry;
228244
let mut dated_derivation = DatedDerivation {
229245
global_index: self.next_global_index,
230246
decision_level: self.current_decision_level,
231247
cause,
232-
accumulated_intersection: store[cause].get(package_id).unwrap().negate(),
248+
accumulated_intersection: incompat_term.negate(),
233249
};
234250
self.next_global_index += 1;
235251
let pa_last_index = self.package_assignments.len().saturating_sub(1);
@@ -366,7 +382,16 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
366382
// Throw away all stored priority levels, And mark that they all need to be recomputed.
367383
self.prioritized_potential_packages.clear();
368384
self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize;
369-
self.has_ever_backtracked = true;
385+
386+
// Update list of last valid contradicted decision levels
387+
self.last_valid_decision_levels
388+
.push(DecisionLevel(u32::MAX));
389+
390+
let index = self
391+
.last_valid_decision_levels
392+
.partition_point(|&l| l <= decision_level);
393+
394+
self.last_valid_decision_levels[index..].fill(decision_level);
370395
}
371396

372397
/// We can add the version to the partial solution as a decision
@@ -383,7 +408,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
383408
package_store: &PackageArena<DP::P>,
384409
dependency_provider: &DP,
385410
) {
386-
if !self.has_ever_backtracked {
411+
if self.last_valid_decision_levels.len() == 1 {
387412
// Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem.
388413
// So let's live with a little bit of risk and add the decision without checking the dependencies.
389414
// The worst that can happen is we will have to do a full backtrack which only removes this one decision.
@@ -546,10 +571,6 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
546571
.unwrap();
547572
decision_level.max(DecisionLevel(1))
548573
}
549-
550-
pub(crate) fn current_decision_level(&self) -> DecisionLevel {
551-
self.current_decision_level
552-
}
553574
}
554575

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

0 commit comments

Comments
 (0)