Skip to content

Commit e6044ad

Browse files
topolarityvtjnash
authored andcommitted
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]> (cherry picked from commit f2cc6b0)
1 parent 90d0063 commit e6044ad

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
@@ -1482,10 +1482,14 @@ static int concretesig_equal(jl_value_t *tt, jl_value_t *simplesig) JL_NOTSAFEPO
14821482
return 1;
14831483
}
14841484

1485+
// if available, returns a TypeMapEntry in the "leafcache" that matches `tt` (by type-equality) and is valid during `world`
14851486
static inline jl_typemap_entry_t *lookup_leafcache(jl_genericmemory_t *leafcache JL_PROPAGATES_ROOT, jl_value_t *tt, size_t world) JL_NOTSAFEPOINT
14861487
{
14871488
jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_eqtable_get(leafcache, (jl_value_t*)tt, NULL);
14881489
if (entry) {
1490+
// search tail of the linked-list (including the returned entry) for an entry intersecting world
1491+
//
1492+
// n.b. this entire chain is type-equal to tt (by construction), so it is unnecessary to call `tt<:entry->sig`
14891493
do {
14901494
if (jl_atomic_load_relaxed(&entry->min_world) <= world && world <= jl_atomic_load_relaxed(&entry->max_world)) {
14911495
if (entry->simplesig == (void*)jl_nothing || concretesig_equal(tt, (jl_value_t*)entry->simplesig))
@@ -4389,21 +4393,31 @@ static jl_method_match_t *make_method_match(jl_tupletype_t *spec_types, jl_svec_
43894393
return match;
43904394
}
43914395

4396+
// callback for typemap_visitor
4397+
//
4398+
// This will exit the search early (by returning 0 / false) if the match limit is proven to be
4399+
// exceeded early. This is only best-effort, since specificity means that many matched methods
4400+
// may be sorted and removed in the output processing for ml_matches and therefore we can only
4401+
// conservatively under-approximate the matches during the search.
43924402
static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure0)
43934403
{
43944404
struct ml_matches_env *closure = container_of(closure0, struct ml_matches_env, match);
43954405
if (closure->intersections == 0 && !closure0->issubty)
43964406
return 1;
4407+
4408+
// First, check the world range of the typemap entry to ensure that it intersects
4409+
// the query world. If it does not, narrow the result world range to guarantee
4410+
// excluding it from the results is valid for the full span.
43974411
size_t min_world = jl_atomic_load_relaxed(&ml->min_world);
43984412
size_t max_world = jl_atomic_load_relaxed(&ml->max_world);
43994413
if (closure->world < min_world) {
4400-
// ignore method table entries that are part of a later world
4414+
// exclude method table entries that are part of a later world
44014415
if (closure->match.max_valid >= min_world)
44024416
closure->match.max_valid = min_world - 1;
44034417
return 1;
44044418
}
44054419
else if (closure->world > max_world) {
4406-
// ignore method table entries that have been replaced in the current world
4420+
// exclude method table entries that have been replaced in the current world
44074421
if (closure->match.min_valid <= max_world)
44084422
closure->match.min_valid = max_world + 1;
44094423
return 1;
@@ -4601,21 +4615,47 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
46014615
else
46024616
va = NULL;
46034617
}
4604-
struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0,
4605-
/* .min_valid = */ *min_valid, /* .max_valid = */ *max_valid,
4606-
/* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0},
4607-
intersections, world, lim, include_ambiguous, /* .t = */ jl_an_empty_vec_any,
4608-
/* .matc = */ NULL};
4618+
struct ml_matches_env env = {
4619+
/* match */ {
4620+
/* inputs */
4621+
/* fptr / callback */ ml_matches_visitor,
4622+
/* sig */ (jl_value_t*)type,
4623+
/* vararg type / tparam0 */ va,
4624+
4625+
/* temporaries */
4626+
/* .search_slurp = */ 0,
4627+
4628+
/* outputs */
4629+
/* .min_valid = */ *min_valid,
4630+
/* .max_valid = */ *max_valid,
4631+
/* .ti = */ NULL,
4632+
/* .env = */ jl_emptysvec,
4633+
/* .issubty = */ 0
4634+
},
4635+
/* inputs */
4636+
intersections,
4637+
world,
4638+
lim,
4639+
include_ambiguous,
4640+
4641+
/* outputs */
4642+
/* .t = */ jl_an_empty_vec_any,
4643+
4644+
/* temporaries */
4645+
/* .matc = */ NULL
4646+
};
46094647
struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec};
46104648
jl_value_t *isect2 = NULL;
46114649
JL_GC_PUSH6(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti, &isect2);
46124650

46134651
if (mc) {
4614-
// check the leaf cache if this type can be in there
4652+
// first check the leaf cache if the type might have been put in there
46154653
if (((jl_datatype_t*)unw)->isdispatchtuple) {
46164654
jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mc->leafcache);
46174655
jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world);
46184656
if (entry) {
4657+
// leafcache found a match, construct the MethodMatch by computing the effective
4658+
// types + sparams and the world bounds
46194659
jl_method_instance_t *mi = entry->func.linfo;
46204660
jl_method_t *meth = mi->def.method;
46214661
if (!jl_is_unionall(meth->sig)) {
@@ -4644,10 +4684,13 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
46444684
return env.t;
46454685
}
46464686
}
4687+
46474688
// then check the full cache if it seems profitable
46484689
if (((jl_datatype_t*)unw)->isdispatchtuple) {
46494690
jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mc->cache), &search, jl_cachearg_offset(), /*subtype*/1);
46504691
if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) {
4692+
// full cache found a match, construct the MethodMatch by computing the effective
4693+
// types + sparams and the world bounds
46514694
jl_method_instance_t *mi = entry->func.linfo;
46524695
jl_method_t *meth = mi->def.method;
46534696
size_t min_world = jl_atomic_load_relaxed(&entry->min_world);
@@ -4679,7 +4722,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
46794722
// then scan everything
46804723
if (!jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), 0, &env.match) && env.t == jl_an_empty_vec_any) {
46814724
JL_GC_POP();
4682-
// if we return early without returning methods, set only the min/max valid collected from matching
4725+
// if we return early without returning methods, lim was proven to be exceeded
4726+
// during the search set only the min/max valid collected from matching
46834727
*min_valid = env.match.min_valid;
46844728
*max_valid = env.match.max_valid;
46854729
return jl_nothing;
@@ -4689,12 +4733,19 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
46894733
*max_valid = env.match.max_valid;
46904734
// done with many of these values now
46914735
env.match.ti = NULL; env.matc = NULL; env.match.env = NULL; search.env = NULL;
4736+
4737+
// all intersecting methods have been collected now. the remaining work is to sort
4738+
// these and apply specificity to determine a list of dispatch-possible call targets
46924739
size_t i, j, len = jl_array_nrows(env.t);
4740+
4741+
// the 'minmax' method is a method that (1) fully-covers the queried type, and (2) is
4742+
// more-specific than any other fully-covering method (but if !all_subtypes, there are
4743+
// non-fully-covering methods to which it is _likely_ not more specific)
46934744
jl_method_match_t *minmax = NULL;
46944745
int any_subtypes = 0;
46954746
if (len > 1) {
4696-
// first try to pre-process the results to find the most specific
4697-
// result that fully covers the input, since we can do this in O(n^2)
4747+
// first try to pre-process the results to find the most specific option
4748+
// among the fully-covering methods, since we can do this in O(n^2)
46984749
// time, and the rest is O(n^3)
46994750
// - first find a candidate for the best of these method results
47004751
for (i = 0; i < len; i++) {
@@ -4719,8 +4770,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
47194770
}
47204771
}
47214772
}
4722-
// - it may even dominate some choices that are not subtypes!
4723-
// move those into the subtype group, where we're filter them out shortly after
4773+
// - it may even dominate (be more specific than) some choices that are not fully-covering!
4774+
// move those into the subtype group, where we'll filter them out shortly after
47244775
// (potentially avoiding reporting these as an ambiguity, and
47254776
// potentially allowing us to hit the next fast path)
47264777
// - we could always check here if *any* FULLY_COVERS method is
@@ -4733,6 +4784,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
47334784
jl_method_t *minmaxm = NULL;
47344785
if (minmax != NULL)
47354786
minmaxm = minmax->method;
4787+
// scan through all the non-fully-matching methods and count them as "fully-covering" (ish)
4788+
// (i.e. in the 'subtype' group) if `minmax` is more-specific
47364789
for (i = 0; i < len; i++) {
47374790
jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i);
47384791
if (matc->fully_covers != FULLY_COVERS) {
@@ -4753,16 +4806,21 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
47534806
// we've already processed all of the possible outputs
47544807
if (all_subtypes) {
47554808
if (minmax == NULL) {
4809+
// all intersecting methods are fully-covering, but there is no unique most-specific method
47564810
if (!include_ambiguous) {
4811+
// there no unambiguous choice of method
47574812
len = 0;
47584813
env.t = jl_an_empty_vec_any;
47594814
}
47604815
else if (lim == 1) {
4816+
// we'd have to return >1 method due to the ambiguity, so bail early
47614817
JL_GC_POP();
47624818
return jl_nothing;
47634819
}
47644820
}
47654821
else {
4822+
// `minmax` is more-specific than all other matches and is fully-covering
4823+
// we can return it as our only result
47664824
jl_array_ptr_set(env.t, 0, minmax);
47674825
jl_array_del_end((jl_array_t*)env.t, len - 1);
47684826
len = 1;

src/julia.h

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

884884
typedef struct _jl_methcache_t {
885885
JL_DATA_TYPE
886+
// hash map from dispatchtuple type to a linked-list of TypeMapEntry
887+
// entry.sig == type for all entries in the linked-list
886888
_Atomic(jl_genericmemory_t*) leafcache;
889+
890+
// cache for querying everything else (anything that didn't seem profitable to put into leafcache)
887891
_Atomic(jl_typemap_t*) cache;
892+
888893
jl_mutex_t writelock;
889894
} jl_methcache_t;
890895

891896
// contains global MethodTable
892897
typedef struct _jl_methtable_t {
893898
JL_DATA_TYPE
899+
// full set of entries
894900
_Atomic(jl_typemap_t*) defs;
895901
jl_methcache_t *cache;
896902
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
@@ -1331,6 +1331,9 @@ static void jl_typemap_list_insert_(
13311331
jl_typemap_entry_t *newrec)
13321332
{
13331333
jl_typemap_entry_t *l = jl_atomic_load_relaxed(pml);
1334+
1335+
// Pick the first intersection point that guarantees that the list ordering
1336+
// will be (leaf sigs..., simple sigs..., other sigs...)
13341337
while ((jl_value_t*)l != jl_nothing) {
13351338
if (newrec->isleafsig || !l->isleafsig)
13361339
if (newrec->issimplesig || !l->issimplesig)
@@ -1339,6 +1342,7 @@ static void jl_typemap_list_insert_(
13391342
parent = (jl_value_t*)l;
13401343
l = jl_atomic_load_relaxed(&l->next);
13411344
}
1345+
13421346
jl_atomic_store_relaxed(&newrec->next, l);
13431347
jl_gc_wb(newrec, l);
13441348
jl_atomic_store_release(pml, newrec);
@@ -1356,6 +1360,7 @@ static void jl_typemap_insert_generic(
13561360
jl_typemap_memory_insert_(map, (_Atomic(jl_genericmemory_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL);
13571361
return;
13581362
}
1363+
13591364
if (jl_typeof(ml) == (jl_value_t*)jl_typemap_level_type) {
13601365
assert(!doublesplit);
13611366
jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs);

0 commit comments

Comments
 (0)