From 39b564ad6fecf5609da2df74d9b43fe0eb035aa9 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Thu, 7 Apr 2016 03:49:36 -0400 Subject: [PATCH] add a lattice element type for constants in inference this simplifies some of the code, and gives sharper type information. fixes #5090, type inference of `&&` and `||` chains also makes it easy to fix #5560 and #14324 --- base/inference.jl | 561 +++++++++++++++++++--------------------------- test/core.jl | 4 +- 2 files changed, 237 insertions(+), 328 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index e7d524bb694ac4..00cd91e93ff0c2 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -18,6 +18,10 @@ type VarState undef::Bool end +immutable Const + val +end + type InferenceState atypes #::Type # type sig sp::SimpleVector # static parameters @@ -171,11 +175,6 @@ function contains_is(itr, x::ANY) return false end -function _iisconst(s::Symbol, sv) - m = sv.mod - isdefined(m,s) && (ccall(:jl_is_const, Int32, (Any, Any), m, s) != 0) -end - _ieval(x::ANY, sv) = ccall(:jl_interpret_toplevel_expr_in, Any, (Any, Any, Any, Any), sv.mod, x, svec(), svec()) @@ -255,7 +254,8 @@ add_tfunc(Core.Intrinsics.select_value, 3, 3, # TODO: return Bottom if cnd is definitely not a Bool (cnd, x, y)->Union{x,y}) add_tfunc(Core.Intrinsics.arraylen, 1, 1, x->Int) -add_tfunc(is, 2, 2, cmp_tfunc) +add_tfunc(is, 2, 2, + (x::ANY, y::ANY)->(isa(x,Const) && isa(y,Const) ? Const(x.val===y.val) : Bool)) add_tfunc(issubtype, 2, 2, cmp_tfunc) add_tfunc(isa, 2, 2, cmp_tfunc) add_tfunc(isdefined, 1, IInf, (args...)->Bool) @@ -267,11 +267,16 @@ add_tfunc(applicable, 1, IInf, (f, args...)->Bool) # a.parameters[1] : Any)) #add_tfunc(arrayset, 3, IInf, (a,v,i...)->a) add_tfunc(arraysize, 2, 2, (a,d)->Int) -add_tfunc(pointerref, 2, 2, (a,i)->(isa(a,DataType) && a<:Ptr && isa(a.parameters[1],Union{Type,TypeVar}) ? a.parameters[1] : Any)) +add_tfunc(pointerref, 2, 2, function (a,i) + a = widenconst(a) + isa(a,DataType) && a<:Ptr && isa(a.parameters[1],Union{Type,TypeVar}) ? a.parameters[1] : Any + end) add_tfunc(pointerset, 3, 3, (a,v,i)->a) -const typeof_tfunc = function (t::ANY) - if isType(t) +function typeof_tfunc(t::ANY) + if isa(t,Const) + return Type{typeof(t.val)} + elseif isType(t) t = t.parameters[1] if isa(t,TypeVar) DataType @@ -295,10 +300,8 @@ const typeof_tfunc = function (t::ANY) end end add_tfunc(typeof, 1, 1, typeof_tfunc) -# involving constants: typeassert, getfield, fieldtype, apply_type -# therefore they get their arguments unevaluated add_tfunc(typeassert, 2, 2, - (A, v, t)->(isType(t) ? typeintersect(v,t.parameters[1]) : Any)) + (v, t)->(isType(t) ? typeintersect(v,t.parameters[1]) : Any)) function type_depth(t::ANY) if isa(t, Union) @@ -353,50 +356,53 @@ function limit_type_depth(t::ANY, d::Int, cov::Bool, vars) return R end -const getfield_tfunc = function (A, s0::ANY, name) +# returns (type, isexact) +function getfield_tfunc(s0::ANY, name) + if isa(s0, TypeVar) + s0 = s0.ub + end s = s0 if isType(s) s = typeof(s.parameters[1]) if s === TypeVar return Any, false end + elseif isa(s,Const) + if isa(s.val, Module) && isa(name, Const) && isa(name.val, Symbol) + return abstract_eval_global(s.val, name.val), true + end + s = typeof(s.val) end if isa(s,Union) - return reduce(tmerge, Bottom, map(t->getfield_tfunc(A, t, name)[1], s.types)), false - end - if !isa(s,DataType) - return Any, false + return reduce(tmerge, Bottom, map(t->getfield_tfunc(t, name)[1], s.types)), false end - if is(s.name,NTuple.name) - return (name == Symbol ? Bottom : s.parameters[2]), true - end - if s.abstract - return Any, false - end - if s <: Tuple && name === Symbol - return Bottom, true + if isa(s,DataType) + if is(s.name,NTuple.name) + return (name ⊑ Symbol ? Bottom : s.parameters[2]), true + end + if s.abstract + return Any, false + end + if s <: Tuple && name ⊑ Symbol + return Bottom, true + end end - haveargs = A !== nothing && length(A)>1 - if haveargs && isa(A[2],QuoteNode) && isa(A[2].value,Symbol) - fld = A[2].value - A1 = A[1] - if isa(A1,Module) && isdefined(A1,fld) && isconst(A1, fld) - return abstract_eval_constant(eval(A1,fld)), true + if isa(name,Const) && isa(name.val,Symbol) + fld = name.val + if isa(s0,Const) && isa(s0.val,Module) && isdefined(s0.val,fld) && isconst(s0.val,fld) + return abstract_eval_constant(getfield(s0.val,fld)), true end - if s === Module + if s <: Module return Any, false end if isType(s0) sp = s0.parameters[1] if isa(sp,DataType) - # TODO - #if fld === :parameters - # return Type{sp.parameters}, true - #end - #if fld === :types - # return Type{sp.types}, true - #end - if fld === :super + if fld === :parameters + return Const(sp.parameters), true + elseif fld === :types + return Const(sp.types), true + elseif fld === :super return Type{sp.super}, isleaftype(s) end end @@ -415,11 +421,11 @@ const getfield_tfunc = function (A, s0::ANY, name) end end return Bottom, true - elseif haveargs && isa(A[2],Int) - if isa(A[1],Module) || s === Module + elseif isa(name,Const) && isa(name.val,Int) + if s <: Module return Bottom, true end - i::Int = A[2] + i::Int = name.val nf = s.types.length if isvatuple(s) && i >= nf return s.types[nf].parameters[1], false @@ -432,15 +438,17 @@ const getfield_tfunc = function (A, s0::ANY, name) return reduce(tmerge, Bottom, map(unwrapva,s.types)) #=Union{s.types...}=#, false end end -add_tfunc(getfield, 2, 2, (A,s,name)->getfield_tfunc(A,s,name)[1]) +add_tfunc(getfield, 2, 2, (s,name)->getfield_tfunc(s,name)[1]) add_tfunc(setfield!, 3, 3, (o, f, v)->v) -const fieldtype_tfunc = function (A, s::ANY, name) +function fieldtype_tfunc(s::ANY, name) if isType(s) s = s.parameters[1] + elseif isa(s,Const) + s = typeof(s.val) else return Type end - t, exact = getfield_tfunc(A, s, name) + t, exact = getfield_tfunc(s, name) if is(t,Bottom) return t end @@ -458,29 +466,10 @@ function valid_tparam(x::ANY) return isa(x,Int) || isa(x,Symbol) || isa(x,Bool) || (!isa(x,Type) && isbits(x)) end -function extract_simple_tparam(Ai, sv) - if !isa(Ai,Symbol) && !isa(Ai,GenSym) && valid_tparam(Ai) - return Ai - elseif isa(Ai,QuoteNode) && valid_tparam(Ai.value) - return Ai.value - elseif isa(Ai,Expr) && is_known_call(Ai,tuple,sv) - tup = () - for arg in Ai.args[2:end] - val = extract_simple_tparam(arg, sv) - if val === Bottom - return val - end - tup = tuple(tup...,val) - end - return tup - end - return Bottom -end - has_typevars(t::ANY, all=false) = ccall(:jl_has_typevars_, Cint, (Any,Cint), t, all)!=0 # TODO: handle e.g. apply_type(T, R::Union{Type{Int32},Type{Float64}}) -const apply_type_tfunc = function (A::ANY, args...) +function apply_type_tfunc(args...) if !isType(args[1]) return Any end @@ -507,34 +496,16 @@ const apply_type_tfunc = function (A::ANY, args...) end istuple = (headtype === Tuple) uncertain = false - lA = length(A) tparams = Any[] - for i=2:max(lA,largs) + for i=2:largs ai = args[i] if isType(ai) aip1 = ai.parameters[1] uncertain |= has_typevars(aip1) push!(tparams, aip1) + elseif isa(ai, Const) && valid_tparam(ai.val) + push!(tparams, ai.val) else - if i<=lA - val = extract_simple_tparam(A[i], global_sv::InferenceState) - if val !== Bottom - push!(tparams, val) - continue - elseif isa(A[i],Expr) && A[i].head === :static_parameter - n = A[i].args[1] - sp = global_sv.sp - found = false - if n <= length(sp) - val = sp[n] - if valid_tparam(val) - push!(tparams, val) - found = true - end - end - found && continue - end - end if !istuple && i-1 > length(headtype.parameters) # too many parameters for type return Bottom @@ -574,31 +545,31 @@ function tuple_tfunc(argtype::ANY) argtype end -function builtin_tfunction(f::ANY, args::ANY, argtype::ANY) - isva = isvatuple(argtype) - argtypes = argtype.parameters +function builtin_tfunction(f::ANY, argtypes::Array{Any,1}) if is(f,tuple) - return tuple_tfunc(limit_tuple_depth(argtype)) + for a in argtypes + if !isa(a, Const) + return tuple_tfunc(limit_tuple_depth(argtypes_to_type(argtypes))) + end + end + return Const(tuple(map(a->a.val, argtypes)...)) elseif is(f,svec) return SimpleVector elseif is(f,arrayset) - if length(argtypes) < 3 && !isva + if length(argtypes) < 3 return Bottom end a1 = argtypes[1] - if isvarargtype(a1) - return a1.parameters[1] - end return a1 elseif is(f,arrayref) - if length(argtypes) < 2 && !isva + if length(argtypes) < 2 return Bottom end a = argtypes[1] return (isa(a,DataType) && a<:Array && isa(a.parameters[1],Union{Type,TypeVar}) ? a.parameters[1] : Any) elseif is(f,Expr) - if length(argtypes) < 1 && !isva + if length(argtypes) < 1 return Bottom end return Expr @@ -619,73 +590,16 @@ function builtin_tfunction(f::ANY, args::ANY, argtype::ANY) tf = t_ffunc_val[fidx] end tf = tf::Tuple{Real, Real, Any} - if isva - # only some t-funcs can handle varargs (TODO) - #if !is(f, apply_type) - return Any - #end - elseif !(tf[1] <= length(argtypes) <= tf[2]) + if !(tf[1] <= length(argtypes) <= tf[2]) # wrong # of args return Bottom end - if is(f,typeassert) || is(f,getfield) || is(f,apply_type) || is(f,fieldtype) - # TODO: case of apply(), where we do not have the args - return tf[3](args, argtypes...) - end return tf[3](argtypes...) end -function isconstantref(f::ANY, sv::InferenceState) - if isa(f,TopNode) - m = _topmod(sv) - return isconst(m, f.name) && isdefined(m, f.name) && f - end - if isa(f,GlobalRef) - M = f.mod; s = f.name - return isdefined(M,s) && isconst(M,s) && f - end - if isa(f,Expr) - if is(f.head,:call) - if length(f.args) == 3 && isa(f.args[1], TopNode) && - is(f.args[1].name,:getfield) && isa(f.args[3],QuoteNode) - s = f.args[3].value - if isa(f.args[2],Module) - M = f.args[2] - else - M = isconstantref(f.args[2], sv) - if M === false - return false - end - M = _ieval(M, sv) - if !isa(M,Module) - return false - end - end - return isdefined(M,s) && isconst(M,s) && f - end - elseif is(f.head,:inert) - return f - end - return false - end - if isa(f,QuoteNode) - return f - elseif isa(f,GenSym) || isa(f,Slot) - return false - end - if isa(f,Symbol) - return _iisconst(f, sv) && f - elseif !isa(f,Expr) - return f - end - return false -end - -const isconstantfunc = isconstantref - -const limit_tuple_depth = t->limit_tuple_depth_(t,0) +limit_tuple_depth(t::ANY) = limit_tuple_depth_(t,0) -const limit_tuple_depth_ = function (t::ANY,d::Int) +function limit_tuple_depth_(t::ANY, d::Int) if isa(t,Union) # also limit within Union types. # may have to recur into other stuff in the future too. @@ -706,7 +620,7 @@ end limit_tuple_type = (t::ANY) -> limit_tuple_type_n(t, MAX_TUPLETYPE_LEN) -const limit_tuple_type_n = function (t::ANY, lim::Int) +function limit_tuple_type_n(t::ANY, lim::Int) p = t.parameters n = length(p) if n > lim @@ -740,32 +654,31 @@ end #### recursing into expression #### -function abstract_call_gf(f::ANY, fargs, argtype::ANY, e, sv) - argtypes = argtype.parameters +function abstract_call_gf(f::ANY, argtypes::Array{Any,1}, sv) tm = _topmod(sv) - if length(argtypes)>2 && argtypes[3]===Int && - (argtypes[2] <: Tuple || + if length(argtypes)>2 && argtypes[3] ⊑ Int && + (argtypes[2] ⊑ Tuple || (isa(argtypes[2], DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) && (argtypes[2]::DataType).name === Main.Base.Pair.name)) # allow tuple indexing functions to take advantage of constant # index arguments. if istopfunction(tm, f, :getindex) - return getfield_tfunc(fargs, argtypes[2], argtypes[3])[1] + return getfield_tfunc(argtypes[2], argtypes[3])[1] elseif istopfunction(tm, f, :next) - t1 = getfield_tfunc(fargs, argtypes[2], argtypes[3])[1] + t1 = getfield_tfunc(argtypes[2], argtypes[3])[1] return t1===Bottom ? Bottom : Tuple{t1, Int} elseif istopfunction(tm, f, :indexed_next) - t1 = getfield_tfunc(fargs, argtypes[2], argtypes[3])[1] + t1 = getfield_tfunc(argtypes[2], argtypes[3])[1] return t1===Bottom ? Bottom : Tuple{t1, Int} end end if istopfunction(tm, f, :promote_type) || istopfunction(tm, f, :typejoin) return Type end - return abstract_call_gf_by_type(f, argtype, e, sv) + return abstract_call_gf_by_type(f, argtypes_to_type(argtypes), sv) end -function abstract_call_gf_by_type(f::ANY, argtype::ANY, e, sv) +function abstract_call_gf_by_type(f::ANY, argtype::ANY, sv) tm = _topmod(sv) # don't consider more than N methods. this trades off between # compiler performance and generated code performance. @@ -780,7 +693,6 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e, sv) rettype = Bottom if is(applicable, false) # this means too many methods matched - isa(e,Expr) && (e.head = :call) return Any end x::Array{Any,1} = applicable @@ -924,6 +836,12 @@ function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::InferenceState) return typeinf_edge(linfo::LambdaInfo, ti, env, sv)[2] end +# determine whether `ex` abstractly evals to constant `c` +function abstract_evals_to_constant(ex, c::ANY, vtypes, sv) + av = abstract_eval(ex, vtypes, sv) + return isa(av,Const) && av.val === c +end + # `types` is an array of inferred types for expressions in `args`. # if an expression constructs a container (e.g. `svec(x,y,z)`), # refine its type to an array of element types. returns an array of @@ -933,8 +851,9 @@ function precise_container_types(args, types, vtypes::VarTable, sv) assert(n == length(types)) result = cell(n) for i = 1:n - ai = args[i]; ti = types[i] - if isa(ai,Expr) && (is_known_call(ai, svec, sv) || is_known_call(ai, tuple, sv)) + ai = args[i]; ti = types[i]; tti = widenconst(ti) + if isa(ai,Expr) && ai.head === :call && (abstract_evals_to_constant(ai.args[1], svec, vtypes, sv) || + abstract_evals_to_constant(ai.args[1], tuple, vtypes, sv)) aa = ai.args result[i] = Any[ (isa(aa[j],Expr) ? aa[j].typ : abstract_eval(aa[j],vtypes,sv)) for j=2:length(aa) ] if _any(isvarargtype, result[i]) @@ -942,20 +861,20 @@ function precise_container_types(args, types, vtypes::VarTable, sv) end elseif isa(ti, Union) return nothing - elseif ti<:Tuple + elseif ti ⊑ Tuple if i == n - if ti.name === NTuple.name - result[i] = Any[Vararg{ti.parameters[2]}] + if tti.name === NTuple.name + result[i] = Any[Vararg{tti.parameters[2]}] else - result[i] = ti.parameters + result[i] = tti.parameters end - elseif isknownlength(ti) - result[i] = ti.parameters + elseif isknownlength(tti) + result[i] = tti.parameters else return nothing end - elseif ti<:AbstractArray && i==n - result[i] = Any[Vararg{eltype(ti)}] + elseif ti⊑AbstractArray && i==n + result[i] = Any[Vararg{eltype(tti)}] else return nothing end @@ -964,7 +883,7 @@ function precise_container_types(args, types, vtypes::VarTable, sv) end # do apply(af, fargs...), where af is a function value -function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes::VarTable, sv, e) +function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes::VarTable, sv) ctypes = precise_container_types(fargs, aargtypes, vtypes, sv) if ctypes !== nothing # apply with known func with known tuple types @@ -975,43 +894,10 @@ function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes::VarTable tail = foldl((a,b)->tmerge(a,unwrapva(b)), Bottom, at[MAX_TUPLETYPE_LEN+1:n]) at = vcat(at[1:MAX_TUPLETYPE_LEN], Any[Vararg{tail}]) end - return abstract_call(af, (), at, vtypes, sv, ()) + return abstract_call(af, (), at, vtypes, sv) end # apply known function with unknown args => f(Any...) - return abstract_call(af, (), Any[type_typeof(af), Vararg{Any}], vtypes, sv, ()) -end - -function isconstantargs(args, argtypes::Vector{Any}, sv::InferenceState) - if length(argtypes) == 1 # just the function - return true - end - if isvarargtype(argtypes[end]) - return false - end - for i = 2:length(argtypes) - t = argtypes[i] - if !isType(t) || has_typevars(t.parameters[1]) - args === () && return false - arg = args[i] - if isconstantref(arg, sv) === false - return false - end - end - end - return true -end - -function _ieval_args(args, argtypes::Vector{Any}, sv::InferenceState) - c = cell(length(argtypes) - 1) - for i = 2:length(argtypes) - t = argtypes[i] - if isType(t) && !has_typevars(t.parameters[1]) - c[i - 1] = t.parameters[1] - else - c[i - 1] = _ieval(isconstantref(args[i], sv), sv) - end - end - return c + return abstract_call(af, (), Any[type_typeof(af), Vararg{Any}], vtypes, sv) end @pure function type_typeof(v::ANY) @@ -1021,13 +907,14 @@ end return typeof(v) end -function pure_eval_call(f::ANY, fargs, argtypes::ANY, sv, e) - if !isconstantargs(fargs, argtypes, sv) - return false +function pure_eval_call(f::ANY, argtypes::ANY, sv) + for a in argtypes + if !(isa(a,Const) || (isType(a) && !has_typevars(a.parameters[1]))) + return false + end end - args = _ieval_args(fargs, argtypes, sv) - atype = Tuple{type_typeof(f), Any[type_typeof(a) for a in args]...} + atype = argtypes_to_type(argtypes) meth = _methods_by_ftype(atype, 1) if meth === false || length(meth) != 1 return false @@ -1045,22 +932,26 @@ function pure_eval_call(f::ANY, fargs, argtypes::ANY, sv, e) return false end + args = map(a->(isa(a,Const) ? a.val : a.parameters[1]), argtypes[2:end]) try - v = f(args...) - return type_typeof(v) + return abstract_eval_constant(f(args...)) catch return false end end +function argtypes_to_type(argtypes::Array{Any,1}) + return Tuple{map(widenconst, argtypes)...} +end -function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, e) - t = pure_eval_call(f, fargs, argtypes, sv, e) +function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState) + t = pure_eval_call(f, argtypes, sv) t !== false && return t if is(f,_apply) && length(fargs)>1 - af = isconstantfunc(fargs[2], sv) - if af === false - aft = argtypes[2] + aft = argtypes[2] + if isa(aft,Const) + af = aft.val + else if isType(aft) && !isa(aft.parameters[1],TypeVar) af = aft.parameters[1] elseif isleaftype(aft) && isdefined(aft,:instance) @@ -1069,10 +960,8 @@ function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, s # TODO jb/functions: take advantage of case where non-constant `af`'s type is known return Any end - else - af = _ieval(af, sv) end - return abstract_apply(af, fargs[3:end], argtypes[3:end], vtypes, sv, e) + return abstract_apply(af, fargs[3:end], argtypes[3:end], vtypes, sv) end for i=2:(length(argtypes)-1) if isvarargtype(argtypes[i]) @@ -1080,21 +969,14 @@ function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, s end end if is(f,invoke) && length(fargs)>2 - af = isconstantfunc(fargs[2], sv) - if !is(af,false) - af = _ieval(af,sv) + if isa(argtypes[2], Const) + af = argtypes[2].val sig = argtypes[3] if isType(sig) && sig.parameters[1] <: Tuple - return invoke_tfunc(af, sig.parameters[1], Tuple{argtypes[4:end]...}, sv) + return invoke_tfunc(af, sig.parameters[1], argtypes_to_type(argtypes[4:end]), sv) end end end - if is(f,getfield) - val = isconstantref(e, sv) - if !is(val,false) - return abstract_eval_constant(_ieval(val,sv)) - end - end if is(f,Core.kwfunc) && length(fargs)==2 ft = argtypes[2] if isa(ft,DataType) && !ft.abstract @@ -1104,12 +986,11 @@ function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, s end return Any end - fargs = fargs[2:end] if isa(f,Builtin) || isa(f,IntrinsicFunction) - rt = builtin_tfunction(f, fargs, Tuple{argtypes[2:end]...}) + rt = builtin_tfunction(f, argtypes[2:end]) return isa(rt, TypeVar) ? rt.ub : rt end - return abstract_call_gf(f, fargs, Tuple{argtypes...}, e, sv) + return abstract_call_gf(f, argtypes, sv) end function abstract_eval_call(e, vtypes::VarTable, sv::InferenceState) @@ -1118,10 +999,10 @@ function abstract_eval_call(e, vtypes::VarTable, sv::InferenceState) for x in argtypes x === Bottom && return Bottom end - called = e.args[1] - func = isconstantfunc(called, sv) - if is(func,false) - ft = argtypes[1] + ft = argtypes[1] + if isa(ft, Const) + f = ft.val + else if isType(ft) && !isa(ft.parameters[1],TypeVar) f = ft.parameters[1] elseif isleaftype(ft) && isdefined(ft,:instance) @@ -1134,25 +1015,17 @@ function abstract_eval_call(e, vtypes::VarTable, sv::InferenceState) end # non-constant function, but type is known if (isleaftype(ft) || ft <: Type) && !(ft <: Builtin) && !(ft <: IntrinsicFunction) - return abstract_call_gf_by_type(nothing, Tuple{argtypes...}, e, sv) + return abstract_call_gf_by_type(nothing, argtypes_to_type(argtypes), sv) end return Any end - else - f = _ieval(func,sv) - end - if isa(called, Expr) - # if called thing is a constant, still make sure it gets annotated with a type. - # issue #11997 - called.typ = abstract_eval_constant(f) end - return abstract_call(f, e.args, argtypes, vtypes, sv, e) + return abstract_call(f, e.args, argtypes, vtypes, sv) end function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState) if isa(e,QuoteNode) - v = (e::QuoteNode).value - return type_typeof(v) + return abstract_eval_constant((e::QuoteNode).value) elseif isa(e,GenSym) return abstract_eval_gensym(e::GenSym, sv) elseif isa(e,Slot) @@ -1206,7 +1079,7 @@ function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState) end elseif is(e.head,:static_typeof) var = e.args[1] - t = abstract_eval(var, vtypes, sv) + t = widenconst(abstract_eval(var, vtypes, sv)) if isa(t,DataType) && typeseq(t,t.name.primary) # remove unnecessary typevars t = t.name.primary @@ -1245,8 +1118,7 @@ function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState) elseif is(e.head,:copyast) t = abstract_eval(e.args[1], vtypes, sv) elseif is(e.head,:inert) - v = e.args[1] - return type_typeof(v) + return abstract_eval_constant(e.args[1]) else t = Any end @@ -1267,7 +1139,7 @@ function abstract_eval_constant(x::ANY) end return Type{x} end - return typeof(x) + return Const(x) end function abstract_eval_global(M::Module, s::Symbol) @@ -1345,11 +1217,33 @@ function type_too_complex(t::ANY, d) return false end +## lattice operators + +function ⊑(a::ANY, b::ANY) + a === NF && return true + b === NF && return false + if isa(a,Const) + if isa(b,Const) + return a.val === b.val + end + return isa(a.val, b) + elseif isa(b,Const) + return a === Bottom + else + return a <: b + end +end + +widenconst(c::Const) = typeof(c.val) +widenconst(t::ANY) = t + +issubstate(a::VarState, b::VarState) = (a.typ ⊑ b.typ && a.undef <= b.undef) + function tmerge(typea::ANY, typeb::ANY) - is(typea, NF) && return typeb - is(typeb, NF) && return typea - typea <: typeb && return typeb - typeb <: typea && return typea + typea ⊑ typeb && return typeb + typeb ⊑ typea && return typea + typea, typeb = widenconst(typea), widenconst(typeb) + typea === typeb && return typea if (typea <: Tuple) && (typeb <: Tuple) if length(typea.parameters) == length(typeb.parameters) && !isvatuple(typea) && !isvatuple(typeb) return typejoin(typea, typeb) @@ -1365,17 +1259,15 @@ function tmerge(typea::ANY, typeb::ANY) return u end -issubstate(a::VarState,b::VarState) = (a.typ <: b.typ && a.undef <= b.undef) - function smerge(sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState}) - is(sa, NF) && return sb - is(sb, NF) && return sa + sa === NF && return sb + sb === NF && return sa issubstate(sa,sb) && return sb issubstate(sb,sa) && return sa VarState(tmerge(sa.typ, sb.typ), sa.undef | sb.undef) end -tchanged(n::ANY, o::ANY) = is(o,NF) || (!is(n,NF) && !(n <: o)) +tchanged(n::ANY, o::ANY) = is(o,NF) || (!is(n,NF) && !(n ⊑ o)) schanged(n::ANY, o::ANY) = is(o,NF) || (!is(n,NF) && !issubstate(n, o)) function stupdate!(state::Tuple{}, changes::StateUpdate) @@ -1585,8 +1477,8 @@ function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, nee skip = false elseif linfo.name == :getindex || linfo.name == :next || linfo.name == :indexed_next argtypes = atypes.parameters - if length(argtypes)>2 && argtypes[3]===Int && - (argtypes[2] <: Tuple || + if length(argtypes)>2 && argtypes[3] ⊑ Int && + (argtypes[2] ⊑ Tuple || (isa(argtypes[2], DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) && (argtypes[2]::DataType).name === Main.Base.Pair.name)) skip = false @@ -1773,7 +1665,7 @@ function typeinf_frame(frame) id = (changes.var::GenSym).id + 1 new = changes.vtype.typ old = frame.gensym_types[id] - if old===NF || !(new <: old) + if old===NF || !(new ⊑ old) frame.gensym_types[id] = tmerge(old, new) for r in frame.gensym_uses[id] if !is(s[r], ()) # s[r] === () => unreached statement @@ -1787,11 +1679,12 @@ function typeinf_frame(frame) stmt = stmt::Expr hd = stmt.head if is(hd, :gotoifnot) - condexpr = stmt.args[1] + condt = abstract_eval(stmt.args[1], s[pc], frame) + condval = isa(condt, Const) ? condt.val : nothing l = stmt.args[2]::Int # constant conditions - if is(condexpr, true) - elseif is(condexpr, false) + if condval === true + elseif condval === false pc´ = l else # general case @@ -1814,7 +1707,7 @@ function typeinf_frame(frame) id = var.id+1 vt = frame.gensym_types[id] ot = frame.gensym_init[id] - if ot===NF || !typeseq(vt, ot) + if ot===NF || !(vt⊑ot && ot⊑vt) frame.gensym_init[id] = vt if get(frame.fedbackvars, var, false) frame.typegotoredo = true @@ -1827,7 +1720,7 @@ function typeinf_frame(frame) rt = abstract_eval(stmt.args[1], s[pc], frame) if tchanged(rt, frame.bestguess) # new (wider) return type for frame - frame.bestguess = tmerge(frame.bestguess, rt) + frame.bestguess = widenconst(tmerge(frame.bestguess, rt)) for (caller, callerW) in frame.backedges # notify backedges of updated type information for caller_pc in callerW @@ -1991,6 +1884,7 @@ function finish(me::InferenceState) getfield_elim_pass!(me.linfo, me) reindex_labels!(me.linfo, me) end + widen_all_consts!(me.linfo) # finalize and record the linfo result me.inferred = true @@ -2032,6 +1926,7 @@ function finish(me::InferenceState) end function record_var_type(s::Slot, t::ANY, decls) + t = widenconst(t) otherTy = decls[s.id] # keep track of whether a variable is always the same type if !is(otherTy,NF) @@ -2096,7 +1991,7 @@ function type_annotate!(linfo::LambdaInfo, states::Array{Any,1}, sv::ANY, rettyp undefs = fill(false, nslots) # initialize decls with argument types for i = 1:nargs - decls[i] = states[1][i].typ + decls[i] = widenconst(states[1][i].typ) end body = linfo.code for i=1:length(body) @@ -2119,6 +2014,26 @@ function type_annotate!(linfo::LambdaInfo, states::Array{Any,1}, sv::ANY, rettyp nothing end +# widen all Const elements in type annotations +_widen_all_consts(x::ANY) = x +_widen_all_consts(x::Slot) = Slot(x.id, widenconst(x.typ)) +function _widen_all_consts(x::Expr) + x.typ = widenconst(x.typ) + for i = 1:length(x.args) + x.args[i] = _widen_all_consts(x.args[i]) + end + x +end +function widen_all_consts!(linfo::LambdaInfo) + for i = 1:length(linfo.gensymtypes) + linfo.gensymtypes[i] = widenconst(linfo.gensymtypes[i]) + end + for i = 1:length(linfo.code) + linfo.code[i] = _widen_all_consts(linfo.code[i]) + end + linfo +end + # replace slots 1:na with argexprs, static params with spvals, and increment # other slots by offset. function substitute!(e::ANY, na, argexprs, spvals, offset) @@ -2176,14 +2091,13 @@ function exprtype(x::ANY, sv::InferenceState) elseif isa(x,Symbol) return abstract_eval_global(sv.mod, x::Symbol) elseif isa(x,QuoteNode) - v = (x::QuoteNode).value - return type_typeof(v) + return abstract_eval_constant((x::QuoteNode).value) elseif isa(x,Type) return Type{x} elseif isa(x,GlobalRef) return abstract_eval_global(x.mod, (x::GlobalRef).name) else - return typeof(x) + return abstract_eval_constant(x) end end @@ -2229,16 +2143,17 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) allow_volatile && return true return isconst(e.mod, e.name) end - if isconstantref(e, sv) !== false - return true - end + # TODO restore this + #if isconstantref(e, sv) !== false + # return true + #end if isa(e,Expr) e = e::Expr if e.head === :static_typeof return true end ea = e.args - if e.head === :call + if e.head === :call && !isa(e.args[1], GenSym) && !isa(e.args[1], Slot) if is_known_call_p(e, is_pure_builtin, sv) if !allow_volatile if is_known_call(e, arrayref, sv) || is_known_call(e, arraylen, sv) @@ -2255,7 +2170,7 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) return false end if isa(a,GenSym) - typ = exprtype(a,sv) + typ = widenconst(exprtype(a,sv)) if !isa(typ,DataType) || typ.mutable return false end @@ -2274,7 +2189,7 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) elseif e.head === :new if !allow_volatile a = ea[1] - typ = exprtype(a,sv) + typ = widenconst(exprtype(a,sv)) if !isType(typ) || !isa((typ::Type).parameters[1],DataType) || ((typ::Type).parameters[1]::DataType).mutable return false end @@ -2309,10 +2224,10 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference argexprs = e.args, incompletematch = false - if (is(f, typeassert) || is(ft, typeof(typeassert))) && length(atypes)==3 + if (is(f, typeassert) || ft ⊑ typeof(typeassert)) && length(atypes)==3 # typeassert(x::S, T) => x, when S<:T if isType(atypes[3]) && isleaftype(atypes[3]) && - atypes[2] <: atypes[3].parameters[1] + atypes[2] ⊑ atypes[3].parameters[1] return (e.args[2],()) end end @@ -2337,12 +2252,12 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end end end - if isa(f,IntrinsicFunction) || ft === IntrinsicFunction + if isa(f,IntrinsicFunction) || ft ⊑ IntrinsicFunction return NF end local methfunc - atype = Tuple{atypes...} + atype = argtypes_to_type(atypes) if length(atype.parameters) - 1 > MAX_TUPLETYPE_LEN atype = limit_tuple_type(atype) end @@ -2361,7 +2276,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference if linfo === NF return NF end - if linfo.pure && isconstantargs(argexprs, atypes, sv) + if linfo.pure && (isType(e.typ) || isa(e.typ,Const)) # check if any arguments aren't effect_free and need to be kept around stmts = Any[] for i = 1:length(argexprs) @@ -2371,18 +2286,13 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end end - if isType(e.typ) && !has_typevars(e.typ.parameters[1]) - return (QuoteNode(e.typ.parameters[1]), stmts) - end - - constargs = _ieval_args(argexprs, atypes, sv) - try - v = f(constargs...) - return (QuoteNode(v), stmts) - catch ex - thrw = Expr(:call, TopNode(:throw), QuoteNode(ex)) - thrw.typ = Bottom - return (thrw, stmts) + if isType(e.typ) + if !has_typevars(e.typ.parameters[1]) + return (QuoteNode(e.typ.parameters[1]), stmts) + end + else + assert(isa(e.typ,Const)) + return (QuoteNode(e.typ.val), stmts) end end @@ -2472,7 +2382,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end inline_op = (istopfunction(topmod, f, :+) || istopfunction(topmod, f, :*) || istopfunction(topmod, f, :min) || istopfunction(topmod, f, :max)) && - (4 <= length(argexprs) <= 10) && methsig == Tuple{ft,Any,Any,Any,Vararg{Any}} + (4 <= length(argexprs) <= 10) && methsig == Tuple{widenconst(ft),Any,Any,Any,Vararg{Any}} if !inline_op && !inline_worthy(body, cost) # TODO #= @@ -2572,7 +2482,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference for i=na:-1:1 # stmts_free needs to be calculated in reverse-argument order #args_i = args[i] aei = argexprs[i] - aeitype = argtype = exprtype(aei,sv) + aeitype = argtype = widenconst(exprtype(aei,sv)) needtypeassert = false if incompletematch if isva @@ -2652,6 +2562,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference if occ < 6 occ += occurs_more(b, x->(isa(x,Slot)&&x.id==i), 6) end + # TODO: passing `sv` here is wrong since it refers to the enclosing function if occ > 0 && affect_free && !effect_free(b, sv, true) #TODO: we could short-circuit this test better by memoizing effect_free(b) in the for loop over i affect_free = false end @@ -2850,7 +2761,7 @@ end function mk_tuplecall(args, sv::InferenceState) e = Expr(:call, top_tuple, args...) - e.typ = tuple_tfunc(Tuple{Any[exprtype(x,sv) for x in args]...}) + e.typ = tuple_tfunc(Tuple{Any[widenconst(exprtype(x,sv)) for x in args]...}) e end @@ -2955,12 +2866,11 @@ function inlining_pass(e::Expr, sv, linfo) end end - f = isconstantfunc(arg1, sv) - if !is(f,false) - f = _ieval(f,sv); ft = abstract_eval_constant(f) + ft = exprtype(arg1, sv) + if isa(ft, Const) + f = ft.val else f = nothing - ft = exprtype(arg1, sv) if !( isleaftype(ft) || ft<:Type ) return (e, stmts) end @@ -2973,7 +2883,7 @@ function inlining_pass(e::Expr, sv, linfo) a1 = e.args[2] basenumtype = Union{corenumtype, Main.Base.Complex64, Main.Base.Complex128, Main.Base.Rational} if isa(a1,basenumtype) || ((isa(a1,Symbol) || isa(a1,Slot) || isa(a1,GenSym)) && - exprtype(a1,sv) <: basenumtype) + exprtype(a1,sv) ⊑ basenumtype) if e.args[3]==2 e.args = Any[GlobalRef(Main.Base,:*), a1, a1] f = Main.Base.(:*); ft = abstract_eval_constant(f) @@ -3017,7 +2927,7 @@ function inlining_pass(e::Expr, sv, linfo) newargs = cell(na-2) for i = 3:na aarg = e.args[i] - t = exprtype(aarg,sv) + t = widenconst(exprtype(aarg,sv)) if isa(aarg,Expr) && (is_known_call(aarg, tuple, sv) || is_known_call(aarg, svec, sv)) # apply(f,tuple(x,y,...)) => f(x,y,...) newargs[i-2] = aarg.args[2:end] @@ -3035,12 +2945,11 @@ function inlining_pass(e::Expr, sv, linfo) e.args = [Any[e.args[2]]; newargs...] # now try to inline the simplified call - f = isconstantfunc(e.args[1], sv) - if !is(f,false) - f = _ieval(f,sv); ft = abstract_eval_constant(f) + ft = exprtype(e.args[1], sv) + if isa(ft,Const) + f = ft.val else f = nothing - ft = exprtype(e.args[1], sv) if !( isleaftype(ft) || ft<:Type ) return (e,stmts) end @@ -3064,16 +2973,16 @@ function is_known_call(e::Expr, func, sv) if e.head !== :call return false end - f = isconstantfunc(e.args[1], sv) - return !is(f,false) && is(_ieval(f,sv), func) + f = exprtype(e.args[1], sv) + return isa(f,Const) && f.val === func end function is_known_call_p(e::Expr, pred, sv) if e.head !== :call return false end - f = isconstantfunc(e.args[1], sv) - return !is(f,false) && pred(_ieval(f,sv)) + f = exprtype(e.args[1], sv) + return isa(f,Const) && pred(f.val) end is_var_assigned(linfo, v) = isa(v,Slot) && linfo.slotflags[v.id]&2 != 0 @@ -3126,7 +3035,7 @@ function remove_redundant_temp_vars(linfo, sa, T) # (from inlining improved type inference information) # and this transformation would worsen the type information # everywhere later in the function - if init.typ <: (T===GenSym ? gensym_types[v+1] : linfo.slottypes[v]) + if init.typ ⊑ (T===GenSym ? gensym_types[v+1] : linfo.slottypes[v]) delete_var!(linfo, v, T) slot_replace!(linfo, v, init, T) end @@ -3356,14 +3265,14 @@ function replace_getfield!(linfo::LambdaInfo, e::Expr, tupname, vals, field_name # the tuple element expression that's replacing it. if isa(val,Slot) val = val::Slot - if a.typ <: val.typ && !typeseq(a.typ,val.typ) + if a.typ ⊑ val.typ && !(val.typ ⊑ a.typ) val.typ = a.typ - linfo.slottypes[val.id] = a.typ + linfo.slottypes[val.id] = widenconst(a.typ) end elseif isa(val,GenSym) val = val::GenSym typ = exprtype(val, sv) - if a.typ <: typ && !typeseq(a.typ,typ) + if a.typ ⊑ typ && !(typ ⊑ a.typ) sv.gensym_types[val.id+1] = a.typ end end diff --git a/test/core.jl b/test/core.jl index 096ee7163bb2e9..94ac812a667351 100644 --- a/test/core.jl +++ b/test/core.jl @@ -1668,9 +1668,9 @@ macro m6031(x); x; end @test (@m6031 [2,4,6])[2] == 4 # issue #6050 -@test Core.Inference.getfield_tfunc([nothing, QuoteNode(:vals)], +@test Core.Inference.getfield_tfunc( Dict{Int64,Tuple{UnitRange{Int64},UnitRange{Int64}}}, - :vals) == (Array{Tuple{UnitRange{Int64},UnitRange{Int64}},1},true) + Core.Inference.Const(:vals)) == (Array{Tuple{UnitRange{Int64},UnitRange{Int64}},1},true) # issue #6068 x6068 = 1