Skip to content

Commit 65340b6

Browse files
committed
Improve the API and add specializations demo
1 parent 70856be commit 65340b6

File tree

5 files changed

+116
-28
lines changed

5 files changed

+116
-28
lines changed

demos/specialization.jl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using MethodAnalysis
2+
using OrderedCollections
3+
using CodeTracking
4+
5+
const fully_specialized = OrderedSet{Core.MethodInstance}()
6+
const nonspecialized = OrderedSet{Core.MethodInstance}()
7+
8+
function categorizemi(mi::Core.MethodInstance)
9+
bin = all(isconcretetype, Base.unwrap_unionall(mi.specTypes).parameters) ?
10+
fully_specialized : nonspecialized
11+
push!(bin, mi)
12+
return nothing
13+
end
14+
15+
categorizemi(x) = nothing
16+
17+
visit(categorizemi)
18+
# We're especially interested in the nonspecialized ones.
19+
# Start with the ones in Base
20+
ubase = collect(filter(x->x.def.module === Base, nonspecialized))
21+
# Find specializations that have a TypeVar parameter
22+
function hastv(typ)
23+
isa(typ, UnionAll) && return true
24+
if isa(typ, DataType)
25+
for p in typ.parameters
26+
hastv(p) && return true
27+
end
28+
end
29+
return false
30+
end
31+
ubasetv = filter(mi->hastv(mi.specTypes), ubase)
32+
println("There are ", length(ubasetv), " MethodInstances that have a TypeVar in specTypes")
33+
34+
# Let's analyze a specific case
35+
m = which(similar, (Vector,))
36+
mitv = Ref{Any}(nothing)
37+
visit(m.specializations) do mi
38+
mitv[] isa Core.MethodInstance && return nothing
39+
hastv(mi.specTypes) && (mitv[] = mi)
40+
return nothing
41+
end
42+
mi = mitv[]
43+
@assert(mi isa Core.MethodInstance)
44+
println("\n## Performing an analysis of ", mi, '\n')
45+
# Find the last callers with TypeVar specializations
46+
callers = Core.MethodInstance[]
47+
visit_backedges(mi) do caller
48+
hastv(caller.specTypes) || return false
49+
if !isdefined(caller, :backedges)
50+
push!(callers, caller)
51+
return false
52+
end
53+
foundone = false
54+
for edge in caller.backedges
55+
if !hastv(edge.specTypes)
56+
push!(callers, caller)
57+
foundone = true
58+
break
59+
end
60+
end
61+
return !foundone
62+
end
63+
# Let's look at the code of these callers
64+
for caller in callers
65+
for be in caller.backedges
66+
f, t = call_type(be.specTypes)
67+
println("caller: ", be.def, " with argtypes ", t)
68+
code_warntype(f, t)
69+
println()
70+
end
71+
end

src/MethodAnalysis.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,21 @@ module MethodAnalysis
22

33
using Core: MethodInstance, SimpleVector
44

5-
export visit, visit_backedges, all_backedges, terminal_backedges
5+
export visit, visit_backedges, all_backedges, terminal_backedges, call_type
66

77
include("visit.jl")
88
include("backedges.jl")
99

10+
"""
11+
call_type(tt)
12+
13+
Split a signature type like `Tuple{typeof(f),ArgTypes...}` back out to `(f, Tuple{ArgTypes...})`
14+
"""
15+
function call_type(tt)
16+
ft = tt.parameters[1]
17+
argt = Tuple{tt.parameters[2:end]...}
18+
name = ft.name
19+
return (getfield(name.module, Symbol(String(name.name)[2:end])), argt)
20+
end
21+
1022
end # module

src/backedges.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function all_backedges(mi)
22
backedges = Set{MethodInstance}()
3-
visit_backedges(x->push!(backedges, x), mi)
3+
visit_backedges(x->(push!(backedges, x); true), mi)
44
delete!(backedges, mi)
55
return collect(backedges)
66
end
@@ -11,6 +11,7 @@ function terminal_backedges(mi)
1111
if !isdefined(x, :backedges) || isempty(x.backedges)
1212
push!(backedges, x)
1313
end
14+
true
1415
end
1516
delete!(backedges, mi)
1617
return collect(backedges)

src/visit.jl

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
"""
2+
visit(operation)
3+
4+
Scan all loaded modules with `operation`. `operation(x)` should handle `x::Module`, `x::Function`,
5+
`x::Method`, `x::MethodInstance`. Any return value from `operation` will be discarded.
6+
"""
17
function visit(operation)
28
visiting=Set{Module}()
39
for mod in Base.loaded_modules_array()
@@ -73,16 +79,23 @@ end
7379

7480
visit(operation, x) = nothing
7581

82+
"""
83+
visit_backedges(operation, mi::MethodInstance)
84+
85+
Visit the backedges of `mi` and apply `operation`.
86+
`operation(edge::MethodInstance)` should return `true` if the backedges of `edge` should in turn be visited,
87+
`false` otherwise.
88+
"""
7689
visit_backedges(operation, mi::MethodInstance) =
7790
visit_backedges(operation, mi, Set{MethodInstance}())
7891

7992
function visit_backedges(operation, mi, visited)
8093
mi visited && return nothing
8194
push!(visited, mi)
82-
operation(mi)
83-
if isdefined(mi, :backedges)
84-
for be in mi.backedges
85-
visit_backedges(operation, be, visited)
95+
status = operation(mi)
96+
if status && isdefined(mi, :backedges)
97+
for edge in mi.backedges
98+
visit_backedges(operation, edge, visited)
8699
end
87100
end
88101
return nothing

test/runtests.jl

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,25 @@ end
2323
@test !Outer.callh(1.0)
2424
@test Outer.callcallh(1)
2525

26-
function mmatches(mi, mod, name, spectypes)
27-
mi.def.module === mod || return false
28-
mi.def.name === name || return false
29-
for (t1, t2) in zip(Iterators.drop(mi.specTypes.parameters, 1), spectypes)
30-
t1 === t2 || return false
31-
end
32-
return true
33-
end
34-
3526
mis = Core.MethodInstance[]
3627
visit(Outer) do x
3728
isa(x, Core.MethodInstance) && push!(mis, x)
3829
end
39-
@test any(mi->mmatches(mi, Outer, :f, (Nothing,)), mis)
40-
@test count(mi->mmatches(mi, Outer.Inner, :g, (String,)), mis) == 1
41-
@test !any(mi->mmatches(mi, Outer.Inner, :g, (SubString,)), mis)
42-
@test any(mi->mmatches(mi, Outer.Inner, :h, (Int,)), mis)
43-
@test any(mi->mmatches(mi, Outer.Inner, :h, (Float64,)), mis)
44-
@test any(mi->mmatches(mi, Outer, :callh, (Int,)), mis)
45-
@test any(mi->mmatches(mi, Outer, :callh, (Float64,)), mis)
46-
@test any(mi->mmatches(mi, Outer, :callcallh, (Int,)), mis)
47-
@test !any(mi->mmatches(mi, Outer, :callcallh, (Float64,)), mis)
30+
@test any(mi->mi.specTypes === Tuple{typeof(Outer.f), Nothing}, mis)
31+
@test count(mi->mi.specTypes === Tuple{typeof(Outer.Inner.g), String}, mis) == 1
32+
@test !any(mi->mi.specTypes=== Tuple{typeof(Outer.Inner.g), SubString{String}}, mis)
33+
@test any(mi->mi.specTypes === Tuple{typeof(Outer.Inner.h), Int}, mis)
34+
@test any(mi->mi.specTypes === Tuple{typeof(Outer.Inner.h), Float64}, mis)
35+
@test any(mi->mi.specTypes === Tuple{typeof(Outer.callh), Int}, mis)
36+
@test any(mi->mi.specTypes === Tuple{typeof(Outer.callh), Float64}, mis)
37+
@test any(mi->mi.specTypes === Tuple{typeof(Outer.callcallh), Int}, mis)
38+
@test !any(mi->mi.specTypes === Tuple{typeof(Outer.callcallh), Float64}, mis)
4839

49-
mi = mis[findfirst(mi->mmatches(mi, Outer.Inner, :h, (Int,)), mis)]
40+
mi = mis[findfirst(mi->mi.specTypes === Tuple{typeof(Outer.Inner.h), Int}, mis)]
5041
@test length(all_backedges(mi)) == 2
51-
@test terminal_backedges(mi) == [mis[findfirst(mi->mmatches(mi, Outer, :callcallh, (Int,)), mis)]]
52-
mi = mis[findfirst(mi->mmatches(mi, Outer.Inner, :h, (Float64,)), mis)]
42+
@test terminal_backedges(mi) == [mis[findfirst(mi->mi.specTypes === Tuple{typeof(Outer.callcallh), Int}, mis)]]
43+
mi = mis[findfirst(mi->mi.specTypes === Tuple{typeof(Outer.Inner.h), Float64}, mis)]
5344
@test length(all_backedges(mi)) == 1
54-
@test terminal_backedges(mi) == [mis[findfirst(mi->mmatches(mi, Outer, :callh, (Float64,)), mis)]]
45+
@test terminal_backedges(mi) == [mis[findfirst(mi->mi.specTypes === Tuple{typeof(Outer.callh), Float64}, mis)]]
5546
@test all_backedges(mi) == terminal_backedges(mi)
5647
end

0 commit comments

Comments
 (0)