diff --git a/demos/abstract_analyze_versions.jl b/demos/abstract_analyze_versions.jl new file mode 100644 index 0000000..e860553 --- /dev/null +++ b/demos/abstract_analyze_versions.jl @@ -0,0 +1,107 @@ +using PyPlot + +function parseline(line) + m = match(r"(.*) => (.*)", line) + sigstr, count = m.captures[1], parse(Int, m.captures[2]) + sig = try + ex = Meta.parse(sigstr) + eval(ex) + catch + return nothing + end + return sig, count +end + +function parsedata(filename) + lines = readlines(filename) + sigcount = IdDict{Any,Int}() + for line in lines + ret = parseline(line) + ret === nothing && continue + sig, count = ret + sigcount[sig] = count + end + return sigcount +end + +function split_comparable(sigc1, sigc2) + c1, c2, sigs = Int[], Int[], Any[] + for (sig, c) in sigc1 + push!(sigs, sig) + push!(c1, sigc1[sig]) + push!(c2, get(sigc2, sig, 0)) + end + for (sig, c) in sigc2 + if !haskey(sigc1, sig) + push!(sigs, sig) + push!(c1, 0) + push!(c2, c) + end + end + return sigs, c1, c2 +end + +sigc16 = parsedata("/tmp/methdata_$VERSION.log") +sigc14 = parsedata("/tmp/methdata_1.4.3-pre.0.log") + +sigs, c1, c2 = split_comparable(sigc14, sigc16) +mx1, mx2 = maximum(c1), maximum(c2) +isexported(sig) = (ft = Base.unwrap_unionall(sig).parameters[1]; isdefined(Main, ft.name.mt.name)) +colors = [isexported(sig) ? "magenta" : "green" for sig in sigs] + +function on_click(event) + x, y = event.xdata, event.ydata + normsqrdist(pr) = ((pr[1]-x)/mx1)^2 + ((pr[2]-y)/mx2)^2 + idx = argmin(normsqrdist.(zip(c1, c2))) + println(sigs[idx]) +end +begin + fig, ax = plt.subplots() + ax.scatter(c1 .+ 1, c2 .+ 1, c=colors) # + 1 for the log-scaling + ax.set_xlabel("# backedges + 1, 1.4") + ax.set_ylabel("# backedges + 1, 1.6") + ax.set_xscale("log") + ax.set_yscale("log") + fig.canvas.callbacks.connect("button_press_event", on_click) + fig +end + +# Ones we've made progress on: +# ==(::Any, Symbol) +# ==(::Symbol, ::Any) +# ==(::Any, ::Nothing) +# ==(::UUID, ::Any) +# ==(::AbstractString, ::String) +# isequal(::Symbol, ::Any) +# isequal(::Any, ::Symbol) +# isequal(::Any, ::Nothing) +# isequal(::UUID, ::Any) +# cmp(::AbstractString, ::String) +# convert(::Type{Int}, ::Integer) +# convert(::Type{UInt}, ::Integer) +# convert(::Type{Union{Nothing,Module}}, ::Any) +# Base.to_index(::Integer) +# iterate(::Base.OneTo, ::Any) +# repr(::Any) +# thisind(::AbstractString, ::Int) +# getindex(::String, ::Any) +# string(::String, ::Integer, ::String) +# ^(::String, ::Integer) +# repeat(::String, ::Integer) +# Base.isidentifier(::AbstractString) +# +(::Ptr{UInt8}, ::Integer) +# Base._show_default(::Base.GenericIOBuffer{Array{UInt8,1}}, ::Any) + +# Ones that are better but I don't remember helping with +# isconcretetype(::Any) +# pointer(::String, ::Integer) + +# Regressions: +# basename(::AbstractString) +# splitdir(::AbstractString) +# isfile(::Any) +# joinpath(::AbstractString, ::String) +# sizeof(::Unsigned) +# +(::Int, ::Any, ::Any) +# Base.split_sign(::Integer) +# in(::Any, ::Tuple{Symbol}) diff --git a/demos/abstract_gen_data.jl b/demos/abstract_gen_data.jl new file mode 100644 index 0000000..6acd9c9 --- /dev/null +++ b/demos/abstract_gen_data.jl @@ -0,0 +1,129 @@ +using MethodAnalysis + +# Analyze MethodInstance signatures and select those that seem at risk for being invalidated. +function atrisktype(@nospecialize(typ)) + # signatures like `convert(Vector, a)`, `foo(::Vararg{Synbol,N}) where N` do not seem to pose a problem + isa(typ, TypeVar) && return false + # isbits parameters are not a problem + isa(typ, Type) || return false + if isa(typ, UnionAll) + typ = Base.unwrap_unionall(typ) + end + # Exclude signatures with Union{} + typ === Union{} && return false + isa(typ, Union) && return atrisktype(typ.a) | atrisktype(typ.b) + # Type{T}: signatures like `convert(::Type{AbstractString}, ::String)` are not problematic, so mark Type as OK + typ <: Type && return false + if typ <: Tuple && length(typ.parameters) >= 1 + p1 = typ.parameters[1] + # Constructor calls are not themselves a problem (any `convert`s they trigger might be, but those are covered) + isa(p1, Type) && p1 <: Type && return false + # convert(::Type{T}, ::S) where S<:T is not problematic + if p1 === typeof(Base.convert) || p1 === typeof(Core.convert) || p1 === typeof(Core.Compiler.convert) + p2, p3 = typ.parameters[2], typ.parameters[3] + if isa(p2, Type) + p2 = Base.unwrap_unionall(p2) + if isa(p2, DataType) && length(p2.parameters) === 1 + T = p2.parameters[1] + isa(p3, Type) && isa(T, Type) && p3 <: T && return false + end + end + # `getindex`, `length`, etc are OK for various Tuple{T1,T2,...} + elseif (p1 === typeof(Base.getindex) || p1 === typeof(Core.Compiler.getindex)) || + (p1 === typeof(Base.length) || p1 === typeof(Core.Compiler.length)) || + (p1 === typeof(Base.isempty) || p1 === typeof(Core.Compiler.isempty)) || + (p1 === typeof(Base.iterate) || p1 === typeof(Core.iterate) || p1 === typeof(Core.Compiler.iterate)) + p2 = typ.parameters[2] + if isa(p2, Type) + p2 = Base.unwrap_unionall(p2) + p2 <: Tuple && return false + end + # show(io::IO, x) is OK as long as typeof(x) is safe + elseif p1 === typeof(Base.show) + atrisktype(typ.parameters[2]) && return true + length(typ.parameters) == 3 && atrisktype(typ.parameters[3]) && return true + return false + end + end + # Standard DataTypes + isconcretetype(typ) && return false + # ::Function args are excluded + typ === Function && return false + !isempty(typ.parameters) && (any(atrisktype, typ.parameters) || return false) + return true +end + +# A few tests +@assert atrisktype(Tuple{typeof(==),Any,Any}) +@assert atrisktype(Tuple{typeof(==),Symbol,Any}) +@assert atrisktype(Tuple{typeof(==),Any,Symbol}) +@assert !atrisktype(Tuple{typeof(==),Symbol,Symbol}) +@assert !atrisktype(Tuple{typeof(convert),Type{Any},Any}) +@assert !atrisktype(Tuple{typeof(convert),Type{AbstractString},AbstractString}) +@assert !atrisktype(Tuple{typeof(convert),Type{AbstractString},String}) +@assert atrisktype(Tuple{typeof(convert),Type{String},AbstractString}) +@assert !atrisktype(Tuple{typeof(map),Function,Vector{Any}}) +@assert !atrisktype(Tuple{typeof(getindex),Dict{Union{String,Int},Any},Union{String,Int}}) +@assert atrisktype(Tuple{typeof(getindex),Dict{Union{String,Int},Any},Any}) +@assert !atrisktype(Tuple{Type{BoundsError},Any,Any}) +@assert atrisktype(Tuple{typeof(sin),Any}) +@assert !atrisktype(Tuple{typeof(length),Tuple{Any,Any}}) + +function collect_mis(m::Method) + list = Core.MethodInstance[] + visit(m) do item + if isa(item, Core.MethodInstance) + push!(list, item) + return false + end + return true + end + return list +end + +const mis = Dict{Method,Vector{Core.MethodInstance}}() +visit() do item + if item isa Method + m = item + mis[m] = collect_mis(m) + return false + end + return true +end + +# Count # of backedges for MethodInstances with abstract types +const becounter = Dict{Core.MethodInstance,Int}() +visit() do item + if item isa Core.MethodInstance + if atrisktype(item.specTypes) + becounter[item] = length(all_backedges(item)) + end + return false + end + return true +end + +prs = sort!(collect(becounter); by=last) + +# Organize them by method instead + +const mcounter = Dict{Method,Int}() +for (mi, c) in becounter + oc = get(mcounter, mi.def, 0) + mcounter[mi.def] = oc + c +end + +mprs = sort!(collect(mcounter); by=last) + +open("/tmp/methinstdata_$VERSION.log", "w") do io + for (mi, c) in prs + c == 0 && continue + println(io, mi.specTypes=>c) + end +end +open("/tmp/methdata_$VERSION.log", "w") do io + for (m, c) in mprs + c == 0 && continue + println(io, m.sig=>c) + end +end