Skip to content
Open
14 changes: 10 additions & 4 deletions Compiler/src/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,7 @@ function enqueue_specialization!(all::Bool, worklist, mi::Core.MethodInstance)
while codeinst !== nothing
do_compile = false
if codeinst.owner !== nothing
# TODO(vchuravy) native code caching for foreign interpreters
# Skip foreign code instances
# This code instance is from a foreign interpreter, so we skip it
elseif use_const_api(codeinst) # Check if invoke is jl_fptr_const_return
do_compile = true
elseif codeinst.invoke != C_NULL || codeinst.precompile
Expand Down Expand Up @@ -344,7 +343,8 @@ function compile_and_emit_native(worlds::Vector{UInt},
newmodules, # Vector{Module} or Nothing
mod_array, # Vector{Module} or Nothing
all::Bool,
module_init_order::Vector{Any}) # Vector{Module}
module_init_order::Vector{Any}, # Vector{Module}
ext_foreign_cis::Vector{Any}) # Vector{CodeInstance}
latestworld = worlds[end]

# Step 1: Precompile all __init__ methods that will be required
Expand Down Expand Up @@ -375,7 +375,13 @@ function compile_and_emit_native(worlds::Vector{UInt},
if new_ext_cis !== nothing
for i in 1:length(new_ext_cis::Vector{Any})
ci = new_ext_cis[i]::CodeInstance
enqueue_specialization!(all, specialization_worklist, get_ci_mi(ci))
if ci.owner !== nothing
# enqueue_specialization will skip over CIs from foreign interpreters
# and currently will visit at most one (do_compile) CI per method instance
push!(ext_foreign_cis, ci)
else
enqueue_specialization!(all, specialization_worklist, get_ci_mi(ci))
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion Compiler/src/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState, validation
end
inferred_result = maybe_compress_codeinfo(interp, mi, inferred_result)
elseif ci.owner === nothing
# The global cache can only handle objects that codegen understands
# The global cache can only handle objects that codegen understands (nothing or CodeInfo)
inferred_result = nothing
end
else
Expand Down
9 changes: 5 additions & 4 deletions src/aotcompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ static bool canPartition(const Function &F)
// `external_linkage` create linkages between pkgimages.
extern "C" JL_DLLEXPORT_CODEGEN
void *jl_create_native_impl(LLVMOrcThreadSafeModuleRef llvmmod, int trim, int external_linkage, size_t world,
jl_array_t *mod_array, jl_array_t *worklist, int all, jl_array_t *module_init_order)
jl_array_t *mod_array, jl_array_t *worklist, int all, jl_array_t *module_init_order, jl_array_t *ext_foreign_cis)
{
JL_TIMING(INFERENCE, INFERENCE);
auto ct = jl_current_task;
Expand All @@ -723,7 +723,7 @@ void *jl_create_native_impl(LLVMOrcThreadSafeModuleRef llvmmod, int trim, int ex
compiler_start_time = jl_hrtime();

jl_value_t **fargs;
JL_GC_PUSHARGS(fargs, 8);
JL_GC_PUSHARGS(fargs, 9);
#ifdef _P64
jl_value_t *jl_array_ulong_type = jl_array_uint64_type;
#else
Expand All @@ -744,10 +744,11 @@ void *jl_create_native_impl(LLVMOrcThreadSafeModuleRef llvmmod, int trim, int ex
fargs[5] = mod_array ? (jl_value_t*)mod_array : jl_nothing; // mod_array (or nothing)
fargs[6] = jl_box_bool(all);
fargs[7] = module_init_order ? (jl_value_t*)module_init_order : jl_nothing; // module_init_order (or nothing)
fargs[8] = ext_foreign_cis ? (jl_value_t*)ext_foreign_cis : jl_nothing; // ext_foreign_cis (or nothing)
size_t last_age = ct->world_age;
ct->world_age = jl_typeinf_world;
fargs[0] = jl_apply(fargs, 8);
fargs[1] = fargs[2] = fargs[3] = fargs[4] = fargs[5] = fargs[6] = fargs[7] = NULL;
fargs[0] = jl_apply(fargs, 9);
fargs[1] = fargs[2] = fargs[3] = fargs[4] = fargs[5] = fargs[6] = fargs[7] = fargs[8] = NULL;
ct->world_age = last_age;
jl_value_t *codeinfos = fargs[0];
JL_TYPECHK(jl_create_native, array_any, codeinfos);
Expand Down
2 changes: 1 addition & 1 deletion src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -2106,7 +2106,7 @@ JL_DLLIMPORT jl_value_t *jl_dump_function_ir(jl_llvmf_dump_t *dump, char strip_i
JL_DLLIMPORT jl_value_t *jl_dump_function_asm(jl_llvmf_dump_t *dump, char emit_mc, const char* asm_variant, const char *debuginfo, char binary, char raw);

typedef jl_value_t *(*jl_codeinstance_lookup_t)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t min_world, size_t max_world);
JL_DLLIMPORT void *jl_create_native(LLVMOrcThreadSafeModuleRef llvmmod, int trim, int cache, size_t world, jl_array_t *mod_array JL_MAYBE_UNROOTED, jl_array_t *worklist JL_MAYBE_UNROOTED, int all, jl_array_t *module_init_order JL_MAYBE_UNROOTED);
JL_DLLIMPORT void *jl_create_native(LLVMOrcThreadSafeModuleRef llvmmod, int trim, int cache, size_t world, jl_array_t *mod_array JL_MAYBE_UNROOTED, jl_array_t *worklist JL_MAYBE_UNROOTED, int all, jl_array_t *module_init_order JL_MAYBE_UNROOTED, jl_array_t *ext_foreign_cis JL_MAYBE_UNROOTED);
JL_DLLIMPORT void *jl_emit_native(jl_array_t *codeinfos, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _external_linkage);
JL_DLLIMPORT void jl_dump_native(void *native_code,
const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname,
Expand Down
19 changes: 15 additions & 4 deletions src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -3357,18 +3357,20 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli
ff = f;
}

jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_ext_cis = NULL;
jl_array_t *mod_array = NULL, *extext_methods = NULL, *new_ext_cis = NULL, *ext_foreign_cis = NULL;
int64_t checksumpos = 0;
int64_t checksumpos_ff = 0;
int64_t datastartpos = 0;
JL_GC_PUSH3(&mod_array, &extext_methods, &new_ext_cis);
JL_GC_PUSH4(&mod_array, &extext_methods, &new_ext_cis, &ext_foreign_cis);

ext_foreign_cis = jl_alloc_vec_any(0);

mod_array = jl_get_loaded_modules(); // __toplevel__ modules loaded in this session (from Base.loaded_modules_array)
if (worklist) {
if (_native_data != NULL) {
if (suppress_precompile)
newly_inferred = NULL;
*_native_data = jl_create_native(NULL, 0, 1, jl_atomic_load_acquire(&jl_world_counter), NULL, suppress_precompile ? (jl_array_t*)jl_an_empty_vec_any : worklist, 0, module_init_order);
*_native_data = jl_create_native(NULL, 0, 1, jl_atomic_load_acquire(&jl_world_counter), NULL, suppress_precompile ? (jl_array_t*)jl_an_empty_vec_any : worklist, 0, module_init_order, ext_foreign_cis);
}
jl_write_header_for_incremental(f, worklist, mod_array, udeps, srctextpos, &checksumpos);
if (emit_split) {
Expand All @@ -3382,7 +3384,7 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli
}
}
else if (_native_data != NULL) {
*_native_data = jl_create_native(NULL, jl_options.trim, 0, jl_atomic_load_acquire(&jl_world_counter), mod_array, NULL, jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL, module_init_order);
*_native_data = jl_create_native(NULL, jl_options.trim, 0, jl_atomic_load_acquire(&jl_world_counter), mod_array, NULL, jl_options.compile_enabled == JL_OPTIONS_COMPILE_ALL, module_init_order, ext_foreign_cis);
}
if (_native_data != NULL)
native_functions = *_native_data;
Expand Down Expand Up @@ -3420,6 +3422,15 @@ JL_DLLEXPORT void jl_create_system_image(void **_native_data, jl_array_t *workli
new_ext_cis = jl_compute_new_ext_cis();
}

// Merge foreign & external CIs
if (ext_foreign_cis) {
size_t n_ext = jl_array_nrows(ext_foreign_cis);
for (size_t i = 0; i < n_ext; i++) {
jl_array_ptr_1d_push(new_ext_cis, jl_array_ptr_ref(ext_foreign_cis, i));
}
}
ext_foreign_cis = NULL; // not needed anymore, free it

// Collect method extensions
extext_methods = jl_alloc_vec_any(0);
jl_collect_extext_methods(extext_methods, mod_array);
Expand Down
2 changes: 1 addition & 1 deletion src/staticdata_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ static jl_array_t *queue_external_cis(jl_array_t *list, jl_query_cache *query_ca
int dispatch_status = jl_atomic_load_relaxed(&m->dispatch_status);
if (!(dispatch_status & METHOD_SIG_LATEST_WHICH))
continue; // ignore replaced methods
if (ci->owner == jl_nothing && jl_atomic_load_relaxed(&ci->inferred) && jl_is_method(m) && jl_object_in_image((jl_value_t*)m->module)) {
if (jl_atomic_load_relaxed(&ci->inferred) && jl_is_method(m) && jl_object_in_image((jl_value_t*)m->module)) {
int found = has_backedge_to_worklist(mi, &visited, &stack, query_cache);
assert(found == 0 || found == 1 || found == 2);
assert(stack.len == 0);
Expand Down
1 change: 1 addition & 0 deletions test/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1872,6 +1872,7 @@ end
dir = @__DIR__
@test success(pipeline(Cmd(`$(Base.julia_cmd()) --startup-file=no precompile_absint1.jl`; dir); stdout, stderr))
@test success(pipeline(Cmd(`$(Base.julia_cmd()) --startup-file=no precompile_absint2.jl`; dir); stdout, stderr))
@test success(pipeline(Cmd(`$(Base.julia_cmd()) --startup-file=no precompile_extmi.jl`; dir); stdout, stderr))
end

precompile_test_harness("Recursive types") do load_path
Expand Down
30 changes: 13 additions & 17 deletions test/precompile_absint1.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,35 @@ precompile_test_harness() do load_path
let m = only(methods(TestAbsIntPrecompile1.basic_callee))
mi = only(Base.specializations(m))
ci = mi.cache
@test isdefined(ci, :next)
@test ci.owner === cache_owner
ci = check_presence(mi, nothing)
@test ci !== nothing
@test ci.owner === nothing
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile1) ==
Base.object_build_id(ci)
ci = ci.next
@test !isdefined(ci, :next)
@test ci.owner === nothing
ci = check_presence(mi, cache_owner)
@test ci !== nothing
@test ci.owner === cache_owner
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile1) ==
Base.object_build_id(ci)
end
let m = only(methods(sum, (Vector{Float64},)))
found = false
for mi in Base.specializations(m)
if mi isa Core.MethodInstance && mi.specTypes == Tuple{typeof(sum),Vector{Float64}}
ci = mi.cache
@test isdefined(ci, :next)
@test ci.owner === cache_owner
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile1) ==
Base.object_build_id(ci)
ci = ci.next
@test !isdefined(ci, :next)
ci = check_presence(mi, nothing)
@test ci !== nothing
@test ci.owner === nothing
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile1) == Base.object_build_id(ci)
ci = check_presence(mi, cache_owner)
@test ci !== nothing
@test ci.owner === cache_owner
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile1) ==
Base.object_build_id(ci)
found = true
break
end
end
@test found
end
end
end
Expand Down
38 changes: 16 additions & 22 deletions test/precompile_absint2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,40 +62,34 @@ precompile_test_harness() do load_path
TestAbsIntPrecompile2.Custom.PrecompileInterpreter())
let m = only(methods(TestAbsIntPrecompile2.basic_callee))
mi = only(Base.specializations(m))
ci = mi.cache
@test isdefined(ci, :next)
@test ci.owner === cache_owner
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile2) ==
Base.object_build_id(ci)
ci = ci.next
@test !isdefined(ci, :next)

ci = check_presence(mi, nothing)
@test ci !== nothing
@test ci.owner === nothing
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile2) ==
Base.object_build_id(ci)
ci = check_presence(mi, cache_owner)
@test ci !== nothing
@test ci.owner === cache_owner
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile2) == Base.object_build_id(ci)
end
let m = only(methods(sum, (Vector{Float64},)))
found = false
for mi = Base.specializations(m)
if mi isa Core.MethodInstance && mi.specTypes == Tuple{typeof(sum),Vector{Float64}}
ci = mi.cache
@test isdefined(ci, :next)
@test ci.owner === cache_owner
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile2) ==
Base.object_build_id(ci)
ci = ci.next
@test !isdefined(ci, :next)
ci = check_presence(mi, nothing)
@test ci !== nothing
@test ci.owner === nothing
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile2) ==
Base.object_build_id(ci)
found = true
break
@test Base.module_build_id(TestAbsIntPrecompile2) == Base.object_build_id(ci)
ci = check_presence(mi, cache_owner)
@test ci !== nothing
@test ci.owner === cache_owner
@test ci.max_world == typemax(UInt)
@test Base.module_build_id(TestAbsIntPrecompile2) == Base.object_build_id(ci)
end
end
@test found
end
end
end
Expand Down
75 changes: 75 additions & 0 deletions test/precompile_extmi.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

using Test

include("precompile_utils.jl")

precompile_test_harness() do load_path
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aviatesk I added a new test here instead of adding to precopile_absintX since the structure is slightly different.

The test comes from
https://github.com/JuliaGPU/GPUCompiler.jl/blob/master/test/native/precompile.jl

where we have a "compiler" package and a "user" package, and we are adding a code instance to a method instance that is already owned somewhere else identity(::Any) which is why we need the PrecompileTools-esque setup.

write(joinpath(load_path, "ExampleCompiler.jl"), :(
module ExampleCompiler
const CC = Core.Compiler

struct ExampleInterpreter <: CC.AbstractInterpreter
world::UInt
inf_cache::Vector{CC.InferenceResult}
end
ExampleInterpreter(world::UInt) =
ExampleInterpreter(world, CC.InferenceResult[])

CC.InferenceParams(::ExampleInterpreter) = CC.InferenceParams()
CC.OptimizationParams(::ExampleInterpreter) = CC.OptimizationParams()
CC.get_inference_cache(interp::ExampleInterpreter) = interp.inf_cache
CC.cache_owner(interp::ExampleInterpreter) = :ExampleInterpreter

CC.get_inference_world(interp::ExampleInterpreter) = interp.world
CC.lock_mi_inference(::ExampleInterpreter, ::Core.MethodInstance) = nothing
CC.unlock_mi_inference(::ExampleInterpreter, ::Core.MethodInstance) = nothing

function infer(mi, world)
interp = ExampleInterpreter(world)
ci = CC.typeinf_ext(interp, mi, CC.SOURCE_MODE_GET_SOURCE)
@assert ci !== nothing "Inference of $mi failed"

return ci
end

function precompile(f, tt; world = Base.get_world_counter())
mi = Base.method_instance(f, tt; world)
infer(mi, world)
end
end) |> string)
Base.compilecache(Base.PkgId("ExampleCompiler"))

write(joinpath(load_path, "ExampleUser.jl"), :(
module ExampleUser
import ExampleCompiler

function square(x)
return x * x
end
ExampleCompiler.precompile(square, (Float64,))

# Stubbed together version of PrecompileTools
ccall(:jl_tag_newly_inferred_enable, Cvoid, ())
try
# Important `identity(::Any) mi does not belong to us`
ExampleCompiler.precompile(identity, (Float64,))
finally
ccall(:jl_tag_newly_inferred_disable, Cvoid, ())
end
end) |> string)
Base.compilecache(Base.PkgId("ExampleUser"))

@eval let
using ExampleUser
cache_owner = :ExampleInterpreter

mi_square = Base.method_instance(ExampleUser.square, (Float64,))
@test check_presence(mi_square, :ExampleInterpreter) !== nothing

mi_identity = Base.method_instance(identity, (Float64,))
@test check_presence(mi_identity, :ExampleInterpreter) !== nothing
end
end

finish_precompile_test!()
12 changes: 12 additions & 0 deletions test/precompile_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,15 @@ let original_depot_path = copy(Base.DEPOT_PATH)
append!(Base.LOAD_PATH, original_load_path)
end
end

function check_presence(mi, token)
ci = isdefined(mi, :cache) ? mi.cache : nothing
while ci !== nothing
if ci.owner === token
@test ci.max_world == Base.ReinferUtils.WORLD_AGE_REVALIDATION_SENTINEL || ci.min_world <= 1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vtjnash I am a bit confused about this query:

  1. There could be more than one entry? We should select the one that is valid in the current world.

The values I am observing here are:

ci = CodeInstance for MethodInstance for ExampleUser.square(::Float64)
ci.max_world = 0xffffffffffffffff
ci.min_world = 0x0000000000009b8c
ci = CodeInstance for MethodInstance for identity(::Any)
ci.max_world = 0xffffffffffffffff
ci.min_world = 0x0000000000009b8c

Which does look to me like they are correctly integrated? But they of course fail the test, since max_world != WORLD_AGE_REVALIDATION_SENTINAL and min_world is not <=1

return ci
end
ci = isdefined(ci, :next) ? ci.next : nothing
end
return nothing
end