Skip to content

Commit d34fb93

Browse files
committed
typeinfer: add separate compile workqueue for native / call_latest code
1 parent e4e86a2 commit d34fb93

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
@@ -1378,6 +1378,12 @@ end
13781378
# Method and return a compilable MethodInstance for the call, if
13791379
# it will be runtime-dispatched to exactly that MethodInstance
13801380
function compileable_specialization_for_call(interp::AbstractInterpreter, @nospecialize(argtype))
1381+
mt = ccall(:jl_method_table_for, Any, (Any,), argtype)
1382+
if mt === nothing
1383+
# this would require scanning all method tables, so give up instead
1384+
return nothing
1385+
end
1386+
13811387
matches = findall(argtype, method_table(interp); limit = 1)
13821388
matches === nothing && return nothing
13831389
length(matches.matches) == 0 && return nothing
@@ -1392,41 +1398,64 @@ function compileable_specialization_for_call(interp::AbstractInterpreter, @nospe
13921398
else
13931399
mi = specialize_method(match.method, compileable_atype, match.sparams)
13941400
end
1401+
13951402
return mi
13961403
end
13971404

1405+
const QueueItems = Union{CodeInstance,MethodInstance,SimpleVector}
1406+
1407+
struct CompilationQueue
1408+
tocompile::Vector{QueueItems}
1409+
inspected::IdSet{QueueItems}
1410+
interp::Union{AbstractInterpreter,Nothing}
1411+
1412+
CompilationQueue(;
1413+
interp::Union{AbstractInterpreter,Nothing}
1414+
) = new(QueueItems[], IdSet{QueueItems}(), interp)
1415+
1416+
CompilationQueue(queue::CompilationQueue;
1417+
interp::Union{AbstractInterpreter,Nothing}
1418+
) = new(empty!(queue.tocompile), empty!(queue.inspected), interp)
1419+
end
1420+
1421+
Base.push!(queue::CompilationQueue, item) = push!(queue.tocompile, item)
1422+
Base.append!(queue::CompilationQueue, items) = append!(queue.tocompile, items)
1423+
Base.pop!(queue::CompilationQueue) = pop!(queue.tocompile)
1424+
Base.empty!(queue::CompilationQueue) = (empty!(queue.tocompile); empty!(queue.inspected))
1425+
markinspected!(queue::CompilationQueue, item) = push!(queue.inspected, item)
1426+
isinspected(queue::CompilationQueue, item) = item in queue.inspected
1427+
Base.isempty(queue::CompilationQueue) = isempty(queue.tocompile)
1428+
13981429
# collect a list of all code that is needed along with CodeInstance to codegen it fully
1399-
function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo, sptypes::Vector{VarState})
1430+
function collectinvokes!(workqueue::CompilationQueue, ci::CodeInfo, sptypes::Vector{VarState};
1431+
invokelatest_queue::Union{CompilationQueue,Nothing} = nothing)
14001432
src = ci.code
14011433
for i = 1:length(src)
14021434
stmt = src[i]
14031435
isexpr(stmt, :(=)) && (stmt = stmt.args[2])
14041436
if isexpr(stmt, :invoke) || isexpr(stmt, :invoke_modify)
14051437
edge = stmt.args[1]
1406-
edge isa CodeInstance && isdefined(edge, :inferred) && push!(wq, edge)
1438+
edge isa CodeInstance && isdefined(edge, :inferred) && push!(workqueue, edge)
14071439
end
14081440

14091441
if isexpr(stmt, :call)
14101442
farg = stmt.args[1]
14111443
!applicable(argextype, farg, ci, sptypes) && continue # TODO: Why is this failing during bootstrap
14121444
ftyp = widenconst(argextype(farg, ci, sptypes))
14131445
if ftyp <: Builtin
1414-
# TODO: Make interp elsewhere
1415-
interp = NativeInterpreter(Base.get_world_counter())
14161446
if Core.finalizer isa ftyp && length(stmt.args) == 3
14171447
finalizer = argextype(stmt.args[2], ci, sptypes)
14181448
obj = argextype(stmt.args[3], ci, sptypes)
14191449
atype = argtypes_to_type(Any[finalizer, obj])
14201450

1421-
mi = compileable_specialization_for_call(interp, atype)
1422-
mi === nothing && continue
1451+
invokelatest_queue === nothing && continue
1452+
let workqueue = invokelatest_queue
1453+
# make a best-effort attempt to enqueue the relevant code for the finalizer
1454+
mi = compileable_specialization_for_call(workqueue.interp, atype)
1455+
mi === nothing && continue
14231456

1424-
if mi.def.primary_world <= Base.get_world_counter() <= mi.def.deleted_world
1425-
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1426-
# TODO: separate workqueue for NativeInterpreter
1427-
ci isa CodeInstance && push!(wq, ci)
1457+
push!(workqueue, mi)
14281458
end
1429-
# push!(wq, mi)
14301459
end
14311460
end
14321461
end
@@ -1439,41 +1468,40 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn
14391468
ci isa CodeInstance && !ci_has_invoke(ci) || return ci
14401469
codegen = codegen_cache(interp)
14411470
codegen === nothing && return ci
1442-
inspected = IdSet{CodeInstance}()
1443-
tocompile = Vector{CodeInstance}()
1444-
push!(tocompile, ci)
1445-
while !isempty(tocompile)
1471+
workqueue = CompilationQueue(; interp)
1472+
push!(workqueue, ci)
1473+
while !isempty(workqueue)
14461474
# 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)
1447-
callee = pop!(tocompile)
1475+
callee = pop!(workqueue)
14481476
ci_has_invoke(callee) && continue
1449-
callee in inspected && continue
1477+
isinspected(workqueue, callee) && continue
14501478
src = get(codegen, callee, nothing)
14511479
if !isa(src, CodeInfo)
14521480
src = @atomic :monotonic callee.inferred
14531481
if isa(src, String)
14541482
src = _uncompressed_ir(callee, src)
14551483
end
14561484
if !isa(src, CodeInfo)
1457-
newcallee = typeinf_ext(interp, callee.def, source_mode) # always SOURCE_MODE_ABI
1485+
newcallee = typeinf_ext(workqueue.interp, callee.def, source_mode) # always SOURCE_MODE_ABI
14581486
if newcallee isa CodeInstance
14591487
callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee
1460-
push!(tocompile, newcallee)
1488+
push!(workqueue, newcallee)
14611489
end
14621490
if newcallee !== callee
1463-
push!(inspected, callee)
1491+
markinspected!(workqueue, callee)
14641492
end
14651493
continue
14661494
end
14671495
end
1468-
push!(inspected, callee)
1496+
markinspected!(workqueue, callee)
14691497
mi = get_ci_mi(callee)
14701498
sptypes = sptypes_from_meth_instance(mi)
1471-
collectinvokes!(tocompile, src, sptypes)
1499+
collectinvokes!(workqueue, src, sptypes)
14721500
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1473-
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(interp))::CodeInstance
1501+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(workqueue.interp))::CodeInstance
14741502
if cached === callee
14751503
# make sure callee is gc-rooted and cached, as required by jl_add_codeinst_to_jit
1476-
code_cache(interp)[mi] = callee
1504+
code_cache(workqueue.interp)[mi] = callee
14771505
else
14781506
# use an existing CI from the cache, if there is available one that is compatible
14791507
callee === ci && (ci = cached)
@@ -1497,57 +1525,45 @@ function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt
14971525
return typeinf_ext_toplevel(interp, mi, source_mode)
14981526
end
14991527

1500-
# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches
1501-
# The trim_mode can be any of:
1502-
const TRIM_NO = 0
1503-
const TRIM_SAFE = 1
1504-
const TRIM_UNSAFE = 2
1505-
const TRIM_UNSAFE_WARN = 3
1506-
function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int)
1507-
inspected = IdSet{CodeInstance}()
1508-
tocompile = Vector{CodeInstance}()
1509-
codeinfos = []
1510-
# first compute the ABIs of everything
1511-
latest = true # whether this_world == world_counter()
1512-
for this_world in reverse(sort!(worlds))
1513-
interp = NativeInterpreter(
1514-
this_world;
1515-
inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO)
1516-
)
1517-
for i = 1:length(methods)
1518-
# each item in this list is either a MethodInstance indicating something
1519-
# to compile, or an svec(rettype, sig) describing a C-callable alias to create.
1520-
item = methods[i]
1521-
if item isa MethodInstance
1522-
# if this method is generally visible to the current compilation world,
1523-
# and this is either the primary world, or not applicable in the primary world
1524-
# then we want to compile and emit this
1525-
if item.def.primary_world <= this_world <= item.def.deleted_world
1526-
ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE)
1527-
ci isa CodeInstance && push!(tocompile, ci)
1528-
end
1529-
elseif item isa SimpleVector && latest
1530-
(rt::Type, sig::Type) = item
1531-
# make a best-effort attempt to enqueue the relevant code for the ccallable
1532-
ptr = ccall(:jl_get_specialization1,
1533-
#= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint),
1534-
sig, this_world, #= mt_cache =# 0)
1535-
if ptr !== C_NULL
1536-
mi = unsafe_pointer_to_objref(ptr)::MethodInstance
1537-
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1538-
ci isa CodeInstance && push!(tocompile, ci)
1539-
end
1540-
# additionally enqueue the ccallable entrypoint / adapter, which implicitly
1541-
# invokes the above ci
1542-
push!(codeinfos, item)
1528+
function compile!(codeinfos::Vector{Any}, workqueue::CompilationQueue;
1529+
invokelatest_queue::Union{CompilationQueue,Nothing} = nothing,
1530+
)
1531+
interp = workqueue.interp
1532+
world = get_inference_world(interp)
1533+
while !isempty(workqueue)
1534+
item = pop!(workqueue)
1535+
# each item in this list is either a MethodInstance indicating something
1536+
# to compile, or an svec(rettype, sig) describing a C-callable alias to create.
1537+
if item isa MethodInstance
1538+
isinspected(workqueue, item) && continue
1539+
# if this method is generally visible to the current compilation world,
1540+
# and this is either the primary world, or not applicable in the primary world
1541+
# then we want to compile and emit this
1542+
if item.def.primary_world <= world <= item.def.deleted_world
1543+
ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE)
1544+
ci isa CodeInstance && push!(workqueue, ci)
15431545
end
1544-
end
1545-
while !isempty(tocompile)
1546-
callee = pop!(tocompile)
1547-
callee in inspected && continue
1548-
# now make sure everything has source code, if desired
1546+
markinspected!(workqueue, item)
1547+
elseif item isa SimpleVector
1548+
invokelatest_queue === nothing && continue
1549+
(rt::Type, sig::Type) = item
1550+
# make a best-effort attempt to enqueue the relevant code for the ccallable
1551+
ptr = ccall(:jl_get_specialization1,
1552+
#= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint),
1553+
sig, world, #= mt_cache =# 0)
1554+
if ptr !== C_NULL
1555+
mi = unsafe_pointer_to_objref(ptr)::MethodInstance
1556+
ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
1557+
ci isa CodeInstance && push!(invokelatest_queue, ci)
1558+
end
1559+
# additionally enqueue the ccallable entrypoint / adapter, which implicitly
1560+
# invokes the above ci
1561+
push!(codeinfos, item)
1562+
elseif item isa CodeInstance
1563+
callee = item
1564+
isinspected(workqueue, callee) && continue
15491565
mi = get_ci_mi(callee)
1550-
def = mi.def
1566+
# now make sure everything has source code, if desired
15511567
if use_const_api(callee)
15521568
src = codeinfo_for_const(interp, mi, callee.rettype_const)
15531569
else
@@ -1556,21 +1572,21 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
15561572
newcallee = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE)
15571573
if newcallee isa CodeInstance
15581574
@assert use_const_api(newcallee) || haskey(interp.codegen, newcallee)
1559-
push!(tocompile, newcallee)
1575+
push!(workqueue, newcallee)
15601576
end
15611577
if newcallee !== callee
1562-
push!(inspected, callee)
1578+
markinspected!(workqueue, callee)
15631579
end
15641580
continue
15651581
end
15661582
end
1567-
push!(inspected, callee)
1583+
markinspected!(workqueue, callee)
15681584
if src isa CodeInfo
15691585
sptypes = sptypes_from_meth_instance(mi)
1570-
collectinvokes!(tocompile, src, sptypes)
1586+
collectinvokes!(workqueue, src, sptypes; invokelatest_queue)
15711587
# try to reuse an existing CodeInstance from before to avoid making duplicates in the cache
15721588
if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee))
1573-
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, this_world)::CodeInstance
1589+
cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, world)::CodeInstance
15741590
if cached === callee
15751591
code_cache(interp)[mi] = callee
15761592
else
@@ -1581,9 +1597,45 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
15811597
push!(codeinfos, callee)
15821598
push!(codeinfos, src)
15831599
end
1600+
else @assert false "unexpected item in queue" end
1601+
end
1602+
return codeinfos
1603+
end
1604+
1605+
# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches
1606+
# The trim_mode can be any of:
1607+
const TRIM_NO = 0
1608+
const TRIM_SAFE = 1
1609+
const TRIM_UNSAFE = 2
1610+
const TRIM_UNSAFE_WARN = 3
1611+
function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int)
1612+
inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO)
1613+
invokelatest_queue = CompilationQueue(;
1614+
interp = NativeInterpreter(get_world_counter(); inf_params)
1615+
)
1616+
codeinfos = []
1617+
is_latest_world = true # whether this_world == world_counter()
1618+
workqueue = CompilationQueue(; interp = nothing)
1619+
for (i, this_world) in enumerate(sort!(worlds))
1620+
workqueue = CompilationQueue(workqueue;
1621+
interp = NativeInterpreter(this_world; inf_params)
1622+
)
1623+
1624+
append!(workqueue, methods)
1625+
1626+
is_latest_world = (i == length(worlds))
1627+
if is_latest_world
1628+
# Provide the `invokelatest` queue so that we trigger "best-effort" code generation
1629+
# for, e.g., finalizers and cfunction.
1630+
#
1631+
# The queue is intentionally aliased, to handle e.g. a `finalizer` calling `Core.finalizer`
1632+
# (it will enqueue into itself and immediately drain)
1633+
compile!(codeinfos, workqueue; invokelatest_queue = workqueue)
1634+
else
1635+
compile!(codeinfos, workqueue)
15841636
end
1585-
latest = false
15861637
end
1638+
15871639
if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE
15881640
verify_typeinf_trim(codeinfos, trim_mode == TRIM_UNSAFE_WARN)
15891641
end

0 commit comments

Comments
 (0)