JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.4k stars 5.45k forks source link

Runtime invalidation of `CodeInstance`s #39209

Open timholy opened 3 years ago

timholy commented 3 years ago

I'm not sure I understand this properly, but using the master branch of SnoopCompile:

julia> using SnoopCompile
[ Info: Precompiling SnoopCompile [aa65fe97-06da-5843-b5b1-d5d13cad87d2]

julia> function profile_test(n)
           for i = 1:n
               A = randn(100,100,20)
               m = maximum(A)
               Am = mapslices(sum, A; dims=2)
               B = A[:,:,5]
               Bsort = mapslices(sort, B; dims=1)
               b = rand(100)
               C = B.*b
           end
       end
profile_test (generic function with 1 method)

julia> tinf = @snoopi_deep profile_test(1)
InferenceTimingNode: 0.727273/1.292222 on InferenceFrameInfo for Core.Compiler.Timings.ROOT() with 29 direct children

julia> invalidations(tinf)
4-element Vector{Core.MethodInstance}:
 MethodInstance for (NamedTuple{(:dims,), T} where T<:Tuple)(::Tuple{Int64})
 MethodInstance for (NamedTuple{(:init,), T} where T<:Tuple)(::Tuple{Int64})
 MethodInstance for (NamedTuple{(:init,), T} where T<:Tuple)(::Tuple{Int64})
 MethodInstance for (NamedTuple{(:dims,), T} where T<:Tuple)(::Tuple{Int64})

julia> mi = ans[1]
MethodInstance for (NamedTuple{(:dims,), T} where T<:Tuple)(::Tuple{Int64})

julia> mi.cache.max_world
0x0000000000001be8

julia> Base.get_world_counter()
0x000000000000739b

Those were all MethodInstances that were inferred (more likely, Const-inferred) during running of that workload, and yet they have CodeInstances that are no longer valid. A wild guess is that somehow the @generated constructor for NamedTuples triggers invalidation?

For some packages, the list of apparent runtime invalidations is pretty huge. Example (CC @tlienart):

shell> cd test
/home/tim/.julia/dev/Franklin/test

julia> using Literate, Suppressor, DataStructures, DataFrames, HTTP, Franklin  # avoid invalidations from loading packages later

julia> using SnoopCompile

julia> tinf = @snoopi_deep include("runtests.jl")    # @snoopr shows no invalidations given the pre-loading
...

julia> invs = invalidations(tinf)
142-element Vector{Core.MethodInstance}:
 MethodInstance for do_test(::Test.ExecutionResult, ::Any)
 MethodInstance for Test.Fail(::Symbol, ::Any, ::Any, ::Bool, ::LineNumberNode)
 MethodInstance for convert(::Type{Union{Nothing, String}}, ::Any)
 MethodInstance for Test.Error(::Symbol, ::Any, ::Any, ::Nothing, ::LineNumberNode)
 MethodInstance for Test.Error(::Symbol, ::Any, ::Any, ::Any, ::LineNumberNode)
 MethodInstance for finish(::Test.DefaultTestSet)
 MethodInstance for print_test_results(::Test.DefaultTestSet)
 MethodInstance for print_test_results(::Test.DefaultTestSet, ::Int64)
 MethodInstance for _similar_for(::Vector{T} where T, ::Type, ::Base.Generator{_A, Test.var"#24#25"{Int64}} where _A, ::Base.SizeUnknown)
 MethodInstance for similar(::Vector{T} where T, ::Type, ::Int64)
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for (NamedTuple{(:bold, :color), T} where T<:Tuple)(::Tuple{Bool, Symbol})
 MethodInstance for push!(::Vector{String}, ::Any)
 MethodInstance for set_vars!(::LittleDict{String, Pair, Vector{String}, Vector{Pair}}, ::Vector{Pair{String, String}})
 MethodInstance for valid_subtype(::Type{var"#s3"} where var"#s3"<:AbstractArray{T1, K}, ::Type{var"#s2"} where var"#s2"<:AbstractArray{T2, K}) where {T1, T2, K}
 MethodInstance for convert_md(::String, ::Vector{Franklin.LxDef})
 ⋮
 MethodInstance for Crayons.Crayon(::Crayons.ANSIColor, ::Crayons.ANSIColor, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle, ::Crayons.ANSIStyle)
 MethodInstance for Crayons.ANSIColor(::UInt8, ::UInt8, ::UInt8, ::Crayons.ColorMode, ::Bool)
 MethodInstance for Crayons.ANSIColor(::UInt8, ::UInt8, ::UInt8, ::Crayons.ColorMode, ::Bool)
 MethodInstance for Crayons.ANSIColor(::UInt8, ::UInt8, ::UInt8, ::Crayons.ColorMode, ::Bool)
 MethodInstance for convert_block(::Franklin.LxEnv, ::Vector{Franklin.LxDef})
 MethodInstance for resolve_lxobj(::Franklin.LxEnv, ::Vector{Franklin.LxDef})
 MethodInstance for var"#resolve_lxobj#164"(::Bool, ::typeof(Franklin.resolve_lxobj), ::Franklin.LxEnv, ::Vector{Franklin.LxDef})
 MethodInstance for (::Franklin.var"#86#87")(::Franklin.OCBlock)
 MethodInstance for (::Franklin.var"#172#174")(::Any)
 MethodInstance for html_ahref(::String, ::SubString{String})
 MethodInstance for Franklin.Token(::Symbol, ::SubString{String})
 MethodInstance for Franklin.Token(::Symbol, ::SubString{String}, ::Int64)
 MethodInstance for Franklin.Token(::Symbol, ::SubString{String}, ::Int64)
 MethodInstance for Franklin.Token(::Symbol, ::SubString{String})
 MethodInstance for Franklin.Token(::Symbol, ::SubString{String}, ::Int64)
 MethodInstance for Franklin.Token(::Symbol, ::SubString{String})
 MethodInstance for Franklin.Token(::Symbol, ::SubString{String}, ::Int64)
 MethodInstance for (::Franklin.var"#128#136")(::Any)
 MethodInstance for (::Franklin.var"#130#138")(::Any)
 MethodInstance for (NamedTuple{(:recursive,), T} where T<:Tuple)(::Tuple{Bool})
 MethodInstance for (::Franklin.var"#230#233"{NamedTuple{(:other, :infra, :md, :html, :literate), NTuple{5, Dict{Pair{String, String}, Float64}}}})(::Int64, ::LiveServer.SimpleWatcher)
 MethodInstance for HTTP.URIs.URI(::String, ::SubString{String}, ::SubString{String}, ::SubString{String}, ::SubString{String}, ::SubString{String}, ::SubString{String}, ::SubString{String})
 MethodInstance for mbed_err(::Int32)
 MethodInstance for setindex!(::Dict{String, Any}, ::Any, ::Any)

Does anyone know what's going on? Again, I'll emphasize that @snoopr does not record any invalidations with the same protocol.

timholy commented 3 years ago

I've inserted the following debugging code (everywhere in compiler/ that I can see a reference to max_world):

diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl
index 988f2eefa2..0542ecfa80 100644
--- a/base/compiler/inferencestate.jl
+++ b/base/compiler/inferencestate.jl
@@ -100,6 +100,9 @@ mutable struct InferenceState
             inmodule = linfo.def::Module
         end

+        if src.max_world < typemax(UInt)
+            println("here 1 with ", src)
+        end
         valid_worlds = WorldRange(src.min_world,
             src.max_world == typemax(UInt) ? get_world_counter() : src.max_world)
         frame = new(
diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl
index 5082b01ea5..6e63d0d393 100644
--- a/base/compiler/typeinfer.jl
+++ b/base/compiler/typeinfer.jl
@@ -353,6 +353,10 @@ function transform_result_for_cache(interp::AbstractInterpreter, linfo::MethodIn
     end
     if inferred_result isa CodeInfo
         inferred_result.min_world = first(valid_worlds)
+        if last(valid_worlds) < typemax(UInt)
+            println("here 2: inferred_result.max_world = ", inferred_result.max_world)
+            println("setting it to ", last(valid_worlds))
+        end
         inferred_result.max_world = last(valid_worlds)
         inferred_result = maybe_compress_codeinfo(interp, linfo, inferred_result)
     end
@@ -723,6 +727,10 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize
     mi = specialize_method(method, atypes, sparams)::MethodInstance
     code = get(code_cache(interp), mi, nothing)
     if code isa CodeInstance # return existing rettype if the code is already inferred
+        if max_world(code) < typemax(UInt)
+            println("here 3: max_world(code) = ", max_world(code))
+            println("caller state: ", caller.valid_worlds, " and ", caller.world)
+        end
         update_valid_age!(caller, WorldRange(min_world(code), max_world(code)))
         if isdefined(code, :rettype_const)
             if isa(code.rettype_const, Vector{Any}) && !(Vector{Any} <: code.rettype)
@@ -816,6 +824,10 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance)
                 tree.parent = mi
                 tree.rettype = Core.Typeof(code.rettype_const)
                 tree.min_world = code.min_world
+                if code.max_world < typemax(UInt)
+                    println("here 4, tree.max_world = ", tree.max_world)
+                    println("code.max_world = ", code.max_world)
+                end
                 tree.max_world = code.max_world
                 return tree
             elseif isa(inf, CodeInfo)
@@ -824,6 +836,10 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance)
                      inf.max_world == code.max_world &&
                      inf.rettype === code.rettype)
                     inf = copy(inf)
+                    if code.max_world < typemax(UInt)
+                        println("here 5, inf.max_world = ", inf.max_world)
+                        println("code.max_world = ", code.max_world)
+                    end
                     inf.min_world = code.min_world
                     inf.max_world = code.max_world
                     inf.rettype = code.rettype

It doesn't fire on the profile_test example above. Of course there are quite a few places in the C code where that gets set too.

timholy commented 3 years ago

OK, I've narrowed this a lot. Two sources that were essentially spurious:

There are still a few cases that seem to fall outside these categories, so I'll leave this open for now. I also don't know why it's useful to cache CodeInstances, if we're not going to be able to use them.