Skip to content

Commit a074755

Browse files
authored
retry #62, make lines_required!'s norequire logic more configurable: (#64)
1 parent 84becf2 commit a074755

File tree

3 files changed

+35
-16
lines changed

3 files changed

+35
-16
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 = "1.4.0"
4+
version = "2.0.0"
55

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

src/codeedges.jl

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -577,34 +577,52 @@ function lines_required(idx::Int, src::CodeInfo, edges::CodeEdges; kwargs...)
577577
isrequired = falses(length(edges.preds))
578578
isrequired[idx] = true
579579
objs = Set{Union{Symbol,GlobalRef}}()
580-
return lines_required!(isrequired, src, edges; kwargs...)
580+
return lines_required!(isrequired, objs, src, edges; kwargs...)
581581
end
582582

583583
"""
584-
lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges; exclude_named_typedefs::Bool=false)
584+
lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges;
585+
norequire = ())
585586
586587
Like `lines_required`, but where `isrequired[idx]` has already been set to `true` for all statements
587588
that you know you need to evaluate. All other statements should be marked `false` at entry.
588589
On return, the complete set of required statements will be marked `true`.
589590
590-
Use `exclude_named_typedefs=true` if you're extracting method signatures and not evaluating new definitions.
591+
`norequire` keyword argument specifies statements (represented as iterator of `Int`s) that
592+
should _not_ be marked as a requirement.
593+
For example, use `norequire = LoweredCodeUtils.exclude_named_typedefs(src, edges)` if you're
594+
extracting method signatures and not evaluating new definitions.
591595
"""
592596
function lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges; kwargs...)
593597
objs = Set{Union{Symbol,GlobalRef}}()
594598
return lines_required!(isrequired, objs, src, edges; kwargs...)
595599
end
596600

597-
function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo, edges::CodeEdges; exclude_named_typedefs::Bool=false)
601+
function exclude_named_typedefs(src::CodeInfo, edges::CodeEdges)
602+
norequire = BitSet()
603+
i = 1
604+
nstmts = length(src.code)
605+
while i <= nstmts
606+
stmt = rhs(src.code[i])
607+
if istypedef(stmt) && !isanonymous_typedef(stmt::Expr)
608+
r = typedef_range(src, i)
609+
pushall!(norequire, r)
610+
i = last(r)+1
611+
else
612+
i += 1
613+
end
614+
end
615+
return norequire
616+
end
617+
618+
function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo, edges::CodeEdges; norequire = ())
598619
# Do a traveral of "numbered" predecessors
599620
# We'll mostly use generic graph traversal to discover all the lines we need,
600621
# but structs are in a bit of a different category (especially on Julia 1.5+).
601622
# It's easiest to discover these at the beginning.
602-
# Moreover, if we're excluding named type definitions, we'll add them to `norequire`
603-
# to prevent them from being marked.
604623
typedef_blocks, typedef_names = UnitRange{Int}[], Symbol[]
605-
norequire = BitSet()
606-
nstmts = length(src.code)
607624
i = 1
625+
nstmts = length(src.code)
608626
while i <= nstmts
609627
stmt = rhs(src.code[i])
610628
if istypedef(stmt) && !isanonymous_typedef(stmt::Expr)
@@ -618,9 +636,6 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
618636
isa(name, Symbol) || @show src i r stmt
619637
push!(typedef_names, name::Symbol)
620638
i = last(r)+1
621-
if exclude_named_typedefs && !isanonymous_typedef(stmt)
622-
pushall!(norequire, r)
623-
end
624639
else
625640
i += 1
626641
end
@@ -641,19 +656,22 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
641656
iter = 0
642657
while changed
643658
changed = false
659+
644660
# Handle ssa predecessors
645661
for idx = 1:nstmts
646662
if isrequired[idx]
647663
changed |= add_preds!(isrequired, idx, edges, norequire)
648664
end
649665
end
666+
650667
# Handle named dependencies
651668
for (obj, uses) in edges.byname
652669
obj objs && continue
653670
if any(view(isrequired, uses.succs))
654671
changed |= add_obj!(isrequired, objs, obj, edges, norequire)
655672
end
656673
end
674+
657675
# Add control-flow. For any basic block with an evaluated statement inside it,
658676
# check to see if the block has any successors, and if so mark that block's exit statement.
659677
# Likewise, any preceding blocks should have *their* exit statement marked.
@@ -684,6 +702,7 @@ function lines_required!(isrequired::AbstractVector{Bool}, objs, src::CodeInfo,
684702
end
685703
end
686704
end
705+
687706
# So far, everything is generic graph traversal. Now we add some domain-specific information.
688707
# New struct definitions, including their constructors, get spread out over many
689708
# statements. If we're evaluating any of them, it's important to evaluate *all* of them.
@@ -833,7 +852,7 @@ Mark each line of code with its requirement status.
833852
function print_with_code(io::IO, src::CodeInfo, isrequired::AbstractVector{Bool})
834853
nd = ndigits(length(isrequired))
835854
preprint(::IO) = nothing
836-
preprint(io::IO, idx::Int) = print(io, lpad(idx, nd), ' ', isrequired[idx] ? "t " : "f ")
855+
preprint(io::IO, idx::Int) = (c = isrequired[idx]; printstyled(io, lpad(idx, nd), ' ', c ? "t " : "f "; color = c ? :cyan : :plain))
837856
postprint(::IO) = nothing
838857
postprint(io::IO, idx::Int, bbchanged::Bool) = nothing
839858

test/codeedges.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using LoweredCodeUtils
22
using LoweredCodeUtils.JuliaInterpreter
3-
using LoweredCodeUtils: callee_matches, istypedef
3+
using LoweredCodeUtils: callee_matches, istypedef, exclude_named_typedefs
44
using JuliaInterpreter: is_global_ref, is_quotenode
55
using Test
66

@@ -262,7 +262,7 @@ end
262262
frame = Frame(ModEval, ex)
263263
src = frame.framecode.src
264264
edges = CodeEdges(src)
265-
isrequired = minimal_evaluation(stmt->(LoweredCodeUtils.ismethod3(stmt),false), src, edges; exclude_named_typedefs=true) # initially mark only the constructor
265+
isrequired = minimal_evaluation(stmt->(LoweredCodeUtils.ismethod3(stmt),false), src, edges; norequire=exclude_named_typedefs(src, edges)) # initially mark only the constructor
266266
bbs = Core.Compiler.compute_basic_blocks(src.code)
267267
for (iblock, block) in enumerate(bbs.blocks)
268268
r = LoweredCodeUtils.rng(block)
@@ -301,7 +301,7 @@ end
301301
src = thk.args[1]
302302
edges = CodeEdges(src)
303303
idx = findfirst(stmt->Meta.isexpr(stmt, :method), src.code)
304-
lr = lines_required(idx, src, edges; exclude_named_typedefs=true)
304+
lr = lines_required(idx, src, edges; norequire=exclude_named_typedefs(src, edges))
305305
idx = findfirst(stmt->Meta.isexpr(stmt, :(=)) && Meta.isexpr(stmt.args[2], :call) && is_global_ref(stmt.args[2].args[1], Core, :Box), src.code)
306306
@test lr[idx]
307307
# but make sure we don't break primitivetype & abstracttype (https://github.com/timholy/Revise.jl/pull/611)

0 commit comments

Comments
 (0)