Skip to content

Commit

Permalink
use the CFG selection algorithm implememted in LCU v3.0.2 (#662)
Browse files Browse the repository at this point in the history
  • Loading branch information
aviatesk authored Sep 11, 2024
1 parent e7a9d34 commit 321fbd5
Show file tree
Hide file tree
Showing 4 changed files with 11 additions and 144 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ InteractiveUtils = "1.10"
JuliaInterpreter = "0.9.36"
Libdl = "1.10"
Logging = "1.10"
LoweredCodeUtils = "3"
LoweredCodeUtils = "3.0.2"
MacroTools = "0.5.6"
Pkg = "1.10"
PrecompileTools = "1"
Expand Down
10 changes: 5 additions & 5 deletions src/JET.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ using Core: Builtin, IntrinsicFunction, Intrinsics, SimpleVector, svec
using Core.IR

using .CC: @nospecs, ,
AbstractInterpreter, AbstractLattice, ArgInfo, BasicBlock, Bottom, CFG, CachedMethodTable,
CallMeta, ConstCallInfo, InferenceParams, InferenceResult, InferenceState,
InternalMethodTable, InvokeCallInfo, MethodCallResult, MethodMatchInfo, MethodMatches,
NOT_FOUND, OptimizationState, OptimizationParams, OverlayMethodTable, StmtInfo,
UnionSplitInfo, UnionSplitMethodMatches, VarState, VarTable, WorldRange, WorldView,
AbstractInterpreter, AbstractLattice, ArgInfo, Bottom, CFG, CachedMethodTable, CallMeta,
ConstCallInfo, InferenceParams, InferenceResult, InferenceState, InternalMethodTable,
InvokeCallInfo, MethodCallResult, MethodMatchInfo, MethodMatches, NOT_FOUND,
OptimizationState, OptimizationParams, OverlayMethodTable, StmtInfo, UnionSplitInfo,
UnionSplitMethodMatches, VarState, VarTable, WorldRange, WorldView,
argextype, argtype_by_index, argtypes_to_type, compute_basic_blocks,
construct_postdomtree, hasintersect, ignorelimited, instanceof_tfunc, istopfunction,
nearest_common_dominator, singleton_type, slot_id, specialize_method, tmeet, tmerge,
Expand Down
139 changes: 3 additions & 136 deletions src/toplevel/virtualprocess.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1199,92 +1199,6 @@ function is_known_getproperty(@nospecialize(stmt), func::Symbol, stmts::Vector{A
return false
end

# The goal of this function is to request concretization of the minimal necessary control
# flow to evaluate statements whose concretization have already been requested.
# The basic algorithm is based on what was proposed in [^Wei84]. If there is even one active
# block in the blocks reachable from a conditional branch up to its successors' nearest
# common post-dominator (referred to as 𝑰𝑵𝑭𝑳 in the paper), it is necessary to follow
# that conditional branch and execute the code. Otherwise, execution can be short-circuited
# from the conditional branch to the nearest common post-dominator.
#
# COMBAK: It is important to note that in Julia's intermediate code representation (`CodeInfo`),
# "short-circuiting" a specific code region is not a simple task. Simply ignoring the path
# to the post-dominator does not guarantee fall-through to the post-dominator. Therefore,
# a more careful implementation is required for this aspect.
#
# [Wei84]: M. Weiser, "Program Slicing," IEEE Transactions on Software Engineering, 10, pages 352-357, July 1984.
function add_control_flow!(concretize::BitVector, src::CodeInfo, cfg::CFG, postdomtree)
local changed::Bool = false
function mark_concretize!(idx::Int)
if !concretize[idx]
changed |= concretize[idx] = true
return true
end
return false
end
for bbidx = 1:length(cfg.blocks) # forward traversal
bb = cfg.blocks[bbidx]
nsuccs = length(bb.succs)
if nsuccs == 0
continue
elseif nsuccs == 1
continue # leave a fall-through terminator unmarked: `GotoNode`s are marked later
elseif nsuccs == 2
termidx = bb.stmts[end]
@assert is_conditional_terminator(src.code[termidx]) "invalid IR"
if is_conditional_block_active(concretize, bb, cfg, postdomtree)
mark_concretize!(termidx)
else
# fall-through to the post dominator block (by short-circuiting all statements between)
end
end
end
return changed
end

is_conditional_terminator(@nospecialize stmt) = stmt isa GotoIfNot ||
(@static @isdefined(EnterNode) ? stmt isa EnterNode : isexpr(stmt, :enter))

function is_conditional_block_active(concretize::BitVector, bb::BasicBlock, cfg::CFG, postdomtree)
return visit_𝑰𝑵𝑭𝑳_blocks(bb, cfg, postdomtree) do postdominator::Int, 𝑰𝑵𝑭𝑳::BitSet
for blk in 𝑰𝑵𝑭𝑳
if blk == postdominator
continue # skip the post-dominator block and continue to a next infl block
end
if any(@view concretize[cfg.blocks[blk].stmts])
return true
end
end
return false
end
end

function visit_𝑰𝑵𝑭𝑳_blocks(func, bb::BasicBlock, cfg::CFG, postdomtree)
succ1, succ2 = bb.succs
postdominator = nearest_common_dominator(postdomtree, succ1, succ2)
𝑰𝑵𝑭𝑳 = reachable_blocks(cfg, succ1, postdominator) reachable_blocks(cfg, succ2, postdominator)
return func(postdominator, 𝑰𝑵𝑭𝑳)
end

function reachable_blocks(cfg::CFG, from_bb::Int, to_bb::Int)
worklist = Int[from_bb]
visited = BitSet(from_bb)
if to_bb == from_bb
return visited
end
push!(visited, to_bb)
function visit!(bb::Int)
if bb visited
push!(visited, bb)
push!(worklist, bb)
end
end
while !isempty(worklist)
foreach(visit!, cfg.blocks[pop!(worklist)].succs)
end
return visited
end

function add_required_inplace!(concretize::BitVector, src::CodeInfo, edges, cl)
changed = false
for i = 1:length(src.code)
Expand Down Expand Up @@ -1330,7 +1244,7 @@ function select_dependencies!(concretize::BitVector, src::CodeInfo, edges, cl)
while true
changed = false

# Discover Dtruct/method definitions at the beginning,
# Discover struct/method definitions at the beginning,
# and propagate the definition requirements by tracking SSA precedessors.
# (TODO maybe hoist this out of the loop?)
changed |= LoweredCodeUtils.add_typedefs!(concretize, src, edges, typedefs, ())
Expand All @@ -1349,65 +1263,18 @@ function select_dependencies!(concretize::BitVector, src::CodeInfo, edges, cl)
changed |= add_ssa_preds!(concretize, src, edges, ())

# Mark necessary control flows.
changed |= add_control_flow!(concretize, src, cfg, postdomtree)
changed |= LoweredCodeUtils.add_control_flow!(concretize, src, cfg, postdomtree)
changed |= add_ssa_preds!(concretize, src, edges, ())

changed || break
end

# now mark the active goto nodes
add_active_gotos!(concretize, src, cfg, postdomtree)
LoweredCodeUtils.add_active_gotos!(concretize, src, cfg, postdomtree)

nothing
end

function add_active_gotos!(concretize::BitVector, src::CodeInfo, cfg::CFG, postdomtree)
dead_blocks = compute_dead_blocks(concretize, src, cfg, postdomtree)
changed = false
for bbidx = 1:length(cfg.blocks)
if bbidx dead_blocks
bb = cfg.blocks[bbidx]
nsuccs = length(bb.succs)
if nsuccs == 1
termidx = bb.stmts[end]
if src.code[termidx] isa GotoNode
changed |= concretize[termidx] = true
end
end
end
end
return changed
end

# find dead blocks using the same approach as `add_control_flow!`, for the converged `concretize`
function compute_dead_blocks(concretize::BitVector, src::CodeInfo, cfg::CFG, postdomtree)
dead_blocks = BitSet()
for bbidx = 1:length(cfg.blocks)
bb = cfg.blocks[bbidx]
nsuccs = length(bb.succs)
if nsuccs == 2
termidx = bb.stmts[end]
@assert is_conditional_terminator(src.code[termidx]) "invalid IR"
visit_𝑰𝑵𝑭𝑳_blocks(bb, cfg, postdomtree) do postdominator::Int, 𝑰𝑵𝑭𝑳::BitSet
is_𝑰𝑵𝑭𝑳_active = false
for blk in 𝑰𝑵𝑭𝑳
if blk == postdominator
continue # skip the post-dominator block and continue to a next infl block
end
if any(@view concretize[cfg.blocks[blk].stmts])
is_𝑰𝑵𝑭𝑳_active |= true
break
end
end
if !is_𝑰𝑵𝑭𝑳_active
union!(dead_blocks, delete!(𝑰𝑵𝑭𝑳, postdominator))
end
end
end
end
return dead_blocks
end

function JuliaInterpreter.step_expr!(interp::ConcreteInterpreter, frame::Frame, @nospecialize(node), istoplevel::Bool)
@assert istoplevel "ConcreteInterpreter can only work for top-level code"

Expand Down
4 changes: 2 additions & 2 deletions test/toplevel/test_virtualprocess.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1781,8 +1781,8 @@ end
# A more complex test case (xref: https://github.com/JuliaDebug/LoweredCodeUtils.jl/pull/99#issuecomment-2236373067)
# This test case might seem simple at first glance, but note that `x2` and `a2` are
# defined at the top level (because of the `begin` at the top).
# Since global variable type declarations have been allowed since v1.
# 10, a conditional branch that includes `Core.get_binding_type` is generated for
# Since global variable type declarations have been allowed since v1.10,
# a conditional branch that includes `Core.get_binding_type` is generated for
# these simple global variable assignments.
# Specifically, the code is lowered into something like this:
# 1 1: conditional branching based on `x2`'s binding type
Expand Down

0 comments on commit 321fbd5

Please sign in to comment.