From 013fe08d9993dcd6758add3707ecddeddd7b26ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Fri, 18 Apr 2025 11:18:01 -0400 Subject: [PATCH 01/16] Parametrize signatures by method table --- Project.toml | 2 +- src/LoweredCodeUtils.jl | 2 +- src/signatures.jl | 27 +++++++++++++++------------ test/signatures.jl | 21 ++++++++++++--------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Project.toml b/Project.toml index a9616c2..b4d500b 100644 --- a/Project.toml +++ b/Project.toml @@ -7,7 +7,7 @@ version = "3.1.0" JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" [compat] -JuliaInterpreter = "0.9" +JuliaInterpreter = "0.9.45" julia = "1.10" [extras] diff --git a/src/LoweredCodeUtils.jl b/src/LoweredCodeUtils.jl index bdeb7de..b166205 100644 --- a/src/LoweredCodeUtils.jl +++ b/src/LoweredCodeUtils.jl @@ -13,7 +13,7 @@ using JuliaInterpreter using JuliaInterpreter: SSAValue, SlotNumber, Frame using JuliaInterpreter: @lookup, moduleof, pc_expr, step_expr!, is_global_ref, is_quotenode_egal, whichtt, next_until!, finish_and_return!, get_return, nstatements, codelocation, linetable, - is_return, lookup_return + is_return, lookup_return, extract_method_table include("packagedef.jl") diff --git a/src/signatures.jl b/src/signatures.jl index 2e77c8e..c744b66 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -53,9 +53,10 @@ function signature(@nospecialize(recurse), frame::Frame, @nospecialize(stmt), pc stmt = pc_expr(frame, pc) end isa(stmt, Expr) || return nothing, pc + mt = extract_method_table(frame, stmt; eval = false) sigsv = @lookup(frame, stmt.args[2])::SimpleVector sigt = signature(sigsv) - return sigt, lastpc + return mt => sigt, lastpc end signature(@nospecialize(recurse), frame::Frame, pc) = signature(recurse, frame, pc_expr(frame, pc), pc) signature(frame::Frame, pc) = signature(finish_and_return!, frame, pc) @@ -439,9 +440,9 @@ function get_running_name(@nospecialize(recurse), frame, pc, name, parentname) pctop -= 1 stmt = pc_expr(frame, pctop) end # end fix - sigtparent, lastpcparent = signature(recurse, frame, pctop) + (mt, sigtparent), lastpcparent = signature(recurse, frame, pctop) sigtparent === nothing && return name, pc, lastpcparent - methparent = whichtt(sigtparent) + methparent = whichtt(sigtparent, mt) methparent === nothing && return name, pc, lastpcparent # caller isn't defined, no correction is needed if isgen cname = GlobalRef(moduleof(frame), nameof(methparent.generator.gen)) @@ -507,6 +508,8 @@ function skip_until!(predicate, @nospecialize(recurse), frame) return pc end +method_table(method::Method) = isdefined(method, :external_mt) ? method.external_mt::MethodTable : nothing + """ ret = methoddef!(recurse, signatures, frame; define=true) ret = methoddef!(signatures, frame; define=true) @@ -536,22 +539,22 @@ function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecial if ismethod3(stmt) pc3 = pc arg1 = stmt.args[1] - sigt, pc = signature(recurse, frame, stmt, pc) - meth = whichtt(sigt) + (mt, sigt), pc = signature(recurse, frame, stmt, pc) + meth = whichtt(sigt, mt) if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig) pc = define ? step_expr!(recurse, frame, stmt, true) : next_or_nothing!(recurse, frame) elseif define pc = step_expr!(recurse, frame, stmt, true) - meth = whichtt(sigt) + meth = whichtt(sigt, mt) end if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig) - push!(signatures, meth.sig) + push!(signatures, mt => meth.sig) else if arg1 === false || arg1 === nothing # If it's anonymous and not defined, define it pc = step_expr!(recurse, frame, stmt, true) - meth = whichtt(sigt) - isa(meth, Method) && push!(signatures, meth.sig) + meth = whichtt(sigt, mt) + isa(meth, Method) && push!(signatures, mt => meth.sig) return pc, pc3 else # guard against busted lookup, e.g., https://github.com/JuliaLang/julia/issues/31112 @@ -592,7 +595,7 @@ function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecial end found || return nothing while true # methods containing inner methods may need multiple trips through this loop - sigt, pc = signature(recurse, frame, stmt, pc) + (mt, sigt), pc = signature(recurse, frame, stmt, pc) stmt = pc_expr(frame, pc) while !isexpr(stmt, :method, 3) pc = next_or_nothing(recurse, frame, pc) # this should not check define, we've probably already done this once @@ -607,8 +610,8 @@ function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecial # signature of the active method. So let's get the active signature. frame.pc = pc pc = define ? step_expr!(recurse, frame, stmt, true) : next_or_nothing!(recurse, frame) - meth = whichtt(sigt) - isa(meth, Method) && push!(signatures, meth.sig) # inner methods are not visible + meth = whichtt(sigt, mt) + isa(meth, Method) && push!(signatures, mt => meth.sig) # inner methods are not visible name === name3 && return pc, pc3 # if this was an inner method we should keep going stmt = pc_expr(frame, pc) # there *should* be more statements in this frame end diff --git a/test/signatures.jl b/test/signatures.jl index 8adb2b8..cf197b4 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -97,7 +97,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 # Manually add the signature for the Caller constructor, since that was defined # outside of manual lowering - push!(signatures, Tuple{Type{Lowering.Caller}}) + push!(signatures, nothing => Tuple{Type{Lowering.Caller}}) nms = names(Lowering; all=true) modeval, modinclude = getfield(Lowering, :eval), getfield(Lowering, :include) @@ -107,7 +107,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 isa(f, Base.Callable) || continue (f === modeval || f === modinclude) && continue for m in methods(f) - if m.sig ∉ signatures + if (nothing => m.sig) ∉ signatures push!(failed, m.sig) end end @@ -151,7 +151,8 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 rename_framemethods!(frame) methoddefs!(signatures, frame; define=false) @test length(signatures) == 1 - @test LoweredCodeUtils.whichtt(signatures[1]) == first(methods(Lowering.fouter)) + mt, sig = first(signatures) + @test LoweredCodeUtils.whichtt(sig, mt) == first(methods(Lowering.fouter)) # Check output of methoddef! frame = Frame(Lowering, :(function nomethod end)) @@ -170,7 +171,8 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 signatures = Set{Any}() methoddef!(signatures, frame; define=false) @test length(signatures) == 1 - @test first(signatures) == which(Base.max_values, Tuple{Type{Int16}}).sig + mt, sig = first(signatures) + @test sig == which(Base.max_values, Tuple{Type{Int16}}).sig # define ex = :(fdefine(x) = 1) @@ -291,7 +293,8 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 JuliaInterpreter.next_until!(LoweredCodeUtils.ismethod3, frame, true) empty!(signatures) methoddefs!(signatures, frame; define=true) - @test first(signatures).parameters[end] == Int + mt, sig = first(signatures) + @test sig.parameters[end] == Int # Multiple keyword arg methods per frame # (Revise issue #363) @@ -310,7 +313,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 @test kw2sig ∉ signatures pc = methoddefs!(signatures, frame; define=false) @test pc === nothing - @test kw2sig ∈ signatures + @test (nothing => kw2sig) ∈ signatures # Module-scoping ex = :(Base.@irrational π 3.14159265358979323846 pi) @@ -336,7 +339,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 rename_framemethods!(frame) empty!(signatures) methoddefs!(signatures, frame; define=false) - @test Tuple{typeof(Lowering.CustomMS)} ∈ signatures + @test (nothing => Tuple{typeof(Lowering.CustomMS)}) ∈ signatures # https://github.com/timholy/Revise.jl/issues/398 ex = quote @@ -370,7 +373,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 frame = Frame(Lowering422, ex) rename_framemethods!(frame) pc = methoddefs!(signatures, frame; define=false) - @test typeof(Lowering422.fneg) ∈ Set(Base.unwrap_unionall(sig).parameters[1] for sig in signatures) + @test typeof(Lowering422.fneg) ∈ Set(Base.unwrap_unionall(sig).parameters[1] for (mt, sig) in signatures) # Scoped names (https://github.com/timholy/Revise.jl/issues/568) ex = :(f568() = -1) @@ -385,7 +388,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 pc = JuliaInterpreter.step_expr!(finish_and_return!, frame, true) end pc = methoddef!(finish_and_return!, signatures, frame, pc; define=true) - @test Tuple{typeof(Lowering.f568)} ∈ signatures + @test (nothing => Tuple{typeof(Lowering.f568)}) ∈ signatures @test Lowering.f568() == -2 # Undefined names From 4610b4bf6f3b884b0192ed570c43713e65ff70e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Fri, 18 Apr 2025 11:18:21 -0400 Subject: [PATCH 02/16] Add tests --- test/signatures.jl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/signatures.jl b/test/signatures.jl index cf197b4..5fa7132 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -505,4 +505,26 @@ end end +@testset "Support for external method tables" begin + ExternalMT = Module() + Core.eval(ExternalMT, :(Base.Experimental.@MethodTable method_table)) + signatures = [] + + ex = :(Base.sin(::Float64) = "sin") + Core.eval(ExternalMT, ex) + frame = Frame(ExternalMT, ex) + pc = methoddefs!(signatures, frame; define = false) + @test length(signatures) == 1 + (mt, sig) = pop!(signatures) + @test (mt, sig) === (nothing, Tuple{typeof(sin), Float64}) + + ex = :(Base.Experimental.@overlay method_table sin(::Float64) = "sin") + Core.eval(ExternalMT, ex) + frame = Frame(ExternalMT, ex) + pc = methoddefs!(signatures, frame; define = false) + @test length(signatures) == 1 + (mt, sig) = pop!(signatures) + @test (mt, sig) === (ExternalMT.method_table, Tuple{typeof(sin), Float64}) +end + end # module signatures From f53718b889bd3b0805ddced318b2dace3957e1f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 17:21:09 +0200 Subject: [PATCH 03/16] Update src/signatures.jl Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- src/signatures.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/signatures.jl b/src/signatures.jl index c744b66..937f618 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -56,7 +56,7 @@ function signature(@nospecialize(recurse), frame::Frame, @nospecialize(stmt), pc mt = extract_method_table(frame, stmt; eval = false) sigsv = @lookup(frame, stmt.args[2])::SimpleVector sigt = signature(sigsv) - return mt => sigt, lastpc + return Pair{Union{Nothing,MethodTable},Any}(mt, sigt), lastpc end signature(@nospecialize(recurse), frame::Frame, pc) = signature(recurse, frame, pc_expr(frame, pc), pc) signature(frame::Frame, pc) = signature(finish_and_return!, frame, pc) From 6ee24af055ae9f6e4aab1ace59af290cca9bdbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 11:21:34 -0400 Subject: [PATCH 04/16] Update docstring --- src/signatures.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/signatures.jl b/src/signatures.jl index c744b66..a84dbc1 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -514,8 +514,8 @@ method_table(method::Method) = isdefined(method, :external_mt) ? method.external ret = methoddef!(recurse, signatures, frame; define=true) ret = methoddef!(signatures, frame; define=true) -Compute the signature of a method definition. `frame.pc` should point to a -`:method` expression. Upon exit, the new signature will be added to `signatures`. +Compute the method table/signature pair of a method definition. `frame.pc` should point to a +`:method` expression. Upon exit, the new method table/signature pair will be added to `signatures`. There are several possible return values: From 1ecbfcf5d2a1fbc8e389f92173f05ecc558d1e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 11:22:11 -0400 Subject: [PATCH 05/16] Tighten the type of `signatures` --- src/signatures.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/signatures.jl b/src/signatures.jl index a84dbc1..1f9915e 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -534,7 +534,7 @@ occurs for "empty method" expressions, e.g., `:(function foo end)`. `pc` will be By default the method will be defined (evaluated). You can prevent this by setting `define=false`. This is recommended if you are simply extracting signatures from code that has already been evaluated. """ -function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true) +function methoddef!(@nospecialize(recurse), signatures::Vector{Pair{Union{Nothing,MethodTable},Any}}, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true) framecode, pcin = frame.framecode, pc if ismethod3(stmt) pc3 = pc From 80581f0886170b057b4dc3fb94d5e224be934898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 13:00:57 -0400 Subject: [PATCH 06/16] Use MethodInfoKey from CodeTracking --- src/LoweredCodeUtils.jl | 2 ++ src/packagedef.jl | 2 +- src/signatures.jl | 4 ++-- test/signatures.jl | 9 +++++---- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/LoweredCodeUtils.jl b/src/LoweredCodeUtils.jl index 78b3210..629981e 100644 --- a/src/LoweredCodeUtils.jl +++ b/src/LoweredCodeUtils.jl @@ -9,6 +9,8 @@ module LoweredCodeUtils # This somewhat unusual structure is in place to support # the VS Code extension integration. +using CodeTracking: MethodInfoKey + using JuliaInterpreter using JuliaInterpreter: SSAValue, SlotNumber, Frame using JuliaInterpreter: @lookup, moduleof, pc_expr, step_expr!, is_global_ref, is_global_ref_egal, is_quotenode_egal, whichtt, diff --git a/src/packagedef.jl b/src/packagedef.jl index b25c166..c99cedd 100644 --- a/src/packagedef.jl +++ b/src/packagedef.jl @@ -1,6 +1,6 @@ Base.Experimental.@optlevel 1 -using Core: SimpleVector, MethodTable +using Core: SimpleVector using Core.IR: CodeInfo, GotoIfNot, GotoNode, IR, MethodInstance, ReturnNode @static if isdefined(Core.IR, :EnterNode) using Core.IR: EnterNode diff --git a/src/signatures.jl b/src/signatures.jl index 2cc70b5..8b7a5bd 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -56,7 +56,7 @@ function signature(@nospecialize(recurse), frame::Frame, @nospecialize(stmt), pc mt = extract_method_table(frame, stmt; eval = false) sigsv = @lookup(frame, stmt.args[2])::SimpleVector sigt = signature(sigsv) - return Pair{Union{Nothing,MethodTable},Any}(mt, sigt), lastpc + return MethodInfoKey(mt, sigt), lastpc end signature(@nospecialize(recurse), frame::Frame, pc) = signature(recurse, frame, pc_expr(frame, pc), pc) signature(frame::Frame, pc) = signature(finish_and_return!, frame, pc) @@ -534,7 +534,7 @@ occurs for "empty method" expressions, e.g., `:(function foo end)`. `pc` will be By default the method will be defined (evaluated). You can prevent this by setting `define=false`. This is recommended if you are simply extracting signatures from code that has already been evaluated. """ -function methoddef!(@nospecialize(recurse), signatures::Vector{Pair{Union{Nothing,MethodTable},Any}}, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true) +function methoddef!(@nospecialize(recurse), signatures::Vector{MethodInfoKey}, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true) framecode, pcin = frame.framecode, pc if ismethod3(stmt) pc3 = pc diff --git a/test/signatures.jl b/test/signatures.jl index 5ddd0b9..91606c4 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -2,6 +2,7 @@ module Signatures using LoweredCodeUtils using InteractiveUtils +using CodeTracking: MethodInfoKey using JuliaInterpreter using JuliaInterpreter: finish_and_return! using Core: CodeInfo @@ -34,7 +35,7 @@ bodymethtest4(x, y=1) = 4 bodymethtest5(x, y=Dict(1=>2)) = 5 @testset "Signatures" begin - signatures = Set{Any}() + signatures = MethodInfoKey[] newcode = CodeInfo[] for ex in (:(f(x::Int8; y=0) = y), :(f(x::Int16; y::Int=0) = 2), @@ -139,7 +140,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 @test g(3) == 6 # Don't be deceived by inner methods - signatures = [] + signatures = MethodInfoKey[] ex = quote function fouter(x) finner(::Float16) = 2x @@ -168,7 +169,7 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 ex = :(max_values(T::Union{map(X -> Type{X}, Base.BitIntegerSmall_types)...}) = 1 << (8*sizeof(T))) # base/abstractset.jl frame = Frame(Base, ex) rename_framemethods!(frame) - signatures = Set{Any}() + signatures = MethodInfoKey[] methoddef!(signatures, frame; define=false) @test length(signatures) == 1 mt, sig = first(signatures) @@ -507,7 +508,7 @@ end @testset "Support for external method tables" begin ExternalMT = Module() Core.eval(ExternalMT, :(Base.Experimental.@MethodTable method_table)) - signatures = [] + signatures = MethodInfoKey[] ex = :(Base.sin(::Float64) = "sin") Core.eval(ExternalMT, ex) From 1910570e86c5c6311fa883f1ef284b5384b5b87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 13:41:56 -0400 Subject: [PATCH 07/16] Add CodeTracking as a direct dependency --- Project.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Project.toml b/Project.toml index 5e80544..3e7625f 100644 --- a/Project.toml +++ b/Project.toml @@ -4,9 +4,11 @@ authors = ["Tim Holy "] version = "3.2.1" [deps] +CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" [compat] +CodeTracking = "2" JuliaInterpreter = "0.9.45" julia = "1.10" From 118978ae80e6b50caf308ff0543d72327bfa784e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 13:42:49 -0400 Subject: [PATCH 08/16] Remove `method_table` definition We already used `extract_method_table` from JuliaInterpreter, obsoleting the introduction of `method_table`. --- src/signatures.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/signatures.jl b/src/signatures.jl index 8b7a5bd..e533158 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -508,8 +508,6 @@ function skip_until!(predicate, @nospecialize(recurse), frame) return pc end -method_table(method::Method) = isdefined(method, :external_mt) ? method.external_mt::MethodTable : nothing - """ ret = methoddef!(recurse, signatures, frame; define=true) ret = methoddef!(signatures, frame; define=true) From d8bd0bd5703fe36c5258a8bac1cf3425279f9365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 13:43:26 -0400 Subject: [PATCH 09/16] Don't accidentally override `Base.sin(::Float64)` --- test/signatures.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/signatures.jl b/test/signatures.jl index 91606c4..dce60ce 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -510,21 +510,21 @@ end Core.eval(ExternalMT, :(Base.Experimental.@MethodTable method_table)) signatures = MethodInfoKey[] - ex = :(Base.sin(::Float64) = "sin") + ex = :(foo(x) = "foo") Core.eval(ExternalMT, ex) frame = Frame(ExternalMT, ex) pc = methoddefs!(signatures, frame; define = false) @test length(signatures) == 1 (mt, sig) = pop!(signatures) - @test (mt, sig) === (nothing, Tuple{typeof(sin), Float64}) + @test (mt, sig) === (nothing, Tuple{typeof(ExternalMT.foo), Any}) - ex = :(Base.Experimental.@overlay method_table sin(::Float64) = "sin") + ex = :(Base.Experimental.@overlay method_table foo(x) = "overlayed foo") Core.eval(ExternalMT, ex) frame = Frame(ExternalMT, ex) pc = methoddefs!(signatures, frame; define = false) @test length(signatures) == 1 (mt, sig) = pop!(signatures) - @test (mt, sig) === (ExternalMT.method_table, Tuple{typeof(sin), Float64}) + @test (mt, sig) === (ExternalMT.method_table, Tuple{typeof(ExternalMT.foo), Any}) end end # module signatures From 73d89d5741d822e9d564255d83432a24c05813eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 13:46:27 -0400 Subject: [PATCH 10/16] Temporarily add CodeTracking PR for CI testing --- .github/workflows/CI.yml | 1 + .github/workflows/Documenter.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 811de4d..ed696af 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -54,6 +54,7 @@ jobs: ${{ runner.os }}-test-${{ env.cache-name }}- ${{ runner.os }}-test- ${{ runner.os }}- + - run: julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 https://github.com/serenity4/CodeTracking.jl#parametrize-by-mt"' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index b91b123..6b0146a 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -11,6 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - run: julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 https://github.com/serenity4/CodeTracking.jl#parametrize-by-mt"' - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-docdeploy@latest env: From b8520fc7b36be2ffcf75b67267e1c0265a167b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Mon, 21 Apr 2025 14:29:37 -0400 Subject: [PATCH 11/16] Fix patch for Documenter on CI --- .github/workflows/Documenter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 6b0146a..7d9be2a 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -12,6 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - run: julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 https://github.com/serenity4/CodeTracking.jl#parametrize-by-mt"' + - run: cd docs && julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 https://github.com/serenity4/CodeTracking.jl#parametrize-by-mt"' - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-docdeploy@latest env: From 854867989b157f0813bc680b1dd50a3b339e3f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Thu, 24 Apr 2025 12:23:17 -0400 Subject: [PATCH 12/16] Add missing handling for methods defined for external method tables --- src/codeedges.jl | 24 +++++++++++------------- src/packagedef.jl | 2 +- src/signatures.jl | 8 +++++--- test/signatures.jl | 16 ++++++++++++++-- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/codeedges.jl b/src/codeedges.jl index 647eae8..39c66c4 100644 --- a/src/codeedges.jl +++ b/src/codeedges.jl @@ -242,27 +242,25 @@ function direct_links!(cl::CodeLinks, src::CodeInfo) add_inner!(cl, icl, i) continue elseif isexpr(stmt, :method) - if length(stmt.args) === 3 && (arg3 = stmt.args[3]; arg3 isa CodeInfo) - icl = CodeLinks(cl.thismod, arg3) - add_inner!(cl, icl, i) - end - name = stmt.args[1] - if isa(name, GlobalRef) || isa(name, Symbol) + if length(stmt.args) === 1 + # A function with no methods was defined. Associate its new binding to it. + name = stmt.args[1] if isa(name, Symbol) name = GlobalRef(cl.thismod, name) end - assign = get(cl.nameassigns, name, nothing) - if assign === nothing - cl.nameassigns[name] = assign = Int[] + if !isa(name, GlobalRef) + @show stmt + error("name ", typeof(name), " not recognized") end + assign = get!(Vector{Int}, cl.nameassigns, name) push!(assign, i) targetstore = get!(Links, cl.namepreds, name) target = P(name, targetstore) add_links!(target, stmt, cl) - elseif name in (nothing, false) - else - @show stmt - error("name ", typeof(name), " not recognized") + elseif length(stmt.args) === 3 && (arg3 = stmt.args[3]; arg3 isa CodeInfo) # method definition + # A method was defined for an existing function. + icl = CodeLinks(cl.thismod, arg3) + add_inner!(cl, icl, i) end rhs = stmt target = P(SSAValue(i), cl.ssapreds[i]) diff --git a/src/packagedef.jl b/src/packagedef.jl index c99cedd..b25c166 100644 --- a/src/packagedef.jl +++ b/src/packagedef.jl @@ -1,6 +1,6 @@ Base.Experimental.@optlevel 1 -using Core: SimpleVector +using Core: SimpleVector, MethodTable using Core.IR: CodeInfo, GotoIfNot, GotoNode, IR, MethodInstance, ReturnNode @static if isdefined(Core.IR, :EnterNode) using Core.IR: EnterNode diff --git a/src/signatures.jl b/src/signatures.jl index e533158..cf06690 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -53,7 +53,7 @@ function signature(@nospecialize(recurse), frame::Frame, @nospecialize(stmt), pc stmt = pc_expr(frame, pc) end isa(stmt, Expr) || return nothing, pc - mt = extract_method_table(frame, stmt; eval = false) + mt = extract_method_table(frame, stmt) sigsv = @lookup(frame, stmt.args[2])::SimpleVector sigt = signature(sigsv) return MethodInfoKey(mt, sigt), lastpc @@ -189,7 +189,9 @@ function identify_framemethod_calls(frame) end msrc = stmt.args[3] if msrc isa CodeInfo - key = key::Union{GlobalRef,Bool,Nothing} + # XXX: Properly support interpolated `Core.MethodTable`. This will require using + # `stmt.args[2]` instead of `stmt.args[1]` to identify the parent function. + isa(key, Union{GlobalRef,Bool,Nothing}) || continue for (j, mstmt) in enumerate(msrc.code) isa(mstmt, Expr) || continue jj = j @@ -548,7 +550,7 @@ function methoddef!(@nospecialize(recurse), signatures::Vector{MethodInfoKey}, f if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig) push!(signatures, mt => meth.sig) else - if arg1 === false || arg1 === nothing + if arg1 === false || arg1 === nothing || isa(mt, MethodTable) # If it's anonymous and not defined, define it pc = step_expr!(recurse, frame, stmt, true) meth = whichtt(sigt, mt) diff --git a/test/signatures.jl b/test/signatures.jl index dce60ce..ab281bc 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -505,9 +505,12 @@ end end +module ExternalMT + Base.Experimental.@MethodTable method_table + macro overlay(ex) esc(:(Base.Experimental.@overlay $method_table $ex)) end +end + @testset "Support for external method tables" begin - ExternalMT = Module() - Core.eval(ExternalMT, :(Base.Experimental.@MethodTable method_table)) signatures = MethodInfoKey[] ex = :(foo(x) = "foo") @@ -525,6 +528,15 @@ end @test length(signatures) == 1 (mt, sig) = pop!(signatures) @test (mt, sig) === (ExternalMT.method_table, Tuple{typeof(ExternalMT.foo), Any}) + + ex = :(@overlay foo(x::Int64) = "overlayed foo, second edition") + Core.eval(ExternalMT, ex) + frame = Frame(ExternalMT, ex) + pc = methoddefs!(signatures, frame; define = false) + @test length(signatures) == 1 + (mt, sig) = pop!(signatures) + @test (mt, sig) === (ExternalMT.method_table, Tuple{typeof(ExternalMT.foo), Int64}) + LoweredCodeUtils.identify_framemethod_calls(frame) # make sure this does not throw end end # module signatures From 0b583871d02dd5b1b98d01c8af20fdfdd9607b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Belmant?= Date: Fri, 25 Apr 2025 13:15:38 -0400 Subject: [PATCH 13/16] Adjust docs, add type annotations --- src/signatures.jl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/signatures.jl b/src/signatures.jl index 63114ad..3a3378c 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -24,9 +24,9 @@ function signature(sigsv::SimpleVector) end """ - sigt, lastpc = signature([interp::Interpreter=RecursiveInterpreter()], frame::Frame, pc::Int) + (mt, sigt), lastpc = signature([interp::Interpreter=RecursiveInterpreter()], frame::Frame, pc::Int) -Compute the signature-type `sigt` of a method whose definition in `frame` starts at `pc`. +Compute the method table `mt` and signature-type `sigt` of a method whose definition in `frame` starts at `pc`. Generally, `pc` should point to the `Expr(:method, methname)` statement, in which case `lastpc` is the final statement number in `frame` that is part of the signature (i.e, the line above the 3-argument `:method` expression). @@ -34,7 +34,7 @@ Alternatively, `pc` can point to the 3-argument `:method` expression, as long as all the relevant SSAValues have been assigned. In this case, `lastpc == pc`. -If no 3-argument `:method` expression is found, `sigt` will be `nothing`. +If no 3-argument `:method` expression is found, `nothing` will be returned in place of `(mt, sigt)`. """ function signature(interp::Interpreter, frame::Frame, @nospecialize(stmt), pc::Int) mod = moduleof(frame) @@ -620,9 +620,9 @@ function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, fram stmt = pc_expr(frame, pc) # there *should* be more statements in this frame end end -methoddef!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=true) = +methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true) = methoddef!(interp, signatures, frame, pc_expr(frame, pc), pc; define) -function methoddef!(interp::Interpreter, signatures, frame::Frame; define::Bool=true) +function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true) pc = frame.pc stmt = pc_expr(frame, pc) if !ismethod(stmt) @@ -631,27 +631,27 @@ function methoddef!(interp::Interpreter, signatures, frame::Frame; define::Bool= pc === nothing && error("pc at end of frame without finding a method") methoddef!(interp, signatures, frame, pc; define) end -methoddef!(signatures, frame::Frame, pc::Int; define::Bool=true) = +methoddef!(signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true) = methoddef!(RecursiveInterpreter(), signatures, frame, pc_expr(frame, pc), pc; define) -methoddef!(signatures, frame::Frame; define::Bool=true) = +methoddef!(signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true) = methoddef!(RecursiveInterpreter(), signatures, frame; define) -function methoddefs!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=true) +function methoddefs!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true) ret = methoddef!(interp, signatures, frame, pc; define) pc = ret === nothing ? ret : ret[1] return _methoddefs!(interp, signatures, frame, pc; define) end -function methoddefs!(interp::Interpreter, signatures, frame::Frame; define::Bool=true) +function methoddefs!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true) ret = methoddef!(interp, signatures, frame; define) pc = ret === nothing ? ret : ret[1] return _methoddefs!(interp, signatures, frame, pc; define) end -methoddefs!(signatures, frame::Frame, pc::Int; define::Bool=true) = +methoddefs!(signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true) = methoddefs!(RecursiveInterpreter(), signatures, frame, pc; define) -methoddefs!(signatures, frame::Frame; define::Bool=true) = +methoddefs!(signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true) = methoddefs!(RecursiveInterpreter(), signatures, frame; define) -function _methoddefs!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=define) +function _methoddefs!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=define) while pc !== nothing stmt = pc_expr(frame, pc) if !ismethod(stmt) From b9853093f9e99fa6da0f658ee9426cb9ecbd7718 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Tue, 10 Jun 2025 15:28:26 +0200 Subject: [PATCH 14/16] Update CI dependency patch --- .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 ed696af..cf14380 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -54,7 +54,7 @@ jobs: ${{ runner.os }}-test-${{ env.cache-name }}- ${{ runner.os }}-test- ${{ runner.os }}- - - run: julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 https://github.com/serenity4/CodeTracking.jl#parametrize-by-mt"' + - run: julia --project -e 'using Pkg; Pkg.add([PackageSpec(; url="https://github.com/serenity4/JuliaInterpreter.jl", rev="codetracking-v2"), PackageSpec(; name = "CodeTracking", rev="master")])' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From e624e99f1e16571752d40c10a2db1c7d4b16e81b Mon Sep 17 00:00:00 2001 From: serenity4 Date: Tue, 10 Jun 2025 15:47:01 +0200 Subject: [PATCH 15/16] Test function definition, not method definition --- test/codeedges.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/codeedges.jl b/test/codeedges.jl index 412620a..f1a9437 100644 --- a/test/codeedges.jl +++ b/test/codeedges.jl @@ -348,7 +348,7 @@ module ModSelective end edges = CodeEdges(ModEval, src) lr = lines_required(GlobalRef(ModEval, :revise538), src, edges) selective_eval_fromstart!(Frame(ModEval, src), lr, #=istoplevel=#true) - @test isdefined(ModEval, :revise538) && length(methods(ModEval.revise538, (Float32,))) == 1 + @test isdefined(ModEval, :revise538) && isempty(methods(ModEval.revise538)) # function is defined, method is not # https://github.com/timholy/Revise.jl/issues/599 thk = Meta.lower(Main, quote From 8d8784f88f16d950f2e5671c6015fbbb2805f924 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Tue, 10 Jun 2025 15:49:39 +0200 Subject: [PATCH 16/16] Update CI docs dependency patch --- .github/workflows/Documenter.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 7d9be2a..3397e84 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -11,8 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - run: julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 https://github.com/serenity4/CodeTracking.jl#parametrize-by-mt"' - - run: cd docs && julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 https://github.com/serenity4/CodeTracking.jl#parametrize-by-mt"' + - run: julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 CodeTracking#master"' + - run: cd docs && julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 CodeTracking#master"' - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-docdeploy@latest env: