Skip to content
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
34 changes: 23 additions & 11 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,35 @@ jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
version:
- '1.6' # current LTS
- '1' # latest stable
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- x64
include:
- version: 'nightly'
- version: '1' # current stable
os: ubuntu-latest
arch: x64
allow_failures: true
- version: '1.10' # lowest version supported
os: ubuntu-latest
arch: x64
- version: '1.12-nightly' # next release
os: ubuntu-latest
arch: x64
- version: 'nightly' # dev
os: ubuntu-latest
arch: x64
#- version: '1' # x86 ubuntu -- disabled since PyCall/conda is broken on this platform
# os: ubuntu-latest
# arch: x86
- version: '1' # x86 windows
os: windows-latest
arch: x86
- version: '1' # x64 windows
os: windows-latest
arch: x64
- version: '1' # x64 macOS
os: macos-latest
arch: x64
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name = "LoweredCodeUtils"
uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b"
authors = ["Tim Holy <[email protected]>"]
version = "3.0.5"
version = "3.1.0"

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

[compat]
JuliaInterpreter = "0.9"
julia = "1.6"
julia = "1.10"

[extras]
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Expand Down
21 changes: 5 additions & 16 deletions src/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,10 @@ function print_names(io::IO, cl::CodeLinks)
end
end

const preprinter_sentinel = isdefined(Base.IRShow, :statementidx_lineinfo_printer) ? 0 : typemin(Int32)

function print_with_code(preprint, postprint, io::IO, src::CodeInfo)
src = copy(src)
JuliaInterpreter.replace_coretypes!(src; rev=true)
if isdefined(JuliaInterpreter, :reverse_lookup_globalref!)
JuliaInterpreter.reverse_lookup_globalref!(src.code)
end
JuliaInterpreter.reverse_lookup_globalref!(src.code)
io = IOContext(io,
:displaysize=>displaysize(io),
:SOURCE_SLOTNAMES => Base.sourceinfo_slotnames(src))
Expand Down Expand Up @@ -134,7 +130,7 @@ function print_with_code(preprint, postprint, io::IO, src::CodeInfo)
bb_idx_prev = bb_idx
end
max_bb_idx_size = ndigits(length(cfg.blocks))
line_info_preprinter(io, " "^(max_bb_idx_size + 2), preprinter_sentinel)
line_info_preprinter(io, " "^(max_bb_idx_size + 2), 0)
postprint(io)
return nothing
end
Expand Down Expand Up @@ -531,12 +527,7 @@ function print_with_code(io::IO, src::CodeInfo, edges::CodeEdges)
end
printstyled(io, "\nCode:\n", color=:yellow)
end
@static if isdefined(Base.IRShow, :show_ir_stmt)
preprint(::IO, ::Int) = nothing
else
nd = ndigits(length(src.code))
preprint(io::IO, i::Int) = print(io, lpad(i, nd), " ")
end
preprint(::IO, ::Int) = nothing
postprint(::IO) = nothing
postprint(io::IO, idx::Int, bbchanged::Bool) = postprint_lineedges(io, idx, edges, bbchanged)

Expand Down Expand Up @@ -1072,9 +1063,7 @@ end

function print_with_code(io::IO, frame::Frame, obj)
src = frame.framecode.src
if isdefined(JuliaInterpreter, :reverse_lookup_globalref!)
src = copy(src)
JuliaInterpreter.reverse_lookup_globalref!(src.code)
end
src = copy(src)
JuliaInterpreter.reverse_lookup_globalref!(src.code)
print_with_code(io, src, obj)
end
14 changes: 3 additions & 11 deletions src/packagedef.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel"))
@eval Base.Experimental.@optlevel 1
end
Base.Experimental.@optlevel 1

using Core: SimpleVector
using Core.IR
using Core.Compiler: construct_domtree, construct_postdomtree, nearest_common_dominator,
postdominates
using Base.Meta: isexpr

const SSAValues = Union{Core.Compiler.SSAValue, JuliaInterpreter.SSAValue}
Expand All @@ -17,14 +17,6 @@ export CodeEdges, lines_required, lines_required!, selective_eval!, selective_ev
include("utils.jl")
include("signatures.jl")
include("codeedges.jl")
if Base.VERSION < v"1.10"
include("domtree.jl")
else
const construct_domtree = Core.Compiler.construct_domtree
const construct_postdomtree = Core.Compiler.construct_postdomtree
const postdominates = Core.Compiler.postdominates
const nearest_common_dominator = Core.Compiler.nearest_common_dominator
end

# precompilation

Expand Down
57 changes: 7 additions & 50 deletions src/signatures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ function identify_framemethod_calls(frame)
mkey = normalize_defsig(refstmt, frame)
end
end
if is_global_ref(mkey, Core, isdefined(Core, :_apply_iterate) ? :_apply_iterate : :_apply)
if is_global_ref(mkey, Core, :_apply_iterate)
ssaref = mstmt.args[end-1]
if isa(ssaref, JuliaInterpreter.SSAValue)
id = ssaref.id
Expand Down Expand Up @@ -684,54 +684,11 @@ Return the "body method" for a method `m`. `mbody` contains the code of the func
when `m` was defined.
"""
function bodymethod(mkw::Method)
@static if isdefined(Core, :kwcall)
Base.unwrap_unionall(mkw.sig).parameters[1] !== typeof(Core.kwcall) && isempty(Base.kwarg_decl(mkw)) && return mkw
mths = methods(Base.bodyfunction(mkw))
if length(mths) != 1
@show mkw
display(mths)
end
return only(mths)
else
m = mkw
local src
while true
framecode = JuliaInterpreter.get_framecode(m)
fakeargs = Any[nothing for i = 1:(framecode.scope::Method).nargs]
frame = JuliaInterpreter.prepare_frame(framecode, fakeargs, isa(m.sig, UnionAll) ? sparam_ub(m) : Core.svec())
src = framecode.src
(length(src.code) > 1 && is_self_call(src.code[end-1], src.slotnames)) || break
# Build the optional arg, so we can get its type
pc = frame.pc
while pc < length(src.code) - 1
pc = step_expr!(frame)
end
val = pc > 1 ? frame.framedata.ssavalues[pc-1] : (src.code[1]::Expr).args[end]
sig = Tuple{(Base.unwrap_unionall(m.sig)::DataType).parameters..., typeof(val)}
m = whichtt(sig)
end
length(src.code) > 1 || return m
stmt = src.code[end-1]
if isexpr(stmt, :call) && (f = (stmt::Expr).args[1]; isa(f, QuoteNode))
if f.value === (isdefined(Core, :_apply_iterate) ? Core._apply_iterate : Core._apply)
ssaref = stmt.args[end-1]
if isa(ssaref, JuliaInterpreter.SSAValue)
id = ssaref.id
has_self_call(src, src.code[id]) || return m
end
f = stmt.args[end-2]
if isa(f, JuliaInterpreter.SSAValue)
f = src.code[f.id]
end
else
has_self_call(src, stmt) || return m
end
f = f.value
mths = methods(f)
if length(mths) == 1
return first(mths)
end
end
return m
Base.unwrap_unionall(mkw.sig).parameters[1] !== typeof(Core.kwcall) && isempty(Base.kwarg_decl(mkw)) && return mkw
mths = methods(Base.bodyfunction(mkw))
if length(mths) != 1
@show mkw
display(mths)
end
return only(mths)
end
22 changes: 8 additions & 14 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,13 @@ function isanonymous_typedef(stmt)
stmt = src.code[end-1]
isexpr(stmt, :call) || return false
is_global_ref(stmt.args[1], Core, :_typebody!) || return false
@static if VERSION ≥ v"1.9.0-DEV.391"
stmt = isa(stmt.args[3], Core.SSAValue) ? src.code[end-3] : src.code[end-2]
is_assignment_like(stmt) || return false
name = stmt.args[1]
if isa(name, GlobalRef)
name = name.name
else
isa(name, Symbol) || return false
end
stmt = isa(stmt.args[3], Core.SSAValue) ? src.code[end-3] : src.code[end-2]
is_assignment_like(stmt) || return false
name = stmt.args[1]
if isa(name, GlobalRef)
name = name.name
else
name = stmt.args[2]::Symbol
isa(name, Symbol) || return false
end
return startswith(String(name), "#")
end
Expand Down Expand Up @@ -176,11 +172,9 @@ function typedef_range(src::CodeInfo, idx)
if isa(stmt, Expr)
stmt.head === :global && break
if stmt.head === :call
if (is_global_ref(stmt.args[1], Core, :_typebody!) ||
isdefined(Core, :_typebody!) && is_quotenode_egal(stmt.args[1], Core._typebody!))
if (is_global_ref(stmt.args[1], Core, :_typebody!) || is_quotenode_egal(stmt.args[1], Core._typebody!))
have_typebody = true
elseif (is_global_ref(stmt.args[1], Core, :_equiv_typedef) ||
isdefined(Core, :_equiv_typedef) && is_quotenode_egal(stmt.args[1], Core._equiv_typedef))
elseif (is_global_ref(stmt.args[1], Core, :_equiv_typedef) || is_quotenode_egal(stmt.args[1], Core._equiv_typedef))
have_equivtypedef = true
# Advance to the type-assignment
while iend <= n
Expand Down
73 changes: 12 additions & 61 deletions test/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -344,17 +344,15 @@ module ModSelective end
idx = findfirst(@nospecialize(stmt)->Meta.isexpr(stmt, :(=)) && Meta.isexpr(stmt.args[2], :call) && is_global_ref(stmt.args[2].args[1], Core, :Box), src.code)
@test lr[idx]
# but make sure we don't break primitivetype & abstracttype (https://github.com/timholy/Revise.jl/pull/611)
if isdefined(Core, :_primitivetype)
thk = Meta.lower(Main, quote
primitive type WindowsRawSocket sizeof(Ptr) * 8 end
end)
src = thk.args[1]
edges = CodeEdges(Main, src)
idx = findfirst(istypedef, src.code)
r = LoweredCodeUtils.typedef_range(src, idx)
# 1 before :latestworld, 2 after
@test (length(src.code) - last(r)) in (1, 2)
end
thk = Meta.lower(Main, quote
primitive type WindowsRawSocket sizeof(Ptr) * 8 end
end)
src = thk.args[1]
edges = CodeEdges(Main, src)
idx = findfirst(istypedef, src.code)
r = LoweredCodeUtils.typedef_range(src, idx)
# 1 before :latestworld, 2 after
@test (length(src.code) - last(r)) in (1, 2)

@testset "Display" begin
# worth testing because this has proven quite crucial for debugging and
Expand All @@ -381,69 +379,22 @@ module ModSelective end
str = String(take!(io))
@test occursin(r"slot 1:\n preds: ssas: \[\d+, \d+\], slots: ∅, names: ∅;\n succs: ssas: \[\d+, \d+, \d+\], slots: ∅, names: ∅;\n assign @: \[\d+, \d+\]", str)
@test occursin(r"succs: ssas: ∅, slots: \[\d+\], names: ∅;", str)
# Some of these differ due to changes by Julia version in global var inference
if Base.VERSION < v"1.10"
@test occursin(r"s:\n preds: ssas: \[\d+\], slots: ∅, names: ∅;\n succs: ssas: \[\d+, \d+, \d+\], slots: ∅, names: ∅;\n assign @: \[\d, \d+\]", str) ||
occursin(r"s:\n preds: ssas: \[\d+, \d+\], slots: ∅, names: ∅;\n succs: ssas: \[\d+, \d+, \d+\], slots: ∅, names: ∅;\n assign @: \[\d, \d+\]", str) # with global var inference
end
if Base.VERSION < v"1.8"
@test occursin(r"\d+ preds: ssas: \[\d+\], slots: ∅, names: \[\:\(Main\.s\)\];\n\d+ succs: ssas: ∅, slots: ∅, names: \[\:\(Main\.s\)\];", str)
end
LoweredCodeUtils.print_with_code(io, src, cl)
str = String(take!(io))
if isdefined(Base.IRShow, :show_ir_stmt)
@test occursin(r"slot 1:\n preds: ssas: \[\d+, \d+\], slots: ∅, names: ∅;\n succs: ssas: \[\d+, \d+, \d+\], slots: ∅, names: ∅;\n assign @: \[\d+, \d+\]", str)
@test occursin("# see name Main.s", str)
@test occursin("# see slot 1", str)
if Base.VERSION < v"1.8" # changed by global var inference
@test occursin(r"# preds: ssas: \[\d+\], slots: ∅, names: \[\:\(Main\.s\)\]; succs: ssas: ∅, slots: ∅, names: \[\:\(Main\.s\)\];", str)
end
else
@test occursin("No IR statement printer", str)
end
@test occursin(r"slot 1:\n preds: ssas: \[\d+, \d+\], slots: ∅, names: ∅;\n succs: ssas: \[\d+, \d+, \d+\], slots: ∅, names: ∅;\n assign @: \[\d+, \d+\]", str)
@test occursin("# see name Main.s", str)
@test occursin("# see slot 1", str)
# CodeEdges
edges = CodeEdges(Main, src)
show(io, edges)
str = String(take!(io))
if Base.VERSION < v"1.10"
@test occursin(r"s: assigned on \[\d, \d+\], depends on \[\d+\], and used by \[\d+, \d+, \d+\]", str) ||
occursin(r"s: assigned on \[\d, \d+\], depends on \[\d+, \d+\], and used by \[\d+, \d+, \d+\]", str) # global var inference
end
if Base.VERSION < v"1.9"
@test (count(occursin("statement $i depends on [1, $(i-1), $(i+1)] and is used by [1, $(i+1)]", str) for i = 1:length(src.code)) == 1) ||
(count(occursin("statement $i depends on [4, $(i-1), $(i+4)] and is used by [$(i+2)]", str) for i = 1:length(src.code)) == 1)
end
LoweredCodeUtils.print_with_code(io, src, edges)
str = String(take!(io))
if isdefined(Base.IRShow, :show_ir_stmt)
if Base.VERSION < v"1.10"
@test occursin(r"s: assigned on \[\d, \d+\], depends on \[\d+\], and used by \[\d+, \d+, \d+\]", str) ||
occursin(r"s: assigned on \[\d, \d+\], depends on \[\d+, \d+\], and used by \[\d+, \d+, \d+\]", str)
end
if Base.VERSION < v"1.9"
@test (count(occursin("preds: [1, $(i-1), $(i+1)], succs: [1, $(i+1)]", str) for i = 1:length(src.code)) == 1) ||
(count(occursin("preds: [4, $(i-1), $(i+4)], succs: [$(i+2)]", str) for i = 1:length(src.code)) == 1) # global var inference
end
else
@test occursin("No IR statement printer", str)
end
# Works with Frames too
frame = Frame(ModSelective, ex)
edges = CodeEdges(ModSelective, frame.framecode.src)
LoweredCodeUtils.print_with_code(io, frame, edges)
str = String(take!(io))
if isdefined(Base.IRShow, :show_ir_stmt)
if Base.VERSION < v"1.10"
@test occursin(r"s: assigned on \[\d, \d+\], depends on \[\d+\], and used by \[\d+, \d+, \d+\]", str) ||
occursin(r"s: assigned on \[\d, \d+\], depends on \[\d, \d+\], and used by \[\d+, \d+, \d+\]", str) # global var inference
end
if Base.VERSION < v"1.9"
@test (count(occursin("preds: [1, $(i-1), $(i+1)], succs: [1, $(i+1)]", str) for i = 1:length(src.code)) == 1) ||
(count(occursin("preds: [4, $(i-1), $(i+4)], succs: [$(i+2)]", str) for i = 1:length(src.code)) == 1) # global var inference
end
else
@test occursin("No IR statement printer", str)
end

# display slot names
ex = :(let
Expand Down
9 changes: 2 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ using Test
# end

@testset "LoweredCodeUtils.jl" begin
@static if VERSION ≥ v"1.8"
@testset "signatures.jl" include("signatures.jl")
@testset "codeedges.jl" include("codeedges.jl")
else
include("signatures.jl")
include("codeedges.jl")
end
@testset "signatures.jl" include("signatures.jl")
@testset "codeedges.jl" include("codeedges.jl")
end
6 changes: 1 addition & 5 deletions test/signatures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5
rename_framemethods!(frame)
empty!(signatures)
methoddefs!(signatures, frame; define=false)
@test length(signatures) >= 3 - isdefined(Core, :kwcall)
@test length(signatures) >= 2

ex = :(typedsig(x) = 1)
frame = Frame(Lowering, ex)
Expand Down Expand Up @@ -353,10 +353,6 @@ bodymethtest5(x, y=Dict(1=>2)) = 5
@test dct[ks[1]] == dct[ks[2]]
@test ks[1].mod === ks[2].mod === Lowering
@test isdefined(Lowering, ks[1].name) || isdefined(Lowering, ks[2].name)
if !isdefined(Core, :kwcall)
nms = filter(sym->occursin(r"#Items#\d+#\d+", String(sym)), names(Lowering; all=true))
@test length(nms) == 1
end

# https://github.com/timholy/Revise.jl/issues/422
ex = :(@generated function fneg(x::T) where T<:LT{<:FloatingTypes}
Expand Down
Loading