Skip to content

Commit e6b7fa8

Browse files
Add docs for isprecompilable (#416)
This also enhances its implementation to make it more flexible. Closes #411 --------- Co-authored-by: Charles Kawczynski <kawczynski.charles@gmail.com>
1 parent 15a33ee commit e6b7fa8

File tree

5 files changed

+106
-15
lines changed

5 files changed

+106
-15
lines changed

docs/src/reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ InferenceTrigger
4646
runtime_inferencetime
4747
SnoopCompile.parcel
4848
SnoopCompile.write
49+
SnoopCompile.isprecompilable
50+
SnoopCompile.known_type
4951
report_callee
5052
report_callees
5153
report_caller

docs/src/tutorials/snoop_inference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ Users are encouraged to read the ProfileView documentation to understand how to
154154
- right-clicking on a box opens the corresponding method in your editor
155155
- ctrl-click can be used to zoom in
156156
- empty horizontal spaces correspond to activities other than type-inference
157-
- any boxes colored red (there are none in this particular example, but you'll see some later) correspond to *naively non-precompilable* `MethodInstance`s, in which the method is owned by one module but the types are from another unrelated module. Such `MethodInstance`s are omitted from the precompile cache file unless they've been "marked" by `PrecompileTools.@compile_workload` or an explicit `precompile` directive.
157+
- any boxes colored red (there are none in this particular example, but you'll see some later) correspond to *naively non-precompilable* `MethodInstance`s, in which the method is owned by one module but the types are from another unrelated module. Such `MethodInstance`s are omitted from the precompile cache file unless they've been "marked" by `PrecompileTools.@compile_workload` or an explicit `precompile` directive. See [`SnoopCompile.isprecompilable`](@ref) for further explanation and some examples.
158158
- any boxes colored orange-yellow (there is one in this demo) correspond to methods inferred for specific constants (constant propagation).
159159

160160
You can explore this flamegraph and compare it to the output from `print_tree`.

docs/src/tutorials/snoop_inference_parcel.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ In such cases, one alternative is to create a manual list of precompile directiv
1313
`precompile` directives have to be emitted by the module that owns the method and/or types.
1414
SnoopCompile comes with a tool, `parcel`, that splits out the "root-most" precompilable MethodInstances into their constituent modules.
1515
This will typically correspond to the bottom row of boxes in the [flame graph](@ref flamegraph).
16-
In cases where you have some that are not naively precompilable, they will include MethodInstances from higher up in the call tree.
16+
In cases where you have some that are not naively precompilable (see [`SnoopCompile.isprecompilable`](@ref)), they may include MethodInstances from higher up in the call tree.
1717

1818
Let's use `SnoopCompile.parcel` on our [`OptimizeMe`](@ref inferrability) demo:
1919

src/parcel_snoop_inference.jl

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -260,26 +260,108 @@ end
260260

261261
## parcel and supporting infrastructure
262262

263-
function isprecompilable(mi::MethodInstance; excluded_modules=Set([Main::Module]))
263+
"""
264+
isprecompilable(mod::Module, mi::MethodInstance)
265+
isprecompilable(mi::MethodInstance; excluded_modules=Set([Main::Module]))
266+
267+
Determine whether `mi` is able to be precompiled within `mod`. This requires
268+
that all the types in `mi`'s specialization signature are "known" to `mod`. See
269+
[`SnoopCompile.known_type`](@ref) for more information.
270+
271+
`isprecompilable(mi)` sets `mod` to the module in which the corresponding method
272+
was defined. If `mod ∈ excluded_modules`, then `isprecompilable` returns
273+
`false`.
274+
275+
If `mi` has been compiled by the time its defining module "closes" (the final
276+
`end` of the module definition) and `isprecompilable(mi)` returns `true`, then
277+
Julia will automatically include this specialization in that module's precompile
278+
cache.
279+
280+
!!! tip If `mi` is a MethodInstance corresponding to `f(::T)`, then calling
281+
`f(x::T)` before the end of the module definition suffices to force
282+
compilation of `mi`. Alternatively, use `precompile(f, (T,))`.
283+
284+
If you'd like to cache it but `isprecompilable(mi)` returns `false`, you need to
285+
identify a module `mod` for which `isprecompilable(mod, mi)` returns `true`.
286+
However, just ensuring that `mi` gets compiled within `mod` may not be
287+
sufficient to ensure that it gets retained in the cache: by default, Julia will
288+
omit it from the cache if none of the types are "owned" by that module. (For
289+
example, if `mod` didn't define the method, and all the types in `mi`'s
290+
signature come from other modules imported by `mod`, then `mod` does not "own"
291+
any aspect of `mi`.) To force it to be retained, ensure it gets called (for the
292+
first time) within a `PrecompileTools.@compile_workload` block. (This is the
293+
main purpose of PrecompileTools.)
294+
295+
# Examples
296+
297+
```jldoctest isprecompilable; setup=:(using SnoopCompile)
298+
julia> module A
299+
a(x) = x
300+
end
301+
Main.A
302+
303+
julia> module B
304+
using ..A
305+
struct BType end # this type is not known to A
306+
b(x) = x
307+
end
308+
Main.B
309+
```
310+
311+
Now let's run these methods to generate some compiled `MethodInstance`s:
312+
313+
```jldoctest isprecompilable
314+
julia> A.a(3.2) # Float64 is not "owned" by A, but A loads Base so A knows about it
315+
3.2
316+
317+
julia> A.a(B.BType()) # B.BType is not known to A
318+
Main.B.BType()
319+
320+
julia> B.b(B.BType()) # B knows about B.BType
321+
Main.B.BType()
322+
323+
julia> mia1, mia2 = Base.specializations(only(methods(A.a)));
324+
325+
julia> @show mia1 SnoopCompile.isprecompilable(mia1);
326+
mia1 = MethodInstance for Main.A.a(::Float64)
327+
SnoopCompile.isprecompilable(mia1) = true
328+
329+
julia> @show mia2 SnoopCompile.isprecompilable(mia2);
330+
mia2 = MethodInstance for Main.A.a(::Main.B.BType)
331+
SnoopCompile.isprecompilable(mia2) = false
332+
333+
julia> mib = only(Base.specializations(only(methods(B.b))))
334+
MethodInstance for Main.B.b(::Main.B.BType)
335+
336+
julia> SnoopCompile.isprecompilable(mib)
337+
true
338+
339+
julia> SnoopCompile.isprecompilable(A, mib)
340+
false
341+
```
342+
"""
343+
function isprecompilable(mod::Module, mi::MethodInstance)
264344
m = mi.def
265345
if isa(m, Method)
266-
mod = m.module
267-
can_eval = excluded_modules === nothing || mod excluded_modules
268-
if can_eval
269-
params = Base.unwrap_unionall(mi.specTypes)::DataType
270-
for p in params.parameters
271-
if p isa Type
272-
if !known_type(mod, p)
273-
can_eval = false
274-
break
275-
end
276-
end
346+
params = Base.unwrap_unionall(mi.specTypes)::DataType
347+
for p in params.parameters
348+
if p isa Type
349+
known_type(mod, p) || return false
277350
end
278351
end
279-
return can_eval
352+
return true
280353
end
281354
return false
282355
end
356+
function isprecompilable(mi::MethodInstance; excluded_modules=Set([Main::Module]))
357+
m = mi.def
358+
isa(m, Method) || return false
359+
mod = m.module
360+
if excluded_modules !== nothing
361+
mod excluded_modules && return false
362+
end
363+
return isprecompilable(mod, mi)
364+
end
283365

284366
struct Precompiles
285367
mi_info::InferenceFrameInfo # entrance point to inference (the "root")

src/utils.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ let known_type_cache = IdDict{Tuple{Module,Tuple{Vararg{Symbol}},Symbol},Bool}()
6060
return true
6161
end
6262
end
63+
@doc """
64+
known_type(mod::Module, T::Union{Type,TypeVar})
65+
66+
Returns `true` if the type `T` is "known" to the module `mod`, meaning that one could have written
67+
a function with signature `f(x::T)` in `mod` without getting an error.
68+
""" known_type
69+
6370

6471
function add_repr!(list, modgens::Dict{Module, Vector{Method}}, mi::MethodInstance, topmod::Module=mi.def.module; check_eval::Bool, time=nothing, suppress_time::Bool=false, kwargs...)
6572
# Create the string representation of the signature

0 commit comments

Comments
 (0)