Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add "#pragma compile [true]" for opt-in to automatic compilation #12475

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ New language features
but instead of loading it into the current session saves the result of compiling it in
`~/.julia/lib/v0.4` ([#8745]).

* Put `#pragma compilable` at the top of your module file to automatically compile it when it is imported ([#12475]).

* See manual section on `Module initialization and precompilation` (under `Modules`) for details and errata.

* New option `--output-incremental={yes|no}` added to invoke the equivalent of ``Base.compile`` from the command line.
Expand Down Expand Up @@ -1557,3 +1559,4 @@ Too numerous to mention.
[#12137]: https://github.com/JuliaLang/julia/issues/12137
[#12162]: https://github.com/JuliaLang/julia/issues/12162
[#12393]: https://github.com/JuliaLang/julia/issues/12393
[#12475]: https://github.com/JuliaLang/julia/issues/12475
53 changes: 53 additions & 0 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,45 @@ function find_all_in_cache_path(mod::Symbol)
paths
end

const r_compilable = r"^#\s*pragma\s+compile(\s+true)?\s*$"
const r_ncompilable = r"^#\s*pragma\s+compile\s+false\s*$"

# return true if "#pragma compilable [true]" appears in the file before
# any Julia code, false for "#pragma compile false", default otherwise
function compilable(path::AbstractString, default::Bool=false)
return open(path, "r") do f
for line in eachline(f)
s = lstrip(line)
if !isempty(s)
if s[1] == '#'
ismatch(r_compilable, s) && return true
ismatch(r_ncompilable, s) && return false
else
return default
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this does the wrong thing for lines inside a block comment. Or #pragma compilable would also have to be documented as needing to come before any multi-line block comments.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh multiline comments, whoever implemented such an annoying feature?

Initially, we could just tend to document it as coming before #= ... =# block comments (in practice I don't think this would affect people much), but I agree that you'd probably want to relax that eventually. Fortunately, parsing comments (even with nesting) is easy, and this could be added incrementally.

end
end
end
return default
end
end

# compile path on node 1 if path is #pragma compilable,
# returning the cachefile path, or nothing otherwise
function autocompile_on_node1(mod::Symbol, path::AbstractString)
if myid() == 1
if compilable(path)
if isinteractive()
info("Compiling module $mod from $path...")
end
return compile(mod)
else
return nothing
end
else
return remotecall_fetch(1, autocompile_on_node1, mod, path)
end
end

function _include_from_serialized(content::Vector{UInt8})
return ccall(:jl_restore_incremental_from_buf, Any, (Ptr{Uint8},Int), content, sizeof(content))
end
Expand Down Expand Up @@ -166,6 +205,19 @@ function require(mod::Symbol)
name = string(mod)
path = find_in_node_path(name, source_dir(), 1)
path === nothing && throw(ArgumentError("$name not found in path"))

if last || nprocs() == 1
cachefile = autocompile_on_node1(mod, path)
if cachefile !== nothing
if nothing === _require_from_serialized(1, cachefile, last)
warn("require failed to create a precompiled cache file")
else
return
end
end
end

# could not compile, just include(path)
if last && myid() == 1 && nprocs() > 1
# broadcast top-level import/using from node 1 (only)
content = open(readall, path)
Expand Down Expand Up @@ -289,6 +341,7 @@ function compile(name::ByteString)
myid() == 1 || error("can only compile from node 1")
path = find_in_path(name)
path === nothing && throw(ArgumentError("$name not found in path"))
!compilable(path, true) && throw(ArgumentError("$name has #pragma compile false"))
cachepath = LOAD_CACHE_PATH[1]
if !isdir(cachepath)
mkpath(cachepath)
Expand Down
10 changes: 9 additions & 1 deletion doc/manual/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,15 @@ To create a custom system image that can be used to start julia with the -J opti
recompile Julia after modifying the file ``base/userimg.jl`` to require the desired modules.

To create an incremental precompiled module file,
call ``Base.compile(modulename::Symbol)``.
you can call ``Base.compile(modulename::Symbol)``. Alternatively, if you
put ``#pragma compile`` at the top of your module file (before any
non-comment code), then the module will be automatically compiled the
first time it is imported. Compiling a module also recursively compiles
any modules that are imported therein. If you know that it is *not*
safe to compile your module, you should put ``#pragma compile false``
at the top of its file, which cause ``Base.compile`` to throw an error
(and thereby prevent the module from being imported any other compiled
module).
The resulting cache files will be stored in ``Base.LOAD_CACHE_PATH[1]``.

In order to make your module work with precompilation,
Expand Down
7 changes: 5 additions & 2 deletions test/compile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ try

open(file, "w") do f
print(f, """
# Auto-compile this module:
# pragma compile
module $Foo_module
@doc "foo function" foo(x) = x + 1
include_dependency("foo.jl")
Expand All @@ -22,7 +24,9 @@ try
""")
end

cachefile = Base.compile(Foo_module)
# make sure the "#pragma compile" causes Foo to be compiled
cachefile = Base.autocompile_on_node1(Foo_module, file)
@test nothing !== cachefile

# use _require_from_serialized to ensure that the test fails if
# the module doesn't load from the image:
Expand All @@ -41,7 +45,6 @@ try
[:Base,:Core,:Main])
@test sort(deps[2]) == [file,joinpath(dir,"bar.jl"),joinpath(dir,"foo.jl")]
end

finally
splice!(Base.LOAD_CACHE_PATH, 1)
splice!(LOAD_PATH, 1)
Expand Down