Skip to content

Commit

Permalink
add "#pragma compilable [=true]" for opt-in to automatic compilation …
Browse files Browse the repository at this point in the history
…when a module is required (closes JuliaLang#12462)
  • Loading branch information
stevengj committed Aug 5, 2015
1 parent 1fcfa4a commit 7692b69
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 12 deletions.
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
73 changes: 64 additions & 9 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+compilable(\s*=\s*true)?\s*$"
const r_ncompilable = r"^#\s*pragma\s+compilable\s*=\s*false\s*$"

# return true if "#pragma compilable [=true]" appears in the file before
# any Julia code, false for "#pragma compilable=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
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 @@ -84,6 +123,14 @@ function _require_from_serialized(node::Int, path_to_try::ByteString, toplevel_l
restored = _include_from_serialized(content)
end
# otherwise, continue search

if restored !== nothing
for M in restored
if isdefined(M, :__META__)
push!(Base.Docs.modules, M)
end
end
end
return restored
end

Expand Down Expand Up @@ -119,15 +166,9 @@ function require(mod::Symbol)
last = toplevel_load::Bool
try
toplevel_load = false
restored = _require_from_serialized(1, mod, last)
if restored !== nothing
for M in restored
if isdefined(M, :__META__)
push!(Base.Docs.modules, M)
end
end
return true
end
if nothing !== _require_from_serialized(1, mod, last)
return
end
if JLOptions().incremental != 0
# spawn off a new incremental compile task from node 1 for recursive `require` calls
cachefile = compile(mod)
Expand All @@ -140,6 +181,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 @@ -262,6 +316,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 compilable=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 compilable`` 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 compilable=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
10 changes: 8 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 compilable
module $Foo_module
@doc "foo function" foo(x) = x + 1
module Bar
Expand All @@ -20,8 +22,12 @@ try
""")
end

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

# make sure the compiled module is successfully loaded:
@test nothing !== Base._require_from_serialized(1, cachefile, true)
finally
splice!(Base.LOAD_CACHE_PATH, 1)
splice!(LOAD_PATH, 1)
Expand Down

0 comments on commit 7692b69

Please sign in to comment.