@@ -1374,15 +1374,92 @@ function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance)
1374
1374
return ci. rettype
1375
1375
end
1376
1376
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
+
1377
1429
# 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 )
1379
1432
src = ci. code
1380
1433
for i = 1 : length (src)
1381
1434
stmt = src[i]
1382
1435
isexpr (stmt, :(= )) && (stmt = stmt. args[2 ])
1383
1436
if isexpr (stmt, :invoke ) || isexpr (stmt, :invoke_modify )
1384
1437
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
1386
1463
end
1387
1464
# TODO : handle other StmtInfo like @cfunction and OpaqueClosure?
1388
1465
end
@@ -1393,40 +1470,40 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn
1393
1470
ci isa CodeInstance && ! ci_has_invoke (ci) || return ci
1394
1471
codegen = codegen_cache (interp)
1395
1472
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)
1400
1476
# 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 )
1402
1478
ci_has_invoke (callee) && continue
1403
- callee in inspected && continue
1479
+ isinspected (workqueue, callee) && continue
1404
1480
src = get (codegen, callee, nothing )
1405
1481
if ! isa (src, CodeInfo)
1406
1482
src = @atomic :monotonic callee. inferred
1407
1483
if isa (src, String)
1408
1484
src = _uncompressed_ir (callee, src)
1409
1485
end
1410
1486
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
1412
1488
if newcallee isa CodeInstance
1413
1489
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)
1415
1491
end
1416
1492
if newcallee != = callee
1417
- push! (inspected , callee)
1493
+ markinspected! (workqueue , callee)
1418
1494
end
1419
1495
continue
1420
1496
end
1421
1497
end
1422
- push! (inspected, callee)
1423
- collectinvokes! (tocompile, src)
1498
+ markinspected! (workqueue, callee)
1424
1499
mi = get_ci_mi (callee)
1500
+ sptypes = sptypes_from_meth_instance (mi)
1501
+ collectinvokes! (workqueue, src, sptypes)
1425
1502
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
1427
1504
if cached === callee
1428
1505
# 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
1430
1507
else
1431
1508
# use an existing CI from the cache, if there is available one that is compatible
1432
1509
callee === ci && (ci = cached)
@@ -1450,57 +1527,45 @@ function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt
1450
1527
return typeinf_ext_toplevel (interp, mi, source_mode)
1451
1528
end
1452
1529
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)
1496
1547
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
1502
1567
mi = get_ci_mi (callee)
1503
- def = mi . def
1568
+ # now make sure everything has source code, if desired
1504
1569
if use_const_api (callee)
1505
1570
src = codeinfo_for_const (interp, mi, callee. rettype_const)
1506
1571
else
@@ -1509,20 +1574,21 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
1509
1574
newcallee = typeinf_ext (interp, mi, SOURCE_MODE_GET_SOURCE)
1510
1575
if newcallee isa CodeInstance
1511
1576
@assert use_const_api (newcallee) || haskey (interp. codegen, newcallee)
1512
- push! (tocompile , newcallee)
1577
+ push! (workqueue , newcallee)
1513
1578
end
1514
1579
if newcallee != = callee
1515
- push! (inspected , callee)
1580
+ markinspected! (workqueue , callee)
1516
1581
end
1517
1582
continue
1518
1583
end
1519
1584
end
1520
- push! (inspected , callee)
1585
+ markinspected! (workqueue , callee)
1521
1586
if src isa CodeInfo
1522
- collectinvokes! (tocompile, src)
1587
+ sptypes = sptypes_from_meth_instance (mi)
1588
+ collectinvokes! (workqueue, src, sptypes; invokelatest_queue)
1523
1589
# try to reuse an existing CodeInstance from before to avoid making duplicates in the cache
1524
1590
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
1526
1592
if cached === callee
1527
1593
code_cache (interp)[mi] = callee
1528
1594
else
@@ -1533,9 +1599,44 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m
1533
1599
push! (codeinfos, callee)
1534
1600
push! (codeinfos, src)
1535
1601
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)
1536
1636
end
1537
- latest = false
1637
+ is_latest_world = false
1538
1638
end
1639
+
1539
1640
if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE
1540
1641
verify_typeinf_trim (codeinfos, trim_mode == TRIM_UNSAFE_WARN)
1541
1642
end
0 commit comments