//! Virtual registers.
//!
//! A virtual register is a set of related SSA values whose live ranges don't interfere. If all the
//! values in a virtual register are assigned to the same location, fewer copies will result in the
//! output.
//!
//! A virtual register is typically built by merging together SSA values that are "phi-related" -
//! that is, one value is passed as a block argument to a branch and the other is the block parameter
//! value itself.
//!
//! If any values in a virtual register are spilled, they will use the same stack slot. This avoids
//! memory-to-memory copies when a spilled value is passed as a block argument.

use crate::dbg::DisplayList;
use crate::dominator_tree::DominatorTreePreorder;
use crate::entity::entity_impl;
use crate::entity::{EntityList, ListPool};
use crate::entity::{Keys, PrimaryMap, SecondaryMap};
use crate::ir::{Function, Value};
use crate::packed_option::PackedOption;
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::fmt;
use core::slice;
use smallvec::SmallVec;

/// A virtual register reference.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct VirtReg(u32);
entity_impl!(VirtReg, "vreg");

type ValueList = EntityList<Value>;

/// Collection of virtual registers.
///
/// Each virtual register is a list of values. Also maintain a map from values to their unique
/// virtual register, if any.
pub struct VirtRegs {
    /// Memory pool for the value lists.
    pool: ListPool<Value>,

    /// The primary table of virtual registers.
    vregs: PrimaryMap<VirtReg, ValueList>,

    /// Allocated virtual register numbers that are no longer in use.
    unused_vregs: Vec<VirtReg>,

    /// Each value belongs to at most one virtual register.
    value_vregs: SecondaryMap<Value, PackedOption<VirtReg>>,

    /// Table used during the union-find phase while `vregs` is empty.
    union_find: SecondaryMap<Value, i32>,

    /// Values that have been activated in the `union_find` table, but not yet added to any virtual
    /// registers by the `finish_union_find()` function.
    pending_values: Vec<Value>,
}

impl VirtRegs {
    /// Create a new virtual register collection.
    pub fn new() -> Self {
        Self {
            pool: ListPool::new(),
            vregs: PrimaryMap::new(),
            unused_vregs: Vec::new(),
            value_vregs: SecondaryMap::new(),
            union_find: SecondaryMap::new(),
            pending_values: Vec::new(),
        }
    }

    /// Clear all virtual registers.
    pub fn clear(&mut self) {
        self.vregs.clear();
        self.unused_vregs.clear();
        self.value_vregs.clear();
        self.pool.clear();
        self.union_find.clear();
        self.pending_values.clear();
    }

    /// Get the virtual register containing `value`, if any.
    pub fn get(&self, value: Value) -> Option<VirtReg> {
        self.value_vregs[value].into()
    }

    /// Get the list of values in `vreg`.
    pub fn values(&self, vreg: VirtReg) -> &[Value] {
        self.vregs[vreg].as_slice(&self.pool)
    }

    /// Get an iterator over all virtual registers.
    pub fn all_virtregs(&self) -> Keys<VirtReg> {
        self.vregs.keys()
    }

    /// Get the congruence class of `value`.
    ///
    /// If `value` belongs to a virtual register, the congruence class is the values of the virtual
    /// register. Otherwise it is just the value itself.
    #[cfg_attr(feature = "cargo-clippy", allow(clippy::trivially_copy_pass_by_ref))]
    pub fn congruence_class<'a, 'b>(&'a self, value: &'b Value) -> &'b [Value]
    where
        'a: 'b,
    {
        self.get(*value)
            .map_or_else(|| slice::from_ref(value), |vr| self.values(vr))
    }

    /// Check if `a` and `b` belong to the same congruence class.
    pub fn same_class(&self, a: Value, b: Value) -> bool {
        match (self.get(a), self.get(b)) {
            (Some(va), Some(vb)) => va == vb,
            _ => a == b,
        }
    }

    /// Sort the values in `vreg` according to the dominator tree pre-order.
    ///
    /// Returns the slice of sorted values which `values(vreg)` will also return from now on.
    pub fn sort_values(
        &mut self,
        vreg: VirtReg,
        func: &Function,
        preorder: &DominatorTreePreorder,
    ) -> &[Value] {
        let s = self.vregs[vreg].as_mut_slice(&mut self.pool);
        s.sort_unstable_by(|&a, &b| preorder.pre_cmp_def(a, b, func));
        s
    }

    /// Insert a single value into a sorted virtual register.
    ///
    /// It is assumed that the virtual register containing `big` is already sorted by
    /// `sort_values()`, and that `single` does not already belong to a virtual register.
    ///
    /// If `big` is not part of a virtual register, one will be created.
    pub fn insert_single(
        &mut self,
        big: Value,
        single: Value,
        func: &Function,
        preorder: &DominatorTreePreorder,
    ) -> VirtReg {
        debug_assert_eq!(self.get(single), None, "Expected singleton {}", single);

        // Make sure `big` has a vreg.
        let vreg = self.get(big).unwrap_or_else(|| {
            let vr = self.alloc();
            self.vregs[vr].push(big, &mut self.pool);
            self.value_vregs[big] = vr.into();
            vr
        });

        // Determine the insertion position for `single`.
        let index = match self
            .values(vreg)
            .binary_search_by(|&v| preorder.pre_cmp_def(v, single, func))
        {
            Ok(_) => panic!("{} already in {}", single, vreg),
            Err(i) => i,
        };
        self.vregs[vreg].insert(index, single, &mut self.pool);
        self.value_vregs[single] = vreg.into();
        vreg
    }

    /// Remove a virtual register.
    ///
    /// The values in `vreg` become singletons, and the virtual register number may be reused in
    /// the future.
    pub fn remove(&mut self, vreg: VirtReg) {
        // Start by reassigning all the values.
        for &v in self.vregs[vreg].as_slice(&self.pool) {
            let old = self.value_vregs[v].take();
            debug_assert_eq!(old, Some(vreg));
        }

        self.vregs[vreg].clear(&mut self.pool);
        self.unused_vregs.push(vreg);
    }

    /// Allocate a new empty virtual register.
    fn alloc(&mut self) -> VirtReg {
        self.unused_vregs
            .pop()
            .unwrap_or_else(|| self.vregs.push(Default::default()))
    }

    /// Unify `values` into a single virtual register.
    ///
    /// The values in the slice can be singletons or they can belong to a virtual register already.
    /// If a value belongs to a virtual register, all of the values in that register must be
    /// present.
    ///
    /// The values are assumed to already be in topological order.
    pub fn unify(&mut self, values: &[Value]) -> VirtReg {
        // Start by clearing all virtual registers involved.
        let mut singletons = 0;
        let mut cleared = 0;
        for &val in values {
            match self.get(val) {
                None => singletons += 1,
                Some(vreg) => {
                    if !self.vregs[vreg].is_empty() {
                        cleared += self.vregs[vreg].len(&self.pool);
                        self.vregs[vreg].clear(&mut self.pool);
                        self.unused_vregs.push(vreg);
                    }
                }
            }
        }

        debug_assert_eq!(
            values.len(),
            singletons + cleared,
            "Can't unify partial virtual registers"
        );

        let vreg = self.alloc();
        self.vregs[vreg].extend(values.iter().cloned(), &mut self.pool);
        for &v in values {
            self.value_vregs[v] = vreg.into();
        }

        vreg
    }
}

impl fmt::Display for VirtRegs {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for vreg in self.all_virtregs() {
            write!(f, "\n{} = {}", vreg, DisplayList(self.values(vreg)))?;
        }
        Ok(())
    }
}

/// Expanded version of a union-find table entry.
enum UFEntry {
    /// This value is a a set leader. The embedded number is the set's rank.
    Rank(u32),

    /// This value belongs to the same set as the linked value.
    Link(Value),
}

/// The `union_find` table contains `i32` entries that are interpreted as follows:
///
/// x = 0: The value belongs to its own singleton set.
/// x > 0: The value is the leader of a set with rank x.
/// x < 0: The value belongs to the same set as the value numbered !x.
///
/// The rank of a set is an upper bound on the number of links that must be followed from a member
/// of the set to the set leader.
///
/// A singleton set is the same as a set with rank 0. It contains only the leader value.
impl UFEntry {
    /// Decode a table entry.
    fn decode(x: i32) -> Self {
        if x < 0 {
            Self::Link(Value::from_u32((!x) as u32))
        } else {
            Self::Rank(x as u32)
        }
    }

    /// Encode a link entry.
    fn encode_link(v: Value) -> i32 {
        !(v.as_u32() as i32)
    }
}

/// Union-find algorithm for building virtual registers.
///
/// Before values are added to virtual registers, it is possible to use a union-find algorithm to
/// construct virtual registers efficiently. This support implemented here is used as follows:
///
/// 1. Repeatedly call the `union(a, b)` method to request that `a` and `b` are placed in the same
///    virtual register.
/// 2. When done, call `finish_union_find()` to construct the virtual register sets based on the
///    `union()` calls.
///
/// The values that were passed to `union(a, b)` must not belong to any existing virtual registers
/// by the time `finish_union_find()` is called.
///
/// For more information on the algorithm implemented here, see Chapter 21 "Data Structures for
/// Disjoint Sets" of Cormen, Leiserson, Rivest, Stein, "Introduction to algorithms", 3rd Ed.
///
/// The [Wikipedia entry on disjoint-set data
/// structures](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) is also good.
impl VirtRegs {
    /// Find the leader value and rank of the set containing `v`.
    /// Compress the path if needed.
    fn find(&mut self, mut val: Value) -> (Value, u32) {
        let mut val_stack = SmallVec::<[Value; 8]>::new();
        let found = loop {
            match UFEntry::decode(self.union_find[val]) {
                UFEntry::Rank(rank) => break (val, rank),
                UFEntry::Link(parent) => {
                    val_stack.push(val);
                    val = parent;
                }
            }
        };
        // Compress the path
        while let Some(val) = val_stack.pop() {
            self.union_find[val] = UFEntry::encode_link(found.0);
        }
        found
    }

    /// Union the two sets containing `a` and `b`.
    ///
    /// This ensures that `a` and `b` will belong to the same virtual register after calling
    /// `finish_union_find()`.
    pub fn union(&mut self, a: Value, b: Value) {
        let (leader_a, rank_a) = self.find(a);
        let (leader_b, rank_b) = self.find(b);

        if leader_a == leader_b {
            return;
        }

        // The first time we see a value, its rank will be 0. Add it to the list of pending values.
        if rank_a == 0 {
            debug_assert_eq!(a, leader_a);
            self.pending_values.push(a);
        }
        if rank_b == 0 {
            debug_assert_eq!(b, leader_b);
            self.pending_values.push(b);
        }

        // Merge into the set with the greater rank. This preserves the invariant that the rank is
        // an upper bound on the number of links to the leader.
        match rank_a.cmp(&rank_b) {
            Ordering::Less => {
                self.union_find[leader_a] = UFEntry::encode_link(leader_b);
            }
            Ordering::Greater => {
                self.union_find[leader_b] = UFEntry::encode_link(leader_a);
            }
            Ordering::Equal => {
                // When the two sets have the same rank, we arbitrarily pick the a-set to preserve.
                // We need to increase the rank by one since the elements in the b-set are now one
                // link further away from the leader.
                self.union_find[leader_a] += 1;
                self.union_find[leader_b] = UFEntry::encode_link(leader_a);
            }
        }
    }

    /// Compute virtual registers based on previous calls to `union(a, b)`.
    ///
    /// This terminates the union-find algorithm, so the next time `union()` is called, it is for a
    /// new independent batch of values.
    ///
    /// The values in each virtual register will be ordered according to when they were first
    /// passed to `union()`, but backwards. It is expected that `sort_values()` will be used to
    /// create a more sensible value order.
    ///
    /// The new virtual registers will be appended to `new_vregs`, if present.
    pub fn finish_union_find(&mut self, mut new_vregs: Option<&mut Vec<VirtReg>>) {
        debug_assert_eq!(
            self.pending_values.iter().find(|&&v| self.get(v).is_some()),
            None,
            "Values participating in union-find must not belong to existing virtual registers"
        );

        while let Some(val) = self.pending_values.pop() {
            let (leader, _) = self.find(val);

            // Get the vreg for `leader`, or create it.
            let vreg = self.get(leader).unwrap_or_else(|| {
                // Allocate a vreg for `leader`, but leave it empty.
                let vr = self.alloc();
                if let Some(ref mut vec) = new_vregs {
                    vec.push(vr);
                }
                self.value_vregs[leader] = vr.into();
                vr
            });

            // Push values in `pending_values` order, including when `v == leader`.
            self.vregs[vreg].push(val, &mut self.pool);
            self.value_vregs[val] = vreg.into();

            // Clear the entry in the union-find table. The `find(val)` call may still look at this
            // entry in a future iteration, but that it ok. It will return a rank 0 leader that has
            // already been assigned to the correct virtual register.
            self.union_find[val] = 0;
        }

        // We do *not* call `union_find.clear()` table here because re-initializing the table for
        // sparse use takes time linear in the number of values in the function. Instead we reset
        // the entries that are known to be non-zero in the loop above.
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::entity::EntityRef;
    use crate::ir::Value;

    #[test]
    fn empty_union_find() {
        let mut vregs = VirtRegs::new();
        vregs.finish_union_find(None);
        assert_eq!(vregs.all_virtregs().count(), 0);
    }

    #[test]
    fn union_self() {
        let mut vregs = VirtRegs::new();
        let v1 = Value::new(1);
        vregs.union(v1, v1);
        vregs.finish_union_find(None);
        assert_eq!(vregs.get(v1), None);
        assert_eq!(vregs.all_virtregs().count(), 0);
    }

    #[test]
    fn union_pair() {
        let mut vregs = VirtRegs::new();
        let v1 = Value::new(1);
        let v2 = Value::new(2);
        vregs.union(v1, v2);
        vregs.finish_union_find(None);
        assert_eq!(vregs.congruence_class(&v1), &[v2, v1]);
        assert_eq!(vregs.congruence_class(&v2), &[v2, v1]);
        assert_eq!(vregs.all_virtregs().count(), 1);
    }

    #[test]
    fn union_pair_backwards() {
        let mut vregs = VirtRegs::new();
        let v1 = Value::new(1);
        let v2 = Value::new(2);
        vregs.union(v2, v1);
        vregs.finish_union_find(None);
        assert_eq!(vregs.congruence_class(&v1), &[v1, v2]);
        assert_eq!(vregs.congruence_class(&v2), &[v1, v2]);
        assert_eq!(vregs.all_virtregs().count(), 1);
    }

    #[test]
    fn union_tree() {
        let mut vregs = VirtRegs::new();
        let v1 = Value::new(1);
        let v2 = Value::new(2);
        let v3 = Value::new(3);
        let v4 = Value::new(4);

        vregs.union(v2, v4);
        vregs.union(v3, v1);
        // Leaders: v2, v3
        vregs.union(v4, v1);
        vregs.finish_union_find(None);
        assert_eq!(vregs.congruence_class(&v1), &[v1, v3, v4, v2]);
        assert_eq!(vregs.congruence_class(&v2), &[v1, v3, v4, v2]);
        assert_eq!(vregs.congruence_class(&v3), &[v1, v3, v4, v2]);
        assert_eq!(vregs.congruence_class(&v4), &[v1, v3, v4, v2]);
        assert_eq!(vregs.all_virtregs().count(), 1);
    }

    #[test]
    fn union_two() {
        let mut vregs = VirtRegs::new();
        let v1 = Value::new(1);
        let v2 = Value::new(2);
        let v3 = Value::new(3);
        let v4 = Value::new(4);

        vregs.union(v2, v4);
        vregs.union(v3, v1);
        // Leaders: v2, v3
        vregs.finish_union_find(None);
        assert_eq!(vregs.congruence_class(&v1), &[v1, v3]);
        assert_eq!(vregs.congruence_class(&v2), &[v4, v2]);
        assert_eq!(vregs.congruence_class(&v3), &[v1, v3]);
        assert_eq!(vregs.congruence_class(&v4), &[v4, v2]);
        assert_eq!(vregs.all_virtregs().count(), 2);
    }

    #[test]
    fn union_uneven() {
        let mut vregs = VirtRegs::new();
        let v1 = Value::new(1);
        let v2 = Value::new(2);
        let v3 = Value::new(3);
        let v4 = Value::new(4);

        vregs.union(v2, v4); // Rank 0-0
        vregs.union(v3, v2); // Rank 0-1
        vregs.union(v2, v1); // Rank 1-0
        vregs.finish_union_find(None);
        assert_eq!(vregs.congruence_class(&v1), &[v1, v3, v4, v2]);
        assert_eq!(vregs.congruence_class(&v2), &[v1, v3, v4, v2]);
        assert_eq!(vregs.congruence_class(&v3), &[v1, v3, v4, v2]);
        assert_eq!(vregs.congruence_class(&v4), &[v1, v3, v4, v2]);
        assert_eq!(vregs.all_virtregs().count(), 1);
    }
}
