Skip to content

Commit 350e91f

Browse files
committed
Introduce postdominator tree computation
This reuses the existing dominator tree for the actual construction algorithm, but now needs more complex logic for attaching roots.
1 parent ab5d0f4 commit 350e91f

4 files changed

Lines changed: 545 additions & 1 deletion

File tree

crates/dominators/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ cranelift-entity.workspace = true
1515
smallvec.workspace = true
1616

1717
[dev-dependencies]
18-
entity-utils.workspace = true
1918
graphmock.workspace = true
2019
expect-test.workspace = true
2120
itertools.workspace = true

crates/dominators/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extern crate alloc;
77
pub mod depth_map;
88
pub mod domtree;
99
pub mod loops;
10+
pub mod postdomtree;
1011

1112
pub trait IntoCfg {
1213
type Node: Copy;
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
use alloc::vec::Vec;
2+
use core::{marker::PhantomData, ops::ControlFlow};
3+
4+
use cranelift_entity::EntityRef;
5+
use entity_utils::{define_param_entity, set::DenseEntitySet};
6+
use graphwalk::{
7+
Graph, PredGraph,
8+
dfs::{TreePostOrder, TreePreOrder},
9+
scc::SccWalk,
10+
};
11+
use smallvec::SmallVec;
12+
13+
use crate::{
14+
IntoCfg,
15+
domtree::{DomTree, DomTreeNode},
16+
};
17+
18+
define_param_entity!(PostDomTreeCfgNode<N>, "pdn");
19+
20+
impl<N: EntityRef> PostDomTreeCfgNode<N> {
21+
fn virtual_exit() -> Self {
22+
Self::from_u32(0)
23+
}
24+
25+
fn from_cfg_node(node: N) -> Self {
26+
Self::from_u32((node.index() + 1).try_into().unwrap())
27+
}
28+
29+
fn to_cfg_node(self) -> N {
30+
debug_assert!(self.as_u32() != 0);
31+
N::new((self.as_u32() - 1) as usize)
32+
}
33+
}
34+
35+
pub type PostDomTreeNode<N> = DomTreeNode<PostDomTreeCfgNode<N>>;
36+
37+
pub struct PostDomTree<N> {
38+
domtree: DomTree<PostDomTreeCfgNode<N>>,
39+
_marker: PhantomData<N>,
40+
}
41+
42+
impl<N: EntityRef> PostDomTree<N> {
43+
pub fn compute(graph: impl IntoCfg<Node = N>, entry: N) -> Self {
44+
let graph = graph.into_cfg();
45+
46+
let (exits, reachable_nodes) = find_graph_exits(&graph, entry);
47+
48+
let virtual_graph = PostDomTreeCfg {
49+
graph: &graph,
50+
exits: &exits,
51+
reachable_nodes: &reachable_nodes,
52+
};
53+
54+
let domtree = DomTree::compute(virtual_graph, PostDomTreeCfgNode::virtual_exit());
55+
56+
Self {
57+
domtree,
58+
_marker: PhantomData,
59+
}
60+
}
61+
62+
pub fn cfg_ipdom(&self, node: N) -> Option<N> {
63+
let node = self.get_tree_node(node)?;
64+
Some(self.get_cfg_node(self.ipdom(node)?))
65+
}
66+
67+
pub fn cfg_postdominates(&self, a: N, b: N) -> bool {
68+
let Some(a) = self.get_tree_node(a) else {
69+
return false;
70+
};
71+
let Some(b) = self.get_tree_node(b) else {
72+
return false;
73+
};
74+
75+
self.postdominates(a, b)
76+
}
77+
78+
pub fn cfg_strictly_postdominates(&self, a: N, b: N) -> bool {
79+
a != b && self.cfg_postdominates(a, b)
80+
}
81+
82+
#[inline]
83+
pub fn get_tree_node(&self, node: N) -> Option<PostDomTreeNode<N>> {
84+
self.domtree
85+
.get_tree_node(PostDomTreeCfgNode::from_cfg_node(node))
86+
}
87+
88+
#[inline]
89+
pub fn get_cfg_node(&self, node: PostDomTreeNode<N>) -> N {
90+
self.domtree.get_cfg_node(node).to_cfg_node()
91+
}
92+
93+
#[inline]
94+
pub fn roots(&self) -> &[PostDomTreeNode<N>] {
95+
self.domtree.children(self.domtree.root())
96+
}
97+
98+
#[inline]
99+
pub fn ipdom(&self, node: PostDomTreeNode<N>) -> Option<PostDomTreeNode<N>> {
100+
self.domtree
101+
.idom(node)
102+
.filter(|&ipdom| ipdom != self.domtree.root())
103+
}
104+
105+
#[inline]
106+
pub fn children(&self, node: PostDomTreeNode<N>) -> &[PostDomTreeNode<N>] {
107+
self.domtree.children(node)
108+
}
109+
110+
pub fn postdominates(&self, a: PostDomTreeNode<N>, b: PostDomTreeNode<N>) -> bool {
111+
self.domtree.dominates(a, b)
112+
}
113+
114+
pub fn strictly_postdominates(&self, a: PostDomTreeNode<N>, b: PostDomTreeNode<N>) -> bool {
115+
self.domtree.strictly_dominates(a, b)
116+
}
117+
118+
pub fn preorder(&self) -> TreePreOrder<&Self> {
119+
TreePreOrder::new(self, self.roots().iter().copied())
120+
}
121+
122+
pub fn postorder(&self) -> TreePostOrder<&Self> {
123+
TreePostOrder::new(self, self.roots().iter().copied())
124+
}
125+
}
126+
127+
impl<N: EntityRef> graphwalk::Graph for PostDomTree<N> {
128+
type Node = PostDomTreeNode<N>;
129+
130+
fn try_successors(
131+
&self,
132+
node: Self::Node,
133+
mut f: impl FnMut(Self::Node) -> ControlFlow<()>,
134+
) -> ControlFlow<()> {
135+
for &child in self.children(node) {
136+
f(child)?;
137+
}
138+
ControlFlow::Continue(())
139+
}
140+
}
141+
142+
fn find_graph_exits<N: EntityRef>(
143+
graph: &impl Graph<Node = N>,
144+
entry: N,
145+
) -> (SmallVec<[N; 8]>, DenseEntitySet<N>) {
146+
let mut exits = SmallVec::new();
147+
148+
let mut reachable_nodes = DenseEntitySet::new();
149+
150+
let mut scc_members = Vec::new();
151+
let mut walk = SccWalk::new(graph, [entry]);
152+
153+
while let Some(scc_members) = walk.next(&mut scc_members) {
154+
let scc_has_exits = scc_members.iter().any(|&member| {
155+
graph
156+
.try_successors(member, |succ| {
157+
// If we find an edge to a node currently marked as reachable, it must not be
158+
// part of the same SCC, because we haven't marked this SCC yet. If that is the
159+
// case, we know that the current node (and by extension, the current SCC) can
160+
// be exited.
161+
if reachable_nodes.contains(succ) {
162+
ControlFlow::Break(())
163+
} else {
164+
ControlFlow::Continue(())
165+
}
166+
})
167+
.is_break()
168+
});
169+
170+
for &member in scc_members {
171+
reachable_nodes.insert(member);
172+
}
173+
174+
if !scc_has_exits {
175+
// This SCC is either an infinite loop or a standalone exit node; pick an arbitrary
176+
// representative as the true "exit" point.
177+
exits.push(scc_members[0]);
178+
}
179+
}
180+
181+
(exits, reachable_nodes)
182+
}
183+
184+
struct PostDomTreeCfg<'a, G: PredGraph> {
185+
graph: &'a G,
186+
exits: &'a [G::Node],
187+
reachable_nodes: &'a DenseEntitySet<G::Node>,
188+
}
189+
190+
impl<'a, G> Graph for PostDomTreeCfg<'a, G>
191+
where
192+
G: PredGraph,
193+
G::Node: EntityRef,
194+
{
195+
type Node = PostDomTreeCfgNode<G::Node>;
196+
197+
fn try_successors(
198+
&self,
199+
node: PostDomTreeCfgNode<G::Node>,
200+
mut f: impl FnMut(PostDomTreeCfgNode<G::Node>) -> ControlFlow<()>,
201+
) -> ControlFlow<()> {
202+
// Note: take predecessors here so control edges are reversed.
203+
if node == PostDomTreeCfgNode::virtual_exit() {
204+
self.exits
205+
.iter()
206+
.try_for_each(|&node| f(PostDomTreeCfgNode::from_cfg_node(node)))
207+
} else {
208+
self.graph.try_predecessors(node.to_cfg_node(), |succ| {
209+
// Make sure not to follow edges from nodes that aren't forward-reachable.
210+
if self.reachable_nodes.contains(succ) {
211+
f(PostDomTreeCfgNode::from_cfg_node(succ))?;
212+
}
213+
ControlFlow::Continue(())
214+
})
215+
}
216+
}
217+
}
218+
219+
impl<'a, G> PredGraph for PostDomTreeCfg<'a, G>
220+
where
221+
G: PredGraph,
222+
G::Node: EntityRef,
223+
{
224+
fn try_predecessors(
225+
&self,
226+
node: Self::Node,
227+
mut f: impl FnMut(Self::Node) -> ControlFlow<()>,
228+
) -> ControlFlow<()> {
229+
// Note: take successors here so control edges are reversed.
230+
let node = node.to_cfg_node();
231+
self.graph
232+
.try_successors(node, |pred| f(PostDomTreeCfgNode::from_cfg_node(pred)))?;
233+
234+
// TODO: This is currently linear in exit count for every node, which isn't great.
235+
if self.exits.contains(&node) {
236+
f(PostDomTreeCfgNode::virtual_exit())?;
237+
}
238+
239+
ControlFlow::Continue(())
240+
}
241+
}
242+
243+
#[cfg(test)]
244+
mod tests {
245+
use expect_test::expect;
246+
use itertools::Itertools;
247+
248+
use graphmock::{Graph, graph};
249+
250+
use super::*;
251+
252+
fn stringify_exits(g: &Graph) -> String {
253+
find_graph_exits(&g, g.entry())
254+
.0
255+
.iter()
256+
.map(|&node| g.name(node))
257+
.format(" ")
258+
.to_string()
259+
}
260+
261+
macro_rules! test_exits {
262+
($name:ident, $graph:literal, $expected:expr) => {
263+
#[test]
264+
fn $name() {
265+
let g = graph($graph);
266+
$expected.assert_eq(&stringify_exits(&g));
267+
}
268+
};
269+
}
270+
271+
test_exits! {
272+
straight_line,
273+
"a -> b
274+
b -> c",
275+
expect!["c"]
276+
}
277+
278+
test_exits! {
279+
split,
280+
"a -> b
281+
b -> c
282+
b -> d",
283+
expect!["d c"]
284+
}
285+
286+
test_exits! {
287+
diamond,
288+
"a -> b
289+
a -> c
290+
b, c -> d",
291+
expect!["d"]
292+
}
293+
294+
test_exits! {
295+
unreachable_subgraph,
296+
"entry -> exit
297+
a -> b, c
298+
b, c -> exit",
299+
expect!["exit"]
300+
}
301+
302+
test_exits! {
303+
simple_loop_with_exit,
304+
"a -> b
305+
b -> c
306+
c -> b, e",
307+
expect!["e"]
308+
}
309+
310+
test_exits! {
311+
multi_backedge_loop_with_exit,
312+
"entry -> header
313+
header -> a, b
314+
a, b -> header
315+
a -> exit",
316+
expect!["exit"]
317+
}
318+
319+
test_exits! {
320+
tight_infinite_loop,
321+
"a -> b
322+
b -> b",
323+
expect!["b"]
324+
}
325+
326+
test_exits! {
327+
split_with_infinite_loop,
328+
"a -> b, c
329+
b -> b",
330+
expect!["c b"]
331+
}
332+
333+
// Note: the exit here is an arbitrary loop member.
334+
test_exits! {
335+
multi_backedge_infinite_loop,
336+
"entry -> header
337+
header -> a, b
338+
a, b -> header",
339+
expect!["a"]
340+
}
341+
342+
test_exits! {
343+
irreducible_cycle,
344+
"entry -> c1, c2
345+
c1 -> c2, exit
346+
c2 -> c1, exit
347+
exit -> ret",
348+
expect!["ret"]
349+
}
350+
351+
test_exits! {
352+
infinite_irreducible_cycle,
353+
"entry -> c1, c2
354+
c1 -> c2
355+
c2 -> c1",
356+
expect!["c1"]
357+
}
358+
}

0 commit comments

Comments
 (0)