Skip to content

Commit e7ff95d

Browse files
authored
inference: add internal SOURCE_MODE_GET_SOURCE mode (#57878)
Helps avoids some code duplication and divergence of inference behaviors in some edge cases, also slightly more correct caching in some edge cases.
2 parents 8e03cb1 + 14bbb4a commit e7ff95d

File tree

9 files changed

+142
-99
lines changed

9 files changed

+142
-99
lines changed

Compiler/src/bootstrap.jl

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,10 @@ function bootstrap!()
6767
end
6868
mi = specialize_method(m.method, Tuple{params...}, m.sparams)
6969
#isa_compileable_sig(mi) || println(stderr, "WARNING: inferring `", mi, "` which isn't expected to be called.")
70-
push!(methods, mi)
70+
typeinf_ext_toplevel(mi, world, isa_compileable_sig(mi) ? SOURCE_MODE_ABI : SOURCE_MODE_NOT_REQUIRED)
7171
end
7272
end
7373
end
74-
codeinfos = typeinf_ext_toplevel(methods, [world], TRIM_NO)
75-
for i = 1:2:length(codeinfos)
76-
ci = codeinfos[i]::CodeInstance
77-
src = codeinfos[i + 1]::CodeInfo
78-
isa_compileable_sig(ci.def) || continue # println(stderr, "WARNING: compiling `", ci.def, "` which isn't expected to be called.")
79-
ccall(:jl_add_codeinst_to_jit, Cvoid, (Any, Any), ci, src)
80-
end
8174
endtime = time()
8275
println("Base.Compiler ──── ", sub_float(endtime,starttime), " seconds")
8376
end

Compiler/src/typeinfer.jl

Lines changed: 91 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState, validation
156156
# when compiling the compiler to inject everything eagerly
157157
# where codegen can start finding and using it right away
158158
mi = result.linfo
159-
if mi.def isa Method && isa_compileable_sig(mi)
159+
if mi.def isa Method && isa_compileable_sig(mi) && is_cached(caller)
160160
ccall(:jl_add_codeinst_to_jit, Cvoid, (Any, Any), ci, uncompressed)
161161
end
162162
end
@@ -1113,39 +1113,62 @@ end
11131113
"""
11141114
SOURCE_MODE_NOT_REQUIRED
11151115
1116-
Indicates to inference that the source is not required and the only fields
1117-
of the resulting `CodeInstance` that the caller is interested in are types
1118-
and effects. Inference is still free to create a CodeInstance with source,
1119-
but is not required to do so.
1116+
Indicates to inference that the source is not required and the only fields of
1117+
the resulting `CodeInstance` that the caller is interested in are return or
1118+
exception types and IPO effects. Inference is still free to create source for
1119+
it or add it to the JIT even, but is not required or expected to do so.
11201120
"""
11211121
const SOURCE_MODE_NOT_REQUIRED = 0x0
11221122

11231123
"""
11241124
SOURCE_MODE_ABI
11251125
11261126
Indicates to inference that it should return a CodeInstance that can
1127-
either be `->invoke`'d (because it has already been compiled or because
1128-
it has constabi) or one that can be made so by compiling its `->inferred`
1129-
field.
1130-
1131-
N.B.: The `->inferred` field is volatile and the compiler may delete it.
1127+
be `->invoke`'d (because it has already been compiled).
11321128
"""
11331129
const SOURCE_MODE_ABI = 0x1
11341130

11351131
"""
1136-
ci_has_abi(code::CodeInstance)
1132+
SOURCE_MODE_GET_SOURCE
1133+
1134+
Indicates to inference that it should return a CodeInstance after it has
1135+
prepared interp to be able to provide source code for it.
1136+
"""
1137+
const SOURCE_MODE_GET_SOURCE = 0xf
11371138

1138-
Determine whether this CodeInstance is something that could be invoked if we gave it
1139-
to the runtime system (either because it already has an ->invoke ptr, or
1140-
because it has source that could be compiled). Note that this information may
1141-
be stale by the time the user see it, so the user will need to perform their
1142-
own checks if they actually need the abi from it.
11431139
"""
1144-
function ci_has_abi(code::CodeInstance)
1140+
ci_has_abi(interp::AbstractInterpreter, code::CodeInstance)
1141+
1142+
Determine whether this CodeInstance is something that could be invoked if
1143+
interp gave it to the runtime system (either because it already has an ->invoke
1144+
ptr, or because interp has source that could be compiled).
1145+
"""
1146+
function ci_has_abi(interp::AbstractInterpreter, code::CodeInstance)
11451147
(@atomic :acquire code.invoke) !== C_NULL && return true
1148+
return ci_has_source(interp, code)
1149+
end
1150+
1151+
"""
1152+
ci_has_source(interp::AbstractInterpreter, code::CodeInstance)
1153+
1154+
Determine whether this CodeInstance is something that could be compiled from
1155+
source that interp has.
1156+
"""
1157+
function ci_has_source(interp::AbstractInterpreter, code::CodeInstance)
1158+
codegen = codegen_cache(interp)
1159+
codegen === nothing && return false
1160+
use_const_api(code) && return true
1161+
haskey(codegen, code) && return true
11461162
inf = @atomic :monotonic code.inferred
1147-
if code.owner === nothing ? (isa(inf, CodeInfo) || isa(inf, String)) : inf !== nothing
1148-
# interp.codegen[code] = maybe_uncompress(code, inf) # TODO: the correct way to ensure this information doesn't become stale would be to push it into the stable codegen cache
1163+
if isa(inf, String)
1164+
inf = _uncompressed_ir(code, inf)
1165+
end
1166+
if code.owner === nothing
1167+
if isa(inf, CodeInfo)
1168+
codegen[code] = inf
1169+
return true
1170+
end
1171+
elseif inf !== nothing
11491172
return true
11501173
end
11511174
return false
@@ -1155,9 +1178,10 @@ function ci_has_invoke(code::CodeInstance)
11551178
return (@atomic :monotonic code.invoke) !== C_NULL
11561179
end
11571180

1158-
function ci_meets_requirement(code::CodeInstance, source_mode::UInt8)
1181+
function ci_meets_requirement(interp::AbstractInterpreter, code::CodeInstance, source_mode::UInt8)
11591182
source_mode == SOURCE_MODE_NOT_REQUIRED && return true
1160-
source_mode == SOURCE_MODE_ABI && return ci_has_abi(code)
1183+
source_mode == SOURCE_MODE_ABI && return ci_has_abi(interp, code)
1184+
source_mode == SOURCE_MODE_GET_SOURCE && return ci_has_source(interp, code)
11611185
return false
11621186
end
11631187

@@ -1167,7 +1191,7 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod
11671191
let code = get(code_cache(interp), mi, nothing)
11681192
if code isa CodeInstance
11691193
# see if this code already exists in the cache
1170-
if ci_meets_requirement(code, source_mode)
1194+
if ci_meets_requirement(interp, code, source_mode)
11711195
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
11721196
return code
11731197
end
@@ -1179,7 +1203,7 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod
11791203
let code = get(code_cache(interp), mi, nothing)
11801204
if code isa CodeInstance
11811205
# see if this code already exists in the cache
1182-
if ci_meets_requirement(code, source_mode)
1206+
if ci_meets_requirement(interp, code, source_mode)
11831207
engine_reject(interp, ci)
11841208
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
11851209
return code
@@ -1210,18 +1234,11 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod
12101234
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
12111235

12121236
ci = result.ci # reload from result in case it changed
1237+
codegen = codegen_cache(interp)
12131238
@assert frame.cache_mode != CACHE_MODE_NULL
1214-
@assert is_result_constabi_eligible(result) || begin
1215-
codegen = codegen_cache(interp)
1216-
codegen === nothing || haskey(codegen, ci)
1217-
end
1239+
@assert is_result_constabi_eligible(result) || codegen === nothing || haskey(codegen, ci)
12181240
@assert is_result_constabi_eligible(result) == use_const_api(ci)
12191241
@assert isdefined(ci, :inferred) "interpreter did not fulfill our expectations"
1220-
if !is_cached(frame) && source_mode == SOURCE_MODE_ABI
1221-
# XXX: jl_type_infer somewhat ambiguously assumes this must be cached
1222-
# XXX: this should be using the CI from the cache, if possible instead: haskey(cache, mi) && (ci = cache[mi])
1223-
code_cache(interp)[mi] = ci
1224-
end
12251242
return ci
12261243
end
12271244

@@ -1235,35 +1252,9 @@ end
12351252
typeinf_type(interp::AbstractInterpreter, match::MethodMatch) =
12361253
typeinf_type(interp, specialize_method(match))
12371254
function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance)
1238-
# n.b.: this could be replaced with @something(typeinf_ext(interp, mi, SOURCE_MODE_NOT_REQUIRED), return nothing).rettype
1239-
start_time = ccall(:jl_typeinf_timing_begin, UInt64, ())
1240-
let code = get(code_cache(interp), mi, nothing)
1241-
if code isa CodeInstance
1242-
# see if this rettype already exists in the cache
1243-
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
1244-
return code.rettype
1245-
end
1246-
end
1247-
ci = engine_reserve(interp, mi)
1248-
let code = get(code_cache(interp), mi, nothing)
1249-
if code isa CodeInstance
1250-
engine_reject(interp, ci)
1251-
# see if this rettype already exists in the cache
1252-
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
1253-
return code.rettype
1254-
end
1255-
end
1256-
result = InferenceResult(mi, typeinf_lattice(interp))
1257-
result.ci = ci
1258-
frame = InferenceState(result, #=cache_mode=#:global, interp)
1259-
if frame === nothing
1260-
engine_reject(interp, ci)
1261-
return nothing
1262-
end
1263-
typeinf(interp, frame)
1264-
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
1265-
is_inferred(result) || return nothing
1266-
return widenconst(ignorelimited(result.result))
1255+
ci = typeinf_ext(interp, mi, SOURCE_MODE_NOT_REQUIRED)
1256+
ci isa CodeInstance || return nothing
1257+
return ci.rettype
12671258
end
12681259

12691260
# collect a list of all code that is needed along with CodeInstance to codegen it fully
@@ -1300,18 +1291,31 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn
13001291
src = _uncompressed_ir(callee, src)
13011292
end
13021293
if !isa(src, CodeInfo)
1303-
newcallee = typeinf_ext(interp, callee.def, source_mode)
1294+
newcallee = typeinf_ext(interp, callee.def, source_mode) # always SOURCE_MODE_ABI
13041295
if newcallee isa CodeInstance
13051296
callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee
13061297
push!(tocompile, newcallee)
1307-
#else
1308-
# println("warning: could not get source code for ", callee.def)
1298+
end
1299+
if newcallee !== callee
1300+
push!(inspected, callee)
13091301
end
13101302
continue
13111303
end
13121304
end
13131305
push!(inspected, callee)
13141306
collectinvokes!(tocompile, src)
1307+
mi = get_ci_mi(callee)
1308+
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1309+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(interp))::CodeInstance
1310+
if cached === callee
1311+
# make sure callee is gc-rooted and cached, as required by jl_add_codeinst_to_jit
1312+
code_cache(interp)[mi] = callee
1313+
else
1314+
# use an existing CI from the cache, if there is available one that is compatible
1315+
callee === ci && (ci = cached)
1316+
callee = cached
1317+
end
1318+
end
13151319
ccall(:jl_add_codeinst_to_jit, Cvoid, (Any, Any), callee, src)
13161320
end
13171321
return ci
@@ -1355,7 +1359,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
13551359
# and this is either the primary world, or not applicable in the primary world
13561360
# then we want to compile and emit this
13571361
if item.def.primary_world <= this_world <= item.def.deleted_world
1358-
ci = typeinf_ext(interp, item, SOURCE_MODE_NOT_REQUIRED)
1362+
ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE)
13591363
ci isa CodeInstance && push!(tocompile, ci)
13601364
end
13611365
elseif item isa SimpleVector && latest
@@ -1366,7 +1370,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
13661370
sig, this_world, #= mt_cache =# 0)
13671371
if ptr !== C_NULL
13681372
mi = unsafe_pointer_to_objref(ptr)::MethodInstance
1369-
ci = typeinf_ext(interp, mi, SOURCE_MODE_NOT_REQUIRED)
1373+
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
13701374
ci isa CodeInstance && push!(tocompile, ci)
13711375
end
13721376
# additionally enqueue the ccallable entrypoint / adapter, which implicitly
@@ -1377,26 +1381,37 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
13771381
while !isempty(tocompile)
13781382
callee = pop!(tocompile)
13791383
callee in inspected && continue
1380-
push!(inspected, callee)
13811384
# now make sure everything has source code, if desired
13821385
mi = get_ci_mi(callee)
13831386
def = mi.def
13841387
if use_const_api(callee)
13851388
src = codeinfo_for_const(interp, mi, callee.rettype_const)
1386-
elseif haskey(interp.codegen, callee)
1387-
src = interp.codegen[callee]
1388-
elseif isa(def, Method) && !InferenceParams(interp).force_enable_inference && ccall(:jl_get_module_infer, Cint, (Any,), def.module) == 0
1389-
src = retrieve_code_info(mi, get_inference_world(interp))
13901389
else
1391-
# TODO: typeinf_code could return something with different edges/ages/owner/abi (needing an update to callee), which we don't handle here
1392-
src = typeinf_code(interp, mi, true)
1390+
src = get(interp.codegen, callee, nothing)
1391+
if src === nothing
1392+
newcallee = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1393+
if newcallee isa CodeInstance
1394+
@assert use_const_api(newcallee) || haskey(interp.codegen, newcallee)
1395+
push!(tocompile, newcallee)
1396+
end
1397+
if newcallee !== callee
1398+
push!(inspected, callee)
1399+
end
1400+
continue
1401+
end
13931402
end
1403+
push!(inspected, callee)
13941404
if src isa CodeInfo
13951405
collectinvokes!(tocompile, src)
1396-
# It is somewhat ambiguous if typeinf_ext might have callee in the caches,
1397-
# but for the purpose of native compile, we always want them put there.
1406+
# try to reuse an existing CodeInstance from before to avoid making duplicates in the cache
13981407
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1399-
code_cache(interp)[mi] = callee
1408+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, this_world)::CodeInstance
1409+
if cached === callee
1410+
code_cache(interp)[mi] = callee
1411+
else
1412+
# Use an existing CI from the cache, if there is available one that is compatible
1413+
callee = cached
1414+
end
14001415
end
14011416
push!(codeinfos, callee)
14021417
push!(codeinfos, src)

Compiler/src/verifytrim.jl

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ end
110110
function verify_print_error(io::IOContext{IO}, desc::CallMissing, parents::ParentMap)
111111
(; codeinst, codeinfo, sptypes, stmtidx, desc) = desc
112112
frames = verify_create_stackframes(codeinst, stmtidx, parents)
113-
print(io, desc, " from ")
113+
print(io, desc, " from statement ")
114114
verify_print_stmt(io, codeinfo, sptypes, stmtidx)
115115
Base.show_backtrace(io, frames)
116116
print(io, "\n\n")
@@ -181,6 +181,11 @@ function verify_codeinstance!(codeinst::CodeInstance, codeinfo::CodeInfo, inspec
181181
if edge isa CodeInstance
182182
haskey(parents, edge) || (parents[edge] = (codeinst, i))
183183
edge in inspected && continue
184+
edge_mi = get_ci_mi(edge)
185+
if edge_mi === edge.def
186+
ci = get(caches, edge_mi, nothing)
187+
ci isa CodeInstance && continue # assume that only this_world matters for trim
188+
end
184189
end
185190
# TODO: check for calls to Base.atexit?
186191
elseif isexpr(stmt, :call)
@@ -287,7 +292,7 @@ function get_verify_typeinf_trim(codeinfos::Vector{Any})
287292
# TODO: should we find a way to indicate to the user that this gets called via ccallable?
288293
# parent[ci] = something
289294
asrt = ci.rettype
290-
ci in inspected
295+
true
291296
else
292297
false
293298
end
@@ -326,6 +331,14 @@ function verify_typeinf_trim(io::IO, codeinfos::Vector{Any}, onlywarn::Bool)
326331
verify_print_error(io, desc, parents)
327332
end
328333

334+
## TODO: compute and display the minimum and/or full call graph instead of merely the first parent stacktrace?
335+
#for i = 1:length(codeinfos)
336+
# item = codeinfos[i]
337+
# if item isa CodeInstance
338+
# println(item, "::", item.rettype)
339+
# end
340+
#end
341+
329342
let severity = 0
330343
if counts[1] > 0 || counts[2] > 0
331344
print("Trim verify finished with ")

Compiler/test/verifytrim.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ let infos = typeinf_ext_toplevel(Any[Core.svec(Base.SecretBuffer, Tuple{Type{Bas
3333
@test occursin("finalizer", desc.desc)
3434
repr = sprint(verify_print_error, desc, parents)
3535
@test occursin(
36-
r"""^unresolved finalizer registered from \(Core.finalizer\)\(Base.final_shred!, %new\(\)::Base.SecretBuffer\)::Nothing
36+
r"""^unresolved finalizer registered from statement \(Core.finalizer\)\(Base.final_shred!, %new\(\)::Base.SecretBuffer\)::Nothing
3737
Stacktrace:
3838
\[1\] finalizer\(f::typeof\(Base.final_shred!\), o::Base.SecretBuffer\)
3939
@ Base gcutils.jl:(\d+) \[inlined\]

src/aotcompile.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,20 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm
675675
fargs[0] = (jl_value_t*)codeinfos;
676676
void *data = jl_emit_native(codeinfos, llvmmod, &cgparams, external_linkage);
677677

678+
// examine everything just emitted and save it to the caches
679+
if (!external_linkage) {
680+
for (size_t i = 0, l = jl_array_nrows(codeinfos); i < l; i++) {
681+
jl_value_t *item = jl_array_ptr_ref(codeinfos, i);
682+
if (jl_is_code_instance(item)) {
683+
// now add it to our compilation results
684+
jl_code_instance_t *codeinst = (jl_code_instance_t*)item;
685+
jl_code_info_t *src = (jl_code_info_t*)jl_array_ptr_ref(codeinfos, ++i);
686+
assert(jl_is_code_info(src));
687+
jl_add_codeinst_to_cache(codeinst, src);
688+
}
689+
}
690+
}
691+
678692
// move everything inside, now that we've merged everything
679693
// (before adding the exported headers)
680694
((jl_native_code_desc_t*)data)->M.withModuleDo([&](Module &M) {

0 commit comments

Comments
 (0)