Skip to content
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
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ New library functions
inspecting which function `f` was originally wrapped. ([#42717])
* New `pkgversion(m::Module)` function to get the version of the package that loaded
a given module, similar to `pkgdir(m::Module)`. ([#45607])
* New function `Base.is_serializing_code()` to replace `ccall(:jl_generating_output, Cint, ()) == 1` which is being
used widely for guarding precompilation code. ([#45650])

Library changes
---------------
Expand Down
44 changes: 42 additions & 2 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,7 @@ If a module or file is *not* safely precompilable, it should call `__precompile_
order to throw an error if Julia attempts to precompile it.
"""
@noinline function __precompile__(isprecompilable::Bool=true)
if !isprecompilable && ccall(:jl_generating_output, Cint, ()) != 0
if !isprecompilable && is_serializing_code()
throw(PrecompilableError())
end
nothing
Expand Down Expand Up @@ -1285,7 +1285,7 @@ function _require(pkg::PkgId)
end

if JLOptions().use_compiled_modules != 0
if (0 == ccall(:jl_generating_output, Cint, ())) || (JLOptions().incremental != 0)
if !is_serializing_code() || (JLOptions().incremental != 0)
# spawn off a new incremental pre-compile task for recursive `require` calls
# or if the require search declared it was pre-compiled before (and therefore is expected to still be pre-compilable)
cachefile = compilecache(pkg, path)
Expand Down Expand Up @@ -2125,10 +2125,50 @@ macro __DIR__()
return isempty(_dirname) ? pwd() : abspath(_dirname)
end

"""
Base.is_serializing_code()

Returns whether julia is being run in a mode where code is being serialized
to file. For instance during precompilation.

For example, to run or precompile code only during package precompilation:
```julia
module FooBar

foo(x::Int, y::Float64) = ...

if Base.is_serializing_code()
foo(1, 1.0) # execute this method of foo to ensure it and any internal methods are precompiled

precompile(foo, (Int, Float64)) # precompile just this method of foo without executing it
end
end # module
```

Note that the first example here actually executes `foo(1, 1.0)` so can be slower, but is usually more robust
to changes in the codebase than the latter approach of using `precompile` statements. The latter requires explicitly
calling `precompile` on methods that cannot be inferred. i.e. if `foo(1, 1.0)` calls some methods by runtime dispatch,
the dispatched calls must be independently `precompile`d. Consequently the latter can be more work to maintain, but can be
automated via tooling such as the package `SnoopCompile.jl`.

It is therefore recommended, where possible, to find minimal code to execute to get the desired precompilation coverage.

Guarding precompilation code behind this check is also best practice because in some situations,
downstream users may not want to use the precompilation caching, which can be disabled globally
via `--compiled-modules=no`.

See also [`precompile`](@ref).
"""
function is_serializing_code()
return ccall(:jl_generating_output, Cint, ()) != 0
end

"""
precompile(f, args::Tuple{Vararg{Any}})

Compile the given function `f` for the argument tuple (of types) `args`, but do not execute it.

See also [`is_serializing_code`](@ref).
"""
function precompile(@nospecialize(f), @nospecialize(args::Tuple))
precompile(Tuple{Core.Typeof(f), args...})
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ Base.@macroexpand
Base.@macroexpand1
Base.code_lowered
Base.code_typed
Base.is_serializing_code
Base.precompile
Base.jit_total_bytes
```
Expand Down