Skip to content

Commit 4612349

Browse files
authored
refactor lines_required! (#65)
This refactoring will make it more easier to reuse various components involved with `lines_required!`. My motivation is aviatesk/JET.jl#196, where I want to implement another version of `lines_required!`, which will be more suitable for JET's top-level analysis (e.g. it may not respect control-flow in most cases as opposed to `lines_required!`). This change doesn't change any functionality, and thus I'd like to bump minor version. I confirmed Revise.jl passes all its test cases with these changes.
1 parent a074755 commit 4612349

File tree

2 files changed

+163
-126
lines changed

2 files changed

+163
-126
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.0.0"
4+
version = "2.1.0"
55

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

src/codeedges.jl

Lines changed: 162 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ Analyze `src` and determine the chain of dependencies.
394394
- `edges.preds[i]` lists the preceding statements that statement `i` depends on.
395395
- `edges.succs[i]` lists the succeeding statements that depend on statement `i`.
396396
- `edges.byname[v]` returns information about the predecessors, successors, and assignment statements
397-
for an object `v::Union{Symbol,GlobalRef}`.
397+
for an object `v::$NamedVar`.
398398
"""
399399
function CodeEdges(src::CodeInfo)
400400
src.inferred && error("supply lowered but not inferred code")
@@ -557,7 +557,7 @@ function terminal_preds(i::Int, edges::CodeEdges)
557557
end
558558

559559
"""
560-
isrequired = lines_required(obj::Union{Symbol,GlobalRef}, src::CodeInfo, edges::CodeEdges)
560+
isrequired = lines_required(obj::$NamedVar, src::CodeInfo, edges::CodeEdges)
561561
isrequired = lines_required(idx::Int, src::CodeInfo, edges::CodeEdges)
562562
563563
Determine which lines might need to be executed to evaluate `obj` or the statement indexed by `idx`.
@@ -567,16 +567,16 @@ will end up skipping a subset of such statements, perhaps while repeating others
567567
568568
See also [`lines_required!`](@ref) and [`selective_eval!`](@ref).
569569
"""
570-
function lines_required(obj::Union{Symbol,GlobalRef}, src::CodeInfo, edges::CodeEdges; kwargs...)
570+
function lines_required(obj::NamedVar, src::CodeInfo, edges::CodeEdges; kwargs...)
571571
isrequired = falses(length(edges.preds))
572-
objs = Set{Union{Symbol,GlobalRef}}([obj])
572+
objs = Set{NamedVar}([obj])
573573
return lines_required!(isrequired, objs, src, edges; kwargs...)
574574
end
575575

576576
function lines_required(idx::Int, src::CodeInfo, edges::CodeEdges; kwargs...)
577577
isrequired = falses(length(edges.preds))
578578
isrequired[idx] = true
579-
objs = Set{Union{Symbol,GlobalRef}}()
579+
objs = Set{NamedVar}()
580580
return lines_required!(isrequired, objs, src, edges; kwargs...)
581581
end
582582

@@ -594,7 +594,7 @@ For example, use `norequire = LoweredCodeUtils.exclude_named_typedefs(src, edges
594594
extracting method signatures and not evaluating new definitions.
595595
"""
596596
function lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges; kwargs...)
597-
objs = Set{Union{Symbol,GlobalRef}}()
597+
objs = Set{NamedVar}()
598598
return lines_required!(isrequired, objs, src, edges; kwargs...)
599599
end
600600

@@ -616,143 +616,68 @@ function exclude_named_typedefs(src::CodeInfo, edges::CodeEdges)
616616
end
617617

618618
function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo, edges::CodeEdges; norequire = ())
619-
# Do a traveral of "numbered" predecessors
620-
# We'll mostly use generic graph traversal to discover all the lines we need,
621-
# but structs are in a bit of a different category (especially on Julia 1.5+).
622-
# It's easiest to discover these at the beginning.
623-
typedef_blocks, typedef_names = UnitRange{Int}[], Symbol[]
624-
i = 1
625-
nstmts = length(src.code)
626-
while i <= nstmts
627-
stmt = rhs(src.code[i])
628-
if istypedef(stmt) && !isanonymous_typedef(stmt::Expr)
629-
stmt = stmt::Expr
630-
r = typedef_range(src, i)
631-
push!(typedef_blocks, r)
632-
name = stmt.head === :call ? stmt.args[3] : stmt.args[1]
633-
if isa(name, QuoteNode)
634-
name = name.value
635-
end
636-
isa(name, Symbol) || @show src i r stmt
637-
push!(typedef_names, name::Symbol)
638-
i = last(r)+1
639-
else
640-
i += 1
641-
end
642-
end
643-
644619
# Mark any requested objects (their lines of assignment)
645-
objsnew = Set{Union{Symbol,GlobalRef}}()
646-
for obj in objs
647-
add_obj!(isrequired, objsnew, obj, edges, norequire)
648-
end
649-
objs = objsnew
620+
objs = add_requests!(isrequired, objs, edges, norequire)
650621

651622
# Compute basic blocks, which we'll use to make sure we mark necessary control-flow
652-
bbs = Core.Compiler.compute_basic_blocks(src.code) # needed for control-flow analysis
653-
nblocks = length(bbs.blocks)
623+
cfg = Core.Compiler.compute_basic_blocks(src.code) # needed for control-flow analysis
654624

655-
changed::Bool = true
625+
# We'll mostly use generic graph traversal to discover all the lines we need,
626+
# but structs are in a bit of a different category (especially on Julia 1.5+).
627+
# It's easiest to discover these at the beginning.
628+
typedefs = find_typedefs(src)
629+
630+
changed = true
656631
iter = 0
657632
while changed
658633
changed = false
659634

660635
# Handle ssa predecessors
661-
for idx = 1:nstmts
662-
if isrequired[idx]
663-
changed |= add_preds!(isrequired, idx, edges, norequire)
664-
end
665-
end
636+
changed |= add_ssa_preds!(isrequired, src, edges, norequire)
666637

667638
# Handle named dependencies
668-
for (obj, uses) in edges.byname
669-
obj objs && continue
670-
if any(view(isrequired, uses.succs))
671-
changed |= add_obj!(isrequired, objs, obj, edges, norequire)
672-
end
673-
end
639+
changed |= add_named_dependencies!(isrequired, edges, objs, norequire)
674640

675-
# Add control-flow. For any basic block with an evaluated statement inside it,
676-
# check to see if the block has any successors, and if so mark that block's exit statement.
677-
# Likewise, any preceding blocks should have *their* exit statement marked.
678-
for (ibb, bb) in enumerate(bbs.blocks)
679-
r = rng(bb)
680-
if any(view(isrequired, r))
681-
if ibb != nblocks
682-
idxlast = r[end]
683-
idxlast norequire && continue
684-
changed |= !isrequired[idxlast]
685-
isrequired[idxlast] = true
686-
end
687-
for ibbp in bb.preds
688-
ibbp > 0 || continue # see Core.Compiler.compute_basic_blocks, near comment re :enter
689-
rpred = rng(bbs.blocks[ibbp])
690-
idxlast = rpred[end]
691-
idxlast norequire && continue
692-
changed |= !isrequired[idxlast]
693-
isrequired[idxlast] = true
694-
end
695-
for ibbs in bb.succs
696-
ibbs == nblocks && continue
697-
rpred = rng(bbs.blocks[ibbs])
698-
idxlast = rpred[end]
699-
idxlast norequire && continue
700-
changed |= !isrequired[idxlast]
701-
isrequired[idxlast] = true
702-
end
703-
end
704-
end
641+
# Add control-flow
642+
changed |= add_control_flow!(isrequired, cfg, norequire)
643+
644+
# So far, everything is generic graph traversal. Now we add some domain-specific information
645+
changed |= add_typedefs!(isrequired, src, edges, typedefs, norequire)
705646

706-
# So far, everything is generic graph traversal. Now we add some domain-specific information.
707-
# New struct definitions, including their constructors, get spread out over many
708-
# statements. If we're evaluating any of them, it's important to evaluate *all* of them.
709-
idx = 1
710-
while idx < length(src.code)
711-
stmt = src.code[idx]
712-
isrequired[idx] || (idx += 1; continue)
713-
for (typedefr, typedefn) in zip(typedef_blocks, typedef_names)
714-
if idx typedefr
715-
ireq = view(isrequired, typedefr)
716-
if !all(ireq)
717-
changed = true
718-
ireq .= true
719-
# Also mark any by-type constructor(s) associated with this typedef
720-
var = get(edges.byname, typedefn, nothing)
721-
if var !== nothing
722-
for s in var.succs
723-
s norequire && continue
724-
stmt2 = src.code[s]
725-
if isexpr(stmt2, :method) && (fname = (stmt2::Expr).args[1]; fname === false || fname === nothing)
726-
isrequired[s] = true
727-
end
728-
end
729-
end
730-
end
731-
idx = last(typedefr) + 1
732-
continue
733-
end
734-
end
735-
# Anonymous functions may not yet include the method definition
736-
if isanonymous_typedef(stmt)
737-
i = idx + 1
738-
while i <= length(src.code) && !ismethod3(src.code[i])
739-
i += 1
740-
end
741-
if i <= length(src.code) && (src.code[i]::Expr).args[1] == false
742-
tpreds = terminal_preds(i, edges)
743-
if minimum(tpreds) == idx && i norequire
744-
changed |= !isrequired[i]
745-
isrequired[i] = true
746-
end
747-
end
748-
end
749-
idx += 1
750-
end
751647
iter += 1 # just for diagnostics
752648
end
753649
return isrequired
754650
end
755651

652+
function add_requests!(isrequired, objs, edges::CodeEdges, norequire)
653+
objsnew = Set{NamedVar}()
654+
for obj in objs
655+
add_obj!(isrequired, objsnew, obj, edges, norequire)
656+
end
657+
return objsnew
658+
end
659+
660+
function add_ssa_preds!(isrequired, src::CodeInfo, edges::CodeEdges, norequire)
661+
changed = false
662+
for idx = 1:length(src.code)
663+
if isrequired[idx]
664+
changed |= add_preds!(isrequired, idx, edges, norequire)
665+
end
666+
end
667+
return changed
668+
end
669+
670+
function add_named_dependencies!(isrequired, edges::CodeEdges, objs, norequire)
671+
changed = false
672+
for (obj, uses) in edges.byname
673+
obj objs && continue
674+
if any(view(isrequired, uses.succs))
675+
changed |= add_obj!(isrequired, objs, obj, edges, norequire)
676+
end
677+
end
678+
return changed
679+
end
680+
756681
function add_preds!(isrequired, idx, edges::CodeEdges, norequire)
757682
chngd = false
758683
preds = edges.preds[idx]
@@ -788,6 +713,118 @@ function add_obj!(isrequired, objs, obj, edges::CodeEdges, norequire)
788713
return chngd
789714
end
790715

716+
# Add control-flow. For any basic block with an evaluated statement inside it,
717+
# check to see if the block has any successors, and if so mark that block's exit statement.
718+
# Likewise, any preceding blocks should have *their* exit statement marked.
719+
function add_control_flow!(isrequired, cfg, norequire)
720+
changed = false
721+
blocks = cfg.blocks
722+
nblocks = length(blocks)
723+
for (ibb, bb) in enumerate(blocks)
724+
r = rng(bb)
725+
if any(view(isrequired, r))
726+
if ibb != nblocks
727+
idxlast = r[end]
728+
idxlast norequire && continue
729+
changed |= !isrequired[idxlast]
730+
isrequired[idxlast] = true
731+
end
732+
for ibbp in bb.preds
733+
ibbp > 0 || continue # see Core.Compiler.compute_basic_blocks, near comment re :enter
734+
rpred = rng(blocks[ibbp])
735+
idxlast = rpred[end]
736+
idxlast norequire && continue
737+
changed |= !isrequired[idxlast]
738+
isrequired[idxlast] = true
739+
end
740+
for ibbs in bb.succs
741+
ibbs == nblocks && continue
742+
rpred = rng(blocks[ibbs])
743+
idxlast = rpred[end]
744+
idxlast norequire && continue
745+
changed |= !isrequired[idxlast]
746+
isrequired[idxlast] = true
747+
end
748+
end
749+
end
750+
return changed
751+
end
752+
753+
# Do a traveral of "numbered" predecessors and find statement ranges and names of type definitions
754+
function find_typedefs(src::CodeInfo)
755+
typedef_blocks, typedef_names = UnitRange{Int}[], Symbol[]
756+
i = 1
757+
nstmts = length(src.code)
758+
while i <= nstmts
759+
stmt = rhs(src.code[i])
760+
if istypedef(stmt) && !isanonymous_typedef(stmt::Expr)
761+
stmt = stmt::Expr
762+
r = typedef_range(src, i)
763+
push!(typedef_blocks, r)
764+
name = stmt.head === :call ? stmt.args[3] : stmt.args[1]
765+
if isa(name, QuoteNode)
766+
name = name.value
767+
end
768+
isa(name, Symbol) || @show src i r stmt
769+
push!(typedef_names, name::Symbol)
770+
i = last(r)+1
771+
else
772+
i += 1
773+
end
774+
end
775+
return typedef_blocks, typedef_names
776+
end
777+
778+
# New struct definitions, including their constructors, get spread out over many
779+
# statements. If we're evaluating any of them, it's important to evaluate *all* of them.
780+
function add_typedefs!(isrequired, src::CodeInfo, edges::CodeEdges, (typedef_blocks, typedef_names), norequire)
781+
changed = false
782+
stmts = src.code
783+
idx = 1
784+
while idx < length(stmts)
785+
stmt = stmts[idx]
786+
isrequired[idx] || (idx += 1; continue)
787+
for (typedefr, typedefn) in zip(typedef_blocks, typedef_names)
788+
if idx typedefr
789+
ireq = view(isrequired, typedefr)
790+
if !all(ireq)
791+
changed = true
792+
ireq .= true
793+
# Also mark any by-type constructor(s) associated with this typedef
794+
var = get(edges.byname, typedefn, nothing)
795+
if var !== nothing
796+
for s in var.succs
797+
s norequire && continue
798+
stmt2 = stmts[s]
799+
if isexpr(stmt2, :method) && (fname = (stmt2::Expr).args[1]; fname === false || fname === nothing)
800+
isrequired[s] = true
801+
end
802+
end
803+
end
804+
end
805+
idx = last(typedefr) + 1
806+
continue
807+
end
808+
end
809+
# Anonymous functions may not yet include the method definition
810+
if isanonymous_typedef(stmt)
811+
i = idx + 1
812+
while i <= length(stmts) && !ismethod3(stmts[i])
813+
i += 1
814+
end
815+
if i <= length(stmts) && (stmts[i]::Expr).args[1] == false
816+
tpreds = terminal_preds(i, edges)
817+
if minimum(tpreds) == idx && i norequire
818+
changed |= !isrequired[i]
819+
isrequired[i] = true
820+
end
821+
end
822+
end
823+
idx += 1
824+
end
825+
return changed
826+
end
827+
791828
"""
792829
selective_eval!([recurse], frame::Frame, isrequired::AbstractVector{Bool}, istoplevel=false)
793830

0 commit comments

Comments
 (0)