Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use the CFG selection algorithm implememted in LCU v3.0.2 #662

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading