use alloc::{rc::Rc, vec::Vec};
use core::{
cell::{Cell, RefCell},
fmt,
num::NonZeroU32,
};
use smallvec::{SmallVec, smallvec};
use super::{BatchUpdateInfo, SemiNCA};
use crate::{
BlockRef, EntityId, EntityWithId, RegionRef,
cfg::{self, Graph, Inverse, InvertibleGraph},
formatter::DisplayOptional,
};
#[derive(Debug, thiserror::Error)]
pub enum DomTreeError {
#[error("unable to create dominance tree for empty region")]
EmptyRegion,
}
pub enum DomTreeVerificationLevel {
Fast,
Basic,
Full,
}
pub type DominanceTree = DomTreeBase<false>;
pub type PostDominanceTree = DomTreeBase<true>;
pub type DomTreeRoots = SmallVec<[Option<BlockRef>; 4]>;
pub struct DomTreeBase<const IS_POST_DOM: bool> {
roots: DomTreeRoots,
#[allow(clippy::type_complexity)]
nodes: SmallVec<[Option<(Option<BlockRef>, Rc<DomTreeNode>)>; 64]>,
root: Option<Rc<DomTreeNode>>,
parent: RegionRef,
valid: Cell<bool>,
slow_queries: Cell<u32>,
}
pub struct DomTreeNode {
block: Option<BlockRef>,
idom: Cell<Option<Rc<DomTreeNode>>>,
children: RefCell<SmallVec<[Rc<DomTreeNode>; 4]>>,
level: Cell<u32>,
num_in: Cell<Option<NonZeroU32>>,
num_out: Cell<Option<NonZeroU32>>,
}
impl fmt::Display for DomTreeNode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", DisplayOptional(self.block.as_ref().map(|b| b.borrow().id()).as_ref()))
}
}
impl fmt::Debug for DomTreeNode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use crate::EntityWithId;
f.debug_struct("DomTreeNode")
.field_with("block", |f| match self.block.as_ref() {
None => f.write_str("None"),
Some(block_ref) => write!(f, "{}", block_ref.borrow().id()),
})
.field("idom", &unsafe { &*self.idom.as_ptr() }.as_ref().map(|n| n.block))
.field_with("children", |f| {
f.debug_list()
.entries(self.children.borrow().iter().map(|child| child.block))
.finish()
})
.field("level", &self.level.get())
.field("num_in", &self.num_in.get())
.field("num_out", &self.num_out.get())
.finish()
}
}
impl<const IS_POST_DOM: bool> fmt::Debug for DomTreeBase<IS_POST_DOM> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = f.debug_struct("DomTreeBase");
builder
.field("valid", &self.valid.get())
.field("slow_queries", &self.slow_queries.get())
.field_with("root", |f| match self.root.as_ref().and_then(|root| root.block) {
Some(root) => write!(f, "{root}"),
None => f.write_str("<virtual>"),
});
if IS_POST_DOM {
builder.field_with("roots", |f| {
let mut builder = f.debug_set();
for root in self.roots.iter() {
builder.entry_with(|f| match root {
Some(root) => write!(f, "{root}"),
None => f.write_str("<virtual>"),
});
}
builder.finish()
});
}
builder.field_with("nodes", |f| {
f.debug_set()
.entries(self.nodes.iter().filter_map(|node| node.as_ref().map(|(_, n)| n.clone())))
.finish()
});
builder.finish()
}
}
pub type PreOrderDomTreeIter = cfg::PreOrderIter<Rc<DomTreeNode>>;
pub type PostOrderDomTreeIter = cfg::PostOrderIter<Rc<DomTreeNode>>;
impl Graph for Rc<DomTreeNode> {
type ChildEdgeIter = DomTreeSuccessorIter;
type ChildIter = DomTreeSuccessorIter;
type Edge = Rc<DomTreeNode>;
type Node = Rc<DomTreeNode>;
fn size(&self) -> usize {
self.children.borrow().len()
}
fn children(parent: Self::Node) -> Self::ChildIter {
DomTreeSuccessorIter::new(parent)
}
fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter {
DomTreeSuccessorIter::new(parent)
}
fn edge_dest(edge: Self::Edge) -> Self::Node {
edge
}
fn entry_node(&self) -> Self::Node {
Rc::clone(self)
}
}
pub struct DomTreeSuccessorIter {
node: Rc<DomTreeNode>,
num_children: usize,
index: usize,
}
impl DomTreeSuccessorIter {
pub fn new(node: Rc<DomTreeNode>) -> Self {
let num_children = node.num_children();
Self {
node,
num_children,
index: 0,
}
}
}
impl core::iter::FusedIterator for DomTreeSuccessorIter {}
impl ExactSizeIterator for DomTreeSuccessorIter {
#[inline]
fn len(&self) -> usize {
self.num_children.saturating_sub(self.index)
}
#[inline]
fn is_empty(&self) -> bool {
self.index >= self.num_children
}
}
impl Iterator for DomTreeSuccessorIter {
type Item = Rc<DomTreeNode>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.num_children {
return None;
}
let index = self.index;
self.index += 1;
Some(self.node.children.borrow()[index].clone())
}
}
impl DoubleEndedIterator for DomTreeSuccessorIter {
fn next_back(&mut self) -> Option<Self::Item> {
if self.index >= self.num_children {
return None;
}
self.num_children -= 1;
let index = self.num_children;
Some(self.node.children.borrow()[index].clone())
}
}
impl DomTreeNode {
pub fn new(block: Option<BlockRef>, idom: Option<Rc<DomTreeNode>>) -> Self {
let this = Self {
block,
idom: Cell::new(None),
children: Default::default(),
level: Cell::new(0),
num_in: Cell::new(None),
num_out: Cell::new(None),
};
if let Some(idom) = idom {
this.with_idom(idom)
} else {
this
}
}
pub fn with_idom(self, idom: Rc<Self>) -> Self {
self.level.set(idom.level.get() + 1);
self.idom.set(Some(idom));
self
}
pub fn block(&self) -> Option<BlockRef> {
self.block
}
pub fn idom(&self) -> Option<Rc<Self>> {
unsafe { &*self.idom.as_ptr() }.clone()
}
pub(super) fn set_idom(self: Rc<Self>, new_idom: Rc<Self>) {
let idom = self.idom.take().expect("no immediate dominator?");
if idom == new_idom {
self.idom.set(Some(idom));
return;
}
{
let mut children = idom.children.borrow_mut();
let child_index = children
.iter()
.position(|n| Rc::ptr_eq(n, &self))
.expect("not in immediate dominator children!");
children.remove(child_index);
}
{
let mut children = new_idom.children.borrow_mut();
children.push(Rc::clone(&self));
}
self.idom.set(Some(new_idom));
self.update_level();
}
#[inline(always)]
pub fn level(&self) -> u32 {
self.level.get()
}
pub fn is_leaf(&self) -> bool {
self.children.borrow().is_empty()
}
pub fn num_children(&self) -> usize {
self.children.borrow().len()
}
pub fn add_child(&self, child: Rc<DomTreeNode>) {
self.children.borrow_mut().push(child);
}
pub fn clear_children(&self) {
self.children.borrow_mut().clear();
}
pub fn is_dominated_by(&self, other: &Self) -> bool {
let num_in = self.num_in.get().expect("you forgot to call update_dfs_numbers").get();
let other_num_in = other.num_in.get().expect("you forgot to call update_dfs_numbers").get();
let num_out = self.num_out.get().unwrap().get();
let other_num_out = other.num_out.get().unwrap().get();
num_in >= other_num_in && num_out <= other_num_out
}
fn update_level(self: Rc<Self>) {
let idom_level = self.idom().expect("expected to have an immediate dominator").level();
if self.level() == idom_level + 1 {
return;
}
let mut stack = SmallVec::<[Rc<DomTreeNode>; 64]>::from_iter([self.clone()]);
while let Some(current) = stack.pop() {
current.level.set(current.idom().unwrap().level() + 1);
for child in current.children.borrow().iter() {
assert!(child.idom().is_some());
if child.level() != child.idom().unwrap().level() + 1 {
stack.push(Rc::clone(child));
}
}
}
}
}
impl Eq for DomTreeNode {}
impl PartialEq for DomTreeNode {
fn eq(&self, other: &Self) -> bool {
self.block == other.block
}
}
impl DomTreeBase<false> {
#[inline]
pub fn root(&self) -> BlockRef {
self.roots[0].unwrap()
}
pub fn preorder(&self) -> Vec<Rc<DomTreeNode>> {
let mut nodes = self
.nodes
.iter()
.filter_map(|entry| match entry {
Some((Some(_), node)) => Some(node.clone()),
_ => None,
})
.collect::<Vec<_>>();
nodes.sort_by(|a, b| a.num_in.get().cmp(&b.num_in.get()));
nodes
}
pub fn postorder(&self) -> Vec<Rc<DomTreeNode>> {
let mut nodes = self
.nodes
.iter()
.filter_map(|entry| match entry {
Some((Some(_), node)) => Some(node.clone()),
_ => None,
})
.collect::<Vec<_>>();
nodes.sort_by(|a, b| a.num_out.get().cmp(&b.num_out.get()));
nodes
}
pub fn reverse_postorder(&self) -> Vec<Rc<DomTreeNode>> {
let mut nodes = self
.nodes
.iter()
.filter_map(|entry| match entry {
Some((Some(_), node)) => Some(node.clone()),
_ => None,
})
.collect::<Vec<_>>();
nodes.sort_by(|a, b| a.num_out.get().cmp(&b.num_out.get()).reverse());
nodes
}
}
impl<const IS_POST_DOM: bool> DomTreeBase<IS_POST_DOM> {
pub fn new(region: RegionRef) -> Result<Self, DomTreeError> {
let entry = region.borrow().entry_block_ref().ok_or(DomTreeError::EmptyRegion)?;
let root = Rc::new(DomTreeNode::new(Some(entry), None));
let root_id = entry.borrow().id().as_usize() + 1;
let mut nodes = SmallVec::default();
nodes.resize(root_id + 2, None);
nodes[root_id] = Some((Some(entry), root.clone()));
let roots = smallvec![Some(entry)];
let mut this = Self {
parent: region,
root: Some(root),
roots,
nodes,
valid: Cell::new(false),
slow_queries: Cell::new(0),
};
this.compute();
if !region.borrow().has_one_block() {
this.update_dfs_numbers();
}
Ok(this)
}
#[inline]
pub fn parent(&self) -> RegionRef {
self.parent
}
pub fn len(&self) -> usize {
self.nodes.iter().filter(|entry| entry.is_some()).count()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty() || self.nodes.iter().all(|entry| entry.is_none())
}
#[inline]
pub fn num_roots(&self) -> usize {
self.roots.len()
}
#[inline]
pub fn roots(&self) -> &[Option<BlockRef>] {
&self.roots
}
#[inline]
pub fn roots_mut(&mut self) -> &mut DomTreeRoots {
&mut self.roots
}
pub(super) fn set_root(&mut self, root: Rc<DomTreeNode>) {
self.root = Some(root);
}
#[inline(always)]
pub const fn is_post_dominator(&self) -> bool {
IS_POST_DOM
}
pub(super) fn mark_invalid(&self) {
self.valid.set(false);
}
pub fn get(&self, block: Option<BlockRef>) -> Option<Rc<DomTreeNode>> {
let index = self.node_index(block);
self.nodes.get(index).and_then(|entry| match entry {
Some((_, node)) => Some(node.clone()),
_ => None,
})
}
#[inline]
fn node_index(&self, block: Option<BlockRef>) -> usize {
assert!(
block.is_none_or(|block| block.parent().is_some_and(|parent| parent == self.parent)),
"cannot get dominance info of block with different parent"
);
if let Some(block) = block {
block.borrow().id().as_usize() + 1
} else {
0
}
}
pub fn root_node(&self) -> Option<Rc<DomTreeNode>> {
self.root.clone()
}
pub fn get_descendants(&self, r: BlockRef) -> SmallVec<[BlockRef; 2]> {
let mut results = SmallVec::default();
let Some(rn) = self.get(Some(r)) else {
return results;
};
let mut worklist = SmallVec::<[Rc<DomTreeNode>; 8]>::default();
worklist.push(rn);
while let Some(n) = worklist.pop() {
let Some(n_block) = n.block() else {
continue;
};
results.push(n_block);
worklist.extend(n.children.borrow().iter().cloned());
}
results
}
pub fn is_reachable_from_entry(&self, a: BlockRef) -> bool {
assert!(!self.is_post_dominator(), "unimplemented for post dominator trees");
self.get(Some(a)).is_some()
}
#[inline]
pub const fn is_reachable_from_entry_node(&self, a: Option<&Rc<DomTreeNode>>) -> bool {
a.is_some()
}
pub fn properly_dominates(&self, a: Option<BlockRef>, b: Option<BlockRef>) -> bool {
if a == b {
return false;
}
let a = self.get(a);
let b = self.get(b);
if a.is_none() || b.is_none() {
return false;
}
self.properly_dominates_node(a, b)
}
pub fn properly_dominates_node(
&self,
a: Option<Rc<DomTreeNode>>,
b: Option<Rc<DomTreeNode>>,
) -> bool {
a != b && self.dominates_node(a, b)
}
pub fn dominates(&self, a: Option<BlockRef>, b: Option<BlockRef>) -> bool {
if a == b {
return true;
}
let a = self.get(a);
let b = self.get(b);
self.dominates_node(a, b)
}
pub fn dominates_node(&self, a: Option<Rc<DomTreeNode>>, b: Option<Rc<DomTreeNode>>) -> bool {
if a == b {
return true;
}
if b.is_none() {
return true;
}
if a.is_none() {
return false;
}
let a = a.unwrap();
let b = b.unwrap();
if b.idom().is_some_and(|idom| idom == a) {
return true;
}
if a.idom().is_some_and(|idom| idom == b) {
return false;
}
if a.level() >= b.level() {
return false;
}
if self.valid.get() {
return b.is_dominated_by(&a);
}
self.slow_queries.set(self.slow_queries.get() + 1);
if self.slow_queries.get() > 32 {
self.update_dfs_numbers();
return b.is_dominated_by(&a);
}
self.dominated_by_slow_tree_walk(a, b)
}
pub fn find_nearest_common_dominator(&self, a: BlockRef, b: BlockRef) -> Option<BlockRef> {
assert!(a.parent() == b.parent(), "two blocks are not in same region");
if !self.is_post_dominator() {
let parent = a.parent().unwrap();
let entry = parent.borrow().entry_block_ref().unwrap();
if a == entry || b == entry {
return Some(entry);
}
}
let mut a = self.get(Some(a)).expect("'a' must be in the tree");
let mut b = self.get(Some(b)).expect("'b' must be in the tree");
while a != b {
if a.level() < b.level() {
core::mem::swap(&mut a, &mut b);
}
a = a.idom().unwrap();
}
a.block()
}
}
impl<const IS_POST_DOM: bool> DomTreeBase<IS_POST_DOM> {
pub fn insert_edge(&mut self, mut from: Option<BlockRef>, mut to: Option<BlockRef>) {
if self.is_post_dominator() {
core::mem::swap(&mut from, &mut to);
}
SemiNCA::<IS_POST_DOM>::insert_edge(self, None, from, to)
}
pub fn delete_edge(&mut self, mut from: Option<BlockRef>, mut to: Option<BlockRef>) {
if self.is_post_dominator() {
core::mem::swap(&mut from, &mut to);
}
SemiNCA::<IS_POST_DOM>::delete_edge(self, None, from, to)
}
pub fn apply_updates(
&mut self,
pre_view_cfg: cfg::CfgDiff<IS_POST_DOM>,
post_view_cfg: cfg::CfgDiff<IS_POST_DOM>,
) {
SemiNCA::<IS_POST_DOM>::apply_updates(self, pre_view_cfg, post_view_cfg);
}
pub fn compute(&mut self) {
SemiNCA::<IS_POST_DOM>::compute_from_scratch(self, None);
}
pub fn compute_with_updates(&mut self, updates: impl ExactSizeIterator<Item = cfg::CfgUpdate>) {
let pre_view_cfg = cfg::CfgDiff::new(updates, true);
let bui = BatchUpdateInfo::new(pre_view_cfg, None);
SemiNCA::<IS_POST_DOM>::compute_from_scratch(self, Some(bui));
}
pub fn verify(&self, level: DomTreeVerificationLevel) -> bool {
let snca = SemiNCA::new(None);
if !self.is_same_as_fresh_tree() {
return false;
}
if !snca.verify_roots(self)
|| !snca.verify_reachability(self)
|| !snca.verify_levels(self)
|| !snca.verify_dfs_numbers(self)
{
return false;
}
match level {
DomTreeVerificationLevel::Basic => {
if !snca.verify_parent_property(self) {
return false;
}
}
DomTreeVerificationLevel::Full => {
if !snca.verify_parent_property(self) || !snca.verify_sibling_property(self) {
return false;
}
}
_ => (),
}
true
}
fn is_same_as_fresh_tree(&self) -> bool {
let fresh = Self::new(self.parent).unwrap();
let is_same = self == &fresh;
if !is_same {
log::error!(
"{} is different than a freshly computed one!",
if IS_POST_DOM {
"post-dominator tree"
} else {
"dominator tree"
}
);
log::error!("Current: {self}");
log::error!("Fresh: {fresh}");
}
is_same
}
pub fn is_virtual_root(&self, node: &DomTreeNode) -> bool {
self.is_post_dominator() && node.block.is_none()
}
pub fn add_new_block(&mut self, block: BlockRef, idom: Option<BlockRef>) -> Rc<DomTreeNode> {
assert!(self.get(Some(block)).is_none(), "block already in dominator tree");
let idom = self.get(idom).expect("no immediate dominator specified for `idom`");
self.mark_invalid();
self.create_node(Some(block), Some(idom))
}
pub fn set_new_root(&mut self, block: BlockRef) -> Rc<DomTreeNode> {
assert!(self.get(Some(block)).is_none(), "block already in dominator tree");
assert!(!self.is_post_dominator(), "cannot change root of post-dominator tree");
self.valid.set(false);
let node = self.create_node(Some(block), None);
if self.roots.is_empty() {
self.roots.push(Some(block));
} else {
assert_eq!(self.roots.len(), 1);
let old_node = self.get(self.roots[0]).unwrap();
node.add_child(old_node.clone());
old_node.idom.set(Some(node.clone()));
old_node.update_level();
self.roots[0] = Some(block);
}
self.root = Some(node.clone());
node
}
pub fn change_immediate_dominator(&mut self, n: BlockRef, idom: Option<BlockRef>) {
let n = self.get(Some(n)).expect("expected `n` to be in tree");
let idom = self.get(idom).expect("expected `idom` to be in tree");
self.change_immediate_dominator_node(n, idom);
}
pub fn change_immediate_dominator_node(&mut self, n: Rc<DomTreeNode>, idom: Rc<DomTreeNode>) {
self.valid.set(false);
n.idom.set(Some(idom));
}
pub fn erase_node(&mut self, block: BlockRef) {
let node_index = self.node_index(Some(block));
let entry = unsafe { self.nodes.get_unchecked_mut(node_index).take() };
let Some((_, node)) = entry else {
panic!("no node in tree for {block}");
};
assert!(node.is_leaf(), "node is not a leaf node");
self.valid.set(false);
if let Some(idom) = node.idom() {
idom.children.borrow_mut().retain(|child| child != &node);
}
if !IS_POST_DOM {
return;
}
if let Some(root_index) = self.roots.iter().position(|r| r.is_some_and(|r| r == block)) {
self.roots.remove(root_index);
}
}
pub fn update_dfs_numbers(&self) {
if self.valid.get() {
self.slow_queries.set(0);
return;
}
let mut worklist = SmallVec::<[(Rc<DomTreeNode>, usize); 32]>::default();
let this_root = self.root_node().unwrap();
this_root.num_in.set(NonZeroU32::new(1));
worklist.push((this_root, 0));
let mut dfs_num = 1u32;
while let Some((node, child_index)) = worklist.last_mut() {
if *child_index >= node.num_children() {
node.num_out.set(Some(unsafe { NonZeroU32::new_unchecked(dfs_num) }));
dfs_num += 1;
worklist.pop();
} else {
let index = *child_index;
*child_index += 1;
let child = node.children.borrow()[index].clone();
child.num_in.set(Some(unsafe { NonZeroU32::new_unchecked(dfs_num) }));
dfs_num += 1;
worklist.push((child, 0));
}
}
self.slow_queries.set(0);
self.valid.set(true);
}
pub fn reset(&mut self) {
self.nodes.clear();
self.root.take();
self.roots.clear();
self.valid.set(false);
self.slow_queries.set(0);
}
pub(super) fn create_node(
&mut self,
block: Option<BlockRef>,
idom: Option<Rc<DomTreeNode>>,
) -> Rc<DomTreeNode> {
let node = Rc::new(DomTreeNode::new(block, idom.clone()));
let node_index = self.node_index(block);
if node_index >= self.nodes.len() {
self.nodes.resize(node_index + 1, None);
}
self.nodes[node_index] = Some((block, node.clone()));
if let Some(idom) = idom {
idom.add_child(node.clone());
}
node
}
pub fn split_block(&mut self, block: BlockRef) {
if IS_POST_DOM {
self.split::<Inverse<BlockRef>>(block);
} else {
self.split::<BlockRef>(block);
}
}
fn split<G>(&mut self, block: <G as Graph>::Node)
where
G: InvertibleGraph<Node = BlockRef>,
{
let mut successors = G::children(block);
assert_eq!(successors.len(), 1, "`block` should have a single successor");
let succ = successors.next().unwrap();
let predecessors = G::inverse_children(block).collect::<SmallVec<[BlockRef; 4]>>();
assert!(!predecessors.is_empty(), "expected at at least one predecessor");
let mut block_dominates_succ = true;
for pred in G::inverse_children(succ) {
if pred != block
&& !self.dominates(Some(succ), Some(pred))
&& self.is_reachable_from_entry(pred)
{
block_dominates_succ = false;
break;
}
}
let idom = predecessors.iter().find(|p| self.is_reachable_from_entry(**p)).copied();
let Some(idom) = idom else {
return;
};
let idom = predecessors.iter().copied().fold(idom, |idom, p| {
if self.is_reachable_from_entry(p) {
self.find_nearest_common_dominator(idom, p).expect("expected idom")
} else {
idom
}
});
let node = self.add_new_block(block, Some(idom));
if block_dominates_succ {
let succ_node = self.get(Some(succ)).expect("expected 'succ' to be in dominator tree");
self.change_immediate_dominator_node(succ_node, node);
}
}
fn dominated_by_slow_tree_walk(&self, a: Rc<DomTreeNode>, b: Rc<DomTreeNode>) -> bool {
assert_ne!(a, b);
let a_level = a.level();
let mut b = b;
while b.level() > a_level {
match b.idom() {
Some(b_idom) => b = b_idom,
None => break,
}
}
b == a
}
}
impl<const IS_POST_DOM: bool> fmt::Display for DomTreeBase<IS_POST_DOM> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use core::fmt::Write;
f.write_str("=============================--------------------------------\n")?;
if IS_POST_DOM {
f.write_str("Inorder PostDominator Tree: ")?;
} else {
f.write_str("Inorder Dominator Tree: ")?;
}
if !self.valid.get() {
write!(f, "DFS numbers invalid: {} slow queries.", self.slow_queries.get())?;
}
f.write_char('\n')?;
if let Some(root_node) = self.root_node() {
print_dom_tree(root_node, 1, f)?
}
f.write_str("Roots: ")?;
for (i, block) in self.roots.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
if let Some(block) = block {
write!(f, "{block}")?;
} else {
f.write_str("<virtual>")?;
}
}
f.write_char('\n')
}
}
fn print_dom_tree(
node: Rc<DomTreeNode>,
level: usize,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
write!(f, "{: <1$}", "", level)?;
writeln!(f, "[{level}] {node}")?;
for child_node in node.children.borrow().iter().cloned() {
print_dom_tree(child_node, level + 1, f)?;
}
Ok(())
}
impl<const IS_POST_DOM: bool> Eq for DomTreeBase<IS_POST_DOM> {}
impl<const IS_POST_DOM: bool> PartialEq for DomTreeBase<IS_POST_DOM> {
fn eq(&self, other: &Self) -> bool {
self.parent == other.parent
&& self.roots.len() == other.roots.len()
&& self.roots.iter().all(|root| other.roots.contains(root))
&& self.nodes.len() == other.nodes.len()
&& self.nodes.iter().all(|entry| match entry {
Some((_, node)) => {
let block = node.block();
other.get(block).is_some_and(|n| node == &n)
}
None => true,
})
}
}
#[cfg(test)]
mod tests {
use alloc::rc::Rc;
use super::*;
#[test]
fn dom_tree_successor_iter_forwards_backwards_and_mixed() {
let root = Rc::new(DomTreeNode::new(None, None));
let c0 = Rc::new(DomTreeNode::new(None, Some(root.clone())));
let c1 = Rc::new(DomTreeNode::new(None, Some(root.clone())));
let c2 = Rc::new(DomTreeNode::new(None, Some(root.clone())));
root.add_child(c0.clone());
root.add_child(c1.clone());
root.add_child(c2.clone());
let mut iter = DomTreeSuccessorIter::new(root.clone());
let first = iter.next().unwrap();
let second = iter.next().unwrap();
let third = iter.next().unwrap();
assert!(Rc::ptr_eq(&first, &c0));
assert!(Rc::ptr_eq(&second, &c1));
assert!(Rc::ptr_eq(&third, &c2));
assert!(iter.next().is_none());
let mut iter = DomTreeSuccessorIter::new(root.clone());
let last = iter.next_back().unwrap();
let mid = iter.next_back().unwrap();
let first_back = iter.next_back().unwrap();
assert!(Rc::ptr_eq(&last, &c2));
assert!(Rc::ptr_eq(&mid, &c1));
assert!(Rc::ptr_eq(&first_back, &c0));
assert!(iter.next_back().is_none());
let mut iter = DomTreeSuccessorIter::new(root);
let first = iter.next().unwrap();
let last = iter.next_back().unwrap();
let middle = iter.next().unwrap();
assert!(Rc::ptr_eq(&first, &c0));
assert!(Rc::ptr_eq(&last, &c2));
assert!(Rc::ptr_eq(&middle, &c1));
assert!(iter.next().is_none());
assert!(iter.next_back().is_none());
}
}