487 lines · 19.3 KB · Rust
RawDownload
// 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(())    }}