Skip to content

Commit 59a5fc8

Browse files
committed
recache all previously cached / new codeinst
Regardless of existing cache content, these may be useful to be able to examine or reuse in the future. Even if it is just to JIT new code later, we already have this object in memory, so it costs nothing to be able to enumerate them from this linked list. To accomplish that there are four changes to cache insertion: - sorting: add a new item to the cache will insert at the optimal location for runtime performance - idempotency: add a new item to the cache may move it to a new better place, but won't break the cache - universal inclusion: even when the method has no recorded edges, it still might require scanning for Bindings or coverage - correct initialization: edges needs to be set, even if uninferred (so just empty svec), on everything in cache, since many places examine the cache expect to follow those
1 parent 0aed4e1 commit 59a5fc8

19 files changed

+219
-189
lines changed

Compiler/src/precompile.jl

Lines changed: 10 additions & 15 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

Compiler/src/reinfer.jl

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
using ..Compiler.Base
44
using ..Compiler: _findsup, store_backedges, JLOptions, get_world_counter,
5-
_methods_by_ftype, get_methodtable, get_ci_mi, ci_has_abi, should_instrument,
5+
_methods_by_ftype, get_methodtable, get_ci_mi, should_instrument,
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,17 +68,13 @@ 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(ext_ci_list::Union{Nothing,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)
7675
scan_new_methods!(internal_methods, backedges_only)
7776
workspace = VerifyMethodWorkspace()
7877
scan_new_code!(internal_methods, workspace)
79-
if ext_ci_list !== nothing
80-
insert_ext_cache(ext_ci_list)
81-
end
8278
nothing
8379
end
8480

@@ -96,35 +92,19 @@ function scan_new_code!(internal_methods::Vector{Any}, workspace::VerifyMethodWo
9692
end
9793
end
9894

99-
function insert_ext_cache(edges::Vector{Any})
100-
for i = 1:length(edges)
101-
codeinst = edges[i]::CodeInstance
102-
minvalid = codeinst.min_world
103-
maxvalid = codeinst.max_world
104-
# Finally, if this CI is still valid in some world age and belongs to an external method(specialization),
105-
# poke it in that mi's cache, unless there is already one there that has an invoke pointer
106-
if maxvalid minvalid
107-
caller = get_ci_mi(codeinst)
108-
@assert isdefined(codeinst, :inferred) # See #53586, #53109
109-
inferred = @ccall jl_rettype_inferred(
110-
codeinst.owner::Any, caller::Any, minvalid::UInt, maxvalid::UInt)::Any
111-
if inferred !== nothing && (ci_has_abi(inferred::CodeInstance) || !ci_has_abi(codeinst))
112-
# We already got a code instance for this world age range from
113-
# somewhere else - we don't need this one.
114-
else
115-
@ccall jl_mi_cache_insert(caller::Any, codeinst::Any)::Cvoid
116-
end
117-
end
118-
end
119-
end
120-
12195
function verify_method_graph(codeinst::CodeInstance, validation_world::UInt, workspace::VerifyMethodWorkspace)
122-
@assert isempty(workspace.stack); @assert isempty(workspace.visiting);
123-
@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"
124101
child_cycle, minworld, maxworld = verify_method(codeinst, validation_world, workspace)
125102
@assert child_cycle == 0
126-
@assert isempty(workspace.stack); @assert isempty(workspace.visiting);
127-
@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"
128108
nothing
129109
end
130110

@@ -206,10 +186,8 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
206186
continue
207187
end
208188

209-
minworld, maxworld = get_require_world(), validation_world
210-
211189
if haskey(workspace.visiting, initial.codeinst)
212-
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)
213191
workspace.work_states[current_depth] = VerifyMethodWorkState(work.depth, work.cause, work.recursive_index, :return_to_parent)
214192
continue
215193
end
@@ -218,6 +196,9 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
218196
depth = length(workspace.stack)
219197
workspace.visiting[initial.codeinst] = depth
220198

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+
221202
# Check for invalidation of GlobalRef edges
222203
if (initial.def.did_scan_source & 0x1) == 0x0
223204
backedges_only = unsafe_load(cglobal(:jl_first_image_replacement_world, UInt)) == typemax(UInt)
@@ -238,7 +219,7 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
238219
while j <= length(initial.callees)
239220
local min_valid2::UInt, max_valid2::UInt
240221
edge = initial.callees[j]
241-
@assert !(edge isa Method)
222+
@assert !(edge isa Method) "unexpected Method edge indicates corrupt edges list creation"
242223

243224
if edge isa CodeInstance
244225
# Convert CodeInstance to MethodInstance for validation (like original)
@@ -342,12 +323,16 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace
342323
child = pop!(workspace.stack)
343324
if result.result_maxworld 0
344325
@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
345330
end
346331
@atomic :monotonic child.max_world = result.result_maxworld
347-
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)
348333
store_backedges(child, child.edges)
349334
end
350-
@assert workspace.visiting[child] == length(workspace.stack) + 1
335+
@assert workspace.visiting[child] == length(workspace.stack) + 1 "internal error maintaining workspace"
351336
delete!(workspace.visiting, child)
352337
invalidations = _jl_debug_method_invalidation[]
353338
if invalidations !== nothing && result.result_maxworld < validation_world
@@ -509,15 +494,15 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
509494
# Fast path is legal when fully_covers=true
510495
if fully_covers && !iszero(mi.dispatch_status & METHOD_SIG_LATEST_ONLY)
511496
minworld = meth.primary_world
512-
@assert minworld world
497+
@assert minworld world "expected method not present in verification world"
513498
maxworld = typemax(UInt)
514499
return minworld, maxworld
515500
end
516501
end
517502
# Fast path is legal when fully_covers=true
518503
if fully_covers && !iszero(meth.dispatch_status & METHOD_SIG_LATEST_ONLY)
519504
minworld = meth.primary_world
520-
@assert minworld world
505+
@assert minworld world "expected method not present in verification world"
521506
maxworld = typemax(UInt)
522507
return minworld, maxworld
523508
end
@@ -575,7 +560,7 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
575560
end
576561
if interference_fast_path_success
577562
# All interference sets are covered by expecteds, can return success
578-
@assert interference_minworld world
563+
@assert interference_minworld world "expected method not present in verification world"
579564
maxworld = typemax(UInt)
580565
return interference_minworld, maxworld
581566
end
@@ -636,13 +621,13 @@ const METHOD_SIG_LATEST_WHICH = 0x1
636621
const METHOD_SIG_LATEST_ONLY = 0x2
637622

638623
function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UInt, matches::Vector{Any})
639-
@assert invokesig isa Type
624+
@assert invokesig isa Type "corrupt edges list"
640625
local minworld::UInt, maxworld::UInt
641626
empty!(matches)
642627
if invokesig === expected.sig && !iszero(expected.dispatch_status & METHOD_SIG_LATEST_WHICH)
643628
# the invoke match is `expected` for `expected->sig`, unless `expected` is replaced
644629
minworld = expected.primary_world
645-
@assert minworld world
630+
@assert minworld world "expected method not present in verification world"
646631
maxworld = typemax(UInt)
647632
else
648633
mt = get_methodtable(expected)
@@ -667,7 +652,7 @@ function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UIn
667652
end
668653

669654
# Wrapper to call insert_backedges in typeinf_world for external calls
670-
function insert_backedges_typeinf(ext_ci_list::Union{Nothing,Vector{Any}}, internal_methods::Vector{Any})
671-
args = Any[insert_backedges, ext_ci_list, internal_methods]
655+
function insert_backedges_typeinf(internal_methods::Vector{Any})
656+
args = Any[insert_backedges, internal_methods]
672657
return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Any}, Cint), args, length(args))
673658
end

Compiler/src/typeinfer.jl

Lines changed: 12 additions & 12 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)
@@ -1288,8 +1290,6 @@ prepared interp to be able to provide source code for it.
12881290
"""
12891291
const SOURCE_MODE_GET_SOURCE = 0xf
12901292

1291-
ci_has_abi(code::CodeInstance) = (@atomic :acquire code.invoke) !== C_NULL
1292-
12931293
"""
12941294
ci_has_abi(interp::AbstractInterpreter, code::CodeInstance)
12951295
@@ -1298,7 +1298,7 @@ interp gave it to the runtime system (either because it already has an ->invoke
12981298
ptr, or because interp has source that could be compiled).
12991299
"""
13001300
function ci_has_abi(interp::AbstractInterpreter, code::CodeInstance)
1301-
ci_has_abi(code) && return true
1301+
(@atomic :acquire code.invoke) !== C_NULL && return true
13021302
return ci_has_source(interp, code)
13031303
end
13041304

@@ -1619,7 +1619,7 @@ function compile!(codeinfos::Vector{Any}, workqueue::CompilationQueue;
16191619
mi = get_ci_mi(callee)
16201620
# now make sure everything has source code, if desired
16211621
if use_const_api(callee)
1622-
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)
16231623
else
16241624
src = get(interp.codegen, callee, nothing)
16251625
if src === nothing

0 commit comments

Comments
 (0)