Skip to content

Commit 6bd554f

Browse files
committed
pre calculate how far back to back-jump
1 parent 680a4db commit 6bd554f

File tree

3 files changed

+81
-45
lines changed

3 files changed

+81
-45
lines changed

src/cargo/core/resolver/conflict_cache.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ impl ConflictStoreTrie {
2929
if must_contain.is_none() {
3030
// `is_conflicting` checks that all the elements are active,
3131
// but we have checked each one by the recursion of this function.
32-
debug_assert!(cx.is_conflicting(None, c));
32+
debug_assert!(cx.is_conflicting(None, c).is_some());
3333
Some(c)
3434
} else {
3535
// We did not find `must_contain`, so we need to keep looking.
@@ -42,7 +42,7 @@ impl ConflictStoreTrie {
4242
.unwrap_or_else(|| m.range(..))
4343
{
4444
// If the key is active, then we need to check all of the corresponding subtrie.
45-
if cx.is_active(pid) {
45+
if cx.is_active(pid).is_some() {
4646
if let Some(o) =
4747
store.find_conflicting(cx, must_contain.filter(|&f| f != pid))
4848
{

src/cargo/core/resolver/context.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ pub struct Context {
5454
}
5555

5656
/// find the activated version of a crate based on the name, source, and semver compatibility
57-
pub type Activations = im_rc::HashMap<(InternedString, SourceId, SemverCompatibility), Summary>;
57+
/// This all so stores the size of `Activations` when that version was add as an "age".
58+
/// This is used to speed up backtracking.
59+
pub type Activations =
60+
im_rc::HashMap<(InternedString, SourceId, SemverCompatibility), (Summary, usize)>;
5861

5962
/// A type that represents when cargo treats two Versions as compatible.
6063
/// Versions `a` and `b` are compatible if their left-most nonzero digit is the
@@ -107,10 +110,11 @@ impl Context {
107110
/// Returns `true` if this summary with the given method is already activated.
108111
pub fn flag_activated(&mut self, summary: &Summary, method: &Method<'_>) -> CargoResult<bool> {
109112
let id = summary.package_id();
113+
let activations_len = self.activations.len();
110114
match self.activations.entry(id.as_activations_key()) {
111115
im_rc::hashmap::Entry::Occupied(o) => {
112116
debug_assert_eq!(
113-
o.get(),
117+
&o.get().0,
114118
summary,
115119
"cargo does not allow two semver compatible versions"
116120
);
@@ -125,7 +129,7 @@ impl Context {
125129
&*link
126130
);
127131
}
128-
v.insert(summary.clone());
132+
v.insert((summary.clone(), activations_len));
129133
return Ok(false);
130134
}
131135
}
@@ -183,24 +187,30 @@ impl Context {
183187
Ok(deps)
184188
}
185189

186-
pub fn is_active(&self, id: PackageId) -> bool {
190+
/// If the package is active returns the "age" (len of activations) when it was added
191+
pub fn is_active(&self, id: PackageId) -> Option<usize> {
187192
self.activations
188193
.get(&id.as_activations_key())
189-
.map(|s| s.package_id() == id)
190-
.unwrap_or(false)
194+
.and_then(|(s, l)| if s.package_id() == id { Some(*l) } else { None })
191195
}
192196

193197
/// Checks whether all of `parent` and the keys of `conflicting activations`
194198
/// are still active.
199+
/// If so returns the "age" (len of activations) when the newest one was added.
195200
pub fn is_conflicting(
196201
&self,
197202
parent: Option<PackageId>,
198203
conflicting_activations: &ConflictMap,
199-
) -> bool {
200-
conflicting_activations
201-
.keys()
202-
.chain(parent.as_ref())
203-
.all(|&id| self.is_active(id))
204+
) -> Option<usize> {
205+
let mut max = 0;
206+
for &id in conflicting_activations.keys().chain(parent.as_ref()) {
207+
if let Some(age) = self.is_active(id) {
208+
max = std::cmp::max(max, age);
209+
} else {
210+
return None;
211+
}
212+
}
213+
Some(max)
204214
}
205215

206216
/// Returns all dependencies and the features we want from them.

src/cargo/core/resolver/mod.rs

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ pub fn resolve(
137137
let cx = activate_deps_loop(cx, &mut registry, summaries, config)?;
138138

139139
let mut cksums = HashMap::new();
140-
for summary in cx.activations.values() {
140+
for (summary, _) in cx.activations.values() {
141141
let cksum = summary.checksum().map(|s| s.to_string());
142142
cksums.insert(summary.package_id(), cksum);
143143
}
@@ -302,6 +302,7 @@ fn activate_deps_loop(
302302
}
303303

304304
match find_candidate(
305+
&cx,
305306
&mut backtrack_stack,
306307
&parent,
307308
backtracked,
@@ -493,6 +494,7 @@ fn activate_deps_loop(
493494
let activate_for_error_message = has_past_conflicting_dep && !has_another && {
494495
just_here_for_the_error_messages || {
495496
find_candidate(
497+
&cx,
496498
&mut backtrack_stack.clone(),
497499
&parent,
498500
backtracked,
@@ -777,7 +779,7 @@ impl RemainingCandidates {
777779
//
778780
// Here we throw out our candidate if it's *compatible*, yet not
779781
// equal, to all previously activated versions.
780-
if let Some(a) = cx.activations.get(&b_id.as_activations_key()) {
782+
if let Some((a, _)) = cx.activations.get(&b_id.as_activations_key()) {
781783
if *a != b.summary {
782784
conflicting_prev_active
783785
.entry(a.package_id())
@@ -850,11 +852,33 @@ impl RemainingCandidates {
850852
/// Read <https://github.com/rust-lang/cargo/pull/4834>
851853
/// For several more detailed explanations of the logic here.
852854
fn find_candidate(
855+
cx: &Context,
853856
backtrack_stack: &mut Vec<BacktrackFrame>,
854857
parent: &Summary,
855858
backtracked: bool,
856859
conflicting_activations: &ConflictMap,
857860
) -> Option<(Candidate, bool, BacktrackFrame)> {
861+
// When we're calling this method we know that `parent` failed to
862+
// activate. That means that some dependency failed to get resolved for
863+
// whatever reason. Normally, that means that all of those reasons
864+
// (plus maybe some extras) are listed in `conflicting_activations`.
865+
//
866+
// The abnormal situations are things that do not put all of the reasons in `conflicting_activations`:
867+
// If we backtracked we do not know how our `conflicting_activations` related to
868+
// the cause of that backtrack, so we do not update it.
869+
// If we had a PublicDependency conflict, then we do not yet have a compact way to
870+
// represent all the parts of the problem, so `conflicting_activations` is incomplete.
871+
let age = if !backtracked
872+
&& !conflicting_activations
873+
.values()
874+
.any(|c| *c == ConflictReason::PublicDependency)
875+
{
876+
// we dont have abnormal situations. So we can ask `cx` for how far back we need to go.
877+
cx.is_conflicting(Some(parent.package_id()), conflicting_activations)
878+
} else {
879+
None
880+
};
881+
858882
while let Some(mut frame) = backtrack_stack.pop() {
859883
let next = frame.remaining_candidates.next(
860884
&mut frame.conflicting_activations,
@@ -866,37 +890,37 @@ fn find_candidate(
866890
Some(pair) => pair,
867891
None => continue,
868892
};
869-
// When we're calling this method we know that `parent` failed to
870-
// activate. That means that some dependency failed to get resolved for
871-
// whatever reason. Normally, that means that all of those reasons
872-
// (plus maybe some extras) are listed in `conflicting_activations`.
873-
//
874-
// This means that if all members of `conflicting_activations` are still
893+
894+
// If all members of `conflicting_activations` are still
875895
// active in this back up we know that we're guaranteed to not actually
876896
// make any progress. As a result if we hit this condition we can
877897
// completely skip this backtrack frame and move on to the next.
878-
//
879-
// The abnormal situations are things that do not put all of the reasons in `conflicting_activations`:
880-
// If we backtracked we do not know how our `conflicting_activations` related to
881-
// the cause of that backtrack, so we do not update it.
882-
// If we had a PublicDependency conflict, then we do not yet have a compact way to
883-
// represent all the parts of the problem, so `conflicting_activations` is incomplete.
884-
if !backtracked
885-
&& !conflicting_activations
886-
.values()
887-
.any(|c| *c == ConflictReason::PublicDependency)
888-
&& frame
889-
.context
890-
.is_conflicting(Some(parent.package_id()), conflicting_activations)
891-
{
892-
trace!(
893-
"{} = \"{}\" skip as not solving {}: {:?}",
894-
frame.dep.package_name(),
895-
frame.dep.version_req(),
896-
parent.package_id(),
897-
conflicting_activations
898-
);
899-
continue;
898+
if let Some(age) = age {
899+
if frame.context.activations.len() > age {
900+
trace!(
901+
"{} = \"{}\" skip as not solving {}: {:?}",
902+
frame.dep.package_name(),
903+
frame.dep.version_req(),
904+
parent.package_id(),
905+
conflicting_activations
906+
);
907+
// above we use `cx` to determine that this is still going to be conflicting.
908+
// but lets just double check.
909+
debug_assert!(
910+
frame
911+
.context
912+
.is_conflicting(Some(parent.package_id()), conflicting_activations)
913+
== Some(age)
914+
);
915+
continue;
916+
} else {
917+
// above we use `cx` to determine that this is not going to be conflicting.
918+
// but lets just double check.
919+
debug_assert!(frame
920+
.context
921+
.is_conflicting(Some(parent.package_id()), conflicting_activations)
922+
.is_none());
923+
}
900924
}
901925

902926
return Some((candidate, has_another, frame));
@@ -905,8 +929,10 @@ fn find_candidate(
905929
}
906930

907931
fn check_cycles(resolve: &Resolve, activations: &Activations) -> CargoResult<()> {
908-
let summaries: HashMap<PackageId, &Summary> =
909-
activations.values().map(|s| (s.package_id(), s)).collect();
932+
let summaries: HashMap<PackageId, &Summary> = activations
933+
.values()
934+
.map(|(s, _)| (s.package_id(), s))
935+
.collect();
910936

911937
// Sort packages to produce user friendly deterministic errors.
912938
let mut all_packages: Vec<_> = resolve.iter().collect();

0 commit comments

Comments
 (0)