Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 71 additions & 13 deletions src/gf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1566,10 +1566,14 @@ static int concretesig_equal(jl_value_t *tt, jl_value_t *simplesig) JL_NOTSAFEPO
return 1;
}

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

// callback for typemap_visitor
//
// This will exit the search early (by returning 0 / false) if the match limit is proven to be
// exceeded early. This is only best-effort, since specificity means that many matched methods
// may be sorted and removed in the output processing for ml_matches and therefore we can only
// conservatively under-approximate the matches during the search.
static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersection_env *closure0)
{
struct ml_matches_env *closure = container_of(closure0, struct ml_matches_env, match);
if (closure->intersections == 0 && !closure0->issubty)
return 1;

// First, check the world range of the typemap entry to ensure that it intersects
// the query world. If it does not, narrow the result world range to guarantee
// excluding it from the results is valid for the full span.
size_t min_world = jl_atomic_load_relaxed(&ml->min_world);
size_t max_world = jl_atomic_load_relaxed(&ml->max_world);
if (closure->world < min_world) {
// ignore method table entries that are part of a later world
// exclude method table entries that are part of a later world
if (closure->match.max_valid >= min_world)
closure->match.max_valid = min_world - 1;
return 1;
}
else if (closure->world > max_world) {
// ignore method table entries that have been replaced in the current world
// exclude method table entries that have been replaced in the current world
if (closure->match.min_valid <= max_world)
closure->match.min_valid = max_world + 1;
return 1;
Expand Down Expand Up @@ -4844,21 +4858,47 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
else
va = NULL;
}
struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va, /* .search_slurp = */ 0,
/* .min_valid = */ *min_valid, /* .max_valid = */ *max_valid,
/* .ti = */ NULL, /* .env = */ jl_emptysvec, /* .issubty = */ 0},
intersections, world, lim, include_ambiguous, /* .t = */ jl_an_empty_vec_any,
/* .matc = */ NULL};
struct ml_matches_env env = {
/* match */ {
/* inputs */
/* fptr / callback */ ml_matches_visitor,
/* sig */ (jl_value_t*)type,
/* vararg type / tparam0 */ va,

/* temporaries */
/* .search_slurp = */ 0,

/* outputs */
/* .min_valid = */ *min_valid,
/* .max_valid = */ *max_valid,
/* .ti = */ NULL,
/* .env = */ jl_emptysvec,
/* .issubty = */ 0
},
/* inputs */
intersections,
world,
lim,
include_ambiguous,

/* outputs */
/* .t = */ jl_an_empty_vec_any,

/* temporaries */
/* .matc = */ NULL
};
struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec};
jl_value_t *isect2 = NULL;
JL_GC_PUSH6(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti, &isect2);

if (mc) {
// check the leaf cache if this type can be in there
// first check the leaf cache if the type might have been put in there
if (((jl_datatype_t*)unw)->isdispatchtuple) {
jl_genericmemory_t *leafcache = jl_atomic_load_relaxed(&mc->leafcache);
jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world);
if (entry) {
// leafcache found a match, construct the MethodMatch by computing the effective
// types + sparams and the world bounds
jl_method_instance_t *mi = entry->func.linfo;
jl_method_t *meth = mi->def.method;
if (!jl_is_unionall(meth->sig)) {
Expand Down Expand Up @@ -4887,10 +4927,13 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
return env.t;
}
}

// then check the full cache if it seems profitable
if (((jl_datatype_t*)unw)->isdispatchtuple) {
jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mc->cache), &search, jl_cachearg_offset(), /*subtype*/1);
if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) {
// full cache found a match, construct the MethodMatch by computing the effective
// types + sparams and the world bounds
jl_method_instance_t *mi = entry->func.linfo;
jl_method_t *meth = mi->def.method;
size_t min_world = jl_atomic_load_relaxed(&entry->min_world);
Expand Down Expand Up @@ -4922,7 +4965,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
// then scan everything
if (!jl_typemap_intersection_visitor(jl_atomic_load_relaxed(&mt->defs), 0, &env.match) && env.t == jl_an_empty_vec_any) {
JL_GC_POP();
// if we return early without returning methods, set only the min/max valid collected from matching
// if we return early without returning methods, lim was proven to be exceeded
// during the search set only the min/max valid collected from matching
*min_valid = env.match.min_valid;
*max_valid = env.match.max_valid;
return jl_nothing;
Expand All @@ -4932,12 +4976,19 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
*max_valid = env.match.max_valid;
// done with many of these values now
env.match.ti = NULL; env.matc = NULL; env.match.env = NULL; search.env = NULL;

// all intersecting methods have been collected now. the remaining work is to sort
// these and apply specificity to determine a list of dispatch-possible call targets
size_t i, j, len = jl_array_nrows(env.t);

// the 'minmax' method is a method that (1) fully-covers the queried type, and (2) is
// more-specific than any other fully-covering method (but if !all_subtypes, there are
// non-fully-covering methods to which it is _likely_ not more specific)
jl_method_match_t *minmax = NULL;
int any_subtypes = 0;
if (len > 1) {
// first try to pre-process the results to find the most specific
// result that fully covers the input, since we can do this in O(n^2)
// first try to pre-process the results to find the most specific option
// among the fully-covering methods, since we can do this in O(n^2)
// time, and the rest is O(n^3)
// - first find a candidate for the best of these method results
for (i = 0; i < len; i++) {
Expand All @@ -4962,8 +5013,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
}
}
}
// - it may even dominate some choices that are not subtypes!
// move those into the subtype group, where we're filter them out shortly after
// - it may even dominate (be more specific than) some choices that are not fully-covering!
// move those into the subtype group, where we'll filter them out shortly after
// (potentially avoiding reporting these as an ambiguity, and
// potentially allowing us to hit the next fast path)
// - we could always check here if *any* FULLY_COVERS method is
Expand All @@ -4976,6 +5027,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
jl_method_t *minmaxm = NULL;
if (minmax != NULL)
minmaxm = minmax->method;
// scan through all the non-fully-matching methods and count them as "fully-covering" (ish)
// (i.e. in the 'subtype' group) if `minmax` is more-specific
for (i = 0; i < len; i++) {
jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, i);
if (matc->fully_covers != FULLY_COVERS) {
Expand All @@ -4996,16 +5049,21 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, jl_methcache_t *mc,
// we've already processed all of the possible outputs
if (all_subtypes) {
if (minmax == NULL) {
// all intersecting methods are fully-covering, but there is no unique most-specific method
if (!include_ambiguous) {
// there no unambiguous choice of method
len = 0;
env.t = jl_an_empty_vec_any;
}
else if (lim == 1) {
// we'd have to return >1 method due to the ambiguity, so bail early
JL_GC_POP();
return jl_nothing;
}
}
else {
// `minmax` is more-specific than all other matches and is fully-covering
// we can return it as our only result
jl_array_ptr_set(env.t, 0, minmax);
jl_array_del_end((jl_array_t*)env.t, len - 1);
len = 1;
Expand Down
6 changes: 6 additions & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -910,14 +910,20 @@ typedef struct _jl_typemap_level_t {

typedef struct _jl_methcache_t {
JL_DATA_TYPE
// hash map from dispatchtuple type to a linked-list of TypeMapEntry
// entry.sig == type for all entries in the linked-list
_Atomic(jl_genericmemory_t*) leafcache;

// cache for querying everything else (anything that didn't seem profitable to put into leafcache)
_Atomic(jl_typemap_t*) cache;

jl_mutex_t writelock;
} jl_methcache_t;

// contains global MethodTable
typedef struct _jl_methtable_t {
JL_DATA_TYPE
// full set of entries
_Atomic(jl_typemap_t*) defs;
jl_methcache_t *cache;
jl_sym_t *name; // sometimes used for debug printing
Expand Down
5 changes: 5 additions & 0 deletions src/typemap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,9 @@ static void jl_typemap_list_insert_(
jl_typemap_entry_t *newrec)
{
jl_typemap_entry_t *l = jl_atomic_load_relaxed(pml);

// Pick the first intersection point that guarantees that the list ordering
// will be (leaf sigs..., simple sigs..., other sigs...)
while ((jl_value_t*)l != jl_nothing) {
if (newrec->isleafsig || !l->isleafsig)
if (newrec->issimplesig || !l->issimplesig)
Expand All @@ -1340,6 +1343,7 @@ static void jl_typemap_list_insert_(
parent = (jl_value_t*)l;
l = jl_atomic_load_relaxed(&l->next);
}

jl_atomic_store_relaxed(&newrec->next, l);
jl_gc_wb(newrec, l);
jl_atomic_store_release(pml, newrec);
Expand All @@ -1357,6 +1361,7 @@ static void jl_typemap_insert_generic(
jl_typemap_memory_insert_(map, (_Atomic(jl_genericmemory_t*)*)pml, doublesplit, newrec, parent, 0, offs, NULL);
return;
}

if (jl_typeof(ml) == (jl_value_t*)jl_typemap_level_type) {
assert(!doublesplit);
jl_typemap_level_insert_(map, (jl_typemap_level_t*)ml, newrec, offs);
Expand Down