|
| 1 | +using MethodAnalysis |
| 2 | + |
| 3 | + |
| 4 | +""" |
| 5 | + atrisktyp(tt) |
| 6 | +
|
| 7 | +Given a Tuple-type signature (e.g., `Tuple{typeof(sum),Vector{Int}}`), determine whether this signature |
| 8 | +is "at risk" for invalidation. Essentially it returns `true` if one or more arguments are of abstract type, |
| 9 | +although there are prominent exceptions: |
| 10 | +
|
| 11 | +- `Function` is allowed |
| 12 | +- any constructor call is allowed |
| 13 | +- `convert(X, x)` where `isa(x, X)` is true |
| 14 | +- `setindex!` and `push!` methods where the valtype is a subtype of the eltype (likewise keytype for AbstractDicts) |
| 15 | +- `getindex`, `length`, `isempty`, and `iterate` on any tuple |
| 16 | +""" |
| 17 | +function atrisktype(@nospecialize(typ)) |
| 18 | + # signatures like `convert(Vector, a)`, `foo(::Vararg{Synbol,N}) where N` do not seem to pose a problem |
| 19 | + isa(typ, TypeVar) && return false |
| 20 | + # isbits parameters are not a problem |
| 21 | + isa(typ, Type) || return false |
| 22 | + if isa(typ, UnionAll) |
| 23 | + typ = Base.unwrap_unionall(typ) |
| 24 | + end |
| 25 | + # Exclude signatures with Union{} |
| 26 | + typ === Union{} && return false |
| 27 | + isa(typ, Union) && return atrisktype(typ.a) | atrisktype(typ.b) |
| 28 | + # Type{T}: signatures like `convert(::Type{AbstractString}, ::String)` are not problematic |
| 29 | + typ <: Type && return false |
| 30 | + if typ <: Tuple && length(typ.parameters) >= 1 |
| 31 | + p1 = typ.parameters[1] |
| 32 | + # Constructor calls are not themselves a problem (any `convert`s they trigger might be, but those are covered) |
| 33 | + isa(p1, Type) && p1 <: Type && return false |
| 34 | + # convert(::Type{T}, ::S) where S<:T is not problematic |
| 35 | + if p1 === typeof(Base.convert) || p1 === typeof(Core.convert) |
| 36 | + p2, p3 = typ.parameters[2], typ.parameters[3] |
| 37 | + if isa(p2, Type) |
| 38 | + p2 = Base.unwrap_unionall(p2) |
| 39 | + if isa(p2, DataType) && length(p2.parameters) === 1 |
| 40 | + T = p2.parameters[1] |
| 41 | + isa(p3, Type) && isa(T, Type) && p3 <: T && return false |
| 42 | + end |
| 43 | + end |
| 44 | + # `getindex`, `length`, etc are OK for various Tuple{T1,T2,...} |
| 45 | + elseif p1 === typeof(Base.getindex) || |
| 46 | + p1 === typeof(Base.length) || |
| 47 | + p1 === typeof(Base.isempty) || |
| 48 | + p1 === typeof(Base.iterate) || p1 === typeof(Core.iterate) |
| 49 | + p2 = typ.parameters[2] |
| 50 | + if isa(p2, Type) |
| 51 | + p2 = Base.unwrap_unionall(p2) |
| 52 | + p2 <: Tuple && return false |
| 53 | + end |
| 54 | + # show(io::IO, x) is OK as long as typeof(x) is safe |
| 55 | + elseif p1 === typeof(Base.show) || p1 === typeof(Base.print) || p1 === typeof(Base.println) |
| 56 | + # atrisktype(typ.parameters[2]) && return true |
| 57 | + for i = 3:length(typ.parameters) |
| 58 | + atrisktype(typ.parameters[i]) && return true |
| 59 | + end |
| 60 | + return false |
| 61 | + # setindex!(a, x, idx) and push!(a, x) are safe if typeof(x) <: eltype(a) |
| 62 | + elseif (p1 === typeof(Base.setindex!) || p1 === typeof(Base.push!)) && length(typ.parameters) >= 3 |
| 63 | + p2, p3 = typ.parameters[2], typ.parameters[3] |
| 64 | + if isconcretetype(p2) |
| 65 | + if p2 <: AbstractDict && length(typ.parameters) >= 4 |
| 66 | + p4 = typ.parameters[4] |
| 67 | + p3 <: valtype(p2) && p4 <: keytype(p2) && return false |
| 68 | + else |
| 69 | + p3 <: eltype(p2) && return false |
| 70 | + end |
| 71 | + end |
| 72 | + end |
| 73 | + end |
| 74 | + # Standard DataTypes |
| 75 | + isconcretetype(typ) && return false |
| 76 | + # ::Function args are excluded |
| 77 | + typ === Function && return false |
| 78 | + !isempty(typ.parameters) && (any(atrisktype, typ.parameters) || return false) |
| 79 | + return true |
| 80 | +end |
| 81 | + |
| 82 | +@assert atrisktype(Tuple{typeof(==),Any,Any}) |
| 83 | +@assert atrisktype(Tuple{typeof(==),Symbol,Any}) |
| 84 | +@assert atrisktype(Tuple{typeof(==),Any,Symbol}) |
| 85 | +@assert !atrisktype(Tuple{typeof(==),Symbol,Symbol}) |
| 86 | +@assert !atrisktype(Tuple{typeof(convert),Type{Any},Any}) |
| 87 | +@assert !atrisktype(Tuple{typeof(convert),Type{AbstractString},AbstractString}) |
| 88 | +@assert !atrisktype(Tuple{typeof(convert),Type{AbstractString},String}) |
| 89 | +@assert atrisktype(Tuple{typeof(convert),Type{String},AbstractString}) |
| 90 | +@assert !atrisktype(Tuple{typeof(map),Function,Vector{Any}}) |
| 91 | +@assert !atrisktype(Tuple{typeof(getindex),Dict{Union{String,Int},Any},Union{String,Int}}) |
| 92 | +@assert atrisktype(Tuple{typeof(getindex),Dict{Union{String,Int},Any},Any}) |
| 93 | +@assert !atrisktype(Tuple{Type{BoundsError},Any,Any}) |
| 94 | +@assert atrisktype(Tuple{typeof(sin),Any}) |
| 95 | +@assert !atrisktype(Tuple{typeof(length),Tuple{Any,Any}}) |
| 96 | +@assert atrisktype(Tuple{typeof(setindex!),Vector{Int},Any,Int}) |
| 97 | +@assert !atrisktype(Tuple{typeof(setindex!),Vector{Any},Any,Int}) |
| 98 | +@assert atrisktype(Tuple{typeof(push!),Vector{Int},Any}) |
| 99 | +@assert !atrisktype(Tuple{typeof(push!),Vector{Any},Any}) |
| 100 | + |
| 101 | +isexported(mi::Core.MethodInstance) = isdefined(Main, mi.def.name) |
| 102 | +getfunc(mi::Core.MethodInstance) = getfunc(mi.def) |
| 103 | +getfunc(m::Method) = getfield(m.module, m.name) |
| 104 | +nmethods(mi::Core.MethodInstance) = length(methods(getfunc(mi))) |
| 105 | + |
| 106 | +# Test whether a module is Core.Compiler or inside it |
| 107 | +# Methods there are protected from invalidation by other means |
| 108 | +function fromcc(mod::Module) |
| 109 | + fn = fullname(mod) |
| 110 | + return length(fn) >= 2 && fn[1] === :Core && fn[2] === :Compiler |
| 111 | +end |
| 112 | + |
| 113 | +const mis = Dict{Method,Vector{Core.MethodInstance}}() |
| 114 | +visit() do item |
| 115 | + if item isa Method && !fromcc(item.module) |
| 116 | + m = item |
| 117 | + mis[m] = methodinstances(m) |
| 118 | + return false |
| 119 | + end |
| 120 | + return true |
| 121 | +end |
| 122 | + |
| 123 | +# Count # of backedges for MethodInstances with abstract types |
| 124 | +const becounter = Dict{Core.MethodInstance,Int}() |
| 125 | +visit() do item |
| 126 | + if item isa Core.MethodInstance && !fromcc(item.def.module) |
| 127 | + if atrisktype(item.specTypes) |
| 128 | + becounter[item] = length(all_backedges(item)) |
| 129 | + end |
| 130 | + return false |
| 131 | + end |
| 132 | + return true |
| 133 | +end |
| 134 | + |
| 135 | +prs = sort!(collect(becounter); by=last) |
| 136 | +open("/tmp/methdata_$VERSION.log", "w") do io |
| 137 | + for (mi, c) in prs |
| 138 | + c == 0 && continue |
| 139 | + println(io, mi.specTypes=>c) |
| 140 | + end |
| 141 | +end |
| 142 | + |
| 143 | +# Split into exported & private functions |
| 144 | +mtup = (nmethods = 0, nbackedges = 0) |
| 145 | +miexp = Pair{Core.MethodInstance,typeof(mtup)}[] |
| 146 | +mipriv = similar(miexp) |
| 147 | +for (mi, c) in prs |
| 148 | + n = nmethods(mi) |
| 149 | + pr = mi=>(nmethods=n, nbackedges=c) |
| 150 | + if isexported(mi) |
| 151 | + push!(miexp, pr) |
| 152 | + else |
| 153 | + push!(mipriv, pr) |
| 154 | + end |
| 155 | +end |
0 commit comments