Skip to content

Commit b73c7db

Browse files
authored
Add child_modules and module-owned MIs (#30)
`methodinstances_owned_by` captures the MethodInstances that can be serialized in current versions of Julia.
1 parent 001723f commit b73c7db

4 files changed

Lines changed: 151 additions & 2 deletions

File tree

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ AbstractTrees = "0.3"
1111
julia = "1"
1212

1313
[extras]
14+
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
1415
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
1516
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
1617
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1718

1819
[targets]
19-
test = ["Logging", "Pkg", "Test"]
20+
test = ["ImageCore", "Logging", "Pkg", "Test"]

docs/src/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ julia> Base.FastMath ∈ mods
2828
true
2929
```
3030

31+
You can do this more easily with the convenience utility [`child_modules`](@ref).
32+
3133
### Collecting all Methods in Core.Compiler
3234

3335
`visit` also descends into functions, methods, and MethodInstances:
@@ -187,8 +189,11 @@ with_all_backedges
187189
```@docs
188190
methodinstance
189191
methodinstances
192+
methodinstances_owned_by
193+
child_modules
190194
call_type
191195
findcallers
196+
hasbox
192197
worlds
193198
```
194199

src/MethodAnalysis.jl

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ using Base.Meta: isexpr
88

99
export visit, call_type, methodinstance, methodinstances, worlds # findcallers is exported from its own file
1010
export visit_backedges, all_backedges, with_all_backedges, terminal_backedges, direct_backedges
11+
export child_modules, methodinstances_owned_by
1112
export hasbox
1213

1314
include("visit.jl")
@@ -150,7 +151,8 @@ julia> methodinstances(m)
150151
```
151152
152153
Note the method `m` was broader than the signature we queried with, and the returned `MethodInstance`s reflect that breadth.
153-
See [`methodinstances`](@ref) for a more restrictive subset.
154+
See [`methodinstances`](@ref) for a more restrictive subset, and [`methodinstances_owned_by`](@ref) for collecting
155+
MethodInstances owned by specific modules.
154156
"""
155157
function methodinstances(top=())
156158
if isa(top, Module) || isa(top, Function) || isa(top, Type) || isa(top, Method) || isa(top, Base.MethodList)
@@ -190,6 +192,96 @@ function methodinstances(@nospecialize(types::Type))
190192
return methodinstances(f, types)
191193
end
192194

195+
"""
196+
mods = child_modules(mod::Module; external::Bool=false)
197+
198+
Return a list that includes `mod` and all sub-modules of `mod`.
199+
By default, modules loaded from other sources (e.g., packages or those
200+
defined by Julia itself) are excluded, even if exported (or `@reexport`ed,
201+
see https://github.com/simonster/Reexport.jl), unless you set `external=true`.
202+
203+
# Examples
204+
205+
```jldoctest
206+
julia> module Outer
207+
module Inner
208+
export Base
209+
end
210+
end
211+
Main.Outer
212+
213+
julia> child_modules(Outer)
214+
2-element Vector{Module}:
215+
Main.Outer
216+
Main.Outer.Inner
217+
218+
julia> child_modules(Outer.Inner)
219+
1-element Vector{Module}:
220+
Main.Outer.Inner
221+
```
222+
223+
# Extended help
224+
225+
In the example above, because of the `export Base`, the following `visit`-based implementation would
226+
also collect `Base` and all of its sub-modules:
227+
228+
```jldoctest
229+
julia> mods = Module[]
230+
Module[]
231+
232+
julia> visit(Outer) do item
233+
if item isa Module
234+
push!(mods, item)
235+
return true
236+
end
237+
return false
238+
end
239+
240+
julia> Base ∈ mods
241+
true
242+
243+
julia> length(mods) > 20
244+
true
245+
```
246+
"""
247+
function child_modules(mod::Module; external::Bool=false)
248+
function rootmodule(m::Module)
249+
m == mod && return m # anything under `mod` has a root of `mod`
250+
pm = parentmodule(m)
251+
m == pm && return m
252+
return rootmodule(pm)
253+
end
254+
mods = Module[]
255+
visit(mod) do item
256+
if item isa Module && (external || rootmodule(item) == mod)
257+
push!(mods, item)
258+
return true
259+
end
260+
return false # don't recurse into Methods, MethodTables, MethodInstances, etc.
261+
end
262+
return mods
263+
end
264+
265+
"""
266+
mis = methodinstances_owned_by(mod::Module; include_child_modules::Bool=true, kwargs...)
267+
268+
Return a list of `MethodInstance`s that are owned by `mod`. If `include_child_modules` is `true`,
269+
this includes sub-modules of `mod`, in which case `kwargs` are passed to [`child_modules`](@ref).
270+
271+
The primary difference between `methodinstances(mod)` and `methodinstances_owned_by(mod)` is that
272+
the latter excludes `MethodInstances` that belong to re-exported dependent packages.
273+
"""
274+
function methodinstances_owned_by(mod::Module; include_child_modules::Bool=true, kwargs...)
275+
mods = include_child_modules ? child_modules(mod; kwargs...) : [mod]
276+
# get all MethodInstances owned by one of the modules in `mods`
277+
# these are the only MethodInstances that can be precompiled in current versions of Julia
278+
return filter(methodinstances(mod)) do mi
279+
m = mi.def
280+
m isa Method && return m.module mods
281+
return m mods
282+
end
283+
end
284+
193285
if isdefined(Base, :code_typed_by_type)
194286
function hasbox(mi::MethodInstance)
195287
try

test/runtests.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using MethodAnalysis
22
using Test
33
using Logging
4+
using ImageCore
45
using Pkg
56

67
module Outer
@@ -71,6 +72,30 @@ end
7172
end
7273
end
7374

75+
@testset "child_modules" begin
76+
m = Module()
77+
Base.eval(m, :(
78+
module Inner
79+
export Base
80+
end))
81+
mmods = child_modules(m)
82+
@test m mmods
83+
@test m.Inner mmods
84+
@test length(mmods) == 2
85+
imods = child_modules(m.Inner)
86+
@test m.Inner mmods
87+
@test length(imods) == 1
88+
89+
# Base is interesting because it's not its own parent
90+
bmods = child_modules(Base)
91+
@test Base bmods
92+
@test Base.Sort bmods
93+
@test Main bmods
94+
smods = child_modules(Base.Sort)
95+
@test Base smods
96+
@test Base.Sort smods
97+
end
98+
7499
@testset "methodinstance(s)" begin
75100
sum(1:3)
76101
mi = methodinstance(sum, (UnitRange{Int},))
@@ -102,6 +127,32 @@ end
102127
end
103128
end
104129

130+
@testset "AbstractTrees integration" begin
131+
mi = methodinstance(findfirst, (BitVector,))
132+
io = IOBuffer()
133+
MethodAnalysis.AbstractTrees.print_tree(io, mi)
134+
str = String(take!(io))
135+
@test occursin("├─", str)
136+
end
137+
138+
@testset "methodinstances_owned_by" begin
139+
function owned_by(mi::Core.MethodInstance, mod::Module)
140+
m = mi.def
141+
m isa Method && return m.module == mod
142+
return m == mod
143+
end
144+
# ImageCore is a package that does a lot of `@reexport`ing
145+
mis = methodinstances(ImageCore)
146+
@test any(mi -> owned_by(mi, ImageCore), mis)
147+
@test any(mi -> owned_by(mi, ImageCore.ColorTypes), mis) # ColorTypes is a dependency of ImageCore
148+
mis = methodinstances_owned_by(ImageCore)
149+
@test any(mi -> owned_by(mi, ImageCore), mis)
150+
@test !any(mi -> owned_by(mi, ImageCore.ColorTypes), mis)
151+
mis = methodinstances_owned_by(ImageCore; external=true)
152+
@test any(mi -> owned_by(mi, ImageCore), mis)
153+
@test any(mi -> owned_by(mi, ImageCore.ColorTypes), mis)
154+
end
155+
105156
@testset "Backedges" begin
106157
mi = methodinstance(Outer.Inner.h, (Int,))
107158
@test length(all_backedges(mi)) == 2

0 commit comments

Comments
 (0)