Skip to content

Commit 400ee71

Browse files
committed
Add filter parameter to tarjan!
This still isn't an entirely generic version of the algorithm, but it's certainly a step in the right direction.
1 parent d6adba1 commit 400ee71

File tree

2 files changed

+38
-27
lines changed

2 files changed

+38
-27
lines changed

base/compiler/ssair/tarjan.jl

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ function CFGReachability(cfg::CFG, domtree::DomTree)
4646
Int[], # _worklist
4747
SCCStackItem[], # _stack
4848
)
49-
tarjan!(reachability, cfg)
49+
tarjan!(reachability, cfg;
50+
# reducible back-edges don't need to be considered for reachability
51+
filter = (from::Int,to::Int)->!dominates(domtree, to, from)
52+
)
5053
return reachability
5154
end
5255

@@ -59,12 +62,12 @@ bb_in_irreducible_loop(reach::CFGReachability, bb::Int) = reach.irreducible[bb]
5962
#
6063
# `tarjan!` takes the transitive closure of this relation in order to detect
6164
# which BasicBlocks are unreachable.
62-
function _bb_externally_reachable(reach::CFGReachability, cfg::CFG, bb::Int)
65+
function _bb_externally_reachable(reach::CFGReachability, cfg::CFG, bb::Int; filter)
6366
(; scc) = reach
6467
bb == 1 && return true
6568
for pred in cfg.blocks[bb].preds
6669
scc[pred] <= 0 && continue
67-
dominates(reach.domtree, bb, pred) && continue
70+
!filter(pred, bb) && continue
6871
@assert scc[pred] != scc[bb]
6972
return true
7073
end
@@ -85,10 +88,12 @@ Outputs:
8588
- `reach._worklist`: if performing an incremental update (`root != 1`), any traversed nodes that
8689
are unreachable from BasicBlock #1 are enqueued to this worklist
8790
"""
88-
function tarjan!(reach::CFGReachability, cfg::CFG; root::Int=1)
89-
(; scc, irreducible, domtree) = reach
91+
function tarjan!(reach::CFGReachability, cfg::CFG; root::Int=1,
92+
filter = (from::Int,to::Int)->true,
93+
)
94+
(; scc, irreducible) = reach
9095
scc[root] != 0 && return scc
91-
live = _bb_externally_reachable(reach, cfg, root)
96+
live = _bb_externally_reachable(reach, cfg, root; filter)
9297

9398
# the original algorithm has a separate stack and worklist (unrelated to `reach._worklist`)
9499
# here we use a single combined stack for improved memory/cache efficiency
@@ -117,12 +122,8 @@ function tarjan!(reach::CFGReachability, cfg::CFG; root::Int=1)
117122
stack[cursor] = item = SCCStackItem(item; child=child+1)
118123
succ = bb.succs[child]
119124

120-
# ignore any back-edges in a (natural) loop (see `kill_edge!`)
121-
if dominates(domtree, succ, convert(Int, v))
122-
# This check ensures that reducible CFG's will contain no SCC's. The vast majority
123-
# of functions have reducible CFG's, so this optimization is very important.
124-
continue
125-
end
125+
# ignore any edges that don't pass the filter
126+
!filter(convert(Int, v), succ) && continue
126127

127128
if scc[succ] < 0
128129
# next child is already in DFS tree
@@ -134,7 +135,7 @@ function tarjan!(reach::CFGReachability, cfg::CFG; root::Int=1)
134135
elseif scc[succ] == 0
135136
# next child is a new element in DFS tree
136137
preorder_id += 1
137-
live = live || _bb_externally_reachable(reach, cfg, succ)
138+
live = live || _bb_externally_reachable(reach, cfg, succ; filter)
138139
push!(stack, SCCStackItem(
139140
succ, # v
140141
1, # child
@@ -154,7 +155,7 @@ function tarjan!(reach::CFGReachability, cfg::CFG; root::Int=1)
154155
if live
155156
scc[item.v] = v
156157
scan_subgraph!(reach, cfg, convert(Int, item.v),
157-
#= filter =# (pred,x)->(!dominates(domtree, x, pred) && scc[x] > typemax(Int)÷2),
158+
#= filter =# (pred,x)->(filter(pred, x) && scc[x] > typemax(Int)÷2),
158159
#= action =# (x)->(scc[x] -= typemax(Int)÷2;),
159160
)
160161
else # this offset marks a node as 'maybe-dead'
@@ -181,19 +182,19 @@ function tarjan!(reach::CFGReachability, cfg::CFG; root::Int=1)
181182
worklist = reach._worklist
182183

183184
# filter the worklist, leaving any nodes not proven to be reachable from BB #1
184-
n_filtered = 0
185+
n_popped = 0
185186
for i = (worklist_len + 1):length(worklist)
186187
@assert worklist[i] != 1
187188
@assert scc[worklist[i]] > 0
188189
if scc[worklist[i]] > typemax(Int)÷2
189190
# node is unreachable, enqueue it
190191
scc[worklist[i]] = 0
191-
worklist[i - n_filtered] = worklist[i]
192+
worklist[i - n_popped] = worklist[i]
192193
else
193-
n_filtered += 1
194+
n_popped += 1
194195
end
195196
end
196-
resize!(worklist, length(worklist) - n_filtered)
197+
resize!(worklist, length(worklist) - n_popped)
197198

198199
return length(worklist) > worklist_len # if true, a (newly) unreachable node was enqueued
199200
end
@@ -228,16 +229,20 @@ function enqueue_if_unreachable!(reach::CFGReachability, cfg::CFG, bb::Int)
228229
# irreducible CFG
229230
# this requires a full scan of the irreducible loop
230231

232+
# any reducible back-edges do not need to be considered as part of reachability
233+
# (very important optimization, since it means reducible CFGs will have no SCCs)
234+
filter = (from::Int, to::Int)->!dominates(domtree, to, from)
235+
231236
scc′ = scc[bb]
232237
scc[bb] = 0
233238
scan_subgraph!(reach, cfg, bb, # set this SCC to 0
234-
#= filter =# (pred,x)->(!dominates(domtree, x, pred) && scc[x] == scc′),
239+
#= filter =# (pred,x)->(filter(pred, x) && scc[x] == scc′),
235240
#= action =# (x)->(scc[x] = 0;),
236241
)
237242

238243
# re-compute the SCC's for this portion of the CFG, adding any freshly
239244
# unreachable nodes to `reach._worklist`
240-
return tarjan!(reach, cfg; root=bb)
245+
return tarjan!(reach, cfg; root=bb, filter)
241246
else
242247
# target is a reducible CFG node
243248
# this node lives iff it still has an incoming forward edge
@@ -250,6 +255,13 @@ function enqueue_if_unreachable!(reach::CFGReachability, cfg::CFG, bb::Int)
250255
end
251256
end
252257

258+
function kill_cfg_edge!(cfg::CFG, from::Int, to::Int)
259+
preds, succs = cfg.blocks[to].preds, cfg.blocks[from].succs
260+
deleteat!(preds, findfirst(x::Int->x==from, preds)::Int)
261+
deleteat!(succs, findfirst(x::Int->x==to, succs)::Int)
262+
return nothing
263+
end
264+
253265
"""
254266
Remove from `cfg` and `reach` the edge (from → to), as well as any blocks/edges
255267
this causes to become unreachable.
@@ -265,9 +277,7 @@ function kill_edge!(reach::CFGReachability, cfg::CFG, from::Int, to::Int,
265277
@assert reach.scc[to] != 0
266278

267279
# delete (from → to) edge
268-
preds, succs = cfg.blocks[to].preds, cfg.blocks[from].succs
269-
deleteat!(preds, findfirst(x::Int->x==from, preds)::Int)
270-
deleteat!(succs, findfirst(x::Int->x==to, succs)::Int)
280+
kill_cfg_edge!(cfg, from, to)
271281

272282
# check for unreachable target
273283
enqueued = enqueue_if_unreachable!(reach, cfg, to)
@@ -295,5 +305,6 @@ function kill_edge!(reach::CFGReachability, cfg::CFG, from::Int, to::Int,
295305
edge_callback(node, succ)
296306
end
297307
end
308+
empty!(cfg.blocks[node].succs)
298309
end
299310
end

test/compiler/tarjan.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,8 @@ function rand_cfg(V, E)
5656
end
5757

5858
function get_random_edge(cfg::CFG, V)
59-
source = rand(1:V)
60-
while length(cfg.blocks[source].succs) == 0
61-
source = rand(1:V)
62-
end
59+
has_edge = [length(cfg.blocks[bb].succs) != 0 for bb in 1:V]
60+
source = rand(findall(has_edge))
6361
target = rand(cfg.blocks[source].succs)
6462
return source, target
6563
end
@@ -121,6 +119,8 @@ function test_reachability(V, E; deletions = 2E ÷ 3, all_checks=false)
121119
killed_edges = Tuple{Int,Int}[]
122120
killed_blocks = Int[]
123121
for k = 1:deletions
122+
length(blocks) == 1 && break # no more reachable blocks
123+
124124
from, to = get_random_edge(cfg, V)
125125
kill_edge!(reachability, cfg, from, to,
126126
(from::Int, to::Int) -> push!(killed_edges, (from, to)),

0 commit comments

Comments
 (0)