Skip to content

Commit 1697857

Browse files
committed
typeinfer: add separate compile workqueue for native / call_latest code
1 parent 08705ab commit 1697857

File tree

1 file changed

+131
-79
lines changed

1 file changed

+131
-79
lines changed

Compiler/src/typeinfer.jl

Lines changed: 131 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,12 @@ end
12591259
# Method and return a compilable MethodInstance for the call, if
12601260
# it will be runtime-dispatched to exactly that MethodInstance
12611261
function compileable_specialization_for_call(interp::AbstractInterpreter, @nospecialize(argtype))
1262+
mt = ccall(:jl_method_table_for, Any, (Any,), argtype)
1263+
if mt === nothing
1264+
# this would require scanning all method tables, so give up instead
1265+
return nothing
1266+
end
1267+
12621268
matches = findall(argtype, method_table(interp); limit = 1)
12631269
matches === nothing && return nothing
12641270
length(matches.matches) == 0 && return nothing
@@ -1273,41 +1279,64 @@ function compileable_specialization_for_call(interp::AbstractInterpreter, @nospe
12731279
else
12741280
mi = specialize_method(match.method, compileable_atype, match.sparams)
12751281
end
1282+
12761283
return mi
12771284
end
12781285

1286+
const QueueItems = Union{CodeInstance,MethodInstance,SimpleVector}
1287+
1288+
struct CompilationQueue
1289+
tocompile::Vector{QueueItems}
1290+
inspected::IdSet{QueueItems}
1291+
interp::Union{AbstractInterpreter,Nothing}
1292+
1293+
CompilationQueue(;
1294+
interp::Union{AbstractInterpreter,Nothing}
1295+
) = new(QueueItems[], IdSet{QueueItems}(), interp)
1296+
1297+
CompilationQueue(queue::CompilationQueue;
1298+
interp::Union{AbstractInterpreter,Nothing}
1299+
) = new(empty!(queue.tocompile), empty!(queue.inspected), interp)
1300+
end
1301+
1302+
Base.push!(queue::CompilationQueue, item) = push!(queue.tocompile, item)
1303+
Base.append!(queue::CompilationQueue, items) = append!(queue.tocompile, items)
1304+
Base.pop!(queue::CompilationQueue) = pop!(queue.tocompile)
1305+
Base.empty!(queue::CompilationQueue) = (empty!(queue.tocompile); empty!(queue.inspected))
1306+
markinspected!(queue::CompilationQueue, item) = push!(queue.inspected, item)
1307+
isinspected(queue::CompilationQueue, item) = item in queue.inspected
1308+
Base.isempty(queue::CompilationQueue) = isempty(queue.tocompile)
1309+
12791310
# collect a list of all code that is needed along with CodeInstance to codegen it fully
1280-
function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo, sptypes::Vector{VarState})
1311+
function collectinvokes!(workqueue::CompilationQueue, ci::CodeInfo, sptypes::Vector{VarState};
1312+
invokelatest_queue::Union{CompilationQueue,Nothing} = nothing)
12811313
src = ci.code
12821314
for i = 1:length(src)
12831315
stmt = src[i]
12841316
isexpr(stmt, :(=)) && (stmt = stmt.args[2])
12851317
if isexpr(stmt, :invoke) || isexpr(stmt, :invoke_modify)
12861318
edge = stmt.args[1]
1287-
edge isa CodeInstance && isdefined(edge, :inferred) && push!(wq, edge)
1319+
edge isa CodeInstance && isdefined(edge, :inferred) && push!(workqueue, edge)
12881320
end
12891321

12901322
if isexpr(stmt, :call)
12911323
farg = stmt.args[1]
12921324
!applicable(argextype, farg, ci, sptypes) && continue # TODO: Why is this failing during bootstrap
12931325
ftyp = widenconst(argextype(farg, ci, sptypes))
12941326
if ftyp <: Builtin
1295-
# TODO: Make interp elsewhere
1296-
interp = NativeInterpreter(Base.get_world_counter())
12971327
if Core.finalizer isa ftyp && length(stmt.args) == 3
12981328
finalizer = argextype(stmt.args[2], ci, sptypes)
12991329
obj = argextype(stmt.args[3], ci, sptypes)
13001330
atype = argtypes_to_type(Any[finalizer, obj])
13011331

1302-
mi = compileable_specialization_for_call(interp, atype)
1303-
mi === nothing && continue
1332+
invokelatest_queue === nothing && continue
1333+
let workqueue = invokelatest_queue
1334+
# make a best-effort attempt to enqueue the relevant code for the finalizer
1335+
mi = compileable_specialization_for_call(workqueue.interp, atype)
1336+
mi === nothing && continue
13041337

1305-
if mi.def.primary_world <= Base.get_world_counter() <= mi.def.deleted_world
1306-
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1307-
# TODO: separate workqueue for NativeInterpreter
1308-
ci isa CodeInstance && push!(wq, ci)
1338+
push!(workqueue, mi)
13091339
end
1310-
# push!(wq, mi)
13111340
end
13121341
end
13131342
end
@@ -1320,41 +1349,40 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn
13201349
ci isa CodeInstance && !ci_has_invoke(ci) || return ci
13211350
codegen = codegen_cache(interp)
13221351
codegen === nothing && return ci
1323-
inspected = IdSet{CodeInstance}()
1324-
tocompile = Vector{CodeInstance}()
1325-
push!(tocompile, ci)
1326-
while !isempty(tocompile)
1352+
workqueue = CompilationQueue(; interp)
1353+
push!(workqueue, ci)
1354+
while !isempty(workqueue)
13271355
# ci_has_real_invoke(ci) && return ci # optimization: cease looping if ci happens to get compiled (not just jl_fptr_wait_for_compiled, but fully jl_is_compiled_codeinst)
1328-
callee = pop!(tocompile)
1356+
callee = pop!(workqueue)
13291357
ci_has_invoke(callee) && continue
1330-
callee in inspected && continue
1358+
isinspected(workqueue, callee) && continue
13311359
src = get(codegen, callee, nothing)
13321360
if !isa(src, CodeInfo)
13331361
src = @atomic :monotonic callee.inferred
13341362
if isa(src, String)
13351363
src = _uncompressed_ir(callee, src)
13361364
end
13371365
if !isa(src, CodeInfo)
1338-
newcallee = typeinf_ext(interp, callee.def, source_mode) # always SOURCE_MODE_ABI
1366+
newcallee = typeinf_ext(workqueue.interp, callee.def, source_mode) # always SOURCE_MODE_ABI
13391367
if newcallee isa CodeInstance
13401368
callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee
1341-
push!(tocompile, newcallee)
1369+
push!(workqueue, newcallee)
13421370
end
13431371
if newcallee !== callee
1344-
push!(inspected, callee)
1372+
markinspected!(workqueue, callee)
13451373
end
13461374
continue
13471375
end
13481376
end
1349-
push!(inspected, callee)
1377+
markinspected!(workqueue, callee)
13501378
mi = get_ci_mi(callee)
13511379
sptypes = sptypes_from_meth_instance(mi)
1352-
collectinvokes!(tocompile, src, sptypes)
1380+
collectinvokes!(workqueue, src, sptypes)
13531381
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1354-
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(interp))::CodeInstance
1382+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(workqueue.interp))::CodeInstance
13551383
if cached === callee
13561384
# make sure callee is gc-rooted and cached, as required by jl_add_codeinst_to_jit
1357-
code_cache(interp)[mi] = callee
1385+
code_cache(workqueue.interp)[mi] = callee
13581386
else
13591387
# use an existing CI from the cache, if there is available one that is compatible
13601388
callee === ci && (ci = cached)
@@ -1378,57 +1406,45 @@ function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt
13781406
return typeinf_ext_toplevel(interp, mi, source_mode)
13791407
end
13801408

1381-
# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches
1382-
# The trim_mode can be any of:
1383-
const TRIM_NO = 0
1384-
const TRIM_SAFE = 1
1385-
const TRIM_UNSAFE = 2
1386-
const TRIM_UNSAFE_WARN = 3
1387-
function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int)
1388-
inspected = IdSet{CodeInstance}()
1389-
tocompile = Vector{CodeInstance}()
1390-
codeinfos = []
1391-
# first compute the ABIs of everything
1392-
latest = true # whether this_world == world_counter()
1393-
for this_world in reverse(sort!(worlds))
1394-
interp = NativeInterpreter(
1395-
this_world;
1396-
inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO)
1397-
)
1398-
for i = 1:length(methods)
1399-
# each item in this list is either a MethodInstance indicating something
1400-
# to compile, or an svec(rettype, sig) describing a C-callable alias to create.
1401-
item = methods[i]
1402-
if item isa MethodInstance
1403-
# if this method is generally visible to the current compilation world,
1404-
# and this is either the primary world, or not applicable in the primary world
1405-
# then we want to compile and emit this
1406-
if item.def.primary_world <= this_world <= item.def.deleted_world
1407-
ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE)
1408-
ci isa CodeInstance && push!(tocompile, ci)
1409-
end
1410-
elseif item isa SimpleVector && latest
1411-
(rt::Type, sig::Type) = item
1412-
# make a best-effort attempt to enqueue the relevant code for the ccallable
1413-
ptr = ccall(:jl_get_specialization1,
1414-
#= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint),
1415-
sig, this_world, #= mt_cache =# 0)
1416-
if ptr !== C_NULL
1417-
mi = unsafe_pointer_to_objref(ptr)::MethodInstance
1418-
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1419-
ci isa CodeInstance && push!(tocompile, ci)
1420-
end
1421-
# additionally enqueue the ccallable entrypoint / adapter, which implicitly
1422-
# invokes the above ci
1423-
push!(codeinfos, item)
1409+
function compile!(codeinfos::Vector{Any}, workqueue::CompilationQueue;
1410+
invokelatest_queue::Union{CompilationQueue,Nothing} = nothing,
1411+
)
1412+
interp = workqueue.interp
1413+
world = get_inference_world(interp)
1414+
while !isempty(workqueue)
1415+
item = pop!(workqueue)
1416+
# each item in this list is either a MethodInstance indicating something
1417+
# to compile, or an svec(rettype, sig) describing a C-callable alias to create.
1418+
if item isa MethodInstance
1419+
isinspected(workqueue, item) && continue
1420+
# if this method is generally visible to the current compilation world,
1421+
# and this is either the primary world, or not applicable in the primary world
1422+
# then we want to compile and emit this
1423+
if item.def.primary_world <= world <= item.def.deleted_world
1424+
ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE)
1425+
ci isa CodeInstance && push!(workqueue, ci)
14241426
end
1425-
end
1426-
while !isempty(tocompile)
1427-
callee = pop!(tocompile)
1428-
callee in inspected && continue
1429-
# now make sure everything has source code, if desired
1427+
markinspected!(workqueue, item)
1428+
elseif item isa SimpleVector
1429+
invokelatest_queue === nothing && continue
1430+
(rt::Type, sig::Type) = item
1431+
# make a best-effort attempt to enqueue the relevant code for the ccallable
1432+
ptr = ccall(:jl_get_specialization1,
1433+
#= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint),
1434+
sig, world, #= mt_cache =# 0)
1435+
if ptr !== C_NULL
1436+
mi = unsafe_pointer_to_objref(ptr)::MethodInstance
1437+
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1438+
ci isa CodeInstance && push!(invokelatest_queue, ci)
1439+
end
1440+
# additionally enqueue the ccallable entrypoint / adapter, which implicitly
1441+
# invokes the above ci
1442+
push!(codeinfos, item)
1443+
elseif item isa CodeInstance
1444+
callee = item
1445+
isinspected(workqueue, callee) && continue
14301446
mi = get_ci_mi(callee)
1431-
def = mi.def
1447+
# now make sure everything has source code, if desired
14321448
if use_const_api(callee)
14331449
src = codeinfo_for_const(interp, mi, callee.rettype_const)
14341450
else
@@ -1437,21 +1453,21 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
14371453
newcallee = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
14381454
if newcallee isa CodeInstance
14391455
@assert use_const_api(newcallee) || haskey(interp.codegen, newcallee)
1440-
push!(tocompile, newcallee)
1456+
push!(workqueue, newcallee)
14411457
end
14421458
if newcallee !== callee
1443-
push!(inspected, callee)
1459+
markinspected!(workqueue, callee)
14441460
end
14451461
continue
14461462
end
14471463
end
1448-
push!(inspected, callee)
1464+
markinspected!(workqueue, callee)
14491465
if src isa CodeInfo
14501466
sptypes = sptypes_from_meth_instance(mi)
1451-
collectinvokes!(tocompile, src, sptypes)
1467+
collectinvokes!(workqueue, src, sptypes; invokelatest_queue)
14521468
# try to reuse an existing CodeInstance from before to avoid making duplicates in the cache
14531469
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1454-
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, this_world)::CodeInstance
1470+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, world)::CodeInstance
14551471
if cached === callee
14561472
code_cache(interp)[mi] = callee
14571473
else
@@ -1462,9 +1478,45 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
14621478
push!(codeinfos, callee)
14631479
push!(codeinfos, src)
14641480
end
1481+
else @assert false "unexpected item in queue" end
1482+
end
1483+
return codeinfos
1484+
end
1485+
1486+
# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches
1487+
# The trim_mode can be any of:
1488+
const TRIM_NO = 0
1489+
const TRIM_SAFE = 1
1490+
const TRIM_UNSAFE = 2
1491+
const TRIM_UNSAFE_WARN = 3
1492+
function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int)
1493+
inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO)
1494+
invokelatest_queue = CompilationQueue(;
1495+
interp = NativeInterpreter(get_world_counter(); inf_params)
1496+
)
1497+
codeinfos = []
1498+
is_latest_world = true # whether this_world == world_counter()
1499+
workqueue = CompilationQueue(; interp = nothing)
1500+
for (i, this_world) in enumerate(sort!(worlds))
1501+
workqueue = CompilationQueue(workqueue;
1502+
interp = NativeInterpreter(this_world; inf_params)
1503+
)
1504+
1505+
append!(workqueue, methods)
1506+
1507+
is_latest_world = (i == length(worlds))
1508+
if is_latest_world
1509+
# Provide the `invokelatest` queue so that we trigger "best-effort" code generation
1510+
# for, e.g., finalizers and cfunction.
1511+
#
1512+
# The queue is intentionally aliased, to handle e.g. a `finalizer` calling `Core.finalizer`
1513+
# (it will enqueue into itself and immediately drain)
1514+
compile!(codeinfos, workqueue; invokelatest_queue = workqueue)
1515+
else
1516+
compile!(codeinfos, workqueue)
14651517
end
1466-
latest = false
14671518
end
1519+
14681520
if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE
14691521
verify_typeinf_trim(codeinfos, trim_mode == TRIM_UNSAFE_WARN)
14701522
end

0 commit comments

Comments
 (0)