// Copyright 2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::cmp::min; use super::composite::CompositeCommitIndex;use super::entry::GlobalCommitPosition; /// Unit of buffer allocation, which is 4kB.const PAGE_SIZE_IN_BITS: u32 = 4096 * 8;const PAGE_SIZE_IN_WORDS: u32 = PAGE_SIZE_IN_BITS / u64::BITS; /// Bit set of [`GlobalCommitPosition`]s.#[derive(Clone, Debug)]pub(super) struct PositionsBitSet { data: Vec<u64>, bitset_len: u32,} impl PositionsBitSet { /// Creates bit set of the specified capacity. pub fn with_capacity(len: u32) -> Self { Self { data: vec![], bitset_len: u32::div_ceil(len, u64::BITS), } } /// Creates bit set with the maximum position. pub fn with_max_pos(max_pos: GlobalCommitPosition) -> Self { assert_ne!(max_pos, GlobalCommitPosition::MAX); Self::with_capacity(max_pos.0 + 1) } fn ensure_data(&mut self, bitset_pos: u32) { if usize::try_from(bitset_pos).unwrap() < self.data.len() { return; } // Do not pre-allocate large buffer which might not be used. We could // manage buffer chunks as Vec<Vec<u64>>, but the extra indirection // appeared to make get() slow. let new_len = usize::try_from(min( u32::div_ceil(bitset_pos + 1, PAGE_SIZE_IN_WORDS) * PAGE_SIZE_IN_WORDS, self.bitset_len, )) .unwrap(); if self.data.is_empty() { self.data = vec![0; new_len]; // request zeroed page } else { self.data.resize(new_len, 0); // realloc + copy } } fn to_global_pos(&self, (bitset_pos, bit_pos): (u32, u32)) -> GlobalCommitPosition { let bitset_rev_pos = u32::checked_sub(self.bitset_len, bitset_pos + 1) .expect("bitset_pos < self.bitset_len"); GlobalCommitPosition(bitset_rev_pos * u64::BITS + bit_pos) } fn to_bitset_pos(&self, pos: GlobalCommitPosition) -> (u32, u32) { let bitset_rev_pos = pos.0 / u64::BITS; let bit_pos = pos.0 % u64::BITS; let bitset_pos = u32::checked_sub(self.bitset_len, bitset_rev_pos + 1) .expect("bitset_rev_pos < self.bitset_len"); (bitset_pos, bit_pos) } /// Returns `true` if the given `pos` is set. /// /// Panics if the `pos` exceeds the capacity. pub fn get(&self, pos: GlobalCommitPosition) -> bool { self.get_bit(self.to_bitset_pos(pos)) } fn get_bit(&self, (bitset_pos, bit_pos): (u32, u32)) -> bool { let bit = 1_u64 << bit_pos; match self.data.get(usize::try_from(bitset_pos).unwrap()) { Some(word) => *word & bit != 0, None => false, } } /// Sets `pos` to true. /// /// Panics if the `pos` exceeds the capacity. pub fn set(&mut self, pos: GlobalCommitPosition) { self.set_bit(self.to_bitset_pos(pos)); } fn set_bit(&mut self, (bitset_pos, bit_pos): (u32, u32)) { self.ensure_data(bitset_pos); let bit = 1_u64 << bit_pos; self.data[usize::try_from(bitset_pos).unwrap()] |= bit; } /// Sets `pos` to true. Returns `true` if the old value was set. /// /// Panics if the `pos` exceeds the capacity. pub fn get_set(&mut self, pos: GlobalCommitPosition) -> bool { self.get_set_bit(self.to_bitset_pos(pos)) } fn get_set_bit(&mut self, (bitset_pos, bit_pos): (u32, u32)) -> bool { self.ensure_data(bitset_pos); let bit = 1_u64 << bit_pos; let word = &mut self.data[usize::try_from(bitset_pos).unwrap()]; let old = *word & bit != 0; *word |= bit; old }} /// Computes ancestors set lazily.////// This is similar to `RevWalk` functionality-wise, but implemented with the/// different design goals:////// * optimized for dense ancestors set/// * optimized for testing set membership/// * no iterator API (which could be implemented on top)#[derive(Clone, Debug)]pub(super) struct AncestorsBitSet { bitset: PositionsBitSet, next_bitset_pos_to_visit: u32,} impl AncestorsBitSet { /// Creates bit set of the specified capacity. pub fn with_capacity(len: u32) -> Self { let bitset = PositionsBitSet::with_capacity(len); let next_bitset_pos_to_visit = bitset.bitset_len; Self { bitset, next_bitset_pos_to_visit, } } /// Adds head `pos` to the set. /// /// Panics if the `pos` exceeds the capacity. pub fn add_head(&mut self, pos: GlobalCommitPosition) { let (bitset_pos, bit_pos) = self.bitset.to_bitset_pos(pos); self.bitset.set_bit((bitset_pos, bit_pos)); self.next_bitset_pos_to_visit = min(self.next_bitset_pos_to_visit, bitset_pos); } /// Returns `true` if the given `pos` is ancestors of the heads. /// /// Panics if the `pos` exceeds the capacity or has not been visited yet. pub fn contains(&self, pos: GlobalCommitPosition) -> bool { let (bitset_pos, bit_pos) = self.bitset.to_bitset_pos(pos); assert!(bitset_pos < self.next_bitset_pos_to_visit); self.bitset.get_bit((bitset_pos, bit_pos)) } /// Updates set by visiting ancestors until the given `to_visit_pos`. pub fn visit_until( &mut self, index: &CompositeCommitIndex, to_visit_pos: GlobalCommitPosition, ) { let (last_bitset_pos_to_visit, _) = self.bitset.to_bitset_pos(to_visit_pos); if last_bitset_pos_to_visit < self.next_bitset_pos_to_visit { return; } self.bitset.ensure_data(last_bitset_pos_to_visit); for visiting_bitset_pos in self.next_bitset_pos_to_visit..=last_bitset_pos_to_visit { let mut unvisited_bits = self.bitset.data[usize::try_from(visiting_bitset_pos).unwrap()]; while unvisited_bits != 0 { let bit_pos = u64::BITS - unvisited_bits.leading_zeros() - 1; // from MSB unvisited_bits ^= 1_u64 << bit_pos; let current_pos = self.bitset.to_global_pos((visiting_bitset_pos, bit_pos)); for parent_pos in index.entry_by_pos(current_pos).parent_positions() { assert!(parent_pos < current_pos); let (parent_bitset_pos, parent_bit_pos) = self.bitset.to_bitset_pos(parent_pos); self.bitset.ensure_data(parent_bitset_pos); let bit = 1_u64 << parent_bit_pos; self.bitset.data[usize::try_from(parent_bitset_pos).unwrap()] |= bit; if visiting_bitset_pos == parent_bitset_pos { unvisited_bits |= bit; } } } } self.next_bitset_pos_to_visit = last_bitset_pos_to_visit + 1; }} #[cfg(test)]mod tests { use super::super::composite::AsCompositeIndex as _; use super::super::mutable::DefaultMutableIndex; use super::super::readonly::FieldLengths; use super::*; use crate::backend::ChangeId; use crate::backend::CommitId; use crate::tests::TestResult; /// Generator of unique 16-byte CommitId excluding root id fn commit_id_generator() -> impl FnMut() -> CommitId { let mut iter = (1_u128..).map(|n| CommitId::new(n.to_le_bytes().into())); move || iter.next().unwrap() } /// Generator of unique 16-byte ChangeId excluding root id fn change_id_generator() -> impl FnMut() -> ChangeId { let mut iter = (1_u128..).map(|n| ChangeId::new(n.to_le_bytes().into())); move || iter.next().unwrap() } #[test] fn test_positions_bit_set() { // Create with empty capacity, which is useless, but shouldn't panic let _set = PositionsBitSet::with_capacity(0); let mut set = PositionsBitSet::with_capacity(128); assert!(!set.get(GlobalCommitPosition(0))); assert!(!set.get(GlobalCommitPosition(127))); set.set(GlobalCommitPosition(0)); assert!(set.get(GlobalCommitPosition(0))); assert!(!set.get(GlobalCommitPosition(1))); assert!(!set.get(GlobalCommitPosition(127))); let old = set.get_set(GlobalCommitPosition(127)); assert!(!old); assert!(!set.get(GlobalCommitPosition(63))); assert!(!set.get(GlobalCommitPosition(64))); assert!(set.get(GlobalCommitPosition(127))); let old = set.get_set(GlobalCommitPosition(127)); assert!(old); } #[test] fn test_positions_bit_set_allocation() -> TestResult { let page_size_in_words = usize::try_from(PAGE_SIZE_IN_WORDS)?; // Exactly one page let mut set = PositionsBitSet::with_capacity(PAGE_SIZE_IN_BITS); assert!(set.data.is_empty()); assert!(!set.get(GlobalCommitPosition(0))); assert!(!set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1))); assert!(set.data.is_empty()); set.set(GlobalCommitPosition(0)); assert_eq!(set.data.len(), page_size_in_words); set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1)); assert_eq!(set.data.len(), page_size_in_words); assert!(set.get(GlobalCommitPosition(0))); assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1))); // Two pages let mut set = PositionsBitSet::with_capacity(PAGE_SIZE_IN_BITS + 1); assert!(set.data.is_empty()); set.set(GlobalCommitPosition(u64::BITS)); set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS)); assert_eq!(set.data.len(), page_size_in_words); let old = set.get_set(GlobalCommitPosition(u64::BITS - 1)); assert!(!old); assert_eq!(set.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS + 1)?); assert!(set.get(GlobalCommitPosition(u64::BITS - 1))); assert!(set.get(GlobalCommitPosition(u64::BITS))); assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS))); // Exactly three pages let mut set = PositionsBitSet::with_capacity(PAGE_SIZE_IN_BITS * 3); assert!(set.data.is_empty()); set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS * 2)); assert_eq!(set.data.len(), page_size_in_words); set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS)); assert_eq!(set.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS * 2)?); set.set(GlobalCommitPosition(0)); assert_eq!(set.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS * 3)?); assert!(set.get(GlobalCommitPosition(0))); assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS))); assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS * 2))); Ok(()) } #[test] fn test_ancestors_bit_set() { let mut new_commit_id = commit_id_generator(); let mut new_change_id = change_id_generator(); let mut mutable_index = DefaultMutableIndex::full(FieldLengths { commit_id: 16, change_id: 16, }); // F F = 256 // |\ E = 193,194,195,..,254 // E | D D = 192,255 // | |/ C = 66,68,70,..,190 // B C B = 65,67,69,..,189,191 // |/ A = 0,1,2,..,64 // A let id_a0 = new_commit_id(); mutable_index.add_commit_data(id_a0.clone(), new_change_id(), &[]); let id_a64 = (1..=64).fold(id_a0.clone(), |parent_id, i| { assert_eq!(mutable_index.num_commits(), i); let id = new_commit_id(); mutable_index.add_commit_data(id.clone(), new_change_id(), &[parent_id]); id }); let (id_b189, id_c190) = (65..=190).step_by(2).fold( (id_a64.clone(), id_a64.clone()), |(parent_id_b, parent_id_c), i| { assert_eq!(mutable_index.num_commits(), i); let id_b = new_commit_id(); let id_c = new_commit_id(); mutable_index.add_commit_data(id_b.clone(), new_change_id(), &[parent_id_b]); mutable_index.add_commit_data(id_c.clone(), new_change_id(), &[parent_id_c]); (id_b, id_c) }, ); let id_b191 = new_commit_id(); mutable_index.add_commit_data(id_b191.clone(), new_change_id(), &[id_b189]); let id_d192 = new_commit_id(); mutable_index.add_commit_data( id_d192.clone(), new_change_id(), std::slice::from_ref(&id_c190), ); let id_e254 = (193..=254).fold(id_b191.clone(), |parent_id, i| { assert_eq!(mutable_index.num_commits(), i); let id = new_commit_id(); mutable_index.add_commit_data(id.clone(), new_change_id(), &[parent_id]); id }); let id_d255 = new_commit_id(); mutable_index.add_commit_data( id_d255.clone(), new_change_id(), std::slice::from_ref(&id_d192), ); let id_f256 = new_commit_id(); mutable_index.add_commit_data( id_f256.clone(), new_change_id(), &[id_c190.clone(), id_e254.clone()], ); assert_eq!(mutable_index.num_commits(), 257); let index = mutable_index.as_composite().commits(); let to_pos = |id: &CommitId| index.commit_id_to_pos(id).unwrap(); let new_ancestors_set = |heads: &[&CommitId]| { let mut set = AncestorsBitSet::with_capacity(index.num_commits()); for &id in heads { set.add_head(to_pos(id)); } set }; // Nothing reachable let set = new_ancestors_set(&[]); assert_eq!(set.next_bitset_pos_to_visit, 5); for pos in (0..=256).map(GlobalCommitPosition) { assert!(!set.contains(pos), "{pos:?} should be unreachable"); } // All reachable let mut set = new_ancestors_set(&[&id_f256, &id_d255]); assert_eq!(set.next_bitset_pos_to_visit, 0); set.visit_until(index, to_pos(&id_f256)); assert_eq!(set.next_bitset_pos_to_visit, 1); assert!(set.contains(to_pos(&id_f256))); set.visit_until(index, to_pos(&id_d192)); assert_eq!(set.next_bitset_pos_to_visit, 2); assert!(set.contains(to_pos(&id_e254))); assert!(set.contains(to_pos(&id_d255))); assert!(set.contains(to_pos(&id_d192))); set.visit_until(index, to_pos(&id_a0)); assert_eq!(set.next_bitset_pos_to_visit, 5); set.visit_until(index, to_pos(&id_f256)); // should be noop assert_eq!(set.next_bitset_pos_to_visit, 5); for pos in (0..=256).map(GlobalCommitPosition) { assert!(set.contains(pos), "{pos:?} should be reachable"); } // A, B, C, E, F are reachable let mut set = new_ancestors_set(&[&id_f256]); assert_eq!(set.next_bitset_pos_to_visit, 0); set.visit_until(index, to_pos(&id_f256)); assert_eq!(set.next_bitset_pos_to_visit, 1); assert!(set.contains(to_pos(&id_f256))); set.visit_until(index, to_pos(&id_d192)); assert_eq!(set.next_bitset_pos_to_visit, 2); assert!(!set.contains(to_pos(&id_d255))); assert!(!set.contains(to_pos(&id_d192))); set.visit_until(index, to_pos(&id_c190)); assert_eq!(set.next_bitset_pos_to_visit, 3); assert!(set.contains(to_pos(&id_c190))); set.visit_until(index, to_pos(&id_a64)); assert_eq!(set.next_bitset_pos_to_visit, 4); assert!(set.contains(to_pos(&id_b191))); assert!(set.contains(to_pos(&id_a64))); set.visit_until(index, to_pos(&id_a0)); assert_eq!(set.next_bitset_pos_to_visit, 5); assert!(set.contains(to_pos(&id_a0))); // A, C, D are reachable let mut set = new_ancestors_set(&[&id_d255]); assert_eq!(set.next_bitset_pos_to_visit, 1); assert!(!set.contains(to_pos(&id_f256))); set.visit_until(index, to_pos(&id_e254)); assert_eq!(set.next_bitset_pos_to_visit, 2); assert!(!set.contains(to_pos(&id_e254))); set.visit_until(index, to_pos(&id_d255)); assert_eq!(set.next_bitset_pos_to_visit, 2); assert!(set.contains(to_pos(&id_d255))); set.visit_until(index, to_pos(&id_b191)); assert_eq!(set.next_bitset_pos_to_visit, 3); assert!(!set.contains(to_pos(&id_b191))); set.visit_until(index, to_pos(&id_c190)); assert_eq!(set.next_bitset_pos_to_visit, 3); assert!(set.contains(to_pos(&id_c190))); set.visit_until(index, to_pos(&id_a0)); assert_eq!(set.next_bitset_pos_to_visit, 5); assert!(set.contains(to_pos(&id_a64))); assert!(set.contains(to_pos(&id_a0))); } #[test] fn test_ancestors_bit_set_allocation() -> TestResult { let page_size_in_words = usize::try_from(PAGE_SIZE_IN_WORDS)?; let mut new_commit_id = commit_id_generator(); let mut new_change_id = change_id_generator(); let mut mutable_index = DefaultMutableIndex::full(FieldLengths { commit_id: 16, change_id: 16, }); // Linear history of two-page size let id_0 = new_commit_id(); mutable_index.add_commit_data(id_0.clone(), new_change_id(), &[]); (1..=PAGE_SIZE_IN_BITS).fold(id_0.clone(), |parent_id, i| { assert_eq!(mutable_index.num_commits(), i); let id = new_commit_id(); mutable_index.add_commit_data(id.clone(), new_change_id(), &[parent_id]); id }); let index = mutable_index.as_composite().commits(); let mut set = AncestorsBitSet::with_capacity(index.num_commits()); assert_eq!(set.next_bitset_pos_to_visit, PAGE_SIZE_IN_WORDS + 1); // Mark the head commit set.add_head(GlobalCommitPosition(PAGE_SIZE_IN_BITS)); assert_eq!(set.next_bitset_pos_to_visit, 0); assert_eq!(set.bitset.data.len(), page_size_in_words); set.visit_until(index, GlobalCommitPosition(PAGE_SIZE_IN_BITS)); assert_eq!(set.next_bitset_pos_to_visit, 1); assert_eq!(set.bitset.data.len(), page_size_in_words); assert!(set.contains(GlobalCommitPosition(PAGE_SIZE_IN_BITS))); set.visit_until(index, GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1)); assert_eq!(set.next_bitset_pos_to_visit, 2); assert_eq!(set.bitset.data.len(), page_size_in_words); assert!(set.contains(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1))); // Parent link across page boundary set.visit_until(index, GlobalCommitPosition(u64::BITS)); assert_eq!(set.next_bitset_pos_to_visit, PAGE_SIZE_IN_WORDS); assert_eq!( set.bitset.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS + 1)? ); assert!(set.contains(GlobalCommitPosition(u64::BITS))); set.visit_until(index, GlobalCommitPosition(0)); assert_eq!(set.next_bitset_pos_to_visit, PAGE_SIZE_IN_WORDS + 1); assert_eq!( set.bitset.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS + 1)? ); assert!(set.contains(GlobalCommitPosition(0))); Ok(()) }}
// Copyright 2023 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::cmp::min;
use super::composite::CompositeCommitIndex;
use super::entry::GlobalCommitPosition;
/// Unit of buffer allocation, which is 4kB.
const PAGE_SIZE_IN_BITS: u32 = 4096 * 8;
const PAGE_SIZE_IN_WORDS: u32 = PAGE_SIZE_IN_BITS / u64::BITS;
/// Bit set of [`GlobalCommitPosition`]s.
#[derive(Clone, Debug)]
pub(super) struct PositionsBitSet {
data: Vec<u64>,
bitset_len: u32,
}
impl PositionsBitSet {
/// Creates bit set of the specified capacity.
pub fn with_capacity(len: u32) -> Self {
Self {
data: vec![],
bitset_len: u32::div_ceil(len, u64::BITS),
}
}
/// Creates bit set with the maximum position.
pub fn with_max_pos(max_pos: GlobalCommitPosition) -> Self {
assert_ne!(max_pos, GlobalCommitPosition::MAX);
Self::with_capacity(max_pos.0 + 1)
}
fn ensure_data(&mut self, bitset_pos: u32) {
if usize::try_from(bitset_pos).unwrap() < self.data.len() {
return;
}
// Do not pre-allocate large buffer which might not be used. We could
// manage buffer chunks as Vec<Vec<u64>>, but the extra indirection
// appeared to make get() slow.
let new_len = usize::try_from(min(
u32::div_ceil(bitset_pos + 1, PAGE_SIZE_IN_WORDS) * PAGE_SIZE_IN_WORDS,
self.bitset_len,
))
.unwrap();
if self.data.is_empty() {
self.data = vec![0; new_len]; // request zeroed page
} else {
self.data.resize(new_len, 0); // realloc + copy
}
}
fn to_global_pos(&self, (bitset_pos, bit_pos): (u32, u32)) -> GlobalCommitPosition {
let bitset_rev_pos = u32::checked_sub(self.bitset_len, bitset_pos + 1)
.expect("bitset_pos < self.bitset_len");
GlobalCommitPosition(bitset_rev_pos * u64::BITS + bit_pos)
}
fn to_bitset_pos(&self, pos: GlobalCommitPosition) -> (u32, u32) {
let bitset_rev_pos = pos.0 / u64::BITS;
let bit_pos = pos.0 % u64::BITS;
let bitset_pos = u32::checked_sub(self.bitset_len, bitset_rev_pos + 1)
.expect("bitset_rev_pos < self.bitset_len");
(bitset_pos, bit_pos)
}
/// Returns `true` if the given `pos` is set.
///
/// Panics if the `pos` exceeds the capacity.
pub fn get(&self, pos: GlobalCommitPosition) -> bool {
self.get_bit(self.to_bitset_pos(pos))
}
fn get_bit(&self, (bitset_pos, bit_pos): (u32, u32)) -> bool {
let bit = 1_u64 << bit_pos;
match self.data.get(usize::try_from(bitset_pos).unwrap()) {
Some(word) => *word & bit != 0,
None => false,
}
}
/// Sets `pos` to true.
///
/// Panics if the `pos` exceeds the capacity.
pub fn set(&mut self, pos: GlobalCommitPosition) {
self.set_bit(self.to_bitset_pos(pos));
}
fn set_bit(&mut self, (bitset_pos, bit_pos): (u32, u32)) {
self.ensure_data(bitset_pos);
let bit = 1_u64 << bit_pos;
self.data[usize::try_from(bitset_pos).unwrap()] |= bit;
}
/// Sets `pos` to true. Returns `true` if the old value was set.
///
/// Panics if the `pos` exceeds the capacity.
pub fn get_set(&mut self, pos: GlobalCommitPosition) -> bool {
self.get_set_bit(self.to_bitset_pos(pos))
}
fn get_set_bit(&mut self, (bitset_pos, bit_pos): (u32, u32)) -> bool {
self.ensure_data(bitset_pos);
let bit = 1_u64 << bit_pos;
let word = &mut self.data[usize::try_from(bitset_pos).unwrap()];
let old = *word & bit != 0;
*word |= bit;
old
}
}
/// Computes ancestors set lazily.
///
/// This is similar to `RevWalk` functionality-wise, but implemented with the
/// different design goals:
///
/// * optimized for dense ancestors set
/// * optimized for testing set membership
/// * no iterator API (which could be implemented on top)
#[derive(Clone, Debug)]
pub(super) struct AncestorsBitSet {
bitset: PositionsBitSet,
next_bitset_pos_to_visit: u32,
}
impl AncestorsBitSet {
/// Creates bit set of the specified capacity.
pub fn with_capacity(len: u32) -> Self {
let bitset = PositionsBitSet::with_capacity(len);
let next_bitset_pos_to_visit = bitset.bitset_len;
Self {
bitset,
next_bitset_pos_to_visit,
}
}
/// Adds head `pos` to the set.
///
/// Panics if the `pos` exceeds the capacity.
pub fn add_head(&mut self, pos: GlobalCommitPosition) {
let (bitset_pos, bit_pos) = self.bitset.to_bitset_pos(pos);
self.bitset.set_bit((bitset_pos, bit_pos));
self.next_bitset_pos_to_visit = min(self.next_bitset_pos_to_visit, bitset_pos);
}
/// Returns `true` if the given `pos` is ancestors of the heads.
///
/// Panics if the `pos` exceeds the capacity or has not been visited yet.
pub fn contains(&self, pos: GlobalCommitPosition) -> bool {
let (bitset_pos, bit_pos) = self.bitset.to_bitset_pos(pos);
assert!(bitset_pos < self.next_bitset_pos_to_visit);
self.bitset.get_bit((bitset_pos, bit_pos))
}
/// Updates set by visiting ancestors until the given `to_visit_pos`.
pub fn visit_until(
&mut self,
index: &CompositeCommitIndex,
to_visit_pos: GlobalCommitPosition,
) {
let (last_bitset_pos_to_visit, _) = self.bitset.to_bitset_pos(to_visit_pos);
if last_bitset_pos_to_visit < self.next_bitset_pos_to_visit {
return;
}
self.bitset.ensure_data(last_bitset_pos_to_visit);
for visiting_bitset_pos in self.next_bitset_pos_to_visit..=last_bitset_pos_to_visit {
let mut unvisited_bits =
self.bitset.data[usize::try_from(visiting_bitset_pos).unwrap()];
while unvisited_bits != 0 {
let bit_pos = u64::BITS - unvisited_bits.leading_zeros() - 1; // from MSB
unvisited_bits ^= 1_u64 << bit_pos;
let current_pos = self.bitset.to_global_pos((visiting_bitset_pos, bit_pos));
for parent_pos in index.entry_by_pos(current_pos).parent_positions() {
assert!(parent_pos < current_pos);
let (parent_bitset_pos, parent_bit_pos) = self.bitset.to_bitset_pos(parent_pos);
self.bitset.ensure_data(parent_bitset_pos);
let bit = 1_u64 << parent_bit_pos;
self.bitset.data[usize::try_from(parent_bitset_pos).unwrap()] |= bit;
if visiting_bitset_pos == parent_bitset_pos {
unvisited_bits |= bit;
}
}
}
}
self.next_bitset_pos_to_visit = last_bitset_pos_to_visit + 1;
}
}
#[cfg(test)]
mod tests {
use super::super::composite::AsCompositeIndex as _;
use super::super::mutable::DefaultMutableIndex;
use super::super::readonly::FieldLengths;
use super::*;
use crate::backend::ChangeId;
use crate::backend::CommitId;
use crate::tests::TestResult;
/// Generator of unique 16-byte CommitId excluding root id
fn commit_id_generator() -> impl FnMut() -> CommitId {
let mut iter = (1_u128..).map(|n| CommitId::new(n.to_le_bytes().into()));
move || iter.next().unwrap()
}
/// Generator of unique 16-byte ChangeId excluding root id
fn change_id_generator() -> impl FnMut() -> ChangeId {
let mut iter = (1_u128..).map(|n| ChangeId::new(n.to_le_bytes().into()));
move || iter.next().unwrap()
}
#[test]
fn test_positions_bit_set() {
// Create with empty capacity, which is useless, but shouldn't panic
let _set = PositionsBitSet::with_capacity(0);
let mut set = PositionsBitSet::with_capacity(128);
assert!(!set.get(GlobalCommitPosition(0)));
assert!(!set.get(GlobalCommitPosition(127)));
set.set(GlobalCommitPosition(0));
assert!(set.get(GlobalCommitPosition(0)));
assert!(!set.get(GlobalCommitPosition(1)));
assert!(!set.get(GlobalCommitPosition(127)));
let old = set.get_set(GlobalCommitPosition(127));
assert!(!old);
assert!(!set.get(GlobalCommitPosition(63)));
assert!(!set.get(GlobalCommitPosition(64)));
assert!(set.get(GlobalCommitPosition(127)));
let old = set.get_set(GlobalCommitPosition(127));
assert!(old);
}
#[test]
fn test_positions_bit_set_allocation() -> TestResult {
let page_size_in_words = usize::try_from(PAGE_SIZE_IN_WORDS)?;
// Exactly one page
let mut set = PositionsBitSet::with_capacity(PAGE_SIZE_IN_BITS);
assert!(set.data.is_empty());
assert!(!set.get(GlobalCommitPosition(0)));
assert!(!set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1)));
assert!(set.data.is_empty());
set.set(GlobalCommitPosition(0));
assert_eq!(set.data.len(), page_size_in_words);
set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1));
assert_eq!(set.data.len(), page_size_in_words);
assert!(set.get(GlobalCommitPosition(0)));
assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1)));
// Two pages
let mut set = PositionsBitSet::with_capacity(PAGE_SIZE_IN_BITS + 1);
assert!(set.data.is_empty());
set.set(GlobalCommitPosition(u64::BITS));
set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS));
assert_eq!(set.data.len(), page_size_in_words);
let old = set.get_set(GlobalCommitPosition(u64::BITS - 1));
assert!(!old);
assert_eq!(set.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS + 1)?);
assert!(set.get(GlobalCommitPosition(u64::BITS - 1)));
assert!(set.get(GlobalCommitPosition(u64::BITS)));
assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS)));
// Exactly three pages
let mut set = PositionsBitSet::with_capacity(PAGE_SIZE_IN_BITS * 3);
assert!(set.data.is_empty());
set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS * 2));
assert_eq!(set.data.len(), page_size_in_words);
set.set(GlobalCommitPosition(PAGE_SIZE_IN_BITS));
assert_eq!(set.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS * 2)?);
set.set(GlobalCommitPosition(0));
assert_eq!(set.data.len(), usize::try_from(PAGE_SIZE_IN_WORDS * 3)?);
assert!(set.get(GlobalCommitPosition(0)));
assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS)));
assert!(set.get(GlobalCommitPosition(PAGE_SIZE_IN_BITS * 2)));
Ok(())
}
#[test]
fn test_ancestors_bit_set() {
let mut new_commit_id = commit_id_generator();
let mut new_change_id = change_id_generator();
let mut mutable_index = DefaultMutableIndex::full(FieldLengths {
commit_id: 16,
change_id: 16,
});
// F F = 256
// |\ E = 193,194,195,..,254
// E | D D = 192,255
// | |/ C = 66,68,70,..,190
// B C B = 65,67,69,..,189,191
// |/ A = 0,1,2,..,64
// A
let id_a0 = new_commit_id();
mutable_index.add_commit_data(id_a0.clone(), new_change_id(), &[]);
let id_a64 = (1..=64).fold(id_a0.clone(), |parent_id, i| {
assert_eq!(mutable_index.num_commits(), i);
let id = new_commit_id();
mutable_index.add_commit_data(id.clone(), new_change_id(), &[parent_id]);
id
});
let (id_b189, id_c190) = (65..=190).step_by(2).fold(
(id_a64.clone(), id_a64.clone()),
|(parent_id_b, parent_id_c), i| {
assert_eq!(mutable_index.num_commits(), i);
let id_b = new_commit_id();
let id_c = new_commit_id();
mutable_index.add_commit_data(id_b.clone(), new_change_id(), &[parent_id_b]);
mutable_index.add_commit_data(id_c.clone(), new_change_id(), &[parent_id_c]);
(id_b, id_c)
},
);
let id_b191 = new_commit_id();
mutable_index.add_commit_data(id_b191.clone(), new_change_id(), &[id_b189]);
let id_d192 = new_commit_id();
mutable_index.add_commit_data(
id_d192.clone(),
new_change_id(),
std::slice::from_ref(&id_c190),
);
let id_e254 = (193..=254).fold(id_b191.clone(), |parent_id, i| {
assert_eq!(mutable_index.num_commits(), i);
let id = new_commit_id();
mutable_index.add_commit_data(id.clone(), new_change_id(), &[parent_id]);
id
});
let id_d255 = new_commit_id();
mutable_index.add_commit_data(
id_d255.clone(),
new_change_id(),
std::slice::from_ref(&id_d192),
);
let id_f256 = new_commit_id();
mutable_index.add_commit_data(
id_f256.clone(),
new_change_id(),
&[id_c190.clone(), id_e254.clone()],
);
assert_eq!(mutable_index.num_commits(), 257);
let index = mutable_index.as_composite().commits();
let to_pos = |id: &CommitId| index.commit_id_to_pos(id).unwrap();
let new_ancestors_set = |heads: &[&CommitId]| {
let mut set = AncestorsBitSet::with_capacity(index.num_commits());
for &id in heads {
set.add_head(to_pos(id));
}
set
};
// Nothing reachable
let set = new_ancestors_set(&[]);
assert_eq!(set.next_bitset_pos_to_visit, 5);
for pos in (0..=256).map(GlobalCommitPosition) {
assert!(!set.contains(pos), "{pos:?} should be unreachable");
}
// All reachable
let mut set = new_ancestors_set(&[&id_f256, &id_d255]);
assert_eq!(set.next_bitset_pos_to_visit, 0);
set.visit_until(index, to_pos(&id_f256));
assert_eq!(set.next_bitset_pos_to_visit, 1);
assert!(set.contains(to_pos(&id_f256)));
set.visit_until(index, to_pos(&id_d192));
assert_eq!(set.next_bitset_pos_to_visit, 2);
assert!(set.contains(to_pos(&id_e254)));
assert!(set.contains(to_pos(&id_d255)));
assert!(set.contains(to_pos(&id_d192)));
set.visit_until(index, to_pos(&id_a0));
assert_eq!(set.next_bitset_pos_to_visit, 5);
set.visit_until(index, to_pos(&id_f256)); // should be noop
assert_eq!(set.next_bitset_pos_to_visit, 5);
for pos in (0..=256).map(GlobalCommitPosition) {
assert!(set.contains(pos), "{pos:?} should be reachable");
}
// A, B, C, E, F are reachable
let mut set = new_ancestors_set(&[&id_f256]);
assert_eq!(set.next_bitset_pos_to_visit, 0);
set.visit_until(index, to_pos(&id_f256));
assert_eq!(set.next_bitset_pos_to_visit, 1);
assert!(set.contains(to_pos(&id_f256)));
set.visit_until(index, to_pos(&id_d192));
assert_eq!(set.next_bitset_pos_to_visit, 2);
assert!(!set.contains(to_pos(&id_d255)));
assert!(!set.contains(to_pos(&id_d192)));
set.visit_until(index, to_pos(&id_c190));
assert_eq!(set.next_bitset_pos_to_visit, 3);
assert!(set.contains(to_pos(&id_c190)));
set.visit_until(index, to_pos(&id_a64));
assert_eq!(set.next_bitset_pos_to_visit, 4);
assert!(set.contains(to_pos(&id_b191)));
assert!(set.contains(to_pos(&id_a64)));
set.visit_until(index, to_pos(&id_a0));
assert_eq!(set.next_bitset_pos_to_visit, 5);
assert!(set.contains(to_pos(&id_a0)));
// A, C, D are reachable
let mut set = new_ancestors_set(&[&id_d255]);
assert_eq!(set.next_bitset_pos_to_visit, 1);
assert!(!set.contains(to_pos(&id_f256)));
set.visit_until(index, to_pos(&id_e254));
assert_eq!(set.next_bitset_pos_to_visit, 2);
assert!(!set.contains(to_pos(&id_e254)));
set.visit_until(index, to_pos(&id_d255));
assert_eq!(set.next_bitset_pos_to_visit, 2);
assert!(set.contains(to_pos(&id_d255)));
set.visit_until(index, to_pos(&id_b191));
assert_eq!(set.next_bitset_pos_to_visit, 3);
assert!(!set.contains(to_pos(&id_b191)));
set.visit_until(index, to_pos(&id_c190));
assert_eq!(set.next_bitset_pos_to_visit, 3);
assert!(set.contains(to_pos(&id_c190)));
set.visit_until(index, to_pos(&id_a0));
assert_eq!(set.next_bitset_pos_to_visit, 5);
assert!(set.contains(to_pos(&id_a64)));
assert!(set.contains(to_pos(&id_a0)));
}
#[test]
fn test_ancestors_bit_set_allocation() -> TestResult {
let page_size_in_words = usize::try_from(PAGE_SIZE_IN_WORDS)?;
let mut new_commit_id = commit_id_generator();
let mut new_change_id = change_id_generator();
let mut mutable_index = DefaultMutableIndex::full(FieldLengths {
commit_id: 16,
change_id: 16,
});
// Linear history of two-page size
let id_0 = new_commit_id();
mutable_index.add_commit_data(id_0.clone(), new_change_id(), &[]);
(1..=PAGE_SIZE_IN_BITS).fold(id_0.clone(), |parent_id, i| {
assert_eq!(mutable_index.num_commits(), i);
let id = new_commit_id();
mutable_index.add_commit_data(id.clone(), new_change_id(), &[parent_id]);
id
});
let index = mutable_index.as_composite().commits();
let mut set = AncestorsBitSet::with_capacity(index.num_commits());
assert_eq!(set.next_bitset_pos_to_visit, PAGE_SIZE_IN_WORDS + 1);
// Mark the head commit
set.add_head(GlobalCommitPosition(PAGE_SIZE_IN_BITS));
assert_eq!(set.next_bitset_pos_to_visit, 0);
assert_eq!(set.bitset.data.len(), page_size_in_words);
set.visit_until(index, GlobalCommitPosition(PAGE_SIZE_IN_BITS));
assert_eq!(set.next_bitset_pos_to_visit, 1);
assert_eq!(set.bitset.data.len(), page_size_in_words);
assert!(set.contains(GlobalCommitPosition(PAGE_SIZE_IN_BITS)));
set.visit_until(index, GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1));
assert_eq!(set.next_bitset_pos_to_visit, 2);
assert_eq!(set.bitset.data.len(), page_size_in_words);
assert!(set.contains(GlobalCommitPosition(PAGE_SIZE_IN_BITS - 1)));
// Parent link across page boundary
set.visit_until(index, GlobalCommitPosition(u64::BITS));
assert_eq!(set.next_bitset_pos_to_visit, PAGE_SIZE_IN_WORDS);
assert_eq!(
set.bitset.data.len(),
usize::try_from(PAGE_SIZE_IN_WORDS + 1)?
);
assert!(set.contains(GlobalCommitPosition(u64::BITS)));
set.visit_until(index, GlobalCommitPosition(0));
assert_eq!(set.next_bitset_pos_to_visit, PAGE_SIZE_IN_WORDS + 1);
assert_eq!(
set.bitset.data.len(),
usize::try_from(PAGE_SIZE_IN_WORDS + 1)?
);
assert!(set.contains(GlobalCommitPosition(0)));
Ok(())
}
}