Skip to content

Commit b44a8fc

Browse files
committed
Serialize external CodeInstances
Prior to this PR, Julia's precompiled `*.ji` files saved just two categories of code: unspecialized method definitions and type-specialized code for the methods defined by the package. Any novel specializations needed from other code (Base, other packages) were not saved, and therefore effectively thrown away. This PR caches all the code---internal or external---called during package definition that hadn't been previously inferred. This makes precompilation more intuitive (now it saves all relevant inference results), and substantially reduces latency for inference-bound packages. Closes #42016 Fixes #35972 Issue #35972 arose because codegen got started without re-inferring some discarded CodeInstances. This forced the compiler to insert a `jl_invoke`. This PR fixes the issue because needed CodeInstances are no longer discarded by precompilation.
1 parent 7b39515 commit b44a8fc

File tree

12 files changed

+510
-85
lines changed

12 files changed

+510
-85
lines changed

base/Base.jl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ end_base_include = time_ns()
427427
const _sysimage_modules = PkgId[]
428428
in_sysimage(pkgid::PkgId) = pkgid in _sysimage_modules
429429

430-
# Precompiles for Revise
430+
# Precompiles for Revise and other packages
431431
# TODO: move these to contrib/generate_precompile.jl
432432
# The problem is they don't work there
433433
for match = _methods(+, (Int, Int), -1, get_world_counter())
@@ -461,6 +461,23 @@ for match = _methods(+, (Int, Int), -1, get_world_counter())
461461

462462
# Code loading uses this
463463
sortperm(mtime.(readdir(".")), rev=true)
464+
# JLLWrappers uses these
465+
Dict{UUID,Set{String}}()[UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")] = Set{String}()
466+
get!(Set{String}, Dict{UUID,Set{String}}(), UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210"))
467+
eachindex(IndexLinear(), Expr[])
468+
push!(Expr[], Expr(:return, false))
469+
vcat(String[], String[])
470+
k, v = (:hello => nothing)
471+
precompile(indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int))
472+
precompile(indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int, Int))
473+
# Preferences uses these
474+
precompile(get_preferences, (UUID,))
475+
precompile(record_compiletime_preference, (UUID, String))
476+
get(Dict{String,Any}(), "missing", nothing)
477+
delete!(Dict{String,Any}(), "missing")
478+
for (k, v) in Dict{String,Any}()
479+
println(k)
480+
end
464481

465482
break # only actually need to do this once
466483
end

base/binaryplatforms.jl

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ struct Platform <: AbstractPlatform
4040
# The "compare strategy" allows selective overriding on how a tag is compared
4141
compare_strategies::Dict{String,Function}
4242

43-
function Platform(arch::String, os::String;
43+
# Passing `tags` as a `Dict` avoids the need to infer different NamedTuple specializations
44+
function Platform(arch::String, os::String, _tags::Dict{String};
4445
validate_strict::Bool = false,
45-
compare_strategies::Dict{String,<:Function} = Dict{String,Function}(),
46-
kwargs...)
46+
compare_strategies::Dict{String,<:Function} = Dict{String,Function}())
4747
# A wee bit of normalization
4848
os = lowercase(os)
4949
arch = CPUID.normalize_arch(arch)
@@ -52,8 +52,9 @@ struct Platform <: AbstractPlatform
5252
"arch" => arch,
5353
"os" => os,
5454
)
55-
for (tag, value) in kwargs
56-
tag = lowercase(string(tag::Symbol))
55+
for (tag, value) in _tags
56+
value = value::Union{String,VersionNumber,Nothing}
57+
tag = lowercase(tag)
5758
if tag ("arch", "os")
5859
throw(ArgumentError("Cannot double-pass key $(tag)"))
5960
end
@@ -70,8 +71,8 @@ struct Platform <: AbstractPlatform
7071
if tag ("libgfortran_version", "libstdcxx_version", "os_version")
7172
if isa(value, VersionNumber)
7273
value = string(value)
73-
elseif isa(value, AbstractString)
74-
v = tryparse(VersionNumber, String(value)::String)
74+
elseif isa(value, String)
75+
v = tryparse(VersionNumber, value)
7576
if isa(v, VersionNumber)
7677
value = string(v)
7778
end
@@ -110,6 +111,19 @@ struct Platform <: AbstractPlatform
110111
end
111112
end
112113

114+
# Keyword interface (to avoid inference of specialized NamedTuple methods, use the Dict interface for `tags`)
115+
function Platform(arch::String, os::String;
116+
validate_strict::Bool = false,
117+
compare_strategies::Dict{String,<:Function} = Dict{String,Function}(),
118+
kwargs...)
119+
tags = Dict{String,Any}(String(tag)::String=>tagvalue(value) for (tag, value) in kwargs)
120+
return Platform(arch, os, tags; validate_strict, compare_strategies)
121+
end
122+
123+
tagvalue(v::Union{String,VersionNumber,Nothing}) = v
124+
tagvalue(v::Symbol) = String(v)
125+
tagvalue(v::AbstractString) = convert(String, v)::String
126+
113127
# Simple tag insertion that performs a little bit of validation
114128
function add_tag!(tags::Dict{String,String}, tag::String, value::String)
115129
# I know we said only alphanumeric and dots, but let's be generous so that we can expand
@@ -698,21 +712,22 @@ function Base.parse(::Type{Platform}, triplet::AbstractString; validate_strict::
698712
end
699713

700714
# Extract the information we're interested in:
715+
tags = Dict{String,Any}()
701716
arch = get_field(m, arch_mapping)
702717
os = get_field(m, os_mapping)
703-
libc = get_field(m, libc_mapping)
704-
call_abi = get_field(m, call_abi_mapping)
705-
libgfortran_version = get_field(m, libgfortran_version_mapping)
706-
libstdcxx_version = get_field(m, libstdcxx_version_mapping)
707-
cxxstring_abi = get_field(m, cxxstring_abi_mapping)
718+
tags["libc"] = get_field(m, libc_mapping)
719+
tags["call_abi"] = get_field(m, call_abi_mapping)
720+
tags["libgfortran_version"] = get_field(m, libgfortran_version_mapping)
721+
tags["libstdcxx_version"] = get_field(m, libstdcxx_version_mapping)
722+
tags["cxxstring_abi"] = get_field(m, cxxstring_abi_mapping)
708723
function split_tags(tagstr)
709724
tag_fields = split(tagstr, "-"; keepempty=false)
710725
if isempty(tag_fields)
711726
return Pair{String,String}[]
712727
end
713-
return map(v -> Symbol(v[1]) => v[2], split.(tag_fields, "+"))
728+
return map(v -> String(v[1]) => String(v[2]), split.(tag_fields, "+"))
714729
end
715-
tags = split_tags(m["tags"])
730+
merge!(tags, Dict(split_tags(m["tags"])))
716731

717732
# Special parsing of os version number, if any exists
718733
function extract_os_version(os_name, pattern)
@@ -729,18 +744,9 @@ function Base.parse(::Type{Platform}, triplet::AbstractString; validate_strict::
729744
if os == "freebsd"
730745
os_version = extract_os_version("freebsd", r".*freebsd([\d.]+)")
731746
end
747+
tags["os_version"] = os_version
732748

733-
return Platform(
734-
arch, os;
735-
validate_strict,
736-
libc,
737-
call_abi,
738-
libgfortran_version,
739-
cxxstring_abi,
740-
libstdcxx_version,
741-
os_version,
742-
tags...,
743-
)
749+
return Platform(arch, os, tags; validate_strict)
744750
end
745751
throw(ArgumentError("Platform `$(triplet)` is not an officially supported platform"))
746752
end
@@ -1067,4 +1073,9 @@ function select_platform(download_info::Dict, platform::AbstractPlatform = HostP
10671073
return download_info[p]
10681074
end
10691075

1076+
# precompiles to reduce latency (see https://github.com/JuliaLang/julia/pull/43990#issuecomment-1025692379)
1077+
Dict{Platform,String}()[HostPlatform()] = ""
1078+
Platform("x86_64", "linux", Dict{String,Any}(); validate_strict=true)
1079+
Platform("x86_64", "linux", Dict{String,String}(); validate_strict=false) # called this way from Artifacts.unpack_platform
1080+
10701081
end # module

base/compiler/typeinfer.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3+
# Tracking of newly-inferred MethodInstances during precompilation
4+
const track_newly_inferred = RefValue{Bool}(false)
5+
const newly_inferred = MethodInstance[]
6+
37
# build (and start inferring) the inference frame for the top-level MethodInstance
48
function typeinf(interp::AbstractInterpreter, result::InferenceResult, cache::Symbol)
59
frame = InferenceState(result, cache, interp)
@@ -389,6 +393,12 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult)
389393
if !already_inferred
390394
inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src)
391395
code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds)
396+
if track_newly_inferred[]
397+
m = linfo.def
398+
if isa(m, Method)
399+
m.module != Core && push!(newly_inferred, linfo)
400+
end
401+
end
392402
end
393403
unlock_mi_inference(interp, linfo)
394404
nothing

base/loading.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,13 +1395,17 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto
13951395
task_local_storage()[:SOURCE_PATH] = source
13961396
end
13971397

1398+
Core.Compiler.track_newly_inferred.x = true
13981399
try
13991400
Base.include(Base.__toplevel__, input)
14001401
catch ex
14011402
precompilableerror(ex) || rethrow()
14021403
@debug "Aborting `create_expr_cache'" exception=(ErrorException("Declaration of __precompile__(false) not allowed"), catch_backtrace())
14031404
exit(125) # we define status = 125 means PrecompileableError
1405+
finally
1406+
Core.Compiler.track_newly_inferred.x = false
14041407
end
1408+
ccall(:jl_set_newly_inferred, Cvoid, (Any,), Core.Compiler.newly_inferred)
14051409
end
14061410

14071411
const PRECOMPILE_TRACE_COMPILE = Ref{String}()
@@ -2033,12 +2037,12 @@ end
20332037
20342038
Compile the given function `f` for the argument tuple (of types) `args`, but do not execute it.
20352039
"""
2036-
function precompile(@nospecialize(f), args::Tuple)
2040+
function precompile(@nospecialize(f), @nospecialize(args::Tuple))
20372041
precompile(Tuple{Core.Typeof(f), args...})
20382042
end
20392043

20402044
const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
2041-
function precompile(argt::Type)
2045+
function precompile(@nospecialize(argt::Type))
20422046
ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
20432047
if !ret && ENABLE_PRECOMPILE_WARNINGS[]
20442048
@warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0

src/codegen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7849,7 +7849,7 @@ jl_compile_result_t jl_emit_codeinst(
78497849
// don't delete inlineable code, unless it is constant
78507850
(codeinst->invoke == jl_fptr_const_return_addr || !jl_ir_flag_inlineable((jl_array_t*)codeinst->inferred)) &&
78517851
// don't delete code when generating a precompile file
7852-
!imaging_mode) {
7852+
!(imaging_mode || jl_options.incremental)) {
78537853
// if not inlineable, code won't be needed again
78547854
codeinst->inferred = jl_nothing;
78557855
}

0 commit comments

Comments
 (0)