diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 603cab8..5a37b1e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -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 diff --git a/Project.toml b/Project.toml index 9edcd5a..a9616c2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,14 +1,14 @@ name = "LoweredCodeUtils" uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" authors = ["Tim Holy "] -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" diff --git a/src/codeedges.jl b/src/codeedges.jl index c8e9ac0..677ef9b 100644 --- a/src/codeedges.jl +++ b/src/codeedges.jl @@ -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)) @@ -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 @@ -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) @@ -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 diff --git a/src/packagedef.jl b/src/packagedef.jl index 31aac59..7930601 100644 --- a/src/packagedef.jl +++ b/src/packagedef.jl @@ -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} @@ -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 diff --git a/src/signatures.jl b/src/signatures.jl index 0c399b1..2e77c8e 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -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 @@ -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 diff --git a/src/utils.jl b/src/utils.jl index 71b8c8c..21ca245 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -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 @@ -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 diff --git a/test/codeedges.jl b/test/codeedges.jl index c1954d4..9317342 100644 --- a/test/codeedges.jl +++ b/test/codeedges.jl @@ -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 @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index c0f5203..9721a39 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 diff --git a/test/signatures.jl b/test/signatures.jl index 4c0e92e..8adb2b8 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -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) @@ -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}