Skip to content

Commit 5ca5a80

Browse files
committed
add METHOD_SIG_LATEST_ONLY optimization to MethodInstance too
1 parent 5416edb commit 5ca5a80

File tree

8 files changed

+165
-89
lines changed

8 files changed

+165
-89
lines changed

base/essentials.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ Neither `convert` nor `cconvert` should take a Julia object and turn it into a `
731731
"""
732732
function cconvert end
733733

734-
cconvert(T::Type, x) = x isa T ? x : convert(T, x) # do the conversion eagerly in most cases
734+
cconvert(::Type{T}, x) where {T} = x isa T ? x : convert(T, x) # do the conversion eagerly in most cases
735735
cconvert(::Type{Union{}}, x...) = convert(Union{}, x...)
736736
cconvert(::Type{<:Ptr}, x) = x # but defer the conversion to Ptr to unsafe_convert
737737
unsafe_convert(::Type{T}, x::T) where {T} = x # unsafe_convert (like convert) defaults to assuming the convert occurred

base/staticdata.jl

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ end
279279

280280
function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n::Int, world::UInt)
281281
# verify that these edges intersect with the same methods as before
282+
mi = nothing
282283
if n == 1
283284
# first, fast-path a check if the expected method simply dominates its sig anyways
284285
# so the result of ml_matches is already simply known
@@ -287,11 +288,18 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
287288
meth = t
288289
else
289290
if t isa CodeInstance
290-
t = get_ci_mi(t)
291+
mi = get_ci_mi(t)::MethodInstance
291292
else
292-
t = t::MethodInstance
293+
mi = t::MethodInstance
294+
end
295+
meth = mi.def::Method
296+
if !iszero(mi.dispatch_status & METHOD_SIG_LATEST_ONLY)
297+
minworld = meth.primary_world
298+
@assert minworld world
299+
maxworld = typemax(UInt)
300+
result = Any[] # result is unused
301+
return minworld, maxworld, result
293302
end
294-
meth = t.def::Method
295303
end
296304
if !iszero(meth.dispatch_status & METHOD_SIG_LATEST_ONLY)
297305
minworld = meth.primary_world
@@ -325,7 +333,7 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
325333
meth = t
326334
else
327335
if t isa CodeInstance
328-
t = get_ci_mi(t)
336+
t = get_ci_mi(t)::MethodInstance
329337
else
330338
t = t::MethodInstance
331339
end
@@ -352,6 +360,9 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
352360
resize!(result, ins)
353361
end
354362
end
363+
if maxworld[] == typemax(UInt) && mi isa MethodInstance
364+
ccall(:jl_promote_mi_to_current, Cvoid, (Any, UInt, UInt), mi, minworld[], world)
365+
end
355366
return minworld[], maxworld[], result
356367
end
357368

doc/src/devdocs/locks.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ may result in pernicious and hard-to-find deadlocks. BE VERY CAREFUL!
8585
>
8686
> * Libdl.LazyLibrary lock
8787
88+
The following is a level 7 lock, which can only be acquired when not holding any other locks:
89+
90+
> * world_counter_lock
91+
8892

8993
The following is the root lock, meaning no other lock shall be held when trying to acquire it:
9094

src/gf.c

Lines changed: 131 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ jl_method_t *jl_mk_builtin_func(jl_datatype_t *dt, jl_sym_t *sname, jl_fptr_args
308308
m->isva = 1;
309309
m->nargs = 2;
310310
jl_atomic_store_relaxed(&m->primary_world, 1);
311-
jl_atomic_store_relaxed(&m->dispatch_status, METHOD_SIG_LATEST_ONLY | METHOD_SIG_LATEST_ONLY);
311+
jl_atomic_store_relaxed(&m->dispatch_status, METHOD_SIG_LATEST_ONLY | METHOD_SIG_LATEST_WHICH);
312312
m->sig = (jl_value_t*)jl_anytuple_type;
313313
m->slot_syms = jl_an_empty_string;
314314
m->nospecialize = 0;
@@ -1505,22 +1505,26 @@ jl_method_instance_t *cache_method(
15051505
size_t world, size_t min_valid, size_t max_valid,
15061506
jl_svec_t *sparams)
15071507
{
1508-
// caller must hold the parent->writelock
1508+
// caller must hold the parent->writelock, which this releases
15091509
// short-circuit (now that we hold the lock) if this entry is already present
15101510
int8_t offs = mc ? jl_cachearg_offset() : 1;
15111511
{ // scope block
15121512
if (mc) {
15131513
jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mc->leafcache);
15141514
jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world);
1515-
if (entry)
1515+
if (entry) {
1516+
if (mc) JL_UNLOCK(&mc->writelock);
15161517
return entry->func.linfo;
1518+
}
15171519
}
15181520
struct jl_typemap_assoc search = {(jl_value_t*)tt, world, NULL};
15191521
jl_typemap_t *cacheentry = jl_atomic_load_relaxed(cache);
15201522
assert(cacheentry != NULL);
15211523
jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(cacheentry, &search, offs, /*subtype*/1);
1522-
if (entry && entry->func.value)
1524+
if (entry && entry->func.value) {
1525+
if (mc) JL_UNLOCK(&mc->writelock);
15231526
return entry->func.linfo;
1527+
}
15241528
}
15251529

15261530
jl_method_instance_t *newmeth = NULL;
@@ -1533,6 +1537,7 @@ jl_method_instance_t *cache_method(
15331537
JL_GC_PUSH1(&newentry);
15341538
jl_typemap_insert(cache, parent, newentry, offs);
15351539
JL_GC_POP();
1540+
if (mc) JL_UNLOCK(&mc->writelock);
15361541
return newmeth;
15371542
}
15381543
}
@@ -1577,12 +1582,20 @@ jl_method_instance_t *cache_method(
15771582
if (newmeth->cache_with_orig)
15781583
cache_with_orig = 1;
15791584

1585+
// Capture world counter at start to detect races
1586+
size_t current_world = mc ? jl_atomic_load_acquire(&jl_world_counter) : ~(size_t)0;
1587+
int unconstrained_max = max_valid > current_world;
1588+
if (unconstrained_max) {
1589+
assert(max_valid == ~(size_t)0);
1590+
max_valid = current_world;
1591+
}
1592+
15801593
jl_tupletype_t *cachett = tt;
1581-
jl_svec_t* guardsigs = jl_emptysvec;
1594+
jl_svec_t *guardsigs = jl_emptysvec;
15821595
if (!cache_with_orig && mt) {
15831596
// now examine what will happen if we chose to use this sig in the cache
15841597
size_t min_valid2 = 1;
1585-
size_t max_valid2 = ~(size_t)0;
1598+
size_t max_valid2 = current_world;
15861599
temp = ml_matches(mt, mc, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, 1, world, 0, &min_valid2, &max_valid2, NULL);
15871600
int guards = 0;
15881601
if (temp == jl_nothing) {
@@ -1726,12 +1739,91 @@ jl_method_instance_t *cache_method(
17261739
}
17271740
}
17281741
}
1742+
if (mc) {
1743+
JL_UNLOCK(&mc->writelock);
1744+
1745+
// Only set METHOD_SIG_LATEST_ONLY on method instance if method does NOT have the bit, no guards required, and min_valid == primary_world
1746+
int should_set_dispatch_status = !(jl_atomic_load_relaxed(&definition->dispatch_status) & METHOD_SIG_LATEST_ONLY) &&
1747+
(!cache_with_orig && jl_svec_len(guardsigs) == 0) &&
1748+
min_valid == jl_atomic_load_relaxed(&definition->primary_world) &&
1749+
!(jl_atomic_load_relaxed(&newmeth->dispatch_status) & METHOD_SIG_LATEST_ONLY);
1750+
1751+
// Combined trylock for both dispatch_status setting and max_world restoration
1752+
if ((should_set_dispatch_status || unconstrained_max) &&
1753+
jl_atomic_load_relaxed(&jl_world_counter) == current_world) {
1754+
JL_LOCK(&world_counter_lock);
1755+
if (jl_atomic_load_relaxed(&jl_world_counter) == current_world) {
1756+
if (should_set_dispatch_status) {
1757+
jl_atomic_store_relaxed(&newmeth->dispatch_status, METHOD_SIG_LATEST_ONLY);
1758+
}
1759+
if (unconstrained_max) {
1760+
jl_atomic_store_relaxed(&newentry->max_world, ~(size_t)0);
1761+
}
1762+
}
1763+
JL_UNLOCK(&world_counter_lock);
1764+
}
1765+
}
17291766

17301767
JL_GC_POP();
17311768
return newmeth;
17321769
}
17331770

1734-
static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, jl_methtable_t *mt, size_t world, size_t *min_valid, size_t *max_valid);
1771+
static void _jl_promote_ci_to_current(jl_code_instance_t *ci, size_t validated_world) JL_NOTSAFEPOINT
1772+
{
1773+
if (jl_atomic_load_relaxed(&ci->max_world) != validated_world)
1774+
return;
1775+
jl_atomic_store_relaxed(&ci->max_world, ~(size_t)0);
1776+
jl_svec_t *edges = jl_atomic_load_relaxed(&ci->edges);
1777+
for (size_t i = 0; i < jl_svec_len(edges); i++) {
1778+
jl_value_t *edge = jl_svecref(edges, i);
1779+
if (!jl_is_code_instance(edge))
1780+
continue;
1781+
_jl_promote_ci_to_current((jl_code_instance_t *)edge, validated_world);
1782+
}
1783+
}
1784+
1785+
JL_DLLEXPORT void jl_promote_cis_to_current(jl_code_instance_t **cis, size_t n, size_t validated_world)
1786+
{
1787+
size_t current_world = jl_atomic_load_relaxed(&jl_world_counter);
1788+
// No need to acquire the lock if we've been invalidated anyway
1789+
if (current_world > validated_world)
1790+
return;
1791+
JL_LOCK(&world_counter_lock);
1792+
current_world = jl_atomic_load_relaxed(&jl_world_counter);
1793+
if (current_world == validated_world) {
1794+
for (size_t i = 0; i < n; i++) {
1795+
_jl_promote_ci_to_current(cis[i], validated_world);
1796+
}
1797+
}
1798+
JL_UNLOCK(&world_counter_lock);
1799+
}
1800+
1801+
JL_DLLEXPORT void jl_promote_ci_to_current(jl_code_instance_t *ci, size_t validated_world)
1802+
{
1803+
jl_promote_cis_to_current(&ci, 1, validated_world);
1804+
}
1805+
1806+
JL_DLLEXPORT void jl_promote_mi_to_current(jl_method_instance_t *mi, size_t min_world, size_t validated_world)
1807+
{
1808+
size_t current_world = jl_atomic_load_relaxed(&jl_world_counter);
1809+
// No need to acquire the lock if we've been invalidated anyway
1810+
if (current_world > validated_world)
1811+
return;
1812+
// Only set METHOD_SIG_LATEST_ONLY on method instance if method does NOT have the bit and min_valid == primary_world
1813+
jl_method_t *definition = mi->def.method;
1814+
if ((jl_atomic_load_relaxed(&definition->dispatch_status) & METHOD_SIG_LATEST_ONLY) ||
1815+
min_world != jl_atomic_load_relaxed(&definition->primary_world) ||
1816+
(jl_atomic_load_relaxed(&mi->dispatch_status) & METHOD_SIG_LATEST_ONLY))
1817+
return;
1818+
JL_LOCK(&world_counter_lock);
1819+
current_world = jl_atomic_load_relaxed(&jl_world_counter);
1820+
if (current_world == validated_world) {
1821+
jl_atomic_store_relaxed(&mi->dispatch_status, METHOD_SIG_LATEST_ONLY);
1822+
}
1823+
JL_UNLOCK(&world_counter_lock);
1824+
}
1825+
1826+
static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, jl_methtable_t *mt, size_t world, int cache, size_t *min_valid, size_t *max_valid);
17351827

17361828
JL_DLLEXPORT jl_typemap_entry_t *jl_mt_find_cache_entry(jl_methcache_t *mc JL_PROPAGATES_ROOT, jl_datatype_t *tt JL_MAYBE_UNROOTED JL_ROOTS_TEMPORARILY, size_t world)
17371829
{ // exported only for debugging purposes, not for casual use
@@ -1765,11 +1857,13 @@ static jl_method_instance_t *jl_mt_assoc_by_type(jl_methcache_t *mc JL_PROPAGATE
17651857
if (!mi) {
17661858
size_t min_valid = 0;
17671859
size_t max_valid = ~(size_t)0;
1768-
matc = _gf_invoke_lookup((jl_value_t*)tt, jl_method_table, world, &min_valid, &max_valid);
1860+
matc = _gf_invoke_lookup((jl_value_t*)tt, jl_method_table, world, 0, &min_valid, &max_valid);
17691861
if (matc) {
17701862
jl_method_t *m = matc->method;
17711863
jl_svec_t *env = matc->sparams;
17721864
mi = cache_method(jl_method_table, mc, &mc->cache, (jl_value_t*)mc, tt, m, world, min_valid, max_valid, env);
1865+
JL_GC_POP();
1866+
return mi;
17731867
}
17741868
}
17751869
JL_UNLOCK(&mc->writelock);
@@ -2126,6 +2220,7 @@ static int _invalidate_dispatch_backedges(jl_method_instance_t *mi, jl_value_t *
21262220
// invalidate cached methods that overlap this definition
21272221
static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_world, const char *why)
21282222
{
2223+
// Reset dispatch_status when method instance is replaced
21292224
JL_LOCK(&replaced_mi->def.method->writelock);
21302225
_invalidate_backedges(replaced_mi, NULL, max_world, 1);
21312226
JL_UNLOCK(&replaced_mi->def.method->writelock);
@@ -2136,6 +2231,7 @@ static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_w
21362231
jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag);
21372232
JL_GC_POP();
21382233
}
2234+
jl_atomic_store_relaxed(&replaced_mi->dispatch_status, 0);
21392235
}
21402236

21412237
// add a backedge from callee to caller
@@ -2633,6 +2729,8 @@ void jl_method_table_activate(jl_typemap_entry_t *newentry)
26332729
// call invalidate_backedges(mi, max_world, "jl_method_table_insert");
26342730
// but ignore invoke-type edges
26352731
int invalidatedmi = _invalidate_dispatch_backedges(mi, type, m, d, n, replaced_dispatch, ambig, max_world, morespec);
2732+
if (replaced_dispatch)
2733+
jl_atomic_store_relaxed(&mi->dispatch_status, 0);
26362734
jl_array_ptr_1d_push(oldmi, (jl_value_t*)mi);
26372735
if (_jl_debug_method_invalidation && invalidatedmi) {
26382736
jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)mi);
@@ -3404,7 +3502,6 @@ JL_DLLEXPORT jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *matc
34043502
assert(mc);
34053503
JL_LOCK(&mc->writelock);
34063504
mi = cache_method(jl_method_get_table(m), mc, &mc->cache, (jl_value_t*)mc, ti, m, world, min_valid, max_valid, env);
3407-
JL_UNLOCK(&mc->writelock);
34083505
}
34093506
else {
34103507
jl_value_t *tt = jl_normalize_to_compilable_sig(ti, env, m, 1);
@@ -3892,15 +3989,15 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t *F, jl_value_t **args, uint
38923989
return _jl_invoke(F, args, nargs, mfunc, world);
38933990
}
38943991

3895-
static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, jl_methtable_t *mt, size_t world, size_t *min_valid, size_t *max_valid)
3992+
static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, jl_methtable_t *mt, size_t world, int cache_result, size_t *min_valid, size_t *max_valid)
38963993
{
38973994
jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)types);
38983995
if (!jl_is_tuple_type(unw))
38993996
return NULL;
39003997
if (jl_tparam0(unw) == jl_bottom_type)
39013998
return NULL;
39023999
jl_methcache_t *mc = ((jl_methtable_t*)mt)->cache;
3903-
jl_value_t *matches = ml_matches((jl_methtable_t*)mt, mc, (jl_tupletype_t*)types, 1, 0, 0, world, 1, min_valid, max_valid, NULL);
4000+
jl_value_t *matches = ml_matches((jl_methtable_t*)mt, mc, (jl_tupletype_t*)types, 1, 0, 0, world, cache_result, min_valid, max_valid, NULL);
39044001
if (matches == jl_nothing || jl_array_nrows(matches) != 1)
39054002
return NULL;
39064003
jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(matches, 0);
@@ -3914,7 +4011,7 @@ JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_value_t *types, jl_value_t *mt,
39144011
size_t max_valid = ~(size_t)0;
39154012
if (mt == jl_nothing)
39164013
mt = (jl_value_t*)jl_method_table;
3917-
jl_method_match_t *matc = _gf_invoke_lookup(types, (jl_methtable_t*)mt, world, &min_valid, &max_valid);
4014+
jl_method_match_t *matc = _gf_invoke_lookup(types, (jl_methtable_t*)mt, world, 1, &min_valid, &max_valid);
39184015
if (matc == NULL)
39194016
return jl_nothing;
39204017
return (jl_value_t*)matc->method;
@@ -3925,7 +4022,7 @@ JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup_worlds(jl_value_t *types, jl_value_
39254022
{
39264023
if (mt == jl_nothing)
39274024
mt = (jl_value_t*)jl_method_table;
3928-
jl_method_match_t *matc = _gf_invoke_lookup(types, (jl_methtable_t*)mt, world, min_world, max_world);
4025+
jl_method_match_t *matc = _gf_invoke_lookup(types, (jl_methtable_t*)mt, world, 1, min_world, max_world);
39294026
if (matc == NULL)
39304027
return jl_nothing;
39314028
return (jl_value_t*)matc;
@@ -3987,7 +4084,6 @@ jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value
39874084
int sub = jl_subtype_matching((jl_value_t*)tt, (jl_value_t*)method->sig, &tpenv);
39884085
assert(sub); (void)sub;
39894086
}
3990-
39914087
mfunc = cache_method(NULL, NULL, &method->invokes, (jl_value_t*)method, tt, method, 1, 1, ~(size_t)0, tpenv);
39924088
}
39934089
JL_UNLOCK(&method->writelock);
@@ -4486,26 +4582,28 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
44864582
if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) {
44874583
jl_method_instance_t *mi = entry->func.linfo;
44884584
jl_method_t *meth = mi->def.method;
4489-
if (!jl_is_unionall(meth->sig) && ((jl_datatype_t*)unw)->isdispatchtuple) {
4490-
env.match.env = jl_emptysvec;
4491-
env.match.ti = unw;
4492-
}
4493-
else {
4494-
// this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity)
4495-
env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env);
4496-
}
4497-
env.matc = make_method_match((jl_tupletype_t*)env.match.ti,
4498-
env.match.env, meth, FULLY_COVERS);
4499-
env.t = (jl_value_t*)jl_alloc_vec_any(1);
4500-
jl_array_ptr_set(env.t, 0, env.matc);
45014585
size_t min_world = jl_atomic_load_relaxed(&entry->min_world);
4502-
size_t max_world = jl_atomic_load_relaxed(&entry->max_world);
4503-
if (*min_valid < min_world)
4504-
*min_valid = min_world;
4505-
if (*max_valid > max_world)
4506-
*max_valid = max_world;
4507-
JL_GC_POP();
4508-
return env.t;
4586+
if (min_world == meth->primary_world) {
4587+
size_t max_world = jl_atomic_load_relaxed(&entry->max_world);
4588+
if (!jl_is_unionall(meth->sig) && ((jl_datatype_t*)unw)->isdispatchtuple) {
4589+
env.match.env = jl_emptysvec;
4590+
env.match.ti = unw;
4591+
}
4592+
else {
4593+
// this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity)
4594+
env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env);
4595+
}
4596+
env.matc = make_method_match((jl_tupletype_t*)env.match.ti,
4597+
env.match.env, meth, FULLY_COVERS);
4598+
env.t = (jl_value_t*)jl_alloc_vec_any(1);
4599+
jl_array_ptr_set(env.t, 0, env.matc);
4600+
if (*min_valid < min_world)
4601+
*min_valid = min_world;
4602+
if (*max_valid > max_world)
4603+
*max_valid = max_world;
4604+
JL_GC_POP();
4605+
return env.t;
4606+
}
45094607
}
45104608
}
45114609
}
@@ -4770,7 +4868,6 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
47704868
jl_svec_t *tpenv = env.matc->sparams;
47714869
JL_LOCK(&mc->writelock);
47724870
cache_method(mt, mc, &mc->cache, (jl_value_t*)mc, (jl_tupletype_t*)unw, meth, world, env.match.min_valid, env.match.max_valid, tpenv);
4773-
JL_UNLOCK(&mc->writelock);
47744871
}
47754872
}
47764873
*min_valid = env.match.min_valid;

0 commit comments

Comments
 (0)