From 40cf848aaaef9919f8ab2278a34422cfbe370a1a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 22 Sep 2016 17:53:27 -0400 Subject: [PATCH] thread min/max validity of ml-matches results out of jl_matching_methods into inference eventually should probably switch this to use a Ref, or return the value, or something similarly more efficient than a singleton Array, but currently refpointer.jl is not part of Core.Inference --- base/REPLCompletions.jl | 7 +++- base/inference.jl | 39 ++++++++++------- base/reflection.jl | 26 +++++++----- src/gf.c | 93 ++++++++++++++++++++++++++++++----------- 4 files changed, 114 insertions(+), 51 deletions(-) diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl index 9624c2c57dd64..32cd73f191bc2 100644 --- a/base/REPLCompletions.jl +++ b/base/REPLCompletions.jl @@ -281,11 +281,14 @@ function get_type_call(expr::Expr) found ? push!(args, typ) : push!(args, Any) end # use _methods_by_ftype as the function is supplied as a type - mt = Base._methods_by_ftype(Tuple{ft, args...}, -1, typemax(UInt)) + min = UInt[typemin(UInt)] + max = UInt[typemax(UInt)] + world = typemax(UInt) + mt = Base._methods_by_ftype(Tuple{ft, args...}, -1, world, min, max) length(mt) == 1 || return (Any, false) m = first(mt) # Typeinference - return_type = Core.Inference.typeinf_type(m[3], m[1], m[2]) + return_type = Core.Inference.typeinf_type(m[3], m[1], m[2], world) return_type === nothing && return (Any, false) return (return_type, true) end diff --git a/base/inference.jl b/base/inference.jl index 0d91d4127ee28..7fc3f8fbe5491 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -826,7 +826,9 @@ function abstract_call_gf_by_type(f::ANY, atype::ANY, sv::InferenceState) return Any end end - applicable = _methods_by_ftype(argtype, 4, sv.world) + min_valid = UInt[typemin(UInt)] + max_valid = UInt[typemax(UInt)] + applicable = _methods_by_ftype(argtype, 4, sv.world, min_valid, max_valid) rettype = Bottom if is(applicable, false) # this means too many methods matched @@ -956,6 +958,7 @@ function abstract_call_gf_by_type(f::ANY, atype::ANY, sv::InferenceState) # also need an edge to the method table in case something gets # added that did not intersect with any existing method add_mt_backedge(ft.name.mt, argtype, sv) + update_valid_age!(min_valid[1], max_valid[1], sv) end if isempty(x) # TODO: this is needed because type intersection is wrong in some cases @@ -1060,7 +1063,9 @@ function pure_eval_call(f::ANY, argtypes::ANY, atype, vtypes, sv::InferenceState end end - meth = _methods_by_ftype(atype, 1, sv.world) + min_valid = UInt[typemin(UInt)] + max_valid = UInt[typemax(UInt)] + meth = _methods_by_ftype(atype, 1, sv.world, min_valid, max_valid) if meth === false || length(meth) != 1 return false end @@ -1074,6 +1079,7 @@ function pure_eval_call(f::ANY, argtypes::ANY, atype, vtypes, sv::InferenceState args = Any[ isa(a,Const) ? a.val : a.parameters[1] for a in drop(argtypes,1) ] try value = Core._apply_pure(f, args) + # TODO: add some sort of edge(s) return abstract_eval_constant(value) catch return false @@ -1530,6 +1536,7 @@ function converge_valid_age!(sv::InferenceState) i.max_valid = sv.max_valid updated = true end + @assert !isdefined(i.linfo, :def) || !i.cached || i.min_valid <= i.world <= i.max_valid "invalid age range update" if updated converge_valid_age!(i) end @@ -1537,16 +1544,16 @@ function converge_valid_age!(sv::InferenceState) end nothing end -function update_valid_age!(edge::InferenceState, sv::InferenceState) - sv.min_valid = max(edge.min_valid, sv.min_valid) - sv.max_valid = min(edge.max_valid, sv.max_valid) - nothing -end -function update_valid_age!(li::MethodInstance, sv::InferenceState) - sv.min_valid = max(sv.min_valid, min_age(li)) - sv.max_valid = min(sv.max_valid, max_age(li)) + +function update_valid_age!(min_valid::UInt, max_valid::UInt, sv::InferenceState) + sv.min_valid = max(sv.min_valid, min_valid) + sv.max_valid = min(sv.max_valid, max_valid) + @assert !isdefined(sv.linfo, :def) || !sv.cached || sv.min_valid <= sv.world <= sv.max_valid "invalid age range update" nothing end +update_valid_age!(edge::InferenceState, sv::InferenceState) = update_valid_age!(edge.min_valid, edge.max_valid, sv) +update_valid_age!(li::MethodInstance, sv::InferenceState) = update_valid_age!(min_age(li), max_age(li), sv) + function add_backedge(li::MethodInstance, caller::InferenceState) isdefined(caller.linfo, :def) || return # don't add backedges to toplevel exprs if caller.stmt_edges[caller.currpc] === () @@ -1556,6 +1563,7 @@ function add_backedge(li::MethodInstance, caller::InferenceState) update_valid_age!(li, caller) nothing end + function add_mt_backedge(mt::MethodTable, typ::ANY, caller::InferenceState) isdefined(caller.linfo, :def) || return # don't add backedges to toplevel exprs if caller.stmt_edges[caller.currpc] === () @@ -1566,6 +1574,7 @@ function add_mt_backedge(mt::MethodTable, typ::ANY, caller::InferenceState) # TODO: how to compute the affect this has on valid ages for caller? nothing end + function finalize_backedges(frame::InferenceState) caller = frame.linfo for edges in frame.stmt_edges @@ -1666,7 +1675,7 @@ function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, caller code = code_for_method(method, atypes, sparams, caller.world) code === nothing && return Any code = code::MethodInstance - add_backedge(code, caller) + add_backedge(code, caller) # TODO: need to defer the tracking of this backedge till later if isdefined(code, :inferred) # return rettype if the code is already inferred # staged functions make this hard since they have two "inferred" conditions, @@ -2682,7 +2691,9 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference else atype = atype_unlimited end - meth = _methods_by_ftype(atype, 1, sv.world) + min_valid = UInt[typemin(UInt)] + max_valid = UInt[typemax(UInt)] + meth = _methods_by_ftype(atype, 1, sv.world, min_valid, max_valid) if meth === false || length(meth) != 1 return invoke_NF() end @@ -4083,9 +4094,9 @@ end # make sure that typeinf is executed before turning on typeinf_ext # this ensures that typeinf_ext doesn't recurse before it can add the item to the workq -for m in _methods_by_ftype(Tuple{typeof(typeinf_loop), Vararg{Any}}, 10, typemax(UInt)) +for m in _methods_by_ftype(Tuple{typeof(typeinf_loop), Vararg{Any}}, 10, typemax(UInt), UInt[typemin(UInt)], UInt[typemax(UInt)]) typeinf_type(m[3], m[1], m[2], typemax(UInt)) end -for m in _methods_by_ftype(Tuple{typeof(typeinf_edge), Vararg{Any}}, 10, typemax(UInt)) +for m in _methods_by_ftype(Tuple{typeof(typeinf_edge), Vararg{Any}}, 10, typemax(UInt), UInt[typemin(UInt)], UInt[typemax(UInt)]) typeinf_type(m[3], m[1], m[2], typemax(UInt)) end diff --git a/base/reflection.jl b/base/reflection.jl index cdf8d535a62c5..c0b22cd241283 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -349,10 +349,12 @@ end function _methods(f::ANY, t::ANY, lim::Int, world::UInt) ft = isa(f,Type) ? Type{f} : typeof(f) tt = isa(t,Type) ? Tuple{ft, t.parameters...} : Tuple{ft, t...} - return _methods_by_ftype(tt, lim, world) + min = UInt[typemin(UInt)] + max = UInt[typemax(UInt)] + return _methods_by_ftype(tt, lim, world, min, max) end -function _methods_by_ftype(t::ANY, lim::Int, world::UInt) +function _methods_by_ftype(t::ANY, lim::Int, world::UInt, min::Array{UInt,1}, max::Array{UInt,1}) tp = t.parameters::SimpleVector nu = 1 for ti in tp @@ -361,16 +363,16 @@ function _methods_by_ftype(t::ANY, lim::Int, world::UInt) end end if 1 < nu <= 64 - return _methods(Any[tp...], length(tp), lim, [], world) + return _methods_by_ftype(Any[tp...], length(tp), lim, [], world, min, max) end # XXX: the following can return incorrect answers that the above branch would have corrected - return ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt), t, lim, 0, world) + return ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), t, lim, 0, world, min, max) end -function _methods(t::Array, i, lim::Integer, matching::Array{Any,1}, world::UInt) +function _methods_by_ftype(t::Array, i, lim::Integer, matching::Array{Any,1}, world::UInt, min::Array{UInt,1}, max::Array{UInt,1}) if i == 0 world = typemax(UInt) - new = ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt), Tuple{t...}, lim, 0, world) + new = ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), Tuple{t...}, lim, 0, world, min, max) new === false && return false append!(matching, new::Array{Any,1}) else @@ -378,14 +380,14 @@ function _methods(t::Array, i, lim::Integer, matching::Array{Any,1}, world::UInt if isa(ti, Union) for ty in (ti::Union).types t[i] = ty - if _methods(t, i - 1, lim, matching, world) === false + if _methods_by_ftype(t, i - 1, lim, matching, world, min, max) === false t[i] = ti return false end end t[i] = ti else - return _methods(t, i - 1, lim, matching, world) + return _methods_by_ftype(t, i - 1, lim, matching, world, min, max) end end return matching @@ -435,7 +437,9 @@ function methods_including_ambiguous(f::ANY, t::ANY) ft = isa(f,Type) ? Type{f} : typeof(f) tt = isa(t,Type) ? Tuple{ft, t.parameters...} : Tuple{ft, t...} world = typemax(UInt) - ms = ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt), tt, -1, 1, world)::Array{Any,1} + min = UInt[typemin(UInt)] + max = UInt[typemax(UInt)] + ms = ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), tt, -1, 1, world, min, max)::Array{Any,1} return MethodList(Method[m[3] for m in ms], typeof(f).name.mt) end function methods(f::ANY) @@ -745,7 +749,9 @@ end function isambiguous(m1::Method, m2::Method) ti = typeintersect(m1.sig, m2.sig) ti === Bottom && return false - ml = _methods_by_ftype(ti, -1, typemax(UInt)) + min = UInt[typemin(UInt)] + max = UInt[typemax(UInt)] + ml = _methods_by_ftype(ti, -1, typemax(UInt), min, max) isempty(ml) && return true for m in ml if ti <: m[3].sig diff --git a/src/gf.c b/src/gf.c index 0299e533ca6e1..e42f3a17e0b8b 100644 --- a/src/gf.c +++ b/src/gf.c @@ -134,8 +134,10 @@ JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m, jl_typemap_entry_t *sf = jl_typemap_assoc_by_type( m->specializations, type, NULL, 2, /*subtype*/0, /*offs*/0, world); if (sf && jl_is_method_instance(sf->func.value)) { + jl_method_instance_t *linfo = (jl_method_instance_t*)sf->func.value; + assert(linfo->min_world == sf->min_world && linfo->max_world == sf->max_world); JL_UNLOCK(&m->writelock); - return (jl_method_instance_t*)sf->func.value; + return linfo; } jl_method_instance_t *li = jl_get_specialized(m, type, sparams); JL_GC_PUSH1(&li); @@ -322,11 +324,11 @@ JL_DLLEXPORT jl_method_instance_t* jl_set_lambda_inferred( // expand the current (uninferred) entry to cover the full inferred range if (li->min_world != min_world) { li->min_world = min_world; - jl_typemap_visitor(li->def->specializations, set_min_world, (void*)li); + update_caches(li, set_min_world); } if (li->max_world != max_world) { li->max_world = max_world; - jl_typemap_visitor(li->def->specializations, set_max_world, (void*)li); + update_caches(li, set_max_world); } } else { @@ -513,7 +515,7 @@ static jl_tupletype_t *join_tsig(jl_tupletype_t *tt, jl_tupletype_t *sig) static jl_value_t *ml_matches(union jl_typemap_t ml, int offs, jl_tupletype_t *type, int lim, int include_ambiguous, - size_t world); + size_t world, size_t *min_valid, size_t *max_valid); static void jl_cacheable_sig( jl_tupletype_t *const type, // the specialized type signature for type lambda @@ -841,6 +843,8 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t need_guard_entries = 1; } + size_t min_valid = definition->min_world; + size_t max_valid = definition->max_world; int cache_with_orig = 0; jl_svec_t* guardsigs = jl_emptysvec; jl_tupletype_t *origtype = type; // backup the prior value of `type` @@ -849,7 +853,7 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t temp2 = (jl_value_t*)type; } if (need_guard_entries) { - temp = ml_matches(mt->defs, 0, type, -1, 0, 0); // TODO: use MAX_UNSPECIALIZED_CONFLICTS? + temp = ml_matches(mt->defs, 0, type, -1, 0, world, &min_valid, &max_valid); // TODO: use MAX_UNSPECIALIZED_CONFLICTS? int guards = 0; if (temp == jl_false) { cache_with_orig = 1; @@ -890,11 +894,12 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t guards = 0; for(i = 0, l = jl_array_len(temp); i < l; i++) { jl_value_t *m = jl_array_ptr_ref(temp, i); - if (((jl_method_t*)jl_svecref(m,2)) != definition) { + jl_method_t *other = (jl_method_t*)jl_svecref(m, 2); + if (other != definition) { jl_svecset(guardsigs, guards, (jl_tupletype_t*)jl_svecref(m, 0)); guards++; //jl_typemap_insert(cache, parent, (jl_tupletype_t*)jl_svecref(m, 0), - // jl_emptysvec, NULL, jl_emptysvec, /*guard*/NULL, jl_cachearg_offset(mt), &lambda_cache, 1, ~(size_t)0, NULL); + // jl_emptysvec, NULL, jl_emptysvec, /*guard*/NULL, jl_cachearg_offset(mt), &lambda_cache, other->min_world, other->max_world, NULL); } } } @@ -902,6 +907,10 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t // here we infer types and specialize the method newmeth = jl_specializations_get_linfo(definition, type, sparams, world); + if (newmeth->min_world > min_valid) + min_valid = newmeth->min_world; + if (newmeth->max_world < max_valid) + max_valid = newmeth->max_world; if (cache_with_orig) { // if there is a need to cache with one of the original signatures, @@ -940,7 +949,7 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t } jl_typemap_insert(cache, parent, origtype, jl_emptysvec, type, guardsigs, (jl_value_t*)newmeth, jl_cachearg_offset(mt), &lambda_cache, - newmeth->min_world, newmeth->max_world, NULL); + min_valid, max_valid, NULL); if (definition->traced && jl_method_tracer && allow_exec) jl_call_tracer(jl_method_tracer, (jl_value_t*)newmeth); @@ -1378,13 +1387,24 @@ jl_method_instance_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_ int cache, int inexact, int allow_exec, size_t world) { jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt), world); - if (entry) - return entry->func.linfo; + if (entry) { + jl_method_instance_t *linfo = (jl_method_instance_t*)entry->func.value; +#ifndef JULIA_THREADING + // with threading, this condition is only eventually-consistent + assert(linfo->min_world == entry->min_world && linfo->max_world == entry->max_world); +#endif + return linfo; + } JL_LOCK(&mt->writelock); entry = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt), world); if (entry) { + jl_method_instance_t *linfo = (jl_method_instance_t*)entry->func.value; +#ifndef JULIA_THREADING + // with threading, this condition is only eventually-consistent + assert(linfo->min_world == entry->min_world && linfo->max_world == entry->max_world); +#endif JL_UNLOCK(&mt->writelock); - return entry->func.linfo; + return linfo; } if (jl_is_leaf_type((jl_value_t*)types)) cache = 1; @@ -1439,18 +1459,24 @@ jl_method_instance_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, si // // lim is the max # of methods to return. if there are more, returns jl_false. // -1 for no limit. -JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim, int include_ambiguous, size_t world) +JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim, int include_ambiguous, size_t world, size_t *min_valid, size_t *max_valid) { assert(jl_nparams(types) > 0); - if (jl_tparam0(types) == jl_bottom_type) - return (jl_value_t*)jl_alloc_vec_any(0); - if (!jl_is_datatype(jl_tparam0(types))) { + jl_value_t *matches = NULL; + if (jl_tparam0(types) == jl_bottom_type) { + matches = (jl_value_t*)jl_alloc_vec_any(0); + } + else if (!jl_is_datatype(jl_tparam0(types))) { return jl_false; // indeterminate - ml_matches can't deal with this case } - jl_methtable_t *mt = ((jl_datatype_t*)jl_tparam0(types))->name->mt; - if (mt == NULL) - return (jl_value_t*)jl_alloc_vec_any(0); - return ml_matches(mt->defs, 0, types, lim, include_ambiguous, world); + else { + jl_methtable_t *mt = ((jl_datatype_t*)jl_tparam0(types))->name->mt; + if (mt == NULL) + matches = (jl_value_t*)jl_alloc_vec_any(0); + else + matches = ml_matches(mt->defs, 0, types, lim, include_ambiguous, world, min_valid, max_valid); + } + return matches; } jl_llvm_functions_t jl_compile_for_dispatch(jl_method_instance_t **pli, size_t world) @@ -1520,8 +1546,10 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types, size_t world // if one argument type is DataType, multiple Type{} definitions // might match. also be conservative with tuples rather than trying // to analyze them in detail. + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; if (ti == (jl_value_t*)jl_datatype_type || jl_is_tuple_type(ti)) { - jl_value_t *matches = jl_matching_methods(types, 1, 0, world); + jl_value_t *matches = jl_matching_methods(types, 1, 0, world, &min_valid, &max_valid); if (matches == jl_false) return NULL; break; @@ -2259,19 +2287,30 @@ static int tvar_exists_at_top_level(jl_value_t *tv, jl_tupletype_t *sig, int att struct ml_matches_env { struct typemap_intersection_env match; - jl_value_t *t; // results: array of svec(argtypes, params, Method) + // results: + jl_value_t *t; // array of svec(argtypes, params, Method) + size_t min_valid; + size_t max_valid; + // temporary: jl_svec_t *matc; // current working svec + // inputs: + size_t world; int lim; int include_ambiguous; // whether ambiguous matches should be included - size_t world; }; 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); int i; - if (closure->world != 0) // use zero as a flag value for returning all matches - if (closure->world < ml->min_world || closure->world > ml->max_world) + if (closure->world != 0) { // use zero as a flag value for returning all matches + if (ml->min_world < closure->min_valid) + closure->min_valid = ml->min_world; + if (ml->max_world > closure->max_valid) + closure->max_valid = ml->max_world; + if (closure->world < ml->min_world || closure->world > ml->max_world) { return 1; // ignore method table entries that are not part of this world + } + } // a method is shadowed if type <: S <: m->sig where S is the // signature of another applicable method /* @@ -2412,7 +2451,7 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio // See below for the meaning of lim. static jl_value_t *ml_matches(union jl_typemap_t defs, int offs, jl_tupletype_t *type, int lim, int include_ambiguous, - size_t world) + size_t world, size_t *min_valid, size_t *max_valid) { size_t l = jl_svec_len(type->parameters); jl_value_t *va = NULL; @@ -2434,9 +2473,13 @@ static jl_value_t *ml_matches(union jl_typemap_t defs, int offs, env.lim = lim; env.include_ambiguous = include_ambiguous; env.world = world; + env.min_valid = *min_valid; + env.max_valid = *max_valid; JL_GC_PUSH4(&env.t, &env.matc, &env.match.env, &env.match.ti); jl_typemap_intersection_visitor(defs, offs, &env.match); JL_GC_POP(); + *min_valid = env.min_valid; + *max_valid = env.max_valid; return env.t; }