Skip to content

Commit b92529f

Browse files
authored
Fix the demos on abstract signatures (#16)
1 parent 92f6dcd commit b92529f

File tree

2 files changed

+190
-44
lines changed

2 files changed

+190
-44
lines changed

demos/abstract.jl

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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

demos/abstract_analyze_versions.jl

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Do `include("abstract.jl")` on two Julia versions and then run this script.
2+
# You will likely have to change the path for `sigstable` below.
3+
# Typically this should be run using the in-development version of Julia (or whatever "latest" is in your comparison)
4+
15
using PyPlot
26

37
function parseline(line)
@@ -41,10 +45,23 @@ function split_comparable(sigc1, sigc2)
4145
return sigs, c1, c2
4246
end
4347

44-
sigc16 = parsedata("/tmp/methdata_$VERSION.log")
45-
sigc14 = parsedata("/tmp/methdata_1.4.3-pre.0.log")
48+
function tally0(c1, c2)
49+
nz1, nz2 = 0, 0
50+
for (a1, a2) in zip(c1, c2)
51+
a1 == a2 == 0 && continue
52+
a1 == 0 && (nz1 += 1)
53+
a2 == 0 && (nz2 += 1)
54+
end
55+
return nz1, nz2
56+
end
57+
58+
sigmaster = parsedata("/tmp/methdata_$VERSION.log")
59+
sigstable, stablever = parsedata("/tmp/methdata_1.5.1-pre.29.log"), "1.5"
60+
# sigstable, stablever = parsedata("/tmp/methdata_1.4.2.log"), "1.4"
4661

47-
sigs, c1, c2 = split_comparable(sigc14, sigc16)
62+
sigs, c1, c2 = split_comparable(sigstable, sigmaster)
63+
nz1, nz2 = tally0(c1, c2)
64+
println("$stablever has $nz1 with no backedges, master has $nz2")
4865
mx1, mx2 = maximum(c1), maximum(c2)
4966
isexported(sig) = (ft = Base.unwrap_unionall(sig).parameters[1]; isdefined(Main, ft.name.mt.name))
5067
colors = [isexported(sig) ? "magenta" : "green" for sig in sigs]
@@ -56,52 +73,26 @@ function on_click(event)
5673
println(sigs[idx])
5774
end
5875
begin
76+
hfig, axs = plt.subplots(2, 1)
77+
plt.subplots_adjust(hspace=0.3)
78+
logedges = LinRange(0, log10(max(mx1, mx2)+2), 30)
79+
ax = axs[1]
80+
ax.hist(log10.(c1 .+ 1), bins=logedges)
81+
ax.set_xlabel("log₁₀(# backedges + 1), $stablever")
82+
ax.set_ylabel("# 'at risk' signatures")
83+
ax = axs[2]
84+
ax.hist(log10.(c2 .+ 1), bins=logedges)
85+
ax.set_xlabel("log₁₀(# backedges + 1), 1.6")
86+
ax.set_ylabel("# 'at risk' signatures")
87+
88+
display(hfig)
5989
fig, ax = plt.subplots()
6090
ax.scatter(c1 .+ 1, c2 .+ 1, c=colors) # + 1 for the log-scaling
61-
ax.set_xlabel("# backedges + 1, 1.4")
91+
ax.set_xlabel("# backedges + 1, $stablever")
6292
ax.set_ylabel("# backedges + 1, 1.6")
6393
ax.set_xscale("log")
6494
ax.set_yscale("log")
95+
ax.set_aspect("equal")
6596
fig.canvas.callbacks.connect("button_press_event", on_click)
6697
fig
6798
end
68-
69-
# Ones we've made progress on:
70-
# ==(::Any, Symbol)
71-
# ==(::Symbol, ::Any)
72-
# ==(::Any, ::Nothing)
73-
# ==(::UUID, ::Any)
74-
# ==(::AbstractString, ::String)
75-
# isequal(::Symbol, ::Any)
76-
# isequal(::Any, ::Symbol)
77-
# isequal(::Any, ::Nothing)
78-
# isequal(::UUID, ::Any)
79-
# cmp(::AbstractString, ::String)
80-
# convert(::Type{Int}, ::Integer)
81-
# convert(::Type{UInt}, ::Integer)
82-
# convert(::Type{Union{Nothing,Module}}, ::Any)
83-
# Base.to_index(::Integer)
84-
# iterate(::Base.OneTo, ::Any)
85-
# repr(::Any)
86-
# thisind(::AbstractString, ::Int)
87-
# getindex(::String, ::Any)
88-
# string(::String, ::Integer, ::String)
89-
# ^(::String, ::Integer)
90-
# repeat(::String, ::Integer)
91-
# Base.isidentifier(::AbstractString)
92-
# +(::Ptr{UInt8}, ::Integer)
93-
# Base._show_default(::Base.GenericIOBuffer{Array{UInt8,1}}, ::Any)
94-
95-
# Ones that are better but I don't remember helping with
96-
# isconcretetype(::Any)
97-
# pointer(::String, ::Integer)
98-
99-
# Regressions:
100-
# basename(::AbstractString)
101-
# splitdir(::AbstractString)
102-
# isfile(::Any)
103-
# joinpath(::AbstractString, ::String)
104-
# sizeof(::Unsigned)
105-
# +(::Int, ::Any, ::Any)
106-
# Base.split_sign(::Integer)
107-
# in(::Any, ::Tuple{Symbol})

0 commit comments

Comments
 (0)