Skip to content

Commit

Permalink
add "#pragma compile [true]" for opt-in to automatic compilation when…
Browse files Browse the repository at this point in the history
… a module is required (closes JuliaLang#12462)
  • Loading branch information
stevengj committed Aug 6, 2015
1 parent b78659e commit 79ae5c0
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 3 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
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
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

0 comments on commit 79ae5c0

Please sign in to comment.