Skip to content

Commit

Permalink
Simplify the API to @precompile_module(M) (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
rikhuijzer authored Apr 30, 2022
1 parent a7575b2 commit 656a6c1
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 79 deletions.
4 changes: 1 addition & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
name = "PrecompileSignatures"
uuid = "91cefc8d-f054-46dc-8f8c-26e11d7c5411"
authors = ["Rik Huijzer <[email protected]>"]
version = "1.0.1"
version = "2.0.0"

[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"

[compat]
Documenter = "0.27"
Scratch = "1.1"
julia = "1.6"

[extras]
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ pkg> activate Foo
(Foo) pkg> add PrecompileSignatures
```

Next, add the following somewhere in your code:
Next, add `@precompile_module(Foo)` somewhere after your module's logic.
For example:

```julia
using PrecompileSignatures: precompile_directives
module Foo

if ccall(:jl_generating_output, Cint, ()) == 1
include(precompile_directives(Foo))
end
using PrecompileSignatures: @precompile_module

[...]

# Include generated `precompile` directives for this module.
@precompile_module(Foo)

end # module
```

This will generate extra `precompile` directives during the precompilation phase and `include` the generated file.
Expand Down
97 changes: 35 additions & 62 deletions src/PrecompileSignatures.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
module PrecompileSignatures

using Documenter.Utilities: submodules
using Scratch: get_scratch!

export precompilables, precompile_directives, write_directives
export precompile_directives, write_directives, @precompile_module

function _is_macro(f::Function)
text = sprint(show, MIME"text/plain"(), f)
Expand Down Expand Up @@ -136,17 +135,12 @@ end
const SUBMODULES_DEFAULT = true
const SPLIT_UNIONS_DEFAULT = true
const TYPE_CONVERSIONS_DEFAULT = Dict{DataType,DataType}(AbstractString => String)
const DEFAULT_WRITE_HEADER = """
# This file is machine-generated by PrecompileSignatures.jl.
# Editing it directly is not advised.\n
"""

"""
Config(
submodules::Bool=$SUBMODULES_DEFAULT,
split_unions::Bool=$SPLIT_UNIONS_DEFAULT,
type_conversions::Dict{DataType,DataType}=$TYPE_CONVERSIONS_DEFAULT,
header::String=\$DEFAULT_WRITE_HEADER
type_conversions::Dict{DataType,DataType}=$TYPE_CONVERSIONS_DEFAULT
)
Configuration for generating precompile directives.
Expand All @@ -159,16 +153,11 @@ Keyword arguments:
- `abstracttype_conversions`:
Mapping of conversions from on type to another.
For example, for all method signatures containing and argument of type `AbstractString`, you can decide to add a precompile directive for `String` for that type.
- `header`:
Header used when writing the directives to a file.
Defaults to:
$DEFAULT_WRITE_HEADER
"""
@Base.kwdef struct Config
submodules::Bool=SUBMODULES_DEFAULT
split_unions::Bool=SPLIT_UNIONS_DEFAULT
type_conversions::Dict{DataType,DataType}=TYPE_CONVERSIONS_DEFAULT
header::String=DEFAULT_WRITE_HEADER
end

"""
Expand Down Expand Up @@ -242,65 +231,49 @@ function precompilables(M::Module, config::Config=Config())::Vector{DataType}
end

"""
write_directives(path::AbstractString, types::Vector{DataType}, config::Config=Config())
write_directives(path::AbstractString, M::AbstractVector{Module}, config::Config=Config())
_precompile_type(argt::Type)
Write precompile directives to file.
Compile the given `argt` such as `Tuple{typeof(sum), Vector{Int}}`.
"""
function write_directives(
path::AbstractString,
types::Vector{DataType},
config::Config=Config()
)::String
directives = ["precompile($t)" for t in types]
text = string(config.header, join(directives, '\n'))
write(path, text)
return text
function _precompile_type(@nospecialize(argt::Type))
# Not calling `precompile` since that specializes on types before #43990 (Julia < 1.8).
ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
return ret
end
function write_directives(
path::AbstractString,
M::AbstractVector{Module},
config::Config=Config()
)::String
types = precompilables(M, config)
return write_directives(path, types, config)
end
write_directives(path, M::Module, config=Config()) = write_directives(path, [M], config)

function _precompile_path(M::Module)
dir = get_scratch!(M, string(M))
mkpath(dir)
return joinpath(dir, "_precompile.jl")
"This function is called from within `@precompile_module`."
function _precompile_module(M::Module, config::Config=Config())
types = precompilables(M, config)
for type in types
_precompile_type(type)
end
return nothing
end

"""
precompile_directives(M::Module, config::Config=Config())::String
Return the path to a file containing generated `precompile` directives.
@precompile_module(M)
!!! note
This package needs to write the signatures to a file and then include that.
Evaluating the directives directly via `eval` will cause "incremental compilation fatally broken" errors.
Precompile the concrete signatures in module `M`.
"""
function precompile_directives(M::Module, config::Config=Config())::String
# This has to be wrapped in a try-catch to avoid other packages to fail completely.
try
path = _precompile_path(M)
types = precompilables(M, config)
write_directives(path, types, config)
return path
catch e
@warn "Generating precompile directives failed" exception=(e, catch_backtrace())
# Write empty file so that `include(precompile_directives(...))` succeeds.
path, _ = mktemp()
write(path, "")
return path
end
macro precompile_module(M::Symbol)
esc(quote
if ccall(:jl_generating_output, Cint, ()) == 1
try
$PrecompileSignatures._precompile_module($M)
catch e
msg = "Generating and including the `precompile` directives failed"
@warn msg exception=(e, catch_backtrace())
end
end
end)
end

# Include generated `precompile` directives.
if ccall(:jl_generating_output, Cint, ()) == 1
include(precompile_directives(PrecompileSignatures))
end
# This method is only used by the tests.
# The tests check whether this method is precompiled.
# It isn't called by anything so if it is precompiled it must be due to the call below.
_test_precompiled(x::Int) = 3

# Include generated `precompile` directives for this module.
@precompile_module(PrecompileSignatures)

end # module
12 changes: 3 additions & 9 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,15 @@ sig = Tuple{M.a, Union{String, Number}, Union{Float32, String}}
@test isempty(P._directives_datatypes(sig, Config(; split_unions=false)))
@test P._directives_datatypes(Tuple{M.a, Int}, Config()) == [Tuple{M.a, Int}]

@test Set(precompilables(M)) == Set([
@test Set(P.precompilables(M)) == Set([
Tuple{typeof(Main.M.a), Int},
Tuple{typeof(Main.M.b), Float64},
Tuple{typeof(Main.M.b), Float32}
])

types = precompilables(PlutoRunner)
types = P.precompilables(PlutoRunner)
@test 40 < length(types)
# Test whether the precompile directives are correct. Super important.
@test all(precompile.(types))

mktemp() do path, io
types = precompilables(M)
text = write_directives(path, types)
@test contains(text, "machine-generated")
@test contains(text, "precompile(Tuple{typeof(Main.M.a), Int")
end

@test !isempty(only(methods(PrecompileSignatures._test_precompiled)).specializations)

2 comments on commit 656a6c1

@rikhuijzer
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/59409

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v2.0.0 -m "<description of version>" 656a6c13835f5fdb4718b0ece476644ce66bca7d
git push origin v2.0.0

Please sign in to comment.