Revise.jl icon indicating copy to clipboard operation
Revise.jl copied to clipboard

Revising methods with external method tables fails

Open maleadt opened this issue 4 years ago • 7 comments

The error is from JuliaInterpreter:

┌ Error: Failed to revise /home/tim/Julia/pkg/CUDA/src/device/intrinsics/dynamic_parallelism.jl
│   exception =
│    TypeError: in typeassert, expected Union{Nothing, Bool, Symbol}, got a value of type Core.MethodTable
│    Stacktrace:
│     [1] identify_framemethod_calls(frame::JuliaInterpreter.Frame)
│       @ LoweredCodeUtils ~/Julia/depot/packages/LoweredCodeUtils/jZY56/src/signatures.jl:176
└ @ Revise ~/Julia/depot/packages/Revise/OgnOk/src/packagedef.jl:709

Some info from around the error:

msrc = CodeInfo(
    @ /home/tim/Julia/pkg/CUDA/src/device/intrinsics/dynamic_parallelism.jl:25 within `none`
1 ─      res = cudaDeviceSynchronize()
│   @ /home/tim/Julia/pkg/CUDA/src/device/intrinsics/dynamic_parallelism.jl:26 within `none`
│   %2 = res != CUDA.cudaSuccess
└──      goto #3 if not %2
    @ /home/tim/Julia/pkg/CUDA/src/device/intrinsics/dynamic_parallelism.jl:27 within `none`
2 ─ %4 = CUDA.CuDeviceError(res)
│   %5 = CUDA.throw_device_cuerror(%4)
└──      return %5
3 ─      return nothing
)
stmt = :($(Expr(:method, Core.MethodTable(...), %J4, CodeInfo(...))))
key = # 167 methods:
[1] atan(x::Float32, y::Float32) in CUDA at /home/tim/Julia/pkg/CUDA/src/device/intrinsics/math.jl:70
...
[167] mul24(x::Int32, y::Int32) in CUDA at /home/tim/Julia/pkg/CUDA/src/device/intrinsics/math.jl:329

key is CUDA.jl's external method table with all device-specific functions.

This is on Julia 1.7, with Revise 3.1.19 and JuliaInterpreter 0.8.21.

maleadt avatar Aug 24 '21 14:08 maleadt

As I'm sure you can imagine, a reproducer would be lovely. What version of LoweredCodeUtils are you on? I assume that line is https://github.com/JuliaDebug/LoweredCodeUtils.jl/blob/ff8998f6151bce37bbf6a82ce72622e692fd8df3/src/signatures.jl#L175. Obviously I can extend the Union to include Core.MethodTable, but it would be nice to have confidence that everything else is right.

timholy avatar Aug 24 '21 22:08 timholy

Yeah, sorry, was going to put up some additional details, but wanted to make a note as soon as I ran into the issue :slightly_smiling_face:

I was using LoweredCodeUtils v2.1.2, so yeah that was the line.

The basic reproducer is as follows:

Base.Experimental.@MethodTable(method_table)

foo() = 1
Base.Experimental.@overlay method_table foo() = 2

bar() = foo()

This fails to Revise.includet, but with a different failure:

KeyError: key :method_table not found
Stacktrace:
  [1] getindex(h::Dict{Symbol, LoweredCodeUtils.MethodInfo}, key::Symbol)
    @ Base ./dict.jl:481
  [2] identify_framemethod_calls(frame::JuliaInterpreter.Frame)
    @ LoweredCodeUtils ~/.julia/packages/LoweredCodeUtils/jZY56/src/signatures.jl:168
  [3] rename_framemethods!
    @ ~/.julia/packages/LoweredCodeUtils/jZY56/src/signatures.jl:307 [inlined]
  [4] methods_by_execution!(recurse::Any, methodinfo::Revise.CodeTrackingMethodInfo, docexprs::Dict{Module, Vector{Expr}}, mod::Module, ex::Expr; mode::Symbol, disablebp::Bool, always_rethrow::Bool, kwargs::Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:skip_include,), Tuple{Bool}}})
    @ Revise ~/.julia/packages/Revise/OgnOk/src/lowered.jl:191
  [5] #eval_with_signatures#92
    @ ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:463 [inlined]
  [6] instantiate_sigs!(modexsigs::OrderedCollections.OrderedDict{Module, OrderedCollections.OrderedDict{Revise.RelocatableExpr, Union{Nothing, Vector{Any}}}}; mode::Symbol, kwargs::Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:skip_include,), Tuple{Bool}}})
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:471
  [7] track(mod::Module, file::String; mode::Symbol, kwargs::Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:skip_include,), Tuple{Bool}}})
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:899
  [8] includet(mod::Module, file::String)
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:994
  [9] includet(file::String)
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:1016

The reason that the error is slightly different, is that in CUDA.jl/GPUCompiler.jl we use a wrapper macro that interpolates the method_table in the AST, so it isn't a Symbol but a MethodTable directly:

Base.Experimental.@MethodTable(method_table)

foo() = 1
macro override(ex)
    quote
        Base.Experimental.@overlay $method_table $ex
    end
end
@override foo() = 2

bar() = foo()

This yields the error reported above.

maleadt avatar Aug 25 '21 06:08 maleadt

Does it work if you make it a GlobalRef instead? I can't find the reference now, but @aviatesk recently pointed out an older quote of Jeff Bezanson's effectively saying "we allow this, but it's perhaps a mis-feature, because Exprs should be close to the source code itself."

It will still throw a Revise error (GlobalRef is not currently in that Union), but that's very fixable.

timholy avatar Aug 25 '21 06:08 timholy

A GlobalRef works for Julia, but triggers another error in the Revise stack:

macro override(ex)
    quote
        Base.Experimental.@overlay $(GlobalRef(@__MODULE__, :method_table)) $ex
    end
end
MethodError: no method matching nameof(::Core.MethodTable)
Closest candidates are:
  nameof(::DataType) at reflection.jl:223
  nameof(::UnionAll) at reflection.jl:224
  nameof(::Module) at reflection.jl:16
  ...
Stacktrace:
  [1] normalize_defsig(def::Any, frame::JuliaInterpreter.Frame)
    @ LoweredCodeUtils ~/.julia/packages/LoweredCodeUtils/jZY56/src/signatures.jl:217
  [2] identify_framemethod_calls(frame::JuliaInterpreter.Frame)
    @ LoweredCodeUtils ~/.julia/packages/LoweredCodeUtils/jZY56/src/signatures.jl:166
  [3] rename_framemethods!
    @ ~/.julia/packages/LoweredCodeUtils/jZY56/src/signatures.jl:307 [inlined]
  [4] methods_by_execution!(recurse::Any, methodinfo::Revise.CodeTrackingMethodInfo, docexprs::Dict{Module, Vector{Expr}}, mod::Module, ex::Expr; mode::Symbol, disablebp::Bool, always_rethrow::Bool, kwargs::Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:skip_include,), Tuple{Bool}}})
    @ Revise ~/.julia/packages/Revise/OgnOk/src/lowered.jl:191
  [5] #eval_with_signatures#92
    @ ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:463 [inlined]
  [6] instantiate_sigs!(modexsigs::OrderedCollections.OrderedDict{Module, OrderedCollections.OrderedDict{Revise.RelocatableExpr, Union{Nothing, Vector{Any}}}}; mode::Symbol, kwargs::Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:skip_include,), Tuple{Bool}}})
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:471
  [7] track(mod::Module, file::String; mode::Symbol, kwargs::Base.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:skip_include,), Tuple{Bool}}})
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:899
  [8] includet(mod::Module, file::String)
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:994
  [9] includet(file::String)
    @ Revise ~/.julia/packages/Revise/OgnOk/src/packagedef.jl:1016

But why a GlobalRef? Wouldn't source code typically contain a Symbol or Expr? Changing the method_table to an Expr seems 'compatible' (i.e. it doesn't error) with the current logic in LoweredCodeUtils:

macro override(ex)
    quote
        Base.Experimental.@overlay $(@__MODULE__).method_table $ex
    end
end

Similarly, without the wrapper macro, making sure the method table is an Expr (by qualifying the module) gets past Revise.includet:

Base.Experimental.@MethodTable(method_table)

foo() = 1
Base.Experimental.@overlay Main.method_table foo() = 2

So I guess this only needs a fix for the method_table::Symbol case, and if we want to, for when the method_table is actually a full-blown Core.MethodTable.


I quoted 'compatible' above, because even though Revise doesn't choke on these sources anymore, it incorrectly redefines methods. I guess that's the second part of this issue.

Expected behavior:

julia> Base.Experimental.@MethodTable(method_table)

julia> foo() = 1
julia> Base.Experimental.@overlay Main.method_table foo() = 2

julia> foo()
1

julia> Base.Experimental.@overlay Main.method_table foo() = 3

julia> foo()
1

Whereas putting this in a file and having Revise track it yields the following:

julia> using Revise

julia> Revise.includet("wip.jl")

julia> foo()
1

# change @overlay foo()=2 to =3
julia> Revise.revise()

julia> foo()
3

maleadt avatar Aug 25 '21 06:08 maleadt

Is this still relevant or did the PR above fix it?

timholy avatar Jul 24 '22 13:07 timholy

Yes, this is still relevant. The PR just made it so that Revise doesn't error out, but it doesn't correctly handle overlay method tables. Let me demonstrate:

Base.Experimental.@MethodTable(method_table)

foo() = 1
Base.Experimental.@overlay Main.method_table foo() = 2

function main()
    println("Method in main method table:")
    methods = Base._methods_by_ftype(Tuple{typeof(foo)}, nothing, 1, Base.get_world_counter())
    display(Base.uncompressed_ir(methods[1].method))

    println("Method in overlay method table:")
    methods = Base._methods_by_ftype(Tuple{typeof(foo)}, method_table, 1, Base.get_world_counter())
    display(Base.uncompressed_ir(methods[1].method))

    return
end
julia> using Revise

julia> Revise.includet("...")

julia> main()
Method in main method table:
CodeInfo(
    @ /tmp/wip.jl:3 within `foo`
1 ─     return 1
)
Method in overlay method table:
CodeInfo(
    @ /tmp/wip.jl:4 within `#foo`
1 ─     return 2
)

julia> # redefine `@overlay foo() = 2` to `@overlay foo() = 3`

julia> main()
Method in main method table:
CodeInfo(
    @ /tmp/wip.jl:4 within `foo`
1 ─     return 3
)
Method in overlay method table:
CodeInfo(
    @ /tmp/wip.jl:4 within `#foo`
1 ─     return 2
)

i.e. revising the overlaid method results in the main method table method being redefined, instead of the one on the overlay method table.

maleadt avatar Jul 25 '22 08:07 maleadt