Skip to content

Commit 0fb76e8

Browse files
vtjnashKristofferC
authored andcommitted
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. (cherry picked from commit e7ff95d)
1 parent 959b6ef commit 0fb76e8

File tree

8 files changed

+141
-98
lines changed

8 files changed

+141
-98
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
@@ -153,7 +153,7 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState, validation
153153
# when compiling the compiler to inject everything eagerly
154154
# where codegen can start finding and using it right away
155155
mi = result.linfo
156-
if mi.def isa Method && isa_compileable_sig(mi)
156+
if mi.def isa Method && isa_compileable_sig(mi) && is_cached(caller)
157157
ccall(:jl_add_codeinst_to_jit, Cvoid, (Any, Any), ci, uncompressed)
158158
end
159159
end
@@ -1099,39 +1099,62 @@ end
10991099
"""
11001100
SOURCE_MODE_NOT_REQUIRED
11011101
1102-
Indicates to inference that the source is not required and the only fields
1103-
of the resulting `CodeInstance` that the caller is interested in are types
1104-
and effects. Inference is still free to create a CodeInstance with source,
1105-
but is not required to do so.
1102+
Indicates to inference that the source is not required and the only fields of
1103+
the resulting `CodeInstance` that the caller is interested in are return or
1104+
exception types and IPO effects. Inference is still free to create source for
1105+
it or add it to the JIT even, but is not required or expected to do so.
11061106
"""
11071107
const SOURCE_MODE_NOT_REQUIRED = 0x0
11081108

11091109
"""
11101110
SOURCE_MODE_ABI
11111111
11121112
Indicates to inference that it should return a CodeInstance that can
1113-
either be `->invoke`'d (because it has already been compiled or because
1114-
it has constabi) or one that can be made so by compiling its `->inferred`
1115-
field.
1116-
1117-
N.B.: The `->inferred` field is volatile and the compiler may delete it.
1113+
be `->invoke`'d (because it has already been compiled).
11181114
"""
11191115
const SOURCE_MODE_ABI = 0x1
11201116

11211117
"""
1122-
ci_has_abi(code::CodeInstance)
1118+
SOURCE_MODE_GET_SOURCE
1119+
1120+
Indicates to inference that it should return a CodeInstance after it has
1121+
prepared interp to be able to provide source code for it.
1122+
"""
1123+
const SOURCE_MODE_GET_SOURCE = 0xf
11231124

1124-
Determine whether this CodeInstance is something that could be invoked if we gave it
1125-
to the runtime system (either because it already has an ->invoke ptr, or
1126-
because it has source that could be compiled). Note that this information may
1127-
be stale by the time the user see it, so the user will need to perform their
1128-
own checks if they actually need the abi from it.
11291125
"""
1130-
function ci_has_abi(code::CodeInstance)
1126+
ci_has_abi(interp::AbstractInterpreter, code::CodeInstance)
1127+
1128+
Determine whether this CodeInstance is something that could be invoked if
1129+
interp gave it to the runtime system (either because it already has an ->invoke
1130+
ptr, or because interp has source that could be compiled).
1131+
"""
1132+
function ci_has_abi(interp::AbstractInterpreter, code::CodeInstance)
11311133
(@atomic :acquire code.invoke) !== C_NULL && return true
1134+
return ci_has_source(interp, code)
1135+
end
1136+
1137+
"""
1138+
ci_has_source(interp::AbstractInterpreter, code::CodeInstance)
1139+
1140+
Determine whether this CodeInstance is something that could be compiled from
1141+
source that interp has.
1142+
"""
1143+
function ci_has_source(interp::AbstractInterpreter, code::CodeInstance)
1144+
codegen = codegen_cache(interp)
1145+
codegen === nothing && return false
1146+
use_const_api(code) && return true
1147+
haskey(codegen, code) && return true
11321148
inf = @atomic :monotonic code.inferred
1133-
if code.owner === nothing ? (isa(inf, CodeInfo) || isa(inf, String)) : inf !== nothing
1134-
# 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
1149+
if isa(inf, String)
1150+
inf = _uncompressed_ir(code, inf)
1151+
end
1152+
if code.owner === nothing
1153+
if isa(inf, CodeInfo)
1154+
codegen[code] = inf
1155+
return true
1156+
end
1157+
elseif inf !== nothing
11351158
return true
11361159
end
11371160
return false
@@ -1141,9 +1164,10 @@ function ci_has_invoke(code::CodeInstance)
11411164
return (@atomic :monotonic code.invoke) !== C_NULL
11421165
end
11431166

1144-
function ci_meets_requirement(code::CodeInstance, source_mode::UInt8)
1167+
function ci_meets_requirement(interp::AbstractInterpreter, code::CodeInstance, source_mode::UInt8)
11451168
source_mode == SOURCE_MODE_NOT_REQUIRED && return true
1146-
source_mode == SOURCE_MODE_ABI && return ci_has_abi(code)
1169+
source_mode == SOURCE_MODE_ABI && return ci_has_abi(interp, code)
1170+
source_mode == SOURCE_MODE_GET_SOURCE && return ci_has_source(interp, code)
11471171
return false
11481172
end
11491173

@@ -1153,7 +1177,7 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod
11531177
let code = get(code_cache(interp), mi, nothing)
11541178
if code isa CodeInstance
11551179
# see if this code already exists in the cache
1156-
if ci_meets_requirement(code, source_mode)
1180+
if ci_meets_requirement(interp, code, source_mode)
11571181
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
11581182
return code
11591183
end
@@ -1165,7 +1189,7 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod
11651189
let code = get(code_cache(interp), mi, nothing)
11661190
if code isa CodeInstance
11671191
# see if this code already exists in the cache
1168-
if ci_meets_requirement(code, source_mode)
1192+
if ci_meets_requirement(interp, code, source_mode)
11691193
engine_reject(interp, ci)
11701194
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
11711195
return code
@@ -1196,18 +1220,11 @@ function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance, source_mod
11961220
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
11971221

11981222
ci = result.ci # reload from result in case it changed
1223+
codegen = codegen_cache(interp)
11991224
@assert frame.cache_mode != CACHE_MODE_NULL
1200-
@assert is_result_constabi_eligible(result) || begin
1201-
codegen = codegen_cache(interp)
1202-
codegen === nothing || haskey(codegen, ci)
1203-
end
1225+
@assert is_result_constabi_eligible(result) || codegen === nothing || haskey(codegen, ci)
12041226
@assert is_result_constabi_eligible(result) == use_const_api(ci)
12051227
@assert isdefined(ci, :inferred) "interpreter did not fulfill our expectations"
1206-
if !is_cached(frame) && source_mode == SOURCE_MODE_ABI
1207-
# XXX: jl_type_infer somewhat ambiguously assumes this must be cached
1208-
# XXX: this should be using the CI from the cache, if possible instead: haskey(cache, mi) && (ci = cache[mi])
1209-
code_cache(interp)[mi] = ci
1210-
end
12111228
return ci
12121229
end
12131230

@@ -1221,35 +1238,9 @@ end
12211238
typeinf_type(interp::AbstractInterpreter, match::MethodMatch) =
12221239
typeinf_type(interp, specialize_method(match))
12231240
function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance)
1224-
# n.b.: this could be replaced with @something(typeinf_ext(interp, mi, SOURCE_MODE_NOT_REQUIRED), return nothing).rettype
1225-
start_time = ccall(:jl_typeinf_timing_begin, UInt64, ())
1226-
let code = get(code_cache(interp), mi, nothing)
1227-
if code isa CodeInstance
1228-
# see if this rettype already exists in the cache
1229-
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
1230-
return code.rettype
1231-
end
1232-
end
1233-
ci = engine_reserve(interp, mi)
1234-
let code = get(code_cache(interp), mi, nothing)
1235-
if code isa CodeInstance
1236-
engine_reject(interp, ci)
1237-
# see if this rettype already exists in the cache
1238-
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
1239-
return code.rettype
1240-
end
1241-
end
1242-
result = InferenceResult(mi, typeinf_lattice(interp))
1243-
result.ci = ci
1244-
frame = InferenceState(result, #=cache_mode=#:global, interp)
1245-
if frame === nothing
1246-
engine_reject(interp, ci)
1247-
return nothing
1248-
end
1249-
typeinf(interp, frame)
1250-
ccall(:jl_typeinf_timing_end, Cvoid, (UInt64,), start_time)
1251-
is_inferred(result) || return nothing
1252-
return widenconst(ignorelimited(result.result))
1241+
ci = typeinf_ext(interp, mi, SOURCE_MODE_NOT_REQUIRED)
1242+
ci isa CodeInstance || return nothing
1243+
return ci.rettype
12531244
end
12541245

12551246
# collect a list of all code that is needed along with CodeInstance to codegen it fully
@@ -1286,18 +1277,31 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn
12861277
src = _uncompressed_ir(callee, src)
12871278
end
12881279
if !isa(src, CodeInfo)
1289-
newcallee = typeinf_ext(interp, callee.def, source_mode)
1280+
newcallee = typeinf_ext(interp, callee.def, source_mode) # always SOURCE_MODE_ABI
12901281
if newcallee isa CodeInstance
12911282
callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee
12921283
push!(tocompile, newcallee)
1293-
#else
1294-
# println("warning: could not get source code for ", callee.def)
1284+
end
1285+
if newcallee !== callee
1286+
push!(inspected, callee)
12951287
end
12961288
continue
12971289
end
12981290
end
12991291
push!(inspected, callee)
13001292
collectinvokes!(tocompile, src)
1293+
mi = get_ci_mi(callee)
1294+
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1295+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(interp))::CodeInstance
1296+
if cached === callee
1297+
# make sure callee is gc-rooted and cached, as required by jl_add_codeinst_to_jit
1298+
code_cache(interp)[mi] = callee
1299+
else
1300+
# use an existing CI from the cache, if there is available one that is compatible
1301+
callee === ci && (ci = cached)
1302+
callee = cached
1303+
end
1304+
end
13011305
ccall(:jl_add_codeinst_to_jit, Cvoid, (Any, Any), callee, src)
13021306
end
13031307
return ci
@@ -1341,7 +1345,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
13411345
# and this is either the primary world, or not applicable in the primary world
13421346
# then we want to compile and emit this
13431347
if item.def.primary_world <= this_world <= item.def.deleted_world
1344-
ci = typeinf_ext(interp, item, SOURCE_MODE_NOT_REQUIRED)
1348+
ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE)
13451349
ci isa CodeInstance && push!(tocompile, ci)
13461350
end
13471351
elseif item isa SimpleVector && latest
@@ -1352,7 +1356,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
13521356
sig, this_world, #= mt_cache =# 0)
13531357
if ptr !== C_NULL
13541358
mi = unsafe_pointer_to_objref(ptr)::MethodInstance
1355-
ci = typeinf_ext(interp, mi, SOURCE_MODE_NOT_REQUIRED)
1359+
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
13561360
ci isa CodeInstance && push!(tocompile, ci)
13571361
end
13581362
# additionally enqueue the ccallable entrypoint / adapter, which implicitly
@@ -1364,26 +1368,37 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
13641368
while !isempty(tocompile)
13651369
callee = pop!(tocompile)
13661370
callee in inspected && continue
1367-
push!(inspected, callee)
13681371
# now make sure everything has source code, if desired
13691372
mi = get_ci_mi(callee)
13701373
def = mi.def
13711374
if use_const_api(callee)
13721375
src = codeinfo_for_const(interp, mi, callee.rettype_const)
1373-
elseif haskey(interp.codegen, callee)
1374-
src = interp.codegen[callee]
1375-
elseif isa(def, Method) && !InferenceParams(interp).force_enable_inference && ccall(:jl_get_module_infer, Cint, (Any,), def.module) == 0
1376-
src = retrieve_code_info(mi, get_inference_world(interp))
13771376
else
1378-
# TODO: typeinf_code could return something with different edges/ages/owner/abi (needing an update to callee), which we don't handle here
1379-
src = typeinf_code(interp, mi, true)
1377+
src = get(interp.codegen, callee, nothing)
1378+
if src === nothing
1379+
newcallee = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1380+
if newcallee isa CodeInstance
1381+
@assert use_const_api(newcallee) || haskey(interp.codegen, newcallee)
1382+
push!(tocompile, newcallee)
1383+
end
1384+
if newcallee !== callee
1385+
push!(inspected, callee)
1386+
end
1387+
continue
1388+
end
13801389
end
1390+
push!(inspected, callee)
13811391
if src isa CodeInfo
13821392
collectinvokes!(tocompile, src)
1383-
# It is somewhat ambiguous if typeinf_ext might have callee in the caches,
1384-
# but for the purpose of native compile, we always want them put there.
1393+
# try to reuse an existing CodeInstance from before to avoid making duplicates in the cache
13851394
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1386-
code_cache(interp)[mi] = callee
1395+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, this_world)::CodeInstance
1396+
if cached === callee
1397+
code_cache(interp)[mi] = callee
1398+
else
1399+
# Use an existing CI from the cache, if there is available one that is compatible
1400+
callee = cached
1401+
end
13871402
end
13881403
push!(codeinfos, callee)
13891404
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)