Skip to content

Commit 4ac6c42

Browse files
authored
Create an arena for package names (#242)
1 parent 4bde6d6 commit 4ac6c42

File tree

7 files changed

+294
-221
lines changed

7 files changed

+294
-221
lines changed

src/internal/arena.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::hash::{Hash, Hasher};
33
use std::marker::PhantomData;
44
use std::ops::{Index, Range};
55

6+
type FnvIndexSet<V> = indexmap::IndexSet<V, rustc_hash::FxBuildHasher>;
7+
68
/// The index of a value allocated in an arena that holds `T`s.
79
///
810
/// The Clone, Copy and other traits are defined manually because
@@ -124,3 +126,44 @@ impl<T> Index<Range<Id<T>>> for Arena<T> {
124126
&self.data[(id.start.raw as usize)..(id.end.raw as usize)]
125127
}
126128
}
129+
130+
/// Yet another index-based arena. This one de-duplicates entries by hashing.
131+
///
132+
/// An arena is a kind of simple grow-only allocator, backed by a `Vec`
133+
/// where all items have the same lifetime, making it easier
134+
/// to have references between those items.
135+
/// In this case the `Vec` is inside a `IndexSet` allowing fast lookup by value not just index.
136+
/// They are all dropped at once when the arena is dropped.
137+
#[derive(Clone, PartialEq, Eq)]
138+
pub struct HashArena<T: Hash + Eq> {
139+
data: FnvIndexSet<T>,
140+
}
141+
142+
impl<T: Hash + Eq + fmt::Debug> fmt::Debug for HashArena<T> {
143+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
144+
fmt.debug_struct("Arena")
145+
.field("len", &self.data.len())
146+
.field("data", &self.data)
147+
.finish()
148+
}
149+
}
150+
151+
impl<T: Hash + Eq> HashArena<T> {
152+
pub fn new() -> Self {
153+
HashArena {
154+
data: FnvIndexSet::default(),
155+
}
156+
}
157+
158+
pub fn alloc(&mut self, value: T) -> Id<T> {
159+
let (raw, _) = self.data.insert_full(value);
160+
Id::from(raw as u32)
161+
}
162+
}
163+
164+
impl<T: Hash + Eq> Index<Id<T>> for HashArena<T> {
165+
type Output = T;
166+
fn index(&self, id: Id<T>) -> &T {
167+
&self.data[id.raw as usize]
168+
}
169+
}

src/internal/core.rs

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ use std::collections::HashSet as Set;
77
use std::sync::Arc;
88

99
use crate::internal::{
10-
Arena, DecisionLevel, IncompDpId, Incompatibility, PartialSolution, Relation, SatisfierSearch,
11-
SmallVec,
10+
Arena, DecisionLevel, HashArena, Id, IncompDpId, Incompatibility, PartialSolution, Relation,
11+
SatisfierSearch, SmallVec,
1212
};
1313
use crate::{DependencyProvider, DerivationTree, Map, NoSolutionError, VersionSet};
1414

1515
/// Current state of the PubGrub algorithm.
1616
#[derive(Clone)]
1717
pub(crate) struct State<DP: DependencyProvider> {
18-
root_package: DP::P,
18+
pub root_package: Id<DP::P>,
1919
root_version: DP::V,
2020

2121
#[allow(clippy::type_complexity)]
22-
incompatibilities: Map<DP::P, Vec<IncompDpId<DP>>>,
22+
incompatibilities: Map<Id<DP::P>, Vec<IncompDpId<DP>>>,
2323

2424
/// Store the ids of incompatibilities that are already contradicted.
2525
/// For each one keep track of the decision level when it was found to be contradicted.
@@ -29,7 +29,7 @@ pub(crate) struct State<DP: DependencyProvider> {
2929
/// All incompatibilities expressing dependencies,
3030
/// with common dependents merged.
3131
#[allow(clippy::type_complexity)]
32-
merged_dependencies: Map<(DP::P, DP::P), SmallVec<IncompDpId<DP>>>,
32+
merged_dependencies: Map<(Id<DP::P>, Id<DP::P>), SmallVec<IncompDpId<DP>>>,
3333

3434
/// Partial solution.
3535
/// TODO: remove pub.
@@ -38,29 +38,35 @@ pub(crate) struct State<DP: DependencyProvider> {
3838
/// The store is the reference storage for all incompatibilities.
3939
pub(crate) incompatibility_store: Arena<Incompatibility<DP::P, DP::VS, DP::M>>,
4040

41+
/// The store is the reference storage for all packages.
42+
pub(crate) package_store: HashArena<DP::P>,
43+
4144
/// This is a stack of work to be done in `unit_propagation`.
4245
/// It can definitely be a local variable to that method, but
4346
/// this way we can reuse the same allocation for better performance.
44-
unit_propagation_buffer: SmallVec<DP::P>,
47+
unit_propagation_buffer: SmallVec<Id<DP::P>>,
4548
}
4649

4750
impl<DP: DependencyProvider> State<DP> {
4851
/// Initialization of PubGrub state.
4952
pub(crate) fn init(root_package: DP::P, root_version: DP::V) -> Self {
5053
let mut incompatibility_store = Arena::new();
54+
let mut package_store = HashArena::new();
55+
let root_package = package_store.alloc(root_package);
5156
let not_root_id = incompatibility_store.alloc(Incompatibility::not_root(
52-
root_package.clone(),
57+
root_package,
5358
root_version.clone(),
5459
));
5560
let mut incompatibilities = Map::default();
56-
incompatibilities.insert(root_package.clone(), vec![not_root_id]);
61+
incompatibilities.insert(root_package, vec![not_root_id]);
5762
Self {
5863
root_package,
5964
root_version,
6065
incompatibilities,
6166
contradicted_incompatibilities: Map::default(),
6267
partial_solution: PartialSolution::empty(),
6368
incompatibility_store,
69+
package_store,
6470
unit_propagation_buffer: SmallVec::Empty,
6571
merged_dependencies: Map::default(),
6672
}
@@ -75,18 +81,19 @@ impl<DP: DependencyProvider> State<DP> {
7581
/// Add an incompatibility to the state.
7682
pub(crate) fn add_incompatibility_from_dependencies(
7783
&mut self,
78-
package: DP::P,
84+
package: Id<DP::P>,
7985
version: DP::V,
8086
deps: impl IntoIterator<Item = (DP::P, DP::VS)>,
8187
) -> std::ops::Range<IncompDpId<DP>> {
8288
// Create incompatibilities and allocate them in the store.
8389
let new_incompats_id_range =
8490
self.incompatibility_store
85-
.alloc_iter(deps.into_iter().map(|dep| {
91+
.alloc_iter(deps.into_iter().map(|(dep_p, dep_vs)| {
92+
let dep_pid = self.package_store.alloc(dep_p);
8693
Incompatibility::from_dependency(
87-
package.clone(),
94+
package,
8895
<DP::VS as VersionSet>::singleton(version.clone()),
89-
dep,
96+
(dep_pid, dep_vs),
9097
)
9198
}));
9299
// Merge the newly created incompatibilities with the older ones.
@@ -98,7 +105,10 @@ impl<DP: DependencyProvider> State<DP> {
98105

99106
/// Unit propagation is the core mechanism of the solving algorithm.
100107
/// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
101-
pub(crate) fn unit_propagation(&mut self, package: DP::P) -> Result<(), NoSolutionError<DP>> {
108+
pub(crate) fn unit_propagation(
109+
&mut self,
110+
package: Id<DP::P>,
111+
) -> Result<(), NoSolutionError<DP>> {
102112
self.unit_propagation_buffer.clear();
103113
self.unit_propagation_buffer.push(package);
104114
while let Some(current_package) = self.unit_propagation_buffer.pop() {
@@ -120,7 +130,7 @@ impl<DP: DependencyProvider> State<DP> {
120130
Relation::Satisfied => {
121131
log::info!(
122132
"Start conflict resolution because incompat satisfied:\n {}",
123-
current_incompat
133+
current_incompat.display(&self.package_store)
124134
);
125135
conflict_id = Some(incompat_id);
126136
break;
@@ -131,7 +141,7 @@ impl<DP: DependencyProvider> State<DP> {
131141
// but so does allocating a hash map and hashing each item.
132142
// In practice `unit_propagation_buffer` is small enough that we can just do a linear scan.
133143
if !self.unit_propagation_buffer.contains(&package_almost) {
134-
self.unit_propagation_buffer.push(package_almost.clone());
144+
self.unit_propagation_buffer.push(package_almost);
135145
}
136146
// Add (not term) to the partial solution with incompat as cause.
137147
self.partial_solution.add_derivation(
@@ -157,7 +167,7 @@ impl<DP: DependencyProvider> State<DP> {
157167
self.build_derivation_tree(terminal_incompat_id)
158168
})?;
159169
self.unit_propagation_buffer.clear();
160-
self.unit_propagation_buffer.push(package_almost.clone());
170+
self.unit_propagation_buffer.push(package_almost);
161171
// Add to the partial solution with incompat as cause.
162172
self.partial_solution.add_derivation(
163173
package_almost,
@@ -180,12 +190,12 @@ impl<DP: DependencyProvider> State<DP> {
180190
fn conflict_resolution(
181191
&mut self,
182192
incompatibility: IncompDpId<DP>,
183-
) -> Result<(DP::P, IncompDpId<DP>), IncompDpId<DP>> {
193+
) -> Result<(Id<DP::P>, IncompDpId<DP>), IncompDpId<DP>> {
184194
let mut current_incompat_id = incompatibility;
185195
let mut current_incompat_changed = false;
186196
loop {
187197
if self.incompatibility_store[current_incompat_id]
188-
.is_terminal(&self.root_package, &self.root_version)
198+
.is_terminal(self.root_package, &self.root_version)
189199
{
190200
return Err(current_incompat_id);
191201
} else {
@@ -197,7 +207,6 @@ impl<DP: DependencyProvider> State<DP> {
197207
SatisfierSearch::DifferentDecisionLevels {
198208
previous_satisfier_level,
199209
} => {
200-
let package = package.clone();
201210
self.backtrack(
202211
current_incompat_id,
203212
current_incompat_changed,
@@ -213,7 +222,7 @@ impl<DP: DependencyProvider> State<DP> {
213222
package,
214223
&self.incompatibility_store,
215224
);
216-
log::info!("prior cause: {}", prior_cause);
225+
log::info!("prior cause: {}", prior_cause.display(&self.package_store));
217226
current_incompat_id = self.incompatibility_store.alloc(prior_cause);
218227
current_incompat_changed = true;
219228
}
@@ -256,19 +265,16 @@ impl<DP: DependencyProvider> State<DP> {
256265
fn merge_incompatibility(&mut self, mut id: IncompDpId<DP>) {
257266
if let Some((p1, p2)) = self.incompatibility_store[id].as_dependency() {
258267
// If we are a dependency, there's a good chance we can be merged with a previous dependency
259-
let deps_lookup = self
260-
.merged_dependencies
261-
.entry((p1.clone(), p2.clone()))
262-
.or_default();
268+
let deps_lookup = self.merged_dependencies.entry((p1, p2)).or_default();
263269
if let Some((past, merged)) = deps_lookup.as_mut_slice().iter_mut().find_map(|past| {
264270
self.incompatibility_store[id]
265271
.merge_dependents(&self.incompatibility_store[*past])
266272
.map(|m| (past, m))
267273
}) {
268274
let new = self.incompatibility_store.alloc(merged);
269-
for (pkg, _) in self.incompatibility_store[new].iter() {
275+
for (&pkg, _) in self.incompatibility_store[new].iter() {
270276
self.incompatibilities
271-
.entry(pkg.clone())
277+
.entry(pkg)
272278
.or_default()
273279
.retain(|id| id != past);
274280
}
@@ -278,14 +284,11 @@ impl<DP: DependencyProvider> State<DP> {
278284
deps_lookup.push(id);
279285
}
280286
}
281-
for (pkg, term) in self.incompatibility_store[id].iter() {
287+
for (&pkg, term) in self.incompatibility_store[id].iter() {
282288
if cfg!(debug_assertions) {
283289
assert_ne!(term, &crate::term::Term::any());
284290
}
285-
self.incompatibilities
286-
.entry(pkg.clone())
287-
.or_default()
288-
.push(id);
291+
self.incompatibilities.entry(pkg).or_default().push(id);
289292
}
290293
}
291294

@@ -320,6 +323,7 @@ impl<DP: DependencyProvider> State<DP> {
320323
id,
321324
&shared_ids,
322325
&self.incompatibility_store,
326+
&self.package_store,
323327
&precomputed,
324328
);
325329
precomputed.insert(id, Arc::new(tree));

0 commit comments

Comments
 (0)