diff --git a/Project.toml b/Project.toml index e8d6b6f9a..00abd5ae6 100644 --- a/Project.toml +++ b/Project.toml @@ -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" diff --git a/src/JET.jl b/src/JET.jl index 56385ba0b..9f2d60e39 100644 --- a/src/JET.jl +++ b/src/JET.jl @@ -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, diff --git a/src/toplevel/virtualprocess.jl b/src/toplevel/virtualprocess.jl index 2ff81d317..c19f21886 100644 --- a/src/toplevel/virtualprocess.jl +++ b/src/toplevel/virtualprocess.jl @@ -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) @@ -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, ()) @@ -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" diff --git a/test/toplevel/test_virtualprocess.jl b/test/toplevel/test_virtualprocess.jl index dabea6a87..e73d9e75d 100644 --- a/test/toplevel/test_virtualprocess.jl +++ b/test/toplevel/test_virtualprocess.jl @@ -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