Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 88a1cbf
Author: Tim Holy <tim.holy@gmail.com>
Date:   Sat Feb 19 03:14:45 2022 -0600

    Copy over CodeInstances too

commit 48c91b3
Author: Tim Holy <tim.holy@gmail.com>
Date:   Thu Feb 17 14:22:02 2022 -0600

    Exclude MethodInstances that don't link to worklist module

    This should prevent us from serializing too much code.

commit 3241c4c
Author: Tim Holy <tim.holy@gmail.com>
Date:   Thu Feb 17 12:23:12 2022 -0600

    Add invalidation test

commit ead1fd9
Author: Tim Holy <tim.holy@gmail.com>
Date:   Thu Feb 17 10:23:52 2022 -0600

    Fix a failure to invalidate

commit b44a8fc
Author: Tim Holy <tim.holy@gmail.com>
Date:   Thu Jan 27 02:54:47 2022 -0600

    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 JuliaLang#42016
    Fixes JuliaLang#35972

    Issue JuliaLang#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.
  • Loading branch information
N5N3 committed Feb 23, 2022
1 parent fe83716 commit 2a86840
Show file tree
Hide file tree
Showing 13 changed files with 715 additions and 96 deletions.
19 changes: 18 additions & 1 deletion base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ end_base_include = time_ns()
const _sysimage_modules = PkgId[]
in_sysimage(pkgid::PkgId) = pkgid in _sysimage_modules

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

# Code loading uses this
sortperm(mtime.(readdir(".")), rev=true)
# JLLWrappers uses these
Dict{UUID,Set{String}}()[UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")] = Set{String}()
get!(Set{String}, Dict{UUID,Set{String}}(), UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210"))
eachindex(IndexLinear(), Expr[])
push!(Expr[], Expr(:return, false))
vcat(String[], String[])
k, v = (:hello => nothing)
precompile(indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int))
precompile(indexed_iterate, (Pair{Symbol, Union{Nothing, String}}, Int, Int))
# Preferences uses these
precompile(get_preferences, (UUID,))
precompile(record_compiletime_preference, (UUID, String))
get(Dict{String,Any}(), "missing", nothing)
delete!(Dict{String,Any}(), "missing")
for (k, v) in Dict{String,Any}()
println(k)
end

break # only actually need to do this once
end
Expand Down
61 changes: 36 additions & 25 deletions base/binaryplatforms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ struct Platform <: AbstractPlatform
# The "compare strategy" allows selective overriding on how a tag is compared
compare_strategies::Dict{String,Function}

function Platform(arch::String, os::String;
# Passing `tags` as a `Dict` avoids the need to infer different NamedTuple specializations
function Platform(arch::String, os::String, _tags::Dict{String};
validate_strict::Bool = false,
compare_strategies::Dict{String,<:Function} = Dict{String,Function}(),
kwargs...)
compare_strategies::Dict{String,<:Function} = Dict{String,Function}())
# A wee bit of normalization
os = lowercase(os)
arch = CPUID.normalize_arch(arch)
Expand All @@ -52,8 +52,9 @@ struct Platform <: AbstractPlatform
"arch" => arch,
"os" => os,
)
for (tag, value) in kwargs
tag = lowercase(string(tag::Symbol))
for (tag, value) in _tags
value = value::Union{String,VersionNumber,Nothing}
tag = lowercase(tag)
if tag ("arch", "os")
throw(ArgumentError("Cannot double-pass key $(tag)"))
end
Expand All @@ -70,8 +71,8 @@ struct Platform <: AbstractPlatform
if tag ("libgfortran_version", "libstdcxx_version", "os_version")
if isa(value, VersionNumber)
value = string(value)
elseif isa(value, AbstractString)
v = tryparse(VersionNumber, String(value)::String)
elseif isa(value, String)
v = tryparse(VersionNumber, value)
if isa(v, VersionNumber)
value = string(v)
end
Expand Down Expand Up @@ -110,6 +111,19 @@ struct Platform <: AbstractPlatform
end
end

# Keyword interface (to avoid inference of specialized NamedTuple methods, use the Dict interface for `tags`)
function Platform(arch::String, os::String;
validate_strict::Bool = false,
compare_strategies::Dict{String,<:Function} = Dict{String,Function}(),
kwargs...)
tags = Dict{String,Any}(String(tag)::String=>tagvalue(value) for (tag, value) in kwargs)
return Platform(arch, os, tags; validate_strict, compare_strategies)
end

tagvalue(v::Union{String,VersionNumber,Nothing}) = v
tagvalue(v::Symbol) = String(v)
tagvalue(v::AbstractString) = convert(String, v)::String

# Simple tag insertion that performs a little bit of validation
function add_tag!(tags::Dict{String,String}, tag::String, value::String)
# I know we said only alphanumeric and dots, but let's be generous so that we can expand
Expand Down Expand Up @@ -699,21 +713,22 @@ function Base.parse(::Type{Platform}, triplet::AbstractString; validate_strict::
end

# Extract the information we're interested in:
tags = Dict{String,Any}()
arch = get_field(m, arch_mapping)
os = get_field(m, os_mapping)
libc = get_field(m, libc_mapping)
call_abi = get_field(m, call_abi_mapping)
libgfortran_version = get_field(m, libgfortran_version_mapping)
libstdcxx_version = get_field(m, libstdcxx_version_mapping)
cxxstring_abi = get_field(m, cxxstring_abi_mapping)
tags["libc"] = get_field(m, libc_mapping)
tags["call_abi"] = get_field(m, call_abi_mapping)
tags["libgfortran_version"] = get_field(m, libgfortran_version_mapping)
tags["libstdcxx_version"] = get_field(m, libstdcxx_version_mapping)
tags["cxxstring_abi"] = get_field(m, cxxstring_abi_mapping)
function split_tags(tagstr)
tag_fields = split(tagstr, "-"; keepempty=false)
if isempty(tag_fields)
return Pair{String,String}[]
end
return map(v -> Symbol(v[1]) => v[2], split.(tag_fields, "+"))
return map(v -> String(v[1]) => String(v[2]), split.(tag_fields, "+"))
end
tags = split_tags(m["tags"])
merge!(tags, Dict(split_tags(m["tags"])))

# Special parsing of os version number, if any exists
function extract_os_version(os_name, pattern)
Expand All @@ -730,18 +745,9 @@ function Base.parse(::Type{Platform}, triplet::AbstractString; validate_strict::
if os == "freebsd"
os_version = extract_os_version("freebsd", r".*freebsd([\d.]+)")
end
tags["os_version"] = os_version

return Platform(
arch, os;
validate_strict,
libc,
call_abi,
libgfortran_version,
cxxstring_abi,
libstdcxx_version,
os_version,
tags...,
)
return Platform(arch, os, tags; validate_strict)
end
throw(ArgumentError("Platform `$(triplet)` is not an officially supported platform"))
end
Expand Down Expand Up @@ -1068,4 +1074,9 @@ function select_platform(download_info::Dict, platform::AbstractPlatform = HostP
return download_info[p]
end

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

end # module
10 changes: 10 additions & 0 deletions base/compiler/typeinfer.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# Tracking of newly-inferred MethodInstances during precompilation
const track_newly_inferred = RefValue{Bool}(false)
const newly_inferred = MethodInstance[]

# build (and start inferring) the inference frame for the top-level MethodInstance
function typeinf(interp::AbstractInterpreter, result::InferenceResult, cache::Symbol)
frame = InferenceState(result, cache, interp)
Expand Down Expand Up @@ -389,6 +393,12 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult)
if !already_inferred
inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src)
code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds)
if track_newly_inferred[]
m = linfo.def
if isa(m, Method)
m.module != Core && push!(newly_inferred, linfo)
end
end
end
unlock_mi_inference(interp, linfo)
nothing
Expand Down
8 changes: 6 additions & 2 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1395,13 +1395,17 @@ function include_package_for_output(pkg::PkgId, input::String, depot_path::Vecto
task_local_storage()[:SOURCE_PATH] = source
end

Core.Compiler.track_newly_inferred.x = true
try
Base.include(Base.__toplevel__, input)
catch ex
precompilableerror(ex) || rethrow()
@debug "Aborting `create_expr_cache'" exception=(ErrorException("Declaration of __precompile__(false) not allowed"), catch_backtrace())
exit(125) # we define status = 125 means PrecompileableError
finally
Core.Compiler.track_newly_inferred.x = false
end
ccall(:jl_set_newly_inferred, Cvoid, (Any,), Core.Compiler.newly_inferred)
end

const PRECOMPILE_TRACE_COMPILE = Ref{String}()
Expand Down Expand Up @@ -2033,12 +2037,12 @@ end
Compile the given function `f` for the argument tuple (of types) `args`, but do not execute it.
"""
function precompile(@nospecialize(f), args::Tuple)
function precompile(@nospecialize(f), @nospecialize(args::Tuple))
precompile(Tuple{Core.Typeof(f), args...})
end

const ENABLE_PRECOMPILE_WARNINGS = Ref(false)
function precompile(argt::Type)
function precompile(@nospecialize(argt::Type))
ret = ccall(:jl_compile_hint, Int32, (Any,), argt) != 0
if !ret && ENABLE_PRECOMPILE_WARNINGS[]
@warn "Inactive precompile statement" maxlog=100 form=argt _module=nothing _file=nothing _line=0
Expand Down
2 changes: 1 addition & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7857,7 +7857,7 @@ jl_compile_result_t jl_emit_codeinst(
// don't delete inlineable code, unless it is constant
(codeinst->invoke == jl_fptr_const_return_addr || !jl_ir_flag_inlineable((jl_array_t*)codeinst->inferred)) &&
// don't delete code when generating a precompile file
!imaging_mode) {
!(imaging_mode || jl_options.incremental)) {
// if not inlineable, code won't be needed again
codeinst->inferred = jl_nothing;
}
Expand Down
Loading

0 comments on commit 2a86840

Please sign in to comment.