@@ -1374,15 +1374,92 @@ function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance)
13741374 return ci. rettype
13751375end
13761376
1377+ # Resolve a call, as described by `argtype` to a single matching
1378+ # Method and return a compilable MethodInstance for the call, if
1379+ # it will be runtime-dispatched to exactly that MethodInstance
1380+ 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+
1387+ matches = findall (argtype, method_table (interp); limit = 1 )
1388+ matches === nothing && return nothing
1389+ length (matches. matches) == 0 && return nothing
1390+ match = only (matches. matches)
1391+
1392+ compileable_atype = get_compileable_sig (match. method, match. spec_types, match. sparams)
1393+ compileable_atype === nothing && return nothing
1394+ if match. spec_types != = compileable_atype
1395+ sp_ = ccall (:jl_type_intersection_with_env , Any, (Any, Any), compileable_atype, match. method. sig):: SimpleVector
1396+ sparams = sp_[2 ]:: SimpleVector
1397+ mi = specialize_method (match. method, compileable_atype, sparams)
1398+ else
1399+ mi = specialize_method (match. method, compileable_atype, match. sparams)
1400+ end
1401+
1402+ return mi
1403+ end
1404+
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+
13771429# collect a list of all code that is needed along with CodeInstance to codegen it fully
1378- function collectinvokes! (wq:: Vector{CodeInstance} , ci:: CodeInfo )
1430+ function collectinvokes! (workqueue:: CompilationQueue , ci:: CodeInfo , sptypes:: Vector{VarState} ;
1431+ invokelatest_queue:: Union{CompilationQueue,Nothing} = nothing )
13791432 src = ci. code
13801433 for i = 1 : length (src)
13811434 stmt = src[i]
13821435 isexpr (stmt, :(= )) && (stmt = stmt. args[2 ])
13831436 if isexpr (stmt, :invoke ) || isexpr (stmt, :invoke_modify )
13841437 edge = stmt. args[1 ]
1385- edge isa CodeInstance && isdefined (edge, :inferred ) && push! (wq, edge)
1438+ edge isa CodeInstance && isdefined (edge, :inferred ) && push! (workqueue, edge)
1439+ end
1440+
1441+ invokelatest_queue === nothing && continue
1442+ if isexpr (stmt, :call )
1443+ farg = stmt. args[1 ]
1444+ ! applicable (argextype, farg, ci, sptypes) && continue # TODO : Why is this failing during bootstrap
1445+ ftyp = widenconst (argextype (farg, ci, sptypes))
1446+
1447+ if ftyp === typeof (Core. finalizer) && length (stmt. args) == 3
1448+ finalizer = argextype (stmt. args[2 ], ci, sptypes)
1449+ obj = argextype (stmt. args[3 ], ci, sptypes)
1450+ atype = argtypes_to_type (Any[finalizer, obj])
1451+ else
1452+ # No dynamic dispatch to resolve / enqueue
1453+ continue
1454+ end
1455+
1456+ let workqueue = invokelatest_queue
1457+ # make a best-effort attempt to enqueue the relevant code for the finalizer
1458+ mi = compileable_specialization_for_call (workqueue. interp, atype)
1459+ mi === nothing && continue
1460+
1461+ push! (workqueue, mi)
1462+ end
13861463 end
13871464 # TODO : handle other StmtInfo like @cfunction and OpaqueClosure?
13881465 end
@@ -1393,40 +1470,40 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn
13931470 ci isa CodeInstance && ! ci_has_invoke (ci) || return ci
13941471 codegen = codegen_cache (interp)
13951472 codegen === nothing && return ci
1396- inspected = IdSet {CodeInstance} ()
1397- tocompile = Vector {CodeInstance} ()
1398- push! (tocompile, ci)
1399- while ! isempty (tocompile)
1473+ workqueue = CompilationQueue (; interp)
1474+ push! (workqueue, ci)
1475+ while ! isempty (workqueue)
14001476 # 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)
1401- callee = pop! (tocompile )
1477+ callee = pop! (workqueue )
14021478 ci_has_invoke (callee) && continue
1403- callee in inspected && continue
1479+ isinspected (workqueue, callee) && continue
14041480 src = get (codegen, callee, nothing )
14051481 if ! isa (src, CodeInfo)
14061482 src = @atomic :monotonic callee. inferred
14071483 if isa (src, String)
14081484 src = _uncompressed_ir (callee, src)
14091485 end
14101486 if ! isa (src, CodeInfo)
1411- newcallee = typeinf_ext (interp, callee. def, source_mode) # always SOURCE_MODE_ABI
1487+ newcallee = typeinf_ext (workqueue . interp, callee. def, source_mode) # always SOURCE_MODE_ABI
14121488 if newcallee isa CodeInstance
14131489 callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee
1414- push! (tocompile , newcallee)
1490+ push! (workqueue , newcallee)
14151491 end
14161492 if newcallee != = callee
1417- push! (inspected , callee)
1493+ markinspected! (workqueue , callee)
14181494 end
14191495 continue
14201496 end
14211497 end
1422- push! (inspected, callee)
1423- collectinvokes! (tocompile, src)
1498+ markinspected! (workqueue, callee)
14241499 mi = get_ci_mi (callee)
1500+ sptypes = sptypes_from_meth_instance (mi)
1501+ collectinvokes! (workqueue, src, sptypes)
14251502 if iszero (ccall (:jl_mi_cache_has_ci , Cint, (Any, Any), mi, callee))
1426- cached = ccall (:jl_get_ci_equiv , Any, (Any, UInt), callee, get_inference_world (interp)):: CodeInstance
1503+ cached = ccall (:jl_get_ci_equiv , Any, (Any, UInt), callee, get_inference_world (workqueue . interp)):: CodeInstance
14271504 if cached === callee
14281505 # make sure callee is gc-rooted and cached, as required by jl_add_codeinst_to_jit
1429- code_cache (interp)[mi] = callee
1506+ code_cache (workqueue . interp)[mi] = callee
14301507 else
14311508 # use an existing CI from the cache, if there is available one that is compatible
14321509 callee === ci && (ci = cached)
@@ -1450,57 +1527,45 @@ function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt
14501527 return typeinf_ext_toplevel (interp, mi, source_mode)
14511528end
14521529
1453- # This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches
1454- # The trim_mode can be any of:
1455- const TRIM_NO = 0
1456- const TRIM_SAFE = 1
1457- const TRIM_UNSAFE = 2
1458- const TRIM_UNSAFE_WARN = 3
1459- function typeinf_ext_toplevel (methods:: Vector{Any} , worlds:: Vector{UInt} , trim_mode:: Int )
1460- inspected = IdSet {CodeInstance} ()
1461- tocompile = Vector {CodeInstance} ()
1462- codeinfos = []
1463- # first compute the ABIs of everything
1464- latest = true # whether this_world == world_counter()
1465- for this_world in reverse (sort! (worlds))
1466- interp = NativeInterpreter (
1467- this_world;
1468- inf_params = InferenceParams (; force_enable_inference = trim_mode != TRIM_NO)
1469- )
1470- for i = 1 : length (methods)
1471- # each item in this list is either a MethodInstance indicating something
1472- # to compile, or an svec(rettype, sig) describing a C-callable alias to create.
1473- item = methods[i]
1474- if item isa MethodInstance
1475- # if this method is generally visible to the current compilation world,
1476- # and this is either the primary world, or not applicable in the primary world
1477- # then we want to compile and emit this
1478- if item. def. primary_world <= this_world <= item. def. deleted_world
1479- ci = typeinf_ext (interp, item, SOURCE_MODE_GET_SOURCE)
1480- ci isa CodeInstance && push! (tocompile, ci)
1481- end
1482- elseif item isa SimpleVector && latest
1483- (rt:: Type , sig:: Type ) = item
1484- # make a best-effort attempt to enqueue the relevant code for the ccallable
1485- ptr = ccall (:jl_get_specialization1 ,
1486- #= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint),
1487- sig, this_world, #= mt_cache =# 0 )
1488- if ptr != = C_NULL
1489- mi = unsafe_pointer_to_objref (ptr):: MethodInstance
1490- ci = typeinf_ext (interp, mi, SOURCE_MODE_GET_SOURCE)
1491- ci isa CodeInstance && push! (tocompile, ci)
1492- end
1493- # additionally enqueue the ccallable entrypoint / adapter, which implicitly
1494- # invokes the above ci
1495- push! (codeinfos, item)
1530+ function compile! (codeinfos:: Vector{Any} , workqueue:: CompilationQueue ;
1531+ invokelatest_queue:: Union{CompilationQueue,Nothing} = nothing ,
1532+ )
1533+ interp = workqueue. interp
1534+ world = get_inference_world (interp)
1535+ while ! isempty (workqueue)
1536+ item = pop! (workqueue)
1537+ # each item in this list is either a MethodInstance indicating something
1538+ # to compile, or an svec(rettype, sig) describing a C-callable alias to create.
1539+ if item isa MethodInstance
1540+ isinspected (workqueue, item) && continue
1541+ # if this method is generally visible to the current compilation world,
1542+ # and this is either the primary world, or not applicable in the primary world
1543+ # then we want to compile and emit this
1544+ if item. def. primary_world <= world <= item. def. deleted_world
1545+ ci = typeinf_ext (interp, item, SOURCE_MODE_GET_SOURCE)
1546+ ci isa CodeInstance && push! (workqueue, ci)
14961547 end
1497- end
1498- while ! isempty (tocompile)
1499- callee = pop! (tocompile)
1500- callee in inspected && continue
1501- # now make sure everything has source code, if desired
1548+ markinspected! (workqueue, item)
1549+ elseif item isa SimpleVector
1550+ invokelatest_queue === nothing && continue
1551+ (rt:: Type , sig:: Type ) = item
1552+ # make a best-effort attempt to enqueue the relevant code for the ccallable
1553+ ptr = ccall (:jl_get_specialization1 ,
1554+ #= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint),
1555+ sig, world, #= mt_cache =# 0 )
1556+ if ptr != = C_NULL
1557+ mi = unsafe_pointer_to_objref (ptr):: MethodInstance
1558+ ci = typeinf_ext (interp, mi, SOURCE_MODE_GET_SOURCE)
1559+ ci isa CodeInstance && push! (invokelatest_queue, ci)
1560+ end
1561+ # additionally enqueue the ccallable entrypoint / adapter, which implicitly
1562+ # invokes the above ci
1563+ push! (codeinfos, item)
1564+ elseif item isa CodeInstance
1565+ callee = item
1566+ isinspected (workqueue, callee) && continue
15021567 mi = get_ci_mi (callee)
1503- def = mi . def
1568+ # now make sure everything has source code, if desired
15041569 if use_const_api (callee)
15051570 src = codeinfo_for_const (interp, mi, callee. rettype_const)
15061571 else
@@ -1509,20 +1574,21 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
15091574 newcallee = typeinf_ext (interp, mi, SOURCE_MODE_GET_SOURCE)
15101575 if newcallee isa CodeInstance
15111576 @assert use_const_api (newcallee) || haskey (interp. codegen, newcallee)
1512- push! (tocompile , newcallee)
1577+ push! (workqueue , newcallee)
15131578 end
15141579 if newcallee != = callee
1515- push! (inspected , callee)
1580+ markinspected! (workqueue , callee)
15161581 end
15171582 continue
15181583 end
15191584 end
1520- push! (inspected , callee)
1585+ markinspected! (workqueue , callee)
15211586 if src isa CodeInfo
1522- collectinvokes! (tocompile, src)
1587+ sptypes = sptypes_from_meth_instance (mi)
1588+ collectinvokes! (workqueue, src, sptypes; invokelatest_queue)
15231589 # try to reuse an existing CodeInstance from before to avoid making duplicates in the cache
15241590 if iszero (ccall (:jl_mi_cache_has_ci , Cint, (Any, Any), mi, callee))
1525- cached = ccall (:jl_get_ci_equiv , Any, (Any, UInt), callee, this_world ):: CodeInstance
1591+ cached = ccall (:jl_get_ci_equiv , Any, (Any, UInt), callee, world ):: CodeInstance
15261592 if cached === callee
15271593 code_cache (interp)[mi] = callee
15281594 else
@@ -1533,9 +1599,44 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
15331599 push! (codeinfos, callee)
15341600 push! (codeinfos, src)
15351601 end
1602+ else @assert false " unexpected item in queue" end
1603+ end
1604+ return codeinfos
1605+ end
1606+
1607+ # This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches
1608+ # The trim_mode can be any of:
1609+ const TRIM_NO = 0
1610+ const TRIM_SAFE = 1
1611+ const TRIM_UNSAFE = 2
1612+ const TRIM_UNSAFE_WARN = 3
1613+ function typeinf_ext_toplevel (methods:: Vector{Any} , worlds:: Vector{UInt} , trim_mode:: Int )
1614+ inf_params = InferenceParams (; force_enable_inference = trim_mode != TRIM_NO)
1615+ invokelatest_queue = CompilationQueue (;
1616+ interp = NativeInterpreter (get_world_counter (); inf_params)
1617+ )
1618+ codeinfos = []
1619+ is_latest_world = true # whether this_world == world_counter()
1620+ workqueue = CompilationQueue (; interp = nothing )
1621+ for this_world in reverse! (sort! (worlds))
1622+ workqueue = CompilationQueue (workqueue;
1623+ interp = NativeInterpreter (this_world; inf_params)
1624+ )
1625+
1626+ append! (workqueue, methods)
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)
15361636 end
1537- latest = false
1637+ is_latest_world = false
15381638 end
1639+
15391640 if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE
15401641 verify_typeinf_trim (codeinfos, trim_mode == TRIM_UNSAFE_WARN)
15411642 end
0 commit comments