Skip to content

Commit 069f7a6

Browse files
authored
Control flow: use domtree analysis (#98)
Fixes #96 This vendors Julia 1.10's Core.Compiler's domtree code for earlier Julia versions.
1 parent 0380f3c commit 069f7a6

File tree

4 files changed

+449
-52
lines changed

4 files changed

+449
-52
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "LoweredCodeUtils"
22
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
33
authors = ["Tim Holy <[email protected]>"]
4-
version = "2.4.3"
4+
version = "2.4.4"
55

66
[deps]
77
JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"

src/codeedges.jl

Lines changed: 27 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,8 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
609609

610610
# Compute basic blocks, which we'll use to make sure we mark necessary control-flow
611611
cfg = Core.Compiler.compute_basic_blocks(src.code) # needed for control-flow analysis
612-
paths = enumerate_paths(cfg)
612+
domtree = construct_domtree(cfg.blocks)
613+
postdomtree = construct_postdomtree(cfg.blocks)
613614

614615
# We'll mostly use generic graph traversal to discover all the lines we need,
615616
# but structs are in a bit of a different category (especially on Julia 1.5+).
@@ -629,7 +630,7 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
629630

630631
# Add control-flow
631632
changed |= add_loops!(isrequired, cfg)
632-
changed |= add_control_flow!(isrequired, cfg, paths)
633+
changed |= add_control_flow!(isrequired, cfg, domtree, postdomtree)
633634

634635
# So far, everything is generic graph traversal. Now we add some domain-specific information
635636
changed |= add_typedefs!(isrequired, src, edges, typedefs, norequire)
@@ -706,17 +707,6 @@ end
706707

707708
## Add control-flow
708709

709-
struct Path
710-
path::Vector{Int}
711-
visited::BitSet
712-
end
713-
Path() = Path(Int[], BitSet())
714-
Path(i::Int) = Path([i], BitSet([i]))
715-
Path(path::Path) = copy(path)
716-
Base.copy(path::Path) = Path(copy(path.path), copy(path.visited))
717-
Base.in(node::Int, path::Path) = node path.visited
718-
Base.push!(path::Path, node::Int) = (push!(path.path, node); push!(path.visited, node); return path)
719-
720710
# Mark loops that contain evaluated statements
721711
function add_loops!(isrequired, cfg)
722712
changed = false
@@ -741,26 +731,7 @@ function add_loops!(isrequired, cfg)
741731
return changed
742732
end
743733

744-
enumerate_paths(cfg) = enumerate_paths!(Path[], cfg, Path(1))
745-
function enumerate_paths!(paths, cfg, path)
746-
bb = cfg.blocks[path.path[end]]
747-
if isempty(bb.succs)
748-
push!(paths, copy(path))
749-
return paths
750-
end
751-
for ibbs in bb.succs
752-
if ibbs path
753-
push!(paths, push!(copy(path), ibbs)) # close the loop
754-
continue
755-
end
756-
enumerate_paths!(paths, cfg, push!(copy(path), ibbs))
757-
end
758-
return paths
759-
end
760-
761-
# Mark exits of blocks that bifurcate execution paths in ways that matter for required statements
762-
function add_control_flow!(isrequired, cfg, paths::AbstractVector{Path})
763-
withnode, withoutnode, shared = BitSet(), BitSet(), BitSet()
734+
function add_control_flow!(isrequired, cfg, domtree, postdomtree)
764735
changed, _changed = false, true
765736
blocks = cfg.blocks
766737
nblocks = length(blocks)
@@ -769,28 +740,33 @@ function add_control_flow!(isrequired, cfg, paths::AbstractVector{Path})
769740
for (ibb, bb) in enumerate(blocks)
770741
r = rng(bb)
771742
if any(view(isrequired, r))
772-
# Check if the exit of this block is a GotoNode or `return`
773-
if length(bb.succs) < 2 && ibb < nblocks
774-
idxlast = r[end]
775-
_changed |= !isrequired[idxlast]
776-
isrequired[idxlast] = true
777-
end
778-
empty!(withnode)
779-
empty!(withoutnode)
780-
for path in paths
781-
union!(ibb path ? withnode : withoutnode, path.visited)
743+
# Walk up the dominators
744+
jbb = ibb
745+
while jbb != 1
746+
jdbb = domtree.idoms_bb[jbb]
747+
dbb = blocks[jdbb]
748+
# Check the successors; if jbb doesn't post-dominate, mark the last statement
749+
for s in dbb.succs
750+
if !postdominates(postdomtree, jbb, s)
751+
idxlast = rng(dbb)[end]
752+
_changed |= !isrequired[idxlast]
753+
isrequired[idxlast] = true
754+
break
755+
end
756+
end
757+
jbb = jdbb
782758
end
783-
empty!(shared)
784-
union!(shared, withnode)
785-
intersect!(shared, withoutnode)
786-
for icfbb in shared
787-
cfbb = blocks[icfbb]
788-
if any((shared), cfbb.succs)
789-
rcfbb = rng(blocks[icfbb])
790-
idxlast = rcfbb[end]
759+
# Walk down the post-dominators, including self
760+
jbb = ibb
761+
while jbb != 0 && jbb < nblocks
762+
pdbb = blocks[jbb]
763+
# Check if the exit of this block is a GotoNode or `return`
764+
if length(pdbb.succs) < 2
765+
idxlast = rng(pdbb)[end]
791766
_changed |= !isrequired[idxlast]
792767
isrequired[idxlast] = true
793768
end
769+
jbb = postdomtree.idoms_bb[jbb]
794770
end
795771
end
796772
end

0 commit comments

Comments
 (0)