@@ -1378,6 +1378,12 @@ end
1378
1378
# Method and return a compilable MethodInstance for the call, if
1379
1379
# it will be runtime-dispatched to exactly that MethodInstance
1380
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
+
1381
1387
matches = findall (argtype, method_table (interp); limit = 1 )
1382
1388
matches === nothing && return nothing
1383
1389
length (matches. matches) == 0 && return nothing
@@ -1392,41 +1398,64 @@ function compileable_specialization_for_call(interp::AbstractInterpreter, @nospe
1392
1398
else
1393
1399
mi = specialize_method (match. method, compileable_atype, match. sparams)
1394
1400
end
1401
+
1395
1402
return mi
1396
1403
end
1397
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
+
1398
1429
# 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 )
1400
1432
src = ci. code
1401
1433
for i = 1 : length (src)
1402
1434
stmt = src[i]
1403
1435
isexpr (stmt, :(= )) && (stmt = stmt. args[2 ])
1404
1436
if isexpr (stmt, :invoke ) || isexpr (stmt, :invoke_modify )
1405
1437
edge = stmt. args[1 ]
1406
- edge isa CodeInstance && isdefined (edge, :inferred ) && push! (wq , edge)
1438
+ edge isa CodeInstance && isdefined (edge, :inferred ) && push! (workqueue , edge)
1407
1439
end
1408
1440
1409
1441
if isexpr (stmt, :call )
1410
1442
farg = stmt. args[1 ]
1411
1443
! applicable (argextype, farg, ci, sptypes) && continue # TODO : Why is this failing during bootstrap
1412
1444
ftyp = widenconst (argextype (farg, ci, sptypes))
1413
1445
if ftyp <: Builtin
1414
- # TODO : Make interp elsewhere
1415
- interp = NativeInterpreter (Base. get_world_counter ())
1416
1446
if Core. finalizer isa ftyp && length (stmt. args) == 3
1417
1447
finalizer = argextype (stmt. args[2 ], ci, sptypes)
1418
1448
obj = argextype (stmt. args[3 ], ci, sptypes)
1419
1449
atype = argtypes_to_type (Any[finalizer, obj])
1420
1450
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
1423
1456
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)
1428
1458
end
1429
- # push!(wq, mi)
1430
1459
end
1431
1460
end
1432
1461
end
@@ -1439,41 +1468,40 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn
1439
1468
ci isa CodeInstance && ! ci_has_invoke (ci) || return ci
1440
1469
codegen = codegen_cache (interp)
1441
1470
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)
1446
1474
# 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 )
1448
1476
ci_has_invoke (callee) && continue
1449
- callee in inspected && continue
1477
+ isinspected (workqueue, callee) && continue
1450
1478
src = get (codegen, callee, nothing )
1451
1479
if ! isa (src, CodeInfo)
1452
1480
src = @atomic :monotonic callee. inferred
1453
1481
if isa (src, String)
1454
1482
src = _uncompressed_ir (callee, src)
1455
1483
end
1456
1484
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
1458
1486
if newcallee isa CodeInstance
1459
1487
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)
1461
1489
end
1462
1490
if newcallee != = callee
1463
- push! (inspected , callee)
1491
+ markinspected! (workqueue , callee)
1464
1492
end
1465
1493
continue
1466
1494
end
1467
1495
end
1468
- push! (inspected , callee)
1496
+ markinspected! (workqueue , callee)
1469
1497
mi = get_ci_mi (callee)
1470
1498
sptypes = sptypes_from_meth_instance (mi)
1471
- collectinvokes! (tocompile , src, sptypes)
1499
+ collectinvokes! (workqueue , src, sptypes)
1472
1500
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
1474
1502
if cached === callee
1475
1503
# 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
1477
1505
else
1478
1506
# use an existing CI from the cache, if there is available one that is compatible
1479
1507
callee === ci && (ci = cached)
@@ -1497,57 +1525,45 @@ function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt
1497
1525
return typeinf_ext_toplevel (interp, mi, source_mode)
1498
1526
end
1499
1527
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)
1543
1545
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
1549
1565
mi = get_ci_mi (callee)
1550
- def = mi . def
1566
+ # now make sure everything has source code, if desired
1551
1567
if use_const_api (callee)
1552
1568
src = codeinfo_for_const (interp, mi, callee. rettype_const)
1553
1569
else
@@ -1556,21 +1572,21 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
1556
1572
newcallee = typeinf_ext (interp, mi, SOURCE_MODE_GET_SOURCE)
1557
1573
if newcallee isa CodeInstance
1558
1574
@assert use_const_api (newcallee) || haskey (interp. codegen, newcallee)
1559
- push! (tocompile , newcallee)
1575
+ push! (workqueue , newcallee)
1560
1576
end
1561
1577
if newcallee != = callee
1562
- push! (inspected , callee)
1578
+ markinspected! (workqueue , callee)
1563
1579
end
1564
1580
continue
1565
1581
end
1566
1582
end
1567
- push! (inspected , callee)
1583
+ markinspected! (workqueue , callee)
1568
1584
if src isa CodeInfo
1569
1585
sptypes = sptypes_from_meth_instance (mi)
1570
- collectinvokes! (tocompile , src, sptypes)
1586
+ collectinvokes! (workqueue , src, sptypes; invokelatest_queue )
1571
1587
# try to reuse an existing CodeInstance from before to avoid making duplicates in the cache
1572
1588
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
1574
1590
if cached === callee
1575
1591
code_cache (interp)[mi] = callee
1576
1592
else
@@ -1581,9 +1597,45 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
1581
1597
push! (codeinfos, callee)
1582
1598
push! (codeinfos, src)
1583
1599
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)
1584
1636
end
1585
- latest = false
1586
1637
end
1638
+
1587
1639
if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE
1588
1640
verify_typeinf_trim (codeinfos, trim_mode == TRIM_UNSAFE_WARN)
1589
1641
end
0 commit comments