From 258d04e8bfdb8189fd61199bccd7641650fec07b Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 14 May 2020 16:19:30 -0400 Subject: [PATCH] make method table definitions unsorted Rather than stopping at the first subtyping match, this scans the entire table of methods for intersections for every lookup. It then filters the results to remove ambiguities and sort for specificity order. Also exports the has_ambiguity computation from ml-matches, as it's cheap here to provide (already computed), and required by Core.Compiler (and also more correct than what the optimizer had been doing, though only on extreme hypothetical edge cases). --- base/compiler/abstractinterpretation.jl | 11 +- base/compiler/inferencestate.jl | 4 +- base/compiler/optimize.jl | 4 +- base/compiler/ssair/inlining.jl | 27 +- base/compiler/ssair/ir.jl | 3 +- base/compiler/ssair/slot2ssa.jl | 4 - base/compiler/tfuncs.jl | 8 +- base/reflection.jl | 33 +- doc/src/manual/methods.md | 2 +- src/dump.c | 23 +- src/gf.c | 938 +++++++++++------------- src/jltypes.c | 12 +- src/julia.h | 5 - src/julia_internal.h | 2 +- src/method.c | 2 - stdlib/LinearAlgebra/src/symmetric.jl | 8 +- stdlib/REPL/test/replcompletions.jl | 8 +- stdlib/Test/src/Test.jl | 30 +- test/ambiguous.jl | 26 +- test/errorshow.jl | 2 +- test/reflection.jl | 4 - test/worlds.jl | 12 +- 22 files changed, 530 insertions(+), 638 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 0bb74102c8728..573868bbef209 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -16,18 +16,19 @@ const _REF_NAME = Ref.body.name call_result_unused(frame::InferenceState, pc::LineNum=frame.currpc) = isexpr(frame.src.code[frame.currpc], :call) && isempty(frame.ssavalue_uses[pc]) -function matching_methods(@nospecialize(atype), cache::IdDict{Any, Tuple{Any, UInt, UInt}}, max_methods::Int, world::UInt) +function matching_methods(@nospecialize(atype), cache::IdDict{Any, Tuple{Any, UInt, UInt, Bool}}, max_methods::Int, world::UInt) box = Core.Box(atype) return get!(cache, atype) do _min_val = UInt[typemin(UInt)] _max_val = UInt[typemax(UInt)] - ms = _methods_by_ftype(box.contents, max_methods, world, _min_val, _max_val) - return ms, _min_val[1], _max_val[1] + _ambig = Int32[0] + ms = _methods_by_ftype(box.contents, max_methods, world, false, _min_val, _max_val, _ambig) + return ms, _min_val[1], _max_val[1], _ambig[1] != 0 end end -function matching_methods(@nospecialize(atype), cache::IdDict{Any, Tuple{Any, UInt, UInt}}, max_methods::Int, world::UInt, min_valid::Vector{UInt}, max_valid::Vector{UInt}) - ms, minvalid, maxvalid = matching_methods(atype, cache, max_methods, world) +function matching_methods(@nospecialize(atype), cache::IdDict{Any, Tuple{Any, UInt, UInt, Bool}}, max_methods::Int, world::UInt, min_valid::Vector{UInt}, max_valid::Vector{UInt}) + ms, minvalid, maxvalid, ambig = matching_methods(atype, cache, max_methods, world) min_valid[1] = max(min_valid[1], minvalid) max_valid[1] = min(max_valid[1], maxvalid) return ms diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index deee51793806d..2a699572778f5 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -44,7 +44,7 @@ mutable struct InferenceState # cached results of calling `_methods_by_ftype`, including `min_valid` and # `max_valid`, to be used in inlining - matching_methods_cache::IdDict{Any, Tuple{Any, UInt, UInt}} + matching_methods_cache::IdDict{Any, Tuple{Any, UInt, UInt, Bool}} # The interpreter that created this inference state. Not looked at by # NativeInterpreter. But other interpreters may use this to detect cycles @@ -111,7 +111,7 @@ mutable struct InferenceState Vector{InferenceState}(), # callers_in_cycle #=parent=#nothing, cached, false, false, false, - IdDict{Any, Tuple{Any, UInt, UInt}}(), + IdDict{Any, Tuple{Any, UInt, UInt, Bool}}(), interp) result.result = frame cached && push!(get_inference_cache(interp), result) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 10be5d97ecafc..09d0c4d540e3a 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -19,7 +19,7 @@ mutable struct OptimizationState const_api::Bool # cached results of calling `_methods_by_ftype` from inference, including # `min_valid` and `max_valid` - matching_methods_cache::IdDict{Any, Tuple{Any, UInt, UInt}} + matching_methods_cache::IdDict{Any, Tuple{Any, UInt, UInt, Bool}} # TODO: This will be eliminated once optimization no longer needs to do method lookups interp::AbstractInterpreter function OptimizationState(frame::InferenceState, params::OptimizationParams, interp::AbstractInterpreter) @@ -64,7 +64,7 @@ mutable struct OptimizationState src, inmodule, nargs, get_world_counter(), UInt(1), get_world_counter(), sptypes_from_meth_instance(linfo), slottypes, false, - IdDict{Any, Tuple{Any, UInt, UInt}}(), interp) + IdDict{Any, Tuple{Any, UInt, UInt, Bool}}(), interp) end end diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 37f885fb93f60..018bf31f69b26 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -3,7 +3,7 @@ @nospecialize struct InvokeData - entry::Core.TypeMapEntry + entry::Method types0 min_valid::UInt max_valid::UInt @@ -683,17 +683,6 @@ function analyze_method!(idx::Int, sig::Signature, @nospecialize(metharg), meths return nothing end - # Check if we intersect any of this method's ambiguities - # TODO: We could split out the ambiguous case as another "union split" case. - # For now, we just reject the method - if method.ambig !== nothing && invoke_data === nothing - for entry::Core.TypeMapEntry in method.ambig - if typeintersect(sig.atype, entry.sig) !== Bottom - return nothing - end - end - end - # Bail out if any static parameters are left as TypeVar ok = true for i = 1:length(methsp) @@ -943,12 +932,11 @@ is_builtin(s::Signature) = function inline_invoke!(ir::IRCode, idx::Int, sig::Signature, invoke_data::InvokeData, sv::OptimizationState, todo::Vector{Any}) stmt = ir.stmts[idx][:inst] calltype = ir.stmts[idx][:type] - method = invoke_data.entry.func + method = invoke_data.entry (metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), - sig.atype, method.sig)::SimpleVector + sig.atype, method.sig)::SimpleVector methsp = methsp::SimpleVector - result = analyze_method!(idx, sig, metharg, methsp, method, stmt, sv, true, invoke_data, - calltype) + result = analyze_method!(idx, sig, metharg, methsp, method, stmt, sv, true, invoke_data, calltype) handle_single_case!(ir, stmt, idx, result, true, todo) update_valid_age!(invoke_data.min_valid, invoke_data.max_valid, sv) return nothing @@ -1046,10 +1034,11 @@ function assemble_inline_todo!(ir::IRCode, sv::OptimizationState) # in the case that the cache is nonempty, so it should be unchanged # The max number of methods should be the same as in inference most # of the time, and should not affect correctness otherwise. - (meth, min_valid, max_valid) = + (meth, min_valid, max_valid, ambig) = matching_methods(atype, sv.matching_methods_cache, sv.params.MAX_METHODS, sv.world) - if meth === false + if meth === false || ambig # Too many applicable methods + # Or there is a (partial?) ambiguity too_many = true break elseif length(meth) == 0 @@ -1159,7 +1148,7 @@ function compute_invoke_data(@nospecialize(atypes), world::UInt) invoke_entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), invoke_types, world) # XXX: min_valid, max_valid invoke_entry === nothing && return nothing - invoke_data = InvokeData(invoke_entry::Core.TypeMapEntry, invoke_types, min_valid[1], max_valid[1]) + invoke_data = InvokeData(invoke_entry::Method, invoke_types, min_valid[1], max_valid[1]) atype0 = atypes[2] atypes = atypes[4:end] pushfirst!(atypes, atype0) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 9204590fb882b..7a85ac59ca214 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -1,7 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license @inline isexpr(@nospecialize(stmt), head::Symbol) = isa(stmt, Expr) && stmt.head === head -@eval Core.UpsilonNode() = $(Expr(:new, Core.UpsilonNode)) Core.PhiNode() = Core.PhiNode(Any[], Any[]) """ @@ -313,6 +312,8 @@ end struct UndefToken end +const undef_token = UndefToken() + function getindex(x::UseRef) stmt = x.stmt diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index a721dd4f0160d..aa1a6d7a0c063 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -83,10 +83,6 @@ function make_ssa!(ci::CodeInfo, code::Vector{Any}, idx, slot, @nospecialize(typ idx end -struct UndefToken -end -const undef_token = UndefToken() - function new_to_regular(@nospecialize(stmt), new_offset::Int) if isa(stmt, NewSSAValue) return SSAValue(stmt.id + new_offset) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index fb4d8549295af..129a01af12b3f 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1160,14 +1160,14 @@ function invoke_tfunc(interp::AbstractInterpreter, @nospecialize(ft), @nospecial isdispatchelem(ft) || return Any # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types) argtype = Tuple{ft, argtype.parameters...} - entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), types, get_world_counter(interp)) - if entry === nothing + meth = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), types, get_world_counter(interp)) + if meth === nothing return Any end # XXX: update_valid_age!(min_valid[1], max_valid[1], sv) - meth = entry.func + meth = meth::Method (ti, env) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argtype, meth.sig)::SimpleVector - rt, edge = typeinf_edge(interp, meth::Method, ti, env, sv) + rt, edge = typeinf_edge(interp, meth, ti, env, sv) edge !== nothing && add_backedge!(edge::MethodInstance, sv) return rt end diff --git a/base/reflection.jl b/base/reflection.jl index eaa802f737fcd..89d88d2f99145 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -835,10 +835,10 @@ function _methods(@nospecialize(f), @nospecialize(t), lim::Int, world::UInt) end function _methods_by_ftype(@nospecialize(t), lim::Int, world::UInt) - return _methods_by_ftype(t, lim, world, UInt[typemin(UInt)], UInt[typemax(UInt)]) + return _methods_by_ftype(t, lim, world, false, UInt[typemin(UInt)], UInt[typemax(UInt)], Cint[0]) end -function _methods_by_ftype(@nospecialize(t), lim::Int, world::UInt, min::Array{UInt,1}, max::Array{UInt,1}) - return ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), t, lim, 0, world, min, max) +function _methods_by_ftype(@nospecialize(t), lim::Int, world::UInt, ambig::Bool, min::Array{UInt,1}, max::Array{UInt,1}, has_ambig::Array{Int32,1}) + return ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}, Ptr{Int32}), t, lim, ambig, world, min, max, has_ambig)::Union{Array{Any,1}, Bool} end # high-level, more convenient method lookup functions @@ -895,7 +895,9 @@ function methods_including_ambiguous(@nospecialize(f), @nospecialize(t)) world = typemax(UInt) 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} + has_ambig = Int32[0] + ms = _methods_by_ftype(tt, -1, world, true, min, max, has_ambig) + ms === false && return false return MethodList(Method[m[3] for m in ms], typeof(f).name.mt) end @@ -1177,7 +1179,7 @@ function which(@nospecialize(tt::Type)) if m === nothing error("no unique matching method found for the specified argument types") end - return m.func::Method + return m::Method end """ @@ -1294,11 +1296,10 @@ end """ Base.isambiguous(m1, m2; ambiguous_bottom=false) -> Bool -Determine whether two methods `m1` and `m2` (typically of the same -function) are ambiguous. This test is performed in the context of -other methods of the same function; in isolation, `m1` and `m2` might -be ambiguous, but if a third method resolving the ambiguity has been -defined, this returns `false`. +Determine whether two methods `m1` and `m2` may be ambiguous for some call +signature. This test is performed in the context of other methods of the same +function; in isolation, `m1` and `m2` might be ambiguous, but if a third method +resolving the ambiguity has been defined, this returns `false`. For parametric types, the `ambiguous_bottom` keyword argument controls whether `Union{}` counts as an ambiguous intersection of type parameters – when `true`, @@ -1325,15 +1326,23 @@ false ``` """ function isambiguous(m1::Method, m2::Method; ambiguous_bottom::Bool=false) + # TODO: eagerly returning `morespecific` is wrong, and fails to consider + # the possibility of an ambiguity caused by a third method: + # see the precise algorithm in ml_matches for a more correct computation + if m1 === m2 || morespecific(m1.sig, m2.sig) || morespecific(m2.sig, m1.sig) + return false + end ti = typeintersect(m1.sig, m2.sig) + (ti <: m1.sig && ti <: m2.sig) || return false # XXX: completely wrong, obviously ti === Bottom && return false if !ambiguous_bottom has_bottom_parameter(ti) && return false end ml = _methods_by_ftype(ti, -1, typemax(UInt)) - isempty(ml) && return true for m in ml - if ti <: m[3].sig + m === m1 && continue + m === m2 && continue + if ti <: m[3].sig && morespecific(m[3].sig, m1.sig) && morespecific(m[3].sig, m2.sig) return false end end diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index 18d0a77f0fad6..f350ed843c02b 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -243,8 +243,8 @@ julia> g(2, 3.0) julia> g(2.0, 3.0) ERROR: MethodError: g(::Float64, ::Float64) is ambiguous. Candidates: - g(x::Float64, y) in Main at none:1 g(x, y::Float64) in Main at none:1 + g(x::Float64, y) in Main at none:1 Possible fix, define g(::Float64, ::Float64) ``` diff --git a/src/dump.c b/src/dump.c index 09472aa60f6f5..68503642f1393 100644 --- a/src/dump.c +++ b/src/dump.c @@ -568,7 +568,6 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li write_uint8(s->s, TAG_METHOD); jl_method_t *m = (jl_method_t*)v; int internal = 1; - int external_mt = 0; internal = module_in_worklist(m->module); if (!internal) { // flag this in the backref table as special @@ -581,22 +580,11 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li write_uint8(s->s, internal); if (!internal) return; - jl_methtable_t *mt = jl_method_table_for((jl_value_t*)m->sig); - assert((jl_value_t*)mt != jl_nothing); - external_mt = !module_in_worklist(mt->module); jl_serialize_value(s, m->specializations); jl_serialize_value(s, m->speckeyset); jl_serialize_value(s, (jl_value_t*)m->name); jl_serialize_value(s, (jl_value_t*)m->file); write_int32(s->s, m->line); - if (external_mt) { - jl_serialize_value(s, jl_nothing); - jl_serialize_value(s, jl_nothing); - } - else { - jl_serialize_value(s, (jl_value_t*)m->ambig); - jl_serialize_value(s, (jl_value_t*)m->resorted); - } write_int32(s->s, m->called); write_int32(s->s, m->nargs); write_int32(s->s, m->nospecialize); @@ -995,7 +983,8 @@ static void jl_collect_backedges(jl_array_t *s, jl_array_t *t) } size_t min_valid = 0; size_t max_valid = ~(size_t)0; - jl_value_t *matches = jl_matching_methods((jl_tupletype_t*)sig, -1, 0, jl_world_counter, &min_valid, &max_valid); + int ambig = 0; + jl_value_t *matches = jl_matching_methods((jl_tupletype_t*)sig, -1, 0, jl_world_counter, &min_valid, &max_valid, &ambig); if (matches == jl_false) { valid = 0; break; @@ -1405,10 +1394,6 @@ static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_ m->line = read_int32(s->s); m->primary_world = jl_world_counter; m->deleted_world = ~(size_t)0; - m->ambig = jl_deserialize_value(s, (jl_value_t**)&m->ambig); - jl_gc_wb(m, m->ambig); - m->resorted = jl_deserialize_value(s, (jl_value_t**)&m->resorted); - jl_gc_wb(m, m->resorted); m->called = read_int32(s->s); m->nargs = read_int32(s->s); m->nospecialize = read_int32(s->s); @@ -1858,7 +1843,9 @@ static void jl_verify_edges(jl_array_t *targets, jl_array_t **pvalids) int valid = 1; size_t min_valid = 0; size_t max_valid = ~(size_t)0; - jl_value_t *matches = jl_matching_methods((jl_tupletype_t*)sig, -1, 0, jl_world_counter, &min_valid, &max_valid); + int ambig = 0; + // TODO: possibly need to included ambiguities too (for the optimizer correctness)? + jl_value_t *matches = jl_matching_methods((jl_tupletype_t*)sig, -1, 0, jl_world_counter, &min_valid, &max_valid, &ambig); if (matches == jl_false || jl_array_len(matches) != jl_array_len(expected)) { valid = 0; } diff --git a/src/gf.c b/src/gf.c index 5910e117bc4a7..b672c466644ae 100644 --- a/src/gf.c +++ b/src/gf.c @@ -70,14 +70,11 @@ void jl_call_tracer(tracer_cb callback, jl_value_t *tracee) /// ----- Definitions for various internal TypeMaps ----- /// const struct jl_typemap_info method_defs = { - 0, &jl_method_type + 1, &jl_method_type }; const struct jl_typemap_info lambda_cache = { 0, &jl_method_instance_type }; -const struct jl_typemap_info tfunc_cache = { - 1, &jl_any_type -}; static int8_t jl_cachearg_offset(jl_methtable_t *mt) { @@ -566,8 +563,8 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, jl_tupletype_t *type, int lim, int include_ambiguous, - size_t world, size_t *min_valid, size_t *max_valid, - int cache_result); + int intersections, size_t world, int cache_result, + size_t *min_valid, size_t *max_valid, int *has_ambiguity); // get the compilation signature specialization for this method static void jl_compilation_sig( @@ -1038,7 +1035,8 @@ static jl_method_instance_t *cache_method( // TODO: should we first check `compilationsig <: definition`? size_t min_valid2 = 1; size_t max_valid2 = ~(size_t)0; - temp = ml_matches(mt, 0, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, world, &min_valid2, &max_valid2, 0); + int ambig = 0; + temp = ml_matches(mt, 0, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, 1, world, 0, &min_valid2, &max_valid2, &ambig); int guards = 0; if (temp == jl_false) { cache_with_orig = 1; @@ -1167,73 +1165,7 @@ static jl_method_instance_t *cache_method( return newmeth; } -// this is the general entry point for taking a match (returned by jl_typemap_assoc_by_type with subtype=1) -// and getting the most-specific one (or NULL, if there isn't one that is most specific) -static jl_typemap_entry_t *jl_typemap_morespecific_by_type(jl_typemap_entry_t *first JL_PROPAGATES_ROOT, jl_value_t *types, jl_svec_t **penv, size_t world) -{ - jl_typemap_entry_t *candidate = first; - jl_value_t *resorted = first->func.method->resorted; - // pick a method out of the resorted list that is more specific than any other applicable method - if ((jl_value_t*)resorted != jl_nothing) { - size_t i, l = jl_array_len(resorted); - //int isambig = 0; - for (i = 0; i < l; i++) { - jl_typemap_entry_t *prior = (jl_typemap_entry_t*)jl_array_ptr_ref(resorted, i); - if (prior->min_world <= world && world <= prior->max_world && jl_subtype(types, (jl_value_t*)prior->sig)) { - if (candidate == first || jl_type_morespecific((jl_value_t*)prior->sig, (jl_value_t*)candidate->sig)) { - candidate = prior; - } - //else if (!jl_type_morespecific((jl_value_t*)candidate->sig, (jl_value_t*)prior->sig)) { - // isambig = 1; - //} - } - } - // and then make sure it is more specific than all other applicable (unsorted) methods - //if (isambig) - // return NULL; - if (candidate != first) { - jl_value_t *ambigs = first->func.method->ambig; - if ((jl_value_t*)ambigs != jl_nothing) { - size_t i, l = jl_array_len(ambigs); - for (i = 0; i < l; i++) { - jl_typemap_entry_t *ambig = (jl_typemap_entry_t*)jl_array_ptr_ref(ambigs, i); - if (ambig->min_world <= world && world <= ambig->max_world && jl_subtype(types, (jl_value_t*)ambig->sig)) { - if (!jl_type_morespecific((jl_value_t*)candidate->sig, (jl_value_t*)ambig->sig)) - return NULL; - } - } - } - //for (i = 0; i < l; i++) { - // jl_typemap_entry_t *prior = (jl_typemap_entry_t*)jl_array_ptr_ref(resorted, i); - // if (prior == candidate) - // break; // already checked the rest - // if (prior->min_world <= world && world <= prior->max_world && jl_subtype(types, (jl_value_t*)prior->sig)) { - // if (!jl_type_morespecific((jl_value_t*)candidate->sig, (jl_value_t*)prior->sig)) - // return NULL; - // } - //} - } - } - // and then make sure none of the ambiguous methods with our candidate are applicable: - // even if it it is a better match than our first method - // it might actually still be only net ambiguous when we consider ambiguities - jl_value_t *ambigs = candidate->func.method->ambig; - if ((jl_value_t*)ambigs != jl_nothing) { - size_t i, l = jl_array_len(ambigs); - for (i = 0; i < l; i++) { - jl_typemap_entry_t *ambig = (jl_typemap_entry_t*)jl_array_ptr_ref(ambigs, i); - if (ambig->min_world <= world && world <= ambig->max_world && jl_subtype(types, (jl_value_t*)ambig->sig)) { - return NULL; - } - } - } - if (candidate != first && penv) { - // get the updated `env` for our match - int match = jl_subtype_matching(types, (jl_value_t*)candidate->sig, penv); - assert(match); (void)match; - } - return candidate; -} +static jl_value_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid); static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t *tt, size_t world) { @@ -1251,20 +1183,17 @@ static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype if (entry) return entry->func.linfo; + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + jl_value_t *matc = _gf_invoke_lookup((jl_value_t*)tt, world, &min_valid, &max_valid); jl_method_instance_t *nf = NULL; - jl_svec_t *newparams = NULL; - search.env = jl_emptysvec; - JL_GC_PUSH3(&tt, &search.env, &newparams); - entry = jl_typemap_assoc_by_type(mt->defs, &search, /*offs*/0, /*subtype*/1); - - if (entry != NULL) { - entry = jl_typemap_morespecific_by_type(entry, (jl_value_t*)tt, &search.env, world); - if (entry != NULL) { - jl_method_t *m = entry->func.method; - nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, tt, m, world, search.min_valid, search.max_valid, search.env); - } + if (matc) { + JL_GC_PUSH1(&matc); + jl_method_t *m = (jl_method_t*)jl_svecref(matc, 2); + jl_svec_t *env = (jl_svec_t*)jl_svecref(matc, 1); + nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, tt, m, world, min_valid, max_valid, env); + JL_GC_POP(); } - JL_GC_POP(); return nf; } @@ -1277,162 +1206,68 @@ void print_func_loc(JL_STREAM *s, jl_method_t *m) } } -/* - record ambiguous method priorities - - the relative priority of A and B is ambiguous if - !subtype(A,B) && !subtype(B,A) && no corresponding tuple - elements are disjoint. - - for example, (AbstractArray, AbstractMatrix) and (AbstractMatrix, AbstractArray) are ambiguous. - however, (AbstractArray, AbstractMatrix, Foo) and (AbstractMatrix, AbstractArray, Bar) are fine - since Foo and Bar are disjoint, so there would be no confusion over - which one to call. -*/ -struct ambiguous_matches_env { +struct shadowed_matches_env { struct typemap_intersection_env match; - jl_typemap_t *defs; jl_typemap_entry_t *newentry; jl_value_t *shadowed; - int after; }; -const int eager_ambiguity_printing = 0; -static int check_ambiguous_visitor(jl_typemap_entry_t *oldentry, struct typemap_intersection_env *closure0) +static int check_shadowed_visitor(jl_typemap_entry_t *oldentry, struct typemap_intersection_env *closure0) { - struct ambiguous_matches_env *closure = container_of(closure0, struct ambiguous_matches_env, match); - if (oldentry == closure->newentry) { - closure->after = 1; + struct shadowed_matches_env *closure = container_of(closure0, struct shadowed_matches_env, match); + if (oldentry == closure->newentry) + return 1; + if (oldentry->max_world < ~(size_t)0 || oldentry->min_world == closure->newentry->min_world) + // skip if no world has both active + // also be careful not to try to scan something from the current dump-reload though + return 1; + jl_method_t *oldmethod = oldentry->func.method; + if (oldmethod->specializations == jl_emptysvec) + // nothing inferred ever before means nothing shadowed ever before return 1; - } - if (oldentry->max_world < ~(size_t)0) - return 1; // no world has both active - jl_typemap_entry_t *before = (closure->after ? closure->newentry : oldentry); - jl_typemap_entry_t *after = (closure->after ? oldentry : closure->newentry); - jl_tupletype_t *type = (jl_tupletype_t*)closure->match.type; + jl_tupletype_t *type = closure->newentry->sig; jl_tupletype_t *sig = oldentry->sig; - jl_value_t *isect = closure->match.ti; - int msp = 1; // TODO: msp is a really terrible name int shadowed = 0; - int reorder = 0; if (closure->match.issubty) { // (new)type <: (old)sig // new entry is more specific - if (!closure->after) - reorder = 1; shadowed = 1; - // TODO: can stop search now? (copy remainder from oldentry) } else if (jl_subtype((jl_value_t*)sig, (jl_value_t*)type)) { // old entry is more specific - if (closure->after) - reorder = 1; } else if (jl_type_morespecific_no_subtype((jl_value_t*)type, (jl_value_t*)sig)) { // new entry is more specific - if (!closure->after) - reorder = 1; shadowed = 1; } else if (jl_type_morespecific_no_subtype((jl_value_t*)sig, (jl_value_t*)type)) { // old entry is more specific - if (closure->after) - reorder = 1; } else { // sort order is ambiguous - reorder = 1; shadowed = 1; - msp = 0; - } - // TODO: also complete detection of a specificity cycle - - // see if the intersection is covered by another existing entry - // that will resolve the ambiguity (by being more specific than either) - // (in some cases, this also might end up finding - // that isect == type or isect == sig and return the original match) - if (reorder) { - size_t world = closure->newentry->min_world; - if (oldentry->min_world > world) - world = oldentry->min_world; - int exact1 = jl_subtype(isect, (jl_value_t*)sig); - int exact2 = jl_subtype(isect, (jl_value_t*)type); - // TODO: we might like to use `subtype = exact1 && exact2` here, but check_disabled_ambiguous_visitor - // won't be able to handle that, so we might end up making some unnecessary mambig entries here - // but I don't have any examples of such - struct jl_typemap_assoc search = {(jl_value_t*)isect, world, NULL, 0, ~(size_t)0}; - jl_typemap_entry_t *l = jl_typemap_assoc_by_type(closure->defs, &search, /*offs*/0, /*subtype*/0); - //assert((!subtype || l != after) && "bad typemap lookup result"); // should find `before` first - if (l != NULL && l != before && l != after) { - if ((exact1 || jl_type_morespecific_no_subtype((jl_value_t*)l->sig, (jl_value_t*)sig)) && // no subtype since `l->sig >: isect >: sig` - (exact2 || jl_type_morespecific_no_subtype((jl_value_t*)l->sig, (jl_value_t*)type))) { // no subtype since `l->sig >: isect >: type` - // ok, intersection is already covered by a more specific method - reorder = 0; // this lack of ordering doesn't matter (unless we delete this method--then it will) - shadowed = 0; // we wouldn't find anything that mattered - } - } - } - - // ok: record specificity ordering violation - if (reorder) { - jl_method_t *beforem = before->func.method; - jl_method_t *afterm = after->func.method; - if (msp) { - if ((jl_value_t*)beforem->resorted == jl_nothing) { - beforem->resorted = (jl_value_t*)jl_alloc_vec_any(0); - jl_gc_wb(beforem, beforem->resorted); - } - jl_array_ptr_1d_push((jl_array_t*)beforem->resorted, (jl_value_t*)after); - } - else { - if ((jl_value_t*)beforem->ambig == jl_nothing) { - beforem->ambig = (jl_value_t*)jl_alloc_vec_any(0); - jl_gc_wb(beforem, beforem->ambig); - } - if ((jl_value_t*)afterm->ambig == jl_nothing) { - afterm->ambig = (jl_value_t*)jl_alloc_vec_any(0); - jl_gc_wb(afterm, afterm->ambig); - } - jl_array_ptr_1d_push((jl_array_t*)beforem->ambig, (jl_value_t*)after); - jl_array_ptr_1d_push((jl_array_t*)afterm->ambig, (jl_value_t*)before); - if (eager_ambiguity_printing) { - jl_method_t *m1 = closure->newentry->func.method; - jl_method_t *m2 = oldentry->func.method; - JL_STREAM *s = JL_STDERR; - jl_printf(s, "WARNING: New definition \n "); - jl_static_show_func_sig(s, (jl_value_t*)type); - print_func_loc(s, m1); - jl_printf(s, "\nis ambiguous with: \n "); - jl_static_show_func_sig(s, (jl_value_t*)sig); - print_func_loc(s, m2); - jl_printf(s, ".\nTo fix, define \n "); - jl_static_show_func_sig(s, isect); - jl_printf(s, "\nbefore the new definition.\n"); - } - } } // ok: record that this method definition is being partially replaced // (either with a real definition, or an ambiguity error) - // be careful not to try to scan something from the current dump-reload though - if (shadowed && oldentry->min_world != closure->newentry->min_world) { + if (shadowed) { if (closure->shadowed == NULL) { - closure->shadowed = (jl_value_t*)oldentry; + closure->shadowed = (jl_value_t*)oldmethod; } else if (!jl_is_array(closure->shadowed)) { jl_array_t *list = jl_alloc_vec_any(2); jl_array_ptr_set(list, 0, closure->shadowed); - jl_array_ptr_set(list, 1, (jl_value_t*)oldentry); + jl_array_ptr_set(list, 1, (jl_value_t*)oldmethod); closure->shadowed = (jl_value_t*)list; } else { - jl_array_ptr_1d_push((jl_array_t*)closure->shadowed, (jl_value_t*)oldentry); + jl_array_ptr_1d_push((jl_array_t*)closure->shadowed, (jl_value_t*)oldmethod); } } return 1; } -static jl_value_t *check_ambiguous_matches(jl_typemap_t *defs, jl_typemap_entry_t *newentry, jl_typemap_intersection_visitor_fptr fptr) +static jl_value_t *check_shadowed_matches(jl_typemap_t *defs, jl_typemap_entry_t *newentry) { jl_tupletype_t *type = newentry->sig; jl_tupletype_t *ttypes = (jl_tupletype_t*)jl_unwrap_unionall((jl_value_t*)type); @@ -1445,71 +1280,17 @@ static jl_value_t *check_ambiguous_matches(jl_typemap_t *defs, jl_typemap_entry_ else va = NULL; } - struct ambiguous_matches_env env = {{fptr, (jl_value_t*)type, va}}; + struct shadowed_matches_env env = {{check_shadowed_visitor, (jl_value_t*)type, va}}; env.match.ti = NULL; env.match.env = jl_emptysvec; - env.defs = defs; env.newentry = newentry; env.shadowed = NULL; - env.after = 0; JL_GC_PUSH3(&env.match.env, &env.match.ti, &env.shadowed); jl_typemap_intersection_visitor(defs, 0, &env.match); JL_GC_POP(); return env.shadowed; } -static int check_disabled_ambiguous_visitor(jl_typemap_entry_t *oldentry, struct typemap_intersection_env *closure0) -{ - struct ambiguous_matches_env *closure = container_of(closure0, struct ambiguous_matches_env, match); - if (oldentry == closure->newentry) { - closure->after = 1; - return 1; - } - if (oldentry->max_world < ~(size_t)0) - return 1; - jl_tupletype_t *sig = oldentry->sig; - jl_value_t *type = closure->match.type; - jl_value_t *isect2 = NULL; - if (closure->shadowed == NULL) - closure->shadowed = (jl_value_t*)jl_alloc_vec_any(0); - JL_GC_PUSH1(&isect2); - int i, l = jl_array_len(closure->shadowed); - for (i = 0; i < l; i++) { - jl_typemap_entry_t *before = (jl_typemap_entry_t*)jl_array_ptr_ref(closure->shadowed, i); - isect2 = jl_type_intersection((jl_value_t*)before->sig, (jl_value_t*)sig); - // see if the intersection was covered by precisely the disabled method - // that means we now need to record the ambiguity - if (jl_types_equal(type, isect2)) { - jl_method_t *beforem = before->func.method; - jl_method_t *afterm = oldentry->func.method; - int msp = jl_type_morespecific((jl_value_t*)sig, (jl_value_t*)before->sig); - if (msp) { - if ((jl_value_t*)beforem->resorted == jl_nothing) { - beforem->resorted = (jl_value_t*)jl_alloc_vec_any(0); - jl_gc_wb(beforem, beforem->resorted); - } - jl_array_ptr_1d_push((jl_array_t*)beforem->resorted, (jl_value_t*)oldentry); - } - else if (!jl_type_morespecific((jl_value_t*)before->sig, (jl_value_t*)sig)) { - if ((jl_value_t*)beforem->ambig == jl_nothing) { - beforem->ambig = (jl_value_t*)jl_alloc_vec_any(0); - jl_gc_wb(beforem, beforem->ambig); - } - if ((jl_value_t*)afterm->ambig == jl_nothing) { - afterm->ambig = (jl_value_t*)jl_alloc_vec_any(0); - jl_gc_wb(afterm, afterm->ambig); - } - jl_array_ptr_1d_push((jl_array_t*)beforem->ambig, (jl_value_t*)oldentry); - jl_array_ptr_1d_push((jl_array_t*)afterm->ambig, (jl_value_t*)before); - } - } - } - JL_GC_POP(); - jl_array_ptr_1d_push((jl_array_t*)closure->shadowed, (jl_value_t*)oldentry); - return 1; -} - - static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue) { // method overwritten @@ -1600,23 +1381,27 @@ static void invalidate_method_instance(jl_method_instance_t *replaced, size_t ma } // invalidate cached methods that overlap this definition -static int invalidate_backedges(jl_method_instance_t *replaced_linfo, size_t max_world) +static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_world, const char *why) { - JL_LOCK_NOGC(&replaced_linfo->def.method->writelock); - jl_array_t *backedges = replaced_linfo->backedges; - int invalidated = 0; + JL_LOCK_NOGC(&replaced_mi->def.method->writelock); + jl_array_t *backedges = replaced_mi->backedges; if (backedges) { // invalidate callers (if any) - replaced_linfo->backedges = NULL; + replaced_mi->backedges = NULL; size_t i, l = jl_array_len(backedges); jl_method_instance_t **replaced = (jl_method_instance_t**)jl_array_ptr_data(backedges); for (i = 0; i < l; i++) { invalidate_method_instance(replaced[i], max_world, 1); } - invalidated = 1; } - JL_UNLOCK_NOGC(&replaced_linfo->def.method->writelock); - return invalidated; + JL_UNLOCK_NOGC(&replaced_mi->def.method->writelock); + if (why && _jl_debug_method_invalidation) { + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)replaced_mi); + jl_value_t *loctag = jl_cstr_to_string(why); + JL_GC_PUSH1(&loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + JL_GC_POP(); + } } // add a backedge from callee to caller @@ -1672,12 +1457,15 @@ JL_DLLEXPORT void jl_method_table_add_backedge(jl_methtable_t *mt, jl_value_t *t } struct invalidate_mt_env { + jl_typemap_entry_t *newentry; jl_value_t *shadowed; size_t max_world; + int invalidated; }; static int invalidate_mt_cache(jl_typemap_entry_t *oldentry, void *closure0) { struct invalidate_mt_env *env = (struct invalidate_mt_env*)closure0; + JL_GC_PROMISE_ROOTED(env->newentry); if (oldentry->max_world == ~(size_t)0) { jl_method_instance_t *mi = oldentry->func.linfo; jl_method_t *m = mi->def.method; @@ -1689,29 +1477,40 @@ static int invalidate_mt_cache(jl_typemap_entry_t *oldentry, void *closure0) } else { assert(jl_is_array(env->shadowed)); - jl_typemap_entry_t **d = (jl_typemap_entry_t**)jl_array_ptr_data(env->shadowed); + jl_method_t **d = (jl_method_t**)jl_array_ptr_data(env->shadowed); size_t i, n = jl_array_len(env->shadowed); for (i = 0; i < n; i++) { - if (m == d[i]->func.method) { + if (m == d[i]) { intersects = 1; break; } } } + intersects = intersects && !jl_has_empty_intersection((jl_value_t*)oldentry->sig, (jl_value_t*)env->newentry->sig); if (intersects) { if (_jl_debug_method_invalidation) { - jl_value_t *loctag = NULL; + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)oldentry); + jl_value_t *loctag = jl_cstr_to_string("invalidate_mt_cache"); JL_GC_PUSH1(&loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)mi); - loctag = jl_cstr_to_string("invalidate_mt_cache"); jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); JL_GC_POP(); } oldentry->max_world = env->max_world; + env->invalidated = 1; } } return 1; } +static int disable_mt_cache(jl_typemap_entry_t *oldentry, void *closure0) +{ + struct invalidate_mt_env *env = (struct invalidate_mt_env*)closure0; + if (oldentry->max_world < ~(size_t)0) + return 1; + jl_method_t *m = oldentry->func.linfo->def.method; + if (m == env->newentry->func.method) + oldentry->max_world = env->max_world; + return 1; +} static int typemap_search(jl_typemap_entry_t *entry, void *closure) { @@ -1744,20 +1543,21 @@ JL_DLLEXPORT void jl_method_table_disable(jl_methtable_t *mt, jl_method_t *metho JL_LOCK(&mt->writelock); // Narrow the world age on the method to make it uncallable method->deleted_world = methodentry->max_world = jl_world_counter++; - // Recompute ambiguities (deleting a more specific method might reveal ambiguities that it previously resolved) - (void)check_ambiguous_matches(mt->defs, methodentry, check_disabled_ambiguous_visitor); // drop this method from mt->cache struct invalidate_mt_env mt_cache_env; - mt_cache_env.max_world = methodentry->max_world; - mt_cache_env.shadowed = (jl_value_t*)method; - jl_typemap_visitor(mt->cache, invalidate_mt_cache, (void*)&mt_cache_env); + mt_cache_env.max_world = methodentry->max_world - 1; + mt_cache_env.newentry = methodentry; + mt_cache_env.shadowed = NULL; + mt_cache_env.invalidated = 0; + jl_typemap_visitor(mt->cache, disable_mt_cache, (void*)&mt_cache_env); jl_array_t *leafcache = mt->leafcache; size_t i, l = jl_array_len(leafcache); for (i = 1; i < l; i += 2) { jl_typemap_entry_t *oldentry = (jl_typemap_entry_t*)jl_array_ptr_ref(leafcache, i); if (oldentry) { while ((jl_value_t*)oldentry != jl_nothing) { - invalidate_mt_cache(oldentry, (void*)&mt_cache_env); + if (oldentry->max_world == ~(size_t)0) + oldentry->max_world = mt_cache_env.max_world; oldentry = oldentry->next; } } @@ -1765,28 +1565,21 @@ JL_DLLEXPORT void jl_method_table_disable(jl_methtable_t *mt, jl_method_t *metho // Invalidate the backedges int invalidated = 0; jl_svec_t *specializations = methodentry->func.method->specializations; - jl_value_t *loctag = NULL; - JL_GC_PUSH1(&loctag); l = jl_svec_len(specializations); for (i = 0; i < l; i++) { jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); if (mi) { invalidated = 1; - if (invalidate_backedges(mi, methodentry->max_world)) - if (_jl_debug_method_invalidation) { - if (!loctag) - loctag = jl_cstr_to_string("jl_method_table_disable"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - } + invalidate_backedges(mi, methodentry->max_world, "jl_method_table_disable"); } } if (invalidated && _jl_debug_method_invalidation) { jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)method); - if (!loctag) - loctag = jl_cstr_to_string("jl_method_table_disable"); + jl_value_t *loctag = jl_cstr_to_string("jl_method_table_disable"); + JL_GC_PUSH1(&loctag); jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + JL_GC_POP(); } - JL_GC_POP(); JL_UNLOCK(&mt->writelock); } @@ -1816,7 +1609,6 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method newentry = jl_typemap_alloc((jl_tupletype_t*)type, simpletype, jl_emptysvec, (jl_value_t*)method, method->primary_world, method->deleted_world); jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, 0, &method_defs); - oldvalue = check_ambiguous_matches(mt->defs, newentry, check_ambiguous_visitor); if (oldentry) { oldvalue = oldentry->func.value; method_overwrite(newentry, (jl_method_t*)oldvalue); @@ -1829,6 +1621,7 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method for (i = 1; i < na; i += 2) { jl_value_t *backedgetyp = backedges[i - 1]; if (!jl_has_empty_intersection(backedgetyp, (jl_value_t*)type)) { + // TODO: only delete if the ml_matches list (with intersection=0, include_ambiguous=1) is empty jl_method_instance_t *backedge = (jl_method_instance_t*)backedges[i]; invalidate_method_instance(backedge, max_world, 0); invalidated = 1; @@ -1845,14 +1638,17 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method else jl_array_del_end(mt->backedges, na - ins); } + oldvalue = check_shadowed_matches(mt->defs, newentry); } + if (oldvalue) { - if (jl_typeis(oldvalue, jl_typemap_entry_type)) - oldvalue = ((jl_typemap_entry_t*)oldvalue)->func.value; // a method // drop anything in mt->cache that might overlap with the new method struct invalidate_mt_env mt_cache_env; mt_cache_env.max_world = max_world; mt_cache_env.shadowed = oldvalue; + mt_cache_env.newentry = newentry; + mt_cache_env.invalidated = 0; + jl_typemap_visitor(mt->cache, invalidate_mt_cache, (void*)&mt_cache_env); jl_array_t *leafcache = mt->leafcache; size_t i, l = jl_array_len(leafcache); @@ -1865,11 +1661,6 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method } } } - //TODO: if it's small, might it be better to drop it all? - //if (mt != jl_type_type_mt) { - // mt->cache = jl_nothing; - // mt->leafcache = jl_an_empty_vec_any; - //} jl_value_t **d; size_t j, n; @@ -1884,29 +1675,21 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method } for (j = 0; j < n; j++) { jl_value_t *m = d[j]; - if (jl_is_array(oldvalue)) - m = ((jl_typemap_entry_t*)m)->func.value; - jl_svec_t *specializations = ((jl_method_t*)m)->specializations; + jl_svec_t *specializations = jl_atomic_load_acquire(&((jl_method_t*)m)->specializations); + jl_method_instance_t **data = (jl_method_instance_t**)jl_svec_data(specializations); size_t i, l = jl_svec_len(specializations); for (i = 0; i < l; i++) { - jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); - if (mi != NULL && !jl_has_empty_intersection(type, (jl_value_t*)mi->specTypes)) - if (invalidate_backedges(mi, max_world)) { - invalidated = 1; - if (_jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)mi); - if (!loctag) - loctag = jl_cstr_to_string("jl_method_table_insert"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - } - } + jl_method_instance_t *mi = jl_atomic_load_relaxed(&data[i]); + if (mi != NULL && mi->backedges && !jl_has_empty_intersection(type, (jl_value_t*)mi->specTypes)) { + invalidate_backedges(mi, max_world, "jl_method_table_insert"); + invalidated = 1; + } } } } if (invalidated && _jl_debug_method_invalidation) { jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)method); - if (!loctag) - loctag = jl_cstr_to_string("jl_method_table_insert"); + loctag = jl_cstr_to_string("jl_method_table_insert"); jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); } update_max_args(mt, type); @@ -1978,23 +1761,25 @@ jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t w } // return a Vector{Any} of svecs, each describing a method match: -// Any[svec(tt, spvals, m), ...] +// Any[svec(tt, spvals, m, full), ...] // tt is the intersection of the type argument and the method signature, // spvals is any matched static parameter values, m is the Method, +// full is a boolean indicating if that method fully covers the input // // 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, size_t *min_valid, size_t *max_valid) + size_t world, size_t *min_valid, size_t *max_valid, int *ambig) { JL_TIMING(METHOD_MATCH); + *ambig = 0; jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)types); if (jl_is_tuple_type(unw) && jl_tparam0(unw) == jl_bottom_type) return (jl_value_t*)jl_an_empty_vec_any; jl_methtable_t *mt = jl_method_table_for(unw); if ((jl_value_t*)mt == jl_nothing) return jl_false; // indeterminate - ml_matches can't deal with this case - return ml_matches(mt, 0, types, lim, include_ambiguous, world, min_valid, max_valid, 1); + return ml_matches(mt, 0, types, lim, include_ambiguous, 1, world, 1, min_valid, max_valid, ambig); } jl_method_instance_t *jl_get_unspecialized(jl_method_instance_t *method JL_PROPAGATES_ROOT) @@ -2141,12 +1926,13 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES // find if exactly 1 method matches (issue #7302) size_t min_valid2 = 1; size_t max_valid2 = ~(size_t)0; - jl_value_t *matches = jl_matching_methods(types, 1, 1, world, &min_valid2, &max_valid2); + int ambig = 0; + jl_value_t *matches = jl_matching_methods(types, 1, 1, world, &min_valid2, &max_valid2, &ambig); if (*min_valid < min_valid2) *min_valid = min_valid2; if (*max_valid > max_valid2) *max_valid = max_valid2; - if (matches == jl_false || jl_array_len(matches) != 1) + if (matches == jl_false || jl_array_len(matches) != 1 || ambig) return NULL; jl_tupletype_t *tt = NULL; jl_svec_t *newparams = NULL; @@ -2258,28 +2044,11 @@ JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) return 1; } -#ifndef NDEBUG -// see if a call to m with a subtype of `types` might be ambiguous -static int jl_has_call_ambiguities(jl_value_t *types, jl_method_t *m, size_t world) -{ - if (m->ambig == jl_nothing) - return 0; - for (size_t i = 0; i < jl_array_len(m->ambig); i++) { - jl_typemap_entry_t *mambig = (jl_typemap_entry_t*)jl_array_ptr_ref(m->ambig, i); - if (mambig->min_world <= world && world <= mambig->max_world) - if (!jl_has_empty_intersection((jl_value_t*)mambig->sig, types)) - return 1; - } - return 0; -} -#endif - JL_DLLEXPORT jl_value_t *jl_get_spec_lambda(jl_tupletype_t *types, size_t world, size_t *min_valid, size_t *max_valid) { jl_method_instance_t *mi = jl_get_specialization1(types, world, min_valid, max_valid, 0); if (!mi) return jl_nothing; - assert(!jl_has_call_ambiguities((jl_value_t*)types, mi->def.method, world)); return (jl_value_t*)mi; } @@ -2535,21 +2304,31 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t *F, jl_value_t **args, uint return _jl_invoke(F, args, nargs, mfunc, world); } -JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, size_t world) +static jl_value_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid) { - jl_methtable_t *mt = jl_method_table_for(types); + jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)types); + if (jl_is_tuple_type(unw) && jl_tparam0(unw) == jl_bottom_type) + return NULL; + jl_methtable_t *mt = jl_method_table_for(unw); if ((jl_value_t*)mt == jl_nothing) - return jl_nothing; + return NULL; + int ambig = 0; + jl_value_t *matches = ml_matches(mt, 0, (jl_tupletype_t*)types, 1, 0, 0, world, 1, min_valid, max_valid, &ambig); + if (matches == jl_false || jl_array_len(matches) != 1) + return NULL; + jl_value_t *matc = jl_array_ptr_ref(matches, 0); + return matc; +} +JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_value_t *types, size_t world) +{ // XXX: return min/max world - struct jl_typemap_assoc search = {(jl_value_t*)types, world, NULL, 0, ~(size_t)0}; - jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->defs, &search, /*offs*/0, /*subtype*/1); - if (entry == NULL) - return jl_nothing; - jl_typemap_entry_t *m = jl_typemap_morespecific_by_type(entry, (jl_value_t*)types, NULL, world); - if (m == NULL) + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; + jl_value_t *matc = _gf_invoke_lookup(types, world, &min_valid, &max_valid); + if (matc == NULL) return jl_nothing; - return (jl_value_t*)m; + return jl_svecref(matc, 2); } static jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value_t **args, size_t nargs); @@ -2569,9 +2348,10 @@ jl_value_t *jl_gf_invoke(jl_value_t *types0, jl_value_t *gf, jl_value_t **args, jl_value_t *types = NULL; JL_GC_PUSH1(&types); types = jl_argtype_with_function(gf, types0); - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_gf_invoke_lookup(types, world); + jl_method_t *method = (jl_method_t*)jl_gf_invoke_lookup(types, world); + JL_GC_PROMISE_ROOTED(method); - if ((jl_value_t*)entry == jl_nothing) { + if ((jl_value_t*)method == jl_nothing) { jl_method_error_bare(gf, types0, world); // unreachable } @@ -2579,8 +2359,6 @@ jl_value_t *jl_gf_invoke(jl_value_t *types0, jl_value_t *gf, jl_value_t **args, // now we have found the matching definition. // next look for or create a specialization of this definition. JL_GC_POP(); - jl_method_t *method = entry->func.method; - JL_GC_PROMISE_ROOTED(method); return jl_gf_invoke_by_method(method, gf, args, nargs); } @@ -2619,13 +2397,12 @@ static jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, j return _jl_invoke(gf, args, nargs - 1, mfunc, world); } -JL_DLLEXPORT jl_value_t *jl_get_invoke_lambda(jl_typemap_entry_t *entry, jl_value_t *tt) +JL_DLLEXPORT jl_value_t *jl_get_invoke_lambda(jl_method_t *method, jl_value_t *tt) { // TODO: refactor this method to be more like `jl_get_specialization1` if (!jl_is_datatype(tt) || !((jl_datatype_t*)tt)->isdispatchtuple) return jl_nothing; - jl_method_t *method = entry->func.method; jl_typemap_entry_t *tm = NULL; struct jl_typemap_assoc search = {(jl_value_t*)tt, 1, NULL, 0, ~(size_t)0}; if (method->invokes != NULL) { @@ -2726,23 +2503,23 @@ jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module) } struct ml_matches_env { + // inputs: struct typemap_intersection_env match; + int intersections; + size_t world; // results: - jl_value_t *t; // array of svec(argtypes, params, Method) + jl_value_t *t; // array of svec(argtypes, params, Method, fully-covers) size_t min_valid; size_t max_valid; // temporary: - jl_svec_t *matc; // current working svec - htable_t visited; - // inputs: - size_t world; - int lim; - int include_ambiguous; // whether ambiguous matches should be included + jl_svec_t *matc; // current working svec }; 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; if (closure->world < ml->min_world) { // ignore method table entries that are part of a later world if (closure->max_valid >= ml->min_world) @@ -2763,140 +2540,16 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio closure->max_valid = ml->max_world; } jl_method_t *meth = ml->func.method; - assert(meth); - // see if we've already visited this due to method table ordering - if ((jl_value_t*)meth->ambig != jl_nothing) { - // keep track that we've already visited this ambiguous method - // since we could see it again - void **visited = ptrhash_bp(&closure->visited, (void*)ml); - if (*visited != HT_NOTFOUND) - return 1; - *visited = (void*)visited; - } - else if (ptrhash_get(&closure->visited, (void*)ml) != HT_NOTFOUND) { - return 1; // already visited this (via the `resorted` or `ambig` lists) - } - // a method is shadowed if type <: S <: m->sig where S is the - // signature of another applicable method - /* - more generally, we can stop when the type is a subtype of the - union of all the signatures examined so far. - */ - int done = closure0->issubty; // stop; signature fully covers queried type - // if this method would never actually match, we shouldn't add it to the results - // in certain cases - int return_this_match = 1; - if ((jl_value_t*)meth->resorted != jl_nothing) { - // first consider adding any more specific matching methods that got put in the table later than us - jl_value_t *ti = closure->match.ti; - jl_svec_t *env = closure->match.env; - JL_GC_PUSH2(&ti, &env); - size_t j, l = jl_array_len(meth->resorted); - for (j = 0; j < l; j++) { - jl_typemap_entry_t *prior = (jl_typemap_entry_t*)jl_array_ptr_ref(meth->resorted, j); - if (ptrhash_get(&closure->visited, (void*)prior) != HT_NOTFOUND) - continue; // we've already considered this method - closure->match.env = jl_emptysvec; - closure->match.ti = jl_type_intersection_env_s((jl_value_t*)closure->match.type, - (jl_value_t*)prior->sig, &closure->match.env, &closure->match.issubty); - if (closure->match.issubty) - return_this_match = 0; - closure->match.issubty = 0; // don't return 0 recursively below - if (closure->match.ti != (jl_value_t*)jl_bottom_type) { - // also may need to check `prior` against all `meth->ambig` - if (!closure->include_ambiguous && (jl_value_t*)meth->ambig != jl_nothing) { - size_t j, l = jl_array_len(meth->ambig); - for (j = 0; j < l; j++) { - jl_typemap_entry_t *mambig = (jl_typemap_entry_t*)jl_array_ptr_ref(meth->ambig, j); - jl_value_t *sig2 = (jl_value_t*)mambig->sig; - // TODO: involve and update world - if (jl_subtype(closure->match.ti, sig2)) { - if (!jl_type_morespecific((jl_value_t*)prior->sig, (jl_value_t*)sig2)) - break; - } - } - if (j != l) - continue; // skip this match--it's not unambiguously better - } - if (!ml_matches_visitor(prior, closure0)) { - JL_GC_POP(); - return 0; // enough matches already--fast terminate - } - ptrhash_put(&closure->visited, (void*)prior, (void*)prior); // won't need to consider it again - } - } - JL_GC_POP(); - closure->match.ti = ti; - closure->match.env = env; - } - if (return_this_match && !closure->include_ambiguous && (jl_value_t*)meth->ambig != jl_nothing) { - // the current method definitely never matches if the intersection with this method - // is also fully ambiguous with another method's signature - size_t j, l = jl_array_len(meth->ambig); - for (j = 0; j < l; j++) { - jl_typemap_entry_t *mambig = (jl_typemap_entry_t*)jl_array_ptr_ref(meth->ambig, j); - jl_value_t *sig2 = (jl_value_t*)mambig->sig; - // TODO: involve and update world - if (jl_subtype(closure->match.ti, sig2)) { - return_this_match = 0; - break; - } - } - } + closure->matc = jl_svec(4, closure->match.ti, closure->match.env, meth, closure->match.issubty ? jl_true : jl_false); size_t len = jl_array_len(closure->t); - if (return_this_match && closure->lim >= 0) { - // we can skip this match if the type intersection is already fully covered - // by a prior (more specific) match. but only do this in - // the "limited" mode used by type inference. - int i; - for (i = 0; i < len; i++) { - jl_method_t *prior_match = (jl_method_t*)jl_svecref(jl_array_ptr_ref(closure->t, i), 2); - jl_value_t *sig2 = (jl_value_t*)prior_match->sig; - if (closure->include_ambiguous && (jl_value_t*)meth->ambig != jl_nothing) { - // check that prior sig is not actually just ambiguous with this--may need to include both - size_t j, l = jl_array_len(meth->ambig); - for (j = 0; j < l; j++) { - jl_typemap_entry_t *mambig = (jl_typemap_entry_t*)jl_array_ptr_ref(meth->ambig, j); - if (mambig->func.method == prior_match) - break; // continue below - } - if (j != l) - continue; // still include this match - } - if (jl_subtype(closure->match.ti, sig2)) { - return_this_match = 0; - break; - } - } + if (len == 0) { + closure->t = (jl_value_t*)jl_alloc_vec_any(1); + jl_array_ptr_set(closure->t, 0, (jl_value_t*)closure->matc); } - if (return_this_match) { - if (closure->lim >= 0 && len >= closure->lim) { - closure->t = (jl_value_t*)jl_false; - return 0; // too many matches--terminate search - } - closure->matc = jl_svec(3, closure->match.ti, closure->match.env, meth); - if (len == 0) { - closure->t = (jl_value_t*)jl_alloc_vec_any(1); - jl_array_ptr_set(closure->t, 0, (jl_value_t*)closure->matc); - } - else { - jl_array_ptr_1d_push((jl_array_t*)closure->t, (jl_value_t*)closure->matc); - } - } - if (closure->include_ambiguous && (jl_value_t*)meth->ambig != jl_nothing) { - size_t j, l = jl_array_len(meth->ambig); - for (j = 0; j < l; j++) { - jl_typemap_entry_t *prior = (jl_typemap_entry_t*)jl_array_ptr_ref(meth->ambig, j); - closure->match.env = jl_emptysvec; - closure->match.ti = jl_type_intersection_env((jl_value_t*)closure->match.type, - (jl_value_t*)prior->sig, &closure->match.env); - if (closure->match.ti != (jl_value_t*)jl_bottom_type) { - if (!ml_matches_visitor(prior, closure0)) - return 0; // enough matches already--fast terminate - } - } + else { + jl_array_ptr_1d_push((jl_array_t*)closure->t, (jl_value_t*)closure->matc); } - return !done; + return 1; } // This is the collect form of calling jl_typemap_intersection_visitor @@ -2906,8 +2559,8 @@ 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(jl_methtable_t *mt, int offs, jl_tupletype_t *type, int lim, int include_ambiguous, - size_t world, size_t *min_valid, size_t *max_valid, - int cache_result) + int intersections, size_t world, int cache_result, + size_t *min_valid, size_t *max_valid, int *has_ambiguity) { jl_typemap_t *defs = mt->defs; if (defs == jl_nothing) // special-case: ignore builtin functions @@ -2923,14 +2576,11 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, else va = NULL; } - struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va}}; + struct ml_matches_env env = {{ml_matches_visitor, (jl_value_t*)type, va}, intersections, world}; env.match.ti = NULL; env.match.env = jl_emptysvec; env.t = jl_an_empty_vec_any; env.matc = NULL; - env.lim = lim; - env.include_ambiguous = include_ambiguous; - env.world = world; env.min_valid = *min_valid; env.max_valid = *max_valid; struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec, 1, ~(size_t)0}; @@ -2955,7 +2605,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, // this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity) env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); } - env.matc = jl_svec(3, env.match.ti, env.match.env, meth); + env.matc = jl_svec(4, env.match.ti, env.match.env, meth, jl_true); env.t = (jl_value_t*)jl_alloc_vec_any(1); jl_array_ptr_set(env.t, 0, env.matc); if (*min_valid < entry->min_world) @@ -2980,7 +2630,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, // this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity) env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); } - env.matc = jl_svec(3, env.match.ti, env.match.env, meth); + env.matc = jl_svec(4, env.match.ti, env.match.env, meth, jl_true); env.t = (jl_value_t*)jl_alloc_vec_any(1); jl_array_ptr_set(env.t, 0, env.matc); *min_valid = entry->min_world; @@ -2989,26 +2639,294 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, return env.t; } } - htable_new(&env.visited, 0); - if (((jl_datatype_t*)unw)->isdispatchtuple) { - search.min_valid = env.min_valid; - search.max_valid = env.max_valid; - jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(defs, &search, offs, /*subtype*/1); - env.min_valid = search.min_valid; - env.max_valid = search.max_valid; - if (ml) { - env.match.ti = (jl_value_t*)type; - env.match.env = search.env; - env.match.issubty = 1; - env.match.fptr(ml, &env.match); // also matches all ambig and prior, as needed + jl_typemap_intersection_visitor(defs, offs, &env.match); + *min_valid = env.min_valid; + *max_valid = env.max_valid; + // done with many of these values now + env.match.ti = NULL; env.matc = NULL; env.match.env = NULL; search.env = NULL; + size_t i, j, len = jl_array_len(env.t); + 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 linear time, and the rest is O(n^2) + // - first see if this might even be profitable, given the requested output we need to compute + jl_svec_t *minmax = NULL; + int minmax_ambig = 0; + int all_subtypes = 1; + for (i = 0; i < len; i++) { + jl_svec_t *matc = (jl_svec_t*)jl_array_ptr_ref(env.t, i); + if (jl_svecref(matc, 3) != jl_true) { + all_subtypes = 0; + break; + } } + if (all_subtypes || !include_ambiguous) { + // - then find a candidate for the best of these method results + for (i = 0; i < len; i++) { + jl_svec_t *matc = (jl_svec_t*)jl_array_ptr_ref(env.t, i); + if (jl_svecref(matc, 3) == jl_true) { + jl_method_t *m = (jl_method_t*)jl_svecref(matc, 2); + if (minmax != NULL) { + jl_method_t *minmaxm = (jl_method_t*)jl_svecref(minmax, 2); + if (jl_type_morespecific((jl_value_t*)minmaxm->sig, (jl_value_t*)m->sig)) + continue; + } + minmax = matc; + } + } + // - then see if it dominated all of the other choices + if (minmax != NULL) { + for (i = 0; i < len; i++) { + jl_svec_t *matc = (jl_svec_t*)jl_array_ptr_ref(env.t, i); + if (matc == minmax) + break; + if (jl_svecref(matc, 3) == jl_true) { + jl_method_t *m = (jl_method_t*)jl_svecref(matc, 2); + jl_method_t *minmaxm = (jl_method_t*)jl_svecref(minmax, 2); + if (!jl_type_morespecific((jl_value_t*)minmaxm->sig, (jl_value_t*)m->sig)) { + minmax_ambig = 1; + *has_ambiguity = 1; + if (include_ambiguous) + minmax = NULL; + break; + } + } + } + } + // - now we might have a fast-return here, if we see that + // we've already processed all of the possible outputs + if (all_subtypes) { + if (minmax_ambig) { + if (!include_ambiguous) { + JL_GC_POP(); + return jl_an_empty_vec_any; + } + } + else { + assert(minmax != NULL); + jl_array_ptr_set(env.t, 0, minmax); + jl_array_del_end((jl_array_t*)env.t, len - 1); + JL_GC_POP(); + if (lim == 0) + return jl_false; + return env.t; + } + } + } + // need to partially domsort the graph now into a list + // (this is an insertion sort attempt) + // if we have a minmax method, we ignore anything less specific + // we'll clean that up next + for (i = 1; i < len; i++) { + env.matc = (jl_svec_t*)jl_array_ptr_ref(env.t, i); + jl_method_t *m = (jl_method_t*)jl_svecref(env.matc, 2); + if (minmax != NULL && jl_svecref(env.matc, 3) == jl_true) { + continue; // already the biggest + } + for (j = 0; j < i; j++) { + jl_value_t *matc2 = jl_array_ptr_ref(env.t, i - j - 1); + jl_method_t *m2 = (jl_method_t*)jl_svecref(matc2, 2); + if (jl_svecref(matc2, 3) != jl_true && jl_svecref(env.matc, 3) == jl_true) + break; + if (jl_svecref(matc2, 3) == jl_svecref(env.matc, 3)) + if (!jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig)) + break; + jl_array_ptr_set(env.t, i - j, matc2); + } + jl_array_ptr_set(env.t, i - j, env.matc); + } + char *skip = (char*)alloca(len); + memset(skip, 0, len); + // since we had a minmax method, now may now be able to cleanup some of our sort result + if (minmax_ambig && !include_ambiguous) { + for (i = 0; i < len; i++) { + jl_svec_t *matc = (jl_svec_t*)jl_array_ptr_ref(env.t, i); + if (jl_svecref(matc, 3) == jl_true) { + skip[i] = 1; + } + } + } + else if (minmax != NULL) { + assert(all_subtypes || !include_ambiguous); + for (i = 0; i < len; i++) { + jl_svec_t *matc = (jl_svec_t*)jl_array_ptr_ref(env.t, i); + if (minmax != matc && jl_svecref(matc, 3) == jl_true) { + skip[i] = 1; + } + } + } + // now that the results are (mostly) sorted, assign group numbers to each ambiguity + // by computing the specificity-ambiguity matrix covering this query + uint32_t *ambig_groupid = (uint32_t*)alloca(len * sizeof(uint32_t)); + // as we go, keep a rough count of how many methods are disjoint, which + // gives us a lower bound on how many methods we will be returning + // and lets us stop early if we reach our limit + int ndisjoint = 0; + for (i = 0; i < len; i++) { + // can't use skip[i] here, since we still need to make sure the minmax dominates + jl_value_t *matc = jl_array_ptr_ref(env.t, i); + jl_method_t *m = (jl_method_t*)jl_svecref(matc, 2); + int subt = jl_svecref(matc, 3) == jl_true; // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) + ambig_groupid[i] = i; + int disjoint = 1; + for (j = i; j > 0; j--) { + jl_value_t *matc2 = jl_array_ptr_ref(env.t, j - 1); + jl_method_t *m2 = (jl_method_t*)jl_svecref(matc2, 2); + int subt2 = jl_svecref(matc2, 3) == jl_true; // jl_subtype((jl_value_t*)type, (jl_value_t*)m2->sig) + if (skip[j - 1]) { + // if there was a minmax method, we can just pretend these are all in the same group + // they're all together but unsorted in the list, since we'll drop them all later anyways + assert(subt2); + disjoint = 0; + ambig_groupid[i] = j - 1; // ambiguity covering range [i:j) + } + else if (subt || subt2 || !jl_has_empty_intersection(m->sig, m2->sig)) { + if (!jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) + ambig_groupid[i] = j - 1; // ambiguity covering range [i:j) + disjoint = 0; + } + } + if (disjoint && lim >= 0) { + ndisjoint += 1; + if (ndisjoint > lim) { + JL_GC_POP(); + return jl_false; + } + } + } + // then we'll merge those numbers to assign each item in the group the same number + uint32_t groupid = 0; + uint32_t grouphi = 0; + for (i = 0; i < len; i++) { + j = len - i - 1; + uint32_t agid = ambig_groupid[j]; + if (agid != j) { // thus agid < j + if (grouphi == 0) { + groupid = agid; + grouphi = j; + } + else if (agid < groupid) { + groupid = agid; + } + } + if (grouphi && j == groupid) { + do { + ambig_groupid[grouphi--] = groupid; + } while (grouphi > j); + ambig_groupid[j] = groupid; + groupid = 0; + grouphi = 0; + } + } + // always remove matches after the first subtype, now that we've sorted the list for ambiguities + for (i = 0; i < len; i++) { + jl_value_t *matc = jl_array_ptr_ref(env.t, i); + if (jl_svecref(matc, 3) == jl_true) { // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) + uint32_t agid = ambig_groupid[i]; + while (i < len && agid == ambig_groupid[i]) + i++; // keep ambiguous ones + for (; i < len; i++) + skip[i] = 1; // drop the rest + } + } + // when limited, skip matches that are covered by earlier ones (and aren't perhaps ambiguous with them) + if (lim >= 0) { + for (i = 0; i < len; i++) { + if (skip[i]) + continue; + jl_value_t *matc = jl_array_ptr_ref(env.t, i); + jl_method_t *m = (jl_method_t*)jl_svecref(matc, 2); + jl_value_t *ti = jl_svecref(matc, 0); + if (jl_svecref(matc, 3) == jl_true) + break; // remaining matches are ambiguous or already skipped + for (j = 0; j < i; j++) { + jl_value_t *matc2 = jl_array_ptr_ref(env.t, j); + jl_method_t *m2 = (jl_method_t*)jl_svecref(matc2, 2); + if (jl_subtype(ti, m2->sig)) { + if (ambig_groupid[i] != ambig_groupid[j]) { + skip[i] = 1; + break; + } + else if (!include_ambiguous) { + if (!jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig)) { + skip[i] = 1; + break; + } + } + } + } + } + } + // Compute whether anything could be ambiguous by seeing if any two + // methods in the result are in the same ambiguity group. + for (i = 0; i < len; i++) { + if (!skip[i]) { + uint32_t agid = ambig_groupid[i]; + for (; i < len; i++) { + if (!skip[i]) { + if (agid == ambig_groupid[i]) { + *has_ambiguity = 1; + break; + } + agid = ambig_groupid[i]; + } + } + break; + } + } + // If we're only returning possible matches, now filter out any method + // whose intersection is fully ambiguous with the group it is in. + if (!include_ambiguous) { + for (i = 0; i < len; i++) { + if (skip[i]) + continue; + uint32_t agid = ambig_groupid[i]; + jl_value_t *matc = jl_array_ptr_ref(env.t, i); + jl_method_t *m = (jl_method_t*)jl_svecref(matc, 2); + jl_value_t *ti = jl_svecref(matc, 0); + int subt = jl_svecref(matc, 3) == jl_true; // jl_subtype((jl_value_t*)type, (jl_value_t*)m->sig) + char ambig1 = 0; + for (j = agid; j < len && ambig_groupid[j] == agid; j++) { + if (j == i) + continue; + jl_value_t *matc2 = jl_array_ptr_ref(env.t, j); + jl_method_t *m2 = (jl_method_t*)jl_svecref(matc2, 2); + int subt2 = jl_svecref(matc2, 3) == jl_true; // jl_subtype((jl_value_t*)type, (jl_value_t*)m2->sig) + // if their intersection contributes to the ambiguity cycle + if (subt || subt2 || !jl_has_empty_intersection(ti, m2->sig)) { + // and the contribution of m is ambiguous with the portion of the cycle from m2 + if (subt2 || jl_subtype(ti, m2->sig)) { + // but they aren't themselves simply ordered (here + // we don't consider that a third method might be + // disrupting that ordering and just consider them + // pairwise to keep this simple). + if (!jl_type_morespecific((jl_value_t*)m->sig, (jl_value_t*)m2->sig) && + !jl_type_morespecific((jl_value_t*)m2->sig, (jl_value_t*)m->sig)) { + ambig1 = 1; + } + } + else { + // otherwise some aspect of m is not ambiguous + ambig1 = 0; + break; + } + } + } + if (ambig1) + skip[i] = 1; + } + } + // cleanup array to remove skipped entries + for (i = 0, j = 0; i < len; i++) { + jl_value_t *matc = jl_array_ptr_ref(env.t, i); + if (!skip[i]) + jl_array_ptr_set(env.t, j++, matc); + } + if (j != len) + jl_array_del_end((jl_array_t*)env.t, len - j); + len = j; } - else { - jl_typemap_intersection_visitor(defs, offs, &env.match); - } - htable_free(&env.visited); if (cache_result && ((jl_datatype_t*)unw)->isdispatchtuple) { // cache_result parameter keeps this from being recursive - if (env.t != jl_false && jl_array_len(env.t) == 1) { + if (len == 1 && !*has_ambiguity) { env.matc = (jl_svec_t*)jl_array_ptr_ref(env.t, 0); jl_method_t *meth = (jl_method_t*)jl_svecref(env.matc, 2); jl_svec_t *tpenv = (jl_svec_t*)jl_svecref(env.matc, 1); @@ -3016,8 +2934,8 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, } } JL_GC_POP(); - *min_valid = env.min_valid; - *max_valid = env.max_valid; + if (lim >= 0 && len > lim) + return jl_false; return env.t; } diff --git a/src/jltypes.c b/src/jltypes.c index 64a6fb804ffa9..c2756d2a47f73 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2280,7 +2280,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_method_type = jl_new_datatype(jl_symbol("Method"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(24, + jl_perm_symsvec(22, "name", "module", "file", @@ -2288,8 +2288,6 @@ void jl_init_types(void) JL_GC_DISABLED "primary_world", "deleted_world", "sig", - "ambig", - "resorted", "specializations", "speckeyset", "slot_syms", @@ -2305,7 +2303,7 @@ void jl_init_types(void) JL_GC_DISABLED "nkw", "isva", "pure"), - jl_svec(24, + jl_svec(22, jl_symbol_type, jl_module_type, jl_symbol_type, @@ -2313,8 +2311,6 @@ void jl_init_types(void) JL_GC_DISABLED jl_ulong_type, jl_ulong_type, jl_type_type, - jl_any_type, // Union{Vector, Nothing} - jl_any_type, // Union{Vector, Nothing} jl_simplevector_type, jl_array_type, jl_string_type, @@ -2330,7 +2326,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_int32_type, jl_bool_type, jl_bool_type), - 0, 1, 12); + 0, 1, 10); jl_method_instance_type = jl_new_datatype(jl_symbol("MethodInstance"), core, @@ -2497,7 +2493,7 @@ void jl_init_types(void) JL_GC_DISABLED #endif jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); jl_svecset(jl_methtable_type->types, 11, jl_uint8_type); - jl_svecset(jl_method_type->types, 13, jl_method_instance_type); + jl_svecset(jl_method_type->types, 11, jl_method_instance_type); jl_svecset(jl_method_instance_type->types, 5, jl_code_instance_type); jl_svecset(jl_code_instance_type->types, 9, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 10, jl_voidpointer_type); diff --git a/src/julia.h b/src/julia.h index 86f7d7a0380dd..ccfcb3c096e25 100644 --- a/src/julia.h +++ b/src/julia.h @@ -284,11 +284,6 @@ typedef struct _jl_method_t { // method's type signature. redundant with TypeMapEntry->specTypes jl_value_t *sig; - // list of potentially-ambiguous methods (nothing = none, Vector{Any} of TypeMapEntry otherwise) - jl_value_t *ambig; - // forward references to later items (typemap entries) which might sort before this one - jl_value_t *resorted; - // table of all jl_method_instance_t specializations we have jl_svec_t *specializations; // allocated as [hashable, ..., NULL, linear, ....] jl_array_t *speckeyset; // index lookup by hash into specializations diff --git a/src/julia_internal.h b/src/julia_internal.h index 313a9538bc302..e8fc86afa84bb 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -489,7 +489,7 @@ jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t w jl_value_t *jl_gf_invoke(jl_value_t *types, jl_value_t *f, jl_value_t **args, size_t nargs); jl_method_instance_t *jl_lookup_generic(jl_value_t **args, uint32_t nargs, uint32_t callsite, size_t world) JL_ALWAYS_LEAFTYPE; 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); + size_t world, size_t *min_valid, size_t *max_valid, int *ambig); JL_DLLEXPORT jl_datatype_t *jl_first_argument_datatype(jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_argument_datatype(jl_value_t *argt JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; diff --git a/src/method.c b/src/method.c index 4d9ab188c960d..0cec9861bda49 100644 --- a/src/method.c +++ b/src/method.c @@ -609,8 +609,6 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->speckeyset = (jl_array_t*)jl_an_empty_vec_any; m->sig = NULL; m->slot_syms = NULL; - m->ambig = jl_nothing; - m->resorted = jl_nothing; m->roots = NULL; m->ccallable = NULL; m->module = module; diff --git a/stdlib/LinearAlgebra/src/symmetric.jl b/stdlib/LinearAlgebra/src/symmetric.jl index 082ea68e4a48b..6e9495821245f 100644 --- a/stdlib/LinearAlgebra/src/symmetric.jl +++ b/stdlib/LinearAlgebra/src/symmetric.jl @@ -268,11 +268,11 @@ end Array(A::Union{Symmetric,Hermitian}) = convert(Matrix, A) parent(A::HermOrSym) = A.data -Symmetric{T,S}(A::Symmetric{T,S}) where {T,S<:AbstractMatrix} = A -Symmetric{T,S}(A::Symmetric) where {T,S<:AbstractMatrix} = Symmetric{T,S}(convert(S,A.data),A.uplo) +Symmetric{T,S}(A::Symmetric{T,S}) where {T,S<:AbstractMatrix{T}} = A +Symmetric{T,S}(A::Symmetric) where {T,S<:AbstractMatrix{T}} = Symmetric{T,S}(convert(S,A.data),A.uplo) AbstractMatrix{T}(A::Symmetric) where {T} = Symmetric(convert(AbstractMatrix{T}, A.data), sym_uplo(A.uplo)) -Hermitian{T,S}(A::Hermitian{T,S}) where {T,S<:AbstractMatrix} = A -Hermitian{T,S}(A::Hermitian) where {T,S<:AbstractMatrix} = Hermitian{T,S}(convert(S,A.data),A.uplo) +Hermitian{T,S}(A::Hermitian{T,S}) where {T,S<:AbstractMatrix{T}} = A +Hermitian{T,S}(A::Hermitian) where {T,S<:AbstractMatrix{T}} = Hermitian{T,S}(convert(S,A.data),A.uplo) AbstractMatrix{T}(A::Hermitian) where {T} = Hermitian(convert(AbstractMatrix{T}, A.data), sym_uplo(A.uplo)) copy(A::Symmetric{T,S}) where {T,S} = (B = copy(A.data); Symmetric{T,typeof(B)}(B,A.uplo)) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index d37bfd7f3ad7e..f8c5508d89637 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -405,15 +405,14 @@ end let s = "(1, CompletionFoo.test2(`')'`," c, r, res = test_complete(s) - @test c[1] == string(first(methods(Main.CompletionFoo.test2, Tuple{Cmd}))) @test length(c) == 1 + @test c[1] == string(first(methods(Main.CompletionFoo.test2, Tuple{Cmd}))) end let s = "CompletionFoo.test3([1, 2] .+ CompletionFoo.varfloat," c, r, res = test_complete(s) @test !res - @test_broken c[1] == string(first(methods(Main.CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64}))) - @test_broken length(c) == 1 + @test_broken only(c) == string(first(methods(Main.CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64}))) end let s = "CompletionFoo.test3([1.,2.], 1.," @@ -439,8 +438,7 @@ end let s = "CompletionFoo.test5(broadcast((x,y)->x==y, push!(Base.split(\"\",' '),\"\",\"\"), \"\")," c, r, res = test_complete(s) @test !res - @test_broken length(c) == 1 - @test_broken c[1] == string(first(methods(Main.CompletionFoo.test5, Tuple{BitArray{1}}))) + @test_broken only(c) == string(first(methods(Main.CompletionFoo.test5, Tuple{BitArray{1}}))) end # test partial expression expansion diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 1a6ea56ba6105..f26cf247a4442 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1441,11 +1441,12 @@ function detect_ambiguities(mods...; elseif isa(f, DataType) && isdefined(f.name, :mt) && f.name.mt !== Symbol.name.mt mt = Base.MethodList(f.name.mt) for m in mt - if m.ambig !== nothing - for m2 in m.ambig - if Base.isambiguous(m, m2.func, ambiguous_bottom=ambiguous_bottom) - push!(ambs, sortdefs(m, m2.func)) - end + ambig = Int32[0] + for m2 in Base._methods_by_ftype(m.sig, -1, typemax(UInt), true, UInt[typemin(UInt)], UInt[typemax(UInt)], ambig) + ambig[1] == 0 && break + m2 = m2[3] + if Base.isambiguous(m, m2, ambiguous_bottom=ambiguous_bottom) + push!(ambs, sortdefs(m, m2)) end end end @@ -1458,14 +1459,19 @@ function detect_ambiguities(mods...; recursive || return false p = parentmodule(m) p === m && return false - m = parent + m = p end end - for m in Base.MethodList(Symbol.name.mt) - if m.ambig !== nothing && is_in_mods(m.module) - for m2 in m.ambig - if Base.isambiguous(m, m2.func, ambiguous_bottom=ambiguous_bottom) - push!(ambs, sortdefs(m, m2.func)) + let mt = Base.MethodList(Symbol.name.mt) + for m in mt + if is_in_mods(m.module) + ambig = Int32[0] + for m2 in Base._methods_by_ftype(m.sig, -1, typemax(UInt), true, UInt[typemin(UInt)], UInt[typemax(UInt)], ambig) + ambig[1] == 0 && break + m2 = m2[3] + if Base.isambiguous(m, m2, ambiguous_bottom=ambiguous_bottom) + push!(ambs, sortdefs(m, m2)) + end end end end @@ -1505,7 +1511,7 @@ function detect_unbound_args(mods...; params = tuple_sig.parameters[1:(end - 1)] tuple_sig = Base.rewrap_unionall(Tuple{params...}, m.sig) mf = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tuple_sig, typemax(UInt)) - if mf !== nothing && mf.func !== m && mf.func.sig <: tuple_sig + if mf !== nothing && mf !== m && mf.sig <: tuple_sig continue end end diff --git a/test/ambiguous.jl b/test/ambiguous.jl index c9f2d74d9fbdb..5822d6b35c251 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -19,17 +19,17 @@ include("testenv.jl") getline(m::Core.TypeMapEntry) = getline(m.func::Method) getline(m::Method) = m.line - lineoffset -ambigs = Any[[], [3], [2, 5], [], [3]] -for m in methods(ambig) - ln = getline(m) - atarget = ambigs[ln] - if isempty(atarget) - @test m.ambig === nothing - else - aln = Int[getline(a::Core.TypeMapEntry) for a in m.ambig] - @test sort(aln) == atarget - end -end +#ambigs = Any[[], [3], [2, 5], [], [3]] +#for m in methods(ambig) +# ln = getline(m) +# atarget = ambigs[ln] +# if isempty(atarget) +# @test m.ambig === nothing +# else +# aln = Int[getline(a::Core.TypeMapEntry) for a in m.ambig] +# @test sort(aln) == atarget +# end +#end @test length(methods(ambig)) == 5 @test length(Base.methods_including_ambiguous(ambig, Tuple)) == 5 @@ -265,7 +265,9 @@ end @test isempty(methods(Ambig8.f, (Int,))) @test isempty(methods(Ambig8.g, (Int,))) for f in (Ambig8.f, Ambig8.g) - @test length(methods(f, (Integer,))) == 2 + @test length(methods(f, (Integer,))) == 2 # 1 is also acceptable + @test length(methods(f, (Signed,))) == 1 # 2 is also acceptable + @test length(Base.methods_including_ambiguous(f, (Signed,))) == 3 @test f(0x00) == 1 @test f(Ambig8.Irrational2()) == 2 @test f(MathConstants.γ) == 3 diff --git a/test/errorshow.jl b/test/errorshow.jl index 21a78d27a62b6..ae250a8bb5220 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -86,7 +86,7 @@ method_c2(x::Int32, y::Int32, z::Int32) = true method_c2(x::T, y::T, z::T) where {T<:Real} = true Base.show_method_candidates(buf, Base.MethodError(method_c2,(1., 1., 2))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c2(!Matched::Int32, ::Float64, ::Any...)$cfile$(c2line+2)\n method_c2(!Matched::Int32, ::Any...)$cfile$(c2line+1)\n method_c2(::T, ::T, !Matched::T) where T<:Real$cfile$(c2line+5)\n ..." +@test String(take!(buf)) == "\nClosest candidates are:\n method_c2(::T, ::T, !Matched::T) where T<:Real$cfile$(c2line+5)\n method_c2(!Matched::Int32, ::Float64, ::Any...)$cfile$(c2line+2)\n method_c2(!Matched::Int32, ::Any...)$cfile$(c2line+1)\n ..." c3line = @__LINE__() + 1 method_c3(x::Float64, y::Float64) = true diff --git a/test/reflection.jl b/test/reflection.jl index 593965e01b78d..5249524e18b48 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -746,16 +746,12 @@ Base.delete_method(m) foo(::Int, ::Int) = 1 foo(::Real, ::Int) = 2 foo(::Int, ::Real) = 3 -@test all(map(g->g.ambig==nothing, methods(foo))) Base.delete_method(first(methods(foo))) -@test !all(map(g->g.ambig==nothing, methods(foo))) @test_throws MethodError foo(1, 1) foo(::Int, ::Int) = 1 foo(1, 1) -@test map(g->g.ambig==nothing, methods(foo)) == [true, false, false] Base.delete_method(first(methods(foo))) @test_throws MethodError foo(1, 1) -@test map(g->g.ambig==nothing, methods(foo)) == [false, false] # multiple deletions and ambiguities typeparam(::Type{T}, a::Array{T}) where T<:AbstractFloat = 1 diff --git a/test/worlds.jl b/test/worlds.jl index 878dd24adadb4..24783dc66b3fa 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -274,13 +274,12 @@ applyf35855(Any[1]) @test worlds(instance(applyf35855, (Vector{Float64},))) == wfloat wany3 = worlds(instance(applyf35855, (Vector{Any},))) src3 = code_typed(applyf35855, (Vector{Any},))[1] -@test (wany3 == wany2) == equal(src3, src2) # don't invalidate unless you also change the code +@test !(wany3 == wany2) || equal(src3, src2) # code doesn't change unless you invalidate f35855(::AbstractVector) = 4 applyf35855(Any[1]) wany4 = worlds(instance(applyf35855, (Vector{Any},))) src4 = code_typed(applyf35855, (Vector{Any},))[1] -# this passes when max_methods == 3, fails when set to 4 -@test (wany4 == wany3) == equal(src4, src3) +@test !(wany4 == wany3) || equal(src4, src3) # code doesn't change unless you invalidate f35855(::Dict) = 5 applyf35855(Any[1]) wany5 = worlds(instance(applyf35855, (Vector{Any},))) @@ -290,7 +289,8 @@ f35855(::Set) = 6 # with current settings, this shouldn't invalidate applyf35855(Any[1]) wany6 = worlds(instance(applyf35855, (Vector{Any},))) src6 = code_typed(applyf35855, (Vector{Any},))[1] -@test (wany6 == wany5) == equal(src6, src5) +@test wany6 == wany5 +@test equal(src6, src5) applyf35855_2(c) = f35855_2(c[1]) f35855_2(::Int) = 1 @@ -298,11 +298,11 @@ f35855_2(::Float64) = 2 applyf35855_2(Any[1]) wany3 = worlds(instance(applyf35855_2, (Vector{Any},))) src3 = code_typed(applyf35855_2, (Vector{Any},))[1] -f35855_2(::AbstractVector) = 4 # next test would pass if this were ::Vector{Int} +f35855_2(::AbstractVector) = 4 applyf35855_2(Any[1]) wany4 = worlds(instance(applyf35855_2, (Vector{Any},))) src4 = code_typed(applyf35855_2, (Vector{Any},))[1] -@test_broken (wany4 == wany3) == equal(src4, src3) +@test !(wany4 == wany3) || equal(src4, src3) # code doesn't change unless you invalidate ## ambiguities do not trigger invalidation using Printf