From d19ca8c9a6657bf2f012ed2bf2e3a03730418d4b Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 3 Oct 2017 11:35:59 -0400 Subject: [PATCH 1/5] rtutils: improve static show of method signatures --- src/rtutils.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/rtutils.c b/src/rtutils.c index f5fa5bb2a6d58..e81cc2d2a6035 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -538,14 +538,12 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt else if (vt == jl_method_instance_type) { jl_method_instance_t *li = (jl_method_instance_t*)v; if (jl_is_method(li->def.method)) { - jl_method_t *m = li->def.method; - n += jl_static_show_x(out, (jl_value_t*)m->module, depth); if (li->specTypes) { - n += jl_printf(out, "."); - n += jl_show_svec(out, ((jl_datatype_t*)jl_unwrap_unionall(li->specTypes))->parameters, - jl_symbol_name(m->name), "(", ")"); + n += jl_static_show_func_sig(out, li->specTypes); } else { + jl_method_t *m = li->def.method; + n += jl_static_show_x(out, (jl_value_t*)m->module, depth); n += jl_printf(out, ".%s(?)", jl_symbol_name(m->name)); } } @@ -949,7 +947,7 @@ JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) if (ftype == NULL) return jl_static_show(s, type); size_t n = 0; - if (jl_nparams(ftype)==0 || ftype == ((jl_datatype_t*)ftype)->name->wrapper) { + if (jl_nparams(ftype) == 0 || ftype == ((jl_datatype_t*)ftype)->name->wrapper) { n += jl_printf(s, "%s", jl_symbol_name(((jl_datatype_t*)ftype)->name->mt->name)); } else { @@ -957,7 +955,7 @@ JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) n += jl_static_show(s, ftype); n += jl_printf(s, ")"); } - // TODO: better way to show method parameters + jl_unionall_t *tvars = (jl_unionall_t*)type; type = jl_unwrap_unionall(type); if (!jl_is_datatype(type)) { n += jl_printf(s, " "); @@ -984,6 +982,19 @@ JL_DLLEXPORT size_t jl_static_show_func_sig(JL_STREAM *s, jl_value_t *type) } } n += jl_printf(s, ")"); + if (jl_is_unionall(tvars)) { + int first = 1; + n += jl_printf(s, " where {"); + while (jl_is_unionall(tvars)) { + if (first) + first = 0; + else + n += jl_printf(s, ", "); + n += jl_static_show(s, (jl_value_t*)tvars->var); + tvars = (jl_unionall_t*)tvars->body; + } + n += jl_printf(s, "}"); + } return n; } From fe39f10cc4f4598b5d18f8417bce37f20fb59f4b Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 27 Sep 2017 18:30:42 -0400 Subject: [PATCH 2/5] inference: significantly increase the set of cases permitted before triggering recursion detection This is generally sufficient to ensure we can infer "bottleneck"-type functions, without significantly sacrificing our convergence requirements --- base/inference.jl | 24 +++++++++++++++++++++++- test/core.jl | 4 +++- test/inference.jl | 8 ++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 145299a55b2e6..fd6231349f3af 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1799,7 +1799,29 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si topmost = nothing break end - topmost === nothing && (topmost = infstate) + if topmost === nothing + # inspect the parent of this edge, + # to see if they are the same Method as sv + # in which case we'll need to ensure it is convergent + # otherwise, we don't + for parent in infstate.callers_in_cycle + # check in the cycle list first + # all items in here are mutual parents of all others + if parent.linfo.def === sv.linfo.def + topmost = infstate + break + end + end + let parent = infstate.parent + # then check the parent link + if topmost === nothing && parent !== nothing + parent = parent::InferenceState + if parent.cached && parent.linfo.def === sv.linfo.def + topmost = infstate + end + end + end + end end # iterate through the cycle before walking to the parent if cyclei < length(infstate.callers_in_cycle) diff --git a/test/core.jl b/test/core.jl index bb2c2a9ddcff7..60f27aaf6e3e2 100644 --- a/test/core.jl +++ b/test/core.jl @@ -5317,7 +5317,8 @@ module UnionOptimizations using Test const boxedunions = [Union{}, Union{String, Void}] -const unboxedunions = [Union{Int8, Void}, Union{Int8, Float16, Void}, +const unboxedunions = [Union{Int8, Void}, + Union{Int8, Float16, Void}, Union{Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128}, Union{Char, Date, Int}] @@ -5443,6 +5444,7 @@ t4 = vcat(A23567, t2, t3) @test t4[11:15] == A23567 for U in unboxedunions + Base.unionlen(U) > 5 && continue # larger values cause subtyping to crash local U for N in (1, 2, 3, 4) A = Array{U}(ntuple(x->0, N)...) diff --git a/test/inference.jl b/test/inference.jl index 1642878136df1..a0781521d3226 100644 --- a/test/inference.jl +++ b/test/inference.jl @@ -980,13 +980,13 @@ copy_dims_out(out) = () copy_dims_out(out, dim::Int, tail...) = copy_dims_out((out..., dim), tail...) copy_dims_out(out, dim::Colon, tail...) = copy_dims_out((out..., dim), tail...) @test Base.return_types(copy_dims_out, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] -@test all(m -> 2 < count_specializations(m) < 15, methods(copy_dims_out)) +@test all(m -> 15 < count_specializations(m) < 45, methods(copy_dims_out)) copy_dims_pair(out) = () -copy_dims_pair(out, dim::Int, tail...) = copy_dims_out(out => dim, tail...) -copy_dims_pair(out, dim::Colon, tail...) = copy_dims_out(out => dim, tail...) +copy_dims_pair(out, dim::Int, tail...) = copy_dims_pair(out => dim, tail...) +copy_dims_pair(out, dim::Colon, tail...) = copy_dims_pair(out => dim, tail...) @test Base.return_types(copy_dims_pair, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] -@test all(m -> 5 < count_specializations(m) < 25, methods(copy_dims_out)) +@test all(m -> 10 < count_specializations(m) < 35, methods(copy_dims_pair)) # splatting an ::Any should still allow inference to use types of parameters preceding it f22364(::Int, ::Any...) = 0 From a005fd742b459fbe0f12d11c64226363799eb7ac Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 27 Sep 2017 22:05:38 -0400 Subject: [PATCH 3/5] inference: remove hand-written heuristics These should now be covered by the improved recursion detector. --- base/inference.jl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index fd6231349f3af..57dc2e7b84126 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1772,15 +1772,7 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si sigtuple = unwrap_unionall(sig)::DataType tm = _topmod(sv) - if (# promote_typeof signature may be used with many arguments - !istopfunction(tm, f, :promote_typeof) - # assume getindex methods aren't directly recursive, since wrappers like ReshapedArrays won't look like it here - # should still manage to detect recursive growth either via other intermediate methods or actual type-equal signature recursion - && !istopfunction(tm, f, :getindex) - && !istopfunction(tm, f, :setindex!) - # the construct-to-convert method is a bottleneck in inference, - # so just assume that recursion will get prevented at some other point - && !(method.sig == Tuple{Type, Any})) + if (!istopfunction(tm, f, :promote_typeof)) # promote_typeof signature may be used with many arguments, here we'll just assume it is defined non-recursively # otherwise: limit argument type tuple growth of all other functions msig = unwrap_unionall(method.sig) lsig = length(msig.parameters) From 813525cd3703f3d2bf9c029ddd70bb7fe44c0cc8 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 3 Oct 2017 11:35:37 -0400 Subject: [PATCH 4/5] inference: redesign call complexity limiter --- base/array.jl | 20 +++- base/dict.jl | 4 +- base/inference.jl | 234 +++++++++++++++++++++++++++++----------------- base/reduce.jl | 2 +- base/set.jl | 4 +- test/inference.jl | 4 +- 6 files changed, 170 insertions(+), 98 deletions(-) diff --git a/base/array.jl b/base/array.jl index bdd2eaad85c41..f8c7fd9c5ab89 100644 --- a/base/array.jl +++ b/base/array.jl @@ -628,10 +628,20 @@ function _collect_indices(indsA, A) copy!(B, CartesianRange(indices(B)), A, CartesianRange(indsA)) end +# define this as a macro so that the call to Inference +# gets inlined into the caller before recursion detection +# gets a chance to see it, so that recursive calls to the caller +# don't trigger the inference limiter if isdefined(Core, :Inference) - _default_eltype(@nospecialize itrt) = Core.Inference.return_type(first, Tuple{itrt}) + macro default_eltype(itrt) + return quote + Core.Inference.return_type(first, Tuple{$(esc(itrt))}) + end + end else - _default_eltype(@nospecialize itr) = Any + macro default_eltype(itrt) + return :(Any) + end end _array_for(::Type{T}, itr, ::HasLength) where {T} = Array{T,1}(Int(length(itr)::Integer)) @@ -639,7 +649,7 @@ _array_for(::Type{T}, itr, ::HasShape) where {T} = similar(Array{T}, indices(itr function collect(itr::Generator) isz = iteratorsize(itr.iter) - et = _default_eltype(typeof(itr)) + et = @default_eltype(typeof(itr)) if isa(isz, SizeUnknown) return grow_to!(Array{et,1}(0), itr) else @@ -653,12 +663,12 @@ function collect(itr::Generator) end _collect(c, itr, ::EltypeUnknown, isz::SizeUnknown) = - grow_to!(_similar_for(c, _default_eltype(typeof(itr)), itr, isz), itr) + grow_to!(_similar_for(c, @default_eltype(typeof(itr)), itr, isz), itr) function _collect(c, itr, ::EltypeUnknown, isz::Union{HasLength,HasShape}) st = start(itr) if done(itr,st) - return _similar_for(c, _default_eltype(typeof(itr)), itr, isz) + return _similar_for(c, @default_eltype(typeof(itr)), itr, isz) end v1, st = next(itr, st) collect_to_with_first!(_similar_for(c, typeof(v1), itr, isz), v1, itr, st) diff --git a/base/dict.jl b/base/dict.jl index 16533ac854738..67f8fbfd98d34 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -158,9 +158,9 @@ associative_with_eltype(DT_apply, kv, ::TP{K,V}) where {K,V} = DT_apply(K, V)(kv associative_with_eltype(DT_apply, kv::Generator, ::TP{K,V}) where {K,V} = DT_apply(K, V)(kv) associative_with_eltype(DT_apply, ::Type{Pair{K,V}}) where {K,V} = DT_apply(K, V)() associative_with_eltype(DT_apply, ::Type) = DT_apply(Any, Any)() -associative_with_eltype(DT_apply::F, kv, t) where {F} = grow_to!(associative_with_eltype(DT_apply, _default_eltype(typeof(kv))), kv) +associative_with_eltype(DT_apply::F, kv, t) where {F} = grow_to!(associative_with_eltype(DT_apply, @default_eltype(typeof(kv))), kv) function associative_with_eltype(DT_apply::F, kv::Generator, t) where F - T = _default_eltype(typeof(kv)) + T = @default_eltype(typeof(kv)) if T <: Union{Pair, Tuple{Any, Any}} && _isleaftype(T) return associative_with_eltype(DT_apply, kv, T) end diff --git a/base/inference.jl b/base/inference.jl index 57dc2e7b84126..eb7adac3058ba 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -152,6 +152,7 @@ mutable struct InferenceState # TODO: put these in InferenceParams (depends on proper multi-methodcache support) optimize::Bool cached::Bool + limited::Bool inferred::Bool @@ -201,7 +202,7 @@ mutable struct InferenceState end vararg_type = Tuple else - vararg_type_container = limit_tuple_depth(params, tupletype_tail(atypes, la)) + vararg_type_container = limit_tuple_depth(params, tupleparam_tail(atypes.parameters, la)) vararg_type = tuple_tfunc(vararg_type_container) # returns a Const object, if applicable vararg_type = rewrap(vararg_type, linfo.specTypes) end @@ -231,7 +232,7 @@ mutable struct InferenceState # replace singleton types with their equivalent Const object atyp = Const(atyp.instance) elseif isconstType(atyp) - atype = Const(atyp.parameters[1]) + atyp = Const(atyp.parameters[1]) else atyp = rewrap_unionall(atyp, linfo.specTypes) end @@ -282,7 +283,7 @@ mutable struct InferenceState Vector{Tuple{InferenceState,LineNum}}(), # backedges Vector{InferenceState}(), # callers_in_cycle #=parent=#nothing, - false, false, optimize, cached, false, false) + false, false, optimize, cached, false, false, false) return frame end end @@ -314,9 +315,13 @@ end function print_callstack(sv::InferenceState) while sv !== nothing - println(sv.linfo) + print(sv.linfo) + sv.limited && print(" [limited]") + println() for cycle in sv.callers_in_cycle - println(' ', cycle.linfo) + print(' ', cycle.linfo) + cycle.limited && print(" [limited]") + println() end sv = sv.parent end @@ -391,7 +396,18 @@ isknownlength(t::DataType) = !isvatuple(t) || (length(t.parameters) > 0 && isa(unwrap_unionall(t.parameters[end]).parameters[2],Int)) # t[n:end] -tupletype_tail(@nospecialize(t), n) = Tuple{t.parameters[n:end]...} +function tupleparam_tail(t::SimpleVector, n) + lt = length(t) + if n > lt + va = t[lt] + if isvarargtype(va) + # assumes that we should never see Vararg{T, x}, where x is a constant (should be guaranteed by construction) + return Tuple{va} + end + return Tuple{} + end + return Tuple{t[n:lt]...} +end function is_specializable_vararg_slot(arg, sv::InferenceState) return (isa(arg, Slot) && slot_id(arg) == sv.nargs && @@ -889,11 +905,12 @@ end # limit the complexity of type `t` to be simpler than the comparison type `compare` # no new values may be introduced, so the parameter `source` encodes the set of all values already present -function limit_type_size(@nospecialize(t), @nospecialize(compare), @nospecialize(source)) +# the outermost tuple type is permitted to have up to `allowed_tuplelen` parameters +function limit_type_size(@nospecialize(t), @nospecialize(compare), @nospecialize(source), allowed_tuplelen::Int) source = svec(unwrap_unionall(compare), unwrap_unionall(source)) source[1] === source[2] && (source = svec(source[1])) - type_more_complex(t, compare, source, TUPLE_COMPLEXITY_LIMIT_DEPTH) || return t - r = _limit_type_size(t, compare, source) + type_more_complex(t, compare, source, TUPLE_COMPLEXITY_LIMIT_DEPTH, allowed_tuplelen) || return t + r = _limit_type_size(t, compare, source, allowed_tuplelen) @assert t <: r #@assert r === _limit_type_size(r, t, source) # this monotonicity constraint is slightly stronger than actually required, # since we only actually need to demonstrate that repeated application would reaches a fixed point, @@ -903,7 +920,7 @@ end sym_isless(a::Symbol, b::Symbol) = ccall(:strcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}), a, b) < 0 -function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVector, tupledepth::Int) +function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVector, tupledepth::Int, allowed_tuplelen::Int) # detect cases where the comparison is trivial if t === c return false @@ -929,17 +946,17 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe if isa(c, TypeVar) if isa(t, TypeVar) return !(t.lb === Union{} || t.lb === c.lb) || # simplify lb towards Union{} - type_more_complex(t.ub, c.ub, sources, tupledepth) + type_more_complex(t.ub, c.ub, sources, tupledepth, 0) end c.lb === Union{} || return true - return type_more_complex(t, c.ub, sources, max(tupledepth, 1)) # allow replacing a TypeVar with a concrete value + return type_more_complex(t, c.ub, sources, max(tupledepth, 1), 0) # allow replacing a TypeVar with a concrete value elseif isa(c, Union) if isa(t, Union) - return type_more_complex(t.a, c.a, sources, tupledepth) || - type_more_complex(t.b, c.b, sources, tupledepth) + return type_more_complex(t.a, c.a, sources, tupledepth, allowed_tuplelen) || + type_more_complex(t.b, c.b, sources, tupledepth, allowed_tuplelen) end - return type_more_complex(t, c.a, sources, tupledepth) && - type_more_complex(t, c.b, sources, tupledepth) + return type_more_complex(t, c.a, sources, tupledepth, allowed_tuplelen) && + type_more_complex(t, c.b, sources, tupledepth, allowed_tuplelen) elseif isa(t, Int) && isa(c, Int) return t !== 1 # alternatively, could use !(0 <= t < c) end @@ -972,9 +989,11 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe end end end - type_more_complex(tPi, cPi, sources, tupledepth) && return true + type_more_complex(tPi, cPi, sources, tupledepth, 0) && return true end return false + elseif isvarargtype(c) + return type_more_complex(t, unwrapva(c), sources, tupledepth, 0) end if isType(t) # allow taking typeof any source type anywhere as Type{...}, as long as it isn't nesting Type{Type{...}} tt = unwrap_unionall(t.parameters[1]) @@ -1035,7 +1054,7 @@ function is_derived_type_from_any(@nospecialize(t), sources::SimpleVector) return false end -function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVector) # type vs. comparison which was derived from source +function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVector, allowed_tuplelen::Int) # type vs. comparison which was derived from source if t === c return t # quick egal test elseif t === Union{} @@ -1055,8 +1074,8 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec end elseif isa(t, Union) if isa(c, Union) - a = _limit_type_size(t.a, c.a, sources) - b = _limit_type_size(t.b, c.b, sources) + a = _limit_type_size(t.a, c.a, sources, allowed_tuplelen) + b = _limit_type_size(t.b, c.b, sources, allowed_tuplelen) return Union{a, b} end elseif isa(t, UnionAll) @@ -1065,11 +1084,11 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec cv = c.var if tv.ub === cv.ub if tv.lb === cv.lb - return UnionAll(tv, _limit_type_size(t.body, c.body, sources)) + return UnionAll(tv, _limit_type_size(t.body, c.body, sources, allowed_tuplelen)) end ub = tv.ub else - ub = _limit_type_size(tv.ub, cv.ub, sources) + ub = _limit_type_size(tv.ub, cv.ub, sources, 0) end if tv.lb === cv.lb lb = tv.lb @@ -1078,37 +1097,54 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec lb = Bottom end v2 = TypeVar(tv.name, lb, ub) - return UnionAll(v2, _limit_type_size(t{v2}, c{v2}, sources)) + return UnionAll(v2, _limit_type_size(t{v2}, c{v2}, sources, allowed_tuplelen)) end - tbody = _limit_type_size(t.body, c, sources) + tbody = _limit_type_size(t.body, c, sources, allowed_tuplelen) tbody === t.body && return t return UnionAll(t.var, tbody) + elseif isa(c, UnionAll) + # peel off non-matching wrapper of comparison + return _limit_type_size(t, c.body, sources, allowed_tuplelen) elseif isa(t, DataType) if isa(c, DataType) tP = t.parameters cP = c.parameters if t.name === c.name && !isempty(cP) if isvarargtype(t) - VaT = _limit_type_size(tP[1], cP[1], sources) + VaT = _limit_type_size(tP[1], cP[1], sources, 0) N = tP[2] if isa(N, TypeVar) || N === cP[2] return Vararg{VaT, N} end return Vararg{VaT} elseif t.name === Tuple.name - # for covariant datatypes (aka Tuple), - # apply type-size limit elementwise - np = min(length(tP), length(cP)) + # for covariant datatypes (Tuple), + # apply type-size limit element-wise + ltP = length(tP) + lcP = length(cP) + np = min(ltP, max(lcP, allowed_tuplelen)) Q = Any[ tP[i] for i in 1:np ] - if length(tP) > np # implies Tuple + if ltP > np # combine tp[np:end] into tP[np] using Vararg - Q[np] = tuple_tail_elem(Bottom, Any[ tP[i] for i in np:length(tP) ]) + Q[np] = tuple_tail_elem(Bottom, Any[ tP[i] for i in np:ltP ]) end for i = 1:np - Q[i] = _limit_type_size(Q[i], cP[i], sources) + # now apply limit element-wise to Q + # padding out the comparison as needed to allowed_tuplelen elements + if i <= lcP + cPi = cP[i] + elseif isvarargtype(cP[lcP]) + cPi = cP[lcP] + else + cPi = Any + end + Q[i] = _limit_type_size(Q[i], cPi, sources, 0) end return Tuple{Q...} end + elseif isvarargtype(c) + # Tuple{Vararg{T}} --> Tuple{T} is OK + return _limit_type_size(t, cP[1], sources, 0) end end if isType(t) # allow taking typeof as Type{...}, but ensure it doesn't start nesting @@ -1769,26 +1805,27 @@ function abstract_call_gf_by_type(@nospecialize(f), @nospecialize(atype), sv::In end function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(sig), sparams::SimpleVector, sv::InferenceState) - sigtuple = unwrap_unionall(sig)::DataType - - tm = _topmod(sv) - if (!istopfunction(tm, f, :promote_typeof)) # promote_typeof signature may be used with many arguments, here we'll just assume it is defined non-recursively + limited = sv.limited + # If we are operating without inference limits, + # see if we need to enable those. + # The limit will be imposed if we recur on the same method. + topmost = nothing + if !limited && !istopfunction(_topmod(sv), f, :promote_typeof) + # since promote_typeof signature may be used with many arguments, here we'll just assume it is defined non-recursively # otherwise: limit argument type tuple growth of all other functions - msig = unwrap_unionall(method.sig) - lsig = length(msig.parameters) - ls = length(sigtuple.parameters) # look through the parents list to find the topmost # function call to the same method cyclei = 0 infstate = sv - topmost = nothing while infstate !== nothing infstate = infstate::InferenceState if method === infstate.linfo.def if infstate.linfo.specTypes == sig # avoid widening when detecting self-recursion # TODO: merge call cycle and return right away - topmost = nothing + # TODO: this'll improve convergence speed and give better results, + # but is it correct and valid? + limited = false break end if topmost === nothing @@ -1800,6 +1837,7 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si # check in the cycle list first # all items in here are mutual parents of all others if parent.linfo.def === sv.linfo.def + limited = true topmost = infstate break end @@ -1809,6 +1847,7 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si if topmost === nothing && parent !== nothing parent = parent::InferenceState if parent.cached && parent.linfo.def === sv.linfo.def + limited = true topmost = infstate end end @@ -1824,52 +1863,70 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si infstate = infstate.parent end end + end - # TODO: FIXME: this heuristic depends on non-local state making type-inference unpredictable - # it also should be integrated into the cycle resolution and iterated to convergence - if topmost !== nothing - # impose limit if we recur on the same method and the argument type complexity is growing or is beyond MAX_TYPE_DEPTH - newsig = sig - if !isempty(topmost.callers_in_cycle) - # already discovered this method causes dependent self-recursion - # widen fully to avoid making the cycle any larger - newsig = method.sig - else - comparison = topmost.linfo.specTypes - if ls > lsig + 1 && ls > length(unwrap_unionall(comparison).parameters) - # limit length based on size of definition signature. - # for example, given function f(T, Any...), limit to 3 arguments - # instead of the default (MAX_TUPLETYPE_LEN) - fst = sigtuple.parameters[lsig + 1] - allsame = true - # allow specializing on longer arglists if all the trailing - # arguments are the same, since there is no exponential - # blowup in this case. - for i = (lsig + 2):ls - if sigtuple.parameters[i] != fst - allsame = false - break - end + if limited + newsig = sig + sigtuple = unwrap_unionall(sig)::DataType + msig = unwrap_unionall(method.sig)::DataType + max_spec_len = length(msig.parameters) + 1 + ls = length(sigtuple.parameters) + if method === sv.linfo.def + # direct self-recursion permits much greater use of reducers + # without using non-local state (just the total edge) + # here we assume that complexity(specTypes) :>= complexity(sig) + comparison = sv.linfo.specTypes + l_comparison = length(unwrap_unionall(comparison).parameters) + max_spec_len = max(max_spec_len, l_comparison) + else + comparison = method.sig + end + if method.isva && ls > max_spec_len + # limit length based on size of definition signature. + # for example, given function f(T, Any...), limit to 3 arguments + # instead of the default (MAX_TUPLETYPE_LEN) + fst = sigtuple.parameters[max_spec_len] + allsame = true + # allow specializing on longer arglists if all the trailing + # arguments are the same, since there is no exponential + # blowup in this case. + for i = (max_spec_len + 1):ls + if sigtuple.parameters[i] != fst + allsame = false + break + end + end + if !allsame + sigtuple = limit_tuple_type_n(sigtuple, max_spec_len) + newsig = rewrap_unionall(sigtuple, newsig) + end + end + # see if the type is still too big, and limit it further if still required + newsig = limit_type_size(newsig, comparison, sv.linfo.specTypes, max_spec_len) + if newsig !== sig + if !sv.limited + # continue inference, but limit parameter complexity to ensure (quick) convergence + topmost = topmost::InferenceState + infstate = sv + while infstate !== topmost.parent + # TODO: avoid this non-local mutation + infstate.limited = true + if infstate.parent !== nothing + infstate.optimize = false end - if !allsame - sigtuple = limit_tuple_type_n(sigtuple, lsig + 1) - newsig = rewrap_unionall(sigtuple, newsig) + for infstate_cycle in infstate.callers_in_cycle + infstate_cycle.limited = true + if infstate_cycle.parent !== nothing + infstate_cycle.optimize = false + end end + infstate = infstate.parent + infstate === nothing && break end - td = type_depth(newsig) - max_type_depth = min(MAX_TYPE_DEPTH, type_depth(comparison)) - if td > max_type_depth - # limit growth in type depth - newsig = limit_type_depth(newsig, max_type_depth) - end - # see if the type is still too big, and limit it further if required - newsig = limit_type_size(newsig, comparison, sv.linfo.specTypes) - end - if newsig !== sig - sig = newsig - sigtuple = unwrap_unionall(sig) - sparams = svec() + # TODO: break here and restart from "topmost" call-site end + sig = newsig + sparams = svec() end end @@ -3425,6 +3482,10 @@ function optimize(me::InferenceState) force_noinline = popmeta!(code, :noinline)[1] end reindex_labels!(me) + elseif me.cached && me.parent !== nothing + # top parent will be cached still, but not this intermediate work + me.cached = false + me.linfo.inInference = false end # convert all type information into the form consumed by the code-generator @@ -3549,13 +3610,14 @@ function finish(me::InferenceState) if !toplevel if !me.const_api def = me.linfo.def::Method - keeptree = me.src.inlineable || ccall(:jl_is_cacheable_sig, Int32, (Any, Any, Any), - me.linfo.specTypes, def.sig, def) != 0 - if !keeptree - inferred_result = nothing - else + keeptree = me.optimize && + (me.src.inlineable || + ccall(:jl_is_cacheable_sig, Int32, (Any, Any, Any), me.linfo.specTypes, def.sig, def) != 0) + if keeptree # compress code for non-toplevel thunks inferred_result = ccall(:jl_compress_ast, Any, (Any, Any), def, inferred_result) + else + inferred_result = nothing end end end diff --git a/base/reduce.jl b/base/reduce.jl index 87fe2e44329e2..7bc3b9a4c9df8 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -376,7 +376,7 @@ Returns the sum of all elements of `A`, using the Kahan-Babuska-Neumaier compens summation algorithm for additional accuracy. """ function sum_kbn(A) - T = _default_eltype(typeof(A)) + T = @default_eltype(typeof(A)) c = r_promote(+, zero(T)::T) i = start(A) if done(A, i) diff --git a/base/set.jl b/base/set.jl index d9dea0a0ebc36..ad9bf4236f987 100644 --- a/base/set.jl +++ b/base/set.jl @@ -17,7 +17,7 @@ for sets of arbitrary objects. """ Set(itr) = Set{eltype(itr)}(itr) function Set(g::Generator) - T = _default_eltype(typeof(g)) + T = @default_eltype(typeof(g)) (_isleaftype(T) || T === Union{}) || return grow_to!(Set{T}(), g) return Set{T}(g) end @@ -258,7 +258,7 @@ julia> unique(Real[1, 1.0, 2]) ``` """ function unique(itr) - T = _default_eltype(typeof(itr)) + T = @default_eltype(typeof(itr)) out = Vector{T}() seen = Set{T}() i = start(itr) diff --git a/test/inference.jl b/test/inference.jl index a0781521d3226..89f3309c9d352 100644 --- a/test/inference.jl +++ b/test/inference.jl @@ -980,13 +980,13 @@ copy_dims_out(out) = () copy_dims_out(out, dim::Int, tail...) = copy_dims_out((out..., dim), tail...) copy_dims_out(out, dim::Colon, tail...) = copy_dims_out((out..., dim), tail...) @test Base.return_types(copy_dims_out, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] -@test all(m -> 15 < count_specializations(m) < 45, methods(copy_dims_out)) +@test all(m -> 10 < count_specializations(m) < 25, methods(copy_dims_out)) copy_dims_pair(out) = () copy_dims_pair(out, dim::Int, tail...) = copy_dims_pair(out => dim, tail...) copy_dims_pair(out, dim::Colon, tail...) = copy_dims_pair(out => dim, tail...) @test Base.return_types(copy_dims_pair, (Tuple{}, Vararg{Union{Int,Colon}})) == Any[Tuple{}, Tuple{}, Tuple{}] -@test all(m -> 10 < count_specializations(m) < 35, methods(copy_dims_pair)) +@test all(m -> 5 < count_specializations(m) < 25, methods(copy_dims_pair)) # splatting an ::Any should still allow inference to use types of parameters preceding it f22364(::Int, ::Any...) = 0 From b89e88ead58a26c9d214361c78dc6677f172d51a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 3 Oct 2017 22:48:15 -0400 Subject: [PATCH 5/5] correctly limit depth and length remove code to handle exponential blowup, since there isn't any --- base/inference.jl | 110 ++++++++++++++++------------------ base/sparse/higherorderfns.jl | 43 +++++++------ test/core.jl | 4 -- test/inference.jl | 20 ++++++- 4 files changed, 89 insertions(+), 88 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index eb7adac3058ba..c8d60eac7c86e 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -909,8 +909,8 @@ end function limit_type_size(@nospecialize(t), @nospecialize(compare), @nospecialize(source), allowed_tuplelen::Int) source = svec(unwrap_unionall(compare), unwrap_unionall(source)) source[1] === source[2] && (source = svec(source[1])) - type_more_complex(t, compare, source, TUPLE_COMPLEXITY_LIMIT_DEPTH, allowed_tuplelen) || return t - r = _limit_type_size(t, compare, source, allowed_tuplelen) + type_more_complex(t, compare, source, 1, TUPLE_COMPLEXITY_LIMIT_DEPTH, allowed_tuplelen) || return t + r = _limit_type_size(t, compare, source, 1, allowed_tuplelen) @assert t <: r #@assert r === _limit_type_size(r, t, source) # this monotonicity constraint is slightly stronger than actually required, # since we only actually need to demonstrate that repeated application would reaches a fixed point, @@ -920,7 +920,7 @@ end sym_isless(a::Symbol, b::Symbol) = ccall(:strcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}), a, b) < 0 -function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVector, tupledepth::Int, allowed_tuplelen::Int) +function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVector, depth::Int, tupledepth::Int, allowed_tuplelen::Int) # detect cases where the comparison is trivial if t === c return false @@ -930,7 +930,7 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe return false # fastpath: unparameterized types are always finite elseif tupledepth > 0 && isa(unwrap_unionall(t), DataType) && isa(c, Type) && c !== Union{} && c <: t return false # t is already wider than the comparison in the type lattice - elseif tupledepth > 0 && is_derived_type_from_any(unwrap_unionall(t), sources) + elseif tupledepth > 0 && is_derived_type_from_any(unwrap_unionall(t), sources, depth) return false # t isn't something new end # peel off wrappers @@ -944,19 +944,20 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe end # rules for various comparison types if isa(c, TypeVar) + tupledepth = 1 # allow replacing a TypeVar with a concrete value (since we know the UnionAll must be in covariant position) if isa(t, TypeVar) return !(t.lb === Union{} || t.lb === c.lb) || # simplify lb towards Union{} - type_more_complex(t.ub, c.ub, sources, tupledepth, 0) + type_more_complex(t.ub, c.ub, sources, depth + 1, tupledepth, 0) end c.lb === Union{} || return true - return type_more_complex(t, c.ub, sources, max(tupledepth, 1), 0) # allow replacing a TypeVar with a concrete value + return type_more_complex(t, c.ub, sources, depth, tupledepth, 0) elseif isa(c, Union) if isa(t, Union) - return type_more_complex(t.a, c.a, sources, tupledepth, allowed_tuplelen) || - type_more_complex(t.b, c.b, sources, tupledepth, allowed_tuplelen) + return type_more_complex(t.a, c.a, sources, depth, tupledepth, allowed_tuplelen) || + type_more_complex(t.b, c.b, sources, depth, tupledepth, allowed_tuplelen) end - return type_more_complex(t, c.a, sources, tupledepth, allowed_tuplelen) && - type_more_complex(t, c.b, sources, tupledepth, allowed_tuplelen) + return type_more_complex(t, c.a, sources, depth, tupledepth, allowed_tuplelen) && + type_more_complex(t, c.b, sources, depth, tupledepth, allowed_tuplelen) elseif isa(t, Int) && isa(c, Int) return t !== 1 # alternatively, could use !(0 <= t < c) end @@ -989,16 +990,16 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe end end end - type_more_complex(tPi, cPi, sources, tupledepth, 0) && return true + type_more_complex(tPi, cPi, sources, depth + 1, tupledepth, 0) && return true end return false elseif isvarargtype(c) - return type_more_complex(t, unwrapva(c), sources, tupledepth, 0) + return type_more_complex(t, unwrapva(c), sources, depth, tupledepth, 0) end if isType(t) # allow taking typeof any source type anywhere as Type{...}, as long as it isn't nesting Type{Type{...}} tt = unwrap_unionall(t.parameters[1]) if isa(tt, DataType) && !isType(tt) - is_derived_type_from_any(tt, sources) || return true + is_derived_type_from_any(tt, sources, depth) || return true return false end end @@ -1006,17 +1007,24 @@ function type_more_complex(@nospecialize(t), @nospecialize(c), sources::SimpleVe return true end -function is_derived_type(@nospecialize(t), @nospecialize(c)) # try to find `type` somewhere in `comparison` type - t === c && return true +# try to find `type` somewhere in `comparison` type +# at a minimum nesting depth of `mindepth` +function is_derived_type(@nospecialize(t), @nospecialize(c), mindepth::Int) + if mindepth > 0 + mindepth -= 1 + end + if t === c + return mindepth == 0 + end if isa(c, TypeVar) # see if it is replacing a TypeVar upper bound with something simpler - return is_derived_type(t, c.ub) + return is_derived_type(t, c.ub, mindepth) elseif isa(c, Union) # see if it is one of the elements of the union - return is_derived_type(t, c.a) || is_derived_type(t, c.b) + return is_derived_type(t, c.a, mindepth + 1) || is_derived_type(t, c.b, mindepth + 1) elseif isa(c, UnionAll) # see if it is derived from the body - return is_derived_type(t, c.body) + return is_derived_type(t, c.body, mindepth) elseif isa(c, DataType) if isa(t, DataType) # see if it is one of the supertypes of a parameter @@ -1029,7 +1037,7 @@ function is_derived_type(@nospecialize(t), @nospecialize(c)) # try to find `type # see if it was extracted from a type parameter cP = c.parameters for p in cP - is_derived_type(t, p) && return true + is_derived_type(t, p, mindepth) && return true end if isleaftype(c) && isbits(c) # see if it was extracted from a fieldtype @@ -1040,21 +1048,22 @@ function is_derived_type(@nospecialize(t), @nospecialize(c)) # try to find `type # it cannot have a reference cycle in the type graph cF = c.types for f in cF - is_derived_type(t, f) && return true + is_derived_type(t, f, mindepth) && return true end end end return false end -function is_derived_type_from_any(@nospecialize(t), sources::SimpleVector) +function is_derived_type_from_any(@nospecialize(t), sources::SimpleVector, mindepth::Int) for s in sources - is_derived_type(t, s) && return true + is_derived_type(t, s, mindepth) && return true end return false end -function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVector, allowed_tuplelen::Int) # type vs. comparison which was derived from source +# type vs. comparison or which was derived from source +function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVector, depth::Int, allowed_tuplelen::Int) if t === c return t # quick egal test elseif t === Union{} @@ -1063,7 +1072,7 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec return t # fast path: unparameterized are always simple elseif isa(unwrap_unionall(t), DataType) && isa(c, Type) && c !== Union{} && c <: t return t # t is already wider than the comparison in the type lattice - elseif is_derived_type_from_any(unwrap_unionall(t), sources) + elseif is_derived_type_from_any(unwrap_unionall(t), sources, depth) return t # t isn't something new end if isa(t, TypeVar) @@ -1074,8 +1083,8 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec end elseif isa(t, Union) if isa(c, Union) - a = _limit_type_size(t.a, c.a, sources, allowed_tuplelen) - b = _limit_type_size(t.b, c.b, sources, allowed_tuplelen) + a = _limit_type_size(t.a, c.a, sources, depth, allowed_tuplelen) + b = _limit_type_size(t.b, c.b, sources, depth, allowed_tuplelen) return Union{a, b} end elseif isa(t, UnionAll) @@ -1084,11 +1093,11 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec cv = c.var if tv.ub === cv.ub if tv.lb === cv.lb - return UnionAll(tv, _limit_type_size(t.body, c.body, sources, allowed_tuplelen)) + return UnionAll(tv, _limit_type_size(t.body, c.body, sources, depth + 1, allowed_tuplelen)) end ub = tv.ub else - ub = _limit_type_size(tv.ub, cv.ub, sources, 0) + ub = _limit_type_size(tv.ub, cv.ub, sources, depth + 1, 0) end if tv.lb === cv.lb lb = tv.lb @@ -1097,21 +1106,21 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec lb = Bottom end v2 = TypeVar(tv.name, lb, ub) - return UnionAll(v2, _limit_type_size(t{v2}, c{v2}, sources, allowed_tuplelen)) + return UnionAll(v2, _limit_type_size(t{v2}, c{v2}, sources, depth + 1, allowed_tuplelen)) end - tbody = _limit_type_size(t.body, c, sources, allowed_tuplelen) + tbody = _limit_type_size(t.body, c, sources, depth + 1, allowed_tuplelen) tbody === t.body && return t return UnionAll(t.var, tbody) elseif isa(c, UnionAll) # peel off non-matching wrapper of comparison - return _limit_type_size(t, c.body, sources, allowed_tuplelen) + return _limit_type_size(t, c.body, sources, depth, allowed_tuplelen) elseif isa(t, DataType) if isa(c, DataType) tP = t.parameters cP = c.parameters if t.name === c.name && !isempty(cP) if isvarargtype(t) - VaT = _limit_type_size(tP[1], cP[1], sources, 0) + VaT = _limit_type_size(tP[1], cP[1], sources, depth + 1, 0) N = tP[2] if isa(N, TypeVar) || N === cP[2] return Vararg{VaT, N} @@ -1138,19 +1147,19 @@ function _limit_type_size(@nospecialize(t), @nospecialize(c), sources::SimpleVec else cPi = Any end - Q[i] = _limit_type_size(Q[i], cPi, sources, 0) + Q[i] = _limit_type_size(Q[i], cPi, sources, depth + 1, 0) end return Tuple{Q...} end elseif isvarargtype(c) # Tuple{Vararg{T}} --> Tuple{T} is OK - return _limit_type_size(t, cP[1], sources, 0) + return _limit_type_size(t, cP[1], sources, depth, 0) end end if isType(t) # allow taking typeof as Type{...}, but ensure it doesn't start nesting tt = unwrap_unionall(t.parameters[1]) if isa(tt, DataType) && !isType(tt) - is_derived_type_from_any(tt, sources) && return t + is_derived_type_from_any(tt, sources, depth) && return t end end if isvarargtype(t) @@ -1866,10 +1875,9 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si end if limited - newsig = sig sigtuple = unwrap_unionall(sig)::DataType msig = unwrap_unionall(method.sig)::DataType - max_spec_len = length(msig.parameters) + 1 + spec_len = length(msig.parameters) + 1 ls = length(sigtuple.parameters) if method === sv.linfo.def # direct self-recursion permits much greater use of reducers @@ -1877,32 +1885,13 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si # here we assume that complexity(specTypes) :>= complexity(sig) comparison = sv.linfo.specTypes l_comparison = length(unwrap_unionall(comparison).parameters) - max_spec_len = max(max_spec_len, l_comparison) + spec_len = max(spec_len, l_comparison) else comparison = method.sig end - if method.isva && ls > max_spec_len - # limit length based on size of definition signature. - # for example, given function f(T, Any...), limit to 3 arguments - # instead of the default (MAX_TUPLETYPE_LEN) - fst = sigtuple.parameters[max_spec_len] - allsame = true - # allow specializing on longer arglists if all the trailing - # arguments are the same, since there is no exponential - # blowup in this case. - for i = (max_spec_len + 1):ls - if sigtuple.parameters[i] != fst - allsame = false - break - end - end - if !allsame - sigtuple = limit_tuple_type_n(sigtuple, max_spec_len) - newsig = rewrap_unionall(sigtuple, newsig) - end - end - # see if the type is still too big, and limit it further if still required - newsig = limit_type_size(newsig, comparison, sv.linfo.specTypes, max_spec_len) + # see if the type is too big, and limit it if required + newsig = limit_type_size(sig, comparison, sv.linfo.specTypes, spec_len) + if newsig !== sig if !sv.limited # continue inference, but limit parameter complexity to ensure (quick) convergence @@ -1939,6 +1928,7 @@ function abstract_call_method(method::Method, @nospecialize(f), @nospecialize(si end sparams = recomputed[2]::SimpleVector end + rt, edge = typeinf_edge(method, sig, sparams, sv) edge !== nothing && add_backedge!(edge::MethodInstance, sv) return rt diff --git a/base/sparse/higherorderfns.jl b/base/sparse/higherorderfns.jl index c7431a0408730..a6b06c342b2e4 100644 --- a/base/sparse/higherorderfns.jl +++ b/base/sparse/higherorderfns.jl @@ -926,37 +926,34 @@ end # vectors/matrices in mixedargs in their orginal order, and such that the result of # broadcast(parevalf, passedargstup...) is broadcast(f, mixedargs...) @inline function capturescalars(f, mixedargs) - let makeargs = _capturescalars(mixedargs...), - parevalf = (passed...) -> f(makeargs(passed...)...), - passedsrcargstup = _capturenonscalars(mixedargs...) + let (passedsrcargstup, makeargs) = _capturescalars(mixedargs...) + parevalf = (passed...) -> f(makeargs(passed...)...) return (parevalf, passedsrcargstup) end end -@inline _capturenonscalars(nonscalararg::SparseVecOrMat, mixedargs...) = - (nonscalararg, _capturenonscalars(mixedargs...)...) -@inline _capturenonscalars(scalararg, mixedargs...) = - _capturenonscalars(mixedargs...) -@inline _capturenonscalars() = () +nonscalararg(::SparseVecOrMat) = true +nonscalararg(::Any) = false -@inline _capturescalars(nonscalararg::SparseVecOrMat, mixedargs...) = - let f = _capturescalars(mixedargs...) - (head, tail...) -> (head, f(tail...)...) # pass-through +@inline function _capturescalars() + return (), () -> () +end +@inline function _capturescalars(arg, mixedargs...) + let (rest, f) = _capturescalars(mixedargs...) + if nonscalararg(arg) + return (arg, rest...), (head, tail...) -> (head, f(tail...)...) # pass-through to broadcast + else + return rest, (tail...) -> (arg, f(tail...)...) # add back scalararg after (in makeargs) + end end -@inline _capturescalars(scalararg, mixedargs...) = - let f = _capturescalars(mixedargs...) - (tail...) -> (scalararg, f(tail...)...) # add scalararg +end +@inline function _capturescalars(arg) # this definition is just an optimization (to bottom out the recursion slightly sooner) + if nonscalararg(arg) + return (arg,), (head,) -> (head,) # pass-through + else + return (), () -> (arg,) # add scalararg end -# TODO: use the implicit version once inference can handle it -# handle too-many-arguments explicitly -@inline function _capturescalars() - too_many_arguments() = () - too_many_arguments(tail...) = throw(ArgumentError("too many")) end -#@inline _capturescalars(nonscalararg::SparseVecOrMat) = -# (head,) -> (head,) # pass-through -#@inline _capturescalars(scalararg) = -# () -> (scalararg,) # add scalararg # NOTE: The following two method definitions work around #19096. broadcast(f::Tf, ::Type{T}, A::SparseMatrixCSC) where {Tf,T} = broadcast(y -> f(T, y), A) diff --git a/test/core.jl b/test/core.jl index 60f27aaf6e3e2..6b3a8cfadb763 100644 --- a/test/core.jl +++ b/test/core.jl @@ -3339,10 +3339,6 @@ end @test EmptyIIOtherField13175(EmptyImmutable13175(), 1.0) == EmptyIIOtherField13175(EmptyImmutable13175(), 1.0) @test EmptyIIOtherField13175(EmptyImmutable13175(), 1.0) != EmptyIIOtherField13175(EmptyImmutable13175(), 2.0) -# issue #13183 -gg13183(x::X...) where {X} = 1==0 ? gg13183(x, x) : 0 -@test gg13183(5) == 0 - # issue 8932 (llvm return type legalizer error) struct Vec3_8932 x::Float32 diff --git a/test/inference.jl b/test/inference.jl index 89f3309c9d352..cc1f51411450e 100644 --- a/test/inference.jl +++ b/test/inference.jl @@ -2,6 +2,20 @@ # tests for Core.Inference correctness and precision import Core.Inference: Const, Conditional, ⊑ +const isleaftype = Core.Inference._isleaftype + +# demonstrate some of the type-size limits +@test Core.Inference.limit_type_size(Ref{Complex{T} where T}, Ref, Ref, 0) == Ref +@test Core.Inference.limit_type_size(Ref{Complex{T} where T}, Ref{Complex{T} where T}, Ref, 0) == Ref{Complex{T} where T} +let comparison = Tuple{X, X} where X<:Tuple + sig = Tuple{X, X} where X<:comparison + ref = Tuple{X, X} where X + @test Core.Inference.limit_type_size(sig, comparison, comparison, 10) == comparison + @test Core.Inference.limit_type_size(sig, ref, comparison, 10) == comparison + @test Core.Inference.limit_type_size(Tuple{sig}, Tuple{ref}, comparison, 10) == Tuple{comparison} + @test Core.Inference.limit_type_size(sig, ref, Tuple{comparison}, 10) == sig +end + # issue 9770 @noinline x9770() = false @@ -186,7 +200,6 @@ function find_tvar10930(arg) end @test find_tvar10930(Vararg{Int}) === 1 -const isleaftype = Base._isleaftype # issue #12474 @generated function f12474(::Any) @@ -1225,3 +1238,8 @@ end let t = Tuple{Type{T23786{D, N} where N where D<:Tuple{Vararg{Array{T, 1} where T, N} where N}}} @test Core.Inference.limit_type_depth(t, 4) >: t end + +# issue #13183 +_false13183 = false +gg13183(x::X...) where {X} = (_false13183 ? gg13183(x, x) : 0) +@test gg13183(5) == 0