Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 0 additions & 40 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ itertools = "0.15"
petgraph = "0.8"
tracing = "0.1.41"
elsa = "1.11.2"
bitvec = "1.0.1"
serde = { version = "1.0", features = ["derive"], optional = true }
futures = { version = "0.3", default-features = false, features = ["alloc", "async-await"] }
event-listener = "5.4"
Expand Down
29 changes: 20 additions & 9 deletions src/solver/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,15 +390,26 @@ impl<'a, 'cache, D: DependencyProvider> Encoder<'a, 'cache, D> {
//
// We only add these clauses for packages that can actually be selected to
// reduce the overall number of clauses.
for (solvable, variable_id) in candidates
.iter()
.zip(version_set_variables.iter())
.flat_map(|(&candidates, variable)| {
candidates.iter().copied().zip(variable.iter().copied())
})
{
let name_id = self.cache.provider().solvable_name(solvable);
self.register_forbid_target(name_id, variable_id);
for (&candidates, variables) in candidates.iter().zip(version_set_variables.iter()) {
let Some(&first_solvable) = candidates.first() else {
continue;
};
let name_id = self.cache.provider().solvable_name(first_solvable);
debug_assert!(
candidates
.iter()
.all(|&solvable| self.cache.provider().solvable_name(solvable) == name_id),
"all candidates in a version set must have the same package name"
);
if self.state.allow_multiple_names.contains(name_id) {
continue;
}
let pending = self.pending_forbid_clauses.entry(name_id).or_default();
for &variable_id in variables {
if self.forbid_seen.insert(variable_id) {
pending.push(variable_id);
}
}
}

// Queue requesting the dependencies of the candidates as well if they are
Expand Down
96 changes: 85 additions & 11 deletions src/utils/indexed_set.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use std::marker::PhantomData;

use bitvec::vec::BitVec;

use crate::id::DenseIndex;

const WORD_BITS: usize = usize::BITS as usize;

/// A dense set keyed by a [`DenseIndex`]. Equivalent to a `HashSet<Id>` but
/// backed by a [`BitVec`], so test-and-set is O(1) with no hashing overhead.
/// Grows on demand to fit the largest inserted index.
/// backed by native machine words, so test-and-set is O(1) with no hashing
/// overhead. Grows on demand to fit the largest inserted index.
pub struct IndexedSet<Id> {
bits: BitVec,
words: Vec<usize>,
_marker: PhantomData<fn(Id) -> Id>,
}

impl<Id> Default for IndexedSet<Id> {
fn default() -> Self {
Self {
bits: BitVec::new(),
words: Vec::new(),
_marker: PhantomData,
}
}
Expand All @@ -26,16 +26,90 @@ impl<Id: DenseIndex> IndexedSet<Id> {
#[inline]
pub fn insert(&mut self, id: Id) -> bool {
let idx = id.to_index();
if idx >= self.bits.len() {
self.bits.resize(idx + 1, false);
let (word, bit) = (idx / WORD_BITS, 1usize << (idx % WORD_BITS));
if word >= self.words.len() {
self.words.resize(word + 1, 0);
}
// SAFETY: `resize` above guarantees `idx < self.bits.len()`.
!unsafe { self.bits.replace_unchecked(idx, true) }
let entry = &mut self.words[word];
let was_set = *entry & bit != 0;
*entry |= bit;
!was_set
}

/// Returns `true` if `id` is present.
#[inline]
pub fn contains(&self, id: Id) -> bool {
self.bits.get(id.to_index()).is_some_and(|b| *b)
let idx = id.to_index();
self.words
.get(idx / WORD_BITS)
.is_some_and(|word| word & (1usize << (idx % WORD_BITS)) != 0)
}
}

#[cfg(test)]
mod tests {
use super::{IndexedSet, WORD_BITS};
use crate::{DenseIndex, NameId};

fn id(index: usize) -> NameId {
NameId::from_index(index)
}

#[test]
fn word_boundaries() {
let mut set = IndexedSet::<NameId>::default();
for index in [
0,
WORD_BITS - 1,
WORD_BITS,
WORD_BITS + 1,
WORD_BITS * 2 - 1,
WORD_BITS * 2,
] {
assert!(set.insert(id(index)));
assert!(set.contains(id(index)));
assert!(!set.insert(id(index)));
}
for index in [1, WORD_BITS - 2, WORD_BITS + 2, WORD_BITS * 2 + 1] {
assert!(!set.contains(id(index)));
}
}

#[test]
fn sparse_and_large_indices() {
let mut set = IndexedSet::<NameId>::default();
assert!(!set.contains(id(10_000)));
assert!(set.insert(id(10_000)));
assert!(set.contains(id(10_000)));
assert!(!set.contains(id(9_999)));
assert!(!set.contains(id(10_001)));

assert!(set.insert(id(0)));
assert!(set.contains(id(0)));
assert!(set.contains(id(10_000)));
assert!(!set.contains(id(WORD_BITS - 1)));
assert!(!set.contains(id(WORD_BITS)));
assert!(!set.contains(id(WORD_BITS + 1)));
}

#[test]
fn duplicate_inserts_and_sequential_fill() {
let mut set = IndexedSet::<NameId>::default();
for index in 0..=WORD_BITS * 32 {
assert!(set.insert(id(index)));
}
for index in 0..=WORD_BITS * 32 {
assert!(!set.insert(id(index)));
assert!(set.contains(id(index)));
}
assert!(!set.contains(id(WORD_BITS * 32 + 1)));

let mut reverse = IndexedSet::<NameId>::default();
for index in (0..=WORD_BITS * 4).rev() {
assert!(reverse.insert(id(index)));
}
for index in 0..=WORD_BITS * 4 {
assert!(reverse.contains(id(index)));
}
}
}
Loading