@@ -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
12611261function 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
12771284end
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)
13791407end
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