Skip to content

Commit 003a224

Browse files
authored
Merge pull request #1 from timholy/teh/backedges
Make this a real package
2 parents 65340b6 + c4d6d64 commit 003a224

File tree

10 files changed

+542
-42
lines changed

10 files changed

+542
-42
lines changed

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ language: julia
33
os:
44
- linux
55
- osx
6+
- windows
67
julia:
78
- 1.0
9+
- 1
810
- nightly
911
notifications:
1012
email: false
@@ -16,7 +18,7 @@ jobs:
1618
fast_finish: true
1719
include:
1820
- stage: Documentation
19-
julia: 1.0
21+
julia: 1
2022
script: julia --project=docs -e '
2123
using Pkg;
2224
Pkg.develop(PackageSpec(path=pwd()));

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ uuid = "85b6ec6f-f7df-4429-9514-a64bcd9ee824"
33
authors = ["Tim Holy <tim.holy@gmail.com>"]
44
version = "0.1.0"
55

6+
[deps]
7+
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
8+
69
[compat]
710
julia = "1"
11+
AbstractTrees = "0.3"
812

913
[extras]
1014
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# MethodAnalysis
22

33
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://timholy.github.io/MethodAnalysis.jl/stable)
4-
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://timholy.github.io/MethodAnalysis.jl/dev)
54
[![Build Status](https://travis-ci.com/timholy/MethodAnalysis.jl.svg?branch=master)](https://travis-ci.com/timholy/MethodAnalysis.jl)
65
[![Codecov](https://codecov.io/gh/timholy/MethodAnalysis.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/timholy/MethodAnalysis.jl)
6+
7+
This package is useful for inspecting Julia's internals, particularly its MethodInstances and their backedges. See the documentation for details.

demos/blog.jl

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
## Counting invalidations
2+
3+
using SnoopCompile
4+
umis(list) = unique(filter(x->isa(x, Core.MethodInstance), list))
5+
# length(umis(@snoopr using <SomePkg>))
6+
7+
## Counting MethodInstances
8+
9+
using MethodAnalysis
10+
using MethodAnalysis: equal
11+
function getmis(parent...) # use either 0 or 1 argument
12+
mis = Set{Core.MethodInstance}()
13+
visit(parent...) do mi
14+
isa(mi, Core.MethodInstance) && push!(mis, mi)
15+
end
16+
return collect(mis)
17+
end
18+
mis = getmis()
19+
20+
# counting by module
21+
22+
const modcounts = Dict{Module,Int}()
23+
for mi in mis
24+
modcounts[mi.def.module] = get(modcounts, mi.def.module, 0) + 1
25+
end
26+
27+
## When invalidations do and do not happen: the applyf example
28+
29+
f(::Int) = 1
30+
f(::Bool) = 2
31+
function applyf(container)
32+
x1 = f(container[1])
33+
x2 = f(container[2])
34+
return x1 + x2
35+
end
36+
c = Any[1, true]
37+
applyf(c)
38+
w2 = worlds(only(getmis(applyf)))
39+
code2 = only(code_typed(applyf, (Vector{Any},)))
40+
f(::String) = 3
41+
applyf(c)
42+
w3 = worlds(only(getmis(applyf)))
43+
code3 = only(code_typed(applyf, (Vector{Any},)))
44+
f(::AbstractVector) = 4 # if we replaced this with Vector{Int} we'd not have the unnecessary(?) invalidation
45+
applyf(c)
46+
w4 = worlds(only(getmis(applyf)))
47+
code4 = only(code_typed(applyf, (Vector{Any},)))
48+
f(::Missing) = 5
49+
applyf(c)
50+
w5 = worlds(only(getmis(applyf)))
51+
code5 = only(code_typed(applyf, (Vector{Any},)))
52+
f(::Nothing) = 6
53+
applyf(c)
54+
w6 = worlds(only(getmis(applyf)))
55+
code6 = only(code_typed(applyf, (Vector{Any},)))
56+
57+
# One apparently-unnecessary invalidation?
58+
@show equal(code3, code4)
59+
@show w3 == w4
60+
61+
## Determining the source of invalidations
62+
63+
# Mechanism 1: MethodInstances with TypeVar parameters
64+
function hastv(typ)
65+
isa(typ, UnionAll) && return true
66+
if isa(typ, DataType)
67+
for p in typ.parameters
68+
hastv(p) && return true
69+
end
70+
end
71+
return false
72+
end
73+
mitv = filter(mi->hastv(mi.specTypes), mis)
74+
mitvi = Set(with_all_backedges(mitv))

demos/specialization.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ end
1515
categorizemi(x) = nothing
1616

1717
visit(categorizemi)
18+
println("Found ", length(fully_specialized), " MethodInstances and ", length(nonspecialized), " other MethodInstances")
19+
1820
# 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))
2121
# Find specializations that have a TypeVar parameter
2222
function hastv(typ)
2323
isa(typ, UnionAll) && return true
@@ -28,18 +28,18 @@ function hastv(typ)
2828
end
2929
return false
3030
end
31-
ubasetv = filter(mi->hastv(mi.specTypes), ubase)
32-
println("There are ", length(ubasetv), " MethodInstances that have a TypeVar in specTypes")
31+
mitv = filter(mi->hastv(mi.specTypes), nonspecialized)
32+
println("There are ", length(mitv), " MethodInstances that have a TypeVar in specTypes")
3333

3434
# Let's analyze a specific case
3535
m = which(similar, (Vector,))
36-
mitv = Ref{Any}(nothing)
36+
_mi = Ref{Any}(nothing)
3737
visit(m.specializations) do mi
38-
mitv[] isa Core.MethodInstance && return nothing
39-
hastv(mi.specTypes) && (mitv[] = mi)
38+
_mi[] isa Core.MethodInstance && return nothing
39+
hastv(mi.specTypes) && (_mi[] = mi)
4040
return nothing
4141
end
42-
mi = mitv[]
42+
mi = _mi[]
4343
@assert(mi isa Core.MethodInstance)
4444
println("\n## Performing an analysis of ", mi, '\n')
4545
# Find the last callers with TypeVar specializations

docs/src/index.md

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,168 @@
11
# MethodAnalysis.jl
22

3-
```@index
3+
This package facilitates introspection of Julia's internals, with a particular focus on its MethodInstances and their backedges.
4+
5+
!!! warning
6+
Julia's internals are not subject to the same stability guarantee that the rest of the language enjoys.
7+
8+
## Demonstrations
9+
10+
A few demonstrations will give you a taste of what can be done with this package.
11+
12+
### Collecting all submodules of Base
13+
14+
```jldoctest
15+
julia> using MethodAnalysis
16+
17+
julia> mods = Module[];
18+
19+
julia> visit(Base; print=false) do obj
20+
if isa(obj, Module)
21+
push!(mods, obj)
22+
end
23+
end
24+
25+
julia> Base.FastMath ∈ mods
26+
true
27+
```
28+
29+
### Collecting all Methods in Core.Compiler
30+
31+
`visit` also descends into functions, methods, and MethodInstances:
32+
33+
```jldoctest; setup=:(using MethodAnalysis)
34+
julia> meths = []
35+
Any[]
36+
37+
julia> visit(Core.Compiler) do item # without print=false it will display the modules it visits
38+
isa(item, Method) && push!(meths, item)
39+
end
40+
Module Core.Compiler
41+
Module Core.Compiler.CoreDocs
42+
Module Core.Compiler.Iterators
43+
Module Core.Compiler.Order
44+
Module Core.Compiler.Sort
45+
Module Core.Compiler.Sort.Float
46+
47+
julia> first(methods(Core.Compiler.typeinf_ext)) ∈ meths
48+
true
49+
```
50+
51+
### Getting a MethodInstance for a particular set of types
52+
53+
```jldoctest; setup=:(using MethodAnalysis)
54+
julia> foo(::AbstractVector) = 1
55+
foo (generic function with 1 method)
56+
57+
julia> instance(foo, (Vector{Int},)) # we haven't called it yet, so it's not compiled
58+
59+
julia> foo([1,2])
60+
1
61+
62+
julia> instance(foo, (Vector{Int},))
63+
MethodInstance for foo(::Array{Int64,1})
64+
```
65+
66+
### Collecting a subset of MethodInstances for a particular function
67+
68+
Let's collect all single-argument compiled instances of `findfirst`:
69+
70+
```jldoctest findfirst; setup=:(using MethodAnalysis)
71+
julia> mis = Core.MethodInstance[];
72+
73+
julia> visit(findfirst) do item
74+
isa(item, Core.MethodInstance) && length(Base.unwrap_unionall(item.specTypes).parameters) == 2 && push!(mis, item)
75+
end
76+
77+
julia> mis
78+
1-element Array{Core.MethodInstance,1}:
79+
MethodInstance for findfirst(::BitArray{1})
80+
```
81+
82+
We checked that the length was 2, rather than 1, because the first parameter is the function type itself:
83+
84+
```jldoctest findfirst
85+
julia> mis[1].specTypes
86+
Tuple{typeof(findfirst),BitArray{1}}
487
```
588

6-
```@autodocs
7-
Modules = [MethodAnalysis]
89+
### Getting the backedges for a function
90+
91+
Let's see all the compiled instances of `Base.setdiff` and their immediate callers:
92+
93+
```jldoctest; setup=(using MethodAnalysis)
94+
julia> direct_backedges(setdiff)
95+
3-element Array{Any,1}:
96+
MethodInstance for setdiff(::Base.KeySet{Any,Dict{Any,Any}}, ::Base.KeySet{Any,Dict{Any,Any}}) => MethodInstance for keymap_merge(::Dict{Char,Any}, ::Dict{Any,Any})
97+
MethodInstance for setdiff(::Base.KeySet{Any,Dict{Any,Any}}, ::Base.KeySet{Any,Dict{Any,Any}}) => MethodInstance for keymap_merge(::Any, ::Dict{Any,Any})
98+
MethodInstance for setdiff(::Array{Base.UUID,1}, ::Array{Base.UUID,1}) => MethodInstance for deps_graph(::Pkg.Types.Context, ::Dict{Base.UUID,String}, ::Dict{Base.UUID,Pkg.Types.VersionSpec}, ::Dict{Base.UUID,Pkg.Resolve.Fixed})
99+
```
100+
101+
### Printing backedges as a tree
102+
103+
MethodAnalysis uses [AbstractTrees](https://github.com/JuliaCollections/AbstractTrees.jl) to display the complete set of backedges:
104+
105+
```jldoctest; setup=:(using MethodAnalysis)
106+
julia> mi = instance(findfirst, (BitVector,))
107+
MethodInstance for findfirst(::BitArray{1})
108+
109+
julia> MethodAnalysis.print_tree(mi)
110+
MethodInstance for findfirst(::BitArray{1})
111+
├─ MethodInstance for prune_graph!(::Graph)
112+
│ └─ MethodInstance for #simplify_graph!#111(::Bool, ::typeof(simplify_graph!), ::Graph, ::Set{Int64})
113+
│ └─ MethodInstance for simplify_graph!(::Graph, ::Set{Int64})
114+
│ └─ MethodInstance for simplify_graph!(::Graph)
115+
│ ├─ MethodInstance for trigger_failure!(::Graph, ::Array{Int64,1}, ::Tuple{Int64,Int64})
116+
│ │ ⋮
117+
│ │
118+
│ └─ MethodInstance for resolve_versions!(::Context, ::Array{PackageSpec,1})
119+
│ ⋮
120+
121+
└─ MethodInstance for update_solution!(::SolutionTrace, ::Graph)
122+
└─ MethodInstance for converge!(::Graph, ::Messages, ::SolutionTrace, ::NodePerm, ::MaxSumParams)
123+
├─ MethodInstance for converge!(::Graph, ::Messages, ::SolutionTrace, ::NodePerm, ::MaxSumParams)
124+
│ ├─ MethodInstance for converge!(::Graph, ::Messages, ::SolutionTrace, ::NodePerm, ::MaxSumParams)
125+
│ │ ├─ MethodInstance for converge!(::Graph, ::Messages, ::SolutionTrace, ::NodePerm, ::MaxSumParams)
126+
│ │ │ ⋮
127+
│ │ │
128+
│ │ └─ MethodInstance for maxsum(::Graph)
129+
│ │ ⋮
130+
│ │
131+
│ └─ MethodInstance for maxsum(::Graph)
132+
│ └─ MethodInstance for resolve(::Graph)
133+
│ ⋮
134+
135+
└─ MethodInstance for maxsum(::Graph)
136+
└─ MethodInstance for resolve(::Graph)
137+
├─ MethodInstance for trigger_failure!(::Graph, ::Array{Int64,1}, ::Tuple{Int64,Int64})
138+
│ ⋮
139+
140+
└─ MethodInstance for resolve_versions!(::Context, ::Array{PackageSpec,1})
141+
142+
```
143+
144+
## API reference
145+
146+
### visit
147+
148+
```@docs
149+
visit
150+
visit_backedges
151+
```
152+
153+
### backedges
154+
155+
```@docs
156+
all_backedges
157+
direct_backedges
158+
terminal_backedges
159+
with_all_backedges
160+
```
161+
162+
### utilities
163+
164+
```@docs
165+
instance
166+
call_type
167+
worlds
8168
```

0 commit comments

Comments
 (0)