From e79f16db37e3b0c7cc9410899edc9099f864cea9 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 17 Mar 2025 05:43:25 -0500 Subject: [PATCH 1/4] Fix tests for inverted linetable https://github.com/JuliaLang/julia/pull/52415 introduced a fundamental change in how line information is encoded. Because this package delves into internals, there were a couple tests that failed on Julia 1.12+. This adds a utility, `CodeTracking.linetable_scopes`, that makes it easier to work with the new representation across Julia versions. --- src/CodeTracking.jl | 2 +- src/utils.jl | 23 ++++++++++++++++++++++- test/runtests.jl | 10 +++++----- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/CodeTracking.jl b/src/CodeTracking.jl index 8172f7a..585c2ec 100644 --- a/src/CodeTracking.jl +++ b/src/CodeTracking.jl @@ -98,7 +98,7 @@ Otherwise `loc` will be `(filepath, line)`. """ function whereis(sf::StackTraces.StackFrame) sf.linfo === nothing && return nothing - return whereis(sf, sf.linfo.def) + return whereis(sf, getmethod(sf.linfo)) end """ diff --git a/src/utils.jl b/src/utils.jl index bd1c641..9df51b1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -179,7 +179,7 @@ function linerange(def::Expr) end linerange(arg) = linerange(convert(Expr, arg)) # Handle Revise's RelocatableExpr -function findline(ex, order) +function findline(ex::Expr, order) ex.head === :line && return ex.args[1], true for a in order(ex.args) a isa LineNumberNode && return a.line, true @@ -194,6 +194,27 @@ end fileline(lin::LineInfoNode) = String(lin.file), lin.line fileline(lnn::LineNumberNode) = String(lnn.file), lnn.line +if VERSION ≥ v"1.12.0-DEV.173" # https://github.com/JuliaLang/julia/pull/52415 + function linetable_scopes(m::Method) + src = Base.uncompressed_ast(m) + lts = [Vector{Base.Compiler.IRShow.LineInfoNode}() for _ = eachindex(src.code)] + for pc = eachindex(src.code) + Base.IRShow.append_scopes!(lts[pc], pc, src.debuginfo, m) + end + return lts + end +else + function linetable_scopes(m::Method) + src = Base.uncompressed_ast(m) + lt, cl = src.linetable, src.codelocs + return [iszero(cl[pc]) ? Core.LineInfoNode[] : [lt[cl[pc]]] for pc = eachindex(cl)] + end +end + +getmethod(m::Method) = m +getmethod(mi::Core.MethodInstance) = getmethod(mi.def) +getmethod(ci::Core.CodeInstance) = getmethod(ci.def) + # This regex matches the pseudo-file name of a REPL history entry. const rREPL = r"^REPL\[(\d+)\]$" # Match anonymous function names diff --git a/test/runtests.jl b/test/runtests.jl index 5d31bee..c448802 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -129,13 +129,13 @@ isdefined(Main, :Revise) ? Main.Revise.includet("script.jl") : include("script.j @info "hello" end m = first(methods(f150)) - src = Base.uncompressed_ast(m) - idx = findfirst(lin -> String(lin.file) == @__FILE__, src.linetable) - lin = src.linetable[idx] + scopes = CodeTracking.linetable_scopes(m) + idx = findfirst(sc -> all(lin -> String(lin.file) == @__FILE__, sc), scopes) + lin = first(scopes[idx]) file, line = whereis(lin, m) @test endswith(file, String(lin.file)) - idx = findfirst(lin -> String(lin.file) != @__FILE__, src.linetable) - lin = src.linetable[idx] + idx = findfirst(sc -> !all(lin -> String(lin.file) == @__FILE__, sc), scopes) + lin = first(scopes[idx]) file, line = whereis(lin, m) if !Sys.iswindows() @test endswith(file, String(lin.file)) From 29be60268cc9ea400a9b162161099eda2fc3e258 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 17 Mar 2025 05:51:48 -0500 Subject: [PATCH 2/4] Use Revise release in tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e30b022..e5faa5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: julia -e ' using Pkg Pkg.develop(path=".") - Pkg.add(url="https://github.com/timholy/Revise.jl") + Pkg.add("Revise") Pkg.test("Revise") ' - name: Test while running Revise From 6546d8580c0c6961a65e409532aa5b8997fc9244 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 17 Mar 2025 06:43:52 -0500 Subject: [PATCH 3/4] Add docs, fix REPL&Revise --- .github/workflows/ci.yml | 9 +++++++-- src/utils.jl | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5faa5b..4ea7f29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,8 +49,13 @@ jobs: TERM="xterm" julia --project -i --code-coverage -e ' using InteractiveUtils, REPL, Revise, Pkg Pkg.add("ColorTypes") - @async(Base.run_main_repl(true, true, false, true, false)) - sleep(2) + t = @async( + VERSION >= v"1.12.0-DEV.612" ? Base.run_main_repl(true, true, :no, true) : + VERSION >= v"1.11.0-DEV.222" ? Base.run_main_repl(true, true, :no, true, false) : + Base.run_main_repl(true, true, false, true, false)) + isdefined(Base, :errormonitor) && Base.errormonitor(t) + while (!isdefined(Base, :active_repl_backend) || isnothing(Base.active_repl_backend)) sleep(0.1) end + pushfirst!(Base.active_repl_backend.ast_transforms, Revise.revise_first) cd("test") include("runtests.jl") if Base.VERSION.major == 1 && Base.VERSION.minor >= 9 diff --git a/src/utils.jl b/src/utils.jl index 9df51b1..2610018 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -210,6 +210,21 @@ else return [iszero(cl[pc]) ? Core.LineInfoNode[] : [lt[cl[pc]]] for pc = eachindex(cl)] end end +@doc """ + scopes = linetable_scopes(m::Method) + +Return an array of "scopes" for each statement in the lowered code for `m`. +If `src = Base.uncompressed_ast(m)`, then `scopes[pc]` is an vector of `LineInfoNode` +objects that represent the scopes active at the statement at position `pc` in `src.code`. + +On Julia 1.12 and later, `scopes[pc]` may have length larger than 1, where the first entry +is for the source location in `m`, and any later entries reflect code from inlining. +It will be a vector of `Base.Compiler.IRShow.LineInfoNode` objects. + +Prior to Julia 1.12, `scopes[pc]` will have length 1, and will be a vector of `Core.LineInfoNode` +objects (which have more fields than `Base.Compiler.IRShow.LineInfoNode`). It will represent +the final stage of inlining for the statement at position `pc` in `src.code`. +""" linetable_scopes getmethod(m::Method) = m getmethod(mi::Core.MethodInstance) = getmethod(mi.def) From 0567d229e5ca65a9b2b6b48d9f6672384da534e5 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 17 Mar 2025 07:14:13 -0500 Subject: [PATCH 4/4] Fill out list on older Julia versions --- src/utils.jl | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 2610018..0d69bdb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -207,23 +207,36 @@ else function linetable_scopes(m::Method) src = Base.uncompressed_ast(m) lt, cl = src.linetable, src.codelocs - return [iszero(cl[pc]) ? Core.LineInfoNode[] : [lt[cl[pc]]] for pc = eachindex(cl)] + lts = [Vector{Core.LineInfoNode}() for _ = eachindex(src.code)] + for pc = eachindex(src.code) + iszero(cl[pc]) && continue + scope = lts[pc] + push!(scope, lt[cl[pc]]) + while (k = last(scope).inlined_at) != Int32(0) + push!(scope, lt[k]) + end + if length(scope) > 1 + reverse!(scope) + end + end + return lts end end @doc """ scopes = linetable_scopes(m::Method) -Return an array of "scopes" for each statement in the lowered code for `m`. -If `src = Base.uncompressed_ast(m)`, then `scopes[pc]` is an vector of `LineInfoNode` -objects that represent the scopes active at the statement at position `pc` in `src.code`. +Return an array of "scopes" for each statement in the lowered code for `m`. If +`src = Base.uncompressed_ast(m)`, then `scopes[pc]` is an vector of +`LineInfoNode` objects that represent the scopes active at the statement at +position `pc` in `src.code`. -On Julia 1.12 and later, `scopes[pc]` may have length larger than 1, where the first entry -is for the source location in `m`, and any later entries reflect code from inlining. -It will be a vector of `Base.Compiler.IRShow.LineInfoNode` objects. +`scopes[pc]` may have length larger than 1, where the first entry is for the +source location in `m`, and any later entries reflect code from inlining. -Prior to Julia 1.12, `scopes[pc]` will have length 1, and will be a vector of `Core.LineInfoNode` -objects (which have more fields than `Base.Compiler.IRShow.LineInfoNode`). It will represent -the final stage of inlining for the statement at position `pc` in `src.code`. +The precise type of these entries varies with Julia version, +`Base.Compiler.IRShow.LineInfoNode` objects on Julia 1.12 and up, and +`Core.LineInfoNode` objects on earlier versions. These objects differ in some of +their fields. `:method`, `:file`, and `:line` are common to both types. """ linetable_scopes getmethod(m::Method) = m