Skip to content

Commit ddf0a70

Browse files
committed
Optimize incompatibility contradiction
1 parent e36d261 commit ddf0a70

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
@@ -22,11 +22,6 @@ pub(crate) struct State<DP: DependencyProvider> {
2222
#[allow(clippy::type_complexity)]
2323
incompatibilities: Map<Package, Vec<IncompDpId<DP>>>,
2424

25-
/// Store the ids of incompatibilities that are already contradicted.
26-
/// For each one keep track of the decision level when it was found to be contradicted.
27-
/// These will stay contradicted until we have backtracked beyond its associated decision level.
28-
contradicted_incompatibilities: Map<IncompDpId<DP>, DecisionLevel>,
29-
3025
/// All incompatibilities expressing dependencies,
3126
/// with common dependents merged.
3227
#[allow(clippy::type_complexity)]
@@ -57,7 +52,6 @@ impl<DP: DependencyProvider> State<DP> {
5752
root_package,
5853
root_version,
5954
incompatibilities,
60-
contradicted_incompatibilities: Map::default(),
6155
partial_solution: PartialSolution::empty(),
6256
incompatibility_store,
6357
unit_propagation_buffer: SmallVec::Empty,
@@ -107,13 +101,10 @@ impl<DP: DependencyProvider> State<DP> {
107101
let mut conflict_id = None;
108102
// We only care about incompatibilities if it contains the current package.
109103
for &incompat_id in self.incompatibilities[&current_package].iter().rev() {
110-
if self
111-
.contradicted_incompatibilities
112-
.contains_key(&incompat_id)
113-
{
104+
let current_incompat = &mut self.incompatibility_store[incompat_id];
105+
if self.partial_solution.is_contradicted(current_incompat) {
114106
continue;
115107
}
116-
let current_incompat = &self.incompatibility_store[incompat_id];
117108
match self.partial_solution.relation(current_incompat) {
118109
// If the partial solution satisfies the incompatibility
119110
// we must perform conflict resolution.
@@ -137,15 +128,13 @@ impl<DP: DependencyProvider> State<DP> {
137128
self.partial_solution.add_derivation(
138129
package_almost,
139130
incompat_id,
140-
&self.incompatibility_store,
131+
current_incompat.get(package_almost).unwrap(),
141132
);
142133
// With the partial solution updated, the incompatibility is now contradicted.
143-
self.contradicted_incompatibilities
144-
.insert(incompat_id, self.partial_solution.current_decision_level());
134+
self.partial_solution.contradict(current_incompat);
145135
}
146136
Relation::Contradicted(_) => {
147-
self.contradicted_incompatibilities
148-
.insert(incompat_id, self.partial_solution.current_decision_level());
137+
self.partial_solution.contradict(current_incompat);
149138
}
150139
_ => {}
151140
}
@@ -158,16 +147,16 @@ impl<DP: DependencyProvider> State<DP> {
158147
})?;
159148
self.unit_propagation_buffer.clear();
160149
self.unit_propagation_buffer.push(package_almost);
150+
let root_incompat = &mut self.incompatibility_store[root_cause];
161151
// Add to the partial solution with incompat as cause.
162152
self.partial_solution.add_derivation(
163153
package_almost,
164154
root_cause,
165-
&self.incompatibility_store,
155+
root_incompat.get(package_almost).unwrap(),
166156
);
167157
// After conflict resolution and the partial solution update,
168158
// the root cause incompatibility is now contradicted.
169-
self.contradicted_incompatibilities
170-
.insert(root_cause, self.partial_solution.current_decision_level());
159+
self.partial_solution.contradict(root_incompat);
171160
}
172161
}
173162
// If there are no more changed packages, unit propagation is done.
@@ -235,9 +224,6 @@ impl<DP: DependencyProvider> State<DP> {
235224
decision_level: DecisionLevel,
236225
) {
237226
self.partial_solution.backtrack(decision_level);
238-
// Remove contradicted incompatibilities that depend on decisions we just backtracked away.
239-
self.contradicted_incompatibilities
240-
.retain(|_, dl| *dl <= decision_level);
241227
if incompat_changed {
242228
self.merge_incompatibility(incompat);
243229
}

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, Version, 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: 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: Clone + Debug + Display> Incompatibility<M> {
96132
Term::negative(VersionSet::singleton(version)),
97133
)]),
98134
kind: Kind::NotRoot(package, version),
135+
contradiction_info: ContradicationInfo::not_contradicted(),
99136
}
100137
}
101138

@@ -109,6 +146,7 @@ impl<M: 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: 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

@@ -133,6 +172,7 @@ impl<M: Clone + Debug + Display> Incompatibility<M> {
133172
Self {
134173
package_terms: SmallMap::One([(package, term)]),
135174
kind: Kind::Custom(package, set, metadata),
175+
contradiction_info: ContradicationInfo::not_contradicted(),
136176
}
137177
}
138178

@@ -153,6 +193,7 @@ impl<M: Clone + Debug + Display> Incompatibility<M> {
153193
])
154194
},
155195
kind: Kind::FromDependencyOf(package, versions, p2, set2),
196+
contradiction_info: ContradicationInfo::not_contradicted(),
156197
}
157198
}
158199

@@ -226,6 +267,7 @@ impl<M: Clone + Debug + Display> Incompatibility<M> {
226267
Self {
227268
package_terms,
228269
kind,
270+
contradiction_info: ContradicationInfo::not_contradicted(),
229271
}
230272
}
231273

@@ -242,6 +284,21 @@ impl<M: Clone + Debug + Display> Incompatibility<M> {
242284
}
243285
}
244286

287+
/// Check if an incompatibility is contradicted.
288+
pub(crate) fn is_contradicted(&self, last_valid_decision_levels: &[DecisionLevel]) -> bool {
289+
self.contradiction_info
290+
.is_contradicted(last_valid_decision_levels)
291+
}
292+
293+
/// Set incompatibility contradication info.
294+
pub(crate) fn set_contradication_info(
295+
&mut self,
296+
decision_level: DecisionLevel,
297+
backtrack_generation: u32,
298+
) {
299+
self.contradiction_info = ContradicationInfo::new(decision_level, backtrack_generation);
300+
}
301+
245302
/// Get the term related to a given package (if it exists).
246303
pub(crate) fn get(&self, package: Package) -> Option<Term> {
247304
self.package_terms.get(&package).copied()
@@ -382,12 +439,14 @@ pub(crate) mod tests {
382439
let mut store = Arena::new();
383440
let i1 = store.alloc(Incompatibility {
384441
package_terms: SmallMap::Two([(Package(1), t1), (Package(2), t2.negate())]),
385-
kind: Kind::<String>::FromDependencyOf(Package(1), VersionSet::full(), Package(2), VersionSet::full())
442+
kind: Kind::<String>::FromDependencyOf(Package(1), VersionSet::full(), Package(2), VersionSet::full()),
443+
contradiction_info: ContradicationInfo::not_contradicted(),
386444
});
387445

388446
let i2 = store.alloc(Incompatibility {
389447
package_terms: SmallMap::Two([(Package(2), t2), (Package(3), t3)]),
390-
kind: Kind::<String>::FromDependencyOf(Package(2), VersionSet::full(), Package(3), VersionSet::full())
448+
kind: Kind::<String>::FromDependencyOf(Package(2), VersionSet::full(), Package(3), VersionSet::full()),
449+
contradiction_info: ContradicationInfo::not_contradicted(),
391450
});
392451

393452
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: Version) {
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);
@@ -361,7 +375,16 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
361375
// Throw away all stored priority levels, And mark that they all need to be recomputed.
362376
self.prioritized_potential_packages.clear();
363377
self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize;
364-
self.has_ever_backtracked = true;
378+
379+
// Update list of last valid contradicted decision levels
380+
self.last_valid_decision_levels
381+
.push(DecisionLevel(u32::MAX));
382+
383+
let index = self
384+
.last_valid_decision_levels
385+
.partition_point(|&l| l <= decision_level);
386+
387+
self.last_valid_decision_levels[index..].fill(decision_level);
365388
}
366389

367390
/// We can add the version to the partial solution as a decision
@@ -378,7 +401,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
378401
package_store: &PackageArena<DP::P>,
379402
dependency_provider: &DP,
380403
) {
381-
if !self.has_ever_backtracked {
404+
if self.last_valid_decision_levels.len() == 1 {
382405
// Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem.
383406
// So let's live with a little bit of risk and add the decision without checking the dependencies.
384407
// The worst that can happen is we will have to do a full backtrack which only removes this one decision.
@@ -537,10 +560,6 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
537560
.unwrap();
538561
decision_level.max(DecisionLevel(1))
539562
}
540-
541-
pub(crate) fn current_decision_level(&self) -> DecisionLevel {
542-
self.current_decision_level
543-
}
544563
}
545564

546565
impl<M: Clone + Debug + Display> PackageAssignments<M> {

0 commit comments

Comments
 (0)