Skip to content

Commit f2cc6b0

Browse files
topolarityvtjnash
andauthored
gf.c: Add some comments to ml_matches (NFC) (#58304)
Adding notes as I go through this code in detail. Co-authored-by: Jameson Nash <[email protected]>
1 parent 94d0b0c commit f2cc6b0

File tree

3 files changed

+82
-13
lines changed

3 files changed

+82
-13
lines changed

src/gf.c

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,10 +1566,14 @@ static int concretesig_equal(jl_value_t *tt, jl_value_t *simplesig) JL_NOTSAFEPO
15661566
return 1;
15671567
}
15681568

1569+
// if available, returns a TypeMapEntry in the "leafcache" that matches `tt` (by type-equality) and is valid during `world`
15691570
static inline jl_typemap_entry_t *lookup_leafcache(jl_genericmemory_t *leafcache JL_PROPAGATES_ROOT, jl_value_t *tt, size_t world) JL_NOTSAFEPOINT
15701571
{
15711572
jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_eqtable_get(leafcache, (jl_value_t*)tt, NULL);
15721573
if (entry) {
1574+
// search tail of the linked-list (including the returned entry) for an entry intersecting world
1575+
//
1576+
// n.b. this entire chain is type-equal to tt (by construction), so it is unnecessary to call `tt<:entry->sig`
15731577
do {
15741578
if (jl_atomic_load_relaxed(&entry->min_world) <= world && world <= jl_atomic_load_relaxed(&entry->max_world)) {
15751579
if (entry->simplesig == (void*)jl_nothing || concretesig_equal(tt, (jl_value_t*)entry->simplesig))
@@ -4492,21 +4496,31 @@ static jl_method_match_t *make_method_match(jl_tupletype_t *spec_types, jl_svec_
44924496
return match;
44934497
}
44944498

4499+
// callback for typemap_visitor
4500+
//
4501+
// This will exit the search early (by returning 0 / false) if the match limit is proven to be
4502+
// exceeded early. This is only best-effort, since specificity means that many matched methods
4503+
// may be sorted and removed in the output processing for ml_matches and therefore we can only
4504+
// conservatively under-approximate the matches during the search.
44954505
static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure0)
44964506
{
44974507
struct ml_matches_env *closure = container_of(closure0, struct ml_matches_env, match);
44984508
if (closure->intersections == 0 && !closure0->issubty)
44994509
return 1;
4510+
4511+
// First, check the world range of the typemap entry to ensure that it intersects
4512+
// the query world. If it does not, narrow the result world range to guarantee
4513+
// excluding it from the results is valid for the full span.
45004514
size_t min_world = jl_atomic_load_relaxed(&ml->min_world);
45014515
size_t max_world = jl_atomic_load_relaxed(&ml->max_world);
45024516
if (closure->world < min_world) {
4503-
// ignore method table entries that are part of a later world
4517+
// exclude method table entries that are part of a later world
45044518
if (closure->match.max_valid >= min_world)
45054519
closure->match.max_valid = min_world - 1;
45064520
return 1;
45074521
}
45084522
else if (closure->world > max_world) {
4509-
// ignore method table entries that have been replaced in the current world
4523+
// exclude method table entries that have been replaced in the current world
45104524
if (closure->match.min_valid <= max_world)
45114525
closure->match.min_valid = max_world + 1;
45124526
return 1;
@@ -4844,21 +4858,47 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
48444858
else
48454859
va = NULL;
48464860
}
4847-
struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0,
4848-
/* .min_valid = */ *min_valid, /* .max_valid = */ *max_valid,
4849-
/* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0},
4850-
intersections, world, lim, include_ambiguous, /* .t = */ jl_an_empty_vec_any,
4851-
/* .matc = */ NULL};
4861+
struct ml_matches_env env = {
4862+
/* match */ {
4863+
/* inputs */
4864+
/* fptr / callback */ ml_matches_visitor,
4865+
/* sig */ (jl_value_t*)type,
4866+
/* vararg type / tparam0 */ va,
4867+
4868+
/* temporaries */
4869+
/* .search_slurp = */ 0,
4870+
4871+
/* outputs */
4872+
/* .min_valid = */ *min_valid,
4873+
/* .max_valid = */ *max_valid,
4874+
/* .ti = */ NULL,
4875+
/* .env = */ jl_emptysvec,
4876+
/* .issubty = */ 0
4877+
},
4878+
/* inputs */
4879+
intersections,
4880+
world,
4881+
lim,
4882+
include_ambiguous,
4883+
4884+
/* outputs */
4885+
/* .t = */ jl_an_empty_vec_any,
4886+
4887+
/* temporaries */
4888+
/* .matc = */ NULL
4889+
};
48524890
struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec};
48534891
jl_value_t *isect2 = NULL;
48544892
JL_GC_PUSH6(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti, &isect2);
48554893

48564894
if (mc) {
4857-
// check the leaf cache if this type can be in there
4895+
// first check the leaf cache if the type might have been put in there
48584896
if (((jl_datatype_t*)unw)->isdispatchtuple) {
48594897
jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mc->leafcache);
48604898
jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world);
48614899
if (entry) {
4900+
// leafcache found a match, construct the MethodMatch by computing the effective
4901+
// types + sparams and the world bounds
48624902
jl_method_instance_t *mi = entry->func.linfo;
48634903
jl_method_t *meth = mi->def.method;
48644904
if (!jl_is_unionall(meth->sig)) {
@@ -4887,10 +4927,13 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
48874927
return env.t;
48884928
}
48894929
}
4930+
48904931
// then check the full cache if it seems profitable
48914932
if (((jl_datatype_t*)unw)->isdispatchtuple) {
48924933
jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mc->cache), &search, jl_cachearg_offset(), /*subtype*/1);
48934934
if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) {
4935+
// full cache found a match, construct the MethodMatch by computing the effective
4936+
// types + sparams and the world bounds
48944937
jl_method_instance_t *mi = entry->func.linfo;
48954938
jl_method_t *meth = mi->def.method;
48964939
size_t min_world = jl_atomic_load_relaxed(&entry->min_world);
@@ -4922,7 +4965,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
49224965
// then scan everything
49234966
if (!jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), 0, &env.match) && env.t == jl_an_empty_vec_any) {
49244967
JL_GC_POP();
4925-
// if we return early without returning methods, set only the min/max valid collected from matching
4968+
// if we return early without returning methods, lim was proven to be exceeded
4969+
// during the search set only the min/max valid collected from matching
49264970
*min_valid = env.match.min_valid;
49274971
*max_valid = env.match.max_valid;
49284972
return jl_nothing;
@@ -4932,12 +4976,19 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
49324976
*max_valid = env.match.max_valid;
49334977
// done with many of these values now
49344978
env.match.ti = NULL; env.matc = NULL; env.match.env = NULL; search.env = NULL;
4979+
4980+
// all intersecting methods have been collected now. the remaining work is to sort
4981+
// these and apply specificity to determine a list of dispatch-possible call targets
49354982
size_t i, j, len = jl_array_nrows(env.t);
4983+
4984+
// the 'minmax' method is a method that (1) fully-covers the queried type, and (2) is
4985+
// more-specific than any other fully-covering method (but if !all_subtypes, there are
4986+
// non-fully-covering methods to which it is _likely_ not more specific)
49364987
jl_method_match_t *minmax = NULL;
49374988
int any_subtypes = 0;
49384989
if (len > 1) {
4939-
// first try to pre-process the results to find the most specific
4940-
// result that fully covers the input, since we can do this in O(n^2)
4990+
// first try to pre-process the results to find the most specific option
4991+
// among the fully-covering methods, since we can do this in O(n^2)
49414992
// time, and the rest is O(n^3)
49424993
// - first find a candidate for the best of these method results
49434994
for (i = 0; i < len; i++) {
@@ -4962,8 +5013,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
49625013
}
49635014
}
49645015
}
4965-
// - it may even dominate some choices that are not subtypes!
4966-
// move those into the subtype group, where we're filter them out shortly after
5016+
// - it may even dominate (be more specific than) some choices that are not fully-covering!
5017+
// move those into the subtype group, where we'll filter them out shortly after
49675018
// (potentially avoiding reporting these as an ambiguity, and
49685019
// potentially allowing us to hit the next fast path)
49695020
// - we could always check here if *any* FULLY_COVERS method is
@@ -4976,6 +5027,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
49765027
jl_method_t *minmaxm = NULL;
49775028
if (minmax != NULL)
49785029
minmaxm = minmax->method;
5030+
// scan through all the non-fully-matching methods and count them as "fully-covering" (ish)
5031+
// (i.e. in the 'subtype' group) if `minmax` is more-specific
49795032
for (i = 0; i < len; i++) {
49805033
jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i);
49815034
if (matc->fully_covers != FULLY_COVERS) {
@@ -4996,16 +5049,21 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
49965049
// we've already processed all of the possible outputs
49975050
if (all_subtypes) {
49985051
if (minmax == NULL) {
5052+
// all intersecting methods are fully-covering, but there is no unique most-specific method
49995053
if (!include_ambiguous) {
5054+
// there no unambiguous choice of method
50005055
len = 0;
50015056
env.t = jl_an_empty_vec_any;
50025057
}
50035058
else if (lim == 1) {
5059+
// we'd have to return >1 method due to the ambiguity, so bail early
50045060
JL_GC_POP();
50055061
return jl_nothing;
50065062
}
50075063
}
50085064
else {
5065+
// `minmax` is more-specific than all other matches and is fully-covering
5066+
// we can return it as our only result
50095067
jl_array_ptr_set(env.t, 0, minmax);
50105068
jl_array_del_end((jl_array_t*)env.t, len - 1);
50115069
len = 1;

src/julia.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,14 +910,20 @@ typedef struct _jl_typemap_level_t {
910910

911911
typedef struct _jl_methcache_t {
912912
JL_DATA_TYPE
913+
// hash map from dispatchtuple type to a linked-list of TypeMapEntry
914+
// entry.sig == type for all entries in the linked-list
913915
_Atomic(jl_genericmemory_t*) leafcache;
916+
917+
// cache for querying everything else (anything that didn't seem profitable to put into leafcache)
914918
_Atomic(jl_typemap_t*) cache;
919+
915920
jl_mutex_t writelock;
916921
} jl_methcache_t;
917922

918923
// contains global MethodTable
919924
typedef struct _jl_methtable_t {
920925
JL_DATA_TYPE
926+
// full set of entries
921927
_Atomic(jl_typemap_t*) defs;
922928
jl_methcache_t *cache;
923929
jl_sym_t *name; // sometimes used for debug printing

src/typemap.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,6 +1332,9 @@ static void jl_typemap_list_insert_(
13321332
jl_typemap_entry_t *newrec)
13331333
{
13341334
jl_typemap_entry_t *l = jl_atomic_load_relaxed(pml);
1335+
1336+
// Pick the first intersection point that guarantees that the list ordering
1337+
// will be (leaf sigs..., simple sigs..., other sigs...)
13351338
while ((jl_value_t*)l != jl_nothing) {
13361339
if (newrec->isleafsig || !l->isleafsig)
13371340
if (newrec->issimplesig || !l->issimplesig)
@@ -1340,6 +1343,7 @@ static void jl_typemap_list_insert_(
13401343
parent = (jl_value_t*)l;
13411344
l = jl_atomic_load_relaxed(&l->next);
13421345
}
1346+
13431347
jl_atomic_store_relaxed(&newrec->next, l);
13441348
jl_gc_wb(newrec, l);
13451349
jl_atomic_store_release(pml, newrec);
@@ -1357,6 +1361,7 @@ static void jl_typemap_insert_generic(
13571361
jl_typemap_memory_insert_(map, (_Atomic(jl_genericmemory_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL);
13581362
return;
13591363
}
1364+
13601365
if (jl_typeof(ml) == (jl_value_t*)jl_typemap_level_type) {
13611366
assert(!doublesplit);
13621367
jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs);

0 commit comments

Comments
 (0)