Skip to content

Commit 8349aed

Browse files
authored
precompile: fix a number of mistakes in handling precompiled code instance caches (#59436)
Get the real list of external code instances worthwhile to re-cache directly from `native_functions` instead of calling `jl_compute_new_ext_cis` a second time if we have `native_functions`. If not saving native code though, we can still call it to get a list of code instances that might be worth including even without native code.
2 parents abb104c + 59a5fc8 commit 8349aed

25 files changed

+286
-282
lines changed

Compiler/src/bindinginvalidations.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ function scan_new_method!(method::Method, image_backedges_only::Bool)
188188
@atomic method.did_scan_source |= 0x1
189189
end
190190

191-
function scan_new_methods!(extext_methods::Vector{Any}, internal_methods::Vector{Any}, image_backedges_only::Bool)
191+
function scan_new_methods!(internal_methods::Vector{Any}, image_backedges_only::Bool)
192192
if image_backedges_only && generating_output(true)
193193
# Replacing image bindings is forbidden during incremental precompilation - skip backedge insertion
194194
return
@@ -198,7 +198,4 @@ function scan_new_methods!(extext_methods::Vector{Any}, internal_methods::Vector
198198
scan_new_method!(method, image_backedges_only)
199199
end
200200
end
201-
for tme::Core.TypeMapEntry in extext_methods
202-
scan_new_method!(tme.func::Method, image_backedges_only)
203-
end
204201
end

Compiler/src/precompile.jl

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -308,33 +308,28 @@ end
308308
function enqueue_specialization!(all::Bool, worklist, mi::Core.MethodInstance)
309309
# Translation of precompile_enq_specialization_ from C
310310
codeinst = isdefined(mi, :cache) ? mi.cache : nothing
311-
312311
while codeinst !== nothing
313312
do_compile = false
314-
315313
if codeinst.owner !== nothing
316314
# TODO(vchuravy) native code caching for foreign interpreters
317315
# Skip foreign code instances
318-
elseif !use_const_api(codeinst) # Check if invoke is not jl_fptr_const_return
319-
if codeinst.invoke != C_NULL || codeinst.precompile
316+
elseif use_const_api(codeinst) # Check if invoke is jl_fptr_const_return
317+
do_compile = true
318+
elseif codeinst.invoke != C_NULL || codeinst.precompile
319+
do_compile = true
320+
elseif !do_compile && isdefined(codeinst, :inferred)
321+
inferred = codeinst.inferred
322+
# Check compilation options and inlining cost
323+
if (all || inferred === nothing ||
324+
((isa(inferred, String) || isa(inferred, CodeInfo) || isa(inferred, UInt8)) &&
325+
ccall(:jl_ir_inlining_cost, UInt16, (Any,), inferred) == typemax(UInt16)))
320326
do_compile = true
321327
end
322-
if !do_compile && isdefined(codeinst, :inferred)
323-
inferred = codeinst.inferred
324-
# Check compilation options and inlining cost
325-
if (all || inferred === nothing ||
326-
((isa(inferred, String) || isa(inferred, CodeInfo) || isa(inferred, UInt8)) &&
327-
ccall(:jl_ir_inlining_cost, UInt16, (Any,), inferred) == typemax(UInt16)))
328-
do_compile = true
329-
end
330-
end
331328
end
332-
333329
if do_compile
334330
push!(worklist, mi)
335331
return true
336332
end
337-
338333
# Move to the next code instance in the chain
339334
codeinst = isdefined(codeinst, :next) ? codeinst.next : nothing
340335
end
@@ -426,9 +421,6 @@ function compile_and_emit_native(worlds::Vector{UInt},
426421
invokelatest(exit, 1)
427422
end
428423

429-
# Step 5: Always set newly_inferred global for serialization use
430-
#ccall(:jl_set_newly_inferred, Cvoid, (Any,), filter(ci -> ci isa CodeInstance, codeinfos))
431-
432424
return codeinfos
433425

434426
end

Compiler/src/reinfer.jl

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ using ..Compiler: _findsup, store_backedges, JLOptions, get_world_counter,
66
morespecific, RefValue, get_require_world, Vector, IdDict
77
using .Core: CodeInstance, MethodInstance
88

9+
const CI_FLAGS_NATIVE_CACHE_VALID = 0b1000
910
const WORLD_AGE_REVALIDATION_SENTINEL::UInt = 1
1011
const _jl_debug_method_invalidation = RefValue{Union{Nothing,Vector{Any}}}(nothing)
1112
debug_method_invalidation(onoff::Bool) =
@@ -53,8 +54,7 @@ end
5354
function VerifyMethodInitialState(codeinst::CodeInstance)
5455
mi = get_ci_mi(codeinst)
5556
def = mi.def::Method
56-
callees = codeinst.edges
57-
VerifyMethodInitialState(codeinst, mi, def, callees)
57+
VerifyMethodInitialState(codeinst, mi, def, codeinst.edges)
5858
end
5959

6060
function VerifyMethodWorkState(dummy_cause::CodeInstance)
@@ -68,54 +68,43 @@ end
6868

6969
# Restore backedges to external targets
7070
# `edges` = [caller1, ...], the list of worklist-owned code instances internally
71-
# `ext_ci_list` = [caller1, ...], the list of worklist-owned code instances externally
72-
function insert_backedges(edges::Vector{Any}, ext_ci_list::Union{Nothing,Vector{Any}}, extext_methods::Vector{Any}, internal_methods::Vector{Any})
71+
function insert_backedges(internal_methods::Vector{Any})
7372
# determine which CodeInstance objects are still valid in our image
7473
# to enable any applicable new codes
7574
backedges_only = unsafe_load(cglobal(:jl_first_image_replacement_world, UInt)) == typemax(UInt)
76-
scan_new_methods!(extext_methods, internal_methods, backedges_only)
75+
scan_new_methods!(internal_methods, backedges_only)
7776
workspace = VerifyMethodWorkspace()
78-
_insert_backedges(edges, workspace)
79-
if ext_ci_list !== nothing
80-
_insert_backedges(ext_ci_list, workspace, #=external=#true)
81-
end
77+
scan_new_code!(internal_methods, workspace)
78+
nothing
8279
end
8380

84-
function _insert_backedges(edges::Vector{Any}, workspace::VerifyMethodWorkspace, external::Bool=false)
85-
for i = 1:length(edges)
86-
codeinst = edges[i]::CodeInstance
81+
function scan_new_code!(internal_methods::Vector{Any}, workspace::VerifyMethodWorkspace)
82+
for i = 1:length(internal_methods)
83+
codeinst = internal_methods[i]
84+
codeinst isa CodeInstance || continue
85+
# codeinst.owner === nothing || continue
8786
validation_world = get_world_counter()
8887
verify_method_graph(codeinst, validation_world, workspace)
8988
# After validation, under the world_counter_lock, set max_world to typemax(UInt) for all dependencies
9089
# (recursively). From that point onward the ordinary backedge mechanism is responsible for maintaining
9190
# validity.
9291
@ccall jl_promote_ci_to_current(codeinst::Any, validation_world::UInt)::Cvoid
93-
minvalid = codeinst.min_world
94-
maxvalid = codeinst.max_world
95-
# Finally, if this CI is still valid in some world age and belongs to an external method(specialization),
96-
# poke it that mi's cache
97-
if maxvalid minvalid && external
98-
caller = get_ci_mi(codeinst)
99-
@assert isdefined(codeinst, :inferred) # See #53586, #53109
100-
inferred = @ccall jl_rettype_inferred(
101-
codeinst.owner::Any, caller::Any, minvalid::UInt, maxvalid::UInt)::Any
102-
if inferred !== nothing
103-
# We already got a code instance for this world age range from
104-
# somewhere else - we don't need this one.
105-
else
106-
@ccall jl_mi_cache_insert(caller::Any, codeinst::Any)::Cvoid
107-
end
108-
end
10992
end
11093
end
11194

11295
function verify_method_graph(codeinst::CodeInstance, validation_world::UInt, workspace::VerifyMethodWorkspace)
113-
@assert isempty(workspace.stack); @assert isempty(workspace.visiting);
114-
@assert isempty(workspace.initial_states); @assert isempty(workspace.work_states); @assert isempty(workspace.result_states)
96+
@assert isempty(workspace.stack) "workspace corrupted"
97+
@assert isempty(workspace.visiting) "workspace corrupted"
98+
@assert isempty(workspace.initial_states) "workspace corrupted"
99+
@assert isempty(workspace.work_states) "workspace corrupted"
100+
@assert isempty(workspace.result_states) "workspace corrupted"
115101
child_cycle, minworld, maxworld = verify_method(codeinst, validation_world, workspace)
116102
@assert child_cycle == 0
117-
@assert isempty(workspace.stack); @assert isempty(workspace.visiting);
118-
@assert isempty(workspace.initial_states); @assert isempty(workspace.work_states); @assert isempty(workspace.result_states)
103+
@assert isempty(workspace.stack) "workspace corrupted"
104+
@assert isempty(workspace.visiting) "workspace corrupted"
105+
@assert isempty(workspace.initial_states) "workspace corrupted"
106+
@assert isempty(workspace.work_states) "workspace corrupted"
107+
@assert isempty(workspace.result_states) "workspace corrupted"
119108
nothing
120109
end
121110

@@ -197,10 +186,8 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
197186
continue
198187
end
199188

200-
minworld, maxworld = get_require_world(), validation_world
201-
202189
if haskey(workspace.visiting, initial.codeinst)
203-
workspace.result_states[current_depth] = VerifyMethodResultState(workspace.visiting[initial.codeinst], minworld, maxworld)
190+
workspace.result_states[current_depth] = VerifyMethodResultState(workspace.visiting[initial.codeinst], UInt(1), validation_world)
204191
workspace.work_states[current_depth] = VerifyMethodWorkState(work.depth, work.cause, work.recursive_index, :return_to_parent)
205192
continue
206193
end
@@ -209,6 +196,9 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
209196
depth = length(workspace.stack)
210197
workspace.visiting[initial.codeinst] = depth
211198

199+
# unable to backdate before require_world, since Bindings are not able to track that information
200+
minworld, maxworld = get_require_world(), validation_world
201+
212202
# Check for invalidation of GlobalRef edges
213203
if (initial.def.did_scan_source & 0x1) == 0x0
214204
backedges_only = unsafe_load(cglobal(:jl_first_image_replacement_world, UInt)) == typemax(UInt)
@@ -229,7 +219,7 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
229219
while j <= length(initial.callees)
230220
local min_valid2::UInt, max_valid2::UInt
231221
edge = initial.callees[j]
232-
@assert !(edge isa Method)
222+
@assert !(edge isa Method) "unexpected Method edge indicates corrupt edges list creation"
233223

234224
if edge isa CodeInstance
235225
# Convert CodeInstance to MethodInstance for validation (like original)
@@ -333,12 +323,16 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
333323
child = pop!(workspace.stack)
334324
if result.result_maxworld 0
335325
@atomic :monotonic child.min_world = result.result_minworld
326+
# Finally, if this CI is still valid in some world age and marked as valid in the native cache, poke it in that mi's cache now
327+
if child.flags & CI_FLAGS_NATIVE_CACHE_VALID == CI_FLAGS_NATIVE_CACHE_VALID
328+
@ccall jl_mi_cache_insert(get_ci_mi(child)::Any, child::Any)::Cvoid
329+
end
336330
end
337331
@atomic :monotonic child.max_world = result.result_maxworld
338-
if result.result_maxworld == validation_world && validation_world == get_world_counter()
332+
if result.result_maxworld == validation_world && validation_world == get_world_counter() && isdefined(child, :edges)
339333
store_backedges(child, child.edges)
340334
end
341-
@assert workspace.visiting[child] == length(workspace.stack) + 1
335+
@assert workspace.visiting[child] == length(workspace.stack) + 1 "internal error maintaining workspace"
342336
delete!(workspace.visiting, child)
343337
invalidations = _jl_debug_method_invalidation[]
344338
if invalidations !== nothing && result.result_maxworld < validation_world
@@ -500,15 +494,15 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
500494
# Fast path is legal when fully_covers=true
501495
if fully_covers && !iszero(mi.dispatch_status & METHOD_SIG_LATEST_ONLY)
502496
minworld = meth.primary_world
503-
@assert minworld world
497+
@assert minworld world "expected method not present in verification world"
504498
maxworld = typemax(UInt)
505499
return minworld, maxworld
506500
end
507501
end
508502
# Fast path is legal when fully_covers=true
509503
if fully_covers && !iszero(meth.dispatch_status & METHOD_SIG_LATEST_ONLY)
510504
minworld = meth.primary_world
511-
@assert minworld world
505+
@assert minworld world "expected method not present in verification world"
512506
maxworld = typemax(UInt)
513507
return minworld, maxworld
514508
end
@@ -566,7 +560,7 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
566560
end
567561
if interference_fast_path_success
568562
# All interference sets are covered by expecteds, can return success
569-
@assert interference_minworld world
563+
@assert interference_minworld world "expected method not present in verification world"
570564
maxworld = typemax(UInt)
571565
return interference_minworld, maxworld
572566
end
@@ -627,13 +621,13 @@ const METHOD_SIG_LATEST_WHICH = 0x1
627621
const METHOD_SIG_LATEST_ONLY = 0x2
628622

629623
function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UInt, matches::Vector{Any})
630-
@assert invokesig isa Type
624+
@assert invokesig isa Type "corrupt edges list"
631625
local minworld::UInt, maxworld::UInt
632626
empty!(matches)
633627
if invokesig === expected.sig && !iszero(expected.dispatch_status & METHOD_SIG_LATEST_WHICH)
634628
# the invoke match is `expected` for `expected->sig`, unless `expected` is replaced
635629
minworld = expected.primary_world
636-
@assert minworld world
630+
@assert minworld world "expected method not present in verification world"
637631
maxworld = typemax(UInt)
638632
else
639633
mt = get_methodtable(expected)
@@ -658,7 +652,7 @@ function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UIn
658652
end
659653

660654
# Wrapper to call insert_backedges in typeinf_world for external calls
661-
function insert_backedges_typeinf(edges::Vector{Any}, ext_ci_list::Union{Nothing,Vector{Any}}, extext_methods::Vector{Any}, internal_methods::Vector{Any})
662-
args = Any[insert_backedges, edges, ext_ci_list, extext_methods, internal_methods]
655+
function insert_backedges_typeinf(internal_methods::Vector{Any})
656+
args = Any[insert_backedges, internal_methods]
663657
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Any}, Cint), args, length(args))
664658
end

Compiler/src/typeinfer.jl

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ end
103103

104104
function finish!(interp::AbstractInterpreter, caller::InferenceState, validation_world::UInt, time_before::UInt64)
105105
result = caller.result
106-
#@assert last(result.valid_worlds) <= get_world_counter() || isempty(caller.edges)
106+
edges = result_edges(interp, caller)
107+
#@assert last(result.valid_worlds) <= get_world_counter() || isempty(edges)
107108
if caller.cache_mode === CACHE_MODE_LOCAL
108109
@assert !isdefined(result, :ci)
109-
result.src = transform_result_for_local_cache(interp, result)
110+
result.src = transform_result_for_local_cache(interp, result, edges)
110111
elseif isdefined(result, :ci)
111-
edges = result_edges(interp, caller)
112112
ci = result.ci
113113
mi = result.linfo
114114
# if we aren't cached, we don't need this edge
@@ -400,7 +400,7 @@ function inline_cost_model(interp::AbstractInterpreter, result::InferenceResult,
400400
end
401401
end
402402

403-
function transform_result_for_local_cache(interp::AbstractInterpreter, result::InferenceResult)
403+
function transform_result_for_local_cache(interp::AbstractInterpreter, result::InferenceResult, edges::SimpleVector)
404404
if is_result_constabi_eligible(result)
405405
return nothing
406406
end
@@ -1166,14 +1166,14 @@ end
11661166
#### entry points for inferring a MethodInstance given a type signature ####
11671167

11681168
"""
1169-
codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, worlds::WorldRange, @nospecialize(val))
1169+
codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, worlds::WorldRange, edges::SimpleVector, @nospecialize(val))
11701170
11711171
Return a fake CodeInfo that just contains `return \$val`. This function is used in various reflection APIs when asking
11721172
for the code of a function that inference has found to just return a constant. For such functions, no code is actually
11731173
stored - the constant is used directly. However, because this is an ABI implementation detail, it is nice to maintain
11741174
consistency and just synthesize a CodeInfo when the reflection APIs ask for them - this function does that.
11751175
"""
1176-
function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, @nospecialize(val))
1176+
function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, worlds::WorldRange, edges::SimpleVector, @nospecialize(val))
11771177
method = mi.def::Method
11781178
tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ())
11791179
tree.code = Any[ ReturnNode(quoted(val)) ]
@@ -1184,7 +1184,9 @@ function codeinfo_for_const(interp::AbstractInterpreter, mi::MethodInstance, @no
11841184
tree.debuginfo = DebugInfo(mi)
11851185
tree.ssaflags = [IR_FLAG_NULL]
11861186
tree.rettype = Core.Typeof(val)
1187-
tree.edges = Core.svec()
1187+
tree.min_world = first(worlds)
1188+
tree.max_world = last(worlds)
1189+
tree.edges = edges
11881190
set_inlineable!(tree, true)
11891191
tree.parent = mi
11901192
return tree
@@ -1250,7 +1252,7 @@ function typeinf_frame(interp::AbstractInterpreter, mi::MethodInstance, run_opti
12501252
if run_optimizer
12511253
if result_is_constabi(interp, frame.result)
12521254
rt = frame.result.result::Const
1253-
src = codeinfo_for_const(interp, frame.linfo, rt.val)
1255+
src = codeinfo_for_const(interp, frame.linfo, frame.world.valid_worlds, Core.svec(frame.edges...), rt.val)
12541256
else
12551257
opt = OptimizationState(frame, interp)
12561258
optimize(interp, opt, frame.result)
@@ -1617,7 +1619,7 @@ function compile!(codeinfos::Vector{Any}, workqueue::CompilationQueue;
16171619
mi = get_ci_mi(callee)
16181620
# now make sure everything has source code, if desired
16191621
if use_const_api(callee)
1620-
src = codeinfo_for_const(interp, mi, callee.rettype_const)
1622+
src = codeinfo_for_const(interp, mi, WorldRange(callee.min_world, callee.max_world), callee.edges, callee.rettype_const)
16211623
else
16221624
src = get(interp.codegen, callee, nothing)
16231625
if src === nothing

0 commit comments

Comments
 (0)